diff --git a/docs/superpowers/plans/2026-06-05-git-merge-review-foundation-layerA.md b/docs/superpowers/plans/2026-06-05-git-merge-review-foundation-layerA.md new file mode 100644 index 0000000..20c9090 --- /dev/null +++ b/docs/superpowers/plans/2026-06-05-git-merge-review-foundation-layerA.md @@ -0,0 +1,432 @@ +# Git Merge/Review — Shared Foundation + Layer A Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Build the shared worker conflict contract (so parallel Layer B/C sessions branch from frozen interfaces) and rework the Git tab into a single Approve+merge cockpit. + +**Architecture:** Phase 0 adds the conflict-resolution contract to `IWorkerClient`/`WorkerClient` (real `_hub.InvokeAsync` bodies — the worker hub methods are implemented later by Layer C; calls simply fail at runtime until then) plus client-side DTOs and test-fake updates, then commits + pushes so B and C branch from it. Phase A reworks `WorkConsole.axaml`'s Git tab and routes single-task merge/approve conflicts into a `RequestConflictResolution` seam (wired to Layer C's resolver by the integrator at merge time). + +**Tech Stack:** .NET 8, Avalonia 12 (Fluent), CommunityToolkit.Mvvm, SignalR, xUnit. Build individual csproj with `-c Release` (`.slnx` needs .NET 9; a running Worker locks `Debug`). + +**Reference spec:** `docs/superpowers/specs/2026-06-05-git-merge-review-rework-design.md` + +**Note on the canonical diff renderer:** the unified diff model/control already exists — `DiffFileViewModel`/`DiffLineViewModel`/`UnifiedDiffParser` (in `src/ClaudeDo.Ui/ViewModels/Modals/`) rendered by `DiffLinesView` (`src/ClaudeDo.Ui/Views/Controls/DiffLinesView.axaml`). `DiffModalView` and `PlanningDiffView` already use it. So "consolidate diff renderers" for this scope is just verifying that (Task A.3); migrating `WorktreeModalView`'s bespoke diff onto `DiffLinesView` is Layer B's job. + +--- + +## File Structure + +**Phase 0 (foundation — pushed before B/C branch):** +- Modify `src/ClaudeDo.Ui/Services/Interfaces/IWorkerClient.cs` — 5 new method signatures. +- Modify `src/ClaudeDo.Ui/Services/WorkerClient.cs` — 5 `InvokeAsync` bodies + 3 new DTO records. +- Modify `tests/ClaudeDo.Ui.Tests/StubWorkerClient.cs` — 5 new `virtual` no-op methods. +- Modify `tests/ClaudeDo.Worker.Tests/UiVm/TasksIslandViewModelPlanningTests.cs` — 5 new methods on `FakeWorkerClient`. + +**Phase A (Layer A — this session, after foundation commit):** +- Modify `src/ClaudeDo.Ui/ViewModels/Islands/DetailsIslandViewModel.cs` — `RequestConflictResolution` seam; route Approve/Merge conflicts into it. +- Modify `src/ClaudeDo.Ui/Views/Islands/Detail/WorkConsole.axaml` — fuse REVIEW + MERGE sections into one cockpit block. +- Test: `tests/ClaudeDo.Ui.Tests/ViewModels/DetailsIslandPlanningTests.cs` (or a sibling test file in the same folder). + +--- + +## Phase 0 — Shared Foundation + +### Task 0.1: Add the conflict contract (interface + client + DTOs) + +**Files:** +- Modify: `src/ClaudeDo.Ui/Services/Interfaces/IWorkerClient.cs` +- Modify: `src/ClaudeDo.Ui/Services/WorkerClient.cs` + +- [ ] **Step 1: Add the 5 method signatures to `IWorkerClient`** + +In `src/ClaudeDo.Ui/Services/Interfaces/IWorkerClient.cs`, after the existing +`Task CancelReviewAsync(string taskId);` line (line 45), add: + +```csharp + // ── Conflict resolution (worker hub side implemented by Layer C) ── + Task StartConflictMergeAsync(string taskId, string targetBranch); + Task GetMergeConflictsAsync(string taskId); + Task WriteConflictResolutionAsync(string taskId, string path, string resolvedContent); + Task ContinueMergeAsync(string taskId); + Task AbortMergeAsync(string taskId); +``` + +- [ ] **Step 2: Add the 3 DTO records to `WorkerClient.cs`** + +In `src/ClaudeDo.Ui/Services/WorkerClient.cs`, immediately after line 534 +(`public record MergeTargetsDto(...)`), add: + +```csharp +public record MergeConflictsDto(string TaskId, IReadOnlyList Files); +public record ConflictFileDto(string Path, IReadOnlyList Hunks); +public record ConflictHunkDto(string Ours, string Theirs, string? Base); +``` + +- [ ] **Step 3: Add the 5 client method bodies to `WorkerClient.cs`** + +In `src/ClaudeDo.Ui/Services/WorkerClient.cs`, right after the `MergeTaskAsync` +method (ends at line 270), add: + +```csharp + public Task StartConflictMergeAsync(string taskId, string targetBranch) + => _hub.InvokeAsync("StartConflictMerge", taskId, targetBranch); + + public Task GetMergeConflictsAsync(string taskId) + => _hub.InvokeAsync("GetMergeConflicts", taskId); + + public Task WriteConflictResolutionAsync(string taskId, string path, string resolvedContent) + => _hub.InvokeAsync("WriteConflictResolution", taskId, path, resolvedContent); + + public Task ContinueMergeAsync(string taskId) + => _hub.InvokeAsync("ContinueMerge", taskId); + + public Task AbortMergeAsync(string taskId) + => _hub.InvokeAsync("AbortMerge", taskId); +``` + +- [ ] **Step 4: Build the UI project** + +Run: `dotnet build src/ClaudeDo.Ui/ClaudeDo.Ui.csproj -c Release` +Expected: build FAILS — the two test projects won't compile yet, but the UI project +itself should succeed. If the UI project reports "does not implement interface member" +it means a body is missing; fix before continuing. (Test projects are fixed in 0.2.) + +### Task 0.2: Update the hand-rolled test fakes + +**Files:** +- Modify: `tests/ClaudeDo.Ui.Tests/StubWorkerClient.cs` +- Modify: `tests/ClaudeDo.Worker.Tests/UiVm/TasksIslandViewModelPlanningTests.cs` + +- [ ] **Step 1: Add 5 virtual no-ops to `StubWorkerClient`** + +In `tests/ClaudeDo.Ui.Tests/StubWorkerClient.cs`, after the `MergeTaskAsync` override +(line 57), add: + +```csharp + public virtual Task StartConflictMergeAsync(string taskId, string targetBranch) => Task.FromResult(new MergeResultDto("conflict", System.Array.Empty(), null)); + public virtual Task GetMergeConflictsAsync(string taskId) => Task.FromResult(new MergeConflictsDto(taskId, System.Array.Empty())); + public virtual Task WriteConflictResolutionAsync(string taskId, string path, string resolvedContent) => Task.CompletedTask; + public virtual Task ContinueMergeAsync(string taskId) => Task.FromResult(new MergeResultDto("merged", System.Array.Empty(), null)); + public virtual Task AbortMergeAsync(string taskId) => Task.CompletedTask; +``` + +- [ ] **Step 2: Add 5 methods to `FakeWorkerClient`** + +In `tests/ClaudeDo.Worker.Tests/UiVm/TasksIslandViewModelPlanningTests.cs`, after the +`MergeTaskAsync` method (line 47), add: + +```csharp + public Task StartConflictMergeAsync(string taskId, string targetBranch) => Task.FromResult(new MergeResultDto("conflict", System.Array.Empty(), null)); + public Task GetMergeConflictsAsync(string taskId) => Task.FromResult(new MergeConflictsDto(taskId, System.Array.Empty())); + public Task WriteConflictResolutionAsync(string taskId, string path, string resolvedContent) => Task.CompletedTask; + public Task ContinueMergeAsync(string taskId) => Task.FromResult(new MergeResultDto("merged", System.Array.Empty(), null)); + public Task AbortMergeAsync(string taskId) => Task.CompletedTask; +``` + +- [ ] **Step 3: Build both test projects** + +Run: `dotnet build tests/ClaudeDo.Ui.Tests/ClaudeDo.Ui.Tests.csproj -c Release && dotnet build tests/ClaudeDo.Worker.Tests/ClaudeDo.Worker.Tests.csproj -c Release` +Expected: both BUILD succeed. + +- [ ] **Step 4: Run the UI test suite to confirm green baseline** + +Run: `dotnet test tests/ClaudeDo.Ui.Tests/ClaudeDo.Ui.Tests.csproj -c Release` +Expected: PASS (no behavior changed yet). + +### Task 0.3: Commit and push the foundation + +- [ ] **Step 1: Commit** + +```bash +git add src/ClaudeDo.Ui/Services/Interfaces/IWorkerClient.cs src/ClaudeDo.Ui/Services/WorkerClient.cs tests/ClaudeDo.Ui.Tests/StubWorkerClient.cs tests/ClaudeDo.Worker.Tests/UiVm/TasksIslandViewModelPlanningTests.cs +git commit -m "feat(ui): add conflict-resolution worker contract (foundation for merge rework)" +``` + +- [ ] **Step 2: Push so Layer B/C can branch from this commit** + +Run: `git push` +Expected: pushed to `main`. (First push to git.kuns.dev may fail auth — retry once.) +**This commit is the branch point for the Layer B and Layer C kickoff prompts.** + +--- + +## Phase A — Layer A Review/Merge Cockpit + +### Task A.1: Conflict-resolution seam + route Approve/Merge conflicts into it (TDD) + +**Files:** +- Modify: `src/ClaudeDo.Ui/ViewModels/Islands/DetailsIslandViewModel.cs` +- Test: `tests/ClaudeDo.Ui.Tests/ViewModels/DetailsIslandConflictSeamTests.cs` (new) + +- [ ] **Step 1: Write the failing test** + +Create `tests/ClaudeDo.Ui.Tests/ViewModels/DetailsIslandConflictSeamTests.cs`. Mirror +the VM-construction harness used in +`tests/ClaudeDo.Ui.Tests/ViewModels/DetailsIslandPlanningTests.cs` (same folder) — +construct `DetailsIslandViewModel` exactly as that file does, including its +`StubWorkerClient` subclass pattern. The test: + +```csharp +[Fact] +public async Task ApproveReview_OnConflict_InvokesConflictResolutionSeam() +{ + string? resolvedTaskId = null; + string? resolvedTarget = null; + + // Construct the VM as in DetailsIslandPlanningTests, with a worker stub whose + // ApproveReviewAsync returns a conflict result: + // public override Task ApproveReviewAsync(string id, string target) + // => Task.FromResult(new MergeResultDto("conflict", new[]{"a.cs"}, null)); + var vm = CreateVm(/* worker stub above */); + vm.RequestConflictResolution = (taskId, target) => + { + resolvedTaskId = taskId; resolvedTarget = target; + return System.Threading.Tasks.Task.CompletedTask; + }; + // assign a task in WaitingForReview + a SelectedMergeTarget = "main" via the same + // helpers DetailsIslandPlanningTests uses. + + await vm.ApproveReviewCommand.ExecuteAsync(null); + + Assert.Equal(/* the seeded task id */, resolvedTaskId); + Assert.Equal("main", resolvedTarget); +} +``` + +- [ ] **Step 2: Run the test to verify it fails** + +Run: `dotnet test tests/ClaudeDo.Ui.Tests/ClaudeDo.Ui.Tests.csproj -c Release --filter ApproveReview_OnConflict_InvokesConflictResolutionSeam` +Expected: FAIL — `RequestConflictResolution` property does not exist (compile error). + +- [ ] **Step 3: Add the seam property** + +In `src/ClaudeDo.Ui/ViewModels/Islands/DetailsIslandViewModel.cs`, beside the other +view-wired delegates (`ShowDiffModal`, `ShowMergeModal` around line 387–390), add: + +```csharp + // Invoked when a single-task merge/approve hits a conflict. Wired by the + // integrator to Layer C's conflict resolver. Args: (taskId, targetBranch). + public Func? RequestConflictResolution { get; set; } +``` + +- [ ] **Step 4: Route the Approve conflict branch into the seam** + +In `ApproveReviewAsync` (around line 1453), replace the conflict branch body so it +prefers the seam, falling back to the current preview-text behavior: + +```csharp + var result = await _worker.ApproveReviewAsync(Task.Id, SelectedMergeTarget ?? ""); + if (result?.Status == "conflict") + { + if (RequestConflictResolution is not null) + { + await RequestConflictResolution(Task.Id, SelectedMergeTarget ?? ""); + } + else + { + var (text, _, _) = MergePreviewPresenter.Describe( + new MergePreviewDto("conflict", result.ConflictFiles, 0)); + MergePreviewText = text; MergeIsClean = false; MergeIsConflict = true; + } + } +``` + +- [ ] **Step 5: Route the manual Merge conflict branch into the seam** + +In `MergeAsync` (around line 1170), apply the same pattern to its conflict branch: + +```csharp + var result = await _worker.MergeTaskAsync(Task.Id, SelectedMergeTarget ?? "", false, "Merge task"); + if (result.Status == "conflict") + { + if (RequestConflictResolution is not null) + { + await RequestConflictResolution(Task.Id, SelectedMergeTarget ?? ""); + } + else + { + var (text, _, _) = MergePreviewPresenter.Describe( + new MergePreviewDto("conflict", result.ConflictFiles, 0)); + MergePreviewText = text; MergeIsClean = false; MergeIsConflict = true; + } + } + else + { + await RefreshMergePreviewAsync(); + } +``` + +- [ ] **Step 6: Run the test to verify it passes** + +Run: `dotnet test tests/ClaudeDo.Ui.Tests/ClaudeDo.Ui.Tests.csproj -c Release --filter ApproveReview_OnConflict_InvokesConflictResolutionSeam` +Expected: PASS. + +- [ ] **Step 7: Run the full UI suite (no regressions)** + +Run: `dotnet test tests/ClaudeDo.Ui.Tests/ClaudeDo.Ui.Tests.csproj -c Release` +Expected: PASS. + +- [ ] **Step 8: Commit** + +```bash +git add src/ClaudeDo.Ui/ViewModels/Islands/DetailsIslandViewModel.cs tests/ClaudeDo.Ui.Tests/ViewModels/DetailsIslandConflictSeamTests.cs +git commit -m "feat(ui): route single-task merge conflicts into a resolution seam" +``` + +### Task A.2: Fuse the Git tab into one Approve+merge cockpit + +**Files:** +- Modify: `src/ClaudeDo.Ui/Views/Islands/Detail/WorkConsole.axaml` + +- [ ] **Step 1: Replace the two Git-tab sections with one cockpit block** + +In `src/ClaudeDo.Ui/Views/Islands/Detail/WorkConsole.axaml`, replace the entire Git +`ScrollViewer` body (lines 255–313 — the `` block containing the +separate `REVIEW` `StackPanel` and the `MERGE & WORKTREE` `StackPanel`) with a single +cockpit where Approve sits with the merge target/preview/actions. Keep the existing +control class names (`section-label`, `field-label`, `btn`, `btn accent`, `meta`) and +the existing bindings (`SelectedMergeTarget`, `MergeTargetBranches`, `MergePreviewText`, +`MergeIsClean`, `MergeIsConflict`, `ShowMergePreviewMuted`, `OpenDiffCommand`, +`ApproveReviewCommand`, `MergeCommand`, `ShowSingleMerge`, `OpenWorktreeCommand`, +`ReviewCombinedDiffCommand`, `MergeAllCommand`, `CanMergeAll`, `MergeAllDisabledReason`, +`MergeAllError`): + +```xml + + + + + + + + + + + + + + + + + + + +