A messy writer is not safe to extract yet.
You must freeze every created and rewritten path first.
A tree manifest is a small characterization oracle.
The failure that name diffs miss
AI patches often preserve identifiers and miss disk effects.
Relative paths resolve against a hidden working directory.
A helper extract can look tiny and still rewrite files.
Temp files appear, then vanish, then appear again.
Same bytes in a new location still break callers.
Callers that glob directories will notice immediately.
What the tree manifest must pin
Pin four facts for every path the process can touch.
Record the relative path from a locked working directory.
Record the node kind as file, directory, or symlink.
Record byte length and a content hash for files.
Ignore wall-clock mtime unless your contract needs it.
Many writers rewrite identical bytes with new timestamps.
Hashing content keeps the oracle stable across those rewrites.
Do not skip empty files or zero-byte sentinels.
Do not skip directories that exist only as containers.
Absence and presence are both facts in the manifest.
Proposed fixture, not a production module
The listing below is a proposed messy writer.
It is not claimed as production code from a live repo.
Run it only inside a disposable temporary directory.
That writer mixes mkdir, atomic replace, and a delete.
Extracting write_report without a tree oracle is guesswork.
The next section freezes the tree before any extract.
# proposed_messy_writer.py
"""Proposal: a messy writer with mixed path side effects."""
from __future__ import annotations
import json
import os
from pathlib import Path
def run(root: Path) -> int:
root = root.resolve()
cache = root / "cache"
cache.mkdir(parents=True, exist_ok=True)
(cache / "ok.flag").write_text("1\n", encoding="utf-8")
report = root / "report.json"
payload = {"files": 2, "status": "ok"}
tmp = root / "report.json.tmp"
tmp.write_text(json.dumps(payload) + "\n", encoding="utf-8")
os.replace(tmp, report)
nested = root / "out" / "nested"
nested.mkdir(parents=True, exist_ok=True)
(nested / "note.txt").write_text("keep\n", encoding="utf-8")
stale = root / "cache" / "stale.log"
if stale.exists():
stale.unlink()
return 0
if __name__ == "__main__":
raise SystemExit(run(Path.cwd()))
Numbered workflow
1. Lock the working directory
Create a temporary fixture tree for every test run.
Copy seed files into that tree, then chdir into it.
Pass the locked path as an explicit argument if possible.
Do not inherit the developer's current working directory.
Do not write into the repository during characterization.
A leaked path will poison later content hashes.
2. Capture the before-tree
Walk the fixture before the writer runs.
Sort paths in POSIX order using forward slashes.
Store kind, size, and sha256 for each file.
Include directories even when they hold no files.
3. Run the writer as a subprocess
Invoke the module with a fixed cwd and env.
Keep argv, stdin, and stdout out of this oracle.
This article pins disk only; other contracts stay elsewhere.
Set PYTHONHASHSEED to zero for stable subprocess hashing.
4. Capture the after-tree
Walk the fixture again after the process exits.
Diff the two manifests into created, deleted, rewritten.
Store that triple as the characterization fixture file.
5. Extract one writer, then replay
Move one function and keep path strings unchanged.
Replay the same seed tree and the same argv.
Fail the change if any manifest row moves.
Refuse a second extract while the oracle is red.
Reproducible tree-oracle script
The script below is a proposal you can run locally.
It needs only Python 3.9 from the standard library.
It does not call any hosted model during characterization.
Label every output as a fixture, not production evidence.
# tree_oracle.py
"""Proposal: characterize filesystem side effects of one writer."""
from __future__ import annotations
import hashlib
import json
import os
import subprocess
import sys
import tempfile
from pathlib import Path
from typing import Literal
NodeKind = Literal["file", "dir", "symlink"]
def _posix(path: Path, root: Path) -> str:
rel = path.relative_to(root).as_posix()
return "." if rel == "." else rel
def walk_manifest(root: Path) -> dict[str, dict]:
root = root.resolve()
rows: dict[str, dict] = {}
for dirpath, dirnames, filenames in os.walk(root, followlinks=False):
current = Path(dirpath)
dirnames.sort()
filenames.sort()
rel_dir = _posix(current, root)
rows[rel_dir] = {"kind": "dir", "size": None, "sha256": None}
for name in filenames:
path = current / name
rel = _posix(path, root)
if path.is_symlink():
rows[rel] = {
"kind": "symlink",
"size": None,
"sha256": None,
"target": os.readlink(path),
}
continue
data = path.read_bytes()
digest = hashlib.sha256(data).hexdigest()
rows[rel] = {
"kind": "file",
"size": len(data),
"sha256": digest,
}
return dict(sorted(rows.items()))
def seed_tree(root: Path) -> None:
(root / "cache").mkdir(parents=True, exist_ok=True)
(root / "cache" / "stale.log").write_text("old\n", encoding="utf-8")
(root / "keep.txt").write_text("seed\n", encoding="utf-8")
def run_writer(root: Path, module: Path) -> subprocess.CompletedProcess:
env = os.environ.copy()
env["PYTHONHASHSEED"] = "0"
return subprocess.run(
[sys.executable, str(module)],
cwd=root,
env=env,
check=False,
capture_output=True,
text=True,
)
def diff_manifests(before: dict, after: dict) -> dict:
created = sorted(set(after) - set(before))
deleted = sorted(set(before) - set(after))
rewritten = sorted(
key
for key in set(before) & set(after)
if before[key] != after[key]
)
return {
"created": created,
"deleted": deleted,
"rewritten": rewritten,
"after": after,
}
def main() -> int:
module = Path(sys.argv[1]).resolve()
with tempfile.TemporaryDirectory(prefix="tree-oracle-") as tmp:
root = Path(tmp)
seed_tree(root)
before = walk_manifest(root)
proc = run_writer(root, module)
after = walk_manifest(root)
report = {
"exit_code": proc.returncode,
"before": before,
**diff_manifests(before, after),
}
sys.stdout.write(json.dumps(report, indent=2, sort_keys=True))
sys.stdout.write("\n")
return 0 if proc.returncode == 0 else proc.returncode
if __name__ == "__main__":
raise SystemExit(main())
Capture a golden report once, then keep it in git.
The command below writes JSON to a committed file.
python tree_oracle.py proposed_messy_writer.py > tree_manifest.golden.json
After any extract, regenerate the report and compare bytes.
The second command exits nonzero when the trees diverge.
python tree_oracle.py proposed_messy_writer.py > tree_manifest.new.json
diff -u tree_manifest.golden.json tree_manifest.new.json
A nonzero compare means the extract changed disk behavior.
Restore the helper, fix the paths, then replay the oracle.
Do not stack a second extract on a red manifest.
Red means the smallest change was already too large.
Expected golden shape
The golden JSON should name created paths in sorted order.
For the proposed writer, expect cache/ok.flag among creates.
Expect report.json as a rewritten or created file.
Expect cache/stale.log in the deleted list after the run.
out/nested/note.txt should appear with a stable sha256.
keep.txt should remain in both before and after maps.
If report.json.tmp survives, the atomic replace did not finish.
Treat leftover temp names as a contract failure, not noise.
Decision table for the smallest safe change
| Symptom after extract | Manifest signal | Safe next step |
|---|---|---|
| created extra file | new key in created
|
revert; pin the path string |
| missing expected file | new key in deleted
|
revert; restore mkdir order |
| same path, new hash | key in rewritten
|
revert; freeze encoding and newline |
leftover *.tmp name |
unexpected created row |
revert; keep the atomic replace |
| identical manifest | empty extra diffs | keep the one-function extract |
| hashes match only by luck | cwd was not locked | fail the run; lock cwd first |
Read the table left to right before editing again.
One red cell means the extract is not small enough.
Green across the row is the only merge signal.
Do not negotiate extra created files as style cleanup.
Encoding and newline traps
Hash mismatches often come from newline translation, not logic.
Write files with explicit UTF-8 and a single trailing newline.
Open the golden JSON as text and inspect rewritten hashes.
A one-byte CRLF change will flip the entire sha256.
Locale encodings will also split a safe extract.
Windows path separators must not leak into the manifest keys.
The walker stores POSIX relative paths for that reason.
Where a free model can participate
Disclosure: This article was prepared as part of MonkeyCode's product outreach.
MonkeyCode's free model access and free server option can draft one extract.
The frozen tree manifest still decides whether that draft can ship.
Feed the messy function and the golden created-path list.
Ask for one helper and forbid new path literals.
Replay tree_oracle.py before you read the model's rationale.
Skip this step if the golden manifest does not exist yet.
A model without an oracle will invent tidier paths.
Tidier paths are still behavior changes on disk.
Limitations
This oracle ignores stdout, stderr, and exit codes.
It ignores network calls and process-wide environment mutation.
It follows neither directory symlinks nor parent escapes outside root.
Hashing skips permission bits unless you add them later.
Add mode if your writer must keep restricted secret files.
Add symlink targets if your writer creates links.
Do not use this workflow on live production directories.
Do not point the walker at home directories or cloud mounts.
The walker reads every file under the fixture root.
Keep seeds small and free of credentials or customer data.
Who should not use this
Teams without a disposable seed tree should not start here.
Concurrent writers will race the after-tree snapshot on disk.
Pick a single-process fixture first, or skip the extract.
Clock-dependent filenames break content-stable hashing on every run.
If names include timestamps, pin a clock in the fixture.
Otherwise the created list will churn on every replay.
Security-sensitive writers that must preserve file modes need extra fields.
This default manifest will not catch a chmod regression.
Close
Freeze created, deleted, and rewritten paths before any extract.
Extract one writer second, and keep only that change.
Replay the tree manifest before any further split.
If the manifest is already green, draft that extract on a free server.
Top comments (1)
The technical nuance of freezing the directory state before extraction is crucial, especially in handling the unpredictable nature of filesystem changes. I appreciate your emphasis on capturing both presence and absence of files, which can easily be overlooked in less meticulous setups. If you're looking for help in refining the characterizing fixture or enhancing the extraction process, I'd be happy to discuss a paid collaboration to support your project. How do you envision handling edge cases where the filesystem state might change unexpectedly during extraction?