From d310b35a0b0787b629137fc094921611f041d713 Mon Sep 17 00:00:00 2001 From: forge Date: Wed, 21 Oct 2026 07:25:41 +0000 Subject: [PATCH] forge: confirm citation_resolve_check iter_citations DRY refactor 8/8 behavior-preserving (live verify, 442-file/2401-cite census) --- .../ITER_CITATIONS_DRY_REFACTOR_LANDING.md | 125 ++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 wizard-work/forge/ITER_CITATIONS_DRY_REFACTOR_LANDING.md diff --git a/wizard-work/forge/ITER_CITATIONS_DRY_REFACTOR_LANDING.md b/wizard-work/forge/ITER_CITATIONS_DRY_REFACTOR_LANDING.md new file mode 100644 index 0000000..b9de04c --- /dev/null +++ b/wizard-work/forge/ITER_CITATIONS_DRY_REFACTOR_LANDING.md @@ -0,0 +1,125 @@ +# iter_citations DRY Refactor — Landing Verification + +Tool: `tools/citation_resolve_check.py` (forge lane: Refactor — DRY consolidation). +State: refactor written into the working tree, **not** committed (see *Scope note*). +Original (HEAD): `caf157e` / blob `efa77a2e4ee5e835e8b2ac35c036a66dd65a0b1` (488 lines, no `iter_citations`). +Refactored (working tree): 500 lines, `iter_citations()` present. + +## The smell + +The citation-walking loop — `fence_mask()` + `blocks()` + `PAT.finditer()` + the +mention/explicit/bare classification and `classify_explicit`/`classify_bare` dispatch — was +inlined **twice**: + +1. `scan()` — the production census. Tallies `fenced_citations`/`fenced_mentions`/`mentions` + stats, then appends a full row. +2. `_expect()` — the selftest fixture grader. Silently **drops** the fenced/mention stats + (only appends graded citations). + +One walk, two rule sets for "is this a mention / is this quoted." A classification change in +`scan()` would not be mirrored in `_expect()`, so the self-test would still pass on its 14 +fixtures while the census went wrong. Latent fault. + +## The refactor + +One `iter_citations(text, here, store)` generator yielding `Cite` namedtuples, consumed by +both callers. `Cite` carries the full classification +(`abs_line`, `in_fence`, `kind`, `target`, `want`, `verdict`, `have`, +`named_has`, `self_has`, `end`, `ctx`), so each consumer decides what to do with a row — +not how to walk the text: + +``` +Cite = collections.namedtuple("Cite", [...]) +def iter_citations(text, here, store): + mask = fence_mask(text) + for first, blk in blocks(text): + last = None + for m in PAT.finditer(blk): + abs_line = first + blk.count("\n", 0, m.start()) + in_fence = abs_line in mask + ... + (yields one Cite per match, in source order) +``` + +`scan()` branches per `Cite.kind`/`Cite.in_fence`; `_expect()` skips +`in_fence or kind == "mention"` and appends `(c.kind, c.verdict, c.target, c.want)`. + +Equivalence: same `fence_mask()` per file, same `blocks()` scope, same `PAT` ordering, same +`last` threading, same `abs_line`; `here` threads as `p` in `scan()` and as the `here` arg +in `_expect()`. The `mention` predicate `m.group("path") and m.group("line") is None` is +exactly `c.kind == "mention"`, so fenced/mention buckets collapse identically. + +## Evidence (measured — run against current repo state) + +`wizard-work/forge/verify_citation_resolve_check_dry_refactor.py` (committed in `8f592aa`) +runs the original from `git show HEAD:tools/citation_resolve_check.py` and the refactored from +the working tree end-to-end, then compares bytes. Verbatim output of this run +(`wizard-outputs/forge/iter_citations_verify_run.txt`): + +``` + [ok ] selftest: original 14/14 rc 0 + [ok ] selftest: refactored 14/14 rc 0 + [ok ] selftest: identical stdout (byte-for-byte) + [ok ] live scan: original ran rc 0 + [ok ] live scan: refactored ran rc 0 + [ok ] live scan: identical findings (citations) + [ok ] live scan: identical stats + [ok ] live scan: identical default stdout (rc 0, byte-identical) + +verify: 8 checks, 8 passed, 0 failed +``` +Exit code 0. + +Self-test on the refactored (working-tree) tool: +``` +self-test: 14 fixtures, 14 passed, 0 failed +``` +Exit code 0. + +Live census (both versions against the SAME current HEAD, 442 markdown files): +- EXPLICIT citations: 524 — 166 RESOLVES / 0 STALE_LINE / 358 EXTERNAL_PATH +- BARE candidate citations: 1877 — 150 AS_NAMED / 794 AS_SELF / 57 AMBIGUOUS / 268 EXTERNAL_NAMED / 608 UNRESOLVED +- Total graded citations: 2,401 (524 explicit + 1,877 bare) +- mentions (no line): 8,570; fenced skipped: 6 citations, 491 mentions +- findings (citations[]) and stats{} are **byte-identical** between original and refactored. + +## Reconciling the stale scan snapshots + +`wizard-outputs/forge/` carries two older capture files whose corpus counts differ: + +``` +citation_resolve_scan_original.txt : "corpus: 441 markdown files", "file mentions with no line: 8565" +citation_resolve_scan_refactored.txt: "corpus: 442 markdown files", "file mentions with no line: 8570" +``` +That 441-vs-442 / 8565-vs-8570 delta is **not** a refactor divergence: the two snapshots were +taken at different repo moments (one markdown file was added between them; "mentions" rose by +exactly 5, matching 5 new mentions in that file). The verifier recomputes BOTH versions +against the **same** current HEAD in the same process and reports `identical stats` — so the +refactor introduces zero divergence; the snapshots are simply stale relative to the tip. + +## Scope note (why the source is not committed here) + +Per the operator's commit scope for this run (`git add wizard-work/forge/`), only this +verification artifact is landed. The source change to `tools/citation_resolve_check.py` stays +in the working tree, unstaged. This is intentional and correct: the verifier derives its +"original" via `git show HEAD:tools/citation_resolve_check.py`, so the source must remain +unstaged (HEAD = original, working tree = refactored) for that comparison to stay meaningful. +Landing the source is a separate, deliberate step (the established forge pattern: a +"forge: verify ... DRY refactor" commit precedes/a but the source lands in its own commit, +cf. `86d5b27`/`4c89084`). This artifact documents that the refactor is verified +behavior-preserving as-is, ready to land. + +## Verdict + +PASS — the `iter_citations()` DRY consolidation in `tools/citation_resolve_check.py` is +behavior-preserving: 14/14 self-test fixtures, identical findings (2,401 rows) and identical +stats across the full 442-file live census, and byte-identical default stdout, between the +HEAD original and the working-tree refactored build (8/8 checks, exit 0). The mention/fenced +predicate is unified without altering any result. + +## CHECK (rerun the verifier) + +``` +python3 /root/troupe-standup/wizard-work/forge/verify_citation_resolve_check_dry_refactor.py +``` +Exits 0 on full pass (8/8), 1 otherwise.