Files
ClaudeDo/docs/superpowers/specs/2026-07-24-merge-helper-design.md
T

15 KiB
Raw Blame History

Merge Helper ("Let Claude handle it") — Design

Status: Proposed — awaiting approval Date: 2026-07-24 Scope: Feature — a per-list and global button that opens an interactive ConPTY Claude session pre-loaded with a set of user-selected tasks. The session (the "Merge Helper") drives each selected task to completion and merge autonomously via mcp__claudedo__* tools, asks the user interactively (in the ConPTY terminal) only when uncertain, resolves merge conflicts itself, and ends with a written summary of everything that changed.


1. Goal

Collapse the repetitive per-task review→merge clicking into a single "Let Claude handle it" action. The user picks the tasks; an embedded Claude session babysits them — running the ones that still need running, reviewing diffs, merging the clean ones, resolving conflicts, and reporting back — while remaining fully interactive so the user can answer questions mid-run.

This reuses the existing ConPTY infrastructure (UI-process embedded terminal) and the globally-registered claudedo MCP server. The only genuinely new worker capability is MCP-driven conflict resolution (§5), which today exists only in the UI hub.


2. Decisions (locked with user, 2026-07-24)

Question Decision
Which tasks does the helper handle? Free choice, any status. User hand-picks; helper acts per-status.
How are tasks selected for a run? Checkbox dialog before launch (candidates listed, user ticks).
Merge authority / review-gate Auto-merge; asks interactively on uncertainty. The app's per-task diff-gate is intentionally bypassed for helper-driven merges.
Conflict handling Build MCP conflict tools so the helper resolves conflicts in the working tree itself, asking only when unsure (Option B). The helper must handle all cases including parent/children unit merges; where the MCP path doesn't reach, manual resolution by hand (Edit + git) is an accepted fallback (user-confirmed 2026-07-24).

3. UX Flow

  1. Entry points
    • Per-list: context-menu item "Let Claude handle it" on each user-list row in ListsIslandView.axaml (alongside Settings / Worktrees / Open in Explorer).
    • Global: one entry (footer of the lists island) that spans all lists/repos.
  2. Click opens the Merge Helper selection dialog (§4): a checkbox list of candidate tasks, grouped by list/repo, pre-filtered to tasks worth acting on but freely overridable.
  3. User ticks tasks → "Let Claude handle it" confirm button.
  4. UI asks the worker for a MergeHelperLaunchSpec, opens a ConPTY tile in Mission Control running the real claude TUI with the merge-helper prompt.
  5. The session works through the tasks, printing progress and asking questions inline; the user answers directly in the terminal.
  6. On completion Claude prints a summary (merged / skipped / conflicted / follow-ups). The tile stays open for review.

4. Selection Dialog

New modal MergeHelperSelectionDialog (View + VM), built with the existing TaskCompletionSource<T> dialog pattern used by other modals.

Contents:

  • Title: "Let Claude handle it" + subtitle naming the scope ("List: " or "All lists").
  • A scrollable checkbox list of candidate tasks. Per row: checkbox, title, status badge, list/repo name (in global mode).
  • Grouping: by list/repo in global mode; flat in per-list mode.
  • Default selection: all actionable tasks pre-ticked — actionable = WaitingForReview, Idle, Queued, Failed (resettable). Running / WaitingForChildren shown but unticked (helper will poll them). Terminal Done/Cancelled excluded from the list entirely.
  • Footer: "Let Claude handle it" (disabled when nothing ticked) + Cancel. A "select all / none" affordance.

Candidate source: list_tasks via the existing worker client (per list, or across all lists for global). No new query needed; the VM filters client-side by status.

Output: an ordered IReadOnlyList<string> of selected task IDs (+ their list/repo mapping), passed to the launch request.


5. New Worker Capability — MCP Conflict Resolution

Today (verified): merge_task / review_task approve call TaskMergeService.MergeAsync(..., leaveConflictsInTree:false) — on conflict they run git merge --abort (clean rollback, no markers) and return mergeStatus="conflict"; the task stays WaitingForReview. Continue/abort/write-resolution exist only on the SignalR hub (Rider merge editor). An MCP agent therefore cannot resolve conflicts. This section adds that.

5.1 Approach

Reuse the exact engine methods the UI already uses — TaskMergeService.MergeAsync(leaveConflictsInTree:true), ContinueMergeAsync, AbortMergeAsync — and expose them over MCP. The helper resolves conflict markers on disk (it has filesystem access to the repo checkouts via --add-dir, see §6.3) and drives the merge state exclusively through MCP tools so the engine stays authoritative.

5.2 MCP surface changes (ExternalMcpService.cs)

  1. review_task / merge_task — new optional param leaveConflictsInTree: bool = false. When true and the merge conflicts: leave markers in the checkout instead of aborting, and return { mergeStatus: "conflict_in_tree", conflicts: string[], repoPath: string } where repoPath is the checkout holding the markers. Task stays WaitingForReview, merge is in progress. Clean-merge behaviour is unchanged, so the helper can always pass true.

  2. New tool continue_merge(taskId)TaskMergeService.ContinueMergeAsync. Stages the resolved files and commits the merge; on success the task goes to Done and the worktree is marked merged, returning { merged: true, mergeCommit }. If markers remain, returns { merged: false, conflicts: string[] }.

  3. New tool abort_merge(taskId)TaskMergeService.AbortMergeAsync. Aborts the in-progress merge; task stays WaitingForReview. Returns { aborted: true }.

Implementation notes (verify against TaskMergeService.cs during the plan):

  • Confirm the exact signatures of ContinueMergeAsync / AbortMergeAsync and how in-progress-merge state is keyed. The hub tracks a single active conflict merge; the MCP variants must locate the merge from taskId (target branch + repo from the task/list), not shared hub state.
  • Emit the existing TaskUpdated event after continue/abort so the UI list re-buckets live.
  • Guard against a repo already mid-merge (Blocked) — surface it to the agent rather than clobbering.

5.3 Parent/children unit merges

review_task approve on a task with children drives PlanningMergeOrchestrator (a multi-step unit merge with its own continue/abort on the hub). The helper must handle these too. Two paths, tried in order:

  1. MCP (preferred): continue_merge / abort_merge detect which kind of in-progress merge the task has (single-task TaskMergeService vs orchestrated PlanningMergeOrchestrator) and route to the matching engine continue/abort. This keeps the orchestrated path engine-mediated over MCP too. Implement if the orchestrator's continue/abort can be located from the task without shared hub UI state (verify in A1).
  2. Manual fallback (accepted): where the MCP path genuinely can't reach an in-progress merge, the helper resolves the conflict markers on disk (Read/Edit) and completes the merge by hand (git add <paths> + git commit, or git merge --continue). The user has explicitly accepted hand-merging as a fallback. The system prompt still mandates: prefer the MCP tools whenever they apply; only drop to raw git for cases the MCP tools don't cover, and honour the shared-checkout rule (git commit -- <paths>, never a bare commit that sweeps peers' index).

6. Launch — Worker + Wiring

6.1 Prompt templates

Add PromptKind.MergeHelper (system) and PromptKind.MergeHelperInitial (brief) to ClaudeDo.Data/PromptFiles.cs, with built-in defaults and {{token}} rendering, mirroring Planning / PlanningInitial.

  • System prompt (merge-helper-system.md): defines the role and the per-status algorithm (§7), the merge/conflict rules, the "ask on uncertainty" posture, and the required final summary format.
  • Initial brief (merge-helper-initial.md): rendered with the selected tasks — a table of {id, title, status, list, repo} plus the scope label. Written to a session-brief file on disk; the positional prompt is a single-line kickoff pointing at that file via --add-dir (planning pattern — a multi-line positional prompt truncates at the first newline).

6.2 Session files

Path: ~/.todo-app/merge-helper-sessions/<sessionId>/ (a fresh GUID per run — these sessions are ephemeral and never resumed):

  • brief.md — rendered task list + scope + instructions.
  • No per-session MCP config: the session uses the globally-registered claudedo MCP server (same as task/ad-hoc sessions), so no token is needed.

Cleanup: prune session dirs older than N days on app start (best-effort; same posture as planning dirs).

6.3 Launch spec

New InteractiveLaunchSpecService.BuildForMergeHelper(selectedTaskIds, scope, ct) returning a LaunchSpec:

  • Cwd: per-list → the list's repo working dir; global → the first selected task's repo (any valid repo; the agent works cross-repo via MCP).
  • --add-dir: the session-brief dir plus every distinct repo checkout among the selected tasks (so the agent can read/resolve conflict markers in each repo). Computed from each task's list working dir.
  • Args: --permission-mode default, --allowedTools mcp__claudedo__*,Read,Grep,Glob,Edit,Bash,WebFetch,WebSearch,Skill, --append-system-prompt-file <merge-helper-system.md>, --add-dir ..., then the single-line kickoff prompt.
    • Edit is required for conflict resolution; Bash is allowed for read-only git inspection (git status/diff) — the system prompt mandates that all merge state changes go through MCP tools, never raw git merge/commit, to keep the engine authoritative and honour the user's rejection of the "raw git" option.
  • Env: MCP_TOOL_TIMEOUT=200000 (as task/ad-hoc sessions set).

6.4 Hub + client + Mission Control

  • Hub: WorkerHub.GetMergeHelperLaunchSpec(string[] taskIds, string? listId)_launchSpecService.BuildForMergeHelper(...). Sibling to GetAdHocLaunchSpec / GetPlanningStartLaunchSpec.
  • Client: IWorkerClient.GetMergeHelperLaunchSpecAsync(...) + WorkerClient impl.
  • Mission Control: MissionControlViewModel.OpenMergeHelperConPtySessionAsync(spec) — wraps the spec in a TerminalLaunchDescriptor, creates a ConPtyPaneViewModel (ad-hoc style, never deduped — each run is its own tile), adds it to ConPtySessions.
  • Event plumbing: ListsIslandViewModel raises LetClaudeHandleRequested(scope); IslandsShellViewModel forwards to Mission Control, which opens the selection dialog, then (on confirm) fetches the spec and opens the tile.

7. Helper Behaviour (encoded in the system prompt)

For each selected task, act by status:

  • Idle / Queued: run_task_now; poll get_task until terminal or WaitingForReview.
  • Failed: reset_failed_task then run, or ask the user — failures often need a human call; default to asking briefly.
  • Running / WaitingForChildren: poll get_task until it surfaces for review.
  • WaitingForReview: get_task_diff (stat first, then full if needed), sanity-check the change against the task's intent, then review_task approve with leaveConflictsInTree:true.
    • Clean → merged, task Done.
    • Conflict (conflict_in_tree) → open the conflicted files under repoPath (Read/Edit), resolve the markers guided by both sides' intent, then continue_merge. If the resolution is non-obvious or risky, ask the user in the terminal before continuing. abort_merge if the user declines or it's unsafe.
  • Parent with children: clean unit merge proceeds; on conflict, resolve via the MCP tools if they reach the orchestrated merge, else hand-merge the markers and complete it (§5.3) — asking the user first when the resolution is non-obvious.

Cross-cutting rules (in the prompt):

  • Ask the user interactively for anything ambiguous, risky, or destructive — that is the point of the ConPTY session.
  • Never use raw git merge/commit/reset; drive all merge state through the MCP tools.
  • Keep a running tally; at the end print a summary: per task — final status, merge commit (if any), conflicts resolved, anything skipped, and suggested follow-ups.

8. Testing

Automated (ClaudeDo.Worker.Tests, real SQLite + real git):

  • review_task/merge_task with leaveConflictsInTree:true: clean merge → Done; conflicting merge → conflict_in_tree, markers present in the checkout, task stays WaitingForReview.
  • continue_merge: after markers resolved on disk → commits, task Done, worktree merged; with markers still present → returns remaining conflicts.
  • abort_merge: in-progress merge aborted, markers gone, task stays WaitingForReview.
  • continue_merge/abort_merge on a task with no in-progress merge → clean MCP error, no clobber.
  • TaskUpdated fired after continue/abort.
  • BuildForMergeHelper: computes distinct repo --add-dir set, correct cwd per scope, brief file rendered with all selected tasks, allowed-tools string correct.

No real-Claude tests (per project convention) — the end-to-end ConPTY run is a manual smoke item.

Manual (add to docs/open.md):

  • ConPTY tile launches with the brief; MCP tools reachable; a clean multi-task run merges all and prints a summary.
  • A seeded conflict is resolved autonomously via continue_merge.
  • Interactive question round-trip (helper asks, user answers in terminal).
  • Global (multi-repo) run with --add-dir for each repo.
  • Selection dialog: grouping, default ticks, select-all/none, per-list vs global scope.

9. Phasing

Delivered as one plan with three phases (see the plan doc). Phase A is independently useful and merges first.

  • Phase A — Worker MCP conflict tools (§5): leaveConflictsInTree param + continue_merge + abort_merge + tests. No UI.
  • Phase B — Worker launch (§6.16.4 worker side): prompt templates, BuildForMergeHelper, hub endpoint, session-file/brief generation, client method. Contract for C locked here.
  • Phase C — UI: selection dialog (View+VM), per-list + global entries, event plumbing, OpenMergeHelperConPtySessionAsync.

10. Out of scope (v1)

  • Resuming a merge-helper session (--resume); sessions are ephemeral.
  • A non-interactive/headless merge-helper (this is deliberately a ConPTY interactive session).
  • Cross-list batching semantics beyond "act on each selected task independently."
  • Any change to the existing per-task Approve/diff-gate flow.