@opengsd/gsd-pi 1.4.0-dev.c18009cd → 1.5.0-dev.4fb2d460

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 (334) hide show
  1. package/README.md +10 -10
  2. package/dist/resources/.managed-resources-content-hash +1 -1
  3. package/dist/resources/extensions/ask-user-questions.js +1 -1
  4. package/dist/resources/extensions/gsd/auto/closeout.js +97 -5
  5. package/dist/resources/extensions/gsd/auto/orchestrator.js +4 -3
  6. package/dist/resources/extensions/gsd/auto/pre-dispatch.js +6 -3
  7. package/dist/resources/extensions/gsd/auto/unit-phase.js +5 -9
  8. package/dist/resources/extensions/gsd/auto-artifact-paths.js +12 -3
  9. package/dist/resources/extensions/gsd/auto-direct-dispatch.js +11 -7
  10. package/dist/resources/extensions/gsd/auto-dispatch.js +16 -2
  11. package/dist/resources/extensions/gsd/auto-recovery.js +50 -6
  12. package/dist/resources/extensions/gsd/auto-start.js +4 -3
  13. package/dist/resources/extensions/gsd/auto-worktree-branch-lifecycle.js +189 -0
  14. package/dist/resources/extensions/gsd/auto-worktree-cleanup.js +133 -0
  15. package/dist/resources/extensions/gsd/auto-worktree-conflict-auto-resolve.js +35 -0
  16. package/dist/resources/extensions/gsd/auto-worktree-creation.js +130 -0
  17. package/dist/resources/extensions/gsd/auto-worktree-entry.js +118 -0
  18. package/dist/resources/extensions/gsd/auto-worktree-merge-already-merged.js +98 -0
  19. package/dist/resources/extensions/gsd/auto-worktree-merge-branch-head.js +52 -0
  20. package/dist/resources/extensions/gsd/auto-worktree-merge-cleanup.js +87 -0
  21. package/dist/resources/extensions/gsd/auto-worktree-merge-code-changes.js +46 -0
  22. package/dist/resources/extensions/gsd/auto-worktree-merge-db-ready.js +68 -0
  23. package/dist/resources/extensions/gsd/auto-worktree-merge-integration-branch.js +51 -0
  24. package/dist/resources/extensions/gsd/auto-worktree-merge-message.js +106 -0
  25. package/dist/resources/extensions/gsd/auto-worktree-merge-pre-teardown.js +64 -0
  26. package/dist/resources/extensions/gsd/auto-worktree-merge-stash.js +168 -0
  27. package/dist/resources/extensions/gsd/auto-worktree-merge.js +302 -0
  28. package/dist/resources/extensions/gsd/auto-worktree-milestone-shelter.js +119 -0
  29. package/dist/resources/extensions/gsd/auto-worktree-path-resolution.js +5 -0
  30. package/dist/resources/extensions/gsd/auto-worktree-path-utils.js +5 -0
  31. package/dist/resources/extensions/gsd/auto-worktree-resource-version.js +41 -0
  32. package/dist/resources/extensions/gsd/auto-worktree-runtime-cleanup.js +99 -0
  33. package/dist/resources/extensions/gsd/auto-worktree-session-registry.js +57 -0
  34. package/dist/resources/extensions/gsd/auto-worktree-sync.js +249 -0
  35. package/dist/resources/extensions/gsd/auto-worktree-teardown.js +120 -0
  36. package/dist/resources/extensions/gsd/auto-worktree.js +20 -2073
  37. package/dist/resources/extensions/gsd/auto.js +9 -3
  38. package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +1 -1
  39. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +20 -2
  40. package/dist/resources/extensions/gsd/bootstrap/system-context.js +1 -1
  41. package/dist/resources/extensions/gsd/bootstrap/tool-call-loop-guard.js +185 -27
  42. package/dist/resources/extensions/gsd/codebase-generator.js +7 -1
  43. package/dist/resources/extensions/gsd/commands-handlers.js +1 -1
  44. package/dist/resources/extensions/gsd/commands-workflow-templates.js +3 -3
  45. package/dist/resources/extensions/gsd/db/queries.js +9 -9
  46. package/dist/resources/extensions/gsd/db/writers/cascades.js +20 -2
  47. package/dist/resources/extensions/gsd/doctor-engine-checks.js +93 -11
  48. package/dist/resources/extensions/gsd/flat-phase-migration.js +32 -3
  49. package/dist/resources/extensions/gsd/forensics.js +1 -1
  50. package/dist/resources/extensions/gsd/git-conflict-resolve.js +2 -2
  51. package/dist/resources/extensions/gsd/git-service.js +45 -0
  52. package/dist/resources/extensions/gsd/gsd-db.js +23 -11
  53. package/dist/resources/extensions/gsd/guided-flow.js +22 -17
  54. package/dist/resources/extensions/gsd/markdown-renderer.js +15 -3
  55. package/dist/resources/extensions/gsd/md-importer.js +89 -23
  56. package/dist/resources/extensions/gsd/migration-auto-check.js +30 -3
  57. package/dist/resources/extensions/gsd/milestone-merge-transaction.js +11 -0
  58. package/dist/resources/extensions/gsd/milestone-settlement.js +1 -1
  59. package/dist/resources/extensions/gsd/parallel-merge.js +1 -1
  60. package/dist/resources/extensions/gsd/parallel-orchestrator.js +3 -1
  61. package/dist/resources/extensions/gsd/paths.js +17 -3
  62. package/dist/resources/extensions/gsd/preferences-skills.js +10 -16
  63. package/dist/resources/extensions/gsd/preferences-types.js +1 -0
  64. package/dist/resources/extensions/gsd/preferences-validation.js +82 -0
  65. package/dist/resources/extensions/gsd/preferences.js +20 -0
  66. package/dist/resources/extensions/gsd/prompts/system.md +6 -6
  67. package/dist/resources/extensions/gsd/quick.js +12 -3
  68. package/dist/resources/extensions/gsd/skill-discovery.js +36 -23
  69. package/dist/resources/extensions/gsd/slice-parallel-orchestrator.js +2 -1
  70. package/dist/resources/extensions/gsd/state/derive/from-db.js +88 -153
  71. package/dist/resources/extensions/gsd/tool-contract.js +14 -0
  72. package/dist/resources/extensions/gsd/tools/reopen-slice.js +5 -0
  73. package/dist/resources/extensions/gsd/tools/reopen-task.js +13 -5
  74. package/dist/resources/extensions/gsd/unit-registry.js +12 -2
  75. package/dist/resources/extensions/gsd/unmerged-milestone-guard.js +1 -1
  76. package/dist/resources/extensions/gsd/validation-block-guard.js +2 -1
  77. package/dist/resources/extensions/gsd/workspace-git-preflight.js +2 -2
  78. package/dist/resources/extensions/gsd/worktree-command.js +1 -1
  79. package/dist/resources/extensions/gsd/worktree-lifecycle.js +14 -9
  80. package/dist/resources/extensions/gsd/worktree-reentry.js +2 -1
  81. package/dist/resources/extensions/shared/interview-ui.js +62 -9
  82. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  83. package/dist/web/standalone/.next/BUILD_ID +1 -1
  84. package/dist/web/standalone/.next/app-path-routes-manifest.json +8 -8
  85. package/dist/web/standalone/.next/build-manifest.json +2 -2
  86. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  87. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  88. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  89. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  90. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  91. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  92. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  93. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  94. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  95. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  96. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  97. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  98. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  99. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  100. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  101. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  102. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  103. package/dist/web/standalone/.next/server/app/index.html +1 -1
  104. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  105. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  106. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  107. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  108. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  109. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  110. package/dist/web/standalone/.next/server/app-paths-manifest.json +8 -8
  111. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  112. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  113. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  114. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  115. package/dist/web/standalone/node_modules/@gsd/native/package.json +1 -1
  116. package/dist/web/standalone/node_modules/node-pty/build/Makefile +1 -1
  117. package/integrations/hermes/open_gsd_hermes/gsd_client.py +24 -35
  118. package/integrations/hermes/tests/test_gsd_client_timeout.py +4 -4
  119. package/package.json +6 -6
  120. package/packages/cloud-mcp-gateway/package.json +2 -2
  121. package/packages/contracts/package.json +1 -1
  122. package/packages/daemon/package.json +4 -4
  123. package/packages/gsd-agent-core/dist/export-html/template.js +53 -4
  124. package/packages/gsd-agent-core/package.json +5 -5
  125. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  126. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js +3 -2
  127. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  128. package/packages/gsd-agent-modes/package.json +7 -7
  129. package/packages/mcp-server/package.json +4 -4
  130. package/packages/native/package.json +1 -1
  131. package/packages/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
  132. package/packages/pi-agent-core/dist/agent-loop.js +14 -9
  133. package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
  134. package/packages/pi-agent-core/package.json +1 -1
  135. package/packages/pi-ai/dist/index.d.ts +1 -0
  136. package/packages/pi-ai/dist/index.d.ts.map +1 -1
  137. package/packages/pi-ai/dist/index.js +1 -0
  138. package/packages/pi-ai/dist/index.js.map +1 -1
  139. package/packages/pi-ai/dist/providers/transform-messages.d.ts.map +1 -1
  140. package/packages/pi-ai/dist/providers/transform-messages.js +7 -3
  141. package/packages/pi-ai/dist/providers/transform-messages.js.map +1 -1
  142. package/packages/pi-ai/dist/tool-result-content.d.ts +3 -0
  143. package/packages/pi-ai/dist/tool-result-content.d.ts.map +1 -0
  144. package/packages/pi-ai/dist/tool-result-content.js +62 -0
  145. package/packages/pi-ai/dist/tool-result-content.js.map +1 -0
  146. package/packages/pi-ai/dist/types.d.ts +8 -20
  147. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  148. package/packages/pi-ai/dist/types.js.map +1 -1
  149. package/packages/pi-ai/package.json +1 -1
  150. package/packages/pi-coding-agent/dist/core/package-manager.d.ts +9 -0
  151. package/packages/pi-coding-agent/dist/core/package-manager.d.ts.map +1 -1
  152. package/packages/pi-coding-agent/dist/core/package-manager.js +74 -53
  153. package/packages/pi-coding-agent/dist/core/package-manager.js.map +1 -1
  154. package/packages/pi-coding-agent/dist/core/resource-loader.d.ts +6 -0
  155. package/packages/pi-coding-agent/dist/core/resource-loader.d.ts.map +1 -1
  156. package/packages/pi-coding-agent/dist/core/resource-loader.js +30 -2
  157. package/packages/pi-coding-agent/dist/core/resource-loader.js.map +1 -1
  158. package/packages/pi-coding-agent/dist/core/skill-directories.d.ts +66 -0
  159. package/packages/pi-coding-agent/dist/core/skill-directories.d.ts.map +1 -0
  160. package/packages/pi-coding-agent/dist/core/skill-directories.js +138 -0
  161. package/packages/pi-coding-agent/dist/core/skill-directories.js.map +1 -0
  162. package/packages/pi-coding-agent/dist/core/skills.d.ts +4 -1
  163. package/packages/pi-coding-agent/dist/core/skills.d.ts.map +1 -1
  164. package/packages/pi-coding-agent/dist/core/skills.js +4 -1
  165. package/packages/pi-coding-agent/dist/core/skills.js.map +1 -1
  166. package/packages/pi-coding-agent/dist/index.d.ts +1 -0
  167. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  168. package/packages/pi-coding-agent/dist/index.js +1 -0
  169. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  170. package/packages/pi-coding-agent/package.json +7 -7
  171. package/packages/pi-tui/package.json +2 -2
  172. package/packages/rpc-client/package.json +2 -2
  173. package/pkg/dist/core/export-html/template.js +53 -4
  174. package/pkg/package.json +1 -1
  175. package/src/resources/extensions/ask-user-questions.ts +1 -1
  176. package/src/resources/extensions/gsd/auto/closeout.ts +114 -7
  177. package/src/resources/extensions/gsd/auto/orchestrator.ts +4 -3
  178. package/src/resources/extensions/gsd/auto/pre-dispatch.ts +6 -3
  179. package/src/resources/extensions/gsd/auto/unit-phase.ts +5 -9
  180. package/src/resources/extensions/gsd/auto-artifact-paths.ts +12 -3
  181. package/src/resources/extensions/gsd/auto-direct-dispatch.ts +13 -7
  182. package/src/resources/extensions/gsd/auto-dispatch.ts +18 -2
  183. package/src/resources/extensions/gsd/auto-recovery.ts +50 -6
  184. package/src/resources/extensions/gsd/auto-start.ts +5 -3
  185. package/src/resources/extensions/gsd/auto-worktree-branch-lifecycle.ts +237 -0
  186. package/src/resources/extensions/gsd/auto-worktree-cleanup.ts +166 -0
  187. package/src/resources/extensions/gsd/auto-worktree-conflict-auto-resolve.ts +37 -0
  188. package/src/resources/extensions/gsd/auto-worktree-creation.ts +171 -0
  189. package/src/resources/extensions/gsd/auto-worktree-entry.ts +154 -0
  190. package/src/resources/extensions/gsd/auto-worktree-merge-already-merged.ts +161 -0
  191. package/src/resources/extensions/gsd/auto-worktree-merge-branch-head.ts +83 -0
  192. package/src/resources/extensions/gsd/auto-worktree-merge-cleanup.ts +132 -0
  193. package/src/resources/extensions/gsd/auto-worktree-merge-code-changes.ts +71 -0
  194. package/src/resources/extensions/gsd/auto-worktree-merge-db-ready.ts +112 -0
  195. package/src/resources/extensions/gsd/auto-worktree-merge-integration-branch.ts +99 -0
  196. package/src/resources/extensions/gsd/auto-worktree-merge-message.ts +166 -0
  197. package/src/resources/extensions/gsd/auto-worktree-merge-pre-teardown.ts +93 -0
  198. package/src/resources/extensions/gsd/auto-worktree-merge-stash.ts +213 -0
  199. package/src/resources/extensions/gsd/auto-worktree-merge.ts +391 -0
  200. package/src/resources/extensions/gsd/auto-worktree-milestone-shelter.ts +159 -0
  201. package/src/resources/extensions/gsd/auto-worktree-path-resolution.ts +6 -0
  202. package/src/resources/extensions/gsd/auto-worktree-path-utils.ts +10 -0
  203. package/src/resources/extensions/gsd/auto-worktree-resource-version.ts +44 -0
  204. package/src/resources/extensions/gsd/auto-worktree-runtime-cleanup.ts +119 -0
  205. package/src/resources/extensions/gsd/auto-worktree-session-registry.ts +71 -0
  206. package/src/resources/extensions/gsd/auto-worktree-sync.ts +370 -0
  207. package/src/resources/extensions/gsd/auto-worktree-teardown.ts +161 -0
  208. package/src/resources/extensions/gsd/auto-worktree.ts +69 -2429
  209. package/src/resources/extensions/gsd/auto.ts +11 -17
  210. package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +1 -1
  211. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +20 -2
  212. package/src/resources/extensions/gsd/bootstrap/system-context.ts +1 -1
  213. package/src/resources/extensions/gsd/bootstrap/tool-call-loop-guard.ts +218 -27
  214. package/src/resources/extensions/gsd/codebase-generator.ts +7 -1
  215. package/src/resources/extensions/gsd/commands-handlers.ts +1 -1
  216. package/src/resources/extensions/gsd/commands-workflow-templates.ts +3 -3
  217. package/src/resources/extensions/gsd/db/queries.ts +9 -9
  218. package/src/resources/extensions/gsd/db/writers/cascades.ts +23 -1
  219. package/src/resources/extensions/gsd/doctor-engine-checks.ts +109 -10
  220. package/src/resources/extensions/gsd/flat-phase-migration.ts +34 -3
  221. package/src/resources/extensions/gsd/forensics.ts +1 -1
  222. package/src/resources/extensions/gsd/git-conflict-resolve.ts +2 -2
  223. package/src/resources/extensions/gsd/git-service.ts +53 -0
  224. package/src/resources/extensions/gsd/gsd-db.ts +33 -11
  225. package/src/resources/extensions/gsd/guided-flow.ts +26 -18
  226. package/src/resources/extensions/gsd/markdown-renderer.ts +15 -3
  227. package/src/resources/extensions/gsd/md-importer.ts +93 -22
  228. package/src/resources/extensions/gsd/migration-auto-check.ts +32 -3
  229. package/src/resources/extensions/gsd/milestone-merge-transaction.ts +15 -0
  230. package/src/resources/extensions/gsd/milestone-settlement.ts +1 -1
  231. package/src/resources/extensions/gsd/parallel-merge.ts +1 -1
  232. package/src/resources/extensions/gsd/parallel-orchestrator.ts +3 -1
  233. package/src/resources/extensions/gsd/paths.ts +21 -3
  234. package/src/resources/extensions/gsd/preferences-skills.ts +10 -16
  235. package/src/resources/extensions/gsd/preferences-types.ts +30 -0
  236. package/src/resources/extensions/gsd/preferences-validation.ts +69 -0
  237. package/src/resources/extensions/gsd/preferences.ts +20 -0
  238. package/src/resources/extensions/gsd/prompts/system.md +6 -6
  239. package/src/resources/extensions/gsd/quick.ts +11 -3
  240. package/src/resources/extensions/gsd/skill-discovery.ts +47 -23
  241. package/src/resources/extensions/gsd/slice-parallel-orchestrator.ts +3 -1
  242. package/src/resources/extensions/gsd/state/derive/from-db.ts +201 -157
  243. package/src/resources/extensions/gsd/tests/auto-artifact-paths.test.ts +169 -1
  244. package/src/resources/extensions/gsd/tests/auto-loop-no-copy-artifacts.test.ts +2 -1
  245. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +57 -0
  246. package/src/resources/extensions/gsd/tests/auto-start-clean-runtime-db-gated.test.ts +1 -1
  247. package/src/resources/extensions/gsd/tests/auto-warning-noise-regression.test.ts +2 -1
  248. package/src/resources/extensions/gsd/tests/auto-worktree-auto-resolve.test.ts +1 -1
  249. package/src/resources/extensions/gsd/tests/auto-worktree-merge-already-merged.test.ts +177 -0
  250. package/src/resources/extensions/gsd/tests/auto-worktree-merge-branch-head.test.ts +118 -0
  251. package/src/resources/extensions/gsd/tests/auto-worktree-merge-cleanup.test.ts +115 -0
  252. package/src/resources/extensions/gsd/tests/auto-worktree-merge-code-changes.test.ts +76 -0
  253. package/src/resources/extensions/gsd/tests/auto-worktree-merge-db-ready.test.ts +140 -0
  254. package/src/resources/extensions/gsd/tests/auto-worktree-merge-integration-branch.test.ts +123 -0
  255. package/src/resources/extensions/gsd/tests/auto-worktree-merge-message.test.ts +114 -0
  256. package/src/resources/extensions/gsd/tests/auto-worktree-merge-pre-teardown.test.ts +84 -0
  257. package/src/resources/extensions/gsd/tests/auto-worktree-merge-stash.test.ts +70 -0
  258. package/src/resources/extensions/gsd/tests/auto-worktree-milestone-shelter.test.ts +52 -0
  259. package/src/resources/extensions/gsd/tests/auto-worktree-registry.test.ts +6 -6
  260. package/src/resources/extensions/gsd/tests/auto-worktree-resource-version.test.ts +59 -0
  261. package/src/resources/extensions/gsd/tests/auto-worktree-untracked-content.test.ts +3 -1
  262. package/src/resources/extensions/gsd/tests/codebase-generator.test.ts +22 -0
  263. package/src/resources/extensions/gsd/tests/completed-units-metrics-sync.test.ts +1 -1
  264. package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +4 -0
  265. package/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts +174 -0
  266. package/src/resources/extensions/gsd/tests/fast-forward-reused-milestone-branch.test.ts +1 -1
  267. package/src/resources/extensions/gsd/tests/flat-phase-migration.test.ts +71 -1
  268. package/src/resources/extensions/gsd/tests/flat-phase-renderer.test.ts +25 -1
  269. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +40 -0
  270. package/src/resources/extensions/gsd/tests/import-completion-lost-update.test.ts +196 -0
  271. package/src/resources/extensions/gsd/tests/integration/all-milestones-complete-merge.test.ts +4 -6
  272. package/src/resources/extensions/gsd/tests/integration/auto-stash-merge.test.ts +2 -1
  273. package/src/resources/extensions/gsd/tests/integration/auto-worktree-milestone-merge.test.ts +3 -3
  274. package/src/resources/extensions/gsd/tests/integration/auto-worktree.test.ts +7 -8
  275. package/src/resources/extensions/gsd/tests/integration/feature-branch-lifecycle-integration.test.ts +2 -5
  276. package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +76 -0
  277. package/src/resources/extensions/gsd/tests/integration/merge-cwd-restore.test.ts +1 -1
  278. package/src/resources/extensions/gsd/tests/integration/merge-preserve-worktree-on-preteardown-error.test.ts +1 -1
  279. package/src/resources/extensions/gsd/tests/integration/merge-strategy-regular.test.ts +2 -1
  280. package/src/resources/extensions/gsd/tests/integration/milestone-transition-worktree.test.ts +5 -7
  281. package/src/resources/extensions/gsd/tests/integration/workspace-collapse-integration.test.ts +2 -4
  282. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +16 -15
  283. package/src/resources/extensions/gsd/tests/merge-closeout-consistency-gate.test.ts +1 -1
  284. package/src/resources/extensions/gsd/tests/merge-db-cycle.test.ts +1 -1
  285. package/src/resources/extensions/gsd/tests/merge-self-branch-guard.test.ts +1 -1
  286. package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +91 -0
  287. package/src/resources/extensions/gsd/tests/milestone-merge-stash-restore.test.ts +439 -0
  288. package/src/resources/extensions/gsd/tests/milestone-merge-transaction.test.ts +24 -0
  289. package/src/resources/extensions/gsd/tests/none-mode-gates.test.ts +1 -1
  290. package/src/resources/extensions/gsd/tests/originalbase-path-comparison.test.ts +3 -3
  291. package/src/resources/extensions/gsd/tests/parallel-worker-lock-contention.test.ts +1 -1
  292. package/src/resources/extensions/gsd/tests/preferences-worktree-sync.test.ts +1 -4
  293. package/src/resources/extensions/gsd/tests/preferences.test.ts +19 -0
  294. package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +5 -0
  295. package/src/resources/extensions/gsd/tests/reopen-slice.test.ts +52 -0
  296. package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +20 -0
  297. package/src/resources/extensions/gsd/tests/skill-discovery.test.ts +46 -0
  298. package/src/resources/extensions/gsd/tests/stale-worktree-cwd.test.ts +3 -5
  299. package/src/resources/extensions/gsd/tests/stash-pop-gsd-conflict.test.ts +2 -1
  300. package/src/resources/extensions/gsd/tests/stash-queued-context-files.test.ts +2 -1
  301. package/src/resources/extensions/gsd/tests/steer-worktree-path.test.ts +1 -1
  302. package/src/resources/extensions/gsd/tests/sync-worktree-skip-current.test.ts +1 -1
  303. package/src/resources/extensions/gsd/tests/system-flat-phase-layout.test.ts +33 -0
  304. package/src/resources/extensions/gsd/tests/teardown-chdir-failure-clears-registry.test.ts +4 -4
  305. package/src/resources/extensions/gsd/tests/teardown-cleanup-parity.test.ts +2 -1
  306. package/src/resources/extensions/gsd/tests/teardown-failure-clears-registry.test.ts +4 -4
  307. package/src/resources/extensions/gsd/tests/tool-call-loop-guard.test.ts +196 -0
  308. package/src/resources/extensions/gsd/tests/uat-stuck-loop-orphaned-worktree.test.ts +1 -1
  309. package/src/resources/extensions/gsd/tests/unit-registry.test.ts +26 -1
  310. package/src/resources/extensions/gsd/tests/worktree-db-integration.test.ts +1 -1
  311. package/src/resources/extensions/gsd/tests/worktree-db-respawn-truncation.test.ts +1 -1
  312. package/src/resources/extensions/gsd/tests/worktree-db-same-file.test.ts +1 -3
  313. package/src/resources/extensions/gsd/tests/worktree-expected-warnings.test.ts +1 -1
  314. package/src/resources/extensions/gsd/tests/worktree-git-pathspec.test.ts +1 -1
  315. package/src/resources/extensions/gsd/tests/worktree-main-branch.test.ts +1 -1
  316. package/src/resources/extensions/gsd/tests/worktree-preferences-sync.test.ts +1 -1
  317. package/src/resources/extensions/gsd/tests/worktree-reentry.test.ts +2 -1
  318. package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +5 -2
  319. package/src/resources/extensions/gsd/tests/worktree-sync-overwrite-loop.test.ts +1 -1
  320. package/src/resources/extensions/gsd/tests/worktree-sync-tasks.test.ts +1 -1
  321. package/src/resources/extensions/gsd/tool-contract.ts +29 -0
  322. package/src/resources/extensions/gsd/tools/reopen-slice.ts +5 -0
  323. package/src/resources/extensions/gsd/tools/reopen-task.ts +13 -5
  324. package/src/resources/extensions/gsd/unit-registry.ts +16 -2
  325. package/src/resources/extensions/gsd/unmerged-milestone-guard.ts +1 -1
  326. package/src/resources/extensions/gsd/validation-block-guard.ts +2 -1
  327. package/src/resources/extensions/gsd/workspace-git-preflight.ts +2 -2
  328. package/src/resources/extensions/gsd/worktree-command.ts +1 -1
  329. package/src/resources/extensions/gsd/worktree-lifecycle.ts +19 -15
  330. package/src/resources/extensions/gsd/worktree-reentry.ts +2 -1
  331. package/src/resources/extensions/shared/interview-ui.ts +58 -11
  332. package/src/resources/extensions/shared/tests/interview-ui-border.test.ts +32 -0
  333. /package/dist/web/standalone/.next/static/{L2UMqXELiDH3oz-7yguXp → gXoWec4UNqcR0tkuHwYCr}/_buildManifest.js +0 -0
  334. /package/dist/web/standalone/.next/static/{L2UMqXELiDH3oz-7yguXp → gXoWec4UNqcR0tkuHwYCr}/_ssgManifest.js +0 -0
@@ -1,6 +1,6 @@
1
1
  import test from "node:test";
2
2
  import assert from "node:assert/strict";
3
- import { mkdtempSync, mkdirSync, realpathSync, rmSync, writeFileSync } from "node:fs";
3
+ import { mkdtempSync, mkdirSync, realpathSync, rmSync, utimesSync, writeFileSync } from "node:fs";
4
4
  import { join } from "node:path";
5
5
  import { tmpdir } from "node:os";
6
6
 
@@ -360,6 +360,174 @@ test("canonical worktree + project-root legacy dir produces legacy filename (#bu
360
360
  }
361
361
  });
362
362
 
363
+ // ── cursor[bot] 3523932401: bare milestone id must not match a team-suffix ────
364
+ //
365
+ // A bare milestone id (M001, no team-mode suffix) resolves its phase dir by
366
+ // scanning phases/NN-*. Before the guard (commit 8f45bf3),
367
+ // phaseDirMatchesMilestoneId returned true for ANY same-number dir, so a
368
+ // leftover team-mode projection (phases/01-<6char>-.../, from a prior
369
+ // team-suffixed run) matched the bare id too. When that stale dir carried a
370
+ // newer mtime, pickPreferredPhaseDir selected it over the real title dir and
371
+ // resolved the ROADMAP into a directory that has no ROADMAP — the file-not-found
372
+ // retry loop. The guard (slugLooksLikeTeamSuffixProjection) rejects
373
+ // team-suffix-looking slugs for bare ids in the primary pass; the suffixed
374
+ // projection stays available only as a fallback when the primary pass finds
375
+ // nothing (#1195, guarded by the second test below).
376
+
377
+ test("bare milestone id prefers the real title dir over a newer team-suffix projection (cursor 3523932401)", () => {
378
+ const root = realpathSync(mkdtempSync(join(tmpdir(), "gsd-bare-teamsuffix-")));
379
+ try {
380
+ const phasesDir = join(root, ".gsd", "phases");
381
+ // Intended flat-phase dir for the bare id: 01-<title-slug>/ with the ROADMAP.
382
+ const intendedDir = join(phasesDir, "01-clean-continuation");
383
+ mkdirSync(intendedDir, { recursive: true });
384
+ writeFileSync(join(intendedDir, "01-ROADMAP.md"), "# roadmap\n");
385
+
386
+ // Leftover team-mode projection: 01-<6char>-.../ with NO ROADMAP, made
387
+ // NEWER so an mtime tiebreak would wrongly prefer it without the guard.
388
+ const staleDir = join(phasesDir, "01-re4q3k-old-team-projection");
389
+ mkdirSync(staleDir, { recursive: true });
390
+ const newer = new Date(Date.now() + 60_000);
391
+ const older = new Date(Date.now() - 60_000);
392
+ utimesSync(staleDir, newer, newer);
393
+ utimesSync(intendedDir, older, older);
394
+
395
+ _clearGsdRootCache();
396
+ clearPathCache();
397
+
398
+ const resolved = resolveExpectedArtifactPath("plan-milestone", "M001", root);
399
+ assert.equal(
400
+ resolved,
401
+ join(intendedDir, "01-ROADMAP.md"),
402
+ "bare id must resolve to the real title dir, not the newer team-suffix projection",
403
+ );
404
+ assert.notEqual(
405
+ resolved,
406
+ join(staleDir, "01-ROADMAP.md"),
407
+ "must NOT resolve into the team-suffix projection dir that has no ROADMAP",
408
+ );
409
+ } finally {
410
+ _clearGsdRootCache();
411
+ clearPathCache();
412
+ rmSync(root, { recursive: true, force: true });
413
+ }
414
+ });
415
+
416
+ test("bare milestone id still falls back to a lone team-suffix projection (#1195)", () => {
417
+ const root = realpathSync(mkdtempSync(join(tmpdir(), "gsd-bare-teamsuffix-only-")));
418
+ try {
419
+ const phasesDir = join(root, ".gsd", "phases");
420
+ // The only on-disk dir for the bare id is a team-mode projection. With no
421
+ // non-suffixed candidate, the fallback pass must still resolve to it.
422
+ const onlyDir = join(phasesDir, "01-re4q3k-clean-continuation");
423
+ mkdirSync(onlyDir, { recursive: true });
424
+ writeFileSync(join(onlyDir, "01-ROADMAP.md"), "# roadmap\n");
425
+
426
+ _clearGsdRootCache();
427
+ clearPathCache();
428
+
429
+ const resolved = resolveExpectedArtifactPath("plan-milestone", "M001", root);
430
+ assert.equal(
431
+ resolved,
432
+ join(onlyDir, "01-ROADMAP.md"),
433
+ "with no non-suffixed dir, the team-suffix projection is the correct fallback",
434
+ );
435
+ } finally {
436
+ _clearGsdRootCache();
437
+ clearPathCache();
438
+ rmSync(root, { recursive: true, force: true });
439
+ }
440
+ });
441
+
442
+ // ── #1208: flat-phase execute-task summaries resolve at the phase root ────────
443
+ //
444
+ // In flat-phase layout task summaries are written beside the plan files
445
+ // (phases/<phase>/T##-SUMMARY.md), NOT under a tasks/ subdir. A tasks/ dir may
446
+ // still exist to hold auxiliary task-scoped gate artifacts (e.g. T01-VERIFY.json).
447
+ // Before the fix, the mere existence of that tasks/ dir redirected summary
448
+ // verification into tasks/T##-SUMMARY.md — which never exists — trapping auto-mode
449
+ // in a false verification retry that eventually paused after a successful task.
450
+
451
+ test("flat-phase execute-task summary resolves at phase root when no tasks/ dir exists (#1208)", () => {
452
+ const root = realpathSync(mkdtempSync(join(tmpdir(), "gsd-flat-task-summary-")));
453
+ try {
454
+ const gsd = join(root, ".gsd");
455
+ const phaseDir = join(gsd, "phases", "49-end-to-end-mandates");
456
+ mkdirSync(phaseDir, { recursive: true });
457
+ writeFileSync(join(phaseDir, "49-03-PLAN.md"), "# plan\n- [x] T02\n");
458
+
459
+ _clearGsdRootCache();
460
+ clearPathCache();
461
+
462
+ assert.equal(
463
+ resolveExpectedArtifactPath("execute-task", "M049/S03/T02", root),
464
+ join(phaseDir, "T02-SUMMARY.md"),
465
+ );
466
+ } finally {
467
+ _clearGsdRootCache();
468
+ clearPathCache();
469
+ rmSync(root, { recursive: true, force: true });
470
+ }
471
+ });
472
+
473
+ test("flat-phase execute-task summary resolves at phase root even when tasks/ dir holds gate artifacts (#1208)", () => {
474
+ const root = realpathSync(mkdtempSync(join(tmpdir(), "gsd-flat-task-tasksdir-")));
475
+ try {
476
+ const gsd = join(root, ".gsd");
477
+ const phaseDir = join(gsd, "phases", "49-end-to-end-mandates");
478
+ mkdirSync(phaseDir, { recursive: true });
479
+ writeFileSync(join(phaseDir, "49-03-PLAN.md"), "# plan\n- [x] T02\n");
480
+ // Auxiliary task-scoped gate artifact — creates the tasks/ dir but must not
481
+ // redirect the T02 summary into tasks/.
482
+ const tasksDir = join(phaseDir, "tasks");
483
+ mkdirSync(tasksDir, { recursive: true });
484
+ writeFileSync(join(tasksDir, "T01-VERIFY.json"), '{"ok":true}');
485
+
486
+ _clearGsdRootCache();
487
+ clearPathCache();
488
+
489
+ const resolved = resolveExpectedArtifactPath("execute-task", "M049/S03/T02", root);
490
+ assert.equal(
491
+ resolved,
492
+ join(phaseDir, "T02-SUMMARY.md"),
493
+ "flat-phase task summary must resolve at the phase root, not tasks/",
494
+ );
495
+ assert.notEqual(
496
+ resolved,
497
+ join(tasksDir, "T02-SUMMARY.md"),
498
+ "a tasks/ dir with gate artifacts must NOT redirect summary resolution into tasks/",
499
+ );
500
+ } finally {
501
+ _clearGsdRootCache();
502
+ clearPathCache();
503
+ rmSync(root, { recursive: true, force: true });
504
+ }
505
+ });
506
+
507
+ test("legacy execute-task summary still resolves under slices/<SID>/tasks/ (#1208 regression guard)", () => {
508
+ const root = realpathSync(mkdtempSync(join(tmpdir(), "gsd-legacy-task-summary-")));
509
+ try {
510
+ const milestoneDir = join(root, ".gsd", "milestones", "M001");
511
+ const sliceDir = join(milestoneDir, "slices", "S01");
512
+ const tasksDir = join(sliceDir, "tasks");
513
+ mkdirSync(tasksDir, { recursive: true });
514
+ writeFileSync(join(milestoneDir, "M001-CONTEXT.md"), "# context\n");
515
+ writeFileSync(join(sliceDir, "S01-PLAN.md"), "# plan\n");
516
+
517
+ _clearGsdRootCache();
518
+ clearPathCache();
519
+
520
+ assert.equal(
521
+ resolveExpectedArtifactPath("execute-task", "M001/S01/T02", root),
522
+ join(tasksDir, "T02-SUMMARY.md"),
523
+ );
524
+ } finally {
525
+ _clearGsdRootCache();
526
+ clearPathCache();
527
+ rmSync(root, { recursive: true, force: true });
528
+ }
529
+ });
530
+
363
531
  // ── resolveSliceResearchLocation / resolveExistingSliceResearchPath ───────────
364
532
  //
365
533
  // Added in the code-quality consolidation PR as the shared dual-path resolver
@@ -14,7 +14,8 @@ import { join } from "node:path";
14
14
  import { tmpdir } from "node:os";
15
15
  import { execFileSync } from "node:child_process";
16
16
 
17
- import { createAutoWorktree, teardownAutoWorktree } from "../auto-worktree.ts";
17
+ import { createAutoWorktree } from "../auto-worktree-creation.ts";
18
+ import { teardownAutoWorktree } from "../auto-worktree-teardown.ts";
18
19
 
19
20
  function git(args: string[], cwd: string): void {
20
21
  execFileSync("git", args, { cwd, stdio: ["ignore", "pipe", "pipe"] });
@@ -299,6 +299,63 @@ test("refreshRecoveryDbForArtifact treats missing execute-task DB rows as fatal
299
299
  });
300
300
  });
301
301
 
302
+ test("refreshRecoveryDbForArtifact promotes an open execute-task DB row when artifacts are verified on disk (#1204)", () => {
303
+ const dir = makeTmpProject();
304
+ insertTask({
305
+ milestoneId: "M001",
306
+ sliceId: "S01",
307
+ id: "T01",
308
+ title: "Stuck Task",
309
+ status: "pending",
310
+ });
311
+ const sliceDir = join(dir, ".gsd", "milestones", "M001", "slices", "S01");
312
+ mkdirSync(sliceDir, { recursive: true });
313
+ writeFileSync(join(sliceDir, "S01-PLAN.md"), [
314
+ "# S01 Plan",
315
+ "",
316
+ "- [ ] **T01: Implement feature** `est:1h`",
317
+ "",
318
+ ].join("\n"));
319
+
320
+ const result = refreshRecoveryDbForArtifact("execute-task", "M001/S01/T01", dir);
321
+
322
+ assert.deepEqual(result, { ok: true });
323
+ assert.equal(getTask("M001", "S01", "T01")?.status, "complete");
324
+ const planContent = readFileSync(join(sliceDir, "S01-PLAN.md"), "utf-8");
325
+ assert.ok(planContent.includes("[x] **T01:"), "plan checkbox should be re-rendered to [x]");
326
+ const events = readEvents(join(dir, ".gsd", "event-log.jsonl"));
327
+ assert.ok(
328
+ events.some((e) => e.cmd === "complete-task" && e.trigger_reason === "stuck-artifact-recovery"),
329
+ "a complete-task recovery event should be appended",
330
+ );
331
+ });
332
+
333
+ test("refreshRecoveryDbForArtifact still appends the recovery event when the plan checkbox sync throws after DB promotion (#1221)", () => {
334
+ const dir = makeTmpProject();
335
+ insertTask({
336
+ milestoneId: "M001",
337
+ sliceId: "S01",
338
+ id: "T01",
339
+ title: "Stuck Task",
340
+ status: "pending",
341
+ });
342
+ const sliceDir = join(dir, ".gsd", "milestones", "M001", "slices", "S01");
343
+ mkdirSync(sliceDir, { recursive: true });
344
+ // Make the expected plan path a directory so the best-effort checkbox
345
+ // rewrite (readFileSync) throws EISDIR after the DB row is already promoted.
346
+ mkdirSync(join(sliceDir, "S01-PLAN.md"), { recursive: true });
347
+
348
+ const result = refreshRecoveryDbForArtifact("execute-task", "M001/S01/T01", dir);
349
+
350
+ assert.deepEqual(result, { ok: true });
351
+ assert.equal(getTask("M001", "S01", "T01")?.status, "complete");
352
+ const events = readEvents(join(dir, ".gsd", "event-log.jsonl"));
353
+ assert.ok(
354
+ events.some((e) => e.cmd === "complete-task" && e.trigger_reason === "stuck-artifact-recovery"),
355
+ "recovery event must still be appended even when the plan checkbox sync fails",
356
+ );
357
+ });
358
+
302
359
  test("refreshRecoveryDbForArtifact closes complete-milestone DB row when artifacts exist but DB is stale (#5568)", async () => {
303
360
  const base = mkdtempSync(join(tmpdir(), "auto-recovery-complete-ms-"));
304
361
  mkdirSync(join(base, ".gsd"), { recursive: true });
@@ -6,7 +6,7 @@ import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
6
6
  import { join } from "node:path";
7
7
  import { tmpdir } from "node:os";
8
8
  import { randomUUID } from "node:crypto";
9
- import { cleanStaleRuntimeUnits } from "../auto-worktree.ts";
9
+ import { cleanStaleRuntimeUnits } from "../auto-worktree-runtime-cleanup.ts";
10
10
 
11
11
  function makeBase(): string {
12
12
  return join(tmpdir(), `gsd-clean-runtime-${randomUUID()}`);
@@ -28,7 +28,8 @@ import { join } from "node:path";
28
28
  import { tmpdir } from "node:os";
29
29
 
30
30
  import { buildFlatRateContext } from "../auto-model-selection.ts";
31
- import { _isSamePath, _resetAutoWorktreeOriginalBaseForTests } from "../auto-worktree.ts";
31
+ import { _resetAutoWorktreeOriginalBaseForTests } from "../auto-worktree-session-registry.ts";
32
+ import { _isSamePath } from "../auto-worktree-path-utils.ts";
32
33
  import {
33
34
  checkAutoStartAfterDiscuss,
34
35
  clearPendingAutoStart,
@@ -11,7 +11,7 @@ import assert from "node:assert/strict";
11
11
  import {
12
12
  isSafeToAutoResolve,
13
13
  SAFE_AUTO_RESOLVE_PATTERNS,
14
- } from "../auto-worktree.ts";
14
+ } from "../auto-worktree-conflict-auto-resolve.ts";
15
15
 
16
16
  describe("isSafeToAutoResolve", () => {
17
17
  // ─── .gsd/ state files ───────────────────────────────────────────────────
@@ -0,0 +1,177 @@
1
+ // gsd-pi — Regression tests for already-merged milestone cleanup safety.
2
+
3
+ import { execFileSync } from "node:child_process";
4
+ import assert from "node:assert/strict";
5
+ import { mkdirSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from "node:fs";
6
+ import { tmpdir } from "node:os";
7
+ import { join } from "node:path";
8
+ import { afterEach, beforeEach, describe, test } from "node:test";
9
+
10
+ import {
11
+ finalizeAlreadyMergedMilestoneIfReachable,
12
+ type AlreadyMergedMilestoneRequest,
13
+ } from "../auto-worktree-merge-already-merged.js";
14
+ import {
15
+ _resetMilestoneCleanupDepsForTests,
16
+ _setMilestoneCleanupDepsForTests,
17
+ } from "../auto-worktree-merge-cleanup.js";
18
+ import { GSDError } from "../errors.js";
19
+
20
+ const cleanupPaths: string[] = [];
21
+ const initialCwd = process.cwd();
22
+
23
+ beforeEach(() => {
24
+ _resetMilestoneCleanupDepsForTests();
25
+ });
26
+
27
+ afterEach(() => {
28
+ _resetMilestoneCleanupDepsForTests();
29
+ process.chdir(initialCwd);
30
+ for (const path of cleanupPaths.splice(0)) {
31
+ rmSync(path, { recursive: true, force: true });
32
+ }
33
+ });
34
+
35
+ function git(cwd: string, args: string[]): string {
36
+ return execFileSync("git", args, {
37
+ cwd,
38
+ stdio: ["ignore", "pipe", "pipe"],
39
+ encoding: "utf-8",
40
+ }).trim();
41
+ }
42
+
43
+ function commitFile(cwd: string, file: string, content: string, message: string): string {
44
+ writeFileSync(join(cwd, file), content);
45
+ git(cwd, ["add", file]);
46
+ git(cwd, ["commit", "-m", message]);
47
+ return git(cwd, ["rev-parse", "HEAD"]);
48
+ }
49
+
50
+ function createRepo(): string {
51
+ const repo = mkdtempSync(join(tmpdir(), "gsd-already-merged-"));
52
+ cleanupPaths.push(repo);
53
+ git(repo, ["init", "-b", "main"]);
54
+ git(repo, ["config", "user.email", "test@example.invalid"]);
55
+ git(repo, ["config", "user.name", "Test"]);
56
+ commitFile(repo, "README.md", "base\n", "initial");
57
+ return repo;
58
+ }
59
+
60
+ function createRegularMergedMilestone(repo: string, milestoneId = "M001"): { milestoneBranch: string } {
61
+ const milestoneBranch = `milestone/${milestoneId}`;
62
+ git(repo, ["checkout", "-b", milestoneBranch]);
63
+ commitFile(repo, "feature.txt", "milestone work\n", `feat: implement ${milestoneId}`);
64
+ git(repo, ["checkout", "main"]);
65
+ git(repo, ["merge", "--no-ff", milestoneBranch, "-m", `merge ${milestoneId}`]);
66
+ return { milestoneBranch };
67
+ }
68
+
69
+ function request(
70
+ repo: string,
71
+ milestoneBranch: string,
72
+ previousCwd: string,
73
+ ): AlreadyMergedMilestoneRequest {
74
+ return {
75
+ commitMessage: "merge milestone",
76
+ mainBranch: "main",
77
+ milestoneBranch,
78
+ milestoneId: "M001",
79
+ previousCwd,
80
+ projectRoot: repo,
81
+ };
82
+ }
83
+
84
+ describe("finalizeAlreadyMergedMilestoneIfReachable", () => {
85
+ test("returns null when the milestone branch is not reachable from integration branch", () => {
86
+ const repo = createRepo();
87
+ git(repo, ["checkout", "-b", "milestone/M001"]);
88
+ commitFile(repo, "feature.txt", "milestone work\n", "feat: implement M001");
89
+ git(repo, ["checkout", "main"]);
90
+
91
+ let cleanupCalled = false;
92
+ _setMilestoneCleanupDepsForTests({
93
+ removeWorktree: () => {
94
+ cleanupCalled = true;
95
+ return true;
96
+ },
97
+ });
98
+
99
+ const result = finalizeAlreadyMergedMilestoneIfReachable(
100
+ request(repo, "milestone/M001", repo),
101
+ );
102
+
103
+ assert.equal(result, null);
104
+ assert.equal(cleanupCalled, false, "unmerged branch must not be cleaned up");
105
+ });
106
+
107
+ test("cleans up a regular-merged milestone after main advances", () => {
108
+ const repo = createRepo();
109
+ const { milestoneBranch } = createRegularMergedMilestone(repo);
110
+ commitFile(repo, "hotfix.txt", "later main work\n", "fix: advance main");
111
+ const previousCwd = join(repo, ".gsd-worktrees", "M001");
112
+ const calls: string[] = [];
113
+
114
+ _setMilestoneCleanupDepsForTests({
115
+ removeWorktree: (_projectRoot, milestoneId, options) => {
116
+ assert.ok(options, "cleanup must pass branch options");
117
+ calls.push(
118
+ `remove:${milestoneId}:${options.branch}:${String(options.deleteBranch)}`,
119
+ );
120
+ return true;
121
+ },
122
+ nativeBranchDelete: (_projectRoot, branch) => {
123
+ calls.push(`delete:${branch}`);
124
+ },
125
+ setActiveWorkspace: (workspace) => {
126
+ calls.push(`workspace:${String(workspace)}`);
127
+ },
128
+ nudgeGitBranchCache: (path) => {
129
+ calls.push(`nudge:${path}`);
130
+ },
131
+ chdir: (path) => {
132
+ calls.push(`chdir:${path}`);
133
+ },
134
+ });
135
+
136
+ const result = finalizeAlreadyMergedMilestoneIfReachable(
137
+ request(repo, milestoneBranch, previousCwd),
138
+ );
139
+
140
+ assert.deepEqual(result, {
141
+ commitMessage: "merge milestone",
142
+ pushed: false,
143
+ prCreated: false,
144
+ codeFilesChanged: true,
145
+ });
146
+ assert.deepEqual(calls, [
147
+ "remove:M001:milestone/M001:false",
148
+ "delete:milestone/M001",
149
+ "workspace:null",
150
+ `nudge:${previousCwd}`,
151
+ `chdir:${repo}`,
152
+ ]);
153
+ });
154
+
155
+ test("throws and restores cwd when regular-merged code was later removed from main", () => {
156
+ const repo = createRepo();
157
+ const { milestoneBranch } = createRegularMergedMilestone(repo);
158
+ commitFile(repo, "feature.txt", "", "chore: remove milestone feature");
159
+ const previousCwd = join(repo, "caller-cwd");
160
+ mkdirSync(previousCwd);
161
+ process.chdir(repo);
162
+
163
+ assert.throws(
164
+ () => finalizeAlreadyMergedMilestoneIfReachable(
165
+ request(repo, milestoneBranch, previousCwd),
166
+ ),
167
+ (err: unknown) => err instanceof GSDError
168
+ && /milestone-touched code file/.test(err.message)
169
+ && /prevent data loss/.test(err.message),
170
+ );
171
+ assert.equal(
172
+ realpathSync(process.cwd()),
173
+ realpathSync(previousCwd),
174
+ "throws before cleanup but restores to the caller's previous cwd",
175
+ );
176
+ });
177
+ });
@@ -0,0 +1,118 @@
1
+ // gsd-pi — Regression tests for milestone branch/head reconciliation before merge.
2
+
3
+ import { execFileSync } from "node:child_process";
4
+ import assert from "node:assert/strict";
5
+ import { mkdtempSync, realpathSync, rmSync, writeFileSync } from "node:fs";
6
+ import { tmpdir } from "node:os";
7
+ import { join } from "node:path";
8
+ import { afterEach, describe, test } from "node:test";
9
+
10
+ import { GSDError } from "../errors.js";
11
+ import { reconcileMilestoneBranchHead } from "../auto-worktree-merge-branch-head.js";
12
+
13
+ const cleanupPaths: string[] = [];
14
+ const initialCwd = process.cwd();
15
+
16
+ afterEach(() => {
17
+ process.chdir(initialCwd);
18
+ for (const path of cleanupPaths.splice(0)) {
19
+ rmSync(path, { recursive: true, force: true });
20
+ }
21
+ });
22
+
23
+ function git(cwd: string, args: string[]): string {
24
+ return execFileSync("git", args, {
25
+ cwd,
26
+ stdio: ["ignore", "pipe", "pipe"],
27
+ encoding: "utf-8",
28
+ }).trim();
29
+ }
30
+
31
+ function commitFile(cwd: string, file: string, content: string, message: string): string {
32
+ writeFileSync(join(cwd, file), content);
33
+ git(cwd, ["add", file]);
34
+ git(cwd, ["commit", "-m", message]);
35
+ return git(cwd, ["rev-parse", "HEAD"]);
36
+ }
37
+
38
+ function createRepoWithMilestoneWorktree(): { repo: string; worktree: string; branch: string } {
39
+ const repo = mkdtempSync(join(tmpdir(), "gsd-branch-head-"));
40
+ cleanupPaths.push(repo);
41
+ git(repo, ["init", "-b", "main"]);
42
+ git(repo, ["config", "user.email", "test@example.invalid"]);
43
+ git(repo, ["config", "user.name", "Test"]);
44
+ commitFile(repo, "README.md", "base\n", "initial");
45
+
46
+ const branch = "milestone/M001";
47
+ git(repo, ["branch", branch]);
48
+ const worktree = join(repo, ".gsd-worktrees", "M001");
49
+ git(repo, ["worktree", "add", worktree, branch]);
50
+ git(worktree, ["config", "user.email", "test@example.invalid"]);
51
+ git(worktree, ["config", "user.name", "Test"]);
52
+ return { repo, worktree, branch };
53
+ }
54
+
55
+ describe("reconcileMilestoneBranchHead", () => {
56
+ test("fast-forwards stale milestone branch ref to detached worktree HEAD", () => {
57
+ const { repo, worktree, branch } = createRepoWithMilestoneWorktree();
58
+ git(worktree, ["checkout", "--detach"]);
59
+ const detachedHead = commitFile(worktree, "feature.txt", "worktree-only\n", "worktree detached commit");
60
+
61
+ const result = reconcileMilestoneBranchHead({
62
+ projectRoot: repo,
63
+ worktreeCwd: worktree,
64
+ milestoneBranch: branch,
65
+ previousCwd: worktree,
66
+ });
67
+
68
+ assert.deepEqual(result, {
69
+ checked: true,
70
+ updated: true,
71
+ reason: "fast-forwarded",
72
+ });
73
+ assert.equal(git(repo, ["rev-parse", branch]), detachedHead);
74
+ });
75
+
76
+ test("throws and restores cwd when worktree HEAD diverges from milestone branch", () => {
77
+ const { repo, worktree, branch } = createRepoWithMilestoneWorktree();
78
+ const baseHead = git(repo, ["rev-parse", branch]);
79
+ git(worktree, ["checkout", "--detach", baseHead]);
80
+ const detachedHead = commitFile(worktree, "worktree.txt", "detached\n", "detached side");
81
+
82
+ git(repo, ["checkout", branch]);
83
+ const branchHead = commitFile(repo, "branch.txt", "branch\n", "branch side");
84
+ git(repo, ["checkout", "main"]);
85
+ process.chdir(repo);
86
+
87
+ assert.throws(
88
+ () => reconcileMilestoneBranchHead({
89
+ projectRoot: repo,
90
+ worktreeCwd: worktree,
91
+ milestoneBranch: branch,
92
+ previousCwd: worktree,
93
+ }),
94
+ (err: unknown) => err instanceof GSDError && /diverged/.test(err.message),
95
+ );
96
+
97
+ assert.equal(git(repo, ["rev-parse", branch]), branchHead, "diverged branch ref must not be moved");
98
+ assert.equal(git(worktree, ["rev-parse", "HEAD"]), detachedHead, "detached worktree commit remains for manual recovery");
99
+ assert.equal(realpathSync(process.cwd()), realpathSync(worktree), "divergence restores cwd to previous worktree");
100
+ });
101
+
102
+ test("skips reconciliation when cwd is already the project root", () => {
103
+ const { repo, branch } = createRepoWithMilestoneWorktree();
104
+
105
+ const result = reconcileMilestoneBranchHead({
106
+ projectRoot: repo,
107
+ worktreeCwd: repo,
108
+ milestoneBranch: branch,
109
+ previousCwd: repo,
110
+ });
111
+
112
+ assert.deepEqual(result, {
113
+ checked: false,
114
+ updated: false,
115
+ reason: "not-worktree",
116
+ });
117
+ });
118
+ });