feat: wire up AI conflict resolution for parallel execution#283
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
|
Caution Review failedThe pull request is closed. WalkthroughAdds an AI-driven conflict-resolution feature: new AI resolver module (fast-path, prompt, agent invocation, output parsing), config/schema/types, ParallelExecutor wiring and pending-conflict retry/skip, TUI plumbing and keyboard actions, tracker serialization, tests, and testing fixtures/scripts. Changes
Sequence Diagram(s)sequenceDiagram
participant UI as UI / run.tsx
participant PE as ParallelExecutor
participant CR as ConflictResolver
participant AR as AI Resolver
participant Agent as Agent Process
UI->>PE: setAiResolver(createAiResolver(config))
PE->>PE: store AI resolver callback
rect rgba(100,150,200,0.5)
Note over PE,CR: ParallelExecutor detects merge conflict
PE->>CR: report conflict (file, base, ours, theirs, task ctx)
end
rect rgba(150,200,100,0.5)
CR->>AR: request resolution(conflict, context)
AR->>AR: tryFastPathResolution(conflict)
alt Fast-path applies
AR-->>CR: resolved content (strategy: fast-path)
else Fast-path fails
AR->>AR: buildConflictPrompt(conflict, ctx)
AR->>Agent: spawn agent with prompt + timeout
Agent->>Agent: LLM processes prompt
Agent-->>AR: stdout / exit status
AR->>AR: extractResolvedContent(stdout)
AR-->>CR: resolved content or null (strategy: ai)
end
end
rect rgba(200,150,100,0.5)
CR->>PE: apply resolution / emit events / continue execution
PE->>UI: emit conflict:ai-resolving / ai-resolved / ai-failed events (include strategy)
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 inconclusive)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #283 +/- ##
==========================================
- Coverage 43.20% 42.98% -0.23%
==========================================
Files 94 95 +1
Lines 29607 29974 +367
==========================================
+ Hits 12792 12883 +91
- Misses 16815 17091 +276
🚀 New features to boost your workflow:
|
c7dd781 to
59591ff
Compare
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Fix all issues with AI agents
In `@docs/plans/2026-02-04-ai-conflict-resolution-design.md`:
- Around line 49-59: The docs declare fields on ConflictResolutionConfig
(notably confidenceThreshold and strategy) that the implementation doesn't
expose; update either the code or the docs: either add the missing optional
properties (confidenceThreshold: number, strategy: string|enum, with defaults
and used by functions like whatever resolves AI decisions) to the implementation
where ConflictResolutionConfig is defined and consumed, or remove/mark them as
TODO/futureWork in the doc and ensure the doc comment lines 49–59 and the other
referenced section (219–230) consistently reflect the actual implemented shape
and defaults; reference the ConflictResolutionConfig type and any AI
resolution/decision logic functions to wire or document these fields.
- Around line 20-41: The fenced diagram block in the docs lacks a language tag
and violates MD040; update the opening fence for that block to include a
language tag (e.g., "text") so the block is ```text ... ```; locate the block
near references like run.tsx,
parallelExecutor.setAiResolver(createAiResolver(config)),
createAiResolver(config) → AiResolverCallback, and
ConflictResolver/resolveFile() and change the fence accordingly.
In `@src/parallel/ai-resolver.test.ts`:
- Line 6: Remove the unused `mock` import from the test module import list:
update the import statement that currently reads "import { describe, expect,
test, mock } from 'bun:test';" to only import the symbols actually used
(`describe`, `expect`, `test`) so the `mock` identifier is no longer imported
and TS6133 is resolved.
🧹 Nitpick comments (2)
src/parallel/ai-resolver.test.ts (1)
93-105: Test name doesn’t match the expected value.
The test asserts an empty string, not null — consider renaming for clarity.✏️ Suggested rename
- test('returns null when both sides are empty', () => { + test('returns empty string when both sides are empty', () => {src/parallel/ai-resolver.ts (1)
135-147: Consider handling fenced blocks that aren’t the entire output.
If the agent adds a preamble plus a fenced block, the current regex won’t strip the fence and the preamble will be written into the file. A more defensive extraction would prefer the first fenced block when present.🛡️ Suggested adjustment
- const fenceMatch = content.match(/^```[\w]*\n([\s\S]*)\n```$/); - if (fenceMatch) { - content = fenceMatch[1] ?? content; - } + const fenceMatch = content.match(/```[\w]*\n([\s\S]*?)\n```/); + if (fenceMatch) { + const fenced = fenceMatch[1]?.trim() ?? ''; + return fenced.length > 0 ? fenced : null; + }
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@testing/TESTING.md`:
- Around line 272-278: The fenced ASCII diagram block containing the
CONFLICT-001 / CONFLICT-002 / CONFLICT-003 lines is missing a language specifier
which triggers markdownlint MD040; update the fenced code block that surrounds
the CONFLICT-001 ─────┐ ... CONFLICT-003 ─────┘ diagram to include a language
tag such as text or plaintext (e.g., change the opening triple backticks to
```text) so the linter recognizes it as non-executable content while preserving
the diagram.
🧹 Nitpick comments (1)
testing/reset-test.sh (1)
48-54: Consider copying conflict PRD during reset even if absent.The current logic only resets
test-conflict-prd.jsonif it already exists in the workspace. This is intentional per the documentation (manual copy required), but creates an asymmetry: the standard PRD is always copied, whilst the conflict PRD requires prior manual setup.If the intent is to keep conflict testing opt-in, this is fine. Otherwise, consider mirroring the standard PRD logic to always copy if the source exists:
♻️ Optional: Always copy conflict PRD if source exists
-# Also reset conflict PRD if it exists in workspace -if [ -f "$TEST_WORKSPACE/test-conflict-prd.json" ]; then - if [ -f "$SCRIPT_DIR/test-conflict-prd.json" ]; then - cp "$SCRIPT_DIR/test-conflict-prd.json" "$TEST_WORKSPACE/test-conflict-prd.json" - echo -e "${GREEN} Re-copied test-conflict-prd.json from source${NC}" - fi -fi +# Also reset conflict PRD if source exists +if [ -f "$SCRIPT_DIR/test-conflict-prd.json" ]; then + cp "$SCRIPT_DIR/test-conflict-prd.json" "$TEST_WORKSPACE/test-conflict-prd.json" + echo -e "${GREEN} Re-copied test-conflict-prd.json from source${NC}" +fi
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@src/tui/components/ConflictResolutionPanel.tsx`:
- Around line 171-180: The panel shows AI-specific copy regardless of whether AI
conflict resolution is enabled; add a boolean prop aiEnabled to
ConflictResolutionPanelProps and use it inside the ConflictResolutionPanel
component to conditionally render the "AI resolution runs automatically. Merge
completes when all files resolve." text only when aiEnabled is true; update all
callers (notably the places that render ConflictResolutionPanel in RunApp.tsx)
to pass the config flag conflictResolution.enabled into the new aiEnabled prop
so behavior matches configuration.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@src/tui/components/ConflictResolutionPanel.tsx`:
- Around line 31-71: When handling the parallel event "conflict:ai-failed"
update the component state to record a failed ConflictResolutionResult for the
target file (instead of only flipping aiResolving) so
getResolutionState(conflicts, resolutions, aiResolving) will detect the failure;
specifically, in the parallel event handler that currently sets aiResolving,
also add or update an entry in the resolutions array with { filePath: <the
conflict filePath>, success: false, /* optional error/message field */ } (or set
an explicit failure flag tied to that file and thread it into
getResolutionState), then set aiResolving = false so the UI shows the retry/skip
controls.
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@src/commands/run.tsx`:
- Around line 917-918: The comment for the property
parallelCurrentlyResolvingFile contains temporal wording—update it (and the
similar comments around the other occurrence) to an evergreen phrasing without
temporal qualifiers; locate the property parallelCurrentlyResolvingFile and
replace its comment "The file currently being resolved by AI" (and the matching
comment near the other occurrence) with a neutral form such as "The file being
resolved by AI" or "File being resolved by AI".
In `@src/tui/components/ConflictResolutionPanel.tsx`:
- Around line 35-36: Update the JSDoc comment for the currentlyResolvingFile
property in ConflictResolutionPanel.tsx to remove temporal language; replace
"The file currently being resolved by AI" with a timeless phrase such as "The
file being resolved by AI" (or similar) so the comment is evergreen while still
describing the currentlyResolvingFile field.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@src/parallel/index.ts`:
- Around line 512-532: The success branch currently always clears
this.pendingConflictOperation and this.pendingConflictWorkerResult, which drops
earlier failed conflicts; instead only clear those fields if they are null or
refer to the same conflict being completed. Update the allResolved path in the
loop so that before setting this.pendingConflictOperation = null and
this.pendingConflictWorkerResult = null you check whether the existing
pendingConflictWorkerResult is either null or has the same identifier as the
current workerResult (e.g., compare workerResult.task.id); only clear them in
that case, otherwise leave the existing pending failure alone so it can be
retried/skipped later. Ensure the rest of the success logic (await
this.tracker.completeTask(...), await this.mergeProgressFile(workerResult),
totals increment) remains unchanged.
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/tui/components/RunApp.tsx (1)
1497-1525:⚠️ Potential issue | 🔴 CriticalFailed AI resolutions are not tracked in the state checked by retry/skip shortcuts.
The
conflict:ai-failedevent handler does not append a failed result toparallelState.conflictResolutions, unlike theconflict:ai-resolvedhandler. This meanshasFailureswill not detect failures from the AI resolution path since it only checks items inparallelConflictResolutions. The resolver returns aConflictResolutionResultwithsuccess: falseon failure, but this result never reaches the array the UI evaluates.Append the failed result to
conflictResolutionswhenconflict:ai-failedis emitted, or track failures through an explicit state flag.Also, reword the inline comment "has failed resolutions and not currently resolving" to avoid temporal language — use "has failed resolutions and resolution is not in progress" or similar evergreen phrasing.
🤖 Fix all issues with AI agents
In `@src/commands/run.tsx`:
- Around line 1837-1862: onConflictRetry currently sets
parallelState.aiResolving = true and awaits
parallelExecutor.retryConflictResolution() without error handling, which can
leave aiResolving true on exceptions; wrap the await in a try/catch/finally
inside the onConflictRetry handler (referencing onConflictRetry,
parallelState.aiResolving, parallelExecutor.retryConflictResolution, and
triggerRerender) so that failures are caught (log or surface the error
appropriately) and the finally block always resets parallelState.aiResolving =
false and calls triggerRerender?.() to ensure the UI is not left stuck; also
ensure the handler returns a rejected or resolved Promise consistently so
callers that don't await it don't produce unhandled rejections.
In `@src/tui/components/RunApp.tsx`:
- Around line 179-183: Update the temporal comments to state-based descriptions:
replace "The file currently being resolved by AI" with a neutral, state-focused
phrase for parallelCurrentlyResolvingFile (e.g., "The file being resolved by AI
in parallel mode"), change "Whether to show the conflict panel (true during
Phase 2 conflict resolution)" to a state-based comment for parallelShowConflicts
(e.g., "Whether the conflict panel is shown for conflict resolution"), and
similarly revise any other temporal comments around the parallel-mode logic
(including the occurrences noted near the parallel mode code block) so all
comments describe state rather than time-based conditions.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@src/parallel/index.ts`:
- Around line 157-183: In retryConflictResolution, preserve the tracker's
in-memory/persistent state before re-running resolution to avoid stale state
(prd.json) overwriting main state: capture the current tracker state (e.g., via
a snapshot/getState method or serialization), then run the
resolution/completeTask/mergeProgressFile steps, and in a finally block restore
the saved tracker state (e.g., setState/restore) so the tracker is returned to
its prior state if the retry mutates it; update retryConflictResolution
(referencing pendingConflictOperation, pendingConflictWorkerResult,
this.tracker.completeTask, and this.mergeProgressFile) to perform save → attempt
resolution/complete/merge → restore.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@src/parallel/index.ts`:
- Around line 472-480: Wrap the tracker save/restore around merge and conflict
resolution in try/finally to guarantee restore on errors: when you call
this.saveTrackerState() before this.mergeEngine.enqueue(result) /
this.mergeEngine.processNext(), immediately follow with a try { await
this.mergeEngine.processNext(); } finally { await
this.restoreTrackerState(savedState); } and do the same pattern for the block
that calls this.saveTrackerState() before conflictResolver.resolveConflicts()
(ensure you reference saveTrackerState, restoreTrackerState,
mergeEngine.processNext, and conflictResolver.resolveConflicts so
restoreTrackerState(savedState) always runs even if processing throws).
The conflict resolution infrastructure existed but was never connected. This commit wires up actual AI-powered resolution using the session's configured agent. Key changes: - New ai-resolver.ts module with fast-path heuristics and LLM resolution - Fast-path: skip AI when one side is empty or both sides identical - Uses same agent as session (works on remote nodes) - Prompt includes task context so AI understands worker's intent - Config options: enabled, timeoutMs, maxFiles in conflictResolution Fixes #275
Add test-conflict-prd.json specifically designed to trigger merge conflicts during parallel execution. Three parallel tasks all modify the same FEATURES.md file, forcing AI conflict resolution. Update TESTING.md with instructions for testing conflict resolution and update reset-test.sh to clean up conflict test outputs.
…mmands - setup-test-workspace.sh now copies test-conflict-prd.json - Both scripts now display commands for running either test - Updated workspace README to document both test scenarios
The panel was displaying shortcuts (a, r, A) that were never implemented in the keyboard handler. Updated to show only the working controls: - j/↓ and k/↑ for navigation - Esc to close panel Added explanatory text clarifying that AI resolution runs automatically.
When AI conflict resolution fails, users now have actionable options: - Press 'r' to retry AI resolution - Press 's' to skip this task's merge and continue - Press 'Esc' to close the panel Added ParallelExecutor methods: - retryConflictResolution() - re-attempts AI resolution - skipFailedConflict() - abandons the failed merge - hasPendingConflict() - checks for pending conflicts Updated ConflictResolutionPanel to show different controls based on state: - In-progress: shows navigation only - Has failures: shows retry/skip/abort options with explanatory text
When multiple workers run in parallel, their ExecutionEngines share the same tracker instance. Each engine was calling completeTask() when its agent completed, causing concurrent writes to the PRD file that would overwrite each other. This fix skips the tracker update in worker mode (forcedTask set). The ParallelExecutor is now the sole caller of completeTask() after a successful merge, ensuring sequential and consistent PRD updates. Bug: tasks showing passes: false even though work was merged Root cause: concurrent completeTask calls from parallel workers Fix: defer tracker update to ParallelExecutor after merge Fixes #275
1. Add write lock to JSON tracker to prevent race conditions - Multiple completeTask calls no longer overwrite each other - Uses promise-based mutex to serialize read-modify-write operations - Invalidates cache before each write to ensure fresh reads 2. Improve conflict resolution panel UX - Add animated spinner (⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏) when AI is resolving - Show which file is currently being resolved - Fixed-height file list with scroll indicators (max 10 visible) - Per-file status: resolved, resolving, pending, failed - Progress display: "2/5 resolved · 1 pending" 3. Track currently resolving file - New `currentlyResolvingFile` state in parallel executor - Events track which file AI is working on Fixes #275
- Refactored ParallelExecutor to use two-phase merge strategy: Phase 1: Attempt all merges, collect conflicts Phase 2: Resolve all collected conflicts after merges complete - Simplified conflict resolution UI animation: - Removed spinner from header (now static indicator) - Animation only appears next to file being resolved - This fixes the issue where conflict resolution panel appeared mid-batch instead of at the end
- Added parallelShowConflicts flag to control panel visibility - Set showConflicts=true only when conflict:ai-resolving event fires (Phase 2) - Set showConflicts=false when conflict:resolved event fires - Updated useEffect to require both parallelShowConflicts and conflicts - Panel no longer appears prematurely during Phase 1 merge detection
Three interconnected bugs in parallel conflict resolution: 1. PRD file overwritten by stale worktree copies during merge - Added getStateFiles() to TrackerPlugin interface - ParallelExecutor saves/restores tracker state around merges - JSON tracker implements getStateFiles() and clearCache() 2. Wrong conflict list shown in Phase 2 resolution - ConflictResolver now emits conflict:detected at start of resolution - Ensures UI shows correct files for each task being resolved 3. UI state not updated after successful conflict resolution - conflict:resolved handler now moves task to mergedTaskIds - Operation status updated from 'conflicted' to 'completed' - Merge queue refreshed to reflect new status
1. Add tracker state save/restore to retryConflictResolution() - Prevents stale worktree state from overwriting tracker data during retry 2. Add try/catch/finally to onConflictRetry handler - Ensures aiResolving is always reset even on exceptions - Removes redundant manual state clearing (handled by events) 3. Only clear pendingConflictOperation if it refers to same conflict - Prevents dropping earlier failed conflicts when later ones succeed 4. Show keyboard shortcuts only when handlers provided - ConflictResolutionPanel now conditionally renders r/s shortcuts 5. Fix docs (MD040 lint, mark unimplemented features as TODO) - Added language tag to diagram code block - Marked confidenceThreshold and strategy as future work
5a5969a to
1d9fd12
Compare
Ensures restoreTrackerState() always runs even if mergeEngine.processNext() or conflictResolver.resolveConflicts() throws an error.
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/tui/components/RunApp.tsx (1)
1497-1527:⚠️ Potential issue | 🟠 Major
handleKeyboarddependency list is missing new conflict-resolution inputs.
handleKeyboardreadsparallelAiResolving,parallelConflictResolutions,onConflictRetry, andonConflictSkip, but they are not in the dependency array. This creates stale closures that prevent the conflict resolution handlers ('r' and 's' keys) from responding correctly.Suggested fix
- [displayedTasks, selectedIndex, status, engine, onQuit, viewMode, iterations, iterationSelectedIndex, iterationHistoryLength, onIterationDrillDown, showInterruptDialog, onInterruptConfirm, onInterruptCancel, showHelp, showSettings, showQuitDialog, showKillDialog, showEpicLoader, showRemoteManagement, onStart, storedConfig, onSaveSettings, onLoadEpics, subagentDetailLevel, onSubagentPanelVisibilityChange, currentIteration, maxIterations, renderer, detailsViewMode, subagentPanelVisible, focusedPane, navigateSubagentTree, instanceTabs, selectedTabIndex, onSelectTab, isViewingRemote, displayStatus, instanceManager, isParallelMode, parallelWorkers, parallelConflicts, showConflictPanel, onParallelKill, onParallelPause, onParallelResume, onParallelStart, parallelDerivedStatus] + [displayedTasks, selectedIndex, status, engine, onQuit, viewMode, iterations, iterationSelectedIndex, iterationHistoryLength, onIterationDrillDown, showInterruptDialog, onInterruptConfirm, onInterruptCancel, showHelp, showSettings, showQuitDialog, showKillDialog, showEpicLoader, showRemoteManagement, onStart, storedConfig, onSaveSettings, onLoadEpics, subagentDetailLevel, onSubagentPanelVisibilityChange, currentIteration, maxIterations, renderer, detailsViewMode, subagentPanelVisible, focusedPane, navigateSubagentTree, instanceTabs, selectedTabIndex, onSelectTab, isViewingRemote, displayStatus, instanceManager, isParallelMode, parallelWorkers, parallelConflicts, parallelConflictResolutions, parallelAiResolving, showConflictPanel, onConflictRetry, onConflictSkip, onParallelKill, onParallelPause, onParallelResume, onParallelStart, parallelDerivedStatus]
🤖 Fix all issues with AI agents
In `@src/commands/run.tsx`:
- Line 1571: Update the inline comment for the showConflicts property (near
showConflicts: false in src/commands/run.tsx) to remove time‑bound phrasing and
instead describe its behavior and intent; e.g., state that it controls whether
unresolved conflicts are shown (true = show unresolved conflicts, false = hide
when there are none or after they are resolved) so the comment is evergreen and
describes state rather than timing.
- Around line 983-984: The comment for the property parallelShowConflicts
contains temporal wording ("during Phase 2"); update it to be evergreen by
removing the time-bound phrase and describing the intent instead — e.g., change
the JSDoc for parallelShowConflicts to: "Whether to show the conflict panel
(true while resolving conflicts)" or "Whether to show the conflict panel for
conflict resolution" so it documents behavior without referencing phases or
time.
In `@src/parallel/index.ts`:
- Around line 165-195: The finally block calls restoreTrackerState(savedState)
while completion actions (tracker.completeTask, mergeProgressFile, clearing
pendingConflictOperation/pendingConflictWorkerResult, and incrementing
totalConflictsResolved/totalMergesCompleted) run before it, which can let
restoreTrackerState overwrite the completion; change the flow in the method
containing saveTrackerState/restoreTrackerState and
conflictResolver.resolveConflicts so that restoreTrackerState(savedState) is
always called in the finally and all post-success completion work (clear
pendingConflictOperation and pendingConflictWorkerResult, await
tracker.completeTask(workerResult.task.id) with try/catch, await
mergeProgressFile(workerResult), and increment totalConflictsResolved and
totalMergesCompleted) runs after the finally (i.e., after restore) only when
allResolved is true, following the Phase 2 resolution pattern used elsewhere.
🧹 Nitpick comments (2)
testing/TESTING.md (1)
249-249: Consider rewording to avoid split infinitive.The phrase "to intelligently merge" contains a split infinitive. Whilst this is acceptable in modern English, you may prefer "to merge the content intelligently" for a more traditional style.
✍️ Alternative wording
-4. The AI resolver is invoked to intelligently merge the content +4. The AI resolver is invoked to merge the content intelligentlysrc/parallel/ai-resolver.test.ts (1)
93-105: Test name mismatches expected behaviour.
The test says it returns null, but the assertion expects an empty string. Consider renaming for clarity.💡 Suggested fix
- test('returns null when both sides are empty', () => { + test('returns empty string when both sides are empty', () => {
| /** Whether to show the conflict panel (true during Phase 2 conflict resolution) */ | ||
| parallelShowConflicts?: boolean; |
There was a problem hiding this comment.
Remove temporal wording from the conflict panel comment.
“During Phase 2” is time‑bound; keep the comment evergreen.
As per coding guidelines, avoid temporal context in comments; make them evergreen.
Proposed change
- /** Whether to show the conflict panel (true during Phase 2 conflict resolution) */
+ /** Whether to show the conflict panel */📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| /** Whether to show the conflict panel (true during Phase 2 conflict resolution) */ | |
| parallelShowConflicts?: boolean; | |
| /** Whether to show the conflict panel */ | |
| parallelShowConflicts?: boolean; |
🤖 Prompt for AI Agents
In `@src/commands/run.tsx` around lines 983 - 984, The comment for the property
parallelShowConflicts contains temporal wording ("during Phase 2"); update it to
be evergreen by removing the time-bound phrase and describing the intent instead
— e.g., change the JSDoc for parallelShowConflicts to: "Whether to show the
conflict panel (true while resolving conflicts)" or "Whether to show the
conflict panel for conflict resolution" so it documents behavior without
referencing phases or time.
| /** The file currently being resolved by AI */ | ||
| currentlyResolvingFile: '' as string, | ||
| /** Whether to show the conflict panel (set true at Phase 2 start, false when resolved) */ | ||
| showConflicts: false, |
There was a problem hiding this comment.
Avoid time‑bound phrasing in the showConflicts comment.
“Set true at Phase 2 start, false when resolved” is temporal; keep this evergreen.
As per coding guidelines, avoid temporal context in comments; make them evergreen.
Proposed change
- /** Whether to show the conflict panel (set true at Phase 2 start, false when resolved) */
+ /** Whether to show the conflict panel */📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| showConflicts: false, | |
| /** Whether to show the conflict panel */ | |
| showConflicts: false, |
🤖 Prompt for AI Agents
In `@src/commands/run.tsx` at line 1571, Update the inline comment for the
showConflicts property (near showConflicts: false in src/commands/run.tsx) to
remove time‑bound phrasing and instead describe its behavior and intent; e.g.,
state that it controls whether unresolved conflicts are shown (true = show
unresolved conflicts, false = hide when there are none or after they are
resolved) so the comment is evergreen and describes state rather than timing.
| // Save tracker state before resolution to prevent stale worktree state from overwriting | ||
| const savedState = await this.saveTrackerState(); | ||
|
|
||
| try { | ||
| // Re-attempt resolution | ||
| const resolutions = await this.conflictResolver.resolveConflicts(operation); | ||
| const allResolved = resolutions.every((r) => r.success); | ||
|
|
||
| if (allResolved) { | ||
| // Success! Clear pending state and mark task as complete | ||
| this.pendingConflictOperation = null; | ||
| this.pendingConflictWorkerResult = null; | ||
|
|
||
| try { | ||
| await this.tracker.completeTask(workerResult.task.id); | ||
| } catch { | ||
| // Log but don't fail after successful resolution | ||
| } | ||
|
|
||
| await this.mergeProgressFile(workerResult); | ||
| this.totalConflictsResolved += resolutions.length; | ||
| this.totalMergesCompleted++; | ||
| return true; | ||
| } | ||
|
|
||
| // Still failed - keep pending state for another retry | ||
| return false; | ||
| } finally { | ||
| // Always restore tracker state to prevent stale worktree data from persisting | ||
| await this.restoreTrackerState(savedState); | ||
| } |
There was a problem hiding this comment.
Tracker state restore runs after completion, which can undo the completion.
restoreTrackerState() in the finally block can overwrite completeTask() changes on a successful retry. Move completion work after restore (same pattern used in Phase 2 resolution).
🛠️ Suggested fix
- try {
- // Re-attempt resolution
- const resolutions = await this.conflictResolver.resolveConflicts(operation);
- const allResolved = resolutions.every((r) => r.success);
-
- if (allResolved) {
- // Success! Clear pending state and mark task as complete
- this.pendingConflictOperation = null;
- this.pendingConflictWorkerResult = null;
-
- try {
- await this.tracker.completeTask(workerResult.task.id);
- } catch {
- // Log but don't fail after successful resolution
- }
-
- await this.mergeProgressFile(workerResult);
- this.totalConflictsResolved += resolutions.length;
- this.totalMergesCompleted++;
- return true;
- }
-
- // Still failed - keep pending state for another retry
- return false;
- } finally {
- // Always restore tracker state to prevent stale worktree data from persisting
- await this.restoreTrackerState(savedState);
- }
+ let resolutions: Awaited<ReturnType<typeof this.conflictResolver.resolveConflicts>> = [];
+ let allResolved = false;
+ try {
+ // Re-attempt resolution
+ resolutions = await this.conflictResolver.resolveConflicts(operation);
+ allResolved = resolutions.every((r) => r.success);
+ } finally {
+ // Always restore tracker state to prevent stale worktree data from persisting
+ await this.restoreTrackerState(savedState);
+ }
+
+ if (allResolved) {
+ // Success! Clear pending state and mark task as complete
+ this.pendingConflictOperation = null;
+ this.pendingConflictWorkerResult = null;
+
+ try {
+ await this.tracker.completeTask(workerResult.task.id);
+ } catch {
+ // Log but don't fail after successful resolution
+ }
+
+ await this.mergeProgressFile(workerResult);
+ this.totalConflictsResolved += resolutions.length;
+ this.totalMergesCompleted++;
+ return true;
+ }
+
+ // Still failed - keep pending state for another retry
+ return false;🤖 Prompt for AI Agents
In `@src/parallel/index.ts` around lines 165 - 195, The finally block calls
restoreTrackerState(savedState) while completion actions (tracker.completeTask,
mergeProgressFile, clearing
pendingConflictOperation/pendingConflictWorkerResult, and incrementing
totalConflictsResolved/totalMergesCompleted) run before it, which can let
restoreTrackerState overwrite the completion; change the flow in the method
containing saveTrackerState/restoreTrackerState and
conflictResolver.resolveConflicts so that restoreTrackerState(savedState) is
always called in the finally and all post-success completion work (clear
pendingConflictOperation and pendingConflictWorkerResult, await
tracker.completeTask(workerResult.task.id) with try/catch, await
mergeProgressFile(workerResult), and increment totalConflictsResolved and
totalMergesCompleted) runs after the finally (i.e., after restore) only when
allResolved is true, following the Phase 2 resolution pattern used elsewhere.
feat: wire up AI conflict resolution for parallel execution
Summary
The conflict resolution infrastructure existed but was never actually connected -
setAiResolver()was never called after creatingParallelExecutor. This PR wires up actual AI-powered resolution using the session's configured agent.src/parallel/ai-resolver.tswith fast-path heuristics and LLM resolutionconflictResolution.enabled,timeoutMs,maxFilesin configWhat was broken
How it works now
Test plan
tryFastPathResolution()(20 tests pass)buildConflictPrompt()extractResolvedContent()Fixes #275
Summary by CodeRabbit
New Features
Configuration
UI
Tests & Docs