@opengsd/gsd-pi 1.2.0-dev.d6c5343c → 1.2.0-dev.ddc97c10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/mcp-server.js +2 -1
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/gsd/auto/orchestrator.js +28 -10
- package/dist/resources/extensions/gsd/auto/phases.js +47 -4
- package/dist/resources/extensions/gsd/auto/session.js +3 -0
- package/dist/resources/extensions/gsd/auto-direct-dispatch.js +3 -2
- package/dist/resources/extensions/gsd/auto-dispatch.js +11 -2
- package/dist/resources/extensions/gsd/auto-model-selection.js +11 -7
- package/dist/resources/extensions/gsd/auto-post-unit.js +18 -6
- package/dist/resources/extensions/gsd/auto-unit-closeout.js +45 -21
- package/dist/resources/extensions/gsd/auto-verification.js +14 -2
- package/dist/resources/extensions/gsd/auto.js +37 -1
- package/dist/resources/extensions/gsd/blocked-models.js +28 -0
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +26 -6
- package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +2 -2
- package/dist/resources/extensions/gsd/closeout-wizard.js +92 -0
- package/dist/resources/extensions/gsd/commands/context.js +16 -2
- package/dist/resources/extensions/gsd/commands-handlers.js +46 -3
- package/dist/resources/extensions/gsd/consent-question.js +16 -0
- package/dist/resources/extensions/gsd/crash-recovery.js +8 -3
- package/dist/resources/extensions/gsd/doctor-engine-checks.js +3 -3
- package/dist/resources/extensions/gsd/doctor-git-checks.js +2 -18
- package/dist/resources/extensions/gsd/gsd-command-home.js +22 -12
- package/dist/resources/extensions/gsd/gsd-db.js +2 -1
- package/dist/resources/extensions/gsd/guided-flow.js +6 -3
- package/dist/resources/extensions/gsd/milestone-closeout.js +73 -2
- package/dist/resources/extensions/gsd/milestone-planning-persistence.js +2 -2
- package/dist/resources/extensions/gsd/projection-flush.js +7 -0
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/execute-task.md +1 -1
- package/dist/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/quick-task.md +1 -1
- package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
- package/dist/resources/extensions/gsd/prompts/refine-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/replan-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/research-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/research-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/rewrite-docs.md +1 -1
- package/dist/resources/extensions/gsd/prompts/run-uat.md +1 -1
- package/dist/resources/extensions/gsd/prompts/triage-captures.md +1 -1
- package/dist/resources/extensions/gsd/prompts/validate-milestone.md +1 -1
- package/dist/resources/extensions/gsd/roadmap-slices.js +25 -3
- package/dist/resources/extensions/gsd/session-lock.js +1 -1
- package/dist/resources/extensions/gsd/tool-contract.js +14 -3
- package/dist/resources/extensions/gsd/tools/complete-milestone.js +3 -2
- package/dist/resources/extensions/gsd/tools/complete-slice.js +2 -2
- package/dist/resources/extensions/gsd/tools/complete-task.js +3 -2
- package/dist/resources/extensions/gsd/tools/plan-slice.js +2 -2
- package/dist/resources/extensions/gsd/tools/plan-task.js +2 -2
- package/dist/resources/extensions/gsd/tools/reassess-roadmap.js +2 -2
- package/dist/resources/extensions/gsd/tools/reopen-milestone.js +2 -2
- package/dist/resources/extensions/gsd/tools/reopen-slice.js +2 -2
- package/dist/resources/extensions/gsd/tools/reopen-task.js +2 -2
- package/dist/resources/extensions/gsd/tools/replan-slice.js +2 -2
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +67 -2
- package/dist/resources/extensions/gsd/verification-verdict.js +2 -1
- package/dist/resources/extensions/shared/gsd-browser-cli.js +21 -2
- package/dist/resources/shared/gsd-browser-path-sync.js +214 -0
- package/dist/resources/shared/package-manager-detection.js +1 -1
- package/dist/tsconfig.extensions.tsbuildinfo +1 -1
- package/dist/update-check.d.ts +2 -0
- package/dist/update-check.js +24 -1
- package/dist/update-cmd.js +20 -3
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +12 -12
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +12 -12
- package/dist/web/standalone/.next/server/chunks/8357.js +2 -2
- package/dist/web/standalone/.next/server/middleware-build-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/node_modules/node-pty/build/Makefile +1 -1
- package/package.json +1 -1
- package/packages/cloud-mcp-gateway/package.json +2 -2
- package/packages/contracts/package.json +1 -1
- package/packages/daemon/package.json +4 -4
- package/packages/gsd-agent-core/package.json +5 -5
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/mcp-server/dist/cli.js +10 -5
- package/packages/mcp-server/dist/cli.js.map +1 -1
- package/packages/mcp-server/dist/moonshot-tool-schema.d.ts +29 -0
- package/packages/mcp-server/dist/moonshot-tool-schema.d.ts.map +1 -0
- package/packages/mcp-server/dist/moonshot-tool-schema.js +50 -0
- package/packages/mcp-server/dist/moonshot-tool-schema.js.map +1 -0
- package/packages/mcp-server/dist/server.d.ts.map +1 -1
- package/packages/mcp-server/dist/server.js +4 -0
- package/packages/mcp-server/dist/server.js.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.d.ts +18 -18
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +99 -38
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/package.json +5 -4
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-ai/dist/index.d.ts +2 -0
- package/packages/pi-ai/dist/index.d.ts.map +1 -1
- package/packages/pi-ai/dist/index.js +2 -0
- package/packages/pi-ai/dist/index.js.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic.js +12 -7
- package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
- package/packages/pi-ai/dist/providers/google-shared.d.ts +5 -0
- package/packages/pi-ai/dist/providers/google-shared.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/google-shared.js +12 -3
- package/packages/pi-ai/dist/providers/google-shared.js.map +1 -1
- package/packages/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/openai-completions.js +7 -3
- package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
- package/packages/pi-ai/dist/utils/moonshot-tool-schema.d.ts +9 -0
- package/packages/pi-ai/dist/utils/moonshot-tool-schema.d.ts.map +1 -0
- package/packages/pi-ai/dist/utils/moonshot-tool-schema.js +34 -0
- package/packages/pi-ai/dist/utils/moonshot-tool-schema.js.map +1 -0
- package/packages/pi-ai/dist/utils/oauth/github-copilot.d.ts.map +1 -1
- package/packages/pi-ai/dist/utils/oauth/github-copilot.js +6 -2
- package/packages/pi-ai/dist/utils/oauth/github-copilot.js.map +1 -1
- package/packages/pi-ai/package.json +1 -1
- package/packages/pi-coding-agent/package.json +7 -7
- package/packages/pi-tui/package.json +2 -2
- package/packages/rpc-client/package.json +2 -2
- package/pkg/package.json +1 -1
- package/src/resources/extensions/browser-tools/tests/gsd-browser-launch-config.test.mjs +11 -0
- package/src/resources/extensions/gsd/auto/orchestrator.ts +28 -10
- package/src/resources/extensions/gsd/auto/phases.ts +63 -24
- package/src/resources/extensions/gsd/auto/session.ts +3 -0
- package/src/resources/extensions/gsd/auto-direct-dispatch.ts +10 -16
- package/src/resources/extensions/gsd/auto-dispatch.ts +11 -10
- package/src/resources/extensions/gsd/auto-model-selection.ts +16 -7
- package/src/resources/extensions/gsd/auto-post-unit.ts +21 -6
- package/src/resources/extensions/gsd/auto-unit-closeout.ts +83 -28
- package/src/resources/extensions/gsd/auto-verification.ts +18 -2
- package/src/resources/extensions/gsd/auto.ts +44 -1
- package/src/resources/extensions/gsd/blocked-models.ts +49 -0
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +34 -5
- package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +2 -2
- package/src/resources/extensions/gsd/closeout-wizard.ts +102 -0
- package/src/resources/extensions/gsd/commands/context.ts +16 -2
- package/src/resources/extensions/gsd/commands-handlers.ts +46 -3
- package/src/resources/extensions/gsd/consent-question.ts +15 -0
- package/src/resources/extensions/gsd/crash-recovery.ts +10 -2
- package/src/resources/extensions/gsd/doctor-engine-checks.ts +3 -3
- package/src/resources/extensions/gsd/doctor-git-checks.ts +2 -19
- package/src/resources/extensions/gsd/gsd-command-home.ts +13 -3
- package/src/resources/extensions/gsd/gsd-db.ts +4 -3
- package/src/resources/extensions/gsd/guided-flow.ts +21 -26
- package/src/resources/extensions/gsd/milestone-closeout.ts +97 -2
- package/src/resources/extensions/gsd/milestone-planning-persistence.ts +2 -2
- package/src/resources/extensions/gsd/projection-flush.ts +20 -0
- package/src/resources/extensions/gsd/prompts/complete-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/execute-task.md +1 -1
- package/src/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/quick-task.md +1 -1
- package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
- package/src/resources/extensions/gsd/prompts/refine-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/replan-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/research-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/research-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/rewrite-docs.md +1 -1
- package/src/resources/extensions/gsd/prompts/run-uat.md +1 -1
- package/src/resources/extensions/gsd/prompts/triage-captures.md +1 -1
- package/src/resources/extensions/gsd/prompts/validate-milestone.md +1 -1
- package/src/resources/extensions/gsd/roadmap-slices.ts +28 -3
- package/src/resources/extensions/gsd/session-lock.ts +1 -1
- package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +69 -0
- package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +97 -0
- package/src/resources/extensions/gsd/tests/auto-remote-session-lock-cleanup.test.ts +65 -3
- package/src/resources/extensions/gsd/tests/blocked-models.test.ts +19 -0
- package/src/resources/extensions/gsd/tests/consent-question.test.ts +15 -0
- package/src/resources/extensions/gsd/tests/doctor-git-checks-terminal.test.ts +73 -0
- package/src/resources/extensions/gsd/tests/gsd-command-home.test.ts +120 -0
- package/src/resources/extensions/gsd/tests/guided-dispatch-root.test.ts +2 -6
- package/src/resources/extensions/gsd/tests/milestone-closeout.test.ts +95 -4
- package/src/resources/extensions/gsd/tests/parsers-legacy-importers.test.ts +0 -1
- package/src/resources/extensions/gsd/tests/phases-terminal-complete-idempotent.test.ts +242 -0
- package/src/resources/extensions/gsd/tests/post-exec-retry-bypass.test.ts +63 -2
- package/src/resources/extensions/gsd/tests/roadmap-slices.test.ts +68 -0
- package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +19 -1
- package/src/resources/extensions/gsd/tests/tool-unavailable-retry.test.ts +33 -0
- package/src/resources/extensions/gsd/tests/transport-gate-double-complete.test.ts +139 -0
- package/src/resources/extensions/gsd/tests/verification-verdict.test.ts +2 -0
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +273 -38
- package/src/resources/extensions/gsd/tool-contract.ts +38 -3
- package/src/resources/extensions/gsd/tools/complete-milestone.ts +3 -2
- package/src/resources/extensions/gsd/tools/complete-slice.ts +2 -2
- package/src/resources/extensions/gsd/tools/complete-task.ts +3 -2
- package/src/resources/extensions/gsd/tools/plan-slice.ts +2 -2
- package/src/resources/extensions/gsd/tools/plan-task.ts +2 -2
- package/src/resources/extensions/gsd/tools/reassess-roadmap.ts +2 -2
- package/src/resources/extensions/gsd/tools/reopen-milestone.ts +2 -2
- package/src/resources/extensions/gsd/tools/reopen-slice.ts +2 -2
- package/src/resources/extensions/gsd/tools/reopen-task.ts +2 -2
- package/src/resources/extensions/gsd/tools/replan-slice.ts +2 -2
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +81 -2
- package/src/resources/extensions/gsd/verification-verdict.ts +4 -2
- package/src/resources/extensions/shared/gsd-browser-cli.ts +23 -2
- package/src/resources/shared/gsd-browser-path-sync.ts +273 -0
- package/src/resources/shared/package-manager-detection.ts +1 -1
- /package/dist/web/standalone/.next/static/{jmTLg6xZmAuq_LIqKOxrH → McokybTayhff1xEVc-d3T}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{jmTLg6xZmAuq_LIqKOxrH → McokybTayhff1xEVc-d3T}/_ssgManifest.js +0 -0
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { logWarning } from "../workflow-logger.js";
|
|
3
3
|
import { checkDeepProjectSetupAfterTurn, checkAutoStartAfterDiscuss, maybeHandleReadyPhraseWithoutFiles, maybeHandleEmptyIntentTurn, resetEmptyTurnCounter, } from "../guided-flow.js";
|
|
4
4
|
import { clearPathCache } from "../paths.js";
|
|
5
|
-
import { getAutoDashboardData, getAutoModeStartModel, isAutoActive, isAutoCompletionStopInProgress, pauseAuto, setCurrentDispatchedModelId, } from "../auto.js";
|
|
5
|
+
import { getAutoDashboardData, getAutoModeStartModel, isAutoActive, isAutoCompletionStopInProgress, pauseAuto, setCurrentDispatchedModelId, setCurrentUnitModelForRecovery, } from "../auto.js";
|
|
6
6
|
import { getNextFallbackModel, resolveModelWithFallbacksForUnit } from "../preferences.js";
|
|
7
7
|
import { pauseAutoForProviderError } from "../provider-error-pause.js";
|
|
8
8
|
import { isSessionSwitchAbortGraceActive, isSessionSwitchInFlight, resolveAgentEnd, resolveAgentEndCancelled, } from "../auto/resolve.js";
|
|
@@ -13,7 +13,7 @@ import { clearDiscussionFlowState } from "./write-gate.js";
|
|
|
13
13
|
import { clearGuidedUnitContext } from "../guided-unit-context.js";
|
|
14
14
|
import { resumeAutoAfterProviderDelay } from "./provider-error-resume.js";
|
|
15
15
|
import { classifyError, createRetryState, resetRetryState, isTransient, } from "../error-classifier.js";
|
|
16
|
-
import { blockModel, isModelBlocked } from "../blocked-models.js";
|
|
16
|
+
import { blockModel, blockModelUntil, isModelBlocked, isModelTemporarilyUnavailable } from "../blocked-models.js";
|
|
17
17
|
import { getProjectGSDPreferencesPath } from "../preferences.js";
|
|
18
18
|
import { resolveProviderErrorGuidance } from "../provider-error-guidance.js";
|
|
19
19
|
import { formatGuidance } from "../guidance.js";
|
|
@@ -96,9 +96,12 @@ async function tryProviderModelFallback(params) {
|
|
|
96
96
|
if (!nextModelId)
|
|
97
97
|
break;
|
|
98
98
|
const candidate = resolveModelId(nextModelId, availableModels, rejectedProvider);
|
|
99
|
-
if (candidate &&
|
|
99
|
+
if (candidate &&
|
|
100
|
+
!isModelBlocked(basePath, candidate.provider, candidate.id) &&
|
|
101
|
+
!isModelTemporarilyUnavailable(basePath, candidate.provider, candidate.id)) {
|
|
100
102
|
const ok = await pi.setModel(candidate, { persist: false });
|
|
101
103
|
if (ok) {
|
|
104
|
+
setCurrentUnitModelForRecovery(candidate);
|
|
102
105
|
setCurrentDispatchedModelId({ provider: candidate.provider, id: candidate.id });
|
|
103
106
|
switchedNotify(`${candidate.provider}/${candidate.id}`);
|
|
104
107
|
pi.sendMessage({ customType: "gsd-auto-timeout-recovery", content: "Continue execution.", display: false }, { triggerTurn: true });
|
|
@@ -111,11 +114,13 @@ async function tryProviderModelFallback(params) {
|
|
|
111
114
|
const sessionModel = getAutoModeStartModel();
|
|
112
115
|
if (sessionModel &&
|
|
113
116
|
!(sessionModel.provider === rejectedProvider && sessionModel.id === rejectedId) &&
|
|
114
|
-
!isModelBlocked(basePath, sessionModel.provider, sessionModel.id)
|
|
117
|
+
!isModelBlocked(basePath, sessionModel.provider, sessionModel.id) &&
|
|
118
|
+
!isModelTemporarilyUnavailable(basePath, sessionModel.provider, sessionModel.id)) {
|
|
115
119
|
const startModel = availableModels.find((m) => m.provider === sessionModel.provider && m.id === sessionModel.id);
|
|
116
120
|
if (startModel) {
|
|
117
121
|
const ok = await pi.setModel(startModel, { persist: false });
|
|
118
122
|
if (ok) {
|
|
123
|
+
setCurrentUnitModelForRecovery(startModel);
|
|
119
124
|
setCurrentDispatchedModelId({ provider: startModel.provider, id: startModel.id });
|
|
120
125
|
switchedNotify(`${startModel.provider}/${startModel.id}`);
|
|
121
126
|
pi.sendMessage({ customType: "gsd-auto-timeout-recovery", content: "Continue execution.", display: false }, { triggerTurn: true });
|
|
@@ -533,6 +538,10 @@ export async function handleAgentEnd(pi, event, ctx) {
|
|
|
533
538
|
if (currentProvider === "openai-codex" || currentProvider === "google-gemini-cli") {
|
|
534
539
|
cls.retryAfterMs = Math.min(cls.retryAfterMs, 30_000);
|
|
535
540
|
}
|
|
541
|
+
const dash = getAutoDashboardData();
|
|
542
|
+
if (dash.basePath && ctx.model?.provider && ctx.model?.id) {
|
|
543
|
+
blockModelUntil(dash.basePath, ctx.model.provider, ctx.model.id, Date.now() + cls.retryAfterMs, rawErrorMsg || displayMsg || "rate limit");
|
|
544
|
+
}
|
|
536
545
|
}
|
|
537
546
|
// ── 2. Decide & Act ──────────────────────────────────────────────────
|
|
538
547
|
// --- Network errors: same-model retry with backoff ---
|
|
@@ -572,9 +581,14 @@ export async function handleAgentEnd(pi, event, ctx) {
|
|
|
572
581
|
retryState.networkRetryCount = 0;
|
|
573
582
|
retryState.currentRetryModelId = undefined;
|
|
574
583
|
const modelToSet = resolveModelId(nextModelId, availableModels, ctx.model?.provider);
|
|
575
|
-
|
|
584
|
+
const modelUnavailable = dash.basePath && modelToSet
|
|
585
|
+
? isModelBlocked(dash.basePath, modelToSet.provider, modelToSet.id) ||
|
|
586
|
+
isModelTemporarilyUnavailable(dash.basePath, modelToSet.provider, modelToSet.id)
|
|
587
|
+
: false;
|
|
588
|
+
if (modelToSet && !modelUnavailable) {
|
|
576
589
|
const ok = await pi.setModel(modelToSet, { persist: false });
|
|
577
590
|
if (ok) {
|
|
591
|
+
setCurrentUnitModelForRecovery(modelToSet);
|
|
578
592
|
setCurrentDispatchedModelId({ provider: modelToSet.provider, id: modelToSet.id });
|
|
579
593
|
ctx.ui.notify(`Model error${errorDetail}. Switched to fallback: ${nextModelId} and resuming.`, "warning");
|
|
580
594
|
pi.sendMessage({ customType: "gsd-auto-timeout-recovery", content: "Continue execution.", display: false }, { triggerTurn: true });
|
|
@@ -587,11 +601,17 @@ export async function handleAgentEnd(pi, event, ctx) {
|
|
|
587
601
|
// Try restoring session model
|
|
588
602
|
const sessionModel = getAutoModeStartModel();
|
|
589
603
|
if (sessionModel) {
|
|
590
|
-
|
|
604
|
+
const dash = getAutoDashboardData();
|
|
605
|
+
const sessionModelUnavailable = dash.basePath
|
|
606
|
+
? isModelBlocked(dash.basePath, sessionModel.provider, sessionModel.id) ||
|
|
607
|
+
isModelTemporarilyUnavailable(dash.basePath, sessionModel.provider, sessionModel.id)
|
|
608
|
+
: false;
|
|
609
|
+
if (!sessionModelUnavailable && (ctx.model?.id !== sessionModel.id || ctx.model?.provider !== sessionModel.provider)) {
|
|
591
610
|
const startModel = ctx.modelRegistry.getAvailable().find((m) => m.provider === sessionModel.provider && m.id === sessionModel.id);
|
|
592
611
|
if (startModel) {
|
|
593
612
|
const ok = await pi.setModel(startModel, { persist: false });
|
|
594
613
|
if (ok) {
|
|
614
|
+
setCurrentUnitModelForRecovery(startModel);
|
|
595
615
|
setCurrentDispatchedModelId({ provider: startModel.provider, id: startModel.id });
|
|
596
616
|
retryState.networkRetryCount = 0;
|
|
597
617
|
retryState.currentRetryModelId = undefined;
|
|
@@ -114,8 +114,8 @@ export function registerExecTools(pi) {
|
|
|
114
114
|
],
|
|
115
115
|
parameters: Type.Object({
|
|
116
116
|
query: Type.Optional(Type.String({ description: "Substring matched against id and purpose (case-insensitive)." })),
|
|
117
|
-
runtime: Type.Optional(Type.
|
|
118
|
-
description: "Restrict to one runtime.",
|
|
117
|
+
runtime: Type.Optional(Type.String({
|
|
118
|
+
description: "Restrict to one runtime: bash, node, or python.",
|
|
119
119
|
})),
|
|
120
120
|
failing_only: Type.Optional(Type.Boolean({ description: "Only non-zero exit codes and timeouts." })),
|
|
121
121
|
limit: Type.Optional(Type.Number({ description: "Max results (default 20, cap 200)", minimum: 1, maximum: 200 })),
|
|
@@ -1,22 +1,107 @@
|
|
|
1
1
|
// Project/App: gsd-pi
|
|
2
2
|
// File Purpose: Shared closeout detection and merge actions for /gsd home and smart entry.
|
|
3
|
+
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
4
|
+
import { join } from "node:path";
|
|
3
5
|
import { setAutoOutcomeWidget } from "./auto-dashboard.js";
|
|
4
6
|
import { invalidateAllCaches } from "./cache.js";
|
|
7
|
+
import { isDbAvailable } from "./db/engine.js";
|
|
8
|
+
import { getMilestone } from "./db/queries.js";
|
|
9
|
+
import { MILESTONE_ID_RE } from "./milestone-ids.js";
|
|
5
10
|
import { mergeCompletedMilestone } from "./parallel-merge.js";
|
|
6
11
|
import { cleanupQuickBranch, detectStrandedQuickBranch } from "./quick.js";
|
|
12
|
+
import { isClosedStatus } from "./status-guards.js";
|
|
7
13
|
import { findUnmergedCompletedMilestones, } from "./unmerged-milestone-guard.js";
|
|
8
14
|
import { appendRequirementsBacklogToSummary } from "./requirements-backlog.js";
|
|
15
|
+
import { nativeBranchList, nativeIsRepo } from "./native-git-bridge.js";
|
|
16
|
+
import { allWorktreesDirs } from "./worktree-manager.js";
|
|
9
17
|
const MILESTONE_MERGE_CLOSEOUT_COMMANDS = [
|
|
10
18
|
"/gsd status for overview",
|
|
11
19
|
"/gsd visualize to inspect",
|
|
12
20
|
"/gsd notifications for history",
|
|
13
21
|
"/gsd start for new work",
|
|
14
22
|
];
|
|
23
|
+
function listMilestoneWorktreeIds(basePath) {
|
|
24
|
+
const ids = new Set();
|
|
25
|
+
for (const wtDir of allWorktreesDirs(basePath)) {
|
|
26
|
+
if (!existsSync(wtDir))
|
|
27
|
+
continue;
|
|
28
|
+
for (const entry of readdirSync(wtDir)) {
|
|
29
|
+
if (!MILESTONE_ID_RE.test(entry))
|
|
30
|
+
continue;
|
|
31
|
+
try {
|
|
32
|
+
if (statSync(join(wtDir, entry)).isDirectory())
|
|
33
|
+
ids.add(entry);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
// skip unreadable entries
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return [...ids].sort();
|
|
41
|
+
}
|
|
42
|
+
function listMilestoneBranchIds(basePath) {
|
|
43
|
+
try {
|
|
44
|
+
return nativeBranchList(basePath, "milestone/*")
|
|
45
|
+
.map((branch) => branch.replace(/^milestone\//, ""))
|
|
46
|
+
.filter((id) => MILESTONE_ID_RE.test(id))
|
|
47
|
+
.sort();
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return [];
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* A milestone ID is "stranded residue" only when its worktree/branch artifacts
|
|
55
|
+
* exist for a milestone the DB does not consider currently in flight — i.e. the
|
|
56
|
+
* row is closed (complete/done/skipped/closed) or absent. Active, pending,
|
|
57
|
+
* blocked, parked, queued, and deferred rows describe normal in-flight or
|
|
58
|
+
* intentionally-preserved state, never residue. Returning `false` skips the ID;
|
|
59
|
+
* returning `true` keeps it in the hint.
|
|
60
|
+
*/
|
|
61
|
+
function isStrandedMilestoneId(milestoneId) {
|
|
62
|
+
if (!isDbAvailable())
|
|
63
|
+
return true;
|
|
64
|
+
const row = getMilestone(milestoneId);
|
|
65
|
+
if (!row)
|
|
66
|
+
return true;
|
|
67
|
+
return isClosedStatus(row.status);
|
|
68
|
+
}
|
|
69
|
+
/** Surface stranded milestone git residue when closeout guards did not classify it. */
|
|
70
|
+
export function detectIdleMilestoneResidueHint(basePath) {
|
|
71
|
+
if (!nativeIsRepo(basePath))
|
|
72
|
+
return null;
|
|
73
|
+
const gsdDir = join(basePath, ".gsd");
|
|
74
|
+
const dbPath = join(gsdDir, "gsd.db");
|
|
75
|
+
if (!existsSync(gsdDir) || !existsSync(dbPath)) {
|
|
76
|
+
return {
|
|
77
|
+
milestoneIds: [],
|
|
78
|
+
message: "This git repo has no local GSD workflow database (.gsd/gsd.db). " +
|
|
79
|
+
"Workflow state may live in an external worktree, or run /gsd new-project to initialize here.",
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
const worktreeIds = listMilestoneWorktreeIds(basePath);
|
|
83
|
+
const branchIds = listMilestoneBranchIds(basePath);
|
|
84
|
+
const candidateIds = [...new Set([...worktreeIds, ...branchIds])].sort();
|
|
85
|
+
const milestoneIds = candidateIds.filter(isStrandedMilestoneId);
|
|
86
|
+
if (milestoneIds.length === 0)
|
|
87
|
+
return null;
|
|
88
|
+
const listed = milestoneIds.join(", ");
|
|
89
|
+
const recovery = milestoneIds.length === 1
|
|
90
|
+
? `/gsd dispatch complete-milestone ${milestoneIds[0]}`
|
|
91
|
+
: "/gsd doctor --fix";
|
|
92
|
+
return {
|
|
93
|
+
milestoneIds,
|
|
94
|
+
message: `Stranded milestone git residue detected (${listed}: worktree dir and/or milestone/* branch). ` +
|
|
95
|
+
`Run ${recovery} or /gsd status to recover closeout before starting new work.`,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
15
98
|
export async function loadCloseoutContext(basePath) {
|
|
16
99
|
const unmergedMilestones = await findUnmergedCompletedMilestones(basePath);
|
|
100
|
+
const idleResidueHint = unmergedMilestones.length === 0 ? detectIdleMilestoneResidueHint(basePath) : null;
|
|
17
101
|
return {
|
|
18
102
|
strandedQuick: detectStrandedQuickBranch(basePath),
|
|
19
103
|
unmergedMilestones,
|
|
104
|
+
idleResidueHint,
|
|
20
105
|
};
|
|
21
106
|
}
|
|
22
107
|
export function getPrimaryCloseoutRecommendation(closeout) {
|
|
@@ -62,6 +147,13 @@ export function buildIdleMenuSummary(state, closeout) {
|
|
|
62
147
|
`${blocker.milestoneId} is complete but not merged into ${blocker.integrationBranch}.`,
|
|
63
148
|
];
|
|
64
149
|
}
|
|
150
|
+
// Surface idle residue before the completion summary so smart entry shows
|
|
151
|
+
// the same recovery text /gsd home would: a closed/unknown milestone with
|
|
152
|
+
// lingering worktree/branch artifacts must not be hidden behind the
|
|
153
|
+
// "all milestones complete" message.
|
|
154
|
+
if (closeout.idleResidueHint) {
|
|
155
|
+
return [closeout.idleResidueHint.message];
|
|
156
|
+
}
|
|
65
157
|
if (state.phase === "complete") {
|
|
66
158
|
const last = state.lastCompletedMilestone;
|
|
67
159
|
return appendRequirementsBacklogToSummary(state, [
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { checkRemoteAutoSession, isAutoActive, isAutoPaused, stopAutoRemote } from "../auto.js";
|
|
1
|
+
import { checkRemoteAutoSession, forceStopAutoRemote, isAutoActive, isAutoPaused, stopAutoRemote } from "../auto.js";
|
|
2
2
|
import { validateDirectory } from "../validate-directory.js";
|
|
3
3
|
import { resolveProjectRoot } from "../worktree.js";
|
|
4
4
|
import { showNextAction } from "../../shared/tui.js";
|
|
@@ -135,5 +135,19 @@ export async function guardRemoteSession(ctx, pi) {
|
|
|
135
135
|
}
|
|
136
136
|
return false;
|
|
137
137
|
}
|
|
138
|
-
|
|
138
|
+
if (choice === "force") {
|
|
139
|
+
const result = forceStopAutoRemote(projectRoot());
|
|
140
|
+
if (result.error) {
|
|
141
|
+
ctx.ui.notify(`Failed to force-stop remote auto-mode: ${result.error}`, "error");
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
if (result.found) {
|
|
145
|
+
ctx.ui.notify(`Force-stopped auto-mode session (PID ${result.pid}). Starting a new session.`, "warning");
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
ctx.ui.notify("Remote session is no longer running. Starting a new session.", "info");
|
|
149
|
+
}
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
return false;
|
|
139
153
|
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import { existsSync, readFileSync, mkdirSync } from "node:fs";
|
|
8
8
|
import { execFileSync } from "node:child_process";
|
|
9
9
|
import { createRequire } from "node:module";
|
|
10
|
-
import { join, resolve as resolvePath, sep } from "node:path";
|
|
10
|
+
import { join, resolve as resolvePath, sep, win32 as pathWin32 } from "node:path";
|
|
11
11
|
import { homedir } from "node:os";
|
|
12
12
|
import { deriveState } from "./state.js";
|
|
13
13
|
import { gsdRoot } from "./paths.js";
|
|
@@ -20,6 +20,7 @@ import { getAutoWorktreePath } from "./auto-worktree.js";
|
|
|
20
20
|
import { currentDirectoryRoot, projectRoot } from "./commands/context.js";
|
|
21
21
|
import { loadPrompt } from "./prompt-loader.js";
|
|
22
22
|
import { buildClaudeRuntimeFloorAdvisory } from "../../shared/claude-runtime-floor.js";
|
|
23
|
+
import { reconcileGsdBrowserPathAfterInstall } from "../../shared/gsd-browser-path-sync.js";
|
|
23
24
|
import { isPnpmInstall } from "../../shared/package-manager-detection.js";
|
|
24
25
|
import { buildDoctorHealIssuePayload, buildDoctorHealSummary, buildWorkflowDispatchContent, } from "./workflow-protocol.js";
|
|
25
26
|
import { restoreGsdWorkflowTools, scopeGsdWorkflowToolsForDispatch, } from "./bootstrap/register-hooks.js";
|
|
@@ -51,8 +52,31 @@ function resolveInstallCommand(pkg) {
|
|
|
51
52
|
return `bun add -g ${pkg}`;
|
|
52
53
|
if (isPnpmInstall())
|
|
53
54
|
return `pnpm add -g ${pkg}`;
|
|
55
|
+
const npmPrefix = resolveWindowsNpmGlobalPrefix();
|
|
56
|
+
if (npmPrefix)
|
|
57
|
+
return `npm --prefix ${quoteWindowsArg(npmPrefix)} install -g ${pkg}`;
|
|
54
58
|
return `npm install -g ${pkg}`;
|
|
55
59
|
}
|
|
60
|
+
function resolveWindowsNpmGlobalPrefix(argv1 = process.argv[1], platform = process.platform) {
|
|
61
|
+
if (platform !== "win32" || !argv1)
|
|
62
|
+
return null;
|
|
63
|
+
const normalized = pathWin32.normalize(argv1);
|
|
64
|
+
const marker = `${pathWin32.sep}node_modules${pathWin32.sep}`;
|
|
65
|
+
const index = normalized.toLowerCase().lastIndexOf(marker);
|
|
66
|
+
if (index <= 0)
|
|
67
|
+
return null;
|
|
68
|
+
const prefix = normalized.slice(0, index);
|
|
69
|
+
// Verify this is a real npm global prefix: such a directory always contains
|
|
70
|
+
// npm's own bin shim (`npm.cmd`) as a sibling of `node_modules/`. Local
|
|
71
|
+
// project `node_modules/`, npx caches, and other non-global layouts do not,
|
|
72
|
+
// so without this check `--prefix` would target the wrong directory.
|
|
73
|
+
if (!existsSync(pathWin32.join(prefix, "npm.cmd")))
|
|
74
|
+
return null;
|
|
75
|
+
return prefix;
|
|
76
|
+
}
|
|
77
|
+
function quoteWindowsArg(value) {
|
|
78
|
+
return `"${value.replace(/"/g, '\\"')}"`;
|
|
79
|
+
}
|
|
56
80
|
function notifyClaudeRuntimeFloorAdvisory(ctx) {
|
|
57
81
|
let advisory = null;
|
|
58
82
|
try {
|
|
@@ -484,11 +508,30 @@ export async function handleUpdate(ctx, args = "") {
|
|
|
484
508
|
execSync(installCmd, {
|
|
485
509
|
stdio: ["ignore", "pipe", "ignore"],
|
|
486
510
|
});
|
|
511
|
+
let reconcile = null;
|
|
512
|
+
if (browserUpdate) {
|
|
513
|
+
try {
|
|
514
|
+
reconcile = reconcileGsdBrowserPathAfterInstall({
|
|
515
|
+
latestVersion: latest,
|
|
516
|
+
compareSemver: compareSemverLocal,
|
|
517
|
+
resolvePathVersion: resolveGsdBrowserPathVersionForCommand,
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
catch {
|
|
521
|
+
// Reconciliation is best-effort: the install above already succeeded,
|
|
522
|
+
// so a reconcile failure must not flip the result to "Update failed".
|
|
523
|
+
reconcile = null;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
487
526
|
const newPathVersion = browserUpdate ? resolveGsdBrowserPathVersionForCommand() : null;
|
|
488
|
-
const
|
|
527
|
+
const pathNote = browserUpdate && !(newPathVersion && compareSemverLocal(newPathVersion, latest) >= 0)
|
|
528
|
+
? (reconcile?.message
|
|
529
|
+
?? "Ensure the npm global bin directory is on your PATH so MCP automation uses the updated binary.")
|
|
530
|
+
: "";
|
|
489
531
|
ctx.ui.notify(browserUpdate
|
|
490
532
|
? `Updated gsd-browser to v${latest}. Restart your GSD session to use the new browser automation version.` +
|
|
491
|
-
(
|
|
533
|
+
(reconcile?.action === "synced" && reconcile.message ? `\n${reconcile.message}` : "") +
|
|
534
|
+
(pathNote ? `\nNote: ${pathNote}` : "")
|
|
492
535
|
: `Updated to v${latest}. Restart your GSD session to use the new version.`, "info");
|
|
493
536
|
if (!browserUpdate)
|
|
494
537
|
notifyClaudeRuntimeFloorAdvisory(ctx);
|
|
@@ -62,6 +62,20 @@ export function hasApprovalQuestion(text) {
|
|
|
62
62
|
export function hasResearchDecisionQuestion(text) {
|
|
63
63
|
return hasQuestionMatching(text, [RESEARCH_DECISION_QUESTION_RE]);
|
|
64
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* Detect a plain-text "Next steps:" menu — numbered options with an "Other"
|
|
67
|
+
* choice — emitted as prose instead of a structured ask_user_questions call.
|
|
68
|
+
* Without this, auto-mode treats the menu as informational and loops on its
|
|
69
|
+
* own turn until tokens are exhausted (#454).
|
|
70
|
+
*/
|
|
71
|
+
export function hasPlainTextNextStepsMenu(lines) {
|
|
72
|
+
const nextStepsIndex = lines.findIndex((line) => /^next steps\s*:?$/i.test(line));
|
|
73
|
+
if (nextStepsIndex < 0)
|
|
74
|
+
return false;
|
|
75
|
+
const menuLines = lines.slice(nextStepsIndex + 1);
|
|
76
|
+
const numberedOptions = menuLines.filter((line) => /^\d+[.)]\s+\S/.test(line));
|
|
77
|
+
return numberedOptions.length >= 2 && numberedOptions.some((line) => /\bother\b/i.test(line));
|
|
78
|
+
}
|
|
65
79
|
// ── Message text extraction (moved from user-input-boundary) ────────────────
|
|
66
80
|
function extractMessageText(msg, includeThinking) {
|
|
67
81
|
if (!msg || typeof msg !== "object")
|
|
@@ -273,6 +287,8 @@ export function isAwaitingUserInput(messages) {
|
|
|
273
287
|
const lines = text.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
274
288
|
if (lines.some((line) => line.endsWith("?")))
|
|
275
289
|
return true;
|
|
290
|
+
if (hasPlainTextNextStepsMenu(lines))
|
|
291
|
+
return true;
|
|
276
292
|
return hasApprovalQuestion(text);
|
|
277
293
|
}
|
|
278
294
|
export function isAwaitingApprovalBoundary(messages) {
|
|
@@ -186,6 +186,7 @@ export function writeLock(basePath, unitType, unitId, sessionFile) {
|
|
|
186
186
|
* stale session-file pointer.
|
|
187
187
|
*/
|
|
188
188
|
export function clearLock(basePath) {
|
|
189
|
+
const legacyLock = readLegacyLock(basePath);
|
|
189
190
|
clearLegacyLockFile(basePath);
|
|
190
191
|
if (!isDbAvailable())
|
|
191
192
|
return;
|
|
@@ -198,9 +199,13 @@ export function clearLock(basePath) {
|
|
|
198
199
|
deleteRuntimeKv("worker", staleWorker.worker_id, SESSION_FILE_KV_KEY);
|
|
199
200
|
return;
|
|
200
201
|
}
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
202
|
+
if (legacyLock?.pid) {
|
|
203
|
+
markWorkerStoppingByPid(projectRoot, legacyLock.pid);
|
|
204
|
+
const workerByLegacyPid = getAllAutoWorkers().find((w) => w.pid === legacyLock.pid
|
|
205
|
+
&& normalizeRealPath(w.project_root_realpath) === projectRoot);
|
|
206
|
+
if (workerByLegacyPid)
|
|
207
|
+
forceReleaseLeasesForWorker(workerByLegacyPid.worker_id);
|
|
208
|
+
}
|
|
204
209
|
const worker = findActiveWorkerForCurrentProcess(projectRoot);
|
|
205
210
|
if (worker)
|
|
206
211
|
deleteRuntimeKv("worker", worker.worker_id, SESSION_FILE_KV_KEY);
|
|
@@ -5,7 +5,7 @@ import { isAfter, latestExplicitReopenAt } from "./milestone-reopen-events.js";
|
|
|
5
5
|
import { resolveGsdPathContract, resolveMilestoneFile } from "./paths.js";
|
|
6
6
|
import { deriveState } from "./state.js";
|
|
7
7
|
import { readEvents } from "./workflow-events.js";
|
|
8
|
-
import {
|
|
8
|
+
import { flushWorkflowProjections } from "./projection-flush.js";
|
|
9
9
|
export async function checkEngineHealth(basePath, issues, fixesApplied) {
|
|
10
10
|
const dbPath = resolveGsdPathContract(basePath).projectDb;
|
|
11
11
|
if (!isDbAvailable() && existsSync(dbPath)) {
|
|
@@ -239,7 +239,7 @@ export async function checkEngineHealth(basePath, issues, fixesApplied) {
|
|
|
239
239
|
const roadmapPath = resolveMilestoneFile(basePath, milestone.id, "ROADMAP");
|
|
240
240
|
if (!roadmapPath || !existsSync(roadmapPath)) {
|
|
241
241
|
try {
|
|
242
|
-
await
|
|
242
|
+
await flushWorkflowProjections(basePath, { milestoneId: milestone.id });
|
|
243
243
|
fixesApplied.push(`re-rendered missing projections for ${milestone.id}`);
|
|
244
244
|
}
|
|
245
245
|
catch {
|
|
@@ -250,7 +250,7 @@ export async function checkEngineHealth(basePath, issues, fixesApplied) {
|
|
|
250
250
|
const projectionMtime = statSync(roadmapPath).mtimeMs;
|
|
251
251
|
if (lastEventTs > projectionMtime) {
|
|
252
252
|
try {
|
|
253
|
-
await
|
|
253
|
+
await flushWorkflowProjections(basePath, { milestoneId: milestone.id });
|
|
254
254
|
fixesApplied.push(`re-rendered stale projections for ${milestone.id}`);
|
|
255
255
|
}
|
|
256
256
|
catch {
|
|
@@ -3,10 +3,9 @@ import { spawnSync } from "node:child_process";
|
|
|
3
3
|
import { cpSync, existsSync, mkdirSync, readdirSync, realpathSync, rmSync, statSync } from "node:fs";
|
|
4
4
|
import { dirname, join } from "node:path";
|
|
5
5
|
import { loadFile } from "./files.js";
|
|
6
|
-
import { parseRoadmap as parseLegacyRoadmap } from "./parsers-legacy.js";
|
|
7
|
-
import { isDbAvailable, getMilestone } from "./gsd-db.js";
|
|
8
6
|
import { resolveMilestoneFile } from "./paths.js";
|
|
9
|
-
import {
|
|
7
|
+
import { isCompletedMilestoneTerminal } from "./milestone-closeout.js";
|
|
8
|
+
import { deriveState } from "./state.js";
|
|
10
9
|
import { allWorktreesDirs, createWorktree, listWorktrees, resolveGitDir } from "./worktree-manager.js";
|
|
11
10
|
import { abortAndReset } from "./git-self-heal.js";
|
|
12
11
|
import { RUNTIME_EXCLUSION_PATHS, resolveMilestoneIntegrationBranch, writeIntegrationBranch } from "./git-service.js";
|
|
@@ -141,21 +140,6 @@ function getSnapshotDiffCheckFailure(basePath) {
|
|
|
141
140
|
}
|
|
142
141
|
return failures.length > 0 ? failures.join("\n") : null;
|
|
143
142
|
}
|
|
144
|
-
async function isCompletedMilestoneTerminal(basePath, milestoneId) {
|
|
145
|
-
const summaryPath = resolveMilestoneFile(basePath, milestoneId, "SUMMARY");
|
|
146
|
-
if (!summaryPath)
|
|
147
|
-
return false;
|
|
148
|
-
if (isDbAvailable()) {
|
|
149
|
-
const milestone = getMilestone(milestoneId);
|
|
150
|
-
return !!milestone && milestone.status === "complete";
|
|
151
|
-
}
|
|
152
|
-
const roadmapPath = resolveMilestoneFile(basePath, milestoneId, "ROADMAP");
|
|
153
|
-
const roadmapContent = roadmapPath ? await loadFile(roadmapPath) : null;
|
|
154
|
-
if (!roadmapContent)
|
|
155
|
-
return false;
|
|
156
|
-
const roadmap = parseLegacyRoadmap(roadmapContent);
|
|
157
|
-
return isMilestoneComplete(roadmap);
|
|
158
|
-
}
|
|
159
143
|
export async function checkGitHealth(basePath, issues, fixesApplied, shouldFix, isolationMode = "none") {
|
|
160
144
|
// Degrade gracefully if not a git repo
|
|
161
145
|
if (!nativeIsRepo(basePath)) {
|
|
@@ -28,13 +28,17 @@ export function buildGsdHomeModel(state, closeout) {
|
|
|
28
28
|
const workLabel = activeWorkLabel(state);
|
|
29
29
|
const strandedQuick = closeout?.strandedQuick ?? null;
|
|
30
30
|
const unmergedMilestone = closeout?.unmergedMilestones?.[0];
|
|
31
|
+
const idleResidueHint = closeout?.idleResidueHint ?? null;
|
|
32
|
+
const hasIdleResidue = Boolean(idleResidueHint);
|
|
31
33
|
const nextReason = complete
|
|
32
34
|
? "all milestones are complete"
|
|
33
35
|
: blocked
|
|
34
36
|
? "the active milestone is blocked"
|
|
35
|
-
:
|
|
36
|
-
? "
|
|
37
|
-
:
|
|
37
|
+
: hasIdleResidue
|
|
38
|
+
? "milestone git residue needs recovery"
|
|
39
|
+
: !hasActiveWork
|
|
40
|
+
? "there is no active milestone"
|
|
41
|
+
: "";
|
|
38
42
|
const canAdvance = hasActiveWork && !blocked && !complete;
|
|
39
43
|
const unmappedActive = complete ? countUnmappedActiveRequirements() : 0;
|
|
40
44
|
const recommended = strandedQuick
|
|
@@ -43,11 +47,13 @@ export function buildGsdHomeModel(state, closeout) {
|
|
|
43
47
|
? "finish_milestone"
|
|
44
48
|
: blocked
|
|
45
49
|
? "fix_recover"
|
|
46
|
-
:
|
|
47
|
-
? "
|
|
48
|
-
:
|
|
49
|
-
? "
|
|
50
|
-
:
|
|
50
|
+
: hasIdleResidue
|
|
51
|
+
? "fix_recover"
|
|
52
|
+
: canAdvance
|
|
53
|
+
? "continue_step"
|
|
54
|
+
: complete && unmappedActive > 0
|
|
55
|
+
? "review_requirements_backlog"
|
|
56
|
+
: "start_configure";
|
|
51
57
|
const completionSummary = complete
|
|
52
58
|
? appendRequirementsBacklogToSummary(state, [
|
|
53
59
|
`All milestones complete${state.lastCompletedMilestone ? ` after ${state.lastCompletedMilestone.id}: ${state.lastCompletedMilestone.title}` : ""}.`,
|
|
@@ -57,7 +63,9 @@ export function buildGsdHomeModel(state, closeout) {
|
|
|
57
63
|
? [`Quick task Q${strandedQuick.taskNum} finished on ${strandedQuick.quickBranch} but is not merged to ${strandedQuick.originalBranch}.`]
|
|
58
64
|
: unmergedMilestone
|
|
59
65
|
? [`${unmergedMilestone.milestoneId} is complete but not merged into ${unmergedMilestone.integrationBranch}.`]
|
|
60
|
-
:
|
|
66
|
+
: idleResidueHint
|
|
67
|
+
? [idleResidueHint.message]
|
|
68
|
+
: completionSummary;
|
|
61
69
|
return {
|
|
62
70
|
title: "GSD — What now?",
|
|
63
71
|
summary: [
|
|
@@ -130,10 +138,12 @@ export function buildGsdHomeModel(state, closeout) {
|
|
|
130
138
|
label: "Fix or recover",
|
|
131
139
|
description: blocked
|
|
132
140
|
? "Review the blocker and recovery commands for the active milestone."
|
|
133
|
-
:
|
|
134
|
-
|
|
141
|
+
: hasIdleResidue
|
|
142
|
+
? "Review stranded milestone worktrees/branches and run the suggested recovery command."
|
|
143
|
+
: disabled("This becomes active when closeout, validation, or state recovery is needed.", "no blocker is active"),
|
|
144
|
+
enabled: blocked || hasIdleResidue,
|
|
135
145
|
recommended: recommended === "fix_recover",
|
|
136
|
-
disabledReason: blocked ? undefined : "no blocker is active",
|
|
146
|
+
disabledReason: blocked || hasIdleResidue ? undefined : "no blocker is active",
|
|
137
147
|
},
|
|
138
148
|
{
|
|
139
149
|
id: "start_configure",
|
|
@@ -169,7 +169,7 @@ export function insertArtifact(a) {
|
|
|
169
169
|
export function insertMilestone(m) {
|
|
170
170
|
if (!getDbOrNull())
|
|
171
171
|
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
172
|
-
getDbOrNull().prepare(`INSERT OR IGNORE INTO milestones (
|
|
172
|
+
const result = getDbOrNull().prepare(`INSERT OR IGNORE INTO milestones (
|
|
173
173
|
id, title, status, depends_on, created_at,
|
|
174
174
|
vision, success_criteria, key_risks, proof_strategy,
|
|
175
175
|
verification_contract, verification_integration, verification_operational, verification_uat,
|
|
@@ -199,6 +199,7 @@ export function insertMilestone(m) {
|
|
|
199
199
|
":requirement_coverage": m.planning?.requirementCoverage ?? "",
|
|
200
200
|
":boundary_map_markdown": m.planning?.boundaryMapMarkdown ?? "",
|
|
201
201
|
});
|
|
202
|
+
return (result.changes ?? 0) > 0;
|
|
202
203
|
}
|
|
203
204
|
export function upsertMilestonePlanning(milestoneId, planning) {
|
|
204
205
|
if (!getDbOrNull())
|
|
@@ -44,7 +44,8 @@ import { buildCloseoutMenuActions, buildIdleMenuSummary, getPrimaryCloseoutRecom
|
|
|
44
44
|
import { buildRequirementsBacklogDiscussContext, countUnmappedActiveRequirements, showRequirementsBacklogReview, } from "./requirements-backlog.js";
|
|
45
45
|
import { selectAndApplyModel } from "./auto-model-selection.js";
|
|
46
46
|
import { DISCUSS_TOOLS_ALLOWLIST } from "./constants.js";
|
|
47
|
-
import {
|
|
47
|
+
import { supportsStructuredQuestions } from "./workflow-mcp.js";
|
|
48
|
+
import { getUnitWorkflowDispatchReadinessError } from "./tool-contract.js";
|
|
48
49
|
import { runPreparation, formatCodebaseBrief, formatPriorContextBrief, } from "./preparation.js";
|
|
49
50
|
import { verifyExpectedArtifact } from "./auto-recovery.js";
|
|
50
51
|
import { clearPendingGate, extractDepthVerificationMilestoneId, getPendingGate } from "./bootstrap/write-gate.js";
|
|
@@ -382,7 +383,8 @@ async function dispatchWorkflow(pi, note, customType = "gsd-run", ctx, unitType,
|
|
|
382
383
|
const projectRoot = resolveGuidedDispatchProjectRoot(resolvedOptions.basePath);
|
|
383
384
|
const loadPreferences = resolvedOptions.deps?.loadPreferences ?? loadEffectiveGSDPreferences;
|
|
384
385
|
const selectModel = resolvedOptions.deps?.selectModel ?? selectAndApplyModel;
|
|
385
|
-
const
|
|
386
|
+
const getDispatchReadinessError = resolvedOptions.deps?.getDispatchReadinessError
|
|
387
|
+
?? getUnitWorkflowDispatchReadinessError;
|
|
386
388
|
// Route through the dynamic routing pipeline (complexity classification,
|
|
387
389
|
// tier downgrade, fallback chains) — same path as auto-mode dispatches (#2958).
|
|
388
390
|
if (ctx && unitType) {
|
|
@@ -396,7 +398,8 @@ async function dispatchWorkflow(pi, note, customType = "gsd-run", ctx, unitType,
|
|
|
396
398
|
routing: result.routing,
|
|
397
399
|
});
|
|
398
400
|
}
|
|
399
|
-
const compatibilityError =
|
|
401
|
+
const compatibilityError = getDispatchReadinessError({
|
|
402
|
+
provider: result.appliedModel?.provider ?? ctx.model?.provider,
|
|
400
403
|
projectRoot,
|
|
401
404
|
surface: "guided flow",
|
|
402
405
|
unitType,
|