@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
@@ -0,0 +1,149 @@
1
+ // Project/App: gsd-pi
2
+ // File Purpose: Optional gsd-planner handoff after milestone planning.
3
+
4
+ import { spawn as spawnChild, type ChildProcess, type SpawnOptions } from "node:child_process";
5
+ import { existsSync, mkdirSync, writeFileSync } from "node:fs";
6
+ import { join } from "node:path";
7
+
8
+ import { gsdRoot } from "./paths.js";
9
+
10
+ export const PLANNER_HANDOFF_RULE_NAME = "planning review handoff -> gsd-planner";
11
+ export const GSD_PLANNER_COMMAND = "gsd-planner";
12
+
13
+ export interface GsdPlannerSpawnPlan {
14
+ command: string;
15
+ args: string[];
16
+ cwd: string;
17
+ }
18
+
19
+ export interface GsdPlannerLaunchInput {
20
+ basePath: string;
21
+ milestoneId?: string | null;
22
+ extraArgs?: string[];
23
+ }
24
+
25
+ export type GsdPlannerLaunchResult =
26
+ | { status: "launched"; plan: GsdPlannerSpawnPlan }
27
+ | { status: "failed"; plan: GsdPlannerSpawnPlan; error: Error };
28
+
29
+ type SpawnLike = (
30
+ command: string,
31
+ args: readonly string[],
32
+ options: SpawnOptions,
33
+ ) => ChildProcess;
34
+
35
+ export interface GsdPlannerLaunchDeps {
36
+ spawn?: SpawnLike;
37
+ }
38
+
39
+ function handoffDir(basePath: string): string {
40
+ return join(gsdRoot(basePath), "runtime", "planner-handoffs");
41
+ }
42
+
43
+ function safeMilestoneFileSegment(milestoneId: string): string {
44
+ return milestoneId.replace(/[^A-Za-z0-9._-]/g, "_") || "unknown";
45
+ }
46
+
47
+ function handoffMarkerPath(basePath: string, milestoneId: string): string {
48
+ return join(handoffDir(basePath), `${safeMilestoneFileSegment(milestoneId)}.json`);
49
+ }
50
+
51
+ export function hasPlannerHandoffBeenOffered(basePath: string, milestoneId: string): boolean {
52
+ return existsSync(handoffMarkerPath(basePath, milestoneId));
53
+ }
54
+
55
+ export function markPlannerHandoffOffered(
56
+ basePath: string,
57
+ milestoneId: string,
58
+ source: "auto" | "command" = "auto",
59
+ ): void {
60
+ mkdirSync(handoffDir(basePath), { recursive: true });
61
+ writeFileSync(
62
+ handoffMarkerPath(basePath, milestoneId),
63
+ JSON.stringify({
64
+ milestoneId,
65
+ source,
66
+ offeredAt: new Date().toISOString(),
67
+ }, null, 2) + "\n",
68
+ "utf-8",
69
+ );
70
+ }
71
+
72
+ export function buildGsdPlannerSpawnPlan(input: GsdPlannerLaunchInput): GsdPlannerSpawnPlan {
73
+ const args = ["--project", input.basePath];
74
+ const milestoneId = input.milestoneId?.trim();
75
+ if (milestoneId) args.push("--milestone", milestoneId);
76
+ args.push(...(input.extraArgs ?? []));
77
+ return {
78
+ command: GSD_PLANNER_COMMAND,
79
+ args,
80
+ cwd: input.basePath,
81
+ };
82
+ }
83
+
84
+ function quoteArg(arg: string): string {
85
+ return /^[A-Za-z0-9_./:=@+-]+$/.test(arg) ? arg : JSON.stringify(arg);
86
+ }
87
+
88
+ export function formatGsdPlannerCommand(plan: GsdPlannerSpawnPlan): string {
89
+ return [plan.command, ...plan.args].map(quoteArg).join(" ");
90
+ }
91
+
92
+ export async function launchGsdPlanner(
93
+ input: GsdPlannerLaunchInput,
94
+ deps: GsdPlannerLaunchDeps = {},
95
+ ): Promise<GsdPlannerLaunchResult> {
96
+ const plan = buildGsdPlannerSpawnPlan(input);
97
+ const spawn = deps.spawn ?? spawnChild;
98
+
99
+ let child: ChildProcess;
100
+ try {
101
+ child = spawn(plan.command, plan.args, {
102
+ cwd: plan.cwd,
103
+ detached: true,
104
+ stdio: "ignore",
105
+ windowsHide: true,
106
+ });
107
+ } catch (err) {
108
+ return {
109
+ status: "failed",
110
+ plan,
111
+ error: err instanceof Error ? err : new Error(String(err)),
112
+ };
113
+ }
114
+
115
+ return new Promise((resolve) => {
116
+ let settled = false;
117
+ const settle = (result: GsdPlannerLaunchResult) => {
118
+ if (settled) return;
119
+ settled = true;
120
+ resolve(result);
121
+ };
122
+
123
+ child.once("error", (err) => {
124
+ settle({
125
+ status: "failed",
126
+ plan,
127
+ error: err instanceof Error ? err : new Error(String(err)),
128
+ });
129
+ });
130
+ child.once("spawn", () => {
131
+ child.unref();
132
+ settle({ status: "launched", plan });
133
+ });
134
+ });
135
+ }
136
+
137
+ export function formatPlannerHandoffPauseReason(milestoneId: string): string {
138
+ return [
139
+ `Milestone ${milestoneId} is planned. Review or customize the plan before implementation if needed.`,
140
+ `Run /gsd planner to launch ${GSD_PLANNER_COMMAND}, or run /gsd auto to continue without planner changes.`,
141
+ ].join(" ");
142
+ }
143
+
144
+ export function formatPlannerLaunchUnavailable(plan: GsdPlannerSpawnPlan, error: Error): string {
145
+ return [
146
+ `Could not launch ${GSD_PLANNER_COMMAND}: ${error.message}`,
147
+ `Install ${GSD_PLANNER_COMMAND} or run it manually: ${formatGsdPlannerCommand(plan)}`,
148
+ ].join("\n");
149
+ }
@@ -18,6 +18,13 @@ function isInsideAnyBase(bases: string[], candidate: string): boolean {
18
18
  return bases.some((base) => isInsideBase(base, candidate));
19
19
  }
20
20
 
21
+ function resolvesInsideRoot(root: string, candidate: string): boolean {
22
+ const resolvedCandidate = isAbsolute(candidate)
23
+ ? resolve(candidate)
24
+ : resolve(root, candidate);
25
+ return isInsideBase(root, resolvedCandidate);
26
+ }
27
+
21
28
  /**
22
29
  * Planning IO fields are execution contracts. Absolute paths are only safe when
23
30
  * they stay inside the active working directory; in worktree mode, an absolute
@@ -37,13 +44,24 @@ export function validatePlanningPathScope(
37
44
  if (!shouldValidatePlanningPathReference(trimmed)) continue;
38
45
 
39
46
  const candidate = normalizePlannedFileReference(raw);
40
- const resolvedCandidate = isAbsolute(candidate)
41
- ? resolve(candidate)
42
- : resolve(basePath, candidate);
43
- if (isInsideAnyBase(absoluteRoots, resolvedCandidate)) continue;
47
+ if (isAbsolute(candidate)) {
48
+ if (isInsideAnyBase(absoluteRoots, resolve(candidate))) continue;
49
+ } else if (absoluteRoots.some((root) => resolvesInsideRoot(root, candidate))) {
50
+ continue;
51
+ }
44
52
  return `${field} contains path outside allowed repository roots: ${candidate}. Use a path within one of: ${absoluteRoots.join(", ")}.`;
45
53
  }
46
54
  }
47
55
 
48
56
  return null;
49
57
  }
58
+
59
+ export function validatePathOnlyPlanningFields(fields: PlanningPathScopeField[]): string | null {
60
+ for (const { field, values } of fields) {
61
+ for (const raw of values) {
62
+ if (shouldValidatePlanningPathReference(raw)) continue;
63
+ return `${field} must contain only file paths; invalid entry: ${raw}`;
64
+ }
65
+ }
66
+ return null;
67
+ }
@@ -469,6 +469,13 @@ export function shouldValidatePlanningPathReference(raw: string): boolean {
469
469
  return shouldValidateInputAsPath(raw);
470
470
  }
471
471
 
472
+ export function extractPlanningPathReference(raw: string): string | null {
473
+ const trimmed = raw.trim();
474
+ if (!trimmed || NON_PATH_SENTINEL_RE.test(trimmed)) return null;
475
+ if (!shouldValidateInputAsPath(trimmed)) return null;
476
+ return extractPathFromAnnotation(trimmed);
477
+ }
478
+
472
479
  function shouldValidateInputAsPath(raw: string): boolean {
473
480
  const trimmed = raw.trim();
474
481
  if (!trimmed) return false;
@@ -483,7 +490,8 @@ function shouldValidateInputAsPath(raw: string): boolean {
483
490
  if (URL_SCHEME_PATTERN.test(candidate)) return false;
484
491
  if (SCP_PATTERN.test(candidate)) return false;
485
492
 
486
- if (/^`+[^`]+`+/.test(trimmed)) {
493
+ const explicitlyWrappedPath = /^`+[^`]+`+/.test(trimmed) || /^(["'])([^"']+)\1$/.test(trimmed);
494
+ if (explicitlyWrappedPath && looksLikePathOrUrl(candidate)) {
487
495
  return true;
488
496
  }
489
497
 
@@ -496,7 +504,6 @@ function shouldValidateInputAsPath(raw: string): boolean {
496
504
  candidate.startsWith("./") ||
497
505
  candidate.startsWith("../") ||
498
506
  candidate.startsWith("~/") ||
499
- /[\\/]/.test(candidate) ||
500
507
  /[*?[\]{}]/.test(candidate)
501
508
  );
502
509
  }
@@ -19,6 +19,9 @@ import type {
19
19
  GSDPreferences,
20
20
  GSDModelConfigV2,
21
21
  GSDPhaseModelConfig,
22
+ GSDThinkingLevel,
23
+ GSDModelPhaseKey,
24
+ GSDThinkingConfig,
22
25
  ResolvedModelConfig,
23
26
  AutoSupervisorConfig,
24
27
  } from "./preferences-types.js";
@@ -37,82 +40,103 @@ export function resolveModelForUnit(unitType: string): string | undefined {
37
40
  }
38
41
 
39
42
  /**
40
- * Resolve model and fallbacks for a given auto-mode unit type.
41
- * Returns the primary model and ordered fallbacks, or undefined if not configured.
43
+ * Ordered phase-bucket chain a unit type resolves against, most-specific
44
+ * first. The first chain entry with a configured value wins; later entries
45
+ * are siblings the unit falls back to (e.g. `discuss → planning`).
42
46
  *
43
- * Supports both legacy string format and extended object format:
44
- * - Legacy: `planning: claude-opus-4-6`
45
- * - Extended: `planning: { model: claude-opus-4-6, fallbacks: [glm-5, minimax-m2.5] }`
47
+ * Single source of truth for the unit-type phase mapping, shared by model
48
+ * resolution (`resolveModelWithFallbacksForUnit`) and thinking resolution
49
+ * (`resolveThinkingLevelForUnit`) so the two never drift (ADR-026).
46
50
  */
47
- export function resolveModelWithFallbacksForUnit(unitType: string): ResolvedModelConfig | undefined {
48
- const prefs = loadEffectiveGSDPreferences(undefined, { availableModelIds: [] });
49
- const models = prefs?.preferences?.models;
50
- if (!models) return undefined;
51
- const m = models as GSDModelConfigV2;
52
-
53
- let phaseConfig: string | GSDPhaseModelConfig | undefined;
51
+ export function phaseChainForUnit(unitType: string): GSDModelPhaseKey[] | undefined {
54
52
  switch (unitType) {
55
53
  case "research-milestone":
56
54
  case "research-slice":
57
- phaseConfig = m.research;
58
- break;
55
+ // Deep-mode project research orchestrator. Reads PROJECT.md / REQUIREMENTS.md
56
+ // and fans out research subagents. Routes to the research bucket.
57
+ case "research-project":
58
+ return ["research"];
59
59
  case "plan-milestone":
60
60
  case "plan-slice":
61
61
  case "refine-slice":
62
62
  case "replan-slice":
63
- phaseConfig = m.planning;
64
- break;
63
+ return ["planning"];
64
+ // Deep-mode project-level discussion units route to the same model bucket
65
+ // as milestone-level discussion (interactive interview style). Workflow
66
+ // preferences and research-decision are tiny ask_user_questions style units
67
+ // that share the discuss bucket because they are conversational. All fall
68
+ // back to planning when no `discuss` bucket is set.
65
69
  case "discuss-milestone":
66
70
  case "discuss-slice":
67
- // Deep-mode project-level discussion units route to the same model
68
- // bucket as milestone-level discussion (interactive interview style).
69
71
  case "discuss-project":
70
72
  case "discuss-requirements":
71
- // Workflow preferences and research-decision are tiny ask_user_questions
72
- // style units; they share the discuss bucket because they are
73
- // conversational rather than research/execution. Falling back to planning
74
- // when no `discuss` bucket is set keeps parity with the milestone units.
75
73
  case "workflow-preferences":
76
74
  case "research-decision":
77
- phaseConfig = m.discuss ?? m.planning;
78
- break;
79
- // Deep-mode project research orchestrator. Reads PROJECT.md / REQUIREMENTS.md
80
- // and fans out research subagents. Routes to the research bucket so it
81
- // gets the research-tier model when one is configured.
82
- case "research-project":
83
- phaseConfig = m.research;
84
- break;
75
+ return ["discuss", "planning"];
85
76
  case "execute-task":
86
77
  case "reactive-execute":
87
- phaseConfig = m.execution;
88
- break;
78
+ return ["execution"];
89
79
  case "execute-task-simple":
90
- phaseConfig = m.execution_simple ?? m.execution;
91
- break;
80
+ return ["execution_simple", "execution"];
92
81
  case "complete-slice":
93
82
  case "complete-milestone":
94
83
  case "worktree-merge":
95
- phaseConfig = m.completion;
96
- break;
84
+ return ["completion"];
97
85
  case "run-uat":
98
- phaseConfig = m.uat ?? m.completion;
99
- break;
86
+ return ["uat", "completion"];
100
87
  case "reassess-roadmap":
101
88
  case "rewrite-docs":
102
89
  case "gate-evaluate":
103
90
  case "validate-milestone":
104
- phaseConfig = m.validation ?? m.planning;
105
- break;
91
+ return ["validation", "planning"];
106
92
  default:
107
93
  // Subagent unit types (e.g., "subagent", "subagent/scout")
108
94
  if (unitType === "subagent" || unitType.startsWith("subagent/")) {
109
- phaseConfig = m.subagent;
110
- break;
95
+ return ["subagent"];
111
96
  }
112
97
  return undefined;
113
98
  }
99
+ }
114
100
 
115
- if (!phaseConfig) return undefined;
101
+ /**
102
+ * Find the phase bucket whose `models` entry wins the chain for a unit, plus
103
+ * that entry. Returns undefined when no phase in the chain is configured.
104
+ */
105
+ function resolveWinningPhase(
106
+ models: GSDModelConfigV2 | undefined,
107
+ chain: GSDModelPhaseKey[],
108
+ ): { phase: GSDModelPhaseKey; config: string | GSDPhaseModelConfig } | undefined {
109
+ if (!models) return undefined;
110
+ for (const key of chain) {
111
+ const config = models[key];
112
+ // Falsy check (not `!= null`) so an empty-string model is treated as
113
+ // unconfigured and the chain falls through — matches the pre-refactor
114
+ // switch, which bailed via `if (!phaseConfig)`.
115
+ if (!config) continue;
116
+ // An object entry only "wins" if it provides a usable model. A model-less
117
+ // object (e.g. `{ provider: x }`, or `{}` left after stripping an invalid
118
+ // `thinking`) must not shadow sibling fallback or yield `{ primary: undefined }`.
119
+ if (typeof config === "object" && !config.model) continue;
120
+ return { phase: key, config };
121
+ }
122
+ return undefined;
123
+ }
124
+
125
+ /**
126
+ * Resolve model and fallbacks for a given auto-mode unit type.
127
+ * Returns the primary model and ordered fallbacks, or undefined if not configured.
128
+ *
129
+ * Supports both legacy string format and extended object format:
130
+ * - Legacy: `planning: claude-opus-4-6`
131
+ * - Extended: `planning: { model: claude-opus-4-6, fallbacks: [glm-5, minimax-m2.5] }`
132
+ */
133
+ export function resolveModelWithFallbacksForUnit(unitType: string): ResolvedModelConfig | undefined {
134
+ const prefs = loadEffectiveGSDPreferences(undefined, { availableModelIds: [] });
135
+ const chain = phaseChainForUnit(unitType);
136
+ if (!chain) return undefined;
137
+ const winner = resolveWinningPhase(prefs?.preferences?.models as GSDModelConfigV2 | undefined, chain);
138
+ if (!winner) return undefined;
139
+ const phaseConfig = winner.config;
116
140
 
117
141
  // Normalize: string -> { model, fallbacks: [] }
118
142
  if (typeof phaseConfig === "string") {
@@ -131,6 +155,51 @@ export function resolveModelWithFallbacksForUnit(unitType: string): ResolvedMode
131
155
  };
132
156
  }
133
157
 
158
+ /**
159
+ * Resolve the explicitly configured reasoning effort for a unit type (ADR-026).
160
+ *
161
+ * Thinking travels with the model. The chain is walked most-specific-first up to
162
+ * and including the phase whose model won; at each level inline
163
+ * `models.<phase>.thinking` is preferred, then the same phase's `thinking` block
164
+ * entry. This means:
165
+ * - a more-specific block key (`thinking.execution_simple`) surfaces even when
166
+ * the model only resolves on a less-specific sibling (`models.execution`);
167
+ * - inline thinking is honored even on a model-less `models.<phase>` entry
168
+ * (e.g. `{ thinking: "high" }` with no `model`);
169
+ * - a unit that claimed its own model bucket never borrows a *less*-specific
170
+ * sibling's thinking (the walk stops at the winning phase).
171
+ * When no model is configured anywhere in the chain, the walk spans the full
172
+ * chain so inline thinking and the `thinking` block both resolve on their own
173
+ * sibling chain.
174
+ *
175
+ * Returns undefined when nothing explicit is configured — the dispatch path
176
+ * then falls back to the session/default level and applies the code-writing
177
+ * floor. Session level, defaults, the floor, and capability clamping are NOT
178
+ * applied here.
179
+ */
180
+ export function resolveThinkingLevelForUnit(unitType: string): GSDThinkingLevel | undefined {
181
+ const prefs = loadEffectiveGSDPreferences(undefined, { availableModelIds: [] })?.preferences;
182
+ if (!prefs) return undefined;
183
+ const chain = phaseChainForUnit(unitType);
184
+ if (!chain) return undefined;
185
+
186
+ const models = prefs.models as GSDModelConfigV2 | undefined;
187
+ const block = prefs.thinking as GSDThinkingConfig | undefined;
188
+
189
+ // Walk most-specific-first, up to and including the winning model phase (or
190
+ // the full chain when no model is configured), checking inline then block.
191
+ const winner = resolveWinningPhase(models, chain);
192
+ const limit = winner ? chain.indexOf(winner.phase) + 1 : chain.length;
193
+ for (let i = 0; i < limit; i++) {
194
+ const key = chain[i];
195
+ const entry = models?.[key];
196
+ if (typeof entry === "object" && entry?.thinking) return entry.thinking; // inline (incl. model-less)
197
+ const blockLevel = block?.[key];
198
+ if (blockLevel) return blockLevel; // block
199
+ }
200
+ return undefined;
201
+ }
202
+
134
203
  /**
135
204
  * Resolve the default session model from GSD preferences.
136
205
  *
@@ -372,6 +441,7 @@ export function resolveAutoSupervisorConfig(): AutoSupervisorConfig {
372
441
  soft_timeout_minutes: configured.soft_timeout_minutes ?? 20,
373
442
  idle_timeout_minutes: configured.idle_timeout_minutes ?? 10,
374
443
  hard_timeout_minutes: configured.hard_timeout_minutes ?? 30,
444
+ stalled_tool_timeout_minutes: configured.stalled_tool_timeout_minutes ?? 5,
375
445
  ...(configured.model ? { model: configured.model } : {}),
376
446
  };
377
447
  }
@@ -99,6 +99,7 @@ export const KNOWN_PREFERENCE_KEYS = new Set<string>([
99
99
  "skill_rules",
100
100
  "custom_instructions",
101
101
  "models",
102
+ "thinking",
102
103
  "skill_discovery",
103
104
  "skill_staleness_days",
104
105
  "auto_supervisor",
@@ -198,6 +199,28 @@ export interface GSDSkillRule {
198
199
  avoid?: string[];
199
200
  }
200
201
 
202
+ /**
203
+ * Reasoning effort levels. Mirrors `ThinkingLevel` in `@gsd/pi-agent-core`;
204
+ * declared locally so preference parsing/validation does not import the agent
205
+ * core package. `xhigh` is only honored by models whose `thinkingLevelMap`
206
+ * advertises it — unsupported levels are clamped at dispatch (ADR-026).
207
+ */
208
+ export type GSDThinkingLevel = "off" | "minimal" | "low" | "medium" | "high" | "xhigh";
209
+
210
+ /** The nine model-routing phase buckets. */
211
+ export const GSD_MODEL_PHASE_KEYS = [
212
+ "research",
213
+ "planning",
214
+ "discuss",
215
+ "execution",
216
+ "execution_simple",
217
+ "completion",
218
+ "validation",
219
+ "subagent",
220
+ "uat",
221
+ ] as const;
222
+ export type GSDModelPhaseKey = (typeof GSD_MODEL_PHASE_KEYS)[number];
223
+
201
224
  /**
202
225
  * Model configuration for a single phase.
203
226
  * Supports primary model with optional fallbacks for resilience.
@@ -209,8 +232,22 @@ export interface GSDPhaseModelConfig {
209
232
  provider?: string;
210
233
  /** Fallback models to try in order if primary fails (e.g., rate limits, credits exhausted) */
211
234
  fallbacks?: string[];
235
+ /**
236
+ * Reasoning effort for this phase (ADR-026). Travels with the model: an
237
+ * explicit value here bypasses the `execute-task` thinking floor and is
238
+ * capability-clamped to the resolved model at dispatch. Takes precedence
239
+ * over a same-phase entry in the separate `thinking` block.
240
+ */
241
+ thinking?: GSDThinkingLevel;
212
242
  }
213
243
 
244
+ /**
245
+ * Separate per-phase reasoning-effort block (ADR-026). An alternative to
246
+ * inline `models.<phase>.thinking` that lets thinking be pinned without
247
+ * pinning a model. Same nine phase keys as `models`.
248
+ */
249
+ export type GSDThinkingConfig = Partial<Record<GSDModelPhaseKey, GSDThinkingLevel>>;
250
+
214
251
  /**
215
252
  * Legacy model config -- simple string per phase.
216
253
  * Kept for backward compatibility; will be migrated to GSDModelConfigV2 on load.
@@ -256,6 +293,14 @@ export interface AutoSupervisorConfig {
256
293
  soft_timeout_minutes?: number;
257
294
  idle_timeout_minutes?: number;
258
295
  hard_timeout_minutes?: number;
296
+ /**
297
+ * Dedicated budget for a single in-flight tool call before it is treated as
298
+ * hung. Distinct from `idle_timeout_minutes`: a genuinely stuck tool should
299
+ * be recovered in minutes rather than waiting out the full idle window. A
300
+ * long-but-progressing session is not idle, so it must not share the hung-tool
301
+ * threshold.
302
+ */
303
+ stalled_tool_timeout_minutes?: number;
259
304
  }
260
305
 
261
306
  export interface RemoteQuestionsConfig {
@@ -364,6 +409,8 @@ export interface GSDPreferences {
364
409
  skill_rules?: GSDSkillRule[];
365
410
  custom_instructions?: string[];
366
411
  models?: GSDModelConfig | GSDModelConfigV2;
412
+ /** Per-phase reasoning effort, separate from `models` (ADR-026). */
413
+ thinking?: GSDThinkingConfig;
367
414
  skill_discovery?: SkillDiscoveryMode;
368
415
  skill_staleness_days?: number; // Skills unused for N days get deprioritized (#599). 0 = disabled. Default: 60.
369
416
  auto_supervisor?: AutoSupervisorConfig;
@@ -12,18 +12,30 @@ import type { DynamicRoutingConfig } from "./model-router.js";
12
12
  import { isAbsolute } from "node:path";
13
13
  import { VALID_BRANCH_NAME } from "./git-service.js";
14
14
  import { normalizeStringArray } from "../shared/format-utils.js";
15
+ import { getGateIdsForTurn } from "./gate-registry.js";
15
16
 
16
17
  import {
17
18
  KNOWN_PREFERENCE_KEYS,
18
19
  KNOWN_UNIT_LABELS,
20
+ GSD_MODEL_PHASE_KEYS,
19
21
 
20
22
  SKILL_ACTIONS,
21
23
  type WorkflowMode,
22
24
  type GSDPreferences,
23
25
  type GSDSkillRule,
26
+ type GSDThinkingLevel,
24
27
  } from "./preferences-types.js";
25
28
 
26
29
  const VALID_TOKEN_PROFILES = new Set<TokenProfile>(["budget", "balanced", "quality", "burn-max"]);
30
+ const VALID_THINKING_LEVELS = new Set<GSDThinkingLevel>([
31
+ "off",
32
+ "minimal",
33
+ "low",
34
+ "medium",
35
+ "high",
36
+ "xhigh",
37
+ ]);
38
+ const KNOWN_MODEL_PHASE_KEYS = new Set<string>(GSD_MODEL_PHASE_KEYS);
27
39
  const VALID_UOK_TURN_ACTIONS = new Set<"commit" | "snapshot" | "status-only">([
28
40
  "commit",
29
41
  "snapshot",
@@ -37,6 +49,7 @@ const VALID_POST_UNIT_HOOK_ON_BLOCK_ACTIONS = new Set([
37
49
  "queue-slice",
38
50
  "pause",
39
51
  ]);
52
+ const VALID_GATE_EVALUATE_SLICE_GATES = new Set<string>(getGateIdsForTurn("gate-evaluate"));
40
53
 
41
54
  export function validatePreferences(preferences: GSDPreferences): {
42
55
  preferences: GSDPreferences;
@@ -390,12 +403,66 @@ export function validatePreferences(preferences: GSDPreferences): {
390
403
  // ─── Models ─────────────────────────────────────────────────────────
391
404
  if (preferences.models !== undefined) {
392
405
  if (preferences.models && typeof preferences.models === "object") {
393
- validated.models = preferences.models;
406
+ // Static check for inline per-phase thinking (ADR-026). The resolved
407
+ // model isn't known until dispatch, so capability is clamped there; here
408
+ // we warn on illegal level strings AND strip them, so a typo can't reach
409
+ // resolveThinkingLevelForUnit and be treated as explicit configuration.
410
+ const sanitizedModels: Record<string, unknown> = {};
411
+ for (const [phase, entry] of Object.entries(preferences.models as Record<string, unknown>)) {
412
+ if (entry && typeof entry === "object" && "thinking" in entry) {
413
+ const level = (entry as { thinking?: unknown }).thinking;
414
+ if (level !== undefined && !VALID_THINKING_LEVELS.has(level as GSDThinkingLevel)) {
415
+ warnings.push(
416
+ `models.${phase}.thinking "${String(level)}" is not a valid thinking level ` +
417
+ `(off, minimal, low, medium, high, xhigh) — ignored`,
418
+ );
419
+ const { thinking: _ignored, ...rest } = entry as Record<string, unknown>;
420
+ // If stripping the bad thinking leaves no usable model, drop the
421
+ // phase entirely rather than storing a hollow `{}` / `{ provider }`
422
+ // entry that resolveWinningPhase would otherwise treat as configured.
423
+ if (rest.model) {
424
+ sanitizedModels[phase] = rest;
425
+ }
426
+ continue;
427
+ }
428
+ }
429
+ sanitizedModels[phase] = entry;
430
+ }
431
+ validated.models = sanitizedModels as GSDPreferences["models"];
394
432
  } else {
395
433
  errors.push("models must be an object");
396
434
  }
397
435
  }
398
436
 
437
+ // ─── Thinking (separate per-phase block, ADR-026) ───────────────────
438
+ if (preferences.thinking !== undefined) {
439
+ if (preferences.thinking && typeof preferences.thinking === "object" && !Array.isArray(preferences.thinking)) {
440
+ const validatedThinking: Record<string, GSDThinkingLevel> = {};
441
+ for (const [phase, level] of Object.entries(preferences.thinking as Record<string, unknown>)) {
442
+ if (!KNOWN_MODEL_PHASE_KEYS.has(phase)) {
443
+ warnings.push(
444
+ `unknown thinking phase "${phase}" — must be one of: ` +
445
+ `${[...KNOWN_MODEL_PHASE_KEYS].join(", ")} — ignored`,
446
+ );
447
+ continue;
448
+ }
449
+ if (!VALID_THINKING_LEVELS.has(level as GSDThinkingLevel)) {
450
+ warnings.push(
451
+ `thinking.${phase} "${String(level)}" is not a valid thinking level ` +
452
+ `(off, minimal, low, medium, high, xhigh) — ignored`,
453
+ );
454
+ continue;
455
+ }
456
+ validatedThinking[phase] = level as GSDThinkingLevel;
457
+ }
458
+ if (Object.keys(validatedThinking).length > 0) {
459
+ validated.thinking = validatedThinking;
460
+ }
461
+ } else {
462
+ errors.push("thinking must be an object");
463
+ }
464
+ }
465
+
399
466
  // ─── Auto Supervisor ────────────────────────────────────────────────
400
467
  if (preferences.auto_supervisor !== undefined) {
401
468
  if (preferences.auto_supervisor && typeof preferences.auto_supervisor === "object") {
@@ -907,7 +974,14 @@ export function validatePreferences(preferences: GSDPreferences): {
907
974
  }
908
975
  if (ge.slice_gates !== undefined) {
909
976
  if (Array.isArray(ge.slice_gates) && ge.slice_gates.every((g: unknown) => typeof g === "string")) {
910
- validGe.slice_gates = ge.slice_gates;
977
+ const invalid = ge.slice_gates.filter((g) => !VALID_GATE_EVALUATE_SLICE_GATES.has(g));
978
+ if (invalid.length === 0) {
979
+ validGe.slice_gates = ge.slice_gates;
980
+ } else {
981
+ errors.push(
982
+ `gate_evaluation.slice_gates must contain only gate-evaluate slice gates: ${[...VALID_GATE_EVALUATE_SLICE_GATES].join(", ")}`,
983
+ );
984
+ }
911
985
  } else {
912
986
  errors.push("gate_evaluation.slice_gates must be an array of strings");
913
987
  }
@@ -86,6 +86,8 @@ export function resolveSkillStalenessDays(basePath?: string): number {
86
86
  export {
87
87
  resolveModelForUnit,
88
88
  resolveModelWithFallbacksForUnit,
89
+ resolveThinkingLevelForUnit,
90
+ phaseChainForUnit,
89
91
  getNextFallbackModel,
90
92
  isTransientNetworkError,
91
93
  validateModelId,
@@ -437,6 +439,9 @@ function mergePreferences(base: GSDPreferences, override: GSDPreferences): GSDPr
437
439
  skill_rules: [...(base.skill_rules ?? []), ...(override.skill_rules ?? [])],
438
440
  custom_instructions: mergeStringLists(base.custom_instructions, override.custom_instructions),
439
441
  models: { ...(base.models ?? {}), ...(override.models ?? {}) },
442
+ thinking: (base.thinking || override.thinking)
443
+ ? { ...(base.thinking ?? {}), ...(override.thinking ?? {}) }
444
+ : undefined,
440
445
  skill_discovery: override.skill_discovery ?? base.skill_discovery,
441
446
  skill_staleness_days: override.skill_staleness_days ?? base.skill_staleness_days,
442
447
  auto_supervisor: { ...(base.auto_supervisor ?? {}), ...(override.auto_supervisor ?? {}) },
@@ -38,7 +38,7 @@ You are evaluating **quality gates in parallel** for this slice. Each gate is an
38
38
  3. **Verify each gate wrote its result** by checking that `gsd_save_gate_result` was called for each gate ID.
39
39
  - Call it **directly** — do **not** use `ToolSearch` (it is not available in GSD).
40
40
  - Inside Claude Code use the active MCP-scoped workflow name for `gsd_save_gate_result`; otherwise use `gsd_save_gate_result`.
41
- - Always pass all required fields (camelCase): `milestoneId`, `sliceId`, `gateId`, `verdict`, `rationale`. Never call with an empty `{}` object.
41
+ - Always pass all required fields (camelCase): `milestoneId`, `sliceId`, `gateId`, `verdict`, `rationale`, and `findings` (empty string if none). Never call with an empty `{}` object.
42
42
  4. **Report the batch outcome** — which gates passed, which flagged concerns, and which were omitted as not applicable.
43
43
 
44
44
  Gate agents may return `verdict: "omitted"` if the gate question is not applicable to this slice (e.g., no auth surface for Q3, no existing requirements touched for Q4). This is expected for simple slices.