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
@@ -18,6 +18,11 @@ import {
18
18
  transaction,
19
19
  _getAdapter,
20
20
  _resetProvider,
21
+ insertMilestone,
22
+ insertSlice,
23
+ insertTask,
24
+ getTask,
25
+ getSliceTasks,
21
26
  } from '../gsd-db.ts';
22
27
 
23
28
  // ═══════════════════════════════════════════════════════════════════════════
@@ -43,6 +48,16 @@ function cleanup(dbPath: string): void {
43
48
  }
44
49
  }
45
50
 
51
+ function withPlatform<T>(platform: NodeJS.Platform, fn: () => T): T {
52
+ const original = process.platform;
53
+ Object.defineProperty(process, 'platform', { value: platform });
54
+ try {
55
+ return fn();
56
+ } finally {
57
+ Object.defineProperty(process, 'platform', { value: original });
58
+ }
59
+ }
60
+
46
61
  // ═══════════════════════════════════════════════════════════════════════════
47
62
  // gsd-db tests
48
63
  // ═══════════════════════════════════════════════════════════════════════════
@@ -279,6 +294,26 @@ describe('gsd-db', () => {
279
294
  cleanup(dbPath);
280
295
  });
281
296
 
297
+ test('gsd-db: mmap stays disabled on darwin file-backed DBs', () => {
298
+ const darwinDbPath = tempDbPath();
299
+ withPlatform('darwin', () => {
300
+ openDatabase(darwinDbPath);
301
+ const adapter = _getAdapter()!;
302
+ const mmap = adapter.prepare('PRAGMA mmap_size').get();
303
+ assert.deepStrictEqual(mmap?.['mmap_size'], 0, 'darwin should leave mmap_size disabled');
304
+ cleanup(darwinDbPath);
305
+ });
306
+
307
+ const linuxDbPath = tempDbPath();
308
+ withPlatform('linux', () => {
309
+ openDatabase(linuxDbPath);
310
+ const adapter = _getAdapter()!;
311
+ const mmap = adapter.prepare('PRAGMA mmap_size').get();
312
+ assert.deepStrictEqual(mmap?.['mmap_size'], 67108864, 'non-darwin should still enable mmap_size');
313
+ cleanup(linuxDbPath);
314
+ });
315
+ });
316
+
282
317
  test('gsd-db: transaction rollback on error', () => {
283
318
  openDatabase(':memory:');
284
319
 
@@ -329,6 +364,79 @@ describe('gsd-db', () => {
329
364
  closeDatabase();
330
365
  });
331
366
 
367
+ test('gsd-db: recreates missing verification evidence dedup index after removing duplicate rows', () => {
368
+ const dbPath = tempDbPath();
369
+ openDatabase(dbPath);
370
+
371
+ let adapter = _getAdapter()!;
372
+ adapter.prepare("INSERT INTO milestones (id, created_at) VALUES (?, '')").run('M001');
373
+ adapter.prepare("INSERT INTO slices (milestone_id, id, created_at) VALUES (?, ?, '')").run('M001', 'S01');
374
+ adapter.prepare("INSERT INTO tasks (milestone_id, slice_id, id) VALUES (?, ?, ?)").run('M001', 'S01', 'T01');
375
+ adapter.exec('DROP INDEX IF EXISTS idx_verification_evidence_dedup');
376
+
377
+ const insertEvidence = adapter.prepare(
378
+ `INSERT INTO verification_evidence (
379
+ task_id, slice_id, milestone_id, command, exit_code, verdict, duration_ms, created_at
380
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
381
+ );
382
+ insertEvidence.run('T01', 'S01', 'M001', 'npm test', 1, 'fail', 125, '2026-04-12T00:00:00.000Z');
383
+ insertEvidence.run('T01', 'S01', 'M001', 'npm test', 1, 'fail', 125, '2026-04-12T00:00:01.000Z');
384
+ insertEvidence.run('T01', 'S01', 'M001', 'npm run lint', 0, 'pass', 90, '2026-04-12T00:00:02.000Z');
385
+
386
+ closeDatabase();
387
+
388
+ assert.equal(openDatabase(dbPath), true, 'openDatabase should repair legacy duplicate evidence rows');
389
+
390
+ adapter = _getAdapter()!;
391
+ const countRow = adapter.prepare(
392
+ `SELECT count(*) as cnt
393
+ FROM verification_evidence
394
+ WHERE task_id = ? AND slice_id = ? AND milestone_id = ? AND command = ? AND verdict = ?`,
395
+ ).get('T01', 'S01', 'M001', 'npm test', 'fail');
396
+ assert.equal(countRow?.['cnt'], 1, 'duplicate verification evidence rows should be deduplicated before index creation');
397
+
398
+ const indexRow = adapter.prepare(
399
+ "SELECT name FROM sqlite_master WHERE type = 'index' AND name = 'idx_verification_evidence_dedup'",
400
+ ).get();
401
+ assert.equal(indexRow?.['name'], 'idx_verification_evidence_dedup', 'dedup index should be recreated on reopen');
402
+
403
+ cleanup(dbPath);
404
+ });
405
+
406
+ test('gsd-db: rowToTask tolerates legacy comma-separated task arrays', () => {
407
+ openDatabase(':memory:');
408
+
409
+ const adapter = _getAdapter()!;
410
+ adapter.prepare("INSERT INTO milestones (id, created_at) VALUES (?, '')").run('M001');
411
+ adapter.prepare("INSERT INTO slices (milestone_id, id, created_at) VALUES (?, ?, '')").run('M001', 'S01');
412
+ adapter.prepare(
413
+ `INSERT INTO tasks (
414
+ milestone_id, slice_id, id, key_files, key_decisions, files, inputs, expected_output
415
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
416
+ ).run(
417
+ 'M001',
418
+ 'S01',
419
+ 'T01',
420
+ '[]',
421
+ '[]',
422
+ 'tests/test_verify.py, config.yaml, configs/roster_2026-05-11.yaml',
423
+ 'tests/test_verify.py',
424
+ 'reports/summary.md, artifacts/output.json',
425
+ );
426
+
427
+ const task = getTask('M001', 'S01', 'T01');
428
+ assert.ok(task, 'task should load successfully from DB');
429
+ assert.deepEqual(task?.files, [
430
+ 'tests/test_verify.py',
431
+ 'config.yaml',
432
+ 'configs/roster_2026-05-11.yaml',
433
+ ]);
434
+ assert.deepEqual(task?.inputs, ['tests/test_verify.py']);
435
+ assert.deepEqual(task?.expected_output, ['reports/summary.md', 'artifacts/output.json']);
436
+
437
+ closeDatabase();
438
+ });
439
+
332
440
  test('gsd-db: query wrappers return null/empty when DB unavailable', () => {
333
441
  // Ensure DB is closed
334
442
  closeDatabase();
@@ -347,15 +455,67 @@ describe('gsd-db', () => {
347
455
  assert.deepStrictEqual(ar, [], 'getActiveRequirements returns [] when DB closed');
348
456
  });
349
457
 
350
- test('gsd-db: wasDbOpenAttempted tracks openDatabase calls', () => {
351
- // wasDbOpenAttempted should return true once openDatabase has been called
352
- // (previous tests in this suite already called openDatabase, so the flag is set)
458
+ test('gsd-db: closeDatabase resets wasDbOpenAttempted after an intentional close', () => {
459
+ openDatabase(':memory:');
353
460
  assert.ok(wasDbOpenAttempted(), 'wasDbOpenAttempted should be true after openDatabase was called');
354
461
 
355
- // Verify the flag persists even after closeDatabase
356
462
  closeDatabase();
357
463
  assert.ok(!isDbAvailable(), 'DB should not be available after close');
358
- assert.ok(wasDbOpenAttempted(), 'wasDbOpenAttempted should remain true after closeDatabase');
464
+ assert.ok(!wasDbOpenAttempted(), 'wasDbOpenAttempted should reset after closeDatabase');
465
+ });
466
+
467
+ test('gsd-db: rowToTask tolerates corrupt comma-separated task arrays', () => {
468
+ openDatabase(':memory:');
469
+ insertMilestone({ id: 'M001', status: 'active' });
470
+ insertSlice({ milestoneId: 'M001', id: 'S01', status: 'active' });
471
+ insertTask({
472
+ milestoneId: 'M001',
473
+ sliceId: 'S01',
474
+ id: 'T01',
475
+ title: 'Recover corrupt arrays',
476
+ planning: {
477
+ description: 'desc',
478
+ estimate: 'small',
479
+ files: ['src/original.ts'],
480
+ verify: 'npm test',
481
+ inputs: ['docs/original.md'],
482
+ expectedOutput: ['dist/original.md'],
483
+ observabilityImpact: '',
484
+ },
485
+ });
486
+
487
+ const adapter = _getAdapter()!;
488
+ adapter.prepare(
489
+ `UPDATE tasks
490
+ SET files = ?, inputs = ?, expected_output = ?, key_files = ?, key_decisions = ?
491
+ WHERE milestone_id = ? AND slice_id = ? AND id = ?`,
492
+ ).run(
493
+ 'src-erf/Models/foo.cs, src-erf/Models/bar.cs',
494
+ 'docs/input-a.md, docs/input-b.md',
495
+ 'dist/out-a.md, dist/out-b.md',
496
+ 'src/resources/extensions/gsd/gsd-db.ts, src/resources/extensions/gsd/state.ts',
497
+ '"decision-1"',
498
+ 'M001',
499
+ 'S01',
500
+ 'T01',
501
+ );
502
+
503
+ const task = getTask('M001', 'S01', 'T01');
504
+ assert.ok(task, 'getTask should still return the corrupt row');
505
+ assert.deepStrictEqual(task!.files, ['src-erf/Models/foo.cs', 'src-erf/Models/bar.cs']);
506
+ assert.deepStrictEqual(task!.inputs, ['docs/input-a.md', 'docs/input-b.md']);
507
+ assert.deepStrictEqual(task!.expected_output, ['dist/out-a.md', 'dist/out-b.md']);
508
+ assert.deepStrictEqual(
509
+ task!.key_files,
510
+ ['src/resources/extensions/gsd/gsd-db.ts', 'src/resources/extensions/gsd/state.ts'],
511
+ );
512
+ assert.deepStrictEqual(task!.key_decisions, ['decision-1']);
513
+
514
+ const sliceTasks = getSliceTasks('M001', 'S01');
515
+ assert.equal(sliceTasks.length, 1, 'getSliceTasks should also survive corrupt rows');
516
+ assert.deepStrictEqual(sliceTasks[0]!.files, task!.files);
517
+
518
+ closeDatabase();
359
519
  });
360
520
 
361
521
  // ─── Final Report ──────────────────────────────────────────────────────────
@@ -248,23 +248,25 @@ describe('git-service', async () => {
248
248
 
249
249
  assert.deepStrictEqual(
250
250
  RUNTIME_EXCLUSION_PATHS.length,
251
- 13,
252
- "exactly 13 runtime exclusion paths"
251
+ 15,
252
+ "exactly 15 runtime exclusion paths"
253
253
  );
254
254
 
255
255
  const expectedPaths = [
256
256
  ".gsd/activity/",
257
+ ".gsd/forensics/",
257
258
  ".gsd/runtime/",
258
259
  ".gsd/worktrees/",
260
+ ".gsd/parallel/",
259
261
  ".gsd/auto.lock",
260
262
  ".gsd/metrics.json",
261
- ".gsd/completed-units.json",
263
+ ".gsd/completed-units*.json",
264
+ ".gsd/state-manifest.json",
262
265
  ".gsd/STATE.md",
263
- ".gsd/gsd.db",
264
- ".gsd/gsd.db-shm",
265
- ".gsd/gsd.db-wal",
266
+ ".gsd/gsd.db*",
266
267
  ".gsd/journal/",
267
268
  ".gsd/doctor-history.jsonl",
269
+ ".gsd/event-log.jsonl",
268
270
  ".gsd/DISCUSSION-MANIFEST.json",
269
271
  ];
270
272
 
@@ -427,3 +427,66 @@ test("formatDoctorFindings shows findings with appropriate icons", () => {
427
427
  assert.ok(output.includes("1 warning"));
428
428
  assert.ok(output.includes("1 fixed"));
429
429
  });
430
+
431
+ // ─── Regression #3891 — alibaba-coding-plan missing from PROVIDER_REGISTRY ───────
432
+ //
433
+ // Before this fix, `alibaba-coding-plan` was not in PROVIDER_REGISTRY, causing
434
+ // `/gsd keys add alibaba-coding-plan` to silently fail (provider not found).
435
+ // alibaba-dashscope is the new standalone provider added in the same PR.
436
+
437
+ test("regression #3891 — alibaba-coding-plan is in PROVIDER_REGISTRY", () => {
438
+ const provider = findProvider("alibaba-coding-plan");
439
+ assert.ok(provider, "alibaba-coding-plan must be in PROVIDER_REGISTRY for /gsd keys add to work");
440
+ assert.equal(provider.id, "alibaba-coding-plan");
441
+ assert.equal(provider.category, "llm");
442
+ assert.equal(provider.envVar, "ALIBABA_API_KEY");
443
+ });
444
+
445
+ test("alibaba-dashscope is in PROVIDER_REGISTRY", () => {
446
+ const provider = findProvider("alibaba-dashscope");
447
+ assert.ok(provider, "alibaba-dashscope must be in PROVIDER_REGISTRY for /gsd keys add to work");
448
+ assert.equal(provider.id, "alibaba-dashscope");
449
+ assert.equal(provider.category, "llm");
450
+ assert.equal(provider.envVar, "DASHSCOPE_API_KEY");
451
+ });
452
+
453
+ test("alibaba-coding-plan and alibaba-dashscope are separate providers (different env vars)", () => {
454
+ const codingPlan = findProvider("alibaba-coding-plan");
455
+ const dashscope = findProvider("alibaba-dashscope");
456
+ assert.ok(codingPlan, "alibaba-coding-plan must exist");
457
+ assert.ok(dashscope, "alibaba-dashscope must exist");
458
+ assert.notEqual(
459
+ codingPlan.envVar,
460
+ dashscope.envVar,
461
+ "alibaba-coding-plan and alibaba-dashscope must use different env vars",
462
+ );
463
+ });
464
+
465
+ test("getAllKeyStatuses includes alibaba-coding-plan", () => {
466
+ const auth = makeAuth();
467
+ const statuses = getAllKeyStatuses(auth);
468
+ const found = statuses.find((s) => s.provider.id === "alibaba-coding-plan");
469
+ assert.ok(found, "getAllKeyStatuses must include alibaba-coding-plan");
470
+ });
471
+
472
+ test("getAllKeyStatuses includes alibaba-dashscope", () => {
473
+ const auth = makeAuth();
474
+ const statuses = getAllKeyStatuses(auth);
475
+ const found = statuses.find((s) => s.provider.id === "alibaba-dashscope");
476
+ assert.ok(found, "getAllKeyStatuses must include alibaba-dashscope");
477
+ });
478
+
479
+ test("getAllKeyStatuses detects DASHSCOPE_API_KEY for alibaba-dashscope (failure path: missing key shows not configured)", () => {
480
+ const saved = process.env.DASHSCOPE_API_KEY;
481
+ delete process.env.DASHSCOPE_API_KEY;
482
+ try {
483
+ const auth = makeAuth();
484
+ const statuses = getAllKeyStatuses(auth);
485
+ const found = statuses.find((s) => s.provider.id === "alibaba-dashscope");
486
+ assert.ok(found);
487
+ assert.equal(found.configured, false);
488
+ assert.equal(found.source, "none");
489
+ } finally {
490
+ if (saved !== undefined) process.env.DASHSCOPE_API_KEY = saved;
491
+ }
492
+ });
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Regression tests for memory pressure monitoring (#3331) and
3
+ * stuck detection persistence (#3704) in auto/loop.ts.
4
+ */
5
+
6
+ import { describe, test } from "node:test";
7
+ import assert from "node:assert/strict";
8
+ import { readFileSync } from "node:fs";
9
+ import { join, dirname } from "node:path";
10
+ import { fileURLToPath } from "node:url";
11
+
12
+ const __dirname = dirname(fileURLToPath(import.meta.url));
13
+ const loopSource = readFileSync(join(__dirname, "..", "auto", "loop.ts"), "utf-8");
14
+
15
+ describe("memory pressure monitoring (#3331)", () => {
16
+ test("checkMemoryPressure function exists", () => {
17
+ assert.match(loopSource, /function checkMemoryPressure/);
18
+ });
19
+
20
+ test("MEMORY_PRESSURE_THRESHOLD constant is defined", () => {
21
+ assert.match(loopSource, /MEMORY_PRESSURE_THRESHOLD\s*=\s*0\.\d+/);
22
+ });
23
+
24
+ test("memory check runs every MEMORY_CHECK_INTERVAL iterations", () => {
25
+ assert.match(loopSource, /iteration\s*%\s*MEMORY_CHECK_INTERVAL\s*===\s*0/);
26
+ });
27
+
28
+ test("memory pressure triggers graceful stopAuto", () => {
29
+ assert.match(loopSource, /mem\.pressured/);
30
+ assert.match(loopSource, /Stopping gracefully to prevent OOM/);
31
+ });
32
+ });
33
+
34
+ describe("stuck detection persistence (#3704)", () => {
35
+ test("loadStuckState function exists", () => {
36
+ assert.match(loopSource, /function loadStuckState/);
37
+ });
38
+
39
+ test("saveStuckState function exists", () => {
40
+ assert.match(loopSource, /function saveStuckState/);
41
+ });
42
+
43
+ test("loopState initialized from persisted state", () => {
44
+ assert.match(loopSource, /loadStuckState\(s\.basePath\)/);
45
+ });
46
+
47
+ test("stuck state saved after each iteration", () => {
48
+ assert.match(loopSource, /saveStuckState\(s\.basePath,\s*loopState\)/);
49
+ });
50
+
51
+ test("stuck state file path uses runtime directory", () => {
52
+ assert.match(loopSource, /stuck-state\.json/);
53
+ });
54
+ });
@@ -3,10 +3,22 @@ import assert from 'node:assert/strict';
3
3
  import { mkdtempSync, mkdirSync, rmSync, writeFileSync, existsSync, readFileSync } from 'node:fs';
4
4
  import { join } from 'node:path';
5
5
  import { tmpdir } from 'node:os';
6
+ import { execSync } from 'node:child_process';
6
7
 
7
8
  import { deriveState, invalidateStateCache, getActiveMilestoneId } from '../state.ts';
8
9
  import { clearPathCache } from '../paths.ts';
9
10
  import { parkMilestone, unparkMilestone, discardMilestone, isParked, getParkedReason } from '../milestone-actions.ts';
11
+ import {
12
+ closeDatabase,
13
+ getMilestone,
14
+ getMilestoneSlices,
15
+ getSliceTasks,
16
+ insertMilestone,
17
+ insertSlice,
18
+ insertTask,
19
+ openDatabase,
20
+ } from "../gsd-db.ts";
21
+ import { createWorktree } from "../worktree-manager.ts";
10
22
 
11
23
 
12
24
 
@@ -60,9 +72,29 @@ function createMilestone(base: string, mid: string, opts?: { withRoadmap?: boole
60
72
  }
61
73
 
62
74
  function cleanup(base: string): void {
75
+ try {
76
+ closeDatabase();
77
+ } catch {
78
+ // ignore
79
+ }
63
80
  rmSync(base, { recursive: true, force: true });
64
81
  }
65
82
 
83
+ function run(cmd: string, cwd: string): string {
84
+ return execSync(cmd, { cwd, stdio: ["ignore", "pipe", "pipe"], encoding: "utf-8" }).trim();
85
+ }
86
+
87
+ function initGitRepo(base: string): void {
88
+ writeFileSync(join(base, "README.md"), "# test\n", "utf-8");
89
+ writeFileSync(join(base, ".gsd", "STATE.md"), "# State\n", "utf-8");
90
+ run("git init", base);
91
+ run("git config user.email test@test.com", base);
92
+ run("git config user.name Test", base);
93
+ run("git add .", base);
94
+ run('git commit -m "init"', base);
95
+ run("git branch -M main", base);
96
+ }
97
+
66
98
  function clearCaches(): void {
67
99
  clearPathCache();
68
100
  invalidateStateCache();
@@ -294,6 +326,38 @@ test('discardMilestone updates queue order', () => {
294
326
  }
295
327
  });
296
328
 
329
+ test('discardMilestone removes DB rows, worktree, and milestone branch', () => {
330
+ const base = createFixtureBase();
331
+ try {
332
+ createMilestone(base, 'M001', { withRoadmap: true });
333
+ initGitRepo(base);
334
+ clearCaches();
335
+
336
+ assert.ok(openDatabase(join(base, '.gsd', 'gsd.db')), 'database opens');
337
+ insertMilestone({ id: 'M001', title: 'Discard me', status: 'active' });
338
+ insertSlice({ milestoneId: 'M001', id: 'S01', title: 'Only slice', status: 'pending' });
339
+ insertTask({ milestoneId: 'M001', sliceId: 'S01', id: 'T01', title: 'Only task', status: 'pending' });
340
+
341
+ const wt = createWorktree(base, 'M001', { branch: 'milestone/M001' });
342
+ assert.ok(existsSync(wt.path), 'worktree exists before discard');
343
+ assert.ok(run('git branch', base).includes('milestone/M001'), 'milestone branch exists before discard');
344
+ assert.ok(getMilestone('M001'), 'milestone exists in DB before discard');
345
+ assert.equal(getMilestoneSlices('M001').length, 1, 'slice exists in DB before discard');
346
+ assert.equal(getSliceTasks('M001', 'S01').length, 1, 'task exists in DB before discard');
347
+
348
+ const success = discardMilestone(base, 'M001');
349
+ assert.ok(success, 'discardMilestone returns true');
350
+
351
+ assert.equal(getMilestone('M001'), null, 'milestone row removed from DB');
352
+ assert.equal(getMilestoneSlices('M001').length, 0, 'slice rows removed from DB');
353
+ assert.equal(getSliceTasks('M001', 'S01').length, 0, 'task rows removed from DB');
354
+ assert.ok(!existsSync(wt.path), 'worktree removed after discard');
355
+ assert.ok(!run('git branch', base).includes('milestone/M001'), 'milestone branch removed after discard');
356
+ } finally {
357
+ cleanup(base);
358
+ }
359
+ });
360
+
297
361
  // ─── Test 12: All milestones parked → no active milestone ─────────────
298
362
  test('All milestones parked → no active', async () => {
299
363
  const base = createFixtureBase();
@@ -0,0 +1,62 @@
1
+ import { test } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
4
+ import { join } from "node:path";
5
+ import { tmpdir } from "node:os";
6
+
7
+ import { verifyExpectedArtifact } from "../auto-recovery.ts";
8
+
9
+ function createFixtureBase(): string {
10
+ const base = mkdtempSync(join(tmpdir(), "gsd-plan-milestone-artifact-"));
11
+ mkdirSync(join(base, ".gsd", "milestones"), { recursive: true });
12
+ return base;
13
+ }
14
+
15
+ function writeRoadmap(base: string, milestoneId: string, content: string): void {
16
+ const milestoneDir = join(base, ".gsd", "milestones", milestoneId);
17
+ mkdirSync(milestoneDir, { recursive: true });
18
+ writeFileSync(join(milestoneDir, `${milestoneId}-ROADMAP.md`), content, "utf-8");
19
+ }
20
+
21
+ test("#3405: plan-milestone roadmap stub does not count as a verified artifact", () => {
22
+ const base = createFixtureBase();
23
+ try {
24
+ writeRoadmap(base, "M001", [
25
+ "# M001: Placeholder",
26
+ "",
27
+ "**Vision:** Stub only.",
28
+ "",
29
+ "## Slices",
30
+ "",
31
+ "_TBD_",
32
+ "",
33
+ ].join("\n"));
34
+
35
+ const result = verifyExpectedArtifact("plan-milestone", "M001", base);
36
+ assert.equal(result, false, "zero-slice roadmap stubs must fail verification");
37
+ } finally {
38
+ rmSync(base, { recursive: true, force: true });
39
+ }
40
+ });
41
+
42
+ test("#3405: plan-milestone roadmap with real slices still passes artifact verification", () => {
43
+ const base = createFixtureBase();
44
+ try {
45
+ writeRoadmap(base, "M001", [
46
+ "# M001: Real roadmap",
47
+ "",
48
+ "**Vision:** Real work.",
49
+ "",
50
+ "## Slices",
51
+ "",
52
+ "- [ ] **S01: First slice** `risk:low` `depends:[]`",
53
+ " > After this: a real slice exists.",
54
+ "",
55
+ ].join("\n"));
56
+
57
+ const result = verifyExpectedArtifact("plan-milestone", "M001", base);
58
+ assert.equal(result, true, "real roadmap slices should keep passing verification");
59
+ } finally {
60
+ rmSync(base, { recursive: true, force: true });
61
+ }
62
+ });
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Regression test for #3869: normal post-unit flow should rebuild STATE.md
3
+ * before syncing worktree state back to the project root.
4
+ */
5
+
6
+ import test from "node:test";
7
+ import assert from "node:assert/strict";
8
+ import { readFileSync } from "node:fs";
9
+ import { join } from "node:path";
10
+
11
+ const source = readFileSync(join(import.meta.dirname, "..", "auto-post-unit.ts"), "utf-8");
12
+
13
+ test("auto-post-unit imports rebuildState", () => {
14
+ assert.ok(
15
+ source.includes('import { rebuildState } from "./doctor.js";'),
16
+ "auto-post-unit.ts should import rebuildState from doctor.ts",
17
+ );
18
+ });
19
+
20
+ test("postUnitPreVerification rebuilds STATE.md before worktree sync", () => {
21
+ const fnStart = source.indexOf("export async function postUnitPreVerification");
22
+ assert.ok(fnStart > 0, "postUnitPreVerification should exist");
23
+
24
+ const section = source.slice(fnStart, fnStart + 8000);
25
+ const rebuildIdx = section.indexOf('await runSafely("postUnit", "state-rebuild"');
26
+ const syncIdx = section.indexOf('await runSafely("postUnit", "worktree-sync"');
27
+
28
+ assert.ok(rebuildIdx > 0, "postUnitPreVerification should rebuild STATE.md after unit completion");
29
+ assert.ok(syncIdx > 0, "postUnitPreVerification should sync worktree state back to the project root");
30
+ assert.ok(
31
+ rebuildIdx < syncIdx,
32
+ "STATE.md rebuild should happen before worktree sync so synced state is fresh",
33
+ );
34
+ });
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Tests for formatSkillRef — pure formatting logic for skill references
3
+ * in the system prompt. Moved from preferences-skills.ts to preferences-types.ts
4
+ * to break the preferences ↔ preferences-skills circular dependency.
5
+ */
6
+
7
+ import { describe, test } from "node:test";
8
+ import assert from "node:assert/strict";
9
+
10
+ import { formatSkillRef } from "../preferences-types.ts";
11
+ import type { SkillResolution } from "../preferences-types.ts";
12
+
13
+ function makeResolutions(entries: [string, Partial<SkillResolution>][]): Map<string, SkillResolution> {
14
+ const map = new Map<string, SkillResolution>();
15
+ for (const [key, partial] of entries) {
16
+ map.set(key, {
17
+ original: partial.original ?? key,
18
+ resolvedPath: partial.resolvedPath ?? null,
19
+ method: partial.method ?? "unresolved",
20
+ });
21
+ }
22
+ return map;
23
+ }
24
+
25
+ describe("formatSkillRef", () => {
26
+ test("marks unresolved references with a warning", () => {
27
+ const resolutions = makeResolutions([
28
+ ["my-skill", { method: "unresolved" }],
29
+ ]);
30
+ const result = formatSkillRef("my-skill", resolutions);
31
+ assert.match(result, /my-skill/);
32
+ assert.match(result, /not found/);
33
+ });
34
+
35
+ test("marks unknown references (not in map) with a warning", () => {
36
+ const resolutions = new Map<string, SkillResolution>();
37
+ const result = formatSkillRef("unknown-skill", resolutions);
38
+ assert.match(result, /unknown-skill/);
39
+ assert.match(result, /not found/);
40
+ });
41
+
42
+ test("returns bare ref for absolute-path resolution", () => {
43
+ const resolutions = makeResolutions([
44
+ ["/home/user/skills/SKILL.md", {
45
+ method: "absolute-path",
46
+ resolvedPath: "/home/user/skills/SKILL.md",
47
+ }],
48
+ ]);
49
+ const result = formatSkillRef("/home/user/skills/SKILL.md", resolutions);
50
+ assert.equal(result, "/home/user/skills/SKILL.md");
51
+ });
52
+
53
+ test("returns bare ref for absolute-dir resolution", () => {
54
+ const resolutions = makeResolutions([
55
+ ["/home/user/skills/my-skill", {
56
+ method: "absolute-dir",
57
+ resolvedPath: "/home/user/skills/my-skill/SKILL.md",
58
+ }],
59
+ ]);
60
+ const result = formatSkillRef("/home/user/skills/my-skill", resolutions);
61
+ assert.equal(result, "/home/user/skills/my-skill");
62
+ });
63
+
64
+ test("shows resolved path for user-skill resolution", () => {
65
+ const resolutions = makeResolutions([
66
+ ["code-review", {
67
+ method: "user-skill",
68
+ resolvedPath: "/home/user/.claude/skills/code-review/SKILL.md",
69
+ }],
70
+ ]);
71
+ const result = formatSkillRef("code-review", resolutions);
72
+ assert.match(result, /code-review/);
73
+ assert.match(result, /\.claude\/skills\/code-review\/SKILL\.md/);
74
+ });
75
+
76
+ test("shows resolved path for project-skill resolution", () => {
77
+ const resolutions = makeResolutions([
78
+ ["lint-fix", {
79
+ method: "project-skill",
80
+ resolvedPath: "/repo/.gsd/skills/lint-fix/SKILL.md",
81
+ }],
82
+ ]);
83
+ const result = formatSkillRef("lint-fix", resolutions);
84
+ assert.match(result, /lint-fix/);
85
+ assert.match(result, /\.gsd\/skills\/lint-fix\/SKILL\.md/);
86
+ });
87
+ });