gsd-pi 2.78.1-dev.b6a389b66 → 2.78.1-dev.d8826a445
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/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/gsd/auto/phases.js +7 -2
- package/dist/resources/extensions/gsd/auto/session.js +3 -0
- package/dist/resources/extensions/gsd/auto-dispatch.js +3 -2
- package/dist/resources/extensions/gsd/auto-post-unit.js +7 -1
- package/dist/resources/extensions/gsd/auto-worktree.js +185 -40
- package/dist/resources/extensions/gsd/auto.js +62 -1
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +1 -1
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +17 -16
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +67 -55
- package/dist/resources/extensions/gsd/db-writer.js +96 -16
- package/dist/resources/extensions/gsd/delegation-policy.js +155 -0
- package/dist/resources/extensions/gsd/gsd-db.js +194 -0
- package/dist/resources/extensions/gsd/guided-flow-queue.js +1 -1
- package/dist/resources/extensions/gsd/guided-flow.js +117 -25
- package/dist/resources/extensions/gsd/metrics.js +287 -1
- package/dist/resources/extensions/gsd/paths.js +79 -8
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +4 -4
- package/dist/resources/extensions/gsd/prompts/execute-task.md +3 -3
- package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +8 -1
- package/dist/resources/extensions/gsd/prompts/guided-discuss-project.md +22 -7
- package/dist/resources/extensions/gsd/prompts/guided-discuss-requirements.md +6 -2
- package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -1
- package/dist/resources/extensions/gsd/templates/project.md +10 -0
- package/dist/resources/extensions/gsd/workflow-mcp.js +2 -2
- package/dist/resources/extensions/gsd/workspace.js +59 -0
- package/dist/resources/extensions/gsd/worktree-resolver.js +15 -2
- package/dist/resources/extensions/gsd/write-intercept.js +3 -3
- 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 +10 -10
- 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/required-server-files.json +1 -1
- 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 +10 -10
- 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/server.js +1 -1
- package/package.json +1 -1
- package/packages/mcp-server/README.md +2 -11
- package/packages/mcp-server/dist/remote-questions.d.ts +27 -0
- package/packages/mcp-server/dist/remote-questions.d.ts.map +1 -1
- package/packages/mcp-server/dist/remote-questions.js +28 -0
- package/packages/mcp-server/dist/remote-questions.js.map +1 -1
- package/packages/mcp-server/dist/server.d.ts +28 -0
- package/packages/mcp-server/dist/server.d.ts.map +1 -1
- package/packages/mcp-server/dist/server.js +94 -4
- package/packages/mcp-server/dist/server.js.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/src/mcp-server.test.ts +226 -0
- package/packages/mcp-server/src/remote-questions.test.ts +103 -0
- package/packages/mcp-server/src/remote-questions.ts +35 -0
- package/packages/mcp-server/src/server.ts +129 -6
- package/packages/mcp-server/src/workflow-tools.ts +1 -1
- package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
- package/src/resources/extensions/gsd/auto/phases.ts +8 -2
- package/src/resources/extensions/gsd/auto/session.ts +4 -0
- package/src/resources/extensions/gsd/auto-dispatch.ts +10 -2
- package/src/resources/extensions/gsd/auto-post-unit.ts +8 -1
- package/src/resources/extensions/gsd/auto-worktree.ts +225 -47
- package/src/resources/extensions/gsd/auto.ts +79 -1
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +1 -1
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +17 -17
- package/src/resources/extensions/gsd/bootstrap/tests/write-gate-basepath.test.ts +103 -0
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +80 -55
- package/src/resources/extensions/gsd/db-writer.ts +113 -17
- package/src/resources/extensions/gsd/delegation-policy.ts +197 -0
- package/src/resources/extensions/gsd/gsd-db.ts +184 -0
- package/src/resources/extensions/gsd/guided-flow-queue.ts +1 -1
- package/src/resources/extensions/gsd/guided-flow.ts +154 -25
- package/src/resources/extensions/gsd/metrics.ts +321 -1
- package/src/resources/extensions/gsd/paths.ts +67 -8
- package/src/resources/extensions/gsd/prompts/complete-slice.md +4 -4
- package/src/resources/extensions/gsd/prompts/execute-task.md +3 -3
- package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +8 -1
- package/src/resources/extensions/gsd/prompts/guided-discuss-project.md +22 -7
- package/src/resources/extensions/gsd/prompts/guided-discuss-requirements.md +6 -2
- package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -1
- package/src/resources/extensions/gsd/templates/project.md +10 -0
- package/src/resources/extensions/gsd/tests/auto-discuss-milestone-deadlock-4973.test.ts +14 -14
- package/src/resources/extensions/gsd/tests/auto-session-scope.test.ts +331 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-registry.test.ts +176 -0
- package/src/resources/extensions/gsd/tests/db-writer-path-containment.test.ts +152 -0
- package/src/resources/extensions/gsd/tests/db-writer-root-artifact.test.ts +221 -0
- package/src/resources/extensions/gsd/tests/db-writer-scope.test.ts +230 -0
- package/src/resources/extensions/gsd/tests/delegation-policy.test.ts +151 -0
- package/src/resources/extensions/gsd/tests/dispatch-backgroundable-annotation.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/draft-promotion.test.ts +3 -23
- package/src/resources/extensions/gsd/tests/gate-1b-orphan-discrimination.test.ts +193 -0
- package/src/resources/extensions/gsd/tests/gate-1b-recovery-bound-corrections.test.ts +246 -0
- package/src/resources/extensions/gsd/tests/gate-1b-recovery-bound.test.ts +218 -0
- package/src/resources/extensions/gsd/tests/gsd-db-failed-open-restore.test.ts +117 -0
- package/src/resources/extensions/gsd/tests/gsd-db-workspace-scope.test.ts +226 -0
- package/src/resources/extensions/gsd/tests/gsd-root-canonical.test.ts +66 -0
- package/src/resources/extensions/gsd/tests/gsd-root-home-guard.test.ts +68 -5
- package/src/resources/extensions/gsd/tests/guided-flow-prompt-consolidation.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/integration/workspace-collapse-integration.test.ts +371 -0
- package/src/resources/extensions/gsd/tests/metrics-atomic-merge.test.ts +222 -0
- package/src/resources/extensions/gsd/tests/metrics-lock-hardening.test.ts +400 -0
- package/src/resources/extensions/gsd/tests/metrics-lock-not-acquired.test.ts +141 -0
- package/src/resources/extensions/gsd/tests/metrics-lock-retry-sleep.test.ts +287 -0
- package/src/resources/extensions/gsd/tests/metrics-prune-cache-invalidation.test.ts +149 -0
- package/src/resources/extensions/gsd/tests/metrics-scope.test.ts +378 -0
- package/src/resources/extensions/gsd/tests/originalbase-path-comparison.test.ts +329 -0
- package/src/resources/extensions/gsd/tests/path-cache-decoupled.test.ts +209 -0
- package/src/resources/extensions/gsd/tests/path-normalization-unified.test.ts +175 -0
- package/src/resources/extensions/gsd/tests/paths-cache.test.ts +170 -0
- package/src/resources/extensions/gsd/tests/pending-autostart-scope.test.ts +120 -0
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +150 -7
- package/src/resources/extensions/gsd/tests/ready-phrase-no-files-4573.test.ts +74 -0
- package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +28 -16
- package/src/resources/extensions/gsd/tests/resume-missing-worktree-warning.test.ts +209 -0
- package/src/resources/extensions/gsd/tests/sync-layer-scope.test.ts +453 -0
- package/src/resources/extensions/gsd/tests/teardown-chdir-failure-clears-registry.test.ts +162 -0
- package/src/resources/extensions/gsd/tests/teardown-cleanup-parity.test.ts +102 -0
- package/src/resources/extensions/gsd/tests/teardown-failure-clears-registry.test.ts +186 -0
- package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/validator-scope-parity.test.ts +239 -0
- package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +9 -15
- package/src/resources/extensions/gsd/tests/workspace.test.ts +190 -0
- package/src/resources/extensions/gsd/tests/write-gate-predicates.test.ts +35 -35
- package/src/resources/extensions/gsd/tests/write-gate.test.ts +67 -52
- package/src/resources/extensions/gsd/tests/write-intercept.test.ts +1 -1
- package/src/resources/extensions/gsd/workflow-mcp.ts +2 -2
- package/src/resources/extensions/gsd/workspace.ts +95 -0
- package/src/resources/extensions/gsd/worktree-resolver.ts +16 -2
- package/src/resources/extensions/gsd/write-intercept.ts +3 -3
- /package/dist/web/standalone/.next/static/{HahrZrc_Xn4wumj0O1Ydp → AT5qi39nKXkdmQIOIoh0f}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{HahrZrc_Xn4wumj0O1Ydp → AT5qi39nKXkdmQIOIoh0f}/_ssgManifest.js +0 -0
|
@@ -10,7 +10,7 @@ import type { ExtensionAPI, ExtensionContext, ExtensionCommandContext } from "@g
|
|
|
10
10
|
import type { GSDState } from "./types.js";
|
|
11
11
|
import { showNextAction } from "../shared/tui.js";
|
|
12
12
|
import { loadFile, saveFile } from "./files.js";
|
|
13
|
-
import { isDbAvailable, getMilestoneSlices } from "./gsd-db.js";
|
|
13
|
+
import { isDbAvailable, getMilestone, getMilestoneSlices } from "./gsd-db.js";
|
|
14
14
|
import { parseRoadmapSlices } from "./roadmap-slices.js";
|
|
15
15
|
import { loadPrompt, inlineTemplate } from "./prompt-loader.js";
|
|
16
16
|
import {
|
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
buildPlanSlicePrompt,
|
|
22
22
|
buildSkillActivationBlock,
|
|
23
23
|
} from "./auto-prompts.js";
|
|
24
|
-
import { deriveState } from "./state.js";
|
|
24
|
+
import { deriveState, isGhostMilestone } from "./state.js";
|
|
25
25
|
import { invalidateAllCaches } from "./cache.js";
|
|
26
26
|
import { startAutoDetached } from "./auto.js";
|
|
27
27
|
import { clearLock } from "./crash-recovery.js";
|
|
@@ -36,7 +36,7 @@ import { gsdHome } from "./gsd-home.js";
|
|
|
36
36
|
import {
|
|
37
37
|
gsdRoot, milestonesDir, resolveMilestoneFile, resolveMilestonePath,
|
|
38
38
|
resolveSliceFile, resolveSlicePath, resolveGsdRootFile, relGsdRootFile,
|
|
39
|
-
relMilestoneFile, relSliceFile,
|
|
39
|
+
relMilestoneFile, relSliceFile, clearPathCache,
|
|
40
40
|
} from "./paths.js";
|
|
41
41
|
import { join } from "node:path";
|
|
42
42
|
import { readFileSync, existsSync, mkdirSync, readdirSync, rmSync, unlinkSync } from "node:fs";
|
|
@@ -69,6 +69,7 @@ import {
|
|
|
69
69
|
formatPriorContextBrief,
|
|
70
70
|
} from "./preparation.js";
|
|
71
71
|
import { verifyExpectedArtifact } from "./auto-recovery.js";
|
|
72
|
+
import { createWorkspace, scopeMilestone, type MilestoneScope } from "./workspace.js";
|
|
72
73
|
|
|
73
74
|
// ─── Re-exports (preserve public API for existing importers) ────────────────
|
|
74
75
|
export {
|
|
@@ -83,6 +84,46 @@ export {
|
|
|
83
84
|
} from "./guided-flow-queue.js";
|
|
84
85
|
import { logWarning } from "./workflow-logger.js";
|
|
85
86
|
|
|
87
|
+
// ─── Scope-based validator wrappers ──────────────────────────────────────────
|
|
88
|
+
// These thin wrappers accept a MilestoneScope so callers that already hold a
|
|
89
|
+
// pinned scope never have to re-derive (basePath, milestoneId) separately.
|
|
90
|
+
// The underlying implementations in auto-recovery.ts / auto-artifact-paths.ts /
|
|
91
|
+
// state.ts are unchanged — only the call surface in guided-flow.ts is migrated.
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Scope-based overload of verifyExpectedArtifact.
|
|
95
|
+
* Uses scope.workspace.projectRoot as the authoritative base path, making
|
|
96
|
+
* the check immune to cwd-drift and worktree-path divergence.
|
|
97
|
+
*/
|
|
98
|
+
export function verifyExpectedArtifactForScope(
|
|
99
|
+
scope: MilestoneScope,
|
|
100
|
+
unitType: string,
|
|
101
|
+
unitId: string,
|
|
102
|
+
): boolean {
|
|
103
|
+
return verifyExpectedArtifact(unitType, unitId, scope.workspace.projectRoot);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Scope-based overload of resolveExpectedArtifactPath.
|
|
108
|
+
* Returns the canonical absolute path (or null) using the scope's projectRoot.
|
|
109
|
+
*/
|
|
110
|
+
export function resolveExpectedArtifactPathForScope(
|
|
111
|
+
scope: MilestoneScope,
|
|
112
|
+
unitType: string,
|
|
113
|
+
unitId: string,
|
|
114
|
+
): string | null {
|
|
115
|
+
return resolveExpectedArtifactPath(unitType, unitId, scope.workspace.projectRoot);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Scope-based overload of isGhostMilestone.
|
|
120
|
+
* Binds basePath and milestoneId from the scope, ensuring path resolution
|
|
121
|
+
* uses the canonical project root regardless of the cwd at call time.
|
|
122
|
+
*/
|
|
123
|
+
export function isGhostMilestoneByScope(scope: MilestoneScope): boolean {
|
|
124
|
+
return isGhostMilestone(scope.workspace.projectRoot, scope.milestoneId);
|
|
125
|
+
}
|
|
126
|
+
|
|
86
127
|
function needsPlanV2Gate(state: GSDState): boolean {
|
|
87
128
|
return state.phase === "executing"
|
|
88
129
|
|| state.phase === "summarizing"
|
|
@@ -135,6 +176,13 @@ interface PendingAutoStartEntry {
|
|
|
135
176
|
// #4573: counter for how many times the LLM emitted the ready phrase
|
|
136
177
|
// without writing the required artifacts. Cleared on entry delete/recreate.
|
|
137
178
|
readyRejectCount?: number;
|
|
179
|
+
// C1: scope is pinned at reservation time so path resolution is immune to
|
|
180
|
+
// cwd-drift between discuss and checkAutoStartAfterDiscuss.
|
|
181
|
+
// TODO(C3): basePath becomes redundant once all consumers migrate to scope.
|
|
182
|
+
scope: MilestoneScope;
|
|
183
|
+
// H1: retry counter for Gate 1b plan-blocked recovery. Capped at
|
|
184
|
+
// MAX_PLAN_BLOCKED_RECOVERIES to prevent infinite recovery loops (#5012).
|
|
185
|
+
planBlockedRecoveryCount: number;
|
|
138
186
|
}
|
|
139
187
|
|
|
140
188
|
interface PendingDeepProjectSetupEntry {
|
|
@@ -152,6 +200,11 @@ interface PendingDeepProjectSetupEntry {
|
|
|
152
200
|
// phrase before giving up and asking the user to re-run /gsd.
|
|
153
201
|
const MAX_READY_REJECTS = 2;
|
|
154
202
|
|
|
203
|
+
// H1 (#5012): cap for Gate 1b plan-blocked recovery hints. After this many
|
|
204
|
+
// consecutive recovery attempts the loop is stopped and the user is directed
|
|
205
|
+
// to investigate manually.
|
|
206
|
+
const MAX_PLAN_BLOCKED_RECOVERIES = 3;
|
|
207
|
+
|
|
155
208
|
// #4573: matches the canonical ready phrase the discuss prompt asks the LLM
|
|
156
209
|
// to emit. Accepts any M-prefixed milestone ID (three digits + optional
|
|
157
210
|
// suffix) with optional trailing punctuation.
|
|
@@ -187,9 +240,9 @@ This stage is running inside the foreground \`/gsd new-project --deep\` intervie
|
|
|
187
240
|
/**
|
|
188
241
|
* Backward-compat bridge: returns a mutable reference to the entry matching
|
|
189
242
|
* basePath, or the sole entry when only one session exists.
|
|
190
|
-
*
|
|
243
|
+
* Exported for testing — internal use only in production code.
|
|
191
244
|
*/
|
|
192
|
-
function _getPendingAutoStart(basePath?: string): PendingAutoStartEntry | null {
|
|
245
|
+
export function _getPendingAutoStart(basePath?: string): PendingAutoStartEntry | null {
|
|
193
246
|
if (basePath) return pendingAutoStartMap.get(basePath) ?? null;
|
|
194
247
|
if (pendingAutoStartMap.size === 1) return pendingAutoStartMap.values().next().value!;
|
|
195
248
|
return null;
|
|
@@ -233,7 +286,9 @@ function clearEmptyLegacyDeepSetupPseudoMilestones(basePath: string, entries: st
|
|
|
233
286
|
* Exported for testing (#2985).
|
|
234
287
|
*/
|
|
235
288
|
export function setPendingAutoStart(basePath: string, entry: { basePath: string; milestoneId: string; ctx?: ExtensionCommandContext; pi?: ExtensionAPI; step?: boolean; createdAt?: number }): void {
|
|
236
|
-
|
|
289
|
+
const ws = createWorkspace(entry.basePath);
|
|
290
|
+
const scope = scopeMilestone(ws, entry.milestoneId);
|
|
291
|
+
pendingAutoStartMap.set(basePath, { createdAt: Date.now(), planBlockedRecoveryCount: 0, ...entry, scope } as PendingAutoStartEntry);
|
|
237
292
|
}
|
|
238
293
|
|
|
239
294
|
/**
|
|
@@ -343,6 +398,10 @@ export async function checkDeepProjectSetupAfterTurn(
|
|
|
343
398
|
if (!entry) return false;
|
|
344
399
|
|
|
345
400
|
if (entry.currentUnitType && entry.currentUnitId) {
|
|
401
|
+
// TODO(C-future): PendingDeepProjectSetupEntry does not carry a MilestoneScope
|
|
402
|
+
// because deep-project-setup units span non-milestone unit types (discuss-project,
|
|
403
|
+
// discuss-requirements, etc.). Migrate to verifyExpectedArtifactForScope once
|
|
404
|
+
// PendingDeepProjectSetupEntry is extended with a scope field.
|
|
346
405
|
const artifactReady = verifyExpectedArtifact(entry.currentUnitType, entry.currentUnitId, entry.basePath);
|
|
347
406
|
if (!artifactReady) {
|
|
348
407
|
return false;
|
|
@@ -426,17 +485,77 @@ export function checkAutoStartAfterDiscuss(): boolean {
|
|
|
426
485
|
|
|
427
486
|
// Gate 1: Primary milestone must have CONTEXT.md or ROADMAP.md
|
|
428
487
|
// The "discuss" path creates CONTEXT.md; the "plan" path creates ROADMAP.md.
|
|
429
|
-
|
|
430
|
-
const
|
|
488
|
+
// Use pinned scope (immune to cwd-drift) for existence checks.
|
|
489
|
+
const contextFilePath = entry.scope.contextFile();
|
|
490
|
+
const roadmapFilePath = entry.scope.roadmapFile();
|
|
491
|
+
const contextFile = existsSync(contextFilePath) ? contextFilePath : null;
|
|
492
|
+
const roadmapFile = existsSync(roadmapFilePath) ? roadmapFilePath : null;
|
|
431
493
|
if (!contextFile && !roadmapFile) return false; // neither artifact yet — keep waiting
|
|
432
494
|
|
|
495
|
+
// Gate 1b: Discriminate plan-blocked from discuss-incomplete when the DB row is queued.
|
|
496
|
+
// If the DB is available and the row is still "queued" but CONTEXT.md already exists on
|
|
497
|
+
// disk, the discuss phase completed but gsd_plan_milestone was hard-blocked by the
|
|
498
|
+
// depth-verification gate. Emit a recovery hint so the next agent turn can retry
|
|
499
|
+
// gsd_plan_milestone, then return false (keep blocking auto-start).
|
|
500
|
+
// If CONTEXT.md does not exist (discuss-incomplete), Gate 1 already blocked above.
|
|
501
|
+
if (isDbAvailable()) {
|
|
502
|
+
const dbRow = getMilestone(milestoneId);
|
|
503
|
+
if (dbRow?.status === "queued" && contextFile) {
|
|
504
|
+
if (entry.planBlockedRecoveryCount >= MAX_PLAN_BLOCKED_RECOVERIES) {
|
|
505
|
+
// H1: recovery loop cap reached — stop triggering new turns, escalate to user.
|
|
506
|
+
logWarning(
|
|
507
|
+
"guided",
|
|
508
|
+
`Gate 1b: milestone ${milestoneId} plan-blocked recovery limit reached ` +
|
|
509
|
+
`(${entry.planBlockedRecoveryCount}/${MAX_PLAN_BLOCKED_RECOVERIES}); escalating to user`,
|
|
510
|
+
);
|
|
511
|
+
ctx.ui.notify(
|
|
512
|
+
`Milestone ${milestoneId} plan_milestone has been blocked ${entry.planBlockedRecoveryCount} times. ` +
|
|
513
|
+
`Re-run /gsd to reset the recovery counter, or run /gsd-debug to diagnose without resetting.`,
|
|
514
|
+
"error",
|
|
515
|
+
);
|
|
516
|
+
return false;
|
|
517
|
+
}
|
|
518
|
+
logWarning(
|
|
519
|
+
"guided",
|
|
520
|
+
`Gate 1b: milestone ${milestoneId} queued with CONTEXT.md present — ` +
|
|
521
|
+
`plan_milestone was blocked; emitting recovery hint ` +
|
|
522
|
+
`(attempt ${entry.planBlockedRecoveryCount + 1}/${MAX_PLAN_BLOCKED_RECOVERIES})`,
|
|
523
|
+
);
|
|
524
|
+
ctx.ui.notify(
|
|
525
|
+
`Milestone ${milestoneId}: context file exists but milestone is still queued. ` +
|
|
526
|
+
`Retrying gsd_plan_milestone to complete the blocked planning step.`,
|
|
527
|
+
"warning",
|
|
528
|
+
);
|
|
529
|
+
try {
|
|
530
|
+
pi.sendMessage(
|
|
531
|
+
{
|
|
532
|
+
customType: "gsd-plan-milestone-blocked-recovery",
|
|
533
|
+
content:
|
|
534
|
+
`Milestone ${milestoneId} has ${contextFile} on disk but its DB row is still ` +
|
|
535
|
+
`"queued". The gsd_plan_milestone tool was previously blocked by the ` +
|
|
536
|
+
`depth-verification gate. Call gsd_plan_milestone now to complete the ` +
|
|
537
|
+
`planning phase.`,
|
|
538
|
+
display: false,
|
|
539
|
+
},
|
|
540
|
+
{ triggerTurn: true },
|
|
541
|
+
);
|
|
542
|
+
// Increment only after a successful dispatch so transient sendMessage
|
|
543
|
+
// failures do not consume recovery budget.
|
|
544
|
+
entry.planBlockedRecoveryCount += 1;
|
|
545
|
+
} catch (e) {
|
|
546
|
+
logWarning("guided", `Gate 1b recovery sendMessage failed: ${(e as Error).message}`);
|
|
547
|
+
}
|
|
548
|
+
return false;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
433
552
|
// Gate 2: STATE.md must exist — written as the last step in the discuss
|
|
434
553
|
// output phase. This prevents auto-start from firing during Phase 3
|
|
435
554
|
// (sequential readiness gates for remaining milestones) in multi-milestone
|
|
436
555
|
// discussions, where M001-CONTEXT.md exists but M002/M003 haven't been
|
|
437
556
|
// processed yet.
|
|
438
|
-
const
|
|
439
|
-
if (!
|
|
557
|
+
const stateFilePath = entry.scope.stateFile();
|
|
558
|
+
if (!existsSync(stateFilePath)) return false; // discussion not finalized yet
|
|
440
559
|
|
|
441
560
|
// Gate 3: Multi-milestone completeness warning
|
|
442
561
|
// Parse PROJECT.md for milestone sequence, warn if any are missing context.
|
|
@@ -469,7 +588,7 @@ export function checkAutoStartAfterDiscuss(): boolean {
|
|
|
469
588
|
// The LLM writes DISCUSSION-MANIFEST.json after each Phase 3 gate decision.
|
|
470
589
|
// When it exists, validate it before auto-starting. Project history alone is
|
|
471
590
|
// not a reliable signal for the current discussion mode.
|
|
472
|
-
const manifestPath = join(
|
|
591
|
+
const manifestPath = join(entry.scope.workspace.contract.projectGsd, "DISCUSSION-MANIFEST.json");
|
|
473
592
|
if (existsSync(manifestPath)) {
|
|
474
593
|
try {
|
|
475
594
|
const manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
|
|
@@ -588,6 +707,13 @@ export function maybeHandleReadyPhraseWithoutFiles(event: { messages: any[] }):
|
|
|
588
707
|
const text = extractAssistantText(lastMsg);
|
|
589
708
|
if (!READY_PHRASE_RE.test(text)) return false;
|
|
590
709
|
|
|
710
|
+
// Bust paths.ts cached dir listings before checking for fresh writes. The
|
|
711
|
+
// LLM's Write tool calls do not invalidate paths.ts caches, so a stale
|
|
712
|
+
// listing taken before the milestone dir or its CONTEXT/ROADMAP files
|
|
713
|
+
// existed would falsely report the artifacts as missing and trigger the
|
|
714
|
+
// 3-strike "ready without files" abort even though the writes succeeded.
|
|
715
|
+
clearPathCache();
|
|
716
|
+
|
|
591
717
|
// Gate: artifacts must still be missing — if they exist, the happy path
|
|
592
718
|
// already fired and we have nothing to do.
|
|
593
719
|
const contextFile = resolveMilestoneFile(basePath, milestoneId, "CONTEXT");
|
|
@@ -1097,7 +1223,7 @@ export async function showHeadlessMilestoneCreation(
|
|
|
1097
1223
|
const prompt = buildHeadlessDiscussPrompt(nextId, seedContext, basePath);
|
|
1098
1224
|
|
|
1099
1225
|
// Set pending auto start (auto-mode triggers on "Milestone X ready." via checkAutoStartAfterDiscuss)
|
|
1100
|
-
|
|
1226
|
+
setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: nextId });
|
|
1101
1227
|
|
|
1102
1228
|
// Dispatch as discuss-milestone. The LLM writes PROJECT.md, REQUIREMENTS.md,
|
|
1103
1229
|
// and CONTEXT.md, then calls gsd_plan_milestone — this is semantically the
|
|
@@ -1294,12 +1420,12 @@ export async function showDiscuss(
|
|
|
1294
1420
|
const seed = draftContent
|
|
1295
1421
|
? `${basePrompt}\n\n## Prior Discussion (Draft Seed)\n\n${draftContent}`
|
|
1296
1422
|
: basePrompt;
|
|
1297
|
-
|
|
1423
|
+
setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: mid, step: false });
|
|
1298
1424
|
await dispatchWorkflow(pi, seed, "gsd-discuss", ctx, "discuss-milestone");
|
|
1299
1425
|
} else if (choice === "discuss_fresh") {
|
|
1300
1426
|
const discussMilestoneTemplates = inlineTemplate("context", "Context");
|
|
1301
1427
|
const structuredQuestionsAvailable = getStructuredQuestionsAvailability(pi, ctx);
|
|
1302
|
-
|
|
1428
|
+
setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: mid, step: false });
|
|
1303
1429
|
await dispatchWorkflow(pi, loadPrompt("guided-discuss-milestone", {
|
|
1304
1430
|
milestoneId: mid, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
|
|
1305
1431
|
commitInstruction: buildDocsCommitInstruction(`docs(${mid}): milestone context from discuss`),
|
|
@@ -1311,7 +1437,7 @@ export async function showDiscuss(
|
|
|
1311
1437
|
const milestoneIds = findMilestoneIds(basePath);
|
|
1312
1438
|
const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
|
|
1313
1439
|
const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds, basePath);
|
|
1314
|
-
|
|
1440
|
+
setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: nextId, step: false });
|
|
1315
1441
|
await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId, `New milestone ${nextId}.`, basePath), "gsd-run", ctx, "discuss-milestone");
|
|
1316
1442
|
}
|
|
1317
1443
|
return;
|
|
@@ -1602,6 +1728,9 @@ function selfHealRuntimeRecords(basePath: string, ctx: ExtensionContext): { clea
|
|
|
1602
1728
|
for (const record of records) {
|
|
1603
1729
|
const { unitType, unitId, phase } = record;
|
|
1604
1730
|
// Clear records whose expected artifact already exists (completed but not cleaned up)
|
|
1731
|
+
// TODO(C-future): selfHealRuntimeRecords iterates across all unit types (not just milestone
|
|
1732
|
+
// units), so it cannot be converted to resolveExpectedArtifactPathForScope without
|
|
1733
|
+
// first establishing a per-record scope. Migrate once unit runtime records carry scope info.
|
|
1605
1734
|
const artifactPath = resolveExpectedArtifactPath(unitType, unitId, basePath);
|
|
1606
1735
|
if (artifactPath && existsSync(artifactPath)) {
|
|
1607
1736
|
clearUnitRuntimeRecord(basePath, unitType, unitId);
|
|
@@ -1716,7 +1845,7 @@ async function handleMilestoneActions(
|
|
|
1716
1845
|
const milestoneIds = findMilestoneIds(basePath);
|
|
1717
1846
|
const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
|
|
1718
1847
|
const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds, basePath);
|
|
1719
|
-
|
|
1848
|
+
setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode });
|
|
1720
1849
|
await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId,
|
|
1721
1850
|
`New milestone ${nextId}.`,
|
|
1722
1851
|
basePath
|
|
@@ -1944,7 +2073,7 @@ export async function showSmartEntry(
|
|
|
1944
2073
|
|
|
1945
2074
|
if (isFirst) {
|
|
1946
2075
|
// First ever — skip wizard, just ask directly
|
|
1947
|
-
|
|
2076
|
+
setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode });
|
|
1948
2077
|
await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId,
|
|
1949
2078
|
`New project, milestone ${nextId}. Do NOT read or explore .gsd/ — it's empty scaffolding.`,
|
|
1950
2079
|
basePath
|
|
@@ -1965,7 +2094,7 @@ export async function showSmartEntry(
|
|
|
1965
2094
|
});
|
|
1966
2095
|
|
|
1967
2096
|
if (choice === "new_milestone") {
|
|
1968
|
-
|
|
2097
|
+
setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode });
|
|
1969
2098
|
await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId,
|
|
1970
2099
|
`New milestone ${nextId}.`,
|
|
1971
2100
|
basePath
|
|
@@ -1979,7 +2108,7 @@ export async function showSmartEntry(
|
|
|
1979
2108
|
const milestoneTitle = state.activeMilestone.title;
|
|
1980
2109
|
|
|
1981
2110
|
if (planV2GateDecision === "recover-missing-context") {
|
|
1982
|
-
|
|
2111
|
+
setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId, step: stepMode });
|
|
1983
2112
|
await dispatchWorkflow(
|
|
1984
2113
|
pi,
|
|
1985
2114
|
await buildDiscussMilestonePrompt(
|
|
@@ -2021,7 +2150,7 @@ export async function showSmartEntry(
|
|
|
2021
2150
|
const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
|
|
2022
2151
|
const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds, basePath);
|
|
2023
2152
|
|
|
2024
|
-
|
|
2153
|
+
setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode });
|
|
2025
2154
|
await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId,
|
|
2026
2155
|
`New milestone ${nextId}.`,
|
|
2027
2156
|
basePath
|
|
@@ -2073,12 +2202,12 @@ export async function showSmartEntry(
|
|
|
2073
2202
|
const seed = draftContent
|
|
2074
2203
|
? `${basePrompt}\n\n## Prior Discussion (Draft Seed)\n\n${draftContent}`
|
|
2075
2204
|
: basePrompt;
|
|
2076
|
-
|
|
2205
|
+
setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId, step: stepMode });
|
|
2077
2206
|
await dispatchWorkflow(pi, seed, "gsd-discuss", ctx, "discuss-milestone");
|
|
2078
2207
|
} else if (choice === "discuss_fresh") {
|
|
2079
2208
|
const discussMilestoneTemplates = inlineTemplate("context", "Context");
|
|
2080
2209
|
const structuredQuestionsAvailable = getStructuredQuestionsAvailability(pi, ctx);
|
|
2081
|
-
|
|
2210
|
+
setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId, step: stepMode });
|
|
2082
2211
|
await dispatchWorkflow(pi, loadPrompt("guided-discuss-milestone", {
|
|
2083
2212
|
milestoneId, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
|
|
2084
2213
|
commitInstruction: buildDocsCommitInstruction(`docs(${milestoneId}): milestone context from discuss`),
|
|
@@ -2088,7 +2217,7 @@ export async function showSmartEntry(
|
|
|
2088
2217
|
const milestoneIds = findMilestoneIds(basePath);
|
|
2089
2218
|
const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
|
|
2090
2219
|
const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds, basePath);
|
|
2091
|
-
|
|
2220
|
+
setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode });
|
|
2092
2221
|
await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId,
|
|
2093
2222
|
`New milestone ${nextId}.`,
|
|
2094
2223
|
basePath
|
|
@@ -2153,7 +2282,7 @@ export async function showSmartEntry(
|
|
|
2153
2282
|
});
|
|
2154
2283
|
|
|
2155
2284
|
if (choice === "plan") {
|
|
2156
|
-
|
|
2285
|
+
setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId, step: stepMode });
|
|
2157
2286
|
await dispatchWorkflow(
|
|
2158
2287
|
pi,
|
|
2159
2288
|
await buildPlanMilestonePrompt(milestoneId, milestoneTitle, basePath),
|
|
@@ -2173,7 +2302,7 @@ export async function showSmartEntry(
|
|
|
2173
2302
|
const milestoneIds = findMilestoneIds(basePath);
|
|
2174
2303
|
const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
|
|
2175
2304
|
const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds, basePath);
|
|
2176
|
-
|
|
2305
|
+
setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode });
|
|
2177
2306
|
await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId,
|
|
2178
2307
|
`New milestone ${nextId}.`,
|
|
2179
2308
|
basePath
|