gsd-pi 2.63.0-dev.026d309 → 2.63.0-dev.786f0ff

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 (350) 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 +20 -20
  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 +2 -2
  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 +20 -20
  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/package.json +1 -1
  176. package/packages/pi-agent-core/dist/agent-loop.d.ts +8 -0
  177. package/packages/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
  178. package/packages/pi-agent-core/dist/agent-loop.js +50 -0
  179. package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
  180. package/packages/pi-agent-core/src/agent-loop.test.ts +221 -5
  181. package/packages/pi-agent-core/src/agent-loop.ts +53 -0
  182. package/packages/pi-ai/dist/types.d.ts +16 -1
  183. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  184. package/packages/pi-ai/dist/types.js.map +1 -1
  185. package/packages/pi-ai/src/types.ts +18 -1
  186. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts +9 -0
  187. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
  188. package/packages/pi-coding-agent/dist/core/auth-storage.js +50 -1
  189. package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
  190. package/packages/pi-coding-agent/dist/core/auth-storage.test.js +41 -0
  191. package/packages/pi-coding-agent/dist/core/auth-storage.test.js.map +1 -1
  192. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts +7 -0
  193. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  194. package/packages/pi-coding-agent/dist/core/extensions/loader.js +31 -4
  195. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  196. package/packages/pi-coding-agent/dist/core/extensions/loader.test.js +28 -1
  197. package/packages/pi-coding-agent/dist/core/extensions/loader.test.js.map +1 -1
  198. package/packages/pi-coding-agent/dist/core/extensions/provider-registration.test.d.ts +2 -0
  199. package/packages/pi-coding-agent/dist/core/extensions/provider-registration.test.d.ts.map +1 -0
  200. package/packages/pi-coding-agent/dist/core/extensions/provider-registration.test.js +46 -0
  201. package/packages/pi-coding-agent/dist/core/extensions/provider-registration.test.js.map +1 -0
  202. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +2 -0
  203. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
  204. package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
  205. package/packages/pi-coding-agent/dist/core/model-registry.d.ts +1 -0
  206. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  207. package/packages/pi-coding-agent/dist/core/model-registry.js +12 -0
  208. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  209. package/packages/pi-coding-agent/dist/core/model-resolver.js +3 -3
  210. package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
  211. package/packages/pi-coding-agent/dist/core/resource-loader.d.ts +23 -1
  212. package/packages/pi-coding-agent/dist/core/resource-loader.d.ts.map +1 -1
  213. package/packages/pi-coding-agent/dist/core/resource-loader.js +80 -56
  214. package/packages/pi-coding-agent/dist/core/resource-loader.js.map +1 -1
  215. package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
  216. package/packages/pi-coding-agent/dist/core/sdk.js +9 -0
  217. package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
  218. package/packages/pi-coding-agent/src/core/auth-storage.test.ts +53 -0
  219. package/packages/pi-coding-agent/src/core/auth-storage.ts +66 -1
  220. package/packages/pi-coding-agent/src/core/extensions/loader.test.ts +39 -1
  221. package/packages/pi-coding-agent/src/core/extensions/loader.ts +34 -4
  222. package/packages/pi-coding-agent/src/core/extensions/provider-registration.test.ts +81 -0
  223. package/packages/pi-coding-agent/src/core/extensions/types.ts +2 -0
  224. package/packages/pi-coding-agent/src/core/model-registry.ts +14 -0
  225. package/packages/pi-coding-agent/src/core/model-resolver.ts +3 -3
  226. package/packages/pi-coding-agent/src/core/resource-loader.ts +89 -56
  227. package/packages/pi-coding-agent/src/core/sdk.ts +10 -0
  228. package/src/resources/extensions/cmux/index.ts +18 -12
  229. package/src/resources/extensions/gsd/auto/detect-stuck.ts +27 -0
  230. package/src/resources/extensions/gsd/auto/finalize-timeout.ts +46 -0
  231. package/src/resources/extensions/gsd/auto/loop.ts +5 -0
  232. package/src/resources/extensions/gsd/auto/phases.ts +194 -33
  233. package/src/resources/extensions/gsd/auto/session.ts +14 -0
  234. package/src/resources/extensions/gsd/auto-dashboard.ts +11 -3
  235. package/src/resources/extensions/gsd/auto-model-selection.ts +36 -0
  236. package/src/resources/extensions/gsd/auto-post-unit.ts +141 -12
  237. package/src/resources/extensions/gsd/auto-prompts.ts +21 -0
  238. package/src/resources/extensions/gsd/auto-recovery.ts +9 -8
  239. package/src/resources/extensions/gsd/auto-start.ts +11 -20
  240. package/src/resources/extensions/gsd/auto-timers.ts +2 -1
  241. package/src/resources/extensions/gsd/auto-tool-tracking.ts +19 -0
  242. package/src/resources/extensions/gsd/auto-worktree.ts +14 -6
  243. package/src/resources/extensions/gsd/auto.ts +22 -1
  244. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +160 -88
  245. package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +15 -0
  246. package/src/resources/extensions/gsd/bootstrap/query-tools.ts +98 -0
  247. package/src/resources/extensions/gsd/bootstrap/register-extension.ts +4 -0
  248. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +36 -1
  249. package/src/resources/extensions/gsd/bootstrap/sanitize-complete-milestone.ts +57 -0
  250. package/src/resources/extensions/gsd/bootstrap/system-context.ts +31 -2
  251. package/src/resources/extensions/gsd/commands-handlers.ts +10 -4
  252. package/src/resources/extensions/gsd/constants.ts +44 -0
  253. package/src/resources/extensions/gsd/db-writer.ts +78 -4
  254. package/src/resources/extensions/gsd/forensics.ts +21 -5
  255. package/src/resources/extensions/gsd/gsd-db.ts +64 -17
  256. package/src/resources/extensions/gsd/guided-flow.ts +22 -0
  257. package/src/resources/extensions/gsd/metrics.ts +28 -1
  258. package/src/resources/extensions/gsd/native-git-bridge.ts +5 -3
  259. package/src/resources/extensions/gsd/preferences-types.ts +16 -0
  260. package/src/resources/extensions/gsd/preferences.ts +9 -2
  261. package/src/resources/extensions/gsd/prompt-loader.ts +8 -0
  262. package/src/resources/extensions/gsd/prompts/complete-milestone.md +2 -0
  263. package/src/resources/extensions/gsd/prompts/complete-slice.md +2 -0
  264. package/src/resources/extensions/gsd/prompts/doctor-heal.md +1 -0
  265. package/src/resources/extensions/gsd/prompts/forensics.md +2 -0
  266. package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +2 -0
  267. package/src/resources/extensions/gsd/prompts/system.md +4 -7
  268. package/src/resources/extensions/gsd/prompts/validate-milestone.md +2 -0
  269. package/src/resources/extensions/gsd/roadmap-mutations.ts +1 -1
  270. package/src/resources/extensions/gsd/roadmap-slices.ts +10 -5
  271. package/src/resources/extensions/gsd/safety/content-validator.ts +98 -0
  272. package/src/resources/extensions/gsd/safety/destructive-guard.ts +49 -0
  273. package/src/resources/extensions/gsd/safety/evidence-collector.ts +151 -0
  274. package/src/resources/extensions/gsd/safety/evidence-cross-ref.ts +120 -0
  275. package/src/resources/extensions/gsd/safety/file-change-validator.ts +108 -0
  276. package/src/resources/extensions/gsd/safety/git-checkpoint.ts +106 -0
  277. package/src/resources/extensions/gsd/safety/safety-harness.ts +105 -0
  278. package/src/resources/extensions/gsd/slice-parallel-conflict.ts +86 -0
  279. package/src/resources/extensions/gsd/slice-parallel-eligibility.ts +73 -0
  280. package/src/resources/extensions/gsd/slice-parallel-orchestrator.ts +477 -0
  281. package/src/resources/extensions/gsd/state.ts +67 -12
  282. package/src/resources/extensions/gsd/status-guards.ts +13 -0
  283. package/src/resources/extensions/gsd/tests/artifact-corruption-2630.test.ts +288 -0
  284. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +34 -13
  285. package/src/resources/extensions/gsd/tests/cmux.test.ts +58 -0
  286. package/src/resources/extensions/gsd/tests/cold-resume-db-reopen.test.ts +51 -0
  287. package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +140 -0
  288. package/src/resources/extensions/gsd/tests/complete-slice-string-coercion.test.ts +211 -0
  289. package/src/resources/extensions/gsd/tests/complete-task.test.ts +39 -0
  290. package/src/resources/extensions/gsd/tests/dashboard-model-label-ordering.test.ts +107 -0
  291. package/src/resources/extensions/gsd/tests/db-access-guardrails.test.ts +109 -0
  292. package/src/resources/extensions/gsd/tests/db-path-worktree-symlink.test.ts +13 -9
  293. package/src/resources/extensions/gsd/tests/db-writer.test.ts +134 -0
  294. package/src/resources/extensions/gsd/tests/deferred-slice-dispatch.test.ts +203 -0
  295. package/src/resources/extensions/gsd/tests/discuss-tool-scoping.test.ts +130 -0
  296. package/src/resources/extensions/gsd/tests/doctor-fix-flag.test.ts +92 -0
  297. package/src/resources/extensions/gsd/tests/finalize-timeout-guard.test.ts +116 -0
  298. package/src/resources/extensions/gsd/tests/flat-rate-routing-guard.test.ts +50 -0
  299. package/src/resources/extensions/gsd/tests/forensics-stuck-loops.test.ts +103 -0
  300. package/src/resources/extensions/gsd/tests/git-checkpoint.test.ts +94 -0
  301. package/src/resources/extensions/gsd/tests/insert-slice-no-wipe.test.ts +88 -0
  302. package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +27 -7
  303. package/src/resources/extensions/gsd/tests/integration/idle-recovery.test.ts +34 -0
  304. package/src/resources/extensions/gsd/tests/metrics.test.ts +116 -1
  305. package/src/resources/extensions/gsd/tests/milestone-status-tool.test.ts +201 -0
  306. package/src/resources/extensions/gsd/tests/plan-milestone-title.test.ts +2 -1
  307. package/src/resources/extensions/gsd/tests/plan-milestone.test.ts +82 -18
  308. package/src/resources/extensions/gsd/tests/preferences.test.ts +10 -0
  309. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +25 -0
  310. package/src/resources/extensions/gsd/tests/roadmap-slices.test.ts +69 -0
  311. package/src/resources/extensions/gsd/tests/shared-wal.test.ts +30 -0
  312. package/src/resources/extensions/gsd/tests/slice-context-injection.test.ts +50 -0
  313. package/src/resources/extensions/gsd/tests/slice-parallel-conflict.test.ts +92 -0
  314. package/src/resources/extensions/gsd/tests/slice-parallel-eligibility.test.ts +95 -0
  315. package/src/resources/extensions/gsd/tests/slice-parallel-orchestrator.test.ts +83 -0
  316. package/src/resources/extensions/gsd/tests/stuck-detection-coverage.test.ts +42 -0
  317. package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +103 -0
  318. package/src/resources/extensions/gsd/tests/tool-param-optionality.test.ts +349 -0
  319. package/src/resources/extensions/gsd/tests/worktree-health-dispatch.test.ts +35 -2
  320. package/src/resources/extensions/gsd/tests/worktree-health-monorepo.test.ts +73 -0
  321. package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +34 -0
  322. package/src/resources/extensions/gsd/tests/worktree-submodule-safety.test.ts +1 -1
  323. package/src/resources/extensions/gsd/tests/worktree-teardown-safety.test.ts +148 -0
  324. package/src/resources/extensions/gsd/tools/complete-milestone.ts +34 -20
  325. package/src/resources/extensions/gsd/tools/complete-slice.ts +41 -26
  326. package/src/resources/extensions/gsd/tools/complete-task.ts +12 -12
  327. package/src/resources/extensions/gsd/tools/plan-milestone.ts +55 -30
  328. package/src/resources/extensions/gsd/tools/plan-slice.ts +13 -8
  329. package/src/resources/extensions/gsd/types.ts +44 -22
  330. package/src/resources/extensions/gsd/workflow-logger.ts +2 -1
  331. package/src/resources/extensions/gsd/workflow-projections.ts +23 -5
  332. package/src/resources/extensions/gsd/worktree-manager.ts +76 -28
  333. package/src/resources/extensions/gsd/worktree-resolver.ts +4 -3
  334. package/src/resources/extensions/mcp-client/auth.ts +149 -0
  335. package/src/resources/extensions/mcp-client/index.ts +16 -1
  336. package/src/resources/extensions/ollama/index.ts +26 -25
  337. package/src/resources/extensions/ollama/model-capabilities.ts +41 -34
  338. package/src/resources/extensions/ollama/ndjson-stream.ts +63 -0
  339. package/src/resources/extensions/ollama/ollama-auth-mode.test.ts +20 -0
  340. package/src/resources/extensions/ollama/ollama-chat-provider.ts +459 -0
  341. package/src/resources/extensions/ollama/ollama-client.ts +30 -30
  342. package/src/resources/extensions/ollama/ollama-discovery.ts +5 -8
  343. package/src/resources/extensions/ollama/ollama-tool.ts +69 -0
  344. package/src/resources/extensions/ollama/tests/ollama-chat-provider-stream.test.ts +82 -0
  345. package/src/resources/extensions/ollama/tests/ollama-discovery.test.ts +0 -27
  346. package/src/resources/extensions/ollama/thinking-parser.ts +116 -0
  347. package/src/resources/extensions/ollama/types.ts +23 -0
  348. package/dist/web/standalone/.next/server/chunks/2229.js +0 -12
  349. /package/dist/web/standalone/.next/static/{TTlAguZQ5vR9EOv6G8cel → SDB1T-4NqkMjYirjjqQhr}/_buildManifest.js +0 -0
  350. /package/dist/web/standalone/.next/static/{TTlAguZQ5vR9EOv6G8cel → SDB1T-4NqkMjYirjjqQhr}/_ssgManifest.js +0 -0
@@ -0,0 +1,349 @@
1
+ /**
2
+ * tool-param-optionality — Verifies that enrichment/metadata parameters on
3
+ * planning and completion tools are optional, not required.
4
+ *
5
+ * Models with limited tool-calling capability (e.g. kimi-k2.5, glm-5-turbo)
6
+ * cannot reliably populate 20+ top-level parameters in a single tool call.
7
+ * This test ensures that only the core identification and content parameters
8
+ * are required, while enrichment arrays (patterns, requirements, files, etc.)
9
+ * are optional — so any model can call the tool successfully.
10
+ *
11
+ * See: https://github.com/gsd-build/gsd-2/issues/2771
12
+ */
13
+
14
+ import { test } from "node:test";
15
+ import assert from "node:assert/strict";
16
+ import { registerDbTools } from "../bootstrap/db-tools.ts";
17
+ import { Value } from "@sinclair/typebox/value";
18
+
19
+ // ─── Mock PI ──────────────────────────────────────────────────────────────────
20
+
21
+ function makeMockPi() {
22
+ const tools: any[] = [];
23
+ return {
24
+ registerTool: (tool: any) => tools.push(tool),
25
+ tools,
26
+ } as any;
27
+ }
28
+
29
+ const pi = makeMockPi();
30
+ registerDbTools(pi);
31
+
32
+ function getTool(name: string) {
33
+ return pi.tools.find((t: any) => t.name === name);
34
+ }
35
+
36
+ // ─── Helper: count required top-level properties ─────────────────────────────
37
+
38
+ function getRequiredProps(tool: any): string[] {
39
+ const schema = tool.parameters;
40
+ return schema.required ?? [];
41
+ }
42
+
43
+ function getOptionalProps(tool: any): string[] {
44
+ const schema = tool.parameters;
45
+ const allProps = Object.keys(schema.properties ?? {});
46
+ const required = new Set(schema.required ?? []);
47
+ return allProps.filter((p: string) => !required.has(p));
48
+ }
49
+
50
+ // ─── gsd_slice_complete: enrichment arrays must be optional ──────────────────
51
+
52
+ test("gsd_slice_complete — enrichment arrays are optional", () => {
53
+ const tool = getTool("gsd_slice_complete");
54
+ assert.ok(tool, "gsd_slice_complete must be registered");
55
+
56
+ const required = new Set(getRequiredProps(tool));
57
+
58
+ // Core identification and content fields MUST be required
59
+ const coreRequired = [
60
+ "sliceId",
61
+ "milestoneId",
62
+ "sliceTitle",
63
+ "oneLiner",
64
+ "narrative",
65
+ "verification",
66
+ "uatContent",
67
+ ];
68
+ for (const field of coreRequired) {
69
+ assert.ok(required.has(field), `core field "${field}" must be required`);
70
+ }
71
+
72
+ // Enrichment/metadata arrays MUST be optional
73
+ const enrichmentFields = [
74
+ "keyFiles",
75
+ "keyDecisions",
76
+ "patternsEstablished",
77
+ "observabilitySurfaces",
78
+ "provides",
79
+ "requirementsSurfaced",
80
+ "drillDownPaths",
81
+ "affects",
82
+ "requirementsAdvanced",
83
+ "requirementsValidated",
84
+ "requirementsInvalidated",
85
+ "filesModified",
86
+ "requires",
87
+ "deviations",
88
+ "knownLimitations",
89
+ "followUps",
90
+ ];
91
+ for (const field of enrichmentFields) {
92
+ assert.ok(!required.has(field), `enrichment field "${field}" must be optional, not required`);
93
+ }
94
+ });
95
+
96
+ test("gsd_slice_complete — validates with only core params", () => {
97
+ const tool = getTool("gsd_slice_complete");
98
+ assert.ok(tool, "gsd_slice_complete must be registered");
99
+
100
+ const minimalParams = {
101
+ sliceId: "S01",
102
+ milestoneId: "M001",
103
+ sliceTitle: "Test slice",
104
+ oneLiner: "Did the thing",
105
+ narrative: "We did it step by step.",
106
+ verification: "Tests pass.",
107
+ uatContent: "## UAT\n- [x] Works",
108
+ };
109
+
110
+ // Should pass schema validation with only core params
111
+ const errors = [...Value.Errors(tool.parameters, minimalParams)];
112
+ assert.strictEqual(errors.length, 0, `Minimal params should validate but got errors: ${errors.map(e => `${e.path}: ${e.message}`).join(", ")}`);
113
+ });
114
+
115
+ // ─── gsd_plan_milestone: enrichment arrays must be optional ──────────────────
116
+
117
+ test("gsd_plan_milestone — enrichment arrays are optional", () => {
118
+ const tool = getTool("gsd_plan_milestone");
119
+ assert.ok(tool, "gsd_plan_milestone must be registered");
120
+
121
+ const required = new Set(getRequiredProps(tool));
122
+
123
+ // Core fields
124
+ const coreRequired = ["milestoneId", "title", "vision", "slices"];
125
+ for (const field of coreRequired) {
126
+ assert.ok(required.has(field), `core field "${field}" must be required`);
127
+ }
128
+
129
+ // Enrichment fields must be optional
130
+ const enrichmentFields = [
131
+ "successCriteria",
132
+ "keyRisks",
133
+ "proofStrategy",
134
+ "verificationContract",
135
+ "verificationIntegration",
136
+ "verificationOperational",
137
+ "verificationUat",
138
+ "definitionOfDone",
139
+ "requirementCoverage",
140
+ "boundaryMapMarkdown",
141
+ ];
142
+ for (const field of enrichmentFields) {
143
+ assert.ok(!required.has(field), `enrichment field "${field}" must be optional, not required`);
144
+ }
145
+ });
146
+
147
+ test("gsd_plan_milestone — validates with only core params", () => {
148
+ const tool = getTool("gsd_plan_milestone");
149
+ assert.ok(tool, "gsd_plan_milestone must be registered");
150
+
151
+ const minimalParams = {
152
+ milestoneId: "M001",
153
+ title: "Test milestone",
154
+ vision: "Build the thing.",
155
+ slices: [
156
+ {
157
+ sliceId: "S01",
158
+ title: "First slice",
159
+ risk: "Low",
160
+ depends: [],
161
+ demo: "After this, X works",
162
+ goal: "Set up X",
163
+ successCriteria: "X is set up",
164
+ proofLevel: "unit-tests",
165
+ integrationClosure: "N/A",
166
+ observabilityImpact: "None",
167
+ },
168
+ ],
169
+ };
170
+
171
+ const errors = [...Value.Errors(tool.parameters, minimalParams)];
172
+ assert.strictEqual(errors.length, 0, `Minimal params should validate but got errors: ${errors.map(e => `${e.path}: ${e.message}`).join(", ")}`);
173
+ });
174
+
175
+ // ─── gsd_task_complete: enrichment arrays must be optional ───────────────────
176
+
177
+ test("gsd_task_complete — enrichment arrays are optional", () => {
178
+ const tool = getTool("gsd_task_complete");
179
+ assert.ok(tool, "gsd_task_complete must be registered");
180
+
181
+ const required = new Set(getRequiredProps(tool));
182
+
183
+ // Core fields
184
+ const coreRequired = [
185
+ "taskId",
186
+ "sliceId",
187
+ "milestoneId",
188
+ "oneLiner",
189
+ "narrative",
190
+ "verification",
191
+ ];
192
+ for (const field of coreRequired) {
193
+ assert.ok(required.has(field), `core field "${field}" must be required`);
194
+ }
195
+
196
+ // Enrichment fields must be optional
197
+ const enrichmentFields = [
198
+ "keyFiles",
199
+ "keyDecisions",
200
+ "deviations",
201
+ "knownIssues",
202
+ "blockerDiscovered",
203
+ "verificationEvidence",
204
+ ];
205
+ for (const field of enrichmentFields) {
206
+ assert.ok(!required.has(field), `enrichment field "${field}" must be optional, not required`);
207
+ }
208
+ });
209
+
210
+ test("gsd_task_complete — validates with only core params", () => {
211
+ const tool = getTool("gsd_task_complete");
212
+ assert.ok(tool, "gsd_task_complete must be registered");
213
+
214
+ const minimalParams = {
215
+ taskId: "T01",
216
+ sliceId: "S01",
217
+ milestoneId: "M001",
218
+ oneLiner: "Implemented the feature",
219
+ narrative: "Created the module and wired it up.",
220
+ verification: "npm test passes.",
221
+ };
222
+
223
+ const errors = [...Value.Errors(tool.parameters, minimalParams)];
224
+ assert.strictEqual(errors.length, 0, `Minimal params should validate but got errors: ${errors.map(e => `${e.path}: ${e.message}`).join(", ")}`);
225
+ });
226
+
227
+ // ─── gsd_complete_milestone: enrichment arrays must be optional ──────────────
228
+
229
+ test("gsd_complete_milestone — enrichment arrays are optional", () => {
230
+ const tool = getTool("gsd_complete_milestone");
231
+ assert.ok(tool, "gsd_complete_milestone must be registered");
232
+
233
+ const required = new Set(getRequiredProps(tool));
234
+
235
+ // Core fields
236
+ const coreRequired = [
237
+ "milestoneId",
238
+ "title",
239
+ "oneLiner",
240
+ "narrative",
241
+ "verificationPassed",
242
+ ];
243
+ for (const field of coreRequired) {
244
+ assert.ok(required.has(field), `core field "${field}" must be required`);
245
+ }
246
+
247
+ // Enrichment fields must be optional
248
+ const enrichmentFields = [
249
+ "successCriteriaResults",
250
+ "definitionOfDoneResults",
251
+ "requirementOutcomes",
252
+ "keyDecisions",
253
+ "keyFiles",
254
+ "lessonsLearned",
255
+ ];
256
+ for (const field of enrichmentFields) {
257
+ assert.ok(!required.has(field), `enrichment field "${field}" must be optional, not required`);
258
+ }
259
+ });
260
+
261
+ test("gsd_complete_milestone — validates with only core params", () => {
262
+ const tool = getTool("gsd_complete_milestone");
263
+ assert.ok(tool, "gsd_complete_milestone must be registered");
264
+
265
+ const minimalParams = {
266
+ milestoneId: "M001",
267
+ title: "Test milestone",
268
+ oneLiner: "Finished it.",
269
+ narrative: "All work completed.",
270
+ verificationPassed: true,
271
+ };
272
+
273
+ const errors = [...Value.Errors(tool.parameters, minimalParams)];
274
+ assert.strictEqual(errors.length, 0, `Minimal params should validate but got errors: ${errors.map(e => `${e.path}: ${e.message}`).join(", ")}`);
275
+ });
276
+
277
+ // ─── gsd_plan_slice: enrichment fields must be optional ──────────────────────
278
+
279
+ test("gsd_plan_slice — enrichment fields are optional", () => {
280
+ const tool = getTool("gsd_plan_slice");
281
+ assert.ok(tool, "gsd_plan_slice must be registered");
282
+
283
+ const required = new Set(getRequiredProps(tool));
284
+
285
+ // Core fields
286
+ const coreRequired = ["milestoneId", "sliceId", "goal", "tasks"];
287
+ for (const field of coreRequired) {
288
+ assert.ok(required.has(field), `core field "${field}" must be required`);
289
+ }
290
+
291
+ // Enrichment fields
292
+ const enrichmentFields = [
293
+ "successCriteria",
294
+ "proofLevel",
295
+ "integrationClosure",
296
+ "observabilityImpact",
297
+ ];
298
+ for (const field of enrichmentFields) {
299
+ assert.ok(!required.has(field), `enrichment field "${field}" must be optional, not required`);
300
+ }
301
+ });
302
+
303
+ test("gsd_plan_slice — validates with only core params", () => {
304
+ const tool = getTool("gsd_plan_slice");
305
+ assert.ok(tool, "gsd_plan_slice must be registered");
306
+
307
+ const minimalParams = {
308
+ milestoneId: "M001",
309
+ sliceId: "S01",
310
+ goal: "Implement feature X",
311
+ tasks: [
312
+ {
313
+ taskId: "T01",
314
+ title: "Build X",
315
+ description: "Build the thing",
316
+ estimate: "2h",
317
+ files: ["src/x.ts"],
318
+ verify: "npm test",
319
+ inputs: [],
320
+ expectedOutput: ["src/x.ts"],
321
+ },
322
+ ],
323
+ };
324
+
325
+ const errors = [...Value.Errors(tool.parameters, minimalParams)];
326
+ assert.strictEqual(errors.length, 0, `Minimal params should validate but got errors: ${errors.map(e => `${e.path}: ${e.message}`).join(", ")}`);
327
+ });
328
+
329
+ // ─── Required param count ceiling ────────────────────────────────────────────
330
+
331
+ test("no planning/completion tool requires more than 10 top-level params", () => {
332
+ const heavyTools = [
333
+ "gsd_slice_complete",
334
+ "gsd_plan_milestone",
335
+ "gsd_task_complete",
336
+ "gsd_complete_milestone",
337
+ "gsd_plan_slice",
338
+ ];
339
+
340
+ for (const name of heavyTools) {
341
+ const tool = getTool(name);
342
+ assert.ok(tool, `${name} must be registered`);
343
+ const required = getRequiredProps(tool);
344
+ assert.ok(
345
+ required.length <= 10,
346
+ `${name} has ${required.length} required params (max 10) — required: ${required.join(", ")}`,
347
+ );
348
+ }
349
+ });
@@ -9,7 +9,7 @@
9
9
 
10
10
  import { describe, test, beforeEach, afterEach } from "node:test";
11
11
  import assert from "node:assert/strict";
12
- import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from "node:fs";
12
+ import { mkdtempSync, mkdirSync, writeFileSync, rmSync, readdirSync } from "node:fs";
13
13
  import { join } from "node:path";
14
14
  import { tmpdir } from "node:os";
15
15
  import { execSync } from "node:child_process";
@@ -57,13 +57,20 @@ function hasRecognizedProjectFiles(basePath: string, existsSyncFn: (p: string) =
57
57
  return false;
58
58
  }
59
59
 
60
+ /** Simulate the phases.ts Xcode-bundle detection (readdirSync suffix scan). */
61
+ function hasXcodeBundle(basePath: string): boolean {
62
+ try {
63
+ return readdirSync(basePath).some((e) => e.endsWith(".xcodeproj") || e.endsWith(".xcworkspace"));
64
+ } catch { return false; }
65
+ }
66
+
60
67
  import { existsSync } from "node:fs";
61
68
 
62
69
  // ─── Tests ───────────────────────────────────────────────────────────────────
63
70
 
64
71
  test("PROJECT_FILES is exported and contains expected multi-ecosystem entries", () => {
65
72
  assert.ok(Array.isArray(PROJECT_FILES), "PROJECT_FILES is an array");
66
- assert.ok(PROJECT_FILES.length >= 17, `expected >= 17 entries, got ${PROJECT_FILES.length}`);
73
+ assert.ok(PROJECT_FILES.length >= 18, `expected >= 18 entries, got ${PROJECT_FILES.length}`);
67
74
  // Spot-check key ecosystems
68
75
  assert.ok(PROJECT_FILES.includes("Cargo.toml"), "includes Rust marker");
69
76
  assert.ok(PROJECT_FILES.includes("go.mod"), "includes Go marker");
@@ -140,3 +147,29 @@ describe("health check without git repo", () => {
140
147
  assert.ok(!wouldPassHealthCheck(dir, existsSync), "no-git directory should fail health check");
141
148
  });
142
149
  });
150
+
151
+ describe("health check with xcodegen and Xcode bundles", () => {
152
+ let dir: string;
153
+ beforeEach(() => { dir = createGitRepo(); });
154
+ afterEach(() => { rmSync(dir, { recursive: true, force: true }); });
155
+
156
+ test("health check passes for xcodegen project (project.yml, no Package.swift)", () => {
157
+ writeFileSync(join(dir, "project.yml"), "name: MyApp\ntargets:\n MyApp:\n type: application\n");
158
+ assert.ok(wouldPassHealthCheck(dir, existsSync), "xcodegen project should pass health check");
159
+ });
160
+
161
+ // Regression for the real-world failure in #1882: an iOS project with a
162
+ // project-specific Xcode bundle (Sudokuxyz.xcodeproj/) was blocked because
163
+ // PROJECT_FILES only probes exact filenames, not suffix-based directory names.
164
+ test("Xcode bundle (*.xcodeproj) is not in PROJECT_FILES but detected by suffix scan", () => {
165
+ mkdirSync(join(dir, "Sudokuxyz.xcodeproj"), { recursive: true });
166
+ mkdirSync(join(dir, "Sources", "Sudokuxyz"), { recursive: true });
167
+ writeFileSync(join(dir, "Sources", "Sudokuxyz", "ContentView.swift"), "import SwiftUI\n");
168
+ // PROJECT_FILES uses exact names — cannot match project-specific bundle names
169
+ assert.ok(!hasRecognizedProjectFiles(dir, existsSync), "xcodeproj bundle must NOT be in PROJECT_FILES");
170
+ // The readdirSync suffix scan used in phases.ts detects it
171
+ assert.ok(hasXcodeBundle(dir), "readdirSync suffix scan detects .xcodeproj bundle");
172
+ // Health check passes regardless (only requires .git)
173
+ assert.ok(wouldPassHealthCheck(dir, existsSync), "Xcode bundle project should pass health check");
174
+ });
175
+ });
@@ -0,0 +1,73 @@
1
+ /**
2
+ * worktree-health-monorepo.test.ts — #2347
3
+ *
4
+ * The worktree health check in auto/phases.ts falsely rejects monorepos
5
+ * where package.json (or other project markers) is in a parent directory.
6
+ * This test verifies that the health check walks parent directories.
7
+ */
8
+
9
+ import { readFileSync } from "node:fs";
10
+ import { join } from "node:path";
11
+ import { createTestContext } from "./test-helpers.ts";
12
+
13
+ const { assertTrue, report } = createTestContext();
14
+
15
+ const srcPath = join(import.meta.dirname, "..", "auto", "phases.ts");
16
+ const src = readFileSync(srcPath, "utf-8");
17
+
18
+ console.log("\n=== #2347: Worktree health check supports monorepos ===");
19
+
20
+ // ── Test 1: The health check region exists ──────────────────────────────
21
+
22
+ const healthCheckIdx = src.indexOf("Worktree health check");
23
+ assertTrue(healthCheckIdx > 0, "auto/phases.ts has worktree health check section");
24
+
25
+ const healthCheckRegion = src.slice(healthCheckIdx, healthCheckIdx + 2000);
26
+
27
+ // ── Test 2: The check walks parent directories for project markers ──────
28
+
29
+ // The fix should check parent directories for project files, not just s.basePath.
30
+ // Look for patterns like: walking up directories, dirname, parent, or a helper
31
+ // function that checks ancestors.
32
+ const checksParentDirs =
33
+ healthCheckRegion.includes("dirname") ||
34
+ healthCheckRegion.includes("parent") ||
35
+ healthCheckRegion.includes("ancestor") ||
36
+ healthCheckRegion.includes("walk") ||
37
+ // Or a helper function that's called with the base path
38
+ /hasProjectFileInAncestor|findProjectRoot|checkParent/i.test(healthCheckRegion);
39
+
40
+ assertTrue(
41
+ checksParentDirs,
42
+ "Health check should walk parent directories for project markers (monorepo support) (#2347)",
43
+ );
44
+
45
+ // ── Test 3: The parent walk stops at a .git boundary ──────────────────
46
+
47
+ // The parent directory walk must not escape the git repository root.
48
+ // Without this guard, ancestor directories like ~ or /usr/local that
49
+ // happen to contain package.json would cause false positive health checks.
50
+ const hasGitBoundary = healthCheckRegion.includes('.git') &&
51
+ (healthCheckRegion.includes('break') || healthCheckRegion.includes('stop'));
52
+
53
+ assertTrue(
54
+ hasGitBoundary,
55
+ "Parent directory walk must stop at .git repository boundary to prevent false positives",
56
+ );
57
+
58
+ // ── Test 4: The greenfield warning should only trigger when no parent has markers ─
59
+
60
+ // The original code was:
61
+ // const hasProjectFile = PROJECT_FILES.some((f) => deps.existsSync(join(s.basePath, f)));
62
+ // The fix should check parents too, so the greenfield warning only fires
63
+ // when NO ancestor directory has project markers either.
64
+ const hasParentCheck = healthCheckRegion.includes("parent") ||
65
+ healthCheckRegion.includes("dirname") ||
66
+ /ancestor|walk.*up/i.test(healthCheckRegion);
67
+
68
+ assertTrue(
69
+ hasParentCheck,
70
+ "Greenfield check should consider parent directories before warning (#2347)",
71
+ );
72
+
73
+ report();
@@ -550,6 +550,40 @@ test("mergeAndExit failure message tells user worktree and branch are preserved
550
550
  );
551
551
  });
552
552
 
553
+ test("mergeAndExit failure message references /gsd dispatch complete-milestone, not /complete-milestone (#1891)", () => {
554
+ // Regression test: the failure notification previously told users to
555
+ // "retry /complete-milestone" — a command that does not exist. The correct
556
+ // recovery command is "/gsd dispatch complete-milestone".
557
+ const s = makeSession({
558
+ basePath: "/project/.gsd/worktrees/M001",
559
+ originalBasePath: "/project",
560
+ });
561
+ const deps = makeDeps({
562
+ isInAutoWorktree: () => true,
563
+ getIsolationMode: () => "worktree",
564
+ mergeMilestoneToMain: () => {
565
+ throw new Error("dirty working tree");
566
+ },
567
+ });
568
+ const ctx = makeNotifyCtx();
569
+ const resolver = new WorktreeResolver(s, deps);
570
+
571
+ resolver.mergeAndExit("M001", ctx);
572
+
573
+ const warning = ctx.messages.find((m) => m.level === "warning");
574
+ assert.ok(warning, "a warning message is emitted");
575
+ // Must reference the correct dispatch command
576
+ assert.ok(
577
+ warning!.msg.includes("/gsd dispatch complete-milestone"),
578
+ "warning references /gsd dispatch complete-milestone, not bare /complete-milestone",
579
+ );
580
+ // Must NOT contain the bare (incorrect) command without the dispatch prefix
581
+ assert.ok(
582
+ !warning!.msg.match(/retry\s+\/complete-milestone(?!\S)/),
583
+ "warning must not reference the non-existent /complete-milestone command",
584
+ );
585
+ });
586
+
553
587
  // ─── mergeAndExit Tests (branch mode) ────────────────────────────────────────
554
588
 
555
589
  test("mergeAndExit in branch mode merges when on milestone branch", () => {
@@ -22,7 +22,7 @@ console.log("\n=== #2337: Worktree teardown preserves submodule state ===");
22
22
  const removeWorktreeIdx = src.indexOf("export function removeWorktree");
23
23
  assertTrue(removeWorktreeIdx > 0, "worktree-manager.ts exports removeWorktree");
24
24
 
25
- const fnBody = src.slice(removeWorktreeIdx, removeWorktreeIdx + 3000);
25
+ const fnBody = src.slice(removeWorktreeIdx, removeWorktreeIdx + 6000);
26
26
 
27
27
  // ── Test 2: The function checks for submodules before force removal ─────
28
28
 
@@ -0,0 +1,148 @@
1
+ /**
2
+ * worktree-teardown-safety.test.ts — Regression test for #2365.
3
+ *
4
+ * Ensures that removeWorktree() and teardownAutoWorktree() never delete
5
+ * directories outside .gsd/worktrees/. The bug: removeWorktree overrides
6
+ * the computed worktree path with whatever `git worktree list` reports.
7
+ * When .gsd/ was (or is) a symlink, git resolves the symlink at creation
8
+ * time, so its registered path can point to an external directory. If that
9
+ * external path happens to be a project data directory, teardown destroys it.
10
+ *
11
+ * The fix adds path validation so rmSync / nativeWorktreeRemove only operate
12
+ * on paths that are actually under .gsd/worktrees/.
13
+ */
14
+
15
+ import {
16
+ mkdtempSync,
17
+ mkdirSync,
18
+ writeFileSync,
19
+ rmSync,
20
+ existsSync,
21
+ realpathSync,
22
+ readFileSync,
23
+ } from "node:fs";
24
+ import { join } from "node:path";
25
+ import { tmpdir } from "node:os";
26
+ import { execSync } from "node:child_process";
27
+ import { describe, it, after } from "node:test";
28
+
29
+ import { createWorktree, removeWorktree, worktreePath, isInsideWorktreesDir } from "../worktree-manager.ts";
30
+ import { createTestContext } from "./test-helpers.ts";
31
+
32
+ const { assertEq, assertTrue, report } = createTestContext();
33
+
34
+ // ─── Helpers ──────────────────────────────────────────────────────────────
35
+
36
+ function run(command: string, cwd: string): string {
37
+ return execSync(command, { cwd, stdio: ["ignore", "pipe", "pipe"], encoding: "utf-8" }).trim();
38
+ }
39
+
40
+ function createTempRepo(): string {
41
+ const dir = realpathSync(mkdtempSync(join(tmpdir(), "wt-safety-test-")));
42
+ run("git init", dir);
43
+ run("git config user.email test@test.com", dir);
44
+ run("git config user.name Test", dir);
45
+ writeFileSync(join(dir, "README.md"), "# test\n");
46
+ run("git add .", dir);
47
+ run("git commit -m init", dir);
48
+ run("git branch -M main", dir);
49
+ return dir;
50
+ }
51
+
52
+ // ─── Tests ────────────────────────────────────────────────────────────────
53
+
54
+ describe("worktree-teardown-safety", () => {
55
+ const dirs: string[] = [];
56
+
57
+ after(() => {
58
+ for (const d of dirs) rmSync(d, { recursive: true, force: true });
59
+ report();
60
+ });
61
+
62
+ it("removeWorktree does not delete sibling data directories", () => {
63
+ const tempDir = createTempRepo();
64
+ dirs.push(tempDir);
65
+
66
+ // Create a project data directory that lives alongside .gsd/
67
+ const dataDir = join(tempDir, "project-data");
68
+ mkdirSync(dataDir, { recursive: true });
69
+ writeFileSync(join(dataDir, "important.db"), "precious data");
70
+
71
+ // Create a worktree normally
72
+ const wt = createWorktree(tempDir, "test-wt");
73
+ assertTrue(existsSync(wt.path), "worktree created successfully");
74
+
75
+ // Remove the worktree
76
+ removeWorktree(tempDir, "test-wt");
77
+
78
+ // The worktree directory should be gone
79
+ assertTrue(!existsSync(wt.path), "worktree directory removed");
80
+
81
+ // The project data directory MUST still exist
82
+ assertTrue(existsSync(dataDir), "project data directory survives teardown");
83
+ assertTrue(
84
+ existsSync(join(dataDir, "important.db")),
85
+ "project data files survive teardown",
86
+ );
87
+ });
88
+
89
+ it("path validation rejects paths outside .gsd/worktrees/", () => {
90
+ const tempDir = createTempRepo();
91
+ dirs.push(tempDir);
92
+
93
+ const externalDir = join(tempDir, "external-state");
94
+ mkdirSync(externalDir, { recursive: true });
95
+ writeFileSync(join(externalDir, "state.json"), '{"critical": true}');
96
+
97
+ // Create and then remove a worktree that has a legitimate path
98
+ const wt2 = createWorktree(tempDir, "safe-wt");
99
+ assertTrue(existsSync(wt2.path), "second worktree created");
100
+
101
+ removeWorktree(tempDir, "safe-wt");
102
+ assertTrue(!existsSync(wt2.path), "second worktree removed cleanly");
103
+
104
+ // External directory must be untouched
105
+ assertTrue(existsSync(externalDir), "external directory survives second teardown");
106
+ assertEq(
107
+ readFileSync(join(externalDir, "state.json"), "utf-8"),
108
+ '{"critical": true}',
109
+ "external directory contents intact after teardown",
110
+ );
111
+ });
112
+
113
+ it("worktreePath always returns paths under .gsd/worktrees/", () => {
114
+ const tempDir = createTempRepo();
115
+ dirs.push(tempDir);
116
+
117
+ const wtPathResult = worktreePath(tempDir, "anything");
118
+ assertTrue(
119
+ wtPathResult.startsWith(join(tempDir, ".gsd", "worktrees")),
120
+ "worktreePath returns path under .gsd/worktrees/",
121
+ );
122
+ });
123
+
124
+ it("isInsideWorktreesDir rejects path traversal attempts", () => {
125
+ const tempDir = createTempRepo();
126
+ dirs.push(tempDir);
127
+
128
+ assertTrue(
129
+ isInsideWorktreesDir(tempDir, join(tempDir, ".gsd", "worktrees", "my-wt")),
130
+ "path inside .gsd/worktrees/ is accepted",
131
+ );
132
+
133
+ assertTrue(
134
+ !isInsideWorktreesDir(tempDir, join(tempDir, "project-data")),
135
+ "path outside .gsd/worktrees/ is rejected",
136
+ );
137
+
138
+ assertTrue(
139
+ !isInsideWorktreesDir(tempDir, join(tempDir, ".gsd", "worktrees", "..", "..", "project-data")),
140
+ "path traversal via .. is rejected",
141
+ );
142
+
143
+ assertTrue(
144
+ !isInsideWorktreesDir(tempDir, "/tmp/some-other-dir"),
145
+ "completely external path is rejected",
146
+ );
147
+ });
148
+ });