@@ -16,6 +16,33 @@ breaking changes may land in a minor release.
1616
1717### Fixed
1818
19+ - ** Merge-back failures are classified from the measured tree state, and the catch-all stopped
20+ inventing a conflict (#619 ).** A merge that died part-way through its checkout — all three
21+ strategies, ` --ff-only ` included — now raises ` MergeHalfAppliedError ` instead of "refused
22+ before starting": untracked residue is named for you to clear, and a tracked rewrite is
23+ restored automatically by ` git checkout HEAD -- ` over exactly the affected paths.
24+ Attribution is per path — before/after deltas intersected with the branch's incoming set —
25+ so neither your pre-existing dirt nor an edit you make outside that incoming set while the
26+ merge is failing is ever called git's: the repo-wide dirtiness reading this replaces
27+ classified that concurrent-edit scene "failed part-way through checkout" and its repo-wide
28+ ` reset --hard HEAD ` destroyed the edit. (An edit racing the very paths the merge is
29+ rewriting is indistinguishable from git's write and is restored with them — the stated
30+ ceiling.) A post-merge probe that itself fails — the
31+ residue probes, the unmerged-stages reading, or the MERGE_HEAD reading — no longer bypasses the
32+ merge cleanup or impersonates a verdict: cleanup not gated on the dead reading still runs, the
33+ failure raises ` MergeResidueUnreadError ` (checkout state unverified, run ` git status ` ), and an
34+ unread MERGE_HEAD skips the abort it gates and says so. The squash ** replay** reading gets the
35+ same honesty on the far side of success: unreadable, it used to answer "dirty", and the doomed
36+ ` git commit ` that followed dressed the probe failure as a commit refusal with a
37+ ` reset --hard HEAD ` riding on it — now nothing is committed, nothing is reset, and the
38+ escalation names the dead reading. A refused
39+ ** squash commit** now raises ` MergeCommitRefusedError ` like the ` --no-ff ` leg — the squash leg
40+ seals its result with its own ` git commit ` , where commit hooks and signing do run — rolled back
41+ by ` git reset --hard HEAD ` gated on the pre-merge reading having found the tree clean (a dirty
42+ checkout is never reset; the
43+ escalation then names the staged result and clearing it as your first step). A content conflict
44+ is typed too (` MergeConflictError ` ), so anything unclassified escalates saying just that — run
45+ ` git status ` , git's text names the cause — instead of "resolve the conflict by hand".
1946- ** psmux: a hand-back that succeeded no longer reports as failed — or undoes itself (#659 ).**
2047 ` switch_client ` read its verdict off the session's attached-client count, which a same-session
2148 move cannot change — and same-session is the common shape for the return path, so a correct
@@ -51,6 +78,59 @@ breaking changes may land in a minor release.
5178 its sibling rejected-latch shape, the spec folder raises ` stories.StoriesError ` , and ` --dry-run `
5279 reports it before exiting 1. A spec folder that merely lies outside the project tree still comes
5380 back verbatim — that is a supported layout, and only the canonicalization leg refuses.
81+ - ** An unstaged edit in your main checkout no longer escalates the story and pauses an unattended
82+ run (#618 ).** Under ` [scm] isolation = "worktree" ` the merge pre-flight refused over any dirty
83+ _ tracked_ path outside the unit branch's incoming set, so a worktree-only porcelain ` M ` — modified in the
84+ working tree, nothing staged — stopped the run over a hazard git itself does not have. The axis
85+ is the index column, not trackedness: such a stray is now tolerated and journaled
86+ ` merge-target-tolerated ` alongside untracked dirt. A ** staged** stray still escalates.
87+ - ** Dirt on a path the run commits for itself now blocks the merge whatever its index column
88+ (#618 ).** The post-merge carries stage the sprint board and the deferred-work ledger by
89+ pathspec, which takes whatever the working tree holds no matter who wrote it, so narrowing the
90+ pre-flight to staged strays alone would have let an operator's private edit land in history
91+ under a ` chore(sprint-status): carry ... ` message with the tree left clean. Both paths are now
92+ passed to the pre-flight as protected, and a stray among them escalates with its own remedy —
93+ such dirt has to leave the path, not merely be unstaged. Tracked artifacts only.
94+ - ** A resumed run no longer commits — or overwrites — board edits you made while it was down
95+ (#618 ).** When the merge was already journaled ` unit-merged ` , the replay falls through to the
96+ carry commits with no pre-flight in front of them. The carry cannot simply refuse on dirt: a
97+ crashed pass's own half-written advance is dirt on exactly that path and finishing it is what
98+ the leg exists for. It now asks whether the board holds HEAD's content plus this pass's
99+ advance — git's own question, so a board spelled CRLF by one host and LF by another still
100+ answers yes — and proves the index too, since the carry's ` git add ` overwrites it as well as
101+ the working tree; an ABSENT index entry counts as a staged untracking (` git rm --cached ` ) rather
102+ than as nothing to lose. That guards the commit, which is one write too late for the OWN row:
103+ ` advance ` would already have replaced the edit with the target, leaving precisely the bytes the
104+ proof accepts. So that row is now checked BEFORE the advance, and a status that is neither
105+ HEAD's nor this pass's own refuses it. Either refusal journals ` board-advance-carry-foreign-dirt ` .
106+ - ** A merge git refused before it started no longer sends you to resolve a conflict that does not
107+ exist (#619 ).** Every ` GitError ` out of the merge was labelled "content conflict against the
108+ target", but most are git declining at pre-flight — an untracked file the merge would overwrite,
109+ a staged change on an incoming path, a file/directory shape clash, an ` ff ` target that cannot
110+ fast-forward — where nothing merged, the target checkout is untouched, and there are no markers
111+ to find. Those now raise ` verify.MergePreflightError ` (a ` GitError ` subclass, so every existing
112+ handler is unchanged) and escalate describing that state, with git's own text naming the cause.
113+ A third state needed its own type. A ` --no-ff ` that merges cleanly and is then refused at the
114+ COMMIT — a ` pre-merge-commit ` or ` commit-msg ` hook, or a ` commit.gpgsign ` that cannot sign —
115+ leaves no unmerged stages but does leave ` MERGE_HEAD ` , so reading the index alone called a
116+ started merge a pre-flight refusal and sent you to clear a clash that does not exist.
117+ ` verify.MergeCommitRefusedError ` now names it: the merge is aborted and the escalation points at
118+ the policy that declined rather than at a tree with nothing wrong. Where the abort ITSELF fails,
119+ it says so and sends you to recover the mid-merge checkout first — a resume attempted before
120+ that dies on the merge state however well the hook is fixed.
121+ - ** A refused ` squash ` merge no longer destroys the uncommitted work in your main checkout
122+ (#619 ).** ` --squash ` has no ` --abort ` , so the recovery is ` git reset --hard HEAD ` — gated on a
123+ tree-state probe read _ after_ the merge and used to answer "did the squash act". A checkout
124+ already carrying an unstaged edit reads dirty whether or not git touched a byte, so a merge git
125+ refused at pre-flight fired the reset and discarded work the merge never went near. The probe is
126+ now read once _ before_ the squash and a tree found dirty is never reset. The same root cause
127+ corrupted the replay gate that recognises "the squash staged nothing", which now asks the index.
128+ - ** The journal no longer records a path as tolerated when that path is what stopped the merge
129+ (#623 ).** ` merge-target-tolerated ` is written from inside the pre-flight guard, strictly before
130+ the merge runs, so it can only record what the guard decided. A stray outside the incoming set
131+ by _ path_ can still clash with it by _ shape_ — a file where the merge needs a directory, or the
132+ reverse — and git then refuses over the very path the event called harmless. The refusal path
133+ now appends a corrective ` merge-preflight-refused ` naming the same paths and carrying git's text.
54134
55135## [ 0.11.0] — 2026-08-19
56136
0 commit comments