Skip to content
internal/layout
On this page

The directories selfdoc owns under the hidden tool-state root, the manifest that grants each one, and the authority for every path inside them.

#internal/layout

#internal/layout

Package layout is selfdoc's declaration of the per-repository directories it owns, and the single authority for every path it reads or writes inside them.

A repository keeps one hidden directory at its root, [Root], holding function-named directories rather than tool-named ones: docs/ is the pages a person writes, docs-state/ is what selfdoc generates and the repository commits, docs-cache/ is what selfdoc generates and the repository does not commit, posts/ is the blog, vocabulary/ is the project's word lists. Another tool's state lives beside them under its own function name.

#Ownership

Every directory under [Root] has exactly one owner tool, declared in the [ManifestFileName] the directory itself carries. The manifest is the permission to write: [EnsureDir] refuses a directory whose manifest does not name selfdoc, printing the file and the line to put in it, and selfdoc never writes one itself. A manifest is also what makes a directory with no content yet exist in git. Reading and writing are open to anyone; the owner decides whether what was written is acceptable, which is what [Validate] answers.

#Sides

A directory is entirely handwritten or entirely generated -- never both. The side is a property of the function, so it is declared here once rather than per repository, and [Validate] holds the tree to it: a generated page in a handwritten directory, or a handwritten page in a generated one, is a defect with a named remedy.

#The derived ignore file

git needs one file inside [Root] to keep the uncommitted directories out of the repository. It is derived, at [Root]/[IgnoreFileName], from the commitment each tool declares: an uncommitted directory's contents are ignored and its manifest is not, so the permission travels with the repository while the contents do not. selfdoc owns only the block between its two marker comments and leaves every other line of that file alone, so several tools can write their own blocks into one file.

#Root

Go go
const Root = ".stricttools"

Root is the one hidden directory a repository's tool-owned state lives in, relative to the repository root.

#Owner

Go go
const Owner = "selfdoc"

Owner is the name selfdoc is declared under in a directory's manifest.

#ManifestFileName

Go go
const ManifestFileName = "manifest.toml"

ManifestFileName is the ownership declaration a directory under [Root] carries, inside the directory it speaks for.

#OwnerKey

Go go
const OwnerKey = "owner"

OwnerKey is the one key a manifest declares. The generated validator refuses a manifest carrying any other key.

#IgnoreFileName

Go go
const IgnoreFileName = ".gitignore"

IgnoreFileName is the derived ignore file inside [Root], and the one entry there allowed to start with a dot.

#MoveScript

Go go
const MoveScript = "scripts/move-to-stricttools-layout.py"

MoveScript is the script that moves a repository off the layout selfdoc used before this one. Every refusal of the old layout names it.

#GeneratedMarkerPrefix

Go go
const GeneratedMarkerPrefix = "<!-- generated by selfdoc gen"

GeneratedMarkerPrefix opens the HTML comment every page selfdoc generates carries under its frontmatter. It is what tells a generated page from a handwritten one on disk, which is how [Validate] checks a directory's side.

#Handwritten

Go go
const Handwritten Side = "handwritten"

The two sides. A directory is entirely one or entirely the other.

#Generated

Go go
const Generated   Side = "generated"

#Committed

Go go
const Committed   Commitment = "committed"

The two commitments. An uncommitted directory contributes one line to the derived ignore file.

#Uncommitted

Go go
const Uncommitted Commitment = "uncommitted"

#DocsName

Go go
const DocsName       = "docs"

The names of the function directories selfdoc claims, relative to [Root].

#DocsStateName

Go go
const DocsStateName  = "docs-state"

#DocsCacheName

Go go
const DocsCacheName  = "docs-cache"

#PostsName

Go go
const PostsName      = "posts"

#VocabularyName

Go go
const VocabularyName = "vocabulary"

#DocsRel

Go go
const DocsRel       = Root + "/" + DocsName

The function directories, as paths relative to the repository root, in slash form.

#DocsStateRel

Go go
const DocsStateRel  = Root + "/" + DocsStateName

#DocsCacheRel

Go go
const DocsCacheRel  = Root + "/" + DocsCacheName

#PostsRel

Go go
const PostsRel      = Root + "/" + PostsName

#VocabularyRel

Go go
const VocabularyRel = Root + "/" + VocabularyName

#GeneratedPagesRel

Go go
const GeneratedPagesRel = DocsStateRel + "/pages"

GeneratedPagesRel is the second docs root: the pages selfdoc generates, merged into one URL namespace with the handwritten root at build time.

#ManifestRel

Go go
const ManifestRel = DocsStateRel + "/manifest.json"

ManifestRel is the record of what a build published.

#PostManifestRel

Go go
const PostManifestRel = DocsStateRel + "/post-manifest.json"

PostManifestRel is the posts-only manifest the assembly's post publisher reads.

#RevisionsRel

Go go
const RevisionsRel = DocsStateRel + "/revisions.json"

RevisionsRel is the post revision sidecar.

#HashesDirRel

Go go
const HashesDirRel = DocsStateRel + "/hashes"

HashesDirRel holds the content and description hash baselines.

#HashesRel

Go go
const HashesRel = HashesDirRel + "/hashes.json"

HashesRel is the hash store itself.

#DataRel

Go go
const DataRel = DocsStateRel + "/data"

DataRel is where a sandboxed gen-data script writes its output.

#OutputRel

Go go
const OutputRel = DocsCacheRel + "/build"

OutputRel is the built site.

#VersionsRel

Go go
const VersionsRel = DocsCacheRel + "/versions"

VersionsRel holds one extracted checkout per archived version.

#DocsDefault

Go go
const DocsDefault   = DocsRel + "/"

The declared values of the config keys that name a layout directory. A project that declares nothing gets these, and a project that declares something else has to keep it under [Root].

#OutputDefault

Go go
const OutputDefault = OutputRel + "/"

#PostsDefault

Go go
const PostsDefault  = PostsRel + "/"

#DeprecatedRoot

Go go
const DeprecatedRoot = ".selfdoc"

DeprecatedRoot is the directory selfdoc kept a repository's state in before this layout. Its presence is what a refusal recognizes: nothing reads it.

#GeneratedBy

Go go
const GeneratedBy = "0.2.5"

GeneratedBy is the strictspec release that produced this file. The runtime pairing guard hard-errors unless it matches the linked runtime exactly.

#SchemaFormatVersion

Go go
const SchemaFormatVersion = 1

SchemaFormatVersion is the document format_version this validator accepts.

#CheckOwnership

Go go
const CheckOwnership = "ownership"

The names of the rules [Validate] holds a repository to.

#CheckSide

Go go
const CheckSide      = "side"

#CheckHidden

Go go
const CheckHidden    = "hidden"

#CheckIgnore

Go go
const CheckIgnore    = "ignore-file"

#Side

Go go
type Side string

Side is whether a directory's contents are written by a person or by a tool.

#Commitment

Go go
type Commitment string

Commitment is whether a directory's contents belong in the repository.

#Directory

Go go
type Directory struct

Directory is one function directory selfdoc claims: what it is for, which side of the authorship line it sits on, whether the repository commits it, and the paths it replaced.

#OldLayoutError

Go go
type OldLayoutError struct

OldLayoutError is a repository refused for still being laid out the way selfdoc used to lay one out.

There is no migrator and no dual reading: a repository is moved once, by hand, with [MoveScript], and until it is, every command that reads project state refuses it.

#DirectoryManifest

Go go
type DirectoryManifest struct

DirectoryManifest is the frozen typed binding of the "DirectoryManifest" record. Fields are immutable by convention (shallow-plus-generated-immutability); use With* for copy-on-write.

#Problem

Go go
type Problem struct

Problem is one thing wrong with a repository's layout: what is wrong, and what to do about it.

#Declared

Go go
func Declared() []Directory

Declared returns the directories selfdoc claims, in declaration order.

#Lookup

Go go
func Lookup(name string) (Directory, bool)

Lookup returns the declaration of one claimed directory.

#Path

Go go
func Path(baseDir, rel string) string

Path joins a repository root with one of this package's slash-form relative paths, in the operating system's own spelling.

#UnderRoot

Go go
func UnderRoot(declaredPath string) bool

UnderRoot reports whether a declared path names something inside [Root]. A project may spell its docs and output paths as it likes as long as they live in the layout.

#FunctionOf

Go go
func FunctionOf(rel string) (string, bool)

FunctionOf returns the function directory a relative path sits in.

#DirectoryManifestRel

Go go
func DirectoryManifestRel(name string) string

DirectoryManifestRel is a directory's ownership manifest, as a path relative to the repository root, in slash form.

#DirectoryManifestPath

Go go
func DirectoryManifestPath(baseDir, name string) string

DirectoryManifestPath is where a directory's ownership manifest sits, in the operating system's own spelling.

#DirectoryManifestContent

Go go
func DirectoryManifestContent(owner string) string

DirectoryManifestContent renders the manifest that declares one tool the owner of a directory. Every refusal that asks for a manifest prints from here, so the text a person is told to write is generated from the declaration rather than typed beside it.

#ReadDirectoryManifest

Go go
func ReadDirectoryManifest(baseDir, name string) (*DirectoryManifest, error)

ReadDirectoryManifest reads and validates one directory's ownership manifest.

strictspec is the boundary validator: the file is checked against .strictspec/directory-manifest.schema.toml by the generated validator in this package, so a manifest that reaches a caller declares an owner and nothing else. A missing manifest is returned as the underlying [os.ErrNotExist], which callers answer with the file to create.

The schema's format-version gate is supplied here rather than written into the file, the way a frontmatter block's is: a manifest on disk is the one [OwnerKey] line, and a file that declares the gate itself is refused, so the key has exactly one author.

#KnownOwner

Go go
func KnownOwner(owner string) bool

KnownOwner reports whether a manifest names a tool this machine has: selfdoc itself, or any name PATH answers with an executable.

#EnsureOwned

Go go
func EnsureOwned(baseDir, name string) error

EnsureOwned answers whether selfdoc may write into one of the directories under [Root] in this repository.

The manifest is the permission. A directory carrying none, or one naming another tool, is refused -- the first with the file and the line to write, which selfdoc never writes itself.

#EnsureDir

Go go
func EnsureDir(h *effects.Handle, baseDir, rel string) error

EnsureDir creates a directory inside one of selfdoc's function directories, after checking the manifest that permits it, and refreshes the derived ignore file.

rel is one of this package's slash-form relative paths. Creating anything under [Root] goes through here, so no path can reach the filesystem without its owning manifest having been read.

#IgnoreBlock

Go go
func IgnoreBlock() []string

IgnoreBlock is selfdoc's block of the derived ignore file: the marker comments around the lines that keep each uncommitted directory it owns out of the repository.

An uncommitted directory contributes two lines rather than one. Its contents are ignored, and its [ManifestFileName] is not: the manifest is the permission to write into the directory, and a permission that git did not carry would have to be written again in every fresh checkout -- including the ones a multi-version build extracts out of git tags.

#RenderIgnore

Go go
func RenderIgnore(existing string) string

RenderIgnore returns what the derived ignore file should hold, given what it holds now: selfdoc's block replaced in place, or appended when the file carries none, with every other line untouched.

#IgnorePath

Go go
func IgnorePath(baseDir string) string

IgnorePath is where the derived ignore file sits.

#WriteIgnore

Go go
func WriteIgnore(h *effects.Handle, baseDir string) error

WriteIgnore brings the derived ignore file up to date, writing nothing when it already holds what it should.

#IgnoreIsCurrent

Go go
func IgnoreIsCurrent(baseDir string) (bool, string)

IgnoreIsCurrent reports whether the derived ignore file holds selfdoc's block as declared, and returns the content it should hold.

#RefuseOldLayout

Go go
func RefuseOldLayout(baseDir, docsDeclared, outputDeclared, postsDeclared string) error

RefuseOldLayout returns an [OldLayoutError] when a repository has not been moved to this layout yet.

It is called from the config loader, so every command that reads project state refuses before it reads anything. The three declared paths are the config keys that name a layout directory; an empty one is taken as undeclared and therefore already the default, which is inside [Root].

#ValidateBytes

Go go
func ValidateBytes(input []byte, syntax string) (*DirectoryManifest, []strictspec.Diagnostic)

ValidateBytes is the raw-bytes entry point: lossless parse of input in the given syntax ("json" | "toml" | "jsonl"), then validate. It returns the typed root value (nil when any diagnostic fired) and the ordered diagnostics.

#ValidateValue

Go go
func ValidateValue(v strictspec.Value) (*DirectoryManifest, []strictspec.Diagnostic)

ValidateValue is the tagged-value entry point: validate an already-parsed tagged document value (from strictspec.LoadValue or a typed constructor).

#ValidateBytesWithEvidence

Go go
func ValidateBytesWithEvidence(input []byte, syntax string, evidence map[string][]map[string]any) (*DirectoryManifest, []strictspec.Diagnostic)

ValidateBytesWithEvidence is ValidateBytes plus cross-document resolver evidence for the phase-2 constraint vocabulary.

#Validate

Go go
func Validate(baseDir string) ([]Problem, error)

Validate checks one repository's layout and returns every problem it finds, in check order.

The rules: every directory under [Root] carries a [ManifestFileName] naming a tool this machine has, and every directory selfdoc claims that exists names selfdoc; every directory selfdoc owns holds only what its side allows; nothing under [Root] starts with a dot except the derived ignore file; and that file's selfdoc block is what the declaration says it should be.

A missing [Root] is returned as an error rather than a problem: the rest of the rules are unanswerable without it.

#OldLayoutError.Error

Go go
func (e *OldLayoutError) Error() string

#DirectoryManifest.WithFormatVersion

Go go
func (x *DirectoryManifest) WithFormatVersion(v int64) *DirectoryManifest

WithFormatVersion returns a copy of DirectoryManifest with FormatVersion set to the given value.

#DirectoryManifest.WithOwner

Go go
func (x *DirectoryManifest) WithOwner(v string) *DirectoryManifest

WithOwner returns a copy of DirectoryManifest with Owner set to the given value.

#Problem.Error

Go go
func (p Problem) Error() string { return "[" + p.Check + "] " + p.Message }

Error renders a problem the way the command prints it.

More tools from this site

  • claudestream Drive Claude Code from Python: run it as a subprocess and read its output as typed events, with async and sync sessions, sandbox policies, and tools you define in Python
  • claudewheel A TUI Claude Code Launcher that lets you have more than one profile, manage sessions lifecycle, pick the exact CC version, model to use (even older unlisted ones), pick which GitHub account to use, etc.
  • dirstat Fast, single-binary directory statistics CLI: every file under a tree grouped by format, with counts, sizes, and lines of code, as a colored terminal table or as JSON
  • fastware A batteries-included ASGI framework: msgspec JSON, a managed Granian server, dependency injection, SSE, WebSockets, auth, and a test client
  • go-toml-edit Zero-dep TOML editing library for Go with comment preservation
  • howmuchleft The fastest Claude Code statusline: context window, 5-hour, and weekly limit usage as three customizable gradient bars, rendering in about 6 ms
  • orxtra
  • pgdesign
  • predraw Declarative rendering pipeline: describe a scene in JSON and get SVG, PNG and WebP out, with light and dark style tokens, reusable components and text converted to path outlines
  • reposummary Turn a git repository's history into a Markdown journal: pick a time window or revision range and get a readable digest of what changed, optionally narrated by an LLM
  • rlsbl Release orchestration and project scaffolding CLI that bumps versions, validates a structured JSONL changelog, tags only the commit CI verified, and publishes to npm, PyPI, Go and more
  • safegit git wrapper CLI that gives each commit its own temporary index and retries ref updates on conflict, so concurrent agents share one repository
  • saferm Command-line replacement for rm that archives every deletion with a mandatory reason and the context it ran in, so deleted files can be listed, inspected and restored
  • strictcli
  • stricttest An always-on test-isolation floor: a pytest plugin and a Go env-hygiene module that make a test suite structurally unable to reach real credentials, the real HOME, the network, or the development repository.
  • wesktop A Python framework that turns an ASGI web app into a desktop application, serving it from a local Granian server and displaying it in a native OS window via pywebview
Search