Contracts

canonical-term-check-contract.md

Contract: Canonical Glossary Term Usage Check

Owner: new tests/architectural/test_glossary_canonical_terms.py Requirement: FR-013 Pattern precedent: tests/architectural/test_no_legacy_terminology.py

Input

spelling/casing.

target since the check is a standing architectural test, not a one-time script).

  • .kittify/glossaries/spec_kitty_core.yaml — 104 terms, surface field is the canonical
  • docs/*/.md (scope: mission-touched pages at minimum; full docs/ tree is the long-run

Behavior

For each glossary term's surface value, git grep-style scan docs/ for any occurrence of that term's text that does not match the canonical casing/spelling exactly (case-sensitive comparison after normalizing whitespace) — e.g. "Doctrine Artifact" when the canonical surface is "doctrine artifact".

This check does not attempt alias/banned-synonym detection (e.g. flagging "feature" as a banned synonym for a glossary term) — that requires an aliases field the glossary schema does not yet have (C-003, deferred to a follow-up GitHub issue). It only checks: when this term's text appears, is it spelled/cased the way the glossary says it should be?

Output

Standard pytest pass/fail. On failure, the assertion message lists every flagged occurrence as {file}:{line}: found "{actual}", expected canonical form "{surface}".

CI integration

Marked pytest.mark.architectural, pytest.mark.git_repo, and pytest.mark.docs_scoped — the exact marker triple test_no_legacy_terminology.py uses (verified by reading that file directly) — so it runs in the integration-tests-core-misc CI job alongside the existing terminology guard, is visible to the arch pole's docs-only trim (-m git_repo), and does not run in the fast-tests-* suites.

glossary-anchor-contract.md

Contract: Glossary Per-Term Anchors

Owner: scripts/docs/generate_kitty_specs_docs.py (glossary_page()) Consumers: scripts/docs/glossary_linker.py, any doc page linking directly to a term. Requirement: FR-012

Input

The existing .kittify/glossaries/spec_kitty_core.yaml seed — no schema change to input. Each entry: {surface: str, definition: str, confidence: float, status: str}.

Output change

glossary_page()'s rendered HTML gains one attribute per term card that does not exist today:

<div class="glossary-card" id="term-{anchor_id}" data-surface="{surface}">
  ...
</div>

(e.g. "branch strategy gate""branch-strategy-gate").

104 terms, but must be handled), append a numeric suffix (-2, -3, ...) in seed-file order.

  • anchor_id = lowercase, ASCII-only, hyphen-separated slug of surface
  • Collision rule: if two terms slugify to the same anchor_id (not expected with the current

Guarantee

https://docs.spec-kitty.ai/kitty-specs/glossary.html#term-{anchor_id} resolves to (scrolls to or highlights) the correct card for every term, for the lifetime of that term's surface value. Renaming a surface value changes its anchor_id — out of scope for this mission to solve generally (no alias/redirect layer for glossary anchors; C-003).

glossary-linker-contract.md

Contract: Glossary Term Linker

Owner: new scripts/docs/glossary_linker.py Requirement: FR-011, NFR-004 Pipeline position: Runs after DocFX renders HTML, in the same build stage grouping as scripts/docs/seo_postprocess.py — a post-processing pass over _site/*/.html, not a markdown transform.

Input

glossary-anchor-contract).

  • .kittify/glossaries/spec_kitty_core.yaml (term list, with anchor_id per the
  • Rendered HTML files under the DocFX output directory.

Behavior

For each rendered doc page (excluding kitty-specs/glossary.html itself):

1. Walk the page's text nodes, skipping <code>, <pre>, <script>, <style>, and any existing <a> tag's inner text. 2. Match glossary surface strings case-insensitively, longest match first (a term whose surface is a substring of a longer term never pre-empts the longer match). 3. On the first match of a given term on that page, wrap it: ``html <a href="/kitty-specs/glossary.html#term-{anchor_id}" class="glossary-link" title="{definition, HTML-escaped}">{original matched text}</a> `` 4. Every subsequent occurrence of the same term on the same page is left untouched (NFR-004).

Output

Modified HTML files, written back in place. No markdown source file is ever touched (plan-phase decision, Decision Moment 01KY03YGX7GQEBKV45Q2Q8FXK3).

Failure mode

If the glossary seed fails to parse or a term's anchor_id is missing, the linker logs a warning and skips linking for that term rather than failing the whole docs build (a broken glossary link is a UX bug, not a publish-blocking error) — consistent with this being a content-enhancement pass, not a correctness gate (that role belongs to the canonical-term-check contract below).

redirect-map-entry-contract.md

Contract: Redirect Map Entry

Owner: existing scripts/docs/redirect_map.yaml + scripts/docs/redirect_stub_generator.py Requirement: FR-003, FR-009, NFR-006 (this mission is a consumer, not the owner — no schema change to the existing mechanism)

Obligation

Every path this mission moves or removes from docs/ — chiefly during IC-01 (contributor runbooks relocated out of guides/) and any consolidation under FR-009 — gets exactly one entry added to redirect_map.yaml before that move is considered complete.

Entry shape (existing schema, unchanged)

- old_path: guides/pr-landing.html
  new_path: development/pr-landing.html

Verification

redirect_baseline_urls.json (existing, from common-docs-consolidation) is the denominator for confirming no baseline URL 404s after this mission's moves — the same NFR-006 obligation this mission's own tasks phase will re-verify against the pre-mission URL set, extended with this mission's own moved paths.

What this mission does NOT change

The generator script's behavior, the stub HTML it emits (<meta http-equiv="refresh">), or the map's schema — all reused as-is (C-001 spirit: don't touch working pipeline internals without cause).