@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
@@ -51,10 +51,16 @@ import { atomicWriteSync } from "./atomic-write.js";
51
51
  import { captureIntegrationBranch, detectWorktreeName, getCurrentBranch, getMainBranch, setActiveMilestoneId, } from "./worktree.js";
52
52
  import { GitServiceImpl } from "./git-service.js";
53
53
  import { getPriorSliceCompletionBlocker } from "./dispatch-guard.js";
54
- import { createAutoWorktree, teardownAutoWorktree, isInAutoWorktree, getAutoWorktreePath, mergeMilestoneToMain, autoWorktreeBranch, checkResourcesStale, escapeStaleWorktree, } from "./auto-worktree.js";
54
+ import { autoWorktreeBranch } from "./auto-worktree-branch-lifecycle.js";
55
+ import { createAutoWorktree } from "./auto-worktree-creation.js";
56
+ import { isInAutoWorktree } from "./auto-worktree-entry.js";
57
+ import { getAutoWorktreePath } from "./auto-worktree-path-resolution.js";
58
+ import { checkResourcesStale } from "./auto-worktree-resource-version.js";
59
+ import { escapeStaleWorktree } from "./auto-worktree-runtime-cleanup.js";
60
+ import { teardownAutoWorktree } from "./auto-worktree-teardown.js";
55
61
  import { pruneQueueOrder } from "./queue-order.js";
56
62
  import { startCommandPolling as _startCommandPolling, isRemoteConfigured } from "../remote-questions/manager.js";
57
- import { createMilestoneMergeTransaction } from "./milestone-merge-transaction.js";
63
+ import { createDefaultMilestoneMergeTransaction } from "./milestone-merge-transaction.js";
58
64
  import { debugLog, isDebugEnabled, writeDebugSummary } from "./debug-logger.js";
59
65
  import { reconcileMergeState, verifyExpectedArtifact, } from "./auto-recovery.js";
60
66
  import { classifyMilestoneSummaryContent } from "./milestone-summary-classifier.js";
@@ -1746,7 +1752,7 @@ export function buildWorktreeLifecycleDeps() {
1746
1752
  return new GitServiceImpl(basePath, gitConfig);
1747
1753
  },
1748
1754
  worktreeProjection: new WorktreeStateProjection(),
1749
- mergeMilestone: createMilestoneMergeTransaction(mergeMilestoneToMain),
1755
+ mergeMilestone: createDefaultMilestoneMergeTransaction(),
1750
1756
  };
1751
1757
  }
1752
1758
  function buildLifecycle() {
@@ -6,7 +6,7 @@ import { join } from "node:path";
6
6
  import { createBashTool, createEditTool, createReadTool, createWriteTool } from "@gsd/pi-coding-agent";
7
7
  import { logWarning } from "../workflow-logger.js";
8
8
  import { getWorkflowDatabaseStatus, openWorkflowDatabase, } from "../db-workspace.js";
9
- import { getAutoWorktreePath } from "../auto-worktree.js";
9
+ import { getAutoWorktreePath } from "../auto-worktree-path-resolution.js";
10
10
  import { resolveWorktreeProjectRoot } from "../worktree-root.js";
11
11
  import { worktreesDirs } from "../worktree-placement.js";
12
12
  export function safeWorkspaceCwd() {
@@ -15,7 +15,7 @@ import { loadFile, saveFile, formatContinue } from "../files.js";
15
15
  import { clearAutoCompletionStopInProgress, clearToolInvocationError, getAutoRuntimeSnapshot, getSourceObservationStore, isAutoActive, isAutoCompletionStopInProgress, isAutoPaused, isInteractiveElicitationInFlight, markToolEnd, markToolStart, recordAutoToolSurfaceSnapshot, recordToolInvocationError, } from "../auto-runtime-state.js";
16
16
  import { isDeterministicPolicyError, isQueuedUserMessageSkip, isToolInvocationError, isToolUnavailableError, } from "../auto-tool-tracking.js";
17
17
  import { applyProviderPayloadPolicy } from "../provider-payload-policy.js";
18
- import { checkToolCallLoop, recordToolCallLoopMutation, resetToolCallLoopGuard } from "./tool-call-loop-guard.js";
18
+ import { checkToolCallLoop, configureToolCallLoopGuard, recordToolCallLoopMutation, resetToolCallLoopGuard } from "./tool-call-loop-guard.js";
19
19
  import { MINIMAL_AUTO_BASE_TOOL_NAMES } from "./core-session-tools.js";
20
20
  import { maybePauseAutoForApprovalGate, resetPendingGatePauseGuard } from "./pending-gate-pause.js";
21
21
  import { saveActivityLog } from "../activity-log.js";
@@ -432,6 +432,22 @@ async function applyCompactionThresholdOverride(ctx) {
432
432
  ctx.setCompactionThresholdOverride?.(0.6);
433
433
  }
434
434
  }
435
+ /**
436
+ * Apply user-tunable tool-call loop guard thresholds (#1198) from GSD
437
+ * preferences plus GSD_TOOL_LOOP_* env overrides. Runs at session boundaries
438
+ * so both interactive sessions and `/gsd auto` pick up the configuration.
439
+ * Non-fatal: falls back to built-in defaults when preferences cannot be loaded.
440
+ */
441
+ async function applyToolCallLoopGuardConfig(basePath) {
442
+ try {
443
+ const { loadEffectiveGSDPreferences } = await import("../preferences.js");
444
+ const prefs = loadEffectiveGSDPreferences(basePath);
445
+ configureToolCallLoopGuard(prefs?.preferences.tool_call_loop_guard);
446
+ }
447
+ catch {
448
+ configureToolCallLoopGuard(null);
449
+ }
450
+ }
435
451
  function clearDeferredApprovalGate(basePath) {
436
452
  if (!basePath) {
437
453
  deferredApprovalGates.clear();
@@ -779,6 +795,7 @@ export function registerHooks(pi, ecosystemHandlers) {
779
795
  }
780
796
  resetWriteGateState(basePath);
781
797
  resetToolCallLoopGuard();
798
+ await applyToolCallLoopGuardConfig(basePath);
782
799
  approvalQuestionAbortInFlight = false;
783
800
  clearDeferredApprovalGate();
784
801
  await resetAskUserQuestionsTurnCache();
@@ -864,6 +881,7 @@ export function registerHooks(pi, ecosystemHandlers) {
864
881
  initSessionNotifications(ctx);
865
882
  resetWriteGateState(basePath);
866
883
  resetToolCallLoopGuard();
884
+ await applyToolCallLoopGuardConfig(basePath);
867
885
  clearDeferredApprovalGate();
868
886
  await resetAskUserQuestionsTurnCache();
869
887
  clearDiscussionFlowState(basePath);
@@ -11,7 +11,7 @@ import { resolveModelWithFallbacksForUnit } from "../preferences-models.js";
11
11
  import { resolveGsdRootFile, resolveSliceFile, resolveSlicePath, resolveTaskFile, resolveTaskFiles, resolveTasksDir, relSliceFile, relSlicePath, relTaskFile } from "../paths.js";
12
12
  import { extractIntroAndRules } from "../knowledge-parser.js";
13
13
  import { ensureCodebaseMapFresh, readCodebaseMap } from "../codebase-generator.js";
14
- import { getActiveAutoWorktreeContext } from "../auto-worktree.js";
14
+ import { getActiveAutoWorktreeContext } from "../auto-worktree-session-registry.js";
15
15
  import { getActiveWorktreeName, getWorktreeOriginalCwd } from "../worktree-session-state.js";
16
16
  import { deriveState } from "../state.js";
17
17
  import { formatOverridesSection, formatShortcut, loadActiveOverrides, loadFile, parseContinue, parseSummary } from "../files.js";
@@ -21,29 +21,117 @@
21
21
  * signature, so the identical-args streak never trips, while allowing
22
22
  * tool-heavy turns that are making file-mutation progress. Whichever guard
23
23
  * trips first blocks.
24
+ *
25
+ * Thresholds, exempt tools, and enable flags are user-tunable (#1198) via the
26
+ * `tool_call_loop_guard` key in `.gsd/PREFERENCES.md` and `GSD_TOOL_LOOP_*`
27
+ * environment variables, applied through {@link configureToolCallLoopGuard}.
28
+ * Defaults preserve the original hardcoded behavior.
24
29
  */
25
30
  import { createHash } from "node:crypto";
26
31
  import { INHERENTLY_REPEATABLE_TOOL_SET } from "./core-session-tools.js";
27
32
  import { hasBrowserContractPrefix } from "../../shared/browser-contract.js";
28
33
  import { canonicalToolName } from "../engine-hook-contract.js";
29
- const MAX_CONSECUTIVE_IDENTICAL_CALLS = 4;
34
+ /** Built-in defaults. Preserved when preferences/env do not override them. */
35
+ const DEFAULT_MAX_CONSECUTIVE_IDENTICAL_CALLS = 4;
36
+ const DEFAULT_PER_TOOL_DEFAULT_CAP = 6;
37
+ const DEFAULT_PER_TOOL_REPEATABLE_CAP = 15;
38
+ const DEFAULT_PER_TOOL_CAP_EXEMPT_TOOLS = ["find", "glob", "grep", "ls", "read", "search_and_read"];
30
39
  /** Interactive/user-facing tools where even 1 duplicate is confusing. */
31
40
  const STRICT_LOOP_TOOLS = new Set(["ask_user_questions"]);
32
41
  const MAX_CONSECUTIVE_STRICT = 1;
42
+ const STATE_MUTATING_TOOL_SET = new Set(["edit", "write", "multi_edit", "notebook_edit"]);
43
+ function defaultGuardConfig() {
44
+ return {
45
+ guardEnabled: true,
46
+ identicalEnabled: true,
47
+ maxConsecutiveIdentical: DEFAULT_MAX_CONSECUTIVE_IDENTICAL_CALLS,
48
+ repeatedEnabled: true,
49
+ perToolDefaultCap: DEFAULT_PER_TOOL_DEFAULT_CAP,
50
+ perToolRepeatableCap: DEFAULT_PER_TOOL_REPEATABLE_CAP,
51
+ perToolExempt: new Set(DEFAULT_PER_TOOL_CAP_EXEMPT_TOOLS),
52
+ };
53
+ }
54
+ let config = defaultGuardConfig();
55
+ /** Parse a positive-integer env var, returning undefined when unset/invalid. */
56
+ function envPositiveInt(name) {
57
+ const raw = process.env[name];
58
+ if (raw === undefined || raw.trim() === "")
59
+ return undefined;
60
+ const n = Number(raw);
61
+ return Number.isFinite(n) && n >= 1 ? Math.floor(n) : undefined;
62
+ }
63
+ /** Parse a boolean env var (`true`/`1` vs `false`/`0`), undefined when unset. */
64
+ function envBool(name) {
65
+ const raw = process.env[name];
66
+ if (raw === undefined || raw.trim() === "")
67
+ return undefined;
68
+ const v = raw.trim().toLowerCase();
69
+ if (v === "true" || v === "1" || v === "yes" || v === "on")
70
+ return true;
71
+ if (v === "false" || v === "0" || v === "no" || v === "off")
72
+ return false;
73
+ return undefined;
74
+ }
75
+ /** Parse a comma-separated tool-name list env var. */
76
+ function envToolList(name) {
77
+ const raw = process.env[name];
78
+ if (!raw)
79
+ return [];
80
+ return raw.split(",").map((s) => s.trim()).filter(Boolean);
81
+ }
33
82
  /**
34
- * Per-turn cap on calls to the SAME tool name, regardless of args (#783).
83
+ * Resolve preferences + environment overrides into the active configuration.
84
+ * Environment variables win over preferences; preferences win over built-in
85
+ * defaults. User-supplied exempt tools are additive to the built-in exempt
86
+ * set so defaults are always preserved.
35
87
  *
36
- * General-purpose execution tools are routinely called many times per turn
37
- * (touching multiple files, running several commands), so they get a higher
38
- * ceiling. Everything else — workflow one-shot tools (e.g. gsd_complete_milestone)
39
- * and any non-allowlisted tool — gets the default cap. The default is generous
40
- * enough to absorb legitimate retries but catches the reported improvisation
41
- * loop (~51 calls) well before a cost spike.
88
+ * Applied to both interactive sessions and `/gsd auto` (called from the
89
+ * session_start / session_switch hooks).
42
90
  */
43
- const PER_TOOL_DEFAULT_CAP = 6;
44
- const PER_TOOL_REPEATABLE_CAP = 15;
45
- const PER_TOOL_CAP_EXEMPT_TOOLS = new Set(["find", "glob", "grep", "ls", "read", "search_and_read"]);
46
- const STATE_MUTATING_TOOL_SET = new Set(["edit", "write", "multi_edit", "notebook_edit"]);
91
+ export function configureToolCallLoopGuard(prefs) {
92
+ const next = defaultGuardConfig();
93
+ if (prefs) {
94
+ if (typeof prefs.enabled === "boolean")
95
+ next.guardEnabled = prefs.enabled;
96
+ if (prefs.identical_args) {
97
+ if (typeof prefs.identical_args.enabled === "boolean")
98
+ next.identicalEnabled = prefs.identical_args.enabled;
99
+ if (typeof prefs.identical_args.max_consecutive_calls === "number" && prefs.identical_args.max_consecutive_calls >= 1) {
100
+ next.maxConsecutiveIdentical = Math.floor(prefs.identical_args.max_consecutive_calls);
101
+ }
102
+ }
103
+ if (prefs.repeated_tool) {
104
+ if (typeof prefs.repeated_tool.enabled === "boolean")
105
+ next.repeatedEnabled = prefs.repeated_tool.enabled;
106
+ if (typeof prefs.repeated_tool.default_cap === "number" && prefs.repeated_tool.default_cap >= 1) {
107
+ next.perToolDefaultCap = Math.floor(prefs.repeated_tool.default_cap);
108
+ }
109
+ if (typeof prefs.repeated_tool.repeatable_cap === "number" && prefs.repeated_tool.repeatable_cap >= 1) {
110
+ next.perToolRepeatableCap = Math.floor(prefs.repeated_tool.repeatable_cap);
111
+ }
112
+ for (const tool of prefs.repeated_tool.exempt_tools ?? []) {
113
+ if (typeof tool === "string" && tool.trim())
114
+ next.perToolExempt.add(tool.trim());
115
+ }
116
+ }
117
+ }
118
+ // Environment overrides (win over preferences).
119
+ const envEnabled = envBool("GSD_TOOL_LOOP_GUARD_ENABLED");
120
+ if (envEnabled !== undefined)
121
+ next.guardEnabled = envEnabled;
122
+ const envIdenticalMax = envPositiveInt("GSD_TOOL_LOOP_IDENTICAL_MAX");
123
+ if (envIdenticalMax !== undefined)
124
+ next.maxConsecutiveIdentical = envIdenticalMax;
125
+ const envDefaultCap = envPositiveInt("GSD_TOOL_LOOP_REPEATED_DEFAULT_CAP");
126
+ if (envDefaultCap !== undefined)
127
+ next.perToolDefaultCap = envDefaultCap;
128
+ const envRepeatableCap = envPositiveInt("GSD_TOOL_LOOP_REPEATED_REPEATABLE_CAP");
129
+ if (envRepeatableCap !== undefined)
130
+ next.perToolRepeatableCap = envRepeatableCap;
131
+ for (const tool of envToolList("GSD_TOOL_LOOP_EXEMPT_TOOLS"))
132
+ next.perToolExempt.add(tool);
133
+ config = next;
134
+ }
47
135
  let consecutiveCount = 0;
48
136
  let lastSignature = "";
49
137
  let lastToolName = "";
@@ -72,14 +160,17 @@ function hashToolCall(toolName, args) {
72
160
  * Returns `{ block: true, reason }` when the loop threshold is exceeded.
73
161
  *
74
162
  * Two independent guards run; whichever trips first blocks:
75
- * 1. Identical-signature streak (MAX_CONSECUTIVE_IDENTICAL_CALLS, strict for
163
+ * 1. Identical-signature streak (config.maxConsecutiveIdentical, strict for
76
164
  * ask_user_questions).
77
- * 2. Per-tool-name cap (PER_TOOL_DEFAULT_CAP / PER_TOOL_REPEATABLE_CAP),
165
+ * 2. Per-tool-name cap (config.perToolDefaultCap / config.perToolRepeatableCap),
78
166
  * independent of args, reset after file-mutation progress — catches
79
167
  * improvisation loops (#783).
168
+ *
169
+ * Both guards, their thresholds, and the per-tool exempt set are user-tunable
170
+ * via {@link configureToolCallLoopGuard} (#1198).
80
171
  */
81
172
  export function checkToolCallLoop(toolName, args) {
82
- if (!enabled)
173
+ if (!enabled || !config.guardEnabled)
83
174
  return { block: false, count: 0 };
84
175
  const sig = hashToolCall(toolName, args);
85
176
  if (sig === lastSignature) {
@@ -93,12 +184,14 @@ export function checkToolCallLoop(toolName, args) {
93
184
  // ── Guard 1: identical-signature streak ──
94
185
  const threshold = STRICT_LOOP_TOOLS.has(toolName)
95
186
  ? MAX_CONSECUTIVE_STRICT
96
- : MAX_CONSECUTIVE_IDENTICAL_CALLS;
97
- if (consecutiveCount > threshold) {
187
+ : config.maxConsecutiveIdentical;
188
+ if (config.identicalEnabled && consecutiveCount > threshold) {
98
189
  return {
99
190
  block: true,
100
191
  reason: `Tool loop detected (identical args): ${toolName} called ${consecutiveCount} times ` +
101
- `with identical arguments. Blocking to prevent infinite loop. ` +
192
+ `with identical arguments (max ${threshold}). Blocking to prevent infinite loop. ` +
193
+ `Raise tool_call_loop_guard.identical_args.max_consecutive_calls in .gsd/PREFERENCES.md ` +
194
+ `(or GSD_TOOL_LOOP_IDENTICAL_MAX) if this is expected. ` +
102
195
  `Do not retry this tool or call other tools this turn — stop and respond to the user in text.`,
103
196
  count: consecutiveCount,
104
197
  };
@@ -119,19 +212,23 @@ export function checkToolCallLoop(toolName, args) {
119
212
  // so the arg-independent per-tool cap misfires on legitimate verification;
120
213
  // Guard 1's identical-signature streak still catches a genuinely stuck
121
214
  // browser loop.
122
- if (PER_TOOL_CAP_EXEMPT_TOOLS.has(toolName) ||
215
+ if (!config.repeatedEnabled ||
216
+ config.perToolExempt.has(toolName) ||
123
217
  hasBrowserContractPrefix(canonicalToolName(toolName))) {
124
218
  return { block: false, count: consecutiveCount };
125
219
  }
126
220
  const perToolCap = INHERENTLY_REPEATABLE_TOOL_SET.has(toolName)
127
- ? PER_TOOL_REPEATABLE_CAP
128
- : PER_TOOL_DEFAULT_CAP;
221
+ ? config.perToolRepeatableCap
222
+ : config.perToolDefaultCap;
129
223
  if (perToolCount > perToolCap) {
130
224
  return {
131
225
  block: true,
132
226
  reason: `Tool loop detected (repeated tool): ${toolName} called ${perToolCount} times ` +
133
227
  `this turn (cap ${perToolCap}). Blocking to prevent infinite loop. ` +
134
228
  `The tool may be unavailable or failing repeatedly. ` +
229
+ `Raise tool_call_loop_guard.repeated_tool.default_cap/repeatable_cap or add "${toolName}" ` +
230
+ `to tool_call_loop_guard.repeated_tool.exempt_tools in .gsd/PREFERENCES.md ` +
231
+ `(or GSD_TOOL_LOOP_REPEATED_* / GSD_TOOL_LOOP_EXEMPT_TOOLS) if this is expected. ` +
135
232
  `Do not retry this tool or pivot to other tools this turn — stop and respond to the user in text.`,
136
233
  count: perToolCount,
137
234
  };
@@ -16,7 +16,7 @@ import { appendCapture, hasPendingCaptures, loadPendingCaptures } from "./captur
16
16
  import { appendOverride, appendKnowledge } from "./files.js";
17
17
  import { formatDoctorIssuesForPrompt, formatDoctorReport, formatDoctorReportJson, runGSDDoctor, selectDoctorScope, filterDoctorIssues, } from "./doctor.js";
18
18
  import { isAutoActive, checkRemoteAutoSession } from "./auto.js";
19
- import { getAutoWorktreePath } from "./auto-worktree.js";
19
+ import { getAutoWorktreePath } from "./auto-worktree-path-resolution.js";
20
20
  import { currentDirectoryRoot, projectRoot } from "./commands/context.js";
21
21
  import { loadPrompt } from "./prompt-loader.js";
22
22
  import { buildClaudeRuntimeFloorAdvisory } from "../../shared/claude-runtime-floor.js";
@@ -5,7 +5,7 @@ import { join } from "node:path";
5
5
  import { resolveByName, autoDetect, listTemplates, getTemplateInfo, loadWorkflowTemplate, loadRegistry, isLegacyWorkflowMode, } from "./workflow-templates.js";
6
6
  import { loadPrompt } from "./prompt-loader.js";
7
7
  import { gsdRoot } from "./paths.js";
8
- import { createGitService, runGit } from "./git-service.js";
8
+ import { createGitService, runGit, taskBranchArgs } from "./git-service.js";
9
9
  import { isAutoActive, isAutoPaused } from "./auto.js";
10
10
  import { getErrorMessage } from "./error-utils.js";
11
11
  import { resolvePlugin } from "./workflow-plugins.js";
@@ -349,7 +349,7 @@ export async function handleStart(args, ctx, pi) {
349
349
  git.autoCommit("workflow-template", templateId, []);
350
350
  }
351
351
  catch { /* nothing to commit */ }
352
- runGit(basePath, ["checkout", "-b", branchName]);
352
+ runGit(basePath, taskBranchArgs(basePath, branchName, current, git.getMainBranch()));
353
353
  branchCreated = true;
354
354
  }
355
355
  }
@@ -481,7 +481,7 @@ export function dispatchMarkdownPhasePlugin(plugin, description, ctx, pi) {
481
481
  git.autoCommit("workflow-template", templateId, []);
482
482
  }
483
483
  catch { /* nothing to commit */ }
484
- runGit(basePath, ["checkout", "-b", branchName]);
484
+ runGit(basePath, taskBranchArgs(basePath, branchName, current, git.getMainBranch()));
485
485
  branchCreated = true;
486
486
  }
487
487
  }
@@ -218,7 +218,7 @@ export function getMilestone(id) {
218
218
  export function getActiveMilestoneFromDb() {
219
219
  if (!getDbOrNull())
220
220
  return null;
221
- const row = getDbOrNull().prepare("SELECT * FROM milestones WHERE status NOT IN ('complete', 'done', 'skipped', 'closed', 'parked') ORDER BY id LIMIT 1").get();
221
+ const row = getDbOrNull().prepare(`SELECT * FROM milestones WHERE status NOT IN (${TERMINAL_STATUS_SQL}, 'parked') ORDER BY id LIMIT 1`).get();
222
222
  if (!row)
223
223
  return null;
224
224
  return rowToMilestone(row);
@@ -230,11 +230,11 @@ export function getActiveSliceFromDb(milestoneId) {
230
230
  // Uses json_each() to expand the JSON depends array and checks each dep is complete.
231
231
  const row = getDbOrNull().prepare(`SELECT s.* FROM slices s
232
232
  WHERE s.milestone_id = :mid
233
- AND s.status NOT IN ('complete', 'done', 'skipped')
233
+ AND s.status NOT IN (${TERMINAL_STATUS_SQL})
234
234
  AND NOT EXISTS (
235
235
  SELECT 1 FROM json_each(s.depends) AS dep
236
236
  WHERE dep.value NOT IN (
237
- SELECT id FROM slices WHERE milestone_id = :mid AND status IN ('complete', 'done', 'skipped')
237
+ SELECT id FROM slices WHERE milestone_id = :mid AND status IN (${TERMINAL_STATUS_SQL})
238
238
  )
239
239
  )
240
240
  ORDER BY s.sequence, s.id
@@ -246,7 +246,7 @@ export function getActiveSliceFromDb(milestoneId) {
246
246
  export function getActiveTaskFromDb(milestoneId, sliceId) {
247
247
  if (!getDbOrNull())
248
248
  return null;
249
- const row = getDbOrNull().prepare("SELECT * FROM tasks WHERE milestone_id = :mid AND slice_id = :sid AND status NOT IN ('complete', 'done') ORDER BY sequence, id LIMIT 1").get({ ":mid": milestoneId, ":sid": sliceId });
249
+ const row = getDbOrNull().prepare(`SELECT * FROM tasks WHERE milestone_id = :mid AND slice_id = :sid AND status NOT IN (${TERMINAL_STATUS_SQL}) ORDER BY sequence, id LIMIT 1`).get({ ":mid": milestoneId, ":sid": sliceId });
250
250
  if (!row)
251
251
  return null;
252
252
  return rowToTask(row);
@@ -265,7 +265,7 @@ export function getParallelMonitorSliceProgress(milestoneId) {
265
265
  s.id AS id,
266
266
  s.status AS status,
267
267
  COUNT(t.id) AS total,
268
- COALESCE(SUM(CASE WHEN t.status='complete' THEN 1 ELSE 0 END), 0) AS done
268
+ COALESCE(SUM(CASE WHEN t.status IN (${TERMINAL_STATUS_SQL}) THEN 1 ELSE 0 END), 0) AS done
269
269
  FROM slices s
270
270
  LEFT JOIN tasks t ON s.milestone_id=t.milestone_id AND s.id=t.slice_id
271
271
  WHERE s.milestone_id=:mid
@@ -417,7 +417,7 @@ export function getSliceScopedArtifacts(milestoneId, sliceId) {
417
417
  export function getActiveMilestoneIdFromDb() {
418
418
  if (!getDbOrNull())
419
419
  return null;
420
- const row = getDbOrNull().prepare("SELECT id, status FROM milestones WHERE status NOT IN ('complete', 'done', 'skipped', 'closed', 'parked') ORDER BY id LIMIT 1").get();
420
+ const row = getDbOrNull().prepare(`SELECT id, status FROM milestones WHERE status NOT IN (${TERMINAL_STATUS_SQL}, 'parked') ORDER BY id LIMIT 1`).get();
421
421
  if (!row)
422
422
  return null;
423
423
  return rowToIdStatusSummary(row);
@@ -432,7 +432,7 @@ export function getSliceStatusSummary(milestoneId) {
432
432
  export function getActiveTaskIdFromDb(milestoneId, sliceId) {
433
433
  if (!getDbOrNull())
434
434
  return null;
435
- const row = getDbOrNull().prepare("SELECT id, status, title FROM tasks WHERE milestone_id = :mid AND slice_id = :sid AND status NOT IN ('complete', 'done') ORDER BY sequence, id LIMIT 1").get({ ":mid": milestoneId, ":sid": sliceId });
435
+ const row = getDbOrNull().prepare(`SELECT id, status, title FROM tasks WHERE milestone_id = :mid AND slice_id = :sid AND status NOT IN (${TERMINAL_STATUS_SQL}) ORDER BY sequence, id LIMIT 1`).get({ ":mid": milestoneId, ":sid": sliceId });
436
436
  if (!row)
437
437
  return null;
438
438
  return rowToActiveTaskSummary(row);
@@ -443,8 +443,8 @@ export function getSliceTaskCounts(milestoneId, sliceId) {
443
443
  return emptyTaskStatusCounts();
444
444
  const row = getDbOrNull().prepare(`SELECT
445
445
  COUNT(*) as total,
446
- SUM(CASE WHEN status IN ('complete', 'done') THEN 1 ELSE 0 END) as done,
447
- SUM(CASE WHEN status NOT IN ('complete', 'done') THEN 1 ELSE 0 END) as pending
446
+ SUM(CASE WHEN status IN (${TERMINAL_STATUS_SQL}) THEN 1 ELSE 0 END) as done,
447
+ SUM(CASE WHEN status NOT IN (${TERMINAL_STATUS_SQL}) THEN 1 ELSE 0 END) as pending
448
448
  FROM tasks WHERE milestone_id = :mid AND slice_id = :sid`).get({ ":mid": milestoneId, ":sid": sliceId });
449
449
  return rowToTaskStatusCounts(row);
450
450
  }
@@ -3,7 +3,7 @@ import { isAbsolute, join, relative, sep } from "node:path";
3
3
  import { deleteArtifactByPath, getAllMilestones, getMilestoneSlices, getSliceTasks, isDbAvailable, isMemoriesFtsAvailable, repairTaskCompletionFromSummary, _getAdapter, } from "./gsd-db.js";
4
4
  import { MEMORIES_FTS_REBUILT_KEY } from "./db-memory-fts-schema.js";
5
5
  import { isAfter, latestExplicitReopenAt } from "./milestone-reopen-events.js";
6
- import { gsdProjectionRoot, gsdRoot, resolveGsdPathContract, resolveMilestoneFile, resolveSliceFile } from "./paths.js";
6
+ import { buildTaskFileName, gsdProjectionRoot, gsdRoot, resolveGsdPathContract, resolveMilestoneFile, resolveMilestonePath, resolveSliceFile, } from "./paths.js";
7
7
  import { deriveState } from "./state.js";
8
8
  import { isClosedStatus } from "./status-guards.js";
9
9
  import { workflowEventLogPath } from "./workflow-event-ledger.js";
@@ -12,6 +12,7 @@ import { flushWorkflowProjections } from "./projection-flush.js";
12
12
  import { parseRoadmapSlices } from "./roadmap-slices.js";
13
13
  import { parsePlan } from "./parsers-legacy.js";
14
14
  import { parseSummary } from "./files.js";
15
+ import { LAYOUT_SEGMENTS } from "./layout-policy.js";
15
16
  const USER_AUTHORED_ARTIFACT_TYPES = new Set(["CONTEXT", "RESEARCH"]);
16
17
  function escapeRegExp(value) {
17
18
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -113,10 +114,10 @@ function isClearedByMilestoneShellProjectionFlush(basePath, issue, reRenderedMil
113
114
  return false;
114
115
  return issue.file === relativeFile(basePath, roadmapPath);
115
116
  }
116
- function artifactExistsOnDisk(basePath, artifactPath) {
117
- return resolveArtifactDiskPath(basePath, artifactPath) !== null;
117
+ function artifactExistsOnDisk(basePath, artifactPath, row) {
118
+ return resolveArtifactDiskPath(basePath, artifactPath, row) !== null;
118
119
  }
119
- function resolveArtifactDiskPath(basePath, artifactPath) {
120
+ function resolveLiteralArtifactDiskPath(basePath, artifactPath) {
120
121
  const relativeArtifactPath = artifactPathRelativeToGsd(artifactPath);
121
122
  if (isAbsolute(relativeArtifactPath)) {
122
123
  return existsSync(relativeArtifactPath) ? relativeArtifactPath : null;
@@ -128,6 +129,48 @@ function resolveArtifactDiskPath(basePath, artifactPath) {
128
129
  }
129
130
  return null;
130
131
  }
132
+ function relativeToGsdRoot(basePath, diskPath) {
133
+ for (const root of [gsdProjectionRoot(basePath), gsdRoot(basePath)]) {
134
+ if (!isPathInside(root, diskPath))
135
+ continue;
136
+ return relative(root, diskPath).split("\\").join("/");
137
+ }
138
+ return null;
139
+ }
140
+ function isFlatPhaseDiskPath(basePath, diskPath) {
141
+ return relativeToGsdRoot(basePath, diskPath)?.startsWith(`${LAYOUT_SEGMENTS.level1}/`) ?? false;
142
+ }
143
+ function hasFlatPhaseLayout(basePath) {
144
+ return existsSync(join(gsdProjectionRoot(basePath), LAYOUT_SEGMENTS.level1));
145
+ }
146
+ function resolveFlatPhaseArtifactDiskPath(basePath, row) {
147
+ if (!hasFlatPhaseLayout(basePath))
148
+ return null;
149
+ if (!row.milestone_id)
150
+ return null;
151
+ const artifactType = normalizedArtifactType(row.artifact_type);
152
+ if (!artifactType)
153
+ return null;
154
+ if (row.slice_id && row.task_id) {
155
+ const phaseDir = resolveMilestonePath(basePath, row.milestone_id);
156
+ if (!phaseDir || !isFlatPhaseDiskPath(basePath, phaseDir))
157
+ return null;
158
+ const candidate = join(phaseDir, buildTaskFileName(row.task_id, artifactType));
159
+ return existsSync(candidate) ? candidate : null;
160
+ }
161
+ const candidate = row.slice_id
162
+ ? resolveSliceFile(basePath, row.milestone_id, row.slice_id, artifactType)
163
+ : resolveMilestoneFile(basePath, row.milestone_id, artifactType);
164
+ return candidate && isFlatPhaseDiskPath(basePath, candidate) ? candidate : null;
165
+ }
166
+ function resolveArtifactDiskPath(basePath, artifactPath, row) {
167
+ if (row && isMilestonesArtifactPath(row.path)) {
168
+ const flatPath = resolveFlatPhaseArtifactDiskPath(basePath, row);
169
+ if (flatPath)
170
+ return flatPath;
171
+ }
172
+ return resolveLiteralArtifactDiskPath(basePath, artifactPath);
173
+ }
131
174
  function taskExistsInPlan(basePath, milestoneId, sliceId, taskId) {
132
175
  const planPath = resolveSliceFile(basePath, milestoneId, sliceId, "PLAN");
133
176
  if (!planPath || !existsSync(planPath))
@@ -156,7 +199,7 @@ function readTaskCompletionEvidenceFromSummary(basePath, row) {
156
199
  if (!row.task_status && Number(row.task_count) > 0 && !taskExistsInPlan(basePath, row.milestone_id, row.slice_id, row.task_id)) {
157
200
  return null;
158
201
  }
159
- const diskPath = resolveArtifactDiskPath(basePath, row.path);
202
+ const diskPath = resolveArtifactDiskPath(basePath, row.path, { ...row, artifact_type: "SUMMARY" });
160
203
  if (!diskPath)
161
204
  return null;
162
205
  try {
@@ -256,6 +299,43 @@ function hasPresentMilestonesReplacement(basePath, row, artifactRows) {
256
299
  sameArtifactIdentity(row, other) &&
257
300
  artifactExistsOnDisk(basePath, other.path));
258
301
  }
302
+ function hasPresentFlatPhaseReplacement(basePath, row, artifactRows) {
303
+ if (resolveFlatPhaseArtifactDiskPath(basePath, row))
304
+ return true;
305
+ return artifactRows.some((other) => other.path !== row.path &&
306
+ !isMilestonesArtifactPath(other.path) &&
307
+ sameArtifactIdentity(row, other) &&
308
+ artifactExistsOnDisk(basePath, other.path, other));
309
+ }
310
+ function hasNoFlatPhaseFileEquivalent(row) {
311
+ const artifactType = normalizedArtifactType(row.artifact_type);
312
+ if (row.slice_id && row.task_id) {
313
+ return artifactType === "PLAN" || artifactType === "SUMMARY";
314
+ }
315
+ return Boolean(row.slice_id && !row.task_id && artifactType === "SUMMARY");
316
+ }
317
+ function staleMilestonesArtifactRowFixable(basePath, row, artifactRows) {
318
+ if (!isMilestonesArtifactPath(row.path))
319
+ return false;
320
+ if (!hasFlatPhaseLayout(basePath))
321
+ return false;
322
+ if (hasPresentFlatPhaseReplacement(basePath, row, artifactRows))
323
+ return true;
324
+ return hasNoFlatPhaseFileEquivalent(row) && !resolveLiteralArtifactDiskPath(basePath, row.path);
325
+ }
326
+ function stalePhasesArtifactRowFixable(basePath, row, artifactRows) {
327
+ const issuePath = artifactPathRelativeToGsd(row.path);
328
+ return issuePath.startsWith(`${LAYOUT_SEGMENTS.level1}/`) && hasPresentMilestonesReplacement(basePath, row, artifactRows);
329
+ }
330
+ function staleArtifactRowFixable(basePath, row, artifactRows) {
331
+ return staleMilestonesArtifactRowFixable(basePath, row, artifactRows) ||
332
+ stalePhasesArtifactRowFixable(basePath, row, artifactRows);
333
+ }
334
+ function staleArtifactPruneMessage(row) {
335
+ return isMilestonesArtifactPath(row.path)
336
+ ? `pruned stale legacy artifact row ${row.path}`
337
+ : `pruned stale flat-phase artifact row ${row.path}`;
338
+ }
259
339
  export async function checkEngineHealth(basePath, issues, fixesApplied, options) {
260
340
  const dbPath = resolveGsdPathContract(basePath).projectDb;
261
341
  if (!isDbAvailable() && existsSync(dbPath)) {
@@ -456,18 +536,20 @@ export async function checkEngineHealth(basePath, issues, fixesApplied, options)
456
536
  ORDER BY path`)
457
537
  .all();
458
538
  for (const row of artifactRows) {
459
- if (artifactExistsOnDisk(basePath, row.path))
460
- continue;
461
539
  const unitId = artifactUnitId(row);
462
540
  const issuePath = artifactPathRelativeToGsd(row.path);
463
- if (options?.repair && issuePath.startsWith("phases/") && hasPresentMilestonesReplacement(basePath, row, artifactRows)) {
541
+ if (artifactExistsOnDisk(basePath, row.path))
542
+ continue;
543
+ if (options?.repair && staleArtifactRowFixable(basePath, row, artifactRows)) {
464
544
  // Route the write through the Single Writer owner (gsd-db.ts) instead
465
545
  // of issuing raw DELETE SQL here — doctor is a read-only consumer and
466
546
  // the single-writer invariant forbids write SQL outside the allowlist.
467
547
  deleteArtifactByPath(row.path);
468
- fixesApplied.push(`pruned stale flat-phase artifact row ${row.path}`);
548
+ fixesApplied.push(staleArtifactPruneMessage(row));
469
549
  continue;
470
550
  }
551
+ if (artifactExistsOnDisk(basePath, row.path, row))
552
+ continue;
471
553
  if (isUserAuthoredArtifactType(row.artifact_type)) {
472
554
  const artifactType = normalizedArtifactType(row.artifact_type);
473
555
  missingUserContentArtifacts.push({ path: issuePath, artifactType });
@@ -489,7 +571,7 @@ export async function checkEngineHealth(basePath, issues, fixesApplied, options)
489
571
  unitId,
490
572
  message: `Artifact ${issuePath} is recorded in the database as ${row.artifact_type || "UNKNOWN"} but no matching file exists on disk`,
491
573
  file: issuePath,
492
- fixable: issuePath.startsWith("phases/") && hasPresentMilestonesReplacement(basePath, row, artifactRows),
574
+ fixable: staleArtifactRowFixable(basePath, row, artifactRows),
493
575
  });
494
576
  }
495
577
  }
@@ -518,7 +600,7 @@ export async function checkEngineHealth(basePath, issues, fixesApplied, options)
518
600
  .all();
519
601
  const seen = new Set();
520
602
  for (const row of rows) {
521
- if (!artifactExistsOnDisk(basePath, row.path))
603
+ if (!artifactExistsOnDisk(basePath, row.path, row))
522
604
  continue;
523
605
  const reopenAt = latestExplicitReopenAt(basePath, row.milestone_id);
524
606
  if (!isAfter(row.imported_at, reopenAt))
@@ -4,7 +4,7 @@
4
4
  import { cpSync, existsSync, mkdirSync, readdirSync, renameSync, rmSync, statSync } from "node:fs";
5
5
  import { join } from "node:path";
6
6
  import { renderAllFromDb, renderRoadmapFromDb } from "./markdown-renderer.js";
7
- import { getAllMilestones, getMilestoneSlices } from "./gsd-db.js";
7
+ import { deleteArtifactsByPathPrefix, getAllMilestones, getMilestoneSlices } from "./gsd-db.js";
8
8
  import { migrateFromMarkdown } from "./md-importer.js";
9
9
  import { countDbHierarchy } from "./migration-auto-check.js";
10
10
  import { logWarning } from "./workflow-logger.js";
@@ -163,7 +163,8 @@ export function pruneStaleFlatPhaseBackups(basePath) {
163
163
  * 2. Rename .gsd/milestones/ aside so path resolvers target phases/
164
164
  * 3. Render flat-phase from the DB (which already has the data)
165
165
  * 4. Verify counts match
166
- * 5. Remove the renamed legacy tree
166
+ * 5. Prune legacy milestones/ artifact rows
167
+ * 6. Remove the renamed legacy tree
167
168
  *
168
169
  * Idempotent: if .gsd/milestones/ doesn't exist, returns immediately.
169
170
  */
@@ -262,7 +263,17 @@ export async function migrateToFlatPhase(basePath) {
262
263
  rollbackPartialMigration(basePath, backupDir, migratingPath);
263
264
  throw new Error("flat-phase migration verification failed: no artifacts rendered");
264
265
  }
265
- // 6. Verified — remove the renamed legacy tree (backup already on disk).
266
+ // 6. Verified — prune legacy artifact rows now that renderAllFromDb has
267
+ // re-inserted flat-phase rows for artifacts that still have files.
268
+ try {
269
+ deleteArtifactsByPathPrefix("milestones/");
270
+ }
271
+ catch (err) {
272
+ logWarning("migration", `flat-phase migration could not prune legacy artifact rows: ${err.message}`);
273
+ rollbackPartialMigration(basePath, backupDir, migratingPath);
274
+ throw err;
275
+ }
276
+ // 7. Remove the renamed legacy tree (backup already on disk).
266
277
  try {
267
278
  removePathWithRetries(migratingPath);
268
279
  }
@@ -24,7 +24,7 @@ import { loadPrompt } from "./prompt-loader.js";
24
24
  import { gsdRoot } from "./paths.js";
25
25
  import { isDbAvailable, getHierarchyCompletionCounts } from "./gsd-db.js";
26
26
  import { formatDuration } from "../shared/format-utils.js";
27
- import { getAutoWorktreePath } from "./auto-worktree.js";
27
+ import { getAutoWorktreePath } from "./auto-worktree-path-resolution.js";
28
28
  import { clearGSDPreferencesCache, loadEffectiveGSDPreferences, loadGlobalGSDPreferences, getGlobalGSDPreferencesPath } from "./preferences.js";
29
29
  import { showNextAction } from "../shared/tui.js";
30
30
  import { isInteractiveCommandContext, notifyForensicsNeedsInteractiveMenu, } from "./command-feedback.js";
@@ -2,8 +2,8 @@
2
2
  // File Purpose: Shared auto-resolution of safe merge conflict paths.
3
3
  import { logWarning } from "./workflow-logger.js";
4
4
  import { nativeAddPaths, nativeCheckoutTheirs, nativeRmForce, } from "./native-git-bridge.js";
5
- import { isSafeToAutoResolve } from "./auto-worktree.js";
6
- export { isSafeToAutoResolve } from "./auto-worktree.js";
5
+ import { isSafeToAutoResolve } from "./auto-worktree-conflict-auto-resolve.js";
6
+ export { isSafeToAutoResolve } from "./auto-worktree-conflict-auto-resolve.js";
7
7
  /**
8
8
  * Auto-resolve paths that are safe to accept from the milestone side
9
9
  * (.gsd/ state and build artifacts).