fix(coding-agent): resume sessions after a worktree move/rename

When a session's working directory is moved or renamed (e.g. `git worktree
move`), the session file stays under the old cwd-encoded bucket while the new
directory is empty. Resuming was lossy:

- `--continue` rejected the terminal breadcrumb purely on cwd mismatch and then
  found nothing in the new bucket, silently starting a fresh empty session.
- cross-project `--resume <id>` only offered to *fork* (duplicate) the session
  into the new directory, forcing manual id selection and leaving a stale copy.

Detect relocation via the strong, low-false-positive signal "recorded cwd no
longer exists on disk" and re-root in place with the existing `moveTo()`:

- `continueRecent` re-roots the terminal's last session into the current
  directory when its recorded cwd is gone and the new location has no sessions
  of its own (otherwise behavior is unchanged). `readTerminalBreadcrumb` is
  refactored into `readTerminalBreadcrumbEntry` returning the raw cwd +
  session file so callers can interpret a cwd mismatch.
- cross-project `--resume <id>` offers "Move (re-root)" instead of fork when the
  source directory is gone; a still-existing different project still forks.

Tests: continue-relocation (re-root on move, no-hijack on plain cd, prefer
local recent) and cross-project move-vs-fork routing.
This commit is contained in:
bling
2026-06-07 11:34:46 -04:00
parent 84ddda3c1b
commit cfa9a2d53e
6 changed files with 433 additions and 52 deletions
@@ -24,8 +24,8 @@ This document describes operator-visible behavior for session export/share/fork/
| `--fork <id\|path>` | CLI startup | Yes after session creation | Creates a new session fork from the selected source into current cwd/session dir | None |
| `/resume` | Interactive slash command | Yes (active in-memory state replaced) | Switches to selected existing session file | None |
| `--resume` | CLI startup picker | Yes after session creation | Opens selected existing session file | None |
| `--resume <id\|path>` | CLI startup | Yes after session creation | Opens existing session; global cross-project match can fork into current project | None |
| `--continue` | CLI startup | Yes after session creation | Opens terminal breadcrumb or most-recent session; creates new one if none exists | None |
| `--resume <id\|path>` | CLI startup | Yes after session creation | Opens existing session; global cross-project match re-roots (moved dir) or forks into current project | None |
| `--continue` | CLI startup | Yes after session creation | Opens terminal breadcrumb (re-roots it if its dir was moved) or most-recent session; creates new one if none exists | None |
## Export and dump
@@ -215,19 +215,23 @@ Notes:
Cross-project id match behavior:
- If matched session cwd differs from current cwd, CLI asks:
- `Session found in different project ... Fork into current directory? [y/N]`
- On yes: `SessionManager.forkFrom(match.path, cwd, sessionDir)` creates a new local forked file.
- On no/non-TTY default: command errors.
- If matched session cwd differs from current cwd, behavior depends on whether the matched session's recorded directory still exists:
- **Directory gone (moved/renamed, e.g. `git worktree move`)**: CLI asks `Session's directory no longer exists (...). Move (re-root) it into the current directory? [Y/n]`.
- On yes (default): `SessionManager.open(match.path)` then `manager.moveTo(cwd)` re-roots the existing session into the current directory (no duplicate file).
- On no: command cancels (returns no session). On non-TTY: command errors.
- **Directory still exists (genuinely different project)**: CLI asks `Session found in different project ... Fork into current directory? [y/N]`.
- On yes: `SessionManager.forkFrom(match.path, cwd, sessionDir)` creates a new local forked file.
- On no: command cancels. On non-TTY: command errors.
## CLI `--continue`
`SessionManager.continueRecent(cwd, sessionDir)`:
1. Resolves session dir for current cwd.
2. Reads terminal-scoped breadcrumb first.
3. Falls back to most recently modified session file.
4. Opens found session; if none exists, creates new session.
2. Reads the terminal-scoped breadcrumb.
3. If the breadcrumb points at a session recorded under a different cwd whose directory no longer exists (moved/renamed) **and** the current directory has no sessions of its own, re-roots that session into the current directory via `moveTo` instead of starting fresh.
4. Otherwise, if the breadcrumb's cwd matches the current cwd, uses the breadcrumb session; else falls back to the most recently modified session file.
5. Opens the found session; if none exists, creates a new session.
This is startup-only behavior; there is no interactive `/continue` slash command.