# _scan_dry refactor verification (forge lane: Refactor) ## The shipped artifact Three files under `wizard-work/forge/`: - `_scan_dry.py` -- original probe: a DRY smell-scanner that itself runs its full scan (and prints a 4811-byte report) at module top level. - `_scan_dry_refactored.py` -- refactored probe with smells removed. - `verify_scan_dry_refactor.py` -- equivalence + smell-absence + non-vacuity harness (11 checks). Evidence files: `_scan_dry_out.txt` (original stdout+stderr, cksum `2478253836 4811`), `_scan_dry_verifier_out.txt` (full verifier run). ## The smell (what the refactor fixes) The probe *is* a DRY scanner, so this is dogfooding -- the fixes are the smells a detector should flag on its own author: 1. **Import-time side effect.** The `for f in sorted(os.listdir(TOOLS))` loop and every `print(...)` run at module top level. Importing the module (for a test, harness, or monkeypatch) executes the whole scan and emits ~4.8 KB of output -- a side effect masquerading as init. 2. **Redundant `or` clause.** `"len([" in l or "len([" in l` -- the second operand is identical to the first, so `or` adds no match; dead logic that only reads as "len([...]) vs len([...]) patterns" confusion. 3. **Unused `sys` import.** `import ast, os, hashlib, collections, sys` -- `sys` is never referenced anywhere in the body. 4. **Resource leak.** `src = open(path, encoding="utf-8").read()` -- file handle never closed (no `with` block). 5. **No `__main__` guard.** The scan runs on import. ## The refactor `_scan_dry_refactored.py`: - Moved the scan loop into `main()` and guarded it with `if __name__ == "__main__": main()` -- import is now side-effect-free. - Collapsed `"len([" in l or "len([" in l` to `"len([" in l` -- identical count, zero behavior change. - Dropped the unused `sys` import. - Replaced the bare `open()` with a `with open(...) as fh:` context manager. - Extracted two pure, testable helpers: - `scan_file(path)` -- returns `(info, dup_funcs, repeated_lines, nlines, funcs)` (Counter of hits + duplicate-function detection + repeated-line report). - `format_report(...)` -- returns the per-file report as a list of strings (pure: no I/O), so the formatting is unit-testable without running the probe. - Replaced magic literals (`3`, `15`, `12`) with named constants `REPEATED_MIN`, `REPEATED_MIN_LEN`, `BODY_HASH_LEN`. Output is byte-identical to the original: 4811 bytes, cksum `2478253836`. ## Verifier bug found and fixed in this pass The first non-vacuity guard (`check_original_still_smells`) had inverted logic on the `__main__` leg: it appended a "problem" when the original *lacked* a main guard -- but lacking the guard *is* the smell we fixed, not a problem. Corrected so the guard passes iff the original still exhibits the removed smells (no guard, doubled `or`, bare `open`, module-level scan loop). Without this, a vacuous "refactor" that deleted nothing would pass -- the guard now fails closed: the original **must** still smell. ## Evidence (measured, not claimed) Byte-identical stdout+stderr (original vs refactored), captured by the verifier's `check_identical_output`: ``` $ cksum _scan_dry_out.txt 2478253836 4811 _scan_dry_out.txt OK: identical output (exit 0, 4811 bytes) ``` Verifier run (11/11): ``` $ python3 verify_scan_dry_refactor.py; echo "VERIFIER_EXIT=$?" OK: py_compile passes OK: identical output (exit 0, 4811 bytes) OK: __main__ guard present OK: no 'global' statements in code OK: unused 'sys' import removed OK: redundant doubled 'or' clause removed; single 'len([ in l' present OK: bare open() replaced with 'with' context manager OK: scan_file, format_report, main extracted; main() calls scan_file OK: import is side-effect-free (no probe output at import) OK: scan_file real (/root/troupe-standup/tools/check_lint.py: 574 lines, 11 funcs); dup-body detection works OK: original still exhibits the removed smells (refactor is non-vacuous) === Verification: 11/11 checks passed === VERIFIER_EXIT=0 ``` ## Metrics (smell counts, before -> after) | smell | original | refactored | |------------------------------------------------|----------|------------| | import-time side effects (module-level scan+print) | yes | none (guarded `main()`) | | `__main__` guard | absent | present | | redundant `or` clause (`"len([" in l or "len([" in l`) | present | removed (single `"len([" in l`) | | unused `sys` import | present | removed | | resource-leaking bare `open()` | present | `with` context manager | | module-level mutable control flow | yes (`for`+`if`+`print` at top level) | none (pure `scan_file`/`format_report` + `main`) | | byte-identical output (cksum) | `2478253836 4811` | `2478253836 4811` | ## Verdict Behavior-preserving and measurable: py_compile clean, byte-identical output (4811 bytes, identical cksum), all 11 verifier checks pass (exit 0) including the non-vacuity guard that the original still smelled. The scanner's own import-time-side-effect, redundant-`or`, dead-import, and resource-leak smells are eliminated, and the scan logic is decoupled into pure helpers.