Pin a golden workspace before you edit any file. Record the command, inputs, outputs, and hashes. Allow one bounded diff only after those goldens pass.
Messy repositories hide behavior in scripts and side effects. Unit tests often miss those paths entirely. A rewrite can look cleaner and still break callers.
This workflow treats the repo as a workspace machine. You freeze observable results first. Then you spend a tiny, reviewed diff.
Why rewrites fail on messy trees
Messy repos rarely expose a stable public API. Behavior lives in glue scripts and generated files. Callers depend on those side effects, not class names.
Large model-assisted rewrites optimize for local readability. They do not automatically preserve workspace outcomes. Missing files and quiet exit-code changes slip through review.
Characterization at function scope is too narrow here. The unit of risk is the working tree. Pin that tree, then change one seam.
What a golden workspace records
A golden workspace is not a unit test. It is a recorded run against fixtures. The record must stay deterministic after scrubbing.
Capture five fields on every replay. Skip anything you cannot rebuild from git.
- Fixture tree used as input.
- Exact command and working directory.
- Process exit code.
- Scrubbed stdout and scrubbed stderr.
- Relative output paths plus content hashes.
Do not pin wall-clock timestamps. Do not pin random identifiers. Do not pin absolute machine paths.
Decision table: pin, scrub, or skip
Use this table before you write goldens. It keeps the suite honest.
| Observable | Pin it | Scrub it | Skip the scenario |
|---|---|---|---|
| CLI help text | Stable flags only | Version banners | Marketing copy |
| Generated JSON | Canonical keys | UUIDs, dates | Live network payloads |
| Output directory | Relative paths, hashes | tmp prefixes | OS junk files |
| Exit code | Always | Never | Hang or interactive prompt |
| Logs | Error lines you own | Hostnames, pids | Third-party debug dumps |
If a row needs skip, drop that scenario. A flaky golden is worse than no golden.
Artifact: workspace golden harness
The artifact is a small Python harness. Label it as an unexecuted example. Adapt paths to your repo before you trust it.
# example: workspace_golden.py
# unlabeled numbers are placeholders, not measured results
from __future__ import annotations
import argparse, hashlib, json, os, re, shutil, subprocess, sys, tempfile
from pathlib import Path
SCRUBBERS = [
(re.compile(rb"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}"), b"<TIMESTAMP>"),
(re.compile(rb"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}", re.I), b"<UUID>"),
(re.compile(rb"(?:/tmp|/var/folders)[^\s]+"), b"<TMP>"),
]
def scrub(raw: bytes) -> str:
for pat, repl in SCRUBBERS:
raw = pat.sub(repl, raw)
return raw.decode("utf-8", errors="replace").replace("\r\n", "\n")
def hash_tree(root: Path) -> dict[str, str]:
out = {}
for path in sorted(root.rglob("*")):
if not path.is_file():
continue
rel = path.relative_to(root).as_posix()
if rel.startswith(".git/") or rel == "gold.json":
continue
digest = hashlib.sha256(path.read_bytes()).hexdigest()
out[rel] = digest
return out
def run_case(fixture: Path, command: list[str], cwd_name: str) -> dict:
with tempfile.TemporaryDirectory() as td:
work = Path(td) / "work"
shutil.copytree(fixture, work)
cwd = work / cwd_name
proc = subprocess.run(
command,
cwd=cwd,
capture_output=True,
check=False,
)
return {
"command": command,
"cwd": cwd_name,
"exit_code": proc.returncode,
"stdout": scrub(proc.stdout),
"stderr": scrub(proc.stderr),
"files": hash_tree(work),
}
def main() -> int:
p = argparse.ArgumentParser()
p.add_argument("--fixture", type=Path, required=True)
p.add_argument("--gold", type=Path, required=True)
p.add_argument("--cwd", default=".")
p.add_argument("--update", action="store_true")
p.add_argument("command", nargs=argparse.REMAINDER)
args = p.parse_args()
command = args.command[1:] if args.command[:1] == ["--"] else args.command
if not command:
print("missing command after --", file=sys.stderr)
return 2
actual = run_case(args.fixture, command, args.cwd)
if args.update or not args.gold.exists():
args.gold.write_text(json.dumps(actual, indent=2, sort_keys=True) + "\n")
print(f"wrote {args.gold}")
return 0
expected = json.loads(args.gold.read_text())
if actual != expected:
print("workspace golden mismatch", file=sys.stderr)
print(json.dumps({"expected": expected, "actual": actual}, indent=2))
return 1
print("workspace golden ok")
return 0
if __name__ == "__main__":
raise SystemExit(main())
Record one case with a documented command. Keep the fixture tiny and committed.
python workspace_golden.py \
--fixture testdata/invoice_batch \
--gold testdata/invoice_batch.gold.json \
--cwd . \
--update -- \
python messy_cli.py --in ./incoming --out ./reports
Replay the same command in CI or locally. Fail the job on any mismatch. Do not update goldens in the same commit as a behavior change unless the ticket says so.
python workspace_golden.py \
--fixture testdata/invoice_batch \
--gold testdata/invoice_batch.gold.json \
--cwd . -- \
python messy_cli.py --in ./incoming --out ./reports
Numbered workflow
Follow these steps in order. Do not start at the rewrite.
- List the jobs operators actually run. Read README, Makefiles, and CI scripts. Ignore dead entry points with no callers.
- Pick one job with a file-system footprint. Prefer batch scripts over HTTP servers first. Network clocks make goldens lie.
- Build a fixture that is small and legal. Strip secrets, tokens, and customer names. Use synthetic files only.
- Run the harness once with
--update. Inspect gold.json by hand. Confirm hashes match files you expect. - Add scrubbers for the first volatile fields. Re-run until two consecutive replays match. Stop if match requires hiding real errors.
- Freeze the gold file in git. Open a change budget before any refactor. The budget is one module path.
- Apply the smallest diff that preserves goldens. Re-run the harness after every edit. Revert if the gold breaks.
- Only then consider a second seam. New goldens come before that second seam. Never stack refactors on a red harness.
Bound the diff after goldens pass
Passing goldens do not grant a wide rewrite. They grant one bounded edit. Encode that bound as a gate, not a vibe.
# example: diff_budget.py
from __future__ import annotations
import subprocess, sys
MAX_FILES = 3
MAX_HUNKS_HINT = 12 # soft cap for review, not a quality score
def main() -> int:
raw = subprocess.check_output(["git", "diff", "--name-only", "HEAD"], text=True)
files = [line for line in raw.splitlines() if line and not line.endswith(".gold.json")]
if not files:
print("no source files changed")
return 0
tops = {f.split("/")[0] for f in files}
if len(files) > MAX_FILES:
print(f"too many files: {files}", file=sys.stderr)
return 1
if len(tops) > 1:
print(f"more than one top-level path: {sorted(tops)}", file=sys.stderr)
return 1
print(f"diff budget ok: {files}")
return 0
if __name__ == "__main__":
raise SystemExit(main())
Run goldens, then run the budget script. Both must pass before merge. A clean tree with a huge diff is still a failed refactor.
Where a free model may help
Drafting scrubbers and fixture lists is tedious. A coding model can propose regexes from sample logs. It should not choose the seam or rewrite the tree.
Disclosure: This article was prepared as part of MonkeyCode's product outreach. MonkeyCode offers free model access and a free server option. Use those only to draft harness pieces and to replay goldens off your laptop.
Keep the prompt narrow. Paste README command blocks and one redacted log. Ask for scrubber candidates and fixture file names. Reject any answer that edits production modules.
If you already use those free models, generate the harness, not the rewrite. The rewrite stays human-sized and gold-gated.
What this does not prove
A matching gold.json does not prove correctness. It proves replay stability for one fixture. Unknown flags and empty directories remain unpinned.
Hashes ignore semantic equivalence. Pretty-printed JSON will fail even when data matches. Canonicalize on purpose, or accept the noise.
Exit code zero can still hide partial writes. Read the output tree list every time. Add a second fixture when the first path is happy-path only.
Who should not use this
Skip this if the repo already has contract tests. Duplicate goldens add cost without new signal. Skip it for pure libraries with a typed public API.
Skip it when output is inherently live. Market data, clocks, and GUIs need other oracles. Skip it when fixtures would require real secrets.
Skip it for one-off scripts you will delete. Characterization cost should be lower than rewrite risk. If the mess dies this week, do not pin it.
Close
Messy-repo safety is a recording problem first. It is a diff-budget problem second. Models can draft the recorder. They should not spend the budget.
Top comments (0)