Research: Common Docs query — CLI retrieval index
Consolidated from the pre-plan adversarial squad (architect-alphonso + python-pedro, convergent, code-cited). Every decision below is grounded in an existing repo surface; no NEEDS CLARIFICATION remain.
Decision 1 — Sibling artifact, NOT enrichment of the page-inventory
(docs/development/3-2-docs-retrieval-index.yaml) built by a new module (scripts/docs/docs_index.py) with its own DocsQueryEntry schema.
dataclass with a _REQUIRED_KEYS frozenset, imported by 8 tooling modules + 6 test files, rendered byte-stably by render_lockfile (inventory_lockfile.py:227) and guarded by a now-blocking _check_inventory_lockfile_drift (check_docs_freshness.py:681). Widening it changes every committed inventory row → instant drift red, and forces edits to test_inventory_path_stable.py and test_inventory_lockfile_untouched — the exact C-001 consumers. The sibling holds C-001 by construction.
blocking gate). A brand-new framework unrelated to the inventory (rejected — violates C-004; the inventory generator is the proven deterministic pattern to mirror).
- Decision: The retrieval index is a new, separate generated file
- Rationale:
PageInventoryEntry(scripts/docs/_inventory.py:93) is afrozen6-field - Alternatives considered: Enrich
PageInventoryEntryin place (rejected — breaks C-001, reds the
Decision 2 — Reuse the inventory generator machinery (C-004)
ruamel-safe docs-page frontmatter extractor), the sorted rglob("*.md") walk, the DivioType StrEnum (_inventory.py:83, sourced from the frontmatter type: key per inventory_lockfile.py:68), and the InventoryDrift / compare_inventories diff shape.
the right surface (NOT src/specify_cli/frontmatter.py, which is the WP-file manager).
- Decision: Import and reuse
parse_frontmatter(scripts/docs/_inventory.py:38, the canonical - Rationale: These are the canonical, already-tested docs-page primitives.
parse_frontmatteris - Alternatives considered: A second frontmatter parser (rejected — DIRECTIVE_044).
Decision 3 — Parallel freshness gate, inventory ruler untouched
aggregate as a new error-severity ruler; leave _check_inventory_lockfile_drift byte-for-byte unchanged.
aggregate (auto-covering the new gate) or needs an explicit step.
- Decision: Add
_check_docs_index_drifttocheck_docs_freshness.pyand register it in the - Rationale: Mirrors the proven drift pattern; C-001 keeps the inventory gate untouched.
- Open verification (Phase 2): confirm whether
.github/workflows/docs-freshness.ymlinvokes the
Decision 4 — Mirror the glossary CLI shape
registered via app.add_typer(docs_module.app, name="docs", ...) next to glossary (cli/commands/__init__.py:208). JSON path uses print(json.dumps(output, indent=2)) (not Rich console); empty result prints "[]" and returns (exit 0); human path uses a Rich Table.
Acceptance Scenario 2.
consumers). A new top-level command instead of a sub-app (rejected — inconsistent with glossary).
- Decision: New
src/specify_cli/cli/commands/docs.pyTyper sub-app with aquerycommand, - Rationale:
glossary.py:292-363is the exact proven shape; empty→[]/exit-0 satisfies - Alternatives considered: Rich
console.print_json(rejected — markup/formatting risk for machine
Decision 5 — Anchor authority: canonical slugify + ordinal dedup, NOT DocFX-exact
(generate_kitty_specs_docs.py:193, already import-shared by glossary_linker.py) plus ordinal disambiguation (the assign_anchor_ids pattern) for duplicate headings. Anchors are deterministic source-heading slugs; byte-identical fidelity to the rendered DocFX site is an explicit non-goal (C-005).
rendered by the Python slugify. Reverse-engineering DocFX's JS slugger is disproportionate; the index's job is to point an agent at the correct source heading deterministically, which the canonical slugify does. Reusing it (not forking) satisfies DIRECTIVE_044 and keeps NFR-001 byte-stability.
a fresh slugger (rejected — canonical-sources violation).
- Decision: Compute heading anchors with the canonical
slugify - Rationale: The
docs/tree is a DocFX site (toc.yml/docfx.json,seo_postprocess.py), not - Alternatives considered: Replicate DocFX exactly (rejected — scope/maintenance cost, C-005). Write
Decision 6 — Title precedence and abstract fallback
frontmatter description else first non-heading paragraph else empty. Both are total (never crash).
description (the _EXCLUDE_PREFIXES set in description_length_check.py) are included with an empty abstract — consistent with that gate's exemptions.
helpers (no existing extractor); trivially unit-testable.
- Decision:
title= frontmattertitleelse first# H1else path stem.abstract= - Rationale: NFR-001 needs a deterministic, total precedence. ADR/changelog pages with no
- New code: the first-non-heading-paragraph extractor and the
##/###heading scan are new pure
Performance (NFR-002)
term/divio-type/section over that structure. No per-query filesystem walk of doc bodies.
makes full-tree query trivially < 1s.
- Decision: Load the generated index file once at command entry into an in-memory structure; filter
- Rationale: Mirrors the glossary in-memory-cache filter (
glossary.py:160); a pre-generated index