gsd-pi 2.71.0-dev.977c553 → 2.71.0-dev.d4d916a

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 (278) hide show
  1. package/dist/cli.js +12 -3
  2. package/dist/headless-events.d.ts +2 -0
  3. package/dist/headless-events.js +7 -0
  4. package/dist/headless.js +16 -3
  5. package/dist/mcp-server.js +6 -6
  6. package/dist/provider-migrations.d.ts +10 -0
  7. package/dist/provider-migrations.js +12 -0
  8. package/dist/resource-loader.js +139 -13
  9. package/dist/resources/GSD-WORKFLOW.md +1 -1
  10. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +10 -4
  11. package/dist/resources/extensions/gsd/auto/infra-errors.js +34 -0
  12. package/dist/resources/extensions/gsd/auto/loop.js +32 -1
  13. package/dist/resources/extensions/gsd/auto/phases.js +1 -1
  14. package/dist/resources/extensions/gsd/auto/session.js +11 -0
  15. package/dist/resources/extensions/gsd/auto-dashboard.js +22 -16
  16. package/dist/resources/extensions/gsd/auto-model-selection.js +10 -2
  17. package/dist/resources/extensions/gsd/auto-start.js +31 -7
  18. package/dist/resources/extensions/gsd/auto-tool-tracking.js +1 -1
  19. package/dist/resources/extensions/gsd/auto-worktree.js +1 -1
  20. package/dist/resources/extensions/gsd/auto.js +52 -0
  21. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +2 -0
  22. package/dist/resources/extensions/gsd/bootstrap/register-shortcuts.js +63 -51
  23. package/dist/resources/extensions/gsd/bootstrap/system-context.js +6 -0
  24. package/dist/resources/extensions/gsd/commands/context.js +15 -6
  25. package/dist/resources/extensions/gsd/commands/dispatcher.js +12 -2
  26. package/dist/resources/extensions/gsd/commands/handlers/auto.js +10 -33
  27. package/dist/resources/extensions/gsd/commands/handlers/core.js +56 -11
  28. package/dist/resources/extensions/gsd/commands/handlers/notifications-handler.js +15 -6
  29. package/dist/resources/extensions/gsd/commands/handlers/workflow.js +4 -10
  30. package/dist/resources/extensions/gsd/dashboard-overlay.js +8 -3
  31. package/dist/resources/extensions/gsd/dispatch-guard.js +18 -1
  32. package/dist/resources/extensions/gsd/error-classifier.js +1 -1
  33. package/dist/resources/extensions/gsd/forensics.js +19 -6
  34. package/dist/resources/extensions/gsd/guided-flow.js +5 -10
  35. package/dist/resources/extensions/gsd/metrics.js +1 -0
  36. package/dist/resources/extensions/gsd/milestone-actions.js +10 -4
  37. package/dist/resources/extensions/gsd/notification-overlay.js +42 -13
  38. package/dist/resources/extensions/gsd/notification-store.js +56 -5
  39. package/dist/resources/extensions/gsd/notification-widget.js +5 -13
  40. package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +8 -3
  41. package/dist/resources/extensions/gsd/pre-execution-checks.js +35 -2
  42. package/dist/resources/extensions/gsd/prompts/complete-slice.md +2 -2
  43. package/dist/resources/extensions/gsd/prompts/discuss.md +2 -0
  44. package/dist/resources/extensions/gsd/prompts/execute-task.md +20 -19
  45. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +2 -0
  46. package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +2 -0
  47. package/dist/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
  48. package/dist/resources/extensions/gsd/prompts/queue.md +3 -2
  49. package/dist/resources/extensions/gsd/prompts/system.md +1 -0
  50. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +2 -1
  51. package/dist/resources/extensions/gsd/session-model-override.js +25 -0
  52. package/dist/resources/extensions/gsd/shortcut-defs.js +40 -0
  53. package/dist/resources/extensions/ollama/index.js +13 -5
  54. package/dist/resources/skills/create-skill/SKILL.md +2 -0
  55. package/dist/startup-model-validation.d.ts +0 -1
  56. package/dist/startup-model-validation.js +6 -2
  57. package/dist/web/standalone/.next/BUILD_ID +1 -1
  58. package/dist/web/standalone/.next/app-path-routes-manifest.json +14 -14
  59. package/dist/web/standalone/.next/build-manifest.json +2 -2
  60. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  61. package/dist/web/standalone/.next/required-server-files.json +1 -1
  62. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  63. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  64. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  65. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  66. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  67. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  68. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  69. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  70. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  71. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  72. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  73. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  74. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  75. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  77. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  78. package/dist/web/standalone/.next/server/app/index.html +1 -1
  79. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  80. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  81. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  82. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  83. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  84. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  85. package/dist/web/standalone/.next/server/app-paths-manifest.json +14 -14
  86. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  87. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  88. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  89. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  90. package/dist/web/standalone/server.js +1 -1
  91. package/package.json +1 -1
  92. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  93. package/packages/mcp-server/dist/workflow-tools.js +21 -11
  94. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  95. package/packages/mcp-server/src/workflow-tools.test.ts +110 -0
  96. package/packages/mcp-server/src/workflow-tools.ts +31 -11
  97. package/packages/pi-ai/dist/providers/amazon-bedrock.js +11 -2
  98. package/packages/pi-ai/dist/providers/amazon-bedrock.js.map +1 -1
  99. package/packages/pi-ai/dist/providers/anthropic-auth.test.d.ts +2 -0
  100. package/packages/pi-ai/dist/providers/anthropic-auth.test.d.ts.map +1 -0
  101. package/packages/pi-ai/dist/providers/anthropic-auth.test.js +20 -0
  102. package/packages/pi-ai/dist/providers/anthropic-auth.test.js.map +1 -0
  103. package/packages/pi-ai/dist/providers/anthropic-shared.d.ts +4 -1
  104. package/packages/pi-ai/dist/providers/anthropic-shared.d.ts.map +1 -1
  105. package/packages/pi-ai/dist/providers/anthropic-shared.js +8 -3
  106. package/packages/pi-ai/dist/providers/anthropic-shared.js.map +1 -1
  107. package/packages/pi-ai/dist/providers/anthropic-shared.test.js +44 -1
  108. package/packages/pi-ai/dist/providers/anthropic-shared.test.js.map +1 -1
  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/dist/providers/openai-completions.d.ts.map +1 -1
  114. package/packages/pi-ai/dist/providers/openai-completions.js +11 -0
  115. package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
  116. package/packages/pi-ai/src/providers/amazon-bedrock.ts +13 -1
  117. package/packages/pi-ai/src/providers/anthropic-auth.test.ts +32 -0
  118. package/packages/pi-ai/src/providers/anthropic-shared.test.ts +55 -1
  119. package/packages/pi-ai/src/providers/anthropic-shared.ts +14 -3
  120. package/packages/pi-ai/src/providers/anthropic.ts +8 -4
  121. package/packages/pi-ai/src/providers/openai-completions.ts +14 -0
  122. package/packages/pi-coding-agent/dist/core/agent-session-renderable-tools.test.d.ts +2 -0
  123. package/packages/pi-coding-agent/dist/core/agent-session-renderable-tools.test.d.ts.map +1 -0
  124. package/packages/pi-coding-agent/dist/core/agent-session-renderable-tools.test.js +61 -0
  125. package/packages/pi-coding-agent/dist/core/agent-session-renderable-tools.test.js.map +1 -0
  126. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  127. package/packages/pi-coding-agent/dist/core/agent-session.js +2 -1
  128. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  129. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts +10 -0
  130. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
  131. package/packages/pi-coding-agent/dist/core/auth-storage.js +27 -0
  132. package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
  133. package/packages/pi-coding-agent/dist/core/auth-storage.test.js +85 -0
  134. package/packages/pi-coding-agent/dist/core/auth-storage.test.js.map +1 -1
  135. package/packages/pi-coding-agent/dist/core/model-resolver-initial-model-auth.test.d.ts +2 -0
  136. package/packages/pi-coding-agent/dist/core/model-resolver-initial-model-auth.test.d.ts.map +1 -0
  137. package/packages/pi-coding-agent/dist/core/model-resolver-initial-model-auth.test.js +64 -0
  138. package/packages/pi-coding-agent/dist/core/model-resolver-initial-model-auth.test.js.map +1 -0
  139. package/packages/pi-coding-agent/dist/core/model-resolver.d.ts.map +1 -1
  140. package/packages/pi-coding-agent/dist/core/model-resolver.js +22 -18
  141. package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
  142. package/packages/pi-coding-agent/dist/core/model-resolver.test.d.ts +8 -0
  143. package/packages/pi-coding-agent/dist/core/model-resolver.test.d.ts.map +1 -0
  144. package/packages/pi-coding-agent/dist/core/model-resolver.test.js +75 -0
  145. package/packages/pi-coding-agent/dist/core/model-resolver.test.js.map +1 -0
  146. package/packages/pi-coding-agent/dist/core/sdk.d.ts +11 -0
  147. package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
  148. package/packages/pi-coding-agent/dist/core/sdk.js +38 -5
  149. package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
  150. package/packages/pi-coding-agent/dist/core/sdk.test.d.ts +2 -0
  151. package/packages/pi-coding-agent/dist/core/sdk.test.d.ts.map +1 -0
  152. package/packages/pi-coding-agent/dist/core/sdk.test.js +71 -0
  153. package/packages/pi-coding-agent/dist/core/sdk.test.js.map +1 -0
  154. package/packages/pi-coding-agent/dist/index.d.ts +1 -1
  155. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  156. package/packages/pi-coding-agent/dist/index.js +1 -1
  157. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  158. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/login-dialog.test.d.ts +2 -0
  159. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/login-dialog.test.d.ts.map +1 -0
  160. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/login-dialog.test.js +13 -0
  161. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/login-dialog.test.js.map +1 -0
  162. package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.d.ts +4 -0
  163. package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
  164. package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.js +24 -2
  165. package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.js.map +1 -1
  166. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  167. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js +9 -2
  168. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js.map +1 -1
  169. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +4 -0
  170. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  171. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +43 -0
  172. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  173. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  174. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +7 -2
  175. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  176. package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.d.ts.map +1 -1
  177. package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.js +6 -1
  178. package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.js.map +1 -1
  179. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  180. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +4 -3
  181. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  182. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js +4 -2
  183. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js.map +1 -1
  184. package/packages/pi-coding-agent/src/core/agent-session-renderable-tools.test.ts +70 -0
  185. package/packages/pi-coding-agent/src/core/agent-session.ts +2 -1
  186. package/packages/pi-coding-agent/src/core/auth-storage.test.ts +108 -0
  187. package/packages/pi-coding-agent/src/core/auth-storage.ts +30 -0
  188. package/packages/pi-coding-agent/src/core/model-resolver-initial-model-auth.test.ts +78 -0
  189. package/packages/pi-coding-agent/src/core/model-resolver.test.ts +85 -0
  190. package/packages/pi-coding-agent/src/core/model-resolver.ts +22 -18
  191. package/packages/pi-coding-agent/src/core/sdk.test.ts +89 -0
  192. package/packages/pi-coding-agent/src/core/sdk.ts +45 -9
  193. package/packages/pi-coding-agent/src/index.ts +1 -0
  194. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/login-dialog.test.ts +24 -0
  195. package/packages/pi-coding-agent/src/modes/interactive/components/login-dialog.ts +30 -2
  196. package/packages/pi-coding-agent/src/modes/interactive/components/model-selector.ts +15 -6
  197. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +47 -0
  198. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +7 -2
  199. package/packages/pi-coding-agent/src/modes/interactive/controllers/model-controller.ts +6 -1
  200. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +4 -3
  201. package/packages/pi-coding-agent/src/modes/interactive/slash-command-handlers.ts +4 -2
  202. package/src/resources/GSD-WORKFLOW.md +1 -1
  203. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +13 -5
  204. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +56 -4
  205. package/src/resources/extensions/gsd/auto/infra-errors.ts +38 -0
  206. package/src/resources/extensions/gsd/auto/loop-deps.ts +2 -0
  207. package/src/resources/extensions/gsd/auto/loop.ts +45 -1
  208. package/src/resources/extensions/gsd/auto/phases.ts +2 -0
  209. package/src/resources/extensions/gsd/auto/session.ts +11 -0
  210. package/src/resources/extensions/gsd/auto-dashboard.ts +29 -18
  211. package/src/resources/extensions/gsd/auto-model-selection.ts +9 -1
  212. package/src/resources/extensions/gsd/auto-start.ts +38 -7
  213. package/src/resources/extensions/gsd/auto-tool-tracking.ts +1 -1
  214. package/src/resources/extensions/gsd/auto-worktree.ts +1 -1
  215. package/src/resources/extensions/gsd/auto.ts +68 -0
  216. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +2 -0
  217. package/src/resources/extensions/gsd/bootstrap/register-shortcuts.ts +79 -60
  218. package/src/resources/extensions/gsd/bootstrap/system-context.ts +7 -0
  219. package/src/resources/extensions/gsd/commands/context.ts +16 -5
  220. package/src/resources/extensions/gsd/commands/dispatcher.ts +14 -2
  221. package/src/resources/extensions/gsd/commands/handlers/auto.ts +10 -36
  222. package/src/resources/extensions/gsd/commands/handlers/core.ts +58 -11
  223. package/src/resources/extensions/gsd/commands/handlers/notifications-handler.ts +17 -7
  224. package/src/resources/extensions/gsd/commands/handlers/workflow.ts +4 -10
  225. package/src/resources/extensions/gsd/dashboard-overlay.ts +10 -3
  226. package/src/resources/extensions/gsd/dispatch-guard.ts +18 -1
  227. package/src/resources/extensions/gsd/error-classifier.ts +1 -1
  228. package/src/resources/extensions/gsd/forensics.ts +23 -7
  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/notification-overlay.ts +47 -14
  234. package/src/resources/extensions/gsd/notification-store.ts +54 -5
  235. package/src/resources/extensions/gsd/notification-widget.ts +5 -14
  236. package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +10 -3
  237. package/src/resources/extensions/gsd/pre-execution-checks.ts +39 -2
  238. package/src/resources/extensions/gsd/prompts/complete-slice.md +2 -2
  239. package/src/resources/extensions/gsd/prompts/discuss.md +2 -0
  240. package/src/resources/extensions/gsd/prompts/execute-task.md +20 -19
  241. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +2 -0
  242. package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +2 -0
  243. package/src/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
  244. package/src/resources/extensions/gsd/prompts/queue.md +3 -2
  245. package/src/resources/extensions/gsd/prompts/system.md +1 -0
  246. package/src/resources/extensions/gsd/prompts/validate-milestone.md +2 -1
  247. package/src/resources/extensions/gsd/session-model-override.ts +36 -0
  248. package/src/resources/extensions/gsd/shortcut-defs.ts +56 -0
  249. package/src/resources/extensions/gsd/tests/auto-start-model-capture.test.ts +25 -9
  250. package/src/resources/extensions/gsd/tests/auto-start-worktree-db-path.test.ts +28 -0
  251. package/src/resources/extensions/gsd/tests/bootstrap-derive-state-db-open.test.ts +39 -0
  252. package/src/resources/extensions/gsd/tests/complete-slice-prompt-task-summary-layout.test.ts +18 -0
  253. package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +27 -0
  254. package/src/resources/extensions/gsd/tests/execute-task-prompt-existing-artifact-guard.test.ts +33 -0
  255. package/src/resources/extensions/gsd/tests/forensics-stuck-loops.test.ts +62 -0
  256. package/src/resources/extensions/gsd/tests/format-shortcut.test.ts +31 -0
  257. package/src/resources/extensions/gsd/tests/gsd-no-project-error.test.ts +73 -0
  258. package/src/resources/extensions/gsd/tests/infra-errors-cooldown.test.ts +180 -0
  259. package/src/resources/extensions/gsd/tests/integration/auto-worktree-milestone-merge.test.ts +66 -1
  260. package/src/resources/extensions/gsd/tests/model-isolation.test.ts +36 -51
  261. package/src/resources/extensions/gsd/tests/notification-store.test.ts +35 -0
  262. package/src/resources/extensions/gsd/tests/notification-widget.test.ts +26 -0
  263. package/src/resources/extensions/gsd/tests/notifications-handler.test.ts +90 -0
  264. package/src/resources/extensions/gsd/tests/parallel-monitor-overlay.test.ts +1 -0
  265. package/src/resources/extensions/gsd/tests/park-db-sync.test.ts +18 -0
  266. package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +49 -0
  267. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +19 -0
  268. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +7 -0
  269. package/src/resources/extensions/gsd/tests/register-shortcuts.test.ts +63 -5
  270. package/src/resources/extensions/gsd/tests/session-model-override.test.ts +35 -0
  271. package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +90 -0
  272. package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +7 -0
  273. package/src/resources/extensions/gsd/tests/validate-milestone-prompt-verification-classes.test.ts +18 -0
  274. package/src/resources/extensions/ollama/index.ts +13 -3
  275. package/src/resources/extensions/ollama/ollama-status-indicator.test.ts +28 -0
  276. package/src/resources/skills/create-skill/SKILL.md +2 -0
  277. /package/dist/web/standalone/.next/static/{4xyaXTn7-shVHaGMcl75o → IRnpNeY-_eO7SxKBIkTbL}/_buildManifest.js +0 -0
  278. /package/dist/web/standalone/.next/static/{4xyaXTn7-shVHaGMcl75o → IRnpNeY-_eO7SxKBIkTbL}/_ssgManifest.js +0 -0
@@ -650,19 +650,33 @@ function getDbCompletionCounts(): DbCompletionCounts | null {
650
650
  * Exported for testability.
651
651
  */
652
652
  export function detectStuckLoops(units: UnitMetrics[], anomalies: ForensicAnomaly[]): void {
653
- // First, collect unique startedAt values per type/id key
654
- const dispatchMap = new Map<string, Set<number>>();
653
+ // First, collect unique startedAt values per type/id key, bucketed by
654
+ // autoSessionKey when available so cross-session recovery does not look
655
+ // like a within-session stuck loop.
656
+ const dispatchMap = new Map<string, Map<string, Set<number>>>();
655
657
  for (const u of units) {
656
658
  const key = `${u.type}/${u.id}`;
657
- let starts = dispatchMap.get(key);
659
+ let sessionBuckets = dispatchMap.get(key);
660
+ if (!sessionBuckets) {
661
+ sessionBuckets = new Map();
662
+ dispatchMap.set(key, sessionBuckets);
663
+ }
664
+
665
+ const sessionKey = u.autoSessionKey ?? "__legacy__";
666
+ let starts = sessionBuckets.get(sessionKey);
658
667
  if (!starts) {
659
668
  starts = new Set();
660
- dispatchMap.set(key, starts);
669
+ sessionBuckets.set(sessionKey, starts);
661
670
  }
662
671
  starts.add(u.startedAt);
663
672
  }
664
- for (const [key, starts] of dispatchMap) {
665
- const count = starts.size;
673
+
674
+ for (const [key, sessionBuckets] of dispatchMap) {
675
+ const hasSessionAwareData = Array.from(sessionBuckets.keys()).some((sessionKey) => sessionKey !== "__legacy__");
676
+ const count = hasSessionAwareData
677
+ ? Math.max(...Array.from(sessionBuckets.values(), (starts) => starts.size))
678
+ : (sessionBuckets.get("__legacy__")?.size ?? 0);
679
+
666
680
  if (count > 1) {
667
681
  const [unitType, ...idParts] = key.split("/");
668
682
  anomalies.push({
@@ -671,7 +685,9 @@ export function detectStuckLoops(units: UnitMetrics[], anomalies: ForensicAnomal
671
685
  unitType,
672
686
  unitId: idParts.join("/"),
673
687
  summary: `Unit ${key} was dispatched ${count} times`,
674
- details: `Repeated dispatch suggests the unit completed but its artifacts weren't verified, or the state machine kept returning it.`,
688
+ details: hasSessionAwareData
689
+ ? `Repeated dispatch within the same auto session suggests the unit completed but its artifacts were not verified, or the state machine kept returning it. Cross-session recovery runs are ignored.`
690
+ : `Repeated dispatch suggests the unit completed but its artifacts weren't verified, or the state machine kept returning it.`,
675
691
  });
676
692
  }
677
693
  }
@@ -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 {
@@ -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",
@@ -21,7 +21,7 @@ All relevant context has been preloaded below — the slice plan, all task summa
21
21
  Then:
22
22
  1. Use the **Slice Summary** and **UAT** output templates from the inlined context above
23
23
  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.
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. 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
25
  4. If the slice plan includes observability/diagnostic surfaces, confirm they work. Skip this for simple slices that don't have observability sections.
26
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.
27
27
  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.
@@ -35,7 +35,7 @@ Then:
35
35
 
36
36
  **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
37
 
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.
38
+ **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
39
 
40
40
  **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
41
 
@@ -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?