@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
@@ -0,0 +1,353 @@
1
+ import { spawn } from "node:child_process";
2
+ import { createAssistantMessageEventStream } from "@gsd/pi-ai";
3
+ import { buildCursorAgentSpawnInvocation } from "./readiness.js";
4
+ const ZERO_USAGE = {
5
+ input: 0,
6
+ output: 0,
7
+ cacheRead: 0,
8
+ cacheWrite: 0,
9
+ totalTokens: 0,
10
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
11
+ };
12
+ function textBlocks(content) {
13
+ return content
14
+ .map((block) => block.type === "text" ? block.text : `[${block.type} omitted]`)
15
+ .join("\n");
16
+ }
17
+ function messageToText(message) {
18
+ if (message.role === "user") {
19
+ const content = typeof message.content === "string" ? message.content : textBlocks(message.content);
20
+ return `User:\n${content}`;
21
+ }
22
+ if (message.role === "assistant") {
23
+ const text = message.content
24
+ .map((block) => {
25
+ if (block.type === "text")
26
+ return block.text;
27
+ if (block.type === "thinking")
28
+ return "[thinking omitted]";
29
+ if (block.type === "toolCall")
30
+ return `[tool call: ${block.name}]`;
31
+ if (block.type === "serverToolUse")
32
+ return `[server tool: ${block.name}]`;
33
+ if (block.type === "webSearchResult")
34
+ return "[web search result omitted]";
35
+ return `[${block.type} omitted]`;
36
+ })
37
+ .join("\n");
38
+ return `Assistant:\n${text}`;
39
+ }
40
+ return `Tool result (${message.toolName}):\n${textBlocks(message.content)}`;
41
+ }
42
+ export function buildCursorPrompt(context) {
43
+ const parts = [];
44
+ if (context.systemPrompt?.trim())
45
+ parts.push(`System instructions:\n${context.systemPrompt.trim()}`);
46
+ if (context.messages.length > 0)
47
+ parts.push(context.messages.map(messageToText).join("\n\n"));
48
+ if (context.tools?.length) {
49
+ parts.push(`The external Cursor agent may execute its own tools. ` +
50
+ `GSD will not redispatch Cursor-owned internal tool events locally. ` +
51
+ `Requested GSD tools: ${context.tools.map((tool) => tool.name).join(", ")}`);
52
+ }
53
+ return parts.join("\n\n").trim();
54
+ }
55
+ export function buildCursorSpawnInvocation(command, args, platform = process.platform) {
56
+ return buildCursorAgentSpawnInvocation(command, args, platform);
57
+ }
58
+ export function buildCursorAgentRunPlan(modelId, prompt, cwd, platform = process.platform) {
59
+ return buildCursorSpawnInvocation(process.env.CURSOR_AGENT_BIN?.trim() || "cursor-agent", ["-p", prompt, "--output-format", "stream-json", "--model", modelId, "--workspace", cwd, "--trust"], platform);
60
+ }
61
+ function emitCompleteLines(chunk, pending, onLine) {
62
+ pending.value += chunk;
63
+ const lines = pending.value.split(/\r?\n/);
64
+ pending.value = lines.pop() ?? "";
65
+ for (const line of lines)
66
+ onLine(line);
67
+ }
68
+ function flushPendingLine(pending, onLine) {
69
+ if (!pending.value)
70
+ return;
71
+ onLine(pending.value);
72
+ pending.value = "";
73
+ }
74
+ function runCursorAgent(plan, options, onLine) {
75
+ return new Promise((resolve, reject) => {
76
+ const child = spawn(plan.command, plan.args, {
77
+ cwd: options?.cwd || process.cwd(),
78
+ env: process.env,
79
+ stdio: ["ignore", "pipe", "pipe"],
80
+ });
81
+ let stdout = "";
82
+ let stderr = "";
83
+ let settled = false;
84
+ const pendingStdout = { value: "" };
85
+ const settle = (fn) => {
86
+ if (settled)
87
+ return;
88
+ settled = true;
89
+ options?.signal?.removeEventListener("abort", onAbort);
90
+ fn();
91
+ };
92
+ const onAbort = () => {
93
+ child.kill("SIGTERM");
94
+ settle(() => reject(new Error("Request was aborted")));
95
+ };
96
+ if (options?.signal?.aborted) {
97
+ onAbort();
98
+ return;
99
+ }
100
+ options?.signal?.addEventListener("abort", onAbort);
101
+ child.stdout?.setEncoding("utf8");
102
+ child.stderr?.setEncoding("utf8");
103
+ child.stdout?.on("data", (chunk) => {
104
+ stdout += chunk;
105
+ try {
106
+ emitCompleteLines(chunk, pendingStdout, onLine);
107
+ }
108
+ catch (error) {
109
+ child.kill("SIGTERM");
110
+ settle(() => reject(error));
111
+ }
112
+ });
113
+ child.stderr?.on("data", (chunk) => { stderr += chunk; });
114
+ child.on("error", (error) => settle(() => reject(error)));
115
+ child.on("close", (code, signal) => settle(() => {
116
+ try {
117
+ flushPendingLine(pendingStdout, onLine);
118
+ }
119
+ catch (error) {
120
+ reject(error);
121
+ return;
122
+ }
123
+ resolve({ stdout, stderr, code, signal });
124
+ }));
125
+ });
126
+ }
127
+ function stringValue(...values) {
128
+ for (const value of values) {
129
+ if (typeof value === "string" && value.length > 0)
130
+ return value;
131
+ }
132
+ return undefined;
133
+ }
134
+ function objectValue(value) {
135
+ return value && typeof value === "object" && !Array.isArray(value) ? value : undefined;
136
+ }
137
+ function extractText(value) {
138
+ if (typeof value === "string")
139
+ return value;
140
+ if (Array.isArray(value)) {
141
+ return value
142
+ .map((item) => stringValue(objectValue(item)?.text, objectValue(item)?.content, item))
143
+ .filter((item) => Boolean(item))
144
+ .join("\n");
145
+ }
146
+ const obj = objectValue(value);
147
+ if (!obj)
148
+ return "";
149
+ return stringValue(obj.text, obj.content, obj.message, obj.result) ?? "";
150
+ }
151
+ function mapUsage(raw) {
152
+ const input = Number(raw.input_tokens ?? raw.input ?? raw.prompt_tokens ?? 0);
153
+ const output = Number(raw.output_tokens ?? raw.output ?? raw.completion_tokens ?? 0);
154
+ const cacheRead = Number(raw.cache_read_input_tokens ?? raw.cacheRead ?? 0);
155
+ const cacheWrite = Number(raw.cache_creation_input_tokens ?? raw.cacheWrite ?? 0);
156
+ return {
157
+ input,
158
+ output,
159
+ cacheRead,
160
+ cacheWrite,
161
+ };
162
+ }
163
+ function normalizeToolArguments(input) {
164
+ return objectValue(input) ?? { input };
165
+ }
166
+ function normalizeToolResult(raw) {
167
+ const text = extractText(raw.content ?? raw.result ?? raw.output);
168
+ return {
169
+ content: text ? [{ type: "text", text }] : [],
170
+ isError: Boolean(raw.is_error ?? raw.isError ?? raw.error),
171
+ };
172
+ }
173
+ export function parseCursorAgentLine(line) {
174
+ const trimmed = line.trim();
175
+ if (!trimmed)
176
+ return { type: "ignore" };
177
+ let event;
178
+ try {
179
+ event = JSON.parse(trimmed);
180
+ }
181
+ catch {
182
+ return { type: "text", text: trimmed };
183
+ }
184
+ const type = stringValue(event.type, event.event) ?? "";
185
+ if (type === "error") {
186
+ return { type: "error", message: stringValue(event.message, event.error) ?? "cursor_agent_error" };
187
+ }
188
+ if (type === "tool_call") {
189
+ if (event.call_id || event.subtype || event.tool_call)
190
+ return { type: "ignore" };
191
+ const id = stringValue(event.id, event.tool_call_id) ?? `cursor_tool_${Date.now()}`;
192
+ const name = stringValue(event.name, event.tool_name) ?? "cursor_tool";
193
+ return {
194
+ type: "tool_call",
195
+ toolCall: { type: "toolCall", id, name, arguments: normalizeToolArguments(event.input ?? event.arguments) },
196
+ };
197
+ }
198
+ if (type === "tool_result") {
199
+ return {
200
+ type: "tool_result",
201
+ toolCallId: stringValue(event.tool_call_id, event.id) ?? "",
202
+ result: normalizeToolResult(event),
203
+ };
204
+ }
205
+ if (type === "result") {
206
+ const usage = objectValue(event.usage);
207
+ return usage ? { type: "usage", usage: mapUsage(usage) } : { type: "ignore" };
208
+ }
209
+ const text = extractText(event.delta ?? event.text ?? event.content ?? objectValue(event.message)?.content ?? event.message);
210
+ if (text)
211
+ return { type: "text", text };
212
+ const usage = objectValue(event.usage);
213
+ if (usage)
214
+ return { type: "usage", usage: mapUsage(usage) };
215
+ return { type: "ignore" };
216
+ }
217
+ function createUsage(partial) {
218
+ const input = partial?.input ?? 0;
219
+ const output = partial?.output ?? 0;
220
+ const cacheRead = partial?.cacheRead ?? 0;
221
+ const cacheWrite = partial?.cacheWrite ?? 0;
222
+ return {
223
+ ...ZERO_USAGE,
224
+ input,
225
+ output,
226
+ cacheRead,
227
+ cacheWrite,
228
+ totalTokens: input + output + cacheWrite,
229
+ cost: { ...ZERO_USAGE.cost },
230
+ };
231
+ }
232
+ function buildAssistantMessage(model, content, usage, stopReason = "stop", errorMessage) {
233
+ return {
234
+ role: "assistant",
235
+ content,
236
+ api: model.api,
237
+ provider: model.provider,
238
+ model: model.id,
239
+ usage: createUsage(usage),
240
+ stopReason,
241
+ ...(errorMessage ? { errorMessage } : {}),
242
+ timestamp: Date.now(),
243
+ };
244
+ }
245
+ function emitDone(stream, message, text) {
246
+ stream.push({ type: "start", partial: { ...message, content: [] } });
247
+ if (text) {
248
+ stream.push({ type: "text_start", contentIndex: 0, partial: message });
249
+ stream.push({ type: "text_delta", contentIndex: 0, delta: text, partial: message });
250
+ stream.push({ type: "text_end", contentIndex: 0, content: text, partial: message });
251
+ }
252
+ message.content.forEach((block, index) => {
253
+ if (block.type !== "toolCall")
254
+ return;
255
+ stream.push({ type: "toolcall_start", contentIndex: index, partial: message });
256
+ stream.push({ type: "toolcall_end", contentIndex: index, toolCall: block, partial: message });
257
+ });
258
+ stream.push({ type: "done", reason: "stop", message });
259
+ stream.end(message);
260
+ }
261
+ function emitError(stream, model, error) {
262
+ const message = error instanceof Error ? error.message : String(error);
263
+ const output = buildAssistantMessage(model, [], undefined, "error", message);
264
+ stream.push({ type: "error", reason: "error", error: output });
265
+ stream.end(output);
266
+ }
267
+ export function streamViaCursorAgent(model, context, options) {
268
+ const stream = createAssistantMessageEventStream();
269
+ queueMicrotask(async () => {
270
+ try {
271
+ const cwd = options?.cwd || process.cwd();
272
+ const prompt = buildCursorPrompt(context);
273
+ const plan = buildCursorAgentRunPlan(model.id, prompt, cwd);
274
+ const runForTest = options?._cursorAgentRunnerForTest;
275
+ let text = "";
276
+ let usage;
277
+ let streamStarted = false;
278
+ let textStarted = false;
279
+ const toolCalls = new Map();
280
+ const content = [];
281
+ const partialMessage = () => {
282
+ const partialContent = text ? [{ type: "text", text }, ...content] : [...content];
283
+ return buildAssistantMessage(model, partialContent, usage);
284
+ };
285
+ const ensureStart = () => {
286
+ if (streamStarted)
287
+ return;
288
+ streamStarted = true;
289
+ stream.push({ type: "start", partial: buildAssistantMessage(model, []) });
290
+ };
291
+ const handleLine = (line) => {
292
+ const parsed = parseCursorAgentLine(line);
293
+ if (parsed.type === "ignore")
294
+ return;
295
+ if (parsed.type === "error")
296
+ throw new Error(parsed.message);
297
+ if (parsed.type === "text") {
298
+ ensureStart();
299
+ if (!textStarted) {
300
+ textStarted = true;
301
+ stream.push({ type: "text_start", contentIndex: 0, partial: partialMessage() });
302
+ }
303
+ text += parsed.text;
304
+ stream.push({ type: "text_delta", contentIndex: 0, delta: parsed.text, partial: partialMessage() });
305
+ return;
306
+ }
307
+ if (parsed.type === "usage") {
308
+ usage = { ...(usage ?? {}), ...parsed.usage };
309
+ return;
310
+ }
311
+ if (parsed.type === "tool_call") {
312
+ ensureStart();
313
+ toolCalls.set(parsed.toolCall.id, parsed.toolCall);
314
+ content.push(parsed.toolCall);
315
+ const index = text ? content.length : content.length - 1;
316
+ const partial = partialMessage();
317
+ stream.push({ type: "toolcall_start", contentIndex: index, partial });
318
+ stream.push({ type: "toolcall_end", contentIndex: index, toolCall: parsed.toolCall, partial });
319
+ return;
320
+ }
321
+ if (parsed.type === "tool_result") {
322
+ const toolCall = toolCalls.get(parsed.toolCallId);
323
+ if (toolCall)
324
+ toolCall.externalResult = parsed.result;
325
+ }
326
+ };
327
+ const result = runForTest
328
+ ? await runForTest(plan, options, handleLine)
329
+ : await runCursorAgent(plan, options, handleLine);
330
+ if (runForTest) {
331
+ for (const line of result.stdout.split(/\r?\n/))
332
+ handleLine(line);
333
+ }
334
+ if (result.code !== 0) {
335
+ throw new Error((result.stderr || result.stdout || `cursor-agent exited with code ${result.code}`).trim());
336
+ }
337
+ const finalContent = text ? [{ type: "text", text }, ...content] : content;
338
+ const finalMessage = buildAssistantMessage(model, finalContent, usage);
339
+ if (!streamStarted) {
340
+ emitDone(stream, finalMessage, text);
341
+ return;
342
+ }
343
+ if (textStarted)
344
+ stream.push({ type: "text_end", contentIndex: 0, content: text, partial: finalMessage });
345
+ stream.push({ type: "done", reason: "stop", message: finalMessage });
346
+ stream.end(finalMessage);
347
+ }
348
+ catch (error) {
349
+ emitError(stream, model, error);
350
+ }
351
+ });
352
+ return stream;
353
+ }
@@ -13,6 +13,7 @@ import { join } from "node:path";
13
13
  import { GSDError, GSD_IO_ERROR } from "./errors.js";
14
14
  const SEQ_PREFIX_RE = /^(\d+)-/;
15
15
  import { gsdRoot } from "./paths.js";
16
+ import { redactSecrets } from "./redact-secrets.js";
16
17
  import { buildAuditEnvelope, emitUokAuditEvent } from "./uok/audit.js";
17
18
  import { isUnifiedAuditEnabled } from "./uok/audit-toggle.js";
18
19
  const activityLogState = new Map();
@@ -104,7 +105,9 @@ export function saveActivityLog(ctx, basePath, unitType, unitId) {
104
105
  const fd = openSync(filePath, "w");
105
106
  try {
106
107
  for (const entry of entries) {
107
- writeSync(fd, JSON.stringify(entry) + "\n");
108
+ // Redact secret-shaped substrings before persisting; .gsd/ is skipped by
109
+ // the secret scanner, so raw LLM/tool content could otherwise leak keys.
110
+ writeSync(fd, redactSecrets(JSON.stringify(entry)) + "\n");
108
111
  }
109
112
  }
110
113
  finally {
@@ -47,6 +47,10 @@ function parseRoadmapForRecovery(content) {
47
47
  return _roadmapParserFn(content);
48
48
  return parseLegacyRoadmap(content);
49
49
  }
50
+ /** Slice count for plan-milestone verification; shared by scoped and legacy paths. */
51
+ export function countPlanMilestoneRoadmapSlices(content) {
52
+ return parseRoadmapForRecovery(content).slices.length;
53
+ }
50
54
  export function diagnoseWorktreeIntegrityFailure(basePath) {
51
55
  if (!isGsdWorktreePath(basePath))
52
56
  return null;
@@ -294,8 +298,7 @@ export function verifyExpectedArtifact(unitType, unitId, base) {
294
298
  }
295
299
  if (unitType === "plan-milestone") {
296
300
  try {
297
- const roadmap = parseRoadmapForRecovery(readFileSync(absPath, "utf-8"));
298
- if (roadmap.slices.length === 0) {
301
+ if (countPlanMilestoneRoadmapSlices(readFileSync(absPath, "utf-8")) === 0) {
299
302
  logWarning("recovery", `verify-fail ${unitType} ${unitId}: roadmap has zero slices at ${absPath}`);
300
303
  return false;
301
304
  }
@@ -311,6 +314,19 @@ export function verifyExpectedArtifact(unitType, unitId, base) {
311
314
  try {
312
315
  let taskIds = null;
313
316
  let dbPrimary = false;
317
+ const planContent = readFileSync(absPath, "utf-8");
318
+ let parsedTaskIds = null;
319
+ const getParsedTaskIds = () => {
320
+ if (parsedTaskIds)
321
+ return parsedTaskIds;
322
+ parsedTaskIds = parseLegacyPlan(planContent).tasks.map((t) => t.id);
323
+ return parsedTaskIds;
324
+ };
325
+ const tasksBlockMatch = planContent.match(/<tasks>([\s\S]*?)<\/tasks>/i);
326
+ const tasksBlock = tasksBlockMatch?.[1] ?? "";
327
+ const hasEmbeddedTaskEntries = tasksBlock.length > 0 &&
328
+ (/^\s*- \[[xX ]\] \*\*T\d+/m.test(tasksBlock) ||
329
+ /^\s*#{2,4}\s+T\d+\s*(?:--|—|:)/m.test(tasksBlock));
314
330
  if (isDbAvailable()) {
315
331
  const refreshed = refreshWorkflowDatabaseFromDisk();
316
332
  if (refreshed) {
@@ -322,24 +338,24 @@ export function verifyExpectedArtifact(unitType, unitId, base) {
322
338
  }
323
339
  }
324
340
  if (!taskIds) {
325
- const planContent = readFileSync(absPath, "utf-8");
326
341
  const hasCheckboxTask = /^\s*- \[[xX ]\] \*\*T\d+/m.test(planContent);
327
342
  const hasHeadingTask = /^\s*#{2,4}\s+T\d+\s*(?:--|—|:)/m.test(planContent);
328
343
  if (!hasCheckboxTask && !hasHeadingTask) {
329
344
  logWarning("recovery", `verify-fail ${unitType} ${unitId}: plan has no task checkbox/heading (len=${planContent.length}) at ${absPath}`);
330
345
  return false;
331
346
  }
332
- const plan = parseLegacyPlan(planContent);
333
- if (plan.tasks.length > 0)
334
- taskIds = plan.tasks.map((t) => t.id);
347
+ const parsedIds = getParsedTaskIds();
348
+ if (parsedIds.length > 0)
349
+ taskIds = parsedIds;
335
350
  }
336
- if (taskIds && taskIds.length > 0) {
351
+ if (taskIds && taskIds.length > 0 && !hasEmbeddedTaskEntries) {
337
352
  const tasksDir = join(dirname(absPath), "tasks");
338
353
  if (existsSync(tasksDir)) {
339
354
  for (const tid of taskIds) {
340
355
  const taskPlanFile = join(tasksDir, `${tid}-PLAN.md`);
341
- if (!existsSync(taskPlanFile)) {
342
- logWarning("recovery", `verify-fail ${unitType} ${unitId}: task plan missing ${taskPlanFile}`);
356
+ const taskSummaryFile = join(tasksDir, `${tid}-SUMMARY.md`);
357
+ if (!existsSync(taskPlanFile) && !existsSync(taskSummaryFile)) {
358
+ logWarning("recovery", `verify-fail ${unitType} ${unitId}: task artifact missing for ${tid}`);
343
359
  return false;
344
360
  }
345
361
  }
@@ -37,62 +37,51 @@ export async function restorePreflightStashOrStop(ic, preflight, milestoneId) {
37
37
  return stopOnPostflightRecoveryNeeded(ic, result, milestoneId);
38
38
  }
39
39
  /**
40
- * Run a milestone merge surrounded by preflight stash + always-on postflight
41
- * pop. The previous code popped the stash only after a successful merge, which
42
- * leaked `gsd-preflight-stash:M00x:*` entries whenever `mergeAndExit` threw —
43
- * leaving the user's pre-merge working tree silently stashed away after a
44
- * merge-conflict or other merge error. This helper restores the stash on
45
- * every exit path, then surfaces the merge or stash failure (in priority
46
- * order) as the loop's stop reason.
40
+ * Run a milestone merge through Worktree Lifecycle's guarded merge option,
41
+ * which surrounds the inner merge with preflight stash + always-on postflight
42
+ * pop. The previous closeout code popped the stash only after a successful
43
+ * merge, which leaked `gsd-preflight-stash:M00x:*` entries whenever
44
+ * `mergeAndExit` threw leaving the user's pre-merge working tree silently
45
+ * stashed away after a merge-conflict or other merge error. Lifecycle now
46
+ * restores the stash on every attempted merge path, then this adapter surfaces
47
+ * the merge or stash failure (in priority order) as the loop's stop reason.
47
48
  *
48
49
  * Returns a `break` action when auto-mode must stop, or `null` when the merge
49
50
  * succeeded and the stash (if any) was restored cleanly.
50
51
  */
51
52
  export async function _runMilestoneMergeWithStashRestore(ic, milestoneId, options = {}) {
52
53
  const { ctx, pi, s, deps } = ic;
53
- const preflight = deps.preflightCleanRoot(s.originalBasePath || s.basePath, milestoneId, ctx.ui.notify.bind(ctx.ui));
54
- if (preflight.blocked) {
55
- const reason = preflight.blockedReason === "unmerged-conflicts"
54
+ const projectRoot = s.originalBasePath || s.basePath;
55
+ const mergeResult = deps.lifecycle.exitMilestone(milestoneId, {
56
+ merge: true,
57
+ guardedMerge: {
58
+ projectRoot,
59
+ preflightCleanRoot: deps.preflightCleanRoot,
60
+ postflightPopStash: deps.postflightPopStash,
61
+ },
62
+ }, ctx.ui);
63
+ if (mergeResult.ok) {
64
+ await markMilestoneMergedAndRebuild(s);
65
+ return null;
66
+ }
67
+ if (mergeResult.reason === "postflight-stash-restore-failed") {
68
+ await markMilestoneMergedAndRebuild(s);
69
+ }
70
+ if (mergeResult.reason === "preflight-dirty-overlap" || mergeResult.reason === "preflight-unmerged-conflicts") {
71
+ const reason = mergeResult.reason === "preflight-unmerged-conflicts"
56
72
  ? `Pre-merge unresolved Git conflicts block milestone ${milestoneId}`
57
73
  : `Pre-merge dirty working tree overlaps milestone ${milestoneId}`;
58
74
  await deps.stopAuto(ctx, pi, reason, {
59
75
  preserveCompletedMilestoneBranch: true,
60
76
  preserveCloseoutTranscript: options.preserveCloseoutTranscript,
61
77
  });
62
- return {
63
- action: "break",
64
- reason: preflight.blockedReason === "unmerged-conflicts"
65
- ? "preflight-unmerged-conflicts"
66
- : "preflight-dirty-overlap",
67
- };
68
- }
69
- let mergeError = null;
70
- const exitResult = deps.lifecycle.exitMilestone(milestoneId, { merge: true }, ctx.ui);
71
- if (exitResult.ok) {
72
- s.milestoneMergedInPhases = true;
73
- try {
74
- const projectRoot = s.originalBasePath || s.canonicalProjectRoot || s.basePath;
75
- const { rebuildMarkdownProjectionsFromDb } = await import("../commands-maintenance.js");
76
- await rebuildMarkdownProjectionsFromDb(projectRoot);
77
- }
78
- catch (err) {
79
- logWarning("engine", `markdown projection rebuild after milestone merge failed: ${err instanceof Error ? err.message : String(err)}`);
80
- }
81
- }
82
- else {
83
- mergeError = exitResult.cause ?? new Error(`exit ${exitResult.reason}`);
84
- }
85
- // Always attempt to restore the stashed working tree, even on merge error.
86
- // postflightPopStash itself does not throw; failures surface via the
87
- // PostflightResult.needsManualRecovery flag.
88
- let stashResult = null;
89
- if (preflight.stashPushed) {
90
- stashResult = deps.postflightPopStash(s.originalBasePath || s.basePath, milestoneId, preflight.stashMarker, ctx.ui.notify.bind(ctx.ui));
78
+ return { action: "break", reason: mergeResult.reason };
91
79
  }
92
80
  // Merge failure takes priority over stash recovery — the merge is the
93
81
  // authoritative gate. If the stash also needed manual recovery, the user
94
82
  // already saw the postflightPopStash notify above.
95
- if (mergeError) {
83
+ if (mergeResult.reason === "merge-conflict") {
84
+ const mergeError = mergeResult.cause;
96
85
  if (mergeError instanceof MergeConflictError) {
97
86
  // A merge conflict is a recoverable human checkpoint, not an
98
87
  // infrastructure failure — the user resolves the conflict and runs
@@ -109,6 +98,9 @@ export async function _runMilestoneMergeWithStashRestore(ic, milestoneId, option
109
98
  });
110
99
  return { action: "break", reason: "merge-conflict" };
111
100
  }
101
+ }
102
+ if (mergeResult.reason === "merge-failed" || mergeResult.reason === "merge-conflict") {
103
+ const mergeError = mergeResult.cause;
112
104
  logError("engine", "Milestone merge failed with non-conflict error", {
113
105
  milestone: milestoneId,
114
106
  error: String(mergeError),
@@ -125,11 +117,22 @@ export async function _runMilestoneMergeWithStashRestore(ic, milestoneId, option
125
117
  });
126
118
  return { action: "break", reason: "merge-failed" };
127
119
  }
128
- if (stashResult) {
129
- return stopOnPostflightRecoveryNeeded(ic, stashResult, milestoneId);
120
+ if (mergeResult.postflight) {
121
+ return stopOnPostflightRecoveryNeeded(ic, mergeResult.postflight, milestoneId);
130
122
  }
131
123
  return null;
132
124
  }
125
+ async function markMilestoneMergedAndRebuild(s) {
126
+ s.milestoneMergedInPhases = true;
127
+ try {
128
+ const rebuildBasePath = s.originalBasePath || s.canonicalProjectRoot || s.basePath;
129
+ const { rebuildMarkdownProjectionsFromDb } = await import("../commands-maintenance.js");
130
+ await rebuildMarkdownProjectionsFromDb(rebuildBasePath);
131
+ }
132
+ catch (err) {
133
+ logWarning("engine", `markdown projection rebuild after milestone merge failed: ${err instanceof Error ? err.message : String(err)}`);
134
+ }
135
+ }
133
136
  export async function _runMilestoneMergeOnceWithStashRestore(ic, milestoneId, options = {}) {
134
137
  if (ic.s.milestoneMergedInPhases) {
135
138
  debugLog("autoLoop", {
@@ -280,6 +280,15 @@ export async function runFinalize(ic, iterData, loopState, sidecarItem) {
280
280
  files: leak.files.map((file) => ({ path: file.path, status: file.status })),
281
281
  });
282
282
  ctx.ui.notify(message, "error");
283
+ try {
284
+ deps.checkpointWorkflowDatabase?.();
285
+ }
286
+ catch (err) {
287
+ debugLog("autoLoop", {
288
+ phase: "root-write-leak-checkpoint-failed",
289
+ error: err instanceof Error ? err.message : String(err),
290
+ });
291
+ }
283
292
  await deps.stopAuto(ctx, pi, "Root-write leak during isolated auto-mode", {
284
293
  preserveCompletedMilestoneBranch: true,
285
294
  });
@@ -560,7 +560,7 @@ export class AutoOrchestrator {
560
560
  return new GitServiceImpl(basePath, gitConfig);
561
561
  },
562
562
  worktreeProjection: new WorktreeStateProjection(),
563
- mergeMilestoneToMain: createMilestoneMergeTransaction(mergeMilestoneToMain),
563
+ mergeMilestone: createMilestoneMergeTransaction(mergeMilestoneToMain),
564
564
  });
565
565
  }
566
566
  rebuildScope(rawPath, milestoneId) {
@@ -6,7 +6,7 @@ import { debugLog } from "../debug-logger.js";
6
6
  import { logWarning } from "../workflow-logger.js";
7
7
  import { resolveAutoSupervisorConfig } from "../preferences.js";
8
8
  import { readUnitRuntimeRecord } from "../unit-runtime.js";
9
- import { consumeAutoWakeup } from "./schedule-wakeup.js";
9
+ import { clearAutoWakeup, consumeAutoWakeup } from "./schedule-wakeup.js";
10
10
  import { applyUnitSkillVisibility } from "../skill-scope.js";
11
11
  const UNIT_FAILSAFE_BUFFER_MS = 30_000;
12
12
  const UNIT_FAILSAFE_RECHECK_MS = 30_000;
@@ -192,7 +192,7 @@ export async function runUnit(ctx, pi, s, unitType, unitId, prompt) {
192
192
  const UNIT_HARD_TIMEOUT_MS = Math.max(UNIT_FAILSAFE_BUFFER_MS, ((supervisor.hard_timeout_minutes ?? 30) * 60 * 1000) + UNIT_FAILSAFE_BUFFER_MS);
193
193
  const freshProgressMs = Math.max(UNIT_FAILSAFE_BUFFER_MS, ((supervisor.idle_timeout_minutes ?? 10) * 60 * 1000) + UNIT_FAILSAFE_BUFFER_MS);
194
194
  let unitTimeoutHandle;
195
- let result;
195
+ let result = { status: "cancelled" };
196
196
  try {
197
197
  const awaitAgentEndWithTimeout = (agentEndPromise) => {
198
198
  const timeoutResult = new Promise((resolve) => {
@@ -297,6 +297,9 @@ export async function runUnit(ctx, pi, s, unitType, unitId, prompt) {
297
297
  finally {
298
298
  if (unitTimeoutHandle)
299
299
  clearTimeout(unitTimeoutHandle);
300
+ if (result.status !== "completed") {
301
+ clearAutoWakeup(s.basePath, unitType, unitId);
302
+ }
300
303
  ctx.ui.setWorkingMessage?.(undefined);
301
304
  setVisibleSkills?.(undefined);
302
305
  }
@@ -13,6 +13,9 @@ export function consumeAutoWakeup(basePath, unitType, unitId) {
13
13
  pendingWakeups.delete(key);
14
14
  return wakeup;
15
15
  }
16
+ export function clearAutoWakeup(basePath, unitType, unitId) {
17
+ pendingWakeups.delete(wakeupKey(basePath, unitType, unitId));
18
+ }
16
19
  export function _resetAutoWakeupsForTest() {
17
20
  pendingWakeups.clear();
18
21
  }