gsd-pi 2.62.1 → 2.63.0-dev.351157b

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 (399) hide show
  1. package/README.md +46 -134
  2. package/dist/cli.js +44 -6
  3. package/dist/help-text.js +4 -1
  4. package/dist/onboarding.js +15 -8
  5. package/dist/resource-loader.js +18 -3
  6. package/dist/resources/extensions/cmux/index.js +21 -12
  7. package/dist/resources/extensions/gsd/auto/finalize-timeout.js +40 -0
  8. package/dist/resources/extensions/gsd/auto/loop.js +12 -1
  9. package/dist/resources/extensions/gsd/auto/phases.js +133 -25
  10. package/dist/resources/extensions/gsd/auto/session.js +8 -0
  11. package/dist/resources/extensions/gsd/auto-dashboard.js +9 -3
  12. package/dist/resources/extensions/gsd/auto-post-unit.js +47 -10
  13. package/dist/resources/extensions/gsd/auto-prompts.js +25 -0
  14. package/dist/resources/extensions/gsd/auto-recovery.js +15 -7
  15. package/dist/resources/extensions/gsd/auto-start.js +10 -21
  16. package/dist/resources/extensions/gsd/auto-tool-tracking.js +17 -0
  17. package/dist/resources/extensions/gsd/auto-verification.js +14 -3
  18. package/dist/resources/extensions/gsd/auto-worktree.js +13 -7
  19. package/dist/resources/extensions/gsd/auto.js +19 -2
  20. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +73 -60
  21. package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +13 -0
  22. package/dist/resources/extensions/gsd/bootstrap/query-tools.js +85 -0
  23. package/dist/resources/extensions/gsd/bootstrap/register-extension.js +3 -0
  24. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +9 -1
  25. package/dist/resources/extensions/gsd/bootstrap/sanitize-complete-milestone.js +54 -0
  26. package/dist/resources/extensions/gsd/commands-handlers.js +9 -4
  27. package/dist/resources/extensions/gsd/constants.js +42 -0
  28. package/dist/resources/extensions/gsd/db-writer.js +72 -4
  29. package/dist/resources/extensions/gsd/forensics.js +20 -4
  30. package/dist/resources/extensions/gsd/gsd-db.js +64 -17
  31. package/dist/resources/extensions/gsd/guided-flow.js +19 -0
  32. package/dist/resources/extensions/gsd/metrics.js +27 -1
  33. package/dist/resources/extensions/gsd/native-git-bridge.js +5 -3
  34. package/dist/resources/extensions/gsd/preferences-types.js +1 -0
  35. package/dist/resources/extensions/gsd/preferences.js +7 -2
  36. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +2 -0
  37. package/dist/resources/extensions/gsd/prompts/complete-slice.md +2 -0
  38. package/dist/resources/extensions/gsd/prompts/doctor-heal.md +1 -0
  39. package/dist/resources/extensions/gsd/prompts/forensics.md +2 -0
  40. package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +2 -0
  41. package/dist/resources/extensions/gsd/prompts/system.md +1 -0
  42. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +2 -0
  43. package/dist/resources/extensions/gsd/roadmap-mutations.js +1 -1
  44. package/dist/resources/extensions/gsd/roadmap-slices.js +9 -5
  45. package/dist/resources/extensions/gsd/slice-parallel-conflict.js +67 -0
  46. package/dist/resources/extensions/gsd/slice-parallel-eligibility.js +51 -0
  47. package/dist/resources/extensions/gsd/slice-parallel-orchestrator.js +378 -0
  48. package/dist/resources/extensions/gsd/state.js +75 -14
  49. package/dist/resources/extensions/gsd/status-guards.js +11 -0
  50. package/dist/resources/extensions/gsd/tools/complete-milestone.js +17 -12
  51. package/dist/resources/extensions/gsd/tools/complete-slice.js +43 -29
  52. package/dist/resources/extensions/gsd/tools/complete-task.js +12 -12
  53. package/dist/resources/extensions/gsd/tools/plan-milestone.js +33 -25
  54. package/dist/resources/extensions/gsd/tools/plan-slice.js +5 -8
  55. package/dist/resources/extensions/gsd/workflow-logger.js +13 -8
  56. package/dist/resources/extensions/gsd/workflow-projections.js +21 -5
  57. package/dist/resources/extensions/gsd/workflow-reconcile.js +3 -1
  58. package/dist/resources/extensions/gsd/worktree-manager.js +82 -29
  59. package/dist/resources/extensions/gsd/worktree-resolver.js +4 -3
  60. package/dist/resources/extensions/mcp-client/auth.js +101 -0
  61. package/dist/resources/extensions/mcp-client/index.js +10 -1
  62. package/dist/resources/extensions/ollama/index.js +6 -12
  63. package/dist/resources/extensions/ollama/model-capabilities.js +37 -34
  64. package/dist/resources/extensions/ollama/ndjson-stream.js +54 -0
  65. package/dist/resources/extensions/ollama/ollama-chat-provider.js +380 -0
  66. package/dist/resources/extensions/ollama/ollama-client.js +23 -32
  67. package/dist/resources/extensions/ollama/ollama-discovery.js +2 -7
  68. package/dist/resources/extensions/ollama/ollama-tool.js +62 -0
  69. package/dist/resources/extensions/ollama/thinking-parser.js +104 -0
  70. package/dist/web/standalone/.next/BUILD_ID +1 -1
  71. package/dist/web/standalone/.next/app-path-routes-manifest.json +15 -15
  72. package/dist/web/standalone/.next/build-manifest.json +3 -3
  73. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  74. package/dist/web/standalone/.next/required-server-files.json +4 -4
  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 +2 -2
  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 +3 -3
  89. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
  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 +3 -3
  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/boot/route.js.nft.json +1 -1
  97. package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
  98. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
  99. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js.nft.json +1 -1
  100. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
  101. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
  102. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js.nft.json +1 -1
  103. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
  104. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
  105. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js.nft.json +1 -1
  106. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
  107. package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
  108. package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
  109. package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
  110. package/dist/web/standalone/.next/server/app/api/captures/route.js.nft.json +1 -1
  111. package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
  112. package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
  113. package/dist/web/standalone/.next/server/app/api/cleanup/route.js.nft.json +1 -1
  114. package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
  115. package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
  116. package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
  117. package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
  118. package/dist/web/standalone/.next/server/app/api/doctor/route.js.nft.json +1 -1
  119. package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
  120. package/dist/web/standalone/.next/server/app/api/experimental/route.js +2 -2
  121. package/dist/web/standalone/.next/server/app/api/experimental/route_client-reference-manifest.js +1 -1
  122. package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
  123. package/dist/web/standalone/.next/server/app/api/export-data/route.js.nft.json +1 -1
  124. package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
  125. package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
  126. package/dist/web/standalone/.next/server/app/api/files/route.js.nft.json +1 -1
  127. package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
  128. package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
  129. package/dist/web/standalone/.next/server/app/api/forensics/route.js.nft.json +1 -1
  130. package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
  131. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  132. package/dist/web/standalone/.next/server/app/api/git/route.js.nft.json +1 -1
  133. package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
  134. package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
  135. package/dist/web/standalone/.next/server/app/api/history/route.js.nft.json +1 -1
  136. package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
  137. package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
  138. package/dist/web/standalone/.next/server/app/api/hooks/route.js.nft.json +1 -1
  139. package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
  140. package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
  141. package/dist/web/standalone/.next/server/app/api/inspect/route.js.nft.json +1 -1
  142. package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
  143. package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
  144. package/dist/web/standalone/.next/server/app/api/knowledge/route.js.nft.json +1 -1
  145. package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
  146. package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
  147. package/dist/web/standalone/.next/server/app/api/live-state/route.js.nft.json +1 -1
  148. package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
  149. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  150. package/dist/web/standalone/.next/server/app/api/onboarding/route.js.nft.json +1 -1
  151. package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
  152. package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
  153. package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
  154. package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
  155. package/dist/web/standalone/.next/server/app/api/projects/route.js.nft.json +1 -1
  156. package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
  157. package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
  158. package/dist/web/standalone/.next/server/app/api/recovery/route.js.nft.json +1 -1
  159. package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
  160. package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +2 -2
  161. package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
  162. package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
  163. package/dist/web/standalone/.next/server/app/api/session/browser/route.js.nft.json +1 -1
  164. package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
  165. package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
  166. package/dist/web/standalone/.next/server/app/api/session/command/route.js.nft.json +1 -1
  167. package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
  168. package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
  169. package/dist/web/standalone/.next/server/app/api/session/events/route.js.nft.json +1 -1
  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.js.nft.json +1 -1
  173. package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
  174. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  175. package/dist/web/standalone/.next/server/app/api/settings-data/route.js.nft.json +1 -1
  176. package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
  177. package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
  178. package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
  179. package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
  180. package/dist/web/standalone/.next/server/app/api/skill-health/route.js.nft.json +1 -1
  181. package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
  182. package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
  183. package/dist/web/standalone/.next/server/app/api/steer/route.js.nft.json +1 -1
  184. package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
  185. package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
  186. package/dist/web/standalone/.next/server/app/api/switch-root/route.js.nft.json +1 -1
  187. package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
  188. package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +2 -2
  189. package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
  190. package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
  191. package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
  192. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +2 -2
  193. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js.nft.json +1 -1
  194. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
  195. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +4 -4
  196. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js.nft.json +1 -1
  197. package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
  198. package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
  199. package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
  200. package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
  201. package/dist/web/standalone/.next/server/app/api/undo/route.js.nft.json +1 -1
  202. package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
  203. package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
  204. package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  205. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  206. package/dist/web/standalone/.next/server/app/api/visualizer/route.js.nft.json +1 -1
  207. package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
  208. package/dist/web/standalone/.next/server/app/index.html +1 -1
  209. package/dist/web/standalone/.next/server/app/index.rsc +4 -4
  210. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  211. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +4 -4
  212. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  213. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +3 -3
  214. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  215. package/dist/web/standalone/.next/server/app/page.js +2 -2
  216. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  217. package/dist/web/standalone/.next/server/app-paths-manifest.json +15 -15
  218. package/dist/web/standalone/.next/server/chunks/6897.js +12 -0
  219. package/dist/web/standalone/.next/server/chunks/7471.js +3 -3
  220. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  221. package/dist/web/standalone/.next/server/middleware.js +2 -2
  222. package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
  223. package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
  224. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  225. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  226. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  227. package/dist/web/standalone/.next/static/chunks/app/_not-found/{page-2f24283c162b6ab3.js → page-f2a7482d42a5614b.js} +1 -1
  228. package/dist/web/standalone/.next/static/chunks/app/{layout-9ecfd95f343793f0.js → layout-a16c7a7ecdf0c2cf.js} +1 -1
  229. package/dist/web/standalone/.next/static/chunks/app/page-0c485498795110d6.js +1 -0
  230. package/dist/web/standalone/.next/static/chunks/main-app-fdab67f7802d7832.js +1 -0
  231. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-459824ffb8c323dd.js +1 -0
  232. package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
  233. package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
  234. package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
  235. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api.target.mk +14 -14
  236. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_except.target.mk +14 -14
  237. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_maybe.target.mk +14 -14
  238. package/dist/web/standalone/server.js +1 -1
  239. package/package.json +1 -1
  240. package/packages/pi-agent-core/dist/agent-loop.d.ts +8 -0
  241. package/packages/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
  242. package/packages/pi-agent-core/dist/agent-loop.js +50 -0
  243. package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
  244. package/packages/pi-agent-core/src/agent-loop.test.ts +221 -5
  245. package/packages/pi-agent-core/src/agent-loop.ts +53 -0
  246. package/packages/pi-ai/dist/types.d.ts +16 -1
  247. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  248. package/packages/pi-ai/dist/types.js.map +1 -1
  249. package/packages/pi-ai/src/types.ts +18 -1
  250. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts +9 -0
  251. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
  252. package/packages/pi-coding-agent/dist/core/auth-storage.js +50 -1
  253. package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
  254. package/packages/pi-coding-agent/dist/core/auth-storage.test.js +41 -0
  255. package/packages/pi-coding-agent/dist/core/auth-storage.test.js.map +1 -1
  256. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts +7 -0
  257. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  258. package/packages/pi-coding-agent/dist/core/extensions/loader.js +31 -4
  259. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  260. package/packages/pi-coding-agent/dist/core/extensions/loader.test.js +28 -1
  261. package/packages/pi-coding-agent/dist/core/extensions/loader.test.js.map +1 -1
  262. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +2 -0
  263. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
  264. package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
  265. package/packages/pi-coding-agent/dist/core/model-registry.d.ts +1 -0
  266. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  267. package/packages/pi-coding-agent/dist/core/model-registry.js +1 -0
  268. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  269. package/packages/pi-coding-agent/dist/core/model-resolver.js +3 -3
  270. package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
  271. package/packages/pi-coding-agent/dist/core/resource-loader.d.ts +23 -1
  272. package/packages/pi-coding-agent/dist/core/resource-loader.d.ts.map +1 -1
  273. package/packages/pi-coding-agent/dist/core/resource-loader.js +80 -56
  274. package/packages/pi-coding-agent/dist/core/resource-loader.js.map +1 -1
  275. package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
  276. package/packages/pi-coding-agent/dist/core/sdk.js +10 -0
  277. package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
  278. package/packages/pi-coding-agent/package.json +1 -1
  279. package/packages/pi-coding-agent/src/core/auth-storage.test.ts +53 -0
  280. package/packages/pi-coding-agent/src/core/auth-storage.ts +66 -1
  281. package/packages/pi-coding-agent/src/core/extensions/loader.test.ts +39 -1
  282. package/packages/pi-coding-agent/src/core/extensions/loader.ts +34 -4
  283. package/packages/pi-coding-agent/src/core/extensions/types.ts +2 -0
  284. package/packages/pi-coding-agent/src/core/model-registry.ts +2 -0
  285. package/packages/pi-coding-agent/src/core/model-resolver.ts +3 -3
  286. package/packages/pi-coding-agent/src/core/resource-loader.ts +89 -56
  287. package/packages/pi-coding-agent/src/core/sdk.ts +11 -0
  288. package/pkg/package.json +1 -1
  289. package/src/resources/extensions/cmux/index.ts +18 -12
  290. package/src/resources/extensions/gsd/auto/finalize-timeout.ts +46 -0
  291. package/src/resources/extensions/gsd/auto/loop.ts +13 -1
  292. package/src/resources/extensions/gsd/auto/phases.ts +164 -40
  293. package/src/resources/extensions/gsd/auto/session.ts +9 -0
  294. package/src/resources/extensions/gsd/auto-dashboard.ts +11 -3
  295. package/src/resources/extensions/gsd/auto-post-unit.ts +56 -12
  296. package/src/resources/extensions/gsd/auto-prompts.ts +21 -0
  297. package/src/resources/extensions/gsd/auto-recovery.ts +9 -8
  298. package/src/resources/extensions/gsd/auto-start.ts +11 -20
  299. package/src/resources/extensions/gsd/auto-tool-tracking.ts +19 -0
  300. package/src/resources/extensions/gsd/auto-verification.ts +14 -3
  301. package/src/resources/extensions/gsd/auto-worktree.ts +14 -6
  302. package/src/resources/extensions/gsd/auto.ts +22 -1
  303. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +74 -60
  304. package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +15 -0
  305. package/src/resources/extensions/gsd/bootstrap/query-tools.ts +98 -0
  306. package/src/resources/extensions/gsd/bootstrap/register-extension.ts +4 -0
  307. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +9 -1
  308. package/src/resources/extensions/gsd/bootstrap/sanitize-complete-milestone.ts +57 -0
  309. package/src/resources/extensions/gsd/commands-handlers.ts +10 -4
  310. package/src/resources/extensions/gsd/constants.ts +44 -0
  311. package/src/resources/extensions/gsd/db-writer.ts +78 -4
  312. package/src/resources/extensions/gsd/forensics.ts +21 -5
  313. package/src/resources/extensions/gsd/gsd-db.ts +64 -17
  314. package/src/resources/extensions/gsd/guided-flow.ts +22 -0
  315. package/src/resources/extensions/gsd/metrics.ts +28 -1
  316. package/src/resources/extensions/gsd/native-git-bridge.ts +5 -3
  317. package/src/resources/extensions/gsd/preferences-types.ts +3 -0
  318. package/src/resources/extensions/gsd/preferences.ts +9 -2
  319. package/src/resources/extensions/gsd/prompts/complete-milestone.md +2 -0
  320. package/src/resources/extensions/gsd/prompts/complete-slice.md +2 -0
  321. package/src/resources/extensions/gsd/prompts/doctor-heal.md +1 -0
  322. package/src/resources/extensions/gsd/prompts/forensics.md +2 -0
  323. package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +2 -0
  324. package/src/resources/extensions/gsd/prompts/system.md +1 -0
  325. package/src/resources/extensions/gsd/prompts/validate-milestone.md +2 -0
  326. package/src/resources/extensions/gsd/roadmap-mutations.ts +1 -1
  327. package/src/resources/extensions/gsd/roadmap-slices.ts +10 -5
  328. package/src/resources/extensions/gsd/slice-parallel-conflict.ts +86 -0
  329. package/src/resources/extensions/gsd/slice-parallel-eligibility.ts +73 -0
  330. package/src/resources/extensions/gsd/slice-parallel-orchestrator.ts +477 -0
  331. package/src/resources/extensions/gsd/state.ts +68 -12
  332. package/src/resources/extensions/gsd/status-guards.ts +13 -0
  333. package/src/resources/extensions/gsd/tests/artifact-corruption-2630.test.ts +288 -0
  334. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +34 -13
  335. package/src/resources/extensions/gsd/tests/cmux.test.ts +58 -0
  336. package/src/resources/extensions/gsd/tests/cold-resume-db-reopen.test.ts +51 -0
  337. package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +140 -0
  338. package/src/resources/extensions/gsd/tests/complete-task.test.ts +39 -0
  339. package/src/resources/extensions/gsd/tests/dashboard-model-label-ordering.test.ts +107 -0
  340. package/src/resources/extensions/gsd/tests/db-access-guardrails.test.ts +109 -0
  341. package/src/resources/extensions/gsd/tests/db-path-worktree-symlink.test.ts +13 -9
  342. package/src/resources/extensions/gsd/tests/db-writer.test.ts +134 -0
  343. package/src/resources/extensions/gsd/tests/deferred-slice-dispatch.test.ts +203 -0
  344. package/src/resources/extensions/gsd/tests/discuss-tool-scoping.test.ts +130 -0
  345. package/src/resources/extensions/gsd/tests/doctor-fix-flag.test.ts +92 -0
  346. package/src/resources/extensions/gsd/tests/finalize-timeout-guard.test.ts +116 -0
  347. package/src/resources/extensions/gsd/tests/forensics-stuck-loops.test.ts +103 -0
  348. package/src/resources/extensions/gsd/tests/insert-slice-no-wipe.test.ts +88 -0
  349. package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +27 -7
  350. package/src/resources/extensions/gsd/tests/integration/idle-recovery.test.ts +34 -0
  351. package/src/resources/extensions/gsd/tests/metrics.test.ts +116 -1
  352. package/src/resources/extensions/gsd/tests/milestone-status-tool.test.ts +201 -0
  353. package/src/resources/extensions/gsd/tests/plan-milestone-title.test.ts +2 -1
  354. package/src/resources/extensions/gsd/tests/plan-milestone.test.ts +82 -18
  355. package/src/resources/extensions/gsd/tests/preferences.test.ts +10 -0
  356. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +25 -0
  357. package/src/resources/extensions/gsd/tests/roadmap-slices.test.ts +69 -0
  358. package/src/resources/extensions/gsd/tests/shared-wal.test.ts +30 -0
  359. package/src/resources/extensions/gsd/tests/slice-context-injection.test.ts +50 -0
  360. package/src/resources/extensions/gsd/tests/slice-parallel-conflict.test.ts +92 -0
  361. package/src/resources/extensions/gsd/tests/slice-parallel-eligibility.test.ts +95 -0
  362. package/src/resources/extensions/gsd/tests/slice-parallel-orchestrator.test.ts +83 -0
  363. package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +103 -0
  364. package/src/resources/extensions/gsd/tests/tool-param-optionality.test.ts +349 -0
  365. package/src/resources/extensions/gsd/tests/workflow-logger.test.ts +17 -41
  366. package/src/resources/extensions/gsd/tests/worktree-health-dispatch.test.ts +35 -2
  367. package/src/resources/extensions/gsd/tests/worktree-health-monorepo.test.ts +73 -0
  368. package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +34 -0
  369. package/src/resources/extensions/gsd/tests/worktree-submodule-safety.test.ts +1 -1
  370. package/src/resources/extensions/gsd/tests/worktree-teardown-safety.test.ts +148 -0
  371. package/src/resources/extensions/gsd/tools/complete-milestone.ts +34 -20
  372. package/src/resources/extensions/gsd/tools/complete-slice.ts +44 -31
  373. package/src/resources/extensions/gsd/tools/complete-task.ts +12 -12
  374. package/src/resources/extensions/gsd/tools/plan-milestone.ts +55 -30
  375. package/src/resources/extensions/gsd/tools/plan-slice.ts +13 -8
  376. package/src/resources/extensions/gsd/types.ts +44 -22
  377. package/src/resources/extensions/gsd/workflow-logger.ts +13 -8
  378. package/src/resources/extensions/gsd/workflow-projections.ts +23 -5
  379. package/src/resources/extensions/gsd/workflow-reconcile.ts +3 -1
  380. package/src/resources/extensions/gsd/worktree-manager.ts +76 -28
  381. package/src/resources/extensions/gsd/worktree-resolver.ts +4 -3
  382. package/src/resources/extensions/mcp-client/auth.ts +149 -0
  383. package/src/resources/extensions/mcp-client/index.ts +16 -1
  384. package/src/resources/extensions/ollama/index.ts +6 -14
  385. package/src/resources/extensions/ollama/model-capabilities.ts +41 -34
  386. package/src/resources/extensions/ollama/ndjson-stream.ts +63 -0
  387. package/src/resources/extensions/ollama/ollama-chat-provider.ts +459 -0
  388. package/src/resources/extensions/ollama/ollama-client.ts +30 -30
  389. package/src/resources/extensions/ollama/ollama-discovery.ts +5 -8
  390. package/src/resources/extensions/ollama/ollama-tool.ts +69 -0
  391. package/src/resources/extensions/ollama/tests/ollama-discovery.test.ts +0 -27
  392. package/src/resources/extensions/ollama/thinking-parser.ts +116 -0
  393. package/src/resources/extensions/ollama/types.ts +23 -0
  394. package/dist/web/standalone/.next/server/chunks/2229.js +0 -12
  395. package/dist/web/standalone/.next/static/chunks/app/page-62be3b5fa91e4c8f.js +0 -1
  396. package/dist/web/standalone/.next/static/chunks/main-app-d3d4c336195465f9.js +0 -1
  397. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-ab5a8926e07ec673.js +0 -1
  398. /package/dist/web/standalone/.next/static/{86gWhNPP3233lZ7KPwda7 → QmuF-eAbuU_2MQ03t38qr}/_buildManifest.js +0 -0
  399. /package/dist/web/standalone/.next/static/{86gWhNPP3233lZ7KPwda7 → QmuF-eAbuU_2MQ03t38qr}/_ssgManifest.js +0 -0
@@ -449,6 +449,45 @@ console.log('\n=== complete-task: handler with missing plan file ===');
449
449
  cleanup(dbPath);
450
450
  }
451
451
 
452
+ // ═══════════════════════════════════════════════════════════════════════════
453
+ // complete-task: minimal params — no optional fields (#2771 regression)
454
+ // ═══════════════════════════════════════════════════════════════════════════
455
+
456
+ console.log('\n=== complete-task: minimal params (no keyFiles, keyDecisions, verificationEvidence, blockerDiscovered) ===');
457
+ {
458
+ const dbPath = tempDbPath();
459
+ openDatabase(dbPath);
460
+
461
+ const { basePath, planPath } = createTempProject();
462
+
463
+ insertMilestone({ id: 'M001', title: 'Test Milestone' });
464
+ insertSlice({ id: 'S01', milestoneId: 'M001', title: 'Test Slice' });
465
+
466
+ // Minimal params — only required fields, all optional enrichment fields omitted
467
+ const minimalParams = {
468
+ taskId: 'T01',
469
+ sliceId: 'S01',
470
+ milestoneId: 'M001',
471
+ oneLiner: 'Basic task',
472
+ narrative: 'Did the work.',
473
+ verification: 'Looks good.',
474
+ // keyFiles, keyDecisions, verificationEvidence, blockerDiscovered intentionally omitted
475
+ };
476
+
477
+ const result = await handleCompleteTask(minimalParams as any, basePath);
478
+
479
+ assertTrue(!('error' in result), 'handler should not crash with minimal params (no optional fields)');
480
+ if (!('error' in result)) {
481
+ assertTrue(fs.existsSync(result.summaryPath), 'summary file should be written with minimal params');
482
+ const summaryContent = fs.readFileSync(result.summaryPath, 'utf-8');
483
+ assertMatch(summaryContent, /blocker_discovered:\s*false/, 'blocker_discovered should default to false');
484
+ assertMatch(summaryContent, /\(none\)/, 'key_files/key_decisions should show (none) placeholder');
485
+ }
486
+
487
+ cleanupDir(basePath);
488
+ cleanup(dbPath);
489
+ }
490
+
452
491
  // ═══════════════════════════════════════════════════════════════════════════
453
492
 
454
493
  report();
@@ -0,0 +1,107 @@
1
+ /**
2
+ * dashboard-model-label-ordering.test.ts — Regression test for #2899.
3
+ *
4
+ * The dashboard model label was showing the previous unit's model because
5
+ * updateProgressWidget was called before selectAndApplyModel in phases.ts.
6
+ * This test verifies:
7
+ * 1. updateProgressWidget is called AFTER selectAndApplyModel in phases.ts
8
+ * 2. session.ts has a currentDispatchedModelId field
9
+ * 3. auto.ts exposes getCurrentDispatchedModelId in widgetStateAccessors
10
+ * 4. auto-dashboard.ts reads from a dispatched model accessor, not cmdCtx?.model
11
+ */
12
+
13
+ import { readFileSync } from "node:fs";
14
+ import { join } from "node:path";
15
+ import { createTestContext } from "./test-helpers.ts";
16
+
17
+ const { assertTrue, assertMatch, report } = createTestContext();
18
+
19
+ const phasesPath = join(import.meta.dirname, "..", "auto", "phases.ts");
20
+ const sessionPath = join(import.meta.dirname, "..", "auto", "session.ts");
21
+ const autoPath = join(import.meta.dirname, "..", "auto.ts");
22
+ const dashboardPath = join(import.meta.dirname, "..", "auto-dashboard.ts");
23
+
24
+ const phasesSrc = readFileSync(phasesPath, "utf-8");
25
+ const sessionSrc = readFileSync(sessionPath, "utf-8");
26
+ const autoSrc = readFileSync(autoPath, "utf-8");
27
+ const dashboardSrc = readFileSync(dashboardPath, "utf-8");
28
+
29
+ console.log("\n=== #2899: Dashboard model label shows correct (dispatched) model ===");
30
+
31
+ // ── Test 1: updateProgressWidget is called AFTER selectAndApplyModel ──────
32
+
33
+ // Find the positions of the calls in the dispatch function body.
34
+ // selectAndApplyModel must appear BEFORE updateProgressWidget.
35
+ const selectModelPos = phasesSrc.indexOf("deps.selectAndApplyModel(");
36
+ const updateWidgetPos = phasesSrc.indexOf("deps.updateProgressWidget(");
37
+
38
+ assertTrue(
39
+ selectModelPos > 0,
40
+ "phases.ts contains deps.selectAndApplyModel call",
41
+ );
42
+
43
+ assertTrue(
44
+ updateWidgetPos > 0,
45
+ "phases.ts contains deps.updateProgressWidget call",
46
+ );
47
+
48
+ assertTrue(
49
+ selectModelPos < updateWidgetPos,
50
+ `selectAndApplyModel (pos ${selectModelPos}) must be called BEFORE updateProgressWidget (pos ${updateWidgetPos}) — widget needs fresh model`,
51
+ );
52
+
53
+ // ── Test 2: session.ts declares currentDispatchedModelId ──────────────────
54
+
55
+ assertTrue(
56
+ sessionSrc.includes("currentDispatchedModelId"),
57
+ "session.ts has currentDispatchedModelId field",
58
+ );
59
+
60
+ // ── Test 3: auto.ts exposes getCurrentDispatchedModelId in widgetStateAccessors ──
61
+
62
+ assertTrue(
63
+ autoSrc.includes("getCurrentDispatchedModelId"),
64
+ "auto.ts exposes getCurrentDispatchedModelId accessor",
65
+ );
66
+
67
+ // Verify it's in the widgetStateAccessors object
68
+ const accessorsBlock = autoSrc.slice(
69
+ autoSrc.indexOf("const widgetStateAccessors"),
70
+ autoSrc.indexOf("};", autoSrc.indexOf("const widgetStateAccessors")) + 2,
71
+ );
72
+
73
+ assertTrue(
74
+ accessorsBlock.includes("getCurrentDispatchedModelId"),
75
+ "getCurrentDispatchedModelId is in the widgetStateAccessors object",
76
+ );
77
+
78
+ // ── Test 4: WidgetStateAccessors interface has getCurrentDispatchedModelId ──
79
+
80
+ assertTrue(
81
+ dashboardSrc.includes("getCurrentDispatchedModelId"),
82
+ "auto-dashboard.ts references getCurrentDispatchedModelId",
83
+ );
84
+
85
+ // The dashboard render closure should NOT read model from cmdCtx?.model for display.
86
+ // It should use the accessor for the dispatched model ID.
87
+ // Check that the "Model display" section uses the accessor, not cmdCtx?.model directly.
88
+ const modelDisplaySection = dashboardSrc.slice(
89
+ dashboardSrc.indexOf("// Model display"),
90
+ dashboardSrc.indexOf("// Model display") + 500,
91
+ );
92
+
93
+ assertTrue(
94
+ modelDisplaySection.includes("getCurrentDispatchedModelId") ||
95
+ modelDisplaySection.includes("getDispatchedModelId"),
96
+ "Model display section reads from dispatched model accessor, not cmdCtx?.model alone",
97
+ );
98
+
99
+ // ── Test 5: currentDispatchedModelId is set after selectAndApplyModel in phases.ts ──
100
+
101
+ // After selectAndApplyModel returns, phases.ts should store the dispatched model ID
102
+ assertTrue(
103
+ phasesSrc.includes("currentDispatchedModelId"),
104
+ "phases.ts stores currentDispatchedModelId after model selection",
105
+ );
106
+
107
+ report();
@@ -0,0 +1,109 @@
1
+ // GSD2 — Regression tests: DB anti-pattern guardrails in prompt templates
2
+
3
+ import test from "node:test";
4
+ import assert from "node:assert/strict";
5
+ import { readFileSync, readdirSync } from "node:fs";
6
+ import { join } from "node:path";
7
+
8
+ const promptsDir = join(process.cwd(), "src/resources/extensions/gsd/prompts");
9
+
10
+ function readPrompt(name: string): string {
11
+ return readFileSync(join(promptsDir, `${name}.md`), "utf-8");
12
+ }
13
+
14
+ // ─── Layer 1: system.md global guardrail ──────────────────────────────────────
15
+
16
+ test("system.md anti-patterns section prohibits direct .gsd/gsd.db access", () => {
17
+ const prompt = readPrompt("system");
18
+ assert.match(
19
+ prompt,
20
+ /Never query.*\.gsd\/gsd\.db.*directly/i,
21
+ "system.md must prohibit direct .gsd/gsd.db access in the anti-patterns section",
22
+ );
23
+ assert.match(prompt, /sqlite3/, "system.md DB guardrail must name the sqlite3 CLI");
24
+ assert.match(prompt, /better-sqlite3/, "system.md DB guardrail must name better-sqlite3");
25
+ assert.match(prompt, /gsd_\*/, "system.md DB guardrail must redirect to gsd_* tools");
26
+ });
27
+
28
+ test("system.md DB guardrail explains single-writer WAL risk", () => {
29
+ const prompt = readPrompt("system");
30
+ assert.match(prompt, /single-writer WAL/i, "system.md must explain the WAL architecture risk");
31
+ });
32
+
33
+ // ─── Layer 2: high-risk prompt guardrails ─────────────────────────────────────
34
+
35
+ test("validate-milestone.md contains DB access safety guardrail with tool redirect", () => {
36
+ const prompt = readPrompt("validate-milestone");
37
+ assert.match(prompt, /DB access safety/i, "validate-milestone.md must have DB access safety section");
38
+ assert.match(prompt, /gsd_milestone_status/, "validate-milestone.md must name gsd_milestone_status as alternative");
39
+ assert.match(prompt, /Do NOT query.*\.gsd\/gsd\.db/i, "validate-milestone.md must prohibit direct DB queries");
40
+ });
41
+
42
+ test("complete-milestone.md contains DB access safety guardrail with tool redirect", () => {
43
+ const prompt = readPrompt("complete-milestone");
44
+ assert.match(prompt, /DB access safety/i, "complete-milestone.md must have DB access safety section");
45
+ assert.match(prompt, /gsd_milestone_status/, "complete-milestone.md must name gsd_milestone_status as alternative");
46
+ assert.match(prompt, /Do NOT query.*\.gsd\/gsd\.db/i, "complete-milestone.md must prohibit direct DB queries");
47
+ });
48
+
49
+ test("doctor-heal.md contains DB access guardrail naming gsd_milestone_status", () => {
50
+ const prompt = readPrompt("doctor-heal");
51
+ assert.match(prompt, /gsd_milestone_status/, "doctor-heal.md must name gsd_milestone_status as the DB inspection tool");
52
+ assert.match(prompt, /Do NOT query.*\.gsd\/gsd\.db/i, "doctor-heal.md must prohibit direct DB queries");
53
+ });
54
+
55
+ test("forensics.md contains DB inspection guardrail", () => {
56
+ const prompt = readPrompt("forensics");
57
+ assert.match(prompt, /gsd_milestone_status/, "forensics.md must name gsd_milestone_status as the DB inspection tool");
58
+ assert.match(prompt, /sqlite3.*\.gsd\/gsd\.db/i, "forensics.md must prohibit sqlite3 against .gsd/gsd.db");
59
+ });
60
+
61
+ test("reassess-roadmap.md contains DB access safety guardrail", () => {
62
+ const prompt = readPrompt("reassess-roadmap");
63
+ assert.match(prompt, /DB access safety/i, "reassess-roadmap.md must have DB access safety section");
64
+ assert.match(prompt, /gsd_milestone_status/, "reassess-roadmap.md must name gsd_milestone_status as alternative");
65
+ });
66
+
67
+ // ─── Negative assertion: no prompt instructs running sqlite3 as a command ─────
68
+
69
+ test("no prompt file contains an unguarded sqlite3 command invocation", () => {
70
+ const files = readdirSync(promptsDir).filter((f) => f.endsWith(".md"));
71
+ assert.ok(files.length >= 35, `Expected at least 35 prompt files, found ${files.length}`);
72
+
73
+ const violations: string[] = [];
74
+
75
+ for (const file of files) {
76
+ const content = readFileSync(join(promptsDir, file), "utf-8");
77
+ const lines = content.split("\n");
78
+
79
+ for (let i = 0; i < lines.length; i++) {
80
+ const line = lines[i];
81
+ const trimmed = line.trim();
82
+
83
+ // Match lines containing sqlite3 targeting gsd.db in any common form:
84
+ // sqlite3 .gsd/gsd.db, sqlite3 ./.gsd/gsd.db, sqlite3 "/path/.gsd/gsd.db",
85
+ // sqlite3 -header .gsd/gsd.db, etc.
86
+ // Guardrail text that says "Never run" or "Do NOT query" is fine — only flag
87
+ // lines where these appear without a surrounding prohibition keyword.
88
+ if (/sqlite3\b.*gsd\.db/.test(trimmed)) {
89
+ const context = lines.slice(Math.max(0, i - 3), i + 1).join(" ");
90
+ if (!/Never|Do NOT|do not|don't|prohibited|forbidden|never run/i.test(context)) {
91
+ violations.push(`${file}:${i + 1} — unguarded sqlite3 command: ${trimmed}`);
92
+ }
93
+ }
94
+ // Match node -e with better-sqlite3 require in any quoting style
95
+ if (/node\s+-e\s+.*(?:require|import).*better-sqlite3/.test(trimmed)) {
96
+ const context = lines.slice(Math.max(0, i - 3), i + 1).join(" ");
97
+ if (!/Never|Do NOT|do not|don't|prohibited|forbidden|never run/i.test(context)) {
98
+ violations.push(`${file}:${i + 1} — unguarded node -e require command: ${trimmed}`);
99
+ }
100
+ }
101
+ }
102
+ }
103
+
104
+ assert.deepEqual(
105
+ violations,
106
+ [],
107
+ `Found prompts with unguarded sqlite3/better-sqlite3 invocations:\n${violations.join("\n")}`,
108
+ );
109
+ });
@@ -38,13 +38,17 @@ assertEq(
38
38
  "Standard worktree layout resolves to project root DB path",
39
39
  );
40
40
 
41
- // Symlink-resolved layout (the regression — /.gsd/projects/<hash>/worktrees/...)
41
+ // Symlink-resolved layout: /.gsd/projects/<hash>/worktrees/...
42
+ // After PR #2952, these paths resolve to the hash-level DB (same as external-state),
43
+ // because on POSIX getcwd() returns the canonical (symlink-resolved) path anyway, so
44
+ // a path like <proj>/.gsd/projects/<hash>/worktrees/ in practice is always
45
+ // ~/.gsd/projects/<hash>/worktrees/ after the OS resolves the .gsd symlink.
42
46
  const symlinkPath = `/home/user/myproject/.gsd/projects/abc123def/worktrees/M001/work`;
43
47
  const symlinkResult = resolveProjectRootDbPath(symlinkPath);
44
48
  assertEq(
45
49
  symlinkResult,
46
- join("/home/user/myproject", ".gsd", "gsd.db"),
47
- "Symlink-resolved layout (/.gsd/projects/<hash>/worktrees/) resolves to project root DB path (#2517)",
50
+ join("/home/user/myproject/.gsd/projects/abc123def", "gsd.db"),
51
+ "/.gsd/projects/<hash>/worktrees/ resolves to hash-level DB (#2517, updated for #2952)",
48
52
  );
49
53
 
50
54
  // Windows-style separators for symlink layout
@@ -53,8 +57,8 @@ if (sep === "\\") {
53
57
  const winResult = resolveProjectRootDbPath(winSymlinkPath);
54
58
  assertEq(
55
59
  winResult,
56
- join("C:\\Users\\dev\\project", ".gsd", "gsd.db"),
57
- "Windows symlink layout resolves correctly",
60
+ join("C:\\Users\\dev\\project\\.gsd\\projects\\abc123def", "gsd.db"),
61
+ "Windows /.gsd/projects/<hash>/worktrees/ resolves to hash-level DB",
58
62
  );
59
63
  } else {
60
64
  // On non-Windows, test forward-slash variant explicitly
@@ -62,8 +66,8 @@ if (sep === "\\") {
62
66
  const fwdResult = resolveProjectRootDbPath(fwdSymlinkPath);
63
67
  assertEq(
64
68
  fwdResult,
65
- join("/home/user/myproject", ".gsd", "gsd.db"),
66
- "Forward-slash symlink layout resolves correctly on POSIX",
69
+ join("/home/user/myproject/.gsd/projects/abc123def", "gsd.db"),
70
+ "Forward-slash /.gsd/projects/<hash>/worktrees/ resolves to hash-level DB on POSIX",
67
71
  );
68
72
  }
69
73
 
@@ -72,8 +76,8 @@ const deepSymlinkPath = `/home/user/myproject/.gsd/projects/deadbeef42/worktrees
72
76
  const deepResult = resolveProjectRootDbPath(deepSymlinkPath);
73
77
  assertEq(
74
78
  deepResult,
75
- join("/home/user/myproject", ".gsd", "gsd.db"),
76
- "Deep symlink worktree path still resolves to project root DB",
79
+ join("/home/user/myproject/.gsd/projects/deadbeef42", "gsd.db"),
80
+ "Deep /.gsd/projects/<hash>/worktrees/ path resolves to hash-level DB (#2952)",
77
81
  );
78
82
 
79
83
  // Non-worktree path should be unchanged
@@ -24,6 +24,7 @@ import {
24
24
  saveDecisionToDb,
25
25
  updateRequirementInDb,
26
26
  saveArtifactToDb,
27
+ extractDeferredSliceRef,
27
28
  } from '../db-writer.ts';
28
29
  import type { Decision, Requirement } from '../types.ts';
29
30
 
@@ -475,6 +476,71 @@ describe('db-writer', () => {
475
476
  }
476
477
  });
477
478
 
479
+ test('updateRequirementInDb — seeds from REQUIREMENTS.md when DB empty (#3346)', async () => {
480
+ const tmpDir = makeTmpDir();
481
+ const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
482
+ openDatabase(dbPath);
483
+
484
+ try {
485
+ // Write a REQUIREMENTS.md with real content (simulating discussion phase output)
486
+ const reqContent = [
487
+ '# Requirements',
488
+ '',
489
+ '## Active',
490
+ '',
491
+ '### R005 — User authentication',
492
+ '- Class: functional',
493
+ '- Why: Users need secure access',
494
+ '- Source: user-research',
495
+ '- Primary owner: M001/S02',
496
+ '',
497
+ '### R007 — API rate limiting',
498
+ '- Class: non-functional',
499
+ '- Why: Prevent abuse',
500
+ '- Source: architecture',
501
+ '- Primary owner: M001/S03',
502
+ '',
503
+ '## Validated',
504
+ '',
505
+ '### R001 — Database schema',
506
+ '- Class: functional',
507
+ '- Why: Foundation for storage',
508
+ '- Source: design',
509
+ '- Validation: S01 verified',
510
+ ].join('\n');
511
+ fs.writeFileSync(path.join(tmpDir, '.gsd', 'REQUIREMENTS.md'), reqContent);
512
+
513
+ // DB is empty — no requirements seeded. Update R005 to "validated".
514
+ // Before #3346 fix: this would create a skeleton with empty fields.
515
+ // After fix: this seeds all 3 requirements from REQUIREMENTS.md first.
516
+ await updateRequirementInDb('R005', {
517
+ status: 'validated',
518
+ validation: 'S02 — auth flow verified',
519
+ }, tmpDir);
520
+
521
+ // R005 should have the update AND the original content from markdown
522
+ const r005 = getRequirementById('R005');
523
+ assert.ok(r005, 'R005 should exist');
524
+ assert.equal(r005!.status, 'validated', 'status should be updated');
525
+ assert.equal(r005!.validation, 'S02 — auth flow verified', 'validation should be updated');
526
+ assert.equal(r005!.class, 'functional', 'class should be preserved from REQUIREMENTS.md');
527
+ assert.ok(r005!.description?.includes('authentication') || r005!.full_content?.includes('authentication'),
528
+ 'original content should be preserved');
529
+
530
+ // R007 and R001 should also be seeded (not just the one being updated)
531
+ const r007 = getRequirementById('R007');
532
+ assert.ok(r007, 'R007 should be seeded from REQUIREMENTS.md');
533
+ assert.equal(r007!.status, 'active', 'R007 status should be active');
534
+
535
+ const r001 = getRequirementById('R001');
536
+ assert.ok(r001, 'R001 should be seeded from REQUIREMENTS.md');
537
+ assert.equal(r001!.status, 'validated', 'R001 status should be validated (from section heading)');
538
+ } finally {
539
+ closeDatabase();
540
+ cleanupDir(tmpDir);
541
+ }
542
+ });
543
+
478
544
  // ═══════════════════════════════════════════════════════════════════════════
479
545
  // saveArtifactToDb Tests
480
546
  // ═══════════════════════════════════════════════════════════════════════════
@@ -694,4 +760,72 @@ describe('db-writer', () => {
694
760
 
695
761
  // ═══════════════════════════════════════════════════════════════════════════
696
762
 
763
+ // ═══════════════════════════════════════════════════════════════════════════
764
+ // extractDeferredSliceRef
765
+ // ═══════════════════════════════════════════════════════════════════════════
766
+
767
+ describe('extractDeferredSliceRef', () => {
768
+ const fields = (scope: string, choice: string, decision: string) => ({
769
+ scope,
770
+ choice,
771
+ decision,
772
+ });
773
+
774
+ test('detects deferral in scope with M###/S## pattern in choice', () => {
775
+ const result = extractDeferredSliceRef(
776
+ fields('deferral of low-priority work', 'Move M001/S03 to backlog', ''),
777
+ );
778
+ assert.deepStrictEqual(result, { milestoneId: 'M001', sliceId: 'S03' });
779
+ });
780
+
781
+ test('detects deferral in choice field', () => {
782
+ const result = extractDeferredSliceRef(
783
+ fields('slice prioritization', 'defer M002/S01 until next sprint', ''),
784
+ );
785
+ assert.deepStrictEqual(result, { milestoneId: 'M002', sliceId: 'S01' });
786
+ });
787
+
788
+ test('detects deferral in decision field', () => {
789
+ const result = extractDeferredSliceRef(
790
+ fields('resource constraints', '', 'deferred M010/S12 pending review'),
791
+ );
792
+ assert.deepStrictEqual(result, { milestoneId: 'M010', sliceId: 'S12' });
793
+ });
794
+
795
+ test('returns null when no M###/S## pattern is present', () => {
796
+ const result = extractDeferredSliceRef(
797
+ fields('deferral of work', 'will revisit later', 'deferred indefinitely'),
798
+ );
799
+ assert.strictEqual(result, null);
800
+ });
801
+
802
+ test('recognises "deferring" variant', () => {
803
+ const result = extractDeferredSliceRef(
804
+ fields('deferring this slice', 'M005/S02 can wait', ''),
805
+ );
806
+ assert.deepStrictEqual(result, { milestoneId: 'M005', sliceId: 'S02' });
807
+ });
808
+
809
+ test('recognises "defers" variant', () => {
810
+ const result = extractDeferredSliceRef(
811
+ fields('team defers slice', 'M100/S10 not urgent', ''),
812
+ );
813
+ assert.deepStrictEqual(result, { milestoneId: 'M100', sliceId: 'S10' });
814
+ });
815
+
816
+ test('returns first M###/S## match when multiple patterns exist', () => {
817
+ const result = extractDeferredSliceRef(
818
+ fields('', 'defer M003/S01 and M003/S02', ''),
819
+ );
820
+ assert.deepStrictEqual(result, { milestoneId: 'M003', sliceId: 'S01' });
821
+ });
822
+
823
+ test('returns null when no deferral keyword is present', () => {
824
+ const result = extractDeferredSliceRef(
825
+ fields('approved work', 'M001/S01 is ready', 'proceed with M001/S01'),
826
+ );
827
+ assert.strictEqual(result, null);
828
+ });
829
+ });
830
+
697
831
  });
@@ -0,0 +1,203 @@
1
+ /**
2
+ * Regression test for #2661: Auto-mode dispatches deferred slices.
3
+ *
4
+ * When a decision defers a slice, the dispatcher must skip it and advance
5
+ * to the next eligible slice. This tests both:
6
+ * 1. deriveStateFromDb skips slices with status "deferred"
7
+ * 2. saveDecisionToDb updates the slice status when the decision is a deferral
8
+ */
9
+
10
+ import { describe, test } from "node:test";
11
+ import assert from "node:assert/strict";
12
+ import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
13
+ import { join } from "node:path";
14
+ import { tmpdir } from "node:os";
15
+
16
+ import { deriveStateFromDb, invalidateStateCache } from "../state.ts";
17
+ import {
18
+ openDatabase,
19
+ closeDatabase,
20
+ isDbAvailable,
21
+ insertMilestone,
22
+ insertSlice,
23
+ insertTask,
24
+ insertArtifact,
25
+ updateSliceStatus,
26
+ } from "../gsd-db.ts";
27
+ import { isDeferredStatus } from "../status-guards.ts";
28
+
29
+ // ─── Helpers ──────────────────────────────────────────────────────────────
30
+
31
+ function createFixtureBase(): string {
32
+ const base = mkdtempSync(join(tmpdir(), "gsd-deferred-dispatch-"));
33
+ mkdirSync(join(base, ".gsd", "milestones"), { recursive: true });
34
+ return base;
35
+ }
36
+
37
+ function writeFile(base: string, relativePath: string, content: string): void {
38
+ const full = join(base, ".gsd", relativePath);
39
+ mkdirSync(join(full, ".."), { recursive: true });
40
+ writeFileSync(full, content);
41
+ }
42
+
43
+ function cleanup(base: string): void {
44
+ rmSync(base, { recursive: true, force: true });
45
+ }
46
+
47
+ // ─── Tests ────────────────────────────────────────────────────────────────
48
+
49
+ describe("deferred-slice-dispatch (#2661)", () => {
50
+ test("isDeferredStatus returns true for 'deferred'", () => {
51
+ assert.ok(isDeferredStatus("deferred"), "should recognize 'deferred'");
52
+ assert.ok(!isDeferredStatus("active"), "should not match 'active'");
53
+ assert.ok(!isDeferredStatus("complete"), "should not match 'complete'");
54
+ assert.ok(!isDeferredStatus("pending"), "should not match 'pending'");
55
+ });
56
+
57
+ test("deriveStateFromDb skips deferred slice and picks next eligible", async () => {
58
+ const base = createFixtureBase();
59
+ try {
60
+ openDatabase(":memory:");
61
+ assert.ok(isDbAvailable());
62
+
63
+ // M001 with three slices: S01 complete, S02 deferred, S03 pending
64
+ insertMilestone({ id: "M001", title: "Test Milestone", status: "active" });
65
+
66
+ insertSlice({ id: "S01", milestoneId: "M001", title: "Done Slice", status: "complete", risk: "low", depends: [] });
67
+ insertSlice({ id: "S02", milestoneId: "M001", title: "Deferred Slice", status: "deferred", risk: "low", depends: [] });
68
+ insertSlice({ id: "S03", milestoneId: "M001", title: "Next Slice", status: "pending", risk: "low", depends: [] });
69
+
70
+ // S01 needs a SUMMARY file to count as complete for milestone-level checks
71
+ writeFile(base, "milestones/M001/M001-ROADMAP.md", `# M001: Test Milestone
72
+
73
+ **Vision:** Test deferred slices.
74
+
75
+ ## Slices
76
+
77
+ - [x] **S01: Done Slice** \`risk:low\` \`depends:[]\`
78
+ > Done.
79
+
80
+ - [ ] **S02: Deferred Slice** \`risk:low\` \`depends:[]\`
81
+ > Deferred.
82
+
83
+ - [ ] **S03: Next Slice** \`risk:low\` \`depends:[]\`
84
+ > Next.
85
+ `);
86
+ writeFile(base, "milestones/M001/slices/S01/S01-SUMMARY.md", "# S01 Summary\nDone.");
87
+
88
+ invalidateStateCache();
89
+ const state = await deriveStateFromDb(base);
90
+
91
+ // The active slice must be S03, NOT S02 (which is deferred)
92
+ assert.equal(state.activeMilestone?.id, "M001", "active milestone is M001");
93
+ assert.equal(state.activeSlice?.id, "S03", "active slice should skip deferred S02 and land on S03");
94
+ assert.notEqual(state.activeSlice?.id, "S02", "active slice must NOT be the deferred S02");
95
+
96
+ closeDatabase();
97
+ } finally {
98
+ closeDatabase();
99
+ cleanup(base);
100
+ }
101
+ });
102
+
103
+ test("deriveStateFromDb does not count deferred slices as done for progress", async () => {
104
+ const base = createFixtureBase();
105
+ try {
106
+ openDatabase(":memory:");
107
+
108
+ insertMilestone({ id: "M001", title: "Test", status: "active" });
109
+ insertSlice({ id: "S01", milestoneId: "M001", title: "Complete", status: "complete", risk: "low", depends: [] });
110
+ insertSlice({ id: "S02", milestoneId: "M001", title: "Deferred", status: "deferred", risk: "low", depends: [] });
111
+ insertSlice({ id: "S03", milestoneId: "M001", title: "Pending", status: "pending", risk: "low", depends: [] });
112
+
113
+ writeFile(base, "milestones/M001/M001-ROADMAP.md", `# M001
114
+ ## Slices
115
+ - [x] **S01: Complete** \`risk:low\` \`depends:[]\`
116
+ - [ ] **S02: Deferred** \`risk:low\` \`depends:[]\`
117
+ - [ ] **S03: Pending** \`risk:low\` \`depends:[]\`
118
+ `);
119
+ writeFile(base, "milestones/M001/slices/S01/S01-SUMMARY.md", "# Done");
120
+
121
+ invalidateStateCache();
122
+ const state = await deriveStateFromDb(base);
123
+
124
+ // Deferred slices should not count as "done" in progress
125
+ // Only S01 (complete) counts as done
126
+ assert.equal(state.progress?.slices?.done, 1, "only 1 slice (S01) should be done");
127
+ // Total should still be 3 (deferred slices are still part of the milestone)
128
+ assert.equal(state.progress?.slices?.total, 3, "all 3 slices counted in total");
129
+
130
+ closeDatabase();
131
+ } finally {
132
+ closeDatabase();
133
+ cleanup(base);
134
+ }
135
+ });
136
+
137
+ test("all slices deferred results in blocked state", async () => {
138
+ const base = createFixtureBase();
139
+ try {
140
+ openDatabase(":memory:");
141
+
142
+ insertMilestone({ id: "M001", title: "Test", status: "active" });
143
+ insertSlice({ id: "S01", milestoneId: "M001", title: "Deferred A", status: "deferred", risk: "low", depends: [] });
144
+ insertSlice({ id: "S02", milestoneId: "M001", title: "Deferred B", status: "deferred", risk: "low", depends: [] });
145
+
146
+ writeFile(base, "milestones/M001/M001-ROADMAP.md", `# M001
147
+ ## Slices
148
+ - [ ] **S01: Deferred A** \`risk:low\` \`depends:[]\`
149
+ - [ ] **S02: Deferred B** \`risk:low\` \`depends:[]\`
150
+ `);
151
+
152
+ invalidateStateCache();
153
+ const state = await deriveStateFromDb(base);
154
+
155
+ // No eligible slice — should be blocked
156
+ assert.equal(state.activeSlice, null, "no active slice when all deferred");
157
+ assert.equal(state.phase, "blocked", "phase should be blocked when all slices deferred");
158
+
159
+ closeDatabase();
160
+ } finally {
161
+ closeDatabase();
162
+ cleanup(base);
163
+ }
164
+ });
165
+
166
+ test("saveDecisionToDb marks slice as deferred when decision is a deferral", async () => {
167
+ const base = createFixtureBase();
168
+ try {
169
+ openDatabase(":memory:");
170
+
171
+ insertMilestone({ id: "M001", title: "Test", status: "active" });
172
+ insertSlice({ id: "S03", milestoneId: "M001", title: "Target Slice", status: "active", risk: "low", depends: [] });
173
+
174
+ writeFile(base, "milestones/M001/M001-ROADMAP.md", `# M001
175
+ ## Slices
176
+ - [ ] **S03: Target Slice** \`risk:low\` \`depends:[]\`
177
+ `);
178
+
179
+ const { saveDecisionToDb } = await import("../db-writer.ts");
180
+ const { getSlice } = await import("../gsd-db.ts");
181
+
182
+ // Save a deferral decision that references M001/S03
183
+ await saveDecisionToDb(
184
+ {
185
+ scope: "deferral",
186
+ decision: "Defer S03 to focus on higher priority work",
187
+ choice: "defer M001/S03",
188
+ rationale: "Not ready yet",
189
+ },
190
+ base,
191
+ );
192
+
193
+ // The slice status should now be "deferred"
194
+ const slice = getSlice("M001", "S03");
195
+ assert.equal(slice?.status, "deferred", "slice status should be updated to 'deferred' after deferral decision");
196
+
197
+ closeDatabase();
198
+ } finally {
199
+ closeDatabase();
200
+ cleanup(base);
201
+ }
202
+ });
203
+ });