gsd-pi 2.79.0 → 2.80.0
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 +94 -47
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/gsd/auto/contracts.js +1 -0
- package/dist/resources/extensions/gsd/auto/orchestrator.js +146 -0
- package/dist/resources/extensions/gsd/auto/phases.js +61 -7
- package/dist/resources/extensions/gsd/auto/session.js +8 -0
- package/dist/resources/extensions/gsd/auto-artifact-paths.js +2 -2
- package/dist/resources/extensions/gsd/auto-dispatch.js +2 -0
- package/dist/resources/extensions/gsd/auto-prompts.js +52 -29
- package/dist/resources/extensions/gsd/auto-recovery.js +63 -55
- package/dist/resources/extensions/gsd/auto-runtime-state.js +4 -0
- package/dist/resources/extensions/gsd/auto-start.js +3 -2
- package/dist/resources/extensions/gsd/auto.js +159 -2
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +9 -1
- package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +2 -2
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +41 -45
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +8 -8
- package/dist/resources/extensions/gsd/commands/context.js +1 -1
- package/dist/resources/extensions/gsd/gsd-db.js +34 -1
- package/dist/resources/extensions/gsd/guided-flow.js +40 -0
- package/dist/resources/extensions/gsd/paths.js +5 -1
- package/dist/resources/extensions/gsd/post-execution-checks.js +25 -6
- package/dist/resources/extensions/gsd/preferences-types.js +20 -2
- package/dist/resources/extensions/gsd/preferences-validation.js +3 -3
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +82 -2
- package/dist/resources/extensions/gsd/unit-context-composer.js +32 -0
- package/dist/resources/extensions/gsd/unit-context-manifest.js +21 -0
- package/dist/resources/extensions/gsd/uok/audit.js +23 -9
- package/dist/resources/extensions/gsd/uok/contracts.js +69 -1
- package/dist/resources/extensions/gsd/uok/dispatch-envelope.js +3 -0
- package/dist/resources/extensions/gsd/uok/loop-adapter.js +48 -33
- package/dist/resources/extensions/gsd/uok/timeline.js +125 -0
- package/dist/resources/extensions/shared/gsd-phase-state.js +45 -3
- package/dist/resources/extensions/shared/interview-ui.js +15 -4
- 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 +9 -9
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- 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 +9 -9
- 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/package.json +1 -1
- package/packages/daemon/package.json +2 -2
- package/packages/mcp-server/dist/workflow-tools.d.ts +1 -1
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +53 -0
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/package.json +2 -2
- package/packages/mcp-server/src/workflow-tools.test.ts +129 -2
- package/packages/mcp-server/src/workflow-tools.ts +81 -0
- package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-ai/package.json +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-tui/package.json +1 -1
- package/packages/rpc-client/package.json +1 -1
- package/pkg/package.json +1 -1
- package/src/resources/extensions/gsd/auto/contracts.ts +87 -0
- package/src/resources/extensions/gsd/auto/loop-deps.ts +10 -3
- package/src/resources/extensions/gsd/auto/orchestrator.ts +161 -0
- package/src/resources/extensions/gsd/auto/phases.ts +88 -9
- package/src/resources/extensions/gsd/auto/session.ts +11 -0
- package/src/resources/extensions/gsd/auto-artifact-paths.ts +2 -2
- package/src/resources/extensions/gsd/auto-dispatch.ts +1 -0
- package/src/resources/extensions/gsd/auto-prompts.ts +106 -28
- package/src/resources/extensions/gsd/auto-recovery.ts +59 -53
- package/src/resources/extensions/gsd/auto-runtime-state.ts +7 -0
- package/src/resources/extensions/gsd/auto-start.ts +3 -2
- package/src/resources/extensions/gsd/auto.ts +167 -1
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +14 -1
- package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +2 -2
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +49 -46
- package/src/resources/extensions/gsd/bootstrap/tests/write-gate-shouldblock-basepath.test.ts +97 -0
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +8 -4
- package/src/resources/extensions/gsd/commands/context.ts +1 -1
- package/src/resources/extensions/gsd/gsd-db.ts +35 -1
- package/src/resources/extensions/gsd/guided-flow.ts +47 -0
- package/src/resources/extensions/gsd/interrupted-session.ts +1 -0
- package/src/resources/extensions/gsd/paths.ts +6 -1
- package/src/resources/extensions/gsd/post-execution-checks.ts +31 -6
- package/src/resources/extensions/gsd/preferences-types.ts +23 -4
- package/src/resources/extensions/gsd/preferences-validation.ts +3 -3
- package/src/resources/extensions/gsd/tests/auto-abort-pause-regression.test.ts +32 -0
- package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +353 -0
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +108 -1
- package/src/resources/extensions/gsd/tests/auto-runtime-state.test.ts +39 -0
- package/src/resources/extensions/gsd/tests/auto-session-encapsulation.test.ts +3 -0
- package/src/resources/extensions/gsd/tests/bootstrap-derive-state-db-open.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/check-auto-start-pending-gate.test.ts +203 -0
- package/src/resources/extensions/gsd/tests/check-auto-start-ready-guard.test.ts +148 -0
- package/src/resources/extensions/gsd/tests/current-directory-root-homedir-fallback.test.ts +63 -0
- package/src/resources/extensions/gsd/tests/deep-planning-mode-dispatch.test.ts +42 -0
- package/src/resources/extensions/gsd/tests/deep-project-auto-loop.test.ts +63 -2
- package/src/resources/extensions/gsd/tests/execute-summary-save-empty-project.test.ts +109 -0
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +95 -0
- package/src/resources/extensions/gsd/tests/guided-flow-prompt-consolidation.test.ts +14 -0
- package/src/resources/extensions/gsd/tests/integration/auto-recovery.test.ts +79 -0
- package/src/resources/extensions/gsd/tests/journal-integration.test.ts +134 -0
- package/src/resources/extensions/gsd/tests/parallel-skill-prompt-integration.test.ts +8 -0
- package/src/resources/extensions/gsd/tests/paused-session-via-db.test.ts +2 -0
- package/src/resources/extensions/gsd/tests/plan-slice.test.ts +27 -0
- package/src/resources/extensions/gsd/tests/post-execution-checks.test.ts +46 -0
- package/src/resources/extensions/gsd/tests/pre-exec-gate-loop.test.ts +3 -0
- package/src/resources/extensions/gsd/tests/register-hooks-compaction-checkpoint.test.ts +85 -0
- package/src/resources/extensions/gsd/tests/run-uat-composer.test.ts +2 -0
- package/src/resources/extensions/gsd/tests/subagent-model-dispatch.test.ts +59 -0
- package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +38 -0
- package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +32 -0
- package/src/resources/extensions/gsd/tests/uok-contracts.test.ts +109 -1
- package/src/resources/extensions/gsd/tests/uok-loop-adapter-writer.test.ts +98 -0
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +132 -3
- package/src/resources/extensions/gsd/tests/worktree-path-injection.test.ts +3 -0
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +84 -1
- package/src/resources/extensions/gsd/unit-context-composer.ts +49 -0
- package/src/resources/extensions/gsd/unit-context-manifest.ts +34 -0
- package/src/resources/extensions/gsd/uok/audit.ts +25 -9
- package/src/resources/extensions/gsd/uok/contracts.ts +105 -0
- package/src/resources/extensions/gsd/uok/dispatch-envelope.ts +4 -0
- package/src/resources/extensions/gsd/uok/loop-adapter.ts +60 -45
- package/src/resources/extensions/gsd/uok/timeline.ts +158 -0
- package/src/resources/extensions/shared/gsd-phase-state.ts +56 -3
- package/src/resources/extensions/shared/interview-ui.ts +18 -5
- package/src/resources/extensions/shared/tests/gsd-phase-state.test.ts +43 -1
- package/src/resources/extensions/shared/tests/interview-notes-loop.test.ts +41 -0
- /package/dist/web/standalone/.next/static/{J-CU-p_sp45CJHT3R9TJS → V-3Ehy4B24f9FCGiLPWIM}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{J-CU-p_sp45CJHT3R9TJS → V-3Ehy4B24f9FCGiLPWIM}/_ssgManifest.js +0 -0
|
@@ -10,6 +10,7 @@ import { hasVerdict, getUatType } from "./verdict-parser.js";
|
|
|
10
10
|
import { loadPrompt, inlineTemplate } from "./prompt-loader.js";
|
|
11
11
|
import { resolveMilestoneFile, resolveSliceFile, resolveSlicePath, resolveTasksDir, resolveTaskFiles, resolveTaskFile, relMilestoneFile, relSliceFile, relSlicePath, relMilestonePath, resolveGsdRootFile, relGsdRootFile, resolveRuntimeFile, } from "./paths.js";
|
|
12
12
|
import { resolveSkillDiscoveryMode, resolveInlineLevel, loadEffectiveGSDPreferences, resolveAllSkillReferences } from "./preferences.js";
|
|
13
|
+
import { isContextModeEnabled } from "./preferences-types.js";
|
|
13
14
|
import { parseRoadmap } from "./parsers-legacy.js";
|
|
14
15
|
import { getLoadedSkills } from "@gsd/pi-coding-agent";
|
|
15
16
|
import { join, basename } from "node:path";
|
|
@@ -19,7 +20,7 @@ import { getPendingGatesForTurn } from "./gsd-db.js";
|
|
|
19
20
|
import { assertGateCoverage, getGatesForTurn, } from "./gate-registry.js";
|
|
20
21
|
import { formatDecisionsCompact, formatRequirementsCompact } from "./structured-data-formatter.js";
|
|
21
22
|
import { readPhaseAnchor, formatAnchorForPrompt } from "./phase-anchor.js";
|
|
22
|
-
import { composeInlinedContext } from "./unit-context-composer.js";
|
|
23
|
+
import { composeContextModeInstructions, composeInlinedContext } from "./unit-context-composer.js";
|
|
23
24
|
import { logWarning } from "./workflow-logger.js";
|
|
24
25
|
import { inlineGraphSubgraph } from "./graph-context.js";
|
|
25
26
|
import { buildExtractionStepsBlock } from "./commands-extract-learnings.js";
|
|
@@ -71,6 +72,21 @@ function capPreamble(preamble) {
|
|
|
71
72
|
return preamble;
|
|
72
73
|
return truncateAtSectionBoundary(preamble, budget).content;
|
|
73
74
|
}
|
|
75
|
+
function renderContextModeForPrompt(unitType, base, renderMode = "standalone") {
|
|
76
|
+
const effectivePrefs = loadEffectiveGSDPreferences(base)?.preferences;
|
|
77
|
+
return composeContextModeInstructions(unitType, {
|
|
78
|
+
enabled: isContextModeEnabled(effectivePrefs),
|
|
79
|
+
renderMode,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
function prependContextModeToBlock(unitType, base, block, renderMode = "standalone") {
|
|
83
|
+
const contextMode = renderContextModeForPrompt(unitType, base, renderMode);
|
|
84
|
+
if (!contextMode)
|
|
85
|
+
return block;
|
|
86
|
+
if (!block.trim())
|
|
87
|
+
return contextMode;
|
|
88
|
+
return `${contextMode}\n\n${block}`;
|
|
89
|
+
}
|
|
74
90
|
// ─── Executor Constraints ─────────────────────────────────────────────────────
|
|
75
91
|
/**
|
|
76
92
|
* Format executor context constraints for injection into the plan-slice prompt.
|
|
@@ -1119,6 +1135,7 @@ export async function checkNeedsRunUat(base, mid, state, prefs) {
|
|
|
1119
1135
|
*/
|
|
1120
1136
|
export async function buildDiscussMilestonePrompt(mid, midTitle, base, structuredQuestionsAvailable = "false") {
|
|
1121
1137
|
const discussTemplates = inlineTemplate("context", "Context");
|
|
1138
|
+
const contextModeInstructions = renderContextModeForPrompt("discuss-milestone", base);
|
|
1122
1139
|
const basePrompt = loadPrompt("guided-discuss-milestone", {
|
|
1123
1140
|
workingDirectory: base,
|
|
1124
1141
|
milestoneId: mid,
|
|
@@ -1128,13 +1145,14 @@ export async function buildDiscussMilestonePrompt(mid, midTitle, base, structure
|
|
|
1128
1145
|
commitInstruction: "Do not commit planning artifacts — .gsd/ is managed externally.",
|
|
1129
1146
|
fastPathInstruction: "",
|
|
1130
1147
|
});
|
|
1148
|
+
const promptWithContextMode = prependContextModeToBlock("discuss-milestone", base, basePrompt);
|
|
1131
1149
|
// If a CONTEXT-DRAFT.md exists, append it as seed material
|
|
1132
1150
|
const draftPath = resolveMilestoneFile(base, mid, "CONTEXT-DRAFT");
|
|
1133
1151
|
const draftContent = draftPath ? await loadFile(draftPath) : null;
|
|
1134
1152
|
if (draftContent) {
|
|
1135
|
-
return `${
|
|
1153
|
+
return `${promptWithContextMode}\n\n## Prior Discussion (Draft Seed)\n\nThe following draft was captured from a prior multi-milestone discussion. Use it as seed material — the user has already provided this context. Start with a brief reflection on what the draft covers, then probe for any gaps or open questions before writing the full CONTEXT.md.\n\n${draftContent}`;
|
|
1136
1154
|
}
|
|
1137
|
-
return basePrompt;
|
|
1155
|
+
return contextModeInstructions ? promptWithContextMode : basePrompt;
|
|
1138
1156
|
}
|
|
1139
1157
|
/**
|
|
1140
1158
|
* Build a prompt for the workflow-preferences unit type (deep mode).
|
|
@@ -1143,10 +1161,10 @@ export async function buildDiscussMilestonePrompt(mid, midTitle, base, structure
|
|
|
1143
1161
|
* in deep-mode bootstrap before discuss-project.
|
|
1144
1162
|
*/
|
|
1145
1163
|
export async function buildWorkflowPreferencesPrompt(base, structuredQuestionsAvailable = "false") {
|
|
1146
|
-
return loadPrompt("guided-workflow-preferences", {
|
|
1164
|
+
return prependContextModeToBlock("workflow-preferences", base, loadPrompt("guided-workflow-preferences", {
|
|
1147
1165
|
workingDirectory: base,
|
|
1148
1166
|
structuredQuestionsAvailable,
|
|
1149
|
-
});
|
|
1167
|
+
}));
|
|
1150
1168
|
}
|
|
1151
1169
|
/**
|
|
1152
1170
|
* Build a prompt for the research-project (parallel) unit type (deep mode).
|
|
@@ -1156,10 +1174,10 @@ export async function buildWorkflowPreferencesPrompt(base, structuredQuestionsAv
|
|
|
1156
1174
|
* are missing. Skipped entirely if user picked "skip".
|
|
1157
1175
|
*/
|
|
1158
1176
|
export async function buildResearchProjectPrompt(base, structuredQuestionsAvailable = "false") {
|
|
1159
|
-
return loadPrompt("guided-research-project", {
|
|
1177
|
+
return prependContextModeToBlock("research-project", base, loadPrompt("guided-research-project", {
|
|
1160
1178
|
workingDirectory: base,
|
|
1161
1179
|
structuredQuestionsAvailable,
|
|
1162
|
-
});
|
|
1180
|
+
}));
|
|
1163
1181
|
}
|
|
1164
1182
|
/**
|
|
1165
1183
|
* Build a prompt for the research-decision unit type (deep mode).
|
|
@@ -1168,10 +1186,10 @@ export async function buildResearchProjectPrompt(base, structuredQuestionsAvaila
|
|
|
1168
1186
|
* and before research-project-parallel.
|
|
1169
1187
|
*/
|
|
1170
1188
|
export async function buildResearchDecisionPrompt(base, structuredQuestionsAvailable = "false") {
|
|
1171
|
-
return loadPrompt("guided-research-decision", {
|
|
1189
|
+
return prependContextModeToBlock("research-decision", base, loadPrompt("guided-research-decision", {
|
|
1172
1190
|
workingDirectory: base,
|
|
1173
1191
|
structuredQuestionsAvailable,
|
|
1174
|
-
});
|
|
1192
|
+
}));
|
|
1175
1193
|
}
|
|
1176
1194
|
/**
|
|
1177
1195
|
* Build a prompt for the discuss-project unit type (deep mode).
|
|
@@ -1181,12 +1199,12 @@ export async function buildResearchDecisionPrompt(base, structuredQuestionsAvail
|
|
|
1181
1199
|
*/
|
|
1182
1200
|
export async function buildDiscussProjectPrompt(base, structuredQuestionsAvailable = "false") {
|
|
1183
1201
|
const inlinedTemplates = inlineTemplate("project", "Project");
|
|
1184
|
-
return loadPrompt("guided-discuss-project", {
|
|
1202
|
+
return prependContextModeToBlock("discuss-project", base, loadPrompt("guided-discuss-project", {
|
|
1185
1203
|
workingDirectory: base,
|
|
1186
1204
|
inlinedTemplates,
|
|
1187
1205
|
structuredQuestionsAvailable,
|
|
1188
1206
|
commitInstruction: "Do not commit planning artifacts — .gsd/ is managed externally.",
|
|
1189
|
-
});
|
|
1207
|
+
}));
|
|
1190
1208
|
}
|
|
1191
1209
|
/**
|
|
1192
1210
|
* Build a prompt for the discuss-requirements unit type (deep mode).
|
|
@@ -1196,12 +1214,12 @@ export async function buildDiscussProjectPrompt(base, structuredQuestionsAvailab
|
|
|
1196
1214
|
*/
|
|
1197
1215
|
export async function buildDiscussRequirementsPrompt(base, structuredQuestionsAvailable = "false") {
|
|
1198
1216
|
const inlinedTemplates = inlineTemplate("requirements", "Requirements");
|
|
1199
|
-
return loadPrompt("guided-discuss-requirements", {
|
|
1217
|
+
return prependContextModeToBlock("discuss-requirements", base, loadPrompt("guided-discuss-requirements", {
|
|
1200
1218
|
workingDirectory: base,
|
|
1201
1219
|
inlinedTemplates,
|
|
1202
1220
|
structuredQuestionsAvailable,
|
|
1203
1221
|
commitInstruction: "Do not commit planning artifacts — .gsd/ is managed externally.",
|
|
1204
|
-
});
|
|
1222
|
+
}));
|
|
1205
1223
|
}
|
|
1206
1224
|
export async function buildResearchMilestonePrompt(mid, midTitle, base) {
|
|
1207
1225
|
// #4782 phase 3: research-milestone migrated through the composer.
|
|
@@ -1254,7 +1272,7 @@ export async function buildResearchMilestonePrompt(mid, midTitle, base) {
|
|
|
1254
1272
|
if (knowledgeInlineRM)
|
|
1255
1273
|
parts.push(knowledgeInlineRM);
|
|
1256
1274
|
}
|
|
1257
|
-
const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${parts.join("\n\n---\n\n")}`);
|
|
1275
|
+
const inlinedContext = prependContextModeToBlock("research-milestone", base, capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${parts.join("\n\n---\n\n")}`));
|
|
1258
1276
|
const outputRelPath = relMilestoneFile(base, mid, "RESEARCH");
|
|
1259
1277
|
return loadPrompt("research-milestone", {
|
|
1260
1278
|
workingDirectory: base,
|
|
@@ -1324,7 +1342,7 @@ export async function buildPlanMilestonePrompt(mid, midTitle, base, level) {
|
|
|
1324
1342
|
inlined.push(inlineTemplate("plan", "Slice Plan"));
|
|
1325
1343
|
inlined.push(inlineTemplate("task-plan", "Task Plan"));
|
|
1326
1344
|
}
|
|
1327
|
-
const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
|
|
1345
|
+
const inlinedContext = prependContextModeToBlock("plan-milestone", base, capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`));
|
|
1328
1346
|
const outputRelPath = relMilestoneFile(base, mid, "ROADMAP");
|
|
1329
1347
|
const researchOutputPath = join(base, relMilestoneFile(base, mid, "RESEARCH"));
|
|
1330
1348
|
const secretsOutputPath = join(base, relMilestoneFile(base, mid, "SECRETS"));
|
|
@@ -1349,7 +1367,7 @@ export async function buildPlanMilestonePrompt(mid, midTitle, base, level) {
|
|
|
1349
1367
|
...buildSkillDiscoveryVars(),
|
|
1350
1368
|
});
|
|
1351
1369
|
}
|
|
1352
|
-
export async function buildResearchSlicePrompt(mid, _midTitle, sid, sTitle, base) {
|
|
1370
|
+
export async function buildResearchSlicePrompt(mid, _midTitle, sid, sTitle, base, options) {
|
|
1353
1371
|
const roadmapPath = resolveMilestoneFile(base, mid, "ROADMAP");
|
|
1354
1372
|
const roadmapRel = relMilestoneFile(base, mid, "ROADMAP");
|
|
1355
1373
|
const contextPath = resolveMilestoneFile(base, mid, "CONTEXT");
|
|
@@ -1400,7 +1418,7 @@ export async function buildResearchSlicePrompt(mid, _midTitle, sid, sTitle, base
|
|
|
1400
1418
|
const overridesInline = formatOverridesSection(activeOverrides);
|
|
1401
1419
|
if (overridesInline)
|
|
1402
1420
|
inlined.unshift(overridesInline);
|
|
1403
|
-
const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
|
|
1421
|
+
const inlinedContext = prependContextModeToBlock("research-slice", base, capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`), options?.contextModeRenderMode);
|
|
1404
1422
|
const outputRelPath = relSliceFile(base, mid, sid, "RESEARCH");
|
|
1405
1423
|
return loadPrompt("research-slice", {
|
|
1406
1424
|
workingDirectory: base,
|
|
@@ -1483,7 +1501,7 @@ async function renderSlicePrompt(options) {
|
|
|
1483
1501
|
const overridesInline = formatOverridesSection(await loadActiveOverrides(base));
|
|
1484
1502
|
if (overridesInline)
|
|
1485
1503
|
inlined.unshift(overridesInline);
|
|
1486
|
-
const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
|
|
1504
|
+
const inlinedContext = prependContextModeToBlock(promptTemplate, base, capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`), options.contextModeRenderMode);
|
|
1487
1505
|
const executorContextConstraints = formatExecutorConstraints(sessionContextWindow, modelRegistry, sessionProvider);
|
|
1488
1506
|
const outputRelPath = relSliceFile(base, mid, sid, "PLAN");
|
|
1489
1507
|
const commitInstruction = "Do not commit — .gsd/ planning docs are managed externally and not tracked in git.";
|
|
@@ -1686,6 +1704,7 @@ export async function buildExecuteTaskPrompt(mid, sid, sTitle, tid, tTitle, base
|
|
|
1686
1704
|
const etPending = getPendingGatesForTurn(mid, sid, "execute-task", tid);
|
|
1687
1705
|
assertGateCoverage(etPending, "execute-task", { requireAll: false });
|
|
1688
1706
|
const gatesToClose = renderGatesToCloseBlock(getGatesForTurn("execute-task"), { pending: new Set(etPending.map((g) => g.gate_id)), allowOmit: true });
|
|
1707
|
+
phaseAnchorSection = prependContextModeToBlock("execute-task", base, phaseAnchorSection, opts.contextModeRenderMode);
|
|
1689
1708
|
return loadPrompt("execute-task", {
|
|
1690
1709
|
overridesSection,
|
|
1691
1710
|
runtimeContext,
|
|
@@ -1713,6 +1732,7 @@ export async function buildExecuteTaskPrompt(mid, sid, sTitle, tid, tTitle, base
|
|
|
1713
1732
|
taskTitle: tTitle,
|
|
1714
1733
|
taskPlanContent,
|
|
1715
1734
|
extraContext: [taskPlanInline, slicePlanExcerpt, finalCarryForward, resumeSection],
|
|
1735
|
+
unitType: "execute-task",
|
|
1716
1736
|
}),
|
|
1717
1737
|
});
|
|
1718
1738
|
}
|
|
@@ -1803,7 +1823,7 @@ export async function buildCompleteSlicePrompt(mid, midTitle, sid, sTitle, base,
|
|
|
1803
1823
|
const finalBody = completeOverridesInline
|
|
1804
1824
|
? `${completeOverridesInline}\n\n---\n\n${body}`
|
|
1805
1825
|
: body;
|
|
1806
|
-
const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${finalBody}`);
|
|
1826
|
+
const inlinedContext = prependContextModeToBlock("complete-slice", base, capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${finalBody}`));
|
|
1807
1827
|
const roadmapRel = relMilestoneFile(base, mid, "ROADMAP");
|
|
1808
1828
|
const sliceRel = relSlicePath(base, mid, sid);
|
|
1809
1829
|
const sliceSummaryPath = join(base, `${sliceRel}/${sid}-SUMMARY.md`);
|
|
@@ -1894,7 +1914,7 @@ export async function buildCompleteMilestonePrompt(mid, midTitle, base, level) {
|
|
|
1894
1914
|
if (contextInline)
|
|
1895
1915
|
inlined.push(contextInline);
|
|
1896
1916
|
inlined.push(inlineTemplate("milestone-summary", "Milestone Summary"));
|
|
1897
|
-
const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
|
|
1917
|
+
const inlinedContext = prependContextModeToBlock("complete-milestone", base, capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`));
|
|
1898
1918
|
const milestoneSummaryPath = join(base, `${relMilestonePath(base, mid)}/${mid}-SUMMARY.md`);
|
|
1899
1919
|
const learningsRelPath = join(relMilestonePath(base, mid), `${mid}-LEARNINGS.md`);
|
|
1900
1920
|
const learningsAbsPath = join(base, learningsRelPath);
|
|
@@ -2034,7 +2054,7 @@ export async function buildValidateMilestonePrompt(mid, midTitle, base, level) {
|
|
|
2034
2054
|
const contextInline = await inlineFileOptional(contextPath, contextRel, "Milestone Context");
|
|
2035
2055
|
if (contextInline)
|
|
2036
2056
|
inlined.push(contextInline);
|
|
2037
|
-
const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
|
|
2057
|
+
const inlinedContext = prependContextModeToBlock("validate-milestone", base, capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`));
|
|
2038
2058
|
const validationOutputPath = join(base, `${relMilestonePath(base, mid)}/${mid}-VALIDATION.md`);
|
|
2039
2059
|
const roadmapOutputPath = `${relMilestonePath(base, mid)}/${mid}-ROADMAP.md`;
|
|
2040
2060
|
// Every milestone validation turn owns MV01–MV04 unconditionally: the
|
|
@@ -2104,7 +2124,7 @@ export async function buildReplanSlicePrompt(mid, midTitle, sid, sTitle, base) {
|
|
|
2104
2124
|
const replanOverridesInline = formatOverridesSection(replanActiveOverrides);
|
|
2105
2125
|
if (replanOverridesInline)
|
|
2106
2126
|
inlined.unshift(replanOverridesInline);
|
|
2107
|
-
const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
|
|
2127
|
+
const inlinedContext = prependContextModeToBlock("replan-slice", base, capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`));
|
|
2108
2128
|
const replanPath = join(base, `${relSlicePath(base, mid, sid)}/${sid}-REPLAN.md`);
|
|
2109
2129
|
// Build capture context for replan prompt (captures that triggered this replan)
|
|
2110
2130
|
let captureContext = "(none)";
|
|
@@ -2171,7 +2191,7 @@ export async function buildRunUatPrompt(mid, sliceId, uatPath, uatContent, base)
|
|
|
2171
2191
|
}
|
|
2172
2192
|
};
|
|
2173
2193
|
const composed = await composeInlinedContext("run-uat", resolveArtifact);
|
|
2174
|
-
const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${composed}`);
|
|
2194
|
+
const inlinedContext = prependContextModeToBlock("run-uat", base, capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${composed}`));
|
|
2175
2195
|
const uatResultPath = join(base, relSliceFile(base, mid, sliceId, "ASSESSMENT"));
|
|
2176
2196
|
const uatType = getUatType(uatContent);
|
|
2177
2197
|
return loadPrompt("run-uat", {
|
|
@@ -2242,7 +2262,7 @@ export async function buildReassessRoadmapPrompt(mid, midTitle, completedSliceId
|
|
|
2242
2262
|
const knowledgeInlineRA = await inlineKnowledgeBudgeted(base, extractKeywords(midTitle));
|
|
2243
2263
|
if (knowledgeInlineRA)
|
|
2244
2264
|
parts.push(knowledgeInlineRA);
|
|
2245
|
-
const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${parts.join("\n\n---\n\n")}`);
|
|
2265
|
+
const inlinedContext = prependContextModeToBlock("reassess-roadmap", base, capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${parts.join("\n\n---\n\n")}`));
|
|
2246
2266
|
const assessmentPath = join(base, relSliceFile(base, mid, completedSliceId, "ASSESSMENT"));
|
|
2247
2267
|
// Build deferred captures context for reassess prompt
|
|
2248
2268
|
let deferredCaptures = "(none)";
|
|
@@ -2313,6 +2333,7 @@ export async function buildReactiveExecutePrompt(mid, midTitle, sid, sTitle, rea
|
|
|
2313
2333
|
sessionContextWindow: opts?.sessionContextWindow,
|
|
2314
2334
|
modelRegistry: opts?.modelRegistry,
|
|
2315
2335
|
sessionProvider: opts?.sessionProvider,
|
|
2336
|
+
contextModeRenderMode: "nested",
|
|
2316
2337
|
});
|
|
2317
2338
|
const modelSuffix = subagentModel ? ` with model: "${subagentModel}"` : "";
|
|
2318
2339
|
subagentSections.push([
|
|
@@ -2332,7 +2353,7 @@ export async function buildReactiveExecutePrompt(mid, midTitle, sid, sTitle, rea
|
|
|
2332
2353
|
milestoneTitle: midTitle,
|
|
2333
2354
|
sliceId: sid,
|
|
2334
2355
|
sliceTitle: sTitle,
|
|
2335
|
-
graphContext,
|
|
2356
|
+
graphContext: prependContextModeToBlock("reactive-execute", base, graphContext),
|
|
2336
2357
|
readyTaskCount: String(readyTaskIds.length),
|
|
2337
2358
|
readyTaskList: readyTaskListLines.join("\n"),
|
|
2338
2359
|
subagentPrompts: subagentSections.join("\n\n---\n\n"),
|
|
@@ -2382,7 +2403,7 @@ export async function buildParallelResearchSlicesPrompt(mid, midTitle, slices, b
|
|
|
2382
2403
|
const subagentSections = [];
|
|
2383
2404
|
const modelSuffix = subagentModel ? ` with model: "${subagentModel}"` : "";
|
|
2384
2405
|
for (const slice of slices) {
|
|
2385
|
-
const slicePrompt = await buildResearchSlicePrompt(mid, midTitle, slice.id, slice.title, basePath);
|
|
2406
|
+
const slicePrompt = await buildResearchSlicePrompt(mid, midTitle, slice.id, slice.title, basePath, { contextModeRenderMode: "nested" });
|
|
2386
2407
|
subagentSections.push([
|
|
2387
2408
|
`### ${slice.id}: ${slice.title}`,
|
|
2388
2409
|
"",
|
|
@@ -2426,6 +2447,8 @@ export async function buildGateEvaluatePrompt(mid, midTitle, sid, sTitle, base,
|
|
|
2426
2447
|
for (const def of gateDefs) {
|
|
2427
2448
|
gateListLines.push(`- **${def.id}**: ${def.question}`);
|
|
2428
2449
|
const subPrompt = [
|
|
2450
|
+
renderContextModeForPrompt("gate-evaluate", base, "nested"),
|
|
2451
|
+
"",
|
|
2429
2452
|
`You are evaluating quality gate **${def.id}** for slice ${sid} (${sTitle}).`,
|
|
2430
2453
|
"",
|
|
2431
2454
|
`**Working directory:** \`${normalizedBase}\`. All file reads, writes, and shell commands MUST operate relative to this directory. Do NOT \`cd\` to any other directory.`,
|
|
@@ -2466,7 +2489,7 @@ export async function buildGateEvaluatePrompt(mid, midTitle, sid, sTitle, base,
|
|
|
2466
2489
|
milestoneTitle: midTitle,
|
|
2467
2490
|
sliceId: sid,
|
|
2468
2491
|
sliceTitle: sTitle,
|
|
2469
|
-
slicePlanContent: planContent,
|
|
2492
|
+
slicePlanContent: prependContextModeToBlock("gate-evaluate", base, planContent),
|
|
2470
2493
|
gateCount: String(pending.length),
|
|
2471
2494
|
gateList: gateListLines.join("\n"),
|
|
2472
2495
|
subagentPrompts: subagentSections.join("\n\n---\n\n"),
|
|
@@ -2536,7 +2559,7 @@ export async function buildRewriteDocsPrompt(mid, midTitle, activeSlice, base, o
|
|
|
2536
2559
|
`**During:** ${o.appliedAt}`,
|
|
2537
2560
|
].join("\n")).join("\n\n");
|
|
2538
2561
|
const documentList = docList.length > 0 ? docList.join("\n") : "- No active plan documents found.";
|
|
2539
|
-
return loadPrompt("rewrite-docs", {
|
|
2562
|
+
return prependContextModeToBlock("rewrite-docs", base, loadPrompt("rewrite-docs", {
|
|
2540
2563
|
workingDirectory: base,
|
|
2541
2564
|
milestoneId: mid,
|
|
2542
2565
|
milestoneTitle: midTitle,
|
|
@@ -2545,5 +2568,5 @@ export async function buildRewriteDocsPrompt(mid, midTitle, activeSlice, base, o
|
|
|
2545
2568
|
overrideContent,
|
|
2546
2569
|
documentList,
|
|
2547
2570
|
overridesPath: relGsdRootFile("OVERRIDES"),
|
|
2548
|
-
});
|
|
2571
|
+
}));
|
|
2549
2572
|
}
|
|
@@ -12,7 +12,7 @@ import { appendEvent } from "./workflow-events.js";
|
|
|
12
12
|
import { atomicWriteSync } from "./atomic-write.js";
|
|
13
13
|
import { clearParseCache } from "./files.js";
|
|
14
14
|
import { parseRoadmap as parseLegacyRoadmap, parsePlan as parseLegacyPlan } from "./parsers-legacy.js";
|
|
15
|
-
import { isDbAvailable, getTask, getSlice, getSliceTasks, getPendingGates, updateTaskStatus, updateSliceStatus, insertSlice, getMilestone } from "./gsd-db.js";
|
|
15
|
+
import { isDbAvailable, getTask, getSlice, getSliceTasks, getPendingGates, updateTaskStatus, updateSliceStatus, insertSlice, getMilestone, refreshOpenDatabaseFromDisk } from "./gsd-db.js";
|
|
16
16
|
import { isValidationTerminal } from "./state.js";
|
|
17
17
|
import { getErrorMessage } from "./error-utils.js";
|
|
18
18
|
import { logWarning, logError } from "./workflow-logger.js";
|
|
@@ -257,7 +257,7 @@ function scanGsdTaggedCommits(basePath, milestoneId, gitArgs) {
|
|
|
257
257
|
if (!commitMessageHasGsdTrailer(message))
|
|
258
258
|
continue;
|
|
259
259
|
const commitFiles = getChangedFilesForCommit(basePath, hash);
|
|
260
|
-
if (!commitMatchesMilestone(message, milestoneId, commitFiles))
|
|
260
|
+
if (!commitMatchesMilestone(basePath, message, milestoneId, commitFiles))
|
|
261
261
|
continue;
|
|
262
262
|
matched = true;
|
|
263
263
|
for (const file of commitFiles) {
|
|
@@ -278,22 +278,37 @@ function getChangedFilesForCommit(basePath, hash) {
|
|
|
278
278
|
function commitMessageHasGsdTrailer(message) {
|
|
279
279
|
return /^GSD-(?:Task|Unit):\s*\S+/m.test(message);
|
|
280
280
|
}
|
|
281
|
-
function commitMatchesMilestone(message, milestoneId, files) {
|
|
281
|
+
function commitMatchesMilestone(basePath, message, milestoneId, files) {
|
|
282
282
|
if (commitTrailerStartsWithMilestone(message, milestoneId))
|
|
283
283
|
return true;
|
|
284
284
|
// Meaningful execute-task commits currently store task scope as Sxx/Tyy
|
|
285
285
|
// rather than Mxx/Sxx/Tyy. Bind those commits back to the milestone when
|
|
286
286
|
// either the commit touched this milestone's artifacts, or — for projects
|
|
287
287
|
// where .gsd/ is gitignored/external (#5033) — the message explicitly
|
|
288
|
-
// names the milestone.
|
|
288
|
+
// names the milestone or local GSD state proves the task belongs here.
|
|
289
289
|
if (/^GSD-Task:\s*S[^/\s]+\/T\S+/m.test(message)) {
|
|
290
290
|
if (files.some((file) => isMilestoneArtifactPath(file, milestoneId)))
|
|
291
291
|
return true;
|
|
292
292
|
if (commitMessageMentionsMilestone(message, milestoneId))
|
|
293
293
|
return true;
|
|
294
|
+
if (commitTaskTrailerBelongsToMilestone(basePath, message, milestoneId))
|
|
295
|
+
return true;
|
|
294
296
|
}
|
|
295
297
|
return false;
|
|
296
298
|
}
|
|
299
|
+
function commitTaskTrailerBelongsToMilestone(basePath, message, milestoneId) {
|
|
300
|
+
const match = message.match(/^GSD-Task:\s*(S[^/\s]+)\/(T[^\s]+)/m);
|
|
301
|
+
if (!match)
|
|
302
|
+
return false;
|
|
303
|
+
const [, sliceId, taskId] = match;
|
|
304
|
+
if (getTask(milestoneId, sliceId, taskId))
|
|
305
|
+
return true;
|
|
306
|
+
const tasksDir = resolveTasksDir(basePath, milestoneId, sliceId);
|
|
307
|
+
if (!tasksDir)
|
|
308
|
+
return false;
|
|
309
|
+
return existsSync(join(tasksDir, `${taskId}-PLAN.md`))
|
|
310
|
+
|| existsSync(join(tasksDir, `${taskId}-SUMMARY.md`));
|
|
311
|
+
}
|
|
297
312
|
function commitMessageMentionsMilestone(message, milestoneId) {
|
|
298
313
|
if (!MILESTONE_ID_RE.test(milestoneId))
|
|
299
314
|
return false;
|
|
@@ -495,66 +510,32 @@ export function verifyExpectedArtifact(unitType, unitId, base) {
|
|
|
495
510
|
return false;
|
|
496
511
|
}
|
|
497
512
|
}
|
|
498
|
-
// plan-slice
|
|
499
|
-
//
|
|
500
|
-
//
|
|
501
|
-
// unit gets skipped — but deriveState still returns phase:"planning" because the
|
|
502
|
-
// plan has no tasks, creating an infinite skip loop (#699).
|
|
503
|
-
if (unitType === "plan-slice") {
|
|
504
|
-
const planContent = readFileSync(absPath, "utf-8");
|
|
505
|
-
// Accept checkbox-style (- [x] **T01: ...) or heading-style (### T01 -- / ### T01: / ### T01 —)
|
|
506
|
-
const hasCheckboxTask = /^- \[[xX ]\] \*\*T\d+:/m.test(planContent);
|
|
507
|
-
const hasHeadingTask = /^#{2,4}\s+T\d+\s*(?:--|—|:)/m.test(planContent);
|
|
508
|
-
if (!hasCheckboxTask && !hasHeadingTask) {
|
|
509
|
-
logWarning("recovery", `verify-fail ${unitType} ${unitId}: plan has no task checkbox/heading (len=${planContent.length}) at ${absPath}`);
|
|
510
|
-
return false;
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
// execute-task: DB status is authoritative. Fall back to checked-checkbox
|
|
514
|
-
// detection when the DB is unavailable (unmigrated projects), or when the
|
|
515
|
-
// disk artifacts already reflect completion but the DB replay is one beat
|
|
516
|
-
// behind the completion write.
|
|
517
|
-
if (unitType === "execute-task") {
|
|
518
|
-
const { milestone: mid, slice: sid, task: tid } = parseUnitId(unitId);
|
|
519
|
-
if (mid && sid && tid) {
|
|
520
|
-
const dbTask = getTask(mid, sid, tid);
|
|
521
|
-
if (dbTask) {
|
|
522
|
-
if (dbTask.status !== "complete" && dbTask.status !== "done" && !hasCheckedTaskCompletionOnDisk(base, mid, sid, tid)) {
|
|
523
|
-
return false;
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
else if (!isDbAvailable()) {
|
|
527
|
-
// LEGACY: Pre-migration fallback for projects without DB.
|
|
528
|
-
// Require a CHECKED checkbox — a bare heading or unchecked checkbox
|
|
529
|
-
// does not prove gsd_complete_task ran. Summary file on disk alone
|
|
530
|
-
// is not sufficient evidence (could be a rogue write) (#3607).
|
|
531
|
-
if (!hasCheckedTaskCompletionOnDisk(base, mid, sid, tid))
|
|
532
|
-
return false;
|
|
533
|
-
}
|
|
534
|
-
else {
|
|
535
|
-
// DB available but task row not found — completion tool never ran (#3607)
|
|
536
|
-
return false;
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
}
|
|
540
|
-
// plan-slice must also produce individual task plan files for every task listed
|
|
541
|
-
// in the slice plan. Without this check, a plan-slice that wrote S{sid}-PLAN.md
|
|
542
|
-
// but omitted T{tid}-PLAN.md files would be marked complete, causing execute-task
|
|
543
|
-
// to dispatch with a missing task plan (see issue #739).
|
|
513
|
+
// plan-slice verification is DB-primary. The slice plan is a projection, so
|
|
514
|
+
// DB task rows prove the slice was planned even if the rendered markdown no
|
|
515
|
+
// longer uses legacy checkbox/heading syntax.
|
|
544
516
|
if (unitType === "plan-slice") {
|
|
545
517
|
const { milestone: mid, slice: sid } = parseUnitId(unitId);
|
|
546
518
|
if (mid && sid) {
|
|
547
519
|
try {
|
|
548
|
-
// DB primary path — get task IDs to verify task plan files exist
|
|
549
520
|
let taskIds = null;
|
|
550
521
|
if (isDbAvailable()) {
|
|
551
|
-
const
|
|
552
|
-
if (
|
|
553
|
-
|
|
522
|
+
const refreshed = refreshOpenDatabaseFromDisk();
|
|
523
|
+
if (refreshed) {
|
|
524
|
+
const tasks = getSliceTasks(mid, sid);
|
|
525
|
+
if (tasks.length > 0)
|
|
526
|
+
taskIds = tasks.map(t => t.id);
|
|
527
|
+
}
|
|
554
528
|
}
|
|
555
529
|
if (!taskIds) {
|
|
556
|
-
// LEGACY: DB unavailable or no tasks in DB
|
|
530
|
+
// LEGACY: DB unavailable or no tasks in DB. Require actual task
|
|
531
|
+
// entries so an empty scaffold cannot advance the pipeline (#699).
|
|
557
532
|
const planContent = readFileSync(absPath, "utf-8");
|
|
533
|
+
const hasCheckboxTask = /^\s*- \[[xX ]\] \*\*T\d+:/m.test(planContent);
|
|
534
|
+
const hasHeadingTask = /^\s*#{2,4}\s+T\d+\s*(?:--|—|:)/m.test(planContent);
|
|
535
|
+
if (!hasCheckboxTask && !hasHeadingTask) {
|
|
536
|
+
logWarning("recovery", `verify-fail ${unitType} ${unitId}: plan has no task checkbox/heading (len=${planContent.length}) at ${absPath}`);
|
|
537
|
+
return false;
|
|
538
|
+
}
|
|
558
539
|
const plan = parseLegacyPlan(planContent);
|
|
559
540
|
if (plan.tasks.length > 0)
|
|
560
541
|
taskIds = plan.tasks.map((t) => t.id);
|
|
@@ -580,6 +561,33 @@ export function verifyExpectedArtifact(unitType, unitId, base) {
|
|
|
580
561
|
}
|
|
581
562
|
}
|
|
582
563
|
}
|
|
564
|
+
// execute-task: DB status is authoritative. Fall back to checked-checkbox
|
|
565
|
+
// detection when the DB is unavailable (unmigrated projects), or when the
|
|
566
|
+
// disk artifacts already reflect completion but the DB replay is one beat
|
|
567
|
+
// behind the completion write.
|
|
568
|
+
if (unitType === "execute-task") {
|
|
569
|
+
const { milestone: mid, slice: sid, task: tid } = parseUnitId(unitId);
|
|
570
|
+
if (mid && sid && tid) {
|
|
571
|
+
const dbTask = getTask(mid, sid, tid);
|
|
572
|
+
if (dbTask) {
|
|
573
|
+
if (dbTask.status !== "complete" && dbTask.status !== "done" && !hasCheckedTaskCompletionOnDisk(base, mid, sid, tid)) {
|
|
574
|
+
return false;
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
else if (!isDbAvailable()) {
|
|
578
|
+
// LEGACY: Pre-migration fallback for projects without DB.
|
|
579
|
+
// Require a CHECKED checkbox — a bare heading or unchecked checkbox
|
|
580
|
+
// does not prove gsd_complete_task ran. Summary file on disk alone
|
|
581
|
+
// is not sufficient evidence (could be a rogue write) (#3607).
|
|
582
|
+
if (!hasCheckedTaskCompletionOnDisk(base, mid, sid, tid))
|
|
583
|
+
return false;
|
|
584
|
+
}
|
|
585
|
+
else {
|
|
586
|
+
// DB available but task row not found — completion tool never ran (#3607)
|
|
587
|
+
return false;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
}
|
|
583
591
|
// complete-slice: DB status is authoritative for whether the slice is done.
|
|
584
592
|
// Fall back to file-based check (roadmap [x]) when DB is unavailable.
|
|
585
593
|
if (unitType === "complete-slice") {
|
|
@@ -3,11 +3,15 @@ import { AutoSession } from "./auto/session.js";
|
|
|
3
3
|
import { isDeterministicPolicyError, isQueuedUserMessageSkip, isToolInvocationError, markToolEnd as markTrackedToolEnd, markToolStart as markTrackedToolStart, } from "./auto-tool-tracking.js";
|
|
4
4
|
export const autoSession = new AutoSession();
|
|
5
5
|
export function getAutoRuntimeSnapshot() {
|
|
6
|
+
const orchestrationStatus = autoSession.orchestration?.getStatus();
|
|
6
7
|
return {
|
|
7
8
|
active: autoSession.active,
|
|
8
9
|
paused: autoSession.paused,
|
|
9
10
|
currentUnit: autoSession.currentUnit ? { ...autoSession.currentUnit } : null,
|
|
10
11
|
basePath: autoSession.basePath,
|
|
12
|
+
orchestrationPhase: orchestrationStatus?.phase,
|
|
13
|
+
orchestrationTransitionCount: orchestrationStatus?.transitionCount,
|
|
14
|
+
orchestrationLastTransitionAt: orchestrationStatus?.lastTransitionAt,
|
|
11
15
|
};
|
|
12
16
|
}
|
|
13
17
|
export function isAutoActive() {
|
|
@@ -486,8 +486,9 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
|
|
|
486
486
|
// Clear survivor flag — finalization is done
|
|
487
487
|
hasSurvivorBranch = false;
|
|
488
488
|
}
|
|
489
|
+
const effectivePrefs = loadEffectiveGSDPreferences(base)?.preferences;
|
|
489
490
|
const deepProjectStagePending = !hasSurvivorBranch
|
|
490
|
-
? (await import("./auto-dispatch.js")).hasPendingDeepStage(
|
|
491
|
+
? (await import("./auto-dispatch.js")).hasPendingDeepStage(effectivePrefs, base)
|
|
491
492
|
: false;
|
|
492
493
|
if (deepProjectStagePending) {
|
|
493
494
|
// Deep project-level setup runs before the first milestone exists. Let
|
|
@@ -526,7 +527,7 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
|
|
|
526
527
|
const mid = state.activeMilestone.id;
|
|
527
528
|
const contextFile = resolveMilestoneFile(base, mid, "CONTEXT");
|
|
528
529
|
const hasContext = !!(contextFile && (await loadFile(contextFile)));
|
|
529
|
-
if (!hasContext) {
|
|
530
|
+
if (!hasContext && effectivePrefs?.planning_depth !== "deep") {
|
|
530
531
|
const { showSmartEntry } = await import("./guided-flow.js");
|
|
531
532
|
await showSmartEntry(ctx, pi, base, { step: requestedStepMode });
|
|
532
533
|
// showSmartEntry dispatches via pi.sendMessage() which is fire-and-forget:
|