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
@@ -0,0 +1,251 @@
1
+ /**
2
+ * GSD Gate Registry — single source of truth for quality-gate ownership.
3
+ *
4
+ * Each gate declares which workflow turn owns it, the scope at which it is
5
+ * persisted in the `quality_gates` table, and the question/guidance text used
6
+ * in the prompt that turn sends. The registry replaces the ad-hoc
7
+ * `GATE_QUESTIONS` table that used to live in `auto-prompts.ts`, and every
8
+ * layer of the prompt system (prompt builders, dispatch rules, state
9
+ * derivation, tool handlers) consults it so a pending gate can never be
10
+ * silently dropped.
11
+ *
12
+ * Design notes:
13
+ * - `GATE_REGISTRY` is exhaustiveness-checked against `GateId` via
14
+ * `satisfies Record<GateId, GateDefinition>`, so adding a new GateId
15
+ * without a registry entry is a compile error.
16
+ * - `getGatesForTurn(turn)` returns the definitions a turn owns.
17
+ * - `assertGateCoverage(pending, turn)` throws a GSDError if the pending
18
+ * list for a turn contains unknown gates, or if any gate owned by the
19
+ * turn is missing from the pending list.
20
+ */
21
+
22
+ import { GSDError, GSD_PARSE_ERROR } from "./errors.js";
23
+ import type { GateId, GateRow, GateScope } from "./types.js";
24
+
25
+ /** Which workflow turn is responsible for evaluating / closing a gate. */
26
+ export type OwnerTurn =
27
+ | "gate-evaluate"
28
+ | "execute-task"
29
+ | "complete-slice"
30
+ | "validate-milestone";
31
+
32
+ export interface GateDefinition {
33
+ id: GateId;
34
+ scope: GateScope;
35
+ ownerTurn: OwnerTurn;
36
+ /** One-line question the assistant must answer. */
37
+ question: string;
38
+ /** Markdown guidance describing what a good answer looks like. */
39
+ guidance: string;
40
+ /** H3 section header used in the artifact the turn writes
41
+ * (e.g. "Operational Readiness" for Q8 in the slice summary). */
42
+ promptSection: string;
43
+ }
44
+
45
+ export const GATE_REGISTRY = {
46
+ Q3: {
47
+ id: "Q3",
48
+ scope: "slice",
49
+ ownerTurn: "gate-evaluate",
50
+ question: "How can this be exploited?",
51
+ guidance: [
52
+ "Identify abuse scenarios: parameter tampering, replay attacks, privilege escalation.",
53
+ "Map data exposure risks: PII, tokens, secrets accessible through this slice.",
54
+ "Define input trust boundaries: untrusted user input reaching DB, API, or filesystem.",
55
+ "If none apply, return verdict 'omitted' with rationale explaining why.",
56
+ ].join("\n"),
57
+ promptSection: "Abuse Surface",
58
+ },
59
+ Q4: {
60
+ id: "Q4",
61
+ scope: "slice",
62
+ ownerTurn: "gate-evaluate",
63
+ question: "What existing promises does this break?",
64
+ guidance: [
65
+ "List which existing requirements (R001, R003, etc.) are touched by this slice.",
66
+ "Identify what must be re-tested after shipping.",
67
+ "Flag decisions that should be revisited given the new scope.",
68
+ "If no existing requirements are affected, return verdict 'omitted'.",
69
+ ].join("\n"),
70
+ promptSection: "Broken Promises",
71
+ },
72
+ Q5: {
73
+ id: "Q5",
74
+ scope: "task",
75
+ ownerTurn: "execute-task",
76
+ question: "What breaks when dependencies fail?",
77
+ guidance: [
78
+ "Enumerate the task's external dependencies (APIs, filesystem, network, subprocesses).",
79
+ "Describe the failure path for each: timeout, malformed response, connection loss.",
80
+ "Verify the implementation handles each failure or explicitly bubbles the error.",
81
+ "Return verdict 'omitted' only if the task has no external dependencies.",
82
+ ].join("\n"),
83
+ promptSection: "Failure Modes",
84
+ },
85
+ Q6: {
86
+ id: "Q6",
87
+ scope: "task",
88
+ ownerTurn: "execute-task",
89
+ question: "What is the 10x load breakpoint?",
90
+ guidance: [
91
+ "Identify the resource that saturates first at 10x the expected load.",
92
+ "Describe the protection applied (pool sizing, rate limiting, pagination, caching).",
93
+ "Return verdict 'omitted' if the task has no runtime load dimension.",
94
+ ].join("\n"),
95
+ promptSection: "Load Profile",
96
+ },
97
+ Q7: {
98
+ id: "Q7",
99
+ scope: "task",
100
+ ownerTurn: "execute-task",
101
+ question: "What negative tests protect this task?",
102
+ guidance: [
103
+ "List malformed inputs, error paths, and boundary conditions the tests cover.",
104
+ "Point to the specific test files or cases that assert each negative scenario.",
105
+ "Return verdict 'omitted' only if the task has no meaningful negative surface.",
106
+ ].join("\n"),
107
+ promptSection: "Negative Tests",
108
+ },
109
+ Q8: {
110
+ id: "Q8",
111
+ scope: "slice",
112
+ ownerTurn: "complete-slice",
113
+ question: "How will ops know this slice is healthy or broken?",
114
+ guidance: [
115
+ "Describe the health signal (metric, log line, dashboard) that proves the slice works.",
116
+ "Describe the failure signal that triggers an alert or paging.",
117
+ "Document the recovery procedure and any monitoring gaps.",
118
+ "Return verdict 'omitted' only for slices with no runtime behavior at all.",
119
+ ].join("\n"),
120
+ promptSection: "Operational Readiness",
121
+ },
122
+ MV01: {
123
+ id: "MV01",
124
+ scope: "milestone",
125
+ ownerTurn: "validate-milestone",
126
+ question: "Is every success criterion in the milestone roadmap satisfied?",
127
+ guidance: [
128
+ "Walk the success-criteria checklist from the milestone roadmap.",
129
+ "For each criterion, point to the slice / assessment / verification evidence that proves it.",
130
+ "Return verdict 'flag' if any criterion is unmet or unverifiable.",
131
+ ].join("\n"),
132
+ promptSection: "Success Criteria Checklist",
133
+ },
134
+ MV02: {
135
+ id: "MV02",
136
+ scope: "milestone",
137
+ ownerTurn: "validate-milestone",
138
+ question: "Does every slice have a SUMMARY.md and a passing assessment?",
139
+ guidance: [
140
+ "Confirm every slice listed in the roadmap has a SUMMARY.md.",
141
+ "Confirm each slice has an ASSESSMENT verdict of 'pass' (or justified 'omitted').",
142
+ "Flag missing artifacts and slices with outstanding follow-ups or known limitations.",
143
+ ].join("\n"),
144
+ promptSection: "Slice Delivery Audit",
145
+ },
146
+ MV03: {
147
+ id: "MV03",
148
+ scope: "milestone",
149
+ ownerTurn: "validate-milestone",
150
+ question: "Do the slices integrate end-to-end?",
151
+ guidance: [
152
+ "Trace at least one cross-slice flow proving the pieces compose.",
153
+ "Flag gaps where two slices were built in isolation with no integration evidence.",
154
+ ].join("\n"),
155
+ promptSection: "Cross-Slice Integration",
156
+ },
157
+ MV04: {
158
+ id: "MV04",
159
+ scope: "milestone",
160
+ ownerTurn: "validate-milestone",
161
+ question: "Are all touched requirements covered and still coherent?",
162
+ guidance: [
163
+ "For each requirement advanced, validated, surfaced, or invalidated across the milestone's slices, confirm the milestone-level evidence matches.",
164
+ "Flag requirements that slices claim to advance but no artifact proves.",
165
+ ].join("\n"),
166
+ promptSection: "Requirement Coverage",
167
+ },
168
+ } as const satisfies Record<GateId, GateDefinition>;
169
+
170
+ export type GateRegistry = typeof GATE_REGISTRY;
171
+
172
+ /** Stable ordered lists per owner turn — iteration order matches declaration. */
173
+ const ORDERED_GATES: readonly GateDefinition[] = Object.values(GATE_REGISTRY) as readonly GateDefinition[];
174
+
175
+ /** Return every gate owned by a turn, in stable declaration order. */
176
+ export function getGatesForTurn(turn: OwnerTurn): GateDefinition[] {
177
+ return ORDERED_GATES.filter((g) => g.ownerTurn === turn);
178
+ }
179
+
180
+ /** Return the set of gate ids a turn owns. */
181
+ export function getGateIdsForTurn(turn: OwnerTurn): Set<GateId> {
182
+ return new Set(getGatesForTurn(turn).map((g) => g.id));
183
+ }
184
+
185
+ /** Look up a definition by gate id, or undefined if unknown. */
186
+ export function getGateDefinition(id: string): GateDefinition | undefined {
187
+ return (GATE_REGISTRY as Record<string, GateDefinition>)[id];
188
+ }
189
+
190
+ /** Look up the owner turn for a gate id. Throws if the gate is unknown. */
191
+ export function getOwnerTurn(id: GateId): OwnerTurn {
192
+ const def = GATE_REGISTRY[id];
193
+ if (!def) {
194
+ throw new GSDError(GSD_PARSE_ERROR, `gate-registry: unknown gate id "${id}"`);
195
+ }
196
+ return def.ownerTurn;
197
+ }
198
+
199
+ /**
200
+ * Assert that the pending gate rows for a turn match what the registry says
201
+ * the turn owns. Fails loudly rather than silently skipping.
202
+ *
203
+ * - Every row in `pending` must have a definition whose `ownerTurn` matches `turn`.
204
+ * (The caller is responsible for scoping the pending list — e.g. filtering
205
+ * by slice scope before passing it in.)
206
+ * - `options.requireAll` (default true): every gate the turn owns must appear
207
+ * in `pending`. Set to false for turns like `execute-task` that only need
208
+ * coverage for the subset of gates that were seeded (e.g. tasks with no
209
+ * external dependencies have no Q5 row).
210
+ */
211
+ export function assertGateCoverage(
212
+ pending: ReadonlyArray<Pick<GateRow, "gate_id">>,
213
+ turn: OwnerTurn,
214
+ options: { requireAll?: boolean } = {},
215
+ ): void {
216
+ const requireAll = options.requireAll ?? true;
217
+ const expected = getGateIdsForTurn(turn);
218
+ const pendingIds = new Set(pending.map((g) => g.gate_id));
219
+
220
+ const unknown: string[] = [];
221
+ for (const id of pendingIds) {
222
+ const def = getGateDefinition(id);
223
+ if (!def) {
224
+ unknown.push(id);
225
+ continue;
226
+ }
227
+ if (def.ownerTurn !== turn) {
228
+ unknown.push(`${id} (owned by ${def.ownerTurn}, not ${turn})`);
229
+ }
230
+ }
231
+
232
+ if (unknown.length > 0) {
233
+ throw new GSDError(
234
+ GSD_PARSE_ERROR,
235
+ `assertGateCoverage: turn "${turn}" received pending gates it does not own: ${unknown.join(", ")}`,
236
+ );
237
+ }
238
+
239
+ if (requireAll) {
240
+ const missing: GateId[] = [];
241
+ for (const id of expected) {
242
+ if (!pendingIds.has(id)) missing.push(id);
243
+ }
244
+ if (missing.length > 0) {
245
+ throw new GSDError(
246
+ GSD_PARSE_ERROR,
247
+ `assertGateCoverage: turn "${turn}" is missing required gates: ${missing.join(", ")}`,
248
+ );
249
+ }
250
+ }
251
+ }
@@ -10,6 +10,7 @@ import { existsSync, copyFileSync, mkdirSync, realpathSync } from "node:fs";
10
10
  import { dirname } from "node:path";
11
11
  import type { Decision, Requirement, GateRow, GateId, GateScope, GateStatus, GateVerdict } from "./types.js";
12
12
  import { GSDError, GSD_STALE_STATE } from "./errors.js";
13
+ import { getGateIdsForTurn, type OwnerTurn } from "./gate-registry.js";
13
14
  import { logError, logWarning } from "./workflow-logger.js";
14
15
 
15
16
  const _require = createRequire(import.meta.url);
@@ -2302,3 +2303,53 @@ export function getPendingSliceGateCount(milestoneId: string, sliceId: string):
2302
2303
  ).get({ ":mid": milestoneId, ":sid": sliceId });
2303
2304
  return row ? (row["cnt"] as number) : 0;
2304
2305
  }
2306
+
2307
+ /**
2308
+ * Return pending gate rows owned by a specific workflow turn.
2309
+ *
2310
+ * Unlike `getPendingGates(..., scope)`, this filters by the registry's
2311
+ * `ownerTurn` metadata so callers can distinguish Q3/Q4 (owned by
2312
+ * gate-evaluate) from Q8 (owned by complete-slice) even though both are
2313
+ * scope:"slice". Pass `taskId` to narrow task-scoped results to one task.
2314
+ */
2315
+ export function getPendingGatesForTurn(
2316
+ milestoneId: string,
2317
+ sliceId: string,
2318
+ turn: OwnerTurn,
2319
+ taskId?: string,
2320
+ ): GateRow[] {
2321
+ if (!currentDb) return [];
2322
+ const ids = getGateIdsForTurn(turn);
2323
+ if (ids.size === 0) return [];
2324
+ const idList = [...ids];
2325
+ const placeholders = idList.map((_, i) => `:gid${i}`).join(",");
2326
+ const params: Record<string, unknown> = {
2327
+ ":mid": milestoneId,
2328
+ ":sid": sliceId,
2329
+ };
2330
+ idList.forEach((id, i) => {
2331
+ params[`:gid${i}`] = id;
2332
+ });
2333
+ let sql =
2334
+ `SELECT * FROM quality_gates
2335
+ WHERE milestone_id = :mid AND slice_id = :sid
2336
+ AND status = 'pending'
2337
+ AND gate_id IN (${placeholders})`;
2338
+ if (taskId !== undefined) {
2339
+ sql += ` AND task_id = :tid`;
2340
+ params[":tid"] = taskId;
2341
+ }
2342
+ return currentDb.prepare(sql).all(params).map(rowToGate);
2343
+ }
2344
+
2345
+ /**
2346
+ * Count pending gates for a turn. Convenience wrapper used by state
2347
+ * derivation to decide whether a phase transition should pause.
2348
+ */
2349
+ export function getPendingGateCountForTurn(
2350
+ milestoneId: string,
2351
+ sliceId: string,
2352
+ turn: OwnerTurn,
2353
+ ): number {
2354
+ return getPendingGatesForTurn(milestoneId, sliceId, turn).length;
2355
+ }
@@ -15,7 +15,7 @@ import { loadPrompt, inlineTemplate } from "./prompt-loader.js";
15
15
  import { buildSkillActivationBlock } from "./auto-prompts.js";
16
16
  import { deriveState } from "./state.js";
17
17
  import { invalidateAllCaches } from "./cache.js";
18
- import { startAuto } from "./auto.js";
18
+ import { startAutoDetached } from "./auto.js";
19
19
  import { clearLock } from "./crash-recovery.js";
20
20
  import {
21
21
  assessInterruptedSession,
@@ -67,7 +67,6 @@ export {
67
67
  showQueue, handleQueueReorder, showQueueAdd,
68
68
  buildExistingMilestonesContext,
69
69
  } from "./guided-flow-queue.js";
70
- import { getErrorMessage } from "./error-utils.js";
71
70
  import { logWarning } from "./workflow-logger.js";
72
71
 
73
72
  // ─── ID Generation with Reservation ─────────────────────────────────────────
@@ -244,11 +243,7 @@ export function checkAutoStartAfterDiscuss(): boolean {
244
243
 
245
244
  pendingAutoStartMap.delete(basePath);
246
245
  ctx.ui.notify(`Milestone ${milestoneId} ready.`, "info");
247
- startAuto(ctx, pi, basePath, false, { step }).catch((err) => {
248
- ctx.ui.notify(`Auto-start failed: ${getErrorMessage(err)}`, "error");
249
- logWarning("guided", `auto start error: ${getErrorMessage(err)}`);
250
- debugLog("auto-start-failed", { error: getErrorMessage(err) });
251
- });
246
+ startAutoDetached(ctx, pi, basePath, false, { step });
252
247
  return true;
253
248
  }
254
249
 
@@ -1305,7 +1300,7 @@ export async function showSmartEntry(
1305
1300
  ],
1306
1301
  });
1307
1302
  if (resume === "resume") {
1308
- await startAuto(ctx, pi, basePath, false, {
1303
+ startAutoDetached(ctx, pi, basePath, false, {
1309
1304
  interrupted,
1310
1305
  step: interrupted.pausedSession?.stepMode ?? false,
1311
1306
  });
@@ -1647,7 +1642,7 @@ export async function showSmartEntry(
1647
1642
  });
1648
1643
 
1649
1644
  if (choice === "auto") {
1650
- await startAuto(ctx, pi, basePath, false);
1645
+ startAutoDetached(ctx, pi, basePath, false);
1651
1646
  } else if (choice === "status") {
1652
1647
  const { fireStatusViaCommand } = await import("./commands.js");
1653
1648
  await fireStatusViaCommand(ctx);
@@ -1859,7 +1854,7 @@ export async function showSmartEntry(
1859
1854
  });
1860
1855
 
1861
1856
  if (choice === "auto") {
1862
- await startAuto(ctx, pi, basePath, false);
1857
+ startAutoDetached(ctx, pi, basePath, false);
1863
1858
  return;
1864
1859
  }
1865
1860
 
@@ -34,6 +34,7 @@ export interface PausedSessionMetadata {
34
34
  activeEngineId?: string;
35
35
  activeRunDir?: string | null;
36
36
  autoStartTime?: number;
37
+ milestoneLock?: string | null;
37
38
  }
38
39
 
39
40
  export interface InterruptedSessionAssessment {
@@ -41,6 +41,7 @@ export interface UnitMetrics {
41
41
  model: string; // model ID used
42
42
  startedAt: number; // ms timestamp
43
43
  finishedAt: number; // ms timestamp
44
+ autoSessionKey?: string; // identifies one auto-mode run across pause/resume
44
45
  tokens: TokenCounts;
45
46
  cost: number; // total USD cost
46
47
  toolCalls: number;
@@ -133,7 +134,16 @@ export function snapshotUnitMetrics(
133
134
  unitId: string,
134
135
  startedAt: number,
135
136
  model: string,
136
- opts?: { tier?: string; modelDowngraded?: boolean; contextWindowTokens?: number; truncationSections?: number; continueHereFired?: boolean; promptCharCount?: number; baselineCharCount?: number },
137
+ opts?: {
138
+ tier?: string;
139
+ modelDowngraded?: boolean;
140
+ contextWindowTokens?: number;
141
+ truncationSections?: number;
142
+ continueHereFired?: boolean;
143
+ promptCharCount?: number;
144
+ baselineCharCount?: number;
145
+ autoSessionKey?: string;
146
+ },
137
147
  ): UnitMetrics | null {
138
148
  if (!ledger) return null;
139
149
 
@@ -181,6 +191,7 @@ export function snapshotUnitMetrics(
181
191
  model,
182
192
  startedAt,
183
193
  finishedAt: Date.now(),
194
+ ...(opts?.autoSessionKey ? { autoSessionKey: opts.autoSessionKey } : {}),
184
195
  tokens,
185
196
  cost,
186
197
  toolCalls,
@@ -20,7 +20,7 @@ import {
20
20
  } from "./paths.js";
21
21
  import { invalidateAllCaches } from "./cache.js";
22
22
  import { loadQueueOrder, saveQueueOrder } from "./queue-order.js";
23
- import { isDbAvailable, updateMilestoneStatus } from "./gsd-db.js";
23
+ import { getMilestone, isDbAvailable, updateMilestoneStatus } from "./gsd-db.js";
24
24
  import { logWarning } from "./workflow-logger.js";
25
25
 
26
26
  // ─── Park ──────────────────────────────────────────────────────────────────
@@ -77,9 +77,16 @@ export function unparkMilestone(basePath: string, milestoneId: string): boolean
77
77
  if (!mDir || !existsSync(mDir)) return false;
78
78
 
79
79
  const parkedPath = join(mDir, buildMilestoneFileName(milestoneId, "PARKED"));
80
- if (!existsSync(parkedPath)) return false; // not parked
80
+ const hadParkedFile = existsSync(parkedPath);
81
+ const dbThinksParked = isDbAvailable() && getMilestone(milestoneId)?.status === "parked";
81
82
 
82
- unlinkSync(parkedPath);
83
+ // Recover the reverse desync too: DB can still say "parked" even when the
84
+ // PARKED marker was lost on disk, and /gsd unpark should repair that state.
85
+ if (!hadParkedFile && !dbThinksParked) return false;
86
+
87
+ if (hadParkedFile) {
88
+ unlinkSync(parkedPath);
89
+ }
83
90
  // Sync DB status so deriveStateFromDb picks up the unparked milestone (#2694)
84
91
  if (isDbAvailable()) {
85
92
  try {
@@ -6,19 +6,13 @@
6
6
  * records in the DB. This module inserts milestone-level validation gates
7
7
  * that correspond to the validation checks performed.
8
8
  *
9
- * Gate IDs for milestone validation:
10
- * MV01 Success criteria checklist
11
- * MV02 Slice delivery audit
12
- * MV03 — Cross-slice integration
13
- * MV04 — Requirement coverage
14
- *
15
- * These use the existing quality_gates table with scope "milestone".
9
+ * Gate IDs for milestone validation (MV01–MV04) are sourced from the
10
+ * gate registry so the definitions stay in lockstep with prompt builders,
11
+ * dispatch rules, and state derivation. See gate-registry.ts.
16
12
  */
17
13
 
18
14
  import { _getAdapter } from "./gsd-db.js";
19
-
20
- /** Milestone validation gate IDs. */
21
- const MILESTONE_GATE_IDS = ["MV01", "MV02", "MV03", "MV04"] as const;
15
+ import { getGatesForTurn } from "./gate-registry.js";
22
16
 
23
17
  /**
24
18
  * Insert milestone-level quality_gates records for a validation run.
@@ -27,6 +21,9 @@ const MILESTONE_GATE_IDS = ["MV01", "MV02", "MV03", "MV04"] as const;
27
21
  * from the overall milestone validation verdict. Individual gate-level
28
22
  * verdicts are not available (the handler receives a single verdict),
29
23
  * so all gates share the overall verdict.
24
+ *
25
+ * Gate IDs come from the registry — adding/removing an MV-scoped gate
26
+ * in gate-registry.ts automatically flows through here.
30
27
  */
31
28
  export function insertMilestoneValidationGates(
32
29
  milestoneId: string,
@@ -38,8 +35,9 @@ export function insertMilestoneValidationGates(
38
35
  if (!db) return;
39
36
 
40
37
  const gateVerdict = verdict === "pass" ? "pass" : "flag";
38
+ const milestoneGates = getGatesForTurn("validate-milestone");
41
39
 
42
- for (const gateId of MILESTONE_GATE_IDS) {
40
+ for (const def of milestoneGates) {
43
41
  db.prepare(
44
42
  `INSERT OR REPLACE INTO quality_gates
45
43
  (milestone_id, slice_id, gate_id, scope, task_id, status, verdict, rationale, findings, evaluated_at)
@@ -47,9 +45,9 @@ export function insertMilestoneValidationGates(
47
45
  ).run({
48
46
  ":mid": milestoneId,
49
47
  ":sid": sliceId,
50
- ":gid": gateId,
48
+ ":gid": def.id,
51
49
  ":verdict": gateVerdict,
52
- ":rationale": `Milestone validation verdict: ${verdict}`,
50
+ ":rationale": `${def.promptSection} — milestone validation verdict: ${verdict}`,
53
51
  ":evaluated_at": evaluatedAt,
54
52
  });
55
53
  }
@@ -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