gsd-pi 2.71.0-dev.e17e0ce → 2.72.0-dev.3118184

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 (352) hide show
  1. package/README.md +46 -3
  2. package/dist/cli.js +76 -3
  3. package/dist/mcp-server.js +37 -14
  4. package/dist/onboarding.js +10 -0
  5. package/dist/resources/agents/debugger.md +58 -0
  6. package/dist/resources/agents/doc-writer.md +43 -0
  7. package/dist/resources/agents/git-ops.md +56 -0
  8. package/dist/resources/agents/javascript-pro.md +46 -271
  9. package/dist/resources/agents/planner.md +55 -0
  10. package/dist/resources/agents/refactorer.md +47 -0
  11. package/dist/resources/agents/reviewer.md +48 -0
  12. package/dist/resources/agents/security.md +59 -0
  13. package/dist/resources/agents/tester.md +50 -0
  14. package/dist/resources/agents/typescript-pro.md +41 -235
  15. package/dist/resources/extensions/async-jobs/await-tool.js +7 -4
  16. package/dist/resources/extensions/async-jobs/job-manager.js +28 -3
  17. package/dist/resources/extensions/claude-code-cli/partial-builder.js +40 -12
  18. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +132 -10
  19. package/dist/resources/extensions/gsd/auto/loop.js +84 -1
  20. package/dist/resources/extensions/gsd/auto/phases.js +4 -0
  21. package/dist/resources/extensions/gsd/auto-post-unit.js +6 -0
  22. package/dist/resources/extensions/gsd/auto-prompts.js +88 -33
  23. package/dist/resources/extensions/gsd/auto-recovery.js +11 -0
  24. package/dist/resources/extensions/gsd/auto-start.js +24 -4
  25. package/dist/resources/extensions/gsd/auto.js +29 -19
  26. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +3 -3
  27. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +9 -11
  28. package/dist/resources/extensions/gsd/bootstrap/register-shortcuts.js +2 -5
  29. package/dist/resources/extensions/gsd/commands-handlers.js +4 -1
  30. package/dist/resources/extensions/gsd/context-injector.js +1 -1
  31. package/dist/resources/extensions/gsd/custom-workflow-engine.js +3 -7
  32. package/dist/resources/extensions/gsd/definition-io.js +15 -0
  33. package/dist/resources/extensions/gsd/dispatch-guard.js +4 -0
  34. package/dist/resources/extensions/gsd/doctor-providers.js +23 -0
  35. package/dist/resources/extensions/gsd/doctor-runtime-checks.js +6 -3
  36. package/dist/resources/extensions/gsd/error-classifier.js +4 -1
  37. package/dist/resources/extensions/gsd/gate-registry.js +208 -0
  38. package/dist/resources/extensions/gsd/git-service.js +11 -8
  39. package/dist/resources/extensions/gsd/gitignore.js +12 -6
  40. package/dist/resources/extensions/gsd/gsd-db.js +90 -6
  41. package/dist/resources/extensions/gsd/key-manager.js +2 -0
  42. package/dist/resources/extensions/gsd/milestone-validation-gates.js +11 -12
  43. package/dist/resources/extensions/gsd/notification-overlay.js +26 -12
  44. package/dist/resources/extensions/gsd/notification-store.js +5 -4
  45. package/dist/resources/extensions/gsd/preferences-skills.js +2 -34
  46. package/dist/resources/extensions/gsd/preferences-types.js +15 -0
  47. package/dist/resources/extensions/gsd/preferences.js +16 -3
  48. package/dist/resources/extensions/gsd/prompt-loader.js +4 -1
  49. package/dist/resources/extensions/gsd/prompt-validation.js +126 -0
  50. package/dist/resources/extensions/gsd/prompts/complete-slice.md +3 -1
  51. package/dist/resources/extensions/gsd/prompts/discuss.md +122 -13
  52. package/dist/resources/extensions/gsd/prompts/execute-task.md +2 -0
  53. package/dist/resources/extensions/gsd/prompts/system.md +1 -1
  54. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +2 -0
  55. package/dist/resources/extensions/gsd/shortcut-defs.js +7 -1
  56. package/dist/resources/extensions/gsd/state.js +29 -2
  57. package/dist/resources/extensions/gsd/tools/complete-slice.js +52 -1
  58. package/dist/resources/extensions/gsd/tools/complete-task.js +51 -1
  59. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +4 -1
  60. package/dist/resources/extensions/gsd/workflow-projections.js +7 -0
  61. package/dist/resources/extensions/gsd/worktree-manager.js +30 -3
  62. package/dist/resources/extensions/gsd/write-intercept.js +10 -1
  63. package/dist/resources/extensions/ollama/index.js +17 -10
  64. package/dist/resources/extensions/ollama/ollama-client.js +35 -6
  65. package/dist/resources/extensions/ollama/ollama-discovery.js +32 -6
  66. package/dist/resources/extensions/shared/gsd-phase-state.js +35 -0
  67. package/dist/resources/extensions/subagent/agents.js +8 -0
  68. package/dist/resources/extensions/subagent/index.js +17 -0
  69. package/dist/startup-model-validation.d.ts +0 -1
  70. package/dist/startup-model-validation.js +6 -2
  71. package/dist/web/standalone/.next/BUILD_ID +1 -1
  72. package/dist/web/standalone/.next/app-path-routes-manifest.json +14 -14
  73. package/dist/web/standalone/.next/build-manifest.json +2 -2
  74. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  75. package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
  76. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  77. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  78. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  79. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  80. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  81. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  82. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  83. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  84. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  85. package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
  86. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  87. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  88. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  89. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  90. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  91. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  92. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  93. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  94. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  95. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  96. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
  97. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
  98. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
  99. package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
  100. package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
  101. package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
  102. package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
  103. package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
  104. package/dist/web/standalone/.next/server/app/api/experimental/route.js +2 -2
  105. package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
  106. package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
  107. package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
  108. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  109. package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
  110. package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
  111. package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
  112. package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
  113. package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
  114. package/dist/web/standalone/.next/server/app/api/notifications/route.js +2 -2
  115. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  116. package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
  117. package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
  118. package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
  119. package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +2 -2
  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/command/route.js +1 -1
  122. package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
  123. package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
  124. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  125. package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
  126. package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
  127. package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
  128. package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
  129. package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +2 -2
  130. package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
  131. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +2 -2
  132. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +3 -3
  133. package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
  134. package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
  135. package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
  136. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  137. package/dist/web/standalone/.next/server/app/index.html +1 -1
  138. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  139. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  140. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  141. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  142. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  143. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  144. package/dist/web/standalone/.next/server/app/page.js +2 -2
  145. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  146. package/dist/web/standalone/.next/server/app-paths-manifest.json +14 -14
  147. package/dist/web/standalone/.next/server/chunks/2331.js +16 -16
  148. package/dist/web/standalone/.next/server/chunks/4741.js +12 -12
  149. package/dist/web/standalone/.next/server/chunks/5822.js +2 -2
  150. package/dist/web/standalone/.next/server/chunks/63.js +8 -8
  151. package/dist/web/standalone/.next/server/chunks/6897.js +3 -3
  152. package/dist/web/standalone/.next/server/edge-runtime-webpack.js +2 -0
  153. package/dist/web/standalone/.next/server/functions-config-manifest.json +0 -9
  154. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  155. package/dist/web/standalone/.next/server/middleware-manifest.json +29 -2
  156. package/dist/web/standalone/.next/server/middleware.js +4 -12
  157. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  158. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  159. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  160. package/dist/web/standalone/.next/server/webpack-runtime.js +1 -1
  161. package/package.json +1 -1
  162. package/packages/mcp-server/dist/server.d.ts +12 -1
  163. package/packages/mcp-server/dist/server.d.ts.map +1 -1
  164. package/packages/mcp-server/dist/server.js +90 -42
  165. package/packages/mcp-server/dist/server.js.map +1 -1
  166. package/packages/mcp-server/dist/workflow-tools.js +1 -1
  167. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  168. package/packages/mcp-server/src/server.ts +110 -38
  169. package/packages/mcp-server/src/workflow-tools.ts +1 -1
  170. package/packages/pi-ai/dist/env-api-keys.js +1 -0
  171. package/packages/pi-ai/dist/env-api-keys.js.map +1 -1
  172. package/packages/pi-ai/dist/models.custom.d.ts +105 -0
  173. package/packages/pi-ai/dist/models.custom.d.ts.map +1 -1
  174. package/packages/pi-ai/dist/models.custom.js +97 -0
  175. package/packages/pi-ai/dist/models.custom.js.map +1 -1
  176. package/packages/pi-ai/dist/models.generated.d.ts +648 -140
  177. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  178. package/packages/pi-ai/dist/models.generated.js +867 -370
  179. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  180. package/packages/pi-ai/dist/models.generated.test.d.ts +2 -0
  181. package/packages/pi-ai/dist/models.generated.test.d.ts.map +1 -0
  182. package/packages/pi-ai/dist/models.generated.test.js +334 -0
  183. package/packages/pi-ai/dist/models.generated.test.js.map +1 -0
  184. package/packages/pi-ai/dist/models.test.js +105 -0
  185. package/packages/pi-ai/dist/models.test.js.map +1 -1
  186. package/packages/pi-ai/dist/types.d.ts +1 -1
  187. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  188. package/packages/pi-ai/dist/types.js.map +1 -1
  189. package/packages/pi-ai/dist/utils/oauth/github-copilot.d.ts.map +1 -1
  190. package/packages/pi-ai/dist/utils/oauth/github-copilot.js +5 -1
  191. package/packages/pi-ai/dist/utils/oauth/github-copilot.js.map +1 -1
  192. package/packages/pi-ai/dist/utils/oauth/github-copilot.test.d.ts +2 -0
  193. package/packages/pi-ai/dist/utils/oauth/github-copilot.test.d.ts.map +1 -0
  194. package/packages/pi-ai/dist/utils/oauth/github-copilot.test.js +57 -0
  195. package/packages/pi-ai/dist/utils/oauth/github-copilot.test.js.map +1 -0
  196. package/packages/pi-ai/src/env-api-keys.ts +1 -0
  197. package/packages/pi-ai/src/models.custom.ts +98 -0
  198. package/packages/pi-ai/src/models.generated.test.ts +373 -0
  199. package/packages/pi-ai/src/models.generated.ts +867 -370
  200. package/packages/pi-ai/src/models.test.ts +135 -0
  201. package/packages/pi-ai/src/types.ts +1 -0
  202. package/packages/pi-ai/src/utils/oauth/github-copilot.test.ts +71 -0
  203. package/packages/pi-ai/src/utils/oauth/github-copilot.ts +4 -1
  204. package/packages/pi-coding-agent/dist/core/model-resolver.d.ts.map +1 -1
  205. package/packages/pi-coding-agent/dist/core/model-resolver.js +1 -0
  206. package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
  207. package/packages/pi-coding-agent/dist/core/model-resolver.test.d.ts +8 -0
  208. package/packages/pi-coding-agent/dist/core/model-resolver.test.d.ts.map +1 -0
  209. package/packages/pi-coding-agent/dist/core/model-resolver.test.js +75 -0
  210. package/packages/pi-coding-agent/dist/core/model-resolver.test.js.map +1 -0
  211. package/packages/pi-coding-agent/dist/core/retry-handler.d.ts +5 -0
  212. package/packages/pi-coding-agent/dist/core/retry-handler.d.ts.map +1 -1
  213. package/packages/pi-coding-agent/dist/core/retry-handler.js +55 -1
  214. package/packages/pi-coding-agent/dist/core/retry-handler.js.map +1 -1
  215. package/packages/pi-coding-agent/dist/core/retry-handler.test.js +57 -0
  216. package/packages/pi-coding-agent/dist/core/retry-handler.test.js.map +1 -1
  217. package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
  218. package/packages/pi-coding-agent/dist/core/sdk.js +9 -0
  219. package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
  220. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js +36 -0
  221. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js.map +1 -1
  222. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  223. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js +9 -2
  224. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js.map +1 -1
  225. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  226. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +87 -12
  227. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  228. package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.d.ts.map +1 -1
  229. package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.js +6 -1
  230. package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.js.map +1 -1
  231. package/packages/pi-coding-agent/package.json +1 -1
  232. package/packages/pi-coding-agent/src/core/model-resolver.test.ts +85 -0
  233. package/packages/pi-coding-agent/src/core/model-resolver.ts +1 -0
  234. package/packages/pi-coding-agent/src/core/retry-handler.test.ts +83 -0
  235. package/packages/pi-coding-agent/src/core/retry-handler.ts +60 -1
  236. package/packages/pi-coding-agent/src/core/sdk.ts +10 -0
  237. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-execution.test.ts +72 -0
  238. package/packages/pi-coding-agent/src/modes/interactive/components/model-selector.ts +15 -6
  239. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +84 -12
  240. package/packages/pi-coding-agent/src/modes/interactive/controllers/model-controller.ts +6 -1
  241. package/packages/pi-tui/dist/components/__tests__/editor.test.js +12 -0
  242. package/packages/pi-tui/dist/components/__tests__/editor.test.js.map +1 -1
  243. package/packages/pi-tui/dist/components/__tests__/input.test.js +12 -0
  244. package/packages/pi-tui/dist/components/__tests__/input.test.js.map +1 -1
  245. package/packages/pi-tui/dist/keys.d.ts.map +1 -1
  246. package/packages/pi-tui/dist/keys.js +27 -0
  247. package/packages/pi-tui/dist/keys.js.map +1 -1
  248. package/packages/pi-tui/src/components/__tests__/editor.test.ts +18 -0
  249. package/packages/pi-tui/src/components/__tests__/input.test.ts +18 -0
  250. package/packages/pi-tui/src/keys.ts +32 -0
  251. package/pkg/package.json +1 -1
  252. package/src/resources/agents/debugger.md +58 -0
  253. package/src/resources/agents/doc-writer.md +43 -0
  254. package/src/resources/agents/git-ops.md +56 -0
  255. package/src/resources/agents/javascript-pro.md +46 -271
  256. package/src/resources/agents/planner.md +55 -0
  257. package/src/resources/agents/refactorer.md +47 -0
  258. package/src/resources/agents/reviewer.md +48 -0
  259. package/src/resources/agents/security.md +59 -0
  260. package/src/resources/agents/tester.md +50 -0
  261. package/src/resources/agents/typescript-pro.md +41 -235
  262. package/src/resources/extensions/async-jobs/await-tool.test.ts +40 -7
  263. package/src/resources/extensions/async-jobs/await-tool.ts +7 -4
  264. package/src/resources/extensions/async-jobs/job-manager.ts +33 -3
  265. package/src/resources/extensions/claude-code-cli/partial-builder.ts +45 -12
  266. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +139 -8
  267. package/src/resources/extensions/claude-code-cli/tests/partial-builder.test.ts +91 -2
  268. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +245 -2
  269. package/src/resources/extensions/gsd/auto/loop.ts +89 -1
  270. package/src/resources/extensions/gsd/auto/phases.ts +4 -0
  271. package/src/resources/extensions/gsd/auto-post-unit.ts +7 -0
  272. package/src/resources/extensions/gsd/auto-prompts.ts +111 -33
  273. package/src/resources/extensions/gsd/auto-recovery.ts +10 -0
  274. package/src/resources/extensions/gsd/auto-start.ts +31 -4
  275. package/src/resources/extensions/gsd/auto.ts +29 -20
  276. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +3 -3
  277. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +8 -10
  278. package/src/resources/extensions/gsd/bootstrap/register-shortcuts.ts +2 -5
  279. package/src/resources/extensions/gsd/commands-handlers.ts +5 -1
  280. package/src/resources/extensions/gsd/context-injector.ts +1 -1
  281. package/src/resources/extensions/gsd/custom-workflow-engine.ts +4 -8
  282. package/src/resources/extensions/gsd/definition-io.ts +18 -0
  283. package/src/resources/extensions/gsd/dispatch-guard.ts +5 -0
  284. package/src/resources/extensions/gsd/doctor-providers.ts +24 -0
  285. package/src/resources/extensions/gsd/doctor-runtime-checks.ts +6 -3
  286. package/src/resources/extensions/gsd/error-classifier.ts +4 -1
  287. package/src/resources/extensions/gsd/gate-registry.ts +251 -0
  288. package/src/resources/extensions/gsd/git-service.ts +11 -8
  289. package/src/resources/extensions/gsd/gitignore.ts +12 -6
  290. package/src/resources/extensions/gsd/gsd-db.ts +105 -6
  291. package/src/resources/extensions/gsd/key-manager.ts +2 -0
  292. package/src/resources/extensions/gsd/milestone-validation-gates.ts +11 -13
  293. package/src/resources/extensions/gsd/notification-overlay.ts +27 -11
  294. package/src/resources/extensions/gsd/notification-store.ts +5 -4
  295. package/src/resources/extensions/gsd/preferences-skills.ts +2 -36
  296. package/src/resources/extensions/gsd/preferences-types.ts +16 -0
  297. package/src/resources/extensions/gsd/preferences.ts +19 -6
  298. package/src/resources/extensions/gsd/prompt-loader.ts +6 -1
  299. package/src/resources/extensions/gsd/prompt-validation.ts +157 -0
  300. package/src/resources/extensions/gsd/prompts/complete-slice.md +3 -1
  301. package/src/resources/extensions/gsd/prompts/discuss.md +122 -13
  302. package/src/resources/extensions/gsd/prompts/execute-task.md +2 -0
  303. package/src/resources/extensions/gsd/prompts/system.md +1 -1
  304. package/src/resources/extensions/gsd/prompts/validate-milestone.md +2 -0
  305. package/src/resources/extensions/gsd/shortcut-defs.ts +8 -1
  306. package/src/resources/extensions/gsd/state.ts +33 -2
  307. package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +27 -0
  308. package/src/resources/extensions/gsd/tests/auto-start-model-capture.test.ts +14 -0
  309. package/src/resources/extensions/gsd/tests/block-db-writes.test.ts +63 -0
  310. package/src/resources/extensions/gsd/tests/complete-slice-gate-closure.test.ts +167 -0
  311. package/src/resources/extensions/gsd/tests/definition-io.test.ts +57 -0
  312. package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +26 -0
  313. package/src/resources/extensions/gsd/tests/doctor-heal-fixable-warnings.test.ts +14 -0
  314. package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +36 -0
  315. package/src/resources/extensions/gsd/tests/false-degraded-mode-warning.test.ts +104 -0
  316. package/src/resources/extensions/gsd/tests/format-shortcut.test.ts +16 -0
  317. package/src/resources/extensions/gsd/tests/gate-dispatch.test.ts +27 -0
  318. package/src/resources/extensions/gsd/tests/gate-registry.test.ts +140 -0
  319. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +107 -5
  320. package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +8 -6
  321. package/src/resources/extensions/gsd/tests/key-manager.test.ts +63 -0
  322. package/src/resources/extensions/gsd/tests/memory-pressure-stuck-state.test.ts +54 -0
  323. package/src/resources/extensions/gsd/tests/plan-milestone-artifact-verification.test.ts +62 -0
  324. package/src/resources/extensions/gsd/tests/post-unit-state-rebuild.test.ts +34 -0
  325. package/src/resources/extensions/gsd/tests/preferences-formatting.test.ts +87 -0
  326. package/src/resources/extensions/gsd/tests/preferences.test.ts +53 -0
  327. package/src/resources/extensions/gsd/tests/projection-regression.test.ts +96 -1
  328. package/src/resources/extensions/gsd/tests/prompt-loader-working-directory.test.ts +19 -0
  329. package/src/resources/extensions/gsd/tests/prompt-system-gate-coverage.test.ts +208 -0
  330. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +9 -0
  331. package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +97 -0
  332. package/src/resources/extensions/gsd/tests/register-shortcuts.test.ts +3 -2
  333. package/src/resources/extensions/gsd/tests/stale-slice-rows.test.ts +41 -0
  334. package/src/resources/extensions/gsd/tools/complete-slice.ts +63 -0
  335. package/src/resources/extensions/gsd/tools/complete-task.ts +63 -0
  336. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +4 -1
  337. package/src/resources/extensions/gsd/types.ts +26 -0
  338. package/src/resources/extensions/gsd/workflow-projections.ts +8 -0
  339. package/src/resources/extensions/gsd/worktree-manager.ts +29 -3
  340. package/src/resources/extensions/gsd/write-intercept.ts +10 -1
  341. package/src/resources/extensions/ollama/index.ts +17 -8
  342. package/src/resources/extensions/ollama/ollama-client.ts +35 -6
  343. package/src/resources/extensions/ollama/ollama-discovery.ts +37 -6
  344. package/src/resources/extensions/ollama/ollama-status-indicator.test.ts +28 -0
  345. package/src/resources/extensions/ollama/tests/ollama-discovery.test.ts +54 -0
  346. package/src/resources/extensions/shared/gsd-phase-state.ts +42 -0
  347. package/src/resources/extensions/shared/tests/gsd-phase-state.test.ts +48 -0
  348. package/src/resources/extensions/subagent/agents.ts +10 -0
  349. package/src/resources/extensions/subagent/index.ts +18 -0
  350. package/src/resources/extensions/subagent/tests/agents-conflicts.test.ts +33 -0
  351. /package/dist/web/standalone/.next/static/{cYPZv_bAhZk2ms-Pz6vsY → NzO79SOz9jHX-VY5-0t2O}/_buildManifest.js +0 -0
  352. /package/dist/web/standalone/.next/static/{cYPZv_bAhZk2ms-Pz6vsY → NzO79SOz9jHX-VY5-0t2O}/_ssgManifest.js +0 -0
@@ -22,6 +22,8 @@ A researcher explored the codebase and a planner decomposed the work — you are
22
22
 
23
23
  {{slicePlanExcerpt}}
24
24
 
25
+ {{gatesToClose}}
26
+
25
27
  ## Backing Source Artifacts
26
28
  - Slice plan: `{{planPath}}`
27
29
  - Task plan source: `{{taskPlanPath}}`
@@ -35,7 +35,7 @@ GSD ships with bundled skills. Load the relevant skill file with the `read` tool
35
35
  - Read before edit.
36
36
  - Reproduce before fix when possible.
37
37
  - Work is not done until the relevant verification has passed.
38
- - **Never fabricate, simulate, or role-play user responses.** Never generate markers like `[User]`, `[Human]`, `User:`, or similar to represent user input inside your own output. Ask one question round (1-3 questions), then stop and wait for the user's actual response before continuing. If `ask_user_questions` is available, treat its returned response as the only valid structured user input for that round.
38
+ - **Never fabricate, simulate, or role-play user responses.** Never generate markers like `[User]`, `[Human]`, `User:`, or similar to represent user input inside your own output. Prior conversation context may be provided to you inside `<conversation_history>` with `<user_message>` / `<assistant_message>` XML tags — treat those as read-only context and never emit those tags in your response. Ask one question round (1-3 questions), then stop and wait for the user's actual response before continuing. If `ask_user_questions` is available, treat its returned response as the only valid structured user input for that round.
39
39
  - Never print, echo, log, or restate secrets or credentials. Report only key names and applied/skipped status.
40
40
  - Never ask the user to edit `.env` files or set secrets manually. Use `secure_env_collect`.
41
41
  - In enduring files, write current state only unless the file is explicitly historical.
@@ -18,6 +18,8 @@ All relevant context has been preloaded below — the roadmap, all slice summari
18
18
 
19
19
  {{inlinedContext}}
20
20
 
21
+ {{gatesToEvaluate}}
22
+
21
23
  ## Execution Protocol
22
24
 
23
25
  ### Step 1 — Dispatch Parallel Reviewers
@@ -8,6 +8,8 @@ type GSDShortcutDef = {
8
8
  key: "g" | "n" | "p";
9
9
  action: string;
10
10
  command: string;
11
+ /** Whether the Ctrl+Shift fallback is registered (false when it conflicts with an app keybinding). */
12
+ hasFallback: boolean;
11
13
  };
12
14
 
13
15
  export const GSD_SHORTCUTS: Record<GSDShortcutId, GSDShortcutDef> = {
@@ -15,16 +17,19 @@ export const GSD_SHORTCUTS: Record<GSDShortcutId, GSDShortcutDef> = {
15
17
  key: "g",
16
18
  action: "Open GSD dashboard",
17
19
  command: "/gsd status",
20
+ hasFallback: true,
18
21
  },
19
22
  notifications: {
20
23
  key: "n",
21
24
  action: "Open notification history",
22
25
  command: "/gsd notifications",
26
+ hasFallback: true,
23
27
  },
24
28
  parallel: {
25
29
  key: "p",
26
30
  action: "Open parallel worker monitor",
27
31
  command: "/gsd parallel watch",
32
+ hasFallback: false, // Ctrl+Shift+P conflicts with cycleModelBackward
28
33
  },
29
34
  };
30
35
 
@@ -41,7 +46,9 @@ export function fallbackShortcutCombo(id: GSDShortcutId): string {
41
46
  }
42
47
 
43
48
  export function shortcutPair(id: GSDShortcutId, formatter: (combo: string) => string = (combo) => combo): string {
44
- return `${formatter(primaryShortcutCombo(id))} / ${formatter(fallbackShortcutCombo(id))}`;
49
+ const primary = formatter(primaryShortcutCombo(id));
50
+ if (!GSD_SHORTCUTS[id].hasFallback) return primary;
51
+ return `${primary} / ${formatter(fallbackShortcutCombo(id))}`;
45
52
  }
46
53
 
47
54
  export function formattedShortcutPair(id: GSDShortcutId): string {
@@ -57,8 +57,9 @@ import {
57
57
  insertMilestone,
58
58
  insertSlice,
59
59
  insertTask,
60
+ updateSliceStatus,
60
61
  updateTaskStatus,
61
- getPendingSliceGateCount,
62
+ getPendingGateCountForTurn,
62
63
  type MilestoneRow,
63
64
  type SliceRow,
64
65
  type TaskRow,
@@ -358,6 +359,25 @@ function reconcileDiskToDb(basePath: string): MilestoneRow[] {
358
359
  depends: s.depends, demo: s.demo,
359
360
  });
360
361
  }
362
+
363
+ // Reconcile stale *existing* slice rows (#3599): a slice row may exist in
364
+ // the DB with status "pending" even though disk artifacts (SUMMARY) prove
365
+ // completion — the same class of desync that task-level reconciliation
366
+ // (further below) already handles. Without this, the dependency resolver
367
+ // builds doneSliceIds from stale DB rows and downstream slices stay blocked
368
+ // forever with "No slice eligible".
369
+ for (const dbSlice of dbSlices) {
370
+ if (isStatusDone(dbSlice.status)) continue;
371
+ const summaryPath = resolveSliceFile(basePath, mid, dbSlice.id, "SUMMARY");
372
+ if (summaryPath) {
373
+ try {
374
+ updateSliceStatus(mid, dbSlice.id, "complete");
375
+ logWarning("reconcile", `slice ${mid}/${dbSlice.id} status reconciled from "${dbSlice.status}" to "complete" (#3599)`, { mid, sid: dbSlice.id });
376
+ } catch (e) {
377
+ logError("reconcile", `failed to update slice ${dbSlice.id}`, { sid: dbSlice.id, error: (e as Error).message });
378
+ }
379
+ }
380
+ }
361
381
  }
362
382
  return allMilestones;
363
383
  }
@@ -864,7 +884,18 @@ export async function deriveStateFromDb(basePath: string): Promise<GSDState> {
864
884
  }
865
885
  }
866
886
 
867
- const pendingGateCount = getPendingSliceGateCount(activeMilestone.id, activeSlice.id);
887
+ // ── Quality gate evaluation check ──────────────────────────────────
888
+ // Pause before execution only when gates owned by the `gate-evaluate`
889
+ // turn (Q3/Q4) are still pending. Q8 is also `scope:"slice"` but is
890
+ // owned by `complete-slice`, so it must NOT block the evaluating-gates
891
+ // phase — otherwise auto-loop stalls forever waiting for a gate that
892
+ // this turn never evaluates. See gate-registry.ts for the ownership map.
893
+ // Slices with zero gate rows (pre-feature or simple) skip straight through.
894
+ const pendingGateCount = getPendingGateCountForTurn(
895
+ activeMilestone.id,
896
+ activeSlice.id,
897
+ "gate-evaluate",
898
+ );
868
899
  if (pendingGateCount > 0) {
869
900
  return {
870
901
  activeMilestone, activeSlice, activeTask: null,
@@ -0,0 +1,27 @@
1
+ import { test } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { readFileSync } from "node:fs";
4
+ import { dirname, join } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+
7
+ const __dirname = dirname(fileURLToPath(import.meta.url));
8
+ const autoSource = readFileSync(join(__dirname, "..", "auto.ts"), "utf-8");
9
+
10
+ test("#3370: cleanupAfterLoopExit preserves paused auto badge after provider pause", () => {
11
+ const cleanupIdx = autoSource.indexOf("function cleanupAfterLoopExit");
12
+ assert.ok(cleanupIdx > -1, "auto.ts should define cleanupAfterLoopExit");
13
+
14
+ const dispatchIdx = autoSource.indexOf("export async function dispatchHookUnit", cleanupIdx);
15
+ assert.ok(dispatchIdx > cleanupIdx, "cleanupAfterLoopExit body should be bounded by the next export");
16
+
17
+ const cleanupBody = autoSource.slice(cleanupIdx, dispatchIdx);
18
+ const pausedGuardIdx = cleanupBody.indexOf("if (!s.paused) {");
19
+ const clearStatusIdx = cleanupBody.indexOf('ctx.ui.setStatus("gsd-auto", undefined);');
20
+
21
+ assert.ok(pausedGuardIdx > -1, "loop-exit cleanup must guard UI clearing when auto is paused");
22
+ assert.ok(clearStatusIdx > pausedGuardIdx, "status clearing must live behind the paused guard");
23
+ assert.ok(
24
+ autoSource.includes('ctx?.ui.setStatus("gsd-auto", "paused");'),
25
+ "pauseAuto must still set the paused badge for transient provider pauses",
26
+ );
27
+ });
@@ -48,3 +48,17 @@ test("bootstrapAutoSession checks manual session override before preferences", (
48
48
  "manual override and preference fallback must be resolved before building startModelSnapshot",
49
49
  );
50
50
  });
51
+
52
+ test("bootstrapAutoSession validates preferred model against live registry auth (#unconfigured-models)", () => {
53
+ // The raw PREFERENCES.md value must be validated against getAvailable()
54
+ // before being captured as the snapshot, so an unconfigured provider
55
+ // (no API key / OAuth) can't become autoModeStartModel.
56
+ const validationIdx = source.indexOf("ctx.modelRegistry.getAvailable()");
57
+ assert.ok(validationIdx > -1, "auto-start.ts should validate preferred model against getAvailable()");
58
+
59
+ const resolveModelIdIdx = source.indexOf("resolveModelId");
60
+ assert.ok(resolveModelIdIdx > -1, "auto-start.ts should resolve preferred model against the registry");
61
+
62
+ const warningIdx = source.indexOf("is not configured; falling back to session default");
63
+ assert.ok(warningIdx > -1, "auto-start.ts should warn when preferred model is unconfigured");
64
+ });
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Regression test for #3674 — block direct writes to gsd.db
3
+ *
4
+ * When gsd_complete_task was unavailable, agents fell back to shell-based
5
+ * sqlite3 writes, corrupting the WAL-backed database. The fix extends
6
+ * write-intercept to block file writes and bash commands targeting gsd.db.
7
+ */
8
+
9
+ import { describe, test } from 'node:test';
10
+ import assert from 'node:assert/strict';
11
+ import { isBlockedStateFile, isBashWriteToStateFile } from '../write-intercept.ts';
12
+
13
+ describe('isBlockedStateFile blocks gsd.db paths (#3674)', () => {
14
+ test('blocks .gsd/gsd.db', () => {
15
+ assert.ok(isBlockedStateFile('/project/.gsd/gsd.db'));
16
+ });
17
+
18
+ test('blocks .gsd/gsd.db-wal', () => {
19
+ assert.ok(isBlockedStateFile('/project/.gsd/gsd.db-wal'));
20
+ });
21
+
22
+ test('blocks .gsd/gsd.db-shm', () => {
23
+ assert.ok(isBlockedStateFile('/project/.gsd/gsd.db-shm'));
24
+ });
25
+
26
+ test('blocks resolved symlink path under .gsd/projects/', () => {
27
+ assert.ok(isBlockedStateFile('/home/user/.gsd/projects/myproj/gsd.db'));
28
+ });
29
+
30
+ test('still blocks STATE.md', () => {
31
+ assert.ok(isBlockedStateFile('/project/.gsd/STATE.md'));
32
+ });
33
+
34
+ test('does not block other .gsd files', () => {
35
+ assert.ok(!isBlockedStateFile('/project/.gsd/DECISIONS.md'));
36
+ });
37
+ });
38
+
39
+ describe('isBashWriteToStateFile blocks DB shell commands (#3674)', () => {
40
+ test('blocks sqlite3 targeting gsd.db', () => {
41
+ assert.ok(isBashWriteToStateFile('sqlite3 .gsd/gsd.db "INSERT INTO ..."'));
42
+ });
43
+
44
+ test('blocks better-sqlite3 targeting gsd.db', () => {
45
+ assert.ok(isBashWriteToStateFile('node -e "require(\'better-sqlite3\')(\'.gsd/gsd.db\')"'));
46
+ });
47
+
48
+ test('blocks shell redirect to gsd.db', () => {
49
+ assert.ok(isBashWriteToStateFile('echo data > .gsd/gsd.db'));
50
+ });
51
+
52
+ test('blocks cp to gsd.db', () => {
53
+ assert.ok(isBashWriteToStateFile('cp backup.db .gsd/gsd.db'));
54
+ });
55
+
56
+ test('blocks mv to gsd.db', () => {
57
+ assert.ok(isBashWriteToStateFile('mv temp.db .gsd/gsd.db'));
58
+ });
59
+
60
+ test('does not block reading gsd.db with cat', () => {
61
+ assert.ok(!isBashWriteToStateFile('cat .gsd/gsd.db'));
62
+ });
63
+ });
@@ -0,0 +1,167 @@
1
+ /**
2
+ * complete-slice gate closure integration test.
3
+ *
4
+ * Pins the fix for the Q8-stall bug: complete-slice must close every gate
5
+ * owned by the complete-slice turn based on the content of the matching
6
+ * CompleteSliceParams field. Without this, Q8 stays pending forever and
7
+ * blocks state derivation on subsequent loops.
8
+ */
9
+
10
+ import { describe, test, beforeEach, afterEach } from "node:test";
11
+ import assert from "node:assert/strict";
12
+ import * as fs from "node:fs";
13
+ import * as path from "node:path";
14
+ import * as os from "node:os";
15
+
16
+ import {
17
+ openDatabase,
18
+ closeDatabase,
19
+ insertMilestone,
20
+ insertSlice,
21
+ insertTask,
22
+ insertGateRow,
23
+ getGateResults,
24
+ } from "../gsd-db.ts";
25
+ import { handleCompleteSlice } from "../tools/complete-slice.ts";
26
+ import type { CompleteSliceParams } from "../types.ts";
27
+
28
+ function makeValidSliceParams(overrides: Partial<CompleteSliceParams> = {}): CompleteSliceParams {
29
+ return {
30
+ sliceId: "S01",
31
+ milestoneId: "M001",
32
+ sliceTitle: "Test Slice",
33
+ oneLiner: "Implemented test slice",
34
+ narrative: "Built and tested.",
35
+ verification: "All tests pass.",
36
+ deviations: "None.",
37
+ knownLimitations: "None.",
38
+ followUps: "None.",
39
+ keyFiles: ["src/foo.ts"],
40
+ keyDecisions: [],
41
+ patternsEstablished: [],
42
+ observabilitySurfaces: [],
43
+ provides: [],
44
+ requirementsSurfaced: [],
45
+ drillDownPaths: [],
46
+ affects: [],
47
+ requirementsAdvanced: [],
48
+ requirementsValidated: [],
49
+ requirementsInvalidated: [],
50
+ filesModified: [],
51
+ requires: [],
52
+ uatContent: "## Smoke Test\n\nVerify happy path.",
53
+ ...overrides,
54
+ };
55
+ }
56
+
57
+ describe("complete-slice closes complete-slice-owned gates", () => {
58
+ let dbPath: string;
59
+ let basePath: string;
60
+
61
+ beforeEach(() => {
62
+ dbPath = path.join(
63
+ fs.mkdtempSync(path.join(os.tmpdir(), "gsd-slice-gate-")),
64
+ "test.db",
65
+ );
66
+ openDatabase(dbPath);
67
+
68
+ basePath = fs.mkdtempSync(path.join(os.tmpdir(), "gsd-slice-gate-handler-"));
69
+ const sliceDir = path.join(
70
+ basePath, ".gsd", "milestones", "M001", "slices", "S01", "tasks",
71
+ );
72
+ fs.mkdirSync(sliceDir, { recursive: true });
73
+ fs.writeFileSync(
74
+ path.join(basePath, ".gsd", "milestones", "M001", "M001-ROADMAP.md"),
75
+ [
76
+ "# M001: Test Milestone",
77
+ "",
78
+ "## Slices",
79
+ "",
80
+ '- [ ] **S01: Test Slice** `risk:medium` `depends:[]`',
81
+ " - After this: basic functionality works",
82
+ ].join("\n"),
83
+ );
84
+
85
+ insertMilestone({ id: "M001" });
86
+ insertSlice({ id: "S01", milestoneId: "M001" });
87
+ insertTask({
88
+ id: "T01", sliceId: "S01", milestoneId: "M001",
89
+ status: "complete", title: "Task 1",
90
+ });
91
+
92
+ // Seed Q8 as pending — this is what plan-slice does today.
93
+ insertGateRow({
94
+ milestoneId: "M001", sliceId: "S01",
95
+ gateId: "Q8", scope: "slice",
96
+ });
97
+ });
98
+
99
+ afterEach(() => {
100
+ closeDatabase();
101
+ fs.rmSync(path.dirname(dbPath), { recursive: true, force: true });
102
+ fs.rmSync(basePath, { recursive: true, force: true });
103
+ });
104
+
105
+ test("Q8 closes as 'pass' when operationalReadiness is populated", async () => {
106
+ const params = makeValidSliceParams({
107
+ operationalReadiness: [
108
+ "- Health signal: /health endpoint returns 200",
109
+ "- Failure signal: error rate alert in observability dashboard",
110
+ "- Recovery: systemd auto-restart",
111
+ ].join("\n"),
112
+ });
113
+
114
+ const result = await handleCompleteSlice(params, basePath);
115
+ assert.ok(!("error" in result), `handler failed: ${(result as any).error}`);
116
+
117
+ const gates = getGateResults("M001", "S01", "slice");
118
+ const q8 = gates.find((g) => g.gate_id === "Q8");
119
+ assert.ok(q8, "Q8 row must exist after complete-slice");
120
+ assert.equal(q8.status, "complete");
121
+ assert.equal(q8.verdict, "pass");
122
+ assert.ok(
123
+ q8.findings.includes("Health signal"),
124
+ "Q8 findings must capture the operationalReadiness content",
125
+ );
126
+ });
127
+
128
+ test("Q8 closes as 'omitted' when operationalReadiness is empty", async () => {
129
+ const params = makeValidSliceParams({ operationalReadiness: "" });
130
+
131
+ const result = await handleCompleteSlice(params, basePath);
132
+ assert.ok(!("error" in result), `handler failed: ${(result as any).error}`);
133
+
134
+ const gates = getGateResults("M001", "S01", "slice");
135
+ const q8 = gates.find((g) => g.gate_id === "Q8");
136
+ assert.ok(q8, "Q8 row must exist after complete-slice");
137
+ assert.equal(q8.status, "complete");
138
+ assert.equal(q8.verdict, "omitted");
139
+ });
140
+
141
+ test("Q8 also closes when operationalReadiness is omitted entirely", async () => {
142
+ // A model that doesn't pass operationalReadiness at all must still
143
+ // move Q8 out of 'pending' — leaving it pending produces the stall.
144
+ const params = makeValidSliceParams();
145
+ const result = await handleCompleteSlice(params, basePath);
146
+ assert.ok(!("error" in result), `handler failed: ${(result as any).error}`);
147
+
148
+ const gates = getGateResults("M001", "S01", "slice");
149
+ const q8 = gates.find((g) => g.gate_id === "Q8");
150
+ assert.ok(q8);
151
+ assert.notEqual(q8.status, "pending", "Q8 must never remain pending after complete-slice");
152
+ assert.equal(q8.verdict, "omitted");
153
+ });
154
+
155
+ test("summary markdown contains Operational Readiness section", async () => {
156
+ const params = makeValidSliceParams({
157
+ operationalReadiness: "- Health signal: /health\n- Failure signal: alert",
158
+ });
159
+ const result = await handleCompleteSlice(params, basePath);
160
+ assert.ok(!("error" in result));
161
+ if (!("error" in result)) {
162
+ const summary = fs.readFileSync(result.summaryPath, "utf-8");
163
+ assert.match(summary, /^## Operational Readiness/m);
164
+ assert.match(summary, /Health signal: \/health/);
165
+ }
166
+ });
167
+ });
@@ -0,0 +1,57 @@
1
+ /**
2
+ * definition-io.ts — unit tests for readFrozenDefinition.
3
+ */
4
+
5
+ import { describe, test, beforeEach, afterEach } from "node:test";
6
+ import assert from "node:assert/strict";
7
+ import { mkdtempSync, mkdirSync, writeFileSync, rmSync, realpathSync } from "node:fs";
8
+ import { join } from "node:path";
9
+ import { tmpdir } from "node:os";
10
+
11
+ import { readFrozenDefinition } from "../definition-io.ts";
12
+
13
+ function createTmpDir(): string {
14
+ return realpathSync(mkdtempSync(join(tmpdir(), "gsd-defio-test-")));
15
+ }
16
+
17
+ describe("readFrozenDefinition", () => {
18
+ let runDir: string;
19
+
20
+ beforeEach(() => {
21
+ runDir = createTmpDir();
22
+ });
23
+
24
+ afterEach(() => {
25
+ rmSync(runDir, { recursive: true, force: true });
26
+ });
27
+
28
+ test("parses a valid DEFINITION.yaml", () => {
29
+ const yaml = [
30
+ "version: 1",
31
+ "name: test-workflow",
32
+ "description: A test workflow",
33
+ "steps:",
34
+ " - id: step-1",
35
+ " prompt: do the thing",
36
+ ].join("\n");
37
+ writeFileSync(join(runDir, "DEFINITION.yaml"), yaml, "utf-8");
38
+
39
+ const def = readFrozenDefinition(runDir);
40
+ assert.equal(def.version, 1);
41
+ assert.equal(def.name, "test-workflow");
42
+ assert.equal(def.description, "A test workflow");
43
+ assert.equal(def.steps.length, 1);
44
+ assert.equal(def.steps[0].id, "step-1");
45
+ });
46
+
47
+ test("throws when DEFINITION.yaml is missing", () => {
48
+ assert.throws(() => readFrozenDefinition(runDir), {
49
+ code: "ENOENT",
50
+ });
51
+ });
52
+
53
+ test("throws on malformed YAML", () => {
54
+ writeFileSync(join(runDir, "DEFINITION.yaml"), ": : : not valid yaml [", "utf-8");
55
+ assert.throws(() => readFrozenDefinition(runDir));
56
+ });
57
+ });
@@ -172,6 +172,32 @@ test("dispatch guard ignores positionally-earlier reverse dependents for zero-de
172
172
  );
173
173
  });
174
174
 
175
+ test("dispatch guard treats zero-dependency slices as independent when a milestone uses explicit deps (#3998)", (t) => {
176
+ const repo = setupRepo();
177
+ t.after(() => teardownRepo(repo));
178
+
179
+ mkdirSync(join(repo, ".gsd", "milestones", "M022"), { recursive: true });
180
+
181
+ insertMilestone({ id: "M022", title: "Mixed dependency milestone" });
182
+ insertSlice({ id: "S02", milestoneId: "M022", title: "Core A", status: "complete", depends: [], sequence: 2 });
183
+ insertSlice({ id: "S03", milestoneId: "M022", title: "Core B", status: "complete", depends: [], sequence: 3 });
184
+ insertSlice({ id: "S05", milestoneId: "M022", title: "Blocked integration", status: "pending", depends: ["S02", "S03", "S07"], sequence: 5 });
185
+ insertSlice({ id: "S06", milestoneId: "M022", title: "Independent zero-dep slice", status: "pending", depends: [], sequence: 6 });
186
+ insertSlice({ id: "S07", milestoneId: "M022", title: "Late prerequisite", status: "pending", depends: ["S02"], sequence: 7 });
187
+
188
+ writeFileSync(join(repo, ".gsd", "milestones", "M022", "M022-ROADMAP.md"), "# M022\n");
189
+
190
+ assert.equal(
191
+ getPriorSliceCompletionBlocker(repo, "main", "execute-task", "M022/S06/T02"),
192
+ null,
193
+ );
194
+
195
+ assert.equal(
196
+ getPriorSliceCompletionBlocker(repo, "main", "execute-task", "M022/S05/T01"),
197
+ "Cannot dispatch execute-task M022/S05/T01: dependency slice M022/S07 is not complete.",
198
+ );
199
+ });
200
+
175
201
  test("dispatch guard allows slice with all declared dependencies complete", (t) => {
176
202
  const repo = setupRepo();
177
203
  t.after(() => teardownRepo(repo));
@@ -0,0 +1,14 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { isDoctorHealActionable } from "../commands-handlers.js";
4
+
5
+ test("doctor heal actionable filter keeps fixable warnings and errors", () => {
6
+ assert.equal(isDoctorHealActionable({ fixable: true, severity: "warning" }), true);
7
+ assert.equal(isDoctorHealActionable({ fixable: true, severity: "error" }), true);
8
+ });
9
+
10
+ test("doctor heal actionable filter excludes info and non-fixable issues", () => {
11
+ assert.equal(isDoctorHealActionable({ fixable: true, severity: "info" }), false);
12
+ assert.equal(isDoctorHealActionable({ fixable: false, severity: "warning" }), false);
13
+ assert.equal(isDoctorHealActionable({ fixable: false, severity: "error" }), false);
14
+ });
@@ -574,6 +574,42 @@ test("runProviderChecks reports ok for OpenAI via openai-codex auth.json (#2922)
574
574
  rmSync(tmpHome, { recursive: true, force: true });
575
575
  });
576
576
 
577
+ test("runProviderChecks reports ok for claude-code without any API key", () => {
578
+ const repo = realpathSync(mkdtempSync(join(tmpdir(), "gsd-providers-cc-repo-")));
579
+ mkdirSync(join(repo, ".gsd"), { recursive: true });
580
+ writeFileSync(
581
+ join(repo, ".gsd", "PREFERENCES.md"),
582
+ [
583
+ "---",
584
+ "models:",
585
+ " execution:",
586
+ " model: claude-sonnet-4-6",
587
+ " provider: claude-code",
588
+ "---",
589
+ "",
590
+ ].join("\n"),
591
+ );
592
+
593
+ const tmpHome = realpathSync(mkdtempSync(join(tmpdir(), "gsd-providers-cc-home-")));
594
+
595
+ withEnv({
596
+ HOME: tmpHome,
597
+ ANTHROPIC_API_KEY: undefined,
598
+ ANTHROPIC_OAUTH_TOKEN: undefined,
599
+ }, () => {
600
+ withCwd(repo, () => {
601
+ const results = runProviderChecks();
602
+ const cc = results.find(r => r.name === "claude-code");
603
+ assert.ok(cc, "claude-code result should exist");
604
+ assert.equal(cc!.status, "ok", "claude-code uses CLI auth — must be ok without API keys");
605
+ assert.ok(cc!.message.includes("CLI auth"), "should indicate CLI auth");
606
+ });
607
+ });
608
+
609
+ rmSync(repo, { recursive: true, force: true });
610
+ rmSync(tmpHome, { recursive: true, force: true });
611
+ });
612
+
577
613
  test("PROVIDER_ROUTES includes google-gemini-cli as route for google (#2922)", async () => {
578
614
  const { readFileSync: readFS } = await import("node:fs");
579
615
  const { dirname: dirn, join: joinPath } = await import("node:path");
@@ -0,0 +1,104 @@
1
+ /**
2
+ * false-degraded-mode-warning.test.ts — Regression tests for #3922.
3
+ *
4
+ * Before this fix, deriveState() logged a "DB unavailable — degraded mode"
5
+ * warning even when the DB simply hadn't been opened yet (e.g. during
6
+ * before_agent_start context injection). The fix introduces wasDbOpenAttempted()
7
+ * to distinguish "not yet initialized" from "genuinely unavailable."
8
+ *
9
+ * Two aspects:
10
+ * 1. gsd-db: wasDbOpenAttempted() tracks whether openDatabase() was ever called.
11
+ * 2. state: the degraded-mode warning is gated behind wasDbOpenAttempted().
12
+ */
13
+
14
+ import { describe, test } from "node:test";
15
+ import assert from "node:assert/strict";
16
+ import { readFileSync } from "node:fs";
17
+ import { dirname, join } from "node:path";
18
+ import { fileURLToPath } from "node:url";
19
+ import {
20
+ openDatabase,
21
+ closeDatabase,
22
+ isDbAvailable,
23
+ wasDbOpenAttempted,
24
+ } from "../gsd-db.ts";
25
+
26
+ const __dirname = dirname(fileURLToPath(import.meta.url));
27
+ const stateSource = readFileSync(join(__dirname, "..", "state.ts"), "utf-8");
28
+
29
+ // ═══════════════════════════════════════════════════════════════════════════
30
+ // 1. gsd-db: wasDbOpenAttempted flag
31
+ // ═══════════════════════════════════════════════════════════════════════════
32
+
33
+ describe("wasDbOpenAttempted (#3922)", () => {
34
+
35
+ test("wasDbOpenAttempted returns true after openDatabase is called", () => {
36
+ // By this point in the test suite, openDatabase may or may not have been
37
+ // called by other tests. So we call it explicitly and verify it returns true.
38
+ openDatabase(":memory:");
39
+ assert.strictEqual(wasDbOpenAttempted(), true,
40
+ "wasDbOpenAttempted should be true after openDatabase call");
41
+ closeDatabase();
42
+ });
43
+
44
+ test("openDatabase sets the flag even if it fails on invalid path", () => {
45
+ // openDatabase with an unreachable path may fail, but the flag should
46
+ // still be set because the attempt was made.
47
+ try { openDatabase("/nonexistent/path/that/will/fail.db"); } catch { /* expected */ }
48
+ assert.strictEqual(wasDbOpenAttempted(), true,
49
+ "wasDbOpenAttempted should be true even after a failed open attempt");
50
+ });
51
+ });
52
+
53
+ // ═══════════════════════════════════════════════════════════════════════════
54
+ // 2. state.ts: degraded-mode warning is gated behind wasDbOpenAttempted
55
+ // ═══════════════════════════════════════════════════════════════════════════
56
+
57
+ describe("degraded-mode warning guard (#3922)", () => {
58
+
59
+ test("state.ts imports wasDbOpenAttempted from gsd-db", () => {
60
+ assert.ok(
61
+ stateSource.includes("wasDbOpenAttempted"),
62
+ "state.ts must import wasDbOpenAttempted to gate the degraded-mode warning",
63
+ );
64
+ });
65
+
66
+ test("degraded-mode warning is inside a wasDbOpenAttempted() guard", () => {
67
+ // Find the degraded-mode warning string
68
+ const warningStr = 'DB unavailable — using filesystem state derivation (degraded mode)';
69
+ const warningIdx = stateSource.indexOf(warningStr);
70
+ assert.ok(warningIdx > 0, "degraded-mode warning string must exist in state.ts");
71
+
72
+ // The wasDbOpenAttempted() check must appear BEFORE the warning,
73
+ // within the same else-branch (i.e. within a reasonable distance).
74
+ // Look backwards from the warning for the guard.
75
+ const searchWindow = stateSource.slice(Math.max(0, warningIdx - 300), warningIdx);
76
+ assert.ok(
77
+ searchWindow.includes("wasDbOpenAttempted()"),
78
+ "wasDbOpenAttempted() guard must appear shortly before the degraded-mode warning " +
79
+ "to prevent false warnings when DB has not been initialized yet",
80
+ );
81
+ });
82
+
83
+ test("warning is NOT emitted unconditionally in the else branch", () => {
84
+ // The old code had `logWarning(...)` directly in the else branch.
85
+ // The fix wraps it in `if (wasDbOpenAttempted())`.
86
+ // Verify the logWarning call is inside a conditional, not bare.
87
+ const lines = stateSource.split("\n");
88
+ for (let i = 0; i < lines.length; i++) {
89
+ if (lines[i]!.includes("DB unavailable") && lines[i]!.includes("degraded mode")) {
90
+ // This line has the warning. Check that the preceding non-empty line
91
+ // contains an if-condition (wasDbOpenAttempted), not a bare else.
92
+ let prev = i - 1;
93
+ while (prev >= 0 && lines[prev]!.trim() === "") prev--;
94
+ const prevLine = lines[prev]!.trim();
95
+ assert.ok(
96
+ prevLine.includes("wasDbOpenAttempted"),
97
+ `Line ${i + 1} emits degraded-mode warning — preceding line ${prev + 1} must ` +
98
+ `contain wasDbOpenAttempted guard, but found: "${prevLine}"`,
99
+ );
100
+ break;
101
+ }
102
+ }
103
+ });
104
+ });