@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
@@ -3,6 +3,10 @@
3
3
 
4
4
  import test from "node:test";
5
5
  import assert from "node:assert/strict";
6
+ import { execFileSync } from "node:child_process";
7
+ import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
8
+ import { tmpdir } from "node:os";
9
+ import { join } from "node:path";
6
10
 
7
11
  import {
8
12
  _runMilestoneMergeOnceWithStashRestore,
@@ -10,6 +14,9 @@ import {
10
14
  } from "../auto/closeout.js";
11
15
  import type { IterationContext } from "../auto/types.js";
12
16
  import { MergeConflictError } from "../git-service.js";
17
+ import { closeDatabase, insertDecision, insertMilestone, insertSlice, insertTask, openDatabase } from "../gsd-db.js";
18
+ import { renderAllFromDb } from "../markdown-renderer.js";
19
+ import { resolveMilestoneFile } from "../paths.js";
13
20
  import type {
14
21
  PostflightResult,
15
22
  PreflightResult,
@@ -234,6 +241,70 @@ const PREFLIGHT_UNMERGED_EVAL_FAILED: PreflightResult = {
234
241
  summary: "Unable to fully evaluate unresolved Git conflicts before milestone M002 merge.",
235
242
  };
236
243
 
244
+ function git(basePath: string, args: string[], stdio: "ignore" | "pipe" = "pipe"): void {
245
+ execFileSync("git", args, { cwd: basePath, stdio });
246
+ }
247
+
248
+ async function withProjectionBackedProject<T>(
249
+ fn: (basePath: string, roadmapPath: string) => Promise<T>,
250
+ options: { gitInit?: boolean } = {},
251
+ ): Promise<T> {
252
+ const basePath = mkdtempSync(join(tmpdir(), "gsd-postflight-projection-"));
253
+ try {
254
+ mkdirSync(join(basePath, ".gsd"), { recursive: true });
255
+ openDatabase(join(basePath, ".gsd", "gsd.db"));
256
+ insertMilestone({
257
+ id: "M002",
258
+ title: "Postflight Guard",
259
+ status: "active",
260
+ });
261
+ insertSlice({
262
+ id: "S01",
263
+ milestoneId: "M002",
264
+ title: "Slice",
265
+ status: "pending",
266
+ risk: "medium",
267
+ depends: [],
268
+ demo: "demo",
269
+ sequence: 1,
270
+ });
271
+ insertTask({
272
+ id: "T01",
273
+ sliceId: "S01",
274
+ milestoneId: "M002",
275
+ title: "Task",
276
+ status: "pending",
277
+ });
278
+ await renderAllFromDb(basePath);
279
+ if (options.gitInit !== false) {
280
+ git(basePath, ["init"], "ignore");
281
+ git(basePath, ["config", "user.email", "test@example.invalid"]);
282
+ git(basePath, ["config", "user.name", "Test"]);
283
+ git(basePath, ["add", ".gsd"]);
284
+ git(basePath, ["commit", "-m", "initial projections"], "ignore");
285
+ }
286
+ const roadmapPath = resolveMilestoneFile(basePath, "M002", "ROADMAP");
287
+ assert.ok(roadmapPath, "expected rendered M002 roadmap path");
288
+ return await fn(basePath, roadmapPath);
289
+ } finally {
290
+ closeDatabase();
291
+ rmSync(basePath, { recursive: true, force: true });
292
+ }
293
+ }
294
+
295
+ function insertFreshDbSlice(): void {
296
+ insertSlice({
297
+ id: "S02",
298
+ milestoneId: "M002",
299
+ title: "Fresh DB Slice",
300
+ status: "pending",
301
+ risk: "medium",
302
+ depends: [],
303
+ demo: "demo",
304
+ sequence: 2,
305
+ });
306
+ }
307
+
237
308
  const POP_OK: PostflightResult = {
238
309
  restored: true,
239
310
  needsManualRecovery: false,
@@ -469,6 +540,374 @@ test("already-merged milestone skips guarded merge to terminate duplicate closeo
469
540
  );
470
541
  });
471
542
 
543
+
544
+ test("postflight-restored projection edits survive successful merge rebuild", async () => {
545
+ await withProjectionBackedProject(async (basePath, roadmapPath) => {
546
+ const { ic } = buildIc({
547
+ preflightResult: STASH_PUSHED,
548
+ mergeBehavior: "succeed",
549
+ postflightResult: POP_OK,
550
+ });
551
+ (ic.s as { basePath: string; originalBasePath: string }).basePath = basePath;
552
+ (ic.s as { basePath: string; originalBasePath: string }).originalBasePath = basePath;
553
+ (ic.deps as unknown as { postflightPopStash: () => PostflightResult }).postflightPopStash = () => {
554
+ writeFileSync(roadmapPath, "# local projection edit restored from stash\n", "utf8");
555
+ return POP_OK;
556
+ };
557
+
558
+ const result = await _runMilestoneMergeWithStashRestore(ic, "M002");
559
+
560
+ assert.equal(result, null);
561
+ assert.equal(
562
+ readFileSync(roadmapPath, "utf8"),
563
+ "# local projection edit restored from stash\n",
564
+ "projection rebuild must not overwrite user edits restored by postflight stash pop",
565
+ );
566
+ });
567
+ });
568
+
569
+ test("untracked markdown under a new generated .gsd projection directory suppresses successful merge rebuild", async () => {
570
+ await withProjectionBackedProject(async (basePath, roadmapPath) => {
571
+ insertFreshDbSlice();
572
+
573
+ const { ic } = buildIc({
574
+ preflightResult: STASH_PUSHED,
575
+ mergeBehavior: "succeed",
576
+ postflightResult: POP_OK,
577
+ });
578
+ (ic.s as { basePath: string; originalBasePath: string }).basePath = basePath;
579
+ (ic.s as { basePath: string; originalBasePath: string }).originalBasePath = basePath;
580
+ (ic.deps as unknown as { postflightPopStash: () => PostflightResult }).postflightPopStash = () => {
581
+ const restoredDir = join(basePath, ".gsd", "milestones", "M002", "slices", "S99");
582
+ mkdirSync(restoredDir, { recursive: true });
583
+ writeFileSync(join(restoredDir, "S99-SUMMARY.md"), "# restored local markdown\n", "utf8");
584
+ return POP_OK;
585
+ };
586
+
587
+ const result = await _runMilestoneMergeWithStashRestore(ic, "M002");
588
+
589
+ assert.equal(result, null);
590
+ assert.doesNotMatch(
591
+ readFileSync(roadmapPath, "utf8"),
592
+ /Fresh DB Slice/,
593
+ "untracked markdown projections inside generated .gsd directories must suppress rebuild",
594
+ );
595
+ });
596
+ });
597
+
598
+ test("ignored untracked markdown under .gsd suppresses successful merge rebuild", async () => {
599
+ await withProjectionBackedProject(async (basePath, roadmapPath) => {
600
+ writeFileSync(join(basePath, ".gitignore"), ".gsd\n", "utf8");
601
+ git(basePath, ["add", ".gitignore"]);
602
+ git(basePath, ["commit", "-m", "ignore gsd projections"], "ignore");
603
+ insertFreshDbSlice();
604
+
605
+ const { ic } = buildIc({
606
+ preflightResult: STASH_PUSHED,
607
+ mergeBehavior: "succeed",
608
+ postflightResult: POP_OK,
609
+ });
610
+ (ic.s as { basePath: string; originalBasePath: string }).basePath = basePath;
611
+ (ic.s as { basePath: string; originalBasePath: string }).originalBasePath = basePath;
612
+ (ic.deps as unknown as { postflightPopStash: () => PostflightResult }).postflightPopStash = () => {
613
+ const restoredDir = join(basePath, ".gsd", "milestones", "M002", "ignored-restored-projections");
614
+ mkdirSync(restoredDir, { recursive: true });
615
+ writeFileSync(join(restoredDir, "S99-SUMMARY.md"), "# ignored restored local markdown\n", "utf8");
616
+ return POP_OK;
617
+ };
618
+
619
+ const result = await _runMilestoneMergeWithStashRestore(ic, "M002");
620
+
621
+ assert.equal(result, null);
622
+ assert.doesNotMatch(
623
+ readFileSync(roadmapPath, "utf8"),
624
+ /Fresh DB Slice/,
625
+ "ignored .gsd markdown projections restored by postflight must suppress rebuild",
626
+ );
627
+ });
628
+ });
629
+
630
+ test("preexisting ignored markdown under .gsd does not suppress successful merge rebuild", async () => {
631
+ await withProjectionBackedProject(async (basePath, roadmapPath) => {
632
+ writeFileSync(join(basePath, ".gitignore"), ".gsd\n", "utf8");
633
+ git(basePath, ["add", ".gitignore"]);
634
+ git(basePath, ["commit", "-m", "ignore gsd projections"], "ignore");
635
+ mkdirSync(join(basePath, ".gsd", "milestones", "M002", "ignored-existing-projections"), { recursive: true });
636
+ writeFileSync(
637
+ join(basePath, ".gsd", "milestones", "M002", "ignored-existing-projections", "S99-SUMMARY.md"),
638
+ "# already ignored local markdown\n",
639
+ "utf8",
640
+ );
641
+ insertFreshDbSlice();
642
+
643
+ const { ic } = buildIc({
644
+ preflightResult: STASH_PUSHED,
645
+ mergeBehavior: "succeed",
646
+ postflightResult: POP_OK,
647
+ });
648
+ (ic.s as { basePath: string; originalBasePath: string }).basePath = basePath;
649
+ (ic.s as { basePath: string; originalBasePath: string }).originalBasePath = basePath;
650
+
651
+ const result = await _runMilestoneMergeWithStashRestore(ic, "M002");
652
+
653
+ assert.equal(result, null);
654
+ assert.match(
655
+ readFileSync(roadmapPath, "utf8"),
656
+ /Fresh DB Slice/,
657
+ "stable preexisting ignored .gsd markdown must not block the required projection rebuild",
658
+ );
659
+ });
660
+ });
661
+
662
+ test("failed pre-merge ignored snapshot still suppresses rebuild over restored ignored markdown", async () => {
663
+ await withProjectionBackedProject(async (basePath, roadmapPath) => {
664
+ insertFreshDbSlice();
665
+
666
+ const { ic } = buildIc({
667
+ preflightResult: STASH_PUSHED,
668
+ mergeBehavior: "succeed",
669
+ postflightResult: POP_OK,
670
+ });
671
+ (ic.s as { basePath: string; originalBasePath: string }).basePath = basePath;
672
+ (ic.s as { basePath: string; originalBasePath: string }).originalBasePath = basePath;
673
+ (ic.deps as unknown as { postflightPopStash: () => PostflightResult }).postflightPopStash = () => {
674
+ // Git only becomes available AFTER the pre-merge ignored-markdown
675
+ // snapshot was attempted, so `before` is null. Postflight then restores
676
+ // ignored `.gsd` markdown that plain `git status` cannot see. With no
677
+ // baseline to diff against, the guard must fail closed and refuse to
678
+ // rebuild over the restored edit.
679
+ git(basePath, ["init"], "ignore");
680
+ git(basePath, ["config", "user.email", "test@example.invalid"]);
681
+ git(basePath, ["config", "user.name", "Test"]);
682
+ writeFileSync(join(basePath, ".gitignore"), ".gsd\n", "utf8");
683
+ git(basePath, ["add", ".gitignore"]);
684
+ git(basePath, ["commit", "-m", "ignore gsd projections"], "ignore");
685
+ writeFileSync(roadmapPath, "# ignored projection edit restored from stash\n", "utf8");
686
+ return POP_OK;
687
+ };
688
+
689
+ const result = await _runMilestoneMergeWithStashRestore(ic, "M002");
690
+
691
+ assert.equal(result, null);
692
+ assert.equal(
693
+ readFileSync(roadmapPath, "utf8"),
694
+ "# ignored projection edit restored from stash\n",
695
+ "a null pre-merge ignored snapshot must fail closed and preserve restored ignored .gsd markdown",
696
+ );
697
+ }, { gitInit: false });
698
+ });
699
+
700
+ test("non-projection .gsd markdown does not suppress successful merge rebuild", async () => {
701
+ await withProjectionBackedProject(async (basePath, roadmapPath) => {
702
+ insertFreshDbSlice();
703
+
704
+ const { ic } = buildIc({
705
+ preflightResult: STASH_PUSHED,
706
+ mergeBehavior: "succeed",
707
+ postflightResult: POP_OK,
708
+ });
709
+ (ic.s as { basePath: string; originalBasePath: string }).basePath = basePath;
710
+ (ic.s as { basePath: string; originalBasePath: string }).originalBasePath = basePath;
711
+ (ic.deps as unknown as { postflightPopStash: () => PostflightResult }).postflightPopStash = () => {
712
+ writeFileSync(join(basePath, ".gsd", "PREFERENCES.md"), "# local preferences note\n", "utf8");
713
+ mkdirSync(join(basePath, ".gsd", "milestones", "M002"), { recursive: true });
714
+ writeFileSync(join(basePath, ".gsd", "milestones", "M002", "NOTES.md"), "# local milestone note\n", "utf8");
715
+ return POP_OK;
716
+ };
717
+
718
+ const result = await _runMilestoneMergeWithStashRestore(ic, "M002");
719
+
720
+ assert.equal(result, null);
721
+ assert.match(
722
+ readFileSync(roadmapPath, "utf8"),
723
+ /Fresh DB Slice/,
724
+ "non-generated .gsd markdown must not block the required projection rebuild",
725
+ );
726
+ });
727
+ });
728
+
729
+ test("three-digit flat-phase projection restored by postflight suppresses successful merge rebuild", async () => {
730
+ await withProjectionBackedProject(async (basePath, roadmapPath) => {
731
+ insertFreshDbSlice();
732
+
733
+ const { ic } = buildIc({
734
+ preflightResult: STASH_PUSHED,
735
+ mergeBehavior: "succeed",
736
+ postflightResult: POP_OK,
737
+ });
738
+ (ic.s as { basePath: string; originalBasePath: string }).basePath = basePath;
739
+ (ic.s as { basePath: string; originalBasePath: string }).originalBasePath = basePath;
740
+ (ic.deps as unknown as { postflightPopStash: () => PostflightResult }).postflightPopStash = () => {
741
+ const restoredDir = join(basePath, ".gsd", "phases", "999-db-backed-planning");
742
+ mkdirSync(restoredDir, { recursive: true });
743
+ writeFileSync(join(restoredDir, "999-ROADMAP.md"), "# restored 3-digit projection\n", "utf8");
744
+ return POP_OK;
745
+ };
746
+
747
+ const result = await _runMilestoneMergeWithStashRestore(ic, "M002");
748
+
749
+ assert.equal(result, null);
750
+ assert.doesNotMatch(
751
+ readFileSync(roadmapPath, "utf8"),
752
+ /Fresh DB Slice/,
753
+ "three-digit flat-phase projections restored by postflight must suppress rebuild",
754
+ );
755
+ });
756
+ });
757
+
758
+ test("root DECISIONS projection restored by postflight suppresses successful merge rebuild", async () => {
759
+ await withProjectionBackedProject(async (basePath, roadmapPath) => {
760
+ insertFreshDbSlice();
761
+ insertDecision({
762
+ id: "D001",
763
+ when_context: "closeout",
764
+ scope: "global",
765
+ decision: "Regenerate decisions from DB",
766
+ choice: "Use database projection",
767
+ rationale: "Exercise root decision projection rebuild",
768
+ revisable: "Yes",
769
+ made_by: "agent",
770
+ superseded_by: null,
771
+ });
772
+
773
+ const { ic } = buildIc({
774
+ preflightResult: STASH_PUSHED,
775
+ mergeBehavior: "succeed",
776
+ postflightResult: POP_OK,
777
+ });
778
+ (ic.s as { basePath: string; originalBasePath: string }).basePath = basePath;
779
+ (ic.s as { basePath: string; originalBasePath: string }).originalBasePath = basePath;
780
+ (ic.deps as unknown as { postflightPopStash: () => PostflightResult }).postflightPopStash = () => {
781
+ writeFileSync(
782
+ join(basePath, ".gsd", "DECISIONS.md"),
783
+ "# local decisions edit restored from stash\n",
784
+ "utf8",
785
+ );
786
+ return POP_OK;
787
+ };
788
+
789
+ const result = await _runMilestoneMergeWithStashRestore(ic, "M002");
790
+
791
+ assert.equal(result, null);
792
+ assert.doesNotMatch(
793
+ readFileSync(roadmapPath, "utf8"),
794
+ /Fresh DB Slice/,
795
+ "root generated DECISIONS.md restored by postflight must suppress rebuild",
796
+ );
797
+ assert.equal(
798
+ readFileSync(join(basePath, ".gsd", "DECISIONS.md"), "utf8"),
799
+ "# local decisions edit restored from stash\n",
800
+ "rebuild must not overwrite restored root DECISIONS.md edits",
801
+ );
802
+ });
803
+ });
804
+
805
+ test("non-projection .gsd dirt does not suppress successful merge rebuild", async () => {
806
+ await withProjectionBackedProject(async (basePath, roadmapPath) => {
807
+ insertFreshDbSlice();
808
+ mkdirSync(join(basePath, ".gsd", "runtime"), { recursive: true });
809
+
810
+ const { ic } = buildIc({
811
+ preflightResult: STASH_PUSHED,
812
+ mergeBehavior: "succeed",
813
+ postflightResult: POP_OK,
814
+ });
815
+ (ic.s as { basePath: string; originalBasePath: string }).basePath = basePath;
816
+ (ic.s as { basePath: string; originalBasePath: string }).originalBasePath = basePath;
817
+ (ic.deps as unknown as { postflightPopStash: () => PostflightResult }).postflightPopStash = () => {
818
+ writeFileSync(join(basePath, ".gsd", "runtime", "restored.json"), "{}\n", "utf8");
819
+ return POP_OK;
820
+ };
821
+
822
+ const result = await _runMilestoneMergeWithStashRestore(ic, "M002");
823
+
824
+ assert.equal(result, null);
825
+ assert.match(
826
+ readFileSync(roadmapPath, "utf8"),
827
+ /Fresh DB Slice/,
828
+ "non-projection .gsd dirt must not block the required projection rebuild",
829
+ );
830
+ });
831
+ });
832
+
833
+ test("guarded merge stash restore and rebuild target the same canonical project root", async () => {
834
+ await withProjectionBackedProject(async (realRoot, roadmapPath) => {
835
+ insertFreshDbSlice();
836
+ let postflightProjectRoot: string | undefined;
837
+
838
+ const { ic } = buildIc({
839
+ preflightResult: STASH_PUSHED,
840
+ mergeBehavior: "succeed",
841
+ postflightResult: POP_OK,
842
+ });
843
+ // originalBasePath empty + basePath diverging from canonicalProjectRoot is
844
+ // the exact condition where the guard (snapshot / dirty check / rebuild)
845
+ // could run against a different tree than the postflight stash restore.
846
+ // The postflight restore must land on the same canonical root the guard
847
+ // inspects and the rebuild writes, otherwise restored edits are missed.
848
+ (ic.s as unknown as {
849
+ basePath: string;
850
+ originalBasePath: string;
851
+ canonicalProjectRoot: string;
852
+ }).basePath = "/tmp/gsd-decoy-basepath-not-the-project-root";
853
+ (ic.s as unknown as { originalBasePath: string }).originalBasePath = "";
854
+ (ic.s as unknown as { canonicalProjectRoot: string }).canonicalProjectRoot = realRoot;
855
+ (ic.deps as unknown as { postflightPopStash: (projectRoot: string) => PostflightResult }).postflightPopStash =
856
+ (projectRoot: string) => {
857
+ postflightProjectRoot = projectRoot;
858
+ writeFileSync(roadmapPath, "# projection edit restored from stash\n", "utf8");
859
+ return POP_OK;
860
+ };
861
+
862
+ const result = await _runMilestoneMergeWithStashRestore(ic, "M002");
863
+
864
+ assert.equal(result, null);
865
+ assert.equal(
866
+ postflightProjectRoot,
867
+ realRoot,
868
+ "postflight stash restore must run at the canonical project root the guard and rebuild use",
869
+ );
870
+ assert.equal(
871
+ readFileSync(roadmapPath, "utf8"),
872
+ "# projection edit restored from stash\n",
873
+ "restored projection edits on the canonical root must suppress the rebuild",
874
+ );
875
+ });
876
+ });
877
+
878
+ test("git-quoted special-character .gsd markdown paths still suppress rebuild", async () => {
879
+ await withProjectionBackedProject(async (basePath, roadmapPath) => {
880
+ insertFreshDbSlice();
881
+
882
+ const { ic } = buildIc({
883
+ preflightResult: STASH_PUSHED,
884
+ mergeBehavior: "succeed",
885
+ postflightResult: POP_OK,
886
+ });
887
+ (ic.s as { basePath: string; originalBasePath: string }).basePath = basePath;
888
+ (ic.s as { basePath: string; originalBasePath: string }).originalBasePath = basePath;
889
+ (ic.deps as unknown as { postflightPopStash: () => PostflightResult }).postflightPopStash = () => {
890
+ const restoredDir = join(basePath, ".gsd", "milestones", "M002", "restoréd");
891
+ mkdirSync(restoredDir, { recursive: true });
892
+ // A non-ASCII generated projection path that `git status --porcelain`
893
+ // C-quotes without -z; a naive line.endsWith(".md") check would miss the
894
+ // quoted path. NUL-delimited (-z) parsing sees the raw path and still
895
+ // detects it as generated projection dirt.
896
+ writeFileSync(join(restoredDir, "S99-SUMMARY.md"), "# restored local markdown\n", "utf8");
897
+ return POP_OK;
898
+ };
899
+
900
+ const result = await _runMilestoneMergeWithStashRestore(ic, "M002");
901
+
902
+ assert.equal(result, null);
903
+ assert.doesNotMatch(
904
+ readFileSync(roadmapPath, "utf8"),
905
+ /Fresh DB Slice/,
906
+ "special-character generated .gsd markdown restored by postflight must suppress the rebuild",
907
+ );
908
+ });
909
+ });
910
+
472
911
  test("merge succeeds but stash pop needs manual recovery -> postflight-stash-restore-failed break", async () => {
473
912
  const { ic, log } = buildIc({
474
913
  preflightResult: STASH_PUSHED,
@@ -5,6 +5,7 @@ import test from "node:test";
5
5
  import assert from "node:assert/strict";
6
6
 
7
7
  import {
8
+ createDefaultMilestoneMergeTransaction,
8
9
  createMilestoneMergeTransaction,
9
10
  runMilestoneMergeTransaction,
10
11
  } from "../milestone-merge-transaction.js";
@@ -44,3 +45,26 @@ test("createMilestoneMergeTransaction returns a lifecycle-compatible runner", ()
44
45
  codeFilesChanged: false,
45
46
  });
46
47
  });
48
+
49
+ test("createDefaultMilestoneMergeTransaction hides the legacy merge adapter behind the transaction seam", () => {
50
+ const calls: unknown[][] = [];
51
+ const runner = createDefaultMilestoneMergeTransaction(
52
+ (basePath, milestoneId, roadmapContent) => {
53
+ calls.push([basePath, milestoneId, roadmapContent]);
54
+ return {
55
+ pushed: false,
56
+ codeFilesChanged: true,
57
+ commitMessage: "merge M003",
58
+ prCreated: false,
59
+ };
60
+ },
61
+ );
62
+
63
+ assert.deepEqual(runner("/repo", "M003", "# M003"), {
64
+ pushed: false,
65
+ codeFilesChanged: true,
66
+ commitMessage: "merge M003",
67
+ prCreated: false,
68
+ });
69
+ assert.deepEqual(calls, [["/repo", "M003", "# M003"]]);
70
+ });
@@ -18,7 +18,7 @@ import { execFileSync } from "node:child_process";
18
18
 
19
19
  import { shouldUseWorktreeIsolation } from "../auto.ts";
20
20
  import { getIsolationMode } from "../preferences.ts";
21
- import { getActiveAutoWorktreeContext } from "../auto-worktree.ts";
21
+ import { getActiveAutoWorktreeContext } from "../auto-worktree-session-registry.ts";
22
22
  import { invalidateAllCaches } from "../cache.ts";
23
23
  import { describe, test, beforeEach, afterEach } from 'node:test';
24
24
  import assert from 'node:assert/strict';
@@ -26,12 +26,12 @@ import { join } from "node:path";
26
26
  import { tmpdir } from "node:os";
27
27
  import { execFileSync } from "node:child_process";
28
28
 
29
+ import { createAutoWorktree } from "../auto-worktree-creation.ts";
29
30
  import {
30
31
  getAutoWorktreeOriginalBase,
31
32
  _resetAutoWorktreeOriginalBaseForTests,
32
- createAutoWorktree,
33
- teardownAutoWorktree,
34
- } from "../auto-worktree.ts";
33
+ } from "../auto-worktree-session-registry.ts";
34
+ import { teardownAutoWorktree } from "../auto-worktree-teardown.ts";
35
35
  import { normalizeWorktreePathForCompare } from "../worktree-root.ts";
36
36
 
37
37
  // ─── Helpers ──────────────────────────────────────────────────────────────────
@@ -32,7 +32,7 @@ import { gsdRoot } from "../paths.ts";
32
32
  import {
33
33
  syncProjectRootToWorktree,
34
34
  syncStateToProjectRoot,
35
- } from "../auto-worktree.ts";
35
+ } from "../auto-worktree-sync.ts";
36
36
  import { writeLock, readCrashLock, clearLock } from "../crash-recovery.ts";
37
37
  import { describe, test, beforeEach, afterEach } from "node:test";
38
38
  import assert from "node:assert/strict";
@@ -12,7 +12,7 @@ import assert from "node:assert/strict";
12
12
  import { readFileSync, mkdtempSync, mkdirSync, writeFileSync, existsSync, readdirSync, rmSync } from "node:fs";
13
13
  import { join } from "node:path";
14
14
  import { tmpdir } from "node:os";
15
- import { syncWorktreeStateBack } from "../auto-worktree.ts";
15
+ import { syncGsdStateToWorktree, syncWorktreeStateBack } from "../auto-worktree-sync.ts";
16
16
 
17
17
  test("#2684: syncWorktreeStateBack does not overwrite project PREFERENCES.md", () => {
18
18
  const mainBase = mkdtempSync(join(tmpdir(), "gsd-wt-prefs-main-"));
@@ -58,8 +58,6 @@ test("syncGsdStateToWorktree copies canonical PREFERENCES.md", async () => {
58
58
  "---\nversion: 1\n---\n\npost_unit_hooks:\n - name: notify\n command: echo done\n",
59
59
  );
60
60
 
61
- // Import and call syncGsdStateToWorktree
62
- const { syncGsdStateToWorktree } = await import("../auto-worktree.ts");
63
61
  syncGsdStateToWorktree(srcBase, dstBase);
64
62
 
65
63
  // Verify PREFERENCES.md was copied
@@ -93,7 +91,6 @@ test("syncGsdStateToWorktree falls back to legacy lowercase preferences.md", asy
93
91
  "---\nversion: 1\n---\n\ngit:\n auto_push: true\n",
94
92
  );
95
93
 
96
- const { syncGsdStateToWorktree } = await import("../auto-worktree.ts");
97
94
  const result = syncGsdStateToWorktree(srcBase, dstBase);
98
95
 
99
96
  const copiedEntries = readdirSync(dstGsd)
@@ -24,6 +24,7 @@ import {
24
24
  loadProjectGSDPreferences,
25
25
  parsePreferencesMarkdown,
26
26
  renderPreferencesForSystemPrompt,
27
+ renderLanguageDirectiveForPrompt,
27
28
  clearGSDPreferencesCache,
28
29
  _resetParseWarningFlag,
29
30
  } from "../preferences.ts";
@@ -1674,6 +1675,24 @@ test("language: renderPreferencesForSystemPrompt omits language line when not se
1674
1675
  assert.ok(!output.includes("Always respond in"), `expected no language line in output, got:\n${output}`);
1675
1676
  });
1676
1677
 
1678
+ test("language: renderLanguageDirectiveForPrompt emits directive when language set", () => {
1679
+ const output = renderLanguageDirectiveForPrompt({ language: "Chinese" });
1680
+ assert.ok(output.includes("## Response Language"), `expected language heading, got:\n${output}`);
1681
+ assert.ok(output.includes("Always respond in Chinese"), `expected language instruction, got:\n${output}`);
1682
+ });
1683
+
1684
+ test("language: renderLanguageDirectiveForPrompt returns empty when language unset", () => {
1685
+ assert.equal(renderLanguageDirectiveForPrompt({}), "");
1686
+ assert.equal(renderLanguageDirectiveForPrompt(undefined), "");
1687
+ });
1688
+
1689
+ test("language: renderLanguageDirectiveForPrompt sanitizes newlines and caps length", () => {
1690
+ const output = renderLanguageDirectiveForPrompt({ language: `Chinese\ninjected${"x".repeat(80)}` });
1691
+ assert.ok(!output.includes("\ninjected"), `expected newlines stripped, got:\n${output}`);
1692
+ // "Chinese injected" + padding, capped to 50 chars after newline replacement.
1693
+ assert.ok(output.includes("Always respond in Chinese injected"), `got:\n${output}`);
1694
+ });
1695
+
1677
1696
  test("language: parses from markdown frontmatter", () => {
1678
1697
  const content = [
1679
1698
  "---",
@@ -79,6 +79,11 @@ const PREF_SAMPLE_VALUES: Record<string, unknown> = {
79
79
  min_request_interval_ms: 250,
80
80
  stale_commit_threshold_minutes: 15,
81
81
  context_management: { enabled: true },
82
+ tool_call_loop_guard: {
83
+ enabled: true,
84
+ identical_args: { enabled: true, max_consecutive_calls: 4 },
85
+ repeated_tool: { enabled: true, default_cap: 6, repeatable_cap: 15, exempt_tools: ["ctx_execute"] },
86
+ },
82
87
  experimental: { rtk: true },
83
88
  codebase: { indexing: "auto" },
84
89
  slice_parallel: { enabled: true, max_workers: 2 },
@@ -125,6 +125,58 @@ test('handleReopenSlice: rejects slice in a closed milestone', async () => {
125
125
  }
126
126
  });
127
127
 
128
+ test('handleReopenSlice: recovers a desynced slice (open status, completed tasks) — #1205', async () => {
129
+ const base = makeTmpBase();
130
+ openDatabase(join(base, '.gsd', 'gsd.db'));
131
+ try {
132
+ // Desync signature: slice left "pending" by a UAT→planning fallback while
133
+ // its tasks stayed "complete" from the first execution pass.
134
+ insertMilestone({ id: 'M001', title: 'Active', status: 'active' });
135
+ insertSlice({ id: 'S01', milestoneId: 'M001', status: 'pending' });
136
+ insertTask({ id: 'T01', sliceId: 'S01', milestoneId: 'M001', status: 'complete' });
137
+ insertTask({ id: 'T02', sliceId: 'S01', milestoneId: 'M001', status: 'complete' });
138
+
139
+ const result = await handleReopenSlice({ milestoneId: 'M001', sliceId: 'S01' }, base);
140
+
141
+ assert.ok(!('error' in result), `unexpected error: ${'error' in result ? result.error : ''}`);
142
+ assert.equal(result.tasksReset, 2, 'both completed tasks should be reset');
143
+
144
+ const slice = getSlice('M001', 'S01');
145
+ assert.equal(slice!.status, 'in_progress', 'slice should be reopened to in_progress');
146
+ const tasks = getSliceTasks('M001', 'S01');
147
+ assert.ok(tasks.every(t => t.status === 'pending'), 'all tasks should be reset to pending');
148
+ } finally {
149
+ cleanup(base);
150
+ }
151
+ });
152
+
153
+ test('handleReopenSlice: rejects an in-flight slice with mixed task progress and preserves it — #1205', async () => {
154
+ const base = makeTmpBase();
155
+ openDatabase(join(base, '.gsd', 'gsd.db'));
156
+ try {
157
+ // Normal in-flight slice, NOT the #1205 desync: one task finished while
158
+ // another is still in progress. The desync guard must not treat a single
159
+ // completed task as reopenable, or a full reset would wipe live progress.
160
+ insertMilestone({ id: 'M001', title: 'Active', status: 'active' });
161
+ insertSlice({ id: 'S01', milestoneId: 'M001', status: 'in_progress' });
162
+ insertTask({ id: 'T01', sliceId: 'S01', milestoneId: 'M001', status: 'complete' });
163
+ insertTask({ id: 'T02', sliceId: 'S01', milestoneId: 'M001', status: 'in_progress' });
164
+
165
+ const result = await handleReopenSlice({ milestoneId: 'M001', sliceId: 'S01' }, base);
166
+
167
+ assert.ok('error' in result, 'in-flight slice with unfinished tasks must be rejected');
168
+ assert.match(result.error, /not complete/);
169
+
170
+ // Progress must be untouched — nothing reset to pending.
171
+ const byId = Object.fromEntries(getSliceTasks('M001', 'S01').map(t => [t.id, t.status]));
172
+ assert.equal(byId['T01'], 'complete', 'completed task must stay complete');
173
+ assert.equal(byId['T02'], 'in_progress', 'in-progress task must stay in_progress');
174
+ assert.equal(getSlice('M001', 'S01')!.status, 'in_progress', 'slice status must be untouched');
175
+ } finally {
176
+ cleanup(base);
177
+ }
178
+ });
179
+
128
180
  test('handleReopenSlice: rejects reopening a slice that is not complete', async () => {
129
181
  const base = makeTmpBase();
130
182
  openDatabase(join(base, '.gsd', 'gsd.db'));