Skip to content

feat: recover sibling worktrees via git worktree registration (#3)#27

Merged
HumanBean17 merged 1 commit into
mainfrom
feat/sibling-worktree-recovery
Jul 18, 2026
Merged

feat: recover sibling worktrees via git worktree registration (#3)#27
HumanBean17 merged 1 commit into
mainfrom
feat/sibling-worktree-recovery

Conversation

@HumanBean17

Copy link
Copy Markdown
Owner

Summary

resolveMainRepo walked up from a session cwd to the nearest directory .git, which missed sibling worktrees — a checkout beside the main repo (e.g. …/<repo>-wt-cli next to …/<repo>), the layout git worktree add produces for a sibling clone. The main repo is a sibling, not an ancestor, so the walk hit the shared parent (no .git) → null → the session was dropped as unresolvable_cwd. ~29% of surviving sessions on worktree-heavy repos were lost this way (the prior git rev-parse --show-toplevel path dropped them too). Closes #3.

Changes

  • src/git.tsresolveRepo now returns RepoResolution { repo, checkoutRoot }. At each ancestor lacking a directory .git, it scans sibling dirs for a directory .git and reads each candidate's .git/worktrees/<name>/gitdir. Git writes <checkout>/.git there (not the checkout root), so the trailing /.git is stripped and the registered checkout is checked as an ancestor of the cwd. Paths are best-effort canonicalized (handles /var/private/var and since-deleted cwds). resolveMainRepo/walkToRepo are thin wrappers.
  • src/relativize.tsrelativizeFilePath/relativizeEnvelopeFiles take an optional worktreeCheckoutRoot; sibling-worktree files (absolute, outside the repo prefix) are stripped of the checkout root first, so they aggregate with the main checkout instead of fragmenting into absolute-path area keys.
  • src/pipeline.ts — threads checkoutRoot through runScan and runReflect (both --transcript and --latest) into relativization.
  • Tests — rewrote against real git worktree add (authentic <checkout>/.git gitdir format): live + deleted sibling recovery, false-positive and string-prefix-not-ancestor negatives, sibling relativization, and end-to-end runScan + runReflect tests proving recovery and aggregation.
  • Docs — README, ARCHITECTURE, CONSUMER_GUIDE updated; sandbox-safety wording preserved.

Sandbox contract

Preserved: production code only stats / readdirs / reads the tiny gitdir text files under candidate .git/worktrees/. No git invocation, no shell, no network, no writes. (Tests shell out to git only to build fixtures.) Uses git's own worktree registration — not a -wt- naming heuristic (the locked decision in #3).

Review

Two code-review rounds via the requesting-code-review skill (3 parallel reviewers → merged findings → re-review of fixes).

  • Round 1 caught a Critical bug the synthetic tests had masked: git writes <checkout>/.git to gitdir, not <checkout>, so the feature recovered zero real worktrees. Fixed: real gitdir parsing (C1), canonicalization (C2), relativization threading (I1), and rewrote tests against real git worktree add (C3).
  • Round 2 — two fresh reviewers returned Ready to merge, verifying empirically (20/20 real-git worktree add probe incl. the macOS /var/private/var symlink case and false-positive rejection) and confirming the tests are load-bearing (reverting the source makes the sibling tests fail: session_count 1 → 2).

Verification

  • npm run build — clean (tsc, no errors)
  • npm test403/403 passing
  • Throwaway real-git worktree add probe — 8/8: live sibling resolves + relativizes; deleted sibling resolves + relativizes (the dogfood case); no false positives; main checkout / nested worktree behavior unchanged.

🤖 Generated with Claude Code

resolveMainRepo walked up from a session cwd to the nearest directory .git,
which missed SIBLING worktrees — a checkout beside the main repo (e.g.
`…/<repo>-wt-cli` next to `…/<repo>`), the layout `git worktree add` produces
for a sibling clone. ~29% of sessions on worktree-heavy repos were dropped as
unresolvable_cwd.

resolveRepo now returns RepoResolution { repo, checkoutRoot }. At each ancestor
lacking a .git dir it scans SIBLING dirs for a directory .git and reads each
candidate's .git/worktrees/<name>/gitdir (which git writes as <checkout>/.git —
the trailing /.git is stripped); if a registered checkout is an ancestor of the
cwd, that sibling is the main repo. Paths are best-effort canonicalized (handles
/var ↔ /private/var and deleted cwds). The recovered checkout root is threaded
through runScan and runReflect into relativization, so sibling-worktree files
(absolute, outside the repo prefix) collapse onto the main checkout's areas
instead of fragmenting into absolute-path area keys.

Sandbox contract preserved: production code only stat/readdir/reads tiny gitdir
text files — no git invocation, shell, network, or writes.

Two code-review rounds; the first caught a Critical gitdir-format bug that
synthetic tests had masked (fixed: real <checkout>/.git format + canonicalization
+ relativization threading). 403 tests + a real `git worktree add` probe green.

Closes #3.

Co-Authored-By: Claude <noreply@anthropic.com>
@HumanBean17
HumanBean17 merged commit d1851a9 into main Jul 18, 2026
2 checks passed
@HumanBean17
HumanBean17 deleted the feat/sibling-worktree-recovery branch July 18, 2026 12:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Repo resolution: recover deleted sibling worktrees via git worktree registration

1 participant