fix(engine): make merger autostash recovery resilient and AI-resolve conflicts
When rootDir is the developer's primary checkout, the merger stashes uncommitted edits before its hard resets and applies them back at the end. Previously a pop conflict logged a single warning and silently left the stash in place — a subsequent merge would push another autostash on top, burying the first. Recent FN-3299 work was lost this way and surfaced two side-by-side fusion-merger-autostash entries in the local stash list. Three changes: - AI auto-resolve on apply conflict. The new runAiAgentForAutostashConflict spawns the same createResolvedAgentSession path as the in-merge fix-agent, instructs it to clear conflict markers in place without committing, and verifies markers are gone post-run. On verified success the stash is dropped; on any failure or remaining markers the stash is left intact for manual recovery. - Outcome surfaced via new MergeResult.autostash (AutostashOutcome) field so dashboard / CLI / daemon can show developers whether their work was reapplied cleanly, AI-resolved, or needs manual recovery. - Deterministic stash identity. Replaced `git stash push` + label-grep (which races against concurrent stashing tools) with `git stash create` + `git stash store`, capturing SHA atomically with snapshot creation and using it for apply / drop. Untracked files captured via `git add -A` before create; cleanup via `git reset --hard` + `git clean -fd`. Also surfaces orphaned `fusion-merger-autostash:*` entries from prior runs at merge entry, so they can no longer be silently buried. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
export { COLUMNS, COLUMN_LABELS, COLUMN_DESCRIPTIONS, VALID_TRANSITIONS, DEFAULT_SETTINGS, DEFAULT_GLOBAL_SETTINGS, DEFAULT_PROJECT_SETTINGS, GLOBAL_SETTINGS_KEYS, PROJECT_SETTINGS_KEYS, isGlobalSettingsKey, isProjectSettingsKey, THINKING_LEVELS, THEME_MODES, COLOR_THEMES, WORKFLOW_STEP_TEMPLATES, AGENT_PERMISSIONS, agentToConfigSnapshot, diffConfigSnapshots, isEphemeralAgent, hasAgentIdentity, CheckoutConflictError, DEFAULT_HEARTBEAT_PROCEDURE_PATH, getDefaultHeartbeatProcedurePath, EXECUTION_MODES, DEFAULT_EXECUTION_MODE, TASK_PRIORITIES, DEFAULT_TASK_PRIORITY, DASHBOARD_USER_ID, normalizeMessageParticipant, validateMessageMetadata, validateDockerNodeConfig, sanitizeDockerNodeConfigForResponse, normalizeMergeConflictStrategy, buildResearchDocumentKey } from "./types.js";
|
||||
export type { Column, IssueInfo, IssueState, TaskSourceIssue, PrInfo, PrStatus, Task, TaskTokenUsage, TaskAttachment, TaskComment, TaskCommentInput, TaskDocument, TaskDocumentRevision, TaskDocumentCreateInput, TaskDocumentWithTask, TaskCreateInput, TaskSource, SourceType, TaskDetail, InboxTask, TodoList, TodoItem, TodoListCreateInput, TodoListUpdateInput, TodoItemCreateInput, TodoItemUpdateInput, TodoListWithItems, AgentLogEntry, AgentLogType, AgentRole, BoardConfig, MergeDetails, MergeResult, MergeConflictStrategy, CanonicalMergeConflictStrategy, Settings, GlobalSettings, ProjectSettings, WebSearchBackend, ResearchEnabledSources, ResearchGlobalDefaults, ResearchProjectLimits, ResearchProjectSettings, SettingsScope, DaemonTokenSettings, TaskStep, StepStatus, TaskLogEntry, RunMutationContext, ActivityLogEntry, ActivityEventType, ThinkingLevel, ThemeMode, ColorTheme, ExecutionMode, TaskPriority, UnavailableNodePolicy, PlanningQuestion, PlanningSummary, PlanningResponse, PlanningQuestionType, ArchivedTaskEntry, BatchStatusRequest, BatchStatusResponse, BatchStatusEntry, BatchStatusResult, ModelPreset, WorkflowStep, WorkflowStepMode, WorkflowStepPhase, WorkflowStepInput, WorkflowStepResult, WorkflowStepTemplate, Agent, OrgTreeNode, AgentState, AgentDetail, AgentCreateInput, AgentUpdateInput, AgentApiKey, AgentApiKeyCreateResult, AgentCapability, AgentPromptTemplate, AgentPromptsConfig, AgentPermission, TaskAssignSource, AgentAccessState, AgentHeartbeatConfig, AgentBudgetConfig, AgentBudgetStatus, InstructionsBundleConfig, MessageResponseMode, AgentHeartbeatEvent, AgentHeartbeatRun, BlockedStateSnapshot, HeartbeatInvocationSource, AgentTaskSession, AgentRating, AgentRatingSummary, AgentRatingInput, AgentConfigSnapshot, RevisionFieldDiff, AgentConfigRevision, AgentStats, ReflectionTrigger, ReflectionMetrics, AgentReflection, AgentPerformanceSummary, NtfyNotificationEvent, NotificationEvent, NotificationPayload, NotificationProviderConfig, CustomProvider, SteeringComment, ParticipantType, MessageType, Message, MessageCreateInput, MessageFilter, MessageMetadata, MessageReplyReference, Mailbox, CheckoutLease, RunAuditDomain, RunAuditEvent, RunAuditEventInput, RunAuditEventFilter } from "./types.js";
|
||||
export type { Column, IssueInfo, IssueState, TaskSourceIssue, PrInfo, PrStatus, Task, TaskTokenUsage, TaskAttachment, TaskComment, TaskCommentInput, TaskDocument, TaskDocumentRevision, TaskDocumentCreateInput, TaskDocumentWithTask, TaskCreateInput, TaskSource, SourceType, TaskDetail, InboxTask, TodoList, TodoItem, TodoListCreateInput, TodoListUpdateInput, TodoItemCreateInput, TodoItemUpdateInput, TodoListWithItems, AgentLogEntry, AgentLogType, AgentRole, BoardConfig, AutostashOutcome, MergeDetails, MergeResult, MergeConflictStrategy, CanonicalMergeConflictStrategy, Settings, GlobalSettings, ProjectSettings, WebSearchBackend, ResearchEnabledSources, ResearchGlobalDefaults, ResearchProjectLimits, ResearchProjectSettings, SettingsScope, DaemonTokenSettings, TaskStep, StepStatus, TaskLogEntry, RunMutationContext, ActivityLogEntry, ActivityEventType, ThinkingLevel, ThemeMode, ColorTheme, ExecutionMode, TaskPriority, UnavailableNodePolicy, PlanningQuestion, PlanningSummary, PlanningResponse, PlanningQuestionType, ArchivedTaskEntry, BatchStatusRequest, BatchStatusResponse, BatchStatusEntry, BatchStatusResult, ModelPreset, WorkflowStep, WorkflowStepMode, WorkflowStepPhase, WorkflowStepInput, WorkflowStepResult, WorkflowStepTemplate, Agent, OrgTreeNode, AgentState, AgentDetail, AgentCreateInput, AgentUpdateInput, AgentApiKey, AgentApiKeyCreateResult, AgentCapability, AgentPromptTemplate, AgentPromptsConfig, AgentPermission, TaskAssignSource, AgentAccessState, AgentHeartbeatConfig, AgentBudgetConfig, AgentBudgetStatus, InstructionsBundleConfig, MessageResponseMode, AgentHeartbeatEvent, AgentHeartbeatRun, BlockedStateSnapshot, HeartbeatInvocationSource, AgentTaskSession, AgentRating, AgentRatingSummary, AgentRatingInput, AgentConfigSnapshot, RevisionFieldDiff, AgentConfigRevision, AgentStats, ReflectionTrigger, ReflectionMetrics, AgentReflection, AgentPerformanceSummary, NtfyNotificationEvent, NotificationEvent, NotificationPayload, NotificationProviderConfig, CustomProvider, SteeringComment, ParticipantType, MessageType, Message, MessageCreateInput, MessageFilter, MessageMetadata, MessageReplyReference, Mailbox, CheckoutLease, RunAuditDomain, RunAuditEvent, RunAuditEventInput, RunAuditEventFilter } from "./types.js";
|
||||
export { AGENT_VALID_TRANSITIONS } from "./types.js";
|
||||
export * from "./mesh-replication-protocol.js";
|
||||
export {
|
||||
|
||||
@@ -2115,6 +2115,34 @@ export interface BoardConfig {
|
||||
settings?: Settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Outcome of restoring the developer's pre-merge autostash after the merge
|
||||
* completes. Surfaced on MergeResult so the UI / dashboard can show whether
|
||||
* the dev's uncommitted work was reapplied cleanly, AI-resolved, or left
|
||||
* stashed for manual recovery.
|
||||
*
|
||||
* Background: when rootDir is the developer's primary checkout, the merger
|
||||
* stashes any uncommitted edits before running its hard resets, then applies
|
||||
* them back at the end. Historically a pop conflict would log a warning and
|
||||
* silently leave the stash in place — developers had no way to discover this
|
||||
* had happened. See `restoreUnrelatedRootDirChanges` in merger.ts.
|
||||
*/
|
||||
export type AutostashOutcome =
|
||||
| { status: "no-changes" }
|
||||
| { status: "restored"; stashSha: string }
|
||||
| {
|
||||
status: "ai-resolved";
|
||||
stashSha: string;
|
||||
conflictedFiles: string[];
|
||||
}
|
||||
| {
|
||||
status: "conflict-needs-manual";
|
||||
stashSha: string;
|
||||
conflictedFiles: string[];
|
||||
message: string;
|
||||
}
|
||||
| { status: "failed"; stashSha?: string; errorMessage: string };
|
||||
|
||||
export interface MergeResult extends MergeDetails {
|
||||
task: Task;
|
||||
branch: string;
|
||||
@@ -2126,6 +2154,9 @@ export interface MergeResult extends MergeDetails {
|
||||
pushedToRemote?: boolean;
|
||||
/** Error message if push to remote failed. Non-fatal — merge is already committed locally. */
|
||||
pushError?: string;
|
||||
/** Outcome of restoring the developer's pre-merge autostash, when one was
|
||||
* created. Absent when the working tree was already clean at merge start. */
|
||||
autostash?: AutostashOutcome;
|
||||
/** Internal flag to track if a build retry has been attempted. Not persisted. */
|
||||
_buildRetried?: boolean;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user