Spec Tooling
Status: Current Last modified: 2026-10-02 (commit 2d7e886b)
What the generator crates ARE. For the spec system’s contract, which is what you need to write or change a spec, read Spec System; for the procedure, Spec Workflow. This page covers only the tooling, so the three do not overlap.
Two crates, one workspace
spec/ is its own cargo workspace, so every command needs
--manifest-path spec/Cargo.toml.
| Crate | Owns | Depends on the parser? |
|---|---|---|
spec/tools (generators) | reading specs and emitting artifacts | No |
spec/runtime-tools | anything needing the live parser or model | Yes |
That split is the point. spec/tools reads markdown and JSON and produces
tests, fixtures, docs and generated Rust; it never parses CHAT. Work that has to
actually run the parser (verifying a spec example emits its codes, mining the
corpus) lives in spec/runtime-tools.
The artifact registry is split along the same line, and for the same reason:
generators::artifacts::ARTIFACTS holds everything derivable from markdown
alone (plus, since R4, the observation snapshot as a data-file input), and the
runtime half holds the artifacts that need the live parser or ErrorCode
enum: the observation snapshot itself (which regenerates FIRST, being an input
to the tree-sitter corpus), the DiagnosticKind registry, and the book’s
artifact table. spec_gen runs both halves in dependency order, so a
contributor sees one command and one list; the generated artifact table included in the spec-system chapter is the
live inventory.
Layout of spec/tools
src/
bin/ one binary per generator
spec/ markdown spec loaders (constructs, errors)
output/ formatters (tree-sitter corpus, Rust tests, docs)
form_markers/ the form-marker registry: typed model, renderers, drift gate
templates/ Tera templates wrapping fragments into whole CHAT files
generated/ generated symbol sets (never edited by hand)
Determinism, and what enforces it
Generation must be idempotent: a re-run with no source change produces no diff. Three things make that true rather than hoped for.
- Generators write only when content differs, so a no-op run does not churn mtimes.
- Rust output is formatted by the generator, which runs
rustfmtitself. Otherwisejust fmtand the generator each rewrite the same bytes forever, both correct. Both registries do this. - Drift gates compare committed artifacts against what the generators produce, calling the real generators rather than a second description of their output. See Spec System for the full list.
This page last changed: 2026-10-02 (commit 2d7e886b). The whole book last changed: 2026-10-07 (commit 5e895791).