Keeping specs and code in sync¶
A spec is only useful while it still describes the code beneath it. SpexCode does not try to judge whether the prose still matches the code's meaning; that takes reading. What it can do is compute, from Git alone, the moments when the code a spec governs changed and the spec did not. That computation is the whole mechanism, and it is cheap enough to run on every commit.
A spec names the code it governs¶
A node's code: entry names the one file it governs. It can go further and pin named units of that file, such as functions, methods or classes, as anchors: src/ingest/webhookVerifier.ts#verifyWebhook. Anchors are resolved structurally for TypeScript, Python, Go, Rust, Java and Ruby, so they follow the unit through the file rather than a fixed line number. related: lists files the node refers to without owning them.
The window and the intersection¶
A spec's versions are the commits that touched its spec.md. The window opens at the latest version. For every commit after it, Git supplies the lines that commit changed, and SpexCode intersects them with the anchored unit's line range as it existed in that commit.
- If a commit's changed lines overlap an anchored unit, that is
anchor-drift, an error. With the hooks installed it blocks the commit that would introduce it. - If the governed file changed somewhere outside the anchors, or the node has no anchors, that is
drift, a warning. It never blocks. - A change to a
related:file gives a softer warning of its own.
Nothing is stored. There are no hashes and no snapshot of a last-known-good state: every read recomputes versions, windows and overlaps from history.
Two ways to close the window¶
When the intent changed, update the spec with the code. The commit that touches spec.md is a new version, and the window starts again after it.
When only the mechanics changed and the contract still holds, say so. On the commit being made, add a trailer:
For a change that is already committed:
ack records an empty stamp commit carrying the reason. A reasoned ack is reviewed like any other change; an ack without a real reason defeats the point.
What else spex spec lint checks¶
Beside drift, lint keeps the graph itself sound. Errors: a code: or related: path or anchor that does not resolve (integrity), a node governing more than one file (one-govern), a body that grows ## vN changelog headings instead of describing the present (living), and a [[node]] mention that names no node (mention). Warnings: tracked source files no node claims (coverage) and a file governed whole by too many nodes (owners). spex guide spec documents every rule.
Where the checks run¶
Each clone can install a pre-commit hook that runs the same lint and blocks on errors. It is fast feedback, but it is local: a fresh checkout that skipped the install has none, and a commit can skip it with SPEXCODE_SKIP_LINT=1.
The gate no one can skip is spex spec lint in continuous integration on every push and pull request. Because versions and windows come from history, CI needs the full Git history rather than a shallow clone.