feat(kanban): auto-resolve merge-all conflicts with an agent
Some checks failed
CI / Rust Check (push) Has been cancelled
CI / TypeScript Check (docs) (push) Has been cancelled
CI / TypeScript Check (site) (push) Has been cancelled
CI / Security Audit (push) Has been cancelled

When the merge-all batch hits a conflict, spawn a conflict-resolution
agent in the main worktree (where the merge is mid-conflict), await it,
and continue the batch through the remaining branches instead of
aborting for manual resolution.

- worktrees: replace abort-on-conflict mergeBranches with non-aborting
  attemptMerge (leaves the tree mid-merge, returns conflicted paths),
  plus abortMerge/hasMergeInProgress/unmergedPaths/createRecoveryBranch.
  Batch types gain conflict/resolutionRunId/recoveryRef.
- prompt: buildConflictResolutionPrompt resolves both sides preserving
  intent, honors repo conventions (no Rust warning suppression), runs
  cargo/pnpm checks, then completes the merge with a commit.
- runs: mergeAll() is async; per branch it attemptMerges, and on conflict
  starts a streamable resolution run in REPO_ROOT, awaits it, and
  verifies the merge actually completed (no MERGE_HEAD, clean tree) before
  continuing — otherwise aborts and halts. A mergeBatchInProgress guard
  prevents two batches at once; a pre-batch recovery ref snapshots HEAD.
- route: /runs/merge-all is async; node request/socket timeouts disabled
  so a long multi-conflict batch survives.
- UI: per-branch status shows "conflict resolved by agent" + resolution
  run id; a recovery-ref banner documents how to roll the batch back.

Safety: resolution only counts if verified; otherwise the merge is
aborted and main left clean. Resolution runs are real agent_runs (stream
into the dock, stoppable) and own no worktree.
EOF && echo "" && git log --oneline -3
This commit is contained in:
2026-06-17 22:07:49 -04:00
parent d538ccdd4e
commit 607f3fbd8b
8 changed files with 309 additions and 58 deletions

View File

@@ -144,8 +144,18 @@ orchestrator.post('/runs/:id/merge', (c) => {
return c.json(mergeBranch(run.branch));
});
/** Merge all reviewable agent branches into the main branch sequentially. */
orchestrator.post('/runs/merge-all', (c) => c.json(runManager.mergeAll()));
/** Merge all reviewable agent branches into the main branch sequentially,
* spawning a conflict-resolution agent on any conflict. May block for a long
* time while agents resolve conflicts. */
orchestrator.post('/runs/merge-all', async (c) => {
try {
const result = await runManager.mergeAll();
return c.json(result);
} catch (err) {
const message = err instanceof Error ? err.message : 'merge-all failed';
return c.json({ error: message }, 409);
}
});
/** Whether a Bevy playtest is running for a run. */
orchestrator.get('/runs/:id/bevy', (c) => {