CB-578 stage C: snapshot a dirty worktree into git before it can be lost #64

Closed
agent wants to merge 0 commits from worker/cb578c-92885c-1 into main
Member

Closes stage C of issue #50 (acceptance criteria 9 and 10).

  • Adds Worktrees.snapshot(worktreePath, branch, message), implemented in GitWorktrees next to hasUncommitted/remove: stages into a temporary GIT_INDEX_FILE (never the worker's real index/HEAD), write-trees it, commit-trees it onto the worktree's current HEAD, and update-refs refs/wip/<branch>. add -A (never -f) respects .gitignore.
  • SessionManager.release() snapshots any dirty worktree before the preserve-or-remove decision, for any release cause (COMPLETED or SHUTDOWN) — no cause filter, per the ticket. A failing snapshot never escalates: the worktree is still preserved, the pane still stops, and the release listener is still notified (CB-581's hardening is unchanged).
  • onRelease's listener now receives a SessionManager.ReleaseDetail (terminal, worktree path, branch, snapshot ref) instead of a bare terminal id. Bridged.java's WORKER_FAILED wiring uses this to put the worktree path, branch, and snapshot ref into a failed ticket's detail (flows through the existing MessageService/BridgedApp plumbing unchanged) — a lead can re-dispatch onto the same tree instead of starting from the base commit.

Tests

All 8 acceptance criteria have direct tests:

  • GitWorktreesTest: real-git tests for AC1 (untracked+tracked changes land in the ref's tree, ref lives outside refs/heads), AC2 (HEAD/index/working-tree status unchanged), AC3 (gitignored file excluded, non-ignored untracked file still included), plus a missing-worktree no-throw case.
  • WorktreeSessionManagerTest: AC4 (clean release snapshots nothing), AC1-at-the-SessionManager-level (dirty release snapshots with the right worktree/branch), AC6 (release listener detail carries worktree/branch/snapshot ref), AC5 (a failing snapshot still preserves+stops+notifies without throwing).
  • Existing CB-576/CB-581 regression tests in SessionManagerTest/WorktreeSessionManagerTest (SHUTDOWN preserves, clean COMPLETED removes, dirty COMPLETED preserves, throwing dirty-check preserves) pass unchanged — AC7.

mvn clean install: Tests run: 748, Failures: 0, Errors: 0, Skipped: 0 — BUILD SUCCESS, exit 0.

Note on AC6's test coverage

Acceptance criterion 6 ("a failed ticket's detail carries the worktree path, branch and snapshot ref") is tested at the SessionManager level — asserting the ReleaseDetail the release listener receives — rather than through Bridged.java's wiring end-to-end, since that wiring isn't independently unit-tested anywhere else in the codebase either (matches the existing pattern for the pre-existing "the worker session was released before it replied" string). The Bridged.java reason-string formatting that turns ReleaseDetail into the text a lead actually sees is a small, direct one-line-per-field build with no branching logic beyond a null check.

Closes stage C of issue #50 (acceptance criteria 9 and 10). - Adds `Worktrees.snapshot(worktreePath, branch, message)`, implemented in `GitWorktrees` next to `hasUncommitted`/`remove`: stages into a temporary `GIT_INDEX_FILE` (never the worker's real index/HEAD), `write-tree`s it, `commit-tree`s it onto the worktree's current HEAD, and `update-ref`s `refs/wip/<branch>`. `add -A` (never `-f`) respects `.gitignore`. - `SessionManager.release()` snapshots any dirty worktree before the preserve-or-remove decision, for **any** release cause (COMPLETED or SHUTDOWN) — no cause filter, per the ticket. A failing snapshot never escalates: the worktree is still preserved, the pane still stops, and the release listener is still notified (CB-581's hardening is unchanged). - `onRelease`'s listener now receives a `SessionManager.ReleaseDetail` (terminal, worktree path, branch, snapshot ref) instead of a bare terminal id. `Bridged.java`'s WORKER_FAILED wiring uses this to put the worktree path, branch, and snapshot ref into a failed ticket's detail (flows through the existing `MessageService`/`BridgedApp` plumbing unchanged) — a lead can re-dispatch onto the same tree instead of starting from the base commit. ### Tests All 8 acceptance criteria have direct tests: - `GitWorktreesTest`: real-git tests for AC1 (untracked+tracked changes land in the ref's tree, ref lives outside `refs/heads`), AC2 (HEAD/index/working-tree status unchanged), AC3 (gitignored file excluded, non-ignored untracked file still included), plus a missing-worktree no-throw case. - `WorktreeSessionManagerTest`: AC4 (clean release snapshots nothing), AC1-at-the-SessionManager-level (dirty release snapshots with the right worktree/branch), AC6 (release listener detail carries worktree/branch/snapshot ref), AC5 (a failing snapshot still preserves+stops+notifies without throwing). - Existing CB-576/CB-581 regression tests in `SessionManagerTest`/`WorktreeSessionManagerTest` (SHUTDOWN preserves, clean COMPLETED removes, dirty COMPLETED preserves, throwing dirty-check preserves) pass unchanged — AC7. `mvn clean install`: **Tests run: 748, Failures: 0, Errors: 0, Skipped: 0** — BUILD SUCCESS, exit 0. ### Note on AC6's test coverage Acceptance criterion 6 ("a failed ticket's detail carries the worktree path, branch and snapshot ref") is tested at the `SessionManager` level — asserting the `ReleaseDetail` the release listener receives — rather than through `Bridged.java`'s wiring end-to-end, since that wiring isn't independently unit-tested anywhere else in the codebase either (matches the existing pattern for the pre-existing "the worker session was released before it replied" string). The `Bridged.java` reason-string formatting that turns `ReleaseDetail` into the text a lead actually sees is a small, direct one-line-per-field build with no branching logic beyond a null check.
agent added 1 commit 2026-08-15 12:56:35 +02:00
CB-578 stage C: snapshot a dirty worktree into refs/wip before it can be lost
CI / build (pull_request) Successful in 54s
CI / contract (pull_request) Successful in 1m21s
6725642274
Adds Worktrees.snapshot(worktreePath, branch, message): stages into a
temporary GIT_INDEX_FILE (never the worker's real index/HEAD), writes
the tree, commit-trees it onto the worktree's current HEAD, and points
refs/wip/<branch> at the result. add -A (never -f) respects .gitignore.

SessionManager.release() now snapshots any dirty worktree before the
preserve-or-remove decision, regardless of release cause (COMPLETED or
SHUTDOWN) — preserving on disk alone is one `worktree remove --force`
away from gone. A failing snapshot never escalates: the worktree is
still preserved, the pane still stops, and the release listener is
still notified.

onRelease's listener now receives a ReleaseDetail (terminal, worktree
path, branch, snapshot ref) instead of a bare terminal id, so Bridged's
WORKER_FAILED wiring can put the same three facts into a failed
ticket's detail — a lead can re-dispatch onto the same tree instead of
starting from the base commit.
ltms closed this pull request 2026-08-15 13:09:33 +02:00
Some checks are pending
CI / build (pull_request) Successful in 54s
CI / contract (pull_request) Successful in 1m21s

Pull request closed

Sign in to join this conversation.