@opengsd/gsd-pi 1.1.1-dev.a5a2de8 → 1.1.1-dev.b2556262

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 (325) hide show
  1. package/dist/headless-recover.js +56 -1
  2. package/dist/resources/.managed-resources-content-hash +1 -1
  3. package/dist/resources/extensions/browser-tools/engine/managed-gsd-browser.js +18 -2
  4. package/dist/resources/extensions/browser-tools/engine/selection.js +1 -1
  5. package/dist/resources/extensions/browser-tools/extension-manifest.json +1 -1
  6. package/dist/resources/extensions/browser-tools/index.js +68 -24
  7. package/dist/resources/extensions/browser-tools/state.js +12 -0
  8. package/dist/resources/extensions/browser-tools/tools/session.js +3 -2
  9. package/dist/resources/extensions/browser-tools/utils.js +3 -3
  10. package/dist/resources/extensions/browser-tools/web-app-detect.js +52 -0
  11. package/dist/resources/extensions/gsd/auto/loop.js +4 -2
  12. package/dist/resources/extensions/gsd/auto/phases.js +87 -12
  13. package/dist/resources/extensions/gsd/auto/session.js +22 -1
  14. package/dist/resources/extensions/gsd/auto/workflow-kernel.js +1 -0
  15. package/dist/resources/extensions/gsd/auto-dispatch.js +81 -13
  16. package/dist/resources/extensions/gsd/auto-model-selection.js +154 -9
  17. package/dist/resources/extensions/gsd/auto-post-unit.js +19 -2
  18. package/dist/resources/extensions/gsd/auto-prompts.js +26 -21
  19. package/dist/resources/extensions/gsd/auto-recovery.js +4 -2
  20. package/dist/resources/extensions/gsd/auto-runtime-state.js +3 -0
  21. package/dist/resources/extensions/gsd/auto-start.js +1 -1
  22. package/dist/resources/extensions/gsd/auto-timers.js +24 -10
  23. package/dist/resources/extensions/gsd/auto.js +40 -15
  24. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +3 -3
  25. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +192 -77
  26. package/dist/resources/extensions/gsd/bootstrap/system-context.js +1 -1
  27. package/dist/resources/extensions/gsd/closeout-wizard.js +32 -9
  28. package/dist/resources/extensions/gsd/commands/handlers/auto.js +10 -0
  29. package/dist/resources/extensions/gsd/commands/handlers/ops.js +2 -9
  30. package/dist/resources/extensions/gsd/commands-maintenance.js +93 -15
  31. package/dist/resources/extensions/gsd/commands-mcp-status.js +1 -1
  32. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +2 -2
  33. package/dist/resources/extensions/gsd/config-overlay.js +1 -0
  34. package/dist/resources/extensions/gsd/context-masker.js +129 -5
  35. package/dist/resources/extensions/gsd/db-writer.js +35 -0
  36. package/dist/resources/extensions/gsd/docs/preferences-reference.md +50 -1
  37. package/dist/resources/extensions/gsd/gsd-db.js +480 -172
  38. package/dist/resources/extensions/gsd/guided-flow.js +4 -1
  39. package/dist/resources/extensions/gsd/markdown-renderer.js +37 -53
  40. package/dist/resources/extensions/gsd/md-importer.js +38 -3
  41. package/dist/resources/extensions/gsd/migration-auto-check.js +126 -31
  42. package/dist/resources/extensions/gsd/parsers-legacy.js +23 -0
  43. package/dist/resources/extensions/gsd/planner-handoff.js +98 -0
  44. package/dist/resources/extensions/gsd/planning-path-scope.js +22 -4
  45. package/dist/resources/extensions/gsd/pre-execution-checks.js +10 -2
  46. package/dist/resources/extensions/gsd/preferences-models.js +111 -43
  47. package/dist/resources/extensions/gsd/preferences-types.js +13 -0
  48. package/dist/resources/extensions/gsd/preferences-validation.js +68 -3
  49. package/dist/resources/extensions/gsd/preferences.js +4 -1
  50. package/dist/resources/extensions/gsd/prompts/gate-evaluate.md +1 -1
  51. package/dist/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
  52. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  53. package/dist/resources/extensions/gsd/prompts/refine-slice.md +1 -1
  54. package/dist/resources/extensions/gsd/prompts/run-uat.md +2 -2
  55. package/dist/resources/extensions/gsd/prompts/system.md +1 -1
  56. package/dist/resources/extensions/gsd/roadmap-slices.js +5 -1
  57. package/dist/resources/extensions/gsd/safety/content-validator.js +6 -4
  58. package/dist/resources/extensions/gsd/skill-manifest.js +12 -0
  59. package/dist/resources/extensions/gsd/source-observations.js +306 -0
  60. package/dist/resources/extensions/gsd/state-reconciliation/drift/completion.js +15 -8
  61. package/dist/resources/extensions/gsd/state-reconciliation/drift/stale-render.js +33 -5
  62. package/dist/resources/extensions/gsd/state-reconciliation/drift/stale-worker.js +34 -13
  63. package/dist/resources/extensions/gsd/state-reconciliation/index.js +39 -14
  64. package/dist/resources/extensions/gsd/state-reconciliation/spawn-gate.js +4 -4
  65. package/dist/resources/extensions/gsd/state.js +7 -3
  66. package/dist/resources/extensions/gsd/tool-contract.js +15 -1
  67. package/dist/resources/extensions/gsd/tool-presentation-plan.js +24 -2
  68. package/dist/resources/extensions/gsd/tools/complete-slice.js +28 -0
  69. package/dist/resources/extensions/gsd/tools/plan-slice.js +42 -11
  70. package/dist/resources/extensions/gsd/tools/plan-task.js +7 -1
  71. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +62 -406
  72. package/dist/resources/extensions/gsd/uat-policy.js +130 -0
  73. package/dist/resources/extensions/gsd/uat-run.js +414 -0
  74. package/dist/resources/extensions/gsd/unit-context-manifest.js +3 -4
  75. package/dist/resources/extensions/gsd/unit-tool-contracts.js +38 -14
  76. package/dist/resources/extensions/gsd/verdict-parser.js +3 -8
  77. package/dist/resources/extensions/gsd/workflow-manifest.js +132 -5
  78. package/dist/resources/extensions/gsd/workflow-mcp.js +2 -3
  79. package/dist/resources/extensions/gsd/workflow-projections.js +8 -0
  80. package/dist/resources/extensions/gsd/worktree-manager.js +26 -0
  81. package/dist/resources/extensions/gsd/worktree-reentry.js +96 -0
  82. package/dist/resources/extensions/gsd/worktree-state-projection.js +18 -17
  83. package/dist/resources/extensions/shared/gsd-browser-cli.js +6 -0
  84. package/dist/resources/extensions/subagent/agents.js +1 -0
  85. package/dist/resources/extensions/subagent/index.js +27 -12
  86. package/dist/resources/extensions/subagent/launch.js +7 -2
  87. package/dist/web/standalone/.next/BUILD_ID +1 -1
  88. package/dist/web/standalone/.next/app-path-routes-manifest.json +6 -6
  89. package/dist/web/standalone/.next/build-manifest.json +2 -2
  90. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  91. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  92. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  93. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  94. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  95. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  96. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  97. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  98. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  99. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  100. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  101. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  102. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  103. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  104. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  105. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  106. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  107. package/dist/web/standalone/.next/server/app/index.html +1 -1
  108. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  109. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  110. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  111. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  112. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  113. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  114. package/dist/web/standalone/.next/server/app-paths-manifest.json +6 -6
  115. package/dist/web/standalone/.next/server/chunks/8357.js +1 -1
  116. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  117. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  118. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  119. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  120. package/dist/web/standalone/node_modules/@gsd/native/dist/native.js +22 -0
  121. package/dist/web/standalone/node_modules/node-pty/build/Makefile +1 -1
  122. package/package.json +4 -4
  123. package/packages/cloud-mcp-gateway/package.json +2 -2
  124. package/packages/contracts/package.json +1 -1
  125. package/packages/daemon/package.json +4 -4
  126. package/packages/gsd-agent-core/package.json +5 -5
  127. package/packages/gsd-agent-modes/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  128. package/packages/gsd-agent-modes/dist/modes/interactive/components/assistant-message.js +21 -23
  129. package/packages/gsd-agent-modes/dist/modes/interactive/components/assistant-message.js.map +1 -1
  130. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts +3 -0
  131. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  132. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js +25 -0
  133. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js.map +1 -1
  134. package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.d.ts +1 -0
  135. package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.d.ts.map +1 -1
  136. package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js +66 -12
  137. package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js.map +1 -1
  138. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  139. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js +18 -11
  140. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  141. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-chat-render.d.ts.map +1 -1
  142. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-chat-render.js +16 -0
  143. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-chat-render.js.map +1 -1
  144. package/packages/gsd-agent-modes/package.json +7 -7
  145. package/packages/mcp-server/dist/workflow-tools.js +1 -1
  146. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  147. package/packages/mcp-server/package.json +3 -3
  148. package/packages/native/dist/native.js +22 -0
  149. package/packages/native/package.json +1 -1
  150. package/packages/pi-agent-core/package.json +1 -1
  151. package/packages/pi-ai/dist/image-models.generated.d.ts +30 -0
  152. package/packages/pi-ai/dist/image-models.generated.d.ts.map +1 -1
  153. package/packages/pi-ai/dist/image-models.generated.js +30 -0
  154. package/packages/pi-ai/dist/image-models.generated.js.map +1 -1
  155. package/packages/pi-ai/dist/models.generated.d.ts +174 -29
  156. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  157. package/packages/pi-ai/dist/models.generated.js +178 -54
  158. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  159. package/packages/pi-ai/dist/providers/transform-messages.d.ts.map +1 -1
  160. package/packages/pi-ai/dist/providers/transform-messages.js +8 -1
  161. package/packages/pi-ai/dist/providers/transform-messages.js.map +1 -1
  162. package/packages/pi-ai/package.json +1 -1
  163. package/packages/pi-coding-agent/dist/core/settings-manager.js +1 -1
  164. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  165. package/packages/pi-coding-agent/dist/theme/themes.js +1 -1
  166. package/packages/pi-coding-agent/dist/theme/themes.js.map +1 -1
  167. package/packages/pi-coding-agent/package.json +7 -7
  168. package/packages/pi-tui/dist/utils.d.ts +11 -0
  169. package/packages/pi-tui/dist/utils.d.ts.map +1 -1
  170. package/packages/pi-tui/dist/utils.js +119 -6
  171. package/packages/pi-tui/dist/utils.js.map +1 -1
  172. package/packages/pi-tui/package.json +2 -1
  173. package/packages/rpc-client/package.json +2 -2
  174. package/pkg/dist/theme/themes.js +1 -1
  175. package/pkg/dist/theme/themes.js.map +1 -1
  176. package/pkg/package.json +1 -1
  177. package/scripts/install/handoff.js +16 -3
  178. package/src/resources/extensions/browser-tools/engine/managed-gsd-browser.ts +21 -2
  179. package/src/resources/extensions/browser-tools/engine/selection.ts +1 -1
  180. package/src/resources/extensions/browser-tools/extension-manifest.json +1 -1
  181. package/src/resources/extensions/browser-tools/index.ts +75 -27
  182. package/src/resources/extensions/browser-tools/state.ts +13 -0
  183. package/src/resources/extensions/browser-tools/tests/browser-engine-selection.test.mjs +2 -2
  184. package/src/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +57 -0
  185. package/src/resources/extensions/browser-tools/tests/gsd-browser-launch-config.test.mjs +37 -0
  186. package/src/resources/extensions/browser-tools/tests/web-app-detect.test.mjs +68 -0
  187. package/src/resources/extensions/browser-tools/tools/session.ts +4 -2
  188. package/src/resources/extensions/browser-tools/utils.ts +3 -3
  189. package/src/resources/extensions/browser-tools/web-app-detect.ts +63 -0
  190. package/src/resources/extensions/gsd/auto/loop-deps.ts +1 -0
  191. package/src/resources/extensions/gsd/auto/loop.ts +4 -2
  192. package/src/resources/extensions/gsd/auto/phases.ts +89 -15
  193. package/src/resources/extensions/gsd/auto/session.ts +24 -1
  194. package/src/resources/extensions/gsd/auto/workflow-kernel.ts +1 -0
  195. package/src/resources/extensions/gsd/auto-dispatch.ts +117 -12
  196. package/src/resources/extensions/gsd/auto-model-selection.ts +190 -12
  197. package/src/resources/extensions/gsd/auto-post-unit.ts +20 -2
  198. package/src/resources/extensions/gsd/auto-prompts.ts +25 -22
  199. package/src/resources/extensions/gsd/auto-recovery.ts +22 -3
  200. package/src/resources/extensions/gsd/auto-runtime-state.ts +5 -0
  201. package/src/resources/extensions/gsd/auto-start.ts +1 -1
  202. package/src/resources/extensions/gsd/auto-timers.ts +25 -9
  203. package/src/resources/extensions/gsd/auto.ts +41 -14
  204. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +3 -3
  205. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +250 -78
  206. package/src/resources/extensions/gsd/bootstrap/system-context.ts +1 -1
  207. package/src/resources/extensions/gsd/closeout-wizard.ts +47 -13
  208. package/src/resources/extensions/gsd/commands/handlers/auto.ts +9 -0
  209. package/src/resources/extensions/gsd/commands/handlers/ops.ts +2 -17
  210. package/src/resources/extensions/gsd/commands-maintenance.ts +124 -13
  211. package/src/resources/extensions/gsd/commands-mcp-status.ts +1 -1
  212. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +2 -2
  213. package/src/resources/extensions/gsd/config-overlay.ts +1 -0
  214. package/src/resources/extensions/gsd/context-masker.ts +152 -5
  215. package/src/resources/extensions/gsd/db-writer.ts +38 -0
  216. package/src/resources/extensions/gsd/docs/preferences-reference.md +50 -1
  217. package/src/resources/extensions/gsd/gsd-db.ts +564 -186
  218. package/src/resources/extensions/gsd/guided-flow.ts +4 -1
  219. package/src/resources/extensions/gsd/markdown-renderer.ts +44 -66
  220. package/src/resources/extensions/gsd/md-importer.ts +49 -2
  221. package/src/resources/extensions/gsd/migration-auto-check.ts +154 -34
  222. package/src/resources/extensions/gsd/parsers-legacy.ts +20 -0
  223. package/src/resources/extensions/gsd/planner-handoff.ts +149 -0
  224. package/src/resources/extensions/gsd/planning-path-scope.ts +22 -4
  225. package/src/resources/extensions/gsd/pre-execution-checks.ts +9 -2
  226. package/src/resources/extensions/gsd/preferences-models.ts +113 -43
  227. package/src/resources/extensions/gsd/preferences-types.ts +47 -0
  228. package/src/resources/extensions/gsd/preferences-validation.ts +76 -2
  229. package/src/resources/extensions/gsd/preferences.ts +5 -0
  230. package/src/resources/extensions/gsd/prompts/gate-evaluate.md +1 -1
  231. package/src/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
  232. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  233. package/src/resources/extensions/gsd/prompts/refine-slice.md +1 -1
  234. package/src/resources/extensions/gsd/prompts/run-uat.md +2 -2
  235. package/src/resources/extensions/gsd/prompts/system.md +1 -1
  236. package/src/resources/extensions/gsd/roadmap-slices.ts +6 -1
  237. package/src/resources/extensions/gsd/safety/content-validator.ts +8 -5
  238. package/src/resources/extensions/gsd/skill-manifest.ts +12 -0
  239. package/src/resources/extensions/gsd/source-observations.ts +402 -0
  240. package/src/resources/extensions/gsd/state-reconciliation/drift/completion.ts +20 -8
  241. package/src/resources/extensions/gsd/state-reconciliation/drift/stale-render.ts +44 -5
  242. package/src/resources/extensions/gsd/state-reconciliation/drift/stale-worker.ts +39 -11
  243. package/src/resources/extensions/gsd/state-reconciliation/index.ts +45 -15
  244. package/src/resources/extensions/gsd/state-reconciliation/spawn-gate.ts +4 -4
  245. package/src/resources/extensions/gsd/state.ts +7 -4
  246. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +114 -0
  247. package/src/resources/extensions/gsd/tests/auto-model-selection-tool-poisoning.test.ts +66 -4
  248. package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +299 -1
  249. package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +32 -0
  250. package/src/resources/extensions/gsd/tests/auto-phases-lifecycle.test.ts +75 -3
  251. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +22 -1
  252. package/src/resources/extensions/gsd/tests/auto-supervisor.test.mjs +4 -0
  253. package/src/resources/extensions/gsd/tests/before-provider-context-management.test.ts +145 -0
  254. package/src/resources/extensions/gsd/tests/bundled-skill-triggers.test.ts +9 -0
  255. package/src/resources/extensions/gsd/tests/closeout-wizard.test.ts +44 -0
  256. package/src/resources/extensions/gsd/tests/commands-dispatcher-unmerged-milestone.test.ts +26 -1
  257. package/src/resources/extensions/gsd/tests/complete-slice-verification-gate.test.ts +118 -0
  258. package/src/resources/extensions/gsd/tests/content-validator.test.ts +74 -0
  259. package/src/resources/extensions/gsd/tests/context-masker.test.ts +56 -1
  260. package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +17 -2
  261. package/src/resources/extensions/gsd/tests/dispatch-rule-coverage.test.ts +24 -0
  262. package/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts +1 -11
  263. package/src/resources/extensions/gsd/tests/gate-dispatch.test.ts +64 -0
  264. package/src/resources/extensions/gsd/tests/gate-storage.test.ts +15 -0
  265. package/src/resources/extensions/gsd/tests/gsd-recover.test.ts +62 -1
  266. package/src/resources/extensions/gsd/tests/integration/run-uat.test.ts +4 -1
  267. package/src/resources/extensions/gsd/tests/interrupted-session-auto.test.ts +27 -0
  268. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +16 -0
  269. package/src/resources/extensions/gsd/tests/markdown-renderer.test.ts +42 -0
  270. package/src/resources/extensions/gsd/tests/mcp-project-config.test.ts +7 -1
  271. package/src/resources/extensions/gsd/tests/mcp-status.test.ts +1 -1
  272. package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +99 -0
  273. package/src/resources/extensions/gsd/tests/plan-slice.test.ts +99 -2
  274. package/src/resources/extensions/gsd/tests/plan-task.test.ts +19 -0
  275. package/src/resources/extensions/gsd/tests/planner-handoff.test.ts +100 -0
  276. package/src/resources/extensions/gsd/tests/preferences.test.ts +14 -0
  277. package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +1 -0
  278. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +133 -0
  279. package/src/resources/extensions/gsd/tests/provider-switch-observer.test.ts +55 -0
  280. package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +101 -1
  281. package/src/resources/extensions/gsd/tests/repository-registry.test.ts +2 -2
  282. package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +28 -0
  283. package/src/resources/extensions/gsd/tests/schema-v21-sequence.test.ts +5 -3
  284. package/src/resources/extensions/gsd/tests/schema-v27-v28-sequence.test.ts +162 -18
  285. package/src/resources/extensions/gsd/tests/skill-manifest.test.ts +4 -3
  286. package/src/resources/extensions/gsd/tests/skipped-validation-db-atomicity.test.ts +8 -0
  287. package/src/resources/extensions/gsd/tests/source-observations.test.ts +275 -0
  288. package/src/resources/extensions/gsd/tests/stale-queued-milestone.test.ts +43 -0
  289. package/src/resources/extensions/gsd/tests/state-reconciliation-drift.test.ts +76 -21
  290. package/src/resources/extensions/gsd/tests/thinking-level-resolution.test.ts +203 -0
  291. package/src/resources/extensions/gsd/tests/uat-policy.test.ts +170 -0
  292. package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +7 -1
  293. package/src/resources/extensions/gsd/tests/workflow-kernel.test.ts +7 -0
  294. package/src/resources/extensions/gsd/tests/workflow-manifest.test.ts +306 -1
  295. package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +77 -10
  296. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +260 -5
  297. package/src/resources/extensions/gsd/tests/worktree-db.test.ts +511 -1
  298. package/src/resources/extensions/gsd/tests/worktree-reentry.test.ts +102 -0
  299. package/src/resources/extensions/gsd/tests/worktree-state-projection.test.ts +44 -0
  300. package/src/resources/extensions/gsd/tool-contract.ts +29 -1
  301. package/src/resources/extensions/gsd/tool-presentation-plan.ts +41 -6
  302. package/src/resources/extensions/gsd/tools/complete-slice.ts +29 -0
  303. package/src/resources/extensions/gsd/tools/plan-slice.ts +54 -12
  304. package/src/resources/extensions/gsd/tools/plan-task.ts +8 -1
  305. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +71 -489
  306. package/src/resources/extensions/gsd/types.ts +1 -0
  307. package/src/resources/extensions/gsd/uat-policy.ts +191 -0
  308. package/src/resources/extensions/gsd/uat-run.ts +550 -0
  309. package/src/resources/extensions/gsd/unit-context-manifest.ts +3 -4
  310. package/src/resources/extensions/gsd/unit-tool-contracts.ts +38 -14
  311. package/src/resources/extensions/gsd/verdict-parser.ts +3 -10
  312. package/src/resources/extensions/gsd/workflow-manifest.ts +193 -7
  313. package/src/resources/extensions/gsd/workflow-mcp.ts +2 -3
  314. package/src/resources/extensions/gsd/workflow-projections.ts +9 -0
  315. package/src/resources/extensions/gsd/worktree-manager.ts +32 -0
  316. package/src/resources/extensions/gsd/worktree-reentry.ts +103 -0
  317. package/src/resources/extensions/gsd/worktree-state-projection.ts +22 -22
  318. package/src/resources/extensions/shared/gsd-browser-cli.ts +6 -0
  319. package/src/resources/extensions/shared/tests/format-utils.test.ts +8 -3
  320. package/src/resources/extensions/subagent/agents.ts +4 -0
  321. package/src/resources/extensions/subagent/index.ts +28 -3
  322. package/src/resources/extensions/subagent/launch.ts +8 -0
  323. package/src/resources/extensions/subagent/tests/model-override.test.ts +31 -0
  324. /package/dist/web/standalone/.next/static/{9y3LeeR2uGr2yRj9RjY3D → tJOKQbQRO-9MiFDO8DIDS}/_buildManifest.js +0 -0
  325. /package/dist/web/standalone/.next/static/{9y3LeeR2uGr2yRj9RjY3D → tJOKQbQRO-9MiFDO8DIDS}/_ssgManifest.js +0 -0
@@ -61,6 +61,18 @@ function isRepairableStaleRenderReason(reason: string): boolean {
61
61
  );
62
62
  }
63
63
 
64
+ function canonicalizeMilestoneId(dirSegment: string): string {
65
+ if (getMilestone(dirSegment)) return dirSegment;
66
+ const suffixId = dirSegment.match(/^(M\d+-[a-z0-9]{6})(?:$|-)/)?.[1];
67
+ if (suffixId && getMilestone(suffixId)) return suffixId;
68
+
69
+ // Descriptor layout: e.g. M001-DESCRIPTOR → M001
70
+ const baseId = dirSegment.match(/^(M\d+)(?:$|-)/i)?.[1];
71
+ if (baseId && getMilestone(baseId)) return baseId;
72
+
73
+ return suffixId ?? baseId ?? dirSegment;
74
+ }
75
+
64
76
  function resolveRoadmapMilestoneIdFromPath(normPath: string): string {
65
77
  const milestoneMatch = normPath.match(/milestones\/([^/]+)\//);
66
78
  if (!milestoneMatch) {
@@ -106,7 +118,19 @@ async function repairStaleRenderFromBasePath(
106
118
  `stale-render drift: plan path missing milestone/slice segments: ${record.renderPath}`,
107
119
  );
108
120
  }
109
- await renderPlanCheckboxes(basePath, pathMatch[1], pathMatch[2]);
121
+ const milestoneId = canonicalizeMilestoneId(pathMatch[1]);
122
+ const wrote = await renderPlanCheckboxes(
123
+ basePath,
124
+ milestoneId,
125
+ pathMatch[2],
126
+ record.renderPath,
127
+ );
128
+ if (!wrote) {
129
+ throw new Error(
130
+ `stale-render drift: plan re-render wrote nothing for ${milestoneId}/${pathMatch[2]} ` +
131
+ `(${record.renderPath}); slice has no tasks or its path is unresolvable`,
132
+ );
133
+ }
110
134
  return;
111
135
  }
112
136
 
@@ -120,7 +144,15 @@ async function repairStaleRenderFromBasePath(
120
144
  `stale-render drift: task summary path/reason malformed: ${record.renderPath} reason=${reason}`,
121
145
  );
122
146
  }
123
- await renderTaskSummary(basePath, pathMatch[1], pathMatch[2], taskMatch[1]);
147
+ const milestoneId = canonicalizeMilestoneId(pathMatch[1]);
148
+ const wrote = await renderTaskSummary(basePath, milestoneId, pathMatch[2], taskMatch[1]);
149
+ if (!wrote) {
150
+ throw new Error(
151
+ `stale-render drift: task summary re-render wrote nothing for ` +
152
+ `${milestoneId}/${pathMatch[2]}/${taskMatch[1]} (${record.renderPath}); ` +
153
+ `task has no summary in DB or its slice path is unresolvable`,
154
+ );
155
+ }
124
156
  return;
125
157
  }
126
158
 
@@ -131,7 +163,7 @@ async function repairStaleRenderFromBasePath(
131
163
  `stale-render drift: slice summary path missing milestone/slice segments: ${record.renderPath}`,
132
164
  );
133
165
  }
134
- const milestoneId = pathMatch[1];
166
+ const milestoneId = canonicalizeMilestoneId(pathMatch[1]);
135
167
  const sliceId = pathMatch[2];
136
168
  const slice = getSlice(milestoneId, sliceId);
137
169
  const uatPath = join(dirname(record.renderPath), buildSliceFileName(sliceId, "UAT"));
@@ -139,7 +171,14 @@ async function repairStaleRenderFromBasePath(
139
171
  if (slice?.full_uat_md && !existsSync(uatPath)) {
140
172
  setSliceSummaryMd(milestoneId, sliceId, slice.full_summary_md ?? "", "");
141
173
  }
142
- await renderSliceSummary(basePath, milestoneId, sliceId);
174
+ const wrote = await renderSliceSummary(basePath, milestoneId, sliceId);
175
+ if (!wrote) {
176
+ throw new Error(
177
+ `stale-render drift: slice summary re-render wrote nothing for ` +
178
+ `${milestoneId}/${sliceId} (${record.renderPath}); slice has no summary/UAT ` +
179
+ `in DB or its path is unresolvable`,
180
+ );
181
+ }
143
182
  return;
144
183
  }
145
184
 
@@ -152,7 +191,7 @@ async function repairStaleRenderFromBasePath(
152
191
  }
153
192
  // When UAT.md is removed from disk, mirror that intent by clearing stale
154
193
  // persisted UAT content instead of rehydrating it back onto disk.
155
- const milestoneId = pathMatch[1];
194
+ const milestoneId = canonicalizeMilestoneId(pathMatch[1]);
156
195
  const sliceId = pathMatch[2];
157
196
  const slice = getSlice(milestoneId, sliceId);
158
197
  if (!slice) {
@@ -10,6 +10,11 @@ import {
10
10
  readSessionLockData,
11
11
  removeStaleSessionLock,
12
12
  } from "../../session-lock.js";
13
+ import { clearStaleWorkerLock } from "../../crash-recovery.js";
14
+ import { findStaleWorkerForProject } from "../../db/auto-workers.js";
15
+ import { isDbAvailable } from "../../gsd-db.js";
16
+ import { normalizeRealPath } from "../../paths.js";
17
+ import { logWarning } from "../../workflow-logger.js";
13
18
  import type { GSDState } from "../../types.js";
14
19
  import type { DriftContext, DriftHandler, DriftRecord } from "../types.js";
15
20
 
@@ -20,23 +25,46 @@ export function detectStaleWorkerDrift(
20
25
  ctx: DriftContext,
21
26
  ): StaleWorkerDrift[] {
22
27
  const data = readSessionLockData(ctx.basePath);
23
- if (!data) return [];
24
- if (typeof data.pid !== "number") return [];
25
- if (isSessionLockProcessAlive(data)) return [];
26
-
27
- return [
28
- {
29
- kind: "stale-worker",
30
- lockPath: effectiveLockFile(),
31
- pid: data.pid,
32
- },
33
- ];
28
+ if (data && typeof data.pid === "number") {
29
+ return isSessionLockProcessAlive(data)
30
+ ? []
31
+ : [{ kind: "stale-worker", lockPath: effectiveLockFile(), pid: data.pid }];
32
+ }
33
+
34
+ // The lock file is missing or unparseable. It is not the only source of
35
+ // truth: a crashed worker can leave a workers row 'active' with held leases
36
+ // and in-flight dispatches even when its lock file is gone. Fall back to the
37
+ // DB worker registry so that state is still detected and repaired.
38
+ if (isDbAvailable()) {
39
+ try {
40
+ const stale = findStaleWorkerForProject(normalizeRealPath(ctx.basePath));
41
+ if (stale && typeof stale.pid === "number") {
42
+ return [{ kind: "stale-worker", lockPath: effectiveLockFile(), pid: stale.pid }];
43
+ }
44
+ } catch (err) {
45
+ // Best-effort: detection must never throw and abort the reconcile cycle.
46
+ logWarning(
47
+ "reconcile",
48
+ `stale-worker DB fallback detection failed: ${(err as Error).message}`,
49
+ );
50
+ }
51
+ }
52
+
53
+ return [];
34
54
  }
35
55
 
36
56
  export function repairStaleWorker(_record: StaleWorkerDrift, ctx: DriftContext): void {
37
57
  // removeStaleSessionLock is idempotent: it re-reads lock state and is a
38
58
  // no-op when the lock is held by an alive process. Safe under cap=2 retry.
39
59
  removeStaleSessionLock(ctx.basePath);
60
+
61
+ // Removing the lock file alone leaves the DB-side worker state dangling: the
62
+ // dead worker's milestone_leases stay 'held' and its unit_dispatches stay
63
+ // 'running'/'claimed', blocking new claims until the lease TTL expires.
64
+ // clearStaleWorkerLock cancels those dispatches, releases the leases, and
65
+ // marks the worker stopping — the same cleanup the startup crash-recovery
66
+ // path performs. It is DB-gated, idempotent, and best-effort.
67
+ clearStaleWorkerLock(ctx.basePath);
40
68
  }
41
69
 
42
70
  export const staleWorkerHandler: DriftHandler<StaleWorkerDrift> = {
@@ -7,6 +7,8 @@ import {
7
7
  invalidateStateCache as defaultInvalidate,
8
8
  } from "../state.js";
9
9
  import { clearParseCache as defaultClearParseCache } from "../files.js";
10
+ import { clearPathCache } from "../paths.js";
11
+ import { logWarning } from "../workflow-logger.js";
10
12
  import type { GSDState } from "../types.js";
11
13
 
12
14
  import {
@@ -68,18 +70,24 @@ export async function reconcileBeforeDispatch(
68
70
  const stateSnapshot = await deps.deriveState(basePath, deps.deriveStateOptions);
69
71
  const ctx: DriftContext = { basePath, state: stateSnapshot };
70
72
 
71
- const drift = await detectAllDrift(stateSnapshot, ctx, registry, pass);
73
+ const detection = await detectAllDrift(stateSnapshot, ctx, registry);
74
+ const drift = detection.records;
72
75
  if (drift.length === 0) {
73
76
  return {
74
77
  ok: true,
75
78
  stateSnapshot,
76
79
  repaired,
77
- blockers: stateSnapshot.blockers ?? [],
80
+ blockers: [
81
+ ...new Set([
82
+ ...(stateSnapshot.blockers ?? []),
83
+ ...detection.detectBlockers,
84
+ ]),
85
+ ],
78
86
  };
79
87
  }
80
88
 
81
89
  const failures: ReconciliationFailureDetail[] = [];
82
- const blockers: string[] = [];
90
+ const blockers: string[] = [...detection.detectBlockers];
83
91
  let repairedThisPass = false;
84
92
  for (const record of drift) {
85
93
  const handler = registry.find((h) => h.kind === record.kind);
@@ -107,7 +115,11 @@ export async function reconcileBeforeDispatch(
107
115
  }
108
116
 
109
117
  if (repairedThisPass) {
118
+ // A repair may have mutated on-disk structure (e.g. quarantined a slice
119
+ // dir). Clear both the parse cache and the path/dir cache centrally so
120
+ // later passes and any subsequent repair see fresh filesystem state.
110
121
  clearParseCache();
122
+ clearPathCache();
111
123
  }
112
124
  if (blockers.length > 0) {
113
125
  let blockerState = stateSnapshot;
@@ -132,10 +144,11 @@ export async function reconcileBeforeDispatch(
132
144
  deps.invalidateStateCache();
133
145
  const finalState = await deps.deriveState(basePath, deps.deriveStateOptions);
134
146
  const finalCtx: DriftContext = { basePath, state: finalState };
135
- const persistent = await detectAllDrift(finalState, finalCtx, registry);
147
+ const finalDetection = await detectAllDrift(finalState, finalCtx, registry);
148
+ const persistent = finalDetection.records;
136
149
 
137
150
  if (persistent.length > 0) {
138
- const blockers: string[] = [];
151
+ const blockers: string[] = [...finalDetection.detectBlockers];
139
152
  const unblockedPersistent: DriftRecord[] = [];
140
153
  for (const record of persistent) {
141
154
  const handler = registry.find((h) => h.kind === record.kind);
@@ -161,28 +174,45 @@ export async function reconcileBeforeDispatch(
161
174
  ok: true,
162
175
  stateSnapshot: finalState,
163
176
  repaired,
164
- blockers: finalState.blockers ?? [],
177
+ blockers: [
178
+ ...new Set([
179
+ ...(finalState.blockers ?? []),
180
+ ...finalDetection.detectBlockers,
181
+ ]),
182
+ ],
165
183
  };
166
184
  }
167
185
 
186
+ interface DetectionOutcome {
187
+ records: DriftRecord[];
188
+ /** One blocker string per handler whose detect() threw. */
189
+ detectBlockers: string[];
190
+ }
191
+
192
+ /**
193
+ * Run every detector. A single detector throwing (e.g. a transient file read
194
+ * error) must NOT abort the whole cycle and hide every later handler's drift —
195
+ * it is collected as a blocker so dispatch is still gated, while the remaining
196
+ * detectors run and their drift gets repaired (graceful degradation, ADR-017).
197
+ */
168
198
  async function detectAllDrift(
169
199
  state: GSDState,
170
200
  ctx: DriftContext,
171
201
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
172
202
  registry: ReadonlyArray<DriftHandler<any>>,
173
- pass?: number,
174
- ): Promise<DriftRecord[]> {
175
- const collected: DriftRecord[] = [];
203
+ ): Promise<DetectionOutcome> {
204
+ const records: DriftRecord[] = [];
205
+ const detectBlockers: string[] = [];
176
206
  for (const handler of registry) {
177
207
  try {
178
208
  const detected = await handler.detect(state, ctx);
179
- collected.push(...detected);
209
+ records.push(...detected);
180
210
  } catch (cause) {
181
- throw new ReconciliationFailedError({
182
- failures: [{ drift: { kind: handler.kind } as DriftRecord, cause }],
183
- pass,
184
- });
211
+ const message = cause instanceof Error ? cause.message : String(cause);
212
+ const blocker = `Drift detection failed for "${handler.kind}": ${message}`;
213
+ logWarning("reconcile", blocker);
214
+ detectBlockers.push(blocker);
185
215
  }
186
216
  }
187
- return collected;
217
+ return { records, detectBlockers };
188
218
  }
@@ -21,10 +21,10 @@ export interface SpawnGateDeps extends Partial<ReconciliationDeps> {
21
21
  }
22
22
 
23
23
  /**
24
- * Run reconciliation before spawning workers. Returns ok=true when the run
25
- * completed without throwing (blockers ride along but don't fail the gate —
26
- * spawn callers can choose how to handle them). On
27
- * ReconciliationFailedError, returns ok=false with the error message so the
24
+ * Run reconciliation before spawning workers. Returns ok=true only when the run
25
+ * completed without throwing AND surfaced no blockers; any blocker fails the
26
+ * gate (ok=false, reason carries the first blocker) so callers must not spawn.
27
+ * On ReconciliationFailedError, returns ok=false with the error message so the
28
28
  * caller can surface it to the user without re-throwing.
29
29
  *
30
30
  * Other unexpected errors propagate; they are not part of the drift
@@ -483,7 +483,7 @@ async function buildRegistryAndFindActive(
483
483
  let activeMilestoneSlices: SliceRow[] = [];
484
484
  let activeMilestoneFound = false;
485
485
  let activeMilestoneHasDraft = false;
486
- let firstDeferredQueuedShell: { id: string; title: string; deps: string[] } | null = null;
486
+ let firstDeferredQueuedShell: { id: string; title: string; deps: string[]; hasDraftContext: boolean } | null = null;
487
487
 
488
488
  for (const m of milestones) {
489
489
  if (parkedMilestoneIds.has(m.id)) {
@@ -505,6 +505,8 @@ async function buildRegistryAndFindActive(
505
505
  const allSlicesDone = slices.length > 0 && slices.every(s => isStatusDone(s.status));
506
506
 
507
507
  const title = stripMilestonePrefix(m.title) || m.id;
508
+ const hasContext = !!resolveMilestoneFile(basePath, m.id, "CONTEXT");
509
+ const hasDraftContext = !hasContext && !!resolveMilestoneFile(basePath, m.id, "CONTEXT-DRAFT");
508
510
 
509
511
  if (!activeMilestoneFound) {
510
512
  const deps = m.depends_on;
@@ -515,9 +517,9 @@ async function buildRegistryAndFindActive(
515
517
  continue;
516
518
  }
517
519
 
518
- if (m.status === 'queued' && slices.length === 0) {
520
+ if (m.status === 'queued' && slices.length === 0 && !hasContext) {
519
521
  if (!firstDeferredQueuedShell) {
520
- firstDeferredQueuedShell = { id: m.id, title, deps };
522
+ firstDeferredQueuedShell = { id: m.id, title, deps, hasDraftContext };
521
523
  }
522
524
  registry.push({ id: m.id, title, status: 'pending', ...(deps.length > 0 ? { dependsOn: deps } : {}) });
523
525
  continue;
@@ -531,7 +533,7 @@ async function buildRegistryAndFindActive(
531
533
  continue;
532
534
  }
533
535
 
534
- if (m.status === 'needs-discussion') activeMilestoneHasDraft = true;
536
+ if ((m.status === 'needs-discussion' && !hasContext) || hasDraftContext) activeMilestoneHasDraft = true;
535
537
 
536
538
  activeMilestone = { id: m.id, title };
537
539
  activeMilestoneSlices = slices;
@@ -548,6 +550,7 @@ async function buildRegistryAndFindActive(
548
550
  activeMilestone = { id: shell.id, title: shell.title };
549
551
  activeMilestoneSlices = [];
550
552
  activeMilestoneFound = true;
553
+ if (shell.hasDraftContext) activeMilestoneHasDraft = true;
551
554
  const entry = registry.find(e => e.id === shell.id);
552
555
  if (entry) entry.status = 'active';
553
556
  }
@@ -36,6 +36,7 @@ import { registerAutoWorker } from "../db/auto-workers.js";
36
36
  import { claimMilestoneLease } from "../db/milestone-leases.js";
37
37
  import { recordDispatchClaim, markCanceled } from "../db/unit-dispatches.js";
38
38
  import { setRuntimeKv, getRuntimeKv } from "../db/runtime-kv.js";
39
+ import { SourceObservationStore } from "../source-observations.js";
39
40
 
40
41
  // ─── Helpers ─────────────────────────────────────────────────────────────────
41
42
 
@@ -1102,6 +1103,7 @@ function makeLoopSession(overrides?: Partial<Record<string, unknown>>) {
1102
1103
  currentMilestoneId: "M001",
1103
1104
  currentUnit: null,
1104
1105
  currentUnitRouting: null,
1106
+ sourceObservations: new SourceObservationStore(),
1105
1107
  completedUnits: [],
1106
1108
  resourceVersionOnStart: null,
1107
1109
  lastPromptCharCount: undefined,
@@ -1118,6 +1120,7 @@ function makeLoopSession(overrides?: Partial<Record<string, unknown>>) {
1118
1120
  unitLifetimeDispatches: new Map<string, number>(),
1119
1121
  unitRecoveryCount: new Map<string, number>(),
1120
1122
  verificationRetryCount: new Map<string, number>(),
1123
+ zeroToolRetryCount: new Map<string, number>(),
1121
1124
  gitService: null,
1122
1125
  lastRequestTimestamp: 0,
1123
1126
  autoStartTime: Date.now(),
@@ -1125,6 +1128,19 @@ function makeLoopSession(overrides?: Partial<Record<string, unknown>>) {
1125
1128
  newSession: () => Promise.resolve({ cancelled: false }),
1126
1129
  getContextUsage: () => ({ percent: 10, tokens: 1000, limit: 10000 }),
1127
1130
  },
1131
+ setCurrentUnit(this: any, unit: any) {
1132
+ this.currentUnit = unit;
1133
+ this.sourceObservations.beginUnit({
1134
+ unitType: unit.type,
1135
+ unitId: unit.id,
1136
+ startedAt: unit.startedAt,
1137
+ basePath: unit.workspaceRoot ?? this.basePath,
1138
+ });
1139
+ },
1140
+ clearCurrentUnit(this: any) {
1141
+ this.currentUnit = null;
1142
+ this.sourceObservations.clear();
1143
+ },
1128
1144
  clearTimers: () => {},
1129
1145
  ...overrides,
1130
1146
  } as any;
@@ -4693,6 +4709,104 @@ test("runUnitPhase retries 0-tool units with ordinary network-related assistant
4693
4709
  assert.equal(deps.callLog.includes("pauseAuto"), false);
4694
4710
  });
4695
4711
 
4712
+ test("runUnitPhase pauses auto-mode when zero-tool-call retry is exhausted", async (t) => {
4713
+ _resetPendingResolve();
4714
+
4715
+ const basePath = mkdtempSync(join(tmpdir(), "gsd-zero-tool-exhausted-"));
4716
+ t.after(() => {
4717
+ rmSync(basePath, { recursive: true, force: true });
4718
+ });
4719
+
4720
+ const ctx = {
4721
+ ...makeMockCtx(),
4722
+ ui: {
4723
+ notify: () => {},
4724
+ setStatus: () => {},
4725
+ setWorkingMessage: () => {},
4726
+ },
4727
+ sessionManager: {
4728
+ getEntries: () => [],
4729
+ },
4730
+ modelRegistry: {
4731
+ getProviderAuthMode: () => undefined,
4732
+ isProviderRequestReady: () => true,
4733
+ },
4734
+ } as any;
4735
+ const pi = {
4736
+ ...makeMockPi(),
4737
+ sendMessage: () => {
4738
+ queueMicrotask(() => resolveAgentEnd(makeEvent([
4739
+ {
4740
+ role: "assistant",
4741
+ content: [
4742
+ { type: "text", text: "Error: I'll investigate the network error handling next." },
4743
+ ],
4744
+ },
4745
+ ])));
4746
+ },
4747
+ } as any;
4748
+ const s = makeLoopSession({
4749
+ basePath,
4750
+ canonicalProjectRoot: basePath,
4751
+ originalBasePath: basePath,
4752
+ });
4753
+ // Pre-seed counter at MAX_ZERO_TOOL_RETRIES so the next zero-tool turn exhausts the cap
4754
+ s.zeroToolRetryCount.set("execute-task/M001/S01/T01", 1);
4755
+
4756
+ const mockLedger = {
4757
+ version: 1,
4758
+ projectStartedAt: Date.now(),
4759
+ units: [] as any[],
4760
+ };
4761
+ const deps = makeMockDeps({
4762
+ closeoutUnit: async () => {
4763
+ mockLedger.units.push({
4764
+ type: "execute-task",
4765
+ id: "M001/S01/T01",
4766
+ startedAt: s.currentUnit?.startedAt ?? Date.now(),
4767
+ toolCalls: 0,
4768
+ assistantMessages: 1,
4769
+ tokens: { input: 100, output: 20, total: 120, cacheRead: 0, cacheWrite: 0 },
4770
+ cost: 0.01,
4771
+ });
4772
+ },
4773
+ getLedger: () => mockLedger,
4774
+ });
4775
+ let seq = 0;
4776
+
4777
+ const result = await runUnitPhase(
4778
+ { ctx, pi, s, deps, prefs: undefined, iteration: 1, flowId: "flow-zero-tool-exhausted", nextSeq: () => ++seq },
4779
+ {
4780
+ unitType: "execute-task",
4781
+ unitId: "M001/S01/T01",
4782
+ prompt: "do work",
4783
+ finalPrompt: "do work",
4784
+ pauseAfterUatDispatch: false,
4785
+ state: {
4786
+ phase: "executing",
4787
+ activeMilestone: { id: "M001", title: "Milestone" },
4788
+ activeSlice: { id: "S01", title: "Slice" },
4789
+ activeTask: { id: "T01", title: "Task" },
4790
+ registry: [{ id: "M001", title: "Milestone", status: "active" }],
4791
+ recentDecisions: [],
4792
+ blockers: [],
4793
+ nextAction: "",
4794
+ progress: { milestones: { done: 0, total: 1 } },
4795
+ requirements: { active: 0, validated: 0, deferred: 0, outOfScope: 0, blocked: 0, total: 0 },
4796
+ } as any,
4797
+ mid: "M001",
4798
+ midTitle: "Milestone",
4799
+ isRetry: false,
4800
+ previousTier: undefined,
4801
+ },
4802
+ { recentUnits: [{ key: "execute-task/M001/S01/T01" }], stuckRecoveryAttempts: 0, consecutiveFinalizeTimeouts: 0 },
4803
+ );
4804
+
4805
+ assert.equal(result.action, "break");
4806
+ assert.equal((result as any).reason, "zero-tool-calls-exhausted");
4807
+ assert.equal(deps.callLog.includes("pauseAuto"), true);
4808
+ });
4809
+
4696
4810
  test("autoLoop pauses user-driven deep question instead of flagging 0 tool calls", async () => {
4697
4811
  _resetPendingResolve();
4698
4812
 
@@ -33,6 +33,7 @@ import {
33
33
  selectAndApplyModel,
34
34
  ModelPolicyDispatchBlockedError,
35
35
  clearToolBaseline,
36
+ getToolBaselineSnapshot,
36
37
  } from "../auto-model-selection.js";
37
38
  import { applyModelPolicyFilter } from "../uok/model-policy.js";
38
39
  import {
@@ -139,7 +140,7 @@ function makeCtx(
139
140
  test("vacuous-truth (a): unit type with empty workflow-required tools → dispatch succeeds", async () => {
140
141
  const env = makeTempProject();
141
142
  try {
142
- // `refine-slice` is not in the getRequiredWorkflowToolsForAutoUnit switch
143
+ // `rewrite-docs` has no required workflow tools
143
144
  // → returns []. Exercises the empty-requiredTools branch in
144
145
  // applyModelPolicyFilter (existing test used
145
146
  // gate-evaluate which has non-empty required tools and never hit this path).
@@ -161,7 +162,7 @@ test("vacuous-truth (a): unit type with empty workflow-required tools → dispat
161
162
  const result = await selectAndApplyModel(
162
163
  makeCtx(availableModels),
163
164
  pi as any,
164
- "refine-slice",
165
+ "rewrite-docs",
165
166
  "x1",
166
167
  env.dir,
167
168
  undefined,
@@ -308,8 +309,8 @@ test("genuinely-impossible (a): pi-native required tool incompatible with candid
308
309
  test("genuinely-impossible (b): cross-provider routing disabled + provider mismatch → typed error", async () => {
309
310
  const env = makeTempProject();
310
311
  try {
311
- // Use plan-slice (workflow-required: ["gsd_plan_slice"]) but pretend no
312
- // candidate model can carry it. The simplest way: provide a model whose
312
+ // Use plan-slice but pretend no candidate model can carry its required
313
+ // workflow tools. The simplest way: provide a model whose
313
314
  // api is a fictitious "no-tools" string — `filterToolsForProvider` returns
314
315
  // every tool as filtered for an unknown api with toolCalling=false, OR we
315
316
  // can pick a real api that also denies the tool. We use an api that
@@ -711,3 +712,64 @@ test("cross-mode (#4965): auto → guided → auto preserves the original auto-e
711
712
  env.cleanup();
712
713
  }
713
714
  });
715
+
716
+ // ─── 8. Baseline union: MCP tools connected after baseline capture (#477) ─────
717
+ //
718
+ // `getToolBaselineSnapshot` must return the UNION of the frozen WeakMap baseline
719
+ // and the current live tool set. This ensures:
720
+ // (a) Provider-narrowed tools (in baseline, dropped from live) are still seen
721
+ // by transport preflight — the bug-5 fix from #477.
722
+ // (b) Tools connected after the baseline was captured (e.g. MCP server attached
723
+ // mid-session) are also visible — so a paused run that resumes after MCP
724
+ // reconnects clears the transport warning on the first iteration instead of
725
+ // repeating it indefinitely.
726
+
727
+ test("baseline union (#477): getToolBaselineSnapshot includes live tools not present in frozen baseline", async () => {
728
+ const env = makeTempProject();
729
+ try {
730
+ const availableModels = [
731
+ { id: "claude-sonnet-4-6", provider: "anthropic", api: "anthropic-messages" },
732
+ ];
733
+
734
+ const initialTools = ["bash", "read", "write"];
735
+ const pi = makeRecordingPi(initialTools);
736
+ clearToolBaseline(pi as unknown as object);
737
+
738
+ // Capture baseline with only native tools (no MCP connected yet).
739
+ await selectAndApplyModel(
740
+ makeCtx(availableModels),
741
+ pi as any,
742
+ "execute-task",
743
+ "u1",
744
+ env.dir,
745
+ undefined,
746
+ false,
747
+ { provider: "anthropic", id: "claude-sonnet-4-6" },
748
+ undefined,
749
+ /* isAutoMode */ true,
750
+ );
751
+
752
+ // Simulate: provider narrows tools (Groq cap, hook override, etc.).
753
+ // The baseline in the WeakMap still has the full initial set.
754
+ pi.setActiveTools(["bash"]);
755
+
756
+ // Simulate: user connects MCP mid-session (after the baseline was captured).
757
+ const liveTools = pi.getActiveTools().concat(["mcp__gsd-workflow__gsd_uat_exec"]);
758
+ pi.setActiveTools(liveTools);
759
+
760
+ const snapshot = getToolBaselineSnapshot(pi as any);
761
+
762
+ // All baseline tools must be present (even the provider-narrowed ones).
763
+ for (const t of initialTools) {
764
+ assert.ok(snapshot.includes(t), `snapshot must include baseline tool: ${t}`);
765
+ }
766
+ // Newly connected MCP tool must also be present.
767
+ assert.ok(
768
+ snapshot.includes("mcp__gsd-workflow__gsd_uat_exec"),
769
+ "snapshot must include MCP tool connected after baseline capture",
770
+ );
771
+ } finally {
772
+ env.restoreEnv();
773
+ env.cleanup();
774
+ }
775
+ });