gsd-pi 2.72.0 → 2.73.0-dev.27730dc

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 (344) hide show
  1. package/README.md +12 -2
  2. package/dist/cli.js +59 -50
  3. package/dist/help-text.js +1 -1
  4. package/dist/onboarding.js +10 -0
  5. package/dist/resources/extensions/async-jobs/await-tool.js +7 -4
  6. package/dist/resources/extensions/async-jobs/job-manager.js +28 -3
  7. package/dist/resources/extensions/claude-code-cli/partial-builder.js +40 -12
  8. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +48 -23
  9. package/dist/resources/extensions/gsd/auto/loop.js +84 -1
  10. package/dist/resources/extensions/gsd/auto-dispatch.js +5 -3
  11. package/dist/resources/extensions/gsd/auto-post-unit.js +6 -0
  12. package/dist/resources/extensions/gsd/auto-prompts.js +9 -6
  13. package/dist/resources/extensions/gsd/auto-recovery.js +11 -0
  14. package/dist/resources/extensions/gsd/auto.js +30 -20
  15. package/dist/resources/extensions/gsd/bootstrap/crash-log.js +31 -0
  16. package/dist/resources/extensions/gsd/bootstrap/register-extension.js +18 -7
  17. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +9 -11
  18. package/dist/resources/extensions/gsd/bootstrap/system-context.js +6 -1
  19. package/dist/resources/extensions/gsd/commands-handlers.js +4 -1
  20. package/dist/resources/extensions/gsd/context-injector.js +1 -1
  21. package/dist/resources/extensions/gsd/crash-recovery.js +51 -0
  22. package/dist/resources/extensions/gsd/custom-workflow-engine.js +3 -7
  23. package/dist/resources/extensions/gsd/definition-io.js +15 -0
  24. package/dist/resources/extensions/gsd/dispatch-guard.js +4 -0
  25. package/dist/resources/extensions/gsd/doctor-runtime-checks.js +6 -3
  26. package/dist/resources/extensions/gsd/git-service.js +11 -8
  27. package/dist/resources/extensions/gsd/gitignore.js +12 -6
  28. package/dist/resources/extensions/gsd/gsd-db.js +85 -8
  29. package/dist/resources/extensions/gsd/key-manager.js +2 -0
  30. package/dist/resources/extensions/gsd/milestone-actions.js +19 -1
  31. package/dist/resources/extensions/gsd/preferences-skills.js +2 -34
  32. package/dist/resources/extensions/gsd/preferences-types.js +15 -0
  33. package/dist/resources/extensions/gsd/preferences.js +16 -3
  34. package/dist/resources/extensions/gsd/prompt-loader.js +4 -1
  35. package/dist/resources/extensions/gsd/prompts/discuss.md +122 -13
  36. package/dist/resources/extensions/gsd/prompts/system.md +1 -1
  37. package/dist/resources/extensions/gsd/state.js +21 -1
  38. package/dist/resources/extensions/gsd/workflow-projections.js +7 -0
  39. package/dist/resources/extensions/gsd/worktree-manager.js +30 -3
  40. package/dist/resources/extensions/gsd/write-intercept.js +10 -1
  41. package/dist/resources/extensions/ollama/index.js +4 -5
  42. package/dist/resources/extensions/ollama/ollama-client.js +35 -6
  43. package/dist/resources/extensions/ollama/ollama-discovery.js +32 -6
  44. package/dist/startup-model-validation.js +8 -5
  45. package/dist/web/standalone/.next/BUILD_ID +1 -1
  46. package/dist/web/standalone/.next/app-path-routes-manifest.json +10 -10
  47. package/dist/web/standalone/.next/build-manifest.json +3 -3
  48. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  49. package/dist/web/standalone/.next/required-server-files.json +3 -3
  50. package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
  51. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  52. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  53. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  54. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  55. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  56. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  57. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  58. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  59. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  60. package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
  61. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  62. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  63. package/dist/web/standalone/.next/server/app/_not-found.rsc +3 -3
  64. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
  65. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  66. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
  67. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  68. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  69. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  70. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  71. package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
  72. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
  73. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
  74. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
  75. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
  76. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
  77. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
  78. package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
  79. package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
  80. package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
  81. package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
  82. package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
  83. package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
  84. package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
  85. package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
  86. package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
  87. package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
  88. package/dist/web/standalone/.next/server/app/api/experimental/route.js +2 -2
  89. package/dist/web/standalone/.next/server/app/api/experimental/route_client-reference-manifest.js +1 -1
  90. package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
  91. package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
  92. package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
  93. package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
  94. package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
  95. package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
  96. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  97. package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
  98. package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
  99. package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
  100. package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
  101. package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
  102. package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
  103. package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
  104. package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
  105. package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
  106. package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
  107. package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
  108. package/dist/web/standalone/.next/server/app/api/notifications/route.js +2 -2
  109. package/dist/web/standalone/.next/server/app/api/notifications/route_client-reference-manifest.js +1 -1
  110. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  111. package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
  112. package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
  113. package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
  114. package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
  115. package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
  116. package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
  117. package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
  118. package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +2 -2
  119. package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
  120. package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
  121. package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
  122. package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
  123. package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
  124. package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
  125. package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
  126. package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
  127. package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
  128. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  129. package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
  130. package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
  131. package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
  132. package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
  133. package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
  134. package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
  135. package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
  136. package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
  137. package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
  138. package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +2 -2
  139. package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
  140. package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
  141. package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
  142. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +2 -2
  143. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
  144. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +3 -3
  145. package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
  146. package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
  147. package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
  148. package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
  149. package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
  150. package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
  151. package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  152. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  153. package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
  154. package/dist/web/standalone/.next/server/app/index.html +1 -1
  155. package/dist/web/standalone/.next/server/app/index.rsc +4 -4
  156. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  157. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +4 -4
  158. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  159. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +3 -3
  160. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  161. package/dist/web/standalone/.next/server/app/page.js +2 -2
  162. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  163. package/dist/web/standalone/.next/server/app-paths-manifest.json +10 -10
  164. package/dist/web/standalone/.next/server/chunks/2331.js +16 -16
  165. package/dist/web/standalone/.next/server/chunks/4741.js +12 -12
  166. package/dist/web/standalone/.next/server/chunks/5822.js +2 -2
  167. package/dist/web/standalone/.next/server/chunks/63.js +8 -8
  168. package/dist/web/standalone/.next/server/chunks/6897.js +3 -3
  169. package/dist/web/standalone/.next/server/edge-runtime-webpack.js +2 -0
  170. package/dist/web/standalone/.next/server/functions-config-manifest.json +0 -9
  171. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  172. package/dist/web/standalone/.next/server/middleware-manifest.json +29 -2
  173. package/dist/web/standalone/.next/server/middleware.js +4 -12
  174. package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
  175. package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
  176. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  177. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  178. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  179. package/dist/web/standalone/.next/server/webpack-runtime.js +1 -1
  180. package/dist/web/standalone/.next/static/chunks/app/_not-found/{page-2f24283c162b6ab3.js → page-f2a7482d42a5614b.js} +1 -1
  181. package/dist/web/standalone/.next/static/chunks/app/{layout-9ecfd95f343793f0.js → layout-a16c7a7ecdf0c2cf.js} +1 -1
  182. package/dist/web/standalone/.next/static/chunks/app/page-f1e30ab6bb269149.js +1 -0
  183. package/dist/web/standalone/.next/static/chunks/main-app-fdab67f7802d7832.js +1 -0
  184. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-459824ffb8c323dd.js +1 -0
  185. package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
  186. package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
  187. package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
  188. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api.target.mk +14 -14
  189. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_except.target.mk +14 -14
  190. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_maybe.target.mk +14 -14
  191. package/dist/web/standalone/server.js +1 -1
  192. package/package.json +1 -1
  193. package/packages/pi-ai/dist/env-api-keys.js +1 -0
  194. package/packages/pi-ai/dist/env-api-keys.js.map +1 -1
  195. package/packages/pi-ai/dist/models.custom.d.ts +105 -0
  196. package/packages/pi-ai/dist/models.custom.d.ts.map +1 -1
  197. package/packages/pi-ai/dist/models.custom.js +97 -0
  198. package/packages/pi-ai/dist/models.custom.js.map +1 -1
  199. package/packages/pi-ai/dist/models.generated.d.ts +648 -140
  200. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  201. package/packages/pi-ai/dist/models.generated.js +867 -370
  202. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  203. package/packages/pi-ai/dist/models.generated.test.d.ts +2 -0
  204. package/packages/pi-ai/dist/models.generated.test.d.ts.map +1 -0
  205. package/packages/pi-ai/dist/models.generated.test.js +334 -0
  206. package/packages/pi-ai/dist/models.generated.test.js.map +1 -0
  207. package/packages/pi-ai/dist/models.test.js +105 -0
  208. package/packages/pi-ai/dist/models.test.js.map +1 -1
  209. package/packages/pi-ai/dist/types.d.ts +1 -1
  210. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  211. package/packages/pi-ai/dist/types.js.map +1 -1
  212. package/packages/pi-ai/dist/utils/oauth/github-copilot.d.ts.map +1 -1
  213. package/packages/pi-ai/dist/utils/oauth/github-copilot.js +5 -1
  214. package/packages/pi-ai/dist/utils/oauth/github-copilot.js.map +1 -1
  215. package/packages/pi-ai/dist/utils/oauth/github-copilot.test.d.ts +2 -0
  216. package/packages/pi-ai/dist/utils/oauth/github-copilot.test.d.ts.map +1 -0
  217. package/packages/pi-ai/dist/utils/oauth/github-copilot.test.js +57 -0
  218. package/packages/pi-ai/dist/utils/oauth/github-copilot.test.js.map +1 -0
  219. package/packages/pi-ai/src/env-api-keys.ts +1 -0
  220. package/packages/pi-ai/src/models.custom.ts +98 -0
  221. package/packages/pi-ai/src/models.generated.test.ts +373 -0
  222. package/packages/pi-ai/src/models.generated.ts +867 -370
  223. package/packages/pi-ai/src/models.test.ts +135 -0
  224. package/packages/pi-ai/src/types.ts +1 -0
  225. package/packages/pi-ai/src/utils/oauth/github-copilot.test.ts +71 -0
  226. package/packages/pi-ai/src/utils/oauth/github-copilot.ts +4 -1
  227. package/packages/pi-coding-agent/dist/core/auth-storage.js +1 -1
  228. package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
  229. package/packages/pi-coding-agent/dist/core/auth-storage.test.js +27 -0
  230. package/packages/pi-coding-agent/dist/core/auth-storage.test.js.map +1 -1
  231. package/packages/pi-coding-agent/dist/core/model-resolver.d.ts.map +1 -1
  232. package/packages/pi-coding-agent/dist/core/model-resolver.js +25 -67
  233. package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
  234. package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
  235. package/packages/pi-coding-agent/dist/core/sdk.js +9 -0
  236. package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
  237. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js +36 -0
  238. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js.map +1 -1
  239. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  240. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +87 -12
  241. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  242. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts +1 -0
  243. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  244. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +22 -9
  245. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  246. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.d.ts +2 -0
  247. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.d.ts.map +1 -0
  248. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.js +63 -0
  249. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.js.map +1 -0
  250. package/packages/pi-coding-agent/package.json +1 -1
  251. package/packages/pi-coding-agent/src/core/auth-storage.test.ts +38 -0
  252. package/packages/pi-coding-agent/src/core/auth-storage.ts +1 -1
  253. package/packages/pi-coding-agent/src/core/model-resolver.ts +26 -69
  254. package/packages/pi-coding-agent/src/core/sdk.ts +10 -0
  255. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-execution.test.ts +72 -0
  256. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +84 -12
  257. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.test.ts +71 -0
  258. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +23 -9
  259. package/packages/pi-tui/dist/components/__tests__/editor.test.js +12 -0
  260. package/packages/pi-tui/dist/components/__tests__/editor.test.js.map +1 -1
  261. package/packages/pi-tui/dist/components/__tests__/input.test.js +12 -0
  262. package/packages/pi-tui/dist/components/__tests__/input.test.js.map +1 -1
  263. package/packages/pi-tui/dist/keys.d.ts.map +1 -1
  264. package/packages/pi-tui/dist/keys.js +27 -0
  265. package/packages/pi-tui/dist/keys.js.map +1 -1
  266. package/packages/pi-tui/src/components/__tests__/editor.test.ts +18 -0
  267. package/packages/pi-tui/src/components/__tests__/input.test.ts +18 -0
  268. package/packages/pi-tui/src/keys.ts +32 -0
  269. package/pkg/package.json +1 -1
  270. package/src/resources/extensions/async-jobs/await-tool.test.ts +40 -7
  271. package/src/resources/extensions/async-jobs/await-tool.ts +7 -4
  272. package/src/resources/extensions/async-jobs/job-manager.ts +33 -3
  273. package/src/resources/extensions/claude-code-cli/partial-builder.ts +45 -12
  274. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +49 -24
  275. package/src/resources/extensions/claude-code-cli/tests/partial-builder.test.ts +91 -2
  276. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +112 -0
  277. package/src/resources/extensions/gsd/auto/loop.ts +89 -1
  278. package/src/resources/extensions/gsd/auto-dispatch.ts +5 -0
  279. package/src/resources/extensions/gsd/auto-post-unit.ts +7 -0
  280. package/src/resources/extensions/gsd/auto-prompts.ts +9 -3
  281. package/src/resources/extensions/gsd/auto-recovery.ts +10 -0
  282. package/src/resources/extensions/gsd/auto.ts +30 -20
  283. package/src/resources/extensions/gsd/bootstrap/crash-log.ts +32 -0
  284. package/src/resources/extensions/gsd/bootstrap/register-extension.ts +19 -7
  285. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +8 -10
  286. package/src/resources/extensions/gsd/bootstrap/system-context.ts +8 -1
  287. package/src/resources/extensions/gsd/commands-handlers.ts +5 -1
  288. package/src/resources/extensions/gsd/context-injector.ts +1 -1
  289. package/src/resources/extensions/gsd/crash-recovery.ts +59 -0
  290. package/src/resources/extensions/gsd/custom-workflow-engine.ts +4 -8
  291. package/src/resources/extensions/gsd/definition-io.ts +18 -0
  292. package/src/resources/extensions/gsd/dispatch-guard.ts +5 -0
  293. package/src/resources/extensions/gsd/doctor-runtime-checks.ts +6 -3
  294. package/src/resources/extensions/gsd/git-service.ts +11 -8
  295. package/src/resources/extensions/gsd/gitignore.ts +12 -6
  296. package/src/resources/extensions/gsd/gsd-db.ts +106 -8
  297. package/src/resources/extensions/gsd/key-manager.ts +2 -0
  298. package/src/resources/extensions/gsd/milestone-actions.ts +19 -1
  299. package/src/resources/extensions/gsd/preferences-skills.ts +2 -36
  300. package/src/resources/extensions/gsd/preferences-types.ts +16 -0
  301. package/src/resources/extensions/gsd/preferences.ts +19 -6
  302. package/src/resources/extensions/gsd/prompt-loader.ts +6 -1
  303. package/src/resources/extensions/gsd/prompts/discuss.md +122 -13
  304. package/src/resources/extensions/gsd/prompts/system.md +1 -1
  305. package/src/resources/extensions/gsd/state.ts +20 -0
  306. package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +27 -0
  307. package/src/resources/extensions/gsd/tests/block-db-writes.test.ts +63 -0
  308. package/src/resources/extensions/gsd/tests/crash-handler-secondary.test.ts +235 -0
  309. package/src/resources/extensions/gsd/tests/definition-io.test.ts +57 -0
  310. package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +26 -0
  311. package/src/resources/extensions/gsd/tests/doctor-heal-fixable-warnings.test.ts +14 -0
  312. package/src/resources/extensions/gsd/tests/false-degraded-mode-warning.test.ts +104 -0
  313. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +165 -5
  314. package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +8 -6
  315. package/src/resources/extensions/gsd/tests/key-manager.test.ts +63 -0
  316. package/src/resources/extensions/gsd/tests/memory-pressure-stuck-state.test.ts +54 -0
  317. package/src/resources/extensions/gsd/tests/park-milestone.test.ts +64 -0
  318. package/src/resources/extensions/gsd/tests/plan-milestone-artifact-verification.test.ts +62 -0
  319. package/src/resources/extensions/gsd/tests/post-unit-state-rebuild.test.ts +34 -0
  320. package/src/resources/extensions/gsd/tests/preferences-formatting.test.ts +87 -0
  321. package/src/resources/extensions/gsd/tests/preferences.test.ts +53 -0
  322. package/src/resources/extensions/gsd/tests/projection-regression.test.ts +96 -1
  323. package/src/resources/extensions/gsd/tests/prompt-loader-working-directory.test.ts +19 -0
  324. package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +97 -0
  325. package/src/resources/extensions/gsd/tests/stale-slice-rows.test.ts +41 -0
  326. package/src/resources/extensions/gsd/tests/subagent-model-dispatch.test.ts +267 -0
  327. package/src/resources/extensions/gsd/workflow-projections.ts +8 -0
  328. package/src/resources/extensions/gsd/worktree-manager.ts +29 -3
  329. package/src/resources/extensions/gsd/write-intercept.ts +10 -1
  330. package/src/resources/extensions/ollama/index.ts +4 -5
  331. package/src/resources/extensions/ollama/ollama-client.ts +35 -6
  332. package/src/resources/extensions/ollama/ollama-discovery.ts +37 -6
  333. package/src/resources/extensions/ollama/tests/ollama-discovery.test.ts +54 -0
  334. package/dist/resources/extensions/gsd/auto-observability.js +0 -54
  335. package/dist/resources/extensions/gsd/file-watcher.js +0 -80
  336. package/dist/resources/extensions/gsd/rtk-status.js +0 -43
  337. package/dist/web/standalone/.next/static/chunks/app/page-7115e62689b5fd84.js +0 -1
  338. package/dist/web/standalone/.next/static/chunks/main-app-d3d4c336195465f9.js +0 -1
  339. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-ab5a8926e07ec673.js +0 -1
  340. package/src/resources/extensions/gsd/auto-observability.ts +0 -72
  341. package/src/resources/extensions/gsd/file-watcher.ts +0 -100
  342. package/src/resources/extensions/gsd/rtk-status.ts +0 -53
  343. /package/dist/web/standalone/.next/static/{Y0I7CjXJl-tWoV__KidV4 → jNiH700EcljeLnbQ2_RCv}/_buildManifest.js +0 -0
  344. /package/dist/web/standalone/.next/static/{Y0I7CjXJl-tWoV__KidV4 → jNiH700EcljeLnbQ2_RCv}/_ssgManifest.js +0 -0
@@ -1712,7 +1712,7 @@ export async function buildReassessRoadmapPrompt(mid, midTitle, completedSliceId
1712
1712
  });
1713
1713
  }
1714
1714
  // ─── Reactive Execute Prompt ──────────────────────────────────────────────
1715
- export async function buildReactiveExecutePrompt(mid, midTitle, sid, sTitle, readyTaskIds, base) {
1715
+ export async function buildReactiveExecutePrompt(mid, midTitle, sid, sTitle, readyTaskIds, base, subagentModel) {
1716
1716
  const { loadSliceTaskIO, deriveTaskGraph, graphMetrics } = await import("./reactive-graph.js");
1717
1717
  // Build graph for context
1718
1718
  const taskIO = await loadSliceTaskIO(base, mid, sid);
@@ -1744,10 +1744,11 @@ export async function buildReactiveExecutePrompt(mid, midTitle, sid, sTitle, rea
1744
1744
  const depPaths = await getDependencyTaskSummaryPaths(mid, sid, tid, node?.dependsOn ?? [], base);
1745
1745
  // Build a full execute-task prompt with dependency-based carry-forward
1746
1746
  const taskPrompt = await buildExecuteTaskPrompt(mid, sid, sTitle, tid, tTitle, base, { carryForwardPaths: depPaths });
1747
+ const modelSuffix = subagentModel ? ` with model: "${subagentModel}"` : "";
1747
1748
  subagentSections.push([
1748
1749
  `### ${tid}: ${tTitle}`,
1749
1750
  "",
1750
- "Use this as the prompt for a `subagent` call:",
1751
+ `Use this as the prompt for a \`subagent\` call${modelSuffix}:`,
1751
1752
  "",
1752
1753
  "```",
1753
1754
  taskPrompt,
@@ -1806,15 +1807,16 @@ function renderGatesToCloseBlock(gates, opts) {
1806
1807
  }
1807
1808
  return lines.join("\n").trimEnd();
1808
1809
  }
1809
- export async function buildParallelResearchSlicesPrompt(mid, midTitle, slices, basePath) {
1810
+ export async function buildParallelResearchSlicesPrompt(mid, midTitle, slices, basePath, subagentModel) {
1810
1811
  // Build individual research-slice prompts for each slice
1811
1812
  const subagentSections = [];
1813
+ const modelSuffix = subagentModel ? ` with model: "${subagentModel}"` : "";
1812
1814
  for (const slice of slices) {
1813
1815
  const slicePrompt = await buildResearchSlicePrompt(mid, midTitle, slice.id, slice.title, basePath);
1814
1816
  subagentSections.push([
1815
1817
  `### ${slice.id}: ${slice.title}`,
1816
1818
  "",
1817
- "Use this as the prompt for a `subagent` call (agent: `gsd-executor` or the default agent):",
1819
+ `Use this as the prompt for a \`subagent\` call${modelSuffix} (agent: \`gsd-executor\` or the default agent):`,
1818
1820
  "",
1819
1821
  "```",
1820
1822
  slicePrompt,
@@ -1829,7 +1831,7 @@ export async function buildParallelResearchSlicesPrompt(mid, midTitle, slices, b
1829
1831
  subagentPrompts: subagentSections.join("\n\n---\n\n"),
1830
1832
  });
1831
1833
  }
1832
- export async function buildGateEvaluatePrompt(mid, midTitle, sid, sTitle, base) {
1834
+ export async function buildGateEvaluatePrompt(mid, midTitle, sid, sTitle, base, subagentModel) {
1833
1835
  // Pull only the gates this turn actually owns (Q3/Q4). Filter via the
1834
1836
  // registry so that scope:"slice" gates owned by other turns (Q8) can't
1835
1837
  // leak into this prompt and can't block dispatch via silent skip.
@@ -1873,10 +1875,11 @@ export async function buildGateEvaluatePrompt(mid, midTitle, sid, sTitle, base)
1873
1875
  "- `rationale`: one-sentence justification",
1874
1876
  "- `findings`: detailed markdown findings (or empty if omitted)",
1875
1877
  ].join("\n");
1878
+ const modelSuffix = subagentModel ? ` with model: "${subagentModel}"` : "";
1876
1879
  subagentSections.push([
1877
1880
  `### ${def.id}: ${def.question}`,
1878
1881
  "",
1879
- "Use this as the prompt for a `subagent` call:",
1882
+ `Use this as the prompt for a \`subagent\` call${modelSuffix}:`,
1880
1883
  "",
1881
1884
  "```",
1882
1885
  subPrompt,
@@ -224,6 +224,17 @@ export function verifyExpectedArtifact(unitType, unitId, base) {
224
224
  if (!isValidationTerminal(validationContent))
225
225
  return false;
226
226
  }
227
+ if (unitType === "plan-milestone") {
228
+ try {
229
+ const roadmap = parseLegacyRoadmap(readFileSync(absPath, "utf-8"));
230
+ if (roadmap.slices.length === 0)
231
+ return false;
232
+ }
233
+ catch (err) {
234
+ logWarning("recovery", `plan-milestone roadmap verification failed: ${err instanceof Error ? err.message : String(err)}`);
235
+ return false;
236
+ }
237
+ }
227
238
  // plan-slice must produce a plan with actual task entries, not just a scaffold.
228
239
  // The plan file may exist from a prior discussion/context step with only headings
229
240
  // but no tasks. Without this check the artifact is considered "complete" and the
@@ -19,7 +19,7 @@ import { gsdRoot, resolveMilestoneFile, resolveMilestonePath, resolveDir, milest
19
19
  import { invalidateAllCaches } from "./cache.js";
20
20
  import { clearActivityLogState } from "./activity-log.js";
21
21
  import { synthesizeCrashRecovery, getDeepDiagnostic, readActiveMilestoneId, } from "./session-forensics.js";
22
- import { writeLock, clearLock, readCrashLock, isLockProcessAlive, formatCrashInfo, } from "./crash-recovery.js";
22
+ import { writeLock, clearLock, readCrashLock, isLockProcessAlive, formatCrashInfo, emitCrashRecoveredUnitEnd, } from "./crash-recovery.js";
23
23
  import { acquireSessionLock, getSessionLockStatus, releaseSessionLock, updateSessionLock, } from "./session-lock.js";
24
24
  import { resolveAutoSupervisorConfig, loadEffectiveGSDPreferences, getIsolationMode, } from "./preferences.js";
25
25
  import { sendDesktopNotification } from "./notifications.js";
@@ -425,9 +425,13 @@ function cleanupAfterLoopExit(ctx) {
425
425
  /* best-effort — mirror stopAuto cleanup */
426
426
  logWarning("session", `lock cleanup failed: ${err instanceof Error ? err.message : String(err)}`, { file: "auto.ts" });
427
427
  }
428
- ctx.ui.setStatus("gsd-auto", undefined);
429
- ctx.ui.setWidget("gsd-progress", undefined);
430
- ctx.ui.setFooter(undefined);
428
+ // A transient provider-error pause intentionally leaves the paused badge
429
+ // visible so the user still has a resumable auto-mode signal on screen.
430
+ if (!s.paused) {
431
+ ctx.ui.setStatus("gsd-auto", undefined);
432
+ ctx.ui.setWidget("gsd-progress", undefined);
433
+ ctx.ui.setFooter(undefined);
434
+ }
431
435
  // Restore CWD out of worktree back to original project root
432
436
  if (s.originalBasePath) {
433
437
  s.basePath = s.originalBasePath;
@@ -529,7 +533,22 @@ export async function stopAuto(ctx, pi, reason) {
529
533
  catch (e) {
530
534
  debugLog("stop-cleanup-worktree", { error: e instanceof Error ? e.message : String(e) });
531
535
  }
532
- // ── Step 5: DB cleanup ──
536
+ // ── Step 5: Rebuild state while DB is still open (#3599) ──
537
+ // rebuildState() calls deriveState() which needs the DB for authoritative
538
+ // state. Previously this ran after closeDatabase(), forcing a filesystem
539
+ // fallback that could disagree with the DB-backed dispatch decisions —
540
+ // a split-brain where dispatch says "blocked" but STATE.md shows work.
541
+ if (s.basePath) {
542
+ try {
543
+ await rebuildState(s.basePath);
544
+ }
545
+ catch (e) {
546
+ debugLog("stop-rebuild-state-failed", {
547
+ error: e instanceof Error ? e.message : String(e),
548
+ });
549
+ }
550
+ }
551
+ // ── Step 6: DB cleanup ──
533
552
  if (isDbAvailable()) {
534
553
  try {
535
554
  const { closeDatabase } = await import("./gsd-db.js");
@@ -541,7 +560,7 @@ export async function stopAuto(ctx, pi, reason) {
541
560
  });
542
561
  }
543
562
  }
544
- // ── Step 6: Restore basePath and chdir ──
563
+ // ── Step 7: Restore basePath and chdir ──
545
564
  try {
546
565
  if (s.originalBasePath) {
547
566
  s.basePath = s.originalBasePath;
@@ -557,7 +576,7 @@ export async function stopAuto(ctx, pi, reason) {
557
576
  catch (e) {
558
577
  debugLog("stop-cleanup-basepath", { error: e instanceof Error ? e.message : String(e) });
559
578
  }
560
- // ── Step 7: Ledger notification ──
579
+ // ── Step 8: Ledger notification ──
561
580
  try {
562
581
  const ledger = getLedger();
563
582
  if (ledger && ledger.units.length > 0) {
@@ -571,17 +590,6 @@ export async function stopAuto(ctx, pi, reason) {
571
590
  catch (e) {
572
591
  debugLog("stop-cleanup-ledger", { error: e instanceof Error ? e.message : String(e) });
573
592
  }
574
- // ── Step 8: Rebuild state ──
575
- if (s.basePath) {
576
- try {
577
- await rebuildState(s.basePath);
578
- }
579
- catch (e) {
580
- debugLog("stop-rebuild-state-failed", {
581
- error: e instanceof Error ? e.message : String(e),
582
- });
583
- }
584
- }
585
593
  // ── Step 9: Cmux sidebar / event log ──
586
594
  try {
587
595
  clearCmuxSidebar(loadedPreferences);
@@ -1006,6 +1014,10 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
1006
1014
  s.stepMode = requestedStepMode;
1007
1015
  }
1008
1016
  if (freshStartAssessment.lock) {
1017
+ // Emit a synthetic unit-end for any unit-start that has no closing event.
1018
+ // This closes the journal gap reported in #3348 where the worker wrote side
1019
+ // effects (SUMMARY.md, DB updates) but died before emitting unit-end.
1020
+ emitCrashRecoveredUnitEnd(base, freshStartAssessment.lock);
1009
1021
  clearLock(base);
1010
1022
  }
1011
1023
  if (!s.paused) {
@@ -1294,8 +1306,6 @@ export async function dispatchHookUnit(ctx, pi, hookName, triggerUnitType, trigg
1294
1306
  pi.sendMessage({ customType: "gsd-auto", content: hookPrompt, display: true }, { triggerTurn: true });
1295
1307
  return true;
1296
1308
  }
1297
- // Direct phase dispatch → auto-direct-dispatch.ts
1298
- export { dispatchDirectPhase } from "./auto-direct-dispatch.js";
1299
1309
  // Re-export recovery functions for external consumers
1300
1310
  export { buildLoopRemediationSteps, } from "./auto-recovery.js";
1301
1311
  export { resolveExpectedArtifactPath } from "./auto-artifact-paths.js";
@@ -0,0 +1,31 @@
1
+ /**
2
+ * crash-log.ts — Write crash diagnostics to ~/.gsd/crash/<timestamp>.log
3
+ *
4
+ * Zero cross-dependencies: only uses Node.js built-ins so it can be imported
5
+ * safely from uncaughtException / unhandledRejection handlers and from tests
6
+ * without pulling in the full extension dependency tree.
7
+ */
8
+ import { appendFileSync, mkdirSync } from "node:fs";
9
+ import { homedir } from "node:os";
10
+ import { join } from "node:path";
11
+ /**
12
+ * Write a crash log to ~/.gsd/crash/<timestamp>.log (or $GSD_HOME/crash/).
13
+ * Never throws — must be safe to call from any error handler.
14
+ */
15
+ export function writeCrashLog(err, source) {
16
+ try {
17
+ const crashDir = join(process.env.GSD_HOME ?? join(homedir(), ".gsd"), "crash");
18
+ mkdirSync(crashDir, { recursive: true });
19
+ const ts = new Date().toISOString().replace(/[:.]/g, "-");
20
+ const logPath = join(crashDir, `${ts}.log`);
21
+ const lines = [
22
+ `[gsd] ${source}: ${err.message}`,
23
+ `timestamp: ${new Date().toISOString()}`,
24
+ `pid: ${process.pid}`,
25
+ err.stack ?? "(no stack trace available)",
26
+ "",
27
+ ];
28
+ appendFileSync(logPath, lines.join("\n"));
29
+ }
30
+ catch { /* never throw from crash handler */ }
31
+ }
@@ -8,6 +8,8 @@ import { registerJournalTools } from "./journal-tools.js";
8
8
  import { registerQueryTools } from "./query-tools.js";
9
9
  import { registerHooks } from "./register-hooks.js";
10
10
  import { registerShortcuts } from "./register-shortcuts.js";
11
+ import { writeCrashLog } from "./crash-log.js";
12
+ export { writeCrashLog } from "./crash-log.js";
11
13
  export function handleRecoverableExtensionProcessError(err) {
12
14
  if (err.code === "EPIPE") {
13
15
  process.exit(0);
@@ -28,17 +30,26 @@ export function handleRecoverableExtensionProcessError(err) {
28
30
  function installEpipeGuard() {
29
31
  if (!process.listeners("uncaughtException").some((listener) => listener.name === "_gsdEpipeGuard")) {
30
32
  const _gsdEpipeGuard = (err) => {
31
- if (handleRecoverableExtensionProcessError(err)) {
33
+ if (handleRecoverableExtensionProcessError(err))
32
34
  return;
33
- }
34
- // Log unhandled errors instead of re-throwing throwing inside an
35
- // uncaughtException handler is a fatal double-fault in Node.js (#3163).
36
- process.stderr.write(`[gsd] uncaught extension error (non-fatal): ${err.message}\n`);
37
- if (err.stack)
38
- process.stderr.write(`${err.stack}\n`);
35
+ // Write crash log and exit cleanly for unrecoverable errors.
36
+ // Logging and continuing was the original double-fault fix (#3163), but
37
+ // continuing in an indeterminate state is worse than a clean exit (#3348).
38
+ writeCrashLog(err, "uncaughtException");
39
+ process.exit(1);
39
40
  };
40
41
  process.on("uncaughtException", _gsdEpipeGuard);
41
42
  }
43
+ if (!process.listeners("unhandledRejection").some((listener) => listener.name === "_gsdRejectionGuard")) {
44
+ const _gsdRejectionGuard = (reason, _promise) => {
45
+ const err = reason instanceof Error ? reason : new Error(String(reason));
46
+ if (handleRecoverableExtensionProcessError(err))
47
+ return;
48
+ writeCrashLog(err, "unhandledRejection");
49
+ process.exit(1);
50
+ };
51
+ process.on("unhandledRejection", _gsdRejectionGuard);
52
+ }
42
53
  }
43
54
  export function registerGsdExtension(pi) {
44
55
  registerGSDCommand(pi);
@@ -172,14 +172,10 @@ export function registerHooks(pi) {
172
172
  // Only gate-shaped ask_user_questions calls should block execution.
173
173
  // The gate stays pending until the user selects the approval option.
174
174
  if (event.toolName === "ask_user_questions") {
175
- const milestoneId = getDiscussionMilestoneId(discussionBasePath);
176
- const inDiscussion = milestoneId !== null || isQueuePhaseActive();
177
- if (inDiscussion) {
178
- const questions = event.input?.questions ?? [];
179
- const questionId = questions.find((question) => typeof question?.id === "string" && isGateQuestionId(question.id))?.id;
180
- if (typeof questionId === "string") {
181
- setPendingGate(questionId);
182
- }
175
+ const questions = event.input?.questions ?? [];
176
+ const questionId = questions.find((question) => typeof question?.id === "string" && isGateQuestionId(question.id))?.id;
177
+ if (typeof questionId === "string") {
178
+ setPendingGate(questionId);
183
179
  }
184
180
  }
185
181
  // ── Discussion gate enforcement: block tool calls while gate is pending ──
@@ -261,8 +257,6 @@ export function registerHooks(pi) {
261
257
  return;
262
258
  const milestoneId = getDiscussionMilestoneId(process.cwd());
263
259
  const queueActive = isQueuePhaseActive();
264
- if (!milestoneId && !queueActive)
265
- return;
266
260
  const details = event.details;
267
261
  // ── Discussion gate enforcement: handle gate question responses ──
268
262
  // If the result is cancelled or has no response, the pending gate stays active
@@ -293,12 +287,16 @@ export function registerHooks(pi) {
293
287
  // Only unlock the gate if the user selected the first option (confirmation).
294
288
  // Cross-references against the question's defined options to reject free-form "Other" text.
295
289
  const answer = details.response?.answers?.[question.id];
290
+ const inferredMilestoneId = extractDepthVerificationMilestoneId(question.id) ?? milestoneId;
296
291
  if (isDepthConfirmationAnswer(answer?.selected, question.options)) {
297
- markDepthVerified(extractDepthVerificationMilestoneId(question.id) ?? milestoneId);
292
+ markDepthVerified(inferredMilestoneId);
293
+ clearPendingGate();
298
294
  }
299
295
  break;
300
296
  }
301
297
  }
298
+ if (!milestoneId && !queueActive)
299
+ return;
302
300
  if (!milestoneId)
303
301
  return;
304
302
  const basePath = process.cwd();
@@ -6,6 +6,7 @@ import { debugTime } from "../debug-logger.js";
6
6
  import { loadPrompt, getTemplatesDir } from "../prompt-loader.js";
7
7
  import { readForensicsMarker } from "../forensics.js";
8
8
  import { resolveAllSkillReferences, renderPreferencesForSystemPrompt, loadEffectiveGSDPreferences } from "../preferences.js";
9
+ import { resolveModelWithFallbacksForUnit } from "../preferences-models.js";
9
10
  import { resolveSkillReference } from "../preferences-skills.js";
10
11
  import { resolveGsdRootFile, resolveSliceFile, resolveSlicePath, resolveTaskFile, resolveTaskFiles, resolveTasksDir, relSliceFile, relSlicePath, relTaskFile } from "../paths.js";
11
12
  import { ensureCodebaseMapFresh, readCodebaseMap } from "../codebase-generator.js";
@@ -147,7 +148,11 @@ export async function buildBeforeAgentStartResult(event, ctx) {
147
148
  // Re-inject forensics context on follow-up turns (#2941)
148
149
  const forensicsInjection = !injection ? buildForensicsContextInjection(process.cwd(), event.prompt) : null;
149
150
  const worktreeBlock = buildWorktreeContextBlock();
150
- const fullSystem = `${event.systemPrompt}\n\n[SYSTEM CONTEXT — GSD]\n\n${systemContent}${preferenceBlock}${knowledgeBlock}${codebaseBlock}${memoryBlock}${newSkillsBlock}${worktreeBlock}`;
151
+ const subagentModelConfig = resolveModelWithFallbacksForUnit("subagent");
152
+ const subagentModelBlock = subagentModelConfig
153
+ ? `\n\n## Subagent Model\n\nWhen spawning subagents via the \`subagent\` tool, always pass \`model: "${subagentModelConfig.primary}"\` in the tool call parameters. Never omit this — always specify it explicitly.`
154
+ : "";
155
+ const fullSystem = `${event.systemPrompt}\n\n[SYSTEM CONTEXT — GSD]\n\n${systemContent}${preferenceBlock}${knowledgeBlock}${codebaseBlock}${memoryBlock}${newSkillsBlock}${worktreeBlock}${subagentModelBlock}`;
151
156
  stopContextTimer({
152
157
  systemPromptSize: fullSystem.length,
153
158
  injectionSize: injection?.length ?? forensicsInjection?.length ?? 0,
@@ -61,6 +61,9 @@ export function parseDoctorArgs(args) {
61
61
  const requestedScope = mode === "doctor" ? parts[0] : parts[1];
62
62
  return { jsonMode, dryRun, fixFlag, includeBuild, includeTests, mode, requestedScope };
63
63
  }
64
+ export function isDoctorHealActionable(issue) {
65
+ return issue.fixable && issue.severity !== "info";
66
+ }
64
67
  export async function handleDoctor(args, ctx, pi) {
65
68
  const { jsonMode, dryRun, fixFlag, includeBuild, includeTests, mode, requestedScope } = parseDoctorArgs(args);
66
69
  const scope = await selectDoctorScope(projectRoot(), requestedScope);
@@ -88,7 +91,7 @@ export async function handleDoctor(args, ctx, pi) {
88
91
  scope: effectiveScope,
89
92
  includeWarnings: true,
90
93
  });
91
- const actionable = unresolved.filter(issue => issue.severity === "error");
94
+ const actionable = unresolved.filter(isDoctorHealActionable);
92
95
  if (actionable.length === 0) {
93
96
  ctx.ui.notify("Doctor heal found nothing actionable to hand off to the LLM.", "info");
94
97
  return;
@@ -14,7 +14,7 @@
14
14
  */
15
15
  import { readFileSync, existsSync } from "node:fs";
16
16
  import { resolve, sep } from "node:path";
17
- import { readFrozenDefinition } from "./custom-workflow-engine.js";
17
+ import { readFrozenDefinition } from "./definition-io.js";
18
18
  /** Maximum characters per artifact to prevent context window blowout. */
19
19
  const MAX_CONTEXT_CHARS = 10_000;
20
20
  /**
@@ -14,6 +14,7 @@ import { join } from "node:path";
14
14
  import { gsdRoot } from "./paths.js";
15
15
  import { atomicWriteSync } from "./atomic-write.js";
16
16
  import { effectiveLockFile } from "./session-lock.js";
17
+ import { emitJournalEvent, queryJournal } from "./journal.js";
17
18
  function lockPath(basePath) {
18
19
  return join(gsdRoot(basePath), effectiveLockFile());
19
20
  }
@@ -110,3 +111,53 @@ export function formatCrashInfo(lock) {
110
111
  }
111
112
  return lines.join("\n");
112
113
  }
114
+ /**
115
+ * Emit a synthetic unit-end event for a unit that crashed without emitting its own.
116
+ *
117
+ * Queries the journal to find the most recent unit-start for the crashed unit.
118
+ * If a matching unit-end already exists (e.g. the hard timeout fired), this is a
119
+ * no-op. Called during crash recovery, before clearing the stale lock.
120
+ *
121
+ * Addresses the gap reported in #3348 where `unit-start` was emitted but no
122
+ * `unit-end` followed — side effects landed but the worker died before closeout.
123
+ */
124
+ export function emitCrashRecoveredUnitEnd(basePath, lock) {
125
+ // Skip bootstrap / starting pseudo-units — they have no meaningful unit-start event.
126
+ if (!lock.unitType || !lock.unitId || lock.unitType === "starting")
127
+ return;
128
+ try {
129
+ const all = queryJournal(basePath);
130
+ // Find the most recent unit-start for this unitId
131
+ const starts = all.filter((e) => e.eventType === "unit-start" && e.data?.unitId === lock.unitId);
132
+ if (starts.length === 0)
133
+ return;
134
+ const lastStart = starts[starts.length - 1];
135
+ // Check if a unit-end was already emitted (e.g. hard timeout fired after the crash)
136
+ const alreadyClosed = all.some((e) => e.eventType === "unit-end" &&
137
+ e.data?.unitId === lock.unitId &&
138
+ e.causedBy?.flowId === lastStart.flowId &&
139
+ e.causedBy?.seq === lastStart.seq);
140
+ if (alreadyClosed)
141
+ return;
142
+ // Find the highest seq in this flow for monotonic ordering
143
+ const maxSeq = all
144
+ .filter((e) => e.flowId === lastStart.flowId)
145
+ .reduce((max, e) => Math.max(max, e.seq), lastStart.seq);
146
+ emitJournalEvent(basePath, {
147
+ ts: new Date().toISOString(),
148
+ flowId: lastStart.flowId,
149
+ seq: maxSeq + 1,
150
+ eventType: "unit-end",
151
+ data: {
152
+ unitType: lock.unitType,
153
+ unitId: lock.unitId,
154
+ status: "crash-recovered",
155
+ artifactVerified: false,
156
+ },
157
+ causedBy: { flowId: lastStart.flowId, seq: lastStart.seq },
158
+ });
159
+ }
160
+ catch {
161
+ // Never throw from crash recovery path — journal failure must not block recovery
162
+ }
163
+ }
@@ -13,17 +13,13 @@
13
13
  */
14
14
  import { readFileSync } from "node:fs";
15
15
  import { join } from "node:path";
16
- import { parse } from "yaml";
17
16
  import { readGraph, writeGraph, getNextPendingStep, markStepComplete, expandIteration, } from "./graph.js";
18
17
  import { injectContext } from "./context-injector.js";
18
+ import { readFrozenDefinition } from "./definition-io.js";
19
19
  import { parseUnitId } from "./unit-id.js";
20
20
  import { withFileLock } from "./file-lock.js";
21
- /** Read and parse the frozen DEFINITION.yaml from a run directory. */
22
- export function readFrozenDefinition(runDir) {
23
- const defPath = join(runDir, "DEFINITION.yaml");
24
- const raw = readFileSync(defPath, "utf-8");
25
- return parse(raw, { schema: "core" });
26
- }
21
+ // Re-export for downstream consumers
22
+ export { readFrozenDefinition } from "./definition-io.js";
27
23
  export class CustomWorkflowEngine {
28
24
  engineId = "custom";
29
25
  runDir;
@@ -0,0 +1,15 @@
1
+ /**
2
+ * definition-io.ts — Read frozen DEFINITION.yaml from a run directory.
3
+ *
4
+ * Extracted from custom-workflow-engine.ts to break the circular dependency
5
+ * between context-injector.ts and custom-workflow-engine.ts.
6
+ */
7
+ import { readFileSync } from "node:fs";
8
+ import { join } from "node:path";
9
+ import { parse } from "yaml";
10
+ /** Read and parse the frozen DEFINITION.yaml from a run directory. */
11
+ export function readFrozenDefinition(runDir) {
12
+ const defPath = join(runDir, "DEFINITION.yaml");
13
+ const raw = readFileSync(defPath, "utf-8");
14
+ return parse(raw, { schema: "core" });
15
+ }
@@ -102,6 +102,10 @@ export function getPriorSliceCompletionBlocker(base, _mainBranch, unitType, unit
102
102
  }
103
103
  }
104
104
  else {
105
+ const milestoneUsesExplicitDeps = slices.some((slice) => slice.depends.length > 0);
106
+ if (milestoneUsesExplicitDeps) {
107
+ return null;
108
+ }
105
109
  // Positional fallback is only a heuristic for legacy slices with no
106
110
  // declared dependencies. Skip any earlier slice that depends on the
107
111
  // target, directly or transitively, or we can deadlock a valid zero-dep
@@ -277,13 +277,16 @@ export async function checkRuntimeHealth(basePath, issues, fixesApplied, shouldF
277
277
  if (existsSync(gitignorePath) && nativeIsRepo(basePath)) {
278
278
  const content = readFileSync(gitignorePath, "utf-8");
279
279
  const existingLines = new Set(content.split("\n").map(l => l.trim()).filter(l => l && !l.startsWith("#")));
280
- // Check for critical runtime patterns that must be present
280
+ // Check for critical runtime patterns that must be present.
281
+ // NOTE: GSD_RUNTIME_PATTERNS in gitignore.ts is the canonical source of truth.
282
+ // This is a minimal subset for the doctor check.
281
283
  const criticalPatterns = [
282
284
  ".gsd/activity/",
283
285
  ".gsd/runtime/",
284
286
  ".gsd/auto.lock",
285
- ".gsd/gsd.db",
286
- ".gsd/completed-units.json",
287
+ ".gsd/gsd.db*",
288
+ ".gsd/completed-units*.json",
289
+ ".gsd/event-log.jsonl",
287
290
  ];
288
291
  // If blanket .gsd/ or .gsd is present, all patterns are covered
289
292
  const hasBlanketIgnore = existingLines.has(".gsd/") || existingLines.has(".gsd");
@@ -78,22 +78,25 @@ export class MergeConflictError extends GSDError {
78
78
  /**
79
79
  * GSD runtime paths that should be excluded from smart staging.
80
80
  * These are transient/generated artifacts that should never be committed.
81
- * Matches the union of SKIP_PATHS + SKIP_EXACT in worktree-manager.ts
82
- * and the first 7 entries in gitignore.ts BASELINE_PATTERNS.
81
+ *
82
+ * NOTE: GSD_RUNTIME_PATTERNS in gitignore.ts is the canonical source of truth.
83
+ * This array must stay synchronized with it.
83
84
  */
84
85
  export const RUNTIME_EXCLUSION_PATHS = [
85
86
  ".gsd/activity/",
87
+ ".gsd/forensics/",
86
88
  ".gsd/runtime/",
87
89
  ".gsd/worktrees/",
90
+ ".gsd/parallel/",
88
91
  ".gsd/auto.lock",
89
92
  ".gsd/metrics.json",
90
- ".gsd/completed-units.json",
93
+ ".gsd/completed-units*.json", // covers completed-units.json and archived completed-units-{MID}.json
94
+ ".gsd/state-manifest.json",
91
95
  ".gsd/STATE.md",
92
- ".gsd/gsd.db",
93
- ".gsd/gsd.db-shm", // SQLite WAL sidecar — always created alongside gsd.db (#2296)
94
- ".gsd/gsd.db-wal", // SQLite WAL sidecar — always created alongside gsd.db (#2296)
95
- ".gsd/journal/", // daily-rotated JSONL event journal (#2296)
96
- ".gsd/doctor-history.jsonl", // doctor run history (#2296)
96
+ ".gsd/gsd.db*",
97
+ ".gsd/journal/",
98
+ ".gsd/doctor-history.jsonl",
99
+ ".gsd/event-log.jsonl",
97
100
  ".gsd/DISCUSSION-MANIFEST.json",
98
101
  ];
99
102
  // ─── Integration Branch Metadata ───────────────────────────────────────────
@@ -13,6 +13,12 @@ import { gsdRoot } from "./paths.js";
13
13
  import { GIT_NO_PROMPT_ENV } from "./git-constants.js";
14
14
  /**
15
15
  * GSD runtime patterns for git index cleanup.
16
+ *
17
+ * CANONICAL SOURCE OF TRUTH: This array is the authoritative list of runtime
18
+ * ignore patterns. Other modules (RUNTIME_EXCLUSION_PATHS in git-service.ts,
19
+ * SKIP_* arrays in worktree-manager.ts, criticalPatterns in doctor-runtime-checks.ts)
20
+ * must stay synchronized with this list.
21
+ *
16
22
  * With external state (symlink), these are a no-op in most cases,
17
23
  * but retained for backwards compatibility during migration.
18
24
  */
@@ -24,13 +30,13 @@ const GSD_RUNTIME_PATTERNS = [
24
30
  ".gsd/parallel/",
25
31
  ".gsd/auto.lock",
26
32
  ".gsd/metrics.json",
27
- ".gsd/completed-units.json",
33
+ ".gsd/completed-units*.json", // covers completed-units.json and archived completed-units-{MID}.json
34
+ ".gsd/state-manifest.json",
28
35
  ".gsd/STATE.md",
29
- ".gsd/gsd.db",
30
- ".gsd/gsd.db-shm", // SQLite WAL sidecar — always created alongside gsd.db (#2296)
31
- ".gsd/gsd.db-wal", // SQLite WAL sidecar — always created alongside gsd.db (#2296)
32
- ".gsd/journal/", // daily-rotated JSONL event journal (#2296)
33
- ".gsd/doctor-history.jsonl", // doctor run history (#2296)
36
+ ".gsd/gsd.db*",
37
+ ".gsd/journal/",
38
+ ".gsd/doctor-history.jsonl",
39
+ ".gsd/event-log.jsonl",
34
40
  ".gsd/DISCUSSION-MANIFEST.json",
35
41
  ".gsd/milestones/**/*-CONTINUE.md",
36
42
  ".gsd/milestones/**/continue.md",