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
|
@@ -689,6 +689,8 @@ export async function rerootCommandSession(cmdCtx, workspaceRoot) {
|
|
|
689
689
|
}
|
|
690
690
|
}
|
|
691
691
|
export async function cleanupAfterLoopExit(ctx) {
|
|
692
|
+
const preserveStepSurface = s.preserveStepSurfaceAfterLoopExit;
|
|
693
|
+
const preservePausedSurface = s.paused;
|
|
692
694
|
s.currentUnit = null;
|
|
693
695
|
s.active = false;
|
|
694
696
|
deactivateGSD();
|
|
@@ -712,19 +714,24 @@ export async function cleanupAfterLoopExit(ctx) {
|
|
|
712
714
|
// A transient provider-error pause intentionally leaves the paused badge
|
|
713
715
|
// visible so the user still has a resumable auto-mode signal on screen.
|
|
714
716
|
if (!s.paused) {
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
717
|
+
if (preserveStepSurface) {
|
|
718
|
+
s.preserveStepSurfaceAfterLoopExit = false;
|
|
719
|
+
}
|
|
720
|
+
else {
|
|
721
|
+
ctx.ui.setStatus("gsd-auto", undefined);
|
|
722
|
+
ctx.ui.setWidget("gsd-progress", undefined);
|
|
723
|
+
if (s.completionStopInProgress) {
|
|
724
|
+
s.completionStopInProgress = false;
|
|
725
|
+
}
|
|
726
|
+
initHealthWidget(ctx);
|
|
719
727
|
}
|
|
720
|
-
initHealthWidget(ctx);
|
|
721
728
|
}
|
|
722
729
|
// ADR-016 phase 3 (#5693): the stop-path basePath restore + chdir routes
|
|
723
730
|
// through `Lifecycle.restoreToProjectRoot()`, the sole owner of both
|
|
724
731
|
// `s.basePath` mutation and the paired `process.chdir` for auto-loop
|
|
725
732
|
// transitions. The verb assigns `s.basePath` before any throwable work, so
|
|
726
733
|
// a thrown error still leaves basePath restored.
|
|
727
|
-
if (s.originalBasePath) {
|
|
734
|
+
if (s.originalBasePath && !preserveStepSurface && !preservePausedSurface) {
|
|
728
735
|
try {
|
|
729
736
|
buildLifecycle().restoreToProjectRoot();
|
|
730
737
|
}
|
|
@@ -732,7 +739,7 @@ export async function cleanupAfterLoopExit(ctx) {
|
|
|
732
739
|
logWarning("engine", `restore project root failed: ${err instanceof Error ? err.message : String(err)}`, { file: "auto.ts" });
|
|
733
740
|
}
|
|
734
741
|
}
|
|
735
|
-
if (s.originalBasePath && s.cmdCtx) {
|
|
742
|
+
if (s.originalBasePath && s.cmdCtx && !preserveStepSurface && !preservePausedSurface) {
|
|
736
743
|
const result = await rerootCommandSession(s.cmdCtx, s.originalBasePath);
|
|
737
744
|
if (result.status === "cancelled") {
|
|
738
745
|
logWarning("engine", "post-loop session re-root was cancelled", { file: "auto.ts", basePath: s.originalBasePath });
|
|
@@ -1325,7 +1332,6 @@ export async function pauseAuto(ctx, _pi, _errorContext) {
|
|
|
1325
1332
|
restoreProjectRootEnv();
|
|
1326
1333
|
restoreMilestoneLockEnv();
|
|
1327
1334
|
s.pendingVerificationRetry = null;
|
|
1328
|
-
s.verificationRetryCount.clear();
|
|
1329
1335
|
ctx?.ui.setStatus("gsd-auto", "paused");
|
|
1330
1336
|
ctx?.ui.setWidget("gsd-progress", undefined);
|
|
1331
1337
|
const resumeCmd = s.stepMode ? "/gsd next" : "/gsd auto";
|
|
@@ -61,6 +61,11 @@ export function isUserInitiatedAbortMessage(message) {
|
|
|
61
61
|
return false;
|
|
62
62
|
return /\b(?:claude code process aborted by user|request aborted by user|process aborted by user)\b/i.test(message);
|
|
63
63
|
}
|
|
64
|
+
export function shouldDeferTransientErrorToCoreRetry(cls, rawErrorMsg) {
|
|
65
|
+
if (!isTransient(cls) || cls.kind === "rate-limit")
|
|
66
|
+
return false;
|
|
67
|
+
return !/retry failed after \d+ attempts:/i.test(rawErrorMsg);
|
|
68
|
+
}
|
|
64
69
|
function isBareClaudeCodeSessionSwitchAbortMarker(message) {
|
|
65
70
|
if (!message)
|
|
66
71
|
return false;
|
|
@@ -394,7 +399,7 @@ export async function handleAgentEnd(pi, event, ctx) {
|
|
|
394
399
|
// Core retries transient failures in-session after this handler.
|
|
395
400
|
// Keep that behavior for non-rate-limit classes to avoid pause/retry races,
|
|
396
401
|
// but let rate-limit continue into model fallback logic below (#4373).
|
|
397
|
-
if (
|
|
402
|
+
if (shouldDeferTransientErrorToCoreRetry(cls, rawErrorMsg)) {
|
|
398
403
|
return;
|
|
399
404
|
}
|
|
400
405
|
// Cap rate-limit backoff for CLI-style providers (openai-codex, google-gemini-cli)
|
|
@@ -848,7 +848,7 @@ export function registerDbTools(pi) {
|
|
|
848
848
|
name: "gsd_skip_slice",
|
|
849
849
|
label: "Skip Slice",
|
|
850
850
|
description: "Mark a slice as skipped so auto-mode advances past it without executing. " +
|
|
851
|
-
"Non-closed tasks within the slice are cascaded to skipped so milestone completion is not blocked by leftover pending tasks
|
|
851
|
+
"Non-closed tasks within the slice are cascaded to skipped so milestone completion is not blocked by leftover pending tasks. " +
|
|
852
852
|
"The slice data is preserved for reference. The state machine treats skipped slices like completed ones for dependency satisfaction.",
|
|
853
853
|
promptSnippet: "Skip a GSD slice (mark as skipped, auto-mode will advance past it)",
|
|
854
854
|
promptGuidelines: [
|
|
@@ -625,7 +625,7 @@ function matchesAllowedGlob(absPath, basePath, globs) {
|
|
|
625
625
|
function blockReason(unitType, mode, what) {
|
|
626
626
|
return [
|
|
627
627
|
`HARD BLOCK: unit "${unitType}" runs under tools-policy "${mode}" — ${what}.`,
|
|
628
|
-
`This is a mechanical gate enforced by manifest.tools
|
|
628
|
+
`This is a mechanical gate enforced by manifest.tools. You MUST NOT proceed,`,
|
|
629
629
|
`retry the same call, or rationalize past this block. If you need to write user source,`,
|
|
630
630
|
`the work belongs in execute-task, not in a planning unit.`,
|
|
631
631
|
].join(" ");
|
|
@@ -3,7 +3,7 @@ import { join, resolve } from "node:path";
|
|
|
3
3
|
import { loadRegistry } from "../workflow-templates.js";
|
|
4
4
|
import { gsdHome } from "../gsd-home.js";
|
|
5
5
|
import { VISUAL_BRIEF_MODES } from "../../visual-brief/prompts.js";
|
|
6
|
-
export const GSD_COMMAND_DESCRIPTION = "GSD — Get Shit Done: /gsd help|start|templates|next|auto|stop|pause|status|widget|visualize|brief|queue|quick|discuss|capture|triage|dispatch|history|undo|undo-task|reset-slice|rate|skip|export|cleanup|model|mode|prefs|config|keys|hooks|run-hook|skill-health|doctor|debug|logs|forensics|changelog|migrate|remote|steer|knowledge|new-milestone|new-project|parallel|cmux|park|unpark|init|setup|onboarding|inspect|extensions|update|fast|mcp|rethink|workflow|codebase|notifications|ship|do|session-report|backlog|pr-branch|add-tests|scan|language|worktree|eval-review";
|
|
6
|
+
export const GSD_COMMAND_DESCRIPTION = "GSD — Get Shit Done: /gsd help|start|templates|next|auto|stop|pause|status|widget|visualize|brief|queue|quick|discuss|capture|triage|dispatch|verdict|history|undo|undo-task|reset-slice|rate|skip|export|cleanup|model|mode|prefs|config|keys|hooks|run-hook|skill-health|doctor|debug|logs|forensics|changelog|migrate|remote|steer|knowledge|new-milestone|new-project|parallel|cmux|park|unpark|init|setup|onboarding|inspect|extensions|update|fast|mcp|rethink|workflow|codebase|notifications|ship|do|session-report|backlog|pr-branch|add-tests|scan|language|worktree|eval-review";
|
|
7
7
|
export const TOP_LEVEL_SUBCOMMANDS = [
|
|
8
8
|
{ cmd: "help", desc: "Categorized command reference with descriptions" },
|
|
9
9
|
{ cmd: "next", desc: "Explicit step mode (same as /gsd)" },
|
|
@@ -21,6 +21,7 @@ export const TOP_LEVEL_SUBCOMMANDS = [
|
|
|
21
21
|
{ cmd: "changelog", desc: "Show categorized release notes" },
|
|
22
22
|
{ cmd: "triage", desc: "Manually trigger triage of pending captures" },
|
|
23
23
|
{ cmd: "dispatch", desc: "Dispatch a specific phase directly" },
|
|
24
|
+
{ cmd: "verdict", desc: "Override the recorded milestone validation verdict (pass|needs-attention|needs-remediation)" },
|
|
24
25
|
{ cmd: "history", desc: "View execution history" },
|
|
25
26
|
{ cmd: "undo", desc: "Revert last completed unit" },
|
|
26
27
|
{ cmd: "undo-task", desc: "Reset a specific task's completion state (DB + markdown)" },
|
|
@@ -235,6 +236,11 @@ const NESTED_COMPLETIONS = {
|
|
|
235
236
|
{ cmd: "uat", desc: "Run user acceptance testing" },
|
|
236
237
|
{ cmd: "replan", desc: "Replan the current slice" },
|
|
237
238
|
],
|
|
239
|
+
verdict: [
|
|
240
|
+
{ cmd: "pass", desc: "Override the milestone validation verdict to pass" },
|
|
241
|
+
{ cmd: "needs-attention", desc: "Override the verdict to needs-attention (requires --rationale)" },
|
|
242
|
+
{ cmd: "needs-remediation", desc: "Override the verdict to needs-remediation (requires --rationale)" },
|
|
243
|
+
],
|
|
238
244
|
rate: [
|
|
239
245
|
{ cmd: "over", desc: "Model was overqualified for this task" },
|
|
240
246
|
{ cmd: "ok", desc: "Model was appropriate for this task" },
|
|
@@ -65,6 +65,7 @@ export function showHelp(ctx, args = "") {
|
|
|
65
65
|
" /gsd new-project Bootstrap a new project (use --deep for staged project-level discovery)",
|
|
66
66
|
" /gsd quick Execute a quick task without full planning overhead",
|
|
67
67
|
" /gsd dispatch Dispatch a specific phase directly [research|plan|execute|complete|uat|replan]",
|
|
68
|
+
" /gsd verdict <v> Override milestone validation verdict [pass|needs-attention|needs-remediation] [--milestone Mxxx] [--rationale \"...\"]",
|
|
68
69
|
" /gsd parallel Parallel milestone orchestration [start|status|stop|pause|resume|merge|watch]",
|
|
69
70
|
" /gsd workflow Custom workflow lifecycle [new|run|list|validate|pause|resume]",
|
|
70
71
|
"",
|
|
@@ -183,6 +183,11 @@ Examples:
|
|
|
183
183
|
await dispatchDirectPhase(ctx, pi, phase, projectRoot());
|
|
184
184
|
return true;
|
|
185
185
|
}
|
|
186
|
+
if (trimmed === "verdict" || trimmed.startsWith("verdict ")) {
|
|
187
|
+
const { handleVerdict } = await import("../../commands-verdict.js");
|
|
188
|
+
await handleVerdict(trimmed.replace(/^verdict\s*/, "").trim(), ctx, projectRoot());
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
186
191
|
if (trimmed === "notifications" || trimmed.startsWith("notifications ")) {
|
|
187
192
|
const { handleNotificationsCommand } = await import("./notifications-handler.js");
|
|
188
193
|
await handleNotificationsCommand(trimmed.replace(/^notifications\s*/, "").trim(), ctx, pi);
|
|
@@ -571,10 +571,15 @@ async function configureModels(ctx, prefs) {
|
|
|
571
571
|
];
|
|
572
572
|
const models = prefs.models ?? {};
|
|
573
573
|
const availableModels = ctx.modelRegistry.getAvailable();
|
|
574
|
-
|
|
574
|
+
const getAllWithDiscovered = ctx.modelRegistry.getAllWithDiscovered;
|
|
575
|
+
const availableProviders = new Set(availableModels.map((m) => m.provider));
|
|
576
|
+
const selectableModels = typeof getAllWithDiscovered === "function"
|
|
577
|
+
? getAllWithDiscovered().filter((m) => availableProviders.has(m.provider))
|
|
578
|
+
: availableModels;
|
|
579
|
+
if (selectableModels.length > 0) {
|
|
575
580
|
// Group models by provider, sorted alphabetically
|
|
576
581
|
const byProvider = new Map();
|
|
577
|
-
for (const m of
|
|
582
|
+
for (const m of selectableModels) {
|
|
578
583
|
let group = byProvider.get(m.provider);
|
|
579
584
|
if (!group) {
|
|
580
585
|
group = [];
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { loadFile } from "./files.js";
|
|
2
|
+
import { resolveMilestoneFile } from "./paths.js";
|
|
3
|
+
import { deriveState } from "./state.js";
|
|
4
|
+
import { executeValidateMilestone } from "./tools/workflow-tool-executors.js";
|
|
5
|
+
import { VALIDATION_VERDICTS, extractVerdict, isValidMilestoneVerdict, } from "./verdict-parser.js";
|
|
6
|
+
const USAGE = 'Usage: /gsd verdict <pass|needs-attention|needs-remediation> [--milestone Mxxx] [--rationale "..."]';
|
|
7
|
+
function tokenize(raw) {
|
|
8
|
+
const tokens = [];
|
|
9
|
+
const re = /"([^"]*)"|(\S+)/g;
|
|
10
|
+
let match;
|
|
11
|
+
while ((match = re.exec(raw)) !== null) {
|
|
12
|
+
tokens.push(match[1] ?? match[2]);
|
|
13
|
+
}
|
|
14
|
+
return tokens;
|
|
15
|
+
}
|
|
16
|
+
function parseArgs(raw) {
|
|
17
|
+
const tokens = tokenize(raw);
|
|
18
|
+
const out = {};
|
|
19
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
20
|
+
const t = tokens[i];
|
|
21
|
+
if (t === "--milestone") {
|
|
22
|
+
const next = tokens[++i];
|
|
23
|
+
if (!next)
|
|
24
|
+
return { error: "--milestone requires a milestone ID" };
|
|
25
|
+
out.milestoneId = next;
|
|
26
|
+
}
|
|
27
|
+
else if (t === "--rationale") {
|
|
28
|
+
const next = tokens[++i];
|
|
29
|
+
if (next == null)
|
|
30
|
+
return { error: "--rationale requires a value" };
|
|
31
|
+
out.rationale = next;
|
|
32
|
+
}
|
|
33
|
+
else if (!out.verdict) {
|
|
34
|
+
if (!isValidMilestoneVerdict(t)) {
|
|
35
|
+
return {
|
|
36
|
+
error: `Invalid verdict "${t}". Must be one of: ${VALIDATION_VERDICTS.join(", ")}`,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
out.verdict = t;
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
return { error: `Unexpected argument: ${t}` };
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return out;
|
|
46
|
+
}
|
|
47
|
+
function extractRemediationRound(content) {
|
|
48
|
+
const fm = content.match(/^---\n([\s\S]*?)\n---/);
|
|
49
|
+
if (!fm)
|
|
50
|
+
return 0;
|
|
51
|
+
const m = fm[1].match(/^remediation_round:\s*(\d+)/im);
|
|
52
|
+
return m ? Number.parseInt(m[1], 10) : 0;
|
|
53
|
+
}
|
|
54
|
+
function extractSection(content, heading) {
|
|
55
|
+
const escaped = heading.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
56
|
+
// Match section bodies bounded by the next "## " heading or end-of-string.
|
|
57
|
+
// Leading "\n" prefix lets a single pattern handle first-line headings too.
|
|
58
|
+
// No /m flag — we want `$` to mean end-of-string, not end-of-line.
|
|
59
|
+
const re = new RegExp(`\\n## ${escaped}\\s*\\n([\\s\\S]*?)(?=\\n## |$)`);
|
|
60
|
+
const m = ("\n" + content).match(re);
|
|
61
|
+
if (!m)
|
|
62
|
+
return undefined;
|
|
63
|
+
return m[1].replace(/\s+$/, "");
|
|
64
|
+
}
|
|
65
|
+
export function parseValidationFile(content) {
|
|
66
|
+
return {
|
|
67
|
+
verdict: extractVerdict(content),
|
|
68
|
+
remediationRound: extractRemediationRound(content),
|
|
69
|
+
successCriteriaChecklist: extractSection(content, "Success Criteria Checklist") ?? "",
|
|
70
|
+
sliceDeliveryAudit: extractSection(content, "Slice Delivery Audit") ?? "",
|
|
71
|
+
crossSliceIntegration: extractSection(content, "Cross-Slice Integration") ?? "",
|
|
72
|
+
requirementCoverage: extractSection(content, "Requirement Coverage") ?? "",
|
|
73
|
+
verificationClasses: extractSection(content, "Verification Class Compliance"),
|
|
74
|
+
verdictRationale: extractSection(content, "Verdict Rationale") ?? "",
|
|
75
|
+
remediationPlan: extractSection(content, "Remediation Plan"),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
export async function handleVerdict(rawArgs, ctx, basePath) {
|
|
79
|
+
if (!rawArgs.trim()) {
|
|
80
|
+
ctx.ui.notify(USAGE, "warning");
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const parsed = parseArgs(rawArgs);
|
|
84
|
+
if ("error" in parsed) {
|
|
85
|
+
ctx.ui.notify(`${parsed.error}\n${USAGE}`, "warning");
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (!parsed.verdict) {
|
|
89
|
+
ctx.ui.notify(USAGE, "warning");
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
let milestoneId = parsed.milestoneId;
|
|
93
|
+
if (!milestoneId) {
|
|
94
|
+
const state = await deriveState(basePath);
|
|
95
|
+
if (!state.activeMilestone) {
|
|
96
|
+
ctx.ui.notify("No active milestone — pass --milestone Mxxx to target a specific milestone.", "warning");
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
milestoneId = state.activeMilestone.id;
|
|
100
|
+
}
|
|
101
|
+
const validationPath = resolveMilestoneFile(basePath, milestoneId, "VALIDATION");
|
|
102
|
+
if (!validationPath) {
|
|
103
|
+
ctx.ui.notify(`No VALIDATION file found for ${milestoneId}. Run gsd_validate_milestone first to produce one.`, "warning");
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
const existing = await loadFile(validationPath);
|
|
107
|
+
if (!existing) {
|
|
108
|
+
ctx.ui.notify(`Could not read VALIDATION file for ${milestoneId} (${validationPath}).`, "warning");
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const current = parseValidationFile(existing);
|
|
112
|
+
if (parsed.verdict !== "pass" && !parsed.rationale) {
|
|
113
|
+
ctx.ui.notify(`--rationale is required when overriding to ${parsed.verdict}.`, "warning");
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const verdictRationale = parsed.rationale ?? "Manually overridden via /gsd verdict";
|
|
117
|
+
const result = await executeValidateMilestone({
|
|
118
|
+
milestoneId,
|
|
119
|
+
verdict: parsed.verdict,
|
|
120
|
+
remediationRound: current.remediationRound,
|
|
121
|
+
successCriteriaChecklist: current.successCriteriaChecklist,
|
|
122
|
+
sliceDeliveryAudit: current.sliceDeliveryAudit,
|
|
123
|
+
crossSliceIntegration: current.crossSliceIntegration,
|
|
124
|
+
requirementCoverage: current.requirementCoverage,
|
|
125
|
+
verificationClasses: current.verificationClasses,
|
|
126
|
+
verdictRationale,
|
|
127
|
+
remediationPlan: current.remediationPlan,
|
|
128
|
+
}, basePath);
|
|
129
|
+
if (result.isError) {
|
|
130
|
+
const msg = result.content[0]?.type === "text" ? result.content[0].text : "Unknown error";
|
|
131
|
+
ctx.ui.notify(msg, "error");
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
const prevVerdict = current.verdict ?? "unknown";
|
|
135
|
+
ctx.ui.notify(`Milestone ${milestoneId} verdict: ${prevVerdict} -> ${parsed.verdict}`, "success");
|
|
136
|
+
if (parsed.verdict === "needs-remediation") {
|
|
137
|
+
ctx.ui.notify("Follow up with gsd_reassess_roadmap to add remediation slices, then re-run /gsd auto.", "info");
|
|
138
|
+
}
|
|
139
|
+
}
|
|
@@ -23,7 +23,8 @@
|
|
|
23
23
|
import { emitJournalEvent, queryJournal, } from "./journal.js";
|
|
24
24
|
import { readFileSync, unlinkSync, existsSync } from "node:fs";
|
|
25
25
|
import { join } from "node:path";
|
|
26
|
-
import { findStaleWorkerForProject, getAllAutoWorkers, markWorkerCrashed, } from "./db/auto-workers.js";
|
|
26
|
+
import { findStaleWorkerForProject, getAllAutoWorkers, markWorkerCrashed, markWorkerStopping, } from "./db/auto-workers.js";
|
|
27
|
+
import { forceReleaseLeasesForWorker } from "./db/milestone-leases.js";
|
|
27
28
|
import { markLatestActiveForWorkerCanceled } from "./db/unit-dispatches.js";
|
|
28
29
|
import { getRuntimeKv, setRuntimeKv, deleteRuntimeKv } from "./db/runtime-kv.js";
|
|
29
30
|
import { _getAdapter, isDbAvailable } from "./gsd-db.js";
|
|
@@ -182,10 +183,21 @@ export function clearLock(basePath) {
|
|
|
182
183
|
return;
|
|
183
184
|
try {
|
|
184
185
|
const projectRoot = normalizeRealPath(basePath);
|
|
185
|
-
const
|
|
186
|
-
if (
|
|
186
|
+
const staleWorker = findStaleWorkerForProject(projectRoot);
|
|
187
|
+
if (staleWorker) {
|
|
188
|
+
markWorkerCrashed(staleWorker.worker_id);
|
|
189
|
+
forceReleaseLeasesForWorker(staleWorker.worker_id);
|
|
190
|
+
deleteRuntimeKv("worker", staleWorker.worker_id, SESSION_FILE_KV_KEY);
|
|
187
191
|
return;
|
|
188
|
-
|
|
192
|
+
}
|
|
193
|
+
const worker = findActiveWorkerForCurrentProcess(projectRoot);
|
|
194
|
+
if (worker)
|
|
195
|
+
deleteRuntimeKv("worker", worker.worker_id, SESSION_FILE_KV_KEY);
|
|
196
|
+
const stale = findStaleWorkerForProject(projectRoot);
|
|
197
|
+
if (stale) {
|
|
198
|
+
markWorkerStopping(stale.worker_id);
|
|
199
|
+
deleteRuntimeKv("worker", stale.worker_id, SESSION_FILE_KV_KEY);
|
|
200
|
+
}
|
|
189
201
|
}
|
|
190
202
|
catch {
|
|
191
203
|
// Best-effort.
|
|
@@ -193,6 +193,30 @@ export function releaseMilestoneLease(workerId, milestoneId, fencingToken) {
|
|
|
193
193
|
return changes === 1;
|
|
194
194
|
});
|
|
195
195
|
}
|
|
196
|
+
/**
|
|
197
|
+
* Force-release all held leases for a worker.
|
|
198
|
+
*
|
|
199
|
+
* Used by crash recovery once PID liveness has confirmed the worker is dead.
|
|
200
|
+
* No fencing token is required because this path is cleanup-only for a
|
|
201
|
+
* non-running process.
|
|
202
|
+
*/
|
|
203
|
+
export function forceReleaseLeasesForWorker(workerId) {
|
|
204
|
+
if (!isDbAvailable())
|
|
205
|
+
return 0;
|
|
206
|
+
const db = _getAdapter();
|
|
207
|
+
let changes = 0;
|
|
208
|
+
transaction(() => {
|
|
209
|
+
const result = db.prepare(`UPDATE milestone_leases
|
|
210
|
+
SET status = 'released'
|
|
211
|
+
WHERE worker_id = :worker_id
|
|
212
|
+
AND status = 'held'`).run({ ":worker_id": workerId });
|
|
213
|
+
changes =
|
|
214
|
+
typeof result.changes === "number"
|
|
215
|
+
? result.changes
|
|
216
|
+
: 0;
|
|
217
|
+
});
|
|
218
|
+
return changes;
|
|
219
|
+
}
|
|
196
220
|
/**
|
|
197
221
|
* Read current lease row for diagnostics. Returns null if no row exists.
|
|
198
222
|
*/
|
|
@@ -677,7 +677,7 @@ export function detectWorktreeOrphans(summary, anomalies) {
|
|
|
677
677
|
type: "worktree-unmerged-exit",
|
|
678
678
|
severity: "warning",
|
|
679
679
|
summary: `${summary.exitsWithUnmergedWork} auto-exit(s) left milestone work unmerged`,
|
|
680
|
-
details: `Exit reasons: ${reasonBreakdown || "(none)"} · Producer-side signal for
|
|
680
|
+
details: `Exit reasons: ${reasonBreakdown || "(none)"} · Producer-side signal for orphaned worktrees. Inspect .gsd/journal/*.jsonl with eventType:"auto-exit" for per-exit detail.`,
|
|
681
681
|
});
|
|
682
682
|
}
|
|
683
683
|
}
|
|
@@ -884,7 +884,7 @@ function saveForensicReport(basePath, report, problemDescription) {
|
|
|
884
884
|
.map(([r, n]) => `${r}=${n}`).join(", ");
|
|
885
885
|
sections.push(` - Exit reasons: ${breakdown}`);
|
|
886
886
|
}
|
|
887
|
-
sections.push(`- Canonical-root redirects
|
|
887
|
+
sections.push(`- Canonical-root redirects: ${t.canonicalRedirects}`);
|
|
888
888
|
// #4765 slice-cadence counters
|
|
889
889
|
if (t.slicesMerged + t.sliceMergeConflicts + t.milestoneResquashes > 0) {
|
|
890
890
|
sections.push(`- Slices merged: ${t.slicesMerged} · Slice merge conflicts: ${t.sliceMergeConflicts}`);
|
|
@@ -1033,7 +1033,7 @@ function formatReportForPrompt(report) {
|
|
|
1033
1033
|
if (hasSignal) {
|
|
1034
1034
|
sections.push("### Worktree Telemetry");
|
|
1035
1035
|
sections.push(`- Created: ${t.worktreesCreated} · Merged: ${t.worktreesMerged} · Conflicts: ${t.mergeConflicts}`);
|
|
1036
|
-
sections.push(`- Orphans: ${t.orphansDetected} · Unmerged exits: ${t.exitsWithUnmergedWork} · Redirects
|
|
1036
|
+
sections.push(`- Orphans: ${t.orphansDetected} · Unmerged exits: ${t.exitsWithUnmergedWork} · Redirects: ${t.canonicalRedirects}`);
|
|
1037
1037
|
if (t.orphansDetected > 0) {
|
|
1038
1038
|
const breakdown = Object.entries(t.orphansByReason)
|
|
1039
1039
|
.map(([r, n]) => `${r}=${n}`).join(", ");
|
|
@@ -237,6 +237,8 @@ export function readIntegrationBranch(basePath, milestoneId) {
|
|
|
237
237
|
return null;
|
|
238
238
|
}
|
|
239
239
|
}
|
|
240
|
+
/** Re-export for backward compatibility — canonical definitions in branch-patterns.ts */
|
|
241
|
+
export { QUICK_BRANCH_RE, WORKFLOW_BRANCH_RE } from "./branch-patterns.js";
|
|
240
242
|
/**
|
|
241
243
|
* Persist the integration branch for a milestone.
|
|
242
244
|
*
|
|
@@ -247,9 +249,11 @@ export function readIntegrationBranch(basePath, milestoneId) {
|
|
|
247
249
|
*
|
|
248
250
|
* The file is committed immediately so the metadata is persisted in git.
|
|
249
251
|
*/
|
|
250
|
-
/** Re-export for backward compatibility — canonical definitions in branch-patterns.ts */
|
|
251
|
-
export { QUICK_BRANCH_RE, WORKFLOW_BRANCH_RE } from "./branch-patterns.js";
|
|
252
252
|
export function writeIntegrationBranch(basePath, milestoneId, branch) {
|
|
253
|
+
// Never persist milestone branches as integration targets.
|
|
254
|
+
// They are ephemeral execution branches and can cause self-diff corruption.
|
|
255
|
+
if (branch.startsWith("milestone/"))
|
|
256
|
+
return;
|
|
253
257
|
// Don't record slice branches as the integration target
|
|
254
258
|
if (SLICE_BRANCH_RE.test(branch))
|
|
255
259
|
return;
|
|
@@ -54,18 +54,19 @@ const providerLoader = createSqliteProviderLoader({
|
|
|
54
54
|
writeStderr: (message) => process.stderr.write(message),
|
|
55
55
|
});
|
|
56
56
|
export const SCHEMA_VERSION = 28;
|
|
57
|
-
function initSchema(db, fileBacked) {
|
|
57
|
+
function initSchema(db, fileBacked, dbPath) {
|
|
58
|
+
const conservativeFilePragmas = fileBacked && _isLikelyWslDrvFsPathForTest(dbPath);
|
|
58
59
|
if (fileBacked)
|
|
59
|
-
db.exec("PRAGMA journal_mode=WAL");
|
|
60
|
+
db.exec(conservativeFilePragmas ? "PRAGMA journal_mode=DELETE" : "PRAGMA journal_mode=WAL");
|
|
60
61
|
if (fileBacked)
|
|
61
62
|
db.exec("PRAGMA busy_timeout = 5000");
|
|
62
63
|
if (fileBacked)
|
|
63
|
-
db.exec("PRAGMA synchronous = NORMAL");
|
|
64
|
+
db.exec(conservativeFilePragmas ? "PRAGMA synchronous = FULL" : "PRAGMA synchronous = NORMAL");
|
|
64
65
|
if (fileBacked)
|
|
65
66
|
db.exec("PRAGMA auto_vacuum = INCREMENTAL");
|
|
66
67
|
if (fileBacked)
|
|
67
68
|
db.exec("PRAGMA cache_size = -8000"); // 8 MB page cache
|
|
68
|
-
if (fileBacked && process.platform !== "darwin")
|
|
69
|
+
if (fileBacked && !conservativeFilePragmas && process.platform !== "darwin")
|
|
69
70
|
db.exec("PRAGMA mmap_size = 67108864"); // 64 MB mmap
|
|
70
71
|
db.exec("PRAGMA temp_store = MEMORY");
|
|
71
72
|
db.exec("PRAGMA foreign_keys = ON");
|
|
@@ -99,6 +100,19 @@ function initSchema(db, fileBacked) {
|
|
|
99
100
|
}
|
|
100
101
|
migrateSchema(db);
|
|
101
102
|
}
|
|
103
|
+
export function _isLikelyWslDrvFsPathForTest(dbPath) {
|
|
104
|
+
if (!dbPath || process.platform !== "linux")
|
|
105
|
+
return false;
|
|
106
|
+
const drvFsPathPattern = /^\/mnt\/[a-z](?:\/|$)/i;
|
|
107
|
+
if (drvFsPathPattern.test(dbPath))
|
|
108
|
+
return true;
|
|
109
|
+
try {
|
|
110
|
+
return drvFsPathPattern.test(realpathSync(dbPath));
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
102
116
|
/**
|
|
103
117
|
* Create the FTS5 virtual table for memories plus the triggers that keep it
|
|
104
118
|
* in sync with the base table. FTS5 may be unavailable on stripped-down
|
|
@@ -504,7 +518,7 @@ export function openDatabase(path) {
|
|
|
504
518
|
const adapter = createDbAdapter(rawDb);
|
|
505
519
|
const fileBacked = path !== ":memory:";
|
|
506
520
|
try {
|
|
507
|
-
initSchema(adapter, fileBacked);
|
|
521
|
+
initSchema(adapter, fileBacked, path);
|
|
508
522
|
}
|
|
509
523
|
catch (err) {
|
|
510
524
|
// Corrupt freelist: DDL fails with "malformed" but VACUUM can rebuild.
|
|
@@ -512,7 +526,7 @@ export function openDatabase(path) {
|
|
|
512
526
|
if (fileBacked && err instanceof Error && err.message?.includes("malformed")) {
|
|
513
527
|
try {
|
|
514
528
|
adapter.exec("VACUUM");
|
|
515
|
-
initSchema(adapter, fileBacked);
|
|
529
|
+
initSchema(adapter, fileBacked, path);
|
|
516
530
|
process.stderr.write("gsd-db: recovered corrupt database via VACUUM\n");
|
|
517
531
|
}
|
|
518
532
|
catch (retryErr) {
|
|
@@ -18,6 +18,7 @@ import { nativeAddPaths, nativeCommit } from "./native-git-bridge.js";
|
|
|
18
18
|
import { loadEffectiveGSDPreferences } from "./preferences.js";
|
|
19
19
|
import { saveQueueOrder } from "./queue-order.js";
|
|
20
20
|
import { findMilestoneIds, nextMilestoneId } from "./milestone-ids.js";
|
|
21
|
+
import { isFutureMilestoneStatus } from "./status-guards.js";
|
|
21
22
|
// ─── Queue Entry Point ──────────────────────────────────────────────────────
|
|
22
23
|
/**
|
|
23
24
|
* Queue future milestones via conversational intake.
|
|
@@ -48,7 +49,7 @@ export async function showQueue(ctx, pi, basePath) {
|
|
|
48
49
|
return;
|
|
49
50
|
}
|
|
50
51
|
// ── Count pending milestones ────────────────────────────────────────
|
|
51
|
-
const pendingMilestones = state.registry.filter(m => m.status
|
|
52
|
+
const pendingMilestones = state.registry.filter(m => isFutureMilestoneStatus(m.status) || m.status === "active");
|
|
52
53
|
const completeCount = state.registry.filter(m => m.status === "complete").length;
|
|
53
54
|
const parkedCount = state.registry.filter(m => m.status === "parked").length;
|
|
54
55
|
// ── If multiple pending milestones, show queue management hub ──────
|
|
@@ -140,7 +141,7 @@ export async function showQueueAdd(ctx, pi, basePath, state) {
|
|
|
140
141
|
const activePart = state.activeMilestone
|
|
141
142
|
? `Currently executing: ${state.activeMilestone.id} — ${state.activeMilestone.title} (phase: ${state.phase}).`
|
|
142
143
|
: "No milestone currently active.";
|
|
143
|
-
const pendingCount = state.registry.filter(m => m.status
|
|
144
|
+
const pendingCount = state.registry.filter(m => isFutureMilestoneStatus(m.status)).length;
|
|
144
145
|
const completeCount = state.registry.filter(m => m.status === "complete").length;
|
|
145
146
|
const preamble = [
|
|
146
147
|
`Queuing new work onto an existing GSD project.`,
|
|
@@ -223,7 +224,7 @@ export async function buildExistingMilestonesContext(basePath, milestoneIds, sta
|
|
|
223
224
|
}
|
|
224
225
|
// For active/pending/parked milestones, include the roadmap if it exists
|
|
225
226
|
// (shows what's planned but not yet built)
|
|
226
|
-
if (status === "active" || status
|
|
227
|
+
if (status === "active" || isFutureMilestoneStatus(status) || status === "parked") {
|
|
227
228
|
const roadmapFile = resolveMilestoneFile(basePath, mid, "ROADMAP");
|
|
228
229
|
if (roadmapFile) {
|
|
229
230
|
const content = await loadFile(roadmapFile);
|
|
@@ -30,6 +30,7 @@ import { getIsolationMode, loadEffectiveGSDPreferences } from "./preferences.js"
|
|
|
30
30
|
import { resolveUokFlags } from "./uok/flags.js";
|
|
31
31
|
import { ensurePlanV2Graph, isMissingFinalizedContextResult } from "./uok/plan-v2.js";
|
|
32
32
|
import { detectProjectState, hasGsdBootstrapArtifacts } from "./detection.js";
|
|
33
|
+
import { isFutureMilestoneStatus } from "./status-guards.js";
|
|
33
34
|
import { showProjectInit, offerMigration } from "./init-wizard.js";
|
|
34
35
|
import { validateDirectory } from "./validate-directory.js";
|
|
35
36
|
import { showConfirm } from "../shared/tui.js";
|
|
@@ -811,6 +812,7 @@ async function dispatchWorkflow(pi, note, customType = "gsd-run", ctx, unitType,
|
|
|
811
812
|
? ctx.modelRegistry.getProviderAuthMode(ctx.model.provider)
|
|
812
813
|
: undefined,
|
|
813
814
|
baseUrl: result.appliedModel?.baseUrl ?? ctx.model?.baseUrl,
|
|
815
|
+
activeTools: typeof pi.getActiveTools === "function" ? pi.getActiveTools() : [],
|
|
814
816
|
});
|
|
815
817
|
if (compatibilityError) {
|
|
816
818
|
ctx.ui.notify(compatibilityError, "error");
|
|
@@ -1164,7 +1166,7 @@ export async function showDiscuss(ctx, pi, basePath) {
|
|
|
1164
1166
|
// No active milestone (or corrupted milestone with undefined id) —
|
|
1165
1167
|
// check for pending milestones to discuss instead
|
|
1166
1168
|
if (!state.activeMilestone?.id) {
|
|
1167
|
-
const pendingMilestones = state.registry.filter(m => m.status
|
|
1169
|
+
const pendingMilestones = state.registry.filter(m => isFutureMilestoneStatus(m.status));
|
|
1168
1170
|
if (pendingMilestones.length === 0) {
|
|
1169
1171
|
ctx.ui.notify("No active milestone. Run /gsd to create one first.", "warning");
|
|
1170
1172
|
return;
|
|
@@ -1269,7 +1271,7 @@ export async function showDiscuss(ctx, pi, basePath) {
|
|
|
1269
1271
|
const pendingSlices = normSlices.filter(s => !s.done);
|
|
1270
1272
|
if (pendingSlices.length === 0) {
|
|
1271
1273
|
// All slices complete — but queued milestones may still need discussion (#3150)
|
|
1272
|
-
const pendingMilestones = state.registry.filter(m => m.status
|
|
1274
|
+
const pendingMilestones = state.registry.filter(m => isFutureMilestoneStatus(m.status));
|
|
1273
1275
|
if (pendingMilestones.length > 0) {
|
|
1274
1276
|
await showDiscussQueuedMilestone(ctx, pi, basePath, pendingMilestones);
|
|
1275
1277
|
return;
|
|
@@ -1290,7 +1292,7 @@ export async function showDiscuss(ctx, pi, basePath) {
|
|
|
1290
1292
|
// If all pending slices are discussed, check for queued milestones before exiting (#3150)
|
|
1291
1293
|
const allDiscussed = pendingSlices.every(s => discussedMap.get(s.id));
|
|
1292
1294
|
if (allDiscussed) {
|
|
1293
|
-
const pendingMilestones = state.registry.filter(m => m.status
|
|
1295
|
+
const pendingMilestones = state.registry.filter(m => isFutureMilestoneStatus(m.status));
|
|
1294
1296
|
if (pendingMilestones.length > 0) {
|
|
1295
1297
|
await showDiscussQueuedMilestone(ctx, pi, basePath, pendingMilestones);
|
|
1296
1298
|
return;
|
|
@@ -1321,7 +1323,7 @@ export async function showDiscuss(ctx, pi, basePath) {
|
|
|
1321
1323
|
};
|
|
1322
1324
|
});
|
|
1323
1325
|
// Offer access to queued milestones when any exist
|
|
1324
|
-
const pendingMilestones = state.registry.filter(m => m.status
|
|
1326
|
+
const pendingMilestones = state.registry.filter(m => isFutureMilestoneStatus(m.status));
|
|
1325
1327
|
if (pendingMilestones.length > 0) {
|
|
1326
1328
|
actions.push({
|
|
1327
1329
|
id: "discuss_queued_milestone",
|
|
@@ -1385,10 +1387,11 @@ async function showDiscussQueuedMilestone(ctx, pi, basePath, pendingMilestones)
|
|
|
1385
1387
|
const hasContext = !!resolveMilestoneFile(basePath, m.id, "CONTEXT");
|
|
1386
1388
|
const hasDraft = !hasContext && !!resolveMilestoneFile(basePath, m.id, "CONTEXT-DRAFT");
|
|
1387
1389
|
const contextStatus = hasContext ? "context ✓" : hasDraft ? "draft context" : "no context yet";
|
|
1390
|
+
const statusLabel = m.status === "planned" ? "planned" : "queued";
|
|
1388
1391
|
return {
|
|
1389
1392
|
id: m.id,
|
|
1390
1393
|
label: `${m.id}: ${m.title}`,
|
|
1391
|
-
description: `[
|
|
1394
|
+
description: `[${statusLabel}] · ${contextStatus}`,
|
|
1392
1395
|
recommended: i === 0,
|
|
1393
1396
|
};
|
|
1394
1397
|
});
|
|
@@ -14,7 +14,7 @@ import { isClosedStatus } from "./status-guards.js";
|
|
|
14
14
|
import { join, relative } from "node:path";
|
|
15
15
|
import { createRequire } from "node:module";
|
|
16
16
|
import { getAllMilestones, getMilestone, getMilestoneSlices, getSliceTasks, getTask, getSlice, getArtifact, insertArtifact, getGateResults, } from "./gsd-db.js";
|
|
17
|
-
import { resolveMilestoneFile, resolveSliceFile, resolveSlicePath,
|
|
17
|
+
import { resolveMilestoneFile, resolveSliceFile, resolveSlicePath, gsdProjectionRoot, gsdRoot, buildTaskFileName, buildSliceFileName, } from "./paths.js";
|
|
18
18
|
import { saveFile, clearParseCache } from "./files.js";
|
|
19
19
|
import { invalidateStateCache } from "./state.js";
|
|
20
20
|
import { clearPathCache } from "./paths.js";
|
|
@@ -24,7 +24,11 @@ import { clearPathCache } from "./paths.js";
|
|
|
24
24
|
* E.g. "/project/.gsd/milestones/M001/M001-ROADMAP.md" → "milestones/M001/M001-ROADMAP.md"
|
|
25
25
|
*/
|
|
26
26
|
function toArtifactPath(absPath, basePath) {
|
|
27
|
-
const
|
|
27
|
+
const projectionRoot = gsdProjectionRoot(basePath);
|
|
28
|
+
const projectionRel = relative(projectionRoot, absPath);
|
|
29
|
+
const root = projectionRel && !projectionRel.startsWith("..") && !projectionRel.startsWith("/")
|
|
30
|
+
? projectionRoot
|
|
31
|
+
: gsdRoot(basePath);
|
|
28
32
|
const rel = relative(root, absPath);
|
|
29
33
|
// Normalize to forward slashes for consistent DB keys
|
|
30
34
|
return rel.replace(/\\/g, "/");
|
|
@@ -305,10 +309,9 @@ export async function renderPlanFromDb(basePath, milestoneId, sliceId) {
|
|
|
305
309
|
if (tasks.length === 0) {
|
|
306
310
|
throw new Error(`no tasks found for ${milestoneId}/${sliceId}`);
|
|
307
311
|
}
|
|
308
|
-
const slicePath =
|
|
309
|
-
|
|
310
|
-
const absPath =
|
|
311
|
-
?? join(slicePath, `${sliceId}-PLAN.md`);
|
|
312
|
+
const slicePath = join(gsdProjectionRoot(basePath), "milestones", milestoneId, "slices", sliceId);
|
|
313
|
+
mkdirSync(slicePath, { recursive: true });
|
|
314
|
+
const absPath = join(slicePath, `${sliceId}-PLAN.md`);
|
|
312
315
|
const artifactPath = toArtifactPath(absPath, basePath);
|
|
313
316
|
const sliceGates = getGateResults(milestoneId, sliceId, "slice");
|
|
314
317
|
const content = renderSlicePlanMarkdown(slice, tasks, sliceGates);
|
|
@@ -329,8 +332,7 @@ export async function renderTaskPlanFromDb(basePath, milestoneId, sliceId, taskI
|
|
|
329
332
|
if (!task) {
|
|
330
333
|
throw new Error(`task ${milestoneId}/${sliceId}/${taskId} not found`);
|
|
331
334
|
}
|
|
332
|
-
const tasksDir =
|
|
333
|
-
?? join(gsdRoot(basePath), "milestones", milestoneId, "slices", sliceId, "tasks");
|
|
335
|
+
const tasksDir = join(gsdProjectionRoot(basePath), "milestones", milestoneId, "slices", sliceId, "tasks");
|
|
334
336
|
mkdirSync(tasksDir, { recursive: true });
|
|
335
337
|
const absPath = join(tasksDir, buildTaskFileName(taskId, "PLAN"));
|
|
336
338
|
const artifactPath = toArtifactPath(absPath, basePath);
|