gsd-pi 2.66.1-dev.3c26b49 → 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 +20 -20
  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 +20 -20
  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/{ZzNRjwBFLOhqEu4BYCQi9 → HxFcJ8GrYNPsg9ARz7GPz}/_buildManifest.js +0 -0
  230. /package/dist/web/standalone/.next/static/{ZzNRjwBFLOhqEu4BYCQi9 → HxFcJ8GrYNPsg9ARz7GPz}/_ssgManifest.js +0 -0
@@ -11,6 +11,7 @@ import type { GSDState } from "./types.js";
11
11
  import { getCurrentBranch } from "./worktree.js";
12
12
  import { getActiveHook } from "./post-unit-hooks.js";
13
13
  import { getLedger, getProjectTotals } from "./metrics.js";
14
+ import { getErrorMessage } from "./error-utils.js";
14
15
  import {
15
16
  resolveMilestoneFile,
16
17
  resolveSliceFile,
@@ -24,7 +25,11 @@ import { makeUI } from "../shared/tui.js";
24
25
  import { GLYPH, INDENT } from "../shared/mod.js";
25
26
  import { computeProgressScore } from "./progress-score.js";
26
27
  import { getActiveWorktreeName } from "./worktree-command.js";
27
- import { loadEffectiveGSDPreferences, getGlobalGSDPreferencesPath } from "./preferences.js";
28
+ import {
29
+ getGlobalGSDPreferencesPath,
30
+ getProjectGSDPreferencesPath,
31
+ parsePreferencesMarkdown,
32
+ } from "./preferences.js";
28
33
  import { resolveServiceTierIcon, getEffectiveServiceTier } from "./service-tier.js";
29
34
  import { parseUnitId } from "./unit-id.js";
30
35
  import {
@@ -370,26 +375,74 @@ export type WidgetMode = "full" | "small" | "min" | "off";
370
375
  const WIDGET_MODES: WidgetMode[] = ["full", "small", "min", "off"];
371
376
  let widgetMode: WidgetMode = "full";
372
377
  let widgetModeInitialized = false;
378
+ let widgetModePreferencePath: string | null = null;
379
+
380
+ function safeReadTextFile(path: string): string | null {
381
+ try {
382
+ if (!existsSync(path)) return null;
383
+ return readFileSync(path, "utf-8");
384
+ } catch {
385
+ return null;
386
+ }
387
+ }
388
+
389
+ function readWidgetModeFromFile(path: string): WidgetMode | undefined {
390
+ const raw = safeReadTextFile(path);
391
+ if (!raw) return undefined;
392
+ const prefs = parsePreferencesMarkdown(raw);
393
+ const saved = prefs?.widget_mode;
394
+ if (saved && WIDGET_MODES.includes(saved as WidgetMode)) {
395
+ return saved as WidgetMode;
396
+ }
397
+ return undefined;
398
+ }
399
+
400
+ function resolveWidgetModePreferencePath(
401
+ projectPath = getProjectGSDPreferencesPath(),
402
+ globalPath = getGlobalGSDPreferencesPath(),
403
+ ): string {
404
+ if (readWidgetModeFromFile(projectPath)) {
405
+ return projectPath;
406
+ }
407
+
408
+ if (readWidgetModeFromFile(globalPath)) {
409
+ return globalPath;
410
+ }
411
+
412
+ if (safeReadTextFile(projectPath) !== null) return projectPath;
413
+ if (safeReadTextFile(globalPath) !== null) return globalPath;
414
+ return getGlobalGSDPreferencesPath();
415
+ }
373
416
 
374
417
  /** Load widget mode from preferences (once). */
375
- function ensureWidgetModeLoaded(): void {
418
+ function ensureWidgetModeLoaded(projectPath?: string, globalPath?: string): void {
376
419
  if (widgetModeInitialized) return;
377
420
  widgetModeInitialized = true;
378
421
  try {
379
- const loaded = loadEffectiveGSDPreferences();
380
- const saved = loaded?.preferences?.widget_mode;
422
+ const resolvedProjectPath = projectPath ?? getProjectGSDPreferencesPath();
423
+ const resolvedGlobalPath = globalPath ?? getGlobalGSDPreferencesPath();
424
+ const saved = readWidgetModeFromFile(resolvedProjectPath) ?? readWidgetModeFromFile(resolvedGlobalPath);
381
425
  if (saved && WIDGET_MODES.includes(saved as WidgetMode)) {
382
426
  widgetMode = saved as WidgetMode;
383
427
  }
428
+ widgetModePreferencePath = resolveWidgetModePreferencePath(resolvedProjectPath, resolvedGlobalPath);
384
429
  } catch (err) { /* non-fatal — use default */
385
- logWarning("dashboard", `operation failed: ${err instanceof Error ? err.message : String(err)}`);
430
+ logWarning("dashboard", `operation failed: ${getErrorMessage(err)}`);
431
+ widgetModePreferencePath = getGlobalGSDPreferencesPath();
386
432
  }
387
433
  }
388
434
 
389
- /** Persist widget mode to global preferences YAML. */
390
- function persistWidgetMode(mode: WidgetMode): void {
435
+ /**
436
+ * Persist widget mode to the preference file that owns the effective value.
437
+ * Project-scoped widget_mode wins over global; if neither scope defines it,
438
+ * we prefer an existing project preferences file and otherwise fall back to
439
+ * the global preferences file.
440
+ */
441
+ function persistWidgetMode(
442
+ mode: WidgetMode,
443
+ prefsPath = widgetModePreferencePath ?? resolveWidgetModePreferencePath(),
444
+ ): void {
391
445
  try {
392
- const prefsPath = getGlobalGSDPreferencesPath();
393
446
  let content = "";
394
447
  if (existsSync(prefsPath)) {
395
448
  content = readFileSync(prefsPath, "utf-8");
@@ -408,26 +461,34 @@ function persistWidgetMode(mode: WidgetMode): void {
408
461
  }
409
462
 
410
463
  /** Cycle to the next widget mode. Returns the new mode. */
411
- export function cycleWidgetMode(): WidgetMode {
412
- ensureWidgetModeLoaded();
464
+ export function cycleWidgetMode(projectPath?: string, globalPath?: string): WidgetMode {
465
+ ensureWidgetModeLoaded(projectPath, globalPath);
413
466
  const idx = WIDGET_MODES.indexOf(widgetMode);
414
467
  widgetMode = WIDGET_MODES[(idx + 1) % WIDGET_MODES.length];
415
- persistWidgetMode(widgetMode);
468
+ persistWidgetMode(widgetMode, widgetModePreferencePath ?? resolveWidgetModePreferencePath(projectPath, globalPath));
416
469
  return widgetMode;
417
470
  }
418
471
 
419
472
  /** Set widget mode directly. */
420
- export function setWidgetMode(mode: WidgetMode): void {
473
+ export function setWidgetMode(mode: WidgetMode, projectPath?: string, globalPath?: string): void {
474
+ ensureWidgetModeLoaded(projectPath, globalPath);
421
475
  widgetMode = mode;
422
- persistWidgetMode(widgetMode);
476
+ persistWidgetMode(widgetMode, widgetModePreferencePath ?? resolveWidgetModePreferencePath(projectPath, globalPath));
423
477
  }
424
478
 
425
479
  /** Get current widget mode. */
426
- export function getWidgetMode(): WidgetMode {
427
- ensureWidgetModeLoaded();
480
+ export function getWidgetMode(projectPath?: string, globalPath?: string): WidgetMode {
481
+ ensureWidgetModeLoaded(projectPath, globalPath);
428
482
  return widgetMode;
429
483
  }
430
484
 
485
+ /** Test-only reset for widget mode caching. */
486
+ export function _resetWidgetModeForTests(): void {
487
+ widgetMode = "full";
488
+ widgetModeInitialized = false;
489
+ widgetModePreferencePath = null;
490
+ }
491
+
431
492
  // ─── Progress Widget ──────────────────────────────────────────────────────────
432
493
 
433
494
  /** State accessors passed to updateProgressWidget to avoid direct global access */
@@ -901,4 +962,3 @@ function padToWidth(s: string, colWidth: number): string {
901
962
  if (vis >= colWidth) return truncateToWidth(s, colWidth, "…");
902
963
  return s + " ".repeat(colWidth - vis);
903
964
  }
904
-
@@ -379,40 +379,8 @@ export const DISPATCH_RULES: DispatchRule[] = [
379
379
  },
380
380
  },
381
381
  {
382
- name: "planning (no research, not S01) research-slice",
383
- match: async ({ state, mid, midTitle, basePath, prefs }) => {
384
- if (state.phase !== "planning") return null;
385
- // Phase skip: skip research when preference or profile says so
386
- if (prefs?.phases?.skip_research || prefs?.phases?.skip_slice_research)
387
- return null;
388
- if (!state.activeSlice) return missingSliceStop(mid, state.phase);
389
- const sid = state.activeSlice!.id;
390
- const sTitle = state.activeSlice!.title;
391
- const researchFile = resolveSliceFile(basePath, mid, sid, "RESEARCH");
392
- if (researchFile) return null; // has research, fall through
393
- // Skip slice research for S01 when milestone research already exists —
394
- // the milestone research already covers the same ground for the first slice.
395
- const milestoneResearchFile = resolveMilestoneFile(
396
- basePath,
397
- mid,
398
- "RESEARCH",
399
- );
400
- if (milestoneResearchFile && sid === "S01") return null; // fall through to plan-slice
401
- return {
402
- action: "dispatch",
403
- unitType: "research-slice",
404
- unitId: `${mid}/${sid}`,
405
- prompt: await buildResearchSlicePrompt(
406
- mid,
407
- midTitle,
408
- sid,
409
- sTitle,
410
- basePath,
411
- ),
412
- };
413
- },
414
- },
415
- {
382
+ // Keep this rule before the single-slice research rule so the multi-slice
383
+ // path wins whenever 2+ slices are ready.
416
384
  name: "planning (multiple slices need research) → parallel-research-slices",
417
385
  match: async ({ state, mid, midTitle, basePath, prefs }) => {
418
386
  if (state.phase !== "planning") return null;
@@ -459,6 +427,40 @@ export const DISPATCH_RULES: DispatchRule[] = [
459
427
  };
460
428
  },
461
429
  },
430
+ {
431
+ name: "planning (no research, not S01) → research-slice",
432
+ match: async ({ state, mid, midTitle, basePath, prefs }) => {
433
+ if (state.phase !== "planning") return null;
434
+ // Phase skip: skip research when preference or profile says so
435
+ if (prefs?.phases?.skip_research || prefs?.phases?.skip_slice_research)
436
+ return null;
437
+ if (!state.activeSlice) return missingSliceStop(mid, state.phase);
438
+ const sid = state.activeSlice!.id;
439
+ const sTitle = state.activeSlice!.title;
440
+ const researchFile = resolveSliceFile(basePath, mid, sid, "RESEARCH");
441
+ if (researchFile) return null; // has research, fall through
442
+ // Skip slice research for S01 when milestone research already exists —
443
+ // the milestone research already covers the same ground for the first slice.
444
+ const milestoneResearchFile = resolveMilestoneFile(
445
+ basePath,
446
+ mid,
447
+ "RESEARCH",
448
+ );
449
+ if (milestoneResearchFile && sid === "S01") return null; // fall through to plan-slice
450
+ return {
451
+ action: "dispatch",
452
+ unitType: "research-slice",
453
+ unitId: `${mid}/${sid}`,
454
+ prompt: await buildResearchSlicePrompt(
455
+ mid,
456
+ midTitle,
457
+ sid,
458
+ sTitle,
459
+ basePath,
460
+ ),
461
+ };
462
+ },
463
+ },
462
464
  {
463
465
  name: "planning → plan-slice",
464
466
  match: async ({ state, mid, midTitle, basePath }) => {
@@ -883,4 +885,3 @@ export async function resolveDispatch(
883
885
  export function getDispatchRuleNames(): string[] {
884
886
  return DISPATCH_RULES.map((r) => r.name);
885
887
  }
886
-
@@ -1592,7 +1592,7 @@ export async function buildValidateMilestonePrompt(
1592
1592
  logWarning("prompt", `buildValidateMilestonePrompt verification classes lookup failed: ${err instanceof Error ? err.message : String(err)}`);
1593
1593
  }
1594
1594
 
1595
- // Inline all slice summaries and UAT results
1595
+ // Inline all slice summaries and assessment results
1596
1596
  let valSliceIds: string[] = [];
1597
1597
  try {
1598
1598
  const { isDbAvailable, getMilestoneSlices } = await import("./gsd-db.js");
@@ -1617,10 +1617,10 @@ export async function buildValidateMilestonePrompt(
1617
1617
  const summaryRel = relSliceFile(base, mid, sid, "SUMMARY");
1618
1618
  inlined.push(await inlineFile(summaryPath, summaryRel, `${sid} Summary`));
1619
1619
 
1620
- const uatPath = resolveSliceFile(base, mid, sid, "UAT");
1621
- const uatRel = relSliceFile(base, mid, sid, "UAT");
1622
- const uatInline = await inlineFileOptional(uatPath, uatRel, `${sid} UAT Result`);
1623
- if (uatInline) inlined.push(uatInline);
1620
+ const assessmentPath = resolveSliceFile(base, mid, sid, "ASSESSMENT");
1621
+ const assessmentRel = relSliceFile(base, mid, sid, "ASSESSMENT");
1622
+ const assessmentInline = await inlineFileOptional(assessmentPath, assessmentRel, `${sid} Assessment`);
1623
+ if (assessmentInline) inlined.push(assessmentInline);
1624
1624
  }
1625
1625
 
1626
1626
  // Aggregate unresolved follow-ups and known limitations across slices
@@ -2150,4 +2150,3 @@ export async function buildRewriteDocsPrompt(
2150
2150
  overridesPath: relGsdRootFile("OVERRIDES"),
2151
2151
  });
2152
2152
  }
2153
-
@@ -13,7 +13,7 @@ import { appendEvent } from "./workflow-events.js";
13
13
  import { atomicWriteSync } from "./atomic-write.js";
14
14
  import { clearParseCache } from "./files.js";
15
15
  import { parseRoadmap as parseLegacyRoadmap, parsePlan as parseLegacyPlan } from "./parsers-legacy.js";
16
- import { isDbAvailable, getTask, getSlice, getSliceTasks, updateTaskStatus, updateSliceStatus } from "./gsd-db.js";
16
+ import { isDbAvailable, getTask, getSlice, getSliceTasks, getPendingGates, updateTaskStatus, updateSliceStatus } from "./gsd-db.js";
17
17
  import { isValidationTerminal } from "./state.js";
18
18
  import { getErrorMessage } from "./error-utils.js";
19
19
  import { logWarning, logError } from "./workflow-logger.js";
@@ -248,8 +248,7 @@ export function verifyExpectedArtifact(
248
248
  if (gateIds.length === 0) return true;
249
249
 
250
250
  try {
251
- const { getPendingGates: getPending } = require("./gsd-db.js");
252
- const pending = getPending(mid, sid, "slice");
251
+ const pending = getPendingGates(mid, sid, "slice");
253
252
  const pendingIds = new Set(pending.map((g: any) => g.gate_id));
254
253
  // All dispatched gates must no longer be pending
255
254
  for (const gid of gateIds) {
@@ -480,22 +479,23 @@ function abortAndResetMerge(
480
479
  }
481
480
  }
482
481
 
482
+ export type MergeReconcileResult = "clean" | "reconciled" | "blocked";
483
+
483
484
  /**
484
485
  * Detect leftover merge state from a prior session and reconcile it.
485
486
  * If MERGE_HEAD or SQUASH_MSG exists, check whether conflicts are resolved.
486
- * If resolved: finalize the commit. If still conflicted: abort and reset.
487
- *
488
- * Returns true if state was dirty and re-derivation is needed.
487
+ * If resolved: finalize the commit. If only .gsd conflicts remain: auto-resolve.
488
+ * If code conflicts remain: fail safe without modifying the worktree.
489
489
  */
490
490
  export function reconcileMergeState(
491
491
  basePath: string,
492
492
  ctx: ExtensionContext,
493
- ): boolean {
493
+ ): MergeReconcileResult {
494
494
  const mergeHeadPath = join(basePath, ".git", "MERGE_HEAD");
495
495
  const squashMsgPath = join(basePath, ".git", "SQUASH_MSG");
496
496
  const hasMergeHead = existsSync(mergeHeadPath);
497
497
  const hasSquashMsg = existsSync(squashMsgPath);
498
- if (!hasMergeHead && !hasSquashMsg) return false;
498
+ if (!hasMergeHead && !hasSquashMsg) return "clean";
499
499
 
500
500
  const conflictedFiles = nativeConflictFiles(basePath);
501
501
  if (conflictedFiles.length === 0) {
@@ -511,7 +511,7 @@ export function reconcileMergeState(
511
511
  } catch (err) {
512
512
  const errorMessage = getErrorMessage(err);
513
513
  ctx.ui.notify(`Failed to finalize leftover merge/squash commit: ${errorMessage}`, "error");
514
- return false;
514
+ return "blocked";
515
515
  }
516
516
  } else {
517
517
  // Still conflicted — try auto-resolving .gsd/ state file conflicts (#530)
@@ -551,15 +551,16 @@ export function reconcileMergeState(
551
551
  );
552
552
  }
553
553
  } else {
554
- // Code conflicts present — abort and reset
555
- abortAndResetMerge(basePath, hasMergeHead, squashMsgPath);
554
+ // Code conflicts present — fail safe and preserve any manual resolution
555
+ // work instead of discarding it with merge --abort/reset --hard.
556
556
  ctx.ui.notify(
557
- "Detected leftover merge state with unresolved conflicts cleaned up. Re-deriving state.",
558
- "warning",
557
+ "Detected leftover merge state with unresolved code conflicts. Auto-mode will pause without modifying the worktree so manual conflict resolution is preserved.",
558
+ "error",
559
559
  );
560
+ return "blocked";
560
561
  }
561
562
  }
562
- return true;
563
+ return "reconciled";
563
564
  }
564
565
 
565
566
  // ─── Loop Remediation ─────────────────────────────────────────────────────────
@@ -618,4 +619,3 @@ export function buildLoopRemediationSteps(
618
619
  }
619
620
  return null;
620
621
  }
621
-
@@ -7,6 +7,16 @@ import { loadEffectiveGSDPreferences } from "../preferences.js";
7
7
  import { ensureDbOpen } from "./dynamic-tools.js";
8
8
  import { StringEnum } from "@gsd/pi-ai";
9
9
  import { logError } from "../workflow-logger.js";
10
+ import { getErrorMessage } from "../error-utils.js";
11
+ import { shouldBlockContextArtifactSave } from "./write-gate.js";
12
+
13
+ const SUPPORTED_SUMMARY_ARTIFACT_TYPES = ["SUMMARY", "RESEARCH", "CONTEXT", "ASSESSMENT", "CONTEXT-DRAFT"] as const;
14
+
15
+ export function isSupportedSummaryArtifactType(
16
+ artifactType: string,
17
+ ): artifactType is (typeof SUPPORTED_SUMMARY_ARTIFACT_TYPES)[number] {
18
+ return (SUPPORTED_SUMMARY_ARTIFACT_TYPES as readonly string[]).includes(artifactType);
19
+ }
10
20
 
11
21
  /**
12
22
  * Register an alias tool that shares the same execute function as its canonical counterpart.
@@ -283,13 +293,23 @@ export function registerDbTools(pi: ExtensionAPI): void {
283
293
  details: { operation: "save_summary", error: "db_unavailable" } as any,
284
294
  };
285
295
  }
286
- const validTypes = ["SUMMARY", "RESEARCH", "CONTEXT", "ASSESSMENT"];
287
- if (!validTypes.includes(params.artifact_type)) {
296
+ if (!isSupportedSummaryArtifactType(params.artifact_type)) {
288
297
  return {
289
- content: [{ type: "text" as const, text: `Error: Invalid artifact_type "${params.artifact_type}". Must be one of: ${validTypes.join(", ")}` }],
298
+ content: [{ type: "text" as const, text: `Error: Invalid artifact_type "${params.artifact_type}". Must be one of: ${SUPPORTED_SUMMARY_ARTIFACT_TYPES.join(", ")}` }],
290
299
  details: { operation: "save_summary", error: "invalid_artifact_type" } as any,
291
300
  };
292
301
  }
302
+ const contextGuard = shouldBlockContextArtifactSave(
303
+ params.artifact_type,
304
+ params.milestone_id ?? null,
305
+ params.slice_id ?? null,
306
+ );
307
+ if (contextGuard.block) {
308
+ return {
309
+ content: [{ type: "text" as const, text: `Error saving artifact: ${contextGuard.reason ?? "context write blocked"}` }],
310
+ details: { operation: "save_summary", error: "context_write_blocked" } as any,
311
+ };
312
+ }
293
313
  try {
294
314
  let relativePath: string;
295
315
  if (params.task_id && params.slice_id) {
@@ -333,16 +353,17 @@ export function registerDbTools(pi: ExtensionAPI): void {
333
353
  "Computes the file path from milestone/slice/task IDs automatically.",
334
354
  promptSnippet: "Save a GSD artifact (summary/research/context/assessment) to DB and disk",
335
355
  promptGuidelines: [
336
- "Use gsd_summary_save to persist structured artifacts (SUMMARY, RESEARCH, CONTEXT, ASSESSMENT).",
356
+ "Use gsd_summary_save to persist structured artifacts (SUMMARY, RESEARCH, CONTEXT, ASSESSMENT, CONTEXT-DRAFT).",
337
357
  "milestone_id is required. slice_id and task_id are optional — they determine the file path.",
338
358
  "The tool computes the relative path automatically: milestones/M001/M001-SUMMARY.md, milestones/M001/slices/S01/S01-SUMMARY.md, etc.",
339
- "artifact_type must be one of: SUMMARY, RESEARCH, CONTEXT, ASSESSMENT.",
359
+ "artifact_type must be one of: SUMMARY, RESEARCH, CONTEXT, ASSESSMENT, CONTEXT-DRAFT.",
360
+ "Use CONTEXT-DRAFT for incremental draft persistence; use CONTEXT for the final milestone context after depth verification.",
340
361
  ],
341
362
  parameters: Type.Object({
342
363
  milestone_id: Type.String({ description: "Milestone ID (e.g. M001)" }),
343
364
  slice_id: Type.Optional(Type.String({ description: "Slice ID (e.g. S01)" })),
344
365
  task_id: Type.Optional(Type.String({ description: "Task ID (e.g. T01)" })),
345
- artifact_type: Type.String({ description: "One of: SUMMARY, RESEARCH, CONTEXT, ASSESSMENT" }),
366
+ artifact_type: Type.String({ description: "One of: SUMMARY, RESEARCH, CONTEXT, ASSESSMENT, CONTEXT-DRAFT" }),
346
367
  content: Type.String({ description: "The full markdown content of the artifact" }),
347
368
  }),
348
369
  execute: summarySaveExecute,
@@ -6,7 +6,7 @@ import { isToolCallEventType } from "@gsd/pi-coding-agent";
6
6
  import { buildMilestoneFileName, resolveMilestonePath, resolveSliceFile, resolveSlicePath } from "../paths.js";
7
7
  import { buildBeforeAgentStartResult } from "./system-context.js";
8
8
  import { handleAgentEnd } from "./agent-end-recovery.js";
9
- import { clearDiscussionFlowState, isDepthVerified, isDepthConfirmationAnswer, isQueuePhaseActive, markDepthVerified, resetWriteGateState, shouldBlockContextWrite, shouldBlockQueueExecution } from "./write-gate.js";
9
+ import { clearDiscussionFlowState, isDepthConfirmationAnswer, isQueuePhaseActive, markDepthVerified, resetWriteGateState, shouldBlockContextWrite, shouldBlockQueueExecution, isGateQuestionId, setPendingGate, clearPendingGate, getPendingGate, shouldBlockPendingGate, shouldBlockPendingGateBash, extractDepthVerificationMilestoneId } from "./write-gate.js";
10
10
  import { isBlockedStateFile, isBashWriteToStateFile, BLOCKED_WRITE_ERROR } from "../write-intercept.js";
11
11
  import { cleanupQuickBranch } from "../quick.js";
12
12
  import { getDiscussionMilestoneId } from "../guided-flow.js";
@@ -24,6 +24,7 @@ import { logWarning as safetyLogWarning } from "../workflow-logger.js";
24
24
  import { installNotifyInterceptor } from "./notify-interceptor.js";
25
25
  import { initNotificationStore } from "../notification-store.js";
26
26
  import { initNotificationWidget } from "../notification-widget.js";
27
+ import { initHealthWidget } from "../health-widget.js";
27
28
 
28
29
  // Skip the welcome screen on the very first session_start — cli.ts already
29
30
  // printed it before the TUI launched. Only re-print on /clear (subsequent sessions).
@@ -39,6 +40,7 @@ export function registerHooks(pi: ExtensionAPI): void {
39
40
  initNotificationStore(process.cwd());
40
41
  installNotifyInterceptor(ctx);
41
42
  initNotificationWidget(ctx);
43
+ initHealthWidget(ctx);
42
44
  resetWriteGateState();
43
45
  resetToolCallLoopGuard();
44
46
  resetAskUserQuestionsCache();
@@ -162,12 +164,50 @@ export function registerHooks(pi: ExtensionAPI): void {
162
164
  });
163
165
 
164
166
  pi.on("tool_call", async (event) => {
167
+ const discussionBasePath = process.cwd();
165
168
  // ── Loop guard: block repeated identical tool calls ──
166
169
  const loopCheck = checkToolCallLoop(event.toolName, event.input as Record<string, unknown>);
167
170
  if (loopCheck.block) {
168
171
  return { block: true, reason: loopCheck.reason };
169
172
  }
170
173
 
174
+ // ── Discussion gate enforcement: track pending gate questions ─────────
175
+ // Only gate-shaped ask_user_questions calls should block execution.
176
+ // The gate stays pending until the user selects the approval option.
177
+ if (event.toolName === "ask_user_questions") {
178
+ const milestoneId = getDiscussionMilestoneId(discussionBasePath);
179
+ const inDiscussion = milestoneId !== null || isQueuePhaseActive();
180
+ if (inDiscussion) {
181
+ const questions: any[] = (event.input as any)?.questions ?? [];
182
+ const questionId = questions.find((question) => typeof question?.id === "string" && isGateQuestionId(question.id))?.id;
183
+ if (typeof questionId === "string") {
184
+ setPendingGate(questionId);
185
+ }
186
+ }
187
+ }
188
+
189
+ // ── Discussion gate enforcement: block tool calls while gate is pending ──
190
+ // If ask_user_questions was called with a gate ID but hasn't been confirmed,
191
+ // block all non-read-only tool calls to prevent the model from skipping gates.
192
+ if (getPendingGate()) {
193
+ const milestoneId = getDiscussionMilestoneId(discussionBasePath);
194
+ if (isToolCallEventType("bash", event)) {
195
+ const bashGuard = shouldBlockPendingGateBash(
196
+ event.input.command,
197
+ milestoneId,
198
+ isQueuePhaseActive(),
199
+ );
200
+ if (bashGuard.block) return bashGuard;
201
+ } else {
202
+ const gateGuard = shouldBlockPendingGate(
203
+ event.toolName,
204
+ milestoneId,
205
+ isQueuePhaseActive(),
206
+ );
207
+ if (gateGuard.block) return gateGuard;
208
+ }
209
+ }
210
+
171
211
  // ── Queue-mode execution guard (#2545): block source-code mutations ──
172
212
  // When /gsd queue is active, the agent should only create milestones,
173
213
  // not execute work. Block write/edit to non-.gsd/ paths and bash commands
@@ -210,8 +250,7 @@ export function registerHooks(pi: ExtensionAPI): void {
210
250
  const result = shouldBlockContextWrite(
211
251
  event.toolName,
212
252
  event.input.path,
213
- getDiscussionMilestoneId(),
214
- isDepthVerified(),
253
+ getDiscussionMilestoneId(discussionBasePath),
215
254
  isQueuePhaseActive(),
216
255
  );
217
256
  if (result.block) return result;
@@ -239,21 +278,43 @@ export function registerHooks(pi: ExtensionAPI): void {
239
278
 
240
279
  pi.on("tool_result", async (event) => {
241
280
  if (event.toolName !== "ask_user_questions") return;
242
- const milestoneId = getDiscussionMilestoneId();
281
+ const milestoneId = getDiscussionMilestoneId(process.cwd());
243
282
  const queueActive = isQueuePhaseActive();
244
283
  if (!milestoneId && !queueActive) return;
245
284
 
246
285
  const details = event.details as any;
247
- if (details?.cancelled || !details?.response) return;
248
286
 
287
+ // ── Discussion gate enforcement: handle gate question responses ──
288
+ // If the result is cancelled or has no response, the pending gate stays active
289
+ // so the model is blocked from non-read-only tools until it re-asks.
290
+ // If the user responded at all (even "needs adjustment"), clear the pending gate
291
+ // because the user engaged — the prompt handles the re-ask-after-adjustment flow.
249
292
  const questions: any[] = (event.input as any)?.questions ?? [];
293
+ const currentPendingGate = getPendingGate();
294
+ if (currentPendingGate) {
295
+ if (details?.cancelled || !details?.response) {
296
+ // Gate stays pending — model will be blocked from non-read-only tools
297
+ // until it re-asks and gets a valid response
298
+ } else {
299
+ const pendingQuestion = questions.find((question) => question?.id === currentPendingGate);
300
+ if (pendingQuestion) {
301
+ const answer = details.response?.answers?.[currentPendingGate];
302
+ if (isDepthConfirmationAnswer(answer?.selected, pendingQuestion.options)) {
303
+ clearPendingGate();
304
+ }
305
+ }
306
+ }
307
+ }
308
+
309
+ if (details?.cancelled || !details?.response) return;
310
+
250
311
  for (const question of questions) {
251
312
  if (typeof question.id === "string" && question.id.includes("depth_verification")) {
252
313
  // Only unlock the gate if the user selected the first option (confirmation).
253
314
  // Cross-references against the question's defined options to reject free-form "Other" text.
254
315
  const answer = details.response?.answers?.[question.id];
255
316
  if (isDepthConfirmationAnswer(answer?.selected, question.options)) {
256
- markDepthVerified();
317
+ markDepthVerified(extractDepthVerificationMilestoneId(question.id) ?? milestoneId);
257
318
  }
258
319
  break;
259
320
  }
@@ -7,18 +7,20 @@ import { Key } from "@gsd/pi-tui";
7
7
  import { GSDDashboardOverlay } from "../dashboard-overlay.js";
8
8
  import { GSDNotificationOverlay } from "../notification-overlay.js";
9
9
  import { ParallelMonitorOverlay } from "../parallel-monitor-overlay.js";
10
+ import { projectRoot } from "../commands/context.js";
10
11
  import { shortcutDesc } from "../../shared/mod.js";
11
12
 
12
13
  export function registerShortcuts(pi: ExtensionAPI): void {
13
14
  pi.registerShortcut(Key.ctrlAlt("g"), {
14
15
  description: shortcutDesc("Open GSD dashboard", "/gsd status"),
15
16
  handler: async (ctx) => {
16
- if (!existsSync(join(process.cwd(), ".gsd"))) {
17
+ const basePath = projectRoot();
18
+ if (!existsSync(join(basePath, ".gsd"))) {
17
19
  ctx.ui.notify("No .gsd/ directory found. Run /gsd to start.", "info");
18
20
  return;
19
21
  }
20
- await ctx.ui.custom<void>(
21
- (tui, theme, _kb, done) => new GSDDashboardOverlay(tui, theme, () => done()),
22
+ await ctx.ui.custom<boolean>(
23
+ (tui, theme, _kb, done) => new GSDDashboardOverlay(tui, theme, () => done(true)),
22
24
  {
23
25
  overlay: true,
24
26
  overlayOptions: {
@@ -35,8 +37,8 @@ export function registerShortcuts(pi: ExtensionAPI): void {
35
37
  pi.registerShortcut(Key.ctrlAlt("n"), {
36
38
  description: shortcutDesc("Open notification history", "/gsd notifications"),
37
39
  handler: async (ctx) => {
38
- await ctx.ui.custom<void>(
39
- (tui, theme, _kb, done) => new GSDNotificationOverlay(tui, theme, () => done()),
40
+ await ctx.ui.custom<boolean>(
41
+ (tui, theme, _kb, done) => new GSDNotificationOverlay(tui, theme, () => done(true)),
40
42
  {
41
43
  overlay: true,
42
44
  overlayOptions: {
@@ -54,13 +56,14 @@ export function registerShortcuts(pi: ExtensionAPI): void {
54
56
  pi.registerShortcut(Key.ctrlAlt("p"), {
55
57
  description: shortcutDesc("Open parallel worker monitor", "/gsd parallel watch"),
56
58
  handler: async (ctx) => {
57
- const parallelDir = join(process.cwd(), ".gsd", "parallel");
59
+ const basePath = projectRoot();
60
+ const parallelDir = join(basePath, ".gsd", "parallel");
58
61
  if (!existsSync(parallelDir)) {
59
62
  ctx.ui.notify("No parallel workers found. Run /gsd parallel start first.", "info");
60
63
  return;
61
64
  }
62
- await ctx.ui.custom<void>(
63
- (tui, theme, _kb, done) => new ParallelMonitorOverlay(tui, theme, () => done()),
65
+ await ctx.ui.custom<boolean>(
66
+ (tui, theme, _kb, done) => new ParallelMonitorOverlay(tui, theme, () => done(true)),
64
67
  {
65
68
  overlay: true,
66
69
  overlayOptions: {