gsd-pi 2.82.0-dev.3709f22a5 → 2.82.0-dev.4285182e8
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/README.md +2 -2
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +1 -1
- package/dist/resources/extensions/gsd/auto/loop.js +14 -1
- package/dist/resources/extensions/gsd/auto/phases.js +53 -29
- package/dist/resources/extensions/gsd/auto/session.js +4 -0
- package/dist/resources/extensions/gsd/auto/workflow-kernel.js +3 -0
- package/dist/resources/extensions/gsd/auto-direct-dispatch.js +1 -0
- package/dist/resources/extensions/gsd/auto-dispatch.js +13 -19
- package/dist/resources/extensions/gsd/auto-post-unit.js +13 -6
- package/dist/resources/extensions/gsd/auto-recovery.js +40 -13
- package/dist/resources/extensions/gsd/auto-start.js +3 -3
- package/dist/resources/extensions/gsd/auto-verification.js +17 -4
- package/dist/resources/extensions/gsd/auto-worktree.js +65 -9
- package/dist/resources/extensions/gsd/auto.js +14 -8
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +6 -1
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +1 -1
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +1 -1
- package/dist/resources/extensions/gsd/commands/catalog.js +7 -1
- package/dist/resources/extensions/gsd/commands/handlers/core.js +1 -0
- package/dist/resources/extensions/gsd/commands/handlers/ops.js +5 -0
- package/dist/resources/extensions/gsd/commands-prefs-wizard.js +7 -2
- package/dist/resources/extensions/gsd/commands-verdict.js +139 -0
- package/dist/resources/extensions/gsd/crash-recovery.js +16 -4
- package/dist/resources/extensions/gsd/db/milestone-leases.js +24 -0
- package/dist/resources/extensions/gsd/forensics.js +3 -3
- package/dist/resources/extensions/gsd/git-service.js +6 -2
- package/dist/resources/extensions/gsd/gsd-db.js +20 -6
- package/dist/resources/extensions/gsd/guided-flow-queue.js +4 -3
- package/dist/resources/extensions/gsd/guided-flow.js +8 -5
- package/dist/resources/extensions/gsd/markdown-renderer.js +10 -8
- package/dist/resources/extensions/gsd/paths.js +4 -0
- package/dist/resources/extensions/gsd/queue-reorder-ui.js +30 -13
- package/dist/resources/extensions/gsd/state.js +3 -3
- package/dist/resources/extensions/gsd/status-guards.js +7 -0
- package/dist/resources/extensions/gsd/templates/plan.md +1 -0
- package/dist/resources/extensions/gsd/templates/task-plan.md +6 -0
- package/dist/resources/extensions/gsd/tools/plan-slice.js +3 -5
- package/dist/resources/extensions/gsd/workflow-mcp.js +17 -1
- package/dist/resources/extensions/gsd/worktree-manager.js +1 -1
- package/dist/resources/extensions/ttsr/ttsr-manager.js +3 -1
- package/dist/tsconfig.extensions.tsbuildinfo +1 -1
- 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 +3 -3
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/react-loadable-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/api/browse-directories/route.js +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-react-loadable-manifest.js +1 -1
- 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/dist/web/standalone/.next/static/chunks/8359.65b24fac92188a6b.js +10 -0
- package/dist/web/standalone/.next/static/chunks/9441.ff70bb53f6835771.js +1 -0
- package/dist/web/standalone/.next/static/chunks/{webpack-9a4db269f9ed63ad.js → webpack-855d616060cb6e59.js} +1 -1
- package/package.json +1 -1
- package/packages/pi-ai/dist/providers/google-gemini-cli.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/google-gemini-cli.js +5 -0
- package/packages/pi-ai/dist/providers/google-gemini-cli.js.map +1 -1
- package/packages/pi-ai/dist/providers/google-gemini-cli.test.d.ts +2 -0
- package/packages/pi-ai/dist/providers/google-gemini-cli.test.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/google-gemini-cli.test.js +41 -0
- package/packages/pi-ai/dist/providers/google-gemini-cli.test.js.map +1 -0
- package/packages/pi-ai/src/providers/google-gemini-cli.test.ts +49 -0
- package/packages/pi-ai/src/providers/google-gemini-cli.ts +7 -0
- package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js +44 -3
- package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.js +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +71 -97
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-ordering.test.js +25 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-ordering.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +2 -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 +24 -10
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/src/core/chat-controller-ordering.test.ts +53 -3
- package/packages/pi-coding-agent/src/core/sdk.ts +1 -1
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +75 -102
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode-ordering.test.ts +30 -1
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +29 -10
- package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-tui/dist/__tests__/terminal.test.d.ts +2 -0
- package/packages/pi-tui/dist/__tests__/terminal.test.d.ts.map +1 -0
- package/packages/pi-tui/dist/__tests__/terminal.test.js +103 -0
- package/packages/pi-tui/dist/__tests__/terminal.test.js.map +1 -0
- package/packages/pi-tui/dist/terminal.d.ts +2 -0
- package/packages/pi-tui/dist/terminal.d.ts.map +1 -1
- package/packages/pi-tui/dist/terminal.js +12 -0
- package/packages/pi-tui/dist/terminal.js.map +1 -1
- package/packages/pi-tui/src/__tests__/terminal.test.ts +121 -0
- package/packages/pi-tui/src/terminal.ts +11 -0
- package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +1 -1
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +9 -0
- package/src/resources/extensions/gsd/auto/loop.ts +14 -1
- package/src/resources/extensions/gsd/auto/phases.ts +60 -36
- package/src/resources/extensions/gsd/auto/session.ts +4 -0
- package/src/resources/extensions/gsd/auto/workflow-kernel.ts +5 -1
- package/src/resources/extensions/gsd/auto-direct-dispatch.ts +1 -0
- package/src/resources/extensions/gsd/auto-dispatch.ts +13 -19
- package/src/resources/extensions/gsd/auto-post-unit.ts +14 -6
- package/src/resources/extensions/gsd/auto-recovery.ts +45 -11
- package/src/resources/extensions/gsd/auto-start.ts +2 -3
- package/src/resources/extensions/gsd/auto-verification.ts +22 -2
- package/src/resources/extensions/gsd/auto-worktree.ts +74 -9
- package/src/resources/extensions/gsd/auto.ts +13 -8
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +9 -1
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +1 -1
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +1 -1
- package/src/resources/extensions/gsd/commands/catalog.ts +7 -1
- package/src/resources/extensions/gsd/commands/handlers/core.ts +1 -0
- package/src/resources/extensions/gsd/commands/handlers/ops.ts +5 -0
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +8 -3
- package/src/resources/extensions/gsd/commands-verdict.ts +202 -0
- package/src/resources/extensions/gsd/crash-recovery.ts +16 -2
- package/src/resources/extensions/gsd/db/milestone-leases.ts +26 -0
- package/src/resources/extensions/gsd/forensics.ts +3 -3
- package/src/resources/extensions/gsd/git-service.ts +6 -3
- package/src/resources/extensions/gsd/gsd-db.ts +18 -6
- package/src/resources/extensions/gsd/guided-flow-queue.ts +4 -3
- package/src/resources/extensions/gsd/guided-flow.ts +8 -5
- package/src/resources/extensions/gsd/markdown-renderer.ts +10 -8
- package/src/resources/extensions/gsd/paths.ts +5 -0
- package/src/resources/extensions/gsd/queue-reorder-ui.ts +31 -13
- package/src/resources/extensions/gsd/state.ts +3 -3
- package/src/resources/extensions/gsd/status-guards.ts +8 -0
- package/src/resources/extensions/gsd/templates/plan.md +1 -0
- package/src/resources/extensions/gsd/templates/task-plan.md +6 -0
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +2 -0
- package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +139 -1
- package/src/resources/extensions/gsd/tests/auto-post-unit-step-message.test.ts +6 -5
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +76 -5
- package/src/resources/extensions/gsd/tests/checkout-branch-stash-guard.test.ts +87 -0
- package/src/resources/extensions/gsd/tests/commands-verdict.test.ts +378 -0
- package/src/resources/extensions/gsd/tests/crash-recovery-via-db.test.ts +43 -0
- package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +2 -0
- package/src/resources/extensions/gsd/tests/dispatch-complete-milestone-guard.test.ts +27 -0
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +11 -0
- package/src/resources/extensions/gsd/tests/gsdroot-worktree-detection.test.ts +5 -2
- package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +9 -0
- package/src/resources/extensions/gsd/tests/journal-integration.test.ts +46 -0
- package/src/resources/extensions/gsd/tests/merge-db-cycle.test.ts +179 -0
- package/src/resources/extensions/gsd/tests/pipeline-variant-dispatch.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/plan-slice.test.ts +26 -1
- package/src/resources/extensions/gsd/tests/post-unit-state-rebuild.test.ts +84 -0
- package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +59 -0
- package/src/resources/extensions/gsd/tests/provider-errors.test.ts +18 -1
- package/src/resources/extensions/gsd/tests/quality-gates.test.ts +6 -0
- package/src/resources/extensions/gsd/tests/queue-reorder-ui.test.ts +54 -0
- package/src/resources/extensions/gsd/tests/remediation-completion-guard.test.ts +43 -0
- package/src/resources/extensions/gsd/tests/run-uat-replay-cap.test.ts +2 -3
- package/src/resources/extensions/gsd/tests/status-guards.test.ts +13 -1
- package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +17 -0
- package/src/resources/extensions/gsd/tests/validate-milestone-stuck-guard.test.ts +29 -2
- package/src/resources/extensions/gsd/tests/workflow-kernel.test.ts +7 -0
- package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +18 -0
- package/src/resources/extensions/gsd/tools/plan-slice.ts +3 -4
- package/src/resources/extensions/gsd/workflow-mcp.ts +18 -1
- package/src/resources/extensions/gsd/worktree-manager.ts +1 -1
- package/src/resources/extensions/ttsr/ttsr-manager.ts +5 -1
- package/dist/web/standalone/.next/static/chunks/8359.7eb3bb8f8ecf4c01.js +0 -10
- package/dist/web/standalone/.next/static/chunks/9441.1081da1125d1764f.js +0 -1
- /package/dist/web/standalone/.next/static/{kkGf3_VaPFkiDNV_D7Dtl → 78uanrILNOKG-Jpi4itAE}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{kkGf3_VaPFkiDNV_D7Dtl → 78uanrILNOKG-Jpi4itAE}/_ssgManifest.js +0 -0
package/README.md
CHANGED
|
@@ -350,7 +350,7 @@ This is what makes GSD different. Run it, walk away, come back to built software
|
|
|
350
350
|
|
|
351
351
|
Auto mode is a state machine driven by the GSD database at the project root. It derives the next unit of work from authoritative SQLite state, creates a fresh agent session, injects a focused prompt with all relevant context pre-inlined, and lets the LLM execute. When the LLM finishes, auto mode persists the result to the database, refreshes markdown projections such as `STATE.md`, and dispatches the next unit.
|
|
352
352
|
|
|
353
|
-
The database is authoritative for milestones, slices, tasks, requirements, summaries, and completion status. Durable decisions and project knowledge are stored in the `memories` table: decisions are `architecture` memories, and KNOWLEDGE patterns/lessons are `pattern`/`gotcha` memories. Markdown under `.gsd/` is a rendered projection for review, prompts, and git-friendly history; it is not a runtime fallback unless you explicitly run a recovery/import command. In worktree mode,
|
|
353
|
+
The database is authoritative for milestones, slices, tasks, requirements, summaries, and completion status. Durable decisions and project knowledge are stored in the `memories` table: decisions are `architecture` memories, and KNOWLEDGE patterns/lessons are `pattern`/`gotcha` memories. Markdown under `.gsd/` is a rendered projection for review, prompts, and git-friendly history; it is not a runtime fallback unless you explicitly run a recovery/import command. In worktree mode, artifact/projection writes are rendered under the active worktree-local `.gsd/`, while the project-root DB remains authoritative runtime state.
|
|
354
354
|
|
|
355
355
|
`KNOWLEDGE.md` is hybrid: rules remain file-canonical, while patterns and lessons are stored in the `memories` table and rendered back into `KNOWLEDGE.md` on the next session-start projection. Existing pattern and lesson rows are backfilled into memories before projection, so newly captured patterns and lessons may appear in memory-backed prompt context before the file view refreshes.
|
|
356
356
|
|
|
@@ -503,7 +503,7 @@ On first run, GSD launches a branded setup wizard that walks you through LLM pro
|
|
|
503
503
|
| `/gsd mcp` | MCP server status and connectivity |
|
|
504
504
|
| `/gsd status` | Progress dashboard |
|
|
505
505
|
| `/gsd brief <mode>` | Generate a visual HTML brief (diagram, plan, diff, recap, table, slides) |
|
|
506
|
-
| `/gsd queue` | Queue future milestones (safe during auto mode)
|
|
506
|
+
| `/gsd queue` | Queue/reorder future milestones (`pending`, `queued`, or legacy `planned`; safe during auto mode) |
|
|
507
507
|
| `/gsd prefs` | Model selection, timeouts, budget ceiling |
|
|
508
508
|
| `/gsd migrate` | Migrate a v1 `.planning` directory to `.gsd` format |
|
|
509
509
|
| `/gsd help` | Categorized command reference for all GSD subcommands |
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
8102192ede112252
|
|
@@ -269,7 +269,7 @@ function makeErrorMessage(model, errorMsg) {
|
|
|
269
269
|
export function isClaudeCodeAbortErrorMessage(message) {
|
|
270
270
|
if (!message)
|
|
271
271
|
return false;
|
|
272
|
-
return /\b(?:claude code process aborted by user|request aborted by user|process aborted by user)\b/i.test(message);
|
|
272
|
+
return /\b(?:claude code process aborted by user|request aborted by user|process aborted by user|aborterror)\b/i.test(message);
|
|
273
273
|
}
|
|
274
274
|
function isBareClaudeCodeAbortErrorMessage(message) {
|
|
275
275
|
if (!message)
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// Project/App: GSD-2
|
|
2
|
+
// File Purpose: Main auto-mode execution loop.
|
|
1
3
|
/**
|
|
2
4
|
* auto/loop.ts — Main auto-mode execution loop.
|
|
3
5
|
*
|
|
@@ -789,11 +791,18 @@ export async function autoLoop(ctx, pi, s, deps, options) {
|
|
|
789
791
|
unitId: iterData.unitId,
|
|
790
792
|
});
|
|
791
793
|
const finalizeReason = finalizeResult.action === "break" ? finalizeResult.reason : undefined;
|
|
794
|
+
const finalizeStatus = finalizeReason === "step-wizard"
|
|
795
|
+
? "completed"
|
|
796
|
+
: finalizeResult.action === "next"
|
|
797
|
+
? "completed"
|
|
798
|
+
: finalizeResult.action === "continue"
|
|
799
|
+
? "retry"
|
|
800
|
+
: "stopped";
|
|
792
801
|
journalReporter.emit("post-unit-finalize-end", {
|
|
793
802
|
iteration,
|
|
794
803
|
unitType: iterData.unitType,
|
|
795
804
|
unitId: iterData.unitId,
|
|
796
|
-
status:
|
|
805
|
+
status: finalizeStatus,
|
|
797
806
|
action: finalizeResult.action,
|
|
798
807
|
...(finalizeReason ? { reason: finalizeReason } : {}),
|
|
799
808
|
});
|
|
@@ -837,6 +846,10 @@ export async function autoLoop(ctx, pi, s, deps, options) {
|
|
|
837
846
|
}) || dispatchSettled;
|
|
838
847
|
completeIteration();
|
|
839
848
|
finishTurn("completed");
|
|
849
|
+
if (finalizeDecision.action === "complete-and-break") {
|
|
850
|
+
s.preserveStepSurfaceAfterLoopExit = true;
|
|
851
|
+
break;
|
|
852
|
+
}
|
|
840
853
|
}
|
|
841
854
|
catch (loopErr) {
|
|
842
855
|
// ── Blanket catch: absorb unexpected exceptions, apply graduated recovery ──
|
|
@@ -1338,35 +1338,8 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) {
|
|
|
1338
1338
|
s.currentUnit.type === unitType &&
|
|
1339
1339
|
s.currentUnit.id === unitId);
|
|
1340
1340
|
const previousTier = s.currentUnitRouting?.tier;
|
|
1341
|
-
// Scope workflow-logger buffer to this unit so post-finalize drains are
|
|
1342
|
-
// per-unit. Without this, the module-level _buffer accumulates across every
|
|
1343
|
-
// unit in the same Node process (see workflow-logger.ts module header).
|
|
1344
|
-
_resetLogs();
|
|
1345
1341
|
const dispatchKey = `${unitType}/${unitId}`;
|
|
1346
|
-
|
|
1347
|
-
s.currentUnit = { type: unitType, id: unitId, startedAt: Date.now() };
|
|
1348
|
-
s.lastGitActionFailure = null;
|
|
1349
|
-
s.lastGitActionStatus = null;
|
|
1350
|
-
s.lastUnitAgentEndMessages = null;
|
|
1351
|
-
setCurrentPhase(unitType, {
|
|
1352
|
-
basePath: s.basePath,
|
|
1353
|
-
traceId: ic.flowId,
|
|
1354
|
-
turnId: `iter-${ic.iteration}`,
|
|
1355
|
-
causedBy: "unit-start",
|
|
1356
|
-
});
|
|
1357
|
-
s.lastToolInvocationError = null; // #2883: clear stale error from previous unit
|
|
1358
|
-
const unitStartSeq = ic.nextSeq();
|
|
1359
|
-
deps.emitJournalEvent({ ts: new Date().toISOString(), flowId: ic.flowId, seq: unitStartSeq, eventType: "unit-start", data: { unitType, unitId } });
|
|
1360
|
-
deps.captureAvailableSkills();
|
|
1361
|
-
writeUnitRuntimeRecord(s.basePath, unitType, unitId, s.currentUnit.startedAt, {
|
|
1362
|
-
phase: "dispatched",
|
|
1363
|
-
wrapupWarningSent: false,
|
|
1364
|
-
timeoutAt: null,
|
|
1365
|
-
lastProgressAt: s.currentUnit.startedAt,
|
|
1366
|
-
progressCount: 0,
|
|
1367
|
-
lastProgressKind: "dispatch",
|
|
1368
|
-
recoveryAttempts: 0, // Reset so re-dispatched units get full recovery budget (#2322)
|
|
1369
|
-
});
|
|
1342
|
+
const nextDispatchCount = (s.unitDispatchCount.get(dispatchKey) ?? 0) + 1;
|
|
1370
1343
|
// Status bar (widget + preconditions deferred until after model selection — see #2899)
|
|
1371
1344
|
ctx.ui.setStatus("gsd-auto", "auto");
|
|
1372
1345
|
if (mid)
|
|
@@ -1420,7 +1393,7 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) {
|
|
|
1420
1393
|
finalPrompt = `${capped}\n\n---\n\n${finalPrompt}`;
|
|
1421
1394
|
s.pendingCrashRecovery = null;
|
|
1422
1395
|
}
|
|
1423
|
-
else if (
|
|
1396
|
+
else if (nextDispatchCount > 1) {
|
|
1424
1397
|
const diagnostic = deps.getDeepDiagnostic(s.basePath);
|
|
1425
1398
|
if (diagnostic) {
|
|
1426
1399
|
const cappedDiag = diagnostic.length > MAX_RECOVERY_CHARS
|
|
@@ -1459,6 +1432,11 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) {
|
|
|
1459
1432
|
logWarning("engine", "Prompt reorder failed", { error: msg });
|
|
1460
1433
|
}
|
|
1461
1434
|
// Select and apply model (with tier escalation on retry — normal units only)
|
|
1435
|
+
const prevUnitRouting = s.currentUnitRouting;
|
|
1436
|
+
const prevUnitModel = s.currentUnitModel;
|
|
1437
|
+
const prevDispatchedModelId = s.currentDispatchedModelId;
|
|
1438
|
+
const prevSessionModel = ctx.model;
|
|
1439
|
+
const prevSessionThinkingLevel = pi.getThinkingLevel();
|
|
1462
1440
|
const modelResult = await deps.selectAndApplyModel(ctx, pi, unitType, unitId, s.basePath, prefs, s.verbose, s.autoModeStartModel, sidecarItem ? undefined : { isRetry, previousTier }, undefined, s.manualSessionModelOverride, s.autoModeStartThinkingLevel);
|
|
1463
1441
|
s.currentUnitRouting =
|
|
1464
1442
|
modelResult.routing;
|
|
@@ -1502,12 +1480,58 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) {
|
|
|
1502
1480
|
? ctx.modelRegistry.getProviderAuthMode(ctx.model.provider)
|
|
1503
1481
|
: undefined,
|
|
1504
1482
|
baseUrl: s.currentUnitModel?.baseUrl ?? ctx.model?.baseUrl,
|
|
1483
|
+
activeTools: typeof pi.getActiveTools === "function" ? pi.getActiveTools() : [],
|
|
1505
1484
|
});
|
|
1506
1485
|
if (compatibilityError) {
|
|
1486
|
+
s.currentUnitRouting = prevUnitRouting;
|
|
1487
|
+
s.currentUnitModel = prevUnitModel;
|
|
1488
|
+
s.currentDispatchedModelId = prevDispatchedModelId;
|
|
1489
|
+
if (s.checkpointSha) {
|
|
1490
|
+
cleanupCheckpoint(s.basePath, unitId);
|
|
1491
|
+
s.checkpointSha = null;
|
|
1492
|
+
}
|
|
1493
|
+
if (prevSessionModel) {
|
|
1494
|
+
const ok = await pi.setModel(prevSessionModel, { persist: false });
|
|
1495
|
+
if (!ok) {
|
|
1496
|
+
ctx.ui.notify("Failed to restore previous session model after compatibility check failure.", "warning");
|
|
1497
|
+
}
|
|
1498
|
+
if (prevSessionThinkingLevel) {
|
|
1499
|
+
pi.setThinkingLevel(prevSessionThinkingLevel);
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1507
1502
|
ctx.ui.notify(compatibilityError, "error");
|
|
1508
1503
|
await deps.stopAuto(ctx, pi, compatibilityError);
|
|
1509
1504
|
return { action: "break", reason: "workflow-capability" };
|
|
1510
1505
|
}
|
|
1506
|
+
// Scope workflow-logger buffer to this unit so post-finalize drains are
|
|
1507
|
+
// per-unit. Without this, the module-level _buffer accumulates across every
|
|
1508
|
+
// unit in the same Node process (see workflow-logger.ts module header).
|
|
1509
|
+
_resetLogs();
|
|
1510
|
+
const unitStartedAt = Date.now();
|
|
1511
|
+
s.unitDispatchCount.set(dispatchKey, nextDispatchCount);
|
|
1512
|
+
s.currentUnit = { type: unitType, id: unitId, startedAt: unitStartedAt };
|
|
1513
|
+
s.lastGitActionFailure = null;
|
|
1514
|
+
s.lastGitActionStatus = null;
|
|
1515
|
+
s.lastUnitAgentEndMessages = null;
|
|
1516
|
+
setCurrentPhase(unitType, {
|
|
1517
|
+
basePath: s.basePath,
|
|
1518
|
+
traceId: ic.flowId,
|
|
1519
|
+
turnId: `iter-${ic.iteration}`,
|
|
1520
|
+
causedBy: "unit-start",
|
|
1521
|
+
});
|
|
1522
|
+
s.lastToolInvocationError = null; // #2883: clear stale error from previous unit
|
|
1523
|
+
const unitStartSeq = ic.nextSeq();
|
|
1524
|
+
deps.emitJournalEvent({ ts: new Date().toISOString(), flowId: ic.flowId, seq: unitStartSeq, eventType: "unit-start", data: { unitType, unitId } });
|
|
1525
|
+
deps.captureAvailableSkills();
|
|
1526
|
+
writeUnitRuntimeRecord(s.basePath, unitType, unitId, unitStartedAt, {
|
|
1527
|
+
phase: "dispatched",
|
|
1528
|
+
wrapupWarningSent: false,
|
|
1529
|
+
timeoutAt: null,
|
|
1530
|
+
lastProgressAt: unitStartedAt,
|
|
1531
|
+
progressCount: 0,
|
|
1532
|
+
lastProgressKind: "dispatch",
|
|
1533
|
+
recoveryAttempts: 0, // Reset so re-dispatched units get full recovery budget (#2322)
|
|
1534
|
+
});
|
|
1511
1535
|
// Progress widget + preconditions — deferred to after model selection so the
|
|
1512
1536
|
// widget's first render tick shows the correct model (#2899).
|
|
1513
1537
|
deps.updateProgressWidget(ctx, unitType, unitId, state);
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// Project/App: GSD-2
|
|
2
|
+
// File Purpose: Mutable auto-mode session state container.
|
|
1
3
|
/**
|
|
2
4
|
* AutoSession — encapsulates all mutable auto-mode state into a single instance.
|
|
3
5
|
*
|
|
@@ -26,6 +28,7 @@ export class AutoSession {
|
|
|
26
28
|
active = false;
|
|
27
29
|
paused = false;
|
|
28
30
|
completionStopInProgress = false;
|
|
31
|
+
preserveStepSurfaceAfterLoopExit = false;
|
|
29
32
|
stepMode = false;
|
|
30
33
|
verbose = false;
|
|
31
34
|
activeEngineId = null;
|
|
@@ -210,6 +213,7 @@ export class AutoSession {
|
|
|
210
213
|
this.active = false;
|
|
211
214
|
this.paused = false;
|
|
212
215
|
this.completionStopInProgress = false;
|
|
216
|
+
this.preserveStepSurfaceAfterLoopExit = false;
|
|
213
217
|
this.stepMode = false;
|
|
214
218
|
this.verbose = false;
|
|
215
219
|
this.activeEngineId = null;
|
|
@@ -66,6 +66,9 @@ export function decideEngineDispatch(input) {
|
|
|
66
66
|
export function decideFinalizeResult(input) {
|
|
67
67
|
if (input.action === "break") {
|
|
68
68
|
const reason = input.reason ?? "unknown";
|
|
69
|
+
if (reason === "step-wizard") {
|
|
70
|
+
return { action: "complete-and-break" };
|
|
71
|
+
}
|
|
69
72
|
return {
|
|
70
73
|
action: "stop",
|
|
71
74
|
failureClass: reason === "git-closeout-failure" ? "git" : "closeout",
|
|
@@ -224,6 +224,7 @@ export async function dispatchDirectPhase(ctx, pi, phase, base) {
|
|
|
224
224
|
unitType,
|
|
225
225
|
authMode: ctx.model?.provider ? ctx.modelRegistry.getProviderAuthMode(ctx.model.provider) : undefined,
|
|
226
226
|
baseUrl: ctx.model?.baseUrl,
|
|
227
|
+
activeTools: typeof pi.getActiveTools === "function" ? pi.getActiveTools() : [],
|
|
227
228
|
});
|
|
228
229
|
if (compatibilityError) {
|
|
229
230
|
ctx.ui.notify(compatibilityError, "error");
|
|
@@ -299,9 +299,7 @@ export const DISPATCH_RULES = [
|
|
|
299
299
|
const attempts = incrementUatCount(basePath, mid, sliceId);
|
|
300
300
|
if (attempts > MAX_UAT_ATTEMPTS) {
|
|
301
301
|
return {
|
|
302
|
-
action: "
|
|
303
|
-
reason: `run-uat for ${mid}/${sliceId} has been dispatched ${attempts - 1} times without producing a verdict. Verification commands may be broken — fix the UAT spec or manually write an ASSESSMENT verdict.`,
|
|
304
|
-
level: "warning",
|
|
302
|
+
action: "skip",
|
|
305
303
|
};
|
|
306
304
|
}
|
|
307
305
|
const uatFile = resolveSliceFile(basePath, mid, sliceId, "UAT");
|
|
@@ -616,7 +614,7 @@ export const DISPATCH_RULES = [
|
|
|
616
614
|
},
|
|
617
615
|
},
|
|
618
616
|
{
|
|
619
|
-
name: "planning (require_slice_discussion) → pause for discussion
|
|
617
|
+
name: "planning (require_slice_discussion) → pause for discussion",
|
|
620
618
|
match: async ({ state, mid, basePath, prefs }) => {
|
|
621
619
|
if (state.phase !== "planning")
|
|
622
620
|
return null;
|
|
@@ -1023,7 +1021,7 @@ export const DISPATCH_RULES = [
|
|
|
1023
1021
|
mkdirSync(mDir, { recursive: true });
|
|
1024
1022
|
const validationPath = join(mDir, buildMilestoneFileName(mid, "VALIDATION"));
|
|
1025
1023
|
const skipSource = trivialVariant
|
|
1026
|
-
? "trivial-scope pipeline variant
|
|
1024
|
+
? "trivial-scope pipeline variant"
|
|
1027
1025
|
: "`skip_milestone_validation` preference";
|
|
1028
1026
|
const skipValidationReason = trivialVariant ? "trivial-scope" : "preference";
|
|
1029
1027
|
const content = [
|
|
@@ -1098,19 +1096,19 @@ export const DISPATCH_RULES = [
|
|
|
1098
1096
|
return { action: "skip" };
|
|
1099
1097
|
}
|
|
1100
1098
|
}
|
|
1101
|
-
// Safety guard (#2675, #5747): block completion when VALIDATION
|
|
1102
|
-
// verdict is
|
|
1103
|
-
// terminal, but completing-milestone should NOT proceed —
|
|
1104
|
-
// or human attention is needed.
|
|
1099
|
+
// Safety guard (#2675, #5747, #5920): block completion when VALIDATION
|
|
1100
|
+
// verdict is anything other than pass. The state machine treats these
|
|
1101
|
+
// verdicts as terminal, but completing-milestone should NOT proceed —
|
|
1102
|
+
// remediation or human attention is needed.
|
|
1105
1103
|
const validationFile = resolveMilestoneFile(basePath, mid, "VALIDATION");
|
|
1106
1104
|
if (validationFile) {
|
|
1107
1105
|
const validationContent = await loadFile(validationFile);
|
|
1108
1106
|
if (validationContent) {
|
|
1109
1107
|
const verdict = extractVerdict(validationContent);
|
|
1110
|
-
if (verdict
|
|
1108
|
+
if (verdict !== "pass") {
|
|
1111
1109
|
return {
|
|
1112
1110
|
action: "stop",
|
|
1113
|
-
reason: `Cannot complete milestone ${mid}: VALIDATION verdict is "${verdict}". Address the validation findings and re-run validation, or
|
|
1111
|
+
reason: `Cannot complete milestone ${mid}: VALIDATION verdict is "${verdict}". Address the validation findings and re-run validation, or run \`/gsd verdict pass --rationale "..."\` to override.`,
|
|
1114
1112
|
level: "warning",
|
|
1115
1113
|
};
|
|
1116
1114
|
}
|
|
@@ -1125,16 +1123,12 @@ export const DISPATCH_RULES = [
|
|
|
1125
1123
|
level: "error",
|
|
1126
1124
|
};
|
|
1127
1125
|
}
|
|
1128
|
-
// Safety
|
|
1129
|
-
// artifacts
|
|
1130
|
-
//
|
|
1126
|
+
// Safety signal (#1703, #5097): detect milestones with only .gsd/
|
|
1127
|
+
// artifacts. This no longer hard-blocks completion because some
|
|
1128
|
+
// milestones are intentionally planning/documentation-only.
|
|
1131
1129
|
const artifactCheck = hasImplementationArtifacts(basePath, mid);
|
|
1132
1130
|
if (artifactCheck === "absent") {
|
|
1133
|
-
|
|
1134
|
-
action: "stop",
|
|
1135
|
-
reason: `Cannot complete milestone ${mid}: no implementation files found outside .gsd/. The milestone has only plan files — actual code changes are required.`,
|
|
1136
|
-
level: "error",
|
|
1137
|
-
};
|
|
1131
|
+
logWarning("dispatch", `Milestone ${mid} has no implementation files outside .gsd/ — continuing complete-milestone dispatch (planning-only/documentation-only milestone).`);
|
|
1138
1132
|
}
|
|
1139
1133
|
if (artifactCheck === "unknown") {
|
|
1140
1134
|
logWarning("dispatch", `Implementation artifact check inconclusive for ${mid} — proceeding (git context unavailable)`);
|
|
@@ -28,7 +28,7 @@ import { regenerateIfMissing } from "./workflow-projections.js";
|
|
|
28
28
|
import { WorktreeStateProjection } from "./worktree-state-projection.js";
|
|
29
29
|
import { createWorkspace, scopeMilestone } from "./workspace.js";
|
|
30
30
|
import { normalizeWorktreePathForCompare } from "./worktree-root.js";
|
|
31
|
-
import { isDbAvailable, getTask, getSlice, getMilestone, updateTaskStatus, _getAdapter, getVerificationEvidence } from "./gsd-db.js";
|
|
31
|
+
import { isDbAvailable, getDbPath, refreshOpenDatabaseFromDisk, getTask, getSlice, getMilestone, updateTaskStatus, _getAdapter, getVerificationEvidence } from "./gsd-db.js";
|
|
32
32
|
import { renderPlanCheckboxes } from "./markdown-renderer.js";
|
|
33
33
|
import { consumeSignal } from "./session-status-io.js";
|
|
34
34
|
import { checkPostUnitHooks, isRetryPending, consumeRetryTrigger, persistHookState, resolveHookArtifactPath, } from "./post-unit-hooks.js";
|
|
@@ -294,14 +294,14 @@ export function detectRogueFileWrites(unitType, unitId, basePath) {
|
|
|
294
294
|
* looping indefinitely (#2007).
|
|
295
295
|
*/
|
|
296
296
|
export const MAX_ARTIFACT_VERIFICATION_RETRIES = 3;
|
|
297
|
-
export const STEP_COMPLETE_FALLBACK_MESSAGE = "Step complete. Run /clear, then /gsd to continue (or /gsd auto to run continuously).";
|
|
297
|
+
export const STEP_COMPLETE_FALLBACK_MESSAGE = "Step complete. Run /clear if you want a clean view, then /gsd next to continue one step (or /gsd auto to run continuously).";
|
|
298
298
|
export function buildStepCompleteMessage(nextState) {
|
|
299
299
|
if (nextState.phase === "complete") {
|
|
300
300
|
return "Step complete — milestone finished. Run /gsd status to review, or start the next milestone.";
|
|
301
301
|
}
|
|
302
302
|
const next = describeNextUnit(nextState);
|
|
303
303
|
return `Step complete. Next: ${next.label}\n`
|
|
304
|
-
+ `Run /clear, then /gsd to continue (or /gsd auto to run continuously).`;
|
|
304
|
+
+ `Run /clear if you want a clean view, then /gsd next to continue one step (or /gsd auto to run continuously).`;
|
|
305
305
|
}
|
|
306
306
|
/**
|
|
307
307
|
* Decide whether step mode should stop at the step wizard after a unit finishes.
|
|
@@ -553,6 +553,13 @@ export async function postUnitPreVerification(pctx, opts) {
|
|
|
553
553
|
if (!opts?.skipSettleDelay) {
|
|
554
554
|
await new Promise(r => setTimeout(r, 100));
|
|
555
555
|
}
|
|
556
|
+
const dbPath = getDbPath();
|
|
557
|
+
if (isDbAvailable() && dbPath && dbPath !== ":memory:") {
|
|
558
|
+
const refreshed = refreshOpenDatabaseFromDisk();
|
|
559
|
+
if (!refreshed) {
|
|
560
|
+
logWarning("db", "post-unit database refresh failed; derived state may be stale");
|
|
561
|
+
}
|
|
562
|
+
}
|
|
556
563
|
// Turn-level git action (commit | snapshot | status-only)
|
|
557
564
|
if (s.currentUnit) {
|
|
558
565
|
const unit = s.currentUnit;
|
|
@@ -999,7 +1006,7 @@ export async function postUnitPreVerification(pctx, opts) {
|
|
|
999
1006
|
s.verificationRetryCount.delete(retryKey);
|
|
1000
1007
|
s.verificationRetryFailureHashes.delete(retryKey);
|
|
1001
1008
|
writeBlockerPlaceholder(s.currentUnit.type, s.currentUnit.id, s.basePath, reason);
|
|
1002
|
-
ctx.ui.notify(`${s.currentUnit.type} ${s.currentUnit.id} — deterministic policy rejection, wrote blocker placeholder (no retries)
|
|
1009
|
+
ctx.ui.notify(`${s.currentUnit.type} ${s.currentUnit.id} — deterministic policy rejection, wrote blocker placeholder (no retries)`, "warning");
|
|
1003
1010
|
// Fall through to "continue" — do NOT enter the retry or db-unavailable paths.
|
|
1004
1011
|
}
|
|
1005
1012
|
else if (!triggerArtifactVerified && diagnoseWorktreeIntegrityFailure(s.basePath)) {
|
|
@@ -1458,8 +1465,8 @@ export async function postUnitPostVerification(pctx) {
|
|
|
1458
1465
|
}
|
|
1459
1466
|
}
|
|
1460
1467
|
// Step mode → show wizard instead of dispatch.
|
|
1461
|
-
// Without this notify(), /gsd
|
|
1462
|
-
//
|
|
1468
|
+
// Without this notify(), /gsd next finishes a unit and silently exits the
|
|
1469
|
+
// loop, leaving the user with no next-step command.
|
|
1463
1470
|
if (s.stepMode) {
|
|
1464
1471
|
let phaseAfterUnit = null;
|
|
1465
1472
|
try {
|
|
@@ -162,9 +162,16 @@ export function hasImplementationArtifacts(basePath, milestoneId) {
|
|
|
162
162
|
// Strategy: check `git diff --name-only` against the merge-base with the
|
|
163
163
|
// main branch. This captures ALL files changed during the milestone's
|
|
164
164
|
// lifetime while running on a milestone branch.
|
|
165
|
-
const
|
|
166
|
-
? readIntegrationBranch(basePath, milestoneId)
|
|
167
|
-
:
|
|
165
|
+
const recordedIntegrationBranch = milestoneId
|
|
166
|
+
? readIntegrationBranch(basePath, milestoneId)
|
|
167
|
+
: null;
|
|
168
|
+
let integrationBranch;
|
|
169
|
+
if (recordedIntegrationBranch?.startsWith("milestone/")) {
|
|
170
|
+
integrationBranch = detectMainBranch(basePath);
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
integrationBranch = recordedIntegrationBranch ?? detectMainBranch(basePath);
|
|
174
|
+
}
|
|
168
175
|
const currentBranch = getCurrentBranch(basePath);
|
|
169
176
|
const branchDiff = getChangedFilesSinceBranch(basePath, integrationBranch);
|
|
170
177
|
if (!branchDiff.ok)
|
|
@@ -496,29 +503,49 @@ function commitMatchesMilestone(basePath, message, milestoneId, files) {
|
|
|
496
503
|
// rather than Mxx/Sxx/Tyy. Bind those commits back to the milestone when
|
|
497
504
|
// either the commit touched this milestone's artifacts, or — for projects
|
|
498
505
|
// where .gsd/ is gitignored/external (#5033) — the message explicitly
|
|
499
|
-
// names the milestone
|
|
506
|
+
// names the milestone, local GSD state proves the task belongs here, or the
|
|
507
|
+
// commit is implementation-bearing evidence itself (#5100).
|
|
500
508
|
if (/^GSD-Task:\s*S[^/\s]+\/T\S+/m.test(message)) {
|
|
501
509
|
if (files.some((file) => isMilestoneArtifactPath(file, milestoneId)))
|
|
502
510
|
return true;
|
|
503
511
|
if (commitMessageMentionsMilestone(message, milestoneId))
|
|
504
512
|
return true;
|
|
505
|
-
|
|
513
|
+
const taskTrailerOwnership = getTaskOwnershipStatus(basePath, message, milestoneId);
|
|
514
|
+
if (taskTrailerOwnership === true)
|
|
515
|
+
return true;
|
|
516
|
+
if (taskTrailerOwnership === false)
|
|
517
|
+
return false;
|
|
518
|
+
// taskTrailerOwnership === null: unknown ownership. Apply fallback only
|
|
519
|
+
// in this case to avoid cross-milestone attribution.
|
|
520
|
+
if (MILESTONE_ID_RE.test(milestoneId) && classifyImplementationFiles(files) === "present")
|
|
506
521
|
return true;
|
|
507
522
|
}
|
|
508
523
|
return false;
|
|
509
524
|
}
|
|
510
|
-
|
|
525
|
+
/**
|
|
526
|
+
* Tri-state task ownership probe.
|
|
527
|
+
* true => DB or local files confirm this milestone owns the task.
|
|
528
|
+
* false => DB is available and this milestone is registered, but task is absent.
|
|
529
|
+
* null => ownership unknown (milestone not in DB yet, or no DB + no local files).
|
|
530
|
+
*/
|
|
531
|
+
function getTaskOwnershipStatus(basePath, message, milestoneId) {
|
|
511
532
|
const match = message.match(/^GSD-Task:\s*(S[^/\s]+)\/(T[^\s]+)/m);
|
|
512
533
|
if (!match)
|
|
513
|
-
return
|
|
534
|
+
return null;
|
|
514
535
|
const [, sliceId, taskId] = match;
|
|
515
|
-
if (
|
|
516
|
-
|
|
536
|
+
if (isDbAvailable()) {
|
|
537
|
+
if (!getMilestone(milestoneId))
|
|
538
|
+
return null;
|
|
539
|
+
return getTask(milestoneId, sliceId, taskId) ? true : false;
|
|
540
|
+
}
|
|
541
|
+
// DB unavailable: fallback to local task-file presence.
|
|
517
542
|
const tasksDir = resolveTasksDir(basePath, milestoneId, sliceId);
|
|
518
|
-
if (
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
543
|
+
if (tasksDir
|
|
544
|
+
&& (existsSync(join(tasksDir, `${taskId}-PLAN.md`))
|
|
545
|
+
|| existsSync(join(tasksDir, `${taskId}-SUMMARY.md`)))) {
|
|
546
|
+
return true;
|
|
547
|
+
}
|
|
548
|
+
return null;
|
|
522
549
|
}
|
|
523
550
|
function commitMessageMentionsMilestone(message, milestoneId) {
|
|
524
551
|
if (!MILESTONE_ID_RE.test(milestoneId))
|
|
@@ -21,10 +21,10 @@ import { invalidateAllCaches } from "./cache.js";
|
|
|
21
21
|
import { writeLock, clearLock } from "./crash-recovery.js";
|
|
22
22
|
import { acquireSessionLock, releaseSessionLock, updateSessionLock, } from "./session-lock.js";
|
|
23
23
|
import { ensureGitignore, untrackRuntimeFiles } from "./gitignore.js";
|
|
24
|
-
import { nativeIsRepo, nativeInit, nativeAddAll, nativeCommit, nativeGetCurrentBranch, nativeDetectMainBranch,
|
|
24
|
+
import { nativeIsRepo, nativeInit, nativeAddAll, nativeCommit, nativeGetCurrentBranch, nativeDetectMainBranch, nativeBranchList, nativeBranchExists, nativeBranchListMerged, nativeBranchDelete, nativeWorktreeRemove, nativeCommitCountBetween, } from "./native-git-bridge.js";
|
|
25
25
|
import { GitServiceImpl } from "./git-service.js";
|
|
26
26
|
import { captureIntegrationBranch, detectWorktreeName, setActiveMilestoneId, } from "./worktree.js";
|
|
27
|
-
import { getAutoWorktreePath } from "./auto-worktree.js";
|
|
27
|
+
import { getAutoWorktreePath, checkoutBranchWithStashGuard } from "./auto-worktree.js";
|
|
28
28
|
import { readResourceVersion, cleanStaleRuntimeUnits } from "./auto-worktree.js";
|
|
29
29
|
import { worktreePath as getWorktreeDir, isInsideWorktreesDir } from "./worktree-manager.js";
|
|
30
30
|
import { emitWorktreeOrphaned } from "./worktree-telemetry.js";
|
|
@@ -901,7 +901,7 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
|
|
|
901
901
|
const integrationBranch = nativeDetectMainBranch(base);
|
|
902
902
|
const branchToCheckout = resolveIsolationNoneBranchCheckout(currentBranch, integrationBranch, isolationMode, isRepo);
|
|
903
903
|
if (branchToCheckout) {
|
|
904
|
-
|
|
904
|
+
checkoutBranchWithStashGuard(base, branchToCheckout, "isolation-none-recovery");
|
|
905
905
|
logWarning("bootstrap", `Returned to "${branchToCheckout}" — HEAD was on stale milestone branch "${currentBranch}" (isolation: none does not use milestone branches).`);
|
|
906
906
|
}
|
|
907
907
|
}
|
|
@@ -65,12 +65,25 @@ async function runValidateMilestonePostCheck(vctx, pauseAuto) {
|
|
|
65
65
|
const { milestone: mid } = parseUnitId(s.currentUnit.id);
|
|
66
66
|
if (!mid)
|
|
67
67
|
return "continue";
|
|
68
|
+
const setToolFailureRetry = (message) => {
|
|
69
|
+
const retryKey = verificationRetryKey(s.currentUnit.type, s.currentUnit.id);
|
|
70
|
+
const attempt = (s.verificationRetryCount.get(retryKey) ?? 0) + 1;
|
|
71
|
+
s.verificationRetryCount.set(retryKey, attempt);
|
|
72
|
+
s.pendingVerificationRetry = {
|
|
73
|
+
unitId: s.currentUnit.id,
|
|
74
|
+
failureContext: message,
|
|
75
|
+
attempt,
|
|
76
|
+
};
|
|
77
|
+
return "retry";
|
|
78
|
+
};
|
|
68
79
|
const validationFile = resolveMilestoneFile(s.basePath, mid, "VALIDATION");
|
|
69
|
-
if (!validationFile)
|
|
70
|
-
return "
|
|
80
|
+
if (!validationFile) {
|
|
81
|
+
return setToolFailureRetry("You must call gsd_validate_milestone to persist the validation results. No VALIDATION.md was created.");
|
|
82
|
+
}
|
|
71
83
|
const validationContent = await loadFile(validationFile);
|
|
72
|
-
if (!validationContent)
|
|
73
|
-
return "
|
|
84
|
+
if (!validationContent) {
|
|
85
|
+
return setToolFailureRetry("You must call gsd_validate_milestone to persist the validation results. VALIDATION.md exists but is empty.");
|
|
86
|
+
}
|
|
74
87
|
const verdict = extractVerdict(validationContent);
|
|
75
88
|
if (verdict !== "needs-remediation") {
|
|
76
89
|
await persistMilestoneValidationGate("pass", "none", `milestone validation verdict is ${verdict}; no remediation loop risk`, "", mid);
|
|
@@ -870,7 +870,63 @@ export function enterBranchModeForMilestone(basePath, milestoneId) {
|
|
|
870
870
|
reused: true,
|
|
871
871
|
});
|
|
872
872
|
}
|
|
873
|
-
|
|
873
|
+
checkoutBranchWithStashGuard(basePath, branch, `enter-branch-mode:${milestoneId}`);
|
|
874
|
+
}
|
|
875
|
+
export function checkoutBranchWithStashGuard(basePath, branch, reason) {
|
|
876
|
+
let stashMarker = null;
|
|
877
|
+
let stashed = false;
|
|
878
|
+
const status = nativeWorkingTreeStatus(basePath).trim();
|
|
879
|
+
if (status.length > 0) {
|
|
880
|
+
stashMarker = `gsd-checkout-stash:${reason}:${process.pid}:${Date.now()}:${process.hrtime.bigint().toString(36)}`;
|
|
881
|
+
const stashListBefore = execFileSync("git", ["stash", "list"], {
|
|
882
|
+
cwd: basePath,
|
|
883
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
884
|
+
encoding: "utf-8",
|
|
885
|
+
});
|
|
886
|
+
execFileSync("git", ["stash", "push", "--include-untracked", "-m", `gsd: checkout stash [${stashMarker}]`], {
|
|
887
|
+
cwd: basePath,
|
|
888
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
889
|
+
encoding: "utf-8",
|
|
890
|
+
});
|
|
891
|
+
const stashListAfter = execFileSync("git", ["stash", "list"], {
|
|
892
|
+
cwd: basePath,
|
|
893
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
894
|
+
encoding: "utf-8",
|
|
895
|
+
});
|
|
896
|
+
stashed = stashListAfter !== stashListBefore;
|
|
897
|
+
}
|
|
898
|
+
// Checkout and stash-restore are split so we can distinguish two failure
|
|
899
|
+
// modes: (a) checkout failed → HEAD did not move, restore stash and rethrow;
|
|
900
|
+
// (b) checkout succeeded but stash pop failed → HEAD moved to `branch` but
|
|
901
|
+
// the working-tree changes remain in the stash list. We surface a distinct
|
|
902
|
+
// error in case (b) so callers don't assume the branch switch was rolled back.
|
|
903
|
+
try {
|
|
904
|
+
nativeCheckoutBranch(basePath, branch);
|
|
905
|
+
}
|
|
906
|
+
catch (checkoutErr) {
|
|
907
|
+
if (stashed) {
|
|
908
|
+
try {
|
|
909
|
+
popStashByRef(basePath, stashMarker);
|
|
910
|
+
}
|
|
911
|
+
catch (restoreErr) {
|
|
912
|
+
logWarning("worktree", `git stash pop failed during checkout restore: ${restoreErr instanceof Error ? restoreErr.message : String(restoreErr)}`);
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
throw checkoutErr;
|
|
916
|
+
}
|
|
917
|
+
if (stashed) {
|
|
918
|
+
try {
|
|
919
|
+
popStashByRef(basePath, stashMarker);
|
|
920
|
+
}
|
|
921
|
+
catch (popErr) {
|
|
922
|
+
const msg = popErr instanceof Error ? popErr.message : String(popErr);
|
|
923
|
+
const wrapped = new Error(`checkout to '${branch}' succeeded but stash restore failed; working tree changes remain in the stash list. Original error: ${msg}`);
|
|
924
|
+
const ref = popErr?.stashRef;
|
|
925
|
+
if (ref)
|
|
926
|
+
wrapped.stashRef = ref;
|
|
927
|
+
throw wrapped;
|
|
928
|
+
}
|
|
929
|
+
}
|
|
874
930
|
}
|
|
875
931
|
// ─── Public API ────────────────────────────────────────────────────────────
|
|
876
932
|
/**
|
|
@@ -1727,14 +1783,6 @@ export function mergeMilestoneToMain(originalBasePath_, milestoneId, roadmapCont
|
|
|
1727
1783
|
// report the dirty tree if it fails.
|
|
1728
1784
|
logWarning("worktree", `git stash failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1729
1785
|
}
|
|
1730
|
-
if (needsDbCycle && dbPathToReopen) {
|
|
1731
|
-
try {
|
|
1732
|
-
openDatabase(dbPathToReopen);
|
|
1733
|
-
}
|
|
1734
|
-
catch (err) {
|
|
1735
|
-
logWarning("worktree", `post-stash db reopen failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1736
|
-
}
|
|
1737
|
-
}
|
|
1738
1786
|
// 7b. Clean up stale merge state before attempting squash merge (#2912).
|
|
1739
1787
|
// A leftover MERGE_HEAD (from a previous failed merge, libgit2 native path,
|
|
1740
1788
|
// or interrupted operation) causes `git merge --squash` to refuse with
|
|
@@ -1743,6 +1791,14 @@ export function mergeMilestoneToMain(originalBasePath_, milestoneId, roadmapCont
|
|
|
1743
1791
|
removeMergeStateFiles(originalBasePath_, "pre-merge");
|
|
1744
1792
|
// 8. Squash merge — auto-resolve .gsd/ state file conflicts (#530)
|
|
1745
1793
|
const mergeResult = nativeMergeSquash(originalBasePath_, milestoneBranch);
|
|
1794
|
+
if (needsDbCycle && dbPathToReopen) {
|
|
1795
|
+
try {
|
|
1796
|
+
openDatabase(dbPathToReopen);
|
|
1797
|
+
}
|
|
1798
|
+
catch (err) {
|
|
1799
|
+
logWarning("worktree", `post-merge db reopen failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1800
|
+
}
|
|
1801
|
+
}
|
|
1746
1802
|
if (!mergeResult.success) {
|
|
1747
1803
|
// Dirty working tree — the merge was rejected before it started (e.g.
|
|
1748
1804
|
// untracked .gsd/ files left by syncStateToProjectRoot). Preserve the
|