@opengsd/gsd-pi 1.4.0-dev.c18009cd → 1.5.0-dev.553dc518

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 (274) hide show
  1. package/README.md +10 -10
  2. package/dist/resources/.managed-resources-content-hash +1 -1
  3. package/dist/resources/extensions/gsd/auto/closeout.js +97 -5
  4. package/dist/resources/extensions/gsd/auto/orchestrator.js +4 -3
  5. package/dist/resources/extensions/gsd/auto/pre-dispatch.js +6 -3
  6. package/dist/resources/extensions/gsd/auto/unit-phase.js +5 -9
  7. package/dist/resources/extensions/gsd/auto-direct-dispatch.js +4 -5
  8. package/dist/resources/extensions/gsd/auto-start.js +3 -2
  9. package/dist/resources/extensions/gsd/auto-worktree-branch-lifecycle.js +189 -0
  10. package/dist/resources/extensions/gsd/auto-worktree-cleanup.js +133 -0
  11. package/dist/resources/extensions/gsd/auto-worktree-conflict-auto-resolve.js +35 -0
  12. package/dist/resources/extensions/gsd/auto-worktree-creation.js +130 -0
  13. package/dist/resources/extensions/gsd/auto-worktree-entry.js +118 -0
  14. package/dist/resources/extensions/gsd/auto-worktree-merge-already-merged.js +98 -0
  15. package/dist/resources/extensions/gsd/auto-worktree-merge-branch-head.js +52 -0
  16. package/dist/resources/extensions/gsd/auto-worktree-merge-cleanup.js +87 -0
  17. package/dist/resources/extensions/gsd/auto-worktree-merge-code-changes.js +46 -0
  18. package/dist/resources/extensions/gsd/auto-worktree-merge-db-ready.js +68 -0
  19. package/dist/resources/extensions/gsd/auto-worktree-merge-integration-branch.js +51 -0
  20. package/dist/resources/extensions/gsd/auto-worktree-merge-message.js +106 -0
  21. package/dist/resources/extensions/gsd/auto-worktree-merge-pre-teardown.js +64 -0
  22. package/dist/resources/extensions/gsd/auto-worktree-merge-stash.js +168 -0
  23. package/dist/resources/extensions/gsd/auto-worktree-merge.js +302 -0
  24. package/dist/resources/extensions/gsd/auto-worktree-milestone-shelter.js +119 -0
  25. package/dist/resources/extensions/gsd/auto-worktree-path-resolution.js +5 -0
  26. package/dist/resources/extensions/gsd/auto-worktree-path-utils.js +5 -0
  27. package/dist/resources/extensions/gsd/auto-worktree-resource-version.js +41 -0
  28. package/dist/resources/extensions/gsd/auto-worktree-runtime-cleanup.js +99 -0
  29. package/dist/resources/extensions/gsd/auto-worktree-session-registry.js +57 -0
  30. package/dist/resources/extensions/gsd/auto-worktree-sync.js +249 -0
  31. package/dist/resources/extensions/gsd/auto-worktree-teardown.js +120 -0
  32. package/dist/resources/extensions/gsd/auto-worktree.js +20 -2073
  33. package/dist/resources/extensions/gsd/auto.js +9 -3
  34. package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +1 -1
  35. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +19 -1
  36. package/dist/resources/extensions/gsd/bootstrap/system-context.js +1 -1
  37. package/dist/resources/extensions/gsd/bootstrap/tool-call-loop-guard.js +118 -21
  38. package/dist/resources/extensions/gsd/commands-handlers.js +1 -1
  39. package/dist/resources/extensions/gsd/commands-workflow-templates.js +3 -3
  40. package/dist/resources/extensions/gsd/db/queries.js +9 -9
  41. package/dist/resources/extensions/gsd/doctor-engine-checks.js +93 -11
  42. package/dist/resources/extensions/gsd/flat-phase-migration.js +14 -3
  43. package/dist/resources/extensions/gsd/forensics.js +1 -1
  44. package/dist/resources/extensions/gsd/git-conflict-resolve.js +2 -2
  45. package/dist/resources/extensions/gsd/git-service.js +45 -0
  46. package/dist/resources/extensions/gsd/gsd-db.js +11 -0
  47. package/dist/resources/extensions/gsd/guided-flow.js +6 -10
  48. package/dist/resources/extensions/gsd/markdown-renderer.js +15 -3
  49. package/dist/resources/extensions/gsd/milestone-merge-transaction.js +11 -0
  50. package/dist/resources/extensions/gsd/milestone-settlement.js +1 -1
  51. package/dist/resources/extensions/gsd/parallel-merge.js +1 -1
  52. package/dist/resources/extensions/gsd/parallel-orchestrator.js +3 -1
  53. package/dist/resources/extensions/gsd/preferences-types.js +1 -0
  54. package/dist/resources/extensions/gsd/preferences-validation.js +82 -0
  55. package/dist/resources/extensions/gsd/prompts/system.md +6 -6
  56. package/dist/resources/extensions/gsd/quick.js +12 -3
  57. package/dist/resources/extensions/gsd/slice-parallel-orchestrator.js +2 -1
  58. package/dist/resources/extensions/gsd/state/derive/from-db.js +88 -153
  59. package/dist/resources/extensions/gsd/tool-contract.js +14 -0
  60. package/dist/resources/extensions/gsd/unit-registry.js +12 -2
  61. package/dist/resources/extensions/gsd/unmerged-milestone-guard.js +1 -1
  62. package/dist/resources/extensions/gsd/validation-block-guard.js +2 -1
  63. package/dist/resources/extensions/gsd/workspace-git-preflight.js +2 -2
  64. package/dist/resources/extensions/gsd/worktree-command.js +1 -1
  65. package/dist/resources/extensions/gsd/worktree-lifecycle.js +14 -9
  66. package/dist/resources/extensions/gsd/worktree-reentry.js +2 -1
  67. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  68. package/dist/web/standalone/.next/BUILD_ID +1 -1
  69. package/dist/web/standalone/.next/app-path-routes-manifest.json +8 -8
  70. package/dist/web/standalone/.next/build-manifest.json +2 -2
  71. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  72. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  73. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  74. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  75. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  77. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  78. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  79. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  80. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  81. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  82. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  83. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  84. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  85. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  86. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  87. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  88. package/dist/web/standalone/.next/server/app/index.html +1 -1
  89. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  90. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  91. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  92. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  93. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  94. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  95. package/dist/web/standalone/.next/server/app-paths-manifest.json +8 -8
  96. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  97. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  98. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  99. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  100. package/dist/web/standalone/node_modules/@gsd/native/package.json +1 -1
  101. package/dist/web/standalone/node_modules/node-pty/build/Makefile +1 -1
  102. package/integrations/hermes/open_gsd_hermes/gsd_client.py +24 -35
  103. package/integrations/hermes/tests/test_gsd_client_timeout.py +4 -4
  104. package/package.json +6 -6
  105. package/packages/cloud-mcp-gateway/package.json +2 -2
  106. package/packages/contracts/package.json +1 -1
  107. package/packages/daemon/package.json +4 -4
  108. package/packages/gsd-agent-core/dist/export-html/template.js +53 -4
  109. package/packages/gsd-agent-core/package.json +5 -5
  110. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  111. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js +3 -2
  112. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  113. package/packages/gsd-agent-modes/package.json +7 -7
  114. package/packages/mcp-server/package.json +4 -4
  115. package/packages/native/package.json +1 -1
  116. package/packages/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
  117. package/packages/pi-agent-core/dist/agent-loop.js +14 -9
  118. package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
  119. package/packages/pi-agent-core/package.json +1 -1
  120. package/packages/pi-ai/dist/index.d.ts +1 -0
  121. package/packages/pi-ai/dist/index.d.ts.map +1 -1
  122. package/packages/pi-ai/dist/index.js +1 -0
  123. package/packages/pi-ai/dist/index.js.map +1 -1
  124. package/packages/pi-ai/dist/providers/transform-messages.d.ts.map +1 -1
  125. package/packages/pi-ai/dist/providers/transform-messages.js +7 -3
  126. package/packages/pi-ai/dist/providers/transform-messages.js.map +1 -1
  127. package/packages/pi-ai/dist/tool-result-content.d.ts +3 -0
  128. package/packages/pi-ai/dist/tool-result-content.d.ts.map +1 -0
  129. package/packages/pi-ai/dist/tool-result-content.js +62 -0
  130. package/packages/pi-ai/dist/tool-result-content.js.map +1 -0
  131. package/packages/pi-ai/dist/types.d.ts +8 -20
  132. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  133. package/packages/pi-ai/dist/types.js.map +1 -1
  134. package/packages/pi-ai/package.json +1 -1
  135. package/packages/pi-coding-agent/package.json +7 -7
  136. package/packages/pi-tui/package.json +2 -2
  137. package/packages/rpc-client/package.json +2 -2
  138. package/pkg/dist/core/export-html/template.js +53 -4
  139. package/pkg/package.json +1 -1
  140. package/src/resources/extensions/gsd/auto/closeout.ts +114 -7
  141. package/src/resources/extensions/gsd/auto/orchestrator.ts +4 -3
  142. package/src/resources/extensions/gsd/auto/pre-dispatch.ts +6 -3
  143. package/src/resources/extensions/gsd/auto/unit-phase.ts +5 -9
  144. package/src/resources/extensions/gsd/auto-direct-dispatch.ts +4 -5
  145. package/src/resources/extensions/gsd/auto-start.ts +4 -2
  146. package/src/resources/extensions/gsd/auto-worktree-branch-lifecycle.ts +237 -0
  147. package/src/resources/extensions/gsd/auto-worktree-cleanup.ts +166 -0
  148. package/src/resources/extensions/gsd/auto-worktree-conflict-auto-resolve.ts +37 -0
  149. package/src/resources/extensions/gsd/auto-worktree-creation.ts +171 -0
  150. package/src/resources/extensions/gsd/auto-worktree-entry.ts +154 -0
  151. package/src/resources/extensions/gsd/auto-worktree-merge-already-merged.ts +161 -0
  152. package/src/resources/extensions/gsd/auto-worktree-merge-branch-head.ts +83 -0
  153. package/src/resources/extensions/gsd/auto-worktree-merge-cleanup.ts +132 -0
  154. package/src/resources/extensions/gsd/auto-worktree-merge-code-changes.ts +71 -0
  155. package/src/resources/extensions/gsd/auto-worktree-merge-db-ready.ts +112 -0
  156. package/src/resources/extensions/gsd/auto-worktree-merge-integration-branch.ts +99 -0
  157. package/src/resources/extensions/gsd/auto-worktree-merge-message.ts +166 -0
  158. package/src/resources/extensions/gsd/auto-worktree-merge-pre-teardown.ts +93 -0
  159. package/src/resources/extensions/gsd/auto-worktree-merge-stash.ts +213 -0
  160. package/src/resources/extensions/gsd/auto-worktree-merge.ts +391 -0
  161. package/src/resources/extensions/gsd/auto-worktree-milestone-shelter.ts +159 -0
  162. package/src/resources/extensions/gsd/auto-worktree-path-resolution.ts +6 -0
  163. package/src/resources/extensions/gsd/auto-worktree-path-utils.ts +10 -0
  164. package/src/resources/extensions/gsd/auto-worktree-resource-version.ts +44 -0
  165. package/src/resources/extensions/gsd/auto-worktree-runtime-cleanup.ts +119 -0
  166. package/src/resources/extensions/gsd/auto-worktree-session-registry.ts +71 -0
  167. package/src/resources/extensions/gsd/auto-worktree-sync.ts +370 -0
  168. package/src/resources/extensions/gsd/auto-worktree-teardown.ts +161 -0
  169. package/src/resources/extensions/gsd/auto-worktree.ts +69 -2429
  170. package/src/resources/extensions/gsd/auto.ts +11 -17
  171. package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +1 -1
  172. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +19 -1
  173. package/src/resources/extensions/gsd/bootstrap/system-context.ts +1 -1
  174. package/src/resources/extensions/gsd/bootstrap/tool-call-loop-guard.ts +152 -21
  175. package/src/resources/extensions/gsd/commands-handlers.ts +1 -1
  176. package/src/resources/extensions/gsd/commands-workflow-templates.ts +3 -3
  177. package/src/resources/extensions/gsd/db/queries.ts +9 -9
  178. package/src/resources/extensions/gsd/doctor-engine-checks.ts +109 -10
  179. package/src/resources/extensions/gsd/flat-phase-migration.ts +14 -3
  180. package/src/resources/extensions/gsd/forensics.ts +1 -1
  181. package/src/resources/extensions/gsd/git-conflict-resolve.ts +2 -2
  182. package/src/resources/extensions/gsd/git-service.ts +53 -0
  183. package/src/resources/extensions/gsd/gsd-db.ts +13 -0
  184. package/src/resources/extensions/gsd/guided-flow.ts +7 -11
  185. package/src/resources/extensions/gsd/markdown-renderer.ts +15 -3
  186. package/src/resources/extensions/gsd/milestone-merge-transaction.ts +15 -0
  187. package/src/resources/extensions/gsd/milestone-settlement.ts +1 -1
  188. package/src/resources/extensions/gsd/parallel-merge.ts +1 -1
  189. package/src/resources/extensions/gsd/parallel-orchestrator.ts +3 -1
  190. package/src/resources/extensions/gsd/preferences-types.ts +30 -0
  191. package/src/resources/extensions/gsd/preferences-validation.ts +69 -0
  192. package/src/resources/extensions/gsd/prompts/system.md +6 -6
  193. package/src/resources/extensions/gsd/quick.ts +11 -3
  194. package/src/resources/extensions/gsd/slice-parallel-orchestrator.ts +3 -1
  195. package/src/resources/extensions/gsd/state/derive/from-db.ts +201 -157
  196. package/src/resources/extensions/gsd/tests/auto-loop-no-copy-artifacts.test.ts +2 -1
  197. package/src/resources/extensions/gsd/tests/auto-start-clean-runtime-db-gated.test.ts +1 -1
  198. package/src/resources/extensions/gsd/tests/auto-warning-noise-regression.test.ts +2 -1
  199. package/src/resources/extensions/gsd/tests/auto-worktree-auto-resolve.test.ts +1 -1
  200. package/src/resources/extensions/gsd/tests/auto-worktree-merge-already-merged.test.ts +177 -0
  201. package/src/resources/extensions/gsd/tests/auto-worktree-merge-branch-head.test.ts +118 -0
  202. package/src/resources/extensions/gsd/tests/auto-worktree-merge-cleanup.test.ts +115 -0
  203. package/src/resources/extensions/gsd/tests/auto-worktree-merge-code-changes.test.ts +76 -0
  204. package/src/resources/extensions/gsd/tests/auto-worktree-merge-db-ready.test.ts +140 -0
  205. package/src/resources/extensions/gsd/tests/auto-worktree-merge-integration-branch.test.ts +123 -0
  206. package/src/resources/extensions/gsd/tests/auto-worktree-merge-message.test.ts +114 -0
  207. package/src/resources/extensions/gsd/tests/auto-worktree-merge-pre-teardown.test.ts +84 -0
  208. package/src/resources/extensions/gsd/tests/auto-worktree-merge-stash.test.ts +70 -0
  209. package/src/resources/extensions/gsd/tests/auto-worktree-milestone-shelter.test.ts +52 -0
  210. package/src/resources/extensions/gsd/tests/auto-worktree-registry.test.ts +6 -6
  211. package/src/resources/extensions/gsd/tests/auto-worktree-resource-version.test.ts +59 -0
  212. package/src/resources/extensions/gsd/tests/auto-worktree-untracked-content.test.ts +3 -1
  213. package/src/resources/extensions/gsd/tests/completed-units-metrics-sync.test.ts +1 -1
  214. package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +4 -0
  215. package/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts +174 -0
  216. package/src/resources/extensions/gsd/tests/fast-forward-reused-milestone-branch.test.ts +1 -1
  217. package/src/resources/extensions/gsd/tests/flat-phase-migration.test.ts +44 -1
  218. package/src/resources/extensions/gsd/tests/flat-phase-renderer.test.ts +25 -1
  219. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +40 -0
  220. package/src/resources/extensions/gsd/tests/integration/all-milestones-complete-merge.test.ts +4 -6
  221. package/src/resources/extensions/gsd/tests/integration/auto-stash-merge.test.ts +2 -1
  222. package/src/resources/extensions/gsd/tests/integration/auto-worktree-milestone-merge.test.ts +3 -3
  223. package/src/resources/extensions/gsd/tests/integration/auto-worktree.test.ts +7 -8
  224. package/src/resources/extensions/gsd/tests/integration/feature-branch-lifecycle-integration.test.ts +2 -5
  225. package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +76 -0
  226. package/src/resources/extensions/gsd/tests/integration/merge-cwd-restore.test.ts +1 -1
  227. package/src/resources/extensions/gsd/tests/integration/merge-preserve-worktree-on-preteardown-error.test.ts +1 -1
  228. package/src/resources/extensions/gsd/tests/integration/merge-strategy-regular.test.ts +2 -1
  229. package/src/resources/extensions/gsd/tests/integration/milestone-transition-worktree.test.ts +5 -7
  230. package/src/resources/extensions/gsd/tests/integration/workspace-collapse-integration.test.ts +2 -4
  231. package/src/resources/extensions/gsd/tests/merge-closeout-consistency-gate.test.ts +1 -1
  232. package/src/resources/extensions/gsd/tests/merge-db-cycle.test.ts +1 -1
  233. package/src/resources/extensions/gsd/tests/merge-self-branch-guard.test.ts +1 -1
  234. package/src/resources/extensions/gsd/tests/milestone-merge-stash-restore.test.ts +439 -0
  235. package/src/resources/extensions/gsd/tests/milestone-merge-transaction.test.ts +24 -0
  236. package/src/resources/extensions/gsd/tests/none-mode-gates.test.ts +1 -1
  237. package/src/resources/extensions/gsd/tests/originalbase-path-comparison.test.ts +3 -3
  238. package/src/resources/extensions/gsd/tests/parallel-worker-lock-contention.test.ts +1 -1
  239. package/src/resources/extensions/gsd/tests/preferences-worktree-sync.test.ts +1 -4
  240. package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +5 -0
  241. package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +20 -0
  242. package/src/resources/extensions/gsd/tests/stale-worktree-cwd.test.ts +3 -5
  243. package/src/resources/extensions/gsd/tests/stash-pop-gsd-conflict.test.ts +2 -1
  244. package/src/resources/extensions/gsd/tests/stash-queued-context-files.test.ts +2 -1
  245. package/src/resources/extensions/gsd/tests/steer-worktree-path.test.ts +1 -1
  246. package/src/resources/extensions/gsd/tests/sync-worktree-skip-current.test.ts +1 -1
  247. package/src/resources/extensions/gsd/tests/system-flat-phase-layout.test.ts +33 -0
  248. package/src/resources/extensions/gsd/tests/teardown-chdir-failure-clears-registry.test.ts +4 -4
  249. package/src/resources/extensions/gsd/tests/teardown-cleanup-parity.test.ts +2 -1
  250. package/src/resources/extensions/gsd/tests/teardown-failure-clears-registry.test.ts +4 -4
  251. package/src/resources/extensions/gsd/tests/tool-call-loop-guard.test.ts +103 -0
  252. package/src/resources/extensions/gsd/tests/uat-stuck-loop-orphaned-worktree.test.ts +1 -1
  253. package/src/resources/extensions/gsd/tests/unit-registry.test.ts +26 -1
  254. package/src/resources/extensions/gsd/tests/worktree-db-integration.test.ts +1 -1
  255. package/src/resources/extensions/gsd/tests/worktree-db-respawn-truncation.test.ts +1 -1
  256. package/src/resources/extensions/gsd/tests/worktree-db-same-file.test.ts +1 -3
  257. package/src/resources/extensions/gsd/tests/worktree-expected-warnings.test.ts +1 -1
  258. package/src/resources/extensions/gsd/tests/worktree-git-pathspec.test.ts +1 -1
  259. package/src/resources/extensions/gsd/tests/worktree-main-branch.test.ts +1 -1
  260. package/src/resources/extensions/gsd/tests/worktree-preferences-sync.test.ts +1 -1
  261. package/src/resources/extensions/gsd/tests/worktree-reentry.test.ts +2 -1
  262. package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +5 -2
  263. package/src/resources/extensions/gsd/tests/worktree-sync-overwrite-loop.test.ts +1 -1
  264. package/src/resources/extensions/gsd/tests/worktree-sync-tasks.test.ts +1 -1
  265. package/src/resources/extensions/gsd/tool-contract.ts +29 -0
  266. package/src/resources/extensions/gsd/unit-registry.ts +16 -2
  267. package/src/resources/extensions/gsd/unmerged-milestone-guard.ts +1 -1
  268. package/src/resources/extensions/gsd/validation-block-guard.ts +2 -1
  269. package/src/resources/extensions/gsd/workspace-git-preflight.ts +2 -2
  270. package/src/resources/extensions/gsd/worktree-command.ts +1 -1
  271. package/src/resources/extensions/gsd/worktree-lifecycle.ts +19 -15
  272. package/src/resources/extensions/gsd/worktree-reentry.ts +2 -1
  273. /package/dist/web/standalone/.next/static/{L2UMqXELiDH3oz-7yguXp → ZpFDazP-38PXBPiI_LdZf}/_buildManifest.js +0 -0
  274. /package/dist/web/standalone/.next/static/{L2UMqXELiDH3oz-7yguXp → ZpFDazP-38PXBPiI_LdZf}/_ssgManifest.js +0 -0
@@ -894,15 +894,64 @@
894
894
  const isError = result?.isError || false;
895
895
  const statusClass = result ? (isError ? 'error' : 'success') : 'pending';
896
896
 
897
+ // This template runs as a standalone browser script in exported HTML,
898
+ // so it cannot import normalizeToolResultContent from @gsd/pi-ai.
899
+ // Keep these fallback semantics aligned with that helper.
900
+ const stringifyResultContent = (content) => {
901
+ try {
902
+ const value = JSON.stringify(content);
903
+ if (typeof value === 'string') return value;
904
+ } catch {
905
+ // Fall through to String(content). JSON.stringify can throw for cycles.
906
+ }
907
+
908
+ try {
909
+ return String(content);
910
+ } catch {
911
+ return '[unstringifiable tool result content]';
912
+ }
913
+ };
914
+
915
+ const serializableCacheControl = (block) => {
916
+ if (!block || typeof block !== 'object' || !('cache_control' in block)) return {};
917
+ try {
918
+ JSON.stringify(block.cache_control);
919
+ return { cache_control: block.cache_control };
920
+ } catch {
921
+ return {};
922
+ }
923
+ };
924
+
925
+ const normalizeResultContentBlock = (block) => {
926
+ if (typeof block === 'string') return { type: 'text', text: block };
927
+ if (block && typeof block === 'object') {
928
+ const cacheControl = serializableCacheControl(block);
929
+ if (block.type === 'text' && typeof block.text === 'string') return { type: 'text', text: block.text, ...cacheControl };
930
+ if (block.type === 'image' && typeof block.data === 'string' && typeof block.mimeType === 'string') {
931
+ return { type: 'image', data: block.data, mimeType: block.mimeType, ...cacheControl };
932
+ }
933
+ }
934
+ return { type: 'text', text: stringifyResultContent(block) };
935
+ };
936
+
937
+ const getResultContent = () => {
938
+ if (!result) return [];
939
+ if (Array.isArray(result.content)) {
940
+ const blocks = result.content.map(normalizeResultContentBlock);
941
+ return blocks.length > 0 ? blocks : [{ type: 'text', text: '' }];
942
+ }
943
+ if (typeof result.content === 'string') return [{ type: 'text', text: result.content }];
944
+ if (result.content == null) return [{ type: 'text', text: '' }];
945
+ return [{ type: 'text', text: stringifyResultContent(result.content) }];
946
+ };
947
+
897
948
  const getResultText = () => {
898
- if (!result) return '';
899
- const textBlocks = result.content.filter(c => c.type === 'text');
949
+ const textBlocks = getResultContent().filter(c => c.type === 'text');
900
950
  return textBlocks.map(c => c.text).join('\n');
901
951
  };
902
952
 
903
953
  const getResultImages = () => {
904
- if (!result) return [];
905
- return result.content.filter(c => c.type === 'image');
954
+ return getResultContent().filter(c => c.type === 'image');
906
955
  };
907
956
 
908
957
  const renderResultImages = () => {
package/pkg/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glittercowboy/gsd",
3
- "version": "1.4.0-dev.c18009cd",
3
+ "version": "1.5.0-dev.553dc518",
4
4
  "piConfig": {
5
5
  "name": "gsd",
6
6
  "configDir": ".gsd"
@@ -3,8 +3,10 @@
3
3
 
4
4
  import { importExtensionModule, type ExtensionAPI, type ExtensionContext } from "@gsd/pi-coding-agent";
5
5
 
6
+ import { execFileSync } from "node:child_process";
7
+ import { createHash } from "node:crypto";
6
8
  import { join, basename } from "node:path";
7
- import { existsSync, cpSync } from "node:fs";
9
+ import { existsSync, cpSync, readFileSync } from "node:fs";
8
10
  import type { AutoSession } from "./session.js";
9
11
  import type { LoopDeps } from "./loop-deps.js";
10
12
  import type { GSDState } from "../types.js";
@@ -16,7 +18,7 @@ import { getIsolationMode } from "../preferences.js";
16
18
  import { isDbAvailable, getMilestone } from "../gsd-db.js";
17
19
  import { refreshWorkflowDatabaseFromDisk } from "../db-workspace.js";
18
20
  import { isClosedStatus } from "../status-guards.js";
19
- import { gsdRoot } from "../paths.js";
21
+ import { gsdRoot, PLANNING_ARTIFACT_SUFFIXES } from "../paths.js";
20
22
  import { atomicWriteSync } from "../atomic-write.js";
21
23
  import { logWarning, logError } from "../workflow-logger.js";
22
24
  import { debugLog } from "../debug-logger.js";
@@ -98,7 +100,15 @@ export async function _runMilestoneMergeWithStashRestore(
98
100
  ): Promise<{ action: "break"; reason: string } | null> {
99
101
  const { ctx, pi, s, deps } = ic;
100
102
 
101
- const projectRoot = s.originalBasePath || s.basePath;
103
+ // Resolve the project root once, before the merge can flip session path
104
+ // pointers (worktree teardown mutates s.basePath / s.scope). The guarded
105
+ // merge's preflight/postflight stash restore, the pre-merge ignored-markdown
106
+ // snapshot, the dirty-projection check, and the rebuild must all target this
107
+ // same tree; otherwise `.gsd` markdown restored on one tree can be silently
108
+ // overwritten by a rebuild on another. canonicalProjectRoot is the
109
+ // authoritative `.gsd`/DB writer path (see AutoSession.canonicalProjectRoot).
110
+ const projectRoot = s.originalBasePath || s.canonicalProjectRoot || s.basePath;
111
+ const ignoredProjectionSnapshot = snapshotIgnoredGsdMarkdownProjections(projectRoot);
102
112
  const mergeResult = deps.lifecycle.exitMilestone(
103
113
  milestoneId,
104
114
  {
@@ -113,12 +123,12 @@ export async function _runMilestoneMergeWithStashRestore(
113
123
  );
114
124
 
115
125
  if (mergeResult.ok) {
116
- await markMilestoneMergedAndRebuild(s);
126
+ await markMilestoneMergedAndRebuild(s, projectRoot, ignoredProjectionSnapshot);
117
127
  return null;
118
128
  }
119
129
 
120
130
  if (mergeResult.reason === "postflight-stash-restore-failed") {
121
- await markMilestoneMergedAndRebuild(s);
131
+ await markMilestoneMergedAndRebuild(s, projectRoot, ignoredProjectionSnapshot);
122
132
  }
123
133
 
124
134
  if (mergeResult.reason === "preflight-dirty-overlap" || mergeResult.reason === "preflight-unmerged-conflicts") {
@@ -180,10 +190,23 @@ export async function _runMilestoneMergeWithStashRestore(
180
190
  return null;
181
191
  }
182
192
 
183
- async function markMilestoneMergedAndRebuild(s: AutoSession): Promise<void> {
193
+ type GsdMarkdownSnapshot = Map<string, string>;
194
+
195
+ async function markMilestoneMergedAndRebuild(
196
+ s: AutoSession,
197
+ rebuildBasePath: string,
198
+ ignoredProjectionSnapshot: GsdMarkdownSnapshot | null,
199
+ ): Promise<void> {
184
200
  s.milestoneMergedInPhases = true;
201
+ if (hasDirtyGsdMarkdownProjections(rebuildBasePath, ignoredProjectionSnapshot)) {
202
+ logWarning(
203
+ "engine",
204
+ "skipping markdown projection rebuild after milestone merge because restored .gsd edits are dirty",
205
+ );
206
+ return;
207
+ }
208
+
185
209
  try {
186
- const rebuildBasePath = s.originalBasePath || s.canonicalProjectRoot || s.basePath;
187
210
  const { rebuildMarkdownProjectionsFromDb } = await import("../commands-maintenance.js");
188
211
  await rebuildMarkdownProjectionsFromDb(rebuildBasePath);
189
212
  } catch (err) {
@@ -194,6 +217,90 @@ async function markMilestoneMergedAndRebuild(s: AutoSession): Promise<void> {
194
217
  }
195
218
  }
196
219
 
220
+ function hasDirtyGsdMarkdownProjections(
221
+ basePath: string,
222
+ ignoredProjectionSnapshot: GsdMarkdownSnapshot | null,
223
+ ): boolean {
224
+ try {
225
+ const output = execFileSync(
226
+ "git",
227
+ ["status", "--porcelain", "-z", "--untracked-files=all", "--", ".gsd"],
228
+ { cwd: basePath, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] },
229
+ );
230
+ const entries = output.split("\0").filter(Boolean);
231
+ for (let i = 0; i < entries.length; i++) {
232
+ const entry = entries[i];
233
+ if (entry.length <= 3) continue;
234
+ const status = entry.slice(0, 2);
235
+ const path = entry.slice(3);
236
+ if (isGeneratedGsdMarkdownProjection(path)) return true;
237
+ if ((status === "R " || status === "C ") && i + 1 < entries.length) {
238
+ const dest = entries[++i];
239
+ if (isGeneratedGsdMarkdownProjection(dest)) return true;
240
+ }
241
+ }
242
+ } catch (err) {
243
+ void err;
244
+ // Fall through to ignored-file snapshot comparison below.
245
+ }
246
+ return ignoredGsdMarkdownProjectionSnapshotChanged(basePath, ignoredProjectionSnapshot);
247
+ }
248
+
249
+ function isGeneratedGsdMarkdownProjection(path: string): boolean {
250
+ const normalized = path.replace(/\\/g, "/");
251
+ if (normalized === ".gsd/DECISIONS.md") return true;
252
+ if (!normalized.startsWith(".gsd/milestones/") && !normalized.startsWith(".gsd/phases/")) {
253
+ return false;
254
+ }
255
+ return isGeneratedPlanningArtifactFileName(basename(normalized));
256
+ }
257
+
258
+ function isGeneratedPlanningArtifactFileName(fileName: string): boolean {
259
+ if (!fileName.endsWith(".md")) return false;
260
+ const stem = fileName.slice(0, -".md".length).toUpperCase();
261
+ for (const suffix of PLANNING_ARTIFACT_SUFFIXES) {
262
+ if (!stem.endsWith(`-${suffix}`)) continue;
263
+ const idPrefix = stem.slice(0, -suffix.length - 1);
264
+ return /^(?:[MST]\d+|\d{2,})(?:-[A-Z0-9]+)*$/.test(idPrefix);
265
+ }
266
+ return false;
267
+ }
268
+
269
+ function snapshotIgnoredGsdMarkdownProjections(basePath: string): GsdMarkdownSnapshot | null {
270
+ try {
271
+ const output = execFileSync(
272
+ "git",
273
+ ["ls-files", "--others", "--ignored", "--exclude-standard", "-z", "--", ".gsd"],
274
+ { cwd: basePath, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] },
275
+ );
276
+ const snapshot: GsdMarkdownSnapshot = new Map();
277
+ for (const path of output.split("\0")) {
278
+ if (!isGeneratedGsdMarkdownProjection(path)) continue;
279
+ snapshot.set(path, hashFile(join(basePath, path)));
280
+ }
281
+ return snapshot;
282
+ } catch {
283
+ return null;
284
+ }
285
+ }
286
+
287
+ function hashFile(path: string): string {
288
+ return createHash("sha256").update(readFileSync(path)).digest("hex");
289
+ }
290
+
291
+ function ignoredGsdMarkdownProjectionSnapshotChanged(
292
+ basePath: string,
293
+ before: GsdMarkdownSnapshot | null,
294
+ ): boolean {
295
+ if (!before) return true;
296
+ const after = snapshotIgnoredGsdMarkdownProjections(basePath);
297
+ if (!after || after.size !== before.size) return true;
298
+ for (const [path, hash] of before) {
299
+ if (after.get(path) !== hash) return true;
300
+ }
301
+ return false;
302
+ }
303
+
197
304
  export async function _runMilestoneMergeOnceWithStashRestore(
198
305
  ic: IterationContext,
199
306
  milestoneId: string,
@@ -34,7 +34,8 @@ import {
34
34
  preDispatchHealthGate,
35
35
  recordHealthSnapshot,
36
36
  } from "../doctor-proactive.js";
37
- import { checkResourcesStale, autoWorktreeBranch, mergeMilestoneToMain } from "../auto-worktree.js";
37
+ import { autoWorktreeBranch } from "../auto-worktree-branch-lifecycle.js";
38
+ import { checkResourcesStale } from "../auto-worktree-resource-version.js";
38
39
  import { getSessionLockStatus } from "../session-lock.js";
39
40
  import { resolveUokFlags } from "../uok/flags.js";
40
41
  import { emitJournalEvent as _emitJournalEvent } from "../journal.js";
@@ -49,7 +50,7 @@ import { getPriorSliceCompletionBlocker } from "../dispatch-guard.js";
49
50
  import { GitServiceImpl } from "../git-service.js";
50
51
  import { WorktreeStateProjection } from "../worktree-state-projection.js";
51
52
  import { WorktreeLifecycle } from "../worktree-lifecycle.js";
52
- import { createMilestoneMergeTransaction } from "../milestone-merge-transaction.js";
53
+ import { createDefaultMilestoneMergeTransaction } from "../milestone-merge-transaction.js";
53
54
  import { createWorkspace, scopeMilestone } from "../workspace.js";
54
55
  import { supportsStructuredQuestions } from "../workflow-mcp.js";
55
56
  import { getRegisteredToolSnapshot, getToolBaselineSnapshot } from "../auto-model-selection.js";
@@ -731,7 +732,7 @@ export class AutoOrchestrator implements AutoOrchestrationModule {
731
732
  return new GitServiceImpl(basePath, gitConfig);
732
733
  },
733
734
  worktreeProjection: new WorktreeStateProjection(),
734
- mergeMilestone: createMilestoneMergeTransaction(mergeMilestoneToMain),
735
+ mergeMilestone: createDefaultMilestoneMergeTransaction(),
735
736
  });
736
737
  }
737
738
 
@@ -440,7 +440,8 @@ export async function runPreDispatch(
440
440
  if (stop) return stop;
441
441
  }
442
442
 
443
- // PR creation (auto_pr) is handled inside mergeMilestoneToMain (#2302)
443
+ // PR creation (auto_pr) is handled by the publication handoff in
444
+ // the milestone merge transaction (#2302).
444
445
 
445
446
  deps.invalidateAllCaches();
446
447
 
@@ -551,7 +552,8 @@ export async function runPreDispatch(
551
552
  // #2909 / #5538-followup: preflight stash + always-on postflight pop.
552
553
  const stop = await _runMilestoneMergeOnceWithStashRestore(ic, s.currentMilestoneId);
553
554
  if (stop) return stop;
554
- // PR creation (auto_pr) is handled inside mergeMilestoneToMain (#2302)
555
+ // PR creation (auto_pr) is handled by the publication handoff in
556
+ // the milestone merge transaction (#2302).
555
557
  }
556
558
  const unmappedActive = countUnmappedActiveRequirements();
557
559
  const completionStopReason = formatCompletePhaseNextAction(unmappedActive);
@@ -654,7 +656,8 @@ export async function runPreDispatch(
654
656
  // #2909 / #5538-followup: preflight stash + always-on postflight pop.
655
657
  const stop = await _runMilestoneMergeOnceWithStashRestore(ic, s.currentMilestoneId);
656
658
  if (stop) return stop;
657
- // PR creation (auto_pr) is handled inside mergeMilestoneToMain (#2302)
659
+ // PR creation (auto_pr) is handled by the publication handoff in
660
+ // the milestone merge transaction (#2302).
658
661
  }
659
662
  deps.sendDesktopNotification(
660
663
  "GSD",
@@ -29,7 +29,7 @@ import type { MinimalModelRegistry } from "../context-budget.js";
29
29
  import { parseUnitId } from "../unit-id.js";
30
30
  import { createCheckpoint, cleanupCheckpoint, rollbackToCheckpoint } from "../safety/git-checkpoint.js";
31
31
  import { resolveSafetyHarnessConfig } from "../safety/safety-harness.js";
32
- import { getUnitWorkflowDispatchReadinessError } from "../tool-contract.js";
32
+ import { getUnitWorkflowDispatchReadinessErrorForModel } from "../tool-contract.js";
33
33
  import { prepareWorkflowMcpForProject } from "../workflow-mcp-auto-prep.js";
34
34
  import {
35
35
  applyThinkingLevelForModel,
@@ -343,17 +343,13 @@ export async function runUnitPhase(
343
343
  ? `${(s.currentUnitModel as any).provider ?? ""}/${(s.currentUnitModel as any).id ?? ""}`
344
344
  : null;
345
345
 
346
- const compatibilityError = getUnitWorkflowDispatchReadinessError({
347
- provider: s.currentUnitModel?.provider ?? ctx.model?.provider,
346
+ const compatibilityError = getUnitWorkflowDispatchReadinessErrorForModel({
347
+ model: s.currentUnitModel as any,
348
+ fallbackModel: ctx.model,
349
+ getProviderAuthMode: (provider) => ctx.modelRegistry.getProviderAuthMode(provider),
348
350
  projectRoot: s.basePath,
349
351
  surface: "auto-mode",
350
352
  unitType,
351
- authMode: s.currentUnitModel?.provider
352
- ? ctx.modelRegistry.getProviderAuthMode(s.currentUnitModel.provider)
353
- : ctx.model?.provider
354
- ? ctx.modelRegistry.getProviderAuthMode(ctx.model.provider)
355
- : undefined,
356
- baseUrl: (s.currentUnitModel as any)?.baseUrl ?? ctx.model?.baseUrl,
357
353
  activeTools: typeof pi.getActiveTools === "function" ? pi.getActiveTools() : [],
358
354
  });
359
355
  const workflowMcpPrepModel = s.currentUnitModel;
@@ -31,7 +31,7 @@ import { loadEffectiveGSDPreferences } from "./preferences.js";
31
31
  import type { MinimalModelRegistry } from "./context-budget.js";
32
32
  import { pauseAuto } from "./auto.js";
33
33
  import { resolveCanonicalMilestoneRoot } from "./worktree-manager.js";
34
- import { getUnitWorkflowDispatchReadinessError } from "./tool-contract.js";
34
+ import { getUnitWorkflowDispatchReadinessErrorForModel } from "./tool-contract.js";
35
35
 
36
36
  export function parseDirectDispatchPhase(raw: string): { phase: string; milestoneId?: string } {
37
37
  const tokens = raw.trim().split(/\s+/).filter(Boolean);
@@ -266,13 +266,12 @@ export async function dispatchDirectPhase(
266
266
  return;
267
267
  }
268
268
 
269
- const compatibilityError = getUnitWorkflowDispatchReadinessError({
270
- provider: ctx.model?.provider,
269
+ const compatibilityError = getUnitWorkflowDispatchReadinessErrorForModel({
270
+ model: ctx.model,
271
+ getProviderAuthMode: (provider) => ctx.modelRegistry.getProviderAuthMode(provider),
271
272
  projectRoot,
272
273
  surface: "direct phase dispatch",
273
274
  unitType,
274
- authMode: ctx.model?.provider ? ctx.modelRegistry.getProviderAuthMode(ctx.model.provider) : undefined,
275
- baseUrl: ctx.model?.baseUrl,
276
275
  activeTools: typeof pi.getActiveTools === "function" ? pi.getActiveTools() : [],
277
276
  });
278
277
  if (compatibilityError) {
@@ -63,9 +63,11 @@ import {
63
63
  detectWorktreeName,
64
64
  setActiveMilestoneId,
65
65
  } from "./worktree.js";
66
- import { getAutoWorktreePath, isInAutoWorktree } from "./auto-worktree.js";
66
+ import { isInAutoWorktree } from "./auto-worktree-entry.js";
67
+ import { getAutoWorktreePath } from "./auto-worktree-path-resolution.js";
67
68
  import { checkoutBranchWithStashGuard } from "./worktree-git-recovery.js";
68
- import { readResourceVersion, cleanStaleRuntimeUnits } from "./auto-worktree.js";
69
+ import { cleanStaleRuntimeUnits } from "./auto-worktree-runtime-cleanup.js";
70
+ import { readResourceVersion } from "./auto-worktree-resource-version.js";
69
71
  import { worktreePath as getWorktreeDir, isInsideWorktreesDir } from "./worktree-manager.js";
70
72
  import { emitWorktreeOrphaned } from "./worktree-telemetry.js";
71
73
  import { queryJournal } from "./journal.js";
@@ -0,0 +1,237 @@
1
+ // gsd-pi — Auto-worktree branch lifecycle module.
2
+ //
3
+ // Owns milestone branch naming, branch-mode entry, and safe fast-forward of
4
+ // reused milestone branches. Worktree creation/merge code imports this module
5
+ // instead of keeping branch policy inside the legacy auto-worktree barrel.
6
+
7
+ import { GSDError, GSD_GIT_ERROR } from "./errors.js";
8
+ import { readIntegrationBranch } from "./git-service.js";
9
+ import { loadEffectiveGSDPreferences } from "./preferences.js";
10
+ import { debugLog } from "./debug-logger.js";
11
+ import { checkoutBranchWithStashGuard } from "./worktree-git-recovery.js";
12
+ import {
13
+ nativeBranchExists,
14
+ nativeBranchForceReset,
15
+ nativeDetectMainBranch,
16
+ nativeIsAncestor,
17
+ nativeUpdateRef,
18
+ nativeWorktreeList,
19
+ } from "./native-git-bridge.js";
20
+
21
+ /** Returns the git branch name for a milestone worktree (`milestone/<MID>`). */
22
+ export function autoWorktreeBranch(milestoneId: string): string {
23
+ return `milestone/${milestoneId}`;
24
+ }
25
+
26
+ export function _resolveAutoWorktreeStartPoint(
27
+ integrationBranch: string | null | undefined,
28
+ gitMainBranch: string | null | undefined,
29
+ branchExists: (branch: string) => boolean,
30
+ ): string | undefined {
31
+ if (integrationBranch) return integrationBranch;
32
+ return gitMainBranch &&
33
+ typeof gitMainBranch === "string" &&
34
+ gitMainBranch.length > 0 &&
35
+ branchExists(gitMainBranch)
36
+ ? gitMainBranch
37
+ : undefined;
38
+ }
39
+
40
+ /**
41
+ * Enter branch isolation mode for a milestone.
42
+ *
43
+ * Creates `milestone/<MID>` from the integration branch (if it doesn't
44
+ * exist yet) and checks out to it. No worktree directory is created — the
45
+ * project root is the working copy; only HEAD changes.
46
+ *
47
+ * Uses the same 3-tier integration-branch fallback as createAutoWorktree:
48
+ * 1. META.json recorded integration branch
49
+ * 2. git.main_branch preference
50
+ * 3. nativeDetectMainBranch (origin/HEAD auto-detection)
51
+ */
52
+ export function enterBranchModeForMilestone(
53
+ basePath: string,
54
+ milestoneId: string,
55
+ ): void {
56
+ const branch = autoWorktreeBranch(milestoneId);
57
+ const branchExists = nativeBranchExists(basePath, branch);
58
+
59
+ if (!branchExists) {
60
+ // Create the milestone branch from the integration branch start-point.
61
+ const integrationBranch =
62
+ readIntegrationBranch(basePath, milestoneId) ?? undefined;
63
+ const gitPrefs = loadEffectiveGSDPreferences()?.preferences?.git;
64
+ const startPoint =
65
+ _resolveAutoWorktreeStartPoint(
66
+ integrationBranch,
67
+ gitPrefs?.main_branch,
68
+ (branchName) => nativeBranchExists(basePath, branchName),
69
+ ) ??
70
+ nativeDetectMainBranch(basePath);
71
+
72
+ // TOCTOU ancestry guard (Issue #4980 HIGH-3).
73
+ //
74
+ // The outer `branchExists` check is racy: a concurrent process
75
+ // (parallel-orchestrator worker, side-by-side `gsd` instance, or manual
76
+ // `git branch` invocation) may have created the branch with real commits
77
+ // between that check and this point. `nativeBranchForceReset` does
78
+ // `git branch -f`, which silently overwrites the branch ref — orphaning
79
+ // any commits not reachable from `startPoint`. Re-check immediately
80
+ // before the destructive call and refuse if the branch suddenly exists
81
+ // with non-ancestor commits.
82
+ const concurrentlyCreated = nativeBranchExists(basePath, branch);
83
+ if (
84
+ concurrentlyCreated &&
85
+ !nativeIsAncestor(basePath, branch, startPoint)
86
+ ) {
87
+ throw new GSDError(
88
+ GSD_GIT_ERROR,
89
+ `Branch "${branch}" was created concurrently with commits not reachable from "${startPoint}". ` +
90
+ `Refusing to force-reset — would orphan prior work. ` +
91
+ `Resume the existing milestone or run \`git branch -D ${branch}\` to discard.`,
92
+ );
93
+ }
94
+ // nativeBranchForceReset creates (or resets) branch at startPoint,
95
+ // then checkout switches HEAD to it.
96
+ nativeBranchForceReset(basePath, branch, startPoint);
97
+ debugLog("auto-worktree", {
98
+ action: "enterBranchMode",
99
+ milestoneId,
100
+ branch,
101
+ startPoint,
102
+ created: true,
103
+ });
104
+ } else {
105
+ debugLog("auto-worktree", {
106
+ action: "enterBranchMode",
107
+ milestoneId,
108
+ branch,
109
+ reused: true,
110
+ });
111
+ }
112
+
113
+ checkoutBranchWithStashGuard(
114
+ basePath,
115
+ branch,
116
+ `enter-branch-mode:${milestoneId}`,
117
+ );
118
+ }
119
+
120
+ /**
121
+ * True when `branch` is checked out in any worktree listed by
122
+ * `git worktree list --porcelain`. Used to gate ref updates that would
123
+ * otherwise leave a concurrent worktree's HEAD inconsistent with its
124
+ * index/working tree (Codex peer-review of #5538-followup).
125
+ *
126
+ * Best-effort: a `nativeWorktreeList` failure returns true so we err on
127
+ * the side of NOT moving the ref. Better to skip a fast-forward than to
128
+ * silently corrupt another worktree.
129
+ */
130
+ export function _isBranchCheckedOutElsewhere(
131
+ basePath: string,
132
+ branch: string,
133
+ ): boolean {
134
+ try {
135
+ const entries = nativeWorktreeList(basePath);
136
+ return entries.some((entry) => entry.branch === branch);
137
+ } catch {
138
+ return true;
139
+ }
140
+ }
141
+
142
+ /**
143
+ * Resolve the integration branch using the same 3-tier fallback as the
144
+ * fresh-create path: META.json → git.main_branch preference → detected
145
+ * main branch. Returns null when no usable target exists.
146
+ */
147
+ function resolveIntegrationBranchForReuse(
148
+ basePath: string,
149
+ milestoneId: string,
150
+ ): string | null {
151
+ const fromMeta = readIntegrationBranch(basePath, milestoneId);
152
+ if (fromMeta) return fromMeta;
153
+
154
+ const gitPrefs = loadEffectiveGSDPreferences()?.preferences?.git;
155
+ const fromPref = gitPrefs?.main_branch &&
156
+ typeof gitPrefs.main_branch === "string" &&
157
+ gitPrefs.main_branch.length > 0 &&
158
+ nativeBranchExists(basePath, gitPrefs.main_branch)
159
+ ? gitPrefs.main_branch
160
+ : null;
161
+ if (fromPref) return fromPref;
162
+
163
+ try {
164
+ return nativeDetectMainBranch(basePath);
165
+ } catch {
166
+ return null;
167
+ }
168
+ }
169
+
170
+ /**
171
+ * When reusing an existing milestone branch, fast-forward it onto the
172
+ * integration branch when that's safe (branch is a strict ancestor of
173
+ * integration — no commits would be lost). Skips when the branch has its
174
+ * own commits ahead of integration, when the integration branch can't be
175
+ * resolved, or when any git operation fails — the merge gate at milestone
176
+ * completion will surface real divergence as a conflict.
177
+ *
178
+ * The previous behavior re-attached the worktree to whatever stale tip
179
+ * the branch held, which caused new milestone work to fork from a base
180
+ * missing prior milestones' merges (#5538-followup).
181
+ */
182
+ export function fastForwardReusedMilestoneBranchIfSafe(
183
+ basePath: string,
184
+ milestoneId: string,
185
+ branch: string,
186
+ ): void {
187
+ try {
188
+ const integrationBranch = resolveIntegrationBranchForReuse(
189
+ basePath,
190
+ milestoneId,
191
+ );
192
+ if (!integrationBranch || integrationBranch === branch) return;
193
+ if (!nativeBranchExists(basePath, integrationBranch)) return;
194
+
195
+ // Pure fast-forward only: branch must be a strict ancestor of integration.
196
+ // If the branch has its own commits ahead, leave it alone.
197
+ if (!nativeIsAncestor(basePath, branch, integrationBranch)) {
198
+ debugLog("createAutoWorktree", {
199
+ phase: "skip-ff-branch-not-ancestor",
200
+ milestoneId,
201
+ branch,
202
+ integration: integrationBranch,
203
+ });
204
+ return;
205
+ }
206
+
207
+ // Codex peer-review: `nativeUpdateRef` succeeds even when the branch is
208
+ // currently checked out in another worktree, leaving that worktree's HEAD
209
+ // inconsistent with its index/work tree. Skip the fast-forward if any
210
+ // listed worktree has this branch checked out — the merge gate at
211
+ // milestone-completion will surface stale-base divergence as a conflict
212
+ // instead of silently corrupting the other worktree's state.
213
+ if (_isBranchCheckedOutElsewhere(basePath, branch)) {
214
+ debugLog("createAutoWorktree", {
215
+ phase: "skip-ff-branch-checked-out-elsewhere",
216
+ milestoneId,
217
+ branch,
218
+ });
219
+ return;
220
+ }
221
+
222
+ nativeUpdateRef(basePath, `refs/heads/${branch}`, integrationBranch);
223
+ debugLog("createAutoWorktree", {
224
+ phase: "fast-forward-reused-branch",
225
+ milestoneId,
226
+ branch,
227
+ integration: integrationBranch,
228
+ });
229
+ } catch (err) {
230
+ debugLog("createAutoWorktree", {
231
+ phase: "fast-forward-reused-branch-failed",
232
+ milestoneId,
233
+ branch,
234
+ error: err instanceof Error ? err.message : String(err),
235
+ });
236
+ }
237
+ }