@opengsd/gsd-pi 1.4.0 → 1.5.0

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 (329) hide show
  1. package/README.md +11 -11
  2. package/dist/onboarding.js +11 -0
  3. package/dist/resources/.managed-resources-content-hash +1 -1
  4. package/dist/resources/GSD-WORKFLOW.md +20 -25
  5. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +118 -53
  6. package/dist/resources/extensions/cursor-cli/index.js +16 -0
  7. package/dist/resources/extensions/cursor-cli/models.js +63 -0
  8. package/dist/resources/extensions/cursor-cli/package.json +11 -0
  9. package/dist/resources/extensions/cursor-cli/readiness.js +128 -0
  10. package/dist/resources/extensions/cursor-cli/stream-adapter.js +353 -0
  11. package/dist/resources/extensions/gsd/activity-log.js +4 -1
  12. package/dist/resources/extensions/gsd/artifact-verification.js +25 -9
  13. package/dist/resources/extensions/gsd/auto/closeout.js +45 -42
  14. package/dist/resources/extensions/gsd/auto/finalize.js +9 -0
  15. package/dist/resources/extensions/gsd/auto/orchestrator.js +1 -1
  16. package/dist/resources/extensions/gsd/auto/run-unit.js +5 -2
  17. package/dist/resources/extensions/gsd/auto/schedule-wakeup.js +3 -0
  18. package/dist/resources/extensions/gsd/auto-dispatch.js +15 -5
  19. package/dist/resources/extensions/gsd/auto-model-selection.js +36 -18
  20. package/dist/resources/extensions/gsd/auto-prompts.js +76 -2
  21. package/dist/resources/extensions/gsd/auto-start.js +4 -3
  22. package/dist/resources/extensions/gsd/auto-timeout-recovery.js +4 -0
  23. package/dist/resources/extensions/gsd/auto-worktree.js +38 -11
  24. package/dist/resources/extensions/gsd/auto.js +23 -3
  25. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +3 -0
  26. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +114 -5
  27. package/dist/resources/extensions/gsd/bootstrap/schedule-wakeup-tool.js +58 -9
  28. package/dist/resources/extensions/gsd/bootstrap/tool-call-loop-guard.js +10 -2
  29. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +14 -1
  30. package/dist/resources/extensions/gsd/codebase-generator.js +157 -19
  31. package/dist/resources/extensions/gsd/commands/handlers/core.js +40 -4
  32. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +153 -1
  33. package/dist/resources/extensions/gsd/commands-worktree.js +11 -2
  34. package/dist/resources/extensions/gsd/db/queries.js +39 -0
  35. package/dist/resources/extensions/gsd/discussion-handoff.js +8 -3
  36. package/dist/resources/extensions/gsd/docs/preferences-reference.md +8 -2
  37. package/dist/resources/extensions/gsd/doctor-engine-checks.js +206 -14
  38. package/dist/resources/extensions/gsd/doctor-providers.js +38 -10
  39. package/dist/resources/extensions/gsd/doctor.js +93 -3
  40. package/dist/resources/extensions/gsd/exec-sandbox.js +6 -2
  41. package/dist/resources/extensions/gsd/flat-phase-migration.js +74 -23
  42. package/dist/resources/extensions/gsd/git-service.js +14 -18
  43. package/dist/resources/extensions/gsd/gsd-db.js +52 -0
  44. package/dist/resources/extensions/gsd/guidance.js +1 -0
  45. package/dist/resources/extensions/gsd/guided-flow.js +40 -2
  46. package/dist/resources/extensions/gsd/key-manager.js +1 -0
  47. package/dist/resources/extensions/gsd/markdown-renderer.js +25 -8
  48. package/dist/resources/extensions/gsd/migrate-external.js +24 -7
  49. package/dist/resources/extensions/gsd/milestone-merge-transaction.js +3 -3
  50. package/dist/resources/extensions/gsd/preferences-diagnostics.js +24 -1
  51. package/dist/resources/extensions/gsd/preferences-models.js +6 -4
  52. package/dist/resources/extensions/gsd/preferences-validation.js +25 -2
  53. package/dist/resources/extensions/gsd/preferences.js +29 -2
  54. package/dist/resources/extensions/gsd/prompts/plan-slice.md +4 -2
  55. package/dist/resources/extensions/gsd/redact-secrets.js +37 -0
  56. package/dist/resources/extensions/gsd/repo-identity.js +16 -0
  57. package/dist/resources/extensions/gsd/repository-registry.js +6 -1
  58. package/dist/resources/extensions/gsd/root-write-leak-guard.js +4 -2
  59. package/dist/resources/extensions/gsd/safe-fs.js +23 -2
  60. package/dist/resources/extensions/gsd/skills/gsd-headless/SKILL.md +7 -9
  61. package/dist/resources/extensions/gsd/skills/gsd-headless/references/commands.md +1 -0
  62. package/dist/resources/extensions/gsd/tools/plan-task.js +76 -8
  63. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +40 -0
  64. package/dist/resources/extensions/gsd/unit-context-manifest.js +5 -4
  65. package/dist/resources/extensions/gsd/unit-registry.js +27 -2
  66. package/dist/resources/extensions/gsd/unit-runtime.js +21 -0
  67. package/dist/resources/extensions/gsd/workspace.js +26 -8
  68. package/dist/resources/extensions/gsd/worktree-lifecycle.js +67 -5
  69. package/dist/resources/extensions/gsd/worktree-manager.js +97 -4
  70. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  71. package/dist/web/standalone/.next/BUILD_ID +1 -1
  72. package/dist/web/standalone/.next/app-path-routes-manifest.json +7 -7
  73. package/dist/web/standalone/.next/build-manifest.json +2 -2
  74. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  75. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  77. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  78. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  79. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  80. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  81. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  82. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  83. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  84. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  85. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  86. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  87. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  88. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  89. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  90. package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
  91. package/dist/web/standalone/.next/server/app/index.html +1 -1
  92. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  93. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  94. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  95. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  96. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  97. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  98. package/dist/web/standalone/.next/server/app-paths-manifest.json +7 -7
  99. package/dist/web/standalone/.next/server/chunks/8357.js +1 -1
  100. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  101. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  102. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  103. package/dist/web/standalone/node_modules/@gsd/native/package.json +1 -1
  104. package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
  105. package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
  106. package/dist/wizard.d.ts +8 -0
  107. package/dist/wizard.js +8 -1
  108. package/integrations/hermes/CONTEXT.md +47 -0
  109. package/integrations/hermes/README.md +21 -0
  110. package/integrations/hermes/__init__.py +31 -0
  111. package/integrations/hermes/docs/issue-1162-grilling.md +197 -0
  112. package/integrations/hermes/docs/setup.md +4 -0
  113. package/integrations/hermes/open_gsd_hermes/commands.py +141 -0
  114. package/integrations/hermes/open_gsd_hermes/config.py +2 -0
  115. package/integrations/hermes/open_gsd_hermes/gsd_client.py +594 -31
  116. package/integrations/hermes/open_gsd_hermes/notifications.py +10 -1
  117. package/integrations/hermes/plugin.yaml +4 -0
  118. package/integrations/hermes/tests/test_config.py +6 -0
  119. package/integrations/hermes/tests/test_directory_loader.py +37 -0
  120. package/integrations/hermes/tests/test_gsd_client_cache.py +3 -0
  121. package/integrations/hermes/tests/test_gsd_client_execute.py +35 -0
  122. package/integrations/hermes/tests/test_gsd_client_process_cwd.py +22 -0
  123. package/integrations/hermes/tests/test_gsd_client_timeout.py +98 -0
  124. package/integrations/hermes/tests/test_milestone_client.py +447 -0
  125. package/integrations/hermes/tests/test_new_milestone_command.py +313 -0
  126. package/integrations/hermes/tests/test_supervisor_fsm.py +40 -0
  127. package/package.json +8 -8
  128. package/packages/cloud-mcp-gateway/dist/auth-store.d.ts +1 -0
  129. package/packages/cloud-mcp-gateway/dist/auth-store.d.ts.map +1 -1
  130. package/packages/cloud-mcp-gateway/dist/auth-store.js +21 -1
  131. package/packages/cloud-mcp-gateway/dist/auth-store.js.map +1 -1
  132. package/packages/cloud-mcp-gateway/dist/auth-store.test.js +34 -0
  133. package/packages/cloud-mcp-gateway/dist/auth-store.test.js.map +1 -1
  134. package/packages/cloud-mcp-gateway/package.json +2 -2
  135. package/packages/contracts/package.json +1 -1
  136. package/packages/daemon/dist/cloud-runtime.d.ts +2 -0
  137. package/packages/daemon/dist/cloud-runtime.d.ts.map +1 -1
  138. package/packages/daemon/dist/cloud-runtime.js +25 -1
  139. package/packages/daemon/dist/cloud-runtime.js.map +1 -1
  140. package/packages/daemon/dist/cloud-runtime.test.js +51 -0
  141. package/packages/daemon/dist/cloud-runtime.test.js.map +1 -1
  142. package/packages/daemon/dist/message-batcher.d.ts +7 -0
  143. package/packages/daemon/dist/message-batcher.d.ts.map +1 -1
  144. package/packages/daemon/dist/message-batcher.js +13 -0
  145. package/packages/daemon/dist/message-batcher.js.map +1 -1
  146. package/packages/daemon/dist/message-batcher.test.js +48 -0
  147. package/packages/daemon/dist/message-batcher.test.js.map +1 -1
  148. package/packages/daemon/dist/session-manager.js +1 -1
  149. package/packages/daemon/dist/session-manager.js.map +1 -1
  150. package/packages/daemon/package.json +5 -5
  151. package/packages/gsd-agent-core/dist/compaction/compaction.d.ts +13 -1
  152. package/packages/gsd-agent-core/dist/compaction/compaction.d.ts.map +1 -1
  153. package/packages/gsd-agent-core/dist/compaction/compaction.js +15 -1
  154. package/packages/gsd-agent-core/dist/compaction/compaction.js.map +1 -1
  155. package/packages/gsd-agent-core/dist/session/agent-session-navigation.d.ts.map +1 -1
  156. package/packages/gsd-agent-core/dist/session/agent-session-navigation.js +4 -1
  157. package/packages/gsd-agent-core/dist/session/agent-session-navigation.js.map +1 -1
  158. package/packages/gsd-agent-core/package.json +5 -5
  159. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-segment-walker.d.ts.map +1 -1
  160. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-segment-walker.js +1 -0
  161. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-segment-walker.js.map +1 -1
  162. package/packages/gsd-agent-modes/dist/modes/rpc/rpc-client.js +1 -1
  163. package/packages/gsd-agent-modes/dist/modes/rpc/rpc-client.js.map +1 -1
  164. package/packages/gsd-agent-modes/package.json +7 -7
  165. package/packages/mcp-server/dist/server.d.ts +18 -5
  166. package/packages/mcp-server/dist/server.d.ts.map +1 -1
  167. package/packages/mcp-server/dist/server.js +46 -5
  168. package/packages/mcp-server/dist/server.js.map +1 -1
  169. package/packages/mcp-server/dist/session-manager.js +1 -1
  170. package/packages/mcp-server/dist/session-manager.js.map +1 -1
  171. package/packages/mcp-server/package.json +4 -4
  172. package/packages/native/package.json +1 -1
  173. package/packages/pi-agent-core/package.json +1 -1
  174. package/packages/pi-ai/dist/env-api-keys.d.ts.map +1 -1
  175. package/packages/pi-ai/dist/env-api-keys.js +3 -0
  176. package/packages/pi-ai/dist/env-api-keys.js.map +1 -1
  177. package/packages/pi-ai/dist/types.d.ts +2 -2
  178. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  179. package/packages/pi-ai/dist/types.js.map +1 -1
  180. package/packages/pi-ai/package.json +1 -1
  181. package/packages/pi-coding-agent/dist/core/model-resolver.d.ts.map +1 -1
  182. package/packages/pi-coding-agent/dist/core/model-resolver.js +1 -0
  183. package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
  184. package/packages/pi-coding-agent/dist/core/session-manager-types.d.ts +1 -0
  185. package/packages/pi-coding-agent/dist/core/session-manager-types.d.ts.map +1 -1
  186. package/packages/pi-coding-agent/dist/core/session-manager-types.js.map +1 -1
  187. package/packages/pi-coding-agent/dist/core/session-manager.d.ts.map +1 -1
  188. package/packages/pi-coding-agent/dist/core/session-manager.js +3 -0
  189. package/packages/pi-coding-agent/dist/core/session-manager.js.map +1 -1
  190. package/packages/pi-coding-agent/package.json +7 -7
  191. package/packages/pi-tui/package.json +2 -2
  192. package/packages/rpc-client/dist/rpc-client.js +1 -1
  193. package/packages/rpc-client/dist/rpc-client.js.map +1 -1
  194. package/packages/rpc-client/package.json +2 -2
  195. package/pkg/package.json +1 -1
  196. package/scripts/link-workspace-packages.cjs +14 -4
  197. package/src/resources/GSD-WORKFLOW.md +20 -25
  198. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +130 -51
  199. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +371 -39
  200. package/src/resources/extensions/cursor-cli/index.ts +18 -0
  201. package/src/resources/extensions/cursor-cli/models.ts +65 -0
  202. package/src/resources/extensions/cursor-cli/package.json +11 -0
  203. package/src/resources/extensions/cursor-cli/readiness.ts +143 -0
  204. package/src/resources/extensions/cursor-cli/stream-adapter.ts +440 -0
  205. package/src/resources/extensions/cursor-cli/tests/index.test.ts +54 -0
  206. package/src/resources/extensions/cursor-cli/tests/readiness.test.ts +29 -0
  207. package/src/resources/extensions/cursor-cli/tests/stream-adapter.test.ts +166 -0
  208. package/src/resources/extensions/gsd/activity-log.ts +4 -1
  209. package/src/resources/extensions/gsd/artifact-verification.ts +25 -9
  210. package/src/resources/extensions/gsd/auto/closeout.ts +54 -56
  211. package/src/resources/extensions/gsd/auto/finalize.ts +8 -0
  212. package/src/resources/extensions/gsd/auto/loop-deps.ts +1 -0
  213. package/src/resources/extensions/gsd/auto/orchestrator.ts +1 -1
  214. package/src/resources/extensions/gsd/auto/run-unit.ts +5 -2
  215. package/src/resources/extensions/gsd/auto/schedule-wakeup.ts +8 -0
  216. package/src/resources/extensions/gsd/auto-dispatch.ts +17 -5
  217. package/src/resources/extensions/gsd/auto-model-selection.ts +49 -18
  218. package/src/resources/extensions/gsd/auto-prompts.ts +73 -2
  219. package/src/resources/extensions/gsd/auto-start.ts +4 -3
  220. package/src/resources/extensions/gsd/auto-timeout-recovery.ts +4 -0
  221. package/src/resources/extensions/gsd/auto-worktree.ts +50 -11
  222. package/src/resources/extensions/gsd/auto.ts +22 -3
  223. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +3 -0
  224. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +128 -5
  225. package/src/resources/extensions/gsd/bootstrap/schedule-wakeup-tool.ts +80 -12
  226. package/src/resources/extensions/gsd/bootstrap/tool-call-loop-guard.ts +12 -2
  227. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +23 -1
  228. package/src/resources/extensions/gsd/codebase-generator.ts +179 -18
  229. package/src/resources/extensions/gsd/commands/handlers/core.ts +39 -3
  230. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +135 -1
  231. package/src/resources/extensions/gsd/commands-worktree.ts +13 -2
  232. package/src/resources/extensions/gsd/db/queries.ts +40 -0
  233. package/src/resources/extensions/gsd/discussion-handoff.ts +10 -2
  234. package/src/resources/extensions/gsd/docs/preferences-reference.md +8 -2
  235. package/src/resources/extensions/gsd/doctor-engine-checks.ts +269 -18
  236. package/src/resources/extensions/gsd/doctor-providers.ts +39 -10
  237. package/src/resources/extensions/gsd/doctor-types.ts +5 -1
  238. package/src/resources/extensions/gsd/doctor.ts +95 -3
  239. package/src/resources/extensions/gsd/exec-sandbox.ts +6 -2
  240. package/src/resources/extensions/gsd/flat-phase-migration.ts +98 -22
  241. package/src/resources/extensions/gsd/git-service.ts +14 -17
  242. package/src/resources/extensions/gsd/gsd-db.ts +72 -0
  243. package/src/resources/extensions/gsd/guidance.ts +2 -0
  244. package/src/resources/extensions/gsd/guided-flow.ts +45 -2
  245. package/src/resources/extensions/gsd/key-manager.ts +1 -0
  246. package/src/resources/extensions/gsd/markdown-renderer.ts +25 -7
  247. package/src/resources/extensions/gsd/migrate-external.ts +23 -6
  248. package/src/resources/extensions/gsd/milestone-merge-transaction.ts +4 -4
  249. package/src/resources/extensions/gsd/model-router.ts +4 -5
  250. package/src/resources/extensions/gsd/preferences-diagnostics.ts +24 -0
  251. package/src/resources/extensions/gsd/preferences-models.ts +6 -3
  252. package/src/resources/extensions/gsd/preferences-validation.ts +28 -2
  253. package/src/resources/extensions/gsd/preferences.ts +38 -4
  254. package/src/resources/extensions/gsd/prompts/plan-slice.md +4 -2
  255. package/src/resources/extensions/gsd/redact-secrets.ts +40 -0
  256. package/src/resources/extensions/gsd/repo-identity.ts +17 -0
  257. package/src/resources/extensions/gsd/repository-registry.ts +7 -1
  258. package/src/resources/extensions/gsd/root-write-leak-guard.ts +3 -2
  259. package/src/resources/extensions/gsd/safe-fs.ts +22 -2
  260. package/src/resources/extensions/gsd/skills/gsd-headless/SKILL.md +7 -9
  261. package/src/resources/extensions/gsd/skills/gsd-headless/references/commands.md +1 -0
  262. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +131 -5
  263. package/src/resources/extensions/gsd/tests/auto-migrating-recovery.test.ts +18 -0
  264. package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +178 -0
  265. package/src/resources/extensions/gsd/tests/auto-phases-lifecycle.test.ts +43 -1
  266. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +54 -0
  267. package/src/resources/extensions/gsd/tests/auto-remote-session-lock-cleanup.test.ts +41 -0
  268. package/src/resources/extensions/gsd/tests/auto-runtime-state.test.ts +41 -0
  269. package/src/resources/extensions/gsd/tests/auto-session-scope.test.ts +4 -4
  270. package/src/resources/extensions/gsd/tests/auto-start-bootstrap-await-3420.test.ts +3 -0
  271. package/src/resources/extensions/gsd/tests/auto-timeout-recovery-harness-abort.test.ts +55 -0
  272. package/src/resources/extensions/gsd/tests/auto-warning-noise-regression.test.ts +8 -0
  273. package/src/resources/extensions/gsd/tests/batch-task-query.test.ts +85 -0
  274. package/src/resources/extensions/gsd/tests/check-auto-start-ready-guard.test.ts +29 -0
  275. package/src/resources/extensions/gsd/tests/codebase-generator.test.ts +365 -0
  276. package/src/resources/extensions/gsd/tests/complete-milestone-excerpt.test.ts +62 -1
  277. package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +138 -1
  278. package/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts +397 -5
  279. package/src/resources/extensions/gsd/tests/doctor-workspace.test.ts +207 -0
  280. package/src/resources/extensions/gsd/tests/flat-phase-migration.test.ts +38 -1
  281. package/src/resources/extensions/gsd/tests/flat-rate-routing-guard.test.ts +87 -19
  282. package/src/resources/extensions/gsd/tests/gate-1b-orphan-discrimination.test.ts +89 -0
  283. package/src/resources/extensions/gsd/tests/guided-flow-session-isolation.test.ts +9 -0
  284. package/src/resources/extensions/gsd/tests/integration/state-machine-edge-cases.test.ts +4 -2
  285. package/src/resources/extensions/gsd/tests/markdown-renderer.test.ts +122 -0
  286. package/src/resources/extensions/gsd/tests/merge-conflict-stops-loop.test.ts +4 -4
  287. package/src/resources/extensions/gsd/tests/migrate-external-worktree.test.ts +116 -0
  288. package/src/resources/extensions/gsd/tests/milestone-merge-stash-restore.test.ts +152 -22
  289. package/src/resources/extensions/gsd/tests/milestone-merge-transaction.test.ts +1 -1
  290. package/src/resources/extensions/gsd/tests/pending-autostart-scope.test.ts +2 -2
  291. package/src/resources/extensions/gsd/tests/phases-merge-error-stops-auto.test.ts +50 -6
  292. package/src/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +17 -0
  293. package/src/resources/extensions/gsd/tests/plan-task.test.ts +264 -1
  294. package/src/resources/extensions/gsd/tests/preferences.test.ts +150 -0
  295. package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +142 -0
  296. package/src/resources/extensions/gsd/tests/recovery-verify-logs.test.ts +4 -4
  297. package/src/resources/extensions/gsd/tests/redact-secrets.test.ts +56 -0
  298. package/src/resources/extensions/gsd/tests/register-hooks-flat-phase-migration.test.ts +73 -0
  299. package/src/resources/extensions/gsd/tests/register-hooks-loop-guard-auto.test.ts +275 -0
  300. package/src/resources/extensions/gsd/tests/repository-registry.test.ts +41 -2
  301. package/src/resources/extensions/gsd/tests/require-slice-discussion-dispatch.test.ts +55 -0
  302. package/src/resources/extensions/gsd/tests/root-write-leak-guard.test.ts +31 -9
  303. package/src/resources/extensions/gsd/tests/schedule-wakeup-tool.test.ts +167 -0
  304. package/src/resources/extensions/gsd/tests/stalled-tool-recovery.test.ts +49 -0
  305. package/src/resources/extensions/gsd/tests/state-corruption-2945.test.ts +1 -1
  306. package/src/resources/extensions/gsd/tests/status-per-repo.test.ts +94 -0
  307. package/src/resources/extensions/gsd/tests/token-tool-gating.test.ts +35 -0
  308. package/src/resources/extensions/gsd/tests/tool-call-loop-guard.test.ts +63 -0
  309. package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +66 -18
  310. package/src/resources/extensions/gsd/tests/unit-registry.test.ts +56 -0
  311. package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +30 -0
  312. package/src/resources/extensions/gsd/tests/uok-gitops-turn-action.test.ts +52 -0
  313. package/src/resources/extensions/gsd/tests/validator-scope-parity.test.ts +53 -1
  314. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +208 -1
  315. package/src/resources/extensions/gsd/tests/workspace.test.ts +18 -4
  316. package/src/resources/extensions/gsd/tests/worktree-journal-events.test.ts +4 -4
  317. package/src/resources/extensions/gsd/tests/worktree-lifecycle.test.ts +2 -2
  318. package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +31 -1
  319. package/src/resources/extensions/gsd/tests/worktree-teardown-safety.test.ts +32 -0
  320. package/src/resources/extensions/gsd/tools/plan-task.ts +107 -9
  321. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +58 -0
  322. package/src/resources/extensions/gsd/unit-context-manifest.ts +11 -5
  323. package/src/resources/extensions/gsd/unit-registry.ts +34 -2
  324. package/src/resources/extensions/gsd/unit-runtime.ts +42 -0
  325. package/src/resources/extensions/gsd/workspace.ts +42 -8
  326. package/src/resources/extensions/gsd/worktree-lifecycle.ts +135 -11
  327. package/src/resources/extensions/gsd/worktree-manager.ts +143 -5
  328. /package/dist/web/standalone/.next/static/{zQLcXm0Za8lexoXtgDpMG → bCg3aYrFN1DKZr2dqgE7L}/_buildManifest.js +0 -0
  329. /package/dist/web/standalone/.next/static/{zQLcXm0Za8lexoXtgDpMG → bCg3aYrFN1DKZr2dqgE7L}/_ssgManifest.js +0 -0
@@ -22,6 +22,8 @@ import {
22
22
  getMilestoneSlices,
23
23
  getSliceTasks,
24
24
  updateSliceStatus,
25
+ insertGateRow,
26
+ saveGateResult,
25
27
  _getAdapter,
26
28
  } from '../gsd-db.ts';
27
29
  import {
@@ -44,6 +46,7 @@ import {
44
46
  parseSummary,
45
47
  parseTaskPlanFile,
46
48
  clearParseCache,
49
+ extractSection,
47
50
  } from '../files.ts';
48
51
  import { clearPathCache, _clearGsdRootCache } from '../paths.ts';
49
52
  import { invalidateStateCache } from '../state.ts';
@@ -645,6 +648,125 @@ test('── markdown-renderer: slice plan summarizes task descriptions without
645
648
  }
646
649
  });
647
650
 
651
+ test('── markdown-renderer: renderPlanFromDb marks flagged slice gates ──', async () => {
652
+ const tmpDir = makeTmpDir();
653
+ const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
654
+ openDatabase(dbPath);
655
+ clearAllCaches();
656
+
657
+ try {
658
+ insertMilestone({ id: 'M001', title: 'Milestone', status: 'active' });
659
+ scaffoldDirs(tmpDir, 'M001', ['S01']);
660
+ insertSlice({
661
+ id: 'S01',
662
+ milestoneId: 'M001',
663
+ title: 'Risky slice',
664
+ status: 'pending',
665
+ demo: 'Reviewers can see flagged gates.',
666
+ planning: {
667
+ goal: 'Persist gate concern state.',
668
+ successCriteria: '- Flagged gates stay visible',
669
+ },
670
+ });
671
+ insertTask({
672
+ id: 'T01',
673
+ sliceId: 'S01',
674
+ milestoneId: 'M001',
675
+ title: 'Render gate state',
676
+ status: 'pending',
677
+ });
678
+ insertGateRow({ milestoneId: 'M001', sliceId: 'S01', gateId: 'Q3', scope: 'slice' });
679
+ insertGateRow({ milestoneId: 'M001', sliceId: 'S01', gateId: 'Q4', scope: 'slice' });
680
+
681
+ const findings = '- Shared gate findings.';
682
+ saveGateResult({
683
+ milestoneId: 'M001',
684
+ sliceId: 'S01',
685
+ gateId: 'Q3',
686
+ verdict: 'flag',
687
+ rationale: 'Concern recorded.',
688
+ findings,
689
+ });
690
+ saveGateResult({
691
+ milestoneId: 'M001',
692
+ sliceId: 'S01',
693
+ gateId: 'Q4',
694
+ verdict: 'pass',
695
+ rationale: 'No concern.',
696
+ findings,
697
+ });
698
+
699
+ const rendered = await renderPlanFromDb(tmpDir, 'M001', 'S01');
700
+ const threatSurface = extractSection(rendered.content, 'Threat Surface');
701
+ const requirementImpact = extractSection(rendered.content, 'Requirement Impact');
702
+
703
+ assert.ok(threatSurface?.includes('> **Warning:** Verdict: flag - concerns recorded below'));
704
+ assert.ok(threatSurface?.includes(findings));
705
+ assert.ok(!requirementImpact?.includes('Verdict: flag'), 'passing gate should not render a flag warning');
706
+ assert.ok(requirementImpact?.includes(findings), 'passing gate still renders findings');
707
+ } finally {
708
+ closeDatabase();
709
+ cleanupDir(tmpDir);
710
+ }
711
+ });
712
+
713
+ test('── markdown-renderer: renderTaskPlanFromDb marks flagged task gates ──', async () => {
714
+ const tmpDir = makeTmpDir();
715
+ const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
716
+ openDatabase(dbPath);
717
+ clearAllCaches();
718
+
719
+ try {
720
+ insertMilestone({ id: 'M001', title: 'Milestone', status: 'active' });
721
+ scaffoldDirs(tmpDir, 'M001', ['S01']);
722
+ insertSlice({ id: 'S01', milestoneId: 'M001', title: 'Slice', status: 'pending' });
723
+ insertTask({
724
+ id: 'T01',
725
+ sliceId: 'S01',
726
+ milestoneId: 'M001',
727
+ title: 'Task gate render',
728
+ status: 'pending',
729
+ planning: {
730
+ description: 'Render task quality gates.',
731
+ },
732
+ });
733
+ insertGateRow({ milestoneId: 'M001', sliceId: 'S01', gateId: 'Q5', scope: 'task', taskId: 'T01' });
734
+ insertGateRow({ milestoneId: 'M001', sliceId: 'S01', gateId: 'Q6', scope: 'task', taskId: 'T01' });
735
+
736
+ const findings = '- Same task findings.';
737
+ saveGateResult({
738
+ milestoneId: 'M001',
739
+ sliceId: 'S01',
740
+ taskId: 'T01',
741
+ gateId: 'Q5',
742
+ verdict: 'flag',
743
+ rationale: 'Concern recorded.',
744
+ findings,
745
+ });
746
+ saveGateResult({
747
+ milestoneId: 'M001',
748
+ sliceId: 'S01',
749
+ taskId: 'T01',
750
+ gateId: 'Q6',
751
+ verdict: 'pass',
752
+ rationale: 'No concern.',
753
+ findings,
754
+ });
755
+
756
+ const rendered = await renderTaskPlanFromDb(tmpDir, 'M001', 'S01', 'T01');
757
+ const failureModes = extractSection(rendered.content, 'Failure Modes');
758
+ const loadProfile = extractSection(rendered.content, 'Load Profile');
759
+
760
+ assert.ok(failureModes?.includes('> **Warning:** Verdict: flag - concerns recorded below'));
761
+ assert.ok(failureModes?.includes(findings));
762
+ assert.ok(!loadProfile?.includes('Verdict: flag'), 'passing task gate should not render a flag warning');
763
+ assert.ok(loadProfile?.includes(findings), 'passing task gate still renders findings');
764
+ } finally {
765
+ closeDatabase();
766
+ cleanupDir(tmpDir);
767
+ }
768
+ });
769
+
648
770
  test('── markdown-renderer: renderTaskPlanFromDb throws for missing task ──', async () => {
649
771
  const tmpDir = makeTmpDir();
650
772
  const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
@@ -95,7 +95,7 @@ function makeDeps(
95
95
  isInAutoWorktree: () => true,
96
96
  shouldUseWorktreeIsolation: () => true,
97
97
  getIsolationMode: () => "worktree",
98
- mergeMilestoneToMain: () => ({ pushed: false, codeFilesChanged: true }),
98
+ mergeMilestone: () => ({ pushed: false, codeFilesChanged: true }),
99
99
  syncWorktreeStateBack: () => ({ synced: [] }),
100
100
  teardownAutoWorktree: () => undefined,
101
101
  createAutoWorktree: () => "",
@@ -185,7 +185,7 @@ describe("WorktreeResolver.mergeAndExit re-throws MergeConflictError (#2330)", (
185
185
  resolveMilestoneFile: (_base: string, _mid: string, type: string) =>
186
186
  type === "ROADMAP" ? roadmapPath : null,
187
187
  readFileSync: () => "# M001\n",
188
- mergeMilestoneToMain: () => {
188
+ mergeMilestone: () => {
189
189
  throw new MergeConflictError(conflicted, "squash", "worktree/M001", "main");
190
190
  },
191
191
  });
@@ -216,7 +216,7 @@ describe("WorktreeResolver.mergeAndExit re-throws MergeConflictError (#2330)", (
216
216
  resolveMilestoneFile: (_base: string, _mid: string, type: string) =>
217
217
  type === "ROADMAP" ? roadmapPath : null,
218
218
  readFileSync: () => "# M001\n",
219
- mergeMilestoneToMain: () => {
219
+ mergeMilestone: () => {
220
220
  throw new FakePermError("EACCES: permission denied");
221
221
  },
222
222
  });
@@ -242,7 +242,7 @@ describe("WorktreeResolver.mergeAndExit re-throws MergeConflictError (#2330)", (
242
242
  resolveMilestoneFile: (_base: string, _mid: string, type: string) =>
243
243
  type === "ROADMAP" ? roadmapPath : null,
244
244
  readFileSync: () => "# M001\n",
245
- mergeMilestoneToMain: () => ({ pushed: false, codeFilesChanged: true }),
245
+ mergeMilestone: () => ({ pushed: false, codeFilesChanged: true }),
246
246
  });
247
247
 
248
248
  const resolver = makeResolver(makeSession(baseDir), deps);
@@ -16,6 +16,7 @@ import { tmpdir } from "node:os";
16
16
  import { execSync } from "node:child_process";
17
17
 
18
18
  import { migrateToExternalState, recoverFailedMigration } from "../migrate-external.ts";
19
+ import { externalGsdRoot } from "../repo-identity.ts";
19
20
 
20
21
  function run(command: string, cwd: string): string {
21
22
  return execSync(command, {
@@ -119,6 +120,121 @@ describe("migrate-external worktree guard (#2970)", () => {
119
120
  });
120
121
  });
121
122
 
123
+ describe("migrateToExternalState rename fallback (#1179)", () => {
124
+ test("falls back to copy/delete when rename fails with EACCES", () => {
125
+ const base = realpathSync(mkdtempSync(join(tmpdir(), "gsd-migrate-eacces-")));
126
+ const stateDir = realpathSync(mkdtempSync(join(tmpdir(), "gsd-state-eacces-")));
127
+ const previousStateDir = process.env.GSD_STATE_DIR;
128
+ const previousProjectId = process.env.GSD_PROJECT_ID;
129
+ const originalRenameSync = fs.renameSync;
130
+ const localGsd = join(base, ".gsd");
131
+ const migratingPath = join(base, ".gsd.migrating");
132
+ let renameAttempts = 0;
133
+
134
+ process.env.GSD_STATE_DIR = stateDir;
135
+ process.env.GSD_PROJECT_ID = "rename-eacces";
136
+
137
+ try {
138
+ run("git init -b main", base);
139
+ mkdirSync(localGsd, { recursive: true });
140
+ writeFileSync(join(localGsd, "PREFERENCES.md"), "# prefs\n", "utf-8");
141
+
142
+ fs.renameSync = ((src, dst) => {
143
+ if (String(src) === localGsd && String(dst) === migratingPath) {
144
+ renameAttempts += 1;
145
+ const error = new Error("simulated WSL/DrvFs rename lock") as NodeJS.ErrnoException;
146
+ error.code = "EACCES";
147
+ throw error;
148
+ }
149
+ return originalRenameSync(src, dst);
150
+ }) as typeof fs.renameSync;
151
+ syncBuiltinESMExports();
152
+
153
+ const result = migrateToExternalState(base);
154
+
155
+ assert.equal(result.migrated, true, "EACCES rename failure should use copy/delete fallback");
156
+ assert.equal(result.error, undefined);
157
+ assert.equal(renameAttempts, 1, "should exercise the EACCES rename fallback once");
158
+ assert.equal(readFileSync(join(stateDir, "projects", "rename-eacces", "PREFERENCES.md"), "utf-8"), "# prefs\n");
159
+ assert.equal(readFileSync(join(base, ".gsd", "PREFERENCES.md"), "utf-8"), "# prefs\n");
160
+ assert.ok(!existsSync(migratingPath), "backup staging dir must be removed after successful migration");
161
+ } finally {
162
+ fs.renameSync = originalRenameSync;
163
+ syncBuiltinESMExports();
164
+ if (previousStateDir === undefined) {
165
+ delete process.env.GSD_STATE_DIR;
166
+ } else {
167
+ process.env.GSD_STATE_DIR = previousStateDir;
168
+ }
169
+ if (previousProjectId === undefined) {
170
+ delete process.env.GSD_PROJECT_ID;
171
+ } else {
172
+ process.env.GSD_PROJECT_ID = previousProjectId;
173
+ }
174
+ rmSync(base, { recursive: true, force: true });
175
+ rmSync(stateDir, { recursive: true, force: true });
176
+ }
177
+ });
178
+ });
179
+
180
+ describe("migrateToExternalState already-migrated external state guard (#1178)", () => {
181
+ test("skips migration when external state already exists for a real local .gsd directory", () => {
182
+ const base = realpathSync(mkdtempSync(join(tmpdir(), "gsd-migrate-already-external-")));
183
+ const stateDir = realpathSync(mkdtempSync(join(tmpdir(), "gsd-state-already-external-")));
184
+ const previousStateDir = process.env.GSD_STATE_DIR;
185
+ const previousProjectId = process.env.GSD_PROJECT_ID;
186
+ process.env.GSD_STATE_DIR = stateDir;
187
+
188
+ try {
189
+ run("git init -b main", base);
190
+ run('git config user.name "Test"', base);
191
+ run('git config user.email "test@example.com"', base);
192
+ writeFileSync(join(base, "README.md"), "# Test\n", "utf-8");
193
+ run("git add README.md", base);
194
+ run('git commit -m "init"', base);
195
+
196
+ const externalPath = externalGsdRoot(base);
197
+ const identity = externalPath.split(/[\\/]/).pop();
198
+ assert.ok(identity, "external state path must include an identity segment");
199
+ mkdirSync(externalPath, { recursive: true });
200
+ writeFileSync(join(base, ".gsd-id"), `${identity}\n`, "utf-8");
201
+ writeFileSync(join(externalPath, "gsd.db"), "newer external state\n", "utf-8");
202
+
203
+ mkdirSync(join(base, ".gsd"), { recursive: true });
204
+ writeFileSync(join(base, ".gsd", "gsd.db"), "stale local state\n", "utf-8");
205
+
206
+ const result = migrateToExternalState(base);
207
+
208
+ assert.equal(result.migrated, false, "already-migrated project must not re-run migration");
209
+ assert.match(result.error ?? "", /external state already exists/i);
210
+ assert.equal(
211
+ readFileSync(join(externalPath, "gsd.db"), "utf-8"),
212
+ "newer external state\n",
213
+ "newer external gsd.db must not be overwritten by stale local state",
214
+ );
215
+ assert.equal(
216
+ readFileSync(join(base, ".gsd", "gsd.db"), "utf-8"),
217
+ "stale local state\n",
218
+ "local real .gsd directory should be left for explicit recovery",
219
+ );
220
+ assert.ok(!existsSync(join(base, ".gsd.migrating")), "migration staging dir must not be created");
221
+ } finally {
222
+ if (previousStateDir === undefined) {
223
+ delete process.env.GSD_STATE_DIR;
224
+ } else {
225
+ process.env.GSD_STATE_DIR = previousStateDir;
226
+ }
227
+ if (previousProjectId === undefined) {
228
+ delete process.env.GSD_PROJECT_ID;
229
+ } else {
230
+ process.env.GSD_PROJECT_ID = previousProjectId;
231
+ }
232
+ rmSync(base, { recursive: true, force: true });
233
+ rmSync(stateDir, { recursive: true, force: true });
234
+ }
235
+ });
236
+ });
237
+
122
238
  describe("migrateToExternalState copy failure rollback (#1047)", () => {
123
239
  test("restores .gsd when a source entry fails to copy", () => {
124
240
  const base = realpathSync(mkdtempSync(join(tmpdir(), "gsd-migrate-copy-fail-")));
@@ -4,7 +4,10 @@
4
4
  import test from "node:test";
5
5
  import assert from "node:assert/strict";
6
6
 
7
- import { _runMilestoneMergeWithStashRestore } from "../auto/closeout.js";
7
+ import {
8
+ _runMilestoneMergeOnceWithStashRestore,
9
+ _runMilestoneMergeWithStashRestore,
10
+ } from "../auto/closeout.js";
8
11
  import type { IterationContext } from "../auto/types.js";
9
12
  import { MergeConflictError } from "../git-service.js";
10
13
  import type {
@@ -58,6 +61,30 @@ function buildIc(opts: {
58
61
  },
59
62
  };
60
63
 
64
+ const plainExitMilestone = (exitOpts: { merge: boolean }) => {
65
+ log.mergeCalls += 1;
66
+ if (opts.mergeBehavior === "succeed") {
67
+ return { ok: true, merged: exitOpts.merge, codeFilesChanged: false } as const;
68
+ }
69
+ try {
70
+ opts.mergeBehavior();
71
+ return { ok: true, merged: exitOpts.merge, codeFilesChanged: false } as const;
72
+ } catch (err) {
73
+ // Mirror Lifecycle's typed-result wrapping of MergeConflictError
74
+ // and other thrown values per worktree-lifecycle.exitMilestone.
75
+ const isMergeConflict =
76
+ err !== null &&
77
+ typeof err === "object" &&
78
+ err !== undefined &&
79
+ (err as { name?: string }).name === "MergeConflictError";
80
+ return {
81
+ ok: false,
82
+ reason: isMergeConflict ? "merge-conflict" : "teardown-failed",
83
+ cause: err,
84
+ } as const;
85
+ }
86
+ };
87
+
61
88
  const deps = {
62
89
  preflightCleanRoot: () => {
63
90
  log.preflightCalls += 1;
@@ -76,28 +103,72 @@ function buildIc(opts: {
76
103
  },
77
104
  },
78
105
  lifecycle: {
79
- exitMilestone: (_mid: string, exitOpts: { merge: boolean }) => {
80
- log.mergeCalls += 1;
81
- if (opts.mergeBehavior === "succeed") {
82
- return { ok: true, merged: exitOpts.merge, codeFilesChanged: false };
83
- }
84
- try {
85
- opts.mergeBehavior();
86
- return { ok: true, merged: exitOpts.merge, codeFilesChanged: false };
87
- } catch (err) {
88
- // Mirror Lifecycle's typed-result wrapping of MergeConflictError
89
- // and other thrown values per worktree-lifecycle.exitMilestone.
90
- const isMergeConflict =
91
- err !== null &&
92
- typeof err === "object" &&
93
- err !== undefined &&
94
- (err as { name?: string }).name === "MergeConflictError";
95
- return {
96
- ok: false,
97
- reason: isMergeConflict ? "merge-conflict" : "teardown-failed",
98
- cause: err,
99
- } as const;
106
+ exitMilestone: (
107
+ _mid: string,
108
+ exitOpts: {
109
+ merge: boolean;
110
+ guardedMerge?: {
111
+ projectRoot: string;
112
+ preflightCleanRoot: (
113
+ basePath: string,
114
+ milestoneId: string,
115
+ notify: (message: string, level: "info" | "warning" | "error") => void,
116
+ ) => PreflightResult;
117
+ postflightPopStash: (
118
+ basePath: string,
119
+ milestoneId: string,
120
+ stashMarker: string | undefined,
121
+ notify: (message: string, level: "info" | "warning" | "error") => void,
122
+ ) => PostflightResult;
123
+ };
124
+ },
125
+ exitCtx?: { notify: (message: string, level: "info" | "warning" | "error") => void },
126
+ ) => {
127
+ const notify = exitCtx?.notify ?? (() => {});
128
+ const guarded = exitOpts.guardedMerge;
129
+ if (exitOpts.merge && guarded) {
130
+ const preflight = guarded.preflightCleanRoot(guarded.projectRoot, _mid, notify);
131
+ if (preflight.blocked) {
132
+ return {
133
+ ok: false,
134
+ reason: preflight.blockedReason?.startsWith("unmerged-conflicts")
135
+ ? "preflight-unmerged-conflicts"
136
+ : "preflight-dirty-overlap",
137
+ } as const;
138
+ }
139
+
140
+ const mergeResult = plainExitMilestone({ merge: true });
141
+ const postflight = preflight.stashPushed
142
+ ? guarded.postflightPopStash(
143
+ guarded.projectRoot,
144
+ _mid,
145
+ preflight.stashMarker,
146
+ notify,
147
+ )
148
+ : undefined;
149
+
150
+ if (!mergeResult.ok) {
151
+ if (mergeResult.reason === "merge-conflict") {
152
+ return { ...mergeResult, postflight } as const;
153
+ }
154
+ return {
155
+ ok: false,
156
+ reason: "merge-failed",
157
+ cause: mergeResult.cause,
158
+ postflight,
159
+ } as const;
160
+ }
161
+ if (postflight?.needsManualRecovery) {
162
+ return {
163
+ ok: false,
164
+ reason: "postflight-stash-restore-failed",
165
+ postflight,
166
+ } as const;
167
+ }
168
+ return mergeResult;
100
169
  }
170
+
171
+ return plainExitMilestone(exitOpts);
101
172
  },
102
173
  },
103
174
  stopAuto: async (
@@ -155,6 +226,14 @@ const PREFLIGHT_UNMERGED: PreflightResult = {
155
226
  summary: "Working tree has unresolved Git conflicts before milestone M002 merge.",
156
227
  };
157
228
 
229
+ const PREFLIGHT_UNMERGED_EVAL_FAILED: PreflightResult = {
230
+ stashPushed: false,
231
+ blocked: true,
232
+ blockedReason: "unmerged-conflicts-eval-failed",
233
+ conflictedPaths: ["todo.js"],
234
+ summary: "Unable to fully evaluate unresolved Git conflicts before milestone M002 merge.",
235
+ };
236
+
158
237
  const POP_OK: PostflightResult = {
159
238
  restored: true,
160
239
  needsManualRecovery: false,
@@ -344,6 +423,52 @@ test("unmerged conflicts: preflight stops before merge and postflight restore",
344
423
  );
345
424
  });
346
425
 
426
+ test("unmerged conflict evaluation failure stops as preflight-unmerged-conflicts", async () => {
427
+ const { ic, log } = buildIc({
428
+ preflightResult: PREFLIGHT_UNMERGED_EVAL_FAILED,
429
+ mergeBehavior: "succeed",
430
+ postflightResult: POP_OK,
431
+ });
432
+
433
+ const result = await _runMilestoneMergeWithStashRestore(ic, "M002");
434
+
435
+ assert.deepEqual(result, {
436
+ action: "break",
437
+ reason: "preflight-unmerged-conflicts",
438
+ });
439
+ assert.equal(
440
+ log.mergeCalls,
441
+ 0,
442
+ "failed conflict evaluation must not start milestone merge",
443
+ );
444
+ assert.equal(log.postflightCalls, 0, "blocked preflight must not attempt stash restore");
445
+ assert.equal(log.stopAutoCalls.length, 1);
446
+ assert.match(
447
+ log.stopAutoCalls[0] ?? "",
448
+ /Pre-merge unresolved Git conflicts block milestone M002/,
449
+ );
450
+ });
451
+
452
+ test("already-merged milestone skips guarded merge to terminate duplicate closeout loops", async () => {
453
+ const { ic, log } = buildIc({
454
+ preflightResult: STASH_PUSHED,
455
+ mergeBehavior: "succeed",
456
+ postflightResult: POP_OK,
457
+ });
458
+ log.milestoneMergedInPhases = true;
459
+
460
+ const result = await _runMilestoneMergeOnceWithStashRestore(ic, "M002");
461
+
462
+ assert.equal(result, null);
463
+ assert.equal(log.preflightCalls, 0, "already-merged guard must not re-trigger preflight");
464
+ assert.equal(log.mergeCalls, 0, "already-merged guard must not re-trigger merge");
465
+ assert.equal(
466
+ log.postflightCalls,
467
+ 0,
468
+ "already-merged guard must not re-trigger stash restore",
469
+ );
470
+ });
471
+
347
472
  test("merge succeeds but stash pop needs manual recovery -> postflight-stash-restore-failed break", async () => {
348
473
  const { ic, log } = buildIc({
349
474
  preflightResult: STASH_PUSHED,
@@ -358,6 +483,11 @@ test("merge succeeds but stash pop needs manual recovery -> postflight-stash-res
358
483
  reason: "postflight-stash-restore-failed",
359
484
  });
360
485
  assert.equal(log.postflightCalls, 1);
486
+ assert.equal(
487
+ log.milestoneMergedInPhases,
488
+ true,
489
+ "successful merge must set the flag before postflight recovery stops auto-mode",
490
+ );
361
491
  assert.equal(log.stopAutoCalls.length, 1);
362
492
  assert.match(
363
493
  log.stopAutoCalls[0] ?? "",
@@ -13,7 +13,7 @@ test("runMilestoneMergeTransaction delegates to the supplied merge runner", () =
13
13
  const calls: unknown[][] = [];
14
14
  const result = runMilestoneMergeTransaction(
15
15
  {
16
- mergeMilestoneToMain: (basePath, milestoneId, roadmapContent) => {
16
+ mergeMilestone: (basePath, milestoneId, roadmapContent) => {
17
17
  calls.push([basePath, milestoneId, roadmapContent]);
18
18
  return { pushed: true, codeFilesChanged: true, commitMessage: "merge M001" };
19
19
  },
@@ -55,8 +55,8 @@ describe("pendingAutoStart scope pinning (C1)", () => {
55
55
  assert.ok(entry.scope, "entry.scope should be set");
56
56
  assert.equal(entry.scope.milestoneId, "M001");
57
57
 
58
- const expectedContext = join(base, ".gsd", "milestones", "M001", "M001-CONTEXT.md");
59
- const expectedRoadmap = join(base, ".gsd", "milestones", "M001", "M001-ROADMAP.md");
58
+ const expectedContext = join(base, ".gsd", "phases", "01-m001", "01-CONTEXT.md");
59
+ const expectedRoadmap = join(base, ".gsd", "phases", "01-m001", "01-ROADMAP.md");
60
60
  const expectedState = join(base, ".gsd", "STATE.md");
61
61
 
62
62
  assert.equal(entry.scope.contextFile(), expectedContext);
@@ -82,13 +82,57 @@ const ic = {
82
82
  return { ok: true, needsManualRecovery: false };
83
83
  },
84
84
  lifecycle: {
85
- exitMilestone() {
85
+ // Mirror WorktreeLifecycle.exitMilestone's guarded-merge branch
86
+ // (preflight -> inner merge -> postflight). Since #1169 the closeout
87
+ // routes the guard through the lifecycle verb, so a non-conflict inner
88
+ // failure ("teardown-failed") is mapped to "merge-failed" here rather
89
+ // than by the caller.
90
+ exitMilestone(
91
+ milestoneId: string,
92
+ exitOpts: {
93
+ merge: boolean;
94
+ guardedMerge?: {
95
+ projectRoot: string;
96
+ preflightCleanRoot: (
97
+ basePath: string,
98
+ milestoneId: string,
99
+ notify: (message: string, level: "info" | "warning" | "error") => void,
100
+ ) => { blocked?: boolean; blockedReason?: string; stashPushed: boolean; stashMarker?: string };
101
+ postflightPopStash: (
102
+ basePath: string,
103
+ milestoneId: string,
104
+ stashMarker: string | undefined,
105
+ notify: (message: string, level: "info" | "warning" | "error") => void,
106
+ ) => { needsManualRecovery?: boolean };
107
+ };
108
+ },
109
+ exitCtx?: { notify: (message: string, level: "info" | "warning" | "error") => void },
110
+ ) {
111
+ const notify = exitCtx?.notify ?? (() => {});
112
+ const guarded = exitOpts.guardedMerge;
113
+ if (exitOpts.merge && guarded) {
114
+ const preflight = guarded.preflightCleanRoot(guarded.projectRoot, milestoneId, notify);
115
+ if (preflight.blocked) {
116
+ return {
117
+ ok: false,
118
+ reason: preflight.blockedReason?.startsWith("unmerged-conflicts")
119
+ ? "preflight-unmerged-conflicts"
120
+ : "preflight-dirty-overlap",
121
+ };
122
+ }
123
+ // Inner merge fails with a non-conflict error. The lifecycle still
124
+ // runs postflight stash restore on the failed-merge path, then maps
125
+ // the non-conflict failure to "merge-failed" (a MergeConflictError
126
+ // would instead map to "merge-conflict").
127
+ calls.push("merge");
128
+ const cause = new Error("remote rejected push");
129
+ if (preflight.stashPushed) {
130
+ guarded.postflightPopStash(guarded.projectRoot, milestoneId, preflight.stashMarker, notify);
131
+ }
132
+ return { ok: false, reason: "merge-failed", cause };
133
+ }
86
134
  calls.push("merge");
87
- return {
88
- ok: false,
89
- reason: "teardown-failed",
90
- cause: new Error("remote rejected push"),
91
- };
135
+ return { ok: false, reason: "teardown-failed", cause: new Error("remote rejected push") };
92
136
  },
93
137
  },
94
138
  async stopAuto(_ctx: unknown, _pi: unknown, reason?: string) {
@@ -34,6 +34,7 @@ const BASE_VARS = {
34
34
  planTemplatePath: "C:\\Users\\Test\\.gsd\\agent\\extensions\\gsd\\templates\\plan.md",
35
35
  taskPlanTemplatePath: "C:\\Users\\Test\\.gsd\\agent\\extensions\\gsd\\templates\\task-plan.md",
36
36
  skillActivation: "Load the relevant skills.",
37
+ repoRegistry: "",
37
38
  };
38
39
 
39
40
  const DEFAULT_SKILL_ACTIVATION = "If a `GSD Skill Preferences` block is present in system context, use it and the `<available_skills>` catalog in your system prompt to decide which skills to load and follow for this unit, without relaxing required verification or artifact rules.";
@@ -61,6 +62,22 @@ test("plan-slice prompt: all variables substituted", () => {
61
62
  assert.ok(result.includes("S01"));
62
63
  });
63
64
 
65
+ test("plan-slice prompt: repoRegistry block surfaces declared repositories when non-empty", () => {
66
+ const block = "### Declared Repositories\n\n- `frontend` (frontend/)\n- `backend` (backend/)\n\nAssign each task's `targetRepositories`";
67
+ const result = loadPrompt("plan-slice", { ...BASE_VARS, repoRegistry: block, commitInstruction: "Do not commit." });
68
+ assert.ok(result.includes("### Declared Repositories"), "declared repositories block should appear when provided");
69
+ assert.ok(result.includes("`frontend`"), "declared repo ids should be visible to the planner");
70
+ assert.ok(result.includes("targetRepositories"), "repo-assignment guidance should appear");
71
+ });
72
+
73
+ test("plan-slice prompt: empty repoRegistry leaves no orphan marker", () => {
74
+ const result = loadPrompt("plan-slice", { ...BASE_VARS, repoRegistry: "", commitInstruction: "Do not commit." });
75
+ assert.ok(!result.includes("{{repoRegistry}}"), "the repoRegistry placeholder must be substituted");
76
+ // The block heading only renders when repoRegistry is non-empty; the rule-9
77
+ // guidance text mentions "Declared Repositories" but is not a rendered block.
78
+ assert.ok(!result.includes("### Declared Repositories"), "single-repo prompts must not show a declared-repositories block");
79
+ });
80
+
64
81
  test("plan-slice prompt: DB-backed tool names survive template substitution", () => {
65
82
  const result = loadPrompt("plan-slice", { ...BASE_VARS, commitInstruction: "Do not commit." });
66
83
  assert.ok(result.includes("gsd_plan_slice"), "gsd_plan_slice should appear in rendered prompt");