Skip to content

Worktree triage panel (RT-271) - #429

Merged
m4ttheweric merged 45 commits into
mainfrom
worktree-triage-spec
Sep 25, 2026
Merged

m4ttheweric merged 45 commits into
mainfrom
worktree-triage-spec

Conversation

@m4ttheweric

Copy link
Copy Markdown
Collaborator

Worktree triage panel

A tray Worktrees… panel for the trees the merge reactor leaves behind. Each row says why the tree is stuck, whether its work is safe somewhere else, and offers one guarded action. The menu item carries a count, and a once-a-day summary notification points you at the panel. Also fixes RT-271: dispose now accepts a branch that was rebased into its merged MR.

Spec: docs/superpowers/specs/2026-09-24-worktree-triage-panel-design.md. Boards: docs/design/worktrees/.

What changed

Daemon (lib/worktree/triage/, lib/daemon/handlers/worktree-triage.ts)

  • worktree:triage (agent-safe, no network) returns rows, banners and counts.
  • Guarded verbs: triage-dispose, keep, unkeep, push-branch, triage-diff, triage-remove, stop-holders. Each verb checks a fingerprint under the tree lock.
  • Containment check: in-default / on-remote / patch-identical. The patch-identical case fails closed on a missing sha, a local merge commit or a patch-id timeout.
  • Dirt classes: rt.worktrees.junk globs and version-only bun.lock changes.
  • Nothing is deleted outside the 14-day trash:
    • disposeTree gains acceptDirty (skips only the dirty guard) and requireRetention (refuses rather than reaping immediately).
    • Remove moves the folder into the trash.
    • Only an only-copy row with a confirm can force.
  • Daily summary sweep at 09:00 local, once per day, category worktree_triage.

CLI

  • rt worktree triage [--repo] [--json].

Tray (rt-tray/Sources/Worktree*, Sources-core/Worktree/Triage.swift)

  • Panel, rows, link chips, interaction states, the Review sheet and the Dispose anyway confirm.
  • Colour tokens are sampled from the boards.
  • POST support in DaemonClient.
  • Menu badge.
  • Notification click opens the panel.
  • --render-worktree-snapshots harness (DEBUG only). Its renders were compared against every board in light and dark before this PR.

Follow-up

  • RT-275 collects the deferred items: pinning force-disposed commits under a ref, a negative fetch cache, two retention holes, mergedAt enrichment, and the stub-rt PATH pollution.
  • Live check after merge: restart the daemon, swap in the dev app, then verify the alert, titlebar refresh, badge and real GitHub rows.

Testing

  • Unit suite: 9574 pass. Its 18 failures are all rt-tray/Tests/stub-rt, caused by earlier tests dropping bun from PATH. That already happens on main and reproduces with branch-untouched files alone.
  • e2e: 142 pass, 1 environmental fail (local mise node shim).
  • pty: 7/7.
  • mattstack-checks: 355/355.
  • tsc: clean.

🤖 Generated with Claude Code

m4ttheweric and others added 30 commits September 24, 2026 13:42
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ec and boards

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…d timeout (RT-271)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… dash (RT-271)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ume test (RT-271)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
classifyDirtForTriage sorts a tree's uncommitted state into none, junk, lockfile or real, so later dispose tasks know what discard: classified may remove.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
pnpm-lock.yaml has no workspace-own version line, so a version-only diff there is always a dependency bump. Drop it from LOCKFILES; bun.lock stays the only lockfile the classifier can call lockfile.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The handler's deps() omitted fetch, so containmentOf defaulted to a real fetch --no-tags origin <sha> (60s timeout) for every stuck merged tree on every poll, including the tray's 10s cycle. Pass a no-network fetch from the handler so containment reads none instead of blocking.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…reap

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…tention and no live cwd

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Registers worktree_triage in NOTIFICATION_TYPES and settings-kit's mirrored NOTIFICATION_EVENTS, keeping notification-shape-parity.test.ts green.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sources/NotificationManager.swift gains the showWorktreePanel case so the exhaustive Route switch there still compiles.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
TriageFingerprint now hand-writes encode(to:) so a nil mrState serializes as JSON null instead of an omitted key. sameFingerprint on the daemon side treats an absent or undefined mrState the same as null, since a decoded payload that skipped the key would otherwise never equal one carrying null and every MR-less row would compare as changed forever.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…nd polls

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
m4ttheweric and others added 12 commits September 24, 2026 17:22
… cap

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…counts

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A failed load no longer reads as no changes, and Commit and push and Discard and dispose stay disabled until a load succeeds. The sheet also notes files past the daemon's 50-file cap, parses each diff once and lays lines out lazily. The snapshot harness exits non-zero on bad arguments or unreadable fixtures.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Also: no leading divider in the row menu, a More label on it, static date formatters, and the menu badge fed straight from the needs-decision count.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…er and PR/MR noun

Fixtures carry the real wire form.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…e unreadable trees

A broken tree is now gone or unlinked, and an unlinked one's verdict says the folder still has files. An incomplete run scan holds the row as waiting on the run.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…pe banners

A forced dispose refuses runs-unreadable, running-run, in-use and cwds-unreadable. triage-remove refuses mount-unavailable when the gitdir's repo root and its parent are both gone. push-branch refuses not-pushable outside only-copy and look. Banners come from repos with claimed trees, as worktree:list's do.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ve trash path

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Sep 24, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 10 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available. Your 83 included PR review attempts over the past 7 days set your current allowance at 1 review per hour.

Your organization has reached its usage spending cap. Adjust your spending cap in the billing tab.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: f925a58e-880c-4a5f-bf09-4e6ecbd137e1

📥 Commits

Reviewing files that changed from the base of the PR and between b983acc and 67d9abf.

⛔ Files ignored due to path filters (10)
  • docs/design/worktrees/entry-points-dark.png is excluded by !**/*.png
  • docs/design/worktrees/entry-points-light.png is excluded by !**/*.png
  • docs/design/worktrees/interaction-states-dark.png is excluded by !**/*.png
  • docs/design/worktrees/interaction-states-light.png is excluded by !**/*.png
  • docs/design/worktrees/panel-dark.png is excluded by !**/*.png
  • docs/design/worktrees/panel-light.png is excluded by !**/*.png
  • docs/design/worktrees/review-sheet-dark.png is excluded by !**/*.png
  • docs/design/worktrees/review-sheet-light.png is excluded by !**/*.png
  • docs/design/worktrees/state-catalog-dark.png is excluded by !**/*.png
  • docs/design/worktrees/state-catalog-light.png is excluded by !**/*.png
📒 Files selected for processing (59)
  • commands/__tests__/worktree.test.ts
  • commands/worktree.ts
  • docs/superpowers/plans/2026-09-24-worktree-triage-panel.md
  • docs/superpowers/specs/2026-09-24-worktree-triage-panel-design.md
  • lib/__tests__/agent-safe.test.ts
  • lib/command-tree-def.ts
  • lib/daemon.ts
  • lib/daemon/__tests__/triage-summary.test.ts
  • lib/daemon/__tests__/worktree-triage-handlers.test.ts
  • lib/daemon/command-router.ts
  • lib/daemon/handlers/worktree-triage.ts
  • lib/daemon/handlers/worktree.ts
  • lib/daemon/reconciler/__tests__/reactor.test.ts
  • lib/daemon/reconciler/__tests__/stale-claims.test.ts
  • lib/daemon/reconciler/reconcile.ts
  • lib/daemon/triage-summary.ts
  • lib/notifier.ts
  • lib/repo-label.ts
  • lib/worktree/__tests__/config.test.ts
  • lib/worktree/__tests__/containment.test.ts
  • lib/worktree/__tests__/dirt-class.test.ts
  • lib/worktree/__tests__/dispose.test.ts
  • lib/worktree/__tests__/trash.test.ts
  • lib/worktree/config.ts
  • lib/worktree/containment.ts
  • lib/worktree/dirt-class.ts
  • lib/worktree/dispose.ts
  • lib/worktree/registry.ts
  • lib/worktree/trash.ts
  • lib/worktree/triage/__tests__/facts.test.ts
  • lib/worktree/triage/__tests__/fingerprint.test.ts
  • lib/worktree/triage/__tests__/verdict.test.ts
  • lib/worktree/triage/facts.ts
  • lib/worktree/triage/fingerprint.ts
  • lib/worktree/triage/verdict.ts
  • packages/rt-client/src/commands.ts
  • packages/rt-client/src/settings/notification-events.ts
  • packages/rt-client/src/settings/registry-defs.ts
  • packages/settings-kit/src/shapes.ts
  • rt-tray/Sources-core/Launch/NotificationClick.swift
  • rt-tray/Sources-core/Worktree/RepoIdentity.swift
  • rt-tray/Sources-core/Worktree/Triage.swift
  • rt-tray/Sources/AccessibilityIDs.swift
  • rt-tray/Sources/AppDelegate.swift
  • rt-tray/Sources/DaemonClient.swift
  • rt-tray/Sources/NotificationManager.swift
  • rt-tray/Sources/WorktreePanelController.swift
  • rt-tray/Sources/WorktreePanelView.swift
  • rt-tray/Sources/WorktreeReviewSheet.swift
  • rt-tray/Sources/WorktreeSnapshot.swift
  • rt-tray/Sources/WorktreeTokens.swift
  • rt-tray/Sources/main.swift
  • rt-tray/Tests/MattstackCoreChecks/AllChecks.swift
  • rt-tray/Tests/MattstackCoreChecks/TriageChecks.swift
  • rt-tray/Tests/fixtures/worktree-triage-catalog.json
  • rt-tray/Tests/fixtures/worktree-triage-diff.json
  • rt-tray/Tests/fixtures/worktree-triage-panel.json
  • website/docs/reference/worktree/index.mdx
  • website/docs/reference/worktree/triage.mdx

Comment @coderabbitai help to get the list of available commands.

m4ttheweric and others added 3 commits September 24, 2026 18:57
Keeps both notification categories (member_joined from main, worktree_triage from this branch) across the notifier, settings-kit and rt-client lists, the tray route and category registry, and the checks registry.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
rename TriageLabels.progressive to inFlightLabel and reword the plan's busy-label prose so the purity gate's banned-term list stays clean; pass childEnv() into the patch-id spawn in containment.ts so spawn-env.test.ts passes

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@m4ttheweric
m4ttheweric merged commit 6485960 into main Sep 25, 2026
6 checks passed
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.

1 participant