@opengsd/gsd-pi 1.4.0 → 1.5.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 (329) hide show
  1. package/README.md +11 -11
  2. package/dist/onboarding.js +11 -0
  3. package/dist/resources/.managed-resources-content-hash +1 -1
  4. package/dist/resources/GSD-WORKFLOW.md +20 -25
  5. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +118 -53
  6. package/dist/resources/extensions/cursor-cli/index.js +16 -0
  7. package/dist/resources/extensions/cursor-cli/models.js +63 -0
  8. package/dist/resources/extensions/cursor-cli/package.json +11 -0
  9. package/dist/resources/extensions/cursor-cli/readiness.js +128 -0
  10. package/dist/resources/extensions/cursor-cli/stream-adapter.js +353 -0
  11. package/dist/resources/extensions/gsd/activity-log.js +4 -1
  12. package/dist/resources/extensions/gsd/artifact-verification.js +25 -9
  13. package/dist/resources/extensions/gsd/auto/closeout.js +45 -42
  14. package/dist/resources/extensions/gsd/auto/finalize.js +9 -0
  15. package/dist/resources/extensions/gsd/auto/orchestrator.js +1 -1
  16. package/dist/resources/extensions/gsd/auto/run-unit.js +5 -2
  17. package/dist/resources/extensions/gsd/auto/schedule-wakeup.js +3 -0
  18. package/dist/resources/extensions/gsd/auto-dispatch.js +15 -5
  19. package/dist/resources/extensions/gsd/auto-model-selection.js +36 -18
  20. package/dist/resources/extensions/gsd/auto-prompts.js +76 -2
  21. package/dist/resources/extensions/gsd/auto-start.js +4 -3
  22. package/dist/resources/extensions/gsd/auto-timeout-recovery.js +4 -0
  23. package/dist/resources/extensions/gsd/auto-worktree.js +38 -11
  24. package/dist/resources/extensions/gsd/auto.js +23 -3
  25. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +3 -0
  26. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +114 -5
  27. package/dist/resources/extensions/gsd/bootstrap/schedule-wakeup-tool.js +58 -9
  28. package/dist/resources/extensions/gsd/bootstrap/tool-call-loop-guard.js +10 -2
  29. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +14 -1
  30. package/dist/resources/extensions/gsd/codebase-generator.js +157 -19
  31. package/dist/resources/extensions/gsd/commands/handlers/core.js +40 -4
  32. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +153 -1
  33. package/dist/resources/extensions/gsd/commands-worktree.js +11 -2
  34. package/dist/resources/extensions/gsd/db/queries.js +39 -0
  35. package/dist/resources/extensions/gsd/discussion-handoff.js +8 -3
  36. package/dist/resources/extensions/gsd/docs/preferences-reference.md +8 -2
  37. package/dist/resources/extensions/gsd/doctor-engine-checks.js +206 -14
  38. package/dist/resources/extensions/gsd/doctor-providers.js +38 -10
  39. package/dist/resources/extensions/gsd/doctor.js +93 -3
  40. package/dist/resources/extensions/gsd/exec-sandbox.js +6 -2
  41. package/dist/resources/extensions/gsd/flat-phase-migration.js +74 -23
  42. package/dist/resources/extensions/gsd/git-service.js +14 -18
  43. package/dist/resources/extensions/gsd/gsd-db.js +52 -0
  44. package/dist/resources/extensions/gsd/guidance.js +1 -0
  45. package/dist/resources/extensions/gsd/guided-flow.js +40 -2
  46. package/dist/resources/extensions/gsd/key-manager.js +1 -0
  47. package/dist/resources/extensions/gsd/markdown-renderer.js +25 -8
  48. package/dist/resources/extensions/gsd/migrate-external.js +24 -7
  49. package/dist/resources/extensions/gsd/milestone-merge-transaction.js +3 -3
  50. package/dist/resources/extensions/gsd/preferences-diagnostics.js +24 -1
  51. package/dist/resources/extensions/gsd/preferences-models.js +6 -4
  52. package/dist/resources/extensions/gsd/preferences-validation.js +25 -2
  53. package/dist/resources/extensions/gsd/preferences.js +29 -2
  54. package/dist/resources/extensions/gsd/prompts/plan-slice.md +4 -2
  55. package/dist/resources/extensions/gsd/redact-secrets.js +37 -0
  56. package/dist/resources/extensions/gsd/repo-identity.js +16 -0
  57. package/dist/resources/extensions/gsd/repository-registry.js +6 -1
  58. package/dist/resources/extensions/gsd/root-write-leak-guard.js +4 -2
  59. package/dist/resources/extensions/gsd/safe-fs.js +23 -2
  60. package/dist/resources/extensions/gsd/skills/gsd-headless/SKILL.md +7 -9
  61. package/dist/resources/extensions/gsd/skills/gsd-headless/references/commands.md +1 -0
  62. package/dist/resources/extensions/gsd/tools/plan-task.js +76 -8
  63. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +40 -0
  64. package/dist/resources/extensions/gsd/unit-context-manifest.js +5 -4
  65. package/dist/resources/extensions/gsd/unit-registry.js +27 -2
  66. package/dist/resources/extensions/gsd/unit-runtime.js +21 -0
  67. package/dist/resources/extensions/gsd/workspace.js +26 -8
  68. package/dist/resources/extensions/gsd/worktree-lifecycle.js +67 -5
  69. package/dist/resources/extensions/gsd/worktree-manager.js +97 -4
  70. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  71. package/dist/web/standalone/.next/BUILD_ID +1 -1
  72. package/dist/web/standalone/.next/app-path-routes-manifest.json +7 -7
  73. package/dist/web/standalone/.next/build-manifest.json +2 -2
  74. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  75. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  77. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  78. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  79. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  80. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  81. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  82. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  83. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  84. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  85. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  86. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  87. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  88. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  89. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  90. package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
  91. package/dist/web/standalone/.next/server/app/index.html +1 -1
  92. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  93. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  94. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  95. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  96. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  97. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  98. package/dist/web/standalone/.next/server/app-paths-manifest.json +7 -7
  99. package/dist/web/standalone/.next/server/chunks/8357.js +1 -1
  100. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  101. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  102. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  103. package/dist/web/standalone/node_modules/@gsd/native/package.json +1 -1
  104. package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
  105. package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
  106. package/dist/wizard.d.ts +8 -0
  107. package/dist/wizard.js +8 -1
  108. package/integrations/hermes/CONTEXT.md +47 -0
  109. package/integrations/hermes/README.md +21 -0
  110. package/integrations/hermes/__init__.py +31 -0
  111. package/integrations/hermes/docs/issue-1162-grilling.md +197 -0
  112. package/integrations/hermes/docs/setup.md +4 -0
  113. package/integrations/hermes/open_gsd_hermes/commands.py +141 -0
  114. package/integrations/hermes/open_gsd_hermes/config.py +2 -0
  115. package/integrations/hermes/open_gsd_hermes/gsd_client.py +594 -31
  116. package/integrations/hermes/open_gsd_hermes/notifications.py +10 -1
  117. package/integrations/hermes/plugin.yaml +4 -0
  118. package/integrations/hermes/tests/test_config.py +6 -0
  119. package/integrations/hermes/tests/test_directory_loader.py +37 -0
  120. package/integrations/hermes/tests/test_gsd_client_cache.py +3 -0
  121. package/integrations/hermes/tests/test_gsd_client_execute.py +35 -0
  122. package/integrations/hermes/tests/test_gsd_client_process_cwd.py +22 -0
  123. package/integrations/hermes/tests/test_gsd_client_timeout.py +98 -0
  124. package/integrations/hermes/tests/test_milestone_client.py +447 -0
  125. package/integrations/hermes/tests/test_new_milestone_command.py +313 -0
  126. package/integrations/hermes/tests/test_supervisor_fsm.py +40 -0
  127. package/package.json +8 -8
  128. package/packages/cloud-mcp-gateway/dist/auth-store.d.ts +1 -0
  129. package/packages/cloud-mcp-gateway/dist/auth-store.d.ts.map +1 -1
  130. package/packages/cloud-mcp-gateway/dist/auth-store.js +21 -1
  131. package/packages/cloud-mcp-gateway/dist/auth-store.js.map +1 -1
  132. package/packages/cloud-mcp-gateway/dist/auth-store.test.js +34 -0
  133. package/packages/cloud-mcp-gateway/dist/auth-store.test.js.map +1 -1
  134. package/packages/cloud-mcp-gateway/package.json +2 -2
  135. package/packages/contracts/package.json +1 -1
  136. package/packages/daemon/dist/cloud-runtime.d.ts +2 -0
  137. package/packages/daemon/dist/cloud-runtime.d.ts.map +1 -1
  138. package/packages/daemon/dist/cloud-runtime.js +25 -1
  139. package/packages/daemon/dist/cloud-runtime.js.map +1 -1
  140. package/packages/daemon/dist/cloud-runtime.test.js +51 -0
  141. package/packages/daemon/dist/cloud-runtime.test.js.map +1 -1
  142. package/packages/daemon/dist/message-batcher.d.ts +7 -0
  143. package/packages/daemon/dist/message-batcher.d.ts.map +1 -1
  144. package/packages/daemon/dist/message-batcher.js +13 -0
  145. package/packages/daemon/dist/message-batcher.js.map +1 -1
  146. package/packages/daemon/dist/message-batcher.test.js +48 -0
  147. package/packages/daemon/dist/message-batcher.test.js.map +1 -1
  148. package/packages/daemon/dist/session-manager.js +1 -1
  149. package/packages/daemon/dist/session-manager.js.map +1 -1
  150. package/packages/daemon/package.json +5 -5
  151. package/packages/gsd-agent-core/dist/compaction/compaction.d.ts +13 -1
  152. package/packages/gsd-agent-core/dist/compaction/compaction.d.ts.map +1 -1
  153. package/packages/gsd-agent-core/dist/compaction/compaction.js +15 -1
  154. package/packages/gsd-agent-core/dist/compaction/compaction.js.map +1 -1
  155. package/packages/gsd-agent-core/dist/session/agent-session-navigation.d.ts.map +1 -1
  156. package/packages/gsd-agent-core/dist/session/agent-session-navigation.js +4 -1
  157. package/packages/gsd-agent-core/dist/session/agent-session-navigation.js.map +1 -1
  158. package/packages/gsd-agent-core/package.json +5 -5
  159. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-segment-walker.d.ts.map +1 -1
  160. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-segment-walker.js +1 -0
  161. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-segment-walker.js.map +1 -1
  162. package/packages/gsd-agent-modes/dist/modes/rpc/rpc-client.js +1 -1
  163. package/packages/gsd-agent-modes/dist/modes/rpc/rpc-client.js.map +1 -1
  164. package/packages/gsd-agent-modes/package.json +7 -7
  165. package/packages/mcp-server/dist/server.d.ts +18 -5
  166. package/packages/mcp-server/dist/server.d.ts.map +1 -1
  167. package/packages/mcp-server/dist/server.js +46 -5
  168. package/packages/mcp-server/dist/server.js.map +1 -1
  169. package/packages/mcp-server/dist/session-manager.js +1 -1
  170. package/packages/mcp-server/dist/session-manager.js.map +1 -1
  171. package/packages/mcp-server/package.json +4 -4
  172. package/packages/native/package.json +1 -1
  173. package/packages/pi-agent-core/package.json +1 -1
  174. package/packages/pi-ai/dist/env-api-keys.d.ts.map +1 -1
  175. package/packages/pi-ai/dist/env-api-keys.js +3 -0
  176. package/packages/pi-ai/dist/env-api-keys.js.map +1 -1
  177. package/packages/pi-ai/dist/types.d.ts +2 -2
  178. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  179. package/packages/pi-ai/dist/types.js.map +1 -1
  180. package/packages/pi-ai/package.json +1 -1
  181. package/packages/pi-coding-agent/dist/core/model-resolver.d.ts.map +1 -1
  182. package/packages/pi-coding-agent/dist/core/model-resolver.js +1 -0
  183. package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
  184. package/packages/pi-coding-agent/dist/core/session-manager-types.d.ts +1 -0
  185. package/packages/pi-coding-agent/dist/core/session-manager-types.d.ts.map +1 -1
  186. package/packages/pi-coding-agent/dist/core/session-manager-types.js.map +1 -1
  187. package/packages/pi-coding-agent/dist/core/session-manager.d.ts.map +1 -1
  188. package/packages/pi-coding-agent/dist/core/session-manager.js +3 -0
  189. package/packages/pi-coding-agent/dist/core/session-manager.js.map +1 -1
  190. package/packages/pi-coding-agent/package.json +7 -7
  191. package/packages/pi-tui/package.json +2 -2
  192. package/packages/rpc-client/dist/rpc-client.js +1 -1
  193. package/packages/rpc-client/dist/rpc-client.js.map +1 -1
  194. package/packages/rpc-client/package.json +2 -2
  195. package/pkg/package.json +1 -1
  196. package/scripts/link-workspace-packages.cjs +14 -4
  197. package/src/resources/GSD-WORKFLOW.md +20 -25
  198. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +130 -51
  199. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +371 -39
  200. package/src/resources/extensions/cursor-cli/index.ts +18 -0
  201. package/src/resources/extensions/cursor-cli/models.ts +65 -0
  202. package/src/resources/extensions/cursor-cli/package.json +11 -0
  203. package/src/resources/extensions/cursor-cli/readiness.ts +143 -0
  204. package/src/resources/extensions/cursor-cli/stream-adapter.ts +440 -0
  205. package/src/resources/extensions/cursor-cli/tests/index.test.ts +54 -0
  206. package/src/resources/extensions/cursor-cli/tests/readiness.test.ts +29 -0
  207. package/src/resources/extensions/cursor-cli/tests/stream-adapter.test.ts +166 -0
  208. package/src/resources/extensions/gsd/activity-log.ts +4 -1
  209. package/src/resources/extensions/gsd/artifact-verification.ts +25 -9
  210. package/src/resources/extensions/gsd/auto/closeout.ts +54 -56
  211. package/src/resources/extensions/gsd/auto/finalize.ts +8 -0
  212. package/src/resources/extensions/gsd/auto/loop-deps.ts +1 -0
  213. package/src/resources/extensions/gsd/auto/orchestrator.ts +1 -1
  214. package/src/resources/extensions/gsd/auto/run-unit.ts +5 -2
  215. package/src/resources/extensions/gsd/auto/schedule-wakeup.ts +8 -0
  216. package/src/resources/extensions/gsd/auto-dispatch.ts +17 -5
  217. package/src/resources/extensions/gsd/auto-model-selection.ts +49 -18
  218. package/src/resources/extensions/gsd/auto-prompts.ts +73 -2
  219. package/src/resources/extensions/gsd/auto-start.ts +4 -3
  220. package/src/resources/extensions/gsd/auto-timeout-recovery.ts +4 -0
  221. package/src/resources/extensions/gsd/auto-worktree.ts +50 -11
  222. package/src/resources/extensions/gsd/auto.ts +22 -3
  223. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +3 -0
  224. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +128 -5
  225. package/src/resources/extensions/gsd/bootstrap/schedule-wakeup-tool.ts +80 -12
  226. package/src/resources/extensions/gsd/bootstrap/tool-call-loop-guard.ts +12 -2
  227. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +23 -1
  228. package/src/resources/extensions/gsd/codebase-generator.ts +179 -18
  229. package/src/resources/extensions/gsd/commands/handlers/core.ts +39 -3
  230. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +135 -1
  231. package/src/resources/extensions/gsd/commands-worktree.ts +13 -2
  232. package/src/resources/extensions/gsd/db/queries.ts +40 -0
  233. package/src/resources/extensions/gsd/discussion-handoff.ts +10 -2
  234. package/src/resources/extensions/gsd/docs/preferences-reference.md +8 -2
  235. package/src/resources/extensions/gsd/doctor-engine-checks.ts +269 -18
  236. package/src/resources/extensions/gsd/doctor-providers.ts +39 -10
  237. package/src/resources/extensions/gsd/doctor-types.ts +5 -1
  238. package/src/resources/extensions/gsd/doctor.ts +95 -3
  239. package/src/resources/extensions/gsd/exec-sandbox.ts +6 -2
  240. package/src/resources/extensions/gsd/flat-phase-migration.ts +98 -22
  241. package/src/resources/extensions/gsd/git-service.ts +14 -17
  242. package/src/resources/extensions/gsd/gsd-db.ts +72 -0
  243. package/src/resources/extensions/gsd/guidance.ts +2 -0
  244. package/src/resources/extensions/gsd/guided-flow.ts +45 -2
  245. package/src/resources/extensions/gsd/key-manager.ts +1 -0
  246. package/src/resources/extensions/gsd/markdown-renderer.ts +25 -7
  247. package/src/resources/extensions/gsd/migrate-external.ts +23 -6
  248. package/src/resources/extensions/gsd/milestone-merge-transaction.ts +4 -4
  249. package/src/resources/extensions/gsd/model-router.ts +4 -5
  250. package/src/resources/extensions/gsd/preferences-diagnostics.ts +24 -0
  251. package/src/resources/extensions/gsd/preferences-models.ts +6 -3
  252. package/src/resources/extensions/gsd/preferences-validation.ts +28 -2
  253. package/src/resources/extensions/gsd/preferences.ts +38 -4
  254. package/src/resources/extensions/gsd/prompts/plan-slice.md +4 -2
  255. package/src/resources/extensions/gsd/redact-secrets.ts +40 -0
  256. package/src/resources/extensions/gsd/repo-identity.ts +17 -0
  257. package/src/resources/extensions/gsd/repository-registry.ts +7 -1
  258. package/src/resources/extensions/gsd/root-write-leak-guard.ts +3 -2
  259. package/src/resources/extensions/gsd/safe-fs.ts +22 -2
  260. package/src/resources/extensions/gsd/skills/gsd-headless/SKILL.md +7 -9
  261. package/src/resources/extensions/gsd/skills/gsd-headless/references/commands.md +1 -0
  262. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +131 -5
  263. package/src/resources/extensions/gsd/tests/auto-migrating-recovery.test.ts +18 -0
  264. package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +178 -0
  265. package/src/resources/extensions/gsd/tests/auto-phases-lifecycle.test.ts +43 -1
  266. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +54 -0
  267. package/src/resources/extensions/gsd/tests/auto-remote-session-lock-cleanup.test.ts +41 -0
  268. package/src/resources/extensions/gsd/tests/auto-runtime-state.test.ts +41 -0
  269. package/src/resources/extensions/gsd/tests/auto-session-scope.test.ts +4 -4
  270. package/src/resources/extensions/gsd/tests/auto-start-bootstrap-await-3420.test.ts +3 -0
  271. package/src/resources/extensions/gsd/tests/auto-timeout-recovery-harness-abort.test.ts +55 -0
  272. package/src/resources/extensions/gsd/tests/auto-warning-noise-regression.test.ts +8 -0
  273. package/src/resources/extensions/gsd/tests/batch-task-query.test.ts +85 -0
  274. package/src/resources/extensions/gsd/tests/check-auto-start-ready-guard.test.ts +29 -0
  275. package/src/resources/extensions/gsd/tests/codebase-generator.test.ts +365 -0
  276. package/src/resources/extensions/gsd/tests/complete-milestone-excerpt.test.ts +62 -1
  277. package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +138 -1
  278. package/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts +397 -5
  279. package/src/resources/extensions/gsd/tests/doctor-workspace.test.ts +207 -0
  280. package/src/resources/extensions/gsd/tests/flat-phase-migration.test.ts +38 -1
  281. package/src/resources/extensions/gsd/tests/flat-rate-routing-guard.test.ts +87 -19
  282. package/src/resources/extensions/gsd/tests/gate-1b-orphan-discrimination.test.ts +89 -0
  283. package/src/resources/extensions/gsd/tests/guided-flow-session-isolation.test.ts +9 -0
  284. package/src/resources/extensions/gsd/tests/integration/state-machine-edge-cases.test.ts +4 -2
  285. package/src/resources/extensions/gsd/tests/markdown-renderer.test.ts +122 -0
  286. package/src/resources/extensions/gsd/tests/merge-conflict-stops-loop.test.ts +4 -4
  287. package/src/resources/extensions/gsd/tests/migrate-external-worktree.test.ts +116 -0
  288. package/src/resources/extensions/gsd/tests/milestone-merge-stash-restore.test.ts +152 -22
  289. package/src/resources/extensions/gsd/tests/milestone-merge-transaction.test.ts +1 -1
  290. package/src/resources/extensions/gsd/tests/pending-autostart-scope.test.ts +2 -2
  291. package/src/resources/extensions/gsd/tests/phases-merge-error-stops-auto.test.ts +50 -6
  292. package/src/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +17 -0
  293. package/src/resources/extensions/gsd/tests/plan-task.test.ts +264 -1
  294. package/src/resources/extensions/gsd/tests/preferences.test.ts +150 -0
  295. package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +142 -0
  296. package/src/resources/extensions/gsd/tests/recovery-verify-logs.test.ts +4 -4
  297. package/src/resources/extensions/gsd/tests/redact-secrets.test.ts +56 -0
  298. package/src/resources/extensions/gsd/tests/register-hooks-flat-phase-migration.test.ts +73 -0
  299. package/src/resources/extensions/gsd/tests/register-hooks-loop-guard-auto.test.ts +275 -0
  300. package/src/resources/extensions/gsd/tests/repository-registry.test.ts +41 -2
  301. package/src/resources/extensions/gsd/tests/require-slice-discussion-dispatch.test.ts +55 -0
  302. package/src/resources/extensions/gsd/tests/root-write-leak-guard.test.ts +31 -9
  303. package/src/resources/extensions/gsd/tests/schedule-wakeup-tool.test.ts +167 -0
  304. package/src/resources/extensions/gsd/tests/stalled-tool-recovery.test.ts +49 -0
  305. package/src/resources/extensions/gsd/tests/state-corruption-2945.test.ts +1 -1
  306. package/src/resources/extensions/gsd/tests/status-per-repo.test.ts +94 -0
  307. package/src/resources/extensions/gsd/tests/token-tool-gating.test.ts +35 -0
  308. package/src/resources/extensions/gsd/tests/tool-call-loop-guard.test.ts +63 -0
  309. package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +66 -18
  310. package/src/resources/extensions/gsd/tests/unit-registry.test.ts +56 -0
  311. package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +30 -0
  312. package/src/resources/extensions/gsd/tests/uok-gitops-turn-action.test.ts +52 -0
  313. package/src/resources/extensions/gsd/tests/validator-scope-parity.test.ts +53 -1
  314. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +208 -1
  315. package/src/resources/extensions/gsd/tests/workspace.test.ts +18 -4
  316. package/src/resources/extensions/gsd/tests/worktree-journal-events.test.ts +4 -4
  317. package/src/resources/extensions/gsd/tests/worktree-lifecycle.test.ts +2 -2
  318. package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +31 -1
  319. package/src/resources/extensions/gsd/tests/worktree-teardown-safety.test.ts +32 -0
  320. package/src/resources/extensions/gsd/tools/plan-task.ts +107 -9
  321. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +58 -0
  322. package/src/resources/extensions/gsd/unit-context-manifest.ts +11 -5
  323. package/src/resources/extensions/gsd/unit-registry.ts +34 -2
  324. package/src/resources/extensions/gsd/unit-runtime.ts +42 -0
  325. package/src/resources/extensions/gsd/workspace.ts +42 -8
  326. package/src/resources/extensions/gsd/worktree-lifecycle.ts +135 -11
  327. package/src/resources/extensions/gsd/worktree-manager.ts +143 -5
  328. /package/dist/web/standalone/.next/static/{zQLcXm0Za8lexoXtgDpMG → bCg3aYrFN1DKZr2dqgE7L}/_buildManifest.js +0 -0
  329. /package/dist/web/standalone/.next/static/{zQLcXm0Za8lexoXtgDpMG → bCg3aYrFN1DKZr2dqgE7L}/_ssgManifest.js +0 -0
@@ -20,8 +20,9 @@
20
20
  //
21
21
  // Not yet declared here (remaining ADR-033 steps): the manifest data
22
22
  // (`UNIT_MANIFESTS` stays in unit-context-manifest.ts, already type-enforced
23
- // against the registry's `UnitType`) and prompt-template association (still
24
- // implicit in auto-prompts.ts builders).
23
+ // against the registry's `UnitType`). Prompt-template association is declared
24
+ // for direct one-template units; conditional/composite prompt selection stays
25
+ // with the prompt builders until it has a richer registry shape.
25
26
  import { BROWSER_CONTRACT_TOOL_NAMES } from "../shared/browser-contract.js";
26
27
  // ─── Shared tool-name constants (used by registry rows) ──────────────────
27
28
  export const RUN_UAT_WORKFLOW_TOOL_NAMES = [
@@ -50,6 +51,7 @@ export const UNIT_REGISTRY = {
50
51
  kind: "primary",
51
52
  scopeClass: "standard",
52
53
  phaseChain: ["research"],
54
+ promptTemplate: "research-milestone",
53
55
  toolContract: {
54
56
  allowedGsdTools: [
55
57
  "gsd_summary_save",
@@ -65,6 +67,7 @@ export const UNIT_REGISTRY = {
65
67
  kind: "primary",
66
68
  scopeClass: "standard",
67
69
  phaseChain: ["planning"],
70
+ promptTemplate: "plan-milestone",
68
71
  toolContract: {
69
72
  allowedGsdTools: [
70
73
  "gsd_milestone_status",
@@ -113,6 +116,7 @@ export const UNIT_REGISTRY = {
113
116
  kind: "primary",
114
117
  scopeClass: "section-close",
115
118
  phaseChain: ["validation", "planning"],
119
+ promptTemplate: "validate-milestone",
116
120
  toolContract: {
117
121
  allowedGsdTools: [
118
122
  "gsd_milestone_status",
@@ -130,6 +134,7 @@ export const UNIT_REGISTRY = {
130
134
  kind: "primary",
131
135
  scopeClass: "standard",
132
136
  phaseChain: ["completion"],
137
+ promptTemplate: "complete-milestone",
133
138
  toolContract: {
134
139
  allowedGsdTools: [
135
140
  "gsd_milestone_status",
@@ -153,6 +158,7 @@ export const UNIT_REGISTRY = {
153
158
  kind: "primary",
154
159
  scopeClass: "standard",
155
160
  phaseChain: ["research"],
161
+ promptTemplate: "research-slice",
156
162
  toolContract: {
157
163
  allowedGsdTools: [
158
164
  "gsd_milestone_status",
@@ -169,6 +175,7 @@ export const UNIT_REGISTRY = {
169
175
  kind: "primary",
170
176
  scopeClass: "standard",
171
177
  phaseChain: ["planning"],
178
+ promptTemplate: "plan-slice",
172
179
  toolContract: {
173
180
  allowedGsdTools: [
174
181
  "gsd_milestone_status",
@@ -187,6 +194,7 @@ export const UNIT_REGISTRY = {
187
194
  kind: "primary",
188
195
  scopeClass: "standard",
189
196
  phaseChain: ["planning"],
197
+ promptTemplate: "refine-slice",
190
198
  toolContract: {
191
199
  allowedGsdTools: [
192
200
  "gsd_milestone_status",
@@ -203,6 +211,7 @@ export const UNIT_REGISTRY = {
203
211
  kind: "primary",
204
212
  scopeClass: "standard",
205
213
  phaseChain: ["planning"],
214
+ promptTemplate: "replan-slice",
206
215
  toolContract: {
207
216
  allowedGsdTools: ["gsd_replan_slice", "gsd_decision_save"],
208
217
  requiredWorkflowTools: ["gsd_replan_slice"],
@@ -212,6 +221,7 @@ export const UNIT_REGISTRY = {
212
221
  kind: "primary",
213
222
  scopeClass: "section-close",
214
223
  phaseChain: ["completion"],
224
+ promptTemplate: "complete-slice",
215
225
  toolContract: {
216
226
  allowedGsdTools: [
217
227
  "gsd_milestone_status",
@@ -246,6 +256,7 @@ export const UNIT_REGISTRY = {
246
256
  kind: "primary",
247
257
  scopeClass: "standard",
248
258
  phaseChain: ["validation", "planning"],
259
+ promptTemplate: "reassess-roadmap",
249
260
  toolContract: {
250
261
  allowedGsdTools: ["gsd_milestone_status", "gsd_reassess_roadmap"],
251
262
  requiredWorkflowTools: ["gsd_milestone_status", "gsd_reassess_roadmap"],
@@ -255,6 +266,7 @@ export const UNIT_REGISTRY = {
255
266
  kind: "primary",
256
267
  scopeClass: "execute-task",
257
268
  phaseChain: ["execution"],
269
+ promptTemplate: "execute-task",
258
270
  toolContract: {
259
271
  allowedGsdTools: [
260
272
  "gsd_task_complete",
@@ -299,6 +311,7 @@ export const UNIT_REGISTRY = {
299
311
  kind: "primary",
300
312
  scopeClass: "execute-task",
301
313
  phaseChain: ["execution"],
314
+ promptTemplate: "reactive-execute",
302
315
  toolContract: {
303
316
  allowedGsdTools: [
304
317
  "gsd_milestone_status",
@@ -314,6 +327,7 @@ export const UNIT_REGISTRY = {
314
327
  kind: "primary",
315
328
  scopeClass: "standard",
316
329
  phaseChain: ["uat", "completion"],
330
+ promptTemplate: "run-uat",
317
331
  toolContract: {
318
332
  allowedGsdTools: [...RUN_UAT_WORKFLOW_TOOL_NAMES, "subagent"],
319
333
  requiredWorkflowTools: [...RUN_UAT_WORKFLOW_TOOL_NAMES],
@@ -328,6 +342,7 @@ export const UNIT_REGISTRY = {
328
342
  kind: "primary",
329
343
  scopeClass: "standard",
330
344
  phaseChain: ["validation", "planning"],
345
+ promptTemplate: "gate-evaluate",
331
346
  toolContract: {
332
347
  allowedGsdTools: ["gsd_save_gate_result"],
333
348
  requiredWorkflowTools: ["gsd_save_gate_result"],
@@ -337,6 +352,7 @@ export const UNIT_REGISTRY = {
337
352
  kind: "primary",
338
353
  scopeClass: "standard",
339
354
  phaseChain: ["validation", "planning"],
355
+ promptTemplate: "rewrite-docs",
340
356
  toolContract: {
341
357
  allowedGsdTools: ["gsd_summary_save", "gsd_decision_save"],
342
358
  requiredWorkflowTools: [],
@@ -361,6 +377,7 @@ export const UNIT_REGISTRY = {
361
377
  kind: "primary",
362
378
  scopeClass: "standard",
363
379
  phaseChain: ["discuss", "planning"],
380
+ promptTemplate: "guided-workflow-preferences",
364
381
  toolContract: {
365
382
  allowedGsdTools: ["gsd_summary_save"],
366
383
  requiredWorkflowTools: [],
@@ -370,6 +387,7 @@ export const UNIT_REGISTRY = {
370
387
  kind: "primary",
371
388
  scopeClass: "standard",
372
389
  phaseChain: ["discuss", "planning"],
390
+ promptTemplate: "guided-discuss-project",
373
391
  toolContract: {
374
392
  allowedGsdTools: ["gsd_summary_save", "gsd_decision_save", "gsd_requirement_save"],
375
393
  requiredWorkflowTools: ["ask_user_questions", "gsd_summary_save"],
@@ -379,6 +397,7 @@ export const UNIT_REGISTRY = {
379
397
  kind: "primary",
380
398
  scopeClass: "standard",
381
399
  phaseChain: ["discuss", "planning"],
400
+ promptTemplate: "guided-discuss-requirements",
382
401
  toolContract: {
383
402
  allowedGsdTools: ["gsd_requirement_save", "gsd_summary_save"],
384
403
  requiredWorkflowTools: ["ask_user_questions", "gsd_requirement_save", "gsd_summary_save"],
@@ -388,6 +407,7 @@ export const UNIT_REGISTRY = {
388
407
  kind: "primary",
389
408
  scopeClass: "standard",
390
409
  phaseChain: ["discuss", "planning"],
410
+ promptTemplate: "guided-research-decision",
391
411
  toolContract: {
392
412
  allowedGsdTools: ["gsd_summary_save"],
393
413
  requiredWorkflowTools: ["ask_user_questions"],
@@ -400,6 +420,7 @@ export const UNIT_REGISTRY = {
400
420
  kind: "primary",
401
421
  scopeClass: "standard",
402
422
  phaseChain: ["research"],
423
+ promptTemplate: "guided-research-project",
403
424
  toolContract: {
404
425
  allowedGsdTools: [],
405
426
  requiredWorkflowTools: [],
@@ -419,3 +440,7 @@ export function getUnitDescriptor(unitType) {
419
440
  export function getUnitPhaseChain(unitType) {
420
441
  return getUnitDescriptor(unitType)?.phaseChain ?? null;
421
442
  }
443
+ /** Prompt template id for units with a direct one-template builder. */
444
+ export function getUnitPromptTemplate(unitType) {
445
+ return getUnitDescriptor(unitType)?.promptTemplate;
446
+ }
@@ -93,6 +93,8 @@ export function writeUnitRuntimeRecord(basePath, unitType, unitId, startedAt, up
93
93
  const path = runtimePath(basePath, unitType, unitId);
94
94
  return withRecordLock(path, () => {
95
95
  const prev = readUnitRuntimeRecord(basePath, unitType, unitId);
96
+ const sameRun = prev?.startedAt === startedAt;
97
+ const updatesHarnessAbort = Object.prototype.hasOwnProperty.call(updates, "harnessAbort");
96
98
  const next = {
97
99
  version: 1,
98
100
  unitType,
@@ -109,11 +111,24 @@ export function writeUnitRuntimeRecord(basePath, unitType, unitId, startedAt, up
109
111
  recovery: updates.recovery ?? prev?.recovery,
110
112
  recoveryAttempts: updates.recoveryAttempts ?? prev?.recoveryAttempts ?? 0,
111
113
  lastRecoveryReason: updates.lastRecoveryReason ?? prev?.lastRecoveryReason,
114
+ harnessAbort: updatesHarnessAbort
115
+ ? updates.harnessAbort
116
+ : (sameRun ? prev?.harnessAbort : undefined),
112
117
  };
113
118
  atomicWriteSync(path, JSON.stringify(next, null, 2) + "\n", "utf-8");
114
119
  return next;
115
120
  });
116
121
  }
122
+ export function recordUnitHarnessAbort(basePath, unitType, unitId, startedAt, abort) {
123
+ return writeUnitRuntimeRecord(basePath, unitType, unitId, startedAt, {
124
+ harnessAbort: {
125
+ ...abort,
126
+ recordedAt: abort.recordedAt ?? Date.now(),
127
+ },
128
+ lastProgressAt: Date.now(),
129
+ lastProgressKind: `harness-abort:${abort.kind}`,
130
+ });
131
+ }
117
132
  export function readUnitRuntimeRecord(basePath, unitType, unitId) {
118
133
  const path = runtimePath(basePath, unitType, unitId);
119
134
  if (!existsSync(path))
@@ -125,6 +140,12 @@ export function readUnitRuntimeRecord(basePath, unitType, unitId) {
125
140
  return null;
126
141
  }
127
142
  }
143
+ export function readUnitHarnessAbort(basePath, unitType, unitId, startedAt) {
144
+ const record = readUnitRuntimeRecord(basePath, unitType, unitId);
145
+ if (!record || record.startedAt !== startedAt)
146
+ return null;
147
+ return record.harnessAbort ?? null;
148
+ }
128
149
  export function clearUnitRuntimeRecord(basePath, unitType, unitId) {
129
150
  const path = runtimePath(basePath, unitType, unitId);
130
151
  if (existsSync(path))
@@ -1,10 +1,27 @@
1
1
  // gsd-pi + Workspace handle: single source of truth for path resolution per milestone
2
- import { join, resolve } from "node:path";
3
- import { resolveGsdPathContract, normalizeRealPath } from "./paths.js";
2
+ import { dirname, join, resolve } from "node:path";
3
+ import { buildMilestoneFileName, canonicalPhaseDirName, isLegacyMilestonesLayout, milestonesDir, resolveGsdPathContract, resolveMilestoneFile, resolveMilestonePath, normalizeRealPath, } from "./paths.js";
4
4
  import { isGsdWorktreePath, resolveWorktreeProjectRoot } from "./worktree-root.js";
5
5
  function tryRealpath(p) {
6
6
  return normalizeRealPath(p);
7
7
  }
8
+ function isLegacyMilestoneDirectory(dir) {
9
+ const parent = dirname(dir);
10
+ return parent.endsWith("/milestones") || parent.endsWith("\\milestones");
11
+ }
12
+ function scopeMilestoneDir(basePath, milestoneId) {
13
+ return resolveMilestonePath(basePath, milestoneId) ?? join(milestonesDir(basePath), isLegacyMilestonesLayout(basePath) ? milestoneId : canonicalPhaseDirName(milestoneId));
14
+ }
15
+ function scopeMilestoneFile(basePath, milestoneId, suffix) {
16
+ const existing = resolveMilestoneFile(basePath, milestoneId, suffix);
17
+ if (existing)
18
+ return existing;
19
+ const dir = scopeMilestoneDir(basePath, milestoneId);
20
+ const filename = isLegacyMilestoneDirectory(dir)
21
+ ? `${milestoneId}-${suffix}.md`
22
+ : buildMilestoneFileName(milestoneId, suffix);
23
+ return join(dir, filename);
24
+ }
8
25
  /**
9
26
  * Create an immutable GsdWorkspace handle from a raw base path.
10
27
  * Resolves both the project root and (when applicable) the worktree root,
@@ -39,21 +56,22 @@ export function createWorkspace(rawBasePath) {
39
56
  * Bind a milestoneId to a workspace, producing an immutable MilestoneScope
40
57
  * with path-returning closures for DB-authoritative project state.
41
58
  *
42
- * These scope paths intentionally route to contract.projectGsd. In-flight
43
- * markdown artifact readers outside this scope use the projection-aware
44
- * resolvers in paths.ts so worktree units can see worktree-local projections.
59
+ * DB paths route to contract.projectGsd. Milestone artifact paths resolve from
60
+ * the pinned project root with the layout-aware resolvers, so flat-phase and
61
+ * legacy projects use the same scope surface.
45
62
  */
46
63
  export function scopeMilestone(workspace, milestoneId) {
47
64
  const { contract } = workspace;
48
65
  const gsd = contract.projectGsd;
66
+ const basePath = workspace.projectRoot;
49
67
  const scope = Object.freeze({
50
68
  workspace,
51
69
  milestoneId,
52
- contextFile: () => join(gsd, "milestones", milestoneId, `${milestoneId}-CONTEXT.md`),
53
- roadmapFile: () => join(gsd, "milestones", milestoneId, `${milestoneId}-ROADMAP.md`),
70
+ contextFile: () => scopeMilestoneFile(basePath, milestoneId, "CONTEXT"),
71
+ roadmapFile: () => scopeMilestoneFile(basePath, milestoneId, "ROADMAP"),
54
72
  stateFile: () => join(gsd, "STATE.md"),
55
73
  dbPath: () => contract.projectDb,
56
- milestoneDir: () => join(gsd, "milestones", milestoneId),
74
+ milestoneDir: () => scopeMilestoneDir(basePath, milestoneId),
57
75
  metaJson: () => join(gsd, `${milestoneId}-META.json`),
58
76
  });
59
77
  return scope;
@@ -102,6 +102,51 @@ class UserNotifiedError extends Error {
102
102
  function isSamePathPhysical(a, b) {
103
103
  return normalizeWorktreePathForCompare(a) === normalizeWorktreePathForCompare(b);
104
104
  }
105
+ function preflightBlockedMergeReason(blockedReason) {
106
+ if (blockedReason?.startsWith("unmerged-conflicts")) {
107
+ return "preflight-unmerged-conflicts";
108
+ }
109
+ return "preflight-dirty-overlap";
110
+ }
111
+ /**
112
+ * Run a milestone merge behind the Worktree Lifecycle seam with the root-clean
113
+ * stash guard that protects user changes around merge.
114
+ *
115
+ * The helper owns the invariant ordering (preflight -> merge callback ->
116
+ * postflight on every attempted merge path) and returns the same typed result
117
+ * shape as `exitMilestone`. Auto closeout remains responsible for translating
118
+ * that result into loop policy (pause vs. stop, visible transcript
119
+ * preservation, projection rebuild).
120
+ */
121
+ function runGuardedMilestoneMerge(request) {
122
+ const { merge, guard, milestoneId, notify } = request;
123
+ const preflight = guard.preflightCleanRoot(guard.projectRoot, milestoneId, notify);
124
+ if (preflight.blocked) {
125
+ return {
126
+ ok: false,
127
+ reason: preflightBlockedMergeReason(preflight.blockedReason),
128
+ };
129
+ }
130
+ let mergeError = null;
131
+ const exitResult = merge();
132
+ if (!exitResult.ok) {
133
+ mergeError = exitResult.cause ?? new Error(`exit ${exitResult.reason}`);
134
+ }
135
+ let postflight;
136
+ if (preflight.stashPushed) {
137
+ postflight = guard.postflightPopStash(guard.projectRoot, milestoneId, preflight.stashMarker, notify);
138
+ }
139
+ if (mergeError instanceof MergeConflictError) {
140
+ return { ok: false, reason: "merge-conflict", cause: mergeError, postflight };
141
+ }
142
+ if (mergeError) {
143
+ return { ok: false, reason: "merge-failed", cause: mergeError, postflight };
144
+ }
145
+ if (postflight?.needsManualRecovery) {
146
+ return { ok: false, reason: "postflight-stash-restore-failed", postflight };
147
+ }
148
+ return exitResult;
149
+ }
105
150
  // ─── Validation ──────────────────────────────────────────────────────────
106
151
  function isValidMilestoneId(milestoneId) {
107
152
  return !/[\/\\]|\.\./.test(milestoneId);
@@ -645,7 +690,7 @@ function _mergeWorktreeModeImpl(deps, mctx) {
645
690
  pushed: false,
646
691
  };
647
692
  }
648
- const mergeResult = deps.mergeMilestoneToMain(originalBasePath, milestoneId, roadmapResolution.content);
693
+ const mergeResult = deps.mergeMilestone(originalBasePath, milestoneId, roadmapResolution.content);
649
694
  // #2945 Bug 3: mergeMilestoneToMain performs best-effort worktree
650
695
  // cleanup internally (step 12), but it can silently fail on Windows
651
696
  // or when the worktree directory is locked. Perform a secondary
@@ -780,7 +825,7 @@ function _mergeBranchModeImpl(deps, mctx) {
780
825
  pushed: false,
781
826
  };
782
827
  }
783
- const mergeResult = deps.mergeMilestoneToMain(worktreeBasePath, milestoneId, roadmapResolution.content);
828
+ const mergeResult = deps.mergeMilestone(worktreeBasePath, milestoneId, roadmapResolution.content);
784
829
  if (mergeResult.codeFilesChanged) {
785
830
  notify(`Milestone ${milestoneId} merged (branch mode).${mergeResult.pushed ? " Pushed to remote." : ""}`, "info");
786
831
  }
@@ -983,14 +1028,31 @@ export class WorktreeLifecycle {
983
1028
  * With `opts.merge === false`, runs auto-commit and teardown without
984
1029
  * merging to main.
985
1030
  *
1031
+ * When `opts.guardedMerge` is present with `opts.merge === true`, the
1032
+ * lifecycle verb also owns the root-clean guard around the merge: preflight
1033
+ * runs before the inner merge, postflight stash restore runs after every
1034
+ * attempted merge path, and guard failures are returned as typed
1035
+ * `ExitResult` reasons.
1036
+ *
986
1037
  * Returns a typed `ExitResult`. `MergeConflictError` is surfaced as
987
1038
  * `{ ok: false, reason: "merge-conflict", cause }` instead of thrown,
988
- * giving callers a typed branch for the expected failure path.
989
- * Unexpected failures (filesystem, git permissions, etc.) are wrapped
990
- * as `{ ok: false, reason: "teardown-failed", cause }` so callers always
1039
+ * giving callers a typed branch for the expected failure path. Guarded merge
1040
+ * failures surface as `preflight-dirty-overlap`,
1041
+ * `preflight-unmerged-conflicts`, `merge-failed`, or
1042
+ * `postflight-stash-restore-failed`. Unexpected failures (filesystem, git
1043
+ * permissions, etc.) are wrapped as
1044
+ * `{ ok: false, reason: "teardown-failed", cause }` so callers always
991
1045
  * receive a discriminated union — no exceptions for any expected outcome.
992
1046
  */
993
1047
  exitMilestone(milestoneId, opts, ctx) {
1048
+ if (opts.merge && opts.guardedMerge) {
1049
+ return runGuardedMilestoneMerge({
1050
+ merge: () => this.exitMilestone(milestoneId, { merge: true }, ctx),
1051
+ guard: opts.guardedMerge,
1052
+ milestoneId,
1053
+ notify: ctx.notify,
1054
+ });
1055
+ }
994
1056
  if (opts.merge) {
995
1057
  try {
996
1058
  const merged = this._mergeAndExit(milestoneId, ctx);
@@ -17,7 +17,7 @@
17
17
  * 3. merge() — LLM-guided reconciliation of .gsd/ artifacts back to main
18
18
  * 4. remove() — git worktree remove + branch cleanup
19
19
  */
20
- import { existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, realpathSync, rmSync, statSync, unlinkSync } from "node:fs";
20
+ import { cpSync, existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, realpathSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync } from "node:fs";
21
21
  import { execFileSync } from "node:child_process";
22
22
  import { join, resolve, sep } from "node:path";
23
23
  import { GSDError, GSD_PARSE_ERROR, GSD_STALE_STATE, GSD_LOCK_HELD, GSD_GIT_ERROR, GSD_MERGE_CONFLICT } from "./errors.js";
@@ -163,6 +163,76 @@ function isRegisteredGitWorktreeAtPath(basePath, wtPath) {
163
163
  return false;
164
164
  }
165
165
  }
166
+ function inspectUncommittedWorktreeState(wtPath) {
167
+ try {
168
+ const status = execFileSync("git", ["status", "--porcelain=v1", "--untracked-files=all"], { cwd: wtPath, stdio: ["ignore", "pipe", "pipe"], encoding: "utf-8" }).trimEnd();
169
+ return { dirty: status.length > 0, status };
170
+ }
171
+ catch (err) {
172
+ const message = err instanceof Error ? err.message : String(err);
173
+ logWarning("worktree", `dirty worktree inspection failed for ${wtPath}: ${message}`);
174
+ // Fail closed: unknown state must quarantine rather than force-remove.
175
+ return { dirty: true, status: `git status failed: ${message}` };
176
+ }
177
+ }
178
+ function timestampForPath() {
179
+ return new Date().toISOString().replace(/[:.]/g, "-");
180
+ }
181
+ function uniqueQuarantinePath(basePath, name) {
182
+ const root = join(basePath, ".gsd", "quarantine", "worktrees");
183
+ const baseName = `${name}-${timestampForPath()}`;
184
+ let candidate = join(root, baseName);
185
+ let suffix = 2;
186
+ while (existsSync(candidate)) {
187
+ candidate = join(root, `${baseName}-${suffix}`);
188
+ suffix++;
189
+ }
190
+ return candidate;
191
+ }
192
+ function quarantineDirtyWorktree(basePath, name, branch, wtPath, status) {
193
+ const quarantinePath = uniqueQuarantinePath(basePath, name);
194
+ mkdirSync(join(basePath, ".gsd", "quarantine", "worktrees"), { recursive: true });
195
+ let moved = false;
196
+ try {
197
+ renameSync(wtPath, quarantinePath);
198
+ moved = true;
199
+ }
200
+ catch (renameErr) {
201
+ logWarning("worktree", `dirty worktree rename quarantine failed for ${name}: ${renameErr instanceof Error ? renameErr.message : String(renameErr)}; trying copy snapshot`);
202
+ try {
203
+ cpSync(wtPath, quarantinePath, { recursive: true, force: false, errorOnExist: true });
204
+ }
205
+ catch (copyErr) {
206
+ logWarning("reconcile", `Dirty worktree quarantine failed; preserving original worktree ${wtPath}: ${copyErr instanceof Error ? copyErr.message : String(copyErr)}`, { worktree: name, path: wtPath });
207
+ return null;
208
+ }
209
+ }
210
+ try {
211
+ const gitFile = join(quarantinePath, ".git");
212
+ if (existsSync(gitFile) && lstatSync(gitFile).isFile()) {
213
+ unlinkSync(gitFile);
214
+ }
215
+ }
216
+ catch (err) {
217
+ logWarning("worktree", `failed to remove quarantined worktree git pointer: ${err instanceof Error ? err.message : String(err)}`);
218
+ }
219
+ try {
220
+ writeFileSync(join(quarantinePath, ".gsd-quarantine.json"), JSON.stringify({
221
+ kind: "dirty-worktree",
222
+ worktree: name,
223
+ branch,
224
+ originalPath: wtPath,
225
+ moved,
226
+ quarantinedAt: new Date().toISOString(),
227
+ status,
228
+ }, null, 2) + "\n", "utf-8");
229
+ }
230
+ catch (err) {
231
+ logWarning("worktree", `failed to write worktree quarantine metadata: ${err instanceof Error ? err.message : String(err)}`);
232
+ }
233
+ logWarning("reconcile", `Quarantined dirty worktree ${name} at ${quarantinePath} before removal. Branch ${branch} was preserved.`, { worktree: name, path: quarantinePath, branch });
234
+ return quarantinePath;
235
+ }
166
236
  /** True when `wtPath` has a git worktree checkout marker (`.git` file with gitdir pointer). */
167
237
  export function isLiveGitWorktreeCheckout(wtPath) {
168
238
  const gitPath = join(wtPath, ".git");
@@ -610,6 +680,7 @@ export function removeWorktree(basePath, name, opts = {}) {
610
680
  let wtPath = worktreePath(basePath, name);
611
681
  const branch = opts.branch ?? worktreeBranchName(name);
612
682
  const { deleteBranch = true, force = true } = opts;
683
+ let deleteBranchAfterRemoval = deleteBranch;
613
684
  // Resolve the ACTUAL worktree path from git's worktree list.
614
685
  // The computed path may differ when .gsd/ is (or was) a symlink to an
615
686
  // external state directory — git resolves symlinks at worktree creation
@@ -664,10 +735,10 @@ export function removeWorktree(basePath, name, opts = {}) {
664
735
  }
665
736
  if (!existsSync(wtPath)) {
666
737
  nativeWorktreePrune(basePath);
667
- if (deleteBranch) {
738
+ if (deleteBranchAfterRemoval) {
668
739
  deleteBranchIfPresent(basePath, branch, "nativeBranchDelete failed");
669
740
  }
670
- return;
741
+ return true;
671
742
  }
672
743
  // Submodule safety (#2337): detect submodules with uncommitted changes
673
744
  // before force-removing the worktree. Force removal destroys all uncommitted
@@ -721,6 +792,27 @@ export function removeWorktree(basePath, name, opts = {}) {
721
792
  }
722
793
  }
723
794
  }
795
+ // Dirty-worktree quarantine (#2365): once submodule (#2337) and nested-.git
796
+ // (#2616) rescue have run — they commit/clean the tree first — quarantine any
797
+ // uncommitted state that remains instead of force-deleting it. Gate on a live
798
+ // git checkout so a stale local directory left behind by state-sync (which
799
+ // has no `.git` file and is not a registered worktree) is removed normally
800
+ // rather than quarantined; otherwise its branch would never be deleted and
801
+ // the worktree would never be unregistered (#1852).
802
+ if (force && resolvedPathSafe && isLiveGitWorktreeCheckout(resolvedWtPath)) {
803
+ const dirtyState = inspectUncommittedWorktreeState(resolvedWtPath);
804
+ if (dirtyState.dirty) {
805
+ const quarantinePath = quarantineDirtyWorktree(basePath, name, branch, resolvedWtPath, dirtyState.status);
806
+ if (!quarantinePath) {
807
+ return false;
808
+ }
809
+ deleteBranchAfterRemoval = false;
810
+ if (!existsSync(resolvedWtPath)) {
811
+ nativeWorktreePrune(basePath);
812
+ return true;
813
+ }
814
+ }
815
+ }
724
816
  // Remove worktree — only use force/rmSync when the path is safely contained
725
817
  if (resolvedPathSafe) {
726
818
  // Remove worktree: try non-force first when submodules have changes,
@@ -778,9 +870,10 @@ export function removeWorktree(basePath, name, opts = {}) {
778
870
  }
779
871
  // Prune stale entries so git knows the worktree is gone
780
872
  nativeWorktreePrune(basePath);
781
- if (deleteBranch) {
873
+ if (deleteBranchAfterRemoval) {
782
874
  deleteBranchIfPresent(basePath, branch, "final branch delete failed");
783
875
  }
876
+ return true;
784
877
  }
785
878
  /**
786
879
  * Paths to skip in all worktree diffs (internal/runtime artifacts).