@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
@@ -723,7 +723,9 @@ test('(u) run-uat prompt promotes artifact-driven browser specs to browser-execu
723
723
 
724
724
  assert.match(prompt, /\*\*Detected UAT mode:\*\*\s*`browser-executable`/);
725
725
  assert.match(prompt, /uatType: "browser-executable"/);
726
- assert.match(prompt, /use gsd-browser tools/i);
726
+ assert.match(prompt, /use browser tools/i);
727
+ assert.match(prompt, /"browser_navigate"/);
728
+ assert.match(prompt, /"browser_assert"/);
727
729
  } finally {
728
730
  cleanup(base);
729
731
  }
@@ -741,6 +743,7 @@ test('(v) run-uat prompt keeps deferred browser work artifact-driven', async ()
741
743
  assert.match(prompt, /\*\*Detected UAT mode:\*\*\s*`artifact-driven`/);
742
744
  assert.match(prompt, /uatType: "artifact-driven"/);
743
745
  assert.doesNotMatch(prompt, /uatType: "browser-executable"/);
746
+ assert.doesNotMatch(prompt, /"browser_navigate"/);
744
747
  } finally {
745
748
  cleanup(base);
746
749
  }
@@ -226,6 +226,33 @@ test("direct /gsd auto skips paused-session replay when recovered unit already c
226
226
  }
227
227
  });
228
228
 
229
+ test("paused-session resume skips replay when unit identity was never recorded", () => {
230
+ const base = makeTmpBase();
231
+ try {
232
+ // No currentUnit and no persisted unit type/id — identity is unknown. The
233
+ // old code fell back to the literal "unknown" unit, which can neither be
234
+ // verified nor correctly targeted, and synthesized a full tool-call replay
235
+ // (the thrash that turns one stuck unit into several). The fix skips the
236
+ // replay and resumes from rebuilt disk state instead.
237
+ const state = {
238
+ pausedSessionFile: join(base, ".gsd", "activity", "paused-session.jsonl"),
239
+ currentUnit: null,
240
+ pausedUnitType: null,
241
+ pausedUnitId: null,
242
+ pendingCrashRecovery: "stale-recovery-prompt",
243
+ };
244
+
245
+ const result = _handlePausedSessionResumeRecoveryForTest(base, state);
246
+ assert.equal(result.skippedReplay, true);
247
+ assert.equal(state.pausedSessionFile, null);
248
+ assert.equal(state.pendingCrashRecovery, null, "must not synthesize a replay for an unknown unit");
249
+ assert.equal(state.pausedUnitType, null);
250
+ assert.equal(state.pausedUnitId, null);
251
+ } finally {
252
+ cleanup(base);
253
+ }
254
+ });
255
+
229
256
  test("interrupted-session source preserves raw lock and excludes same-pid from running classification", async () => {
230
257
  const source = await import(`node:fs/promises`).then((fs) =>
231
258
  fs.readFile(new URL("../interrupted-session.ts", import.meta.url), "utf-8")
@@ -30,6 +30,7 @@ import {
30
30
  insertTask,
31
31
  openDatabase,
32
32
  } from "../gsd-db.js";
33
+ import { SourceObservationStore } from "../source-observations.js";
33
34
 
34
35
  // ─── Helpers ─────────────────────────────────────────────────────────────────
35
36
 
@@ -186,6 +187,7 @@ function makeSession() {
186
187
  currentMilestoneId: "M001",
187
188
  currentUnit: null,
188
189
  currentUnitRouting: null,
190
+ sourceObservations: new SourceObservationStore(),
189
191
  completedUnits: [],
190
192
  resourceVersionOnStart: null,
191
193
  lastPromptCharCount: undefined,
@@ -200,12 +202,26 @@ function makeSession() {
200
202
  unitLifetimeDispatches: new Map<string, number>(),
201
203
  unitRecoveryCount: new Map<string, number>(),
202
204
  verificationRetryCount: new Map<string, number>(),
205
+ zeroToolRetryCount: new Map<string, number>(),
203
206
  gitService: null,
204
207
  autoStartTime: Date.now(),
205
208
  cmdCtx: {
206
209
  newSession: () => Promise.resolve({ cancelled: false }),
207
210
  getContextUsage: () => ({ percent: 10, tokens: 1000, limit: 10000 }),
208
211
  },
212
+ setCurrentUnit(this: any, unit: any) {
213
+ this.currentUnit = unit;
214
+ this.sourceObservations.beginUnit({
215
+ unitType: unit.type,
216
+ unitId: unit.id,
217
+ startedAt: unit.startedAt,
218
+ basePath: unit.workspaceRoot ?? this.basePath,
219
+ });
220
+ },
221
+ clearCurrentUnit(this: any) {
222
+ this.currentUnit = null;
223
+ this.sourceObservations.clear();
224
+ },
209
225
  clearTimers: () => {},
210
226
  } as any;
211
227
  }
@@ -393,6 +393,48 @@ test('── markdown-renderer: renderPlanCheckboxes round-trip ──', async (
393
393
  }
394
394
  });
395
395
 
396
+ test('── markdown-renderer: renderPlanCheckboxes re-renders DB tasks added after the plan artifact ──', async () => {
397
+ // Regression for the lossy-projection root cause: renderPlanCheckboxes used to
398
+ // patch the cached PLAN artifact in place, silently dropping tasks added to
399
+ // the DB after the artifact was first written (the 4S/0T-vs-5S/13T drift).
400
+ const tmpDir = makeTmpDir();
401
+ const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
402
+ openDatabase(dbPath);
403
+ clearAllCaches();
404
+
405
+ try {
406
+ scaffoldDirs(tmpDir, 'M001', ['S01']);
407
+ insertMilestone({ id: 'M001', title: 'Test', status: 'active' });
408
+ insertSlice({ id: 'S01', milestoneId: 'M001', title: 'Slice', status: 'pending' });
409
+ insertTask({ id: 'T01', sliceId: 'S01', milestoneId: 'M001', title: 'First task', status: 'pending' });
410
+
411
+ // PLAN.md on disk reflects an earlier state with only T01.
412
+ const planPath = path.join(tmpDir, '.gsd', 'milestones', 'M001', 'slices', 'S01', 'S01-PLAN.md');
413
+ fs.writeFileSync(planPath, makePlanContent('S01', [{ id: 'T01', title: 'First task', done: false }]));
414
+ clearAllCaches();
415
+
416
+ // Two more tasks are written to the DB after the artifact already exists.
417
+ insertTask({ id: 'T02', sliceId: 'S01', milestoneId: 'M001', title: 'Second task', status: 'done' });
418
+ insertTask({ id: 'T03', sliceId: 'S01', milestoneId: 'M001', title: 'Third task', status: 'pending' });
419
+
420
+ const ok = await renderPlanCheckboxes(tmpDir, 'M001', 'S01');
421
+ assert.ok(ok, 'renderPlanCheckboxes returns true');
422
+
423
+ const parsed = parsePlan(fs.readFileSync(planPath, 'utf-8'));
424
+ clearAllCaches();
425
+ assert.deepStrictEqual(
426
+ parsed.tasks.map(t => t.id).sort(),
427
+ ['T01', 'T02', 'T03'],
428
+ 'full re-render must include tasks added after the artifact was written',
429
+ );
430
+ assert.ok(parsed.tasks.find(t => t.id === 'T02')!.done, 'T02 reflects done status from DB');
431
+ assert.ok(!parsed.tasks.find(t => t.id === 'T03')!.done, 'T03 reflects pending status from DB');
432
+ } finally {
433
+ closeDatabase();
434
+ cleanupDir(tmpDir);
435
+ }
436
+ });
437
+
396
438
  test('── markdown-renderer: renderPlanCheckboxes bidirectional ──', async () => {
397
439
  const tmpDir = makeTmpDir();
398
440
  const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
@@ -54,7 +54,13 @@ test("ensureProjectWorkflowMcpConfig creates .mcp.json with workflow and browser
54
54
  "--identity-scope",
55
55
  "project",
56
56
  ]);
57
- assert.equal(browserArgs[mcpArgIndex + 6], projectRoot);
57
+ // --identity-scope requires a non-empty --identity-key or gsd-browser exits
58
+ // immediately ("Connection closed"); the key must be stable per project.
59
+ assert.equal(browserArgs[mcpArgIndex + 5], "--identity-key");
60
+ assert.equal(typeof browserArgs[mcpArgIndex + 6], "string");
61
+ assert.ok((browserArgs[mcpArgIndex + 6] ?? "").length > 0, "identity-key must be non-empty");
62
+ assert.equal(browserArgs[mcpArgIndex + 7], "--identity-project");
63
+ assert.equal(browserArgs[mcpArgIndex + 8], projectRoot);
58
64
  assert.equal((browserServer as { cwd?: string })?.cwd, projectRoot);
59
65
 
60
66
  const settings = JSON.parse(readFileSync(join(projectRoot, ".claude", "settings.local.json"), "utf-8")) as {
@@ -342,7 +342,7 @@ describe("formatMcpInitResult", () => {
342
342
  assert.match(result, /\/tmp\/project\/\.mcp\.json/);
343
343
  assert.match(result, /mcp-capable clients/i);
344
344
  assert.match(result, /workflow and gsd-browser MCP servers/i);
345
- assert.match(result, /Pi Providers use the managed gsd-browser engine/i);
345
+ assert.match(result, /Pi Providers use built-in browser tools/i);
346
346
  assert.doesNotMatch(result, /claude code/i);
347
347
  });
348
348
 
@@ -151,6 +151,105 @@ test("migration auto-check leaves matching DB hierarchy alone", async () => {
151
151
  }
152
152
  });
153
153
 
154
+ test("migration auto-check flags a populated DB with missing markdown and points at rebuild (not recover)", async () => {
155
+ const base = makeBase();
156
+ try {
157
+ // A project with no milestone markdown: simulate lost/empty projections
158
+ // over a populated DB. The previous early return treated all-zero markdown
159
+ // as 'no project' and never even opened the DB, silently hiding the rows.
160
+ await writeGSDDirectory({ projectContent: "# P\n", decisionsContent: "", requirements: [], milestones: [] }, base);
161
+ assert.equal(await ensureDbOpen(base), true);
162
+ assert.deepEqual(countMarkdownHierarchy(base), { milestones: 0, slices: 0, tasks: 0 });
163
+ insertMilestone({ id: "M001", title: "Legacy Milestone", status: "active" });
164
+ insertSlice({ id: "S01", milestoneId: "M001", title: "Legacy Slice", status: "pending", risk: "medium", depends: [], demo: "Legacy slice demo", sequence: 1 });
165
+ insertTask({ id: "T01", sliceId: "S01", milestoneId: "M001", title: "Legacy Task", status: "pending" });
166
+
167
+ const result = await checkMarkdownHierarchyAgainstDb(base);
168
+ assert.equal(result.action, "recovery-required");
169
+ assert.equal(result.reason, "markdown-missing");
170
+ // The DB is the richer side, so recover (md → DB) would DELETE rows. The
171
+ // safe repair is to re-project from the DB.
172
+ assert.equal(result.recoveryCommand, "/gsd rebuild markdown");
173
+ assert.match(result.message ?? "", /rebuild markdown/);
174
+ assert.match(result.message ?? "", /Do NOT run/);
175
+ // The check must not mutate the DB.
176
+ assert.equal(getAllMilestones().length, 1);
177
+ assert.equal(getSliceTasks("M001", "S01").length, 1);
178
+ } finally {
179
+ cleanup(base);
180
+ }
181
+ });
182
+
183
+ test("migration auto-check detects identity drift even when counts match", async () => {
184
+ const base = makeBase();
185
+ try {
186
+ await writeGSDDirectory(projectFixture(), base); // markdown: M001 / S01 / T01
187
+ assert.equal(await ensureDbOpen(base), true);
188
+ // Same cardinalities (1M/1S/1T) but a DIFFERENT slice identity (S99 vs S01).
189
+ insertMilestone({ id: "M001", title: "Legacy Milestone", status: "active" });
190
+ insertSlice({ id: "S99", milestoneId: "M001", title: "Other Slice", status: "pending", risk: "medium", depends: [], demo: "d", sequence: 1 });
191
+ insertTask({ id: "T01", sliceId: "S99", milestoneId: "M001", title: "Legacy Task", status: "pending" });
192
+
193
+ const result = await checkMarkdownHierarchyAgainstDb(base);
194
+ // Counts are equal on both sides, so the old count-only comparison reported
195
+ // 'in-sync'. Identity comparison must catch the divergence instead.
196
+ assert.equal(result.action, "recovery-required");
197
+ assert.notEqual(result.reason, "in-sync");
198
+ assert.deepEqual(result.markdown, { milestones: 1, slices: 1, tasks: 1 });
199
+ assert.deepEqual(result.beforeDb, { milestones: 1, slices: 1, tasks: 1 });
200
+ // The DB holds S99 (which markdown lacks), so recover would DELETE it. Even
201
+ // at equal counts the safe recommendation must be rebuild, not recover.
202
+ assert.equal(result.recoveryCommand, "/gsd rebuild markdown");
203
+ assert.match(result.message ?? "", /Do NOT run/);
204
+ } finally {
205
+ cleanup(base);
206
+ }
207
+ });
208
+
209
+ test("recoverWouldDeleteDbRows flags identity drift the markdown lacks (even at equal counts)", async () => {
210
+ const base = makeBase();
211
+ try {
212
+ await writeGSDDirectory(projectFixture(), base); // markdown: M001 / S01 / T01
213
+ assert.equal(await ensureDbOpen(base), true);
214
+ // DB row identity (S99) differs from markdown (S01) at the same count.
215
+ insertMilestone({ id: "M001", title: "Legacy Milestone", status: "active" });
216
+ insertSlice({ id: "S99", milestoneId: "M001", title: "Other Slice", status: "pending", risk: "medium", depends: [], demo: "d", sequence: 1 });
217
+ insertTask({ id: "T01", sliceId: "S99", milestoneId: "M001", title: "Legacy Task", status: "pending" });
218
+
219
+ const { recoverWouldDeleteDbRows } = await import("../migration-auto-check.ts");
220
+ assert.equal(recoverWouldDeleteDbRows(base), true, "DB S99 is absent from markdown — recover would delete it");
221
+ } finally {
222
+ cleanup(base);
223
+ }
224
+ });
225
+
226
+ test("migration auto-check canonicalizes a legacy descriptor milestone dir (no false drift)", async () => {
227
+ const base = makeBase();
228
+ try {
229
+ await writeGSDDirectory(projectFixture(), base); // creates .gsd/milestones/M001
230
+ // Rename the dir to a legacy descriptor form while the DB id stays "M001".
231
+ // scanMarkdownHierarchy must canonicalize "M001-old" → "M001" so the
232
+ // identity sets line up with scanDbHierarchy (which uses milestone.id).
233
+ const milestonesRoot = join(base, ".gsd", "milestones");
234
+ renameSync(join(milestonesRoot, "M001"), join(milestonesRoot, "M001-old"));
235
+
236
+ assert.equal(await ensureDbOpen(base), true);
237
+ insertMilestone({ id: "M001", title: "Legacy Milestone", status: "active" });
238
+ insertSlice({ id: "S01", milestoneId: "M001", title: "Legacy Slice", status: "pending", risk: "medium", depends: [], demo: "Legacy slice demo", sequence: 1 });
239
+ insertTask({ id: "T01", sliceId: "S01", milestoneId: "M001", title: "Legacy Task", status: "pending" });
240
+
241
+ const result = await checkMarkdownHierarchyAgainstDb(base);
242
+ // Must be in-sync: the raw dir name "M001-old" would otherwise mismatch the
243
+ // DB id "M001" and be flagged as false drift.
244
+ assert.equal(result.action, "none");
245
+ assert.equal(result.reason, "in-sync");
246
+ assert.deepEqual(result.markdown, { milestones: 1, slices: 1, tasks: 1 });
247
+ assert.deepEqual(result.beforeDb, { milestones: 1, slices: 1, tasks: 1 });
248
+ } finally {
249
+ cleanup(base);
250
+ }
251
+ });
252
+
154
253
  test("migration auto-check refreshes a stale open DB handle before comparing", async () => {
155
254
  const base = makeBase();
156
255
  try {
@@ -133,6 +133,36 @@ test('handlePlanSlice persists explicit slice/task target repositories', async (
133
133
  }
134
134
  });
135
135
 
136
+ test('handlePlanSlice honors configured gate-evaluation gate sets', async () => {
137
+ const base = makeTmpBase();
138
+ openDatabase(join(base, '.gsd', 'gsd.db'));
139
+
140
+ try {
141
+ seedParentSlice();
142
+ writeFileSync(
143
+ join(base, '.gsd', 'PREFERENCES.md'),
144
+ [
145
+ '---',
146
+ 'gate_evaluation:',
147
+ ' enabled: true',
148
+ ' slice_gates:',
149
+ ' - Q3',
150
+ ' task_gates: false',
151
+ '---',
152
+ ].join('\n'),
153
+ 'utf-8',
154
+ );
155
+
156
+ const result = await handlePlanSlice(validParams(), base);
157
+ assert.ok(!('error' in result), `unexpected error: ${'error' in result ? result.error : ''}`);
158
+
159
+ const gateIds = getGateResults('M001', 'S02').map((gate) => gate.gate_id).sort();
160
+ assert.deepEqual(gateIds, ['Q3', 'Q8']);
161
+ } finally {
162
+ cleanup(base);
163
+ }
164
+ });
165
+
136
166
  test('handlePlanSlice rejects unknown target repositories', async () => {
137
167
  const base = makeTmpBase();
138
168
  openDatabase(join(base, '.gsd', 'gsd.db'));
@@ -196,6 +226,50 @@ test('handlePlanSlice enforces absolute path scope to declared target repositori
196
226
  }
197
227
  });
198
228
 
229
+ test('handlePlanSlice resolves relative task IO paths against declared target repository roots', async () => {
230
+ const base = makeTmpBase();
231
+ openDatabase(join(base, '.gsd', 'gsd.db'));
232
+
233
+ try {
234
+ seedParentSlice();
235
+ mkdirSync(join(base, 'frontend'), { recursive: true });
236
+ writeFileSync(join(base, 'frontend', 'app.js'), 'export {};\n', 'utf-8');
237
+ writeFileSync(
238
+ join(base, '.gsd', 'PREFERENCES.md'),
239
+ [
240
+ '---',
241
+ 'workspace:',
242
+ ' mode: parent',
243
+ ' repositories:',
244
+ ' frontend:',
245
+ ' path: frontend',
246
+ '---',
247
+ ].join('\n'),
248
+ 'utf-8',
249
+ );
250
+
251
+ const params = validParams();
252
+ const result = await handlePlanSlice({
253
+ ...params,
254
+ targetRepositories: ['frontend'],
255
+ tasks: [
256
+ {
257
+ ...params.tasks[0],
258
+ files: ['app.js'],
259
+ inputs: ['app.js'],
260
+ expectedOutput: ['app.js'],
261
+ targetRepositories: ['frontend'],
262
+ },
263
+ ],
264
+ }, base);
265
+
266
+ assert.ok(!('error' in result), `unexpected error: ${'error' in result ? result.error : ''}`);
267
+ assert.deepEqual(getSliceTasks('M001', 'S02').map((task) => task.id), ['T01']);
268
+ } finally {
269
+ cleanup(base);
270
+ }
271
+ });
272
+
199
273
  test('handlePlanSlice rejects relative traversal outside declared target repositories', async () => {
200
274
  const base = makeTmpBase();
201
275
  openDatabase(join(base, '.gsd', 'gsd.db'));
@@ -749,7 +823,7 @@ test('regression: validateTasks surfaces clean per-field errors for non-array IO
749
823
  }
750
824
  });
751
825
 
752
- test('handlePlanSlice skips prose and sentinel values in planning path scope', async () => {
826
+ test('handlePlanSlice skips prose and sentinel input values in planning path scope', async () => {
753
827
  const base = makeTmpBase();
754
828
  openDatabase(join(base, '.gsd', 'gsd.db'));
755
829
 
@@ -760,7 +834,7 @@ test('handlePlanSlice skips prose and sentinel values in planning path scope', a
760
834
  tasks: [{
761
835
  ...validParams().tasks[0],
762
836
  inputs: ['Current enum shape in codebase', 'None'],
763
- expectedOutput: ['Updated planning-path-scope.ts — validates paths only'],
837
+ expectedOutput: ['src/resources/extensions/gsd/planning-path-scope.ts'],
764
838
  }],
765
839
  }, base);
766
840
 
@@ -771,6 +845,29 @@ test('handlePlanSlice skips prose and sentinel values in planning path scope', a
771
845
  }
772
846
  });
773
847
 
848
+ test('handlePlanSlice rejects prose expectedOutput entries before path-scope validation', async () => {
849
+ const base = makeTmpBase();
850
+ openDatabase(join(base, '.gsd', 'gsd.db'));
851
+
852
+ try {
853
+ seedParentSlice();
854
+ const result = await handlePlanSlice({
855
+ ...validParams(),
856
+ tasks: [{
857
+ ...validParams().tasks[0],
858
+ expectedOutput: ['Browser UI supports due-date add/edit flows and mixed-list urgency rendering.'],
859
+ }],
860
+ }, base);
861
+
862
+ assert.ok('error' in result);
863
+ assert.match(result.error, /expectedOutput must contain only file paths/);
864
+ assert.doesNotMatch(result.error, /outside allowed repository roots/);
865
+ assert.equal(getSliceTasks('M001', 'S02').length, 0, 'invalid output contract must not persist tasks');
866
+ } finally {
867
+ cleanup(base);
868
+ }
869
+ });
870
+
774
871
  test('handlePlanSlice resolves relative task IO paths against worktree roots', async () => {
775
872
  const base = makeTmpBase();
776
873
  const worktree = join(base, '.gsd', 'worktrees', 'M001');
@@ -96,6 +96,25 @@ test('handlePlanTask explains string IO fields must be arrays', async () => {
96
96
  }
97
97
  });
98
98
 
99
+ test('handlePlanTask rejects prose expectedOutput entries', async () => {
100
+ const base = makeTmpBase();
101
+ openDatabase(join(base, '.gsd', 'gsd.db'));
102
+
103
+ try {
104
+ seedParent();
105
+ const result = await handlePlanTask({
106
+ ...validParams(),
107
+ expectedOutput: ['Browser UI supports due-date add/edit flows and mixed-list urgency rendering.'],
108
+ }, base);
109
+ assert.ok('error' in result);
110
+ assert.match(result.error, /validation failed: expectedOutput must contain only file paths/);
111
+ assert.doesNotMatch(result.error, /outside allowed repository roots/);
112
+ assert.equal(getTask('M001', 'S02', 'T02'), null, 'invalid output contract must not persist the task');
113
+ } finally {
114
+ cleanup(base);
115
+ }
116
+ });
117
+
99
118
  test('handlePlanTask rejects absolute task IO paths outside the active worktree', async () => {
100
119
  const base = makeTmpBase();
101
120
  openDatabase(join(base, '.gsd', 'gsd.db'));
@@ -0,0 +1,100 @@
1
+ import { test, describe } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { mkdtempSync, rmSync } from "node:fs";
4
+ import { join } from "node:path";
5
+ import { tmpdir } from "node:os";
6
+
7
+ import { GSD_COMMAND_DESCRIPTION, getGsdArgumentCompletions, TOP_LEVEL_SUBCOMMANDS } from "../commands/catalog.ts";
8
+ import { handleCoreCommand } from "../commands/handlers/core.ts";
9
+ import { DISPATCH_RULES } from "../auto-dispatch.ts";
10
+ import {
11
+ buildGsdPlannerSpawnPlan,
12
+ formatGsdPlannerCommand,
13
+ hasPlannerHandoffBeenOffered,
14
+ markPlannerHandoffOffered,
15
+ PLANNER_HANDOFF_RULE_NAME,
16
+ } from "../planner-handoff.ts";
17
+
18
+ describe("planner handoff command catalog", () => {
19
+ test("/gsd planner is hidden from description and completions", () => {
20
+ assert.doesNotMatch(GSD_COMMAND_DESCRIPTION, /\|planner(?:\||$)/);
21
+ assert.equal(
22
+ TOP_LEVEL_SUBCOMMANDS.some((command) => command.cmd === "planner"),
23
+ false,
24
+ "planner should not appear in top-level commands",
25
+ );
26
+
27
+ const completions = getGsdArgumentCompletions("pla");
28
+
29
+ assert.equal(
30
+ completions.some((completion) => completion.value === "planner"),
31
+ false,
32
+ "planner should not appear in top-level completions",
33
+ );
34
+
35
+ assert.deepEqual(
36
+ getGsdArgumentCompletions("planner --"),
37
+ [],
38
+ "planner should not expose nested completions",
39
+ );
40
+ });
41
+ });
42
+
43
+ describe("planner handoff command handler", () => {
44
+ test("/gsd planner falls through to the unknown-command path", async () => {
45
+ const notifications: Array<{ message: string; level?: string }> = [];
46
+ const ctx = {
47
+ ui: {
48
+ notify(message: string, level?: string) {
49
+ notifications.push({ message, level });
50
+ },
51
+ },
52
+ };
53
+
54
+ const handled = await handleCoreCommand("planner M001 --dry-run --inspect", ctx as any);
55
+
56
+ assert.equal(handled, false);
57
+ assert.deepEqual(notifications, []);
58
+ });
59
+ });
60
+
61
+ describe("planner handoff launcher", () => {
62
+ test("builds gsd-planner command with project and milestone context", () => {
63
+ const plan = buildGsdPlannerSpawnPlan({
64
+ basePath: "/tmp/project with spaces",
65
+ milestoneId: "M001",
66
+ extraArgs: ["--inspect"],
67
+ });
68
+
69
+ assert.deepEqual(plan, {
70
+ command: "gsd-planner",
71
+ args: ["--project", "/tmp/project with spaces", "--milestone", "M001", "--inspect"],
72
+ cwd: "/tmp/project with spaces",
73
+ });
74
+ assert.equal(
75
+ formatGsdPlannerCommand(plan),
76
+ 'gsd-planner --project "/tmp/project with spaces" --milestone M001 --inspect',
77
+ );
78
+ });
79
+
80
+ test("records one-shot handoff markers per milestone", () => {
81
+ const basePath = mkdtempSync(join(tmpdir(), "gsd-planner-marker-"));
82
+ try {
83
+ assert.equal(hasPlannerHandoffBeenOffered(basePath, "M001"), false);
84
+ markPlannerHandoffOffered(basePath, "M001");
85
+ assert.equal(hasPlannerHandoffBeenOffered(basePath, "M001"), true);
86
+ assert.equal(hasPlannerHandoffBeenOffered(basePath, "M002"), false);
87
+ } finally {
88
+ rmSync(basePath, { recursive: true, force: true });
89
+ }
90
+ });
91
+ });
92
+
93
+ describe("planner handoff dispatch rule", () => {
94
+ test("rule is not registered while /gsd planner is disabled", () => {
95
+ assert.equal(
96
+ DISPATCH_RULES.some((rule) => rule.name === PLANNER_HANDOFF_RULE_NAME),
97
+ false,
98
+ );
99
+ });
100
+ });
@@ -467,6 +467,20 @@ test("notification fields validate correctly", () => {
467
467
  assert.equal(preferences.notifications?.on_complete, false);
468
468
  });
469
469
 
470
+ test("gate_evaluation slice_gates only accepts gate-evaluate-owned gates", () => {
471
+ const valid = validatePreferences({
472
+ gate_evaluation: { enabled: true, slice_gates: ["Q3", "Q4"], task_gates: false },
473
+ });
474
+ assert.equal(valid.errors.length, 0);
475
+ assert.deepEqual(valid.preferences.gate_evaluation?.slice_gates, ["Q3", "Q4"]);
476
+ assert.equal(valid.preferences.gate_evaluation?.task_gates, false);
477
+
478
+ const invalid = validatePreferences({
479
+ gate_evaluation: { enabled: true, slice_gates: ["Q3", "Q8"] },
480
+ });
481
+ assert.ok(invalid.errors.some((error) => error.includes("gate_evaluation.slice_gates")));
482
+ });
483
+
470
484
  test("cmux fields validate correctly", () => {
471
485
  const { preferences, errors } = validatePreferences({
472
486
  cmux: {
@@ -18,6 +18,7 @@ const PREF_SAMPLE_VALUES: Record<string, unknown> = {
18
18
  skill_rules: [{ when: "unit:execute-task", use: ["test-writer-fixer"] }],
19
19
  custom_instructions: ["Keep changes focused."],
20
20
  models: { execution: "openai/gpt-5" },
21
+ thinking: { planning: "xhigh", execution: "low" },
21
22
  skill_discovery: "auto",
22
23
  skill_staleness_days: 7,
23
24
  auto_supervisor: { soft_timeout_minutes: 20, idle_timeout_minutes: 10, hard_timeout_minutes: 30 },