gsd-pi 2.71.0 → 2.72.0-dev.de4c4b3

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 (459) hide show
  1. package/README.md +57 -17
  2. package/dist/cli.js +29 -3
  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 +40 -17
  7. package/dist/provider-migrations.d.ts +10 -0
  8. package/dist/provider-migrations.js +12 -0
  9. package/dist/resource-loader.js +139 -13
  10. package/dist/resources/GSD-WORKFLOW.md +1 -1
  11. package/dist/resources/agents/debugger.md +58 -0
  12. package/dist/resources/agents/doc-writer.md +43 -0
  13. package/dist/resources/agents/git-ops.md +56 -0
  14. package/dist/resources/agents/javascript-pro.md +46 -271
  15. package/dist/resources/agents/planner.md +55 -0
  16. package/dist/resources/agents/refactorer.md +47 -0
  17. package/dist/resources/agents/reviewer.md +48 -0
  18. package/dist/resources/agents/security.md +59 -0
  19. package/dist/resources/agents/tester.md +50 -0
  20. package/dist/resources/agents/typescript-pro.md +41 -235
  21. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +113 -10
  22. package/dist/resources/extensions/gsd/auto/infra-errors.js +34 -0
  23. package/dist/resources/extensions/gsd/auto/loop.js +32 -1
  24. package/dist/resources/extensions/gsd/auto/phases.js +5 -1
  25. package/dist/resources/extensions/gsd/auto/session.js +11 -0
  26. package/dist/resources/extensions/gsd/auto-dashboard.js +22 -16
  27. package/dist/resources/extensions/gsd/auto-model-selection.js +10 -2
  28. package/dist/resources/extensions/gsd/auto-prompts.js +88 -33
  29. package/dist/resources/extensions/gsd/auto-start.js +34 -7
  30. package/dist/resources/extensions/gsd/auto-tool-tracking.js +1 -1
  31. package/dist/resources/extensions/gsd/auto-worktree.js +1 -1
  32. package/dist/resources/extensions/gsd/auto.js +56 -0
  33. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +3 -3
  34. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +2 -0
  35. package/dist/resources/extensions/gsd/bootstrap/register-shortcuts.js +63 -51
  36. package/dist/resources/extensions/gsd/bootstrap/system-context.js +6 -0
  37. package/dist/resources/extensions/gsd/commands/context.js +15 -6
  38. package/dist/resources/extensions/gsd/commands/dispatcher.js +12 -2
  39. package/dist/resources/extensions/gsd/commands/handlers/auto.js +10 -33
  40. package/dist/resources/extensions/gsd/commands/handlers/core.js +56 -11
  41. package/dist/resources/extensions/gsd/commands/handlers/notifications-handler.js +15 -6
  42. package/dist/resources/extensions/gsd/commands/handlers/workflow.js +4 -10
  43. package/dist/resources/extensions/gsd/dashboard-overlay.js +8 -3
  44. package/dist/resources/extensions/gsd/dispatch-guard.js +18 -1
  45. package/dist/resources/extensions/gsd/doctor-providers.js +23 -0
  46. package/dist/resources/extensions/gsd/error-classifier.js +5 -2
  47. package/dist/resources/extensions/gsd/forensics.js +19 -6
  48. package/dist/resources/extensions/gsd/gate-registry.js +208 -0
  49. package/dist/resources/extensions/gsd/gsd-db.js +41 -0
  50. package/dist/resources/extensions/gsd/guided-flow.js +5 -10
  51. package/dist/resources/extensions/gsd/metrics.js +1 -0
  52. package/dist/resources/extensions/gsd/milestone-actions.js +10 -4
  53. package/dist/resources/extensions/gsd/milestone-validation-gates.js +11 -12
  54. package/dist/resources/extensions/gsd/notification-overlay.js +42 -13
  55. package/dist/resources/extensions/gsd/notification-store.js +56 -5
  56. package/dist/resources/extensions/gsd/notification-widget.js +5 -13
  57. package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +8 -3
  58. package/dist/resources/extensions/gsd/pre-execution-checks.js +35 -2
  59. package/dist/resources/extensions/gsd/prompt-validation.js +126 -0
  60. package/dist/resources/extensions/gsd/prompts/complete-slice.md +5 -3
  61. package/dist/resources/extensions/gsd/prompts/discuss.md +2 -0
  62. package/dist/resources/extensions/gsd/prompts/execute-task.md +22 -19
  63. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +2 -0
  64. package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +2 -0
  65. package/dist/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
  66. package/dist/resources/extensions/gsd/prompts/queue.md +3 -2
  67. package/dist/resources/extensions/gsd/prompts/system.md +1 -0
  68. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +4 -1
  69. package/dist/resources/extensions/gsd/session-model-override.js +25 -0
  70. package/dist/resources/extensions/gsd/shortcut-defs.js +40 -0
  71. package/dist/resources/extensions/gsd/state.js +9 -2
  72. package/dist/resources/extensions/gsd/tools/complete-slice.js +52 -1
  73. package/dist/resources/extensions/gsd/tools/complete-task.js +51 -1
  74. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +4 -1
  75. package/dist/resources/extensions/ollama/index.js +13 -5
  76. package/dist/resources/extensions/shared/gsd-phase-state.js +35 -0
  77. package/dist/resources/extensions/subagent/agents.js +8 -0
  78. package/dist/resources/extensions/subagent/index.js +17 -0
  79. package/dist/resources/skills/create-skill/SKILL.md +2 -0
  80. package/dist/startup-model-validation.d.ts +0 -1
  81. package/dist/startup-model-validation.js +6 -2
  82. package/dist/web/standalone/.next/BUILD_ID +1 -1
  83. package/dist/web/standalone/.next/app-path-routes-manifest.json +10 -10
  84. package/dist/web/standalone/.next/build-manifest.json +3 -3
  85. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  86. package/dist/web/standalone/.next/required-server-files.json +3 -3
  87. package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
  88. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  89. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  90. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  91. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  92. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  93. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  94. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  95. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  96. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  97. package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
  98. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  99. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  100. package/dist/web/standalone/.next/server/app/_not-found.rsc +3 -3
  101. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
  102. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  103. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
  104. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  105. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  106. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  107. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  108. package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
  109. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
  110. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
  111. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
  112. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
  113. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
  114. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
  115. package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
  116. package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
  117. package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
  118. package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
  119. package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
  120. package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
  121. package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
  122. package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
  123. package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
  124. package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
  125. package/dist/web/standalone/.next/server/app/api/experimental/route.js +2 -2
  126. package/dist/web/standalone/.next/server/app/api/experimental/route_client-reference-manifest.js +1 -1
  127. package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
  128. package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
  129. package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
  130. package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
  131. package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
  132. package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
  133. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  134. package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
  135. package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
  136. package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
  137. package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
  138. package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
  139. package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
  140. package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
  141. package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
  142. package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
  143. package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
  144. package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
  145. package/dist/web/standalone/.next/server/app/api/notifications/route.js +2 -2
  146. package/dist/web/standalone/.next/server/app/api/notifications/route_client-reference-manifest.js +1 -1
  147. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  148. package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
  149. package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
  150. package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
  151. package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
  152. package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
  153. package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
  154. package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
  155. package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +2 -2
  156. package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
  157. package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
  158. package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
  159. package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
  160. package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
  161. package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
  162. package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
  163. package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
  164. package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
  165. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  166. package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
  167. package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
  168. package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
  169. package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
  170. package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
  171. package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
  172. package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
  173. package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
  174. package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
  175. package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +1 -1
  176. package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
  177. package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
  178. package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
  179. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +1 -1
  180. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
  181. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +2 -2
  182. package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
  183. package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
  184. package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
  185. package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
  186. package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
  187. package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
  188. package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  189. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  190. package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
  191. package/dist/web/standalone/.next/server/app/index.html +1 -1
  192. package/dist/web/standalone/.next/server/app/index.rsc +4 -4
  193. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  194. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +4 -4
  195. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  196. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +3 -3
  197. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  198. package/dist/web/standalone/.next/server/app/page.js +2 -2
  199. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  200. package/dist/web/standalone/.next/server/app-paths-manifest.json +10 -10
  201. package/dist/web/standalone/.next/server/chunks/63.js +3 -3
  202. package/dist/web/standalone/.next/server/chunks/6897.js +1 -1
  203. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  204. package/dist/web/standalone/.next/server/middleware.js +2 -2
  205. package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
  206. package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
  207. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  208. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  209. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  210. package/dist/web/standalone/.next/static/chunks/app/_not-found/{page-2f24283c162b6ab3.js → page-f2a7482d42a5614b.js} +1 -1
  211. package/dist/web/standalone/.next/static/chunks/app/{layout-9ecfd95f343793f0.js → layout-a16c7a7ecdf0c2cf.js} +1 -1
  212. package/dist/web/standalone/.next/static/chunks/app/page-f1e30ab6bb269149.js +1 -0
  213. package/dist/web/standalone/.next/static/chunks/main-app-fdab67f7802d7832.js +1 -0
  214. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-459824ffb8c323dd.js +1 -0
  215. package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
  216. package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
  217. package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
  218. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api.target.mk +14 -14
  219. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_except.target.mk +14 -14
  220. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_maybe.target.mk +14 -14
  221. package/dist/web/standalone/server.js +1 -1
  222. package/package.json +1 -1
  223. package/packages/mcp-server/dist/server.d.ts +12 -1
  224. package/packages/mcp-server/dist/server.d.ts.map +1 -1
  225. package/packages/mcp-server/dist/server.js +90 -42
  226. package/packages/mcp-server/dist/server.js.map +1 -1
  227. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  228. package/packages/mcp-server/dist/workflow-tools.js +22 -12
  229. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  230. package/packages/mcp-server/src/server.ts +110 -38
  231. package/packages/mcp-server/src/workflow-tools.test.ts +110 -0
  232. package/packages/mcp-server/src/workflow-tools.ts +32 -12
  233. package/packages/pi-ai/dist/providers/amazon-bedrock.js +11 -2
  234. package/packages/pi-ai/dist/providers/amazon-bedrock.js.map +1 -1
  235. package/packages/pi-ai/dist/providers/anthropic-auth.test.d.ts +2 -0
  236. package/packages/pi-ai/dist/providers/anthropic-auth.test.d.ts.map +1 -0
  237. package/packages/pi-ai/dist/providers/anthropic-auth.test.js +20 -0
  238. package/packages/pi-ai/dist/providers/anthropic-auth.test.js.map +1 -0
  239. package/packages/pi-ai/dist/providers/anthropic-shared.d.ts +4 -1
  240. package/packages/pi-ai/dist/providers/anthropic-shared.d.ts.map +1 -1
  241. package/packages/pi-ai/dist/providers/anthropic-shared.js +8 -3
  242. package/packages/pi-ai/dist/providers/anthropic-shared.js.map +1 -1
  243. package/packages/pi-ai/dist/providers/anthropic-shared.test.js +44 -1
  244. package/packages/pi-ai/dist/providers/anthropic-shared.test.js.map +1 -1
  245. package/packages/pi-ai/dist/providers/anthropic.d.ts +2 -1
  246. package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
  247. package/packages/pi-ai/dist/providers/anthropic.js +7 -4
  248. package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
  249. package/packages/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
  250. package/packages/pi-ai/dist/providers/openai-completions.js +11 -0
  251. package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
  252. package/packages/pi-ai/src/providers/amazon-bedrock.ts +13 -1
  253. package/packages/pi-ai/src/providers/anthropic-auth.test.ts +32 -0
  254. package/packages/pi-ai/src/providers/anthropic-shared.test.ts +55 -1
  255. package/packages/pi-ai/src/providers/anthropic-shared.ts +14 -3
  256. package/packages/pi-ai/src/providers/anthropic.ts +8 -4
  257. package/packages/pi-ai/src/providers/openai-completions.ts +14 -0
  258. package/packages/pi-coding-agent/dist/core/agent-session-renderable-tools.test.d.ts +2 -0
  259. package/packages/pi-coding-agent/dist/core/agent-session-renderable-tools.test.d.ts.map +1 -0
  260. package/packages/pi-coding-agent/dist/core/agent-session-renderable-tools.test.js +61 -0
  261. package/packages/pi-coding-agent/dist/core/agent-session-renderable-tools.test.js.map +1 -0
  262. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  263. package/packages/pi-coding-agent/dist/core/agent-session.js +2 -1
  264. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  265. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts +10 -0
  266. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
  267. package/packages/pi-coding-agent/dist/core/auth-storage.js +27 -0
  268. package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
  269. package/packages/pi-coding-agent/dist/core/auth-storage.test.js +85 -0
  270. package/packages/pi-coding-agent/dist/core/auth-storage.test.js.map +1 -1
  271. package/packages/pi-coding-agent/dist/core/model-resolver-initial-model-auth.test.d.ts +2 -0
  272. package/packages/pi-coding-agent/dist/core/model-resolver-initial-model-auth.test.d.ts.map +1 -0
  273. package/packages/pi-coding-agent/dist/core/model-resolver-initial-model-auth.test.js +64 -0
  274. package/packages/pi-coding-agent/dist/core/model-resolver-initial-model-auth.test.js.map +1 -0
  275. package/packages/pi-coding-agent/dist/core/model-resolver.d.ts.map +1 -1
  276. package/packages/pi-coding-agent/dist/core/model-resolver.js +22 -18
  277. package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
  278. package/packages/pi-coding-agent/dist/core/model-resolver.test.d.ts +8 -0
  279. package/packages/pi-coding-agent/dist/core/model-resolver.test.d.ts.map +1 -0
  280. package/packages/pi-coding-agent/dist/core/model-resolver.test.js +75 -0
  281. package/packages/pi-coding-agent/dist/core/model-resolver.test.js.map +1 -0
  282. package/packages/pi-coding-agent/dist/core/retry-handler.d.ts +5 -0
  283. package/packages/pi-coding-agent/dist/core/retry-handler.d.ts.map +1 -1
  284. package/packages/pi-coding-agent/dist/core/retry-handler.js +55 -1
  285. package/packages/pi-coding-agent/dist/core/retry-handler.js.map +1 -1
  286. package/packages/pi-coding-agent/dist/core/retry-handler.test.js +57 -0
  287. package/packages/pi-coding-agent/dist/core/retry-handler.test.js.map +1 -1
  288. package/packages/pi-coding-agent/dist/core/sdk.d.ts +11 -0
  289. package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
  290. package/packages/pi-coding-agent/dist/core/sdk.js +38 -5
  291. package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
  292. package/packages/pi-coding-agent/dist/core/sdk.test.d.ts +2 -0
  293. package/packages/pi-coding-agent/dist/core/sdk.test.d.ts.map +1 -0
  294. package/packages/pi-coding-agent/dist/core/sdk.test.js +71 -0
  295. package/packages/pi-coding-agent/dist/core/sdk.test.js.map +1 -0
  296. package/packages/pi-coding-agent/dist/index.d.ts +1 -1
  297. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  298. package/packages/pi-coding-agent/dist/index.js +1 -1
  299. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  300. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/login-dialog.test.d.ts +2 -0
  301. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/login-dialog.test.d.ts.map +1 -0
  302. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/login-dialog.test.js +13 -0
  303. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/login-dialog.test.js.map +1 -0
  304. package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.d.ts +4 -0
  305. package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
  306. package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.js +24 -2
  307. package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.js.map +1 -1
  308. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  309. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js +9 -2
  310. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js.map +1 -1
  311. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +4 -0
  312. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  313. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +43 -0
  314. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  315. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  316. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +7 -2
  317. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  318. package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.d.ts.map +1 -1
  319. package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.js +6 -1
  320. package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.js.map +1 -1
  321. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  322. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +4 -3
  323. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  324. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js +4 -2
  325. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js.map +1 -1
  326. package/packages/pi-coding-agent/package.json +1 -1
  327. package/packages/pi-coding-agent/src/core/agent-session-renderable-tools.test.ts +70 -0
  328. package/packages/pi-coding-agent/src/core/agent-session.ts +2 -1
  329. package/packages/pi-coding-agent/src/core/auth-storage.test.ts +108 -0
  330. package/packages/pi-coding-agent/src/core/auth-storage.ts +30 -0
  331. package/packages/pi-coding-agent/src/core/model-resolver-initial-model-auth.test.ts +78 -0
  332. package/packages/pi-coding-agent/src/core/model-resolver.test.ts +85 -0
  333. package/packages/pi-coding-agent/src/core/model-resolver.ts +22 -18
  334. package/packages/pi-coding-agent/src/core/retry-handler.test.ts +83 -0
  335. package/packages/pi-coding-agent/src/core/retry-handler.ts +60 -1
  336. package/packages/pi-coding-agent/src/core/sdk.test.ts +89 -0
  337. package/packages/pi-coding-agent/src/core/sdk.ts +45 -9
  338. package/packages/pi-coding-agent/src/index.ts +1 -0
  339. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/login-dialog.test.ts +24 -0
  340. package/packages/pi-coding-agent/src/modes/interactive/components/login-dialog.ts +30 -2
  341. package/packages/pi-coding-agent/src/modes/interactive/components/model-selector.ts +15 -6
  342. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +47 -0
  343. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +7 -2
  344. package/packages/pi-coding-agent/src/modes/interactive/controllers/model-controller.ts +6 -1
  345. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +4 -3
  346. package/packages/pi-coding-agent/src/modes/interactive/slash-command-handlers.ts +4 -2
  347. package/pkg/package.json +1 -1
  348. package/src/resources/GSD-WORKFLOW.md +1 -1
  349. package/src/resources/agents/debugger.md +58 -0
  350. package/src/resources/agents/doc-writer.md +43 -0
  351. package/src/resources/agents/git-ops.md +56 -0
  352. package/src/resources/agents/javascript-pro.md +46 -271
  353. package/src/resources/agents/planner.md +55 -0
  354. package/src/resources/agents/refactorer.md +47 -0
  355. package/src/resources/agents/reviewer.md +48 -0
  356. package/src/resources/agents/security.md +59 -0
  357. package/src/resources/agents/tester.md +50 -0
  358. package/src/resources/agents/typescript-pro.md +41 -235
  359. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +122 -8
  360. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +189 -6
  361. package/src/resources/extensions/gsd/auto/infra-errors.ts +38 -0
  362. package/src/resources/extensions/gsd/auto/loop-deps.ts +2 -0
  363. package/src/resources/extensions/gsd/auto/loop.ts +45 -1
  364. package/src/resources/extensions/gsd/auto/phases.ts +6 -0
  365. package/src/resources/extensions/gsd/auto/session.ts +11 -0
  366. package/src/resources/extensions/gsd/auto-dashboard.ts +29 -18
  367. package/src/resources/extensions/gsd/auto-model-selection.ts +9 -1
  368. package/src/resources/extensions/gsd/auto-prompts.ts +111 -33
  369. package/src/resources/extensions/gsd/auto-start.ts +41 -7
  370. package/src/resources/extensions/gsd/auto-tool-tracking.ts +1 -1
  371. package/src/resources/extensions/gsd/auto-worktree.ts +1 -1
  372. package/src/resources/extensions/gsd/auto.ts +72 -0
  373. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +3 -3
  374. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +2 -0
  375. package/src/resources/extensions/gsd/bootstrap/register-shortcuts.ts +79 -60
  376. package/src/resources/extensions/gsd/bootstrap/system-context.ts +7 -0
  377. package/src/resources/extensions/gsd/commands/context.ts +16 -5
  378. package/src/resources/extensions/gsd/commands/dispatcher.ts +14 -2
  379. package/src/resources/extensions/gsd/commands/handlers/auto.ts +10 -36
  380. package/src/resources/extensions/gsd/commands/handlers/core.ts +58 -11
  381. package/src/resources/extensions/gsd/commands/handlers/notifications-handler.ts +17 -7
  382. package/src/resources/extensions/gsd/commands/handlers/workflow.ts +4 -10
  383. package/src/resources/extensions/gsd/dashboard-overlay.ts +10 -3
  384. package/src/resources/extensions/gsd/dispatch-guard.ts +18 -1
  385. package/src/resources/extensions/gsd/doctor-providers.ts +24 -0
  386. package/src/resources/extensions/gsd/error-classifier.ts +5 -2
  387. package/src/resources/extensions/gsd/forensics.ts +23 -7
  388. package/src/resources/extensions/gsd/gate-registry.ts +251 -0
  389. package/src/resources/extensions/gsd/gsd-db.ts +51 -0
  390. package/src/resources/extensions/gsd/guided-flow.ts +5 -10
  391. package/src/resources/extensions/gsd/interrupted-session.ts +1 -0
  392. package/src/resources/extensions/gsd/metrics.ts +12 -1
  393. package/src/resources/extensions/gsd/milestone-actions.ts +10 -3
  394. package/src/resources/extensions/gsd/milestone-validation-gates.ts +11 -13
  395. package/src/resources/extensions/gsd/notification-overlay.ts +47 -14
  396. package/src/resources/extensions/gsd/notification-store.ts +54 -5
  397. package/src/resources/extensions/gsd/notification-widget.ts +5 -14
  398. package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +10 -3
  399. package/src/resources/extensions/gsd/pre-execution-checks.ts +39 -2
  400. package/src/resources/extensions/gsd/prompt-validation.ts +157 -0
  401. package/src/resources/extensions/gsd/prompts/complete-slice.md +5 -3
  402. package/src/resources/extensions/gsd/prompts/discuss.md +2 -0
  403. package/src/resources/extensions/gsd/prompts/execute-task.md +22 -19
  404. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +2 -0
  405. package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +2 -0
  406. package/src/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
  407. package/src/resources/extensions/gsd/prompts/queue.md +3 -2
  408. package/src/resources/extensions/gsd/prompts/system.md +1 -0
  409. package/src/resources/extensions/gsd/prompts/validate-milestone.md +4 -1
  410. package/src/resources/extensions/gsd/session-model-override.ts +36 -0
  411. package/src/resources/extensions/gsd/shortcut-defs.ts +56 -0
  412. package/src/resources/extensions/gsd/state.ts +13 -2
  413. package/src/resources/extensions/gsd/tests/auto-start-model-capture.test.ts +25 -9
  414. package/src/resources/extensions/gsd/tests/auto-start-worktree-db-path.test.ts +28 -0
  415. package/src/resources/extensions/gsd/tests/bootstrap-derive-state-db-open.test.ts +39 -0
  416. package/src/resources/extensions/gsd/tests/complete-slice-gate-closure.test.ts +167 -0
  417. package/src/resources/extensions/gsd/tests/complete-slice-prompt-task-summary-layout.test.ts +18 -0
  418. package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +27 -0
  419. package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +36 -0
  420. package/src/resources/extensions/gsd/tests/execute-task-prompt-existing-artifact-guard.test.ts +33 -0
  421. package/src/resources/extensions/gsd/tests/forensics-stuck-loops.test.ts +62 -0
  422. package/src/resources/extensions/gsd/tests/format-shortcut.test.ts +31 -0
  423. package/src/resources/extensions/gsd/tests/gate-dispatch.test.ts +27 -0
  424. package/src/resources/extensions/gsd/tests/gate-registry.test.ts +140 -0
  425. package/src/resources/extensions/gsd/tests/gsd-no-project-error.test.ts +73 -0
  426. package/src/resources/extensions/gsd/tests/infra-errors-cooldown.test.ts +180 -0
  427. package/src/resources/extensions/gsd/tests/integration/auto-worktree-milestone-merge.test.ts +66 -1
  428. package/src/resources/extensions/gsd/tests/model-isolation.test.ts +36 -51
  429. package/src/resources/extensions/gsd/tests/notification-store.test.ts +35 -0
  430. package/src/resources/extensions/gsd/tests/notification-widget.test.ts +26 -0
  431. package/src/resources/extensions/gsd/tests/notifications-handler.test.ts +90 -0
  432. package/src/resources/extensions/gsd/tests/parallel-monitor-overlay.test.ts +1 -0
  433. package/src/resources/extensions/gsd/tests/park-db-sync.test.ts +18 -0
  434. package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +49 -0
  435. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +19 -0
  436. package/src/resources/extensions/gsd/tests/prompt-system-gate-coverage.test.ts +208 -0
  437. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +16 -0
  438. package/src/resources/extensions/gsd/tests/register-shortcuts.test.ts +63 -5
  439. package/src/resources/extensions/gsd/tests/session-model-override.test.ts +35 -0
  440. package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +90 -0
  441. package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +7 -0
  442. package/src/resources/extensions/gsd/tests/validate-milestone-prompt-verification-classes.test.ts +18 -0
  443. package/src/resources/extensions/gsd/tools/complete-slice.ts +63 -0
  444. package/src/resources/extensions/gsd/tools/complete-task.ts +63 -0
  445. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +4 -1
  446. package/src/resources/extensions/gsd/types.ts +26 -0
  447. package/src/resources/extensions/ollama/index.ts +13 -3
  448. package/src/resources/extensions/ollama/ollama-status-indicator.test.ts +28 -0
  449. package/src/resources/extensions/shared/gsd-phase-state.ts +42 -0
  450. package/src/resources/extensions/shared/tests/gsd-phase-state.test.ts +48 -0
  451. package/src/resources/extensions/subagent/agents.ts +10 -0
  452. package/src/resources/extensions/subagent/index.ts +18 -0
  453. package/src/resources/extensions/subagent/tests/agents-conflicts.test.ts +33 -0
  454. package/src/resources/skills/create-skill/SKILL.md +2 -0
  455. package/dist/web/standalone/.next/static/chunks/app/page-7115e62689b5fd84.js +0 -1
  456. package/dist/web/standalone/.next/static/chunks/main-app-d3d4c336195465f9.js +0 -1
  457. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-ab5a8926e07ec673.js +0 -1
  458. /package/dist/web/standalone/.next/static/{nPky_WQC28aBD77eZsRAB → f-Gremw0nLxxFUySaHRPw}/_buildManifest.js +0 -0
  459. /package/dist/web/standalone/.next/static/{nPky_WQC28aBD77eZsRAB → f-Gremw0nLxxFUySaHRPw}/_ssgManifest.js +0 -0
@@ -1,6 +1,6 @@
1
1
  // GSD Extension — Notification History Overlay
2
2
  // Scrollable panel showing all persisted notifications with severity filtering.
3
- // Toggled with Ctrl+Alt+N (⌃⌥N on macOS) or opened from /gsd notifications.
3
+ // Toggled with Ctrl+Alt+N (⌃⌥N on macOS), Ctrl+Shift+N fallback, or /gsd notifications.
4
4
 
5
5
  import type { Theme } from "@gsd/pi-coding-agent";
6
6
  import { truncateToWidth, visibleWidth, matchesKey, Key } from "@gsd/pi-tui";
@@ -9,11 +9,12 @@ import {
9
9
  readNotifications,
10
10
  markAllRead,
11
11
  clearNotifications,
12
- getUnreadCount,
12
+ onNotificationStoreChange,
13
13
  type NotificationEntry,
14
14
  type NotifySeverity,
15
15
  } from "./notification-store.js";
16
- import { padRight, centerLine, joinColumns, formatDuration } from "../shared/mod.js";
16
+ import { formattedShortcutPair } from "./shortcut-defs.js";
17
+ import { padRight, joinColumns } from "../shared/mod.js";
17
18
 
18
19
  type FilterMode = "all" | "error" | "warning" | "info";
19
20
  const FILTER_CYCLE: FilterMode[] = ["all", "error", "warning", "info"];
@@ -63,6 +64,12 @@ function formatTimestamp(ts: string): string {
63
64
  }
64
65
  }
65
66
 
67
+ function notificationSignature(entries: readonly NotificationEntry[]): string {
68
+ return entries
69
+ .map((entry) => `${entry.ts}|${entry.severity}|${entry.read ? 1 : 0}|${entry.message}`)
70
+ .join("\n");
71
+ }
72
+
66
73
  export class GSDNotificationOverlay {
67
74
  private tui: { requestRender: () => void };
68
75
  private theme: Theme;
@@ -72,9 +79,11 @@ export class GSDNotificationOverlay {
72
79
  private scrollOffset = 0;
73
80
  private filterIndex = 0;
74
81
  private entries: NotificationEntry[] = [];
82
+ private entriesSignature = "";
75
83
  private refreshTimer: ReturnType<typeof setInterval>;
76
84
  private disposed = false;
77
85
  private resizeHandler: (() => void) | null = null;
86
+ private unsubscribeStore: (() => void) | null = null;
78
87
 
79
88
  constructor(
80
89
  tui: { requestRender: () => void },
@@ -88,6 +97,7 @@ export class GSDNotificationOverlay {
88
97
  // Mark all as read on open
89
98
  markAllRead();
90
99
  this.entries = readNotifications();
100
+ this.entriesSignature = notificationSignature(this.entries);
91
101
 
92
102
  // Resize handler
93
103
  this.resizeHandler = () => {
@@ -97,17 +107,17 @@ export class GSDNotificationOverlay {
97
107
  };
98
108
  process.stdout.on("resize", this.resizeHandler);
99
109
 
100
- // Refresh every 3s for new notifications
110
+ // Subscribe to store mutations for immediate updates
111
+ this.unsubscribeStore = onNotificationStoreChange(() => {
112
+ if (this.disposed) return;
113
+ this._refreshFromDisk();
114
+ });
115
+
116
+ // 30s safety-net for cross-process edits (web subprocess, parallel workers)
101
117
  this.refreshTimer = setInterval(() => {
102
118
  if (this.disposed) return;
103
- const fresh = readNotifications();
104
- if (fresh.length !== this.entries.length) {
105
- this.entries = fresh;
106
- markAllRead();
107
- this.invalidate();
108
- this.tui.requestRender();
109
- }
110
- }, 3000);
119
+ this._refreshFromDisk();
120
+ }, 30_000);
111
121
  }
112
122
 
113
123
  private get filter(): FilterMode {
@@ -120,7 +130,12 @@ export class GSDNotificationOverlay {
120
130
  }
121
131
 
122
132
  handleInput(data: string): void {
123
- if (matchesKey(data, Key.escape) || matchesKey(data, Key.ctrl("c")) || matchesKey(data, Key.ctrlAlt("n"))) {
133
+ if (
134
+ matchesKey(data, Key.escape) ||
135
+ matchesKey(data, Key.ctrl("c")) ||
136
+ matchesKey(data, Key.ctrlAlt("n")) ||
137
+ matchesKey(data, Key.ctrlShift("n"))
138
+ ) {
124
139
  this.dispose();
125
140
  this.onClose();
126
141
  return;
@@ -165,6 +180,7 @@ export class GSDNotificationOverlay {
165
180
  if (data === "c") {
166
181
  clearNotifications();
167
182
  this.entries = [];
183
+ this.entriesSignature = notificationSignature(this.entries);
168
184
  this.scrollOffset = 0;
169
185
  this.invalidate();
170
186
  this.tui.requestRender();
@@ -199,12 +215,28 @@ export class GSDNotificationOverlay {
199
215
  dispose(): void {
200
216
  this.disposed = true;
201
217
  clearInterval(this.refreshTimer);
218
+ if (this.unsubscribeStore) {
219
+ this.unsubscribeStore();
220
+ this.unsubscribeStore = null;
221
+ }
202
222
  if (this.resizeHandler) {
203
223
  process.stdout.removeListener("resize", this.resizeHandler);
204
224
  this.resizeHandler = null;
205
225
  }
206
226
  }
207
227
 
228
+ private _refreshFromDisk(): void {
229
+ const fresh = readNotifications();
230
+ const signature = notificationSignature(fresh);
231
+ if (signature !== this.entriesSignature) {
232
+ markAllRead();
233
+ this.entries = readNotifications();
234
+ this.entriesSignature = notificationSignature(this.entries);
235
+ this.invalidate();
236
+ this.tui.requestRender();
237
+ }
238
+ }
239
+
208
240
  private wrapInBox(inner: string[], width: number): string[] {
209
241
  const th = this.theme;
210
242
  const border = (s: string) => th.fg("borderAccent", s);
@@ -250,7 +282,8 @@ export class GSDNotificationOverlay {
250
282
  lines.push(hr());
251
283
 
252
284
  // Controls
253
- lines.push(row(th.fg("dim", "↑/↓ scroll f filter c clear Esc close")));
285
+ const closeShortcut = formattedShortcutPair("notifications");
286
+ lines.push(row(th.fg("dim", `↑/↓ scroll f filter c clear Esc close (${closeShortcut})`)));
254
287
  lines.push(blank());
255
288
 
256
289
  // Entries
@@ -26,12 +26,16 @@ export interface NotificationEntry {
26
26
  const MAX_ENTRIES = 500;
27
27
  const FILENAME = "notifications.jsonl";
28
28
  const LOCKFILE = "notifications.lock";
29
+ const DEDUP_WINDOW_MS = 30_000;
30
+ const DEDUP_PRUNE_THRESHOLD = 200;
29
31
 
30
32
  // ─── Module State ───────────────────────────────────────────────────────
31
33
 
32
34
  let _basePath: string | null = null;
33
35
  let _lineCount = 0; // Hint for rotation — not authoritative for public API
34
36
  let _suppressCount = 0;
37
+ let _recentMessageTimestamps = new Map<string, number>();
38
+ const _changeListeners = new Set<() => void>();
35
39
 
36
40
  // ─── Public API ─────────────────────────────────────────────────────────
37
41
 
@@ -40,6 +44,9 @@ let _suppressCount = 0;
40
44
  * project root. Seeds in-memory counters from the existing file on disk.
41
45
  */
42
46
  export function initNotificationStore(basePath: string): void {
47
+ if (_basePath !== basePath) {
48
+ _recentMessageTimestamps.clear();
49
+ }
43
50
  _basePath = basePath;
44
51
  // Seed line count hint for rotation — public counters read from disk
45
52
  _lineCount = _readEntriesFromDisk(basePath).length;
@@ -56,12 +63,23 @@ export function appendNotification(
56
63
  ): void {
57
64
  if (!_basePath) return;
58
65
  if (_suppressCount > 0) return;
66
+ const persistedMessage = message.length > 500 ? message.slice(0, 500) + "…" : message;
67
+ const dedupKey = `${_basePath}:${severity}:${source}:${persistedMessage}`;
68
+ const now = Date.now();
69
+ const lastSeen = _recentMessageTimestamps.get(dedupKey);
70
+ if (lastSeen !== undefined && now - lastSeen < DEDUP_WINDOW_MS) return;
71
+ _recentMessageTimestamps.set(dedupKey, now);
72
+ if (_recentMessageTimestamps.size > DEDUP_PRUNE_THRESHOLD) {
73
+ for (const [key, ts] of _recentMessageTimestamps) {
74
+ if (now - ts > DEDUP_WINDOW_MS) _recentMessageTimestamps.delete(key);
75
+ }
76
+ }
59
77
 
60
78
  const entry: NotificationEntry = {
61
79
  id: randomUUID(),
62
80
  ts: new Date().toISOString(),
63
81
  severity,
64
- message: message.length > 500 ? message.slice(0, 500) + "…" : message,
82
+ message: persistedMessage,
65
83
  source,
66
84
  read: false,
67
85
  };
@@ -76,6 +94,7 @@ export function appendNotification(
76
94
  if (_lineCount > MAX_ENTRIES) {
77
95
  _rotate();
78
96
  }
97
+ _emitChange();
79
98
  } catch {
80
99
  // Non-fatal — never let persistence break the caller
81
100
  }
@@ -104,6 +123,7 @@ export function markAllRead(basePath?: string): void {
104
123
  const hasUnread = entries.some((e) => !e.read);
105
124
  if (!hasUnread) return;
106
125
 
126
+ let changed = false;
107
127
  try {
108
128
  _withLock(bp, () => {
109
129
  // Re-read inside lock to get freshest state
@@ -111,10 +131,12 @@ export function markAllRead(basePath?: string): void {
111
131
  if (fresh.length === 0 || !fresh.some((e) => !e.read)) return;
112
132
  const lines = fresh.map((e) => JSON.stringify({ ...e, read: true }));
113
133
  _atomicWrite(bp, lines.join("\n") + "\n");
134
+ changed = true;
114
135
  });
115
136
  } catch {
116
137
  // Non-fatal
117
138
  }
139
+ if (changed) _emitChange();
118
140
  }
119
141
 
120
142
  /**
@@ -128,6 +150,8 @@ export function clearNotifications(basePath?: string): void {
128
150
  _withLock(bp, () => {
129
151
  _atomicWrite(bp, "");
130
152
  });
153
+ _lineCount = 0;
154
+ _emitChange();
131
155
  } catch {
132
156
  // Non-fatal
133
157
  }
@@ -172,6 +196,17 @@ export function unsuppressPersistence(): void {
172
196
  _suppressCount = Math.max(0, _suppressCount - 1);
173
197
  }
174
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
+
175
210
  // ─── Test Helpers ───────────────────────────────────────────────────────
176
211
 
177
212
  /**
@@ -181,6 +216,8 @@ export function _resetNotificationStore(): void {
181
216
  _basePath = null;
182
217
  _lineCount = 0;
183
218
  _suppressCount = 0;
219
+ _recentMessageTimestamps = new Map();
220
+ _changeListeners.clear();
184
221
  }
185
222
 
186
223
  // ─── Internal ───────────────────────────────────────────────────────────
@@ -216,12 +253,23 @@ function _rotate(): void {
216
253
  const trimmed = entries.slice(entries.length - MAX_ENTRIES);
217
254
  const lines = trimmed.map((e) => JSON.stringify(e));
218
255
  _atomicWrite(_basePath!, lines.join("\n") + "\n");
256
+ _lineCount = trimmed.length;
219
257
  });
220
258
  } catch {
221
259
  // Non-fatal
222
260
  }
223
261
  }
224
262
 
263
+ function _emitChange(): void {
264
+ for (const listener of _changeListeners) {
265
+ try {
266
+ listener();
267
+ } catch {
268
+ // Non-fatal
269
+ }
270
+ }
271
+ }
272
+
225
273
  /**
226
274
  * Atomic file rewrite via temp-file + rename. Prevents partial reads
227
275
  * by other processes (web API subprocess, parallel workers).
@@ -275,10 +323,11 @@ function _withLock<T>(basePath: string, fn: () => T): T {
275
323
  }
276
324
  }
277
325
 
278
- // Only run the mutation if we actually own the lock
279
- 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;
280
329
  try {
281
- if (ownsLock && fd !== null) {
330
+ if (createdLock && fd !== null) {
282
331
  // Write our PID timestamp into the lock for stale detection
283
332
  writeFileSync(lockPath, String(Date.now()), "utf-8");
284
333
  closeSync(fd);
@@ -286,7 +335,7 @@ function _withLock<T>(basePath: string, fn: () => T): T {
286
335
  return fn();
287
336
  } finally {
288
337
  // Only delete the lock if we created it — never remove another process's lock
289
- if (ownsLock) {
338
+ if (createdLock) {
290
339
  try { unlinkSync(lockPath); } catch { /* best-effort cleanup */ }
291
340
  }
292
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")} to view)`];
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;
@@ -280,6 +280,38 @@ function extractPathFromAnnotation(raw: string): string {
280
280
  return trimmed.replace(/`/g, "");
281
281
  }
282
282
 
283
+ /**
284
+ * Planning units sometimes use task.inputs for prose like "Current enum shape"
285
+ * instead of concrete file paths. Those entries should not fail path checks.
286
+ * Keep validation for anything that still looks like a real file reference:
287
+ * explicit backticks, globs, separators, dot-paths, or single-token basenames
288
+ * like Dockerfile.
289
+ */
290
+ function shouldValidateInputAsPath(raw: string): boolean {
291
+ const trimmed = raw.trim();
292
+ if (!trimmed) return false;
293
+
294
+ if (/^`+[^`]+`+/.test(trimmed)) {
295
+ return true;
296
+ }
297
+
298
+ const candidate = extractPathFromAnnotation(trimmed);
299
+ if (!candidate) return false;
300
+
301
+ if (!/\s/.test(candidate)) {
302
+ return true;
303
+ }
304
+
305
+ return (
306
+ candidate.startsWith("/") ||
307
+ candidate.startsWith("./") ||
308
+ candidate.startsWith("../") ||
309
+ candidate.startsWith("~/") ||
310
+ /[\\/]/.test(candidate) ||
311
+ /[*?[\]{}]/.test(candidate)
312
+ );
313
+ }
314
+
283
315
  /**
284
316
  * Build a set of files that will be created by tasks up to (but not including) taskIndex.
285
317
  * All paths are normalized for consistent comparison.
@@ -318,6 +350,7 @@ export function checkFilePathConsistency(
318
350
  for (const file of filesToCheck) {
319
351
  // Skip empty strings
320
352
  if (!file.trim()) continue;
353
+ if (!shouldValidateInputAsPath(file)) continue;
321
354
 
322
355
  // Normalize path for consistent comparison
323
356
  const normalizedFile = normalizeFilePath(file);
@@ -354,7 +387,7 @@ export function checkFilePathConsistency(
354
387
  */
355
388
  export function checkTaskOrdering(
356
389
  tasks: TaskRow[],
357
- _basePath: string
390
+ basePath: string
358
391
  ): PreExecutionCheckJSON[] {
359
392
  const results: PreExecutionCheckJSON[] = [];
360
393
 
@@ -378,9 +411,13 @@ export function checkTaskOrdering(
378
411
  const filesToCheck = [...task.inputs];
379
412
 
380
413
  for (const file of filesToCheck) {
414
+ if (!shouldValidateInputAsPath(file)) continue;
415
+
381
416
  const normalizedFile = normalizeFilePath(file);
382
417
  const creator = fileCreators.get(normalizedFile);
383
- if (creator && creator.index > i) {
418
+ const absolutePath = resolve(basePath, normalizedFile);
419
+ const existsOnDisk = existsSync(absolutePath);
420
+ if (creator && creator.index > i && !existsOnDisk) {
384
421
  // Task reads file that is created later — impossible ordering
385
422
  results.push({
386
423
  category: "file",
@@ -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,14 +16,16 @@ 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:
22
24
  1. Use the **Slice Summary** and **UAT** output templates from the inlined context above
23
25
  2. {{skillActivation}}
24
- 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.
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.
@@ -35,7 +37,7 @@ Then:
35
37
 
36
38
  **Autonomous execution:** Do not call `ask_user_questions` or `secure_env_collect`. You are running in auto-mode — there is no human available to answer questions. Make reasonable assumptions and document them in the slice summary. If a decision genuinely requires human input, note it in the summary and proceed with the best available option.
37
39
 
38
- **File system safety:** Task summaries are preloaded in the inlined context above. If you need to re-read any of them, use `find .gsd/milestones/{{milestoneId}}/slices/{{sliceId}}/tasks -name "*-SUMMARY.md"` to list file paths first never pass `{{slicePath}}` or any other directory path directly to the `read` tool. The `read` tool only accepts file paths, not directories.
40
+ **File system safety:** Task summaries are preloaded in the inlined context above. Task artifacts use a **flat file layout** — files such as `T01-SUMMARY.md` and `T02-SUMMARY.md` live directly inside the `tasks/` directory, not inside per-task subdirectories like `tasks/T01/SUMMARY.md`. If you need to re-read any of them, use `find .gsd/milestones/{{milestoneId}}/slices/{{sliceId}}/tasks -name "*-SUMMARY.md"` to list file paths first. Never use `tasks/*/SUMMARY.md`, and never pass `{{slicePath}}` or any other directory path directly to the `read` tool. The `read` tool only accepts file paths, not directories.
39
41
 
40
42
  **You MUST call `gsd_complete_slice` with the slice summary and UAT content before finishing. The tool persists to both DB and disk and renders `{{sliceSummaryPath}}` and `{{sliceUatPath}}` automatically.**
41
43
 
@@ -73,6 +73,8 @@ After each round of answers, decide whether you already have enough depth to wri
73
73
 
74
74
  You are a thinking partner, not an interviewer.
75
75
 
76
+ **Turn-taking contract (non-bypassable).** Never fabricate, simulate, or role-play user responses. Never generate fake transcript markers like `[User]`, `[Human]`, or `User:` to invent input. Ask one question round (1-3 questions) per turn, then stop and wait for the user's actual response before continuing. If you use `ask_user_questions`, call it at most once per turn and treat its returned response as the only valid structured user input for that round.
77
+
76
78
  **Start open, follow energy.** Let the user's enthusiasm guide where you dig deeper. If they light up about a particular aspect, explore it. If they're vague about something, that's where you probe.
77
79
 
78
80
  **Challenge vagueness, make abstract concrete.** When the user says something abstract ("it should be smart" / "it needs to handle edge cases" / "good UX"), push for specifics. What does "smart" mean in practice? Which edge cases? What does good UX look like for this specific interaction?