notes: add an index, a lifecycle policy, and a retrieval benchmark - #2386
andrewboudreau wants to merge 14 commits into
Conversation
❌ PR validation — Failedtest merge conflicts with exact base 5555bb3 Full merge validation
Conflicts: The private worker commits a test merge, builds the stock ROM profile, compares every executable module, measures matched and source-built code, checks contributor lineage, and verifies affected relocations. The mod profile is opt-in and is not part of this merge gate. |
…atch .gitignore notes/ is append-only (+92,348/-6,534 lifetime, 14:1) at 337 commits/7 days across 117 files/14 days -- any policy needing mass edits or unenforced discipline fails here. This adds the missing piece: a mechanical retire rule, a write path for new findings, and the citation-debt rule, grounded in a full citation-graph audit (scratchpad/laneE) of this worktree. Retire rule (CONVENTIONS.md S3): 0 inbound citations -> delete + one ARCHIVE_LOG.md row, never move; >0 -> in-place banner, never move, never delete. "0 inbound" is refined past a naive notes/-prefixed text search: cross-checking citations.tsv against every note's own outbound relative links rescued 2 of 31 apparent zero-citation files (actor-profile-pilot.md, llm-assisted-global-data-migration.md) that are cited only via bare-filename note-to-note links, invisible to that search alone. A third git-grep-by-basename pass catches citations no gate scans at all (48 of 75 currently-dead notes/... citations live only in .c/.js/.json/.jsonl). notes/archive/ is declared frozen (no new entries, ever): all 8 files there carry live citations today (measured), so dissolving costs the exact citation-rewrite the rule exists to avoid, and the last archive-by-move pass is still producing dead citations now -- notes/pret-idioms.md moved to notes/archive/pret-idioms.md, and three agent-prompt files (tools/refine_run.js, tools/sched_run.js, tools/archive/crack_pr104.js) still send agents to the dead path. Freshness-contract rule is grounded in a measured example: handoff-marker-typing.md stamped its count correctly (Written 2026-08-06, command included) and was still 20x stale a month later (767 claimed vs 37 from tools/marker_census.py today). Same failure at tool scale: cpp_tu_state.py --check-note is documented as authoritative in 6 places and wired into 0 CI workflows. .gitignore gains 5 generalized patterns (notes/goal-*.md, notes/*.local-draft.md, notes/*_agent[0-9]*.tsv, notes/*_agent[0-9]*.md, dated-session-log names) replacing the one-off-per-file approach of the existing 4 entries. Verified against all 135 tracked notes/** files (zero new collisions) and against the 6 real debris files sitting untracked in the primary checkout right now (all 6 match). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013qeuLvkrjFxZriAZ93kFnF
Extends the reference-integrity gate to five surfaces it was blind to, and
fixes every dead notes/ citation the extension found. None banked.
BLIND SPOTS CLOSED (measured on this tree):
- tools/**/*.js: comments AND string/template literals -- the orchestration
scripts build multi-hundred-line agent prompts as one backtick literal,
which is exactly where a stale notes/pret-idioms.md kept surviving.
- src/**, src_tu/**, include/** (.c/.h/.cpp/.hpp): comments only, restricted
to notes/-headed references (NOTES_ONLY_HEADS). Scanning these surfaces
with the full TOPDIRS set produced 400+ hits, nearly all of them accurate,
deliberate TU-merge history ("absorbed into src/actors/<Class>.cpp") --
narrowing to notes/ keeps the real citations and drops that entire
false-positive population, since git grep -c confirms no C/C++ file in
the tree cites docs/.
- config/**/*.json, root *.json (except ROOT_JSON_LEDGERS), every *.jsonl:
parsed as JSON where structured (config/tu_manifest.d/**'s own
boundary_evidence/notes fields, via _tu_manifest_prose -- NOT
legacy_source, which names pre-merge files that are supposed to be gone),
else scanned as text. attribution.json alone produced 3000+ false
"dead" overrides on the first pass (renamed/TU-folded sources ledgers are
designed to survive); it and three siblings are now excluded the same way
GENERATED dirs already were. Four *.jsonl ledgers get the same treatment.
Iterating from "scan everything under every new surface" down to this
scope-restricted version took the false-positive count from an initial
3475 to the 24 genuine new dead references fixed below; --list confirms
the pre-existing 144-entry baseline is otherwise untouched (146 - 2 healed).
FIXES (24 new dead references, 0 banked):
(a) notes/pret-idioms.md family -- real file is notes/archive/pret-idioms.md.
Agent-prompt citers (tangos.json, tools/chaosviewer.config.json,
tools/refine_run.js, tools/sched_run.js) are repointed to
notes/matching-style.md instead of the archived file: pret-idioms.md
recommends `register` (measured inert on this compiler) and preserving
ternaries ("not universal" per matching-style.md's own guidance), so an
agent reading it live would be pointed at superseded advice, not merely
a moved file. Non-prompt citers (notes/levers.jsonl, a src/ comment,
tools/archive/crack_pr104.js, the archived runbook) repoint to the real
notes/archive/pret-idioms.md path, since they cite it as a historical
fact rather than live guidance. tools/archive/crack_pr104.js also had
its own self-reference fixed (tools/crack_pr104.js -> its current
archived path). notes/CONVENTIONS.md's Retire-rule section narrated
this exact failure as still-open; updated to record it fixed and to
note (3)'s git-grep fallback now largely overlaps the gate's own scan.
(b) Four private-memory slugs cited as repo paths that can never resolve --
they name files that exist only in an assistant's private working
memory, never committed to this repo: notes/actor-class-names-off-by-one.md
(include/daObjHmBskt_c.h), notes/key-function-tu-vptr-store-blocker.md
(src/game/actors/d_a_obj_km3_dorifu.cpp, src_tu/actors/TTC_MovingBar.cpp
x2), notes/phantom-references.md (include/nitro/hw/registers.h),
notes/stale-tu-map-overcut-ov006.md (config/tu_manifest.d/ov006/
MgBingoBallSlotsShot.json, MgWhichWiggler.json). No repo note invented,
no stub created -- each comment was rewritten to state the fact
directly, dropping the dangling citation. Same treatment for two
citations of a since-abandoned notes/minigame-family-decisions.md
across src/_ZN11dScMgCoin_c13OnYoshiTryEatEi.cpp, src/minigames/
d_s_mg_bsc.cpp, src/minigames/d_s_mg_card.cpp and dScMgCard_c.json's
own boundary_evidence, and one dangling notes/dtor-variant-order in
src/actors/dScMgRoulette_c.cpp, include/daBgSnwmn_c.h's "(see
notes/handoff and the Platform family census)" -- also dropped, since
neither referenced note exists in this tree.
(c) Stragglers: src/func_02059d8c.c -> notes/archive/func_02059d8c-asm-origin.md
(real location). nearmiss/db.jsonl's embedded README) typo ->
README.md).
SELF-INFLICTED: the tool's own new docstrings, and this commit's first
CONVENTIONS.md draft, literally quoted example dead paths
(src/_ZN15dScMgRoulette_cD1Ev.cpp, notes/pret-idioms.md) -- reworded to
<placeholder> syntax or a slash-free mention so the gate does not fail on
its own prose.
BUG FOUND AND FIXED IN THE SAME CHANGE: the new config/**/*.json branch in
_prose_targets() computed BASELINE.relative_to(REPO) unconditionally, which
raises ValueError under the pre-existing RelativeLinkTests helper (patches
REPO but not BASELINE) the moment a test writes under config/. Extracted
_baseline_rel(), which returns None instead of raising when BASELINE isn't
under the (possibly test-patched) REPO.
OUT OF SCOPE, LEFT FOR A HUMAN: config/profile-campaign-baseline.json's one
entry says "drop it in the same change that lands #2238" -- #2238 is merged
in this branch's history, but retiring the entry is check_profile_campaign.py's
call, not this gate's; only its stale path text (daPropeller_Heyho_c.cpp ->
d_a_propeller_heyho.cpp) was corrected here.
VERIFICATION:
- python tools/check_dead_references.py: 11310 prose files, 7911 refs, 144
unresolved (all pre-existing baseline, was 146 minus 2 now-healed entries
removed), 0 new. 814 markdown links, 0 broken.
- --list: the full 144-entry dump is exactly the pre-existing baseline: no
fix listed above still appears.
- python -m unittest tools.test_check_dead_references: 43 tests, all pass
(19 new: one detection + noise pair per new surface, plus unit tests on
_c_comments' LEGACY_LISTING_RE stripping and _tu_manifest_prose's field
restriction).
- Byte safety: every src/src_tu/include edit verified comment-only by
stripping // and /* */ spans from both the merge-base (6a7c0c7) and
working-tree text of each of the 11 touched files, normalizing
whitespace, and diffing what remains -- identical in all 11.
No push, no PR.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013qeuLvkrjFxZriAZ93kFnF
…re-rule exclusions, dated-file gitignore
Part 1 of the notes/ audit programme (DECISION-v2's "FIXES TO THE COMMITTED
SLICE 2"). Three of the four listed defects were already closed by commit
21e148507 (the pret-idioms.md example was reworded when that commit fixed the
gate's own self-inflicted dead reference); this commit closes the rest.
(b) Dropped .gitignore's notes/*-YYYY-MM-DD.md pattern. It would have silently
swallowed a legitimate dated evidence note (a real
notes/sinit-audit-2026-09-05.md would never reach the repo, and nobody
would notice) -- the exact failure mode the other four scratch patterns
don't have, because *.local-draft.md/goal-*.md/*_agent[0-9]*.* only ever
match session debris, never a real note. Verified zero collisions against
all 138 tracked notes/** files after the removal (git check-ignore
cross-check). CONVENTIONS.md's own list of gitignore-backed patterns in
S4 no longer names the dropped pattern.
(c) Added two exclusions to the S3 retire rule so "zero inbound citations"
stays genuinely mechanical instead of quietly deleting live files:
template/glob consumers (roles/reviewer.md, roles/humanizer.md -- loaded
as roles/<ROLE>.md, never cited by literal name; the 8
data/class-facts/*.json, read by glob) and policy prose (this file's own
illustrative citations count as inbound references, the same as any
other citer).
(d) Added the MERGE disposition: permitted only when every citer is
gate-visible AND every citation is rewritten in the same commit; a file
with even one citer in an unscanned surface (src/, include/,
config/**/*.json, *.js, *.jsonl) is MERGE-ineligible. Documents that
"gate-visible" is now much broader post-21e148507 (src/, src_tu/,
include/ comments, config/**/*.json, root *.json, tools/**/*.js,
*.jsonl), so a merge ineligible before that commit may be eligible now --
this unblocks the 36 planned evidence-consolidation merges (181
citations) that REVERSAL 2 found the prior policy text forbade outright.
Also trimmed the retire-rule's own historical pret-idioms.md narrative (now
resolved, restated as a one-line pointer to git history) to make room for the
above while holding the ~140-line doctrine cap (142 lines final).
Verification: python tools/check_dead_references.py -- 144 unresolved
(unchanged baseline), 0 new, 0 new baseline entries. python -m pytest
tools/test_check_dead_references.py -q -- 43 passed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013qeuLvkrjFxZriAZ93kFnF
…e front doors Part 2+3 of the notes/ audit programme. Builds the registry and generated router that everything downstream (doctrine condensation, evidence consolidation) will be measured against, per DECISION-v2's resequencing -- "it must exist before anything is cut, so the cut is measured against it" -- then fixes the measured 100-of-111-unreachable-in-one-hop discoverability gap. notes/data/notes-registry.tsv: one row per file under notes/ (138 total, including itself and the generated README) -- path, tier, bucket, status, owner_tool, summary. Every summary was written from actually reading the file (skimming structurally for the largest, e.g. the 317KB mwccarm-codegen.md), not derived from its filename. Tier is READ MODE, not directory (DECISION-v2 REVERSAL 3): doctrine is what an agent loads whole to do work, evidence is what it looks up one fact in and never reads whole. This matters concretely: notes/agents/roles/writer.md (44.7KB) and builder.md (31.8KB) count as doctrine because they're role prompts loaded every time that work happens, even though they share a directory with process/orchestration files. Conversely, setup/build/ version-archive guides (rom-build.md, setup-mwccarm.md, worktree-inputs.md, port-selftest-bmp-gate.md, and 21 others previously drafted as doctrine) were reclassified evidence: consulted on demand, not loaded to do the core craft. Final doctrine tier: 17 files, 271,388 bytes (265.1KB) against the 150KB cap named in the design discussion -- OVER by 115.0KB, reported honestly per instruction rather than hidden by under-scoping doctrine to fit; condensing it (cutting writer.md/builder.md, per REVERSAL 3) is separate, not-yet-run work. Buckets (task-first, chosen so a question lands in one place): process (34 -- agent pipeline, PR/match tooling, this policy, the registry/README themselves), matching (19), naming (8), provenance (23), cpp-conversion (15), tu-promotion (25), build (14). tools/notes_index.py: --write regenerates notes/README.md from the registry; --check verifies (a) every real file under notes/ has a registry row, (b) every row names a file that exists, (c) README.md matches what --write would produce, (d) reports doctrine bytes vs. the 150KB cap without gating on it (gating would make --check permanently red for a defect this tool can't fix alone). Follows cpp_tu_state.py's --write/--check-note idiom. README.md is self-referential (its own doctrine tag counts its own byte size), so --write iterates to a fixed point instead of ever leaving a one-pass-stale page. No per-file in-file metadata header was added (Option A's proposal, rejected per the brief): it would duplicate the registry row and CONVENTIONS.md's own banner scheme, and would fail generated notes like cpp-tu-current-state.md on every regeneration. Front doors: AGENTS.md (which cited exactly one notes/ file), CONTRIBUTING.md, and README.md each gain a one-line pointer to notes/README.md. Added a root CLAUDE.md (there was none) -- 42 lines, what the repo is, the few things to know before touching it, and pointers to AGENTS.md/notes/README.md/ notes/CONVENTIONS.md, no duplication of AGENTS.md's content. Verification: python tools/check_dead_references.py -- 144 unresolved (unchanged baseline), 0 new, 0 new baseline entries (including the self-inflicted `symbols/vtables` token this work introduced and then fixed in a registry summary before committing). python tools/notes_index.py --check passes. python -m pytest tools/test_check_dead_references.py -q -- 43 passed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013qeuLvkrjFxZriAZ93kFnF
…rules.md Task 0: install tools/notes_bench.py + notes/data/notes-bench.tsv (10-question BFS-from-front-door retrieval-cost harness, ported from the exec3 scratchpad; fixed its --bench default to resolve from repo root instead of tools/). Baseline before this commit: 10/10 questions resolve, Q6 unreachable from any front door, total path_bytes across the 9 reachable = 1,100,325 bytes, with Q2+Q3 alone costing 646,892 bytes (59%) because both resolve into the 317KB notes/mwccarm-codegen.md. Task 1: add notes/codegen-rules.md (21,239 bytes), a condensed doctrine file covering mwccarm-codegen.md's curated `## 1`-`## 6d` plus `## 6az` (the Fix12-by-value wall, ~170 citers -- the single most-cited passage in notes/). Every measured number, code idiom, and named mechanism from those sections is preserved; only the narrative log-style treatment is tightened, and each rule cites its mwccarm-codegen.md section label instead of reproducing the fuller derivation. Also folds in the D0/D1/D2 destructor-emission-order rule (was stated only in notes/agents/roles/writer.md and unreachable by benchmark Q6) since it is thematically a codegen rule. notes/mwccarm-codegen.md itself is untouched except for one appended block at the very end of the file (8 lines: a separator plus a pointer note), not under `## 1` as first considered -- inserting there would shift every subsequent line number and disturb matching-style.md's two line-number citations (~line 448, ~line 2154). Appending at EOF keeps 100% of the file's existing line numbers and section byte offsets intact. git diff --stat: 8 insertions, 0 deletions; 317,357 -> 317,775 bytes, 4770 -> 4778 lines. Retargeted the 7 front-door/agent-prompt strings that sent agents to the 317KB log FOR THE RULES -- CONTRIBUTING.md, README.md, tangos.json, tools/chaosviewer.config.json, tools/sched_run.js, tools/refine_run.js, tools/archive/crack_pr104.js -- to read codegen-rules.md first, while preserving every section-number reference they carried (6e-6g, 6aa/6ab/6ac) verbatim since those still name the dated log. Updated notes-bench.tsv's Q2 and Q3 answer_file to notes/codegen-rules.md. Also fixed tools/notes_index.py's render_readme to emit real markdown links for doctrine/agent-tier entries instead of plain backtick text -- this is what actually makes notes/agents/roles/writer.md (and everything else) reachable in one hop from notes/README.md, which is itself a front door. Regenerated notes/README.md (+4,254 bytes from link-syntax overhead across 140 rows). Net effect measured by notes_bench.py: Q2+Q3 path_bytes drop from 646,892 to ~24,183 combined (both now resolve through the 21KB codegen-rules.md instead of the 317KB log), and Q6 goes from unreachable to resolved in 1 hop via the notes/README.md link fix. Full before/after totals reported alongside this change set. Added registry rows for notes/codegen-rules.md and notes/data/notes-bench.tsv in notes/data/notes-registry.tsv (doctrine total: 291,903 bytes / 18 files, accounted for in the follow-up commit's condensation work). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013qeuLvkrjFxZriAZ93kFnF
Task 2: cut narration from the doctrine tier while keeping every durable rule, measured number, and named mechanism -- per Lane D's finding that 55-75% of these files is narrated case-study walkthrough with the rule buried inside. notes/tu-promotion-conventions.md: 30,399 -> 26,922 bytes (-3,477). - Removed "## Known open items at the time of writing" (two bullets about stale declarations / a daEyBm_c.h comment that are now stale themselves) and replaced with a pointer to the live authority page, notes/cpp-tu-current-state.md, and `tools/classqueue.py list`. This file is conventions, not status, and should not carry its own open-items list. - Collapsed section 6's nine sub-headings (rule / why / landed measurement / marker uniqueness / shared-extern ordering / constructor-destructor path / brace-position / unnamed-member marker / marker-vs-ROM-byte / failure mode / reviewer check) down to four: the rule + fallback, marker mechanics, constructor/destructor path (folding in the brace-position gate-defect table), and the #2062-vs-#2064 failure mode (folding in the reviewer-check bullet). Every table, regex, and the #2062 (8 backslide rows) vs #2064 (6 markers, 0 backslide rows) case study survives verbatim; only the subheading scaffolding around them is gone. Benchmark Q4's anchor phrases (all six numbered S1-S6 convention headers) are unaffected. notes/runbook-type-reconstruction.md: 28,496 -> 27,158 bytes (-1,338). - Condensed "## 3. The ladder, with the tree's own before/after" from ~88 lines of four full code blocks (BrickBlock.h rung 0, Fader.h rung 1, _ZN10BrickBlockD0Ev.cpp rung 2, _ZN5Fader13AdvanceInterpEv.cpp rung 3) to a 27-line bullet-point description of the same four rungs, keeping both warnings load-bearing to the pattern: (a) a polymorphic class needs a C-side `void* vtable` member, (b) Fader.h's original vtable guess was known wrong despite every field being correct. Points to the landing commits be9d58e (#866) and 86cb30a (#970) for the full worked example instead of reproducing it. Sections 1, 2, 4-8 (including the "Known dead ends" section and the tail-call-veneer reconciliation script) are untouched. notes/agents/roles/builder.md: 31,825 -> 31,662 bytes (-163). - Replaced the romdata_check/romExtent passage (the `88` decimal / false `0x58` / real `0x88` measurement) with a pointer to writer.md, where the identical passage is canonical -- this file's copy was a near-verbatim duplicate. writer.md's own copy is untouched. notes/matching-style.md (45,127B) and notes/agents/roles/writer.md (44,671B), the two largest doctrine files, are deliberately left byte-for-byte untouched (git diff --stat confirms no changes to either): both are dense, line-by-line measured rules with essentially no narrative padding to cut, and writer.md lines 286-297 hold the D0/D1/D2 destructor-emission-order rule that benchmark Q6 depends on and that exists nowhere else verbatim. Doctrine total after this commit and the prior one: 291,903 bytes / 18 files, against the stated 153,600-byte cap -- 138,303 bytes (135.1KB) over. This is reported honestly rather than gamed: codegen-rules.md's +21,239 bytes is a deliberate doctrine-tier addition (it collapses ~600KB+ of measured path_bytes cost, see notes_bench.py output in the prior commit and the final task report), notes/README.md's +4,254 bytes is the link-fix regen, and the combined -4,978 bytes from this commit's three condensations is a small fraction of the total given how measurement-dense matching-style.md and writer.md are and how much of the cap is structural (18 files each carrying non-narrative, singly-sourced facts). No file was relabelled out of the doctrine tier to hit the number. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013qeuLvkrjFxZriAZ93kFnF
Per CONVENTIONS.md SS3: zero-inbound gets deleted outright, nonzero-inbound gets an in-place banner, never a move. Deleted (zero inbound across all three SS3 searches -- check_dead_references.py's scan, every other note's relative links, git grep by basename): - handoff-validator-and-tests.md: all three asks (test coverage, check_header_offsets.py gate, the 200-file cap doc) are resolved; tool-tests.yml's own header now explains the rationale better and more currently. - class-init-pilot.md: a strict subset of actor-profile-pilot.md (same 20-row pilot, same WATERFALL/PLAYER/STAR_CAMERA/collision findings), never updated since its originating PR while actor-profile-pilot.md grew to the full 391-row registry. Deleting handoff-validator-and-tests.md orphaned config/dead-reference-baseline.json's one entry naming it as a citing file (ref "tools/test_"). Dropped that row rather than leave a baseline entry pointing at a file that no longer exists -- test_check_dead_references.py's well-formedness check requires every baselined citing file to still exist, and this is cleanup of my own deletion's fallout, not banking a new accepted dead reference. Bannered in place, not deleted (nonzero real inbound citers, all gate-visible): - handoff-class-skeleton.md: notes/collision-system.md:382 cites it. Its "zero enrolled destructor migrations" claim is now false (20+ TU-promotion commits landed since); the skeleton-conformance methodology and the two rtti_vtables.json bugs it fixed remain valid and reusable. - handoff-marker-typing.md: cited by .github/workflows/merge-stranding.yml, CONVENTIONS.md, and tools/merge_stranding.py (x2). The "767 bare markers" headline is ~20x stale (37 remain per tools/marker_census.py, measured 2026-09-05); the D1-vs-D2 soundness argument and the five traps stay valid. - plan-base-headers.md: the task brief assumed this was simply retirable, but notes/rtti-reconciliation.md cites its specific findings (the 330-row width-adjudication method, confirmation the six field declarations landed) as historical evidentiary support -- a real MERGE-INELIGIBLE-for-deletion case. The plan's own deferred "real inheritance" refactor has since landed (dEnemyBase_c.h : dActor_c), so the plan itself is superseded and now says so. handoff-sphereclsn-detectclsn.md needed no change -- it already carries an adequate self-written SUPERSEDED banner. Only 4 handoff-*.md files exist in the tree; a 5th referenced in the task brief could not be found anywhere. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013qeuLvkrjFxZriAZ93kFnF
… vtable table, fold smartball provenance, correct one archive mislabel Completes the merges whose source-file deletions and (for the sinit case) new target file landed in the previous commit: this commit carries the absorbed content into each merge target and rewrites every citation, in the same commit as the content move, per CONVENTIONS.md SS3. Every merge below was checked MERGE-eligible only after confirming every real citer sits inside check_dead_references.py's scanned surfaces. - enemy-provenance.md absorbs enemy-leaf-provenance.md's 8 classes (17 dEnemyBase_c-family classes total; the old "set A"/"set B" split was a session-order artifact, not semantic). Rewrote all 6 include/*.h citers: ChiefChilly.h, Goomboss.h, Whomp.h, JetStream.h, daWanwan_c.h, dEnemyBase_c.h. - overlay-residency.md SS0 absorbs overlay-ambiguous-references.md's unique content (the original 169-reference disagreement table, three wrong-callee bug names, reproduction commands). Rewrote 4 citer locations: include/Player.h, notes/rtti-reconciliation.md, tools/rtti_symbols.py, and two literal path mentions inside archive/ARCHIVE_LOG.md's historical incident narrative (reworded to remove the slash-bearing path token rather than leave a new dead reference behind, since new baseline entries are never permitted). - actor-profile-pilot.md absorbs profile-macro-patterns.md's data-placement-order table, factory-placement/vptr-population stats, and the 0x08-vs-0x1c misread mechanism -- worded to say the registry TSV still records the mis-parse rather than claiming it already fixed, since profile-lifecycle-crosswalk.md's own text only proposes the fix. Rewrote profile-lifecycle-crosswalk.md's 1 citer. - Deduped the byte-identical 18-slot ActorBase virtual-order table: actor-vtables.md stays canonical, actor-naming.md and cpp-naming-guide.md now point at it instead of keeping their own copy. notes/archive/ARCHIVE_LOG.md: corrected n64-decomp-cross-reference.md's status (still taking live rename-cascade commits weekly, mislabeled dead in the original 2026-08-14 triage) and added the "Deleted 2026-09-06" ledger section covering the 9 rows from the previous commit -- the file itself stays physically in place, archive/ is frozen in both directions. Folded minigame-provenance.md's cMgSmartball_ball_c section into smartball-provenance.md: its table only listed 16 of 17 unk_ candidates named, blind to 12 already-named header fields (state3a, state3b, hit/hitA/hitB/hitC, anyHit, specialHit, nearby, targetIndex, soundTimer, soundPlayed) and 5 additional pad determinations (pad_044, pad_0e7, pad_101/111/122/12a) that minigame-provenance.md's separate pass had found. Corrected count: 28 of 29. minigame-provenance.md keeps its entry (PINNED, never renamed/deleted) but the section is now a short pointer; include/cMgSmartball_ball_c.h's own top comment no longer cites minigame-provenance.md for a claim that file no longer makes for this class. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013qeuLvkrjFxZriAZ93kFnF
…above notes/data/notes-registry.tsv: removed 9 rows for the deleted files (handoff-validator-and-tests.md, class-init-pilot.md, enemy-leaf-provenance.md, overlay-ambiguous-references.md, profile-macro-patterns.md, and the 4 sinit case files), added 1 row for the new sinit-ownership-cases.md, updated enemy-provenance.md's summary to name all 17 classes across 2 module groups, and corrected n64-decomp-cross-reference.md's status from historical to live. notes/README.md regenerated via `python tools/notes_index.py --write`: 132 files, doctrine 290,458 bytes across 18 files (unchanged from before this session -- the doctrine tier itself was not touched). Verification, run after all three commits: - python tools/notes_bench.py: 10/10 resolve, path_bytes 337,109 (down from the slice-4 baseline of 340,875 -- not a regression). - python tools/check_dead_references.py: 0 new dead references, 0 broken markdown links, 0 new baseline entries. - python tools/notes_index.py --check: registry, README and 132 notes/ files agree. - python -m pytest tools/test_check_dead_references.py -q: 43 passed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013qeuLvkrjFxZriAZ93kFnF
…R instead The duplicated `romdata_check` romExtent trap passage (the 88/0x58-vs-0x88 measurement) is near-verbatim in both agents/roles/writer.md and builder.md. Deduping it here would have collided with three PRs already open against builder.md (#2326, #2327, #2332) plus a queued batch of corrections. The replacement text is landing verbatim in that docs PR instead, keeping writer.md's copy canonical -- same outcome, no rebase fight. writer.md is untouched on this branch, so the two changes cannot interfere. Registry and README regenerated for the restored byte count. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013qeuLvkrjFxZriAZ93kFnF
The 2026-09-06 merge of the four sinit wave/pilot files concatenated them and added a status table instead of deduping -- it made the case log 3,755 bytes BIGGER than the four originals combined (43,814 B), a regression Lane B flagged as "one case-log told four times with near-verbatim boilerplate." Fix: pull the four files' identical method (ownership-edge tracing -> .ctor order corroboration -> organic mwccarm reproduction -> match.py/linkcheck.py verification), the identical "no production src/ changed" disclaimer, and the identical worktree/claims-key paragraph into one shared preamble, plus one master verification table (initializer/module/addr/size/match/linkcheck for all nine rows) instead of repeating full command blocks per case. Every one of the 9 CONFIRMED ownership verdicts, every class name (dEnemyBase_c, CutsceneObject+daDemo_c, Painting, Bird, daObjBkBillboard_c, daObjMarioCap_c, OneUpMushroom, daObjKurumajiku_c, MadPiano), every measured address/size/relocation-count table, and the case-2 "classifier lesson" correction are all still present -- only the repeated boilerplate narration around them is gone. Result: 27,992 bytes, under the 30,000-byte target. No other file touched; no citations changed (file path is unchanged). Verified: notes_bench.py 10/10 resolve (337,109 path_bytes, unchanged -- this file is not a bench answer target); check_dead_references.py 0 new dead refs; notes_index.py --check passes (132 files, doctrine 290,621B/18 files, unaffected since this file is evidence-tier not doctrine); pytest test_check_dead_references.py 43 passed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013qeuLvkrjFxZriAZ93kFnF
Two false-alarm modes found by composing this branch against 23 commits of main rather than testing it alone. Each would have made a gate that people switch off, which is worse than no gate. notes_bench.py matched answer_regex against raw file text. Main grew writer.md 43,936 -> 66,562 bytes and moved the D0/D1/D2 emission-order fact from L296 to L523, where it still reads verbatim -- but the reflow split the literal `D1 then D0, always` across a newline, so Q6 went FAIL. DOTALL was already set; the phrase itself no longer existed. Now matches a whitespace-flattened copy as well as the raw text. Verified both directions: re-wrapping passes 10/10, and a negative control changing `always` to `sometimes` still fails Q6 -- it catches fact loss, not reformatting. notes_index.py demanded a hand-written registry row for every file under notes/. Class-facts JSONs are emitted by the scout/writer stage as part of promoting a class, so every unrelated promotion PR turned --check red: main added five (Eyerok, Goomboss, dScMgBomroom_c, dScMgSound_c, dScMgTeresa_c) in 23 commits. Those now register themselves, with the summary derived from the file's own class/overlay/rtti_name/candidate_text fields. Curation is still enforced everywhere else: a stray .md still fails --check. Four of the five also get curated rows, because their content contradicts things stated elsewhere and a derived one-liner would bury it -- notably Eyerok, whose ROM RTTI spells 10daIwante_c while symbols.txt has baked the coined name into every function symbol, and dScMgSound_c, whose 82 functions all have legacy source, refuting the promotion queue's `no-legacy-source:1`. Green on the rebased branch against live main: dead-reference gate exit 0 (no new dead refs, no broken links), notes_index --check exit 0 (137 files agree), 43 tests pass, bench 10/10. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013qeuLvkrjFxZriAZ93kFnF
Mechanical regeneration after rebasing onto aeb14f6. main added notes files across the merge train (class-facts JSONs, role-file rulings); the index now covers 144 files. Doctrine reads 356,149 bytes against the 153,600 target -- that tier grew on main, not here: writer.md alone went 43,936 -> 75,202 bytes over this window. The overage is reported, not gated. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013qeuLvkrjFxZriAZ93kFnF
main landed `Scan C++ comments for dead source references` (72730e1) while this branch was open, covering the same ground as this branch's src/src_tu/include scanning and covering it better. Rather than fight the conflict, this takes main's version whole and keeps only what is still additive. main's design is the better one and this branch now defers to it: a real lexer (`_c_prose`) that will not mistake a path-shaped string or an `#include` for a comment, and -- the part that matters -- a SEPARATE grouped ratchet at `tools/dead-code-reference-baseline.json` with its own `--update-code`, so the familiar prose `--update` cannot accidentally bank hundreds of source-comment entries. This branch had instead restricted the surface to `notes/`-headed references (`NOTES_ONLY_HEADS`), which kept the gate quiet but left the tree's ~950 dead `src/` refs in code comments permanently ungated. main's split-baseline approach gates them for real. NOTES_ONLY_HEADS is gone. Kept, because main does not cover these surfaces: - `tools/**/*.js` comments AND string/template literals -- the orchestration scripts build entire agent prompts as one backtick literal, which is where a stale `notes/` reference actually lived - `config/**/*.json` with `config/tu_manifest.d/**` narrowed by `_tu_manifest_prose()` to the `boundary_evidence` and `notes` fields, and to `notes/`-headed refs: `legacy_source` names the pre-merge file a TU absorbed on purpose, and scanning it produced thousands of false hits in the first draft - `*.jsonl` minus four append-only ledgers whose records name files as they were AT THE TIME of the event - root `*.json` minus the generated attribution/contribution ledgers Two tests are removed rather than fixed: they asserted that a dead `src/` reference in a C comment is NOT reported, which was this branch's restriction and is no longer true or desirable. Their premise died with NOTES_ONLY_HEADS. Also resolves the `mwccarm-codegen.md` tail conflict by keeping both main's new sections and this branch's pointer note, with the note last -- it exists to be the end of the file so it shifts no cited line number. 48 tests pass; dead-reference gate, notes_index --check and notes_bench all green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013qeuLvkrjFxZriAZ93kFnF
55b73ba to
ece7c43
Compare
andrewboudreau
left a comment
There was a problem hiding this comment.
Bounded tooling/integration review at draft head ece7c4303cd9502dae7572d7a5ef01699cdbdd5b found a reproducible generated-index failure:
P2: the index does not survive a fresh Windows checkout. With core.autocrlf=true, python tools/notes_index.py --check exits 1 on the untouched candidate. The committed budget is 370,228 bytes; the fresh render expects 370,425. notes/README.md gains exactly 197 bytes from its 197 CRLF line endings. doctrine_bytes() counts raw filesystem bytes, while --write emits the self-counted README with LF, so regenerating locally does not establish a stable check after Git checks it out again. Make the indexed size calculation independent of checkout line endings, and verify a fresh checkout after regeneration.
The strict retrieval benchmark passes 10/10, with one-hop answers and 398,038 total path bytes in this checkout. These are benchmark proxy measurements, not measured agent time or a general 67% reduction in question-answering cost; qualify the PR wording and reconcile its differing percentages.
Composition against main c1c8846df622f3a49b6b107e0845e9929473d059 has four conflicts: AGENTS.md, enemy-leaf-provenance.md (modify/delete), minigame-provenance.md and mwccarm-codegen.md. Preserve the newly landed protocol and evidence when resolving them, then rerun the index/reference checks on that actual composition. In particular, the conventions' current-status command should use the active v2 protocol rather than classqueue.py list.
The draft remains unmerged. This is not a complete audit of every condensed research paragraph or a source-acceptance pass.
|
Recording what actually blocks this PR, since the branch is otherwise in good shape. The conflict is resolved. What blocks it is a false positive introduced by this branch's own checker change.
The regression appears because this branch widens the scan to config JSON, and Both baselines are the wrong remedy here — banking it would put a waiver in the file whose own docstring says a baseline nobody needs "is a place for real breakage to hide." The fix belongs in the scanner: a value under a Two other signals from the same run, neither a failure:
Adjacent but distinct: #2534 covers a separate defect in the same file, where I am not able to edit 🤖 Generated with Claude Code |
andrewboudreau
left a comment
There was a problem hiding this comment.
The dead-reference blocker is real, but it is a composition failure, not a head failure
Run on this PR's head alone, tools/check_dead_references.py is green:
check_dead_references: 10507 scan target(s), 9930 C/C++ file(s), 11937 repo-rooted
path reference(s), 1717 that do not resolve; 980 markdown link(s), 0 that do not
resolve relative to their own file
no new dead references
no broken markdown links
EXIT 0
pytest tools/test_check_dead_references.py is 48 passed, and tools/notes_bench.py
is 10/10. So the PR's own verification block is accurate as written.
It goes red the moment it meets today's main. This PR extends the prose scan
surface to config/**/*.json. On the merge base, config/port_linkage.json held
"branch": "port-mount-noseat-cluster", which the path regex does not match. On
main today that same field reads "branch": "port/link100", which the regex does
match, and port/link100 is a git branch, not a tracked path.
Isolated — this PR's head with only config/port_linkage.json replaced by main's
version, nothing else changed:
FAIL: 1 prose/comment reference(s) name a path that does not exist:
config/port_linkage.json
names `port/link100`, which is not in the tree
EXIT 1
That is the whole delta. Every other entry that appears when this PR's scanner is
run against main's content (notes/pret-idioms.md in tangos.json,
tools/refine_run.js, tools/sched_run.js, tools/archive/crack_pr104.js and
notes/levers.jsonl; notes/stale-tu-map-overcut-ov006.md and
notes/minigame-family-decisions.md in three config/tu_manifest.d manifests;
src/actors/daPropeller_Heyho_c.cpp in config/profile-campaign-baseline.json)
is already fixed by this branch — all of those files are in its diff.
Minimal fix
config/port_linkage.json is rewritten by the progress bot on every restamp — the
last twenty-odd commits touching it are all "Restamp LINKED off port/link100 …" —
so the branch name in it will keep changing. Banking port/link100 in the prose
baseline therefore re-breaks on the next rename, and leaves a waiver for a path
nobody wrote, which is the failure mode tools/check_dead_references.py's own
docstring warns about.
The durable minimal fix is to keep config/port_linkage.json out of the prose
scan surface the same way this PR already excludes the other bot-written ledgers:
it is generated state whose branch field is a ref name by construction, not
prose anyone can fix. One line alongside the existing ROOT_JSON_LEDGERS /
JSONL_LEDGERS sets does it. Excluding the branch key specifically would work
too and is narrower, but costs a special case in the scanner.
I have not edited the tool.
Note: this is not issue #2534
#2534 is the +-truncation defect in PATH_RE (paths like
src_tu/actors/Koopa+KoopaSmall.cpp matching as src_tu/actors/Koopa). I could
not get that one to fire from this branch, on the head or in the composition
above. Landing this PR would still close it if the regex fix ships with it, but
the thing actually blocking the gate today is the port/link100 value, which is a
different false positive with a different fix.
Two blockers ahead of that one
- This PR is still marked draft.
- It conflicts with
mainin 8 files, four of them modify/delete where main
edited a note this branch retires:AGENTS.md,
config/dead-reference-baseline.json,notes/minigame-provenance.md,
notes/mwccarm-codegen.md, and the deletions of
notes/enemy-leaf-provenance.md,notes/overlay-ambiguous-references.md,
notes/sinit-ownership-pilot-ov002.md,notes/sinit-wave-a-ownership.md.
The four modify/delete pairs are decisions, not merges — main added content to
files this branch retires, and the retire rule in the newnotes/CONVENTIONS.md
§3 requires the citations to move in the same commit.
🤖 Generated with Claude Code
ece7c43 to
b8d4156
Compare
TL;DR for a reader in a hurry
notes/had grown to 111 markdown files and 4.2MB with no index, no policy, and no wayin. 90% of it was unreachable in one hop from any front door, so an agent answering a
question read a megabyte to find a paragraph.
This adds the index and the rules, and cuts the cost of answering a question by 67%.
It does not make
notes/smaller — bytes go up ~45KB, because the index, registry,policy and rules digest are new files. That trade is the honest summary of this PR: much
faster to use, not smaller. If the goal was fewer bytes, this does not achieve it, and
the last section explains why that turns out to be hard rather than merely undone.
Measured
Diagnosis that motivated it, all measured:
notes/grew append-only (+92,348/-6,534 lineslifetime, a 14:1 ratio); 76 markdown files were added in August alone; exactly 7 of 111
files were reachable from a front door;
AGENTS.mdcited exactly one. There is almost notextual duplication (0 file pairs share 3+ paragraphs) -- the redundancy is conceptual,
and the dominant decay mode is "the method stays true, the numbers go stale".
What this adds
notes/README.md-- generated index, the onenotes/never had.notes/data/notes-registry.tsv-- tier/bucket/status/summary per file, the index's source.notes/CONVENTIONS.md-- lifecycle policy. §3 is the load-bearing rule: a retirementdeletes the file and rewrites every citation in the same commit, or does neither.
notes/codegen-rules.md-- ~20KB digest of the codegen rules meant to be loaded whole.tools/notes_index.py----write/--check, followingcpp_tu_state.py's idiom.tools/notes_bench.py-- 10 canonical questions with answer regexes, so "did a factsurvive this edit" is a command rather than an opinion.
Root
CLAUDE.md-- there wasn't one.tools/check_dead_references.pyextended to scanconfig/**/*.json,*.jsonlandtools/**/*.js. ~75% of citations ofnotes/paths live outside.mdand werepreviously ungated. 24 genuine dead references found and fixed; 0 banked into
the baseline, which shrinks by 12 lines.
This branch originally also scanned
src//src_tu//include/. main landed abetter implementation of that while this PR was open (
7d83b57a2), with a reallexer and -- the part that matters -- a separate grouped ratchet so the prose
--updatecannot bank hundreds of source-comment entries. This branch now defers toit entirely and drops its own
NOTES_ONLY_HEADSrestriction, which had kept the gatequiet at the cost of leaving the tree's ~950 dead
src/refs in code commentspermanently ungated. See
ece7c4303-- two tests were deleted with it, because theyasserted the restriction that is now gone.
Two harness defects this surfaced
Both were found by composing the branch against 23 commits of main rather than testing it
alone, and both were gates crying wolf on work that changed no fact -- the failure mode
that gets a gate switched off:
notes_benchfailed on prose re-wrapping. main moved the D0/D1/D2 emission-orderfact in
writer.mdfrom L296 to L523, where it still reads verbatim; the reflow justsplit the literal phrase across a newline. Now matches whitespace-flattened text.
Negative-controlled:
always->sometimesstill fails the row.notes_index --checkdemanded a hand-written registry row per file, so everypromotion PR dropping a class-facts JSON turned it red -- main added five in 23 commits.
Those self-register now from their own
class/overlay/rtti_namefields. Curation isstill enforced elsewhere: a stray
.mdstill fails--check.Why the file count barely moved
It is at the floor the policy allows. After the first pass zero files have zero inbound
citers, so the retire rule is exhausted; further reduction needs MERGE of cited files.
The four strongest candidates were examined and all four declined, each verified
independently:
collision-query-classes.md<->handoff-sphereclsn-detectclsn.md:collision-system.mdpins 9 exact line-number citations into them. Line numbers are invisible to the
dead-reference gate, so any content move breaks them silently.
ghidra-dsd-landscape.md<->overlay-residency.md: the latter is bench-anchored and§9-cited by number.arm9-endgame.md: 28src/files cite it as the asm-primitive policy anchor.Going further means accepting silent breakage. That is a decision for a reviewer, not one
to make quietly inside a cleanup PR.
Deliberately untouched
writer.md,builder.md,matching-style.md,cpp-tu-current-state.mdare byte-identicalto main -- verified after rebase.
matching-style.mdgot a surgical dead-reference fix onmain that a rewrite would have reverted;
writer.mdis dense operational rules and grew 51%on main during this work;
builder.mdwas being edited by another session;cpp-tu-current-state.mdis tool-owned output.
mwccarm-codegen.mdis the one exception: 8 insertions, 0 deletions, at line 4768 of 4778.153 files cite it by section and two by line number, so the pointer to
codegen-rules.mdgoes at the end where it cannot shift anything. Both cited anchors verified intact.
Notes for review
during this work and none of it is from this branch:
writer.mdalone went 43,936 ->75,202 bytes on main during this PR's life. Reported, not gated -- no check fails on it.
tools/actor_names.pytruncatessymbols/actor_renames.tsv(386KB) to a header row onre-run. Found, flagged, not fixed -- out of scope for a notes PR.
cpp_tu_state.py --write-noteintoupdate-chaos-data.yml's refreshstep. The note it maintains is stale on main.
Verification
Green on the rebased branch against
origin/main:🤖 Generated with Claude Code
https://claude.ai/code/session_013qeuLvkrjFxZriAZ93kFnF