exp/core-dossiers
needs attentionviewing older commit910fc6a · incrementalPR #270reviewed 2026-07-10 23:48 UTC1H · 9M · 6L · 4I- Purpose
- Stands up the capability-dossier system — a knowledge base for the FE + public-API surface that the backend ontology/FCIS docs never covered. 9 audited core-domain dossiers with citations to file:line.
- Goal
- Mechanical freshness gate (Level 1): CI-checks that dossier citations resolve and solely-owned hook/route surfaces are documented. Prevents dossiers from rotting silently after code changes.
- Sub-goals
- SG-1: Dossier front-matter YAML (contracts:/hooks: ownership declarations) added to all 9 entities
- SG-2: check-freshness.mjs — checks A (citation existence), B (hook coverage), C (route coverage)
- SG-3: CI-gated via pr-checks.yml pnpm dossier:check (advisory, continue-on-error: true)
- SG-4: capability-dossier.md + SKILL.md updated to document Mode D (Check)
- What
- Front-matter added to 8 entity dossiers; new check-freshness.mjs (173 lines); pr-checks.yml new step; package.json new dossier:check script; capability-dossier.md + SKILL.md updated.
- Why
- Dossiers without a freshness gate rot silently — file renames break citations and new hooks/routes go undocumented. This is the mechanical half of the freshness guarantee (the review half is capability-dossier.md's freshness gate).
- Areas
- .claude/rules+113−0.claude/skills/dossier+421−0.claude/skills/capability-walkthrough+47−0.github/workflows+7−2package.json+1−0
- Blast
- 20 files, +1,239/−2. All in .claude/ tooling + GHA workflow + package.json. Zero runtime code.
Findings · 19
correctness4
lineCount under-counts for files ending with a blank line — false-positive dangling-citation errors
.claude/skills/dossier/lib/check-freshness.mjs:21
A file ending with \n\n has '' as last element after split('\n'); lineCount() returns N-1. A dossier citation to the final blank line (line N) is falsely flagged as dangling even though it exists. Common in TypeScript files where formatters append a trailing blank line. Fix: use `(content.match(/\n/g)??[]).length` or check `content.endsWith('\n\n')` separately.
frontMatter key-reset too broad — any lowercase-starting line clears the active list key
.claude/skills/dossier/lib/check-freshness.mjs:45
else if (ln.match(/^[a-z]/)) key = null fires on any lowercase word, not just YAML keys. A description field between contracts: and hooks: silently truncates the contracts list. Fix: match /^[a-z][a-z_]+:\s*/ to distinguish keys.
hookNames regex matches export const re-exports — false-positive undocumented-hook findings
.claude/skills/dossier/lib/check-freshness.mjs:71
export const useSomething = useSomeHook (re-export) matches the const regex. Re-exported hooks are flagged as undocumented in the re-exporting file's dossier. Consider filtering by whether the name is defined vs. referenced in the same file.
Shared ownership silently suppresses coverage — no warning emitted
.claude/skills/dossier/lib/check-freshness.mjs:88
When owners[p].length > 1, ownedBy() returns false and coverage is silently skipped. Emit a warning line so maintainers know the gate is inactive for shared files.
security2
Front-matter declared paths not prefix-validated — path traversal possible
.claude/skills/dossier/lib/check-freshness.mjs
CITE regex enforces apps|packages|domains|services|infra prefix on citations, but contracts:/hooks: list items from front-matter are joined with ROOT without a prefix guard. A crafted dossier could declare ../../.env. Low exploitability (internal repo, checked-in files only). Add a prefix guard matching the CITE allowlist.
continue-on-error swallows path-read errors silently in CI
.github/workflows/pr-checks.yml:66
Compounding factor for the path traversal concern. Non-blocking given the advisory posture.
conventions7
20-line JSDoc block comment violates no-multi-line-docstring convention
.claude/skills/dossier/lib/check-freshness.mjs:2
CLAUDE.md: 'no multi-line comment blocks — one short line max'. The opening /** ... */ block spanning lines 2–27 is a preamble docstring. Move the A/B/C explanations to inline comments at each check section (lines ~118, ~127, ~134).
invitation.md + profile.md co-declare auth.contract.ts / useAuthApi.ts — auth coverage silently skipped for both
.claude/skills/dossier/references/entities/invitation.md:5
ownedBy() requires exactly one declarer. Both dossiers listing the same files means neither gets auth-route or auth-hook coverage-checked. Rule 7: 'list only entity-scoped hook files'. Remove auth.contract.ts and useAuthApi.ts from both front-matters; citations in the dossier body still get existence-checked.
site.md + site-location.md co-declare sites.contract.ts / useSitesApi.ts — site coverage silently skipped
.claude/skills/dossier/references/entities/site.md:4
Same ownership collision. sites.contract.ts is the busiest contract in the entity group. site.md should be sole owner; site-location.md should drop these from front-matter.
profile.md + user-preferences.md co-declare profiles.contract.ts / useProfileApi.ts — profile coverage silently dropped
.claude/skills/dossier/references/entities/user-preferences.md:4
profiles.contract.ts and useProfileApi.ts are profile-scoped. profile.md is the natural sole owner. user-preferences.md should remove them from its front-matter.
organization.md declares a components/ file in hooks: — framing inconsistency
.claude/skills/dossier/references/entities/organization.md:9
useLifecycleStatuses.ts lives under components/lifecycle/, not lib/hooks/api/. Functionally correct (it is a React hook). Add a brief inline comment to clarify why this component-directory file is listed.
secret-configuration.md uses block-form empty hooks list instead of inline []
.claude/skills/dossier/references/entities/secret-configuration.md:8
Normalize to hooks: [] (inline) for consistency.
admin-organizations.contract.ts shared between organization.md and secret-configuration.md — no admin route coverage
.claude/skills/dossier/references/entities/organization.md:6
Acknowledged behavior per the tool design. Worth noting in a comment alongside the declarations.
tests3
frontMatter() edge cases untested — silent false-negatives before gate hardens to blocking
.claude/skills/dossier/lib/check-freshness.mjs
Parser bugs silently return empty hooks/contracts → Checks B/C trivially pass. continue-on-error masks failures. Add table-driven tests (no-front-matter, inline [], block list, mixed keys) before promoting to blocking.
CITE regex lastIndex reset and extractor comment-skip logic are untested
.claude/skills/dossier/lib/check-freshness.mjs
Low risk while continue-on-error is set (CI acts as integration test). Add before hardening gate to blocking.
CI itself acts as integration test on every PR — bounded risk
.github/workflows/pr-checks.yml
Real dossiers and real contract/hook files exercise all code paths on every PR run. Substantially bounds the missing-test risk.
improvement3
lineCount reads entire file content to count lines; files re-read per dossier
.claude/skills/dossier/lib/check-freshness.mjs:51
Use a read-cache (Map<path,string>) populated on first access and shared across lineCount/routePaths/hookNames. Count newlines with (content.match(/\n/g)??[]).length to avoid O(n) array allocation.
Module-level CITE global regex is a hidden coupling
.claude/skills/dossier/lib/check-freshness.mjs:130
Use str.matchAll(new RegExp(...)) to eliminate the lastIndex = 0 requirement and hidden state.
existsSync imported but never used
.claude/skills/dossier/lib/check-freshness.mjs:28
Remove the unused import.
History · 7 commits
- 8ff8773needs attentionincremental1H · 3M · 2L2026-07-27 17:46
- 7640070needs attentionincremental10H · 18M · 14L2026-07-18 02:11
- a516a9dneeds attentionincremental4H · 5M · 4L2026-07-17 04:38
- 910fc6aneeds attentionincremental1H · 9M · 6L2026-07-10 23:48current
- 0b75850safeincremental0H · 0M · 0L2026-07-10 23:17
- 380a931safeincremental0H · 0M · 0L2026-07-09 19:17
- 5a9ac90blockedfull5H · 8M · 5L2026-07-07 22:07