gsd-pi 2.63.0 → 2.64.0

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 (353) hide show
  1. package/README.md +46 -134
  2. package/dist/cli.js +48 -6
  3. package/dist/headless-query.js +11 -1
  4. package/dist/help-text.js +4 -1
  5. package/dist/onboarding.js +15 -8
  6. package/dist/resource-loader.js +18 -3
  7. package/dist/resources/extensions/cmux/index.js +21 -12
  8. package/dist/resources/extensions/gsd/auto/detect-stuck.js +27 -0
  9. package/dist/resources/extensions/gsd/auto/finalize-timeout.js +40 -0
  10. package/dist/resources/extensions/gsd/auto/loop.js +4 -0
  11. package/dist/resources/extensions/gsd/auto/phases.js +157 -22
  12. package/dist/resources/extensions/gsd/auto/session.js +12 -0
  13. package/dist/resources/extensions/gsd/auto-dashboard.js +9 -3
  14. package/dist/resources/extensions/gsd/auto-model-selection.js +32 -0
  15. package/dist/resources/extensions/gsd/auto-post-unit.js +124 -10
  16. package/dist/resources/extensions/gsd/auto-prompts.js +25 -0
  17. package/dist/resources/extensions/gsd/auto-recovery.js +15 -7
  18. package/dist/resources/extensions/gsd/auto-start.js +10 -21
  19. package/dist/resources/extensions/gsd/auto-timers.js +2 -1
  20. package/dist/resources/extensions/gsd/auto-tool-tracking.js +17 -0
  21. package/dist/resources/extensions/gsd/auto-worktree.js +13 -7
  22. package/dist/resources/extensions/gsd/auto.js +19 -2
  23. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +147 -75
  24. package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +13 -0
  25. package/dist/resources/extensions/gsd/bootstrap/query-tools.js +85 -0
  26. package/dist/resources/extensions/gsd/bootstrap/register-extension.js +3 -0
  27. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +32 -1
  28. package/dist/resources/extensions/gsd/bootstrap/sanitize-complete-milestone.js +54 -0
  29. package/dist/resources/extensions/gsd/bootstrap/system-context.js +30 -2
  30. package/dist/resources/extensions/gsd/commands-handlers.js +9 -4
  31. package/dist/resources/extensions/gsd/constants.js +42 -0
  32. package/dist/resources/extensions/gsd/db-writer.js +72 -4
  33. package/dist/resources/extensions/gsd/forensics.js +20 -4
  34. package/dist/resources/extensions/gsd/gsd-db.js +64 -17
  35. package/dist/resources/extensions/gsd/guided-flow.js +19 -0
  36. package/dist/resources/extensions/gsd/metrics.js +27 -1
  37. package/dist/resources/extensions/gsd/native-git-bridge.js +5 -3
  38. package/dist/resources/extensions/gsd/preferences-types.js +2 -0
  39. package/dist/resources/extensions/gsd/preferences.js +7 -2
  40. package/dist/resources/extensions/gsd/prompt-loader.js +7 -0
  41. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +2 -0
  42. package/dist/resources/extensions/gsd/prompts/complete-slice.md +2 -0
  43. package/dist/resources/extensions/gsd/prompts/doctor-heal.md +1 -0
  44. package/dist/resources/extensions/gsd/prompts/forensics.md +2 -0
  45. package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +2 -0
  46. package/dist/resources/extensions/gsd/prompts/system.md +4 -7
  47. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +2 -0
  48. package/dist/resources/extensions/gsd/roadmap-mutations.js +1 -1
  49. package/dist/resources/extensions/gsd/roadmap-slices.js +9 -5
  50. package/dist/resources/extensions/gsd/safety/content-validator.js +73 -0
  51. package/dist/resources/extensions/gsd/safety/destructive-guard.js +34 -0
  52. package/dist/resources/extensions/gsd/safety/evidence-collector.js +109 -0
  53. package/dist/resources/extensions/gsd/safety/evidence-cross-ref.js +83 -0
  54. package/dist/resources/extensions/gsd/safety/file-change-validator.js +71 -0
  55. package/dist/resources/extensions/gsd/safety/git-checkpoint.js +91 -0
  56. package/dist/resources/extensions/gsd/safety/safety-harness.js +64 -0
  57. package/dist/resources/extensions/gsd/slice-parallel-conflict.js +67 -0
  58. package/dist/resources/extensions/gsd/slice-parallel-eligibility.js +51 -0
  59. package/dist/resources/extensions/gsd/slice-parallel-orchestrator.js +378 -0
  60. package/dist/resources/extensions/gsd/state.js +74 -14
  61. package/dist/resources/extensions/gsd/status-guards.js +11 -0
  62. package/dist/resources/extensions/gsd/tools/complete-milestone.js +17 -12
  63. package/dist/resources/extensions/gsd/tools/complete-slice.js +40 -26
  64. package/dist/resources/extensions/gsd/tools/complete-task.js +12 -12
  65. package/dist/resources/extensions/gsd/tools/plan-milestone.js +33 -25
  66. package/dist/resources/extensions/gsd/tools/plan-slice.js +5 -8
  67. package/dist/resources/extensions/gsd/workflow-projections.js +21 -5
  68. package/dist/resources/extensions/gsd/worktree-manager.js +82 -29
  69. package/dist/resources/extensions/gsd/worktree-resolver.js +4 -3
  70. package/dist/resources/extensions/mcp-client/auth.js +101 -0
  71. package/dist/resources/extensions/mcp-client/index.js +10 -1
  72. package/dist/resources/extensions/ollama/index.js +28 -22
  73. package/dist/resources/extensions/ollama/model-capabilities.js +37 -34
  74. package/dist/resources/extensions/ollama/ndjson-stream.js +54 -0
  75. package/dist/resources/extensions/ollama/ollama-chat-provider.js +380 -0
  76. package/dist/resources/extensions/ollama/ollama-client.js +23 -32
  77. package/dist/resources/extensions/ollama/ollama-discovery.js +2 -7
  78. package/dist/resources/extensions/ollama/ollama-tool.js +62 -0
  79. package/dist/resources/extensions/ollama/thinking-parser.js +104 -0
  80. package/dist/update-cmd.js +4 -2
  81. package/dist/web/standalone/.next/BUILD_ID +1 -1
  82. package/dist/web/standalone/.next/app-path-routes-manifest.json +12 -12
  83. package/dist/web/standalone/.next/build-manifest.json +2 -2
  84. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  85. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  86. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  87. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  88. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  89. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  90. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  91. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  92. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  93. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  94. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  95. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  96. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  97. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  98. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  99. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  100. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  101. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  102. package/dist/web/standalone/.next/server/app/api/boot/route.js.nft.json +1 -1
  103. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
  104. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js.nft.json +1 -1
  105. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
  106. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js.nft.json +1 -1
  107. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
  108. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js.nft.json +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/cleanup/route.js +1 -1
  112. package/dist/web/standalone/.next/server/app/api/cleanup/route.js.nft.json +1 -1
  113. package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
  114. package/dist/web/standalone/.next/server/app/api/doctor/route.js.nft.json +1 -1
  115. package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
  116. package/dist/web/standalone/.next/server/app/api/export-data/route.js.nft.json +1 -1
  117. package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
  118. package/dist/web/standalone/.next/server/app/api/files/route.js.nft.json +1 -1
  119. package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
  120. package/dist/web/standalone/.next/server/app/api/forensics/route.js.nft.json +1 -1
  121. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  122. package/dist/web/standalone/.next/server/app/api/git/route.js.nft.json +1 -1
  123. package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
  124. package/dist/web/standalone/.next/server/app/api/history/route.js.nft.json +1 -1
  125. package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
  126. package/dist/web/standalone/.next/server/app/api/hooks/route.js.nft.json +1 -1
  127. package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
  128. package/dist/web/standalone/.next/server/app/api/inspect/route.js.nft.json +1 -1
  129. package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
  130. package/dist/web/standalone/.next/server/app/api/knowledge/route.js.nft.json +1 -1
  131. package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
  132. package/dist/web/standalone/.next/server/app/api/live-state/route.js.nft.json +1 -1
  133. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  134. package/dist/web/standalone/.next/server/app/api/onboarding/route.js.nft.json +1 -1
  135. package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
  136. package/dist/web/standalone/.next/server/app/api/projects/route.js.nft.json +1 -1
  137. package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
  138. package/dist/web/standalone/.next/server/app/api/recovery/route.js.nft.json +1 -1
  139. package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
  140. package/dist/web/standalone/.next/server/app/api/session/browser/route.js.nft.json +1 -1
  141. package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
  142. package/dist/web/standalone/.next/server/app/api/session/command/route.js.nft.json +1 -1
  143. package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
  144. package/dist/web/standalone/.next/server/app/api/session/events/route.js.nft.json +1 -1
  145. package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
  146. package/dist/web/standalone/.next/server/app/api/session/manage/route.js.nft.json +1 -1
  147. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  148. package/dist/web/standalone/.next/server/app/api/settings-data/route.js.nft.json +1 -1
  149. package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
  150. package/dist/web/standalone/.next/server/app/api/skill-health/route.js.nft.json +1 -1
  151. package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
  152. package/dist/web/standalone/.next/server/app/api/steer/route.js.nft.json +1 -1
  153. package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
  154. package/dist/web/standalone/.next/server/app/api/switch-root/route.js.nft.json +1 -1
  155. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +1 -1
  156. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js.nft.json +1 -1
  157. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +2 -2
  158. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js.nft.json +1 -1
  159. package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
  160. package/dist/web/standalone/.next/server/app/api/undo/route.js.nft.json +1 -1
  161. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  162. package/dist/web/standalone/.next/server/app/api/visualizer/route.js.nft.json +1 -1
  163. package/dist/web/standalone/.next/server/app/index.html +1 -1
  164. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  165. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  166. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  167. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  168. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  169. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  170. package/dist/web/standalone/.next/server/app-paths-manifest.json +12 -12
  171. package/dist/web/standalone/.next/server/chunks/6897.js +12 -0
  172. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  173. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  174. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  175. package/dist/welcome-screen.js +1 -1
  176. package/package.json +1 -1
  177. package/packages/pi-agent-core/dist/agent-loop.d.ts +8 -0
  178. package/packages/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
  179. package/packages/pi-agent-core/dist/agent-loop.js +50 -0
  180. package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
  181. package/packages/pi-agent-core/src/agent-loop.test.ts +221 -5
  182. package/packages/pi-agent-core/src/agent-loop.ts +53 -0
  183. package/packages/pi-ai/dist/types.d.ts +16 -1
  184. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  185. package/packages/pi-ai/dist/types.js.map +1 -1
  186. package/packages/pi-ai/src/types.ts +18 -1
  187. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts +9 -0
  188. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
  189. package/packages/pi-coding-agent/dist/core/auth-storage.js +50 -1
  190. package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
  191. package/packages/pi-coding-agent/dist/core/auth-storage.test.js +41 -0
  192. package/packages/pi-coding-agent/dist/core/auth-storage.test.js.map +1 -1
  193. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts +7 -0
  194. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  195. package/packages/pi-coding-agent/dist/core/extensions/loader.js +31 -4
  196. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  197. package/packages/pi-coding-agent/dist/core/extensions/loader.test.js +28 -1
  198. package/packages/pi-coding-agent/dist/core/extensions/loader.test.js.map +1 -1
  199. package/packages/pi-coding-agent/dist/core/extensions/provider-registration.test.d.ts +2 -0
  200. package/packages/pi-coding-agent/dist/core/extensions/provider-registration.test.d.ts.map +1 -0
  201. package/packages/pi-coding-agent/dist/core/extensions/provider-registration.test.js +46 -0
  202. package/packages/pi-coding-agent/dist/core/extensions/provider-registration.test.js.map +1 -0
  203. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +2 -0
  204. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
  205. package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
  206. package/packages/pi-coding-agent/dist/core/model-registry.d.ts +1 -0
  207. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  208. package/packages/pi-coding-agent/dist/core/model-registry.js +12 -0
  209. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  210. package/packages/pi-coding-agent/dist/core/model-resolver.js +3 -3
  211. package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
  212. package/packages/pi-coding-agent/dist/core/resource-loader.d.ts +23 -1
  213. package/packages/pi-coding-agent/dist/core/resource-loader.d.ts.map +1 -1
  214. package/packages/pi-coding-agent/dist/core/resource-loader.js +80 -56
  215. package/packages/pi-coding-agent/dist/core/resource-loader.js.map +1 -1
  216. package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
  217. package/packages/pi-coding-agent/dist/core/sdk.js +9 -0
  218. package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
  219. package/packages/pi-coding-agent/package.json +1 -1
  220. package/packages/pi-coding-agent/src/core/auth-storage.test.ts +53 -0
  221. package/packages/pi-coding-agent/src/core/auth-storage.ts +66 -1
  222. package/packages/pi-coding-agent/src/core/extensions/loader.test.ts +39 -1
  223. package/packages/pi-coding-agent/src/core/extensions/loader.ts +34 -4
  224. package/packages/pi-coding-agent/src/core/extensions/provider-registration.test.ts +81 -0
  225. package/packages/pi-coding-agent/src/core/extensions/types.ts +2 -0
  226. package/packages/pi-coding-agent/src/core/model-registry.ts +14 -0
  227. package/packages/pi-coding-agent/src/core/model-resolver.ts +3 -3
  228. package/packages/pi-coding-agent/src/core/resource-loader.ts +89 -56
  229. package/packages/pi-coding-agent/src/core/sdk.ts +10 -0
  230. package/pkg/package.json +1 -1
  231. package/src/resources/extensions/cmux/index.ts +18 -12
  232. package/src/resources/extensions/gsd/auto/detect-stuck.ts +27 -0
  233. package/src/resources/extensions/gsd/auto/finalize-timeout.ts +46 -0
  234. package/src/resources/extensions/gsd/auto/loop.ts +5 -0
  235. package/src/resources/extensions/gsd/auto/phases.ts +194 -33
  236. package/src/resources/extensions/gsd/auto/session.ts +14 -0
  237. package/src/resources/extensions/gsd/auto-dashboard.ts +11 -3
  238. package/src/resources/extensions/gsd/auto-model-selection.ts +36 -0
  239. package/src/resources/extensions/gsd/auto-post-unit.ts +141 -12
  240. package/src/resources/extensions/gsd/auto-prompts.ts +21 -0
  241. package/src/resources/extensions/gsd/auto-recovery.ts +9 -8
  242. package/src/resources/extensions/gsd/auto-start.ts +11 -20
  243. package/src/resources/extensions/gsd/auto-timers.ts +2 -1
  244. package/src/resources/extensions/gsd/auto-tool-tracking.ts +19 -0
  245. package/src/resources/extensions/gsd/auto-worktree.ts +14 -6
  246. package/src/resources/extensions/gsd/auto.ts +22 -1
  247. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +160 -88
  248. package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +15 -0
  249. package/src/resources/extensions/gsd/bootstrap/query-tools.ts +98 -0
  250. package/src/resources/extensions/gsd/bootstrap/register-extension.ts +4 -0
  251. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +36 -1
  252. package/src/resources/extensions/gsd/bootstrap/sanitize-complete-milestone.ts +57 -0
  253. package/src/resources/extensions/gsd/bootstrap/system-context.ts +31 -2
  254. package/src/resources/extensions/gsd/commands-handlers.ts +10 -4
  255. package/src/resources/extensions/gsd/constants.ts +44 -0
  256. package/src/resources/extensions/gsd/db-writer.ts +78 -4
  257. package/src/resources/extensions/gsd/forensics.ts +21 -5
  258. package/src/resources/extensions/gsd/gsd-db.ts +64 -17
  259. package/src/resources/extensions/gsd/guided-flow.ts +22 -0
  260. package/src/resources/extensions/gsd/metrics.ts +28 -1
  261. package/src/resources/extensions/gsd/native-git-bridge.ts +5 -3
  262. package/src/resources/extensions/gsd/preferences-types.ts +16 -0
  263. package/src/resources/extensions/gsd/preferences.ts +9 -2
  264. package/src/resources/extensions/gsd/prompt-loader.ts +8 -0
  265. package/src/resources/extensions/gsd/prompts/complete-milestone.md +2 -0
  266. package/src/resources/extensions/gsd/prompts/complete-slice.md +2 -0
  267. package/src/resources/extensions/gsd/prompts/doctor-heal.md +1 -0
  268. package/src/resources/extensions/gsd/prompts/forensics.md +2 -0
  269. package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +2 -0
  270. package/src/resources/extensions/gsd/prompts/system.md +4 -7
  271. package/src/resources/extensions/gsd/prompts/validate-milestone.md +2 -0
  272. package/src/resources/extensions/gsd/roadmap-mutations.ts +1 -1
  273. package/src/resources/extensions/gsd/roadmap-slices.ts +10 -5
  274. package/src/resources/extensions/gsd/safety/content-validator.ts +98 -0
  275. package/src/resources/extensions/gsd/safety/destructive-guard.ts +49 -0
  276. package/src/resources/extensions/gsd/safety/evidence-collector.ts +151 -0
  277. package/src/resources/extensions/gsd/safety/evidence-cross-ref.ts +120 -0
  278. package/src/resources/extensions/gsd/safety/file-change-validator.ts +108 -0
  279. package/src/resources/extensions/gsd/safety/git-checkpoint.ts +106 -0
  280. package/src/resources/extensions/gsd/safety/safety-harness.ts +105 -0
  281. package/src/resources/extensions/gsd/slice-parallel-conflict.ts +86 -0
  282. package/src/resources/extensions/gsd/slice-parallel-eligibility.ts +73 -0
  283. package/src/resources/extensions/gsd/slice-parallel-orchestrator.ts +477 -0
  284. package/src/resources/extensions/gsd/state.ts +67 -12
  285. package/src/resources/extensions/gsd/status-guards.ts +13 -0
  286. package/src/resources/extensions/gsd/tests/artifact-corruption-2630.test.ts +288 -0
  287. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +34 -13
  288. package/src/resources/extensions/gsd/tests/cmux.test.ts +58 -0
  289. package/src/resources/extensions/gsd/tests/cold-resume-db-reopen.test.ts +51 -0
  290. package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +140 -0
  291. package/src/resources/extensions/gsd/tests/complete-slice-string-coercion.test.ts +211 -0
  292. package/src/resources/extensions/gsd/tests/complete-task.test.ts +39 -0
  293. package/src/resources/extensions/gsd/tests/dashboard-model-label-ordering.test.ts +107 -0
  294. package/src/resources/extensions/gsd/tests/db-access-guardrails.test.ts +109 -0
  295. package/src/resources/extensions/gsd/tests/db-path-worktree-symlink.test.ts +13 -9
  296. package/src/resources/extensions/gsd/tests/db-writer.test.ts +134 -0
  297. package/src/resources/extensions/gsd/tests/deferred-slice-dispatch.test.ts +203 -0
  298. package/src/resources/extensions/gsd/tests/discuss-tool-scoping.test.ts +130 -0
  299. package/src/resources/extensions/gsd/tests/doctor-fix-flag.test.ts +92 -0
  300. package/src/resources/extensions/gsd/tests/finalize-timeout-guard.test.ts +116 -0
  301. package/src/resources/extensions/gsd/tests/flat-rate-routing-guard.test.ts +50 -0
  302. package/src/resources/extensions/gsd/tests/forensics-stuck-loops.test.ts +103 -0
  303. package/src/resources/extensions/gsd/tests/git-checkpoint.test.ts +94 -0
  304. package/src/resources/extensions/gsd/tests/insert-slice-no-wipe.test.ts +88 -0
  305. package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +27 -7
  306. package/src/resources/extensions/gsd/tests/integration/idle-recovery.test.ts +34 -0
  307. package/src/resources/extensions/gsd/tests/metrics.test.ts +116 -1
  308. package/src/resources/extensions/gsd/tests/milestone-status-tool.test.ts +201 -0
  309. package/src/resources/extensions/gsd/tests/plan-milestone-title.test.ts +2 -1
  310. package/src/resources/extensions/gsd/tests/plan-milestone.test.ts +82 -18
  311. package/src/resources/extensions/gsd/tests/preferences.test.ts +10 -0
  312. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +25 -0
  313. package/src/resources/extensions/gsd/tests/roadmap-slices.test.ts +69 -0
  314. package/src/resources/extensions/gsd/tests/shared-wal.test.ts +30 -0
  315. package/src/resources/extensions/gsd/tests/slice-context-injection.test.ts +50 -0
  316. package/src/resources/extensions/gsd/tests/slice-parallel-conflict.test.ts +92 -0
  317. package/src/resources/extensions/gsd/tests/slice-parallel-eligibility.test.ts +95 -0
  318. package/src/resources/extensions/gsd/tests/slice-parallel-orchestrator.test.ts +83 -0
  319. package/src/resources/extensions/gsd/tests/stuck-detection-coverage.test.ts +42 -0
  320. package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +103 -0
  321. package/src/resources/extensions/gsd/tests/tool-param-optionality.test.ts +349 -0
  322. package/src/resources/extensions/gsd/tests/worktree-health-dispatch.test.ts +35 -2
  323. package/src/resources/extensions/gsd/tests/worktree-health-monorepo.test.ts +73 -0
  324. package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +34 -0
  325. package/src/resources/extensions/gsd/tests/worktree-submodule-safety.test.ts +1 -1
  326. package/src/resources/extensions/gsd/tests/worktree-teardown-safety.test.ts +148 -0
  327. package/src/resources/extensions/gsd/tools/complete-milestone.ts +34 -20
  328. package/src/resources/extensions/gsd/tools/complete-slice.ts +41 -26
  329. package/src/resources/extensions/gsd/tools/complete-task.ts +12 -12
  330. package/src/resources/extensions/gsd/tools/plan-milestone.ts +55 -30
  331. package/src/resources/extensions/gsd/tools/plan-slice.ts +13 -8
  332. package/src/resources/extensions/gsd/types.ts +44 -22
  333. package/src/resources/extensions/gsd/workflow-logger.ts +2 -1
  334. package/src/resources/extensions/gsd/workflow-projections.ts +23 -5
  335. package/src/resources/extensions/gsd/worktree-manager.ts +76 -28
  336. package/src/resources/extensions/gsd/worktree-resolver.ts +4 -3
  337. package/src/resources/extensions/mcp-client/auth.ts +149 -0
  338. package/src/resources/extensions/mcp-client/index.ts +16 -1
  339. package/src/resources/extensions/ollama/index.ts +26 -25
  340. package/src/resources/extensions/ollama/model-capabilities.ts +41 -34
  341. package/src/resources/extensions/ollama/ndjson-stream.ts +63 -0
  342. package/src/resources/extensions/ollama/ollama-auth-mode.test.ts +20 -0
  343. package/src/resources/extensions/ollama/ollama-chat-provider.ts +459 -0
  344. package/src/resources/extensions/ollama/ollama-client.ts +30 -30
  345. package/src/resources/extensions/ollama/ollama-discovery.ts +5 -8
  346. package/src/resources/extensions/ollama/ollama-tool.ts +69 -0
  347. package/src/resources/extensions/ollama/tests/ollama-chat-provider-stream.test.ts +82 -0
  348. package/src/resources/extensions/ollama/tests/ollama-discovery.test.ts +0 -27
  349. package/src/resources/extensions/ollama/thinking-parser.ts +116 -0
  350. package/src/resources/extensions/ollama/types.ts +23 -0
  351. package/dist/web/standalone/.next/server/chunks/2229.js +0 -12
  352. /package/dist/web/standalone/.next/static/{5FLUBNdqolRyyehCyChPd → eebXKteM9EaWyseHKTjqp}/_buildManifest.js +0 -0
  353. /package/dist/web/standalone/.next/static/{5FLUBNdqolRyyehCyChPd → eebXKteM9EaWyseHKTjqp}/_ssgManifest.js +0 -0
@@ -0,0 +1,211 @@
1
+ // GSD Extension — String coercion regression tests for complete-slice/task tools
2
+
3
+ import { describe, test, beforeEach, afterEach } from "node:test";
4
+ import assert from "node:assert/strict";
5
+ import * as fs from "node:fs";
6
+ import * as path from "node:path";
7
+ import * as os from "node:os";
8
+ import {
9
+ openDatabase,
10
+ closeDatabase,
11
+ insertMilestone,
12
+ insertSlice,
13
+ insertTask,
14
+ } from "../gsd-db.ts";
15
+ import { handleCompleteSlice } from "../tools/complete-slice.ts";
16
+ import type { CompleteSliceParams } from "../types.ts";
17
+
18
+ // ─── Helpers ─────────────────────────────────────────────────────────────
19
+
20
+ /**
21
+ * The splitPair coercion logic extracted from db-tools.ts sliceCompleteExecute.
22
+ * Duplicated here so we can unit-test it directly.
23
+ */
24
+ function splitPair(s: string): [string, string] {
25
+ const m = s.match(/^(.+?)\s*(?:—|-)\s+(.+)$/);
26
+ return m ? [m[1].trim(), m[2].trim()] : [s.trim(), ""];
27
+ }
28
+
29
+ function makeValidSliceParams(): CompleteSliceParams {
30
+ return {
31
+ sliceId: "S01",
32
+ milestoneId: "M001",
33
+ sliceTitle: "Test Slice",
34
+ oneLiner: "Implemented test slice",
35
+ narrative: "Built and tested.",
36
+ verification: "All tests pass.",
37
+ deviations: "None.",
38
+ knownLimitations: "None.",
39
+ followUps: "None.",
40
+ keyFiles: ["src/foo.ts"],
41
+ keyDecisions: ["D001"],
42
+ patternsEstablished: [],
43
+ observabilitySurfaces: [],
44
+ provides: ["test handler"],
45
+ requirementsSurfaced: [],
46
+ drillDownPaths: [],
47
+ affects: [],
48
+ requirementsAdvanced: [{ id: "R001", how: "Handler validates" }],
49
+ requirementsValidated: [],
50
+ requirementsInvalidated: [],
51
+ filesModified: [{ path: "src/foo.ts", description: "Handler" }],
52
+ requires: [],
53
+ uatContent: "## Smoke Test\n\nVerify all assertions pass.",
54
+ };
55
+ }
56
+
57
+ // ─── splitPair unit tests ────────────────────────────────────────────────
58
+
59
+ describe("splitPair coercion helper (#3565)", () => {
60
+ test("plain string without delimiter returns string + empty", () => {
61
+ const [a, b] = splitPair("src/foo.ts");
62
+ assert.equal(a, "src/foo.ts");
63
+ assert.equal(b, "");
64
+ });
65
+
66
+ test("em-dash delimiter parses both parts", () => {
67
+ const [id, how] = splitPair("R001 — Handler validates task completion");
68
+ assert.equal(id, "R001");
69
+ assert.equal(how, "Handler validates task completion");
70
+ });
71
+
72
+ test("hyphen delimiter parses both parts", () => {
73
+ const [id, proof] = splitPair("R002 - Tests pass");
74
+ assert.equal(id, "R002");
75
+ assert.equal(proof, "Tests pass");
76
+ });
77
+
78
+ test("string with no space around hyphen is treated as plain", () => {
79
+ // e.g. a file path like "src/foo-bar.ts" should not split
80
+ const [a, b] = splitPair("src/foo-bar.ts");
81
+ assert.equal(a, "src/foo-bar.ts");
82
+ assert.equal(b, "");
83
+ });
84
+
85
+ test("whitespace is trimmed from both parts", () => {
86
+ const [id, how] = splitPair(" R003 — Trimmed value ");
87
+ assert.equal(id, "R003");
88
+ assert.equal(how, "Trimmed value");
89
+ });
90
+ });
91
+
92
+ // ─── verificationEvidence sentinel tests ─────────────────────────────────
93
+
94
+ describe("verificationEvidence sentinel coercion (#3565)", () => {
95
+ function coerceEvidence(v: any) {
96
+ return typeof v === "string"
97
+ ? { command: v, exitCode: -1, verdict: "unknown (coerced from string)", durationMs: 0 }
98
+ : v;
99
+ }
100
+
101
+ test("string input produces non-passing sentinel", () => {
102
+ const result = coerceEvidence("npm test");
103
+ assert.equal(result.command, "npm test");
104
+ assert.equal(result.exitCode, -1);
105
+ assert.equal(result.verdict, "unknown (coerced from string)");
106
+ assert.equal(result.durationMs, 0);
107
+ });
108
+
109
+ test("object input passes through unchanged", () => {
110
+ const obj = { command: "npm test", exitCode: 0, verdict: "pass", durationMs: 1234 };
111
+ const result = coerceEvidence(obj);
112
+ assert.equal(result.exitCode, 0);
113
+ assert.equal(result.verdict, "pass");
114
+ assert.equal(result.durationMs, 1234);
115
+ });
116
+
117
+ test("sentinel exitCode is not 0 (must not fabricate success)", () => {
118
+ const result = coerceEvidence("anything");
119
+ assert.notEqual(result.exitCode, 0, "exitCode must not be 0 for coerced strings");
120
+ assert.ok(
121
+ !result.verdict.includes("pass"),
122
+ "verdict must not contain 'pass' for coerced strings",
123
+ );
124
+ });
125
+ });
126
+
127
+ // ─── Handler integration with coerced params ─────────────────────────────
128
+
129
+ describe("handleCompleteSlice with coerced string arrays (#3565)", () => {
130
+ let dbPath: string;
131
+ let basePath: string;
132
+
133
+ beforeEach(() => {
134
+ dbPath = path.join(
135
+ fs.mkdtempSync(path.join(os.tmpdir(), "gsd-coerce-")),
136
+ "test.db",
137
+ );
138
+ openDatabase(dbPath);
139
+
140
+ basePath = fs.mkdtempSync(path.join(os.tmpdir(), "gsd-coerce-handler-"));
141
+ const sliceDir = path.join(basePath, ".gsd", "milestones", "M001", "slices", "S01", "tasks");
142
+ fs.mkdirSync(sliceDir, { recursive: true });
143
+
144
+ const roadmapPath = path.join(basePath, ".gsd", "milestones", "M001", "M001-ROADMAP.md");
145
+ fs.writeFileSync(
146
+ roadmapPath,
147
+ [
148
+ "# M001: Test Milestone",
149
+ "",
150
+ "## Slices",
151
+ "",
152
+ '- [ ] **S01: Test Slice** `risk:medium` `depends:[]`',
153
+ " - After this: basic functionality works",
154
+ ].join("\n"),
155
+ );
156
+
157
+ insertMilestone({ id: "M001" });
158
+ insertSlice({ id: "S01", milestoneId: "M001" });
159
+ insertTask({ id: "T01", sliceId: "S01", milestoneId: "M001", status: "complete", title: "Task 1" });
160
+ });
161
+
162
+ afterEach(() => {
163
+ closeDatabase();
164
+ fs.rmSync(path.dirname(dbPath), { recursive: true, force: true });
165
+ fs.rmSync(basePath, { recursive: true, force: true });
166
+ });
167
+
168
+ test("handler succeeds with coerced filesModified and requirementsAdvanced", async () => {
169
+ const params = makeValidSliceParams();
170
+ // Simulate coercion from plain strings
171
+ params.filesModified = ["src/foo.ts", "src/bar.ts"].map((f) => {
172
+ const [p, d] = splitPair(f);
173
+ return { path: p, description: d };
174
+ });
175
+ params.requirementsAdvanced = ["R001 — Handler validates task completion"].map((r) => {
176
+ const [id, how] = splitPair(r);
177
+ return { id, how };
178
+ });
179
+
180
+ const result = await handleCompleteSlice(params, basePath);
181
+ assert.ok(!("error" in result), "handler should succeed");
182
+ if (!("error" in result)) {
183
+ const summary = fs.readFileSync(result.summaryPath, "utf-8");
184
+ assert.match(summary, /src\/foo\.ts/);
185
+ assert.match(summary, /R001/);
186
+ assert.match(summary, /Handler validates task completion/);
187
+ }
188
+ });
189
+
190
+ test("handler succeeds with coerced requires and requirementsValidated", async () => {
191
+ const params = makeValidSliceParams();
192
+ params.requires = ["S00 — Provided base infrastructure"].map((r) => {
193
+ const [slice, provides] = splitPair(r);
194
+ return { slice, provides };
195
+ });
196
+ params.requirementsValidated = ["R002 - Tests pass"].map((r) => {
197
+ const [id, proof] = splitPair(r);
198
+ return { id, proof };
199
+ });
200
+
201
+ const result = await handleCompleteSlice(params, basePath);
202
+ assert.ok(!("error" in result), "handler should succeed");
203
+ if (!("error" in result)) {
204
+ const summary = fs.readFileSync(result.summaryPath, "utf-8");
205
+ assert.match(summary, /S00/);
206
+ assert.match(summary, /Provided base infrastructure/);
207
+ assert.match(summary, /R002/);
208
+ assert.match(summary, /Tests pass/);
209
+ }
210
+ });
211
+ });
@@ -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