← all branches

exp/core-dossiers

needs attentionviewing older commit
910fc6a · incrementalPR #270reviewed 2026-07-10 23:48 UTC1H · 9M · 6L · 4I
The branch
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)
The changes (whole branch)
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+1130.claude/skills/dossier+4210.claude/skills/capability-walkthrough+470.github/workflows+72package.json+10
Blast
20 files, +1,239/−2. All in .claude/ tooling + GHA workflow + package.json. Zero runtime code.
advisory-ci-gate no-runtime-changes dx-tooling-only
typecheck· No CI check access (GraphQL token restriction)tests· No CI check accesscoderabbit· No .coderabbit.yaml

Findings · 19

correctness4

high

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.

medium

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.

medium

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.

low

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

low

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.

info

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

medium

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).

medium

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.

medium

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.

medium

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.

low

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.

low

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.

info

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

medium

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.

low

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.

info

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

medium

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.

low

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.

info

existsSync imported but never used

.claude/skills/dossier/lib/check-freshness.mjs:28

Remove the unused import.

History · 7 commits

  1. 8ff8773needs attentionincremental1H · 3M · 2L2026-07-27 17:46
  2. 7640070needs attentionincremental10H · 18M · 14L2026-07-18 02:11
  3. a516a9dneeds attentionincremental4H · 5M · 4L2026-07-17 04:38
  4. 910fc6aneeds attentionincremental1H · 9M · 6L2026-07-10 23:48current
  5. 0b75850safeincremental0H · 0M · 0L2026-07-10 23:17
  6. 380a931safeincremental0H · 0M · 0L2026-07-09 19:17
  7. 5a9ac90blockedfull5H · 8M · 5L2026-07-07 22:07