@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.
Files changed (225) hide show
  1. package/dist/mcp-server.js +2 -1
  2. package/dist/resources/.managed-resources-content-hash +1 -1
  3. package/dist/resources/extensions/gsd/auto/orchestrator.js +28 -10
  4. package/dist/resources/extensions/gsd/auto/phases.js +47 -4
  5. package/dist/resources/extensions/gsd/auto/session.js +3 -0
  6. package/dist/resources/extensions/gsd/auto-direct-dispatch.js +3 -2
  7. package/dist/resources/extensions/gsd/auto-dispatch.js +11 -2
  8. package/dist/resources/extensions/gsd/auto-model-selection.js +11 -7
  9. package/dist/resources/extensions/gsd/auto-post-unit.js +18 -6
  10. package/dist/resources/extensions/gsd/auto-unit-closeout.js +45 -21
  11. package/dist/resources/extensions/gsd/auto-verification.js +14 -2
  12. package/dist/resources/extensions/gsd/auto.js +37 -1
  13. package/dist/resources/extensions/gsd/blocked-models.js +28 -0
  14. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +26 -6
  15. package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +2 -2
  16. package/dist/resources/extensions/gsd/closeout-wizard.js +92 -0
  17. package/dist/resources/extensions/gsd/commands/context.js +16 -2
  18. package/dist/resources/extensions/gsd/commands-handlers.js +46 -3
  19. package/dist/resources/extensions/gsd/consent-question.js +16 -0
  20. package/dist/resources/extensions/gsd/crash-recovery.js +8 -3
  21. package/dist/resources/extensions/gsd/doctor-engine-checks.js +3 -3
  22. package/dist/resources/extensions/gsd/doctor-git-checks.js +2 -18
  23. package/dist/resources/extensions/gsd/gsd-command-home.js +22 -12
  24. package/dist/resources/extensions/gsd/gsd-db.js +2 -1
  25. package/dist/resources/extensions/gsd/guided-flow.js +6 -3
  26. package/dist/resources/extensions/gsd/milestone-closeout.js +73 -2
  27. package/dist/resources/extensions/gsd/milestone-planning-persistence.js +2 -2
  28. package/dist/resources/extensions/gsd/projection-flush.js +7 -0
  29. package/dist/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  30. package/dist/resources/extensions/gsd/prompts/execute-task.md +1 -1
  31. package/dist/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
  32. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  33. package/dist/resources/extensions/gsd/prompts/quick-task.md +1 -1
  34. package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
  35. package/dist/resources/extensions/gsd/prompts/refine-slice.md +1 -1
  36. package/dist/resources/extensions/gsd/prompts/replan-slice.md +1 -1
  37. package/dist/resources/extensions/gsd/prompts/research-milestone.md +1 -1
  38. package/dist/resources/extensions/gsd/prompts/research-slice.md +1 -1
  39. package/dist/resources/extensions/gsd/prompts/rewrite-docs.md +1 -1
  40. package/dist/resources/extensions/gsd/prompts/run-uat.md +1 -1
  41. package/dist/resources/extensions/gsd/prompts/triage-captures.md +1 -1
  42. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +1 -1
  43. package/dist/resources/extensions/gsd/roadmap-slices.js +25 -3
  44. package/dist/resources/extensions/gsd/session-lock.js +1 -1
  45. package/dist/resources/extensions/gsd/tool-contract.js +14 -3
  46. package/dist/resources/extensions/gsd/tools/complete-milestone.js +3 -2
  47. package/dist/resources/extensions/gsd/tools/complete-slice.js +2 -2
  48. package/dist/resources/extensions/gsd/tools/complete-task.js +3 -2
  49. package/dist/resources/extensions/gsd/tools/plan-slice.js +2 -2
  50. package/dist/resources/extensions/gsd/tools/plan-task.js +2 -2
  51. package/dist/resources/extensions/gsd/tools/reassess-roadmap.js +2 -2
  52. package/dist/resources/extensions/gsd/tools/reopen-milestone.js +2 -2
  53. package/dist/resources/extensions/gsd/tools/reopen-slice.js +2 -2
  54. package/dist/resources/extensions/gsd/tools/reopen-task.js +2 -2
  55. package/dist/resources/extensions/gsd/tools/replan-slice.js +2 -2
  56. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +67 -2
  57. package/dist/resources/extensions/gsd/verification-verdict.js +2 -1
  58. package/dist/resources/extensions/shared/gsd-browser-cli.js +21 -2
  59. package/dist/resources/shared/gsd-browser-path-sync.js +214 -0
  60. package/dist/resources/shared/package-manager-detection.js +1 -1
  61. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  62. package/dist/update-check.d.ts +2 -0
  63. package/dist/update-check.js +24 -1
  64. package/dist/update-cmd.js +20 -3
  65. package/dist/web/standalone/.next/BUILD_ID +1 -1
  66. package/dist/web/standalone/.next/app-path-routes-manifest.json +12 -12
  67. package/dist/web/standalone/.next/build-manifest.json +2 -2
  68. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  69. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  70. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  71. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  72. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  73. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  74. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  75. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  77. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  78. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  79. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  80. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  81. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  82. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  83. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  84. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  85. package/dist/web/standalone/.next/server/app/index.html +1 -1
  86. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  87. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  88. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  89. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  90. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  91. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  92. package/dist/web/standalone/.next/server/app-paths-manifest.json +12 -12
  93. package/dist/web/standalone/.next/server/chunks/8357.js +2 -2
  94. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  95. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  96. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  97. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  98. package/dist/web/standalone/node_modules/node-pty/build/Makefile +1 -1
  99. package/package.json +1 -1
  100. package/packages/cloud-mcp-gateway/package.json +2 -2
  101. package/packages/contracts/package.json +1 -1
  102. package/packages/daemon/package.json +4 -4
  103. package/packages/gsd-agent-core/package.json +5 -5
  104. package/packages/gsd-agent-modes/package.json +7 -7
  105. package/packages/mcp-server/dist/cli.js +10 -5
  106. package/packages/mcp-server/dist/cli.js.map +1 -1
  107. package/packages/mcp-server/dist/moonshot-tool-schema.d.ts +29 -0
  108. package/packages/mcp-server/dist/moonshot-tool-schema.d.ts.map +1 -0
  109. package/packages/mcp-server/dist/moonshot-tool-schema.js +50 -0
  110. package/packages/mcp-server/dist/moonshot-tool-schema.js.map +1 -0
  111. package/packages/mcp-server/dist/server.d.ts.map +1 -1
  112. package/packages/mcp-server/dist/server.js +4 -0
  113. package/packages/mcp-server/dist/server.js.map +1 -1
  114. package/packages/mcp-server/dist/workflow-tools.d.ts +18 -18
  115. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  116. package/packages/mcp-server/dist/workflow-tools.js +99 -38
  117. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  118. package/packages/mcp-server/package.json +5 -4
  119. package/packages/native/package.json +1 -1
  120. package/packages/pi-agent-core/package.json +1 -1
  121. package/packages/pi-ai/dist/index.d.ts +2 -0
  122. package/packages/pi-ai/dist/index.d.ts.map +1 -1
  123. package/packages/pi-ai/dist/index.js +2 -0
  124. package/packages/pi-ai/dist/index.js.map +1 -1
  125. package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
  126. package/packages/pi-ai/dist/providers/anthropic.js +12 -7
  127. package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
  128. package/packages/pi-ai/dist/providers/google-shared.d.ts +5 -0
  129. package/packages/pi-ai/dist/providers/google-shared.d.ts.map +1 -1
  130. package/packages/pi-ai/dist/providers/google-shared.js +12 -3
  131. package/packages/pi-ai/dist/providers/google-shared.js.map +1 -1
  132. package/packages/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
  133. package/packages/pi-ai/dist/providers/openai-completions.js +7 -3
  134. package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
  135. package/packages/pi-ai/dist/utils/moonshot-tool-schema.d.ts +9 -0
  136. package/packages/pi-ai/dist/utils/moonshot-tool-schema.d.ts.map +1 -0
  137. package/packages/pi-ai/dist/utils/moonshot-tool-schema.js +34 -0
  138. package/packages/pi-ai/dist/utils/moonshot-tool-schema.js.map +1 -0
  139. package/packages/pi-ai/dist/utils/oauth/github-copilot.d.ts.map +1 -1
  140. package/packages/pi-ai/dist/utils/oauth/github-copilot.js +6 -2
  141. package/packages/pi-ai/dist/utils/oauth/github-copilot.js.map +1 -1
  142. package/packages/pi-ai/package.json +1 -1
  143. package/packages/pi-coding-agent/package.json +7 -7
  144. package/packages/pi-tui/package.json +2 -2
  145. package/packages/rpc-client/package.json +2 -2
  146. package/pkg/package.json +1 -1
  147. package/src/resources/extensions/browser-tools/tests/gsd-browser-launch-config.test.mjs +11 -0
  148. package/src/resources/extensions/gsd/auto/orchestrator.ts +28 -10
  149. package/src/resources/extensions/gsd/auto/phases.ts +63 -24
  150. package/src/resources/extensions/gsd/auto/session.ts +3 -0
  151. package/src/resources/extensions/gsd/auto-direct-dispatch.ts +10 -16
  152. package/src/resources/extensions/gsd/auto-dispatch.ts +11 -10
  153. package/src/resources/extensions/gsd/auto-model-selection.ts +16 -7
  154. package/src/resources/extensions/gsd/auto-post-unit.ts +21 -6
  155. package/src/resources/extensions/gsd/auto-unit-closeout.ts +83 -28
  156. package/src/resources/extensions/gsd/auto-verification.ts +18 -2
  157. package/src/resources/extensions/gsd/auto.ts +44 -1
  158. package/src/resources/extensions/gsd/blocked-models.ts +49 -0
  159. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +34 -5
  160. package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +2 -2
  161. package/src/resources/extensions/gsd/closeout-wizard.ts +102 -0
  162. package/src/resources/extensions/gsd/commands/context.ts +16 -2
  163. package/src/resources/extensions/gsd/commands-handlers.ts +46 -3
  164. package/src/resources/extensions/gsd/consent-question.ts +15 -0
  165. package/src/resources/extensions/gsd/crash-recovery.ts +10 -2
  166. package/src/resources/extensions/gsd/doctor-engine-checks.ts +3 -3
  167. package/src/resources/extensions/gsd/doctor-git-checks.ts +2 -19
  168. package/src/resources/extensions/gsd/gsd-command-home.ts +13 -3
  169. package/src/resources/extensions/gsd/gsd-db.ts +4 -3
  170. package/src/resources/extensions/gsd/guided-flow.ts +21 -26
  171. package/src/resources/extensions/gsd/milestone-closeout.ts +97 -2
  172. package/src/resources/extensions/gsd/milestone-planning-persistence.ts +2 -2
  173. package/src/resources/extensions/gsd/projection-flush.ts +20 -0
  174. package/src/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  175. package/src/resources/extensions/gsd/prompts/execute-task.md +1 -1
  176. package/src/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
  177. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  178. package/src/resources/extensions/gsd/prompts/quick-task.md +1 -1
  179. package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
  180. package/src/resources/extensions/gsd/prompts/refine-slice.md +1 -1
  181. package/src/resources/extensions/gsd/prompts/replan-slice.md +1 -1
  182. package/src/resources/extensions/gsd/prompts/research-milestone.md +1 -1
  183. package/src/resources/extensions/gsd/prompts/research-slice.md +1 -1
  184. package/src/resources/extensions/gsd/prompts/rewrite-docs.md +1 -1
  185. package/src/resources/extensions/gsd/prompts/run-uat.md +1 -1
  186. package/src/resources/extensions/gsd/prompts/triage-captures.md +1 -1
  187. package/src/resources/extensions/gsd/prompts/validate-milestone.md +1 -1
  188. package/src/resources/extensions/gsd/roadmap-slices.ts +28 -3
  189. package/src/resources/extensions/gsd/session-lock.ts +1 -1
  190. package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +69 -0
  191. package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +97 -0
  192. package/src/resources/extensions/gsd/tests/auto-remote-session-lock-cleanup.test.ts +65 -3
  193. package/src/resources/extensions/gsd/tests/blocked-models.test.ts +19 -0
  194. package/src/resources/extensions/gsd/tests/consent-question.test.ts +15 -0
  195. package/src/resources/extensions/gsd/tests/doctor-git-checks-terminal.test.ts +73 -0
  196. package/src/resources/extensions/gsd/tests/gsd-command-home.test.ts +120 -0
  197. package/src/resources/extensions/gsd/tests/guided-dispatch-root.test.ts +2 -6
  198. package/src/resources/extensions/gsd/tests/milestone-closeout.test.ts +95 -4
  199. package/src/resources/extensions/gsd/tests/parsers-legacy-importers.test.ts +0 -1
  200. package/src/resources/extensions/gsd/tests/phases-terminal-complete-idempotent.test.ts +242 -0
  201. package/src/resources/extensions/gsd/tests/post-exec-retry-bypass.test.ts +63 -2
  202. package/src/resources/extensions/gsd/tests/roadmap-slices.test.ts +68 -0
  203. package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +19 -1
  204. package/src/resources/extensions/gsd/tests/tool-unavailable-retry.test.ts +33 -0
  205. package/src/resources/extensions/gsd/tests/transport-gate-double-complete.test.ts +139 -0
  206. package/src/resources/extensions/gsd/tests/verification-verdict.test.ts +2 -0
  207. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +273 -38
  208. package/src/resources/extensions/gsd/tool-contract.ts +38 -3
  209. package/src/resources/extensions/gsd/tools/complete-milestone.ts +3 -2
  210. package/src/resources/extensions/gsd/tools/complete-slice.ts +2 -2
  211. package/src/resources/extensions/gsd/tools/complete-task.ts +3 -2
  212. package/src/resources/extensions/gsd/tools/plan-slice.ts +2 -2
  213. package/src/resources/extensions/gsd/tools/plan-task.ts +2 -2
  214. package/src/resources/extensions/gsd/tools/reassess-roadmap.ts +2 -2
  215. package/src/resources/extensions/gsd/tools/reopen-milestone.ts +2 -2
  216. package/src/resources/extensions/gsd/tools/reopen-slice.ts +2 -2
  217. package/src/resources/extensions/gsd/tools/reopen-task.ts +2 -2
  218. package/src/resources/extensions/gsd/tools/replan-slice.ts +2 -2
  219. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +81 -2
  220. package/src/resources/extensions/gsd/verification-verdict.ts +4 -2
  221. package/src/resources/extensions/shared/gsd-browser-cli.ts +23 -2
  222. package/src/resources/shared/gsd-browser-path-sync.ts +273 -0
  223. package/src/resources/shared/package-manager-detection.ts +1 -1
  224. /package/dist/web/standalone/.next/static/{jmTLg6xZmAuq_LIqKOxrH → McokybTayhff1xEVc-d3T}/_buildManifest.js +0 -0
  225. /package/dist/web/standalone/.next/static/{jmTLg6xZmAuq_LIqKOxrH → McokybTayhff1xEVc-d3T}/_ssgManifest.js +0 -0
@@ -9,7 +9,7 @@ import type { ExtensionAPI, ExtensionCommandContext } from "@gsd/pi-coding-agent
9
9
  import { existsSync, readFileSync, mkdirSync } from "node:fs";
10
10
  import { execFileSync } from "node:child_process";
11
11
  import { createRequire } from "node:module";
12
- import { join, resolve as resolvePath, sep } from "node:path";
12
+ import { join, resolve as resolvePath, sep, win32 as pathWin32 } from "node:path";
13
13
  import { homedir } from "node:os";
14
14
  import { deriveState } from "./state.js";
15
15
  import { gsdRoot } from "./paths.js";
@@ -29,6 +29,7 @@ import { getAutoWorktreePath } from "./auto-worktree.js";
29
29
  import { currentDirectoryRoot, projectRoot } from "./commands/context.js";
30
30
  import { loadPrompt } from "./prompt-loader.js";
31
31
  import { buildClaudeRuntimeFloorAdvisory } from "../../shared/claude-runtime-floor.js";
32
+ import { reconcileGsdBrowserPathAfterInstall } from "../../shared/gsd-browser-path-sync.js";
32
33
  import { isPnpmInstall } from "../../shared/package-manager-detection.js";
33
34
  import {
34
35
  buildDoctorHealIssuePayload,
@@ -65,9 +66,33 @@ function isBunInstall(argv1: string | undefined = process.argv[1]): boolean {
65
66
  function resolveInstallCommand(pkg: string): string {
66
67
  if (isBunInstall()) return `bun add -g ${pkg}`;
67
68
  if (isPnpmInstall()) return `pnpm add -g ${pkg}`;
69
+ const npmPrefix = resolveWindowsNpmGlobalPrefix();
70
+ if (npmPrefix) return `npm --prefix ${quoteWindowsArg(npmPrefix)} install -g ${pkg}`;
68
71
  return `npm install -g ${pkg}`;
69
72
  }
70
73
 
74
+ function resolveWindowsNpmGlobalPrefix(
75
+ argv1: string | undefined = process.argv[1],
76
+ platform: NodeJS.Platform = process.platform,
77
+ ): string | null {
78
+ if (platform !== "win32" || !argv1) return null;
79
+ const normalized = pathWin32.normalize(argv1);
80
+ const marker = `${pathWin32.sep}node_modules${pathWin32.sep}`;
81
+ const index = normalized.toLowerCase().lastIndexOf(marker);
82
+ if (index <= 0) return null;
83
+ const prefix = normalized.slice(0, index);
84
+ // Verify this is a real npm global prefix: such a directory always contains
85
+ // npm's own bin shim (`npm.cmd`) as a sibling of `node_modules/`. Local
86
+ // project `node_modules/`, npx caches, and other non-global layouts do not,
87
+ // so without this check `--prefix` would target the wrong directory.
88
+ if (!existsSync(pathWin32.join(prefix, "npm.cmd"))) return null;
89
+ return prefix;
90
+ }
91
+
92
+ function quoteWindowsArg(value: string): string {
93
+ return `"${value.replace(/"/g, '\\"')}"`;
94
+ }
95
+
71
96
  function notifyClaudeRuntimeFloorAdvisory(ctx: ExtensionCommandContext): void {
72
97
  let advisory: string | null = null;
73
98
  try {
@@ -576,12 +601,30 @@ export async function handleUpdate(ctx: ExtensionCommandContext, args = ""): Pro
576
601
  execSync(installCmd, {
577
602
  stdio: ["ignore", "pipe", "ignore"],
578
603
  });
604
+ let reconcile: ReturnType<typeof reconcileGsdBrowserPathAfterInstall> | null = null;
605
+ if (browserUpdate) {
606
+ try {
607
+ reconcile = reconcileGsdBrowserPathAfterInstall({
608
+ latestVersion: latest,
609
+ compareSemver: compareSemverLocal,
610
+ resolvePathVersion: resolveGsdBrowserPathVersionForCommand,
611
+ });
612
+ } catch {
613
+ // Reconciliation is best-effort: the install above already succeeded,
614
+ // so a reconcile failure must not flip the result to "Update failed".
615
+ reconcile = null;
616
+ }
617
+ }
579
618
  const newPathVersion = browserUpdate ? resolveGsdBrowserPathVersionForCommand() : null;
580
- const pathReady = !browserUpdate || (!!newPathVersion && compareSemverLocal(newPathVersion, latest) >= 0);
619
+ const pathNote = browserUpdate && !(newPathVersion && compareSemverLocal(newPathVersion, latest) >= 0)
620
+ ? (reconcile?.message
621
+ ?? "Ensure the npm global bin directory is on your PATH so MCP automation uses the updated binary.")
622
+ : "";
581
623
  ctx.ui.notify(
582
624
  browserUpdate
583
625
  ? `Updated gsd-browser to v${latest}. Restart your GSD session to use the new browser automation version.` +
584
- (pathReady ? "" : "\nNote: Ensure the npm global bin directory is on your PATH so MCP automation uses the updated binary.")
626
+ (reconcile?.action === "synced" && reconcile.message ? `\n${reconcile.message}` : "") +
627
+ (pathNote ? `\nNote: ${pathNote}` : "")
585
628
  : `Updated to v${latest}. Restart your GSD session to use the new version.`,
586
629
  "info",
587
630
  );
@@ -107,6 +107,20 @@ export function hasResearchDecisionQuestion(text: string): boolean {
107
107
  return hasQuestionMatching(text, [RESEARCH_DECISION_QUESTION_RE]);
108
108
  }
109
109
 
110
+ /**
111
+ * Detect a plain-text "Next steps:" menu — numbered options with an "Other"
112
+ * choice — emitted as prose instead of a structured ask_user_questions call.
113
+ * Without this, auto-mode treats the menu as informational and loops on its
114
+ * own turn until tokens are exhausted (#454).
115
+ */
116
+ export function hasPlainTextNextStepsMenu(lines: string[]): boolean {
117
+ const nextStepsIndex = lines.findIndex((line) => /^next steps\s*:?$/i.test(line));
118
+ if (nextStepsIndex < 0) return false;
119
+ const menuLines = lines.slice(nextStepsIndex + 1);
120
+ const numberedOptions = menuLines.filter((line) => /^\d+[.)]\s+\S/.test(line));
121
+ return numberedOptions.length >= 2 && numberedOptions.some((line) => /\bother\b/i.test(line));
122
+ }
123
+
110
124
  // ── Message text extraction (moved from user-input-boundary) ────────────────
111
125
 
112
126
  function extractMessageText(msg: unknown, includeThinking: boolean): string {
@@ -347,6 +361,7 @@ export function isAwaitingUserInput(messages: unknown[] | undefined): boolean {
347
361
  if (!text) return false;
348
362
  const lines = text.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
349
363
  if (lines.some((line) => line.endsWith("?"))) return true;
364
+ if (hasPlainTextNextStepsMenu(lines)) return true;
350
365
  return hasApprovalQuestion(text);
351
366
  }
352
367
 
@@ -223,6 +223,7 @@ export function writeLock(
223
223
  * stale session-file pointer.
224
224
  */
225
225
  export function clearLock(basePath: string): void {
226
+ const legacyLock = readLegacyLock(basePath);
226
227
  clearLegacyLockFile(basePath);
227
228
 
228
229
  if (!isDbAvailable()) return;
@@ -235,8 +236,15 @@ export function clearLock(basePath: string): void {
235
236
  deleteRuntimeKv("worker", staleWorker.worker_id, SESSION_FILE_KV_KEY);
236
237
  return;
237
238
  }
238
- const lock = readLegacyLock(basePath);
239
- if (lock?.pid) markWorkerStoppingByPid(projectRoot, lock.pid);
239
+ if (legacyLock?.pid) {
240
+ markWorkerStoppingByPid(projectRoot, legacyLock.pid);
241
+ const workerByLegacyPid = getAllAutoWorkers().find(
242
+ (w) =>
243
+ w.pid === legacyLock.pid
244
+ && normalizeRealPath(w.project_root_realpath) === projectRoot,
245
+ );
246
+ if (workerByLegacyPid) forceReleaseLeasesForWorker(workerByLegacyPid.worker_id);
247
+ }
240
248
  const worker = findActiveWorkerForCurrentProcess(projectRoot);
241
249
  if (worker) deleteRuntimeKv("worker", worker.worker_id, SESSION_FILE_KV_KEY);
242
250
 
@@ -7,7 +7,7 @@ import { isAfter, latestExplicitReopenAt } from "./milestone-reopen-events.js";
7
7
  import { resolveGsdPathContract, resolveMilestoneFile } from "./paths.js";
8
8
  import { deriveState } from "./state.js";
9
9
  import { readEvents } from "./workflow-events.js";
10
- import { renderAllProjections } from "./workflow-projections.js";
10
+ import { flushWorkflowProjections } from "./projection-flush.js";
11
11
 
12
12
  export async function checkEngineHealth(
13
13
  basePath: string,
@@ -270,7 +270,7 @@ export async function checkEngineHealth(
270
270
  const roadmapPath = resolveMilestoneFile(basePath, milestone.id, "ROADMAP");
271
271
  if (!roadmapPath || !existsSync(roadmapPath)) {
272
272
  try {
273
- await renderAllProjections(basePath, milestone.id);
273
+ await flushWorkflowProjections(basePath, { milestoneId: milestone.id });
274
274
  fixesApplied.push(`re-rendered missing projections for ${milestone.id}`);
275
275
  } catch {
276
276
  // Non-fatal — projection re-render failed
@@ -280,7 +280,7 @@ export async function checkEngineHealth(
280
280
  const projectionMtime = statSync(roadmapPath).mtimeMs;
281
281
  if (lastEventTs > projectionMtime) {
282
282
  try {
283
- await renderAllProjections(basePath, milestone.id);
283
+ await flushWorkflowProjections(basePath, { milestoneId: milestone.id });
284
284
  fixesApplied.push(`re-rendered stale projections for ${milestone.id}`);
285
285
  } catch {
286
286
  // Non-fatal — projection re-render failed
@@ -5,10 +5,9 @@ import { dirname, join } from "node:path";
5
5
 
6
6
  import type { DoctorIssue, DoctorIssueCode } from "./doctor-types.js";
7
7
  import { loadFile } from "./files.js";
8
- import { parseRoadmap as parseLegacyRoadmap } from "./parsers-legacy.js";
9
- import { isDbAvailable, getMilestone } from "./gsd-db.js";
10
8
  import { resolveMilestoneFile } from "./paths.js";
11
- import { deriveState, isMilestoneComplete } from "./state.js";
9
+ import { isCompletedMilestoneTerminal } from "./milestone-closeout.js";
10
+ import { deriveState } from "./state.js";
12
11
  import { allWorktreesDirs, createWorktree, listWorktrees, resolveGitDir } from "./worktree-manager.js";
13
12
  import { abortAndReset } from "./git-self-heal.js";
14
13
  import { RUNTIME_EXCLUSION_PATHS, resolveMilestoneIntegrationBranch, writeIntegrationBranch } from "./git-service.js";
@@ -146,22 +145,6 @@ function getSnapshotDiffCheckFailure(basePath: string): string | null {
146
145
  return failures.length > 0 ? failures.join("\n") : null;
147
146
  }
148
147
 
149
- async function isCompletedMilestoneTerminal(basePath: string, milestoneId: string): Promise<boolean> {
150
- const summaryPath = resolveMilestoneFile(basePath, milestoneId, "SUMMARY");
151
- if (!summaryPath) return false;
152
-
153
- if (isDbAvailable()) {
154
- const milestone = getMilestone(milestoneId);
155
- return !!milestone && milestone.status === "complete";
156
- }
157
-
158
- const roadmapPath = resolveMilestoneFile(basePath, milestoneId, "ROADMAP");
159
- const roadmapContent = roadmapPath ? await loadFile(roadmapPath) : null;
160
- if (!roadmapContent) return false;
161
- const roadmap = parseLegacyRoadmap(roadmapContent);
162
- return isMilestoneComplete(roadmap);
163
- }
164
-
165
148
  export async function checkGitHealth(
166
149
  basePath: string,
167
150
  issues: DoctorIssue[],
@@ -66,7 +66,7 @@ function disabled(description: string, reason: string): string {
66
66
 
67
67
  export function buildGsdHomeModel(
68
68
  state: GSDState,
69
- closeout?: Pick<CloseoutContext, "strandedQuick" | "unmergedMilestones">,
69
+ closeout?: Pick<CloseoutContext, "strandedQuick" | "unmergedMilestones" | "idleResidueHint">,
70
70
  ): GsdHomeModel {
71
71
  const blocked = isBlocked(state);
72
72
  const complete = state.phase === "complete";
@@ -74,10 +74,14 @@ export function buildGsdHomeModel(
74
74
  const workLabel = activeWorkLabel(state);
75
75
  const strandedQuick = closeout?.strandedQuick ?? null;
76
76
  const unmergedMilestone = closeout?.unmergedMilestones?.[0];
77
+ const idleResidueHint = closeout?.idleResidueHint ?? null;
78
+ const hasIdleResidue = Boolean(idleResidueHint);
77
79
  const nextReason = complete
78
80
  ? "all milestones are complete"
79
81
  : blocked
80
82
  ? "the active milestone is blocked"
83
+ : hasIdleResidue
84
+ ? "milestone git residue needs recovery"
81
85
  : !hasActiveWork
82
86
  ? "there is no active milestone"
83
87
  : "";
@@ -91,6 +95,8 @@ export function buildGsdHomeModel(
91
95
  ? "finish_milestone"
92
96
  : blocked
93
97
  ? "fix_recover"
98
+ : hasIdleResidue
99
+ ? "fix_recover"
94
100
  : canAdvance
95
101
  ? "continue_step"
96
102
  : complete && unmappedActive > 0
@@ -107,6 +113,8 @@ export function buildGsdHomeModel(
107
113
  ? [`Quick task Q${strandedQuick.taskNum} finished on ${strandedQuick.quickBranch} but is not merged to ${strandedQuick.originalBranch}.`]
108
114
  : unmergedMilestone
109
115
  ? [`${unmergedMilestone.milestoneId} is complete but not merged into ${unmergedMilestone.integrationBranch}.`]
116
+ : idleResidueHint
117
+ ? [idleResidueHint.message]
110
118
  : completionSummary;
111
119
 
112
120
  return {
@@ -181,10 +189,12 @@ export function buildGsdHomeModel(
181
189
  label: "Fix or recover",
182
190
  description: blocked
183
191
  ? "Review the blocker and recovery commands for the active milestone."
192
+ : hasIdleResidue
193
+ ? "Review stranded milestone worktrees/branches and run the suggested recovery command."
184
194
  : disabled("This becomes active when closeout, validation, or state recovery is needed.", "no blocker is active"),
185
- enabled: blocked,
195
+ enabled: blocked || hasIdleResidue,
186
196
  recommended: recommended === "fix_recover",
187
- disabledReason: blocked ? undefined : "no blocker is active",
197
+ disabledReason: blocked || hasIdleResidue ? undefined : "no blocker is active",
188
198
  },
189
199
  {
190
200
  id: "start_configure",
@@ -246,9 +246,9 @@ export function insertMilestone(m: {
246
246
  status?: string;
247
247
  depends_on?: string[];
248
248
  planning?: Partial<MilestonePlanningRecord>;
249
- }): void {
249
+ }): boolean {
250
250
  if (!getDbOrNull()!) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
251
- getDbOrNull()!.prepare(
251
+ const result = getDbOrNull()!.prepare(
252
252
  `INSERT OR IGNORE INTO milestones (
253
253
  id, title, status, depends_on, created_at,
254
254
  vision, success_criteria, key_risks, proof_strategy,
@@ -279,7 +279,8 @@ export function insertMilestone(m: {
279
279
  ":definition_of_done": JSON.stringify(m.planning?.definitionOfDone ?? []),
280
280
  ":requirement_coverage": m.planning?.requirementCoverage ?? "",
281
281
  ":boundary_map_markdown": m.planning?.boundaryMapMarkdown ?? "",
282
- });
282
+ }) as { changes?: number };
283
+ return (result.changes ?? 0) > 0;
283
284
  }
284
285
 
285
286
  export function upsertMilestonePlanning(milestoneId: string, planning: Partial<MilestonePlanningRecord> & { title?: string; status?: string; depends_on?: string[] }): void {
@@ -78,11 +78,8 @@ import {
78
78
  } from "./requirements-backlog.js";
79
79
  import { selectAndApplyModel } from "./auto-model-selection.js";
80
80
  import { DISCUSS_TOOLS_ALLOWLIST } from "./constants.js";
81
- import {
82
- getWorkflowTransportSupportError,
83
- getRequiredWorkflowToolsForGuidedUnit,
84
- supportsStructuredQuestions,
85
- } from "./workflow-mcp.js";
81
+ import { supportsStructuredQuestions } from "./workflow-mcp.js";
82
+ import { getUnitWorkflowDispatchReadinessError } from "./tool-contract.js";
86
83
  import {
87
84
  runPreparation,
88
85
  formatCodebaseBrief,
@@ -555,7 +552,7 @@ interface DispatchWorkflowOptions {
555
552
  deps?: {
556
553
  loadPreferences?: typeof loadEffectiveGSDPreferences;
557
554
  selectModel?: typeof selectAndApplyModel;
558
- getTransportSupportError?: typeof getWorkflowTransportSupportError;
555
+ getDispatchReadinessError?: typeof getUnitWorkflowDispatchReadinessError;
559
556
  };
560
557
  }
561
558
 
@@ -584,7 +581,8 @@ async function dispatchWorkflow(
584
581
  const projectRoot = resolveGuidedDispatchProjectRoot(resolvedOptions.basePath);
585
582
  const loadPreferences = resolvedOptions.deps?.loadPreferences ?? loadEffectiveGSDPreferences;
586
583
  const selectModel = resolvedOptions.deps?.selectModel ?? selectAndApplyModel;
587
- const getTransportSupportError = resolvedOptions.deps?.getTransportSupportError ?? getWorkflowTransportSupportError;
584
+ const getDispatchReadinessError = resolvedOptions.deps?.getDispatchReadinessError
585
+ ?? getUnitWorkflowDispatchReadinessError;
588
586
 
589
587
  // Route through the dynamic routing pipeline (complexity classification,
590
588
  // tier downgrade, fallback chains) — same path as auto-mode dispatches (#2958).
@@ -603,25 +601,22 @@ async function dispatchWorkflow(
603
601
  });
604
602
  }
605
603
 
606
- const compatibilityError = getTransportSupportError(
607
- result.appliedModel?.provider ?? ctx.model?.provider,
608
- getRequiredWorkflowToolsForGuidedUnit(unitType),
609
- {
610
- projectRoot,
611
- surface: "guided flow",
612
- unitType,
613
- authMode: result.appliedModel?.provider
614
- ? ctx.modelRegistry.getProviderAuthMode(result.appliedModel.provider)
615
- : ctx.model?.provider
616
- ? ctx.modelRegistry.getProviderAuthMode(ctx.model.provider)
617
- : undefined,
618
- baseUrl: result.appliedModel?.baseUrl ?? ctx.model?.baseUrl,
619
- // Guided flow starts the MCP workflow server as part of dispatch, so the
620
- // parent session's activeTools doesn't include MCP tools yet. The MCP
621
- // launch config check (detectWorkflowMcpLaunchConfig) is the right gate
622
- // here — not whether MCP tools are pre-registered in the parent session.
623
- },
624
- );
604
+ const compatibilityError = getDispatchReadinessError({
605
+ provider: result.appliedModel?.provider ?? ctx.model?.provider,
606
+ projectRoot,
607
+ surface: "guided flow",
608
+ unitType,
609
+ authMode: result.appliedModel?.provider
610
+ ? ctx.modelRegistry.getProviderAuthMode(result.appliedModel.provider)
611
+ : ctx.model?.provider
612
+ ? ctx.modelRegistry.getProviderAuthMode(ctx.model.provider)
613
+ : undefined,
614
+ baseUrl: result.appliedModel?.baseUrl ?? ctx.model?.baseUrl,
615
+ // Guided flow starts the MCP workflow server as part of dispatch, so the
616
+ // parent session's activeTools doesn't include MCP tools yet. The MCP
617
+ // launch config check (detectWorkflowMcpLaunchConfig) is the right gate
618
+ // here not whether MCP tools are pre-registered in the parent session.
619
+ });
625
620
  if (compatibilityError) {
626
621
  ctx.ui.notify(compatibilityError, "error");
627
622
  return;
@@ -5,10 +5,20 @@
5
5
  // - postUnit: git commit, artifact verify, DB settle, then GitHub finalize
6
6
  // - recovery: DB repair from artifacts, then GitHub finalize
7
7
 
8
+ import { existsSync } from "node:fs";
9
+
8
10
  import { loadFile } from "./files.js";
9
11
  import { resolveMilestoneFile } from "./paths.js";
10
- import { getMilestone, getClosedSliceIds, isDbAvailable } from "./gsd-db.js";
12
+ import {
13
+ getMilestone,
14
+ getClosedSliceIds,
15
+ getLatestAssessmentByScope,
16
+ getMilestoneSlices,
17
+ isDbAvailable,
18
+ } from "./gsd-db.js";
11
19
  import { isClosedStatus } from "./status-guards.js";
20
+ import { resolveExpectedArtifactPath } from "./auto-artifact-paths.js";
21
+ import { handleCompleteMilestone } from "./tools/complete-milestone.js";
12
22
  import { runSafely } from "./auto-utils.js";
13
23
  import { extractVerdict, isAcceptableUatVerdict } from "./verdict-parser.js";
14
24
  import { uatSignoffBlockerGuidance } from "./guidance.js";
@@ -28,6 +38,76 @@ import {
28
38
  const COMPLETE_MILESTONE_DB_SETTLE_MS = 1500;
29
39
  const COMPLETE_MILESTONE_DB_SETTLE_POLL_MS = 100;
30
40
 
41
+ /**
42
+ * True when a milestone is terminal for git cleanup (orphaned worktrees, stale branches).
43
+ * DB-authoritative (ADR-017): closed status, or validation-pass with all slices closed.
44
+ * When the DB is unavailable we cannot make this decision and conservatively
45
+ * return false so callers leave the worktree/branch alone instead of cleaning
46
+ * up based on parsed projections.
47
+ */
48
+ export async function isCompletedMilestoneTerminal(
49
+ _basePath: string,
50
+ milestoneId: string,
51
+ ): Promise<boolean> {
52
+ if (!isDbAvailable()) return false;
53
+
54
+ const milestone = getMilestone(milestoneId);
55
+ if (!milestone) return false;
56
+
57
+ if (isClosedStatus(milestone.status)) {
58
+ return true;
59
+ }
60
+
61
+ const validation = getLatestAssessmentByScope(milestoneId, "milestone-validation");
62
+ if (validation?.status !== "pass") {
63
+ return false;
64
+ }
65
+
66
+ const slices = getMilestoneSlices(milestoneId);
67
+ if (slices.length === 0) return false;
68
+ return slices.every((slice) => isClosedStatus(slice.status));
69
+ }
70
+
71
+ /** Write a missing milestone SUMMARY projection when canonical DB closeout already settled. */
72
+ export async function repairMissingMilestoneSummaryProjection(
73
+ basePath: string,
74
+ milestoneId: string,
75
+ ): Promise<{ ok: true } | { ok: false; error: string }> {
76
+ const milestone = getMilestone(milestoneId);
77
+ if (!milestone) {
78
+ return { ok: false, error: `milestone not found: ${milestoneId}` };
79
+ }
80
+
81
+ const artifactBasePath = resolveCanonicalMilestoneRoot(basePath, milestoneId);
82
+ const summaryPath = resolveExpectedArtifactPath("complete-milestone", milestoneId, artifactBasePath);
83
+ if (summaryPath && existsSync(summaryPath)) {
84
+ return { ok: true };
85
+ }
86
+
87
+ const result = await handleCompleteMilestone(
88
+ {
89
+ milestoneId,
90
+ title: milestone.title,
91
+ oneLiner: "Canonical closeout completed; summary projection repaired automatically.",
92
+ narrative:
93
+ "The workflow database recorded this milestone as complete, but the milestone SUMMARY artifact was missing on disk. " +
94
+ "Dispatch policy repaired the projection so closeout proof and cleanup can proceed.",
95
+ verificationPassed: true,
96
+ triggerReason: "closeout-projection-repair",
97
+ },
98
+ basePath,
99
+ );
100
+
101
+ if ("error" in result) {
102
+ return { ok: false, error: result.error };
103
+ }
104
+ const writtenSummaryPath = result.summaryPath;
105
+ if (result.stale || !writtenSummaryPath || !existsSync(writtenSummaryPath)) {
106
+ return { ok: false, error: "milestone SUMMARY projection write failed" };
107
+ }
108
+ return { ok: true };
109
+ }
110
+
31
111
  /**
32
112
  * True when the milestone is closed in the DB and the completion summary artifact exists.
33
113
  * Polls briefly so post-unit verification can observe the tool's DB write.
@@ -78,7 +158,22 @@ export async function evaluateCompleteMilestoneDispatch(
78
158
  if (isDbAvailable()) {
79
159
  const milestone = getMilestone(mid);
80
160
  if (milestone && isClosedStatus(milestone.status)) {
81
- return { action: "skip" };
161
+ const artifactBasePath = resolveCanonicalMilestoneRoot(basePath, mid);
162
+ const summaryPath = resolveExpectedArtifactPath("complete-milestone", mid, artifactBasePath);
163
+ const summaryMissing = !summaryPath || !existsSync(summaryPath);
164
+ if (summaryMissing) {
165
+ const repair = await repairMissingMilestoneSummaryProjection(basePath, mid);
166
+ if (!repair.ok) {
167
+ logWarning(
168
+ "dispatch",
169
+ `Milestone ${mid} is closed in DB but SUMMARY repair failed: ${repair.error}. Dispatching complete-milestone to retry.`,
170
+ );
171
+ } else {
172
+ return { action: "skip" };
173
+ }
174
+ } else {
175
+ return { action: "skip" };
176
+ }
82
177
  }
83
178
  }
84
179
 
@@ -15,7 +15,7 @@ import {
15
15
  } from "./gsd-db.js";
16
16
  import { invalidateStateCache } from "./state.js";
17
17
  import { renderRoadmapFromDb } from "./markdown-renderer.js";
18
- import { renderAllProjections } from "./workflow-projections.js";
18
+ import { flushWorkflowProjections } from "./projection-flush.js";
19
19
  import { writeManifest } from "./workflow-manifest.js";
20
20
  import { appendEvent } from "./workflow-events.js";
21
21
  import { logWarning } from "./workflow-logger.js";
@@ -171,7 +171,7 @@ async function renderPlanArtifacts(
171
171
 
172
172
  async function runPostPlanHooks(basePath: string, params: PersistMilestonePlanParams): Promise<void> {
173
173
  try {
174
- await renderAllProjections(basePath, params.milestoneId);
174
+ await flushWorkflowProjections(basePath, { milestoneId: params.milestoneId });
175
175
  writeManifest(basePath);
176
176
  appendEvent(basePath, {
177
177
  cmd: "plan-milestone",
@@ -0,0 +1,20 @@
1
+ // Project/App: gsd-pi
2
+ // File Purpose: Single workflow projection flush seam for mutation exits.
3
+
4
+ import { renderAllProjections } from "./workflow-projections.js";
5
+
6
+ export interface ProjectionFlushScope {
7
+ milestoneId: string;
8
+ }
9
+
10
+ export interface ProjectionFlushResult {
11
+ milestoneId: string;
12
+ }
13
+
14
+ export async function flushWorkflowProjections(
15
+ basePath: string,
16
+ scope: ProjectionFlushScope,
17
+ ): Promise<ProjectionFlushResult> {
18
+ await renderAllProjections(basePath, scope.milestoneId);
19
+ return { milestoneId: scope.milestoneId };
20
+ }
@@ -49,4 +49,4 @@ Use `subagent` only when useful: reviewer, security, or tester. Apply findings b
49
49
 
50
50
  **You MUST call `gsd_slice_complete` with summary and UAT content only after verification passes.**
51
51
 
52
- When done, say: "Slice {{sliceId}} complete."
52
+ When done, say: "Slice {{sliceId}} complete." Say this exactly once — if you already said it in a prior message, do not repeat it.
@@ -76,4 +76,4 @@ Keep about **{{verificationBudget}}** for verification and summary. If context i
76
76
 
77
77
  **You MUST call `gsd_task_complete` before finishing, including when the stale-path safety rule stops execution.**
78
78
 
79
- When done, say: "Task {{taskId}} complete."
79
+ When done, say: "Task {{taskId}} complete." Say this exactly once — if you already said it in a prior message, do not repeat it.
@@ -118,4 +118,4 @@ If external API keys or secrets are required, use the inlined **Secrets Manifest
118
118
 
119
119
  If no external API keys or secrets are required, skip this step; do not create an empty manifest.
120
120
 
121
- When done, say: "Milestone {{milestoneId}} planned."
121
+ When done, say: "Milestone {{milestoneId}} planned." Say this exactly once — if you already said it in a prior message, do not repeat it.
@@ -57,4 +57,4 @@ The slice directory already exists. Do not mkdir.
57
57
 
58
58
  **You MUST call `gsd_plan_slice` to persist planning state before finishing, unless the stale-path safety rule above stops the unit before safe planning can occur.**
59
59
 
60
- When done, say: "Slice {{sliceId}} planned."
60
+ When done, say: "Slice {{sliceId}} planned." Say this exactly once — if you already said it in a prior message, do not repeat it.
@@ -37,4 +37,4 @@ You are executing a GSD quick task — a lightweight, focused unit of work outsi
37
37
  - <what was tested/verified>
38
38
  ```
39
39
 
40
- When done, say: "Quick task {{taskNum}} complete."
40
+ When done, say: "Quick task {{taskNum}} complete." Say this exactly once — if you already said it in a prior message, do not repeat it.
@@ -67,4 +67,4 @@ If `.gsd/REQUIREMENTS.md` exists and requirement ownership or status changed, up
67
67
 
68
68
  **DB access safety:** Do NOT query `.gsd/gsd.db` directly via `sqlite3` or `node -e require('better-sqlite3')`. Use `gsd_milestone_status` to read current milestone and slice state. All roadmap mutations go through `gsd_reassess_roadmap` — the tool writes to the DB and re-renders ROADMAP.md atomically.
69
69
 
70
- When done, say: "Roadmap reassessed."
70
+ When done, say: "Roadmap reassessed." Say this exactly once — if you already said it in a prior message, do not repeat it.
@@ -77,4 +77,4 @@ The slice directory and tasks/ subdirectory already exist. Do NOT mkdir.
77
77
 
78
78
  **You MUST call `gsd_plan_slice` to persist planning state before finishing.** After success, the pipeline clears the sketch flag on next state derivation; the on-disk PLAN file is the signal.
79
79
 
80
- When done, say: "Slice {{sliceId}} refined."
80
+ When done, say: "Slice {{sliceId}} refined." Say this exactly once — if you already said it in a prior message, do not repeat it.
@@ -38,4 +38,4 @@ Consider these captures when rewriting the remaining tasks — they represent th
38
38
  4. If any incomplete task had a `T0x-PLAN.md`, remove or rewrite it to match the new task description.
39
39
  5. Do not commit manually — the system auto-commits your changes after this unit completes.
40
40
 
41
- When done, say: "Slice {{sliceId}} replanned."
41
+ When done, say: "Slice {{sliceId}} replanned." Say this exactly once — if you already said it in a prior message, do not repeat it.
@@ -46,4 +46,4 @@ Then research the codebase and relevant technologies. Narrate key findings and s
46
46
 
47
47
  **You MUST call `gsd_summary_save` with the research content before finishing.**
48
48
 
49
- When done, say: "Milestone {{milestoneId}} researched."
49
+ When done, say: "Milestone {{milestoneId}} researched." Say this exactly once — if you already said it in a prior message, do not repeat it.
@@ -55,4 +55,4 @@ The slice directory already exists at `{{slicePath}}/`. Do NOT mkdir.
55
55
 
56
56
  **You MUST call `gsd_summary_save` with the research content before finishing.**
57
57
 
58
- When done, say: "Slice {{sliceId}} researched."
58
+ When done, say: "Slice {{sliceId}} researched." Say this exactly once — if you already said it in a prior message, do not repeat it.
@@ -30,4 +30,4 @@ An override was issued by the user that changes a fundamental decision or approa
30
30
 
31
31
  **You MUST update the relevant documents AND mark overrides as resolved in `{{overridesPath}}` before finishing.**
32
32
 
33
- When done, say: "Override applied across all documents."
33
+ When done, say: "Override applied across all documents." Say this exactly once — if you already said it in a prior message, do not repeat it.
@@ -100,4 +100,4 @@ checks: [{
100
100
 
101
101
  **You MUST call `gsd_uat_result_save` before finishing. Do not write the assessment file directly, and do not call `gsd_summary_save` as a substitute.**
102
102
 
103
- When done, say: "UAT {{sliceId}} complete."
103
+ When done, say: "UAT {{sliceId}} complete." Say this exactly once — if you already said it in a prior message, do not repeat it.
@@ -65,4 +65,4 @@ Classify each capture as one of:
65
65
 
66
66
  **Important:** Do NOT execute any resolutions. Only classify and update CAPTURES.md. Resolution execution happens separately (in auto-mode dispatch or manually by the user).
67
67
 
68
- When done, say: "Triage complete."
68
+ When done, say: "Triage complete." Say this exactly once — if you already said it in a prior message, do not repeat it.
@@ -87,4 +87,4 @@ If verdict is `needs-remediation`:
87
87
 
88
88
  **File system safety:** When scanning milestone directories, use `ls` or `find` first. Never pass a directory path such as `tasks/` or `slices/` directly to `read`; it only accepts files.
89
89
 
90
- When done, say: "Milestone {{milestoneId}} validation complete — verdict: <verdict>."
90
+ When done, say: "Milestone {{milestoneId}} validation complete — verdict: <verdict>." Say this exactly once — if you already said it in a prior message, do not repeat it.