@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
@@ -1199,11 +1199,11 @@ export function teardownAutoWorktree(
1199
1199
  const branch = autoWorktreeBranch(milestoneId);
1200
1200
  const { preserveBranch = false, preserveWorktree = false } = opts;
1201
1201
  const previousCwd = safeCwd(originalBasePath);
1202
+ let clearActiveWorkspace = true;
1202
1203
 
1203
1204
  // Wrap the entire teardown body in a single try/finally so activeWorkspace
1204
- // is ALWAYS cleared — even if process.chdir throws (e.g. originalBasePath
1205
- // was deleted before teardown ran). Previously the finally only covered
1206
- // removeWorktree, leaving the registry stale on a chdir failure (H3 fix).
1205
+ // is cleared on completion or irrecoverable failure — even if process.chdir
1206
+ // throws (e.g. originalBasePath was deleted before teardown ran).
1207
1207
  try {
1208
1208
  try {
1209
1209
  process.chdir(originalBasePath);
@@ -1245,18 +1245,28 @@ export function teardownAutoWorktree(
1245
1245
  // 3. Remove the worktree unless this exit path explicitly preserves it
1246
1246
  // (slice-parallel dispatch stops the parent loop but keeps the parent
1247
1247
  // milestone worktree for restart/re-entry).
1248
+ let worktreeRemoved = preserveWorktree;
1248
1249
  if (!preserveWorktree) {
1249
- removeWorktree(originalBasePath, milestoneId, {
1250
+ worktreeRemoved = removeWorktree(originalBasePath, milestoneId, {
1250
1251
  branch,
1251
1252
  deleteBranch: !preserveBranch,
1252
1253
  });
1254
+ if (!worktreeRemoved) {
1255
+ logWarning(
1256
+ "worktree",
1257
+ `Worktree removal aborted for ${milestoneId}; uncommitted work was preserved. ` +
1258
+ `Manual quarantine recovery may be needed before retrying teardown.`,
1259
+ { worktree: milestoneId },
1260
+ );
1261
+ clearActiveWorkspace = false;
1262
+ }
1253
1263
  }
1254
1264
 
1255
1265
  // Verify cleanup succeeded — warn if the worktree directory is still on disk.
1256
1266
  // On Windows, bash-based cleanup can silently fail when paths contain
1257
1267
  // backslashes (#1436), leaving ~1 GB+ orphaned directories.
1258
1268
  const wtDir = worktreePath(originalBasePath, milestoneId);
1259
- if (!preserveWorktree && existsSync(wtDir)) {
1269
+ if (!preserveWorktree && worktreeRemoved && existsSync(wtDir)) {
1260
1270
  logWarning(
1261
1271
  "reconcile",
1262
1272
  `Worktree directory still exists after teardown: ${wtDir}. ` +
@@ -1280,10 +1290,12 @@ export function teardownAutoWorktree(
1280
1290
  }
1281
1291
  }
1282
1292
  } finally {
1283
- // Clear module state unconditionally regardless of which step above
1284
- // failed. A stale activeWorkspace causes getActiveAutoWorktreeContext()
1285
- // to return wrong data for subsequent operations.
1286
- setActiveWorkspace(null);
1293
+ // Clear module state when teardown completed or failed irrecoverably.
1294
+ // When removeWorktree returns false (quarantine failure), preserve the
1295
+ // registry so recovery logic still sees the active milestone worktree.
1296
+ if (clearActiveWorkspace) {
1297
+ setActiveWorkspace(null);
1298
+ }
1287
1299
  }
1288
1300
  }
1289
1301
 
@@ -1791,14 +1803,31 @@ export function mergeMilestoneToMain(
1791
1803
  } catch (err) {
1792
1804
  logWarning("worktree", `clearProjectRootStateFiles failed during already-merged cleanup: ${err instanceof Error ? err.message : String(err)}`);
1793
1805
  }
1806
+ let worktreeRemoved = true;
1794
1807
  try {
1795
- removeWorktree(originalBasePath_, milestoneId, {
1808
+ worktreeRemoved = removeWorktree(originalBasePath_, milestoneId, {
1796
1809
  branch: milestoneBranch,
1797
1810
  deleteBranch: false,
1798
1811
  });
1799
1812
  } catch (err) {
1813
+ worktreeRemoved = false;
1800
1814
  logWarning("worktree", `worktree removal failed: ${err instanceof Error ? err.message : String(err)}`);
1801
1815
  }
1816
+
1817
+ if (!worktreeRemoved) {
1818
+ logWarning(
1819
+ "worktree",
1820
+ `Skipping milestone branch deletion for ${milestoneBranch}; worktree removal was aborted to preserve uncommitted milestone work.`,
1821
+ );
1822
+ nudgeGitBranchCache(previousCwd);
1823
+ try {
1824
+ process.chdir(originalBasePath_);
1825
+ } catch (err) {
1826
+ logWarning("worktree", `chdir to project root after already-merged cleanup failed: ${err instanceof Error ? err.message : String(err)}`);
1827
+ }
1828
+ return { commitMessage, pushed: false, prCreated: false, codeFilesChanged: true };
1829
+ }
1830
+
1802
1831
  try {
1803
1832
  nativeBranchDelete(originalBasePath_, milestoneBranch);
1804
1833
  } catch (err) {
@@ -2270,16 +2299,26 @@ export function mergeMilestoneToMain(
2270
2299
 
2271
2300
  const finalizeMilestoneCleanup = (): void => {
2272
2301
  // 12. Remove worktree directory first (must happen before branch deletion)
2302
+ let worktreeRemoved = true;
2273
2303
  try {
2274
- removeWorktree(originalBasePath_, milestoneId, {
2304
+ worktreeRemoved = removeWorktree(originalBasePath_, milestoneId, {
2275
2305
  branch: milestoneBranch,
2276
2306
  deleteBranch: false,
2277
2307
  });
2278
2308
  } catch (err) {
2309
+ worktreeRemoved = false;
2279
2310
  // Best-effort -- worktree dir may already be gone
2280
2311
  logWarning("worktree", `worktree removal failed: ${err instanceof Error ? err.message : String(err)}`);
2281
2312
  }
2282
2313
 
2314
+ if (!worktreeRemoved) {
2315
+ logWarning(
2316
+ "worktree",
2317
+ `Skipping milestone branch deletion for ${milestoneBranch}; worktree removal was aborted to preserve uncommitted milestone work.`,
2318
+ );
2319
+ return;
2320
+ }
2321
+
2283
2322
  // 13. Delete milestone branch (after worktree removal so ref is unlocked)
2284
2323
  try {
2285
2324
  nativeBranchDelete(originalBasePath_, milestoneBranch);
@@ -236,7 +236,7 @@ import {
236
236
  getSlice,
237
237
  getTask,
238
238
  } from "./gsd-db.js";
239
- import { closeWorkflowDatabase } from "./db-workspace.js";
239
+ import { checkpointWorkflowDatabase, closeWorkflowDatabase } from "./db-workspace.js";
240
240
  import { markLatestActiveForWorkerCanceled } from "./db/unit-dispatches.js";
241
241
  import { writeUnitRuntimeRecord } from "./unit-runtime.js";
242
242
  import { countPendingCaptures } from "./captures.js";
@@ -1051,6 +1051,12 @@ export function stopAutoRemote(projectRoot: string): {
1051
1051
  * The normal stop path stays SIGTERM-only so cooperative sessions can clean up;
1052
1052
  * this path is only for the explicit "Force start" action.
1053
1053
  */
1054
+ // Grace window between SIGTERM and SIGKILL so the target can run its cleanup
1055
+ // handler (lock release, DB snapshot flush, worktree teardown). Polled so a
1056
+ // process that exits early is not made to wait the full window.
1057
+ const FORCE_STOP_GRACE_MS = 3000;
1058
+ const FORCE_STOP_POLL_MS = 100;
1059
+
1054
1060
  export function forceStopAutoRemote(projectRoot: string): {
1055
1061
  found: boolean;
1056
1062
  pid?: number;
@@ -1071,6 +1077,13 @@ export function forceStopAutoRemote(projectRoot: string): {
1071
1077
 
1072
1078
  try {
1073
1079
  process.kill(lock.pid, "SIGTERM");
1080
+ // Poll for graceful exit before escalating. Synchronous sleep keeps this
1081
+ // function sync for its callers (Atomics.wait times out with no notify).
1082
+ const deadline = Date.now() + FORCE_STOP_GRACE_MS;
1083
+ while (Date.now() < deadline) {
1084
+ if (!isLockProcessAlive(lock)) break;
1085
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, FORCE_STOP_POLL_MS);
1086
+ }
1074
1087
  if (isLockProcessAlive(lock)) {
1075
1088
  process.kill(lock.pid, "SIGKILL");
1076
1089
  }
@@ -1615,6 +1628,11 @@ export async function stopAuto(
1615
1628
  const installTerminalCloseoutOutcome = completionStopRequested && preserveCloseoutTranscript;
1616
1629
  const preserveCompletionSurface = completionStopRequested || preserveCloseoutTranscript;
1617
1630
  s.completionStopInProgress = preserveCompletionSurface;
1631
+ try {
1632
+ checkpointWorkflowDatabase();
1633
+ } catch (e) {
1634
+ debugLog("stop-checkpoint-failed", { error: e instanceof Error ? e.message : String(e) });
1635
+ }
1618
1636
  playNotificationBell("stop", loadedPreferences?.notifications);
1619
1637
 
1620
1638
  // #4764 — telemetry: record the exit reason, isolation mode, whether an auto
@@ -2299,7 +2317,7 @@ export function buildWorktreeLifecycleDeps(): WorktreeLifecycleDeps {
2299
2317
  // C4 (#5627) — GitServiceImpl constructor → gitServiceFactory
2300
2318
  //
2301
2319
  // Final WorktreeLifecycleDeps shape: 3 fields (gitServiceFactory,
2302
- // worktreeProjection, mergeMilestoneToMain transaction runner). Down from 18 at slice-7
2320
+ // worktreeProjection, mergeMilestone transaction runner). Down from 18 at slice-7
2303
2321
  // closure.
2304
2322
  return {
2305
2323
  gitServiceFactory: (basePath: string) => {
@@ -2308,7 +2326,7 @@ export function buildWorktreeLifecycleDeps(): WorktreeLifecycleDeps {
2308
2326
  return new GitServiceImpl(basePath, gitConfig);
2309
2327
  },
2310
2328
  worktreeProjection: new WorktreeStateProjection(),
2311
- mergeMilestoneToMain: createMilestoneMergeTransaction(mergeMilestoneToMain),
2329
+ mergeMilestone: createMilestoneMergeTransaction(mergeMilestoneToMain),
2312
2330
  };
2313
2331
  }
2314
2332
 
@@ -2357,6 +2375,7 @@ function buildLoopDeps(pi: ExtensionAPI, ctx: ExtensionContext): LoopDeps {
2357
2375
  stopAuto,
2358
2376
  pauseAuto,
2359
2377
  clearUnitTimeout,
2378
+ checkpointWorkflowDatabase,
2360
2379
  updateProgressWidget,
2361
2380
  ...cmux,
2362
2381
  handleLostSessionLock: (ctx: ExtensionContext | undefined, lockStatus: SessionLockStatus | undefined) => {
@@ -710,12 +710,14 @@ export function registerDbTools(pi: ExtensionAPI): void {
710
710
  inputs: Type.Array(Type.String(), { description: "Array<string> of input files or references; pass [\"path\"] or [], never a single string" }),
711
711
  expectedOutput: Type.Array(Type.String(), { description: "Array<string> of files this task creates or overwrites; pass [\"path\"] or [], never prose or a single string" }),
712
712
  observabilityImpact: Type.Optional(Type.String({ description: "Task observability impact" })),
713
+ targetRepositories: Type.Optional(Type.Array(Type.String(), { description: "Repository id(s) this task touches (parent workspace); must match a Declared Repository. Omit for single-repo projects." })),
713
714
  }), { description: "Optional full task replacement for the slice. Omit for incremental planning, then call gsd_plan_task once per task." })),
714
715
  // ── Enrichment metadata (optional — defaults to empty) ────────────
715
716
  successCriteria: Type.Optional(Type.String({ description: "Slice success criteria block" })),
716
717
  proofLevel: Type.Optional(Type.String({ description: "Slice proof level" })),
717
718
  integrationClosure: Type.Optional(Type.String({ description: "Slice integration closure" })),
718
719
  observabilityImpact: Type.Optional(Type.String({ description: "Slice observability impact" })),
720
+ targetRepositories: Type.Optional(Type.Array(Type.String(), { description: "Slice-wide default repository id(s) (parent workspace); must match a Declared Repository. Omit for single-repo projects." })),
719
721
  // Single-writer v3 audit trail (Stream 2): caller-provided actor identity + causation.
720
722
  actorName: Type.Optional(Type.String({ description: "Caller-provided actor identity for the audit trail (e.g. 'executor-01', 'gsd-orchestrator')" })),
721
723
  triggerReason: Type.Optional(Type.String({ description: "Caller-provided reason this action was triggered (e.g. 'plan-phase complete')" })),
@@ -789,6 +791,7 @@ export function registerDbTools(pi: ExtensionAPI): void {
789
791
  inputs: Type.Array(Type.String(), { description: "Array<string> of input files or references; pass [\"path\"] or [], never a single string" }),
790
792
  expectedOutput: Type.Array(Type.String(), { description: "Array<string> of files this task creates or overwrites; pass [\"path\"] or [], never prose or a single string" }),
791
793
  observabilityImpact: Type.Optional(Type.String({ description: "Task observability impact" })),
794
+ targetRepositories: Type.Optional(Type.Array(Type.String(), { description: "Repository id(s) this task touches (parent workspace); must match a Declared Repository. Omit for single-repo projects." })),
792
795
  // Single-writer v3 audit trail (Stream 2): caller-provided actor identity + causation.
793
796
  actorName: Type.Optional(Type.String({ description: "Caller-provided actor identity for the audit trail (e.g. 'executor-01', 'gsd-orchestrator')" })),
794
797
  triggerReason: Type.Optional(Type.String({ description: "Caller-provided reason this action was triggered (e.g. 'plan-phase complete')" })),
@@ -32,6 +32,12 @@ import {
32
32
  recordAutoToolSurfaceSnapshot,
33
33
  recordToolInvocationError,
34
34
  } from "../auto-runtime-state.js";
35
+ import {
36
+ isDeterministicPolicyError,
37
+ isQueuedUserMessageSkip,
38
+ isToolInvocationError,
39
+ isToolUnavailableError,
40
+ } from "../auto-tool-tracking.js";
35
41
  import { applyProviderPayloadPolicy } from "../provider-payload-policy.js";
36
42
 
37
43
  import { checkToolCallLoop, recordToolCallLoopMutation, resetToolCallLoopGuard } from "./tool-call-loop-guard.js";
@@ -77,9 +83,24 @@ import { clearPendingAutoStart } from "../pending-auto-start.js";
77
83
  import { resolveWorkflowToolBasePath } from "./dynamic-tools.js";
78
84
  import { getRequiredWorkflowToolsForUnit } from "../unit-tool-contracts.js";
79
85
  import { flushAllManifests } from "../workflow-manifest.js";
86
+ import { recordUnitHarnessAbort, type UnitHarnessAbortRecord } from "../unit-runtime.js";
80
87
 
81
88
  let approvalQuestionAbortInFlight = false;
82
89
 
90
+ function recordCurrentUnitHarnessAbort(
91
+ abort: Omit<UnitHarnessAbortRecord, "recordedAt"> & { recordedAt?: number },
92
+ ): void {
93
+ const dash = getAutoRuntimeSnapshot();
94
+ if (!dash.active || !dash.basePath || !dash.currentUnit) return;
95
+ recordUnitHarnessAbort(
96
+ dash.basePath,
97
+ dash.currentUnit.type,
98
+ dash.currentUnit.id,
99
+ dash.currentUnit.startedAt,
100
+ abort,
101
+ );
102
+ }
103
+
83
104
  type WelcomeScreenModule = {
84
105
  buildWelcomeScreenLines(opts: { version: string; remoteChannel?: string; width?: number }): string[];
85
106
  };
@@ -209,10 +230,26 @@ const WORKFLOW_GSD_TOOL_NAMES = [
209
230
  ...Object.values(AUTO_UNIT_SCOPED_TOOLS).flat(),
210
231
  ].filter(isGsdManagedTool);
211
232
 
233
+ const WORKFLOW_ONLY_AUTO_BASE_TOOL_NAMES = [
234
+ "find",
235
+ "glob",
236
+ "grep",
237
+ "ls",
238
+ "read",
239
+ "subagent",
240
+ ] as const;
241
+
212
242
  function isGsdManagedTool(name: string): boolean {
213
243
  return name.startsWith("gsd_") || name === "memory_query" || name === "capture_thought" || name === "gsd_graph";
214
244
  }
215
245
 
246
+ function autoBaseToolNamesForUnit(unitType: string | undefined): readonly string[] {
247
+ const manifest = unitType ? resolveManifest(unitType) : null;
248
+ return manifest?.tools.mode === "workflow-only"
249
+ ? WORKFLOW_ONLY_AUTO_BASE_TOOL_NAMES
250
+ : MINIMAL_AUTO_BASE_TOOL_NAMES;
251
+ }
252
+
216
253
  /**
217
254
  * Resolves requested tool names against active tools using exact and MCP-scoped matches.
218
255
  *
@@ -275,7 +312,7 @@ export function buildMinimalAutoGsdToolSet(
275
312
  return buildRunUatGsdToolSet(activeToolNames, registeredToolNames);
276
313
  }
277
314
  const unitTools = unitType ? AUTO_UNIT_SCOPED_TOOLS[unitType] ?? [] : [];
278
- const autoBaseTools = new Set<string>(MINIMAL_AUTO_BASE_TOOL_NAMES);
315
+ const autoBaseTools = new Set<string>(autoBaseToolNamesForUnit(unitType));
279
316
  const availableBaseTools = registeredToolNames.filter((name) => autoBaseTools.has(name));
280
317
  const preserved = [...new Set([
281
318
  ...activeToolNames.filter((name) => autoBaseTools.has(name)),
@@ -599,6 +636,72 @@ function formatLoopGuardBlockReason(reason: string | undefined): string | undefi
599
636
  );
600
637
  }
601
638
 
639
+ function isGateResultPersistenceTool(toolName: string): boolean {
640
+ return toolName === "gsd_save_gate_result" || toolName === "gsd_uat_result_save";
641
+ }
642
+
643
+ function resultDetails(result: unknown): Record<string, unknown> {
644
+ if (!result || typeof result !== "object") return {};
645
+ const details = (result as { details?: unknown }).details;
646
+ return details && typeof details === "object" ? details as Record<string, unknown> : {};
647
+ }
648
+
649
+ function isAbortedExecutionToolResult(toolName: string, result: unknown): boolean {
650
+ if (toolName !== "gsd_exec" && toolName !== "gsd_uat_exec") return false;
651
+ const details = resultDetails(result);
652
+ return details.aborted === true || details.force_resolved === true || details.timed_out === true;
653
+ }
654
+
655
+ function isRetryableHarnessToolError(toolName: string, result: unknown, errorText: string): boolean {
656
+ if (isGateResultPersistenceTool(toolName)) return false;
657
+ if (isDeterministicPolicyError(errorText)) return false;
658
+ if (toolName === "gsd_exec" || toolName === "gsd_uat_exec") {
659
+ if (isAbortedExecutionToolResult(toolName, result)) return true;
660
+ if (isToolUnavailableError(errorText)) return true;
661
+ if (isQueuedUserMessageSkip(errorText)) return true;
662
+ return isToolInvocationError(errorText);
663
+ }
664
+ if (isToolUnavailableError(errorText)) return true;
665
+ if (isQueuedUserMessageSkip(errorText)) return true;
666
+ return false;
667
+ }
668
+
669
+ function recordRetryableHarnessToolError(toolName: string, result: unknown, errorText: string): void {
670
+ if (!isRetryableHarnessToolError(toolName, result, errorText)) return;
671
+ recordCurrentUnitHarnessAbort({
672
+ kind: "tool-error",
673
+ reason: errorText || "Tool execution failed before the unit could complete its gate evaluation.",
674
+ toolName,
675
+ });
676
+ }
677
+
678
+ function recordRetryableTurnAbort(event: { abortOrigin?: unknown; messages?: unknown[] }): void {
679
+ const origin = typeof event.abortOrigin === "string" ? event.abortOrigin : undefined;
680
+ if (origin === "session-transition") return;
681
+
682
+ const messages = Array.isArray(event.messages) ? event.messages : [];
683
+ const lastMsg = messages[messages.length - 1];
684
+ const stopReason = lastMsg && typeof lastMsg === "object"
685
+ ? (lastMsg as { stopReason?: unknown }).stopReason
686
+ : undefined;
687
+ if (stopReason !== "aborted" && stopReason !== "error") return;
688
+
689
+ const errorMessage = lastMsg && typeof lastMsg === "object"
690
+ ? (lastMsg as { errorMessage?: unknown }).errorMessage
691
+ : undefined;
692
+ const reason = [
693
+ "Agent turn aborted before the unit could complete its gate evaluation.",
694
+ origin ? `origin=${origin}` : undefined,
695
+ typeof stopReason === "string" ? `stopReason=${stopReason}` : undefined,
696
+ typeof errorMessage === "string" && errorMessage.trim() ? `error=${errorMessage.trim()}` : undefined,
697
+ ].filter(Boolean).join(" ");
698
+
699
+ recordCurrentUnitHarnessAbort({
700
+ kind: "turn-abort",
701
+ reason,
702
+ });
703
+ }
704
+
602
705
  function beginSourceObservationStoreForCurrentUnit(
603
706
  ctx?: { cwd?: string },
604
707
  ): ReturnType<typeof getSourceObservationStore> | null {
@@ -902,8 +1005,8 @@ export function registerHooks(
902
1005
  await prepareWorkflowMcpForHookContext(ctx, basePath);
903
1006
 
904
1007
  // Migrate legacy .gsd/milestones/ to flat-phase .gsd/phases/ when detected.
905
- // Best-effort never blocks session startup. Skipped inside auto-worktrees
906
- // (migration already ran on the project root before the worktree was created).
1008
+ // Fail closed on migration errors: resolvers assume flat-phase paths after
1009
+ // startup, so continuing with nested disk state corrupts later checks.
907
1010
  try {
908
1011
  const { isInAutoWorktree } = await import("../auto-worktree.js");
909
1012
  if (!isInAutoWorktree(basePath)) {
@@ -917,11 +1020,21 @@ export function registerHooks(
917
1020
  } else {
918
1021
  safetyLogWarning(
919
1022
  "bootstrap",
920
- "flat-phase migration deferred: legacy .gsd/milestones/ layout detected but the workflow database could not be opened — will retry on next session",
1023
+ "flat-phase migration required: legacy .gsd/milestones/ layout detected but the workflow database could not be opened — fix database access before starting GSD",
1024
+ );
1025
+ throw new Error(
1026
+ "flat-phase migration required but the workflow database could not be opened; fix database access before starting GSD",
921
1027
  );
922
1028
  }
923
1029
  }
924
1030
  }
1031
+ } catch (err) {
1032
+ const message = err instanceof Error ? err.message : String(err);
1033
+ safetyLogWarning("bootstrap", `flat-phase migration failed: ${message}`);
1034
+ throw new Error(`flat-phase migration failed: ${message}`);
1035
+ }
1036
+
1037
+ try {
925
1038
  const projectRoot = resolveWorktreeProjectRoot(basePath);
926
1039
  const { pruneStaleFlatPhaseBackups } = await import("../flat-phase-migration.js");
927
1040
  const pruned = pruneStaleFlatPhaseBackups(projectRoot);
@@ -932,7 +1045,7 @@ export function registerHooks(
932
1045
  );
933
1046
  }
934
1047
  } catch (err) {
935
- safetyLogWarning("bootstrap", `flat-phase migration: ${err instanceof Error ? err.message : String(err)}`);
1048
+ safetyLogWarning("bootstrap", `flat-phase backup pruning: ${err instanceof Error ? err.message : String(err)}`);
936
1049
  }
937
1050
 
938
1051
  // Apply show_token_cost preference (#1515)
@@ -1090,6 +1203,7 @@ export function registerHooks(
1090
1203
 
1091
1204
  pi.on("agent_end", async (event, ctx: ExtensionContext) => {
1092
1205
  approvalQuestionAbortInFlight = false;
1206
+ recordRetryableTurnAbort(event);
1093
1207
  resetToolCallLoopGuard();
1094
1208
  resetPendingGatePauseGuard();
1095
1209
  await resetAskUserQuestionsTurnCache();
@@ -1315,6 +1429,12 @@ export function registerHooks(
1315
1429
  // ── Loop guard: block repeated identical tool calls ──
1316
1430
  const loopCheck = checkToolCallLoop(toolName, event.input as Record<string, unknown>);
1317
1431
  if (loopCheck.block) {
1432
+ recordCurrentUnitHarnessAbort({
1433
+ kind: "tool-loop-guard",
1434
+ reason: loopCheck.reason ?? "Tool-call loop guard blocked a repeated tool call.",
1435
+ toolName,
1436
+ count: loopCheck.count,
1437
+ });
1318
1438
  return { block: true, reason: formatLoopGuardBlockReason(loopCheck.reason) };
1319
1439
  }
1320
1440
 
@@ -1603,6 +1723,7 @@ export function registerHooks(
1603
1723
  // Let recordToolInvocationError classify the failure so non-gsd_ harness
1604
1724
  // errors and deterministic policy rejections are handled consistently.
1605
1725
  recordToolInvocationError(event.toolName, errorText);
1726
+ recordRetryableHarnessToolError(toolName, resultPayload, errorText);
1606
1727
  } else if (isAutoActive()) {
1607
1728
  clearToolInvocationError();
1608
1729
  }
@@ -1799,6 +1920,7 @@ export function registerHooks(
1799
1920
  // for every finalized tool call on every engine, so error classification
1800
1921
  // and evidence persistence here cover external engines that skip tool_result.
1801
1922
  pi.on("tool_execution_end", async (event) => {
1923
+ const toolName = canonicalToolName(event.toolName);
1802
1924
  markToolEnd(event.toolCallId);
1803
1925
  // #2883/#4974: Capture deterministic invocation/policy errors
1804
1926
  // so postUnitPreVerification can break the retry loop instead of re-dispatching.
@@ -1809,6 +1931,7 @@ export function registerHooks(
1809
1931
  // Let recordToolInvocationError classify the failure so non-gsd_ harness
1810
1932
  // errors and deterministic policy rejections are handled consistently.
1811
1933
  recordToolInvocationError(event.toolName, errorText);
1934
+ recordRetryableHarnessToolError(toolName, event.result, errorText);
1812
1935
  } else if (isAutoActive()) {
1813
1936
  clearToolInvocationError();
1814
1937
  }
@@ -6,22 +6,85 @@ import type { ExtensionAPI } from "@gsd/pi-coding-agent";
6
6
 
7
7
  import { getAutoRuntimeSnapshot } from "../auto-runtime-state.js";
8
8
  import { scheduleAutoWakeup } from "../auto/schedule-wakeup.js";
9
+ import { logWarning } from "../workflow-logger.js";
9
10
  import { resolveCtxCwd } from "./dynamic-tools.js";
10
11
 
11
12
  const MAX_WAKEUP_DELAY_SECONDS = 24 * 60 * 60;
13
+ const INTERACTIVE_WAKEUP_CUSTOM_TYPE = "gsd-schedule-wakeup";
14
+
15
+ // One pending interactive wakeup per session, keyed by base path — the same
16
+ // scoping auto-mode uses for its wakeup map (see `wakeupKey`). Re-arming cancels
17
+ // only that session's prior timer, so repeated polling never stacks overlapping
18
+ // wakeups, and concurrent projects in one host process don't cancel each other.
19
+ const pendingInteractiveWakeups = new Map<string, ReturnType<typeof setTimeout>>();
20
+
21
+ function scheduleInteractiveWakeup(
22
+ pi: ExtensionAPI,
23
+ key: string,
24
+ delaySeconds: number,
25
+ prompt: string,
26
+ reason: string,
27
+ ): void {
28
+ const existing = pendingInteractiveWakeups.get(key);
29
+ if (existing) clearTimeout(existing);
30
+
31
+ const handle = setTimeout(() => {
32
+ if (pendingInteractiveWakeups.get(key) === handle) {
33
+ pendingInteractiveWakeups.delete(key);
34
+ }
35
+ try {
36
+ void Promise.resolve(pi.sendMessage(
37
+ {
38
+ customType: INTERACTIVE_WAKEUP_CUSTOM_TYPE,
39
+ content: prompt,
40
+ display: true,
41
+ details: { delaySeconds, reason },
42
+ },
43
+ { triggerTurn: true },
44
+ )).catch((error) => {
45
+ logWarning(
46
+ "bootstrap",
47
+ `ScheduleWakeup interactive dispatch failed: ${error instanceof Error ? error.message : String(error)}`,
48
+ );
49
+ });
50
+ } catch (error) {
51
+ logWarning(
52
+ "bootstrap",
53
+ `ScheduleWakeup interactive dispatch failed: ${error instanceof Error ? error.message : String(error)}`,
54
+ );
55
+ }
56
+ }, delaySeconds * 1000);
57
+ pendingInteractiveWakeups.set(key, handle);
58
+ if (
59
+ typeof handle === "object" &&
60
+ handle !== null &&
61
+ "unref" in handle &&
62
+ typeof handle.unref === "function"
63
+ ) {
64
+ handle.unref();
65
+ }
66
+ }
67
+
68
+ export function _resetInteractiveWakeupsForTest(): void {
69
+ for (const handle of pendingInteractiveWakeups.values()) {
70
+ clearTimeout(handle);
71
+ }
72
+ pendingInteractiveWakeups.clear();
73
+ }
12
74
 
13
75
  export function registerScheduleWakeupTool(pi: ExtensionAPI): void {
14
76
  pi.registerTool({
15
77
  name: "ScheduleWakeup",
16
78
  label: "Schedule Wakeup",
17
79
  description:
18
- "In GSD auto-mode, pause the current unit and continue the same session after a delay. " +
19
- "Use this for long external processes that need polling without ending the unit as no-artifact.",
20
- promptSnippet: "Schedule a same-session auto-mode wakeup after a delay.",
80
+ "Schedule a delayed continuation turn. In GSD auto-mode, continue the current unit in the same session; " +
81
+ "outside auto-mode, start a new triggered turn with the supplied wakeup prompt.",
82
+ promptSnippet: "Schedule a wakeup prompt after a delay.",
21
83
  promptGuidelines: [
22
84
  "Use ScheduleWakeup at the end of an execute-task turn when waiting for a long external process.",
23
85
  "Include a prompt that says exactly what external state to check next and what artifact to write when done.",
24
86
  "Re-arm ScheduleWakeup on each polling turn if the external process is still running.",
87
+ "Outside auto-mode, use ScheduleWakeup when the user asks you to check back or poll later.",
25
88
  ],
26
89
  parameters: Type.Object({
27
90
  delaySeconds: Type.Number({
@@ -40,27 +103,32 @@ export function registerScheduleWakeupTool(pi: ExtensionAPI): void {
40
103
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
41
104
  const dash = getAutoRuntimeSnapshot();
42
105
  const currentUnit = dash.currentUnit;
43
- const basePath = dash.basePath || resolveCtxCwd(ctx);
106
+ const delaySeconds = Math.max(
107
+ 1,
108
+ Math.min(MAX_WAKEUP_DELAY_SECONDS, Math.floor(params.delaySeconds)),
109
+ );
110
+ const reason = params.reason ?? "";
44
111
 
45
112
  if (!dash.active || !currentUnit) {
113
+ const interactiveKey = dash.basePath || resolveCtxCwd(ctx);
114
+ scheduleInteractiveWakeup(pi, interactiveKey, delaySeconds, params.prompt, reason);
46
115
  return {
47
- content: [{ type: "text", text: "ScheduleWakeup is only available during an active GSD auto-mode unit." }],
48
- details: { operation: "schedule_wakeup", error: "auto_mode_inactive" },
49
- isError: true,
116
+ content: [{
117
+ type: "text",
118
+ text: `Wakeup scheduled for ${delaySeconds}s. GSD will start a new turn with the wakeup prompt.`,
119
+ }],
120
+ details: { operation: "schedule_wakeup", delaySeconds, mode: "interactive" },
50
121
  };
51
122
  }
52
123
 
53
- const delaySeconds = Math.max(
54
- 1,
55
- Math.min(MAX_WAKEUP_DELAY_SECONDS, Math.floor(params.delaySeconds)),
56
- );
124
+ const basePath = dash.basePath || resolveCtxCwd(ctx);
57
125
  scheduleAutoWakeup({
58
126
  basePath,
59
127
  unitType: currentUnit.type,
60
128
  unitId: currentUnit.id,
61
129
  delayMs: delaySeconds * 1000,
62
130
  prompt: params.prompt,
63
- reason: params.reason ?? "",
131
+ reason,
64
132
  createdAt: Date.now(),
65
133
  });
66
134
 
@@ -25,6 +25,8 @@
25
25
 
26
26
  import { createHash } from "node:crypto";
27
27
  import { INHERENTLY_REPEATABLE_TOOL_SET } from "./core-session-tools.js";
28
+ import { hasBrowserContractPrefix } from "../../shared/browser-contract.js";
29
+ import { canonicalToolName } from "../engine-hook-contract.js";
28
30
 
29
31
  const MAX_CONSECUTIVE_IDENTICAL_CALLS = 4;
30
32
 
@@ -130,8 +132,16 @@ export function checkToolCallLoop(
130
132
  perToolLastMutationEpoch.set(toolName, mutationEpoch);
131
133
 
132
134
  // Read-only navigation tools are normal context gathering; Guard 1 still
133
- // catches true reread loops with identical arguments.
134
- if (PER_TOOL_CAP_EXEMPT_TOOLS.has(toolName)) {
135
+ // catches true reread loops with identical arguments. Browser Automation
136
+ // Contract tools (browser_*) are the same shape: a browser-backed UAT makes
137
+ // many distinct-arg calls (read a message, count edits, inspect a card, …),
138
+ // so the arg-independent per-tool cap misfires on legitimate verification;
139
+ // Guard 1's identical-signature streak still catches a genuinely stuck
140
+ // browser loop.
141
+ if (
142
+ PER_TOOL_CAP_EXEMPT_TOOLS.has(toolName) ||
143
+ hasBrowserContractPrefix(canonicalToolName(toolName))
144
+ ) {
135
145
  return { block: false, count: consecutiveCount };
136
146
  }
137
147
 
@@ -1169,7 +1169,7 @@ function allowsControlledSubagentDispatch(
1169
1169
  policy: ToolsPolicy,
1170
1170
  ): policy is ToolsPolicy & { readonly allowedSubagents: readonly string[] } {
1171
1171
  return (
1172
- (policy.mode === "planning-dispatch" || policy.mode === "verification") &&
1172
+ (policy.mode === "planning-dispatch" || policy.mode === "verification" || policy.mode === "workflow-only") &&
1173
1173
  Array.isArray((policy as { readonly allowedSubagents?: unknown }).allowedSubagents)
1174
1174
  );
1175
1175
  }
@@ -1199,6 +1199,7 @@ function warnMissingControlledDispatchAgentClasses(unitType: string, mode: strin
1199
1199
  const PLANNING_SAFE_TOOLS = new Set([
1200
1200
  "read", "grep", "find", "ls", "glob",
1201
1201
  "ask_user_questions",
1202
+ "memory_query",
1202
1203
  "search-the-web", "resolve_library", "get_library_docs", "fetch_page",
1203
1204
  "search_and_read",
1204
1205
  ]);
@@ -1265,6 +1266,9 @@ type PlanningUnitBlockResult = {
1265
1266
  * → allows Bash for project verification commands, keeps
1266
1267
  * writes restricted to .gsd/, and permits subagent dispatch
1267
1268
  * only when the manifest declares allowedSubagents.
1269
+ * - "workflow-only"
1270
+ * → allows reads, GSD workflow tools, and declared read-only
1271
+ * subagents; blocks generic Bash and direct file writes.
1268
1272
  *
1269
1273
  * `pathOrCommand` is the file path for write/edit-shaped tools and the
1270
1274
  * shell command for bash. Other tools ignore this argument.
@@ -1354,6 +1358,24 @@ export function shouldBlockPlanningUnit(
1354
1358
  return planningBlock(unitType, policy.mode, "subagent dispatch is not permitted in planning units");
1355
1359
  }
1356
1360
 
1361
+ if (policy.mode === "workflow-only") {
1362
+ if (tool === "bash") {
1363
+ return planningBlock(
1364
+ unitType,
1365
+ policy.mode,
1366
+ `bash is not permitted; use the unit's dedicated GSD workflow tools for durable validation output`,
1367
+ );
1368
+ }
1369
+ if (PLANNING_WRITE_TOOLS.has(tool)) {
1370
+ return planningBlock(
1371
+ unitType,
1372
+ policy.mode,
1373
+ `cannot ${tool} "${pathOrCommand}" — direct artifact writes are disabled; call the required GSD workflow tool instead`,
1374
+ );
1375
+ }
1376
+ return planningBlock(unitType, policy.mode, `tool "${tool}" is not on the workflow-only allowlist`);
1377
+ }
1378
+
1357
1379
  if (tool === "bash") {
1358
1380
  if (policy.mode === "verification") {
1359
1381
  if (BASH_VERIFICATION_RE.test(pathOrCommand) || BASH_READ_ONLY_RE.test(pathOrCommand)) return { block: false };