forge: confirm citation_resolve_check iter_citations DRY refactor 8/8 behavior-preserving (live verify, 442-file/2401-cite census)
This commit is contained in:
1 parent
caf157e729
commit
d310b35a0b
1 file changed
+125
@@ -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.
|
||||
Reference in new issue
Block a user