diff --git a/docs/superpowers/plans/2026-07-24-merge-helper.md b/docs/superpowers/plans/2026-07-24-merge-helper.md new file mode 100644 index 00000000..e2e0030d --- /dev/null +++ b/docs/superpowers/plans/2026-07-24-merge-helper.md @@ -0,0 +1,78 @@ +# Merge Helper — Implementation Plan + +Spec: `docs/superpowers/specs/2026-07-24-merge-helper-design.md` +Approach: subagent-driven (one subagent per task, `sonnet`, TDD, stage files by path — never `git add -A`). Build with `-c Release` per-csproj (a running Worker locks `Debug`). Commit per task, Conventional Commits. + +--- + +## Phase A — Worker MCP conflict tools + +Independently useful; merges first. All in `src/ClaudeDo.Worker/External/ExternalMcpService.cs` + tests in `tests/ClaudeDo.Worker.Tests/`. + +### A1 — Verify engine surface (spike, no commit) +Read `TaskMergeService.MergeAsync` / `ContinueMergeAsync` / `AbortMergeAsync` and the hub conflict flow (`WorkerHub.StartConflictMerge`/`ContinueConflictMerge`/`AbortConflictMerge`). Pin down: +- exact `ContinueMergeAsync` / `AbortMergeAsync` signatures and how in-progress-merge state is located (repo + target branch from task/list, not shared hub state); +- how the childless approve path (`ApproveAndMergeAsync`) threads `leaveConflictsInTree`. +Record findings in the task notes; feeds A2/A3. + +### A2 — `leaveConflictsInTree` on review_task / merge_task +- TDD: tests in `Worker.Tests` (real git) — clean merge → Done; conflict + flag → `conflict_in_tree`, markers present, task stays `WaitingForReview`, `repoPath` returned. +- Add optional param `leaveConflictsInTree = false` to `MergeTask` and `ReviewTask` (approve branch). When true, call the `leaveConflictsInTree:true` engine path and map the conflict result to `{ mergeStatus/merged, conflicts, repoPath }`. +- Keep default behaviour (abort-on-conflict) byte-identical when the flag is absent/false. +- Commit: `feat(worker): let review_task/merge_task leave conflicts in tree via MCP` + +### A3 — `continue_merge` + `abort_merge` MCP tools +- TDD: continue after on-disk resolution → committed, task Done, worktree merged; continue with markers remaining → returns conflicts; abort → markers gone, task `WaitingForReview`; both on no-active-merge → clean MCP error; `TaskUpdated` fired. +- Add `[McpServerTool] continue_merge(taskId)` → `ContinueMergeAsync`; `abort_merge(taskId)` → `AbortMergeAsync`. Locate the merge from the task's repo/target. Emit `TaskUpdated`. +- Commit: `feat(worker): add continue_merge and abort_merge MCP tools` + +--- + +## Phase B — Worker launch for the merge-helper session + +### B1 — Prompt templates +- Add `PromptKind.MergeHelper` + `PromptKind.MergeHelperInitial` to `ClaudeDo.Data/PromptFiles.cs` (file names `merge-helper-system.md` / `merge-helper-initial.md`, built-in `DefaultFor`, `Render` tokens for the initial brief). +- System prompt encodes §7 behaviour (per-status algorithm, ask-on-uncertainty, MCP-only merge state, summary format). +- Initial brief renders a task table `{id,title,status,list,repo}` + scope label. +- TDD: `PromptFiles` tests — kinds resolve, defaults non-empty, `Render` substitutes brief tokens. +- Commit: `feat(data): add merge-helper prompt templates` + +### B2 — `BuildForMergeHelper` launch spec +- TDD (`Worker.Tests`): distinct-repo `--add-dir` set computed from selected tasks; correct cwd per scope (per-list repo vs first repo global); brief file written to `~/.todo-app/merge-helper-sessions//brief.md`; allowed-tools + `--permission-mode default` + `MCP_TOOL_TIMEOUT` env correct; single-line kickoff points at the brief. +- Implement `InteractiveLaunchSpecService.BuildForMergeHelper(IReadOnlyList taskIds, MergeHelperScope scope, ct)`. Reuse the planning brief-file/kickoff pattern. +- Commit: `feat(worker): build merge-helper interactive launch spec` + +### B3 — Hub endpoint + client method +- `WorkerHub.GetMergeHelperLaunchSpec(string[] taskIds, string? listId)`; `IWorkerClient.GetMergeHelperLaunchSpecAsync(...)` + `WorkerClient` impl. +- Update hand-rolled `IWorkerClient` fakes in **both** test projects (see gotcha memory). +- Commit: `feat(worker): expose merge-helper launch spec over the hub` + +--- + +## Phase C — UI + +### C1 — Selection dialog (View + VM) +- New `MergeHelperSelectionViewModel` + `MergeHelperSelectionDialog.axaml` (compiled bindings, `TaskCompletionSource` pattern). Checkbox rows (title, status badge, list/repo), grouping in global mode, default ticks per §4, select-all/none, confirm disabled when empty. +- Candidates via existing `list_tasks`/worker client; filter client-side. +- TDD (`Ui.Tests`): default-tick logic, empty→confirm-disabled, returns ordered selected IDs + list mapping. +- Commit: `feat(ui): add merge-helper task selection dialog` + +### C2 — Entry points + event plumbing +- Per-list context-menu item **"Let Claude handle it"** in `ListsIslandView.axaml` (user-list rows) + one global entry in the footer. Bind to `LetClaudeHandleCommand` on `ListsIslandViewModel` (param = `ListNavItemViewModel` or a global sentinel). +- VM raises `LetClaudeHandleRequested(MergeHelperScope)`; `IslandsShellViewModel` forwards to Mission Control. +- Commit: `feat(ui): add "Let Claude handle it" entry points` + +### C3 — Mission Control wiring +- `MissionControlViewModel.OpenMergeHelperConPtySessionAsync(scope)`: open selection dialog → on confirm, `GetMergeHelperLaunchSpecAsync` → wrap in `TerminalLaunchDescriptor` → new `ConPtyPaneViewModel` (never deduped) → add to `ConPtySessions`/`Panes`. +- Commit: `feat(ui): open merge-helper ConPTY tile from selection` + +--- + +## Verify (per task + at the end) +- Read each subagent diff; build the touched csproj `-c Release`; run the relevant test project. +- `locales/en.json` + `de.json` parity for any new UI strings (Localization.Tests enforces it). +- Flag visual-verification gaps (dialog layout, tile) for the user — never claim UI works without a run. +- End-to-end ConPTY smoke (real Claude) is a manual item in `docs/open.md`. + +## Commit docs first +`docs(merge-helper): spec + implementation plan` (this file + the spec). diff --git a/docs/superpowers/specs/2026-07-24-merge-helper-design.md b/docs/superpowers/specs/2026-07-24-merge-helper-design.md new file mode 100644 index 00000000..49875857 --- /dev/null +++ b/docs/superpowers/specs/2026-07-24-merge-helper-design.md @@ -0,0 +1,180 @@ +# 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). | + +--- + +## 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` 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` 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 v1 boundary — parent/children unit merges + +`review_task approve` on a task **with children** drives `PlanningMergeOrchestrator` (multi-step unit merge with its own continue/abort). Autonomous MCP conflict resolution for that orchestrated path is **out of scope for v1**: if a unit merge conflicts, the helper reports it and hands off to the UI (as today). Clean unit merges still complete autonomously. Childless single-task merges — the common review/merge case — get full autonomous conflict resolution. This limit is called out to the user, not hidden. + +--- + +## 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//` (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 `, `--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, report and tell the user to resolve in the UI (§5.3). + +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.1–6.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) + +- Autonomous conflict resolution for parent/children **unit** merges (§5.3) — report + UI hand-off only. +- 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.