gsd-pi 2.66.1-dev.0df32ec → 2.66.1-dev.3cea7ac

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 (230) hide show
  1. package/dist/resources/extensions/ask-user-questions.js +79 -11
  2. package/dist/resources/extensions/claude-code-cli/partial-builder.js +4 -3
  3. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +10 -3
  4. package/dist/resources/extensions/gsd/auto/loop.js +13 -1
  5. package/dist/resources/extensions/gsd/auto/phases.js +10 -4
  6. package/dist/resources/extensions/gsd/auto/run-unit.js +10 -2
  7. package/dist/resources/extensions/gsd/auto/session.js +1 -1
  8. package/dist/resources/extensions/gsd/auto-dashboard.js +65 -15
  9. package/dist/resources/extensions/gsd/auto-dispatch.js +30 -28
  10. package/dist/resources/extensions/gsd/auto-prompts.js +6 -6
  11. package/dist/resources/extensions/gsd/auto-recovery.js +11 -12
  12. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +18 -6
  13. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +59 -5
  14. package/dist/resources/extensions/gsd/bootstrap/register-shortcuts.js +8 -5
  15. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +186 -14
  16. package/dist/resources/extensions/gsd/codebase-generator.js +4 -0
  17. package/dist/resources/extensions/gsd/commands/handlers/core.js +3 -3
  18. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +10 -4
  19. package/dist/resources/extensions/gsd/custom-workflow-engine.js +3 -1
  20. package/dist/resources/extensions/gsd/detection.js +6 -0
  21. package/dist/resources/extensions/gsd/files.js +19 -2
  22. package/dist/resources/extensions/gsd/guided-flow.js +12 -8
  23. package/dist/resources/extensions/gsd/index.js +1 -1
  24. package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +2 -0
  25. package/dist/resources/extensions/gsd/parsers-legacy.js +3 -1
  26. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
  27. package/dist/resources/extensions/gsd/prompts/discuss-prepared.md +7 -7
  28. package/dist/resources/extensions/gsd/prompts/discuss.md +3 -3
  29. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +3 -3
  30. package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +3 -1
  31. package/dist/resources/extensions/gsd/prompts/rethink.md +6 -2
  32. package/dist/resources/extensions/gsd/prompts/system.md +1 -1
  33. package/dist/resources/extensions/gsd/prompts/triage-captures.md +1 -1
  34. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +4 -4
  35. package/dist/resources/extensions/gsd/prompts/worktree-merge.md +3 -1
  36. package/dist/resources/extensions/gsd/safety/file-change-validator.js +2 -1
  37. package/dist/resources/extensions/gsd/state.js +2 -1
  38. package/dist/resources/extensions/gsd/visualizer-overlay.js +27 -26
  39. package/dist/resources/extensions/gsd/workflow-reconcile.js +46 -7
  40. package/dist/resources/extensions/remote-questions/manager.js +8 -0
  41. package/dist/resources/extensions/shared/interview-ui.js +10 -0
  42. package/dist/web/standalone/.next/BUILD_ID +1 -1
  43. package/dist/web/standalone/.next/app-path-routes-manifest.json +17 -17
  44. package/dist/web/standalone/.next/build-manifest.json +2 -2
  45. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  46. package/dist/web/standalone/.next/required-server-files.json +1 -1
  47. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  48. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  49. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  50. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  51. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  52. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  53. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  54. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  55. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  56. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  57. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  58. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  59. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  60. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  61. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  62. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  63. package/dist/web/standalone/.next/server/app/index.html +1 -1
  64. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  65. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  66. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  67. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  68. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  69. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  70. package/dist/web/standalone/.next/server/app-paths-manifest.json +17 -17
  71. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  72. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  73. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  74. package/dist/web/standalone/server.js +1 -1
  75. package/package.json +1 -1
  76. package/packages/pi-ai/dist/providers/anthropic-shared.d.ts.map +1 -1
  77. package/packages/pi-ai/dist/providers/anthropic-shared.js +4 -3
  78. package/packages/pi-ai/dist/providers/anthropic-shared.js.map +1 -1
  79. package/packages/pi-ai/dist/utils/json-parse.d.ts.map +1 -1
  80. package/packages/pi-ai/dist/utils/json-parse.js +11 -1
  81. package/packages/pi-ai/dist/utils/json-parse.js.map +1 -1
  82. package/packages/pi-ai/dist/utils/repair-tool-json.d.ts.map +1 -1
  83. package/packages/pi-ai/dist/utils/repair-tool-json.js +60 -1
  84. package/packages/pi-ai/dist/utils/repair-tool-json.js.map +1 -1
  85. package/packages/pi-ai/dist/utils/tests/json-parse.test.d.ts +2 -0
  86. package/packages/pi-ai/dist/utils/tests/json-parse.test.d.ts.map +1 -0
  87. package/packages/pi-ai/dist/utils/tests/json-parse.test.js +14 -0
  88. package/packages/pi-ai/dist/utils/tests/json-parse.test.js.map +1 -0
  89. package/packages/pi-ai/dist/utils/tests/repair-tool-json.test.js +10 -0
  90. package/packages/pi-ai/dist/utils/tests/repair-tool-json.test.js.map +1 -1
  91. package/packages/pi-ai/src/providers/anthropic-shared.ts +4 -3
  92. package/packages/pi-ai/src/utils/json-parse.ts +11 -1
  93. package/packages/pi-ai/src/utils/repair-tool-json.ts +69 -1
  94. package/packages/pi-ai/src/utils/tests/json-parse.test.ts +17 -0
  95. package/packages/pi-ai/src/utils/tests/repair-tool-json.test.ts +13 -0
  96. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/provider-display-name.test.d.ts +2 -0
  97. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/provider-display-name.test.d.ts.map +1 -0
  98. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/provider-display-name.test.js +17 -0
  99. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/provider-display-name.test.js.map +1 -0
  100. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts.map +1 -1
  101. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js +2 -1
  102. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js.map +1 -1
  103. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.d.ts +1 -0
  104. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  105. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js +9 -2
  106. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js.map +1 -1
  107. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.d.ts.map +1 -1
  108. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js +2 -1
  109. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js.map +1 -1
  110. package/packages/pi-coding-agent/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
  111. package/packages/pi-coding-agent/dist/modes/interactive/components/scoped-models-selector.js +2 -1
  112. package/packages/pi-coding-agent/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
  113. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +2 -2
  114. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  115. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/provider-display-name.test.ts +18 -0
  116. package/packages/pi-coding-agent/src/modes/interactive/components/footer.ts +2 -1
  117. package/packages/pi-coding-agent/src/modes/interactive/components/model-selector.ts +11 -2
  118. package/packages/pi-coding-agent/src/modes/interactive/components/provider-manager.ts +2 -1
  119. package/packages/pi-coding-agent/src/modes/interactive/components/scoped-models-selector.ts +2 -1
  120. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +2 -2
  121. package/packages/pi-tui/dist/__tests__/autocomplete.test.js +13 -0
  122. package/packages/pi-tui/dist/__tests__/autocomplete.test.js.map +1 -1
  123. package/packages/pi-tui/dist/__tests__/stdin-buffer.test.d.ts +2 -0
  124. package/packages/pi-tui/dist/__tests__/stdin-buffer.test.d.ts.map +1 -0
  125. package/packages/pi-tui/dist/__tests__/stdin-buffer.test.js +35 -0
  126. package/packages/pi-tui/dist/__tests__/stdin-buffer.test.js.map +1 -0
  127. package/packages/pi-tui/dist/__tests__/tui.test.d.ts +2 -0
  128. package/packages/pi-tui/dist/__tests__/tui.test.d.ts.map +1 -0
  129. package/packages/pi-tui/dist/__tests__/tui.test.js +43 -0
  130. package/packages/pi-tui/dist/__tests__/tui.test.js.map +1 -0
  131. package/packages/pi-tui/dist/autocomplete.d.ts.map +1 -1
  132. package/packages/pi-tui/dist/autocomplete.js +9 -7
  133. package/packages/pi-tui/dist/autocomplete.js.map +1 -1
  134. package/packages/pi-tui/dist/components/__tests__/editor.test.d.ts +2 -0
  135. package/packages/pi-tui/dist/components/__tests__/editor.test.d.ts.map +1 -0
  136. package/packages/pi-tui/dist/components/__tests__/editor.test.js +54 -0
  137. package/packages/pi-tui/dist/components/__tests__/editor.test.js.map +1 -0
  138. package/packages/pi-tui/dist/components/editor.d.ts +3 -1
  139. package/packages/pi-tui/dist/components/editor.d.ts.map +1 -1
  140. package/packages/pi-tui/dist/components/editor.js +14 -3
  141. package/packages/pi-tui/dist/components/editor.js.map +1 -1
  142. package/packages/pi-tui/dist/stdin-buffer.d.ts.map +1 -1
  143. package/packages/pi-tui/dist/stdin-buffer.js +6 -0
  144. package/packages/pi-tui/dist/stdin-buffer.js.map +1 -1
  145. package/packages/pi-tui/dist/tui.d.ts.map +1 -1
  146. package/packages/pi-tui/dist/tui.js +8 -0
  147. package/packages/pi-tui/dist/tui.js.map +1 -1
  148. package/packages/pi-tui/src/__tests__/autocomplete.test.ts +15 -0
  149. package/packages/pi-tui/src/__tests__/stdin-buffer.test.ts +43 -0
  150. package/packages/pi-tui/src/__tests__/tui.test.ts +50 -0
  151. package/packages/pi-tui/src/autocomplete.ts +9 -7
  152. package/packages/pi-tui/src/components/__tests__/editor.test.ts +64 -0
  153. package/packages/pi-tui/src/components/editor.ts +14 -3
  154. package/packages/pi-tui/src/stdin-buffer.ts +7 -0
  155. package/packages/pi-tui/src/tui.ts +9 -0
  156. package/src/resources/extensions/ask-user-questions.ts +103 -11
  157. package/src/resources/extensions/claude-code-cli/partial-builder.ts +4 -3
  158. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +12 -3
  159. package/src/resources/extensions/claude-code-cli/tests/partial-builder.test.ts +17 -0
  160. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +18 -0
  161. package/src/resources/extensions/gsd/auto/loop-deps.ts +2 -1
  162. package/src/resources/extensions/gsd/auto/loop.ts +14 -1
  163. package/src/resources/extensions/gsd/auto/phases.ts +10 -5
  164. package/src/resources/extensions/gsd/auto/run-unit.ts +14 -2
  165. package/src/resources/extensions/gsd/auto/session.ts +1 -1
  166. package/src/resources/extensions/gsd/auto-dashboard.ts +76 -16
  167. package/src/resources/extensions/gsd/auto-dispatch.ts +36 -35
  168. package/src/resources/extensions/gsd/auto-prompts.ts +5 -6
  169. package/src/resources/extensions/gsd/auto-recovery.ts +15 -15
  170. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +27 -6
  171. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +67 -6
  172. package/src/resources/extensions/gsd/bootstrap/register-shortcuts.ts +11 -8
  173. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +209 -16
  174. package/src/resources/extensions/gsd/codebase-generator.ts +4 -0
  175. package/src/resources/extensions/gsd/commands/handlers/core.ts +6 -6
  176. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +11 -4
  177. package/src/resources/extensions/gsd/custom-workflow-engine.ts +3 -1
  178. package/src/resources/extensions/gsd/detection.ts +6 -0
  179. package/src/resources/extensions/gsd/files.ts +21 -2
  180. package/src/resources/extensions/gsd/guided-flow.ts +15 -8
  181. package/src/resources/extensions/gsd/index.ts +6 -0
  182. package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +2 -0
  183. package/src/resources/extensions/gsd/parsers-legacy.ts +3 -1
  184. package/src/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
  185. package/src/resources/extensions/gsd/prompts/discuss-prepared.md +7 -7
  186. package/src/resources/extensions/gsd/prompts/discuss.md +3 -3
  187. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +3 -3
  188. package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +3 -1
  189. package/src/resources/extensions/gsd/prompts/rethink.md +6 -2
  190. package/src/resources/extensions/gsd/prompts/system.md +1 -1
  191. package/src/resources/extensions/gsd/prompts/triage-captures.md +1 -1
  192. package/src/resources/extensions/gsd/prompts/validate-milestone.md +4 -4
  193. package/src/resources/extensions/gsd/prompts/worktree-merge.md +3 -1
  194. package/src/resources/extensions/gsd/safety/file-change-validator.ts +4 -1
  195. package/src/resources/extensions/gsd/state.ts +2 -1
  196. package/src/resources/extensions/gsd/tests/auto-dashboard.test.ts +52 -1
  197. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +50 -2
  198. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +48 -0
  199. package/src/resources/extensions/gsd/tests/codebase-generator.test.ts +22 -0
  200. package/src/resources/extensions/gsd/tests/core-overlay-fallback.test.ts +44 -0
  201. package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +7 -1
  202. package/src/resources/extensions/gsd/tests/custom-workflow-engine.test.ts +31 -0
  203. package/src/resources/extensions/gsd/tests/detection.test.ts +37 -0
  204. package/src/resources/extensions/gsd/tests/file-change-validator.test.ts +50 -0
  205. package/src/resources/extensions/gsd/tests/gsd-tools.test.ts +35 -0
  206. package/src/resources/extensions/gsd/tests/guided-flow-session-isolation.test.ts +34 -0
  207. package/src/resources/extensions/gsd/tests/health-widget.test.ts +45 -0
  208. package/src/resources/extensions/gsd/tests/integration/auto-recovery.test.ts +53 -13
  209. package/src/resources/extensions/gsd/tests/integration/state-machine-runtime-failures.test.ts +2 -2
  210. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +1 -1
  211. package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +3 -4
  212. package/src/resources/extensions/gsd/tests/parallel-monitor-overlay.test.ts +21 -0
  213. package/src/resources/extensions/gsd/tests/parallel-research-dispatch.test.ts +71 -2
  214. package/src/resources/extensions/gsd/tests/parsers.test.ts +25 -0
  215. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +8 -1
  216. package/src/resources/extensions/gsd/tests/queue-execution-guard.test.ts +9 -0
  217. package/src/resources/extensions/gsd/tests/reactive-graph.test.ts +19 -0
  218. package/src/resources/extensions/gsd/tests/register-shortcuts.test.ts +73 -0
  219. package/src/resources/extensions/gsd/tests/remote-questions.test.ts +98 -0
  220. package/src/resources/extensions/gsd/tests/smart-entry-complete.test.ts +2 -2
  221. package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +26 -0
  222. package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +59 -0
  223. package/src/resources/extensions/gsd/tests/workflow-reconcile.test.ts +91 -0
  224. package/src/resources/extensions/gsd/tests/write-gate.test.ts +210 -35
  225. package/src/resources/extensions/gsd/visualizer-overlay.ts +31 -27
  226. package/src/resources/extensions/gsd/workflow-reconcile.ts +59 -8
  227. package/src/resources/extensions/remote-questions/manager.ts +9 -0
  228. package/src/resources/extensions/shared/interview-ui.ts +13 -0
  229. /package/dist/web/standalone/.next/static/{Zw5aZFHFtOwjJSOsINh1m → HxFcJ8GrYNPsg9ARz7GPz}/_buildManifest.js +0 -0
  230. /package/dist/web/standalone/.next/static/{Zw5aZFHFtOwjJSOsINh1m → HxFcJ8GrYNPsg9ARz7GPz}/_ssgManifest.js +0 -0
@@ -3,7 +3,7 @@ import { isToolCallEventType } from "@gsd/pi-coding-agent";
3
3
  import { buildMilestoneFileName, resolveMilestonePath, resolveSliceFile, resolveSlicePath } from "../paths.js";
4
4
  import { buildBeforeAgentStartResult } from "./system-context.js";
5
5
  import { handleAgentEnd } from "./agent-end-recovery.js";
6
- import { clearDiscussionFlowState, isDepthVerified, isDepthConfirmationAnswer, isQueuePhaseActive, markDepthVerified, resetWriteGateState, shouldBlockContextWrite, shouldBlockQueueExecution } from "./write-gate.js";
6
+ import { clearDiscussionFlowState, isDepthConfirmationAnswer, isQueuePhaseActive, markDepthVerified, resetWriteGateState, shouldBlockContextWrite, shouldBlockQueueExecution, isGateQuestionId, setPendingGate, clearPendingGate, getPendingGate, shouldBlockPendingGate, shouldBlockPendingGateBash, extractDepthVerificationMilestoneId } from "./write-gate.js";
7
7
  import { isBlockedStateFile, isBashWriteToStateFile, BLOCKED_WRITE_ERROR } from "../write-intercept.js";
8
8
  import { cleanupQuickBranch } from "../quick.js";
9
9
  import { getDiscussionMilestoneId } from "../guided-flow.js";
@@ -21,6 +21,7 @@ import { logWarning as safetyLogWarning } from "../workflow-logger.js";
21
21
  import { installNotifyInterceptor } from "./notify-interceptor.js";
22
22
  import { initNotificationStore } from "../notification-store.js";
23
23
  import { initNotificationWidget } from "../notification-widget.js";
24
+ import { initHealthWidget } from "../health-widget.js";
24
25
  // Skip the welcome screen on the very first session_start — cli.ts already
25
26
  // printed it before the TUI launched. Only re-print on /clear (subsequent sessions).
26
27
  let isFirstSession = true;
@@ -33,6 +34,7 @@ export function registerHooks(pi) {
33
34
  initNotificationStore(process.cwd());
34
35
  installNotifyInterceptor(ctx);
35
36
  initNotificationWidget(ctx);
37
+ initHealthWidget(ctx);
36
38
  resetWriteGateState();
37
39
  resetToolCallLoopGuard();
38
40
  resetAskUserQuestionsCache();
@@ -154,11 +156,42 @@ export function registerHooks(pi) {
154
156
  }
155
157
  });
156
158
  pi.on("tool_call", async (event) => {
159
+ const discussionBasePath = process.cwd();
157
160
  // ── Loop guard: block repeated identical tool calls ──
158
161
  const loopCheck = checkToolCallLoop(event.toolName, event.input);
159
162
  if (loopCheck.block) {
160
163
  return { block: true, reason: loopCheck.reason };
161
164
  }
165
+ // ── Discussion gate enforcement: track pending gate questions ─────────
166
+ // Only gate-shaped ask_user_questions calls should block execution.
167
+ // The gate stays pending until the user selects the approval option.
168
+ if (event.toolName === "ask_user_questions") {
169
+ const milestoneId = getDiscussionMilestoneId(discussionBasePath);
170
+ const inDiscussion = milestoneId !== null || isQueuePhaseActive();
171
+ if (inDiscussion) {
172
+ const questions = event.input?.questions ?? [];
173
+ const questionId = questions.find((question) => typeof question?.id === "string" && isGateQuestionId(question.id))?.id;
174
+ if (typeof questionId === "string") {
175
+ setPendingGate(questionId);
176
+ }
177
+ }
178
+ }
179
+ // ── Discussion gate enforcement: block tool calls while gate is pending ──
180
+ // If ask_user_questions was called with a gate ID but hasn't been confirmed,
181
+ // block all non-read-only tool calls to prevent the model from skipping gates.
182
+ if (getPendingGate()) {
183
+ const milestoneId = getDiscussionMilestoneId(discussionBasePath);
184
+ if (isToolCallEventType("bash", event)) {
185
+ const bashGuard = shouldBlockPendingGateBash(event.input.command, milestoneId, isQueuePhaseActive());
186
+ if (bashGuard.block)
187
+ return bashGuard;
188
+ }
189
+ else {
190
+ const gateGuard = shouldBlockPendingGate(event.toolName, milestoneId, isQueuePhaseActive());
191
+ if (gateGuard.block)
192
+ return gateGuard;
193
+ }
194
+ }
162
195
  // ── Queue-mode execution guard (#2545): block source-code mutations ──
163
196
  // When /gsd queue is active, the agent should only create milestones,
164
197
  // not execute work. Block write/edit to non-.gsd/ paths and bash commands
@@ -197,7 +230,7 @@ export function registerHooks(pi) {
197
230
  }
198
231
  if (!isToolCallEventType("write", event))
199
232
  return;
200
- const result = shouldBlockContextWrite(event.toolName, event.input.path, getDiscussionMilestoneId(), isDepthVerified(), isQueuePhaseActive());
233
+ const result = shouldBlockContextWrite(event.toolName, event.input.path, getDiscussionMilestoneId(discussionBasePath), isQueuePhaseActive());
201
234
  if (result.block)
202
235
  return result;
203
236
  });
@@ -220,21 +253,42 @@ export function registerHooks(pi) {
220
253
  pi.on("tool_result", async (event) => {
221
254
  if (event.toolName !== "ask_user_questions")
222
255
  return;
223
- const milestoneId = getDiscussionMilestoneId();
256
+ const milestoneId = getDiscussionMilestoneId(process.cwd());
224
257
  const queueActive = isQueuePhaseActive();
225
258
  if (!milestoneId && !queueActive)
226
259
  return;
227
260
  const details = event.details;
261
+ // ── Discussion gate enforcement: handle gate question responses ──
262
+ // If the result is cancelled or has no response, the pending gate stays active
263
+ // so the model is blocked from non-read-only tools until it re-asks.
264
+ // If the user responded at all (even "needs adjustment"), clear the pending gate
265
+ // because the user engaged — the prompt handles the re-ask-after-adjustment flow.
266
+ const questions = event.input?.questions ?? [];
267
+ const currentPendingGate = getPendingGate();
268
+ if (currentPendingGate) {
269
+ if (details?.cancelled || !details?.response) {
270
+ // Gate stays pending — model will be blocked from non-read-only tools
271
+ // until it re-asks and gets a valid response
272
+ }
273
+ else {
274
+ const pendingQuestion = questions.find((question) => question?.id === currentPendingGate);
275
+ if (pendingQuestion) {
276
+ const answer = details.response?.answers?.[currentPendingGate];
277
+ if (isDepthConfirmationAnswer(answer?.selected, pendingQuestion.options)) {
278
+ clearPendingGate();
279
+ }
280
+ }
281
+ }
282
+ }
228
283
  if (details?.cancelled || !details?.response)
229
284
  return;
230
- const questions = event.input?.questions ?? [];
231
285
  for (const question of questions) {
232
286
  if (typeof question.id === "string" && question.id.includes("depth_verification")) {
233
287
  // Only unlock the gate if the user selected the first option (confirmation).
234
288
  // Cross-references against the question's defined options to reject free-form "Other" text.
235
289
  const answer = details.response?.answers?.[question.id];
236
290
  if (isDepthConfirmationAnswer(answer?.selected, question.options)) {
237
- markDepthVerified();
291
+ markDepthVerified(extractDepthVerificationMilestoneId(question.id) ?? milestoneId);
238
292
  }
239
293
  break;
240
294
  }
@@ -4,16 +4,18 @@ import { Key } from "@gsd/pi-tui";
4
4
  import { GSDDashboardOverlay } from "../dashboard-overlay.js";
5
5
  import { GSDNotificationOverlay } from "../notification-overlay.js";
6
6
  import { ParallelMonitorOverlay } from "../parallel-monitor-overlay.js";
7
+ import { projectRoot } from "../commands/context.js";
7
8
  import { shortcutDesc } from "../../shared/mod.js";
8
9
  export function registerShortcuts(pi) {
9
10
  pi.registerShortcut(Key.ctrlAlt("g"), {
10
11
  description: shortcutDesc("Open GSD dashboard", "/gsd status"),
11
12
  handler: async (ctx) => {
12
- if (!existsSync(join(process.cwd(), ".gsd"))) {
13
+ const basePath = projectRoot();
14
+ if (!existsSync(join(basePath, ".gsd"))) {
13
15
  ctx.ui.notify("No .gsd/ directory found. Run /gsd to start.", "info");
14
16
  return;
15
17
  }
16
- await ctx.ui.custom((tui, theme, _kb, done) => new GSDDashboardOverlay(tui, theme, () => done()), {
18
+ await ctx.ui.custom((tui, theme, _kb, done) => new GSDDashboardOverlay(tui, theme, () => done(true)), {
17
19
  overlay: true,
18
20
  overlayOptions: {
19
21
  width: "90%",
@@ -27,7 +29,7 @@ export function registerShortcuts(pi) {
27
29
  pi.registerShortcut(Key.ctrlAlt("n"), {
28
30
  description: shortcutDesc("Open notification history", "/gsd notifications"),
29
31
  handler: async (ctx) => {
30
- await ctx.ui.custom((tui, theme, _kb, done) => new GSDNotificationOverlay(tui, theme, () => done()), {
32
+ await ctx.ui.custom((tui, theme, _kb, done) => new GSDNotificationOverlay(tui, theme, () => done(true)), {
31
33
  overlay: true,
32
34
  overlayOptions: {
33
35
  width: "80%",
@@ -42,12 +44,13 @@ export function registerShortcuts(pi) {
42
44
  pi.registerShortcut(Key.ctrlAlt("p"), {
43
45
  description: shortcutDesc("Open parallel worker monitor", "/gsd parallel watch"),
44
46
  handler: async (ctx) => {
45
- const parallelDir = join(process.cwd(), ".gsd", "parallel");
47
+ const basePath = projectRoot();
48
+ const parallelDir = join(basePath, ".gsd", "parallel");
46
49
  if (!existsSync(parallelDir)) {
47
50
  ctx.ui.notify("No parallel workers found. Run /gsd parallel start first.", "info");
48
51
  return;
49
52
  }
50
- await ctx.ui.custom((tui, theme, _kb, done) => new ParallelMonitorOverlay(tui, theme, () => done()), {
53
+ await ctx.ui.custom((tui, theme, _kb, done) => new ParallelMonitorOverlay(tui, theme, () => done(true)), {
51
54
  overlay: true,
52
55
  overlayOptions: {
53
56
  width: "90%",
@@ -1,4 +1,6 @@
1
1
  const MILESTONE_CONTEXT_RE = /M\d+(?:-[a-z0-9]{6})?-CONTEXT\.md$/;
2
+ const CONTEXT_MILESTONE_RE = /(?:^|[/\\])(M\d+(?:-[a-z0-9]{6})?)-CONTEXT\.md$/i;
3
+ const DEPTH_VERIFICATION_MILESTONE_RE = /depth_verification[_-](M\d+(?:-[a-z0-9]{6})?)/i;
2
4
  /**
3
5
  * Path segment that identifies .gsd/ planning artifacts.
4
6
  * Writes to these paths are allowed during queue mode.
@@ -22,10 +24,49 @@ const QUEUE_SAFE_TOOLS = new Set([
22
24
  * Matches the leading command in a bash invocation.
23
25
  */
24
26
  const BASH_READ_ONLY_RE = /^\s*(cat|head|tail|less|more|wc|file|stat|du|df|which|type|echo|printf|ls|find|grep|rg|awk|sed\b(?!.*-i)|sort|uniq|diff|comm|tr|cut|tee\s+-a\s+\/dev\/null|git\s+(log|show|diff|status|branch|tag|remote|rev-parse|ls-files|blame|shortlog|describe|stash\s+list|config\s+--get|cat-file)|gh\s+(issue|pr|api|repo|release)\s+(view|list|diff|status|checks)|mkdir\s+-p\s+\.gsd|rtk\s)/;
25
- let depthVerificationDone = false;
27
+ const verifiedDepthMilestones = new Set();
26
28
  let activeQueuePhase = false;
29
+ /**
30
+ * Discussion gate enforcement state.
31
+ *
32
+ * When ask_user_questions is called with a recognized gate question ID,
33
+ * we track the pending gate. Until the gate is confirmed (user selects the
34
+ * first/recommended option), all non-read-only tool calls are blocked.
35
+ * This mechanically prevents the model from rationalizing past failed or
36
+ * cancelled gate questions.
37
+ */
38
+ let pendingGateId = null;
39
+ /**
40
+ * Recognized gate question ID patterns.
41
+ * These appear in both discuss-prepared.md (4-layer) and discuss.md (depth/requirements/roadmap).
42
+ */
43
+ const GATE_QUESTION_PATTERNS = [
44
+ "layer1_scope_gate",
45
+ "layer2_architecture_gate",
46
+ "layer3_error_gate",
47
+ "layer4_quality_gate",
48
+ "depth_verification",
49
+ ];
50
+ /**
51
+ * Tools that are safe to call while a gate is pending.
52
+ * Includes read-only tools and ask_user_questions itself (so the model can re-ask).
53
+ */
54
+ const GATE_SAFE_TOOLS = new Set([
55
+ "ask_user_questions",
56
+ "read", "grep", "find", "ls", "glob",
57
+ "search-the-web", "resolve_library", "get_library_docs", "fetch_page",
58
+ "search_and_read",
59
+ ]);
27
60
  export function isDepthVerified() {
28
- return depthVerificationDone;
61
+ return verifiedDepthMilestones.size > 0;
62
+ }
63
+ /**
64
+ * Check whether a specific milestone has passed depth verification.
65
+ */
66
+ export function isMilestoneDepthVerified(milestoneId) {
67
+ if (!milestoneId)
68
+ return false;
69
+ return verifiedDepthMilestones.has(milestoneId);
29
70
  }
30
71
  export function isQueuePhaseActive() {
31
72
  return activeQueuePhase;
@@ -34,14 +75,103 @@ export function setQueuePhaseActive(active) {
34
75
  activeQueuePhase = active;
35
76
  }
36
77
  export function resetWriteGateState() {
37
- depthVerificationDone = false;
78
+ verifiedDepthMilestones.clear();
79
+ pendingGateId = null;
38
80
  }
39
81
  export function clearDiscussionFlowState() {
40
- depthVerificationDone = false;
82
+ verifiedDepthMilestones.clear();
41
83
  activeQueuePhase = false;
84
+ pendingGateId = null;
85
+ }
86
+ export function markDepthVerified(milestoneId) {
87
+ if (!milestoneId)
88
+ return;
89
+ verifiedDepthMilestones.add(milestoneId);
90
+ }
91
+ /**
92
+ * Check whether a question ID matches a recognized gate pattern.
93
+ */
94
+ export function isGateQuestionId(questionId) {
95
+ return GATE_QUESTION_PATTERNS.some(pattern => questionId.includes(pattern));
96
+ }
97
+ /**
98
+ * Extract the milestone ID embedded in a depth-verification question id.
99
+ * Prompts are expected to use ids like `depth_verification_M001_confirm`.
100
+ */
101
+ export function extractDepthVerificationMilestoneId(questionId) {
102
+ const match = questionId.match(DEPTH_VERIFICATION_MILESTONE_RE);
103
+ return match?.[1] ?? null;
104
+ }
105
+ /**
106
+ * Extract the milestone ID from a milestone CONTEXT file path.
107
+ */
108
+ function extractContextMilestoneId(inputPath) {
109
+ const match = inputPath.match(CONTEXT_MILESTONE_RE);
110
+ return match?.[1] ?? null;
111
+ }
112
+ /**
113
+ * Mark a gate as pending (called when ask_user_questions is invoked with a gate ID).
114
+ */
115
+ export function setPendingGate(gateId) {
116
+ pendingGateId = gateId;
117
+ }
118
+ /**
119
+ * Clear the pending gate (called when the user confirms).
120
+ */
121
+ export function clearPendingGate() {
122
+ pendingGateId = null;
123
+ }
124
+ /**
125
+ * Get the currently pending gate, if any.
126
+ */
127
+ export function getPendingGate() {
128
+ return pendingGateId;
129
+ }
130
+ /**
131
+ * Check whether a tool call should be blocked because a discussion gate
132
+ * is pending (ask_user_questions was called but not confirmed).
133
+ *
134
+ * Returns { block: true, reason } if the tool should be blocked.
135
+ * Read-only tools and ask_user_questions itself are always allowed.
136
+ */
137
+ export function shouldBlockPendingGate(toolName, _milestoneId, _queuePhaseActive) {
138
+ if (!pendingGateId)
139
+ return { block: false };
140
+ if (GATE_SAFE_TOOLS.has(toolName))
141
+ return { block: false };
142
+ // Bash read-only commands are also safe
143
+ if (toolName === "bash")
144
+ return { block: false }; // bash is checked separately below
145
+ return {
146
+ block: true,
147
+ reason: [
148
+ `HARD BLOCK: Discussion gate "${pendingGateId}" has not been confirmed by the user.`,
149
+ `You MUST re-call ask_user_questions with the gate question before making any other tool calls.`,
150
+ `If the previous ask_user_questions call failed, errored, was cancelled, or the user's response`,
151
+ `did not match a provided option, you MUST re-ask — never rationalize past the block.`,
152
+ `Do NOT proceed, do NOT use alternative approaches, do NOT skip the gate.`,
153
+ ].join(" "),
154
+ };
42
155
  }
43
- export function markDepthVerified() {
44
- depthVerificationDone = true;
156
+ /**
157
+ * Check whether a bash command should be blocked because a discussion gate is pending.
158
+ * Read-only bash commands are allowed; mutating commands are blocked.
159
+ */
160
+ export function shouldBlockPendingGateBash(command, _milestoneId, _queuePhaseActive) {
161
+ if (!pendingGateId)
162
+ return { block: false };
163
+ // Allow read-only bash commands
164
+ if (BASH_READ_ONLY_RE.test(command))
165
+ return { block: false };
166
+ return {
167
+ block: true,
168
+ reason: [
169
+ `HARD BLOCK: Discussion gate "${pendingGateId}" has not been confirmed by the user.`,
170
+ `You MUST re-call ask_user_questions with the gate question before running mutating commands.`,
171
+ `If the previous ask_user_questions call failed, errored, was cancelled, or the user's response`,
172
+ `did not match a provided option, you MUST re-ask — never rationalize past the block.`,
173
+ ].join(" "),
174
+ };
45
175
  }
46
176
  /**
47
177
  * Check whether a depth_verification answer confirms the discussion is complete.
@@ -67,16 +197,23 @@ export function isDepthConfirmationAnswer(selected, options) {
67
197
  // accept only if it contains "(Recommended)" — the prompt convention suffix.
68
198
  return value.includes("(Recommended)");
69
199
  }
70
- export function shouldBlockContextWrite(toolName, inputPath, milestoneId, depthVerified, queuePhaseActive) {
200
+ export function shouldBlockContextWrite(toolName, inputPath, milestoneId, _queuePhaseActive) {
71
201
  if (toolName !== "write")
72
202
  return { block: false };
73
- const inDiscussion = milestoneId !== null;
74
- const inQueue = queuePhaseActive ?? false;
75
- if (!inDiscussion && !inQueue)
76
- return { block: false };
77
203
  if (!MILESTONE_CONTEXT_RE.test(inputPath))
78
204
  return { block: false };
79
- if (depthVerified)
205
+ const targetMilestoneId = extractContextMilestoneId(inputPath) ?? milestoneId;
206
+ if (!targetMilestoneId) {
207
+ return {
208
+ block: true,
209
+ reason: [
210
+ `HARD BLOCK: Cannot write milestone CONTEXT.md without knowing which milestone it belongs to.`,
211
+ `This is a mechanical gate — you MUST NOT proceed, retry, or rationalize past this block.`,
212
+ `Required action: call ask_user_questions with question id containing "depth_verification" and the milestone id.`,
213
+ ].join(" "),
214
+ };
215
+ }
216
+ if (isMilestoneDepthVerified(targetMilestoneId))
80
217
  return { block: false };
81
218
  return {
82
219
  block: true,
@@ -89,6 +226,37 @@ export function shouldBlockContextWrite(toolName, inputPath, milestoneId, depthV
89
226
  ].join(" "),
90
227
  };
91
228
  }
229
+ /**
230
+ * Check whether a gsd_summary_save CONTEXT artifact should be blocked.
231
+ * Slice-level CONTEXT artifacts are allowed; milestone-level CONTEXT writes
232
+ * require the milestone to be depth-verified first.
233
+ */
234
+ export function shouldBlockContextArtifactSave(artifactType, milestoneId, sliceId) {
235
+ if (artifactType !== "CONTEXT")
236
+ return { block: false };
237
+ if (sliceId)
238
+ return { block: false };
239
+ if (!milestoneId) {
240
+ return {
241
+ block: true,
242
+ reason: [
243
+ `HARD BLOCK: Cannot save milestone CONTEXT without a milestone_id.`,
244
+ `This is a mechanical gate — you MUST NOT proceed, retry, or rationalize past this block.`,
245
+ ].join(" "),
246
+ };
247
+ }
248
+ if (isMilestoneDepthVerified(milestoneId))
249
+ return { block: false };
250
+ return {
251
+ block: true,
252
+ reason: [
253
+ `HARD BLOCK: Cannot save milestone CONTEXT without depth verification for ${milestoneId}.`,
254
+ `This is a mechanical gate — you MUST NOT proceed, retry, or rationalize past this block.`,
255
+ `Required action: call ask_user_questions with question id containing "depth_verification_${milestoneId}".`,
256
+ `The user MUST select the "(Recommended)" confirmation option to unlock this gate.`,
257
+ ].join(" "),
258
+ };
259
+ }
92
260
  /**
93
261
  * Queue-mode execution guard (#2545).
94
262
  *
@@ -130,6 +298,10 @@ export function shouldBlockQueueExecution(toolName, input, queuePhaseActive) {
130
298
  `Use read-only commands (cat, grep, git log, etc.) to investigate, then write planning artifacts.`,
131
299
  };
132
300
  }
133
- // Unknown tools — allow by default (custom extension tools, etc.)
134
- return { block: false };
301
+ // Unknown tools — block by default in queue mode so custom tools cannot
302
+ // bypass execution restrictions.
303
+ return {
304
+ block: true,
305
+ reason: `Blocked: /gsd queue is a planning tool — it creates milestones, not executes work. Unknown tools are not permitted during queue mode.`,
306
+ };
135
307
  }
@@ -15,6 +15,10 @@ import { gsdRoot } from "./paths.js";
15
15
  const DEFAULT_EXCLUDES = [
16
16
  ".gsd/",
17
17
  ".planning/",
18
+ ".plans/",
19
+ ".claude/",
20
+ ".cursor/",
21
+ ".vscode/",
18
22
  ".git/",
19
23
  "node_modules/",
20
24
  "dist/",
@@ -77,7 +77,7 @@ export async function handleStatus(ctx) {
77
77
  return;
78
78
  }
79
79
  const { GSDDashboardOverlay } = await import("../../dashboard-overlay.js");
80
- const result = await ctx.ui.custom((tui, theme, _kb, done) => new GSDDashboardOverlay(tui, theme, () => done()), {
80
+ const result = await ctx.ui.custom((tui, theme, _kb, done) => new GSDDashboardOverlay(tui, theme, () => done(true)), {
81
81
  overlay: true,
82
82
  overlayOptions: {
83
83
  width: "70%",
@@ -99,7 +99,7 @@ export async function handleVisualize(ctx) {
99
99
  return;
100
100
  }
101
101
  const { GSDVisualizerOverlay } = await import("../../visualizer-overlay.js");
102
- const result = await ctx.ui.custom((tui, theme, _kb, done) => new GSDVisualizerOverlay(tui, theme, () => done()), {
102
+ const result = await ctx.ui.custom((tui, theme, _kb, done) => new GSDVisualizerOverlay(tui, theme, () => done(true)), {
103
103
  overlay: true,
104
104
  overlayOptions: {
105
105
  width: "80%",
@@ -195,7 +195,7 @@ export async function handleCoreCommand(trimmed, ctx) {
195
195
  }
196
196
  if (trimmed === "show-config") {
197
197
  const { GSDConfigOverlay, formatConfigText } = await import("../../config-overlay.js");
198
- const result = await ctx.ui.custom((tui, theme, _kb, done) => new GSDConfigOverlay(tui, theme, () => done()), {
198
+ const result = await ctx.ui.custom((tui, theme, _kb, done) => new GSDConfigOverlay(tui, theme, () => done(true)), {
199
199
  overlay: true,
200
200
  overlayOptions: {
201
201
  width: "65%",
@@ -240,10 +240,16 @@ async function configureModels(ctx, prefs) {
240
240
  for (const group of byProvider.values()) {
241
241
  group.sort((a, b) => a.id.localeCompare(b.id));
242
242
  }
243
- // Build provider menu with model counts
243
+ // Display names for providers in the preferences wizard UI.
244
+ const PROVIDER_DISPLAY_NAMES = { anthropic: "anthropic-api" };
245
+ const displayName = (p) => PROVIDER_DISPLAY_NAMES[p] ?? p;
246
+ // Build provider menu with model counts (display name → real name lookup)
247
+ const displayToReal = new Map();
244
248
  const providerOptions = providers.map(p => {
245
249
  const count = byProvider.get(p).length;
246
- return `${p} (${count} models)`;
250
+ const label = `${displayName(p)} (${count} models)`;
251
+ displayToReal.set(label, p);
252
+ return label;
247
253
  });
248
254
  providerOptions.push("(keep current)", "(clear)", "(type manually)");
249
255
  for (const phase of modelPhases) {
@@ -267,13 +273,13 @@ async function configureModels(ctx, prefs) {
267
273
  continue;
268
274
  }
269
275
  // Step 2: pick model within provider
270
- const providerName = providerChoice.replace(/ \(\d+ models?\)$/, "");
276
+ const providerName = displayToReal.get(providerChoice) ?? providerChoice.replace(/ \(\d+ models?\)$/, "");
271
277
  const group = byProvider.get(providerName);
272
278
  if (!group)
273
279
  continue;
274
280
  const modelOptions = group.map(m => m.id);
275
281
  modelOptions.push("(keep current)", "(clear)");
276
- const modelChoice = await ctx.ui.select(`${phaseLabel} — ${providerName}:`, modelOptions);
282
+ const modelChoice = await ctx.ui.select(`${phaseLabel} — ${displayName(providerName)}:`, modelOptions);
277
283
  if (modelChoice && typeof modelChoice === "string" && modelChoice !== "(keep current)") {
278
284
  if (modelChoice === "(clear)") {
279
285
  delete models[phase];
@@ -135,7 +135,9 @@ export class CustomWorkflowEngine {
135
135
  * Returns "milestone-complete" when all steps are now done, "continue" otherwise.
136
136
  */
137
137
  async reconcile(state, completedStep) {
138
- const graph = state.raw;
138
+ // Re-read the graph from disk so we do not overwrite concurrent
139
+ // workflow edits with a stale in-memory snapshot from deriveState().
140
+ const graph = readGraph(this.runDir);
139
141
  // Extract stepId from "<workflowName>/<stepId>"
140
142
  const { milestone, slice, task } = parseUnitId(completedStep.unitId);
141
143
  const stepId = task ?? slice ?? milestone;
@@ -170,6 +170,12 @@ const TEST_MARKERS = [
170
170
  /** Directories skipped during bounded recursive project scans. */
171
171
  const RECURSIVE_SCAN_IGNORED_DIRS = new Set([
172
172
  ".git",
173
+ ".gsd",
174
+ ".planning",
175
+ ".plans",
176
+ ".claude",
177
+ ".cursor",
178
+ ".vscode",
173
179
  "node_modules",
174
180
  ".venv",
175
181
  "venv",
@@ -104,6 +104,23 @@ export function extractAllSections(body, level = 2) {
104
104
  function escapeRegex(s) {
105
105
  return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
106
106
  }
107
+ /**
108
+ * Normalize a task-plan file reference that may include inline description text
109
+ * after the path, for example:
110
+ * "docs/file.md — explanation"
111
+ * "docs/file.md - explanation"
112
+ */
113
+ export function normalizePlannedFileReference(value) {
114
+ const trimmed = value.trim().replace(/`/g, "");
115
+ const match = /^(.*?)(?:\s+(?:—|-)\s+)(.+)$/.exec(trimmed);
116
+ if (!match)
117
+ return trimmed;
118
+ const pathCandidate = match[1].trim();
119
+ if (pathCandidate.includes("/") || pathCandidate.includes("\\") || pathCandidate.includes(".")) {
120
+ return pathCandidate;
121
+ }
122
+ return trimmed;
123
+ }
107
124
  /** Parse bullet list items from a text block. */
108
125
  export function parseBullets(text) {
109
126
  return text.split('\n')
@@ -540,11 +557,11 @@ export function parseTaskPlanIO(content) {
540
557
  let match;
541
558
  backtickPathRegex.lastIndex = 0;
542
559
  while ((match = backtickPathRegex.exec(trimmed)) !== null) {
543
- const candidate = match[1];
560
+ const candidate = normalizePlannedFileReference(match[1]);
544
561
  // Filter out things that look like code tokens rather than file paths
545
562
  // (e.g. `true`, `false`, `npm run test`). A file path has at least one
546
563
  // dot or slash.
547
- if (candidate.includes("/") || candidate.includes(".")) {
564
+ if (candidate.includes("/") || candidate.includes("\\") || candidate.includes(".")) {
548
565
  paths.push(candidate);
549
566
  }
550
567
  }
@@ -142,12 +142,13 @@ export function checkAutoStartAfterDiscuss() {
142
142
  // Parse PROJECT.md for milestone sequence, warn if any are missing context.
143
143
  // Don't block — milestones can be intentionally queued without context.
144
144
  const projectFile = resolveGsdRootFile(basePath, "PROJECT");
145
+ let projectIds = [];
145
146
  if (projectFile) {
146
147
  try {
147
148
  const projectContent = readFileSync(projectFile, "utf-8");
148
- const milestoneIds = parseMilestoneSequenceFromProject(projectContent);
149
- if (milestoneIds.length > 1) {
150
- const missing = milestoneIds.filter(id => {
149
+ projectIds = parseMilestoneSequenceFromProject(projectContent);
150
+ if (projectIds.length > 1) {
151
+ const missing = projectIds.filter(id => {
151
152
  const hasContext = !!resolveMilestoneFile(basePath, id, "CONTEXT");
152
153
  const hasDraft = !!resolveMilestoneFile(basePath, id, "CONTEXT-DRAFT");
153
154
  const hasDir = existsSync(join(gsdRoot(basePath), "milestones", id));
@@ -165,9 +166,14 @@ export function checkAutoStartAfterDiscuss() {
165
166
  }
166
167
  // Gate 4: Discussion manifest process verification (multi-milestone only)
167
168
  // The LLM writes DISCUSSION-MANIFEST.json after each Phase 3 gate decision.
168
- // If the manifest exists but gates_completed < total, the LLM hasn't finished
169
- // presenting all readiness gates to the user — block auto-start.
169
+ // If the project is multi-milestone, the manifest is required. When it is
170
+ // missing, fail closed instead of assuming the discussion finished.
170
171
  const manifestPath = join(gsdRoot(basePath), "DISCUSSION-MANIFEST.json");
172
+ const requiresManifest = projectIds.length > 1 || findMilestoneIds(basePath).length > 1;
173
+ if (requiresManifest && !existsSync(manifestPath)) {
174
+ ctx.ui.notify("Multi-milestone discussion manifest is missing. Auto-start will remain paused until the manifest is written.", "warning");
175
+ return false;
176
+ }
171
177
  if (existsSync(manifestPath)) {
172
178
  try {
173
179
  const manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
@@ -178,9 +184,7 @@ export function checkAutoStartAfterDiscuss() {
178
184
  return false;
179
185
  }
180
186
  // Cross-check manifest milestones against PROJECT.md if available
181
- if (projectFile) {
182
- const projectContent = readFileSync(projectFile, "utf-8");
183
- const projectIds = parseMilestoneSequenceFromProject(projectContent);
187
+ if (projectIds.length > 0) {
184
188
  const manifestIds = Object.keys(manifest.milestones ?? {});
185
189
  const untracked = projectIds.filter(id => !manifestIds.includes(id));
186
190
  if (untracked.length > 0) {
@@ -1,4 +1,4 @@
1
- export { isDepthConfirmationAnswer, isDepthVerified, isQueuePhaseActive, setQueuePhaseActive, shouldBlockContextWrite, shouldBlockQueueExecution, } from "./bootstrap/write-gate.js";
1
+ export { isDepthConfirmationAnswer, isDepthVerified, isGateQuestionId, isQueuePhaseActive, setQueuePhaseActive, shouldBlockContextWrite, shouldBlockPendingGate, shouldBlockPendingGateBash, shouldBlockQueueExecution, setPendingGate, clearPendingGate, getPendingGate, } from "./bootstrap/write-gate.js";
2
2
  export default async function registerExtension(pi) {
3
3
  const { registerGsdExtension } = await import("./bootstrap/register-extension.js");
4
4
  registerGsdExtension(pi);
@@ -406,6 +406,8 @@ export class ParallelMonitorOverlay {
406
406
  lines.push(t.fg("muted", " ESC/q to close │ ↑↓ scroll"));
407
407
  // Apply scroll — use terminal rows as height estimate
408
408
  const termHeight = process.stdout.rows || 40;
409
+ const maxScroll = Math.max(0, lines.length - termHeight);
410
+ this.scrollOffset = Math.min(Math.max(this.scrollOffset, 0), maxScroll);
409
411
  const visible = lines.slice(this.scrollOffset, this.scrollOffset + termHeight);
410
412
  this.cachedLines = visible;
411
413
  return visible;
@@ -177,7 +177,9 @@ function _parsePlanImpl(content) {
177
177
  for (const line of lines) {
178
178
  const cbMatch = line.match(/^-\s+\[([ xX])\]\s+\*\*([\w.]+):\s+(.+?)\*\*\s*(.*)/);
179
179
  // Heading-style: ### T01 -- Title, ### T01: Title, ### T01 — Title
180
- const hdMatch = !cbMatch ? line.match(/^#{2,4}\s+([\w.]+)\s*(?:--|—|:)\s*(.+)/) : null;
180
+ const hdMatch = !cbMatch
181
+ ? line.match(/^#{2,4}\s+([A-Z]+\d+(?:\.[A-Z]+\d+)*)\s*(?:--|—|:)\s*(.+)/)
182
+ : null;
181
183
  if (cbMatch || hdMatch) {
182
184
  const taskId = cbMatch ? cbMatch[2] : hdMatch[1];
183
185
  // Skip tasks already found in the Tasks section
@@ -63,6 +63,6 @@ Then:
63
63
  13. Do not commit manually — the system auto-commits your changes after this unit completes.
64
64
  - Say: "Milestone {{milestoneId}} complete."
65
65
 
66
- **Important:** Do NOT skip the code change verification, success criteria, or definition of done verification (steps 3-5). The milestone summary must reflect actual verified outcomes, not assumed success. Verification failures BLOCK completion — there is no override. The milestone stays in its current state until issues are resolved and verification is re-run.
66
+ **Important:** Do NOT skip the code change verification, success criteria, or definition of done verification (steps 3-5). The milestone summary must reflect actual verified outcomes, not assumed success. Verification failures BLOCK completion — there is no override. The milestone stays in its current state until issues are resolved and verification is re-run. **If a verification tool itself fails, errors, or returns unexpected output, treat it as a verification failure** — never rationalize past a tool error ("tool didn't respond, assuming success" is forbidden). A tool that cannot verify is a tool that did not verify.
67
67
 
68
68
  **File system safety:** When scanning milestone directories for evidence, use `ls` or `find` to list directory contents first — never pass a directory path (e.g. `tasks/`, `slices/`) directly to the `read` tool. The `read` tool only accepts file paths, not directories.