stopAuto Step 4 previously always called exitMilestone(preserveBranch: true), which preserved the worktree branch but never merged it back. When auto-mode stopped after complete-milestone, the code stayed stranded on the worktree branch. Now checks if the milestone has a SUMMARY file (completion signal) and calls mergeAndExit instead, so completed milestone code reaches main. Fixes #2317 Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
f21ad837ac
commit
58631bba2b
2 changed files with 104 additions and 3 deletions
|
|
@ -610,14 +610,48 @@ export async function stopAuto(
|
|||
}
|
||||
|
||||
// ── Step 4: Auto-worktree exit ──
|
||||
// When the milestone is complete (has a SUMMARY), merge the worktree branch
|
||||
// back to main so code isn't stranded on the worktree branch (#2317).
|
||||
// For incomplete milestones, preserve the branch for later resumption.
|
||||
try {
|
||||
if (s.currentMilestoneId) {
|
||||
const notifyCtx = ctx
|
||||
? { notify: ctx.ui.notify.bind(ctx.ui) }
|
||||
: { notify: () => {} };
|
||||
buildResolver().exitMilestone(s.currentMilestoneId, notifyCtx, {
|
||||
preserveBranch: true,
|
||||
});
|
||||
const resolver = buildResolver();
|
||||
|
||||
// Check if the milestone is complete — SUMMARY file is the authoritative signal.
|
||||
let milestoneComplete = false;
|
||||
try {
|
||||
const summaryPath = resolveMilestoneFile(
|
||||
s.originalBasePath || s.basePath,
|
||||
s.currentMilestoneId,
|
||||
"SUMMARY",
|
||||
);
|
||||
if (!summaryPath) {
|
||||
// Also check in the worktree path (SUMMARY may not be synced yet)
|
||||
const wtSummaryPath = resolveMilestoneFile(
|
||||
s.basePath,
|
||||
s.currentMilestoneId,
|
||||
"SUMMARY",
|
||||
);
|
||||
milestoneComplete = wtSummaryPath !== null;
|
||||
} else {
|
||||
milestoneComplete = true;
|
||||
}
|
||||
} catch {
|
||||
// Non-fatal — fall through to preserveBranch path
|
||||
}
|
||||
|
||||
if (milestoneComplete) {
|
||||
// Milestone is complete — merge worktree branch back to main
|
||||
resolver.mergeAndExit(s.currentMilestoneId, notifyCtx);
|
||||
} else {
|
||||
// Milestone still in progress — preserve branch for later resumption
|
||||
resolver.exitMilestone(s.currentMilestoneId, notifyCtx, {
|
||||
preserveBranch: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
debugLog("stop-cleanup-worktree", { error: e instanceof Error ? e.message : String(e) });
|
||||
|
|
|
|||
|
|
@ -0,0 +1,67 @@
|
|||
/**
|
||||
* stop-auto-merge-back.test.ts — Regression test for #2317.
|
||||
*
|
||||
* When auto-mode stops after a milestone is complete, stopAuto should trigger
|
||||
* merge-back (mergeAndExit) instead of just exiting the worktree with
|
||||
* preserveBranch: true. Otherwise milestone code stays stranded on the
|
||||
* worktree branch and never reaches main.
|
||||
*/
|
||||
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
|
||||
// ─── Source analysis: stopAuto calls mergeAndExit for complete milestones ────
|
||||
|
||||
const autoSrcPath = join(import.meta.dirname, "..", "auto.ts");
|
||||
const autoSrc = readFileSync(autoSrcPath, "utf-8");
|
||||
|
||||
test("#2317: stopAuto should check milestone completion status before choosing exit strategy", () => {
|
||||
// stopAuto Step 4 should NOT unconditionally call exitMilestone(preserveBranch: true).
|
||||
// It should check if the milestone is complete and call mergeAndExit instead.
|
||||
|
||||
// Find the Step 4 section
|
||||
const step4Idx = autoSrc.indexOf("Step 4: Auto-worktree exit");
|
||||
assert.ok(step4Idx !== -1, "Step 4 comment exists in stopAuto");
|
||||
|
||||
// Extract a reasonable window around Step 4 (up to Step 5)
|
||||
const step5Idx = autoSrc.indexOf("Step 5:", step4Idx);
|
||||
const step4Block = autoSrc.slice(step4Idx, step5Idx);
|
||||
|
||||
// The fix: Step 4 should call mergeAndExit when milestone is complete
|
||||
assert.ok(
|
||||
step4Block.includes("mergeAndExit"),
|
||||
"Step 4 should call mergeAndExit for completed milestones",
|
||||
);
|
||||
});
|
||||
|
||||
test("#2317: stopAuto should detect milestone completion via SUMMARY file or DB", () => {
|
||||
const step4Idx = autoSrc.indexOf("Step 4: Auto-worktree exit");
|
||||
const step5Idx = autoSrc.indexOf("Step 5:", step4Idx);
|
||||
const step4Block = autoSrc.slice(step4Idx, step5Idx);
|
||||
|
||||
// Should check completion status — either via SUMMARY file, DB getMilestone, or phase
|
||||
const checksCompletion =
|
||||
step4Block.includes("SUMMARY") ||
|
||||
step4Block.includes("getMilestone") ||
|
||||
step4Block.includes("complete") ||
|
||||
step4Block.includes("isMilestoneComplete");
|
||||
|
||||
assert.ok(
|
||||
checksCompletion,
|
||||
"Step 4 should check if milestone is complete before deciding exit strategy",
|
||||
);
|
||||
});
|
||||
|
||||
test("#2317: stopAuto still preserves branch for incomplete milestones", () => {
|
||||
const step4Idx = autoSrc.indexOf("Step 4: Auto-worktree exit");
|
||||
const step5Idx = autoSrc.indexOf("Step 5:", step4Idx);
|
||||
const step4Block = autoSrc.slice(step4Idx, step5Idx);
|
||||
|
||||
// preserveBranch should still be used as fallback for non-complete milestones
|
||||
assert.ok(
|
||||
step4Block.includes("preserveBranch"),
|
||||
"Step 4 should still preserve branch for incomplete milestones (fallback path)",
|
||||
);
|
||||
});
|
||||
Loading…
Add table
Reference in a new issue