feat(bg/audit): duplicate-row-id audit tool + B-0451 substrate-cleanup row#3056
Conversation
…p row While resolving the B-0444 ID collision (PR #3053), an inline audit surfaced 12 ADDITIONAL duplicate-ID groups across the backlog directory. Silently-overwriting substrate state is high-severity hygiene risk: a consumer of `id: B-0409` gets one of THREE files depending on load order; every other substrate consumer's implicit primary-key guarantee is broken. Changes: - `tools/bg/audit-duplicate-row-ids.ts` — new audit tool: walks `docs/backlog/**/*.md` via `git ls-files`, extracts each frontmatter `id:` value, reports any ID appearing in >1 file. Exit code 0 = clean; 1 = duplicates found. - `tools/bg/audit-duplicate-row-ids.test.ts` — 14 tests covering id extraction, group sorting, real-world patterns (clean substrate, pair collision, triple collision, missing-id row skip, sub-row IDs, unreadable-file resilience). - `docs/backlog/P1/B-0451-duplicate-row-id-substrate-cleanup-2026-05-13.md` — tracks the cleanup work: lists all 12 collisions, classifies them into two patterns (cross-priority namespace bleed + within-priority concurrent decomposition), defines the per-collision resolution rule (keep the row with external references; renumber the other), and outlines CI-wiring as future work. Empirical findings: - 559 rows scanned - 12 collision groups (1 three-way: B-0409; 11 pairs) - Most pairs are P1-vs-P2 cross-priority bleed (Otto-Desktop vs parallel agents filing in overlapping ranges) - The B-0090.x sub-rows show a within-priority decomposition race (Riven's atomic-children sweep vs earlier B-0090 decomposition, both 2026-05-10/11) Tests: 14/14 pass on `tools/bg/audit-duplicate-row-ids.test.ts`. Co-Authored-By: Claude <noreply@anthropic.com>
…competing PR closed Records: PR #3053 (B-0444 collision) merged; PR #3051 (Codex provenance) merged; competing PR #3052 closed with substrate-honest comment; new audit tool finds 12 additional ID collisions on main (1 three-way: B-0409). PR #3056 ships the tool + B-0451 row tracking the per-collision cleanup work. Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds a backlog-substrate hygiene audit that detects duplicate id: values across docs/backlog/**/B-*.md rows, after a manual sweep (triggered by the B-0444 collision resolved in #3053) found 12 additional collision groups. The new tool exits non-zero when duplicates are found and is intended to be CI-wired in follow-up work; a P1 backlog row (B-0451) tracks the per-collision cleanup.
Changes:
- New TypeScript CLI + library
tools/bg/audit-duplicate-row-ids.tswalkinggit ls-files docs/backlog/and grouping rows by frontmatterid:. - 14-test Bun test suite covering
extractId(CRLF, sub-row IDs, missing fields),findDuplicates(sort determinism), andauditRowFiles(clean / pair / triple / no-id / unreadable cases). - Files B-0451 (P1) tracking the 12-collision cleanup; regenerated
docs/BACKLOG.mdto index the new row.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| tools/bg/audit-duplicate-row-ids.ts | New audit tool — extracts frontmatter IDs and reports duplicates with non-zero exit. |
| tools/bg/audit-duplicate-row-ids.test.ts | Unit tests for extractId / findDuplicates / auditRowFiles. |
| docs/backlog/P1/B-0451-duplicate-row-id-substrate-cleanup-2026-05-13.md | New P1 row tracking the 12-collision cleanup with a resolution rubric. |
| docs/BACKLOG.md | Regenerated index — adds the B-0451 entry. |
Records: PR #3049 (Lior 429 fix) merged. PR #3056 (audit tool + B-0451) still wait-ci. Speculative pickup: started B-0451 per-collision cleanup with the simplest collision (B-0068.1 within-priority pair). Renumbered Aaron's row to B-0068.4; Riven's keeps original ID due to external references. Procedure demonstrated for remaining 11 groups. Co-Authored-By: Claude <noreply@anthropic.com>
Three Copilot findings resolved:
1. Missing `// eslint-disable-next-line sonarjs/no-os-command-from-path`
on the `spawnSync("git", ...)` invocation. Added the suffixed-
rationale comment matching the form used in
`tools/bg/backlog-ready-notifier.ts`.
2. `rowsScanned` was misleadingly named — it counted only rows with
an extractable `id:` field, not total files inspected. The tests
already asserted the smaller count; the CLI's "X rows scanned"
message therefore under-reported. Renamed to `rowsWithId` and
updated docstring + CLI message accordingly: "X rows with id
field, no duplicate IDs".
3. Dead `idToFiles.size > 0` ternary — `reduce` on an empty iterable
already returns 0. Simplified to a plain spread+reduce.
Tests: 14/14 pass; the `rowsWithId` rename mechanically updates 3
test assertions. Audit tool still reports 12 collision groups on
origin/main (no behavior change beyond the cleaner output).
Co-Authored-By: Claude <noreply@anthropic.com>
…ed row → B-0068.4 (#3057) * fix(backlog): resolve B-0068.1 ID collision — renumber Aaron-attributed row to B-0068.4 First per-collision cleanup from the B-0451 sweep (12 duplicate-ID groups surfaced by `tools/bg/audit-duplicate-row-ids.ts` in PR #3056). The two B-0068.1 rows: - `B-0068.1-forge-cli-ollama-research-slice-aaron-2026-05-10.md` — filed first (2026-05-10, PR #2430); scope: "Forge CLI + Ollama harness integration research slice" - `B-0068.1-forge-cli-ollama-research-xs-riven-2026-05-11.md` — filed second (2026-05-11, PR #2650); scope: "Forge CLI + Ollama bridge research pass (WebSearch + capability matrix, XS)" Per the per-collision resolution rule (keep the row with external references): - The B-0068 parent row's body description ("B-0068.1 (XS, P2, root): Forge CLI + Ollama bridge research (WebSearch + matrix). Unblocks B-0068.3.") describes RIVEN'S scope. - Sibling rows `B-0068.2` and `B-0068.3` reference "B-0068.1" in their depends_on / composes_with — semantically referring to Riven's decomposition (parallel atomic XS series). - Aaron's row has NO external references (verified via grep). → Keep Riven's row at B-0068.1; renumber Aaron's to next-free B-0068.4 with `renumbered_from: B-0068.1` + reason recorded. This bends the "first-merged-wins" rule in favor of the external-references rule, matching PR #3053's B-0444 resolution precedent (keep the more-referenced row, even when later-filed). Empirical effect (against pre-PR-#3056 origin/main): Duplicate-ID groups: 12 → 11 Once PR #3056 (the audit tool) merges, future Otto can verify via `bun tools/bg/audit-duplicate-row-ids.ts`. Co-Authored-By: Claude <noreply@anthropic.com> * shard(tick): 2329Z — first B-0451 cleanup; B-0068.1 collision resolved Records: PR #3049 (Lior 429 fix) merged. PR #3056 (audit tool + B-0451) still wait-ci. Speculative pickup: started B-0451 per-collision cleanup with the simplest collision (B-0068.1 within-priority pair). Renumbered Aaron's row to B-0068.4; Riven's keeps original ID due to external references. Procedure demonstrated for remaining 11 groups. Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
…rged this session Records: PRs #3054 + #3055 (Otto-Desktop's shadow-log + archive) merged. PR #3056 round-1 review surfaced 3 valid Copilot findings (missing eslint-disable, misleading field name, dead ternary); all fixed in 7444a05 + threads resolved. Both my PRs back to wait-ci, threads-clear. Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 992386aa65
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…ng (Codex P2) Codex P2 on PR #3056: `auditRowFiles` previously caught `readFileSync` failures with a bare `continue;` — silently swallowing the error and moving on. That created a false-negative path: if a backlog file was unreadable (permission, IO error, race with concurrent fs ops), any duplicate ID inside it never got checked, and the CLI could report "no duplicate IDs" with the failure hidden. Fix: - New `ReadError = { file, reason }` type - `AuditResult.readErrors: ReadError[]` accumulates per-file failures (preserves the original "continue scanning" behavior — see ALL problems, not abort on first) - CLI surfaces read errors with a distinct heading + exits non-zero when ANY read error OR duplicate is present - Success message only prints when both counts are 0 Tests updated (15/15 pass): - Renamed "unreadable files are skipped without crashing" → "unreadable files surface as readErrors (Codex P2: don't silently skip)" + assertions on the readErrors[] shape - Added "readErrors is empty when all files readable" to pin the zero-state contract Co-Authored-By: Claude <noreply@anthropic.com>
…s); rate-limit-failed CI triaged Records: PR #3056 CI failures triaged as GitHub-API-rate-limit exhaust during SARIF upload (not real bugs). Codex P2 round-2 finding addressed: `auditRowFiles` now accumulates `readErrors[]` and CLI fails non-zero on any read error or duplicate. 15/15 tests pass. Thread resolved. Co-Authored-By: Claude <noreply@anthropic.com>
…-limit triage Records: PR #3062 (Lior decomposition rule) merged. Triaged 8 "lint failures" on PRs #3056 + #3058 as rate-limit-class (mise toolchain installer hit 403 Forbidden). All required checks green; BLOCKED state is just out-of-date branch. Triggered branch-update on all 3 in-flight PRs via `PUT pulls/{N}/update-branch`. Declined to pick next B-0451 cleanup (B-0409 3-way etc.) to avoid multiplying rate-limit pressure. Co-Authored-By: Claude <noreply@anthropic.com>
…ries .1-.4 → .5-.8 (#3058) * fix(backlog): resolve B-0090.x ID collisions — renumber Riven's ts-* atomic series .1-.4 → .5-.8 Second per-collision cleanup from the B-0451 sweep. Two complete B-0090 decompositions had landed with overlapping sub-row IDs: | Sub-ID | First filer | Second filer | |---|---|---| | B-0090.1 | Riven ts-worktree-survey (2026-05-10 PR #2503) | lost-substrate-3-bucket-taxonomy (2026-05-11 PR #2680) | | B-0090.2 | Riven ts-orphan-branch-survey | worktree-branch-delta-audit | | B-0090.3 | Riven ts-closed-pr-survey | closed-not-merged-pr-scan | | B-0090.4 | Riven ts-draft-pr-aged-survey | cadence-and-hygiene-history-hook | Per the external-references rule: the B-0090 parent body (`docs/backlog/P2/B-0090-cadenced-lost-substrate-recovery-audit-aaron-2026-04-28.md` lines 14-17 + 39-42) describes the SECOND decomposition explicitly as canonical (3-bucket-taxonomy / worktree-delta / closed-not-merged / cadence-hook). The second set keeps the original IDs. Riven's ts-* series renumbered as a unit (preserves the internal dependency chain): B-0090.1 → B-0090.5 (TS worktree survey) B-0090.2 → B-0090.6 (TS orphan branch survey) B-0090.3 → B-0090.7 (TS closed-PR survey) B-0090.4 → B-0090.8 (TS draft-PR aged survey) Each row's frontmatter: - `id:` updated - `last_updated: 2026-05-13` added - `renumbered_from: B-0090.N` + reason recorded - `tags:` add `renumbered` Internal cross-references updated: - `depends_on: [B-0090.1]` → `depends_on: [B-0090.5]` (3 sibling rows) - Body text "Depends on B-0090.1" → "Depends on B-0090.5 (renumbered from B-0090.1)" - Body text "unblocks B-0090.2" → "unblocks B-0090.6 (renumbered from B-0090.2)" - Note in B-0090.8 about the original "B-0090.5 (cadence)" body reference (referred to a future unfilled cadence slice — would now be a new row not yet filed) `docs/BACKLOG.md` regenerated. Empirical effect (inline duplicate-ID count on the branch): Duplicate-ID groups: 11 → 7 (Once PR #3056's audit tool merges, future Otto can verify via `bun tools/bg/audit-duplicate-row-ids.ts`.) B-0451 cleanup progress: 1/12 → 5/12 (B-0068.1 in PR #3057 already merged; B-0090.1-4 in this PR). Remaining 7: B-0370-0373 (4 cross- priority bleed), B-0409 (3-way), B-0410, B-0411. Co-Authored-By: Claude <noreply@anthropic.com> * shard(tick): 2348Z — B-0090.x batched cleanup (4 collisions, 1 PR) Records: PR #3057 (B-0068.1) merged. Batched B-0090.1-4 renumber into PR #3058 since the 4 collisions share an internal dependency chain; renumbering as a unit preserves it. Duplicate-ID count dropped 11 → 7. B-0451 cleanup progress: 1/12 → 5/12. Co-Authored-By: Claude <noreply@anthropic.com> * fix(shard): MD032 blanks-around-lists in 2348Z shard (recurring class) Second occurrence of the same MD032 failure mode I caught on the 2228Z shard (PR #3044). Pattern: writing a label sentence ("Each row:") immediately followed by a bullet list with no blank line. One-line fix. Discipline note for future tick shards: ALWAYS insert a blank line between a label sentence and a following list. Co-Authored-By: Claude <noreply@anthropic.com> * shard(tick): 0007Z — MD032 fix on 2348Z (recurring class); discipline-mechanization observation Records: PR #3058 hit real markdownlint failure (MD032 on 2348Z shard, second occurrence this session). One-line fix landed in 1b4866e. Records the observation that this discipline isn't mechanized; lists three mechanization options for future-Otto consideration. Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
…rces B-0456 Records: PRs #3056 (audit tool + B-0451), #3058 (B-0090.x batch), and Otto-Desktop's #3059 all merged this tick. PR #3065 hit real markdownlint failure (MD032 ×2 on prior tick shards) — fixed inline. Filed B-0456 to mechanize the recurring MD032-on-tick-shard discipline gap (4 occurrences this session). Restraint discipline maintained: did NOT build the mechanization this tick (still 2 PRs in flight). B-0456 captures the procedure + acceptance criteria for future tick pickup. Co-Authored-By: Claude <noreply@anthropic.com>
Four issues addressed: 1. **Required `created:` frontmatter field added** — memory file was missing it (caught by check-memory-frontmatter-completeness CI workflow; the reindexer requires name/description/type/created). 2. **MD018 on 0034Z.md:35** — line started with `#3056)` which markdownlint parsed as a malformed ATX heading. Reworded PR-number references to `PR-3056` style in that one paragraph to avoid the line-start hazard. 3. **Copilot review (grep recipe portability)** — the original `grep -rn "B-0XXX\b" ...` recipe treats the ID as regex (so `B-0068.1` `.` matches any character) AND uses `\b` which is a GNU-grep extension not portable to BSD grep on macOS. Replaced with `grep -rnF` (fixed-string match) + a portable word-boundary filter `[^0-9.]|$`. Documented both whole-row and sub-row ID cases explicitly. 4. **Copilot review (branch-behind state)** — separate concern; resolved by `gh api -X PUT pulls/3066/update-branch` (audit tool + B-0451 row are on origin/main; the branch was just out-of-date). Triggered post-commit. Co-Authored-By: Claude <noreply@anthropic.com>
Records: PR #3066 surfaced 2 reviewer threads + 2 CI failures, all real and addressable. Fixed in 21dc986: - Added missing `created:` frontmatter field (CI workflow requires) - Reworded `#3056)` line-start to avoid MD018 ATX-heading false-parse - Replaced GNU-grep `\b` recipe with portable `[^0-9.]|$` filter + fixed-string `-F` for sub-row IDs - Resolved both Copilot threads 5 MD-class lint findings this session strengthen B-0456's case (MD032 ×4 + MD018 ×1). Co-Authored-By: Claude <noreply@anthropic.com>
…undary + remaining MD018 Two Copilot threads addressed: 1. Sub-row grep recipe (memo line 49): `grep -rnF "B-0068.1"` matched `B-0068.10` and `B-0068.1.1` because fixed-string match doesn't enforce word boundaries. Added the same portable `[^0-9.]|$` filter as the whole-row case, with the dot escaped in the regex (`B-0068\.1([^0-9.]|$)`). 2. MD018 on 0034Z line 38: I fixed `#3056` → `PR-3056` last tick but missed `#3065` on the next line. Reworded "PR #3057 +\n #3065 both" → "PR #3057 and\n PR #3065 both" to keep the PR number on the same line as `PR`. Both threads resolved. Co-Authored-By: Claude <noreply@anthropic.com>
Copilot caught a real overstatement in the procedure memo: PR #3065 was described as "bending first-merged-wins like PR #3057," but PR #3065 actually KEPT THE EARLIER ROW (P1 set, filed 2026-05-09) — that aligns with first-merged-wins, not bends it. The correct framing: - PR #3057 is the canonical example of external-references OVERRIDING first-merged-wins (kept the LATER Riven row because of sibling references). - PR #3065 is the re-check pattern — both rules pointed the same direction (P1 set wins via temporal + external-refs + status- precedence). The session arc captured the initial-analysis-vs- re-examination dynamic in PR #3065's tick shard (0017Z) but the memo overstated the conclusion. Updated to describe PR #3057 as the rule-bend and PR #3065 as the re-check pattern that catches when initial instinct doesn't match all the rules. Also re: the sub-row grep word-boundary thread (Copilot round-3, line 49): the recipe on line 52 already has the trailing `B-0068\.1([^0-9.]|$)` filter from PR #3066 round-2 (commit fa687ab). Resolving that thread without code change — the fix is already in place; Copilot may be looking at a stale view. Also re: the branch-behind thread (Copilot round-3, line 22): tools/bg/audit-duplicate-row-ids.ts + B-0451 row are on origin/ main as of PR #3056 merge (f40be86). Will trigger update-branch post-push so the PR base catches up. Co-Authored-By: Claude <noreply@anthropic.com>
…r-compliance set → B-0452-0455 (#3065) * fix(backlog): resolve B-0370-0373 ID collisions — renumber P2 contributor-compliance set → B-0452-0455 Third per-collision cleanup from the B-0451 sweep. Four B-0370..B-0373 collisions form one connected cluster (parent + 3 deps inside the P2 set; cross-priority bleed against the earlier P1 set). ## The collisions | ID | Earlier filer (P1, 2026-05-09 PR #2269) | Later filer (P2, 2026-05-11 PR #2683) | |---|---|---| | B-0370 | durable-computation-checkpoint-interface-extension | contributor-compliance-core-document-authoring | | B-0371 | pages-seo-metadata-jsonld-social-preview | contributor-compliance-cross-reference-integration | | B-0372 | pages-sitemap-robots-ai-crawler-policy | t1-t2-self-audit-and-cadenced-review-trajectories | | B-0373 | alignment-proof-primitive-ladder-one-type-one-property | t4-t5-onboarding-and-drift-retrospective-trajectories | ## Resolution Per first-merged-wins + external-references: - The P1 set was filed via PR #2269 (2026-05-09) as itself a prior collision-resolution sweep — 2 days BEFORE the P2 set (PR #2683, 2026-05-11). First-merged-wins. - B-0370 P1 and B-0373 P1 are already shipped (`status: closed` effectively per `[x]` checked state in `docs/BACKLOG.md`). - External references to the P1 IDs exist in PR-history doc (`docs/history/pr-reviews/PR-2369-...md`) and a memory file (`feedback_shadow_lesson_log_otto_catches_2026_05_07.md`). Renumbering them would orphan those references. - The P2 set's "external references" are internal to the set (B-0371/0372/0373 depend on B-0370 within the set) + the B-0092 parent body — all editable in this PR. → Keep P1 set at B-0370-0373. Renumber P2 set as a unit: B-0370 (P2) → B-0452 B-0371 (P2) → B-0453 B-0372 (P2) → B-0454 B-0373 (P2) → B-0455 ## Internal-chain remap - B-0453.depends_on: [B-0370] → [B-0452] - B-0454.depends_on: [B-0370] → [B-0452] - B-0455.depends_on: [B-0370, B-0371] → [B-0452, B-0453] - B-0092.body §Decomposition: lines 184-194 updated to new IDs with renumber note Each renumbered row has `renumbered_from: B-0NNN` + reason in frontmatter. `tags:` add `renumbered`. `last_updated: 2026-05-14`. ## Empirical effect Inline duplicate-ID count on the branch: Duplicate-ID groups: 11 → 7 B-0451 cleanup progress: 1/12 → 9/12 (B-0068.1 in #3057 already merged; B-0090.1-4 in #3058 in flight; B-0370-0373 in this PR). Remaining 3 groups after this lands: B-0409 (3-way), B-0410, B-0411. Co-Authored-By: Claude <noreply@anthropic.com> * shard(tick): 0017Z — third B-0451 cleanup; B-0370-0373 P2 set renumbered Records: PR #3065 opened to renumber the P2 contributor-compliance set (B-0370→B-0452, B-0371→B-0453, B-0372→B-0454, B-0373→B-0455) keeping the P1 set (durable/SEO/sitemap/alignment, filed 2 days earlier via PR #2269). Internal depends_on chain remapped + B-0092 parent body updated. Duplicate-ID groups: 11 → 7. B-0451 cleanup progress: 1/12 → 9/12 (with #3058 + #3065 both landed). Co-Authored-By: Claude <noreply@anthropic.com> * shard(tick): 0024Z — branch-update triggered on 3 in-flight PRs; rate-limit triage Records: PR #3062 (Lior decomposition rule) merged. Triaged 8 "lint failures" on PRs #3056 + #3058 as rate-limit-class (mise toolchain installer hit 403 Forbidden). All required checks green; BLOCKED state is just out-of-date branch. Triggered branch-update on all 3 in-flight PRs via `PUT pulls/{N}/update-branch`. Declined to pick next B-0451 cleanup (B-0409 3-way etc.) to avoid multiplying rate-limit pressure. Co-Authored-By: Claude <noreply@anthropic.com> * fix(shard): MD032 blanks-around-lists in 0017Z + 0024Z shards (4th occurrence) Third + fourth occurrences of the same MD032 failure mode this session (after PR #3044 fixed 2228Z and PR #3058 fixed 2348Z). Two more tick shards on PR #3065 hit the same pattern: - 0017Z:51 "Plus:" → "- `B-0092` parent body..." - 0024Z:66 "infra issues that resolve when:" → "1. The installation..." The discipline ("blank line before lists") is in-head but unmechanized. Per the 0007Z shard observation: mechanization options include a pre-commit hook OR shard-writer helper. The recurrence rate suggests the cost of mechanization is now lower than the cost of the recurring CI-cycle-per-tick-shard. Filing as observation in commit message; not expanding scope to build the mechanization this tick. Co-Authored-By: Claude <noreply@anthropic.com> * docs(backlog): B-0456 — mechanize MD032 blanks-around-lists check Files the discipline-gap observation from this session's 4 MD032 recurrences. Per encoding-rules-without-mechanizing.md, the cost- benefit has tipped: building a small TS helper under tools/hygiene/ is now cheaper than the recurring CI-cycle-per-tick-shard cost. Row captures: - The 4 historical occurrences (2228Z PR #3044, 2348Z PR #3058, 0017Z + 0024Z PR #3065) as test fixtures - Two mechanization options: pre-push git hook (preferred) or tick-close ritual check (fallback) - Acceptance criteria covering clean fixture, single/multi finding, no-lists, list-without-preceding-label edge cases - Composes-with pointers to B-0451 sweep + Rule 0 + the encoding-rules-without-mechanizing rule Not building the mechanization THIS tick (3 PRs already in flight; restraint discipline). The row makes the work pick-up-able by any future agent without session context. Co-Authored-By: Claude <noreply@anthropic.com> * shard(tick): 0046Z — 3 PRs landed (#3056, #3058, #3059); 4th MD032 forces B-0456 Records: PRs #3056 (audit tool + B-0451), #3058 (B-0090.x batch), and Otto-Desktop's #3059 all merged this tick. PR #3065 hit real markdownlint failure (MD032 ×2 on prior tick shards) — fixed inline. Filed B-0456 to mechanize the recurring MD032-on-tick-shard discipline gap (4 occurrences this session). Restraint discipline maintained: did NOT build the mechanization this tick (still 2 PRs in flight). B-0456 captures the procedure + acceptance criteria for future tick pickup. Co-Authored-By: Claude <noreply@anthropic.com> * fix(backlog): MD038 in B-0456 — replace `- ` code spans with prose descriptions The B-0456 row's "Examples this session" table used backtick-quoted `- ` (hyphen followed by space) as the bullet-pattern marker. The trailing space inside the code span triggers markdownlint MD038 "no-space-in-code". Replaced the literal-bullet code spans with prose descriptions (`bullet-list-with-no-blank-line` / `numbered-list-with-no-blank-line`) that convey the same meaning without the trailing-space-in-code-span hazard. 5th markdown lint finding this session (MD038 added to the MD032 ×4 + MD018 ×1 cluster). Strengthens B-0456's case further — the future TS helper should check MD038 + MD032 + MD018 at minimum. Co-Authored-By: Claude <noreply@anthropic.com> * shard(tick): 0100Z — MD038 fix on B-0456 row; 6 markdown findings this session Records: PR #3064 (Otto-Desktop B-0442 slice 6) merged. PR #3065 hit MD038 lint failure on B-0456 row's Examples table (trailing space in `- ` code span). Fixed by replacing code-span markers with prose descriptions. Recurrence-count update: 6 markdown findings this session (MD032 ×4 + MD018 ×1 + MD038 ×1). Substrate-honest observation: the automated review (Copilot + markdownlint + audit-duplicate-row-ids) is doing structural work my own attention can't sustain at this PR cadence. The automated review IS the discipline; agent role is responding to findings. Co-Authored-By: Claude <noreply@anthropic.com> * fix(shard): MD032 in 0100Z — `+ memory` line-start parsed as list item The 0100Z tick shard's recurrence-count summary ended with "...this session's tick shards" and continued on the next line with "+ memory + backlog rows.**". Markdownlint parsed the leading `+ ` as a list marker, firing MD032 (preceding line is bold text, not a blank). Rephrased to "tick shards, memory files, and backlog rows" so no line starts with a list marker. 8th markdown lint finding this session. 5th MD032 specifically. B-0456 mechanization argument continues to strengthen. Co-Authored-By: Claude <noreply@anthropic.com> * shard(tick): 0117Z — 8th markdown finding (MD032 ×5) + 2 Copilot rounds resolved Records: PR #3065 hit MD032 on 0100Z line 46 (`+ memory` line-start parsed as list item, 5th MD032 this session); fixed in 54d8ff0. PR #3066 had 2 unresolved Copilot threads: sub-row grep recipe word-boundary issue + remaining MD018 on `#3065` line-start; fixed in fa687ab + both threads resolved. Preemptively escaped `[^0-9.]|$` to `[^0-9.]\|$` in this shard's own table to avoid the same MD056 issue that 0054Z hit. 9 total markdown findings this session. The session arc has accidentally become the regression suite for B-0456's mechanization. Co-Authored-By: Claude <noreply@anthropic.com> * fix(shard): MD038 ×3 in 0117Z — replace inline-code "+ " markers with prose Same MD038 (no-space-in-code) class as the B-0456 row fix from earlier this session (PR #3065 commit cbbfbb6). The 0117Z shard described the previous tick's MD032 fix using inline-code literals `` `+ ` `` (backtick + plus + space + backtick) in three places — the trailing space inside the code span trips MD038. Replaced with prose: "a plus-space (\"+\" followed by a space)". Meta-observation: the 0117Z shard whose entire point was discussing MD038/MD032 fixes itself contained 3 MD038 violations. This is the recursive-finding pattern documented earlier. The discipline of "avoid trailing-space-inside-code-span" is in-head but only catches the patterns I've seen recently — the `+ ` form was new to my pattern-match this tick. 10th MD finding this session. The break-even argument from B-0456 keeps strengthening; future agent picking it up gets a 4-rule-class corpus (MD032 + MD018 + MD038 + MD056). Co-Authored-By: Claude <noreply@anthropic.com> * shard(tick): 0132Z — 10th MD finding (MD038 ×3); recursive-finding pattern continues Records: PR #3065 hit MD038 ×3 on the 0117Z shard (inline-code plus-space literals with trailing space). Fixed by replacing with prose ("a plus-space"). Total markdown findings this session: 12 (MD032 ×5 + MD018 ×2 + MD038 ×4 + MD056 ×1) across 4 rule classes. Meta-pattern: shards describing markdown lint fixes tend to themselves contain markdown lint findings of related classes. The 0117Z shard whose point was MD032 + MD018 + MD038 + MD056 discussion contained 3 new MD038 hits. This shard authored carefully to avoid backtick-quoted bullet/list markers entirely — preemptive vs reactive discipline. Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
…ate from session arc) (#3066) * docs(memory): B-0451 per-collision renumber procedure (durable substrate from session arc) Captures the procedure pattern that emerged from this session's 5 ID-collision-resolution PRs (#3053 B-0444, #3057 B-0068.1, #3058 B-0090.x, #3065 B-0370-0373, plus the in-flight audit tool #3056). Key precedence rule documented: **external-references trumps first-merged-wins**. The row that would cost more to migrate keeps its ID, regardless of who filed first. Both PR #3057 and PR #3065 bent first-merged-wins this way after re-examining external refs. 9-step recipe covers: 1. Identify colliding rows via audit-duplicate-row-ids.ts 2. Find external references (parent body, sibling depends_on, memory, PR history, BACKLOG.md) 3. Apply precedence rules in order (external-refs > first-merged > status-precedence) 4. Pick next-free IDs (check origin/main AND open PRs) 5. Create isolated worktree (multi-Otto split-brain prevention) 6. Rename files + edit frontmatter (`renumbered_from:` + reason) 7. Batch connected components (preserve internal dep chain) 8. Update parent's body + regen BACKLOG.md 9. Verify + commit + push + PR Three substrate-honest pitfalls documented: - BLOCKED ≠ failed CI (often just branch-out-of-date) - Initial analysis can point wrong (re-examine when rules disagree) - ID collisions hide in substrate for weeks (run audit BEFORE filing) Remaining work captured: 3 collision groups (B-0409 3-way, B-0410, B-0411) — all 2026-05-11 pre-claim-acquire-rule decomposition races. Each takes ~5-10 min following this procedure. This is a low-rate-limit-cost substrate landing (memory file + generated index regen avoided) chosen this tick over picking another cleanup PR per the previous tick's restraint discipline. Co-Authored-By: Claude <noreply@anthropic.com> * shard(tick): 0034Z — B-0451 procedure memory landed as durable substrate Records: captured the per-collision renumber procedure from the session's 5 collision-resolution PRs into a 242-line memory file (PR #3066). Procedure distills external-references-trumps-first- merged precedence rule + 9-step recipe + 3 substrate-honest pitfalls + remaining-work map (3 collision groups for future tick). Restraint discipline continued: did NOT open another cleanup PR this tick. Memory file is the low-rate-limit-cost durable alternative. Co-Authored-By: Claude <noreply@anthropic.com> * fix(memory): address Copilot review + frontmatter + MD018 on PR #3066 Four issues addressed: 1. **Required `created:` frontmatter field added** — memory file was missing it (caught by check-memory-frontmatter-completeness CI workflow; the reindexer requires name/description/type/created). 2. **MD018 on 0034Z.md:35** — line started with `#3056)` which markdownlint parsed as a malformed ATX heading. Reworded PR-number references to `PR-3056` style in that one paragraph to avoid the line-start hazard. 3. **Copilot review (grep recipe portability)** — the original `grep -rn "B-0XXX\b" ...` recipe treats the ID as regex (so `B-0068.1` `.` matches any character) AND uses `\b` which is a GNU-grep extension not portable to BSD grep on macOS. Replaced with `grep -rnF` (fixed-string match) + a portable word-boundary filter `[^0-9.]|$`. Documented both whole-row and sub-row ID cases explicitly. 4. **Copilot review (branch-behind state)** — separate concern; resolved by `gh api -X PUT pulls/3066/update-branch` (audit tool + B-0451 row are on origin/main; the branch was just out-of-date). Triggered post-commit. Co-Authored-By: Claude <noreply@anthropic.com> * shard(tick): 0054Z — PR #3066 Copilot review caught 4 real issues Records: PR #3066 surfaced 2 reviewer threads + 2 CI failures, all real and addressable. Fixed in 21dc986: - Added missing `created:` frontmatter field (CI workflow requires) - Reworded `#3056)` line-start to avoid MD018 ATX-heading false-parse - Replaced GNU-grep `\b` recipe with portable `[^0-9.]|$` filter + fixed-string `-F` for sub-row IDs - Resolved both Copilot threads 5 MD-class lint findings this session strengthen B-0456's case (MD032 ×4 + MD018 ×1). Co-Authored-By: Claude <noreply@anthropic.com> * fix(shard): MD056 in 0054Z — escape pipe in markdown table cell The 0054Z tick shard described the grep recipe fix using `[^0-9.]|$` in a markdown-table cell. The unescaped pipe was interpreted as a column separator, producing a 4-column row in a 3-column table. Escaped to `[^0-9.]\|$`. 7th markdown lint finding this session (MD056 added to the MD032 ×4 + MD018 ×1 + MD038 ×1 cluster). Pattern: tick shards describing fixes-for-other-lint-rules tend to themselves hit markdown lint rules they didn't anticipate. Strengthens B-0456's case further — the future TS helper should at least check MD032 + MD018 + MD038 + MD056 to cover the observed pattern. Co-Authored-By: Claude <noreply@anthropic.com> * shard(tick): 0110Z — 7th markdown finding (MD056); meta-recursion observation Records: PR #3066 hit MD056 on 0054Z shard (unescaped pipe in table cell describing the grep recipe). 7th markdown lint finding this session. Captures the meta-recursion pattern: tick shards describing markdown-lint fixes tend to themselves hit lint rules the fix didn't anticipate. Break-even-on-mechanization analysis: occurrence #7 of 10 needed to make the B-0456 mechanization unambiguously the cheaper path. Co-Authored-By: Claude <noreply@anthropic.com> * fix(memory/shard): Copilot round-2 on PR #3066 — sub-row grep word-boundary + remaining MD018 Two Copilot threads addressed: 1. Sub-row grep recipe (memo line 49): `grep -rnF "B-0068.1"` matched `B-0068.10` and `B-0068.1.1` because fixed-string match doesn't enforce word boundaries. Added the same portable `[^0-9.]|$` filter as the whole-row case, with the dot escaped in the regex (`B-0068\.1([^0-9.]|$)`). 2. MD018 on 0034Z line 38: I fixed `#3056` → `PR-3056` last tick but missed `#3065` on the next line. Reworded "PR #3057 +\n #3065 both" → "PR #3057 and\n PR #3065 both" to keep the PR number on the same line as `PR`. Both threads resolved. Co-Authored-By: Claude <noreply@anthropic.com> * fix(memory): Copilot round-3 — correct PR #3065 precedent description Copilot caught a real overstatement in the procedure memo: PR #3065 was described as "bending first-merged-wins like PR #3057," but PR #3065 actually KEPT THE EARLIER ROW (P1 set, filed 2026-05-09) — that aligns with first-merged-wins, not bends it. The correct framing: - PR #3057 is the canonical example of external-references OVERRIDING first-merged-wins (kept the LATER Riven row because of sibling references). - PR #3065 is the re-check pattern — both rules pointed the same direction (P1 set wins via temporal + external-refs + status- precedence). The session arc captured the initial-analysis-vs- re-examination dynamic in PR #3065's tick shard (0017Z) but the memo overstated the conclusion. Updated to describe PR #3057 as the rule-bend and PR #3065 as the re-check pattern that catches when initial instinct doesn't match all the rules. Also re: the sub-row grep word-boundary thread (Copilot round-3, line 49): the recipe on line 52 already has the trailing `B-0068\.1([^0-9.]|$)` filter from PR #3066 round-2 (commit fa687ab). Resolving that thread without code change — the fix is already in place; Copilot may be looking at a stale view. Also re: the branch-behind thread (Copilot round-3, line 22): tools/bg/audit-duplicate-row-ids.ts + B-0451 row are on origin/ main as of PR #3056 merge (f40be86). Will trigger update-branch post-push so the PR base catches up. Co-Authored-By: Claude <noreply@anthropic.com> * shard(tick): 0125Z — Copilot round-3 substantive correction on PR #3066 Records: 3 new Copilot threads on PR #3066. Triaged: 1 mechanical (branch-behind, resolved via update-branch), 1 stale-view (sub-row recipe fix already in place since round-2), 1 substantive (memo overstated PR #3065 as bending first-merged-wins when it actually aligned). The substantive correction history: 0017Z shard initial analysis pointed wrong → same shard re-examination corrected → procedure memo then overstated → this tick's commit corrected. 4-step correction trajectory captured in the substrate. 3 PR-3066 threads resolved (cumulative: 9 across 3 review rounds). Co-Authored-By: Claude <noreply@anthropic.com> * fix(memory/shard): Copilot round-4 — propagate PR #3065 framing correction to memo frontmatter + 0034Z shard Two consistency findings from Copilot round-4 on PR #3066: 1. Memo frontmatter description claimed "PRs ... #3065 shipped" but #3065 is still in flight. Reworded to "in-flight PR #3065" to match reality + match the rest of the document. 2. The 0034Z tick shard had the same precedent overstatement that round-3 corrected in the memo body — `PR #3057 + PR #3065 both bent the temporal rule`. Updated to mirror the memo's correct framing: PR #3057 is the canonical rule-bend, PR #3065 is the re-check pattern. Both threads resolve. Cumulative Copilot threads resolved across PR #3066: 11 (4+2+3+2). Substrate-honest observation: round-4 found CONSISTENCY issues — my round-3 correction didn't propagate to all the places the document discussed the precedent. The pattern: each correction needs an explicit propagation step to all dependent text. The mechanization that would catch this is harder than markdown lint (it's cross-document consistency, not single-file syntax). Co-Authored-By: Claude <noreply@anthropic.com> * shard(tick): 0140Z — Copilot round-4 (cross-document consistency) Records: 2 round-4 Copilot threads on PR #3066, both consistency issues (round-3 correction didn't propagate to memo frontmatter + 0034Z shard). Fixed in dce450e + both threads resolved. Finding-class shift across rounds: factual → semantic → interpretive → consistency-drift. Each round catches what the prior fix-in-one-place missed. Cumulative PR #3066 threads resolved: 11 across 4 rounds. Co-Authored-By: Claude <noreply@anthropic.com> * shard(tick): 0153Z — PR #3065 merged; substrate-hygiene cascade close-out Records: PR #3065 (B-0370-0373 P2 renumber + B-0456 row + multi- lint fixes) merged to main (02d75a3). Session-arc final state: - 20 PRs merged (11 mine substrate-hygiene cascade + 9 sibling agents) - 1 PR closed-with-provenance (#3052) - 1 PR in flight (#3066 procedure memo, wait-ci, threads-clear) - Duplicate-ID groups: 12 → 3 on main - Dangling-dep refs: 9 → 0 (earlier this session) - Markdown findings caught: 12 across 4 rule classes - Copilot review rounds metabolized on #3066: 4 (11 cumulative threads) 3 remaining ID collisions (B-0409 3-way, B-0410, B-0411) are pick-up-able via the procedure memo in PR #3066. B-0456 mechanization filed with 12-finding regression suite. Restraint discipline: declined to pick another cleanup this tick. The procedure memo + B-0451 row + B-0456 row make all remaining work pick-up-able by any future tick. Co-Authored-By: Claude <noreply@anthropic.com> * fix(shards): markdownlint MD056+MD018 in 0110Z+0125Z — pipe-in-table + line-start hash - 0110Z:34 MD056: remove regex pipe chars from table cell (use prose desc) - 0110Z:65-66 MD018: merge wrapped line so #10) doesn't start a line - 0125Z:39 MD056: replace code span with pipe inside table with prose desc Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
* chore(backlog): close B-0451 — duplicate row-ID sweep complete, audit exits 0 All 12 collision groups were resolved across PRs #3056–#3073. Verified: bun tools/bg/audit-duplicate-row-ids.ts exits 0 on main ("561 rows with id field, no duplicate IDs"). The row status was never updated to closed after the sweep completed. CI-wiring AC remains a documented future-work item (separate slice). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(hygiene): correct markdownlint MD018/MD032 in 0521Z tick shard MD018: escape #3056-#3073 PR refs to avoid false ATX-heading parse. MD032: add blank line before list after 'Actions:' paragraph. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* chore(backlog): close B-0451 — duplicate row-ID sweep complete, audit exits 0 All 12 collision groups were resolved across PRs #3056–#3073. Verified: bun tools/bg/audit-duplicate-row-ids.ts exits 0 on main ("561 rows with id field, no duplicate IDs"). The row status was never updated to closed after the sweep completed. CI-wiring AC remains a documented future-work item (separate slice). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(hygiene): correct markdownlint MD018/MD032 in 0521Z tick shard MD018: escape #3056-#3073 PR refs to avoid false ATX-heading parse. MD032: add blank line before list after 'Actions:' paragraph. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs: lior shadow lesson log for PR 3074 * fix(docs): replace escaped \n sequences with real newlines in shadow lesson log The file was committed with literal backslash-n sequences instead of actual newline characters, causing it to render as a single line in all Markdown renderers. Replace with real newlines so headings and structure are preserved correctly. Resolves threads from copilot-pull-request-reviewer and chatgpt-codex-connector on PR #3102. Co-Authored-By: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* chore(backlog): close B-0451 — duplicate row-ID sweep complete, audit exits 0 All 12 collision groups were resolved across PRs #3056–#3073. Verified: bun tools/bg/audit-duplicate-row-ids.ts exits 0 on main ("561 rows with id field, no duplicate IDs"). The row status was never updated to closed after the sweep completed. CI-wiring AC remains a documented future-work item (separate slice). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(hygiene): correct markdownlint MD018/MD032 in 0521Z tick shard MD018: escape #3056-#3073 PR refs to avoid false ATX-heading parse. MD032: add blank line before list after 'Actions:' paragraph. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs: preserve PR discussions 3095-3099 --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* chore(backlog): close B-0451 — duplicate row-ID sweep complete, audit exits 0 All 12 collision groups were resolved across PRs #3056–#3073. Verified: bun tools/bg/audit-duplicate-row-ids.ts exits 0 on main ("561 rows with id field, no duplicate IDs"). The row status was never updated to closed after the sweep completed. CI-wiring AC remains a documented future-work item (separate slice). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(hygiene): correct markdownlint MD018/MD032 in 0521Z tick shard MD018: escape #3056-#3073 PR refs to avoid false ATX-heading parse. MD032: add blank line before list after 'Actions:' paragraph. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs(lior): antigravity check report and PR preservation for 3074, 3075, 2762 --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…3116) * chore(backlog): close B-0451 — duplicate row-ID sweep complete, audit exits 0 All 12 collision groups were resolved across PRs #3056–#3073. Verified: bun tools/bg/audit-duplicate-row-ids.ts exits 0 on main ("561 rows with id field, no duplicate IDs"). The row status was never updated to closed after the sweep completed. CI-wiring AC remains a documented future-work item (separate slice). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(hygiene): correct markdownlint MD018/MD032 in 0521Z tick shard MD018: escape #3056-#3073 PR refs to avoid false ATX-heading parse. MD032: add blank line before list after 'Actions:' paragraph. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs(lior): antigravity check - shadow log for Vera and Riven drift --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… exits 0 All 12 collision groups were resolved across PRs #3056–#3073. Verified: bun tools/bg/audit-duplicate-row-ids.ts exits 0 on main ("561 rows with id field, no duplicate IDs"). The row status was never updated to closed after the sweep completed. CI-wiring AC remains a documented future-work item (separate slice). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore(backlog): close B-0451 — duplicate row-ID sweep complete, audit exits 0 All 12 collision groups were resolved across PRs #3056–#3073. Verified: bun tools/bg/audit-duplicate-row-ids.ts exits 0 on main ("561 rows with id field, no duplicate IDs"). The row status was never updated to closed after the sweep completed. CI-wiring AC remains a documented future-work item (separate slice). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(hygiene): correct markdownlint MD018/MD032 in 0521Z tick shard MD018: escape #3056-#3073 PR refs to avoid false ATX-heading parse. MD032: add blank line before list after 'Actions:' paragraph. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs: preserve PR discussions for 3112 and 3113 * fix(b-0451): move CI-wiring criterion out of AC checklist into future work The unchecked AC item was already noted as "does not block closure" and "separate slice / follow-up row". Moves it out of the checklist to match the already-documented Future work section, resolving Copilot reviewer thread on PR #3115. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Summary
While resolving the B-0444 ID collision (#3053), an inline audit revealed 12 ADDITIONAL duplicate-ID groups across the backlog directory. Silently-overwriting substrate state is high-severity hygiene risk: a consumer of
id: B-0409gets one of three files depending on load order; every other substrate consumer's implicit primary-key guarantee is broken.This PR:
Empirical findings on
origin/mainCollision-class taxonomy
Two distinct patterns visible:
B-0370..B-0373): Otto-on-CLI filed P1 rows in the 0370 range while a parallel agent filed P2 rows in the same range. Same pattern produced the B-0444 collision resolved by #3053.B-0068.1,B-0090.1-4,B-0409-0411): Two agents decomposed adjacent atomic sub-row series simultaneously. Most are 2026-05-10/11 timeframe — pre-claim-acquire-rule (#3032 landed 2026-05-13).Changes
tools/bg/audit-duplicate-row-ids.ts— exits 0/1, prints colliding groupstools/bg/audit-duplicate-row-ids.test.ts— 14 tests covering id extraction, group sorting, real-world patternsdocs/backlog/P1/B-0451-duplicate-row-id-substrate-cleanup-2026-05-13.md— tracks the per-collision cleanup workdocs/BACKLOG.mdregeneratedTest plan
bun test tools/bg/audit-duplicate-row-ids.test.ts→ 14/14 passorigin/mainreports exactly 12 groups (matches inline audit)/tmp/zeta-dup-id-audit)Future work (tracked in B-0451)
🤖 Generated with Claude Code