Contracts
build_cli_reference.md
Contract: scripts/docs/build_cli_reference.py
Purpose: Implement FR-007 / FR-008. Build the 3.2 CLI reference markdown from the live Typer app, capturing every visible command path's --help output.
Inputs
- Environment:
SPEC_KITTY_ENABLE_SAAS_SYNC=1andSPEC_KITTY_NO_UPGRADE_CHECK=1MUST be set in the process environment beforespecify_cli.appis imported. The script enforces this by setting them inos.environat module top before any other import. --output PATH(defaultdocs/reference/cli-commands.md) — destination markdown file.--agent-output PATH(defaultdocs/reference/agent-subcommands.md) — destination foragent-rooted subtree (kept as a separate page per repo convention).--include-hiddenflag — append an internal appendix listing the 5 hidden paths.--mode {generated, hybrid, hand}(defaulthybrid) — controls the generated-block delimiter behavior:generated: the entire body is auto-generated; no hand-authored prose allowed between<!-- BEGIN GENERATED -->and<!-- END GENERATED -->.hybrid(plan default): the generated block is embedded; hand-authored prose lives outside the block and is preserved across runs.hand: the script only writes the deprecation/internal classification table; everything else is hand-authored.--dry-runflag — print the diff without writing.
Outputs
docs/reference/cli-commands.mdrewritten in place (or printed to stdout under--dry-run).docs/reference/agent-subcommands.mdrewritten in place (same gating).- stderr: a short summary table — visible/hidden/deprecated counts, generated lines, hand-authored lines preserved.
Exit Codes
| Code | Meaning |
|---|---|
| 0 | Wrote (or would have written) successfully; output matched the expected freshness rules. |
| 1 | The live tree contains visible paths the generator could not classify; output not written. |
| 2 | Input error (missing dependencies, malformed CLI app, pyproject.toml not found). |
| 3 | Environmental setup error (SPEC_KITTY_ENABLE_SAAS_SYNC not set, git status shows uncommitted writes in target files). |
Guarantees
- Read-only on the Typer app. The script imports
specify_cli.appbut never patches or rebinds command objects. - Subprocess-isolated per-path help capture. Each
--helpis collected viasubprocess.run(["uv", "run", "spec-kitty", *path, "--help"], …)with stdout captured. The walker prefersgroup.namethengroup.typer_instance.info.namepercli-audit-3-2.md. - Idempotent. Re-running on a tree with no changes leaves the output byte-identical.
- Refuses to write if the target file has uncommitted edits already in the working tree (avoids stomping a reviewer's in-progress prose). Override with
--forcefor ad-hoc local runs.
Non-guarantees
- The script does NOT decide the hand-vs-generator policy. That is decision
01KS4KTM69EG2KVX5MQ54FQ939. The script honours whichever--modeis passed. - The script does NOT modify Typer command code, command files, or
docs/toc.yml. Any help-text discrepancy is logged as aMetaIssuerow prompt on stderr, not silently fixed.
Test fixtures
tests/docs/fixtures/sample_cli_reference.md— the expected output for a synthetic Typer app fixture (a tiny app used in unit tests so the test does not depend on the real CLI).- Integration test (smoke) imports the real
specify_cli.appwith the env flags set and asserts: visible count ≥ 192, deprecated count == 2, hidden count == 5 (current snapshot fromcli-audit-3-2.md; test tolerates ±10% with explicit log so an intentional command addition is visible).
Error taxonomy (exemplar messages)
BUILD-ENV-MISSING-SAAS-SYNC
SPEC_KITTY_ENABLE_SAAS_SYNC=1 must be set before import. tracker/issue-search will be missing without it.
BUILD-UNCLASSIFIED-VISIBLE-PATH spec-kitty foo bar
Visible command with no help summary and no test reference; classify in cli-audit meta-issues and re-run.
BUILD-TARGET-DIRTY docs/reference/cli-commands.md
Target has uncommitted edits in the working tree. Stash, commit, or pass --force.
check_cli_reference_freshness.md
Contract: scripts/docs/check_cli_reference_freshness.py
Purpose: Implement FR-020 / NFR-001. Detect drift between the live Typer tree and the committed CLI reference.
Inputs
- Environment:
SPEC_KITTY_ENABLE_SAAS_SYNC=1andSPEC_KITTY_NO_UPGRADE_CHECK=1set before import. --reference PATH(defaultdocs/reference/cli-commands.md).--agent-reference PATH(defaultdocs/reference/agent-subcommands.md).--report PATH(optional) — JSON report slice.--ciflag — plain-text output for CI annotations.--strict-modeflag — also fail when an entry exists in the reference but the matching visible path'shelp_summaryhas drifted from what the reference recorded (HELP-DRIFT); off by default to allow hand-authored prose.
Outputs
- stdout: findings table; counts by rule.
- optional JSON report.
Rules
rule_id | Severity | Condition |
|---|---|---|
REF-MISSING | error | A visible command path is missing from the reference. |
REF-EXTRA | error | A reference entry names a command path not in the live tree. |
REF-DEPRECATED-UNCLASSIFIED | error | A deprecated/removed visible command path is not classified as such in the reference. |
REF-INTERNAL-LEAK | error | A path whose live help_summary starts with Internal - appears in the user-facing reference without an "internal" classification banner. |
REF-SAAS-SYNC-OFF | error | SPEC_KITTY_ENABLE_SAAS_SYNC was not set, so tracker/issue-search paths could not be evaluated; refuse to declare clean. |
HELP-DRIFT | warning (error in --strict-mode) | The reference's recorded summary differs from the live help_summary. |
REF-HIDDEN-LEAK | error | A hidden path appears in the user-facing reference (allowed only in the --include-hidden appendix). |
Exit Codes
| Code | Meaning |
|---|---|
| 0 | No errors. |
| 1 | One or more errors. |
| 2 | Input error (missing reference file). |
| 3 | Environmental setup error (SaaS sync off, CLI fails to import). |
Guarantees
- Read-only.
- Deterministic.
- Discovers visible paths the same way
build_cli_reference.pydoes (_typer_walker.pyis shared).
Non-guarantees
- Does not enforce documentation prose quality. Reviewer judgement remains the gate for tone, examples, and clarity.
- Does not validate that linked tests exist or pass; that is the architectural test's job.
Test fixtures
tests/docs/fixtures/sample_cli_reference.md(clean) — exit code 0.tests/docs/fixtures/sample_cli_reference_missing.md—REF-MISSINGcount == 1; exit 1.tests/docs/fixtures/sample_cli_reference_extra.md—REF-EXTRAcount == 1; exit 1.tests/docs/fixtures/sample_cli_reference_no_saas.md— env flag off in subtest;REF-SAAS-SYNC-OFF; exit 3.
check_docs_freshness.md
Contract: scripts/docs/check_docs_freshness.py
Purpose: Implement FR-020 / FR-021. Orchestrate every docs-freshness check into a single CI-friendly report.
Inputs
- Environment: same as
check_cli_reference_freshness.py(SPEC_KITTY_ENABLE_SAAS_SYNC=1,SPEC_KITTY_NO_UPGRADE_CHECK=1). --inventory PATH,--docs-root PATH,--reference PATH,--agent-reference PATH— pass-through to the underlying checks.--link-check {none,spot,full}(defaultspot) —noneskips link health;spotchecks 20 randomcurrent-tagged pages plus the CLI reference forhttp(s)://links and runs HEAD requests;fullchecks every external link.--report PATH(optional) — single JSONFreshnessReport.--ciflag — plain text output.--strict-modeflag — pass-through to underlying CLI freshness checker.
Sub-checks
1. version_leakage_check.py — gathers LEAK- findings. 2. check_cli_reference_freshness.py — gathers REF- and HELP-* findings. 3. Link health (per --link-check). 4. Page-inventory completeness — every .md under docs/ is present in the manifest or explicitly excluded by glob.
Outputs
- Aggregated
FreshnessReport. Errors from any sub-check propagate. Warnings are reported but do not fail.
Exit Codes
| Code | Meaning |
|---|---|
| 0 | No errors across any sub-check. |
| 1 | One or more sub-checks reported errors. |
| 2 | Input error (missing inventory or reference). |
| 3 | Environmental setup error. |
Guarantees
- Single point of entry for the publication gate (FR-021).
- Each sub-check runs in isolation; one sub-check's input error does not skip the others.
Non-guarantees
- Does not build the docs site; that remains a separate workflow step in CI.
- Does not write to disk except for the optional
--reportJSON.
CI wiring (informational)
Tasks-phase WP F3 wires this into .github/workflows/ci-quality.yml (or the current equivalent) as:
- name: Docs freshness
run: |
SPEC_KITTY_ENABLE_SAAS_SYNC=1 SPEC_KITTY_NO_UPGRADE_CHECK=1 \
uv run scripts/docs/check_docs_freshness.py --ci --report freshness.json
The CI step uploads freshness.json as an artifact for the publication checklist evidence.
Test fixtures
tests/docs/test_check_docs_freshness.pycovers:- Happy path → exit 0.
- One leak + one reference miss → aggregated exit 1.
- Missing inventory → exit 2.
- SaaS sync off → exit 3.
version_leakage_check.md
Contract: scripts/docs/version_leakage_check.py
Purpose: Enforce FR-005 / NFR-002. Detect docs pages tagged current that link to archival pages without an explicit archive or migration banner, and detect pages whose frontmatter version_tag disagrees with the page-inventory manifest.
Inputs
--inventory PATH(defaultdocs/development/3-2-page-inventory.yaml) —PageInventoryEntryrows.--docs-root PATH(defaultdocs/) — root directory to scan.--banner-regex PATTERN(defaultr"^>\s*(?:Archive notice|Migration note)\b") — pattern that must appear within the first 20 non-empty lines of anarchivalormigrationpage.--report PATH(optional) — write a JSONFreshnessReportslice to this path.--ciflag — suppress rich output; emit plain-text lines suitable for CI annotations.
Environment: no SaaS access required. No mutation of input files at any time.
Outputs
- stdout: rich table (interactive) or plain text (CI) listing findings. Findings use
rule_idprefixes: LEAK-CURRENT-LINKS-ARCHIVAL— acurrentpage links to anarchivalpath without a migration banner.LEAK-MISSING-BANNER— anarchivalormigrationpage is missing the banner.LEAK-FRONTMATTER-MISMATCH— the page's frontmatterversion_tagdisagrees with the manifest row.LEAK-MISSING-INVENTORY— a markdown file underdocs/is not in the manifest.LEAK-MISSING-FILE— a manifest row points at a non-existent file.- optional JSON report (
--report).
Exit Codes
| Code | Meaning |
|---|---|
| 0 | No findings of severity error. Warnings allowed. |
| 1 | One or more error findings. |
| 2 | Input error (missing inventory file, malformed YAML). |
| 3 | Environmental setup error (cannot resolve docs-root, permission denied). |
Guarantees
- Read-only. The script never writes to
docs/or to the inventory. - Deterministic. Given the same inputs the output is byte-identical.
- O(N) over the number of pages × number of links per page. Designed for repos with <5k pages and <50 links per page.
Non-guarantees
- No network access. Link health (HTTP 200 vs 404) is not checked here; see
check_docs_freshness.pyfor the orchestrated link spot-check. - No language-aware link normalization (anchor parsing is naïve:
textstrips#anchorbefore classification).
Test fixtures
tests/docs/fixtures/clean_inventory.yaml+tests/docs/fixtures/sample_pages/— happy path; exit code 0.tests/docs/fixtures/dirty_inventory.yaml— one of every finding rule; exit code 1; findings count == 5.tests/docs/fixtures/missing_inventory.yaml— input error; exit code 2.
Error taxonomy (exemplar messages)
LEAK-CURRENT-LINKS-ARCHIVAL docs/how-to/install-macos.md
-> links to docs/1x/legacy-install.md without migration banner
fix: add archive callout above the link, or update target to docs/migration/from-2x-to-3-2.md
LEAK-MISSING-BANNER docs/1x/index.md
-> archival page missing banner matching /^>\s*Archive notice\b/
fix: prepend "> Archive notice: This page documents Spec Kitty 1.x and is preserved for historical context." to the page.
LEAK-FRONTMATTER-MISMATCH docs/explanation/mission-model.md
-> frontmatter says version_tag=supported, manifest says version_tag=current
fix: reconcile by updating frontmatter or by editing 3-2-page-inventory.yaml.