gsd-pi 2.71.0-dev.06b86c6 → 2.71.0-dev.4c35d99

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 (276) hide show
  1. package/README.md +34 -1
  2. package/dist/cli.js +17 -0
  3. package/dist/headless-events.d.ts +2 -0
  4. package/dist/headless-events.js +7 -0
  5. package/dist/headless.js +16 -3
  6. package/dist/mcp-server.js +37 -14
  7. package/dist/resource-loader.js +6 -3
  8. package/dist/resources/agents/debugger.md +58 -0
  9. package/dist/resources/agents/doc-writer.md +43 -0
  10. package/dist/resources/agents/git-ops.md +56 -0
  11. package/dist/resources/agents/javascript-pro.md +46 -271
  12. package/dist/resources/agents/planner.md +55 -0
  13. package/dist/resources/agents/refactorer.md +47 -0
  14. package/dist/resources/agents/reviewer.md +48 -0
  15. package/dist/resources/agents/security.md +59 -0
  16. package/dist/resources/agents/tester.md +50 -0
  17. package/dist/resources/agents/typescript-pro.md +41 -235
  18. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +113 -10
  19. package/dist/resources/extensions/gsd/auto/infra-errors.js +34 -0
  20. package/dist/resources/extensions/gsd/auto/loop.js +32 -1
  21. package/dist/resources/extensions/gsd/auto/phases.js +5 -1
  22. package/dist/resources/extensions/gsd/auto/session.js +11 -0
  23. package/dist/resources/extensions/gsd/auto-dashboard.js +22 -16
  24. package/dist/resources/extensions/gsd/auto-model-selection.js +10 -2
  25. package/dist/resources/extensions/gsd/auto-prompts.js +88 -33
  26. package/dist/resources/extensions/gsd/auto-start.js +33 -6
  27. package/dist/resources/extensions/gsd/auto-worktree.js +1 -1
  28. package/dist/resources/extensions/gsd/auto.js +56 -0
  29. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +3 -3
  30. package/dist/resources/extensions/gsd/bootstrap/register-shortcuts.js +63 -51
  31. package/dist/resources/extensions/gsd/commands/handlers/auto.js +10 -33
  32. package/dist/resources/extensions/gsd/commands/handlers/core.js +56 -11
  33. package/dist/resources/extensions/gsd/commands/handlers/notifications-handler.js +15 -6
  34. package/dist/resources/extensions/gsd/commands/handlers/workflow.js +4 -10
  35. package/dist/resources/extensions/gsd/dashboard-overlay.js +8 -3
  36. package/dist/resources/extensions/gsd/doctor-providers.js +23 -0
  37. package/dist/resources/extensions/gsd/forensics.js +19 -6
  38. package/dist/resources/extensions/gsd/gate-registry.js +208 -0
  39. package/dist/resources/extensions/gsd/gsd-db.js +41 -0
  40. package/dist/resources/extensions/gsd/guided-flow.js +5 -10
  41. package/dist/resources/extensions/gsd/metrics.js +1 -0
  42. package/dist/resources/extensions/gsd/milestone-actions.js +10 -4
  43. package/dist/resources/extensions/gsd/milestone-validation-gates.js +11 -12
  44. package/dist/resources/extensions/gsd/notification-overlay.js +42 -13
  45. package/dist/resources/extensions/gsd/notification-store.js +35 -4
  46. package/dist/resources/extensions/gsd/notification-widget.js +5 -13
  47. package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +8 -3
  48. package/dist/resources/extensions/gsd/prompt-validation.js +126 -0
  49. package/dist/resources/extensions/gsd/prompts/complete-slice.md +3 -1
  50. package/dist/resources/extensions/gsd/prompts/execute-task.md +2 -0
  51. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +2 -0
  52. package/dist/resources/extensions/gsd/session-model-override.js +25 -0
  53. package/dist/resources/extensions/gsd/shortcut-defs.js +40 -0
  54. package/dist/resources/extensions/gsd/state.js +9 -2
  55. package/dist/resources/extensions/gsd/tools/complete-slice.js +52 -1
  56. package/dist/resources/extensions/gsd/tools/complete-task.js +51 -1
  57. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +4 -1
  58. package/dist/resources/extensions/ollama/index.js +13 -5
  59. package/dist/resources/extensions/shared/gsd-phase-state.js +35 -0
  60. package/dist/resources/extensions/subagent/agents.js +8 -0
  61. package/dist/resources/extensions/subagent/index.js +17 -0
  62. package/dist/startup-model-validation.d.ts +0 -1
  63. package/dist/startup-model-validation.js +6 -2
  64. package/dist/web/standalone/.next/BUILD_ID +1 -1
  65. package/dist/web/standalone/.next/app-path-routes-manifest.json +17 -17
  66. package/dist/web/standalone/.next/build-manifest.json +2 -2
  67. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  68. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  69. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  70. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  71. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  72. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  73. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  74. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  75. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  77. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  78. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  79. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  80. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  81. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  82. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  83. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  84. package/dist/web/standalone/.next/server/app/index.html +1 -1
  85. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  86. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  87. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  88. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  89. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  90. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  91. package/dist/web/standalone/.next/server/app-paths-manifest.json +17 -17
  92. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  93. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  94. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  95. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  96. package/package.json +1 -1
  97. package/packages/mcp-server/dist/server.d.ts +12 -1
  98. package/packages/mcp-server/dist/server.d.ts.map +1 -1
  99. package/packages/mcp-server/dist/server.js +90 -42
  100. package/packages/mcp-server/dist/server.js.map +1 -1
  101. package/packages/mcp-server/dist/workflow-tools.js +1 -1
  102. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  103. package/packages/mcp-server/src/server.ts +110 -38
  104. package/packages/mcp-server/src/workflow-tools.ts +1 -1
  105. package/packages/pi-ai/dist/providers/anthropic-auth.test.d.ts +2 -0
  106. package/packages/pi-ai/dist/providers/anthropic-auth.test.d.ts.map +1 -0
  107. package/packages/pi-ai/dist/providers/anthropic-auth.test.js +20 -0
  108. package/packages/pi-ai/dist/providers/anthropic-auth.test.js.map +1 -0
  109. package/packages/pi-ai/dist/providers/anthropic.d.ts +2 -1
  110. package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
  111. package/packages/pi-ai/dist/providers/anthropic.js +7 -4
  112. package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
  113. package/packages/pi-ai/src/providers/anthropic-auth.test.ts +32 -0
  114. package/packages/pi-ai/src/providers/anthropic.ts +8 -4
  115. package/packages/pi-coding-agent/dist/core/agent-session-renderable-tools.test.d.ts +2 -0
  116. package/packages/pi-coding-agent/dist/core/agent-session-renderable-tools.test.d.ts.map +1 -0
  117. package/packages/pi-coding-agent/dist/core/agent-session-renderable-tools.test.js +61 -0
  118. package/packages/pi-coding-agent/dist/core/agent-session-renderable-tools.test.js.map +1 -0
  119. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  120. package/packages/pi-coding-agent/dist/core/agent-session.js +2 -1
  121. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  122. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts +10 -0
  123. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
  124. package/packages/pi-coding-agent/dist/core/auth-storage.js +27 -0
  125. package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
  126. package/packages/pi-coding-agent/dist/core/auth-storage.test.js +85 -0
  127. package/packages/pi-coding-agent/dist/core/auth-storage.test.js.map +1 -1
  128. package/packages/pi-coding-agent/dist/core/model-resolver-initial-model-auth.test.d.ts +2 -0
  129. package/packages/pi-coding-agent/dist/core/model-resolver-initial-model-auth.test.d.ts.map +1 -0
  130. package/packages/pi-coding-agent/dist/core/model-resolver-initial-model-auth.test.js +64 -0
  131. package/packages/pi-coding-agent/dist/core/model-resolver-initial-model-auth.test.js.map +1 -0
  132. package/packages/pi-coding-agent/dist/core/model-resolver.d.ts.map +1 -1
  133. package/packages/pi-coding-agent/dist/core/model-resolver.js +22 -18
  134. package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
  135. package/packages/pi-coding-agent/dist/core/model-resolver.test.d.ts +8 -0
  136. package/packages/pi-coding-agent/dist/core/model-resolver.test.d.ts.map +1 -0
  137. package/packages/pi-coding-agent/dist/core/model-resolver.test.js +75 -0
  138. package/packages/pi-coding-agent/dist/core/model-resolver.test.js.map +1 -0
  139. package/packages/pi-coding-agent/dist/core/sdk.d.ts +11 -0
  140. package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
  141. package/packages/pi-coding-agent/dist/core/sdk.js +38 -5
  142. package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
  143. package/packages/pi-coding-agent/dist/core/sdk.test.d.ts +2 -0
  144. package/packages/pi-coding-agent/dist/core/sdk.test.d.ts.map +1 -0
  145. package/packages/pi-coding-agent/dist/core/sdk.test.js +71 -0
  146. package/packages/pi-coding-agent/dist/core/sdk.test.js.map +1 -0
  147. package/packages/pi-coding-agent/dist/index.d.ts +1 -1
  148. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  149. package/packages/pi-coding-agent/dist/index.js +1 -1
  150. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  151. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/login-dialog.test.d.ts +2 -0
  152. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/login-dialog.test.d.ts.map +1 -0
  153. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/login-dialog.test.js +13 -0
  154. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/login-dialog.test.js.map +1 -0
  155. package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.d.ts +4 -0
  156. package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
  157. package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.js +24 -2
  158. package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.js.map +1 -1
  159. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  160. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js +9 -2
  161. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js.map +1 -1
  162. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +4 -0
  163. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  164. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +43 -0
  165. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  166. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  167. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +7 -2
  168. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  169. package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.d.ts.map +1 -1
  170. package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.js +6 -1
  171. package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.js.map +1 -1
  172. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  173. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +4 -3
  174. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  175. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js +4 -2
  176. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js.map +1 -1
  177. package/packages/pi-coding-agent/src/core/agent-session-renderable-tools.test.ts +70 -0
  178. package/packages/pi-coding-agent/src/core/agent-session.ts +2 -1
  179. package/packages/pi-coding-agent/src/core/auth-storage.test.ts +108 -0
  180. package/packages/pi-coding-agent/src/core/auth-storage.ts +30 -0
  181. package/packages/pi-coding-agent/src/core/model-resolver-initial-model-auth.test.ts +78 -0
  182. package/packages/pi-coding-agent/src/core/model-resolver.test.ts +85 -0
  183. package/packages/pi-coding-agent/src/core/model-resolver.ts +22 -18
  184. package/packages/pi-coding-agent/src/core/sdk.test.ts +89 -0
  185. package/packages/pi-coding-agent/src/core/sdk.ts +45 -9
  186. package/packages/pi-coding-agent/src/index.ts +1 -0
  187. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/login-dialog.test.ts +24 -0
  188. package/packages/pi-coding-agent/src/modes/interactive/components/login-dialog.ts +30 -2
  189. package/packages/pi-coding-agent/src/modes/interactive/components/model-selector.ts +15 -6
  190. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +47 -0
  191. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +7 -2
  192. package/packages/pi-coding-agent/src/modes/interactive/controllers/model-controller.ts +6 -1
  193. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +4 -3
  194. package/packages/pi-coding-agent/src/modes/interactive/slash-command-handlers.ts +4 -2
  195. package/src/resources/agents/debugger.md +58 -0
  196. package/src/resources/agents/doc-writer.md +43 -0
  197. package/src/resources/agents/git-ops.md +56 -0
  198. package/src/resources/agents/javascript-pro.md +46 -271
  199. package/src/resources/agents/planner.md +55 -0
  200. package/src/resources/agents/refactorer.md +47 -0
  201. package/src/resources/agents/reviewer.md +48 -0
  202. package/src/resources/agents/security.md +59 -0
  203. package/src/resources/agents/tester.md +50 -0
  204. package/src/resources/agents/typescript-pro.md +41 -235
  205. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +122 -8
  206. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +189 -6
  207. package/src/resources/extensions/gsd/auto/infra-errors.ts +38 -0
  208. package/src/resources/extensions/gsd/auto/loop-deps.ts +2 -0
  209. package/src/resources/extensions/gsd/auto/loop.ts +45 -1
  210. package/src/resources/extensions/gsd/auto/phases.ts +6 -0
  211. package/src/resources/extensions/gsd/auto/session.ts +11 -0
  212. package/src/resources/extensions/gsd/auto-dashboard.ts +29 -18
  213. package/src/resources/extensions/gsd/auto-model-selection.ts +9 -1
  214. package/src/resources/extensions/gsd/auto-prompts.ts +111 -33
  215. package/src/resources/extensions/gsd/auto-start.ts +40 -6
  216. package/src/resources/extensions/gsd/auto-worktree.ts +1 -1
  217. package/src/resources/extensions/gsd/auto.ts +72 -0
  218. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +3 -3
  219. package/src/resources/extensions/gsd/bootstrap/register-shortcuts.ts +79 -60
  220. package/src/resources/extensions/gsd/commands/handlers/auto.ts +10 -36
  221. package/src/resources/extensions/gsd/commands/handlers/core.ts +58 -11
  222. package/src/resources/extensions/gsd/commands/handlers/notifications-handler.ts +17 -7
  223. package/src/resources/extensions/gsd/commands/handlers/workflow.ts +4 -10
  224. package/src/resources/extensions/gsd/dashboard-overlay.ts +10 -3
  225. package/src/resources/extensions/gsd/doctor-providers.ts +24 -0
  226. package/src/resources/extensions/gsd/forensics.ts +23 -7
  227. package/src/resources/extensions/gsd/gate-registry.ts +251 -0
  228. package/src/resources/extensions/gsd/gsd-db.ts +51 -0
  229. package/src/resources/extensions/gsd/guided-flow.ts +5 -10
  230. package/src/resources/extensions/gsd/interrupted-session.ts +1 -0
  231. package/src/resources/extensions/gsd/metrics.ts +12 -1
  232. package/src/resources/extensions/gsd/milestone-actions.ts +10 -3
  233. package/src/resources/extensions/gsd/milestone-validation-gates.ts +11 -13
  234. package/src/resources/extensions/gsd/notification-overlay.ts +47 -14
  235. package/src/resources/extensions/gsd/notification-store.ts +35 -4
  236. package/src/resources/extensions/gsd/notification-widget.ts +5 -14
  237. package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +10 -3
  238. package/src/resources/extensions/gsd/prompt-validation.ts +157 -0
  239. package/src/resources/extensions/gsd/prompts/complete-slice.md +3 -1
  240. package/src/resources/extensions/gsd/prompts/execute-task.md +2 -0
  241. package/src/resources/extensions/gsd/prompts/validate-milestone.md +2 -0
  242. package/src/resources/extensions/gsd/session-model-override.ts +36 -0
  243. package/src/resources/extensions/gsd/shortcut-defs.ts +56 -0
  244. package/src/resources/extensions/gsd/state.ts +13 -2
  245. package/src/resources/extensions/gsd/tests/auto-start-model-capture.test.ts +25 -9
  246. package/src/resources/extensions/gsd/tests/complete-slice-gate-closure.test.ts +167 -0
  247. package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +36 -0
  248. package/src/resources/extensions/gsd/tests/forensics-stuck-loops.test.ts +62 -0
  249. package/src/resources/extensions/gsd/tests/format-shortcut.test.ts +31 -0
  250. package/src/resources/extensions/gsd/tests/gate-dispatch.test.ts +27 -0
  251. package/src/resources/extensions/gsd/tests/gate-registry.test.ts +140 -0
  252. package/src/resources/extensions/gsd/tests/infra-errors-cooldown.test.ts +180 -0
  253. package/src/resources/extensions/gsd/tests/integration/auto-worktree-milestone-merge.test.ts +66 -1
  254. package/src/resources/extensions/gsd/tests/model-isolation.test.ts +36 -51
  255. package/src/resources/extensions/gsd/tests/notification-store.test.ts +18 -0
  256. package/src/resources/extensions/gsd/tests/notification-widget.test.ts +3 -2
  257. package/src/resources/extensions/gsd/tests/notifications-handler.test.ts +90 -0
  258. package/src/resources/extensions/gsd/tests/parallel-monitor-overlay.test.ts +1 -0
  259. package/src/resources/extensions/gsd/tests/park-db-sync.test.ts +18 -0
  260. package/src/resources/extensions/gsd/tests/prompt-system-gate-coverage.test.ts +208 -0
  261. package/src/resources/extensions/gsd/tests/register-shortcuts.test.ts +63 -5
  262. package/src/resources/extensions/gsd/tests/session-model-override.test.ts +35 -0
  263. package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +90 -0
  264. package/src/resources/extensions/gsd/tools/complete-slice.ts +63 -0
  265. package/src/resources/extensions/gsd/tools/complete-task.ts +63 -0
  266. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +4 -1
  267. package/src/resources/extensions/gsd/types.ts +26 -0
  268. package/src/resources/extensions/ollama/index.ts +13 -3
  269. package/src/resources/extensions/ollama/ollama-status-indicator.test.ts +28 -0
  270. package/src/resources/extensions/shared/gsd-phase-state.ts +42 -0
  271. package/src/resources/extensions/shared/tests/gsd-phase-state.test.ts +48 -0
  272. package/src/resources/extensions/subagent/agents.ts +10 -0
  273. package/src/resources/extensions/subagent/index.ts +18 -0
  274. package/src/resources/extensions/subagent/tests/agents-conflicts.test.ts +33 -0
  275. /package/dist/web/standalone/.next/static/{dYVdRaunb2ZSEA8fjkT-V → OI4n_CKC-lM8IQbvGJ_tK}/_buildManifest.js +0 -0
  276. /package/dist/web/standalone/.next/static/{dYVdRaunb2ZSEA8fjkT-V → OI4n_CKC-lM8IQbvGJ_tK}/_ssgManifest.js +0 -0
@@ -35,6 +35,7 @@ let _basePath: string | null = null;
35
35
  let _lineCount = 0; // Hint for rotation — not authoritative for public API
36
36
  let _suppressCount = 0;
37
37
  let _recentMessageTimestamps = new Map<string, number>();
38
+ const _changeListeners = new Set<() => void>();
38
39
 
39
40
  // ─── Public API ─────────────────────────────────────────────────────────
40
41
 
@@ -93,6 +94,7 @@ export function appendNotification(
93
94
  if (_lineCount > MAX_ENTRIES) {
94
95
  _rotate();
95
96
  }
97
+ _emitChange();
96
98
  } catch {
97
99
  // Non-fatal — never let persistence break the caller
98
100
  }
@@ -121,6 +123,7 @@ export function markAllRead(basePath?: string): void {
121
123
  const hasUnread = entries.some((e) => !e.read);
122
124
  if (!hasUnread) return;
123
125
 
126
+ let changed = false;
124
127
  try {
125
128
  _withLock(bp, () => {
126
129
  // Re-read inside lock to get freshest state
@@ -128,10 +131,12 @@ export function markAllRead(basePath?: string): void {
128
131
  if (fresh.length === 0 || !fresh.some((e) => !e.read)) return;
129
132
  const lines = fresh.map((e) => JSON.stringify({ ...e, read: true }));
130
133
  _atomicWrite(bp, lines.join("\n") + "\n");
134
+ changed = true;
131
135
  });
132
136
  } catch {
133
137
  // Non-fatal
134
138
  }
139
+ if (changed) _emitChange();
135
140
  }
136
141
 
137
142
  /**
@@ -145,6 +150,8 @@ export function clearNotifications(basePath?: string): void {
145
150
  _withLock(bp, () => {
146
151
  _atomicWrite(bp, "");
147
152
  });
153
+ _lineCount = 0;
154
+ _emitChange();
148
155
  } catch {
149
156
  // Non-fatal
150
157
  }
@@ -189,6 +196,17 @@ export function unsuppressPersistence(): void {
189
196
  _suppressCount = Math.max(0, _suppressCount - 1);
190
197
  }
191
198
 
199
+ /**
200
+ * Subscribe to notification-store mutations (append, mark-read, clear).
201
+ * Returns an unsubscribe function.
202
+ */
203
+ export function onNotificationStoreChange(listener: () => void): () => void {
204
+ _changeListeners.add(listener);
205
+ return () => {
206
+ _changeListeners.delete(listener);
207
+ };
208
+ }
209
+
192
210
  // ─── Test Helpers ───────────────────────────────────────────────────────
193
211
 
194
212
  /**
@@ -199,6 +217,7 @@ export function _resetNotificationStore(): void {
199
217
  _lineCount = 0;
200
218
  _suppressCount = 0;
201
219
  _recentMessageTimestamps = new Map();
220
+ _changeListeners.clear();
202
221
  }
203
222
 
204
223
  // ─── Internal ───────────────────────────────────────────────────────────
@@ -234,12 +253,23 @@ function _rotate(): void {
234
253
  const trimmed = entries.slice(entries.length - MAX_ENTRIES);
235
254
  const lines = trimmed.map((e) => JSON.stringify(e));
236
255
  _atomicWrite(_basePath!, lines.join("\n") + "\n");
256
+ _lineCount = trimmed.length;
237
257
  });
238
258
  } catch {
239
259
  // Non-fatal
240
260
  }
241
261
  }
242
262
 
263
+ function _emitChange(): void {
264
+ for (const listener of _changeListeners) {
265
+ try {
266
+ listener();
267
+ } catch {
268
+ // Non-fatal
269
+ }
270
+ }
271
+ }
272
+
243
273
  /**
244
274
  * Atomic file rewrite via temp-file + rename. Prevents partial reads
245
275
  * by other processes (web API subprocess, parallel workers).
@@ -293,10 +323,11 @@ function _withLock<T>(basePath: string, fn: () => T): T {
293
323
  }
294
324
  }
295
325
 
296
- // Only run the mutation if we actually own the lock
297
- const ownsLock = fd !== null;
326
+ // Best-effort: mutation runs regardless of lock status (idempotent overwrites).
327
+ // createdLock gates cleanup only — never skip fn() on lock failure.
328
+ const createdLock = fd !== null;
298
329
  try {
299
- if (ownsLock && fd !== null) {
330
+ if (createdLock && fd !== null) {
300
331
  // Write our PID timestamp into the lock for stale detection
301
332
  writeFileSync(lockPath, String(Date.now()), "utf-8");
302
333
  closeSync(fd);
@@ -304,7 +335,7 @@ function _withLock<T>(basePath: string, fn: () => T): T {
304
335
  return fn();
305
336
  } finally {
306
337
  // Only delete the lock if we created it — never remove another process's lock
307
- if (ownsLock) {
338
+ if (createdLock) {
308
339
  try { unlinkSync(lockPath); } catch { /* best-effort cleanup */ }
309
340
  }
310
341
  }
@@ -5,8 +5,8 @@
5
5
 
6
6
  import type { ExtensionContext } from "@gsd/pi-coding-agent";
7
7
 
8
- import { getUnreadCount, readNotifications } from "./notification-store.js";
9
- import { formatShortcut } from "./files.js";
8
+ import { getUnreadCount, onNotificationStoreChange } from "./notification-store.js";
9
+ import { formattedShortcutPair } from "./shortcut-defs.js";
10
10
 
11
11
  // ─── Pure rendering ──���────────────────────────���─────────────────────────
12
12
 
@@ -14,18 +14,7 @@ export function buildNotificationWidgetLines(): string[] {
14
14
  const unread = getUnreadCount();
15
15
  if (unread === 0) return [];
16
16
 
17
- const entries = readNotifications();
18
- const latest = entries[0]; // newest-first
19
- if (!latest) return [];
20
-
21
- const icon = latest.severity === "error" ? "✗" : latest.severity === "warning" ? "⚠" : "●";
22
- const badge = `${unread} unread`;
23
- const msgMax = 80;
24
- const truncated = latest.message.length > msgMax
25
- ? latest.message.slice(0, msgMax - 1) + "…"
26
- : latest.message;
27
-
28
- return [` ${icon} [${badge}] ${truncated} (${formatShortcut("Ctrl+Alt+N")} or /gsd notifications)`];
17
+ return [` 🔔 Notifications: ${unread} unread (${formattedShortcutPair("notifications")})`];
29
18
  }
30
19
 
31
20
  // ─── Widget init ────────────────────────────────────────────────────────
@@ -51,6 +40,7 @@ export function initNotificationWidget(ctx: ExtensionContext): void {
51
40
  _tui.requestRender();
52
41
  };
53
42
 
43
+ const unsubscribe = onNotificationStoreChange(refresh);
54
44
  const refreshTimer = setInterval(refresh, REFRESH_INTERVAL_MS);
55
45
 
56
46
  return {
@@ -62,6 +52,7 @@ export function initNotificationWidget(ctx: ExtensionContext): void {
62
52
  cachedLines = undefined;
63
53
  },
64
54
  dispose(): void {
55
+ unsubscribe();
65
56
  clearInterval(refreshTimer);
66
57
  },
67
58
  };
@@ -2,7 +2,8 @@
2
2
  * GSD Parallel Monitor Overlay
3
3
  *
4
4
  * Full-screen TUI overlay showing real-time parallel worker progress.
5
- * Opened via `/gsd parallel watch` or Ctrl+Alt+P (⌃⌥P on macOS).
5
+ * Opened via `/gsd parallel watch`, Ctrl+Alt+P (⌃⌥P on macOS),
6
+ * or Ctrl+Shift+P fallback.
6
7
  * Reads the same data sources as `scripts/parallel-monitor.mjs` but
7
8
  * renders as a native pi-tui overlay with theme integration.
8
9
  */
@@ -15,6 +16,7 @@ import type { Theme } from "@gsd/pi-coding-agent";
15
16
  import { truncateToWidth, visibleWidth, matchesKey, Key } from "@gsd/pi-tui";
16
17
 
17
18
  import { formatDuration, STATUS_GLYPH, STATUS_COLOR } from "../shared/mod.js";
19
+ import { formattedShortcutPair } from "./shortcut-defs.js";
18
20
 
19
21
  // ─── Types ────────────────────────────────────────────────────────────────
20
22
 
@@ -347,7 +349,12 @@ export class ParallelMonitorOverlay {
347
349
  }
348
350
 
349
351
  handleInput(data: string): void {
350
- if (matchesKey(data, Key.escape) || data === "q") {
352
+ if (
353
+ matchesKey(data, Key.escape) ||
354
+ matchesKey(data, Key.ctrlAlt("p")) ||
355
+ matchesKey(data, Key.ctrlShift("p")) ||
356
+ data === "q"
357
+ ) {
351
358
  this.dispose();
352
359
  this.onClose();
353
360
  return;
@@ -486,7 +493,7 @@ export class ParallelMonitorOverlay {
486
493
  }
487
494
  lines.push(` ${t.bold("Total: $" + this.workers.reduce((s, wk) => s + wk.cost, 0).toFixed(2))}`);
488
495
  }
489
- lines.push(t.fg("muted", " ESC/q to close │ ↑↓ scroll"));
496
+ lines.push(t.fg("muted", ` ESC/q/${formattedShortcutPair("parallel")} close │ ↑↓ scroll`));
490
497
 
491
498
  // Apply scroll — use terminal rows as height estimate
492
499
  const termHeight = process.stdout.rows || 40;
@@ -0,0 +1,157 @@
1
+ /**
2
+ * GSD Prompt Validation — Validates enhanced context and turn output
3
+ * artifacts before writing.
4
+ *
5
+ * Implements R109 validation requirement: CONTEXT.md must have required
6
+ * sections before being written to disk. Additionally, per-turn validators
7
+ * check that artifacts produced by gate-owning turns contain the gate
8
+ * sections declared in gate-registry.ts, so a malformed summary/validation
9
+ * markdown file cannot silently drop a quality gate.
10
+ */
11
+
12
+ import { getGatesForTurn, type OwnerTurn } from "./gate-registry.js";
13
+
14
+ /**
15
+ * Result of validating enhanced context output.
16
+ */
17
+ export interface ValidationResult {
18
+ /** Whether all required sections are present. */
19
+ valid: boolean;
20
+ /** List of missing required sections. */
21
+ missing: string[];
22
+ }
23
+
24
+ /**
25
+ * Validate that enhanced context content has all required sections.
26
+ *
27
+ * Required sections per R109:
28
+ * - Scope section (## Scope, ## Milestone Scope, or ## Why This Milestone)
29
+ * - Architectural Decisions section (## Architectural Decisions)
30
+ * - Acceptance Criteria section (## Acceptance Criteria or ## Final Integrated Acceptance)
31
+ *
32
+ * Additionally validates that the Architectural Decisions section contains
33
+ * at least one decision entry (### heading or **Decision marker).
34
+ *
35
+ * @param content - The enhanced context markdown content
36
+ * @returns ValidationResult with valid flag and list of missing sections
37
+ */
38
+ export function validateEnhancedContext(content: string): ValidationResult {
39
+ const missing: string[] = [];
40
+
41
+ // Required section 1: Scope (multiple acceptable header variants)
42
+ const hasScopeSection =
43
+ /^## Scope\b/m.test(content) ||
44
+ /^## Milestone Scope\b/m.test(content) ||
45
+ /^## Why This Milestone\b/m.test(content);
46
+
47
+ if (!hasScopeSection) {
48
+ missing.push("Milestone Scope or Why This Milestone");
49
+ }
50
+
51
+ // Required section 2: Architectural Decisions
52
+ const hasArchitecturalDecisions = /^## Architectural Decisions\b/m.test(content);
53
+ if (!hasArchitecturalDecisions) {
54
+ missing.push("Architectural Decisions");
55
+ }
56
+
57
+ // Required section 3: Acceptance Criteria (multiple acceptable header variants)
58
+ const hasAcceptanceCriteria =
59
+ /^## Acceptance Criteria\b/m.test(content) ||
60
+ /^## Final Integrated Acceptance\b/m.test(content);
61
+
62
+ if (!hasAcceptanceCriteria) {
63
+ missing.push("Acceptance Criteria");
64
+ }
65
+
66
+ // Additional validation: Architectural Decisions must have at least one entry
67
+ if (hasArchitecturalDecisions) {
68
+ // Extract the section content between ## Architectural Decisions and the next ## heading.
69
+ // Uses indexOf-based extraction instead of regex with \z (which is invalid in JavaScript
70
+ // regex — it's PCRE/Ruby syntax and JS treats it as literal 'z').
71
+ const sectionStart = content.indexOf("## Architectural Decisions");
72
+ if (sectionStart === -1) {
73
+ missing.push("Architectural Decisions");
74
+ } else {
75
+ const afterHeading = content.slice(sectionStart + "## Architectural Decisions".length);
76
+ const nextSection = afterHeading.search(/^## /m);
77
+ const sectionContent = nextSection === -1 ? afterHeading : afterHeading.slice(0, nextSection);
78
+
79
+ // Check for actual decision entries:
80
+ // - ### heading (subsection per decision)
81
+ // - **Decision marker (inline decision format)
82
+ const hasDecisionEntry = /^### /m.test(sectionContent) || /^\*\*Decision/m.test(sectionContent);
83
+
84
+ if (!hasDecisionEntry) {
85
+ missing.push("At least one architectural decision entry");
86
+ }
87
+ }
88
+ }
89
+
90
+ return {
91
+ valid: missing.length === 0,
92
+ missing,
93
+ };
94
+ }
95
+
96
+ // ─── Per-Turn Gate Section Validators ─────────────────────────────────────
97
+ //
98
+ // Each validator checks that the artifact written by a turn contains a
99
+ // heading for every gate owned by that turn. The registry is the source
100
+ // of truth for which sections must exist; adding a new gate automatically
101
+ // flows through via `getGatesForTurn(turn)`.
102
+
103
+ /**
104
+ * Escape a string so it can be embedded safely inside a regular expression.
105
+ */
106
+ function escapeRegExp(value: string): string {
107
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
108
+ }
109
+
110
+ /**
111
+ * Validate that an artifact contains an `## H2` heading for every gate the
112
+ * named turn owns. Returns the list of missing gate section headers.
113
+ *
114
+ * Soft rule: a section counts as "present" if it is declared (H2 heading
115
+ * exists) — empty-body sections are allowed and handled by the tool
116
+ * handler, which will record such gates as `omitted`.
117
+ */
118
+ export function validateGateSections(
119
+ content: string,
120
+ turn: OwnerTurn,
121
+ ): ValidationResult {
122
+ const missing: string[] = [];
123
+ for (const def of getGatesForTurn(turn)) {
124
+ const pattern = new RegExp(`^##\\s+${escapeRegExp(def.promptSection)}\\b`, "m");
125
+ if (!pattern.test(content)) {
126
+ missing.push(`${def.id} (## ${def.promptSection})`);
127
+ }
128
+ }
129
+ return { valid: missing.length === 0, missing };
130
+ }
131
+
132
+ /**
133
+ * Validate a SUMMARY.md produced by the complete-slice turn. Requires
134
+ * an H2 heading for every gate owned by complete-slice (e.g. Q8 →
135
+ * "## Operational Readiness"). Intended for use in the tool handler's
136
+ * pre-write checks or in the post-unit validation sweep.
137
+ */
138
+ export function validateSliceSummaryOutput(content: string): ValidationResult {
139
+ return validateGateSections(content, "complete-slice");
140
+ }
141
+
142
+ /**
143
+ * Validate a task SUMMARY.md produced by the execute-task turn. Only
144
+ * flags gates that are still pending for the task; skips the check
145
+ * when no rows are seeded (simple task).
146
+ */
147
+ export function validateTaskSummaryOutput(content: string): ValidationResult {
148
+ return validateGateSections(content, "execute-task");
149
+ }
150
+
151
+ /**
152
+ * Validate a VALIDATION.md produced by the validate-milestone turn.
153
+ * Requires an H2 heading for every MV gate declared in the registry.
154
+ */
155
+ export function validateMilestoneValidationOutput(content: string): ValidationResult {
156
+ return validateGateSections(content, "validate-milestone");
157
+ }
@@ -16,6 +16,8 @@ All relevant context has been preloaded below — the slice plan, all task summa
16
16
 
17
17
  {{inlinedContext}}
18
18
 
19
+ {{gatesToClose}}
20
+
19
21
  **Match effort to complexity.** A simple slice with 1-2 tasks needs a brief summary and lightweight verification. A complex slice with 5 tasks across multiple subsystems needs thorough verification and a detailed summary. Scale the work below accordingly.
20
22
 
21
23
  Then:
@@ -23,7 +25,7 @@ Then:
23
25
  2. {{skillActivation}}
24
26
  3. Run all slice-level verification checks defined in the slice plan. All must pass before marking the slice done. If any fail, fix them first. Task artifacts use a **flat file layout** directly inside `tasks/` (for example `T01-SUMMARY.md`, `T02-SUMMARY.md`) rather than per-task subdirectories. If you need to count or re-read task summaries during verification, use `find .gsd/milestones/{{milestoneId}}/slices/{{sliceId}}/tasks -name "*-SUMMARY.md"` or `ls .gsd/milestones/{{milestoneId}}/slices/{{sliceId}}/tasks/*-SUMMARY.md`. Never use `tasks/*/SUMMARY.md` — that glob expects subdirectories that do not exist.
25
27
  4. If the slice plan includes observability/diagnostic surfaces, confirm they work. Skip this for simple slices that don't have observability sections.
26
- 5. If the slice involved runtime behavior, fill the **Operational Readiness** section (Q8) in the slice summary: health signal, failure signal, recovery procedure, and monitoring gaps. Omit entirely for simple slices with no runtime concerns.
28
+ 5. Address every gate listed in the **Gates to Close** section above — each gate maps to a specific slice-summary section the handler inspects (for example, Q8 maps to **Operational Readiness**: health signal, failure signal, recovery procedure, and monitoring gaps). Leaving a section empty records the gate as `omitted`.
27
29
  6. If this slice produced evidence that a requirement changed status (Active → Validated, Active → Deferred, etc.), call `gsd_requirement_update` with the requirement ID, updated `status`, and `validation` evidence. Do NOT write `.gsd/REQUIREMENTS.md` directly — the engine renders it from the database.
28
30
  7. Prepare the slice completion content you will pass to `gsd_complete_slice` using the camelCase fields `milestoneId`, `sliceId`, `sliceTitle`, `oneLiner`, `narrative`, `verification`, and `uatContent`. Do **not** manually write `{{sliceSummaryPath}}`. Do **not** manually write `{{sliceUatPath}}` — the DB-backed tool is the canonical write path for both artifacts.
29
31
  8. Draft the UAT content you will pass as `uatContent` — a concrete UAT script with real test cases derived from the slice plan and task summaries. Include preconditions, numbered steps with expected outcomes, and edge cases. This must NOT be a placeholder or generic template — tailor every test case to what this slice actually built.
@@ -22,6 +22,8 @@ A researcher explored the codebase and a planner decomposed the work — you are
22
22
 
23
23
  {{slicePlanExcerpt}}
24
24
 
25
+ {{gatesToClose}}
26
+
25
27
  ## Backing Source Artifacts
26
28
  - Slice plan: `{{planPath}}`
27
29
  - Task plan source: `{{taskPlanPath}}`
@@ -18,6 +18,8 @@ All relevant context has been preloaded below — the roadmap, all slice summari
18
18
 
19
19
  {{inlinedContext}}
20
20
 
21
+ {{gatesToEvaluate}}
22
+
21
23
  ## Execution Protocol
22
24
 
23
25
  ### Step 1 — Dispatch Parallel Reviewers
@@ -0,0 +1,36 @@
1
+ export interface SessionModelOverride {
2
+ provider: string;
3
+ id: string;
4
+ }
5
+
6
+ const sessionOverrides = new Map<string, SessionModelOverride>();
7
+
8
+ function normalizeSessionId(sessionId: string): string {
9
+ return typeof sessionId === "string" ? sessionId.trim() : "";
10
+ }
11
+
12
+ export function setSessionModelOverride(
13
+ sessionId: string,
14
+ override: SessionModelOverride,
15
+ ): void {
16
+ const key = normalizeSessionId(sessionId);
17
+ if (!key) return;
18
+ sessionOverrides.set(key, {
19
+ provider: override.provider,
20
+ id: override.id,
21
+ });
22
+ }
23
+
24
+ export function getSessionModelOverride(
25
+ sessionId: string,
26
+ ): SessionModelOverride | undefined {
27
+ const key = normalizeSessionId(sessionId);
28
+ if (!key) return undefined;
29
+ return sessionOverrides.get(key);
30
+ }
31
+
32
+ export function clearSessionModelOverride(sessionId: string): void {
33
+ const key = normalizeSessionId(sessionId);
34
+ if (!key) return;
35
+ sessionOverrides.delete(key);
36
+ }
@@ -0,0 +1,56 @@
1
+ // Canonical GSD shortcut definitions used by registration, help text, and overlays.
2
+
3
+ import { formatShortcut } from "./files.js";
4
+
5
+ export type GSDShortcutId = "dashboard" | "notifications" | "parallel";
6
+
7
+ type GSDShortcutDef = {
8
+ key: "g" | "n" | "p";
9
+ action: string;
10
+ command: string;
11
+ /** Whether the Ctrl+Shift fallback is registered (false when it conflicts with an app keybinding). */
12
+ hasFallback: boolean;
13
+ };
14
+
15
+ export const GSD_SHORTCUTS: Record<GSDShortcutId, GSDShortcutDef> = {
16
+ dashboard: {
17
+ key: "g",
18
+ action: "Open GSD dashboard",
19
+ command: "/gsd status",
20
+ hasFallback: true,
21
+ },
22
+ notifications: {
23
+ key: "n",
24
+ action: "Open notification history",
25
+ command: "/gsd notifications",
26
+ hasFallback: true,
27
+ },
28
+ parallel: {
29
+ key: "p",
30
+ action: "Open parallel worker monitor",
31
+ command: "/gsd parallel watch",
32
+ hasFallback: false, // Ctrl+Shift+P conflicts with cycleModelBackward
33
+ },
34
+ };
35
+
36
+ function combo(prefix: "Ctrl+Alt+" | "Ctrl+Shift+", key: string): string {
37
+ return `${prefix}${key.toUpperCase()}`;
38
+ }
39
+
40
+ export function primaryShortcutCombo(id: GSDShortcutId): string {
41
+ return combo("Ctrl+Alt+", GSD_SHORTCUTS[id].key);
42
+ }
43
+
44
+ export function fallbackShortcutCombo(id: GSDShortcutId): string {
45
+ return combo("Ctrl+Shift+", GSD_SHORTCUTS[id].key);
46
+ }
47
+
48
+ export function shortcutPair(id: GSDShortcutId, formatter: (combo: string) => string = (combo) => combo): string {
49
+ const primary = formatter(primaryShortcutCombo(id));
50
+ if (!GSD_SHORTCUTS[id].hasFallback) return primary;
51
+ return `${primary} / ${formatter(fallbackShortcutCombo(id))}`;
52
+ }
53
+
54
+ export function formattedShortcutPair(id: GSDShortcutId): string {
55
+ return shortcutPair(id, formatShortcut);
56
+ }
@@ -58,7 +58,7 @@ import {
58
58
  insertSlice,
59
59
  insertTask,
60
60
  updateTaskStatus,
61
- getPendingSliceGateCount,
61
+ getPendingGateCountForTurn,
62
62
  type MilestoneRow,
63
63
  type SliceRow,
64
64
  type TaskRow,
@@ -864,7 +864,18 @@ export async function deriveStateFromDb(basePath: string): Promise<GSDState> {
864
864
  }
865
865
  }
866
866
 
867
- const pendingGateCount = getPendingSliceGateCount(activeMilestone.id, activeSlice.id);
867
+ // ── Quality gate evaluation check ──────────────────────────────────
868
+ // Pause before execution only when gates owned by the `gate-evaluate`
869
+ // turn (Q3/Q4) are still pending. Q8 is also `scope:"slice"` but is
870
+ // owned by `complete-slice`, so it must NOT block the evaluating-gates
871
+ // phase — otherwise auto-loop stalls forever waiting for a gate that
872
+ // this turn never evaluates. See gate-registry.ts for the ownership map.
873
+ // Slices with zero gate rows (pre-feature or simple) skip straight through.
874
+ const pendingGateCount = getPendingGateCountForTurn(
875
+ activeMilestone.id,
876
+ activeSlice.id,
877
+ "gate-evaluate",
878
+ );
868
879
  if (pendingGateCount > 0) {
869
880
  return {
870
881
  activeMilestone, activeSlice, activeTask: null,
@@ -7,9 +7,8 @@ const sourcePath = join(import.meta.dirname, "..", "auto-start.ts");
7
7
  const source = readFileSync(sourcePath, "utf-8");
8
8
 
9
9
  test("bootstrapAutoSession snapshots ctx.model before guided-flow entry (#2829)", () => {
10
- // #3517 changed the snapshot to prefer GSD preferences, but the ordering
11
- // guarantee still holds: the snapshot must be built before guided-flow.
12
- const snapshotIdx = source.indexOf("const startModelSnapshot = preferredModel");
10
+ // The snapshot ordering guarantee still holds: build snapshot before guided-flow.
11
+ const snapshotIdx = source.indexOf("const startModelSnapshot = manualSessionOverride");
13
12
  assert.ok(snapshotIdx > -1, "auto-start.ts should snapshot model at bootstrap start");
14
13
 
15
14
  const firstDiscussIdx = source.indexOf('await showSmartEntry(ctx, pi, base, { step: requestedStepMode });');
@@ -29,8 +28,11 @@ test("bootstrapAutoSession restores autoModeStartModel from the early snapshot (
29
28
  assert.ok(snapshotRefIdx > -1, "autoModeStartModel should be restored from startModelSnapshot");
30
29
  });
31
30
 
32
- test("bootstrapAutoSession prefers GSD PREFERENCES.md over settings.json for start model (#3517)", () => {
33
- // resolveDefaultSessionModel() should be called before the snapshot is built
31
+ test("bootstrapAutoSession checks manual session override before preferences", () => {
32
+ const manualIdx = source.indexOf("const manualSessionOverride = getSessionModelOverride(");
33
+ assert.ok(manualIdx > -1, "auto-start.ts should read session model override first");
34
+
35
+ // resolveDefaultSessionModel() should still be called for fallback behavior
34
36
  const preferredIdx = source.indexOf("const preferredModel = resolveDefaultSessionModel(");
35
37
  assert.ok(preferredIdx > -1, "auto-start.ts should call resolveDefaultSessionModel()");
36
38
 
@@ -38,11 +40,25 @@ test("bootstrapAutoSession prefers GSD PREFERENCES.md over settings.json for sta
38
40
  const withProviderIdx = source.indexOf("resolveDefaultSessionModel(ctx.model?.provider)");
39
41
  assert.ok(withProviderIdx > -1, "auto-start.ts should pass ctx.model?.provider for bare ID resolution");
40
42
 
41
- const snapshotIdx = source.indexOf("const startModelSnapshot = preferredModel");
42
- assert.ok(snapshotIdx > -1, "startModelSnapshot should use preferredModel when available");
43
+ const snapshotIdx = source.indexOf("const startModelSnapshot = manualSessionOverride");
44
+ assert.ok(snapshotIdx > -1, "startModelSnapshot should prefer manual session override");
43
45
 
44
46
  assert.ok(
45
- preferredIdx < snapshotIdx,
46
- "resolveDefaultSessionModel() must be called before building startModelSnapshot",
47
+ manualIdx < snapshotIdx && preferredIdx < snapshotIdx,
48
+ "manual override and preference fallback must be resolved before building startModelSnapshot",
47
49
  );
48
50
  });
51
+
52
+ test("bootstrapAutoSession validates preferred model against live registry auth (#unconfigured-models)", () => {
53
+ // The raw PREFERENCES.md value must be validated against getAvailable()
54
+ // before being captured as the snapshot, so an unconfigured provider
55
+ // (no API key / OAuth) can't become autoModeStartModel.
56
+ const validationIdx = source.indexOf("ctx.modelRegistry.getAvailable()");
57
+ assert.ok(validationIdx > -1, "auto-start.ts should validate preferred model against getAvailable()");
58
+
59
+ const resolveModelIdIdx = source.indexOf("resolveModelId");
60
+ assert.ok(resolveModelIdIdx > -1, "auto-start.ts should resolve preferred model against the registry");
61
+
62
+ const warningIdx = source.indexOf("is not configured; falling back to session default");
63
+ assert.ok(warningIdx > -1, "auto-start.ts should warn when preferred model is unconfigured");
64
+ });