Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 4 additions & 11 deletions data/outstanding-issues-snapshot.json
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
{
"version": "outstanding-issues-snapshot-v1",
"ledger_revision": {
"sha": "d247db72afce3d28a45d496334927a6c5cb5762e",
"committed_at": "2026-09-02T03:17:21+00:00"
"sha": "a4a95ec89b59bec602065376278598b8a0b896fa",
"committed_at": "2026-09-02T14:22:01+08:00"
},
"counts": {
"open": 70,
"p1": 2,
"p2": 49,
"p3": 19,
"queued": 7,
"pending": 1,
"pending": 0,
"resolved": 459
},
"queue": [
Expand Down Expand Up @@ -724,12 +724,5 @@
"added": "2026-08-24"
}
],
"pending": [
{
"request_id": "f5078359-d4ea-4a91-b376-d5bbecb452a6",
"action": "add",
"summary": "Corpus health panel and the hub document count have never been seen against the real library",
"created_at": "2026-09-02"
}
]
"pending": []
}
31,664 changes: 15,830 additions & 15,834 deletions data/repo-awareness-snapshot.json

Large diffs are not rendered by default.

20 changes: 20 additions & 0 deletions docs/codebase-index.md
Original file line number Diff line number Diff line change
Expand Up @@ -635,6 +635,26 @@ repo-awareness-snapshot.ts` (`loadRepoAwarenessSnapshot`) is the typed reader, w
fails when the committed snapshot is behind the repository it describes. `src/lib/developer-area/
freshness.ts` is the label-agnostic content-age helper both the ledger and the repo-awareness
pages use to render their freshness stamp.
- **What the snapshot deliberately does NOT commit, and why:** the gate compares only `routes`,
`documentation` and `test_health`. The two keys it excludes are excluded because they change on
both sides of every concurrent append, and a committed field that always differs is a merge
conflict — which sets `mergeable_state=dirty` on GitHub, suppresses `refs/pull/<n>/merge`, and
leaves the PR check list reading empty rather than red. So the excluded keys carry only content
that can merge: `review_state.records` is ordered by `head` (a uniformly distributed sha, so two
branches appending a review record land far apart), it stores no aggregate `counts`
(`reviewStateCounts()` derives them at render), and `REVISION_INPUTS` excludes the review corpus
so a `ledger:append` no longer moves `captured_revision`. Adding anything back to those keys
means meeting the same bar: too volatile to compare is too volatile to commit in a conflicting
shape.
- **The gate needs git, and says so when it has none:** `check:repo-awareness-snapshot` reaches git
through the generator (`git ls-files`, `git log`), so in a checkout with no git — a `git archive`
export, or a container image without `.git` — it logs `Skipped: no git repository rooted here`
and exits **0**. A skip and a pass share that exit code, so read the message, not the code. The
check is deliberately "a repository ROOTED HERE" rather than "inside a work tree": an export
extracted inside another checkout answers `git rev-parse --is-inside-work-tree` with the outer
repository's `true`, and the generator then reads that repository, finds every document
untracked, and fails with an unexplained six-hundred-path error. Nothing is lost by skipping —
CI always runs this gate against a real checkout.
- **Task ledger data:** `src/lib/developer-area/ledger-snapshot.ts` imports the generated
`data/outstanding-issues-snapshot.json` (never hand-edited; listed in `.prettierignore`) rather
than reading `docs/outstanding-issues.md` at runtime — the production Docker image never copies
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"version": 2,
"id": "39f1842e-9780-4c73-8082-28cf9f271104",
"createdOn": "2026-09-02",
"action": "add",
"payload": {
"pri": "P3",
"type": "issue",
"summary": "The developer hub's pending-requests panel reads empty during local dev, because no lifecycle regenerates the snapshot for the dev server",
"detail": "Raised by Codex review on PR #2530 (scripts/generate-outstanding-issues-snapshot.mjs) and verified rather than accepted on trust. THE FINDING IS CORRECT: npm run dev and npm run ensure invoke no prebuild, so ledger-snapshot.ts imports the committed JSON and the ledger page's 'requests not yet applied' count and list render empty while requests exist in docs/outstanding-issues-inbox/. That is the #338 class of failure the developer hub exists to prevent - a panel silently under-reporting. IT IS NOT A REGRESSION FROM PR #2530, and this matters for how it gets fixed: measured on main at 45a3dcacb before that branch existed, the committed snapshot already carried counts.pending 0 while 8 inbox requests were tracked in the same commit. The panel was already blank in dev. PR #2530 changed it from accidentally blank to blank by design, and added a gate that keeps it that way, so the fix now has to be deliberate. THE OBVIOUS FIX IS WRONG: adding a predev that generates the pending-inclusive form would write a populated pending into the tracked working tree on every dev-server start, which is precisely the churn #Y090R5 records and PR #2530 removed - and check:outstanding-issues-snapshot now fails on a committed non-empty pending, so a developer would be left with a permanently dirty tracked file that the gate refuses. Two shapes are worth weighing instead. (a) A development-only reader: the ledger page merges docs/outstanding-issues-inbox/*.json from disk when NODE_ENV is not production, leaving the committed artefact untouched. Cheap, but it adds an environment-conditional code path to a module whose docstring records that reading docs/ live was deliberately rejected because the production image never copies docs/ - the divergence would be benign here (production gets the true list from prebuild) but the reasoning must be written down rather than quietly reversed. (b) Generate the pending-inclusive form to a SEPARATE gitignored sidecar that the page reads when present, so nothing tracked is ever dirtied; costs a second artefact and a tolerant reader. Neither is urgent: the panel is administrator-gated, 404s for non-admins in production, and is correct in the deployed image because prebuild regenerates it there.",
"source": "Codex review on PR #2530, 2026-09-02",
"issueUlid": "01M1GC7018707F0970T8GT50SM"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"version": 2,
"id": "52120ceb-7c70-4c40-b401-c4bbd051903d",
"createdOn": "2026-09-02",
"action": "done",
"payload": {
"id": "#EFETZT",
"outcome": "Fixed the merge-conflict half. The committed snapshot now carries only content that can merge: review_state.records is ordered by head (a uniformly distributed sha) instead of date-descending, so two branches appending a review record insert hundreds of lines apart and git resolves both hunks; review_state.counts is gone because an aggregate over an append-only set changes on both sides of every append and no ordering can disperse that (reviewStateCounts() derives it at render); and REVISION_INPUTS now excludes docs/branch-review-records/** and the rotated archives, so a ledger:append no longer moves captured_revision. Snapshot version bumped to repo-awareness-snapshot-v2 so a stale committed file fails loudly. The staleness half was already fixed by the COMPARED_CONTENT_KEYS exclusion. Also carved data/repo-awareness-snapshot.json into ci-change-scope perfExclusionPatterns, mirroring the sibling: regenerating it was flipping perf_changed and forcing a ~7-minute Lighthouse run against a budget the change cannot move, which was part of this row's measured per-occurrence cost.",
"baseRowFingerprint": "6925801b83cc83fc17f4c83405e29ddbdc69e3cd26cae804e2d22ab05df1aae6"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"version": 2,
"id": "a24114b2-feb8-4588-94f8-82a88ea3fc53",
"createdOn": "2026-09-02",
"action": "add",
"payload": {
"pri": "P3",
"type": "issue",
"summary": "Developer-hub CODE still flips perf_changed, so an admin-only mockup route pulls a full Lighthouse run",
"detail": "Found while fixing #EFETZT on 2026-09-02 and deliberately NOT fixed in that PR, because it means editing a fail-closed CI classification surface. PR #2530 added data/repo-awareness-snapshot.json to perfExclusionPatterns in scripts/ci-change-scope.mjs, mirroring the carve-out data/outstanding-issues-snapshot.json already had for the same reason (PR #2302). That closes the common case - a handoff PR that only regenerates the snapshot no longer pays a ~7-minute Lighthouse budget run against a budget the change cannot move. It does NOT close the code case. src/components/developer-area/hub/** and src/lib/developer-area/** match the generic 'src' entry in perfPatterns (ci-change-scope.mjs:226) and are not excluded, because only the ROUTE WRAPPER lives under the excluded src/app/mockups prefix - the panel components live one directory hop away under src/components. So a PR touching the developer hub's own code still triggers lighthouse-budget for /mockups/development/**, which 404s for non-admins in production (src/app/mockups/layout.tsx and src/proxy.ts gate it behind DEVELOPER_AREA_HEADER) and cannot appear in either budgeted journey. WHY IT WAS LEFT: the exclusion list is a fail-closed safety surface, and widening it by directory prefix risks exempting a future component that IS reachable from a budgeted route. The safe shape is probably an explicit list of the developer-hub component and lib paths rather than a prefix, pinned by an assertScope self-test beside the two that already exist (ci-change-scope.mjs:1022), plus a test proving a non-hub file under src/components still flips perf_changed. Cost of leaving it is bounded and only paid by developer-hub PRs, which are rare.",
"source": "session 2026-09-02, PR #2530; verification-router review",
"issueUlid": "01M1G8RXVY9ZGNW7CR1YQ4SE5J"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"version": 2,
"id": "b11bdfdd-70eb-4bf9-9384-4d30af2a6ad2",
"createdOn": "2026-09-02",
"action": "add",
"payload": {
"pri": "P2",
"type": "issue",
"summary": "Web-container sessions start on a shallow clone, so two committed specs fail with messages that read as content errors",
"detail": "Measured 2026-09-02 in a Claude Code web container on origin/main 45a3dcacb. The session's checkout is a SHALLOW CLONE (depth 102). Two specs in the committed suite then fail, stably across two full runs, and neither message says anything about history depth: tests/clinical-hazard-controls.test.ts fails with 'CLINICAL-TRUTH-AUTHORITY: reviewedCommit does not exist 883f1007a85cd4e02198f39c12c4a4e467d4b89e' plus 'path is absent from reviewedCommit', and tests/rag-plan-package-parity.test.ts fails with 'manifest reconciledBase is unavailable locally: f3d1a3cce2c943ad3083425ed9c7c46dbef23087' from scripts/build-rag-plan-packages.mjs --check --require-origin-main. Both read as governance-manifest corruption, which is exactly the wrong conclusion: the commits are real and simply absent from a truncated history. PROOF: git fetch --deepen=2000 takes the clone from 102 to 5462 commits, both commits then resolve under git cat-file -e, and the two specs pass 27/27 with no code change. AGENTS.md already prescribes this remedy under 'Deleting code you believe is dead' for check:dead-code-candidate ('assessed on a shallow clone, where nothing can be dated - run git fetch --deepen=2000 first'), so the fix is known; it is simply not applied at session start and these two specs are not covered by that note. COST: a full npm run test is about six minutes, so a session can burn two of them before recognising the pattern, and the plausible-looking failure text invites someone to 'fix' a hazard manifest that is not broken. OPTIONS, cheapest first: have .claude/hooks/session-start.sh deepen the clone when git rev-parse --is-shallow-repository reports true (it already provisions Node and runs npm ci, so it is the natural place and the cost is one fetch); or make the two checks detect a shallow repository and fail with that as the stated reason rather than naming the commit; or document it in docs/testing.md. The first is preferred because it fixes the dead-code gate's known problem at the same time.",
"source": "session 2026-09-02, PR #2530 verification",
"issueUlid": "01M1G8RF0Q1M0J6DDX9C5G79V9"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"version": 2,
"id": "bcbdf769-62cf-4d67-be04-9065a603f439",
"createdOn": "2026-09-02",
"action": "done",
"payload": {
"id": "#JFRCZ4",
"outcome": "Established by the git archive export the row asked for, and the probe found a real defect rather than confirming the skip. Outside any repository the gate logs a skip and exits 0, which was already correct. But an export extracted INSIDE another checkout answered git rev-parse --is-inside-work-tree with the OUTER repository's true; the generator then read that repository, found all 566 documents untracked, and threw an error the skip path could not recognise - exit 1 with a six-hundred-path dump and no explanation, exactly the unexplained failure this row predicted. isGitRepository now requires a repository ROOTED at the checkout (comparing git rev-parse --show-toplevel to cwd, normalised for Windows separators and case), pinned by a nested-repository fixture test. The isGitRepoImpl false branch also had no direct coverage - the existing test drove the message-sniffing path instead - and now has a test asserting generation is never reached. Behaviour is documented in docs/codebase-index.md, including that a skip and a pass share exit code 0 so the message must be read. Also silenced git's stderr in listDocumentPaths, which made a successful filesystem fallback print 'fatal: not a git repository'.",
"baseRowFingerprint": "7528fa05cd74f1ff7c85fcd6916fbc34ce4e46d1d26ca89d852925b72df9fa9d"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"version": 2,
"id": "d1a71c3e-a4ce-4c64-b31d-aab39ed3c79c",
"createdOn": "2026-09-02",
"action": "done",
"payload": {
"id": "#Y090R5",
"outcome": "Both symptoms closed. The core conflict was already fixed before this row was actioned - check-outstanding-issues-snapshot.mjs excludes pending and counts.pending, so an inbox PR no longer has to regenerate the snapshot - but two residues were live and are now fixed. First, regenerating still WROTE pending into the committed file (measured: generate() returned counts.pending 8 against a file committed with 0), so npm run build via prebuild, or npm run docs:update, dirtied the tree and re-armed the conflict for whoever committed the result; the generator now emits an empty pending by default and only fills it under --with-pending, which prebuild passes, so the built image still shows the true unapplied list while the committed artefact never carries another branch's queued requests. Second, the clinicalRisk:true misclassification named on this row is fixed: scripts/pr-policy.mjs now exempts data/repo-awareness-snapshot.json and data/outstanding-issues-snapshot.json by exact path from the blanket data/ clinical-risk rule, pinned by self-test assertions including one proving a neighbour under data/ keeps its risk. Ledger PRs no longer need a Clinical Governance Preflight for files holding no clinical data.",
"baseRowFingerprint": "ebf94cc0a0c0d5af374a264181b2d727a0e3bc7d158271aba2d8fe59fc797b83"
}
}
2 changes: 1 addition & 1 deletion docs/scripts-index.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Scripts index

Curated map of `scripts/` (284 files) and the `package.json` script surface (286 entries),
Curated map of `scripts/` (284 files) and the `package.json` script surface (287 entries),
grouped by purpose. This is orientation, not an exhaustive per-file listing — the authoritative
command list is `package.json`, and `npm run docs:check-scripts` verifies every `npm run <x>`
referenced in docs resolves to a real script. `npm run docs:update` refreshes the exact counts above.
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,8 @@
"check:outstanding-issues": "node scripts/check-outstanding-issues.mjs --self-test && node scripts/outstanding-issues.mjs --self-test && node scripts/ledger-inbox.mjs --self-test && node scripts/ledger-inbox.mjs check && node scripts/check-outstanding-issues.mjs && npm run check:outstanding-issues-snapshot",
"snapshot:issues": "node scripts/generate-outstanding-issues-snapshot.mjs",
"snapshot:repo-awareness": "node scripts/run-tsx.mjs scripts/generate-repo-awareness-snapshot.ts",
"prebuild": "npm run snapshot:issues",
"prebuild": "node scripts/generate-outstanding-issues-snapshot.mjs --with-pending",
"postbuild": "node scripts/generate-outstanding-issues-snapshot.mjs",
"check:outstanding-issues-snapshot": "node scripts/check-outstanding-issues-snapshot.mjs",
"check:ledger-write-discipline": "node scripts/check-ledger-write-discipline.mjs --self-test && node scripts/check-ledger-write-discipline.mjs",
"issues:add": "node scripts/ledger-inbox.mjs add",
Expand Down
20 changes: 20 additions & 0 deletions scripts/check-outstanding-issues-snapshot.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ const FIX = "node scripts/generate-outstanding-issues-snapshot.mjs";
*
* Excluding them fails safe: every canonical ledger content difference (open,
* queue, counts.open, counts.p1..p3, counts.queued, counts.resolved) is still caught.
*
* Excluding `pending` from the VALUE comparison is not the same as permitting it
* in the committed file, and `compareSnapshots` enforces the difference below.
*/
const COMPARED_CONTENT_KEYS = ["queue", "open"];

Expand Down Expand Up @@ -52,6 +55,23 @@ export function compareSnapshots(committed, regenerated) {
}
}

// `pending` is excluded from the value comparison above, but the committed
// file must still be EMPTY of it. Excluding a field from comparison never
// stopped it conflicting in git — the bytes still ship, and their value
// depends on every other branch's queued requests, which is the whole of
// `#Y090R5`. The generator defaults to an empty `pending` for exactly this
// reason, but `prebuild` passes `--with-pending` so the built image can show
// the true list, and `prebuild` also runs on any local `npm run build`. That
// leaves a dirtied working tree a developer can commit by accident, which
// re-arms the conflict. This is the backstop that makes the invariant
// enforced rather than merely conventional.
if (Array.isArray(committed?.pending) && committed.pending.length > 0) {
differences.push(
`pending: the committed snapshot carries ${committed.pending.length} inbox request(s); it must be empty. ` +
"A local `npm run build` rewrites it via `prebuild --with-pending` — re-run the generator with no flag to clear it.",
);
}

const topLevelKeys = new Set([...Object.keys(regenerated), ...Object.keys(committed ?? {})]);
for (const key of topLevelKeys) {
if (!(key in regenerated)) differences.push(`unexpected key in the committed snapshot: ${key}`);
Expand Down
Loading
Loading