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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (334) hide show
  1. package/README.md +10 -10
  2. package/dist/resources/.managed-resources-content-hash +1 -1
  3. package/dist/resources/extensions/ask-user-questions.js +1 -1
  4. package/dist/resources/extensions/gsd/auto/closeout.js +97 -5
  5. package/dist/resources/extensions/gsd/auto/orchestrator.js +4 -3
  6. package/dist/resources/extensions/gsd/auto/pre-dispatch.js +6 -3
  7. package/dist/resources/extensions/gsd/auto/unit-phase.js +5 -9
  8. package/dist/resources/extensions/gsd/auto-artifact-paths.js +12 -3
  9. package/dist/resources/extensions/gsd/auto-direct-dispatch.js +11 -7
  10. package/dist/resources/extensions/gsd/auto-dispatch.js +16 -2
  11. package/dist/resources/extensions/gsd/auto-recovery.js +50 -6
  12. package/dist/resources/extensions/gsd/auto-start.js +4 -3
  13. package/dist/resources/extensions/gsd/auto-worktree-branch-lifecycle.js +189 -0
  14. package/dist/resources/extensions/gsd/auto-worktree-cleanup.js +133 -0
  15. package/dist/resources/extensions/gsd/auto-worktree-conflict-auto-resolve.js +35 -0
  16. package/dist/resources/extensions/gsd/auto-worktree-creation.js +130 -0
  17. package/dist/resources/extensions/gsd/auto-worktree-entry.js +118 -0
  18. package/dist/resources/extensions/gsd/auto-worktree-merge-already-merged.js +98 -0
  19. package/dist/resources/extensions/gsd/auto-worktree-merge-branch-head.js +52 -0
  20. package/dist/resources/extensions/gsd/auto-worktree-merge-cleanup.js +87 -0
  21. package/dist/resources/extensions/gsd/auto-worktree-merge-code-changes.js +46 -0
  22. package/dist/resources/extensions/gsd/auto-worktree-merge-db-ready.js +68 -0
  23. package/dist/resources/extensions/gsd/auto-worktree-merge-integration-branch.js +51 -0
  24. package/dist/resources/extensions/gsd/auto-worktree-merge-message.js +106 -0
  25. package/dist/resources/extensions/gsd/auto-worktree-merge-pre-teardown.js +64 -0
  26. package/dist/resources/extensions/gsd/auto-worktree-merge-stash.js +168 -0
  27. package/dist/resources/extensions/gsd/auto-worktree-merge.js +302 -0
  28. package/dist/resources/extensions/gsd/auto-worktree-milestone-shelter.js +119 -0
  29. package/dist/resources/extensions/gsd/auto-worktree-path-resolution.js +5 -0
  30. package/dist/resources/extensions/gsd/auto-worktree-path-utils.js +5 -0
  31. package/dist/resources/extensions/gsd/auto-worktree-resource-version.js +41 -0
  32. package/dist/resources/extensions/gsd/auto-worktree-runtime-cleanup.js +99 -0
  33. package/dist/resources/extensions/gsd/auto-worktree-session-registry.js +57 -0
  34. package/dist/resources/extensions/gsd/auto-worktree-sync.js +249 -0
  35. package/dist/resources/extensions/gsd/auto-worktree-teardown.js +120 -0
  36. package/dist/resources/extensions/gsd/auto-worktree.js +20 -2073
  37. package/dist/resources/extensions/gsd/auto.js +9 -3
  38. package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +1 -1
  39. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +20 -2
  40. package/dist/resources/extensions/gsd/bootstrap/system-context.js +1 -1
  41. package/dist/resources/extensions/gsd/bootstrap/tool-call-loop-guard.js +185 -27
  42. package/dist/resources/extensions/gsd/codebase-generator.js +7 -1
  43. package/dist/resources/extensions/gsd/commands-handlers.js +1 -1
  44. package/dist/resources/extensions/gsd/commands-workflow-templates.js +3 -3
  45. package/dist/resources/extensions/gsd/db/queries.js +9 -9
  46. package/dist/resources/extensions/gsd/db/writers/cascades.js +20 -2
  47. package/dist/resources/extensions/gsd/doctor-engine-checks.js +93 -11
  48. package/dist/resources/extensions/gsd/flat-phase-migration.js +32 -3
  49. package/dist/resources/extensions/gsd/forensics.js +1 -1
  50. package/dist/resources/extensions/gsd/git-conflict-resolve.js +2 -2
  51. package/dist/resources/extensions/gsd/git-service.js +45 -0
  52. package/dist/resources/extensions/gsd/gsd-db.js +23 -11
  53. package/dist/resources/extensions/gsd/guided-flow.js +22 -17
  54. package/dist/resources/extensions/gsd/markdown-renderer.js +15 -3
  55. package/dist/resources/extensions/gsd/md-importer.js +89 -23
  56. package/dist/resources/extensions/gsd/migration-auto-check.js +30 -3
  57. package/dist/resources/extensions/gsd/milestone-merge-transaction.js +11 -0
  58. package/dist/resources/extensions/gsd/milestone-settlement.js +1 -1
  59. package/dist/resources/extensions/gsd/parallel-merge.js +1 -1
  60. package/dist/resources/extensions/gsd/parallel-orchestrator.js +3 -1
  61. package/dist/resources/extensions/gsd/paths.js +17 -3
  62. package/dist/resources/extensions/gsd/preferences-skills.js +10 -16
  63. package/dist/resources/extensions/gsd/preferences-types.js +1 -0
  64. package/dist/resources/extensions/gsd/preferences-validation.js +82 -0
  65. package/dist/resources/extensions/gsd/preferences.js +20 -0
  66. package/dist/resources/extensions/gsd/prompts/system.md +6 -6
  67. package/dist/resources/extensions/gsd/quick.js +12 -3
  68. package/dist/resources/extensions/gsd/skill-discovery.js +36 -23
  69. package/dist/resources/extensions/gsd/slice-parallel-orchestrator.js +2 -1
  70. package/dist/resources/extensions/gsd/state/derive/from-db.js +88 -153
  71. package/dist/resources/extensions/gsd/tool-contract.js +14 -0
  72. package/dist/resources/extensions/gsd/tools/reopen-slice.js +5 -0
  73. package/dist/resources/extensions/gsd/tools/reopen-task.js +13 -5
  74. package/dist/resources/extensions/gsd/unit-registry.js +12 -2
  75. package/dist/resources/extensions/gsd/unmerged-milestone-guard.js +1 -1
  76. package/dist/resources/extensions/gsd/validation-block-guard.js +2 -1
  77. package/dist/resources/extensions/gsd/workspace-git-preflight.js +2 -2
  78. package/dist/resources/extensions/gsd/worktree-command.js +1 -1
  79. package/dist/resources/extensions/gsd/worktree-lifecycle.js +14 -9
  80. package/dist/resources/extensions/gsd/worktree-reentry.js +2 -1
  81. package/dist/resources/extensions/shared/interview-ui.js +62 -9
  82. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  83. package/dist/web/standalone/.next/BUILD_ID +1 -1
  84. package/dist/web/standalone/.next/app-path-routes-manifest.json +8 -8
  85. package/dist/web/standalone/.next/build-manifest.json +2 -2
  86. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  87. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  88. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  89. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  90. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  91. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  92. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  93. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  94. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  95. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  96. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  97. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  98. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  99. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  100. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  101. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  102. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  103. package/dist/web/standalone/.next/server/app/index.html +1 -1
  104. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  105. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  106. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  107. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  108. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  109. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  110. package/dist/web/standalone/.next/server/app-paths-manifest.json +8 -8
  111. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  112. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  113. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  114. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  115. package/dist/web/standalone/node_modules/@gsd/native/package.json +1 -1
  116. package/dist/web/standalone/node_modules/node-pty/build/Makefile +1 -1
  117. package/integrations/hermes/open_gsd_hermes/gsd_client.py +24 -35
  118. package/integrations/hermes/tests/test_gsd_client_timeout.py +4 -4
  119. package/package.json +6 -6
  120. package/packages/cloud-mcp-gateway/package.json +2 -2
  121. package/packages/contracts/package.json +1 -1
  122. package/packages/daemon/package.json +4 -4
  123. package/packages/gsd-agent-core/dist/export-html/template.js +53 -4
  124. package/packages/gsd-agent-core/package.json +5 -5
  125. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  126. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js +3 -2
  127. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  128. package/packages/gsd-agent-modes/package.json +7 -7
  129. package/packages/mcp-server/package.json +4 -4
  130. package/packages/native/package.json +1 -1
  131. package/packages/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
  132. package/packages/pi-agent-core/dist/agent-loop.js +14 -9
  133. package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
  134. package/packages/pi-agent-core/package.json +1 -1
  135. package/packages/pi-ai/dist/index.d.ts +1 -0
  136. package/packages/pi-ai/dist/index.d.ts.map +1 -1
  137. package/packages/pi-ai/dist/index.js +1 -0
  138. package/packages/pi-ai/dist/index.js.map +1 -1
  139. package/packages/pi-ai/dist/providers/transform-messages.d.ts.map +1 -1
  140. package/packages/pi-ai/dist/providers/transform-messages.js +7 -3
  141. package/packages/pi-ai/dist/providers/transform-messages.js.map +1 -1
  142. package/packages/pi-ai/dist/tool-result-content.d.ts +3 -0
  143. package/packages/pi-ai/dist/tool-result-content.d.ts.map +1 -0
  144. package/packages/pi-ai/dist/tool-result-content.js +62 -0
  145. package/packages/pi-ai/dist/tool-result-content.js.map +1 -0
  146. package/packages/pi-ai/dist/types.d.ts +8 -20
  147. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  148. package/packages/pi-ai/dist/types.js.map +1 -1
  149. package/packages/pi-ai/package.json +1 -1
  150. package/packages/pi-coding-agent/dist/core/package-manager.d.ts +9 -0
  151. package/packages/pi-coding-agent/dist/core/package-manager.d.ts.map +1 -1
  152. package/packages/pi-coding-agent/dist/core/package-manager.js +74 -53
  153. package/packages/pi-coding-agent/dist/core/package-manager.js.map +1 -1
  154. package/packages/pi-coding-agent/dist/core/resource-loader.d.ts +6 -0
  155. package/packages/pi-coding-agent/dist/core/resource-loader.d.ts.map +1 -1
  156. package/packages/pi-coding-agent/dist/core/resource-loader.js +30 -2
  157. package/packages/pi-coding-agent/dist/core/resource-loader.js.map +1 -1
  158. package/packages/pi-coding-agent/dist/core/skill-directories.d.ts +66 -0
  159. package/packages/pi-coding-agent/dist/core/skill-directories.d.ts.map +1 -0
  160. package/packages/pi-coding-agent/dist/core/skill-directories.js +138 -0
  161. package/packages/pi-coding-agent/dist/core/skill-directories.js.map +1 -0
  162. package/packages/pi-coding-agent/dist/core/skills.d.ts +4 -1
  163. package/packages/pi-coding-agent/dist/core/skills.d.ts.map +1 -1
  164. package/packages/pi-coding-agent/dist/core/skills.js +4 -1
  165. package/packages/pi-coding-agent/dist/core/skills.js.map +1 -1
  166. package/packages/pi-coding-agent/dist/index.d.ts +1 -0
  167. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  168. package/packages/pi-coding-agent/dist/index.js +1 -0
  169. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  170. package/packages/pi-coding-agent/package.json +7 -7
  171. package/packages/pi-tui/package.json +2 -2
  172. package/packages/rpc-client/package.json +2 -2
  173. package/pkg/dist/core/export-html/template.js +53 -4
  174. package/pkg/package.json +1 -1
  175. package/src/resources/extensions/ask-user-questions.ts +1 -1
  176. package/src/resources/extensions/gsd/auto/closeout.ts +114 -7
  177. package/src/resources/extensions/gsd/auto/orchestrator.ts +4 -3
  178. package/src/resources/extensions/gsd/auto/pre-dispatch.ts +6 -3
  179. package/src/resources/extensions/gsd/auto/unit-phase.ts +5 -9
  180. package/src/resources/extensions/gsd/auto-artifact-paths.ts +12 -3
  181. package/src/resources/extensions/gsd/auto-direct-dispatch.ts +13 -7
  182. package/src/resources/extensions/gsd/auto-dispatch.ts +18 -2
  183. package/src/resources/extensions/gsd/auto-recovery.ts +50 -6
  184. package/src/resources/extensions/gsd/auto-start.ts +5 -3
  185. package/src/resources/extensions/gsd/auto-worktree-branch-lifecycle.ts +237 -0
  186. package/src/resources/extensions/gsd/auto-worktree-cleanup.ts +166 -0
  187. package/src/resources/extensions/gsd/auto-worktree-conflict-auto-resolve.ts +37 -0
  188. package/src/resources/extensions/gsd/auto-worktree-creation.ts +171 -0
  189. package/src/resources/extensions/gsd/auto-worktree-entry.ts +154 -0
  190. package/src/resources/extensions/gsd/auto-worktree-merge-already-merged.ts +161 -0
  191. package/src/resources/extensions/gsd/auto-worktree-merge-branch-head.ts +83 -0
  192. package/src/resources/extensions/gsd/auto-worktree-merge-cleanup.ts +132 -0
  193. package/src/resources/extensions/gsd/auto-worktree-merge-code-changes.ts +71 -0
  194. package/src/resources/extensions/gsd/auto-worktree-merge-db-ready.ts +112 -0
  195. package/src/resources/extensions/gsd/auto-worktree-merge-integration-branch.ts +99 -0
  196. package/src/resources/extensions/gsd/auto-worktree-merge-message.ts +166 -0
  197. package/src/resources/extensions/gsd/auto-worktree-merge-pre-teardown.ts +93 -0
  198. package/src/resources/extensions/gsd/auto-worktree-merge-stash.ts +213 -0
  199. package/src/resources/extensions/gsd/auto-worktree-merge.ts +391 -0
  200. package/src/resources/extensions/gsd/auto-worktree-milestone-shelter.ts +159 -0
  201. package/src/resources/extensions/gsd/auto-worktree-path-resolution.ts +6 -0
  202. package/src/resources/extensions/gsd/auto-worktree-path-utils.ts +10 -0
  203. package/src/resources/extensions/gsd/auto-worktree-resource-version.ts +44 -0
  204. package/src/resources/extensions/gsd/auto-worktree-runtime-cleanup.ts +119 -0
  205. package/src/resources/extensions/gsd/auto-worktree-session-registry.ts +71 -0
  206. package/src/resources/extensions/gsd/auto-worktree-sync.ts +370 -0
  207. package/src/resources/extensions/gsd/auto-worktree-teardown.ts +161 -0
  208. package/src/resources/extensions/gsd/auto-worktree.ts +69 -2429
  209. package/src/resources/extensions/gsd/auto.ts +11 -17
  210. package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +1 -1
  211. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +20 -2
  212. package/src/resources/extensions/gsd/bootstrap/system-context.ts +1 -1
  213. package/src/resources/extensions/gsd/bootstrap/tool-call-loop-guard.ts +218 -27
  214. package/src/resources/extensions/gsd/codebase-generator.ts +7 -1
  215. package/src/resources/extensions/gsd/commands-handlers.ts +1 -1
  216. package/src/resources/extensions/gsd/commands-workflow-templates.ts +3 -3
  217. package/src/resources/extensions/gsd/db/queries.ts +9 -9
  218. package/src/resources/extensions/gsd/db/writers/cascades.ts +23 -1
  219. package/src/resources/extensions/gsd/doctor-engine-checks.ts +109 -10
  220. package/src/resources/extensions/gsd/flat-phase-migration.ts +34 -3
  221. package/src/resources/extensions/gsd/forensics.ts +1 -1
  222. package/src/resources/extensions/gsd/git-conflict-resolve.ts +2 -2
  223. package/src/resources/extensions/gsd/git-service.ts +53 -0
  224. package/src/resources/extensions/gsd/gsd-db.ts +33 -11
  225. package/src/resources/extensions/gsd/guided-flow.ts +26 -18
  226. package/src/resources/extensions/gsd/markdown-renderer.ts +15 -3
  227. package/src/resources/extensions/gsd/md-importer.ts +93 -22
  228. package/src/resources/extensions/gsd/migration-auto-check.ts +32 -3
  229. package/src/resources/extensions/gsd/milestone-merge-transaction.ts +15 -0
  230. package/src/resources/extensions/gsd/milestone-settlement.ts +1 -1
  231. package/src/resources/extensions/gsd/parallel-merge.ts +1 -1
  232. package/src/resources/extensions/gsd/parallel-orchestrator.ts +3 -1
  233. package/src/resources/extensions/gsd/paths.ts +21 -3
  234. package/src/resources/extensions/gsd/preferences-skills.ts +10 -16
  235. package/src/resources/extensions/gsd/preferences-types.ts +30 -0
  236. package/src/resources/extensions/gsd/preferences-validation.ts +69 -0
  237. package/src/resources/extensions/gsd/preferences.ts +20 -0
  238. package/src/resources/extensions/gsd/prompts/system.md +6 -6
  239. package/src/resources/extensions/gsd/quick.ts +11 -3
  240. package/src/resources/extensions/gsd/skill-discovery.ts +47 -23
  241. package/src/resources/extensions/gsd/slice-parallel-orchestrator.ts +3 -1
  242. package/src/resources/extensions/gsd/state/derive/from-db.ts +201 -157
  243. package/src/resources/extensions/gsd/tests/auto-artifact-paths.test.ts +169 -1
  244. package/src/resources/extensions/gsd/tests/auto-loop-no-copy-artifacts.test.ts +2 -1
  245. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +57 -0
  246. package/src/resources/extensions/gsd/tests/auto-start-clean-runtime-db-gated.test.ts +1 -1
  247. package/src/resources/extensions/gsd/tests/auto-warning-noise-regression.test.ts +2 -1
  248. package/src/resources/extensions/gsd/tests/auto-worktree-auto-resolve.test.ts +1 -1
  249. package/src/resources/extensions/gsd/tests/auto-worktree-merge-already-merged.test.ts +177 -0
  250. package/src/resources/extensions/gsd/tests/auto-worktree-merge-branch-head.test.ts +118 -0
  251. package/src/resources/extensions/gsd/tests/auto-worktree-merge-cleanup.test.ts +115 -0
  252. package/src/resources/extensions/gsd/tests/auto-worktree-merge-code-changes.test.ts +76 -0
  253. package/src/resources/extensions/gsd/tests/auto-worktree-merge-db-ready.test.ts +140 -0
  254. package/src/resources/extensions/gsd/tests/auto-worktree-merge-integration-branch.test.ts +123 -0
  255. package/src/resources/extensions/gsd/tests/auto-worktree-merge-message.test.ts +114 -0
  256. package/src/resources/extensions/gsd/tests/auto-worktree-merge-pre-teardown.test.ts +84 -0
  257. package/src/resources/extensions/gsd/tests/auto-worktree-merge-stash.test.ts +70 -0
  258. package/src/resources/extensions/gsd/tests/auto-worktree-milestone-shelter.test.ts +52 -0
  259. package/src/resources/extensions/gsd/tests/auto-worktree-registry.test.ts +6 -6
  260. package/src/resources/extensions/gsd/tests/auto-worktree-resource-version.test.ts +59 -0
  261. package/src/resources/extensions/gsd/tests/auto-worktree-untracked-content.test.ts +3 -1
  262. package/src/resources/extensions/gsd/tests/codebase-generator.test.ts +22 -0
  263. package/src/resources/extensions/gsd/tests/completed-units-metrics-sync.test.ts +1 -1
  264. package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +4 -0
  265. package/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts +174 -0
  266. package/src/resources/extensions/gsd/tests/fast-forward-reused-milestone-branch.test.ts +1 -1
  267. package/src/resources/extensions/gsd/tests/flat-phase-migration.test.ts +71 -1
  268. package/src/resources/extensions/gsd/tests/flat-phase-renderer.test.ts +25 -1
  269. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +40 -0
  270. package/src/resources/extensions/gsd/tests/import-completion-lost-update.test.ts +196 -0
  271. package/src/resources/extensions/gsd/tests/integration/all-milestones-complete-merge.test.ts +4 -6
  272. package/src/resources/extensions/gsd/tests/integration/auto-stash-merge.test.ts +2 -1
  273. package/src/resources/extensions/gsd/tests/integration/auto-worktree-milestone-merge.test.ts +3 -3
  274. package/src/resources/extensions/gsd/tests/integration/auto-worktree.test.ts +7 -8
  275. package/src/resources/extensions/gsd/tests/integration/feature-branch-lifecycle-integration.test.ts +2 -5
  276. package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +76 -0
  277. package/src/resources/extensions/gsd/tests/integration/merge-cwd-restore.test.ts +1 -1
  278. package/src/resources/extensions/gsd/tests/integration/merge-preserve-worktree-on-preteardown-error.test.ts +1 -1
  279. package/src/resources/extensions/gsd/tests/integration/merge-strategy-regular.test.ts +2 -1
  280. package/src/resources/extensions/gsd/tests/integration/milestone-transition-worktree.test.ts +5 -7
  281. package/src/resources/extensions/gsd/tests/integration/workspace-collapse-integration.test.ts +2 -4
  282. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +16 -15
  283. package/src/resources/extensions/gsd/tests/merge-closeout-consistency-gate.test.ts +1 -1
  284. package/src/resources/extensions/gsd/tests/merge-db-cycle.test.ts +1 -1
  285. package/src/resources/extensions/gsd/tests/merge-self-branch-guard.test.ts +1 -1
  286. package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +91 -0
  287. package/src/resources/extensions/gsd/tests/milestone-merge-stash-restore.test.ts +439 -0
  288. package/src/resources/extensions/gsd/tests/milestone-merge-transaction.test.ts +24 -0
  289. package/src/resources/extensions/gsd/tests/none-mode-gates.test.ts +1 -1
  290. package/src/resources/extensions/gsd/tests/originalbase-path-comparison.test.ts +3 -3
  291. package/src/resources/extensions/gsd/tests/parallel-worker-lock-contention.test.ts +1 -1
  292. package/src/resources/extensions/gsd/tests/preferences-worktree-sync.test.ts +1 -4
  293. package/src/resources/extensions/gsd/tests/preferences.test.ts +19 -0
  294. package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +5 -0
  295. package/src/resources/extensions/gsd/tests/reopen-slice.test.ts +52 -0
  296. package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +20 -0
  297. package/src/resources/extensions/gsd/tests/skill-discovery.test.ts +46 -0
  298. package/src/resources/extensions/gsd/tests/stale-worktree-cwd.test.ts +3 -5
  299. package/src/resources/extensions/gsd/tests/stash-pop-gsd-conflict.test.ts +2 -1
  300. package/src/resources/extensions/gsd/tests/stash-queued-context-files.test.ts +2 -1
  301. package/src/resources/extensions/gsd/tests/steer-worktree-path.test.ts +1 -1
  302. package/src/resources/extensions/gsd/tests/sync-worktree-skip-current.test.ts +1 -1
  303. package/src/resources/extensions/gsd/tests/system-flat-phase-layout.test.ts +33 -0
  304. package/src/resources/extensions/gsd/tests/teardown-chdir-failure-clears-registry.test.ts +4 -4
  305. package/src/resources/extensions/gsd/tests/teardown-cleanup-parity.test.ts +2 -1
  306. package/src/resources/extensions/gsd/tests/teardown-failure-clears-registry.test.ts +4 -4
  307. package/src/resources/extensions/gsd/tests/tool-call-loop-guard.test.ts +196 -0
  308. package/src/resources/extensions/gsd/tests/uat-stuck-loop-orphaned-worktree.test.ts +1 -1
  309. package/src/resources/extensions/gsd/tests/unit-registry.test.ts +26 -1
  310. package/src/resources/extensions/gsd/tests/worktree-db-integration.test.ts +1 -1
  311. package/src/resources/extensions/gsd/tests/worktree-db-respawn-truncation.test.ts +1 -1
  312. package/src/resources/extensions/gsd/tests/worktree-db-same-file.test.ts +1 -3
  313. package/src/resources/extensions/gsd/tests/worktree-expected-warnings.test.ts +1 -1
  314. package/src/resources/extensions/gsd/tests/worktree-git-pathspec.test.ts +1 -1
  315. package/src/resources/extensions/gsd/tests/worktree-main-branch.test.ts +1 -1
  316. package/src/resources/extensions/gsd/tests/worktree-preferences-sync.test.ts +1 -1
  317. package/src/resources/extensions/gsd/tests/worktree-reentry.test.ts +2 -1
  318. package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +5 -2
  319. package/src/resources/extensions/gsd/tests/worktree-sync-overwrite-loop.test.ts +1 -1
  320. package/src/resources/extensions/gsd/tests/worktree-sync-tasks.test.ts +1 -1
  321. package/src/resources/extensions/gsd/tool-contract.ts +29 -0
  322. package/src/resources/extensions/gsd/tools/reopen-slice.ts +5 -0
  323. package/src/resources/extensions/gsd/tools/reopen-task.ts +13 -5
  324. package/src/resources/extensions/gsd/unit-registry.ts +16 -2
  325. package/src/resources/extensions/gsd/unmerged-milestone-guard.ts +1 -1
  326. package/src/resources/extensions/gsd/validation-block-guard.ts +2 -1
  327. package/src/resources/extensions/gsd/workspace-git-preflight.ts +2 -2
  328. package/src/resources/extensions/gsd/worktree-command.ts +1 -1
  329. package/src/resources/extensions/gsd/worktree-lifecycle.ts +19 -15
  330. package/src/resources/extensions/gsd/worktree-reentry.ts +2 -1
  331. package/src/resources/extensions/shared/interview-ui.ts +58 -11
  332. package/src/resources/extensions/shared/tests/interview-ui-border.test.ts +32 -0
  333. /package/dist/web/standalone/.next/static/{L2UMqXELiDH3oz-7yguXp → gXoWec4UNqcR0tkuHwYCr}/_buildManifest.js +0 -0
  334. /package/dist/web/standalone/.next/static/{L2UMqXELiDH3oz-7yguXp → gXoWec4UNqcR0tkuHwYCr}/_ssgManifest.js +0 -0
@@ -14,7 +14,15 @@ import {
14
14
  } from "./gsd-db.js";
15
15
  import { MEMORIES_FTS_REBUILT_KEY } from "./db-memory-fts-schema.js";
16
16
  import { isAfter, latestExplicitReopenAt } from "./milestone-reopen-events.js";
17
- import { gsdProjectionRoot, gsdRoot, resolveGsdPathContract, resolveMilestoneFile, resolveSliceFile } from "./paths.js";
17
+ import {
18
+ buildTaskFileName,
19
+ gsdProjectionRoot,
20
+ gsdRoot,
21
+ resolveGsdPathContract,
22
+ resolveMilestoneFile,
23
+ resolveMilestonePath,
24
+ resolveSliceFile,
25
+ } from "./paths.js";
18
26
  import { deriveState } from "./state.js";
19
27
  import { isClosedStatus } from "./status-guards.js";
20
28
  import { workflowEventLogPath } from "./workflow-event-ledger.js";
@@ -23,6 +31,7 @@ import { flushWorkflowProjections } from "./projection-flush.js";
23
31
  import { parseRoadmapSlices } from "./roadmap-slices.js";
24
32
  import { parsePlan } from "./parsers-legacy.js";
25
33
  import { parseSummary } from "./files.js";
34
+ import { LAYOUT_SEGMENTS } from "./layout-policy.js";
26
35
 
27
36
  const USER_AUTHORED_ARTIFACT_TYPES = new Set(["CONTEXT", "RESEARCH"]);
28
37
 
@@ -160,11 +169,11 @@ function isClearedByMilestoneShellProjectionFlush(
160
169
  return issue.file === relativeFile(basePath, roadmapPath);
161
170
  }
162
171
 
163
- function artifactExistsOnDisk(basePath: string, artifactPath: string): boolean {
164
- return resolveArtifactDiskPath(basePath, artifactPath) !== null;
172
+ function artifactExistsOnDisk(basePath: string, artifactPath: string, row?: ArtifactRow): boolean {
173
+ return resolveArtifactDiskPath(basePath, artifactPath, row) !== null;
165
174
  }
166
175
 
167
- function resolveArtifactDiskPath(basePath: string, artifactPath: string): string | null {
176
+ function resolveLiteralArtifactDiskPath(basePath: string, artifactPath: string): string | null {
168
177
  const relativeArtifactPath = artifactPathRelativeToGsd(artifactPath);
169
178
  if (isAbsolute(relativeArtifactPath)) {
170
179
  return existsSync(relativeArtifactPath) ? relativeArtifactPath : null;
@@ -176,6 +185,50 @@ function resolveArtifactDiskPath(basePath: string, artifactPath: string): string
176
185
  return null;
177
186
  }
178
187
 
188
+ function relativeToGsdRoot(basePath: string, diskPath: string): string | null {
189
+ for (const root of [gsdProjectionRoot(basePath), gsdRoot(basePath)]) {
190
+ if (!isPathInside(root, diskPath)) continue;
191
+ return relative(root, diskPath).split("\\").join("/");
192
+ }
193
+ return null;
194
+ }
195
+
196
+ function isFlatPhaseDiskPath(basePath: string, diskPath: string): boolean {
197
+ return relativeToGsdRoot(basePath, diskPath)?.startsWith(`${LAYOUT_SEGMENTS.level1}/`) ?? false;
198
+ }
199
+
200
+ function hasFlatPhaseLayout(basePath: string): boolean {
201
+ return existsSync(join(gsdProjectionRoot(basePath), LAYOUT_SEGMENTS.level1));
202
+ }
203
+
204
+ function resolveFlatPhaseArtifactDiskPath(basePath: string, row: ArtifactRow): string | null {
205
+ if (!hasFlatPhaseLayout(basePath)) return null;
206
+ if (!row.milestone_id) return null;
207
+ const artifactType = normalizedArtifactType(row.artifact_type);
208
+ if (!artifactType) return null;
209
+
210
+ if (row.slice_id && row.task_id) {
211
+ const phaseDir = resolveMilestonePath(basePath, row.milestone_id);
212
+ if (!phaseDir || !isFlatPhaseDiskPath(basePath, phaseDir)) return null;
213
+ const candidate = join(phaseDir, buildTaskFileName(row.task_id, artifactType));
214
+ return existsSync(candidate) ? candidate : null;
215
+ }
216
+
217
+ const candidate = row.slice_id
218
+ ? resolveSliceFile(basePath, row.milestone_id, row.slice_id, artifactType)
219
+ : resolveMilestoneFile(basePath, row.milestone_id, artifactType);
220
+
221
+ return candidate && isFlatPhaseDiskPath(basePath, candidate) ? candidate : null;
222
+ }
223
+
224
+ function resolveArtifactDiskPath(basePath: string, artifactPath: string, row?: ArtifactRow): string | null {
225
+ if (row && isMilestonesArtifactPath(row.path)) {
226
+ const flatPath = resolveFlatPhaseArtifactDiskPath(basePath, row);
227
+ if (flatPath) return flatPath;
228
+ }
229
+ return resolveLiteralArtifactDiskPath(basePath, artifactPath);
230
+ }
231
+
179
232
  function taskExistsInPlan(basePath: string, milestoneId: string, sliceId: string, taskId: string): boolean {
180
233
  const planPath = resolveSliceFile(basePath, milestoneId, sliceId, "PLAN");
181
234
  if (!planPath || !existsSync(planPath)) return false;
@@ -228,7 +281,7 @@ function readTaskCompletionEvidenceFromSummary(
228
281
  if (!row.task_status && Number(row.task_count) > 0 && !taskExistsInPlan(basePath, row.milestone_id, row.slice_id, row.task_id)) {
229
282
  return null;
230
283
  }
231
- const diskPath = resolveArtifactDiskPath(basePath, row.path);
284
+ const diskPath = resolveArtifactDiskPath(basePath, row.path, { ...row, artifact_type: "SUMMARY" });
232
285
  if (!diskPath) return null;
233
286
  try {
234
287
  const fullSummaryMd = readFileSync(diskPath, "utf-8");
@@ -262,6 +315,7 @@ function repairTaskArtifactDbStatusDivergence(
262
315
  basePath: string,
263
316
  row: {
264
317
  path: string;
318
+ artifact_type?: string;
265
319
  milestone_id: string;
266
320
  slice_id: string | null;
267
321
  task_id: string | null;
@@ -353,6 +407,49 @@ function hasPresentMilestonesReplacement(basePath: string, row: ArtifactRow, art
353
407
  );
354
408
  }
355
409
 
410
+ function hasPresentFlatPhaseReplacement(basePath: string, row: ArtifactRow, artifactRows: ArtifactRow[]): boolean {
411
+ if (resolveFlatPhaseArtifactDiskPath(basePath, row)) return true;
412
+
413
+ return artifactRows.some(
414
+ (other) =>
415
+ other.path !== row.path &&
416
+ !isMilestonesArtifactPath(other.path) &&
417
+ sameArtifactIdentity(row, other) &&
418
+ artifactExistsOnDisk(basePath, other.path, other),
419
+ );
420
+ }
421
+
422
+ function hasNoFlatPhaseFileEquivalent(row: ArtifactRow): boolean {
423
+ const artifactType = normalizedArtifactType(row.artifact_type);
424
+ if (row.slice_id && row.task_id) {
425
+ return artifactType === "PLAN" || artifactType === "SUMMARY";
426
+ }
427
+ return Boolean(row.slice_id && !row.task_id && artifactType === "SUMMARY");
428
+ }
429
+
430
+ function staleMilestonesArtifactRowFixable(basePath: string, row: ArtifactRow, artifactRows: ArtifactRow[]): boolean {
431
+ if (!isMilestonesArtifactPath(row.path)) return false;
432
+ if (!hasFlatPhaseLayout(basePath)) return false;
433
+ if (hasPresentFlatPhaseReplacement(basePath, row, artifactRows)) return true;
434
+ return hasNoFlatPhaseFileEquivalent(row) && !resolveLiteralArtifactDiskPath(basePath, row.path);
435
+ }
436
+
437
+ function stalePhasesArtifactRowFixable(basePath: string, row: ArtifactRow, artifactRows: ArtifactRow[]): boolean {
438
+ const issuePath = artifactPathRelativeToGsd(row.path);
439
+ return issuePath.startsWith(`${LAYOUT_SEGMENTS.level1}/`) && hasPresentMilestonesReplacement(basePath, row, artifactRows);
440
+ }
441
+
442
+ function staleArtifactRowFixable(basePath: string, row: ArtifactRow, artifactRows: ArtifactRow[]): boolean {
443
+ return staleMilestonesArtifactRowFixable(basePath, row, artifactRows) ||
444
+ stalePhasesArtifactRowFixable(basePath, row, artifactRows);
445
+ }
446
+
447
+ function staleArtifactPruneMessage(row: ArtifactRow): string {
448
+ return isMilestonesArtifactPath(row.path)
449
+ ? `pruned stale legacy artifact row ${row.path}`
450
+ : `pruned stale flat-phase artifact row ${row.path}`;
451
+ }
452
+
356
453
  export async function checkEngineHealth(
357
454
  basePath: string,
358
455
  issues: DoctorIssue[],
@@ -579,17 +676,18 @@ export async function checkEngineHealth(
579
676
  .all() as ArtifactRow[];
580
677
 
581
678
  for (const row of artifactRows) {
582
- if (artifactExistsOnDisk(basePath, row.path)) continue;
583
679
  const unitId = artifactUnitId(row);
584
680
  const issuePath = artifactPathRelativeToGsd(row.path);
585
- if (options?.repair && issuePath.startsWith("phases/") && hasPresentMilestonesReplacement(basePath, row, artifactRows)) {
681
+ if (artifactExistsOnDisk(basePath, row.path)) continue;
682
+ if (options?.repair && staleArtifactRowFixable(basePath, row, artifactRows)) {
586
683
  // Route the write through the Single Writer owner (gsd-db.ts) instead
587
684
  // of issuing raw DELETE SQL here — doctor is a read-only consumer and
588
685
  // the single-writer invariant forbids write SQL outside the allowlist.
589
686
  deleteArtifactByPath(row.path);
590
- fixesApplied.push(`pruned stale flat-phase artifact row ${row.path}`);
687
+ fixesApplied.push(staleArtifactPruneMessage(row));
591
688
  continue;
592
689
  }
690
+ if (artifactExistsOnDisk(basePath, row.path, row)) continue;
593
691
  if (isUserAuthoredArtifactType(row.artifact_type)) {
594
692
  const artifactType = normalizedArtifactType(row.artifact_type);
595
693
  missingUserContentArtifacts.push({ path: issuePath, artifactType });
@@ -611,7 +709,7 @@ export async function checkEngineHealth(
611
709
  unitId,
612
710
  message: `Artifact ${issuePath} is recorded in the database as ${row.artifact_type || "UNKNOWN"} but no matching file exists on disk`,
613
711
  file: issuePath,
614
- fixable: issuePath.startsWith("phases/") && hasPresentMilestonesReplacement(basePath, row, artifactRows),
712
+ fixable: staleArtifactRowFixable(basePath, row, artifactRows),
615
713
  });
616
714
  }
617
715
  } catch {
@@ -643,6 +741,7 @@ export async function checkEngineHealth(
643
741
  )
644
742
  .all() as Array<{
645
743
  path: string;
744
+ artifact_type: string;
646
745
  milestone_id: string;
647
746
  slice_id: string | null;
648
747
  task_id: string | null;
@@ -654,7 +753,7 @@ export async function checkEngineHealth(
654
753
 
655
754
  const seen = new Set<string>();
656
755
  for (const row of rows) {
657
- if (!artifactExistsOnDisk(basePath, row.path)) continue;
756
+ if (!artifactExistsOnDisk(basePath, row.path, row)) continue;
658
757
  const reopenAt = latestExplicitReopenAt(basePath, row.milestone_id);
659
758
  if (!isAfter(row.imported_at, reopenAt)) continue;
660
759
  const isSliceSummary = row.slice_id && !row.task_id && row.slice_status && !["complete", "done", "skipped", "closed"].includes(row.slice_status);
@@ -6,7 +6,7 @@ import { cpSync, existsSync, mkdirSync, readdirSync, renameSync, rmSync, statSyn
6
6
  import { join } from "node:path";
7
7
 
8
8
  import { renderAllFromDb, renderRoadmapFromDb } from "./markdown-renderer.js";
9
- import { getAllMilestones, getMilestoneSlices } from "./gsd-db.js";
9
+ import { deleteArtifactsByPathPrefix, getAllMilestones, getMilestoneSlices } from "./gsd-db.js";
10
10
  import { migrateFromMarkdown } from "./md-importer.js";
11
11
  import { countDbHierarchy } from "./migration-auto-check.js";
12
12
  import { logWarning } from "./workflow-logger.js";
@@ -108,15 +108,35 @@ function rollbackPartialMigration(
108
108
  `rollback: could not remove partial ${LAYOUT_SEGMENTS.level1}/: ${(removeErr as Error).message}`,
109
109
  );
110
110
  }
111
+ // Only the standalone .gsd-backups/migrate-<ts>/ copy is disposable once the
112
+ // restore succeeds. When resuming an interrupted migration, backupDir IS the
113
+ // preserved migrating tree (the sole surviving data source) and must never be
114
+ // removed here. Deleting the backup after a successful rollback keeps the gate
115
+ // from leaking one .gsd-backups/migrate-<ts>/ per session_start.
116
+ const isDisposableBackup = Boolean(backupDir) && backupDir !== migratingPath;
117
+ const cleanupBackup = (): void => {
118
+ if (!isDisposableBackup || !existsSync(backupDir)) return;
119
+ try {
120
+ removePathWithRetries(backupDir);
121
+ } catch (cleanupErr) {
122
+ logWarning(
123
+ "migration",
124
+ `rollback: could not clean backup ${backupDir}: ${(cleanupErr as Error).message}`,
125
+ );
126
+ }
127
+ };
128
+
111
129
  // Restore milestones/ — prefer renaming the preserved migrating copy back.
112
130
  const milestonesPath = join(basePath, ".gsd", "milestones");
113
131
  try {
114
132
  if (migratingPath && existsSync(migratingPath) && !existsSync(milestonesPath)) {
115
133
  movePathWithCopyDeleteFallback(migratingPath, milestonesPath);
134
+ cleanupBackup();
116
135
  return;
117
136
  }
118
137
  if (existsSync(backupDir)) {
119
138
  cpSync(backupDir, milestonesPath, { recursive: true });
139
+ cleanupBackup();
120
140
  }
121
141
  if (migratingPath && existsSync(migratingPath)) {
122
142
  removePathWithRetries(migratingPath);
@@ -195,7 +215,8 @@ export function pruneStaleFlatPhaseBackups(basePath: string): number {
195
215
  * 2. Rename .gsd/milestones/ aside so path resolvers target phases/
196
216
  * 3. Render flat-phase from the DB (which already has the data)
197
217
  * 4. Verify counts match
198
- * 5. Remove the renamed legacy tree
218
+ * 5. Prune legacy milestones/ artifact rows
219
+ * 6. Remove the renamed legacy tree
199
220
  *
200
221
  * Idempotent: if .gsd/milestones/ doesn't exist, returns immediately.
201
222
  */
@@ -313,7 +334,17 @@ export async function migrateToFlatPhase(basePath: string): Promise<void> {
313
334
  throw new Error("flat-phase migration verification failed: no artifacts rendered");
314
335
  }
315
336
 
316
- // 6. Verified — remove the renamed legacy tree (backup already on disk).
337
+ // 6. Verified — prune legacy artifact rows now that renderAllFromDb has
338
+ // re-inserted flat-phase rows for artifacts that still have files.
339
+ try {
340
+ deleteArtifactsByPathPrefix("milestones/");
341
+ } catch (err) {
342
+ logWarning("migration", `flat-phase migration could not prune legacy artifact rows: ${(err as Error).message}`);
343
+ rollbackPartialMigration(basePath, backupDir, migratingPath);
344
+ throw err;
345
+ }
346
+
347
+ // 7. Remove the renamed legacy tree (backup already on disk).
317
348
  try {
318
349
  removePathWithRetries(migratingPath);
319
350
  } catch (err) {
@@ -30,7 +30,7 @@ import { loadPrompt } from "./prompt-loader.js";
30
30
  import { gsdRoot } from "./paths.js";
31
31
  import { isDbAvailable, getHierarchyCompletionCounts } from "./gsd-db.js";
32
32
  import { formatDuration } from "../shared/format-utils.js";
33
- import { getAutoWorktreePath } from "./auto-worktree.js";
33
+ import { getAutoWorktreePath } from "./auto-worktree-path-resolution.js";
34
34
  import { clearGSDPreferencesCache, loadEffectiveGSDPreferences, loadGlobalGSDPreferences, getGlobalGSDPreferencesPath } from "./preferences.js";
35
35
  import { showNextAction } from "../shared/tui.js";
36
36
  import {
@@ -7,9 +7,9 @@ import {
7
7
  nativeCheckoutTheirs,
8
8
  nativeRmForce,
9
9
  } from "./native-git-bridge.js";
10
- import { isSafeToAutoResolve } from "./auto-worktree.js";
10
+ import { isSafeToAutoResolve } from "./auto-worktree-conflict-auto-resolve.js";
11
11
 
12
- export { isSafeToAutoResolve } from "./auto-worktree.js";
12
+ export { isSafeToAutoResolve } from "./auto-worktree-conflict-auto-resolve.js";
13
13
 
14
14
  export interface AutoResolveSafePathsResult {
15
15
  resolved: string[];
@@ -704,6 +704,59 @@ export function runGit(basePath: string, args: string[], options: { allowFailure
704
704
  }
705
705
  }
706
706
 
707
+ /**
708
+ * Arguments for creating a task/workflow branch.
709
+ *
710
+ * When the current branch is a leftover GSD task branch (workflow template,
711
+ * quick task, or slice), the new branch starts from `baseBranch` — the
712
+ * caller's resolved integration branch, normally `GitServiceImpl.getMainBranch()`
713
+ * so `main_branch` preference, milestone integration branch, and worktree base
714
+ * all take priority over repo-default detection. Branching from HEAD there
715
+ * silently stacks consecutive workflow branches on top of each other, so every
716
+ * new gsd/* branch (and its eventual PR) drags along the previous workflow's
717
+ * commits.
718
+ *
719
+ * On any other branch (a user's feature branch, a worktree base, the main
720
+ * branch itself) the legacy branch-from-HEAD behavior is kept: basing new work
721
+ * on the branch the user chose to stand on is intentional there.
722
+ *
723
+ * Falls back to branch-from-HEAD, with a logged warning, when the base branch
724
+ * has no local ref or the working tree is still dirty (a start point that
725
+ * changes the tree would abort the checkout or strand in-flight changes).
726
+ * The base is the local ref — deliberately no fetch, so branch creation stays
727
+ * offline-safe even if the local base is behind its remote.
728
+ */
729
+ export function taskBranchArgs(basePath: string, branchName: string, currentBranch: string, baseBranch: string): string[] {
730
+ const args = ["checkout", "-b", branchName];
731
+ const onTaskBranch =
732
+ WORKFLOW_BRANCH_RE.test(currentBranch) ||
733
+ QUICK_BRANCH_RE.test(currentBranch) ||
734
+ SLICE_BRANCH_RE.test(currentBranch);
735
+ if (!onTaskBranch || !baseBranch || baseBranch === currentBranch) return args;
736
+ try {
737
+ if (!nativeBranchExists(basePath, baseBranch)) {
738
+ logWarning(
739
+ "engine",
740
+ `taskBranchArgs: base branch "${baseBranch}" has no local ref — branching ${branchName} from HEAD (${currentBranch})`,
741
+ { file: "git-service.ts" },
742
+ );
743
+ return args;
744
+ }
745
+ if (nativeHasChanges(basePath)) {
746
+ logWarning(
747
+ "engine",
748
+ `taskBranchArgs: working tree still dirty after auto-commit — branching ${branchName} from HEAD (${currentBranch}) to preserve in-flight changes`,
749
+ { file: "git-service.ts" },
750
+ );
751
+ return args;
752
+ }
753
+ } catch {
754
+ return args;
755
+ }
756
+ args.push(baseBranch);
757
+ return args;
758
+ }
759
+
707
760
  // ─── Commit Type Inference ─────────────────────────────────────────────────
708
761
 
709
762
  /**
@@ -454,6 +454,14 @@ export function insertTask(t: {
454
454
  fullSummaryMd?: string;
455
455
  sequence?: number;
456
456
  planning?: Partial<TaskPlanningRecord>;
457
+ // #1222 (metadata half): when re-importing from markdown, the caller only
458
+ // knows the plan/status — not the execution prose that gsd_task_complete
459
+ // wrote to the DB. Setting this preserves the existing row's execution
460
+ // columns (and completed_at) whenever the incoming value is empty/default,
461
+ // so a re-import cannot silently blank a completed task's summary metadata
462
+ // or refresh its completion timestamp. Off by default: callers that own the
463
+ // execution metadata (gsd_task_complete, planning) keep overwrite semantics.
464
+ preserveCompletionMetadata?: boolean;
457
465
  }): void {
458
466
  if (!getDbOrNull()!) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
459
467
  getDbOrNull()!.prepare(
@@ -473,17 +481,17 @@ export function insertTask(t: {
473
481
  ON CONFLICT(milestone_id, slice_id, id) DO UPDATE SET
474
482
  title = CASE WHEN NULLIF(:title, '') IS NOT NULL THEN :title ELSE tasks.title END,
475
483
  status = :status,
476
- one_liner = :one_liner,
477
- narrative = :narrative,
478
- verification_result = :verification_result,
479
- duration = :duration,
480
- completed_at = :completed_at,
481
- blocker_discovered = :blocker_discovered,
482
- deviations = :deviations,
483
- known_issues = :known_issues,
484
- key_files = :key_files,
485
- key_decisions = :key_decisions,
486
- full_summary_md = :full_summary_md,
484
+ one_liner = CASE WHEN :preserve_completion = 1 AND NULLIF(:one_liner, '') IS NULL THEN tasks.one_liner ELSE :one_liner END,
485
+ narrative = CASE WHEN :preserve_completion = 1 AND NULLIF(:narrative, '') IS NULL THEN tasks.narrative ELSE :narrative END,
486
+ verification_result = CASE WHEN :preserve_completion = 1 AND NULLIF(:verification_result, '') IS NULL THEN tasks.verification_result ELSE :verification_result END,
487
+ duration = CASE WHEN :preserve_completion = 1 AND NULLIF(:duration, '') IS NULL THEN tasks.duration ELSE :duration END,
488
+ completed_at = CASE WHEN :preserve_completion = 1 AND tasks.completed_at IS NOT NULL THEN tasks.completed_at ELSE :completed_at END,
489
+ blocker_discovered = CASE WHEN :preserve_completion = 1 AND :blocker_discovered = 0 THEN tasks.blocker_discovered ELSE :blocker_discovered END,
490
+ deviations = CASE WHEN :preserve_completion = 1 AND NULLIF(:deviations, '') IS NULL THEN tasks.deviations ELSE :deviations END,
491
+ known_issues = CASE WHEN :preserve_completion = 1 AND NULLIF(:known_issues, '') IS NULL THEN tasks.known_issues ELSE :known_issues END,
492
+ key_files = CASE WHEN :preserve_completion = 1 AND NULLIF(:key_files, '[]') IS NULL THEN tasks.key_files ELSE :key_files END,
493
+ key_decisions = CASE WHEN :preserve_completion = 1 AND NULLIF(:key_decisions, '[]') IS NULL THEN tasks.key_decisions ELSE :key_decisions END,
494
+ full_summary_md = CASE WHEN :preserve_completion = 1 AND NULLIF(:full_summary_md, '') IS NULL THEN tasks.full_summary_md ELSE :full_summary_md END,
487
495
  description = CASE WHEN NULLIF(:description, '') IS NOT NULL THEN :description ELSE tasks.description END,
488
496
  estimate = CASE WHEN NULLIF(:estimate, '') IS NOT NULL THEN :estimate ELSE tasks.estimate END,
489
497
  files = CASE WHEN NULLIF(:files, '[]') IS NOT NULL THEN :files ELSE tasks.files END,
@@ -523,6 +531,7 @@ export function insertTask(t: {
523
531
  ":observability_impact": t.planning?.observabilityImpact ?? "",
524
532
  ":full_plan_md": t.planning?.fullPlanMd ?? "",
525
533
  ":sequence": t.sequence ?? 0,
534
+ ":preserve_completion": t.preserveCompletionMetadata && (t.status === "complete" || t.status === "done") ? 1 : 0,
526
535
  ":target_repositories": JSON.stringify(t.planning?.targetRepositories ?? []),
527
536
  ":raw_target_repositories":
528
537
  t.planning && "targetRepositories" in t.planning
@@ -1397,6 +1406,19 @@ export function deleteArtifactByPath(path: string): void {
1397
1406
  getDbOrNull()!.prepare("DELETE FROM artifacts WHERE path = :path").run({ ":path": path });
1398
1407
  }
1399
1408
 
1409
+ /** Delete artifact rows whose paths share a DB-relative prefix. */
1410
+ export function deleteArtifactsByPathPrefix(prefix: string): number {
1411
+ if (!getDbOrNull()!) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
1412
+ return transaction(() => {
1413
+ const likePrefix = `${prefix}%`;
1414
+ const countRow = getDbOrNull()!.prepare(
1415
+ "SELECT COUNT(*) AS count FROM artifacts WHERE path LIKE :prefix",
1416
+ ).get({ ":prefix": likePrefix });
1417
+ getDbOrNull()!.prepare("DELETE FROM artifacts WHERE path LIKE :prefix").run({ ":prefix": likePrefix });
1418
+ return Number(countRow?.["count"] ?? 0);
1419
+ });
1420
+ }
1421
+
1400
1422
  /**
1401
1423
  * Drop hierarchy rows in dependency order inside a transaction. Used by
1402
1424
  * `gsd recover --confirm` to rebuild engine state from markdown.
@@ -50,8 +50,8 @@ import { readSessionLockData, isSessionLockProcessAlive } from "./session-lock.j
50
50
  import { nativeAddAll, nativeCommit, nativeHasCommittedHead, nativeIsRepo, nativeInit } from "./native-git-bridge.js";
51
51
  import { isInheritedRepo } from "./repo-identity.js";
52
52
  import { ensureGitignore, ensurePreferences, untrackRuntimeFiles } from "./gitignore.js";
53
- import { getIsolationMode, loadEffectiveGSDPreferences } from "./preferences.js";
54
- import { getAutoWorktreePath } from "./auto-worktree.js";
53
+ import { getIsolationMode, loadEffectiveGSDPreferences, renderLanguageDirectiveForPrompt } from "./preferences.js";
54
+ import { getAutoWorktreePath } from "./auto-worktree-path-resolution.js";
55
55
  import { resolveUokFlags } from "./uok/flags.js";
56
56
  import { ensurePlanV2Graph, isMissingFinalizedContextResult } from "./uok/plan-v2.js";
57
57
  import { detectProjectState, hasGsdBootstrapArtifacts } from "./detection.js";
@@ -94,7 +94,7 @@ import {
94
94
  import { probeCoversRequiredWorkflowTools } from "./tool-surface-readiness.js";
95
95
  import { getRequiredWorkflowToolsForUnit } from "./unit-tool-contracts.js";
96
96
  import { isWorkflowToolSurfaceName } from "./workflow-tool-surface.js";
97
- import { getUnitWorkflowDispatchReadinessError } from "./tool-contract.js";
97
+ import { getUnitWorkflowDispatchReadinessErrorForModel } from "./tool-contract.js";
98
98
  import {
99
99
  runPreparation,
100
100
  formatCodebaseBrief,
@@ -613,7 +613,7 @@ interface DispatchWorkflowOptions {
613
613
  deps?: {
614
614
  loadPreferences?: typeof loadEffectiveGSDPreferences;
615
615
  selectModel?: typeof selectAndApplyModel;
616
- getDispatchReadinessError?: typeof getUnitWorkflowDispatchReadinessError;
616
+ getDispatchReadinessError?: typeof getUnitWorkflowDispatchReadinessErrorForModel;
617
617
  };
618
618
  }
619
619
 
@@ -730,7 +730,7 @@ async function dispatchWorkflow(
730
730
  const loadPreferences = resolvedOptions.deps?.loadPreferences ?? loadEffectiveGSDPreferences;
731
731
  const selectModel = resolvedOptions.deps?.selectModel ?? selectAndApplyModel;
732
732
  const getDispatchReadinessError = resolvedOptions.deps?.getDispatchReadinessError
733
- ?? getUnitWorkflowDispatchReadinessError;
733
+ ?? getUnitWorkflowDispatchReadinessErrorForModel;
734
734
 
735
735
  // Route through the dynamic routing pipeline (complexity classification,
736
736
  // tier downgrade, fallback chains) — same path as auto-mode dispatches (#2958).
@@ -750,16 +750,12 @@ async function dispatchWorkflow(
750
750
  }
751
751
 
752
752
  const compatibilityError = getDispatchReadinessError({
753
- provider: result.appliedModel?.provider ?? ctx.model?.provider,
753
+ model: result.appliedModel,
754
+ fallbackModel: ctx.model,
755
+ getProviderAuthMode: (provider) => ctx.modelRegistry.getProviderAuthMode(provider),
754
756
  projectRoot,
755
757
  surface: "guided flow",
756
758
  unitType,
757
- authMode: result.appliedModel?.provider
758
- ? ctx.modelRegistry.getProviderAuthMode(result.appliedModel.provider)
759
- : ctx.model?.provider
760
- ? ctx.modelRegistry.getProviderAuthMode(ctx.model.provider)
761
- : undefined,
762
- baseUrl: result.appliedModel?.baseUrl ?? ctx.model?.baseUrl,
763
759
  // Guided flow starts the MCP workflow server as part of dispatch, so the
764
760
  // parent session's activeTools doesn't include MCP tools yet. The MCP
765
761
  // launch config check (detectWorkflowMcpLaunchConfig) is the right gate
@@ -925,7 +921,7 @@ function resolveAvailableModel<T extends { id: string; provider: string }>(
925
921
  * Build the discuss-and-plan prompt for a new milestone.
926
922
  * Used by all three "new milestone" paths (first ever, no active, all complete).
927
923
  */
928
- function buildDiscussPrompt(nextId: string, preamble: string, _basePath: string, pi: ExtensionAPI, ctx: ExtensionCommandContext, preparationContext?: string): string {
924
+ function buildDiscussPrompt(nextId: string, preamble: string, basePath: string, pi: ExtensionAPI, ctx: ExtensionCommandContext, preparationContext?: string): string {
929
925
  const milestoneRel = `.gsd/milestones/${nextId}`;
930
926
  const structuredQuestionsAvailable = getStructuredQuestionsAvailability(pi, ctx);
931
927
  const inlinedTemplates = [
@@ -935,7 +931,7 @@ function buildDiscussPrompt(nextId: string, preamble: string, _basePath: string,
935
931
  inlineTemplate("roadmap", "Roadmap"),
936
932
  inlineTemplate("decisions", "Decisions"),
937
933
  ].join("\n\n---\n\n");
938
- return loadPrompt("discuss", {
934
+ return prependLanguageDirective(basePath, loadPrompt("discuss", {
939
935
  milestoneId: nextId,
940
936
  preamble,
941
937
  preparationContext: preparationContext ?? "",
@@ -945,14 +941,26 @@ function buildDiscussPrompt(nextId: string, preamble: string, _basePath: string,
945
941
  inlinedTemplates,
946
942
  commitInstruction: buildDocsCommitInstruction(`docs(${nextId}): context, requirements, and roadmap`),
947
943
  multiMilestoneCommitInstruction: buildDocsCommitInstruction("docs: project plan — N milestones"),
948
- });
944
+ }));
945
+ }
946
+
947
+ /**
948
+ * Prepend the configured response-language directive to a dispatched discuss
949
+ * prompt so the discuss/QA conversation is held in the language set in
950
+ * PREFERENCES.md (#1210). No-op when no language is configured.
951
+ */
952
+ function prependLanguageDirective(basePath: string, prompt: string): string {
953
+ const directive = renderLanguageDirectiveForPrompt(
954
+ loadEffectiveGSDPreferences(basePath)?.preferences,
955
+ );
956
+ return directive ? `${directive}\n\n${prompt}` : prompt;
949
957
  }
950
958
 
951
959
  /**
952
960
  * Build the discuss prompt for headless milestone creation.
953
961
  * Uses the discuss-headless prompt template with seed context injected.
954
962
  */
955
- function buildHeadlessDiscussPrompt(nextId: string, seedContext: string, _basePath: string): string {
963
+ function buildHeadlessDiscussPrompt(nextId: string, seedContext: string, basePath: string): string {
956
964
  const milestoneRel = `.gsd/milestones/${nextId}`;
957
965
  const inlinedTemplates = [
958
966
  inlineTemplate("project", "Project"),
@@ -961,7 +969,7 @@ function buildHeadlessDiscussPrompt(nextId: string, seedContext: string, _basePa
961
969
  inlineTemplate("roadmap", "Roadmap"),
962
970
  inlineTemplate("decisions", "Decisions"),
963
971
  ].join("\n\n---\n\n");
964
- return loadPrompt("discuss-headless", {
972
+ return prependLanguageDirective(basePath, loadPrompt("discuss-headless", {
965
973
  milestoneId: nextId,
966
974
  seedContext,
967
975
  contextPath: `${milestoneRel}/${nextId}-CONTEXT.md`,
@@ -969,7 +977,7 @@ function buildHeadlessDiscussPrompt(nextId: string, seedContext: string, _basePa
969
977
  inlinedTemplates,
970
978
  commitInstruction: buildDocsCommitInstruction(`docs(${nextId}): context, requirements, and roadmap`),
971
979
  multiMilestoneCommitInstruction: buildDocsCommitInstruction("docs: project plan — N milestones"),
972
- });
980
+ }));
973
981
  }
974
982
 
975
983
  /**
@@ -1299,9 +1299,21 @@ export async function renderAssessmentFromDb(
1299
1299
  sliceId: string,
1300
1300
  assessmentData: AssessmentData,
1301
1301
  ): Promise<{ assessmentPath: string; content: string }> {
1302
- // Flat-phase: assessment file lives in the phase dir
1303
- const slicePath = resolveSlicePath(basePath, milestoneId, sliceId)
1304
- ?? join(milestonesDir(basePath), canonicalPhaseDirName(milestoneId, getMilestone(milestoneId)?.title));
1302
+ // Flat-phase: the assessment file lives in the phase dir, NOT in a slices/SID/
1303
+ // subdir. resolveSlicePath() detects a slices/SID/ dir unconditionally, so a
1304
+ // stray slices/SID/ created by an earlier planning step would send this write
1305
+ // to a hybrid path (wrong dir, flat filename) that verification cannot find.
1306
+ // Guard with the same legacy-base check relSlicePath() uses so flat-phase
1307
+ // milestones always target the phase dir.
1308
+ const mDir = resolveMilestonePath(basePath, milestoneId);
1309
+ const legacyBase = legacyMilestonesDir(basePath);
1310
+ const isLegacyLayout = mDir
1311
+ ? mDir.startsWith(legacyBase + "/") || mDir.startsWith(legacyBase + "\\")
1312
+ : false;
1313
+ const fallbackDir = join(milestonesDir(basePath), canonicalPhaseDirName(milestoneId, getMilestone(milestoneId)?.title));
1314
+ const slicePath = isLegacyLayout
1315
+ ? (resolveSlicePath(basePath, milestoneId, sliceId) ?? fallbackDir)
1316
+ : (mDir ?? fallbackDir);
1305
1317
  const phaseNum = milestoneIdToPhaseNum(milestoneId);
1306
1318
  const planNum = sliceIdToPlanNum(sliceId);
1307
1319
  const absPath = join(slicePath, planFileName(phaseNum, planNum, "ASSESSMENT"));