gsd-pi 2.73.1-dev.47ed32a → 2.73.1-dev.49397e5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli-web-branch.d.ts +4 -3
- package/dist/cli-web-branch.js +10 -7
- package/dist/cli.js +99 -206
- package/dist/headless-query.js +4 -1
- package/dist/logo.d.ts +1 -1
- package/dist/logo.js +1 -1
- package/dist/onboarding.js +59 -53
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +59 -1
- package/dist/resources/extensions/gsd/auto/detect-stuck.js +11 -4
- package/dist/resources/extensions/gsd/auto/phases.js +60 -10
- package/dist/resources/extensions/gsd/auto-dispatch.js +11 -3
- package/dist/resources/extensions/gsd/auto-post-unit.js +93 -57
- package/dist/resources/extensions/gsd/auto-timeout-recovery.js +13 -0
- package/dist/resources/extensions/gsd/auto-verification.js +88 -3
- package/dist/resources/extensions/gsd/auto.js +29 -10
- package/dist/resources/extensions/gsd/bootstrap/register-extension.js +21 -8
- package/dist/resources/extensions/gsd/commands/catalog.js +26 -1
- package/dist/resources/extensions/gsd/commands/handlers/ops.js +20 -0
- package/dist/resources/extensions/gsd/commands/handlers/workflow.js +68 -9
- package/dist/resources/extensions/gsd/commands-add-tests.js +111 -0
- package/dist/resources/extensions/gsd/commands-backlog.js +140 -0
- package/dist/resources/extensions/gsd/commands-do.js +79 -0
- package/dist/resources/extensions/gsd/commands-maintenance.js +6 -6
- package/dist/resources/extensions/gsd/commands-pr-branch.js +180 -0
- package/dist/resources/extensions/gsd/commands-session-report.js +82 -0
- package/dist/resources/extensions/gsd/commands-ship.js +187 -0
- package/dist/resources/extensions/gsd/db-writer.js +3 -5
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +1 -1
- package/dist/resources/extensions/gsd/gsd-db.js +321 -0
- package/dist/resources/extensions/gsd/index.js +15 -2
- package/dist/resources/extensions/gsd/md-importer.js +3 -4
- package/dist/resources/extensions/gsd/memory-store.js +19 -51
- package/dist/resources/extensions/gsd/milestone-validation-gates.js +13 -12
- package/dist/resources/extensions/gsd/native-git-bridge.js +7 -4
- package/dist/resources/extensions/gsd/notification-widget.js +2 -2
- package/dist/resources/extensions/gsd/prompts/add-tests.md +35 -0
- package/dist/resources/extensions/gsd/state.js +66 -15
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +3 -14
- package/dist/resources/extensions/gsd/triage-resolution.js +2 -5
- package/dist/resources/extensions/gsd/workflow-manifest.js +8 -69
- package/dist/resources/extensions/gsd/workflow-migration.js +21 -22
- package/dist/resources/extensions/gsd/workflow-projections.js +4 -1
- package/dist/resources/extensions/gsd/workflow-reconcile.js +14 -11
- package/dist/tsconfig.extensions.tsbuildinfo +1 -0
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +11 -11
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +11 -11
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware-manifest.json +5 -5
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/package.json +3 -3
- package/packages/mcp-server/tsconfig.json +1 -0
- package/packages/mcp-server/tsconfig.tsbuildinfo +1 -0
- package/packages/native/tsconfig.tsbuildinfo +1 -0
- package/packages/pi-agent-core/tsconfig.json +1 -0
- package/packages/pi-agent-core/tsconfig.tsbuildinfo +1 -0
- package/packages/pi-ai/tsconfig.json +1 -0
- package/packages/pi-ai/tsconfig.tsbuildinfo +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js +10 -2
- package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.d.ts +2 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.js +9 -3
- package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.test.js +52 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +5 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/modes/interactive/components/assistant-message.ts +12 -6
- package/packages/pi-coding-agent/src/modes/interactive/components/dynamic-border.test.ts +73 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/dynamic-border.ts +9 -3
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +5 -1
- package/packages/pi-coding-agent/tsconfig.json +1 -0
- package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -0
- package/packages/pi-tui/dist/__tests__/tui.test.js +30 -0
- package/packages/pi-tui/dist/__tests__/tui.test.js.map +1 -1
- package/packages/pi-tui/dist/tui.d.ts +1 -0
- package/packages/pi-tui/dist/tui.d.ts.map +1 -1
- package/packages/pi-tui/dist/tui.js +22 -3
- package/packages/pi-tui/dist/tui.js.map +1 -1
- package/packages/pi-tui/src/__tests__/tui.test.ts +38 -0
- package/packages/pi-tui/src/tui.ts +20 -3
- package/packages/pi-tui/tsconfig.json +1 -0
- package/packages/pi-tui/tsconfig.tsbuildinfo +1 -0
- package/packages/rpc-client/tsconfig.json +1 -0
- package/packages/rpc-client/tsconfig.tsbuildinfo +1 -0
- package/pkg/package.json +1 -1
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +95 -1
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +88 -0
- package/src/resources/extensions/gsd/auto/detect-stuck.ts +12 -4
- package/src/resources/extensions/gsd/auto/loop-deps.ts +6 -0
- package/src/resources/extensions/gsd/auto/phases.ts +90 -10
- package/src/resources/extensions/gsd/auto-dispatch.ts +10 -4
- package/src/resources/extensions/gsd/auto-post-unit.ts +107 -58
- package/src/resources/extensions/gsd/auto-timeout-recovery.ts +17 -0
- package/src/resources/extensions/gsd/auto-verification.ts +98 -3
- package/src/resources/extensions/gsd/auto.ts +33 -14
- package/src/resources/extensions/gsd/bootstrap/register-extension.ts +24 -8
- package/src/resources/extensions/gsd/commands/catalog.ts +26 -1
- package/src/resources/extensions/gsd/commands/handlers/ops.ts +20 -0
- package/src/resources/extensions/gsd/commands/handlers/workflow.ts +74 -9
- package/src/resources/extensions/gsd/commands-add-tests.ts +137 -0
- package/src/resources/extensions/gsd/commands-backlog.ts +182 -0
- package/src/resources/extensions/gsd/commands-do.ts +109 -0
- package/src/resources/extensions/gsd/commands-maintenance.ts +6 -6
- package/src/resources/extensions/gsd/commands-pr-branch.ts +234 -0
- package/src/resources/extensions/gsd/commands-session-report.ts +101 -0
- package/src/resources/extensions/gsd/commands-ship.ts +219 -0
- package/src/resources/extensions/gsd/db-writer.ts +3 -5
- package/src/resources/extensions/gsd/docs/preferences-reference.md +1 -1
- package/src/resources/extensions/gsd/gsd-db.ts +467 -0
- package/src/resources/extensions/gsd/index.ts +18 -2
- package/src/resources/extensions/gsd/md-importer.ts +3 -5
- package/src/resources/extensions/gsd/memory-store.ts +31 -62
- package/src/resources/extensions/gsd/milestone-validation-gates.ts +13 -14
- package/src/resources/extensions/gsd/native-git-bridge.ts +11 -12
- package/src/resources/extensions/gsd/notification-widget.ts +2 -2
- package/src/resources/extensions/gsd/prompts/add-tests.md +35 -0
- package/src/resources/extensions/gsd/state.ts +80 -17
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/auto-post-unit-step-message.test.ts +53 -0
- package/src/resources/extensions/gsd/tests/commands-backlog.test.ts +158 -0
- package/src/resources/extensions/gsd/tests/commands-do.test.ts +127 -0
- package/src/resources/extensions/gsd/tests/commands-pr-branch.test.ts +68 -0
- package/src/resources/extensions/gsd/tests/commands-session-report.test.ts +82 -0
- package/src/resources/extensions/gsd/tests/commands-ship.test.ts +71 -0
- package/src/resources/extensions/gsd/tests/commands-workflow-custom.test.ts +14 -0
- package/src/resources/extensions/gsd/tests/complete-milestone-false-merge.test.ts +142 -0
- package/src/resources/extensions/gsd/tests/completed-at-reconcile.test.ts +42 -0
- package/src/resources/extensions/gsd/tests/derive-state-crossval.test.ts +3 -2
- package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +3 -2
- package/src/resources/extensions/gsd/tests/derive-state-helpers.test.ts +68 -8
- package/src/resources/extensions/gsd/tests/derive-state.test.ts +3 -3
- package/src/resources/extensions/gsd/tests/extension-bootstrap-isolation.test.ts +154 -0
- package/src/resources/extensions/gsd/tests/integration/state-machine-edge-cases.test.ts +4 -2
- package/src/resources/extensions/gsd/tests/journal-integration.test.ts +68 -1
- package/src/resources/extensions/gsd/tests/native-git-bridge-exec-fallback.test.ts +140 -0
- package/src/resources/extensions/gsd/tests/single-writer-invariant.test.ts +180 -0
- package/src/resources/extensions/gsd/tests/state-machine-full-walkthrough.test.ts +5 -7
- package/src/resources/extensions/gsd/tests/token-profile.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/validate-milestone-stuck-guard.test.ts +179 -0
- package/src/resources/extensions/gsd/tests/workflow-logger-wiring.test.ts +223 -0
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +3 -11
- package/src/resources/extensions/gsd/triage-resolution.ts +2 -7
- package/src/resources/extensions/gsd/workflow-manifest.ts +9 -104
- package/src/resources/extensions/gsd/workflow-migration.ts +21 -29
- package/src/resources/extensions/gsd/workflow-projections.ts +8 -1
- package/src/resources/extensions/gsd/workflow-reconcile.ts +15 -15
- /package/dist/web/standalone/.next/static/{WawMb3TMcYLkGJLTg_x1u → tAsSblRKK1lG7MVOwsO92}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{WawMb3TMcYLkGJLTg_x1u → tAsSblRKK1lG7MVOwsO92}/_ssgManifest.js +0 -0
|
@@ -30,7 +30,15 @@ import { MergeConflictError } from "../git-service.js";
|
|
|
30
30
|
import { setCurrentPhase, clearCurrentPhase } from "../../shared/gsd-phase-state.js";
|
|
31
31
|
import { join, basename, dirname, parse as parsePath } from "node:path";
|
|
32
32
|
import { existsSync, cpSync, readdirSync } from "node:fs";
|
|
33
|
-
import {
|
|
33
|
+
import {
|
|
34
|
+
logWarning,
|
|
35
|
+
logError,
|
|
36
|
+
_resetLogs,
|
|
37
|
+
drainLogs,
|
|
38
|
+
drainAndSummarize,
|
|
39
|
+
formatForNotification,
|
|
40
|
+
hasAnyIssues,
|
|
41
|
+
} from "../workflow-logger.js";
|
|
34
42
|
import { gsdRoot } from "../paths.js";
|
|
35
43
|
import { atomicWriteSync } from "../atomic-write.js";
|
|
36
44
|
import { verifyExpectedArtifact, diagnoseExpectedArtifact, buildLoopRemediationSteps } from "../auto-recovery.js";
|
|
@@ -159,6 +167,29 @@ async function closeoutAndStop(
|
|
|
159
167
|
await deps.stopAuto(ctx, pi, reason);
|
|
160
168
|
}
|
|
161
169
|
|
|
170
|
+
async function emitCancelledUnitEnd(
|
|
171
|
+
ic: IterationContext,
|
|
172
|
+
unitType: string,
|
|
173
|
+
unitId: string,
|
|
174
|
+
unitStartSeq: number,
|
|
175
|
+
errorContext?: { message: string; category: string; stopReason?: string; isTransient?: boolean; retryAfterMs?: number },
|
|
176
|
+
): Promise<void> {
|
|
177
|
+
ic.deps.emitJournalEvent({
|
|
178
|
+
ts: new Date().toISOString(),
|
|
179
|
+
flowId: ic.flowId,
|
|
180
|
+
seq: ic.nextSeq(),
|
|
181
|
+
eventType: "unit-end",
|
|
182
|
+
data: {
|
|
183
|
+
unitType,
|
|
184
|
+
unitId,
|
|
185
|
+
status: "cancelled",
|
|
186
|
+
artifactVerified: false,
|
|
187
|
+
...(errorContext ? { errorContext } : {}),
|
|
188
|
+
},
|
|
189
|
+
causedBy: { flowId: ic.flowId, seq: unitStartSeq },
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
162
193
|
// ─── runPreDispatch ───────────────────────────────────────────────────────────
|
|
163
194
|
|
|
164
195
|
/**
|
|
@@ -481,10 +512,13 @@ export async function runPreDispatch(
|
|
|
481
512
|
);
|
|
482
513
|
} else if (state.phase === "blocked") {
|
|
483
514
|
const blockerMsg = `Blocked: ${state.blockers.join(", ")}`;
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
deps.
|
|
515
|
+
// Pause instead of hard-stop so the session is resumable with `/gsd auto`.
|
|
516
|
+
// Hard-stop here was causing premature termination when slice dependencies
|
|
517
|
+
// were temporarily unresolvable (e.g. after reassessment added new slices).
|
|
518
|
+
await deps.pauseAuto(ctx, pi);
|
|
519
|
+
ctx.ui.notify(`${blockerMsg}. Fix and run /gsd auto to resume.`, "warning");
|
|
520
|
+
deps.sendDesktopNotification("GSD", blockerMsg, "warning", "attention", basename(s.originalBasePath || s.basePath));
|
|
521
|
+
deps.logCmuxEvent(prefs, blockerMsg, "warning");
|
|
488
522
|
} else {
|
|
489
523
|
const ids = incomplete.map((m: { id: string }) => m.id).join(", ");
|
|
490
524
|
const diag = `basePath=${s.basePath}, milestones=[${state.registry.map((m: { id: string; status: string }) => `${m.id}:${m.status}`).join(", ")}], phase=${state.phase}`;
|
|
@@ -583,13 +617,23 @@ export async function runPreDispatch(
|
|
|
583
617
|
return { action: "break", reason: "milestone-complete" };
|
|
584
618
|
}
|
|
585
619
|
|
|
586
|
-
// Terminal: blocked
|
|
620
|
+
// Terminal: blocked — pause instead of hard-stop so the session is resumable.
|
|
587
621
|
if (state.phase === "blocked") {
|
|
588
622
|
const blockerMsg = `Blocked: ${state.blockers.join(", ")}`;
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
623
|
+
if (s.currentUnit) {
|
|
624
|
+
await deps.closeoutUnit(
|
|
625
|
+
ctx,
|
|
626
|
+
s.basePath,
|
|
627
|
+
s.currentUnit.type,
|
|
628
|
+
s.currentUnit.id,
|
|
629
|
+
s.currentUnit.startedAt,
|
|
630
|
+
deps.buildSnapshotOpts(s.currentUnit.type, s.currentUnit.id),
|
|
631
|
+
);
|
|
632
|
+
}
|
|
633
|
+
await deps.pauseAuto(ctx, pi);
|
|
634
|
+
ctx.ui.notify(`${blockerMsg}. Fix and run /gsd auto to resume.`, "warning");
|
|
635
|
+
deps.sendDesktopNotification("GSD", blockerMsg, "warning", "attention", basename(s.originalBasePath || s.basePath));
|
|
636
|
+
deps.logCmuxEvent(prefs, blockerMsg, "warning");
|
|
593
637
|
debugLog("autoLoop", { phase: "exit", reason: "blocked" });
|
|
594
638
|
deps.emitJournalEvent({ ts: new Date().toISOString(), flowId: ic.flowId, seq: ic.nextSeq(), eventType: "terminal", data: { reason: "blocked", blockers: state.blockers } });
|
|
595
639
|
return { action: "break", reason: "blocked" };
|
|
@@ -1068,6 +1112,10 @@ export async function runUnitPhase(
|
|
|
1068
1112
|
);
|
|
1069
1113
|
const previousTier = s.currentUnitRouting?.tier;
|
|
1070
1114
|
|
|
1115
|
+
// Scope workflow-logger buffer to this unit so post-finalize drains are
|
|
1116
|
+
// per-unit. Without this, the module-level _buffer accumulates across every
|
|
1117
|
+
// unit in the same Node process (see workflow-logger.ts module header).
|
|
1118
|
+
_resetLogs();
|
|
1071
1119
|
s.currentUnit = { type: unitType, id: unitId, startedAt: Date.now() };
|
|
1072
1120
|
setCurrentPhase(unitType);
|
|
1073
1121
|
s.lastToolInvocationError = null; // #2883: clear stale error from previous unit
|
|
@@ -1334,6 +1382,7 @@ export async function runUnitPhase(
|
|
|
1334
1382
|
// Provider-error pause: pauseAuto already handled cleanup and scheduled
|
|
1335
1383
|
// recovery. Don't hard-stop — just break out of the loop (#2762).
|
|
1336
1384
|
if (unitResult.errorContext?.category === "provider") {
|
|
1385
|
+
await emitCancelledUnitEnd(ic, unitType, unitId, unitStartSeq, unitResult.errorContext);
|
|
1337
1386
|
debugLog("autoLoop", { phase: "exit", reason: "provider-pause", isTransient: unitResult.errorContext.isTransient });
|
|
1338
1387
|
return { action: "break", reason: "provider-pause" };
|
|
1339
1388
|
}
|
|
@@ -1352,9 +1401,23 @@ export async function runUnitPhase(
|
|
|
1352
1401
|
);
|
|
1353
1402
|
debugLog("autoLoop", { phase: "session-timeout-pause", unitType, unitId });
|
|
1354
1403
|
await deps.pauseAuto(ctx, pi);
|
|
1404
|
+
await deps.autoCommitUnit?.(s.basePath, unitType, unitId, ctx);
|
|
1405
|
+
await emitCancelledUnitEnd(ic, unitType, unitId, unitStartSeq, unitResult.errorContext);
|
|
1355
1406
|
return { action: "break", reason: "session-timeout" };
|
|
1356
1407
|
}
|
|
1357
1408
|
// All other cancelled states (structural errors, non-transient failures): hard stop
|
|
1409
|
+
if (s.currentUnit) {
|
|
1410
|
+
await deps.closeoutUnit(
|
|
1411
|
+
ctx,
|
|
1412
|
+
s.basePath,
|
|
1413
|
+
unitType,
|
|
1414
|
+
unitId,
|
|
1415
|
+
s.currentUnit.startedAt,
|
|
1416
|
+
deps.buildSnapshotOpts(unitType, unitId),
|
|
1417
|
+
);
|
|
1418
|
+
}
|
|
1419
|
+
await deps.autoCommitUnit?.(s.basePath, unitType, unitId, ctx);
|
|
1420
|
+
await emitCancelledUnitEnd(ic, unitType, unitId, unitStartSeq, unitResult.errorContext);
|
|
1358
1421
|
ctx.ui.notify(
|
|
1359
1422
|
`Session creation failed for ${unitType} ${unitId}: ${unitResult.errorContext?.message ?? "unknown"}. Stopping auto-mode.`,
|
|
1360
1423
|
"warning",
|
|
@@ -1532,6 +1595,9 @@ export async function runFinalize(
|
|
|
1532
1595
|
// cannot mutate state for the next unit (#3757).
|
|
1533
1596
|
s.currentUnit = null;
|
|
1534
1597
|
clearCurrentPhase();
|
|
1598
|
+
// Drop any logger entries from the timed-out unit so they don't bleed
|
|
1599
|
+
// into the next iteration's drain.
|
|
1600
|
+
drainLogs();
|
|
1535
1601
|
loopState.consecutiveFinalizeTimeouts++;
|
|
1536
1602
|
debugLog("autoLoop", {
|
|
1537
1603
|
phase: "pre-verification-timeout",
|
|
@@ -1630,6 +1696,9 @@ export async function runFinalize(
|
|
|
1630
1696
|
// cannot mutate state for the next unit (#3757).
|
|
1631
1697
|
s.currentUnit = null;
|
|
1632
1698
|
clearCurrentPhase();
|
|
1699
|
+
// Drop any logger entries from the timed-out unit so they don't bleed
|
|
1700
|
+
// into the next iteration's drain.
|
|
1701
|
+
drainLogs();
|
|
1633
1702
|
loopState.consecutiveFinalizeTimeouts++;
|
|
1634
1703
|
debugLog("autoLoop", {
|
|
1635
1704
|
phase: "post-verification-timeout",
|
|
@@ -1674,5 +1743,16 @@ export async function runFinalize(
|
|
|
1674
1743
|
// Both pre and post verification completed without timeout — reset counter
|
|
1675
1744
|
loopState.consecutiveFinalizeTimeouts = 0;
|
|
1676
1745
|
|
|
1746
|
+
// Surface accumulated workflow-logger issues for this unit to the user.
|
|
1747
|
+
// Warnings/errors logged during the unit are buffered in the logger and
|
|
1748
|
+
// drained here so the user sees a single consolidated post-unit alert.
|
|
1749
|
+
if (hasAnyIssues()) {
|
|
1750
|
+
const { logs } = drainAndSummarize();
|
|
1751
|
+
if (logs.length > 0) {
|
|
1752
|
+
const severity = logs.some((e) => e.severity === "error") ? "error" : "warning";
|
|
1753
|
+
ctx.ui.notify(formatForNotification(logs), severity);
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1756
|
+
|
|
1677
1757
|
return { action: "next", data: undefined as void };
|
|
1678
1758
|
}
|
|
@@ -307,8 +307,11 @@ export const DISPATCH_RULES: DispatchRule[] = [
|
|
|
307
307
|
{
|
|
308
308
|
name: "reassess-roadmap (post-completion)",
|
|
309
309
|
match: async ({ state, mid, midTitle, basePath, prefs }) => {
|
|
310
|
-
if (prefs?.phases?.skip_reassess
|
|
311
|
-
|
|
310
|
+
if (prefs?.phases?.skip_reassess) return null;
|
|
311
|
+
// Default reassess_after_slice to true — reassessment after slice completion
|
|
312
|
+
// is essential for roadmap integrity. Opt-out via explicit `false`.
|
|
313
|
+
const reassessEnabled = prefs?.phases?.reassess_after_slice ?? true;
|
|
314
|
+
if (!reassessEnabled) return null;
|
|
312
315
|
const needsReassess = await checkNeedsReassessment(basePath, mid, state);
|
|
313
316
|
if (!needsReassess) return null;
|
|
314
317
|
return {
|
|
@@ -877,11 +880,14 @@ export async function resolveDispatch(
|
|
|
877
880
|
}
|
|
878
881
|
}
|
|
879
882
|
|
|
880
|
-
// No rule matched — unhandled phase
|
|
883
|
+
// No rule matched — unhandled phase.
|
|
884
|
+
// Use level "warning" so the loop pauses (resumable) instead of hard-stopping.
|
|
885
|
+
// Hard-stop here was causing premature termination for transient phase gaps
|
|
886
|
+
// (e.g. after reassessment modifies the roadmap and state needs re-derivation).
|
|
881
887
|
return {
|
|
882
888
|
action: "stop",
|
|
883
889
|
reason: `Unhandled phase "${ctx.state.phase}" — run /gsd doctor to diagnose.`,
|
|
884
|
-
level: "
|
|
890
|
+
level: "warning",
|
|
885
891
|
matchedRule: "<no-match>",
|
|
886
892
|
};
|
|
887
893
|
}
|
|
@@ -104,6 +104,7 @@ import {
|
|
|
104
104
|
updateSliceProgressCache,
|
|
105
105
|
unitVerb,
|
|
106
106
|
hideFooter,
|
|
107
|
+
describeNextUnit,
|
|
107
108
|
} from "./auto-dashboard.js";
|
|
108
109
|
import { existsSync, unlinkSync } from "node:fs";
|
|
109
110
|
import { join } from "node:path";
|
|
@@ -233,6 +234,18 @@ export function detectRogueFileWrites(
|
|
|
233
234
|
return rogues;
|
|
234
235
|
}
|
|
235
236
|
|
|
237
|
+
export const STEP_COMPLETE_FALLBACK_MESSAGE =
|
|
238
|
+
"Step complete. Run /clear, then /gsd to continue (or /gsd auto to run continuously).";
|
|
239
|
+
|
|
240
|
+
export function buildStepCompleteMessage(nextState: import("./types.js").GSDState): string {
|
|
241
|
+
if (nextState.phase === "complete") {
|
|
242
|
+
return "Step complete — milestone finished. Run /gsd status to review, or start the next milestone.";
|
|
243
|
+
}
|
|
244
|
+
const next = describeNextUnit(nextState);
|
|
245
|
+
return `Step complete. Next: ${next.label}\n`
|
|
246
|
+
+ `Run /clear, then /gsd to continue (or /gsd auto to run continuously).`;
|
|
247
|
+
}
|
|
248
|
+
|
|
236
249
|
export interface PreVerificationOpts {
|
|
237
250
|
skipSettleDelay?: boolean;
|
|
238
251
|
skipWorktreeSync?: boolean;
|
|
@@ -249,6 +262,65 @@ export interface PostUnitContext {
|
|
|
249
262
|
updateProgressWidget: (ctx: ExtensionContext, unitType: string, unitId: string, state: import("./types.js").GSDState) => void;
|
|
250
263
|
}
|
|
251
264
|
|
|
265
|
+
export async function autoCommitUnit(
|
|
266
|
+
basePath: string,
|
|
267
|
+
unitType: string,
|
|
268
|
+
unitId: string,
|
|
269
|
+
ctx?: ExtensionContext,
|
|
270
|
+
): Promise<string | null> {
|
|
271
|
+
try {
|
|
272
|
+
let taskContext: TaskCommitContext | undefined;
|
|
273
|
+
|
|
274
|
+
if (unitType === "execute-task") {
|
|
275
|
+
const { milestone: mid, slice: sid, task: tid } = parseUnitId(unitId);
|
|
276
|
+
if (mid && sid && tid) {
|
|
277
|
+
const summaryPath = resolveTaskFile(basePath, mid, sid, tid, "SUMMARY");
|
|
278
|
+
if (summaryPath) {
|
|
279
|
+
try {
|
|
280
|
+
const summaryContent = await loadFile(summaryPath);
|
|
281
|
+
if (summaryContent) {
|
|
282
|
+
const summary = parseSummary(summaryContent);
|
|
283
|
+
let ghIssueNumber: number | undefined;
|
|
284
|
+
try {
|
|
285
|
+
const { getTaskIssueNumberForCommit } = await import("../github-sync/sync.js");
|
|
286
|
+
ghIssueNumber = getTaskIssueNumberForCommit(basePath, mid, sid, tid) ?? undefined;
|
|
287
|
+
} catch (err) {
|
|
288
|
+
logWarning("engine", `GitHub issue lookup failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
taskContext = {
|
|
292
|
+
taskId: `${sid}/${tid}`,
|
|
293
|
+
taskTitle: summary.title?.replace(/^T\d+:\s*/, "") || tid,
|
|
294
|
+
oneLiner: summary.oneLiner || undefined,
|
|
295
|
+
keyFiles: summary.frontmatter.key_files?.filter(f => !f.includes("{{")) || undefined,
|
|
296
|
+
issueNumber: ghIssueNumber,
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
} catch (e) {
|
|
300
|
+
debugLog("postUnit", { phase: "task-summary-parse", error: String(e) });
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
_resetHasChangesCache();
|
|
307
|
+
|
|
308
|
+
if (LIFECYCLE_ONLY_UNITS.has(unitType)) {
|
|
309
|
+
return null;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const commitMsg = autoCommitCurrentBranch(basePath, unitType, unitId, taskContext);
|
|
313
|
+
if (commitMsg) {
|
|
314
|
+
ctx?.ui.notify(`Committed: ${commitMsg.split("\n")[0]}`, "info");
|
|
315
|
+
}
|
|
316
|
+
return commitMsg;
|
|
317
|
+
} catch (e) {
|
|
318
|
+
debugLog("postUnit", { phase: "auto-commit", error: String(e) });
|
|
319
|
+
ctx?.ui.notify(`Auto-commit failed: ${String(e).split("\n")[0]}`, "warning");
|
|
320
|
+
return null;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
252
324
|
/**
|
|
253
325
|
* Pre-verification processing: parallel worker signal check, cache invalidation,
|
|
254
326
|
* auto-commit, doctor run, state rebuild, worktree sync, artifact verification.
|
|
@@ -288,63 +360,7 @@ export async function postUnitPreVerification(pctx: PostUnitContext, opts?: PreV
|
|
|
288
360
|
// Auto-commit
|
|
289
361
|
if (s.currentUnit) {
|
|
290
362
|
const unit = s.currentUnit;
|
|
291
|
-
|
|
292
|
-
let taskContext: TaskCommitContext | undefined;
|
|
293
|
-
|
|
294
|
-
if (s.currentUnit.type === "execute-task") {
|
|
295
|
-
const { milestone: mid, slice: sid, task: tid } = parseUnitId(s.currentUnit.id);
|
|
296
|
-
if (mid && sid && tid) {
|
|
297
|
-
const summaryPath = resolveTaskFile(s.basePath, mid, sid, tid, "SUMMARY");
|
|
298
|
-
if (summaryPath) {
|
|
299
|
-
try {
|
|
300
|
-
const summaryContent = await loadFile(summaryPath);
|
|
301
|
-
if (summaryContent) {
|
|
302
|
-
const summary = parseSummary(summaryContent);
|
|
303
|
-
// Look up GitHub issue number for commit linking
|
|
304
|
-
let ghIssueNumber: number | undefined;
|
|
305
|
-
try {
|
|
306
|
-
const { getTaskIssueNumberForCommit } = await import("../github-sync/sync.js");
|
|
307
|
-
ghIssueNumber = getTaskIssueNumberForCommit(s.basePath, mid, sid, tid) ?? undefined;
|
|
308
|
-
} catch (err) {
|
|
309
|
-
// GitHub sync not available — skip
|
|
310
|
-
logWarning("engine", `GitHub issue lookup failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
taskContext = {
|
|
314
|
-
taskId: `${sid}/${tid}`,
|
|
315
|
-
taskTitle: summary.title?.replace(/^T\d+:\s*/, "") || tid,
|
|
316
|
-
oneLiner: summary.oneLiner || undefined,
|
|
317
|
-
keyFiles: summary.frontmatter.key_files?.filter(f => !f.includes("{{")) || undefined,
|
|
318
|
-
issueNumber: ghIssueNumber,
|
|
319
|
-
};
|
|
320
|
-
}
|
|
321
|
-
} catch (e) {
|
|
322
|
-
debugLog("postUnit", { phase: "task-summary-parse", error: String(e) });
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
// Invalidate the nativeHasChanges cache before auto-commit (#1853).
|
|
329
|
-
// The cache has a 10-second TTL and is keyed by basePath. A stale
|
|
330
|
-
// `false` result causes autoCommit to skip staging entirely, leaving
|
|
331
|
-
// code files only in the working tree where they are destroyed by
|
|
332
|
-
// `git worktree remove --force` during teardown.
|
|
333
|
-
_resetHasChangesCache();
|
|
334
|
-
|
|
335
|
-
// Skip auto-commit for lifecycle-only units (#2553) — they only touch
|
|
336
|
-
// `.gsd/` internal state files. Those files are picked up by the next
|
|
337
|
-
// actual task commit via smartStage().
|
|
338
|
-
if (!LIFECYCLE_ONLY_UNITS.has(s.currentUnit.type)) {
|
|
339
|
-
const commitMsg = autoCommitCurrentBranch(s.basePath, s.currentUnit.type, s.currentUnit.id, taskContext);
|
|
340
|
-
if (commitMsg) {
|
|
341
|
-
ctx.ui.notify(`Committed: ${commitMsg.split("\n")[0]}`, "info");
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
} catch (e) {
|
|
345
|
-
debugLog("postUnit", { phase: "auto-commit", error: String(e) });
|
|
346
|
-
ctx.ui.notify(`Auto-commit failed: ${String(e).split("\n")[0]}`, "warning");
|
|
347
|
-
}
|
|
363
|
+
await autoCommitUnit(s.basePath, unit.type, unit.id, ctx);
|
|
348
364
|
|
|
349
365
|
// GitHub sync (non-blocking, opt-in)
|
|
350
366
|
await runSafely("postUnit", "github-sync", async () => {
|
|
@@ -619,6 +635,30 @@ export async function postUnitPreVerification(pctx: PostUnitContext, opts?: PreV
|
|
|
619
635
|
s.verificationRetryCount.set(retryKey, attempt);
|
|
620
636
|
|
|
621
637
|
if (attempt > MAX_VERIFICATION_RETRIES) {
|
|
638
|
+
// #4175: For complete-milestone, a blocker placeholder is harmful —
|
|
639
|
+
// the stub SUMMARY has no recovery value (milestone is terminal),
|
|
640
|
+
// it does not update DB status (so deriveState never advances),
|
|
641
|
+
// and it fools stopAuto's presence check into merging a milestone
|
|
642
|
+
// that was never legitimately completed. Pause auto-mode with a
|
|
643
|
+
// clear single failure signal and preserve the worktree branch.
|
|
644
|
+
if (s.currentUnit.type === "complete-milestone") {
|
|
645
|
+
debugLog("postUnit", {
|
|
646
|
+
phase: "artifact-verify-pause-complete-milestone",
|
|
647
|
+
unitType: s.currentUnit.type,
|
|
648
|
+
unitId: s.currentUnit.id,
|
|
649
|
+
attempt,
|
|
650
|
+
maxRetries: MAX_VERIFICATION_RETRIES,
|
|
651
|
+
});
|
|
652
|
+
s.verificationRetryCount.delete(retryKey);
|
|
653
|
+
s.pendingVerificationRetry = null;
|
|
654
|
+
ctx.ui.notify(
|
|
655
|
+
`Milestone ${s.currentUnit.id} verification failed after ${MAX_VERIFICATION_RETRIES} retries — worktree branch preserved. Re-run /gsd auto once blockers are resolved.`,
|
|
656
|
+
"error",
|
|
657
|
+
);
|
|
658
|
+
await pauseAuto(ctx, pi);
|
|
659
|
+
return "dispatched";
|
|
660
|
+
}
|
|
661
|
+
|
|
622
662
|
// Retries exhausted — write a blocker placeholder so the pipeline
|
|
623
663
|
// can advance past this stuck unit (#2653).
|
|
624
664
|
debugLog("postUnit", {
|
|
@@ -1025,8 +1065,17 @@ export async function postUnitPostVerification(pctx: PostUnitContext): Promise<"
|
|
|
1025
1065
|
}
|
|
1026
1066
|
}
|
|
1027
1067
|
|
|
1028
|
-
// Step mode → show wizard instead of dispatch
|
|
1068
|
+
// Step mode → show wizard instead of dispatch.
|
|
1069
|
+
// Without this notify(), /gsd in step mode finishes a unit and silently
|
|
1070
|
+
// exits the loop, leaving the user with no hint to /clear and /gsd again.
|
|
1029
1071
|
if (s.stepMode) {
|
|
1072
|
+
try {
|
|
1073
|
+
const nextState = await deriveState(s.basePath);
|
|
1074
|
+
ctx.ui.notify(buildStepCompleteMessage(nextState), "info");
|
|
1075
|
+
} catch (e) {
|
|
1076
|
+
debugLog("postUnit", { phase: "step-wizard-notify", error: String(e) });
|
|
1077
|
+
ctx.ui.notify(STEP_COMPLETE_FALLBACK_MESSAGE, "info");
|
|
1078
|
+
}
|
|
1030
1079
|
return "step-wizard";
|
|
1031
1080
|
}
|
|
1032
1081
|
|
|
@@ -230,6 +230,23 @@ export async function recoverTimedOutUnit(
|
|
|
230
230
|
return "recovered";
|
|
231
231
|
}
|
|
232
232
|
|
|
233
|
+
// #4175: For complete-milestone, never write a blocker placeholder — a stub
|
|
234
|
+
// SUMMARY has no recovery value (milestone is terminal), it does not update
|
|
235
|
+
// DB status, and downstream merge paths can treat the stub as a legitimate
|
|
236
|
+
// completion signal. Pause instead so the worktree branch is preserved.
|
|
237
|
+
if (unitType === "complete-milestone") {
|
|
238
|
+
writeUnitRuntimeRecord(basePath, unitType, unitId, currentUnitStartedAt, {
|
|
239
|
+
phase: "paused",
|
|
240
|
+
recoveryAttempts: recoveryAttempts + 1,
|
|
241
|
+
lastRecoveryReason: reason,
|
|
242
|
+
});
|
|
243
|
+
ctx.ui.notify(
|
|
244
|
+
`Milestone ${unitId} ${reason}-recovery exhausted ${maxRecoveryAttempts} attempt(s) — worktree branch preserved. Re-run /gsd auto once blockers are resolved.`,
|
|
245
|
+
"error",
|
|
246
|
+
);
|
|
247
|
+
return "paused";
|
|
248
|
+
}
|
|
249
|
+
|
|
233
250
|
// Retries exhausted — write a blocker placeholder and advance the pipeline
|
|
234
251
|
// instead of silently stalling.
|
|
235
252
|
const placeholder = writeBlockerPlaceholder(
|
|
@@ -12,10 +12,15 @@
|
|
|
12
12
|
|
|
13
13
|
import type { ExtensionContext, ExtensionAPI } from "@gsd/pi-coding-agent";
|
|
14
14
|
import { mkdirSync, writeFileSync } from "node:fs";
|
|
15
|
-
import { resolveSliceFile, resolveSlicePath } from "./paths.js";
|
|
15
|
+
import { resolveSliceFile, resolveSlicePath, resolveMilestoneFile } from "./paths.js";
|
|
16
16
|
import { parseUnitId } from "./unit-id.js";
|
|
17
|
-
import { isDbAvailable, getTask, getSliceTasks, type TaskRow } from "./gsd-db.js";
|
|
17
|
+
import { isDbAvailable, getTask, getSliceTasks, getMilestoneSlices, type TaskRow } from "./gsd-db.js";
|
|
18
18
|
import { loadEffectiveGSDPreferences } from "./preferences.js";
|
|
19
|
+
import { extractVerdict } from "./verdict-parser.js";
|
|
20
|
+
import { isClosedStatus } from "./status-guards.js";
|
|
21
|
+
import { loadFile } from "./files.js";
|
|
22
|
+
import { parseRoadmap } from "./parsers-legacy.js";
|
|
23
|
+
import { isMilestoneComplete } from "./state.js";
|
|
19
24
|
import {
|
|
20
25
|
runVerificationGate,
|
|
21
26
|
formatFailureContext,
|
|
@@ -43,6 +48,88 @@ function isInfraVerificationFailure(stderr: string): boolean {
|
|
|
43
48
|
);
|
|
44
49
|
}
|
|
45
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Post-unit guard for `validate-milestone` units (#4094).
|
|
53
|
+
*
|
|
54
|
+
* When validate-milestone writes verdict=needs-remediation, the agent is
|
|
55
|
+
* expected to also call gsd_reassess_roadmap in the same turn to add
|
|
56
|
+
* remediation slices. If they don't, the state machine re-derives
|
|
57
|
+
* `phase: validating-milestone` indefinitely (all slices still complete +
|
|
58
|
+
* verdict still needs-remediation), wasting ~3 dispatches before the stuck
|
|
59
|
+
* detector fires.
|
|
60
|
+
*
|
|
61
|
+
* This guard fires immediately on the first occurrence: if VALIDATION.md
|
|
62
|
+
* verdict is needs-remediation and no incomplete slices exist for the
|
|
63
|
+
* milestone, pause the auto-loop with a clear blocker.
|
|
64
|
+
*/
|
|
65
|
+
async function runValidateMilestonePostCheck(
|
|
66
|
+
vctx: VerificationContext,
|
|
67
|
+
pauseAuto: (ctx?: ExtensionContext, pi?: ExtensionAPI) => Promise<void>,
|
|
68
|
+
): Promise<VerificationResult> {
|
|
69
|
+
const { s, ctx, pi } = vctx;
|
|
70
|
+
if (!s.currentUnit) return "continue";
|
|
71
|
+
|
|
72
|
+
const { milestone: mid } = parseUnitId(s.currentUnit.id);
|
|
73
|
+
if (!mid) return "continue";
|
|
74
|
+
|
|
75
|
+
const validationFile = resolveMilestoneFile(s.basePath, mid, "VALIDATION");
|
|
76
|
+
if (!validationFile) return "continue";
|
|
77
|
+
|
|
78
|
+
const validationContent = await loadFile(validationFile);
|
|
79
|
+
if (!validationContent) return "continue";
|
|
80
|
+
|
|
81
|
+
const verdict = extractVerdict(validationContent);
|
|
82
|
+
if (verdict !== "needs-remediation") return "continue";
|
|
83
|
+
|
|
84
|
+
const incompleteSliceCount = await countIncompleteSlices(s.basePath, mid);
|
|
85
|
+
|
|
86
|
+
// If any non-closed slices exist, the agent successfully queued remediation
|
|
87
|
+
// work — proceed normally. The state machine will execute those slices and
|
|
88
|
+
// re-validate per the #3596/#3670 fix.
|
|
89
|
+
if (incompleteSliceCount > 0) return "continue";
|
|
90
|
+
|
|
91
|
+
ctx.ui.notify(
|
|
92
|
+
`Milestone ${mid} validation returned verdict=needs-remediation but no remediation slices were added. Pausing for human review.`,
|
|
93
|
+
"error",
|
|
94
|
+
);
|
|
95
|
+
process.stderr.write(
|
|
96
|
+
`validate-milestone: pausing — verdict=needs-remediation with no incomplete slices for ${mid}. ` +
|
|
97
|
+
`The agent must call gsd_reassess_roadmap to add remediation slices before re-validation.\n`,
|
|
98
|
+
);
|
|
99
|
+
await pauseAuto(ctx, pi);
|
|
100
|
+
return "pause";
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Count slices for a milestone that are not in a closed status.
|
|
105
|
+
* DB-backed projects are authoritative (#4094 peer review); falls back to
|
|
106
|
+
* roadmap parsing only when the DB is unavailable.
|
|
107
|
+
*/
|
|
108
|
+
async function countIncompleteSlices(basePath: string, milestoneId: string): Promise<number> {
|
|
109
|
+
if (isDbAvailable()) {
|
|
110
|
+
const slices = getMilestoneSlices(milestoneId);
|
|
111
|
+
if (slices.length === 0) {
|
|
112
|
+
// No DB rows — treat as "unknown", do not pause.
|
|
113
|
+
return 1;
|
|
114
|
+
}
|
|
115
|
+
return slices.filter((slice) => !isClosedStatus(slice.status)).length;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Filesystem fallback: parse the roadmap markdown.
|
|
119
|
+
try {
|
|
120
|
+
const roadmapFile = resolveMilestoneFile(basePath, milestoneId, "ROADMAP");
|
|
121
|
+
if (!roadmapFile) return 1;
|
|
122
|
+
const roadmapContent = await loadFile(roadmapFile);
|
|
123
|
+
if (!roadmapContent) return 1;
|
|
124
|
+
const roadmap = parseRoadmap(roadmapContent);
|
|
125
|
+
if (roadmap.slices.length === 0) return 1;
|
|
126
|
+
return isMilestoneComplete(roadmap) ? 0 : 1;
|
|
127
|
+
} catch {
|
|
128
|
+
// Parsing failures should not cause false-positive pauses.
|
|
129
|
+
return 1;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
46
133
|
/**
|
|
47
134
|
* Run the verification gate for the current execute-task unit.
|
|
48
135
|
* Returns:
|
|
@@ -56,7 +143,15 @@ export async function runPostUnitVerification(
|
|
|
56
143
|
): Promise<VerificationResult> {
|
|
57
144
|
const { s, ctx, pi } = vctx;
|
|
58
145
|
|
|
59
|
-
if (!s.currentUnit
|
|
146
|
+
if (!s.currentUnit) {
|
|
147
|
+
return "continue";
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (s.currentUnit.type === "validate-milestone") {
|
|
151
|
+
return await runValidateMilestonePostCheck(vctx, pauseAuto);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (s.currentUnit.type !== "execute-task") {
|
|
60
155
|
return "continue";
|
|
61
156
|
}
|
|
62
157
|
|
|
@@ -187,7 +187,7 @@ import {
|
|
|
187
187
|
deregisterSigtermHandler as _deregisterSigtermHandler,
|
|
188
188
|
detectWorkingTreeActivity,
|
|
189
189
|
} from "./auto-supervisor.js";
|
|
190
|
-
import { isDbAvailable } from "./gsd-db.js";
|
|
190
|
+
import { isDbAvailable, getMilestone } from "./gsd-db.js";
|
|
191
191
|
import { countPendingCaptures } from "./captures.js";
|
|
192
192
|
import { clearCmuxSidebar, logCmuxEvent, syncCmuxSidebar } from "../cmux/index.js";
|
|
193
193
|
|
|
@@ -195,6 +195,7 @@ import { clearCmuxSidebar, logCmuxEvent, syncCmuxSidebar } from "../cmux/index.j
|
|
|
195
195
|
import { startUnitSupervision } from "./auto-timers.js";
|
|
196
196
|
import { runPostUnitVerification } from "./auto-verification.js";
|
|
197
197
|
import {
|
|
198
|
+
autoCommitUnit,
|
|
198
199
|
postUnitPreVerification,
|
|
199
200
|
postUnitPostVerification,
|
|
200
201
|
} from "./auto-post-unit.js";
|
|
@@ -761,24 +762,36 @@ export async function stopAuto(
|
|
|
761
762
|
: { notify: () => {} };
|
|
762
763
|
const resolver = buildResolver();
|
|
763
764
|
|
|
764
|
-
// Check if the milestone is complete
|
|
765
|
+
// Check if the milestone is complete. DB status is the authoritative
|
|
766
|
+
// signal — only a successful gsd_complete_milestone call flips it to
|
|
767
|
+
// "complete" (tools/complete-milestone.ts). SUMMARY file presence is
|
|
768
|
+
// NOT sufficient: a blocker placeholder stub or a partial write can
|
|
769
|
+
// leave a file behind without the milestone actually being done,
|
|
770
|
+
// which previously caused stopAuto to merge a failed milestone and
|
|
771
|
+
// emit a misleading metadata-only merge warning (#4175).
|
|
772
|
+
// DB-unavailable projects fall back to SUMMARY-file presence.
|
|
765
773
|
let milestoneComplete = false;
|
|
766
774
|
try {
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
// Also check in the worktree path (SUMMARY may not be synced yet)
|
|
774
|
-
const wtSummaryPath = resolveMilestoneFile(
|
|
775
|
-
s.basePath,
|
|
775
|
+
if (isDbAvailable()) {
|
|
776
|
+
const dbRow = getMilestone(s.currentMilestoneId);
|
|
777
|
+
milestoneComplete = dbRow?.status === "complete";
|
|
778
|
+
} else {
|
|
779
|
+
const summaryPath = resolveMilestoneFile(
|
|
780
|
+
s.originalBasePath || s.basePath,
|
|
776
781
|
s.currentMilestoneId,
|
|
777
782
|
"SUMMARY",
|
|
778
783
|
);
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
784
|
+
if (!summaryPath) {
|
|
785
|
+
// Also check in the worktree path (SUMMARY may not be synced yet)
|
|
786
|
+
const wtSummaryPath = resolveMilestoneFile(
|
|
787
|
+
s.basePath,
|
|
788
|
+
s.currentMilestoneId,
|
|
789
|
+
"SUMMARY",
|
|
790
|
+
);
|
|
791
|
+
milestoneComplete = wtSummaryPath !== null;
|
|
792
|
+
} else {
|
|
793
|
+
milestoneComplete = true;
|
|
794
|
+
}
|
|
782
795
|
}
|
|
783
796
|
} catch (err) {
|
|
784
797
|
// Non-fatal — fall through to preserveBranch path
|
|
@@ -1168,6 +1181,7 @@ function buildLoopDeps(): LoopDeps {
|
|
|
1168
1181
|
getMainBranch,
|
|
1169
1182
|
// Unit closeout + runtime records
|
|
1170
1183
|
closeoutUnit,
|
|
1184
|
+
autoCommitUnit,
|
|
1171
1185
|
recordOutcome,
|
|
1172
1186
|
writeLock,
|
|
1173
1187
|
captureAvailableSkills,
|
|
@@ -1388,6 +1402,11 @@ export async function startAuto(
|
|
|
1388
1402
|
s.stepMode = requestedStepMode;
|
|
1389
1403
|
s.cmdCtx = ctx;
|
|
1390
1404
|
s.basePath = base;
|
|
1405
|
+
// Ensure the workflow-logger audit log is pinned to the project root
|
|
1406
|
+
// even when auto-mode is entered via a path that bypasses the
|
|
1407
|
+
// bootstrap/dynamic-tools ensureDbOpen() → setLogBasePath() chain
|
|
1408
|
+
// (e.g. /clear resume, hot-reload).
|
|
1409
|
+
setLogBasePath(base);
|
|
1391
1410
|
s.unitDispatchCount.clear();
|
|
1392
1411
|
s.unitLifetimeDispatches.clear();
|
|
1393
1412
|
if (!getLedger()) initMetrics(base);
|