Skip to content

Keeping specs and code in sync

A spec is only useful while it still describes the code beneath it. SpexCode ties each node to the files it governs through its code: list, and spex lint walks that graph to find where the two have parted ways. It does not judge whether the prose still matches the code's meaning — that takes reading — but it catches the structural signs that a spec has gone stale, cheaply enough to run on every commit.

What spex lint checks

The first check is a broken link: if a node's code: list names a file that no longer exists, that is an error and it blocks. A spec that points at nothing is worse than no spec.

The second is coverage. SpexCode enumerates the project's git-tracked source files and warns about any that no node claims. Because the file list comes from the git index, build output and dependencies never enter the count, so pointing the tool at the whole project is safe. Coverage is a warning, not an error — it is the list of code that has drifted out of the spec's field of view, worked down over time.

The third is drift: a governed file has commits more recent than the last revision of its spec. That is the signal that the code moved and the spec may not have kept up. Drift is a warning by default, though a file that has fallen several versions behind will block a commit that touches it.

Lint also raises softer, advisory warnings — when a body grows into a re-narration of the implementation, when a node fans out into too many children, or when one file is claimed by too many nodes. Those are readability signals rather than sync signals, surfaced in the same pass.

How drift is computed

Drift is derived live from git. There are no stored hashes and no snapshot of a last-known-good state: SpexCode compares the last commit to a governed file against the last commit that revised its spec. Since git already records both, there is nothing extra to keep current.

A file changing after its spec does not always mean the spec is wrong — a refactor can move code around while the intent stays exactly true. For that case you acknowledge the change: spex ack records that the spec still holds for a node and requires you to say why. When the intent itself has changed there is no shortcut; you rewrite the spec to describe the new behavior, and the new version clears the drift on its own.

Where the checks run

Every clone can install a git hook that runs the same lint before each commit, so a broken link or a badly drifted node surfaces before it lands. This is useful, but it is local and bypassable: the hook lives outside the repository, a fresh checkout that skipped the install has none, and any commit can skip it with an environment flag.

The enforcement no one can skip is running spex lint in continuous integration on every push and pull request, where the build fails on lint errors regardless of what happened on any developer's machine. Treat the hook as fast feedback and CI as the actual gate. Because lint derives the version timeline and drift from history, CI needs the full git history rather than a shallow clone.