gsd-pi 2.70.1 → 2.71.0-dev.4c35d99

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (533) hide show
  1. package/README.md +57 -17
  2. package/dist/cli.js +29 -3
  3. package/dist/headless-events.d.ts +2 -0
  4. package/dist/headless-events.js +7 -0
  5. package/dist/headless.js +16 -3
  6. package/dist/mcp-server.js +40 -17
  7. package/dist/provider-migrations.d.ts +10 -0
  8. package/dist/provider-migrations.js +12 -0
  9. package/dist/resource-loader.js +139 -13
  10. package/dist/resources/GSD-WORKFLOW.md +1 -1
  11. package/dist/resources/agents/debugger.md +58 -0
  12. package/dist/resources/agents/doc-writer.md +43 -0
  13. package/dist/resources/agents/git-ops.md +56 -0
  14. package/dist/resources/agents/javascript-pro.md +46 -271
  15. package/dist/resources/agents/planner.md +55 -0
  16. package/dist/resources/agents/refactorer.md +47 -0
  17. package/dist/resources/agents/reviewer.md +48 -0
  18. package/dist/resources/agents/security.md +59 -0
  19. package/dist/resources/agents/tester.md +50 -0
  20. package/dist/resources/agents/typescript-pro.md +41 -235
  21. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +242 -40
  22. package/dist/resources/extensions/get-secrets-from-user.js +17 -1
  23. package/dist/resources/extensions/gsd/auto/infra-errors.js +34 -0
  24. package/dist/resources/extensions/gsd/auto/loop.js +32 -1
  25. package/dist/resources/extensions/gsd/auto/phases.js +5 -1
  26. package/dist/resources/extensions/gsd/auto/session.js +11 -0
  27. package/dist/resources/extensions/gsd/auto-dashboard.js +22 -16
  28. package/dist/resources/extensions/gsd/auto-model-selection.js +10 -2
  29. package/dist/resources/extensions/gsd/auto-prompts.js +88 -33
  30. package/dist/resources/extensions/gsd/auto-start.js +37 -18
  31. package/dist/resources/extensions/gsd/auto-tool-tracking.js +1 -1
  32. package/dist/resources/extensions/gsd/auto-worktree.js +1 -1
  33. package/dist/resources/extensions/gsd/auto.js +56 -0
  34. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +3 -3
  35. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +6 -0
  36. package/dist/resources/extensions/gsd/bootstrap/register-shortcuts.js +63 -51
  37. package/dist/resources/extensions/gsd/bootstrap/system-context.js +6 -0
  38. package/dist/resources/extensions/gsd/commands/context.js +15 -6
  39. package/dist/resources/extensions/gsd/commands/dispatcher.js +12 -2
  40. package/dist/resources/extensions/gsd/commands/handlers/auto.js +10 -33
  41. package/dist/resources/extensions/gsd/commands/handlers/core.js +56 -11
  42. package/dist/resources/extensions/gsd/commands/handlers/notifications-handler.js +15 -6
  43. package/dist/resources/extensions/gsd/commands/handlers/workflow.js +4 -10
  44. package/dist/resources/extensions/gsd/custom-workflow-engine.js +16 -12
  45. package/dist/resources/extensions/gsd/dashboard-overlay.js +8 -3
  46. package/dist/resources/extensions/gsd/dispatch-guard.js +18 -1
  47. package/dist/resources/extensions/gsd/doctor-providers.js +23 -0
  48. package/dist/resources/extensions/gsd/error-classifier.js +1 -1
  49. package/dist/resources/extensions/gsd/file-lock.js +60 -0
  50. package/dist/resources/extensions/gsd/forensics.js +19 -6
  51. package/dist/resources/extensions/gsd/gate-registry.js +208 -0
  52. package/dist/resources/extensions/gsd/gsd-db.js +41 -0
  53. package/dist/resources/extensions/gsd/guided-flow.js +17 -20
  54. package/dist/resources/extensions/gsd/init-wizard.js +3 -11
  55. package/dist/resources/extensions/gsd/metrics.js +1 -0
  56. package/dist/resources/extensions/gsd/milestone-actions.js +10 -4
  57. package/dist/resources/extensions/gsd/milestone-validation-gates.js +11 -12
  58. package/dist/resources/extensions/gsd/notification-overlay.js +42 -13
  59. package/dist/resources/extensions/gsd/notification-store.js +56 -5
  60. package/dist/resources/extensions/gsd/notification-widget.js +5 -13
  61. package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +8 -3
  62. package/dist/resources/extensions/gsd/pre-execution-checks.js +35 -2
  63. package/dist/resources/extensions/gsd/prompt-validation.js +126 -0
  64. package/dist/resources/extensions/gsd/prompts/complete-slice.md +5 -3
  65. package/dist/resources/extensions/gsd/prompts/discuss.md +33 -13
  66. package/dist/resources/extensions/gsd/prompts/execute-task.md +22 -19
  67. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +2 -0
  68. package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +2 -0
  69. package/dist/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
  70. package/dist/resources/extensions/gsd/prompts/queue.md +3 -2
  71. package/dist/resources/extensions/gsd/prompts/system.md +1 -0
  72. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +4 -1
  73. package/dist/resources/extensions/gsd/session-model-override.js +25 -0
  74. package/dist/resources/extensions/gsd/shortcut-defs.js +40 -0
  75. package/dist/resources/extensions/gsd/state.js +241 -332
  76. package/dist/resources/extensions/gsd/tools/complete-slice.js +52 -1
  77. package/dist/resources/extensions/gsd/tools/complete-task.js +51 -1
  78. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +38 -1
  79. package/dist/resources/extensions/gsd/workflow-events.js +25 -13
  80. package/dist/resources/extensions/gsd/workflow-mcp-auto-prep.js +56 -0
  81. package/dist/resources/extensions/gsd/workflow-mcp.js +1 -1
  82. package/dist/resources/extensions/ollama/index.js +13 -5
  83. package/dist/resources/extensions/shared/gsd-phase-state.js +35 -0
  84. package/dist/resources/extensions/subagent/agents.js +8 -0
  85. package/dist/resources/extensions/subagent/index.js +17 -0
  86. package/dist/resources/skills/create-skill/SKILL.md +2 -0
  87. package/dist/startup-model-validation.d.ts +0 -1
  88. package/dist/startup-model-validation.js +6 -2
  89. package/dist/web/standalone/.next/BUILD_ID +1 -1
  90. package/dist/web/standalone/.next/app-path-routes-manifest.json +17 -17
  91. package/dist/web/standalone/.next/build-manifest.json +4 -4
  92. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  93. package/dist/web/standalone/.next/react-loadable-manifest.json +1 -1
  94. package/dist/web/standalone/.next/required-server-files.json +3 -3
  95. package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
  96. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  97. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  98. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  99. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  100. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  101. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  102. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  103. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  104. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  105. package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
  106. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  107. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  108. package/dist/web/standalone/.next/server/app/_not-found.rsc +3 -3
  109. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
  110. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  111. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
  112. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  113. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  114. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  115. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  116. package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
  117. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
  118. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
  119. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
  120. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
  121. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
  122. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
  123. package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
  124. package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
  125. package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
  126. package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
  127. package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
  128. package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
  129. package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
  130. package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
  131. package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
  132. package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
  133. package/dist/web/standalone/.next/server/app/api/experimental/route.js +2 -2
  134. package/dist/web/standalone/.next/server/app/api/experimental/route_client-reference-manifest.js +1 -1
  135. package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
  136. package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
  137. package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
  138. package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
  139. package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
  140. package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
  141. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  142. package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
  143. package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
  144. package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
  145. package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
  146. package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
  147. package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
  148. package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
  149. package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
  150. package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
  151. package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
  152. package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
  153. package/dist/web/standalone/.next/server/app/api/notifications/route.js +2 -2
  154. package/dist/web/standalone/.next/server/app/api/notifications/route_client-reference-manifest.js +1 -1
  155. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  156. package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
  157. package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
  158. package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
  159. package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
  160. package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
  161. package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
  162. package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
  163. package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +2 -2
  164. package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
  165. package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
  166. package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
  167. package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
  168. package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
  169. package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
  170. package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
  171. package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
  172. package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
  173. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  174. package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
  175. package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
  176. package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
  177. package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
  178. package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
  179. package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
  180. package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
  181. package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
  182. package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
  183. package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +1 -1
  184. package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
  185. package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
  186. package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
  187. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +1 -1
  188. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
  189. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +2 -2
  190. package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
  191. package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
  192. package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
  193. package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
  194. package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
  195. package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
  196. package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  197. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  198. package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
  199. package/dist/web/standalone/.next/server/app/index.html +1 -1
  200. package/dist/web/standalone/.next/server/app/index.rsc +4 -4
  201. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  202. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +4 -4
  203. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  204. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +3 -3
  205. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  206. package/dist/web/standalone/.next/server/app/page.js +2 -2
  207. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  208. package/dist/web/standalone/.next/server/app-paths-manifest.json +17 -17
  209. package/dist/web/standalone/.next/server/chunks/63.js +3 -3
  210. package/dist/web/standalone/.next/server/chunks/6897.js +1 -1
  211. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  212. package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
  213. package/dist/web/standalone/.next/server/middleware.js +2 -2
  214. package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
  215. package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
  216. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  217. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  218. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  219. package/dist/web/standalone/.next/static/chunks/2826.dd3dc8bbd3025fa5.js +9 -0
  220. package/dist/web/standalone/.next/static/chunks/app/_not-found/{page-2f24283c162b6ab3.js → page-f2a7482d42a5614b.js} +1 -1
  221. package/dist/web/standalone/.next/static/chunks/app/{layout-9ecfd95f343793f0.js → layout-a16c7a7ecdf0c2cf.js} +1 -1
  222. package/dist/web/standalone/.next/static/chunks/app/page-f1e30ab6bb269149.js +1 -0
  223. package/dist/web/standalone/.next/static/chunks/main-app-fdab67f7802d7832.js +1 -0
  224. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-459824ffb8c323dd.js +1 -0
  225. package/dist/web/standalone/.next/static/chunks/{webpack-6e4d7e9a4f57bed4.js → webpack-b868033a5834586d.js} +1 -1
  226. package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
  227. package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
  228. package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
  229. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api.target.mk +14 -14
  230. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_except.target.mk +14 -14
  231. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_maybe.target.mk +14 -14
  232. package/dist/web/standalone/server.js +1 -1
  233. package/package.json +1 -1
  234. package/packages/mcp-server/dist/env-writer.d.ts +39 -0
  235. package/packages/mcp-server/dist/env-writer.d.ts.map +1 -0
  236. package/packages/mcp-server/dist/env-writer.js +158 -0
  237. package/packages/mcp-server/dist/env-writer.js.map +1 -0
  238. package/packages/mcp-server/dist/server.d.ts +23 -3
  239. package/packages/mcp-server/dist/server.d.ts.map +1 -1
  240. package/packages/mcp-server/dist/server.js +192 -44
  241. package/packages/mcp-server/dist/server.js.map +1 -1
  242. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  243. package/packages/mcp-server/dist/workflow-tools.js +22 -12
  244. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  245. package/packages/mcp-server/src/env-writer.test.ts +280 -0
  246. package/packages/mcp-server/src/env-writer.ts +183 -0
  247. package/packages/mcp-server/src/secure-env-collect.test.ts +265 -0
  248. package/packages/mcp-server/src/server.ts +247 -41
  249. package/packages/mcp-server/src/workflow-tools.test.ts +110 -0
  250. package/packages/mcp-server/src/workflow-tools.ts +32 -12
  251. package/packages/pi-ai/dist/providers/amazon-bedrock.js +11 -2
  252. package/packages/pi-ai/dist/providers/amazon-bedrock.js.map +1 -1
  253. package/packages/pi-ai/dist/providers/anthropic-auth.test.d.ts +2 -0
  254. package/packages/pi-ai/dist/providers/anthropic-auth.test.d.ts.map +1 -0
  255. package/packages/pi-ai/dist/providers/anthropic-auth.test.js +20 -0
  256. package/packages/pi-ai/dist/providers/anthropic-auth.test.js.map +1 -0
  257. package/packages/pi-ai/dist/providers/anthropic-shared.d.ts +4 -1
  258. package/packages/pi-ai/dist/providers/anthropic-shared.d.ts.map +1 -1
  259. package/packages/pi-ai/dist/providers/anthropic-shared.js +8 -3
  260. package/packages/pi-ai/dist/providers/anthropic-shared.js.map +1 -1
  261. package/packages/pi-ai/dist/providers/anthropic-shared.test.js +44 -1
  262. package/packages/pi-ai/dist/providers/anthropic-shared.test.js.map +1 -1
  263. package/packages/pi-ai/dist/providers/anthropic.d.ts +2 -1
  264. package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
  265. package/packages/pi-ai/dist/providers/anthropic.js +7 -4
  266. package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
  267. package/packages/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
  268. package/packages/pi-ai/dist/providers/openai-completions.js +11 -0
  269. package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
  270. package/packages/pi-ai/src/providers/amazon-bedrock.ts +13 -1
  271. package/packages/pi-ai/src/providers/anthropic-auth.test.ts +32 -0
  272. package/packages/pi-ai/src/providers/anthropic-shared.test.ts +55 -1
  273. package/packages/pi-ai/src/providers/anthropic-shared.ts +14 -3
  274. package/packages/pi-ai/src/providers/anthropic.ts +8 -4
  275. package/packages/pi-ai/src/providers/openai-completions.ts +14 -0
  276. package/packages/pi-coding-agent/dist/core/agent-session-renderable-tools.test.d.ts +2 -0
  277. package/packages/pi-coding-agent/dist/core/agent-session-renderable-tools.test.d.ts.map +1 -0
  278. package/packages/pi-coding-agent/dist/core/agent-session-renderable-tools.test.js +61 -0
  279. package/packages/pi-coding-agent/dist/core/agent-session-renderable-tools.test.js.map +1 -0
  280. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  281. package/packages/pi-coding-agent/dist/core/agent-session.js +2 -1
  282. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  283. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts +10 -0
  284. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
  285. package/packages/pi-coding-agent/dist/core/auth-storage.js +27 -0
  286. package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
  287. package/packages/pi-coding-agent/dist/core/auth-storage.test.js +85 -0
  288. package/packages/pi-coding-agent/dist/core/auth-storage.test.js.map +1 -1
  289. package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.d.ts +2 -0
  290. package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.d.ts.map +1 -0
  291. package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js +388 -0
  292. package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js.map +1 -0
  293. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +2 -0
  294. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
  295. package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
  296. package/packages/pi-coding-agent/dist/core/model-resolver-initial-model-auth.test.d.ts +2 -0
  297. package/packages/pi-coding-agent/dist/core/model-resolver-initial-model-auth.test.d.ts.map +1 -0
  298. package/packages/pi-coding-agent/dist/core/model-resolver-initial-model-auth.test.js +64 -0
  299. package/packages/pi-coding-agent/dist/core/model-resolver-initial-model-auth.test.js.map +1 -0
  300. package/packages/pi-coding-agent/dist/core/model-resolver.d.ts.map +1 -1
  301. package/packages/pi-coding-agent/dist/core/model-resolver.js +22 -18
  302. package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
  303. package/packages/pi-coding-agent/dist/core/model-resolver.test.d.ts +8 -0
  304. package/packages/pi-coding-agent/dist/core/model-resolver.test.d.ts.map +1 -0
  305. package/packages/pi-coding-agent/dist/core/model-resolver.test.js +75 -0
  306. package/packages/pi-coding-agent/dist/core/model-resolver.test.js.map +1 -0
  307. package/packages/pi-coding-agent/dist/core/sdk.d.ts +11 -0
  308. package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
  309. package/packages/pi-coding-agent/dist/core/sdk.js +38 -5
  310. package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
  311. package/packages/pi-coding-agent/dist/core/sdk.test.d.ts +2 -0
  312. package/packages/pi-coding-agent/dist/core/sdk.test.d.ts.map +1 -0
  313. package/packages/pi-coding-agent/dist/core/sdk.test.js +71 -0
  314. package/packages/pi-coding-agent/dist/core/sdk.test.js.map +1 -0
  315. package/packages/pi-coding-agent/dist/index.d.ts +1 -1
  316. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  317. package/packages/pi-coding-agent/dist/index.js +1 -1
  318. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  319. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/login-dialog.test.d.ts +2 -0
  320. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/login-dialog.test.d.ts.map +1 -0
  321. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/login-dialog.test.js +13 -0
  322. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/login-dialog.test.js.map +1 -0
  323. package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.d.ts +19 -2
  324. package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.d.ts.map +1 -1
  325. package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.js +50 -1
  326. package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.js.map +1 -1
  327. package/packages/pi-coding-agent/dist/modes/interactive/components/extension-input.d.ts +1 -0
  328. package/packages/pi-coding-agent/dist/modes/interactive/components/extension-input.d.ts.map +1 -1
  329. package/packages/pi-coding-agent/dist/modes/interactive/components/extension-input.js +1 -0
  330. package/packages/pi-coding-agent/dist/modes/interactive/components/extension-input.js.map +1 -1
  331. package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.d.ts +4 -0
  332. package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
  333. package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.js +24 -2
  334. package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.js.map +1 -1
  335. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  336. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js +9 -2
  337. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js.map +1 -1
  338. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +4 -0
  339. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  340. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +43 -0
  341. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  342. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  343. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +175 -25
  344. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  345. package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.d.ts.map +1 -1
  346. package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.js +6 -1
  347. package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.js.map +1 -1
  348. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts +1 -0
  349. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts.map +1 -1
  350. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.js.map +1 -1
  351. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +6 -0
  352. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  353. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +62 -5
  354. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  355. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js +4 -2
  356. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js.map +1 -1
  357. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js +1 -1
  358. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js.map +1 -1
  359. package/packages/pi-coding-agent/dist/modes/rpc/rpc-types.d.ts +1 -0
  360. package/packages/pi-coding-agent/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  361. package/packages/pi-coding-agent/dist/modes/rpc/rpc-types.js.map +1 -1
  362. package/packages/pi-coding-agent/package.json +1 -1
  363. package/packages/pi-coding-agent/src/core/agent-session-renderable-tools.test.ts +70 -0
  364. package/packages/pi-coding-agent/src/core/agent-session.ts +2 -1
  365. package/packages/pi-coding-agent/src/core/auth-storage.test.ts +108 -0
  366. package/packages/pi-coding-agent/src/core/auth-storage.ts +30 -0
  367. package/packages/pi-coding-agent/src/core/chat-controller-ordering.test.ts +468 -0
  368. package/packages/pi-coding-agent/src/core/extensions/types.ts +2 -0
  369. package/packages/pi-coding-agent/src/core/model-resolver-initial-model-auth.test.ts +78 -0
  370. package/packages/pi-coding-agent/src/core/model-resolver.test.ts +85 -0
  371. package/packages/pi-coding-agent/src/core/model-resolver.ts +22 -18
  372. package/packages/pi-coding-agent/src/core/sdk.test.ts +89 -0
  373. package/packages/pi-coding-agent/src/core/sdk.ts +45 -9
  374. package/packages/pi-coding-agent/src/index.ts +1 -0
  375. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/login-dialog.test.ts +24 -0
  376. package/packages/pi-coding-agent/src/modes/interactive/components/dynamic-border.ts +58 -2
  377. package/packages/pi-coding-agent/src/modes/interactive/components/extension-input.ts +2 -0
  378. package/packages/pi-coding-agent/src/modes/interactive/components/login-dialog.ts +30 -2
  379. package/packages/pi-coding-agent/src/modes/interactive/components/model-selector.ts +15 -6
  380. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +47 -0
  381. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +205 -31
  382. package/packages/pi-coding-agent/src/modes/interactive/controllers/model-controller.ts +6 -1
  383. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode-state.ts +1 -0
  384. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +70 -5
  385. package/packages/pi-coding-agent/src/modes/interactive/slash-command-handlers.ts +4 -2
  386. package/packages/pi-coding-agent/src/modes/rpc/rpc-mode.ts +1 -1
  387. package/packages/pi-coding-agent/src/modes/rpc/rpc-types.ts +1 -0
  388. package/packages/pi-tui/dist/components/__tests__/input.test.js +9 -0
  389. package/packages/pi-tui/dist/components/__tests__/input.test.js.map +1 -1
  390. package/packages/pi-tui/dist/components/__tests__/markdown-maxlines.test.d.ts +2 -0
  391. package/packages/pi-tui/dist/components/__tests__/markdown-maxlines.test.d.ts.map +1 -0
  392. package/packages/pi-tui/dist/components/__tests__/markdown-maxlines.test.js +66 -0
  393. package/packages/pi-tui/dist/components/__tests__/markdown-maxlines.test.js.map +1 -0
  394. package/packages/pi-tui/dist/components/input.d.ts +2 -0
  395. package/packages/pi-tui/dist/components/input.d.ts.map +1 -1
  396. package/packages/pi-tui/dist/components/input.js +7 -4
  397. package/packages/pi-tui/dist/components/input.js.map +1 -1
  398. package/packages/pi-tui/dist/components/markdown.d.ts +3 -0
  399. package/packages/pi-tui/dist/components/markdown.d.ts.map +1 -1
  400. package/packages/pi-tui/dist/components/markdown.js +17 -1
  401. package/packages/pi-tui/dist/components/markdown.js.map +1 -1
  402. package/packages/pi-tui/src/components/__tests__/input.test.ts +11 -0
  403. package/packages/pi-tui/src/components/__tests__/markdown-maxlines.test.ts +75 -0
  404. package/packages/pi-tui/src/components/input.ts +7 -4
  405. package/packages/pi-tui/src/components/markdown.ts +22 -1
  406. package/pkg/package.json +1 -1
  407. package/src/resources/GSD-WORKFLOW.md +1 -1
  408. package/src/resources/agents/debugger.md +58 -0
  409. package/src/resources/agents/doc-writer.md +43 -0
  410. package/src/resources/agents/git-ops.md +56 -0
  411. package/src/resources/agents/javascript-pro.md +46 -271
  412. package/src/resources/agents/planner.md +55 -0
  413. package/src/resources/agents/refactorer.md +47 -0
  414. package/src/resources/agents/reviewer.md +48 -0
  415. package/src/resources/agents/security.md +59 -0
  416. package/src/resources/agents/tester.md +50 -0
  417. package/src/resources/agents/typescript-pro.md +41 -235
  418. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +288 -39
  419. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +330 -2
  420. package/src/resources/extensions/get-secrets-from-user.ts +24 -1
  421. package/src/resources/extensions/gsd/auto/infra-errors.ts +38 -0
  422. package/src/resources/extensions/gsd/auto/loop-deps.ts +2 -0
  423. package/src/resources/extensions/gsd/auto/loop.ts +45 -1
  424. package/src/resources/extensions/gsd/auto/phases.ts +6 -0
  425. package/src/resources/extensions/gsd/auto/session.ts +11 -0
  426. package/src/resources/extensions/gsd/auto-dashboard.ts +29 -18
  427. package/src/resources/extensions/gsd/auto-model-selection.ts +9 -1
  428. package/src/resources/extensions/gsd/auto-prompts.ts +111 -33
  429. package/src/resources/extensions/gsd/auto-start.ts +44 -20
  430. package/src/resources/extensions/gsd/auto-tool-tracking.ts +1 -1
  431. package/src/resources/extensions/gsd/auto-worktree.ts +1 -1
  432. package/src/resources/extensions/gsd/auto.ts +72 -0
  433. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +3 -3
  434. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +6 -0
  435. package/src/resources/extensions/gsd/bootstrap/register-shortcuts.ts +79 -60
  436. package/src/resources/extensions/gsd/bootstrap/system-context.ts +7 -0
  437. package/src/resources/extensions/gsd/commands/context.ts +16 -5
  438. package/src/resources/extensions/gsd/commands/dispatcher.ts +14 -2
  439. package/src/resources/extensions/gsd/commands/handlers/auto.ts +10 -36
  440. package/src/resources/extensions/gsd/commands/handlers/core.ts +58 -11
  441. package/src/resources/extensions/gsd/commands/handlers/notifications-handler.ts +17 -7
  442. package/src/resources/extensions/gsd/commands/handlers/workflow.ts +4 -10
  443. package/src/resources/extensions/gsd/custom-workflow-engine.ts +19 -14
  444. package/src/resources/extensions/gsd/dashboard-overlay.ts +10 -3
  445. package/src/resources/extensions/gsd/dispatch-guard.ts +18 -1
  446. package/src/resources/extensions/gsd/doctor-providers.ts +24 -0
  447. package/src/resources/extensions/gsd/error-classifier.ts +1 -1
  448. package/src/resources/extensions/gsd/file-lock.ts +59 -0
  449. package/src/resources/extensions/gsd/forensics.ts +23 -7
  450. package/src/resources/extensions/gsd/gate-registry.ts +251 -0
  451. package/src/resources/extensions/gsd/gsd-db.ts +51 -0
  452. package/src/resources/extensions/gsd/guided-flow.ts +17 -19
  453. package/src/resources/extensions/gsd/init-wizard.ts +3 -13
  454. package/src/resources/extensions/gsd/interrupted-session.ts +1 -0
  455. package/src/resources/extensions/gsd/metrics.ts +12 -1
  456. package/src/resources/extensions/gsd/milestone-actions.ts +10 -3
  457. package/src/resources/extensions/gsd/milestone-validation-gates.ts +11 -13
  458. package/src/resources/extensions/gsd/notification-overlay.ts +47 -14
  459. package/src/resources/extensions/gsd/notification-store.ts +54 -5
  460. package/src/resources/extensions/gsd/notification-widget.ts +5 -14
  461. package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +10 -3
  462. package/src/resources/extensions/gsd/pre-execution-checks.ts +39 -2
  463. package/src/resources/extensions/gsd/prompt-validation.ts +157 -0
  464. package/src/resources/extensions/gsd/prompts/complete-slice.md +5 -3
  465. package/src/resources/extensions/gsd/prompts/discuss.md +33 -13
  466. package/src/resources/extensions/gsd/prompts/execute-task.md +22 -19
  467. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +2 -0
  468. package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +2 -0
  469. package/src/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
  470. package/src/resources/extensions/gsd/prompts/queue.md +3 -2
  471. package/src/resources/extensions/gsd/prompts/system.md +1 -0
  472. package/src/resources/extensions/gsd/prompts/validate-milestone.md +4 -1
  473. package/src/resources/extensions/gsd/session-model-override.ts +36 -0
  474. package/src/resources/extensions/gsd/shortcut-defs.ts +56 -0
  475. package/src/resources/extensions/gsd/state.ts +285 -344
  476. package/src/resources/extensions/gsd/tests/auto-start-model-capture.test.ts +25 -9
  477. package/src/resources/extensions/gsd/tests/auto-start-worktree-db-path.test.ts +28 -0
  478. package/src/resources/extensions/gsd/tests/bootstrap-derive-state-db-open.test.ts +39 -0
  479. package/src/resources/extensions/gsd/tests/complete-slice-gate-closure.test.ts +167 -0
  480. package/src/resources/extensions/gsd/tests/complete-slice-prompt-task-summary-layout.test.ts +18 -0
  481. package/src/resources/extensions/gsd/tests/derive-state-helpers.test.ts +436 -0
  482. package/src/resources/extensions/gsd/tests/discuss-incremental-persistence.test.ts +9 -0
  483. package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +27 -0
  484. package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +36 -0
  485. package/src/resources/extensions/gsd/tests/execute-task-prompt-existing-artifact-guard.test.ts +33 -0
  486. package/src/resources/extensions/gsd/tests/file-lock.test.ts +103 -0
  487. package/src/resources/extensions/gsd/tests/forensics-stuck-loops.test.ts +62 -0
  488. package/src/resources/extensions/gsd/tests/format-shortcut.test.ts +31 -0
  489. package/src/resources/extensions/gsd/tests/gate-dispatch.test.ts +27 -0
  490. package/src/resources/extensions/gsd/tests/gate-registry.test.ts +140 -0
  491. package/src/resources/extensions/gsd/tests/gsd-no-project-error.test.ts +73 -0
  492. package/src/resources/extensions/gsd/tests/infra-errors-cooldown.test.ts +180 -0
  493. package/src/resources/extensions/gsd/tests/integration/auto-worktree-milestone-merge.test.ts +66 -1
  494. package/src/resources/extensions/gsd/tests/model-isolation.test.ts +36 -51
  495. package/src/resources/extensions/gsd/tests/notification-store.test.ts +35 -0
  496. package/src/resources/extensions/gsd/tests/notification-widget.test.ts +26 -0
  497. package/src/resources/extensions/gsd/tests/notifications-handler.test.ts +90 -0
  498. package/src/resources/extensions/gsd/tests/parallel-monitor-overlay.test.ts +1 -0
  499. package/src/resources/extensions/gsd/tests/park-db-sync.test.ts +18 -0
  500. package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +49 -0
  501. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +19 -0
  502. package/src/resources/extensions/gsd/tests/prompt-system-gate-coverage.test.ts +208 -0
  503. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +7 -0
  504. package/src/resources/extensions/gsd/tests/register-shortcuts.test.ts +63 -5
  505. package/src/resources/extensions/gsd/tests/secure-env-collect.test.ts +45 -0
  506. package/src/resources/extensions/gsd/tests/session-model-override.test.ts +35 -0
  507. package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +90 -0
  508. package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +7 -0
  509. package/src/resources/extensions/gsd/tests/validate-milestone-prompt-verification-classes.test.ts +18 -0
  510. package/src/resources/extensions/gsd/tests/workflow-mcp-auto-prep.test.ts +76 -0
  511. package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +155 -1
  512. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +22 -0
  513. package/src/resources/extensions/gsd/tools/complete-slice.ts +63 -0
  514. package/src/resources/extensions/gsd/tools/complete-task.ts +63 -0
  515. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +64 -26
  516. package/src/resources/extensions/gsd/types.ts +26 -0
  517. package/src/resources/extensions/gsd/workflow-events.ts +34 -25
  518. package/src/resources/extensions/gsd/workflow-mcp-auto-prep.ts +76 -0
  519. package/src/resources/extensions/gsd/workflow-mcp.ts +1 -1
  520. package/src/resources/extensions/ollama/index.ts +13 -3
  521. package/src/resources/extensions/ollama/ollama-status-indicator.test.ts +28 -0
  522. package/src/resources/extensions/shared/gsd-phase-state.ts +42 -0
  523. package/src/resources/extensions/shared/tests/gsd-phase-state.test.ts +48 -0
  524. package/src/resources/extensions/subagent/agents.ts +10 -0
  525. package/src/resources/extensions/subagent/index.ts +18 -0
  526. package/src/resources/extensions/subagent/tests/agents-conflicts.test.ts +33 -0
  527. package/src/resources/skills/create-skill/SKILL.md +2 -0
  528. package/dist/web/standalone/.next/static/chunks/2826.821e01b07d92e948.js +0 -9
  529. package/dist/web/standalone/.next/static/chunks/app/page-7115e62689b5fd84.js +0 -1
  530. package/dist/web/standalone/.next/static/chunks/main-app-d3d4c336195465f9.js +0 -1
  531. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-ab5a8926e07ec673.js +0 -1
  532. /package/dist/web/standalone/.next/static/{9pw9EXtXjdM7EFrCXUEPf → OI4n_CKC-lM8IQbvGJ_tK}/_buildManifest.js +0 -0
  533. /package/dist/web/standalone/.next/static/{9pw9EXtXjdM7EFrCXUEPf → OI4n_CKC-lM8IQbvGJ_tK}/_ssgManifest.js +0 -0
@@ -0,0 +1,436 @@
1
+ // GSD Extension — Tests for extracted deriveStateFromDb helper functions
2
+ // Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
3
+ //
4
+ // Tests the composable helpers extracted from deriveStateFromDb:
5
+ // reconcileDiskToDb, buildCompletenessSet, buildRegistryAndFindActive,
6
+ // handleNoActiveMilestone, resolveSliceDependencies, reconcileSliceTasks,
7
+ // detectBlockers, checkReplanTrigger, checkInterruptedWork
8
+ //
9
+ // Helpers are private — exercised through deriveStateFromDb integration.
10
+
11
+ import { describe, test, beforeEach, afterEach } from 'node:test';
12
+ import assert from 'node:assert/strict';
13
+ import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
14
+ import { join } from 'node:path';
15
+ import { tmpdir } from 'node:os';
16
+
17
+ import { invalidateStateCache, deriveStateFromDb } from '../state.ts';
18
+ import {
19
+ openDatabase,
20
+ closeDatabase,
21
+ insertMilestone,
22
+ insertSlice,
23
+ insertTask,
24
+ updateTaskStatus,
25
+ } from '../gsd-db.ts';
26
+
27
+ // ─── Fixture Helpers ───────────────────────────────────────────────────────
28
+
29
+ function createFixtureBase(): string {
30
+ const base = mkdtempSync(join(tmpdir(), 'gsd-helpers-'));
31
+ mkdirSync(join(base, '.gsd', 'milestones'), { recursive: true });
32
+ return base;
33
+ }
34
+
35
+ function writeFile(base: string, relativePath: string, content: string): void {
36
+ const full = join(base, '.gsd', relativePath);
37
+ mkdirSync(join(full, '..'), { recursive: true });
38
+ writeFileSync(full, content);
39
+ }
40
+
41
+ function cleanup(base: string): void {
42
+ rmSync(base, { recursive: true, force: true });
43
+ }
44
+
45
+ const ROADMAP_CONTENT = `# M001: Test Milestone
46
+
47
+ **Vision:** Test helpers.
48
+
49
+ ## Slices
50
+
51
+ - [ ] **S01: First Slice** \`risk:low\` \`depends:[]\`
52
+ > After this: Slice done.
53
+
54
+ - [ ] **S02: Second Slice** \`risk:low\` \`depends:[S01]\`
55
+ > After this: All done.
56
+ `;
57
+
58
+ const PLAN_CONTENT = `# S01: First Slice
59
+
60
+ **Goal:** Test executing.
61
+ **Demo:** Tests pass.
62
+
63
+ ## Tasks
64
+
65
+ - [ ] **T01: First Task** \`est:10m\`
66
+ First task description.
67
+
68
+ - [x] **T02: Done Task** \`est:10m\`
69
+ Already done.
70
+ `;
71
+
72
+ // ═══════════════════════════════════════════════════════════════════════════
73
+ // Tests
74
+ // ═══════════════════════════════════════════════════════════════════════════
75
+
76
+ describe('derive-state-helpers', () => {
77
+
78
+ // ─── handleNoActiveMilestone: all parked ─────────────────────────────
79
+ test('handleNoActiveMilestone: all milestones parked returns pre-planning with unpark hint', async () => {
80
+ const base = createFixtureBase();
81
+ try {
82
+ writeFile(base, 'milestones/M001/M001-CONTEXT.md', '# M001\n\nContext.');
83
+ writeFile(base, 'milestones/M001/M001-PARKED.md', 'Parked.');
84
+ writeFile(base, 'milestones/M002/M002-CONTEXT.md', '# M002\n\nContext.');
85
+ writeFile(base, 'milestones/M002/M002-PARKED.md', 'Also parked.');
86
+
87
+ openDatabase(':memory:');
88
+ insertMilestone({ id: 'M001', title: 'First', status: 'parked' });
89
+ insertMilestone({ id: 'M002', title: 'Second', status: 'parked' });
90
+
91
+ invalidateStateCache();
92
+ const state = await deriveStateFromDb(base);
93
+
94
+ assert.equal(state.phase, 'pre-planning', 'all-parked: phase is pre-planning');
95
+ assert.equal(state.activeMilestone, null, 'all-parked: no active milestone');
96
+ assert.ok(state.nextAction.includes('parked'), 'all-parked: nextAction mentions parked');
97
+ assert.ok(state.nextAction.includes('unpark'), 'all-parked: nextAction hints unpark');
98
+ assert.equal(state.registry.length, 2, 'all-parked: both in registry');
99
+ assert.ok(state.registry.every(e => e.status === 'parked'), 'all-parked: all registry entries parked');
100
+ } finally {
101
+ closeDatabase();
102
+ cleanup(base);
103
+ }
104
+ });
105
+
106
+ // ─── handleNoActiveMilestone: all complete with active requirements ──
107
+ test('handleNoActiveMilestone: all complete with unmapped requirements', async () => {
108
+ const base = createFixtureBase();
109
+ try {
110
+ writeFile(base, 'milestones/M001/M001-SUMMARY.md', '# M001 Summary\n\nDone.');
111
+ writeFile(base, 'REQUIREMENTS.md', `# Requirements\n\n## Active\n\n### R001 — Unmapped\n- Status: active\n- Description: Not mapped.\n`);
112
+
113
+ openDatabase(':memory:');
114
+ insertMilestone({ id: 'M001', title: 'First', status: 'complete' });
115
+
116
+ invalidateStateCache();
117
+ const state = await deriveStateFromDb(base);
118
+
119
+ assert.equal(state.phase, 'complete', 'complete-reqs: phase is complete');
120
+ assert.ok(state.nextAction.includes('1 active requirement'), 'complete-reqs: nextAction notes unmapped reqs');
121
+ assert.equal(state.requirements?.active, 1, 'complete-reqs: requirements.active = 1');
122
+ } finally {
123
+ closeDatabase();
124
+ cleanup(base);
125
+ }
126
+ });
127
+
128
+ // ─── resolveSliceDependencies: GSD_SLICE_LOCK with missing slice ────
129
+ test('resolveSliceDependencies: GSD_SLICE_LOCK pointing to non-existent slice returns blocked', async () => {
130
+ const base = createFixtureBase();
131
+ const origLock = process.env.GSD_SLICE_LOCK;
132
+ try {
133
+ writeFile(base, 'milestones/M001/M001-ROADMAP.md', ROADMAP_CONTENT);
134
+ writeFile(base, 'milestones/M001/slices/S01/S01-PLAN.md', PLAN_CONTENT);
135
+ writeFile(base, 'milestones/M001/slices/S01/tasks/.gitkeep', '');
136
+ writeFile(base, 'milestones/M001/slices/S01/tasks/T01-PLAN.md', '# T01 Plan');
137
+
138
+ openDatabase(':memory:');
139
+ insertMilestone({ id: 'M001', title: 'Test', status: 'active' });
140
+ insertSlice({ id: 'S01', milestoneId: 'M001', title: 'First', status: 'active', risk: 'low', depends: [] });
141
+ insertTask({ id: 'T01', sliceId: 'S01', milestoneId: 'M001', title: 'First Task', status: 'pending' });
142
+
143
+ process.env.GSD_SLICE_LOCK = 'S99';
144
+
145
+ invalidateStateCache();
146
+ const state = await deriveStateFromDb(base);
147
+
148
+ assert.equal(state.phase, 'blocked', 'slice-lock-miss: phase is blocked');
149
+ assert.ok(state.blockers.some(b => b.includes('GSD_SLICE_LOCK=S99')), 'slice-lock-miss: blocker mentions lock');
150
+ } finally {
151
+ if (origLock !== undefined) process.env.GSD_SLICE_LOCK = origLock;
152
+ else delete process.env.GSD_SLICE_LOCK;
153
+ closeDatabase();
154
+ cleanup(base);
155
+ }
156
+ });
157
+
158
+ // ─── resolveSliceDependencies: GSD_SLICE_LOCK with valid slice ──────
159
+ test('resolveSliceDependencies: GSD_SLICE_LOCK targeting valid slice bypasses deps', async () => {
160
+ const base = createFixtureBase();
161
+ const origLock = process.env.GSD_SLICE_LOCK;
162
+ try {
163
+ writeFile(base, 'milestones/M001/M001-ROADMAP.md', ROADMAP_CONTENT);
164
+ // S02 depends on S01 but we lock to S02 directly
165
+ writeFile(base, 'milestones/M001/slices/S02/S02-PLAN.md', `# S02\n\n**Goal:** Test.\n**Demo:** Pass.\n\n## Tasks\n\n- [ ] **T01: Task** \`est:5m\`\n Do thing.\n`);
166
+ writeFile(base, 'milestones/M001/slices/S02/tasks/.gitkeep', '');
167
+ writeFile(base, 'milestones/M001/slices/S02/tasks/T01-PLAN.md', '# T01 Plan');
168
+
169
+ openDatabase(':memory:');
170
+ insertMilestone({ id: 'M001', title: 'Test', status: 'active' });
171
+ insertSlice({ id: 'S01', milestoneId: 'M001', title: 'First', status: 'pending', risk: 'low', depends: [] });
172
+ insertSlice({ id: 'S02', milestoneId: 'M001', title: 'Second', status: 'pending', risk: 'low', depends: ['S01'] });
173
+ insertTask({ id: 'T01', sliceId: 'S02', milestoneId: 'M001', title: 'Task', status: 'pending' });
174
+
175
+ process.env.GSD_SLICE_LOCK = 'S02';
176
+
177
+ invalidateStateCache();
178
+ const state = await deriveStateFromDb(base);
179
+
180
+ assert.equal(state.activeSlice?.id, 'S02', 'slice-lock-valid: activeSlice is S02 (locked)');
181
+ assert.equal(state.phase, 'executing', 'slice-lock-valid: phase is executing');
182
+ } finally {
183
+ if (origLock !== undefined) process.env.GSD_SLICE_LOCK = origLock;
184
+ else delete process.env.GSD_SLICE_LOCK;
185
+ closeDatabase();
186
+ cleanup(base);
187
+ }
188
+ });
189
+
190
+ // ─── reconcileSliceTasks: plan file imports tasks when DB empty ──────
191
+ test('reconcileSliceTasks: imports tasks from plan file when DB has zero tasks (#3600)', async () => {
192
+ const base = createFixtureBase();
193
+ try {
194
+ writeFile(base, 'milestones/M001/M001-ROADMAP.md', ROADMAP_CONTENT);
195
+ writeFile(base, 'milestones/M001/slices/S01/S01-PLAN.md', PLAN_CONTENT);
196
+ writeFile(base, 'milestones/M001/slices/S01/tasks/.gitkeep', '');
197
+ writeFile(base, 'milestones/M001/slices/S01/tasks/T01-PLAN.md', '# T01 Plan');
198
+
199
+ openDatabase(':memory:');
200
+ insertMilestone({ id: 'M001', title: 'Test', status: 'active' });
201
+ insertSlice({ id: 'S01', milestoneId: 'M001', title: 'First', status: 'active', risk: 'low', depends: [] });
202
+ insertSlice({ id: 'S02', milestoneId: 'M001', title: 'Second', status: 'pending', risk: 'low', depends: ['S01'] });
203
+ // No tasks inserted — reconcileSliceTasks should import from plan file
204
+
205
+ invalidateStateCache();
206
+ const state = await deriveStateFromDb(base);
207
+
208
+ // Plan has T01 (pending) and T02 (done) — reconciliation imports both
209
+ assert.equal(state.phase, 'executing', 'task-reconcile: phase is executing (tasks imported)');
210
+ assert.equal(state.activeTask?.id, 'T01', 'task-reconcile: activeTask is T01');
211
+ assert.equal(state.progress?.tasks?.total, 2, 'task-reconcile: total tasks = 2');
212
+ assert.equal(state.progress?.tasks?.done, 1, 'task-reconcile: done tasks = 1 (T02 was [x])');
213
+ } finally {
214
+ closeDatabase();
215
+ cleanup(base);
216
+ }
217
+ });
218
+
219
+ // ─── reconcileSliceTasks: stale task reconciled from disk summary ────
220
+ test('reconcileSliceTasks: stale pending task reconciled to complete when disk SUMMARY exists (#2514)', async () => {
221
+ const base = createFixtureBase();
222
+ try {
223
+ writeFile(base, 'milestones/M001/M001-ROADMAP.md', ROADMAP_CONTENT);
224
+ writeFile(base, 'milestones/M001/slices/S01/S01-PLAN.md', PLAN_CONTENT);
225
+ writeFile(base, 'milestones/M001/slices/S01/tasks/.gitkeep', '');
226
+ writeFile(base, 'milestones/M001/slices/S01/tasks/T01-PLAN.md', '# T01 Plan');
227
+ // T01 has a summary on disk but DB still says pending
228
+ writeFile(base, 'milestones/M001/slices/S01/tasks/T01-SUMMARY.md', '# T01 Summary\n\nDone on disk.');
229
+
230
+ openDatabase(':memory:');
231
+ insertMilestone({ id: 'M001', title: 'Test', status: 'active' });
232
+ insertSlice({ id: 'S01', milestoneId: 'M001', title: 'First', status: 'active', risk: 'low', depends: [] });
233
+ insertSlice({ id: 'S02', milestoneId: 'M001', title: 'Second', status: 'pending', risk: 'low', depends: ['S01'] });
234
+ insertTask({ id: 'T01', sliceId: 'S01', milestoneId: 'M001', title: 'First Task', status: 'pending' });
235
+ insertTask({ id: 'T02', sliceId: 'S01', milestoneId: 'M001', title: 'Done Task', status: 'complete' });
236
+
237
+ invalidateStateCache();
238
+ const state = await deriveStateFromDb(base);
239
+
240
+ // T01 should have been reconciled to complete (SUMMARY exists on disk)
241
+ // Both tasks complete → phase should be summarizing
242
+ assert.equal(state.phase, 'summarizing', 'stale-task: phase is summarizing (T01 reconciled)');
243
+ assert.equal(state.activeTask, null, 'stale-task: no active task (all done)');
244
+ assert.equal(state.progress?.tasks?.done, 2, 'stale-task: tasks.done = 2');
245
+ } finally {
246
+ closeDatabase();
247
+ cleanup(base);
248
+ }
249
+ });
250
+
251
+ // ─── detectBlockers: blocker_discovered triggers replanning ──────────
252
+ test('detectBlockers: task with blocker_discovered triggers replanning-slice', async () => {
253
+ const base = createFixtureBase();
254
+ try {
255
+ writeFile(base, 'milestones/M001/M001-ROADMAP.md', ROADMAP_CONTENT);
256
+ writeFile(base, 'milestones/M001/slices/S01/S01-PLAN.md', PLAN_CONTENT);
257
+ writeFile(base, 'milestones/M001/slices/S01/tasks/.gitkeep', '');
258
+ writeFile(base, 'milestones/M001/slices/S01/tasks/T01-PLAN.md', '# T01 Plan');
259
+ // T02 completed with blocker discovered — written in summary frontmatter
260
+ writeFile(base, 'milestones/M001/slices/S01/tasks/T02-SUMMARY.md',
261
+ '---\nblocker_discovered: true\n---\n\n# T02 Summary\n\nFound a blocker.');
262
+
263
+ openDatabase(':memory:');
264
+ insertMilestone({ id: 'M001', title: 'Test', status: 'active' });
265
+ insertSlice({ id: 'S01', milestoneId: 'M001', title: 'First', status: 'active', risk: 'low', depends: [] });
266
+ insertSlice({ id: 'S02', milestoneId: 'M001', title: 'Second', status: 'pending', risk: 'low', depends: ['S01'] });
267
+ insertTask({ id: 'T01', sliceId: 'S01', milestoneId: 'M001', title: 'First Task', status: 'pending' });
268
+ insertTask({ id: 'T02', sliceId: 'S01', milestoneId: 'M001', title: 'Done Task', status: 'complete' });
269
+
270
+ invalidateStateCache();
271
+ const state = await deriveStateFromDb(base);
272
+
273
+ assert.equal(state.phase, 'replanning-slice', 'blocker: phase is replanning-slice');
274
+ assert.ok(state.blockers.some(b => b.includes('T02')), 'blocker: blockers mention T02');
275
+ } finally {
276
+ closeDatabase();
277
+ cleanup(base);
278
+ }
279
+ });
280
+
281
+ // ─── checkInterruptedWork: continue.md triggers resume hint ─────────
282
+ test('checkInterruptedWork: continue.md present triggers resume nextAction', async () => {
283
+ const base = createFixtureBase();
284
+ try {
285
+ writeFile(base, 'milestones/M001/M001-ROADMAP.md', ROADMAP_CONTENT);
286
+ writeFile(base, 'milestones/M001/slices/S01/S01-PLAN.md', PLAN_CONTENT);
287
+ writeFile(base, 'milestones/M001/slices/S01/tasks/.gitkeep', '');
288
+ writeFile(base, 'milestones/M001/slices/S01/tasks/T01-PLAN.md', '# T01 Plan');
289
+ writeFile(base, 'milestones/M001/slices/S01/S01-CONTINUE.md', 'Resume from here.');
290
+
291
+ openDatabase(':memory:');
292
+ insertMilestone({ id: 'M001', title: 'Test', status: 'active' });
293
+ insertSlice({ id: 'S01', milestoneId: 'M001', title: 'First', status: 'active', risk: 'low', depends: [] });
294
+ insertSlice({ id: 'S02', milestoneId: 'M001', title: 'Second', status: 'pending', risk: 'low', depends: ['S01'] });
295
+ insertTask({ id: 'T01', sliceId: 'S01', milestoneId: 'M001', title: 'First Task', status: 'pending' });
296
+ insertTask({ id: 'T02', sliceId: 'S01', milestoneId: 'M001', title: 'Done Task', status: 'complete' });
297
+
298
+ invalidateStateCache();
299
+ const state = await deriveStateFromDb(base);
300
+
301
+ assert.equal(state.phase, 'executing', 'continue: phase is still executing');
302
+ assert.ok(state.nextAction.includes('Resume interrupted work'), 'continue: nextAction mentions resume');
303
+ assert.ok(state.nextAction.includes('continue.md'), 'continue: nextAction mentions continue.md');
304
+ } finally {
305
+ closeDatabase();
306
+ cleanup(base);
307
+ }
308
+ });
309
+
310
+ // ─── buildCompletenessSet: SUMMARY-on-disk marks complete ───────────
311
+ test('buildCompletenessSet: milestone with SUMMARY on disk treated as complete', async () => {
312
+ const base = createFixtureBase();
313
+ try {
314
+ // M001 has summary on disk but DB status is still 'active'
315
+ writeFile(base, 'milestones/M001/M001-ROADMAP.md', ROADMAP_CONTENT);
316
+ writeFile(base, 'milestones/M001/M001-SUMMARY.md', '# M001 Summary\n\nDone.');
317
+ // M002 is the real active milestone
318
+ writeFile(base, 'milestones/M002/M002-CONTEXT.md', '# M002\n\nActive.');
319
+
320
+ openDatabase(':memory:');
321
+ insertMilestone({ id: 'M001', title: 'First', status: 'active' });
322
+ insertMilestone({ id: 'M002', title: 'Second', status: 'active' });
323
+
324
+ invalidateStateCache();
325
+ const state = await deriveStateFromDb(base);
326
+
327
+ // M001 should be complete (summary on disk), M002 should be active
328
+ const m1 = state.registry.find(e => e.id === 'M001');
329
+ assert.equal(m1?.status, 'complete', 'summary-disk: M001 marked complete via disk SUMMARY');
330
+ assert.equal(state.activeMilestone?.id, 'M002', 'summary-disk: M002 is active');
331
+ } finally {
332
+ closeDatabase();
333
+ cleanup(base);
334
+ }
335
+ });
336
+
337
+ // ─── reconcileDiskToDb: disk slices synced into DB (#2533) ──────────
338
+ test('reconcileDiskToDb: slices in ROADMAP.md but missing from DB are auto-inserted (#2533)', async () => {
339
+ const base = createFixtureBase();
340
+ try {
341
+ writeFile(base, 'milestones/M001/M001-ROADMAP.md', ROADMAP_CONTENT);
342
+
343
+ openDatabase(':memory:');
344
+ insertMilestone({ id: 'M001', title: 'Test', status: 'active' });
345
+ // No slices inserted — reconcileDiskToDb should insert from roadmap
346
+
347
+ invalidateStateCache();
348
+ const state = await deriveStateFromDb(base);
349
+
350
+ // Slices should have been reconciled from roadmap, S01 should be the active slice
351
+ assert.equal(state.activeMilestone?.id, 'M001', 'slice-reconcile: M001 is active');
352
+ assert.equal(state.activeSlice?.id, 'S01', 'slice-reconcile: S01 reconciled and active');
353
+ assert.ok((state.progress?.slices?.total ?? 0) >= 2, 'slice-reconcile: at least 2 slices reconciled');
354
+ } finally {
355
+ closeDatabase();
356
+ cleanup(base);
357
+ }
358
+ });
359
+
360
+ // ─── Queue order: milestones sorted by custom queue order ───────────
361
+ test('deriveStateFromDb respects custom queue order from QUEUE-ORDER.json', async () => {
362
+ const base = createFixtureBase();
363
+ try {
364
+ // M003 should come first per queue order, M001 second
365
+ const queueOrder = JSON.stringify({ order: ['M003', 'M001', 'M002'], updatedAt: new Date().toISOString() });
366
+ writeFileSync(join(base, '.gsd', 'QUEUE-ORDER.json'), queueOrder);
367
+ writeFile(base, 'milestones/M001/M001-CONTEXT.md', '# M001\n\nContext.');
368
+ writeFile(base, 'milestones/M002/M002-CONTEXT.md', '# M002\n\nContext.');
369
+ writeFile(base, 'milestones/M003/M003-CONTEXT.md', '# M003\n\nContext.');
370
+
371
+ openDatabase(':memory:');
372
+ // Insert in natural order — queue ordering should override
373
+ insertMilestone({ id: 'M001', title: 'First', status: 'active' });
374
+ insertMilestone({ id: 'M002', title: 'Second', status: 'active' });
375
+ insertMilestone({ id: 'M003', title: 'Third', status: 'active' });
376
+
377
+ invalidateStateCache();
378
+ const state = await deriveStateFromDb(base);
379
+
380
+ // M003 should be the active milestone (first in queue)
381
+ assert.equal(state.activeMilestone?.id, 'M003', 'queue-order: M003 is active (first in queue)');
382
+ assert.equal(state.registry[0]?.id, 'M003', 'queue-order: registry[0] is M003');
383
+ } finally {
384
+ closeDatabase();
385
+ cleanup(base);
386
+ }
387
+ });
388
+
389
+ // ─── handleAllSlicesDone: needs-remediation re-triggers validation ──
390
+ test('handleAllSlicesDone: needs-remediation verdict triggers validating-milestone', async () => {
391
+ const base = createFixtureBase();
392
+ try {
393
+ const doneRoadmap = `# M001: Remediation Test\n\n**Vision:** Test.\n\n## Slices\n\n- [x] **S01: Done** \`risk:low\` \`depends:[]\`\n > Done.\n`;
394
+ writeFile(base, 'milestones/M001/M001-ROADMAP.md', doneRoadmap);
395
+ writeFile(base, 'milestones/M001/M001-VALIDATION.md',
396
+ '---\nverdict: needs-remediation\nremediation_round: 1\n---\n\n# Validation\nNeeds remediation.');
397
+
398
+ openDatabase(':memory:');
399
+ insertMilestone({ id: 'M001', title: 'Remediation Test', status: 'active' });
400
+ insertSlice({ id: 'S01', milestoneId: 'M001', title: 'Done', status: 'complete', risk: 'low', depends: [] });
401
+
402
+ invalidateStateCache();
403
+ const state = await deriveStateFromDb(base);
404
+
405
+ assert.equal(state.phase, 'validating-milestone', 'remediation: phase is validating-milestone');
406
+ assert.equal(state.activeMilestone?.id, 'M001', 'remediation: activeMilestone is M001');
407
+ } finally {
408
+ closeDatabase();
409
+ cleanup(base);
410
+ }
411
+ });
412
+
413
+ // ─── Deferred queued shell: shell milestone deferred, real one promoted ──
414
+ test('buildRegistryAndFindActive: queued shell deferred, later real milestone becomes active (#3470)', async () => {
415
+ const base = createFixtureBase();
416
+ try {
417
+ // M001: queued shell — no content, no slices
418
+ mkdirSync(join(base, '.gsd', 'milestones', 'M001'), { recursive: true });
419
+ // M002: real milestone with context
420
+ writeFile(base, 'milestones/M002/M002-CONTEXT.md', '# M002: Real\n\nActive milestone.');
421
+
422
+ openDatabase(':memory:');
423
+ insertMilestone({ id: 'M001', title: 'Shell', status: 'queued' });
424
+ insertMilestone({ id: 'M002', title: 'Real', status: 'active' });
425
+
426
+ invalidateStateCache();
427
+ const state = await deriveStateFromDb(base);
428
+
429
+ // M002 should be active (M001 queued shell deferred)
430
+ assert.equal(state.activeMilestone?.id, 'M002', 'deferred-shell: M002 is active (shell deferred)');
431
+ } finally {
432
+ closeDatabase();
433
+ cleanup(base);
434
+ }
435
+ });
436
+ });
@@ -27,10 +27,19 @@ describe("discuss incremental persistence (#2152)", () => {
27
27
  assert.match(content, /Incremental persistence/, "should have incremental persistence section");
28
28
  });
29
29
 
30
+ test("new-project discuss prompt includes CONTEXT-DRAFT save instruction", () => {
31
+ const content = readFileSync(join(promptsDir, "discuss.md"), "utf-8");
32
+ assert.match(content, /CONTEXT-DRAFT/, "should mention CONTEXT-DRAFT");
33
+ assert.match(content, /Incremental persistence/, "should have incremental persistence section");
34
+ assert.match(content, /gsd_summary_save/, "should use gsd_summary_save tool");
35
+ });
36
+
30
37
  test("drafts are saved silently without user notification", () => {
31
38
  const milestone = readFileSync(join(promptsDir, "guided-discuss-milestone.md"), "utf-8");
32
39
  const slice = readFileSync(join(promptsDir, "guided-discuss-slice.md"), "utf-8");
40
+ const discuss = readFileSync(join(promptsDir, "discuss.md"), "utf-8");
33
41
  assert.match(milestone, /Do NOT mention this save to the user/);
34
42
  assert.match(slice, /Do NOT mention this to the user/);
43
+ assert.match(discuss, /Do NOT mention this save to the user/);
35
44
  });
36
45
  });
@@ -145,6 +145,33 @@ test("dispatch guard falls back to positional ordering when no dependencies decl
145
145
  );
146
146
  });
147
147
 
148
+ test("dispatch guard ignores positionally-earlier reverse dependents for zero-dependency slices (#3720)", (t) => {
149
+ const repo = setupRepo();
150
+ t.after(() => teardownRepo(repo));
151
+
152
+ mkdirSync(join(repo, ".gsd", "milestones", "M015"), { recursive: true });
153
+
154
+ insertMilestone({ id: "M015", title: "Reverse dependency fallback" });
155
+ insertSlice({ id: "S03", milestoneId: "M015", title: "Complete prerequisite", status: "complete", depends: [], sequence: 0 });
156
+ insertSlice({ id: "S04", milestoneId: "M015", title: "Depends on S04A", status: "pending", depends: ["S03", "S04A"], sequence: 0 });
157
+ insertSlice({ id: "S04A", milestoneId: "M015", title: "No explicit deps", status: "pending", depends: [], sequence: 0 });
158
+
159
+ writeFileSync(join(repo, ".gsd", "milestones", "M015", "M015-ROADMAP.md"), "# M015\n");
160
+
161
+ // S04A has no declared dependencies and should not be blocked by S04, because
162
+ // S04 itself depends on S04A. With sequence=0, DB ordering falls back to id.
163
+ assert.equal(
164
+ getPriorSliceCompletionBlocker(repo, "main", "execute-task", "M015/S04A/T02"),
165
+ null,
166
+ );
167
+
168
+ // The reverse direction is still blocked normally.
169
+ assert.equal(
170
+ getPriorSliceCompletionBlocker(repo, "main", "execute-task", "M015/S04/T01"),
171
+ "Cannot dispatch execute-task M015/S04/T01: dependency slice M015/S04A is not complete.",
172
+ );
173
+ });
174
+
148
175
  test("dispatch guard allows slice with all declared dependencies complete", (t) => {
149
176
  const repo = setupRepo();
150
177
  t.after(() => teardownRepo(repo));
@@ -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,33 @@
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 promptsDir = join(__dirname, "..", "prompts");
9
+
10
+ test("execute-task prompt requires reading existing artifacts before write", () => {
11
+ const prompt = readFileSync(join(promptsDir, "execute-task.md"), "utf-8");
12
+
13
+ assert.match(
14
+ prompt,
15
+ /Before any `Write` that creates an artifact or output file, check whether that path already exists\./,
16
+ "execute-task prompt should require an existence check before creating artifacts",
17
+ );
18
+ assert.match(
19
+ prompt,
20
+ /If it does, read it first and decide whether the work is already done, should be extended, or truly needs replacement\./,
21
+ "execute-task prompt should require reading existing artifacts before replacement",
22
+ );
23
+ });
24
+
25
+ test("guided resume prompt checks for pre-existing artifacts", () => {
26
+ const prompt = readFileSync(join(promptsDir, "guided-resume-task.md"), "utf-8");
27
+
28
+ assert.match(
29
+ prompt,
30
+ /Before you create any expected artifact or output file, check whether it already exists and read it first/i,
31
+ "guided resume prompt should guard pre-existing artifacts",
32
+ );
33
+ });
@@ -0,0 +1,103 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
4
+ import { createRequire } from "node:module";
5
+ import { join } from "node:path";
6
+ import { tmpdir } from "node:os";
7
+
8
+ import { withFileLock, withFileLockSync } from "../file-lock.ts";
9
+
10
+ const require = createRequire(import.meta.url);
11
+
12
+ function hasProperLockfile(): boolean {
13
+ try {
14
+ require("proper-lockfile");
15
+ return true;
16
+ } catch {
17
+ return false;
18
+ }
19
+ }
20
+
21
+ test("withFileLockSync: executes callback when file does not exist", () => {
22
+ const dir = mkdtempSync(join(tmpdir(), "gsd-file-lock-test-"));
23
+ try {
24
+ const missingPath = join(dir, "missing.txt");
25
+ let called = 0;
26
+ const result = withFileLockSync(missingPath, () => {
27
+ called++;
28
+ return "ok";
29
+ });
30
+
31
+ assert.equal(result, "ok");
32
+ assert.equal(called, 1, "callback should execute exactly once");
33
+ } finally {
34
+ rmSync(dir, { recursive: true, force: true });
35
+ }
36
+ });
37
+
38
+ test("withFileLock: executes callback when file does not exist", async () => {
39
+ const dir = mkdtempSync(join(tmpdir(), "gsd-file-lock-test-"));
40
+ try {
41
+ const missingPath = join(dir, "missing.txt");
42
+ let called = 0;
43
+ const result = await withFileLock(missingPath, async () => {
44
+ called++;
45
+ return "ok";
46
+ });
47
+
48
+ assert.equal(result, "ok");
49
+ assert.equal(called, 1, "callback should execute exactly once");
50
+ } finally {
51
+ rmSync(dir, { recursive: true, force: true });
52
+ }
53
+ });
54
+
55
+ test("withFileLockSync: falls back to unlocked callback on ELOCKED", () => {
56
+ if (!hasProperLockfile() || process.platform === "win32") {
57
+ return;
58
+ }
59
+
60
+ const lockfile = require("proper-lockfile");
61
+ const dir = mkdtempSync(join(tmpdir(), "gsd-file-lock-test-"));
62
+ const filePath = join(dir, "locked.jsonl");
63
+ writeFileSync(filePath, "{}\n", "utf-8");
64
+
65
+ const release = lockfile.lockSync(filePath, { retries: 0, stale: 10000 });
66
+ try {
67
+ let called = 0;
68
+ const result = withFileLockSync(filePath, () => {
69
+ called++;
70
+ return "fallback-ok";
71
+ });
72
+ assert.equal(result, "fallback-ok");
73
+ assert.equal(called, 1, "callback should run even when lock acquisition fails");
74
+ } finally {
75
+ release();
76
+ rmSync(dir, { recursive: true, force: true });
77
+ }
78
+ });
79
+
80
+ test("withFileLock: falls back to unlocked callback on ELOCKED", async () => {
81
+ if (!hasProperLockfile() || process.platform === "win32") {
82
+ return;
83
+ }
84
+
85
+ const lockfile = require("proper-lockfile");
86
+ const dir = mkdtempSync(join(tmpdir(), "gsd-file-lock-test-"));
87
+ const filePath = join(dir, "locked.jsonl");
88
+ writeFileSync(filePath, "{}\n", "utf-8");
89
+
90
+ const release = await lockfile.lock(filePath, { retries: 0, stale: 10000 });
91
+ try {
92
+ let called = 0;
93
+ const result = await withFileLock(filePath, async () => {
94
+ called++;
95
+ return "fallback-ok";
96
+ });
97
+ assert.equal(result, "fallback-ok");
98
+ assert.equal(called, 1, "callback should run even when lock acquisition fails");
99
+ } finally {
100
+ await release();
101
+ rmSync(dir, { recursive: true, force: true });
102
+ }
103
+ });