diff --git a/docs/repo-lint-nextfree-bug-FINDING.md b/docs/repo-lint-nextfree-bug-FINDING.md new file mode 100644 index 0000000..e1c52e0 --- /dev/null +++ b/docs/repo-lint-nextfree-bug-FINDING.md @@ -0,0 +1,65 @@ +# FINDING: repo_lint.py L5 "next-free identifiers" is unreliable -- do not use it for numbering + +**Status:** DOCFIX candidate. Read-only analysis by the main-chat stream at repo HEAD +`59a7c73` (2026-07-06). No code changed. Code assigns the DOCFIX number when actioning +(re-run `ledger-scan` for next-free first; main-chat consumed none of these). + +**One-line:** `scripts/repo_lint.py` prints an `[info] L5 next-free identifiers` line that +is wrong on all three counters and disagrees with `ledger-scan`. Numbers must be taken +from `ledger-scan`, never from repo_lint. This is a live collision risk while the jumphost +stream is consuming DOCFIX numbers rapidly. + +## Evidence + +At this HEAD the two tools disagree: + +| counter | repo_lint L5 says | ledger-scan says (authoritative) | ground truth | +|---|---|---|---| +| D | next-free `D-075` | next-free `D-074` | highest header `D-073`; `D-074` exists only as a PROPOSAL mention | +| DOCFIX | next-free `DOCFIX-100` | next-free `DOCFIX-106` | highest consumed `DOCFIX-105`; `DOCFIX-106` is a next-free pointer | +| BUNDLEFIX | next-free `BUNDLEFIX-051` | next-free `BUNDLEFIX-012` | highest consumed `BUNDLEFIX-011`; `051` traces to a test fixture (below) | + +`ledger-scan` is correct on all three. Each repo_lint number is wrong via a DIFFERENT +defect, which is why this is worth fixing rather than tweaking. + +## Root cause -- three independent defects in the L5 next-free block + +The block (`repo_lint.py`, L5 identifier numbering) computes next-free as `max(seen)+1` +over `re.finditer(r"\b(D|DOCFIX|BUNDLEFIX)-(0\d{2})\b", ...)` across `all_text()`: + +1. **Band-limited regex `0\d{2}`.** It only matches identifiers `000`-`099`. It is blind to + `DOCFIX-100..106` (the current live range), so it reports `DOCFIX-100` when true next-free + is `106`. This is the same defect `ledger-scan` already carried and Code already fixed + (its regex is now `[0-9]{3}`); repo_lint still has the old band-limited form. +2. **Mention-counting, no header-authority, no pointer-exclusion.** It counts every + identifier OCCURRENCE, not definition-headers, and does not skip "Next-free:" pointer + lines. So a PROPOSAL reference inflates it -- the main-chat CIDR plan mentions `D-074`, + which is exactly why repo_lint reports `D-075`. `ledger-scan` uses design-decisions + headers for D and excludes `next-free` pointer lines. +3. **Scans the test tree.** `all_text()` includes `tests/`. `tests/ledger-scan/run-tests.sh` + contains a deliberately-high fixture line + (`Next-free: D-071, DOCFIX-099, BUNDLEFIX-050 ... must NOT inflate`) written to PROVE + `ledger-scan` ignores pointer lines. repo_lint ingests that fixture's `BUNDLEFIX-050` + and reports `051` -- the precise trap the fixture was authored to catch. + +Correction of the record: an earlier main-chat note called `BUNDLEFIX-051` "spurious." It is +not -- it is `max+1` of the fixture token in defect 3. The imprecision is corrected here. + +## Fix options (Code's lane; not implemented here) + +**Recommended -- remove the next-free print from repo_lint entirely.** repo_lint's actual L5 +job is the duplicate-definition-heading collision guard; keep that. Next-free is +`ledger-scan`'s job, and having two tools derive it by different regexes is how they drifted +apart. Single source of truth (the same principle already floated for the machine-derived +ledger cache). Lowest risk, removes the drift surface. + +**Fallback -- if repo_lint must keep printing next-free, make it match `ledger-scan`:** +widen the regex to `[0-9]{3,}`; use design-decisions headers for D; exclude `next-free` +pointer lines; and exclude `tests/` from `all_text()` for identifier scanning. More code, +same answer -- prefer the removal. + +## Interim guidance (both streams, until fixed) + +Take D / DOCFIX / BUNDLEFIX next-free from `bash scripts/ledger-scan.sh` ONLY. Treat +repo_lint's `[info] L5 next-free` line as advisory-and-currently-wrong; it does not gate +(it is `[info]`, 0-fail), so it is safe to ignore for numbering.