Feature Catalog
A machine-readable index mapping every Functional Requirement (FR-S089-001, FR-082-3c-005, etc.) to the file:line that implements it and the test files that cover it. Generated deterministically by npm run generate:feature-catalog and committed to the repo at docs/develop/feature-catalog.json.
Purpose
The catalog is the AI-coding-agent entry point for the codebase. When an agent (Claude Code, a sub-agent, or a future automation) is asked to extend a feature, it looks up the FR id in the catalog and gets back: spec headline, implementing files, covering tests. No grep over a 3,000-test repo required.
Source of truth lives in specs/<NNN>-*/spec.md. The catalog is a strict view over those specs + the codebase.
Shape
{
"schemaVersion": 1,
"generatedAt": "2026-04-30T...",
"counts": {
"specs": 77,
"frs": 1208,
"frsWithImpl": 676,
"frsWithoutImpl": 532
},
"specs": {
"090-extractor-enhancements": {
"specPath": "specs/090-extractor-enhancements/spec.md",
"frCount": 21,
"frs": {
"FR-S090-011": {
"id": "FR-S090-011",
"headline": "When extract_pdf_text returns no extractable text, System MUST fall through to vision-LLM OCR ...",
"specLine": 56,
"impl": [
{ "path": "apps/ml/services/vision_ocr.py", "line": 12, "text": "..." },
{ "path": "apps/ml/services/routes/extract.py", "line": 134, "text": "..." }
],
"tests": [
{ "path": "apps/ml/tests/test_extract_route_spec090.py", "line": 102, "text": "..." }
],
"hasImpl": true,
"hasTests": true
}
}
}
}
}
How the catalog is generated
scripts/generate-feature-catalog.mjs walks every specs/<NNN>-*/spec.md, extracts each FR- identifier, then runs git grep -nF for that identifier across apps/, packages/, scripts/, docs/. Matches under __tests__/, /tests/, or with .spec. / .test. in the path become tests; everything else becomes impl.
Re-run after every PR that touches an FR or its implementing code:
npm run generate:feature-catalog
Stale-check (CI):
npm run check:feature-catalog
What "without impl" means
frsWithoutImpl counts FRs that have no code reference to the FR id as a comment marker. Two cases:
- Implemented but undocumented — the code does the thing but doesn't reference the FR by id. The drift audit (Spec 091) flagged ~14 of these; they're real impl, just under-marked. The fix is a one-line comment on the implementing file. Find them via the catalog:
frsWithoutImpl > 0 && headline matches X. - Genuinely deferred — the FR is in the spec but hasn't been built yet (e.g., Spec 082 Phase 5 knowledge accumulation, Spec 088 FR-S088-004 SSE client subscription). Tracked as follow-up PRs.
When to read the catalog
- Before starting a feature: look up the surrounding FRs to understand what already exists and what still needs to be built.
- Before opening a PR: re-run
generate:feature-catalogso the diff captures the new FR↔file mappings. - During code review: cross-reference an FR claim in the PR description against the catalog to confirm the implementation actually references the FR.
- When onboarding an AI agent: the catalog plus per-module READMEs (
apps/api/src/modules/<module>/README.md) are the primary navigation surface.
Per-module entry-point READMEs
The richest AI-agent context lives in module-level READMEs. Read these before editing the corresponding directory:
| Module | README | Specs covered |
|---|---|---|
| Extractor | apps/api/src/modules/extractor/README.md | 084, 090 |
| Extraction Review | apps/api/src/modules/extraction-review/README.md | 085, 086, 087 |
| Agents (panel deliberation) | apps/api/src/modules/admin/agents/README.md | 082 (Phase 1–4 + 3c), 09b/c/d, 089 |
Per-module READMEs document: pipeline diagrams, FR → file:line mappings, audit-chain action list, "how to extend" recipes, and "what NOT to do" lists. They're the recipe an AI agent needs before writing the next change in that module.