Concepts¶
SpexCode organizes a project around a tree of spec nodes. A node is a short document describing what some part of the system is supposed to do, kept next to a pointer at the code that does it. This page covers how those nodes are structured, how they connect to code, and where their state comes from.
Spec nodes and the tree¶
A spec node is a directory that contains a file named spec.md. The node's id is the directory name. Nodes nest: a child node is a subdirectory of its parent, so the tree of directories under .spec/ is the tree of nodes. Keeping a node as a directory rather than a lone file lets it hold both its children and any co-located assets, while the intent itself always lives in the same place. When two nodes share a leaf name, the id is qualified by just enough of the parent path to stay unique.
Linking a node to code¶
A node declares its relationship to source files through two frontmatter lists. code: names the files the node governs — the files it is the source of truth for. This is the sharp link, ideally a single file, and it is what drift attaches to. related: names files the node references but does not own; these count toward coverage but carry no ownership.
More than one node may govern the same file; that is ordinary composition. The tool only remarks when a file is governed by too many nodes, reading that as a sign the file has accreted too much rather than a problem with ownership itself.
The body: raw source and expanded spec¶
A node's body describes its present intent and is rewritten in place. It is not a changelog — version history is kept in git, not in accumulating sections inside the file.
The body has two labelled parts with two different owners. Raw source is the human's statement of intent: short, changed rarely, and only with human approval. Expanded spec is a fuller reading of what the code should do — a behavioral description, not the implementation — revised freely as long as it stays consistent with the raw source. There is deliberately no "current state" or "what's done" section; progress is derived rather than written down, so a body cannot narrate a completion that never happened.
Git as the store¶
There is no separate database. A node's observable state is computed from git and the working tree when read. Its version is the number of commits that changed its spec.md, excluding pure renames. Its history — each version's reason, author, and diff — comes from those same commits, attributed through a trailer each commit carries.
Status is derived¶
A node's status is computed, not typed by hand: pending (a spec with no implementing code yet), active (a node an in-flight change is currently touching), drifted (governed code has moved ahead of the spec's latest version), or merged (committed and in sync). Frontmatter may state a status, but for a node with code that is only a fallback for when git cannot be read; otherwise the derivation wins. This avoids the common failure where a document claims something is done because someone typed that it was.
Reading it¶
Two surfaces render the tree and its history: the spex command line and a web dashboard. Both read the same assembled view — the node tree, each node's derived status and version, and any in-flight changes. The dashboard is a read-time view recomputed from git on each load, not a second copy of the state.