P0 — parsePorcelainZ rename/copy handling
Git's -z porcelain emits `R <new>\0<old>\0` for renames (and
C for copies). The naive split-and-slice treated <old> as an
independent dirty path, which made runObservedDestructiveSyncOp
warn about phantom "cleared paths" whenever a rename was in
flight. Now we detect R/C status and skip the trailing entry.
P1 — race-rescue loop unstages between attempts
`git stash create` snapshots the index without clearing it, so
iteration 2's `git add -A` would re-stage atop iteration 1's
leftovers. Tree differences inside the loop then reflected stale
staging rather than genuine new writes. Added a `git reset` at
the top of each iteration so every attempt starts from a clean
index baseline.
P1 — writeActiveMergerStatus is now atomic
Switched from in-place writeFileSync to temp-file + renameSync.
POSIX guarantees rename atomicity on the same filesystem, so a
reader can no longer catch the file mid-flush and return a
false-negative "no merger active" advisory.
P2 — Step regex em-dash clarity
`[—\-:]` is functionally fine but obscures intent; switched to
`(?:—|-|:)` so the em-dash branch is obvious. Added a test case
for the em-dash separator.
New tests:
- parse-porcelain-z.test.ts (8 cases including renames + copies)
- em-dash case added to derive-subject-summary.test.ts
247/247 merger-suite tests pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- deriveDeterministicSubjectSummary now picks the lowest-numbered
`complete Step N` headline (or the oldest commit) instead of the most
recent commit, so trailing quality-gate revisions stop hijacking the
squash-merge subject (FN-3617 landed as "align mailbox modal css..."
when 4 of 5 commits were the actual Claude OAuth fix).
- AI subject + body system prompts in ai-summarize.ts now weight by
commit theme rather than file size, so a small token cleanup that
touches a large CSS file no longer dominates the summary.
- stashUnrelatedRootDirChanges adds a bounded re-snapshot loop after
the primary stash is persisted but before \`git reset --hard\`. Any
late-dirty paths (concurrent dev edits during a long merger run,
parallel merger runs racing on rootDir, late test/build artifacts)
get captured in labeled \`race-rescue-N\` stashes recoverable from
\`git stash list\`, instead of being wiped by the destructive reset.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>