@opengsd/gsd-pi 1.4.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (329) hide show
  1. package/README.md +11 -11
  2. package/dist/onboarding.js +11 -0
  3. package/dist/resources/.managed-resources-content-hash +1 -1
  4. package/dist/resources/GSD-WORKFLOW.md +20 -25
  5. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +118 -53
  6. package/dist/resources/extensions/cursor-cli/index.js +16 -0
  7. package/dist/resources/extensions/cursor-cli/models.js +63 -0
  8. package/dist/resources/extensions/cursor-cli/package.json +11 -0
  9. package/dist/resources/extensions/cursor-cli/readiness.js +128 -0
  10. package/dist/resources/extensions/cursor-cli/stream-adapter.js +353 -0
  11. package/dist/resources/extensions/gsd/activity-log.js +4 -1
  12. package/dist/resources/extensions/gsd/artifact-verification.js +25 -9
  13. package/dist/resources/extensions/gsd/auto/closeout.js +45 -42
  14. package/dist/resources/extensions/gsd/auto/finalize.js +9 -0
  15. package/dist/resources/extensions/gsd/auto/orchestrator.js +1 -1
  16. package/dist/resources/extensions/gsd/auto/run-unit.js +5 -2
  17. package/dist/resources/extensions/gsd/auto/schedule-wakeup.js +3 -0
  18. package/dist/resources/extensions/gsd/auto-dispatch.js +15 -5
  19. package/dist/resources/extensions/gsd/auto-model-selection.js +36 -18
  20. package/dist/resources/extensions/gsd/auto-prompts.js +76 -2
  21. package/dist/resources/extensions/gsd/auto-start.js +4 -3
  22. package/dist/resources/extensions/gsd/auto-timeout-recovery.js +4 -0
  23. package/dist/resources/extensions/gsd/auto-worktree.js +38 -11
  24. package/dist/resources/extensions/gsd/auto.js +23 -3
  25. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +3 -0
  26. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +114 -5
  27. package/dist/resources/extensions/gsd/bootstrap/schedule-wakeup-tool.js +58 -9
  28. package/dist/resources/extensions/gsd/bootstrap/tool-call-loop-guard.js +10 -2
  29. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +14 -1
  30. package/dist/resources/extensions/gsd/codebase-generator.js +157 -19
  31. package/dist/resources/extensions/gsd/commands/handlers/core.js +40 -4
  32. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +153 -1
  33. package/dist/resources/extensions/gsd/commands-worktree.js +11 -2
  34. package/dist/resources/extensions/gsd/db/queries.js +39 -0
  35. package/dist/resources/extensions/gsd/discussion-handoff.js +8 -3
  36. package/dist/resources/extensions/gsd/docs/preferences-reference.md +8 -2
  37. package/dist/resources/extensions/gsd/doctor-engine-checks.js +206 -14
  38. package/dist/resources/extensions/gsd/doctor-providers.js +38 -10
  39. package/dist/resources/extensions/gsd/doctor.js +93 -3
  40. package/dist/resources/extensions/gsd/exec-sandbox.js +6 -2
  41. package/dist/resources/extensions/gsd/flat-phase-migration.js +74 -23
  42. package/dist/resources/extensions/gsd/git-service.js +14 -18
  43. package/dist/resources/extensions/gsd/gsd-db.js +52 -0
  44. package/dist/resources/extensions/gsd/guidance.js +1 -0
  45. package/dist/resources/extensions/gsd/guided-flow.js +40 -2
  46. package/dist/resources/extensions/gsd/key-manager.js +1 -0
  47. package/dist/resources/extensions/gsd/markdown-renderer.js +25 -8
  48. package/dist/resources/extensions/gsd/migrate-external.js +24 -7
  49. package/dist/resources/extensions/gsd/milestone-merge-transaction.js +3 -3
  50. package/dist/resources/extensions/gsd/preferences-diagnostics.js +24 -1
  51. package/dist/resources/extensions/gsd/preferences-models.js +6 -4
  52. package/dist/resources/extensions/gsd/preferences-validation.js +25 -2
  53. package/dist/resources/extensions/gsd/preferences.js +29 -2
  54. package/dist/resources/extensions/gsd/prompts/plan-slice.md +4 -2
  55. package/dist/resources/extensions/gsd/redact-secrets.js +37 -0
  56. package/dist/resources/extensions/gsd/repo-identity.js +16 -0
  57. package/dist/resources/extensions/gsd/repository-registry.js +6 -1
  58. package/dist/resources/extensions/gsd/root-write-leak-guard.js +4 -2
  59. package/dist/resources/extensions/gsd/safe-fs.js +23 -2
  60. package/dist/resources/extensions/gsd/skills/gsd-headless/SKILL.md +7 -9
  61. package/dist/resources/extensions/gsd/skills/gsd-headless/references/commands.md +1 -0
  62. package/dist/resources/extensions/gsd/tools/plan-task.js +76 -8
  63. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +40 -0
  64. package/dist/resources/extensions/gsd/unit-context-manifest.js +5 -4
  65. package/dist/resources/extensions/gsd/unit-registry.js +27 -2
  66. package/dist/resources/extensions/gsd/unit-runtime.js +21 -0
  67. package/dist/resources/extensions/gsd/workspace.js +26 -8
  68. package/dist/resources/extensions/gsd/worktree-lifecycle.js +67 -5
  69. package/dist/resources/extensions/gsd/worktree-manager.js +97 -4
  70. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  71. package/dist/web/standalone/.next/BUILD_ID +1 -1
  72. package/dist/web/standalone/.next/app-path-routes-manifest.json +7 -7
  73. package/dist/web/standalone/.next/build-manifest.json +2 -2
  74. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  75. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  77. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  78. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  79. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  80. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  81. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  82. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  83. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  84. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  85. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  86. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  87. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  88. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  89. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  90. package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
  91. package/dist/web/standalone/.next/server/app/index.html +1 -1
  92. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  93. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  94. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  95. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  96. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  97. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  98. package/dist/web/standalone/.next/server/app-paths-manifest.json +7 -7
  99. package/dist/web/standalone/.next/server/chunks/8357.js +1 -1
  100. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  101. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  102. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  103. package/dist/web/standalone/node_modules/@gsd/native/package.json +1 -1
  104. package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
  105. package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
  106. package/dist/wizard.d.ts +8 -0
  107. package/dist/wizard.js +8 -1
  108. package/integrations/hermes/CONTEXT.md +47 -0
  109. package/integrations/hermes/README.md +21 -0
  110. package/integrations/hermes/__init__.py +31 -0
  111. package/integrations/hermes/docs/issue-1162-grilling.md +197 -0
  112. package/integrations/hermes/docs/setup.md +4 -0
  113. package/integrations/hermes/open_gsd_hermes/commands.py +141 -0
  114. package/integrations/hermes/open_gsd_hermes/config.py +2 -0
  115. package/integrations/hermes/open_gsd_hermes/gsd_client.py +594 -31
  116. package/integrations/hermes/open_gsd_hermes/notifications.py +10 -1
  117. package/integrations/hermes/plugin.yaml +4 -0
  118. package/integrations/hermes/tests/test_config.py +6 -0
  119. package/integrations/hermes/tests/test_directory_loader.py +37 -0
  120. package/integrations/hermes/tests/test_gsd_client_cache.py +3 -0
  121. package/integrations/hermes/tests/test_gsd_client_execute.py +35 -0
  122. package/integrations/hermes/tests/test_gsd_client_process_cwd.py +22 -0
  123. package/integrations/hermes/tests/test_gsd_client_timeout.py +98 -0
  124. package/integrations/hermes/tests/test_milestone_client.py +447 -0
  125. package/integrations/hermes/tests/test_new_milestone_command.py +313 -0
  126. package/integrations/hermes/tests/test_supervisor_fsm.py +40 -0
  127. package/package.json +8 -8
  128. package/packages/cloud-mcp-gateway/dist/auth-store.d.ts +1 -0
  129. package/packages/cloud-mcp-gateway/dist/auth-store.d.ts.map +1 -1
  130. package/packages/cloud-mcp-gateway/dist/auth-store.js +21 -1
  131. package/packages/cloud-mcp-gateway/dist/auth-store.js.map +1 -1
  132. package/packages/cloud-mcp-gateway/dist/auth-store.test.js +34 -0
  133. package/packages/cloud-mcp-gateway/dist/auth-store.test.js.map +1 -1
  134. package/packages/cloud-mcp-gateway/package.json +2 -2
  135. package/packages/contracts/package.json +1 -1
  136. package/packages/daemon/dist/cloud-runtime.d.ts +2 -0
  137. package/packages/daemon/dist/cloud-runtime.d.ts.map +1 -1
  138. package/packages/daemon/dist/cloud-runtime.js +25 -1
  139. package/packages/daemon/dist/cloud-runtime.js.map +1 -1
  140. package/packages/daemon/dist/cloud-runtime.test.js +51 -0
  141. package/packages/daemon/dist/cloud-runtime.test.js.map +1 -1
  142. package/packages/daemon/dist/message-batcher.d.ts +7 -0
  143. package/packages/daemon/dist/message-batcher.d.ts.map +1 -1
  144. package/packages/daemon/dist/message-batcher.js +13 -0
  145. package/packages/daemon/dist/message-batcher.js.map +1 -1
  146. package/packages/daemon/dist/message-batcher.test.js +48 -0
  147. package/packages/daemon/dist/message-batcher.test.js.map +1 -1
  148. package/packages/daemon/dist/session-manager.js +1 -1
  149. package/packages/daemon/dist/session-manager.js.map +1 -1
  150. package/packages/daemon/package.json +5 -5
  151. package/packages/gsd-agent-core/dist/compaction/compaction.d.ts +13 -1
  152. package/packages/gsd-agent-core/dist/compaction/compaction.d.ts.map +1 -1
  153. package/packages/gsd-agent-core/dist/compaction/compaction.js +15 -1
  154. package/packages/gsd-agent-core/dist/compaction/compaction.js.map +1 -1
  155. package/packages/gsd-agent-core/dist/session/agent-session-navigation.d.ts.map +1 -1
  156. package/packages/gsd-agent-core/dist/session/agent-session-navigation.js +4 -1
  157. package/packages/gsd-agent-core/dist/session/agent-session-navigation.js.map +1 -1
  158. package/packages/gsd-agent-core/package.json +5 -5
  159. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-segment-walker.d.ts.map +1 -1
  160. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-segment-walker.js +1 -0
  161. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-segment-walker.js.map +1 -1
  162. package/packages/gsd-agent-modes/dist/modes/rpc/rpc-client.js +1 -1
  163. package/packages/gsd-agent-modes/dist/modes/rpc/rpc-client.js.map +1 -1
  164. package/packages/gsd-agent-modes/package.json +7 -7
  165. package/packages/mcp-server/dist/server.d.ts +18 -5
  166. package/packages/mcp-server/dist/server.d.ts.map +1 -1
  167. package/packages/mcp-server/dist/server.js +46 -5
  168. package/packages/mcp-server/dist/server.js.map +1 -1
  169. package/packages/mcp-server/dist/session-manager.js +1 -1
  170. package/packages/mcp-server/dist/session-manager.js.map +1 -1
  171. package/packages/mcp-server/package.json +4 -4
  172. package/packages/native/package.json +1 -1
  173. package/packages/pi-agent-core/package.json +1 -1
  174. package/packages/pi-ai/dist/env-api-keys.d.ts.map +1 -1
  175. package/packages/pi-ai/dist/env-api-keys.js +3 -0
  176. package/packages/pi-ai/dist/env-api-keys.js.map +1 -1
  177. package/packages/pi-ai/dist/types.d.ts +2 -2
  178. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  179. package/packages/pi-ai/dist/types.js.map +1 -1
  180. package/packages/pi-ai/package.json +1 -1
  181. package/packages/pi-coding-agent/dist/core/model-resolver.d.ts.map +1 -1
  182. package/packages/pi-coding-agent/dist/core/model-resolver.js +1 -0
  183. package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
  184. package/packages/pi-coding-agent/dist/core/session-manager-types.d.ts +1 -0
  185. package/packages/pi-coding-agent/dist/core/session-manager-types.d.ts.map +1 -1
  186. package/packages/pi-coding-agent/dist/core/session-manager-types.js.map +1 -1
  187. package/packages/pi-coding-agent/dist/core/session-manager.d.ts.map +1 -1
  188. package/packages/pi-coding-agent/dist/core/session-manager.js +3 -0
  189. package/packages/pi-coding-agent/dist/core/session-manager.js.map +1 -1
  190. package/packages/pi-coding-agent/package.json +7 -7
  191. package/packages/pi-tui/package.json +2 -2
  192. package/packages/rpc-client/dist/rpc-client.js +1 -1
  193. package/packages/rpc-client/dist/rpc-client.js.map +1 -1
  194. package/packages/rpc-client/package.json +2 -2
  195. package/pkg/package.json +1 -1
  196. package/scripts/link-workspace-packages.cjs +14 -4
  197. package/src/resources/GSD-WORKFLOW.md +20 -25
  198. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +130 -51
  199. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +371 -39
  200. package/src/resources/extensions/cursor-cli/index.ts +18 -0
  201. package/src/resources/extensions/cursor-cli/models.ts +65 -0
  202. package/src/resources/extensions/cursor-cli/package.json +11 -0
  203. package/src/resources/extensions/cursor-cli/readiness.ts +143 -0
  204. package/src/resources/extensions/cursor-cli/stream-adapter.ts +440 -0
  205. package/src/resources/extensions/cursor-cli/tests/index.test.ts +54 -0
  206. package/src/resources/extensions/cursor-cli/tests/readiness.test.ts +29 -0
  207. package/src/resources/extensions/cursor-cli/tests/stream-adapter.test.ts +166 -0
  208. package/src/resources/extensions/gsd/activity-log.ts +4 -1
  209. package/src/resources/extensions/gsd/artifact-verification.ts +25 -9
  210. package/src/resources/extensions/gsd/auto/closeout.ts +54 -56
  211. package/src/resources/extensions/gsd/auto/finalize.ts +8 -0
  212. package/src/resources/extensions/gsd/auto/loop-deps.ts +1 -0
  213. package/src/resources/extensions/gsd/auto/orchestrator.ts +1 -1
  214. package/src/resources/extensions/gsd/auto/run-unit.ts +5 -2
  215. package/src/resources/extensions/gsd/auto/schedule-wakeup.ts +8 -0
  216. package/src/resources/extensions/gsd/auto-dispatch.ts +17 -5
  217. package/src/resources/extensions/gsd/auto-model-selection.ts +49 -18
  218. package/src/resources/extensions/gsd/auto-prompts.ts +73 -2
  219. package/src/resources/extensions/gsd/auto-start.ts +4 -3
  220. package/src/resources/extensions/gsd/auto-timeout-recovery.ts +4 -0
  221. package/src/resources/extensions/gsd/auto-worktree.ts +50 -11
  222. package/src/resources/extensions/gsd/auto.ts +22 -3
  223. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +3 -0
  224. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +128 -5
  225. package/src/resources/extensions/gsd/bootstrap/schedule-wakeup-tool.ts +80 -12
  226. package/src/resources/extensions/gsd/bootstrap/tool-call-loop-guard.ts +12 -2
  227. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +23 -1
  228. package/src/resources/extensions/gsd/codebase-generator.ts +179 -18
  229. package/src/resources/extensions/gsd/commands/handlers/core.ts +39 -3
  230. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +135 -1
  231. package/src/resources/extensions/gsd/commands-worktree.ts +13 -2
  232. package/src/resources/extensions/gsd/db/queries.ts +40 -0
  233. package/src/resources/extensions/gsd/discussion-handoff.ts +10 -2
  234. package/src/resources/extensions/gsd/docs/preferences-reference.md +8 -2
  235. package/src/resources/extensions/gsd/doctor-engine-checks.ts +269 -18
  236. package/src/resources/extensions/gsd/doctor-providers.ts +39 -10
  237. package/src/resources/extensions/gsd/doctor-types.ts +5 -1
  238. package/src/resources/extensions/gsd/doctor.ts +95 -3
  239. package/src/resources/extensions/gsd/exec-sandbox.ts +6 -2
  240. package/src/resources/extensions/gsd/flat-phase-migration.ts +98 -22
  241. package/src/resources/extensions/gsd/git-service.ts +14 -17
  242. package/src/resources/extensions/gsd/gsd-db.ts +72 -0
  243. package/src/resources/extensions/gsd/guidance.ts +2 -0
  244. package/src/resources/extensions/gsd/guided-flow.ts +45 -2
  245. package/src/resources/extensions/gsd/key-manager.ts +1 -0
  246. package/src/resources/extensions/gsd/markdown-renderer.ts +25 -7
  247. package/src/resources/extensions/gsd/migrate-external.ts +23 -6
  248. package/src/resources/extensions/gsd/milestone-merge-transaction.ts +4 -4
  249. package/src/resources/extensions/gsd/model-router.ts +4 -5
  250. package/src/resources/extensions/gsd/preferences-diagnostics.ts +24 -0
  251. package/src/resources/extensions/gsd/preferences-models.ts +6 -3
  252. package/src/resources/extensions/gsd/preferences-validation.ts +28 -2
  253. package/src/resources/extensions/gsd/preferences.ts +38 -4
  254. package/src/resources/extensions/gsd/prompts/plan-slice.md +4 -2
  255. package/src/resources/extensions/gsd/redact-secrets.ts +40 -0
  256. package/src/resources/extensions/gsd/repo-identity.ts +17 -0
  257. package/src/resources/extensions/gsd/repository-registry.ts +7 -1
  258. package/src/resources/extensions/gsd/root-write-leak-guard.ts +3 -2
  259. package/src/resources/extensions/gsd/safe-fs.ts +22 -2
  260. package/src/resources/extensions/gsd/skills/gsd-headless/SKILL.md +7 -9
  261. package/src/resources/extensions/gsd/skills/gsd-headless/references/commands.md +1 -0
  262. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +131 -5
  263. package/src/resources/extensions/gsd/tests/auto-migrating-recovery.test.ts +18 -0
  264. package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +178 -0
  265. package/src/resources/extensions/gsd/tests/auto-phases-lifecycle.test.ts +43 -1
  266. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +54 -0
  267. package/src/resources/extensions/gsd/tests/auto-remote-session-lock-cleanup.test.ts +41 -0
  268. package/src/resources/extensions/gsd/tests/auto-runtime-state.test.ts +41 -0
  269. package/src/resources/extensions/gsd/tests/auto-session-scope.test.ts +4 -4
  270. package/src/resources/extensions/gsd/tests/auto-start-bootstrap-await-3420.test.ts +3 -0
  271. package/src/resources/extensions/gsd/tests/auto-timeout-recovery-harness-abort.test.ts +55 -0
  272. package/src/resources/extensions/gsd/tests/auto-warning-noise-regression.test.ts +8 -0
  273. package/src/resources/extensions/gsd/tests/batch-task-query.test.ts +85 -0
  274. package/src/resources/extensions/gsd/tests/check-auto-start-ready-guard.test.ts +29 -0
  275. package/src/resources/extensions/gsd/tests/codebase-generator.test.ts +365 -0
  276. package/src/resources/extensions/gsd/tests/complete-milestone-excerpt.test.ts +62 -1
  277. package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +138 -1
  278. package/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts +397 -5
  279. package/src/resources/extensions/gsd/tests/doctor-workspace.test.ts +207 -0
  280. package/src/resources/extensions/gsd/tests/flat-phase-migration.test.ts +38 -1
  281. package/src/resources/extensions/gsd/tests/flat-rate-routing-guard.test.ts +87 -19
  282. package/src/resources/extensions/gsd/tests/gate-1b-orphan-discrimination.test.ts +89 -0
  283. package/src/resources/extensions/gsd/tests/guided-flow-session-isolation.test.ts +9 -0
  284. package/src/resources/extensions/gsd/tests/integration/state-machine-edge-cases.test.ts +4 -2
  285. package/src/resources/extensions/gsd/tests/markdown-renderer.test.ts +122 -0
  286. package/src/resources/extensions/gsd/tests/merge-conflict-stops-loop.test.ts +4 -4
  287. package/src/resources/extensions/gsd/tests/migrate-external-worktree.test.ts +116 -0
  288. package/src/resources/extensions/gsd/tests/milestone-merge-stash-restore.test.ts +152 -22
  289. package/src/resources/extensions/gsd/tests/milestone-merge-transaction.test.ts +1 -1
  290. package/src/resources/extensions/gsd/tests/pending-autostart-scope.test.ts +2 -2
  291. package/src/resources/extensions/gsd/tests/phases-merge-error-stops-auto.test.ts +50 -6
  292. package/src/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +17 -0
  293. package/src/resources/extensions/gsd/tests/plan-task.test.ts +264 -1
  294. package/src/resources/extensions/gsd/tests/preferences.test.ts +150 -0
  295. package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +142 -0
  296. package/src/resources/extensions/gsd/tests/recovery-verify-logs.test.ts +4 -4
  297. package/src/resources/extensions/gsd/tests/redact-secrets.test.ts +56 -0
  298. package/src/resources/extensions/gsd/tests/register-hooks-flat-phase-migration.test.ts +73 -0
  299. package/src/resources/extensions/gsd/tests/register-hooks-loop-guard-auto.test.ts +275 -0
  300. package/src/resources/extensions/gsd/tests/repository-registry.test.ts +41 -2
  301. package/src/resources/extensions/gsd/tests/require-slice-discussion-dispatch.test.ts +55 -0
  302. package/src/resources/extensions/gsd/tests/root-write-leak-guard.test.ts +31 -9
  303. package/src/resources/extensions/gsd/tests/schedule-wakeup-tool.test.ts +167 -0
  304. package/src/resources/extensions/gsd/tests/stalled-tool-recovery.test.ts +49 -0
  305. package/src/resources/extensions/gsd/tests/state-corruption-2945.test.ts +1 -1
  306. package/src/resources/extensions/gsd/tests/status-per-repo.test.ts +94 -0
  307. package/src/resources/extensions/gsd/tests/token-tool-gating.test.ts +35 -0
  308. package/src/resources/extensions/gsd/tests/tool-call-loop-guard.test.ts +63 -0
  309. package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +66 -18
  310. package/src/resources/extensions/gsd/tests/unit-registry.test.ts +56 -0
  311. package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +30 -0
  312. package/src/resources/extensions/gsd/tests/uok-gitops-turn-action.test.ts +52 -0
  313. package/src/resources/extensions/gsd/tests/validator-scope-parity.test.ts +53 -1
  314. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +208 -1
  315. package/src/resources/extensions/gsd/tests/workspace.test.ts +18 -4
  316. package/src/resources/extensions/gsd/tests/worktree-journal-events.test.ts +4 -4
  317. package/src/resources/extensions/gsd/tests/worktree-lifecycle.test.ts +2 -2
  318. package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +31 -1
  319. package/src/resources/extensions/gsd/tests/worktree-teardown-safety.test.ts +32 -0
  320. package/src/resources/extensions/gsd/tools/plan-task.ts +107 -9
  321. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +58 -0
  322. package/src/resources/extensions/gsd/unit-context-manifest.ts +11 -5
  323. package/src/resources/extensions/gsd/unit-registry.ts +34 -2
  324. package/src/resources/extensions/gsd/unit-runtime.ts +42 -0
  325. package/src/resources/extensions/gsd/workspace.ts +42 -8
  326. package/src/resources/extensions/gsd/worktree-lifecycle.ts +135 -11
  327. package/src/resources/extensions/gsd/worktree-manager.ts +143 -5
  328. /package/dist/web/standalone/.next/static/{zQLcXm0Za8lexoXtgDpMG → bCg3aYrFN1DKZr2dqgE7L}/_buildManifest.js +0 -0
  329. /package/dist/web/standalone/.next/static/{zQLcXm0Za8lexoXtgDpMG → bCg3aYrFN1DKZr2dqgE7L}/_ssgManifest.js +0 -0
@@ -0,0 +1,207 @@
1
+ /**
2
+ * doctor-workspace.test.ts — Parent-workspace declared-repository probe (#818).
3
+ *
4
+ * Covers:
5
+ * - declared child repo path missing on disk → workspace_repo_path_missing
6
+ * - declared child repo path exists but is not a git repo → workspace_repo_not_a_repo
7
+ * - valid child repos → no workspace issues
8
+ * - single-repo (project-mode) project → probe is a no-op
9
+ * - non-git parent root is handled gracefully (the common layout)
10
+ */
11
+
12
+ import test from "node:test";
13
+ import assert from "node:assert/strict";
14
+ import { chmodSync, mkdtempSync, mkdirSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
15
+ import { delimiter, join } from "node:path";
16
+ import { tmpdir } from "node:os";
17
+ import { execFileSync } from "node:child_process";
18
+
19
+ import { runGSDDoctor } from "../doctor.ts";
20
+ import { GIT_NO_PROMPT_ENV } from "../git-constants.ts";
21
+
22
+ function gitInit(cwd: string): void {
23
+ execFileSync("git", ["init"], { cwd, stdio: "ignore" });
24
+ }
25
+
26
+ function writeParentPrefs(base: string, repos: Record<string, { path: string }>): void {
27
+ const repoLines = Object.entries(repos)
28
+ .map(([id, cfg]) => ` ${id}:\n path: ${cfg.path}`)
29
+ .join("\n");
30
+ writeFileSync(
31
+ join(base, ".gsd", "PREFERENCES.md"),
32
+ `---\nversion: 1\nworkspace:\n mode: parent\n repositories:\n${repoLines}\n---\n`,
33
+ "utf-8",
34
+ );
35
+ }
36
+
37
+ test("doctor flags a declared child repo whose path is missing on disk", async () => {
38
+ const base = mkdtempSync(join(tmpdir(), "gsd-doctor-ws-missing-"));
39
+ try {
40
+ mkdirSync(join(base, ".gsd"), { recursive: true });
41
+ gitInit(base);
42
+ // `frontend` is declared but never created on disk.
43
+ writeParentPrefs(base, { frontend: { path: "frontend" } });
44
+
45
+ const report = await runGSDDoctor(base);
46
+ const issue = report.issues.find((i) => i.code === "workspace_repo_path_missing");
47
+ assert.ok(issue, "expected a workspace_repo_path_missing issue");
48
+ assert.equal(issue?.unitId, "workspace.repositories.frontend");
49
+ assert.match(issue?.message ?? "", /does not exist on disk/);
50
+ } finally {
51
+ rmSync(base, { recursive: true, force: true });
52
+ }
53
+ });
54
+
55
+ test("doctor flags a declared child repo path that is not a git repository", async () => {
56
+ const base = mkdtempSync(join(tmpdir(), "gsd-doctor-ws-notrepo-"));
57
+ try {
58
+ mkdirSync(join(base, ".gsd"), { recursive: true });
59
+ gitInit(base);
60
+ // `backend` exists as a plain directory but is not a git repo.
61
+ mkdirSync(join(base, "backend"), { recursive: true });
62
+ writeParentPrefs(base, { backend: { path: "backend" } });
63
+
64
+ const report = await runGSDDoctor(base);
65
+ const issue = report.issues.find((i) => i.code === "workspace_repo_not_a_repo");
66
+ assert.ok(issue, "expected a workspace_repo_not_a_repo issue");
67
+ assert.equal(issue?.unitId, "workspace.repositories.backend");
68
+ assert.match(issue?.message ?? "", /not a git repository/);
69
+ } finally {
70
+ rmSync(base, { recursive: true, force: true });
71
+ }
72
+ });
73
+
74
+ test("doctor reports no workspace issues when declared child repos are valid", async () => {
75
+ const base = mkdtempSync(join(tmpdir(), "gsd-doctor-ws-valid-"));
76
+ try {
77
+ mkdirSync(join(base, ".gsd"), { recursive: true });
78
+ gitInit(base);
79
+ mkdirSync(join(base, "frontend"), { recursive: true });
80
+ gitInit(join(base, "frontend"));
81
+ writeParentPrefs(base, { frontend: { path: "frontend" } });
82
+
83
+ const report = await runGSDDoctor(base);
84
+ const wsIssues = report.issues.filter(
85
+ (i) => i.code === "workspace_repo_path_missing" || i.code === "workspace_repo_not_a_repo",
86
+ );
87
+ assert.deepEqual(wsIssues, []);
88
+ } finally {
89
+ rmSync(base, { recursive: true, force: true });
90
+ }
91
+ });
92
+
93
+ test("doctor workspace git probe uses safe env and canonical toplevel comparison", async (t) => {
94
+ if (process.platform === "win32") {
95
+ t.skip("POSIX-only git shim regression");
96
+ return;
97
+ }
98
+
99
+ const originalProcessGitDir = process.env.GIT_DIR;
100
+ const originalProcessGitWorkTree = process.env.GIT_WORK_TREE;
101
+ const originalGitEnvPath = GIT_NO_PROMPT_ENV.PATH;
102
+ const originalGitEnvRealGit = GIT_NO_PROMPT_ENV.GSD_REAL_GIT;
103
+ const originalGitEnvFakeCwd = GIT_NO_PROMPT_ENV.GSD_FAKE_TOPLEVEL_CWD;
104
+ const originalGitEnvFakeToplevel = GIT_NO_PROMPT_ENV.GSD_FAKE_TOPLEVEL;
105
+ const base = mkdtempSync(join(tmpdir(), "gsd-doctor-ws-safeenv-"));
106
+
107
+ t.after(() => {
108
+ if (originalProcessGitDir === undefined) delete process.env.GIT_DIR;
109
+ else process.env.GIT_DIR = originalProcessGitDir;
110
+ if (originalProcessGitWorkTree === undefined) delete process.env.GIT_WORK_TREE;
111
+ else process.env.GIT_WORK_TREE = originalProcessGitWorkTree;
112
+ if (originalGitEnvPath === undefined) delete GIT_NO_PROMPT_ENV.PATH;
113
+ else GIT_NO_PROMPT_ENV.PATH = originalGitEnvPath;
114
+ if (originalGitEnvRealGit === undefined) delete GIT_NO_PROMPT_ENV.GSD_REAL_GIT;
115
+ else GIT_NO_PROMPT_ENV.GSD_REAL_GIT = originalGitEnvRealGit;
116
+ if (originalGitEnvFakeCwd === undefined) delete GIT_NO_PROMPT_ENV.GSD_FAKE_TOPLEVEL_CWD;
117
+ else GIT_NO_PROMPT_ENV.GSD_FAKE_TOPLEVEL_CWD = originalGitEnvFakeCwd;
118
+ if (originalGitEnvFakeToplevel === undefined) delete GIT_NO_PROMPT_ENV.GSD_FAKE_TOPLEVEL;
119
+ else GIT_NO_PROMPT_ENV.GSD_FAKE_TOPLEVEL = originalGitEnvFakeToplevel;
120
+ rmSync(base, { recursive: true, force: true });
121
+ });
122
+
123
+ mkdirSync(join(base, ".gsd"), { recursive: true });
124
+ const realChild = join(base, "frontend-real");
125
+ const linkedChild = join(base, "frontend");
126
+ mkdirSync(realChild, { recursive: true });
127
+ gitInit(realChild);
128
+ symlinkSync(realChild, linkedChild, "dir");
129
+ writeParentPrefs(base, { frontend: { path: "frontend" } });
130
+
131
+ const shimDir = join(base, "bin");
132
+ mkdirSync(shimDir, { recursive: true });
133
+ const realGit = execFileSync("which", ["git"], { encoding: "utf-8" }).trim();
134
+ const shim = join(shimDir, "git");
135
+ writeFileSync(
136
+ shim,
137
+ [
138
+ "#!/usr/bin/env bash",
139
+ "set -euo pipefail",
140
+ "if [ -n \"${GIT_DIR:-}\" ] || [ -n \"${GIT_WORK_TREE:-}\" ]; then",
141
+ " echo \"leaked git env\" >&2",
142
+ " exit 97",
143
+ "fi",
144
+ "if [ \"${1:-}\" = \"rev-parse\" ] && [ \"${2:-}\" = \"--show-toplevel\" ] && [ \"$(pwd -P)\" = \"$GSD_FAKE_TOPLEVEL_CWD\" ]; then",
145
+ " printf '%s\\n' \"$GSD_FAKE_TOPLEVEL\"",
146
+ " exit 0",
147
+ "fi",
148
+ "exec \"$GSD_REAL_GIT\" \"$@\"",
149
+ "",
150
+ ].join("\n"),
151
+ "utf-8",
152
+ );
153
+ chmodSync(shim, 0o755);
154
+
155
+ process.env.GIT_DIR = join(base, ".git");
156
+ process.env.GIT_WORK_TREE = base;
157
+ GIT_NO_PROMPT_ENV.PATH = `${shimDir}${delimiter}${process.env.PATH ?? ""}`;
158
+ GIT_NO_PROMPT_ENV.GSD_REAL_GIT = realGit;
159
+ GIT_NO_PROMPT_ENV.GSD_FAKE_TOPLEVEL_CWD = realChild;
160
+ GIT_NO_PROMPT_ENV.GSD_FAKE_TOPLEVEL = linkedChild;
161
+
162
+ const report = await runGSDDoctor(base);
163
+ const wsIssues = report.issues.filter(
164
+ (i) => i.code === "workspace_repo_path_missing" || i.code === "workspace_repo_not_a_repo",
165
+ );
166
+ assert.deepEqual(wsIssues, []);
167
+ });
168
+
169
+ test("doctor workspace probe is a no-op for single-repo (project-mode) projects", async () => {
170
+ const base = mkdtempSync(join(tmpdir(), "gsd-doctor-ws-noproj-"));
171
+ try {
172
+ mkdirSync(join(base, ".gsd"), { recursive: true });
173
+ gitInit(base);
174
+ // No workspace config → project mode (default).
175
+ const report = await runGSDDoctor(base);
176
+ const wsIssues = report.issues.filter(
177
+ (i) => i.code === "workspace_repo_path_missing" || i.code === "workspace_repo_not_a_repo",
178
+ );
179
+ assert.deepEqual(wsIssues, []);
180
+ } finally {
181
+ rmSync(base, { recursive: true, force: true });
182
+ }
183
+ });
184
+
185
+ test("doctor runs cleanly when the parent root is itself not a git repo (common layout)", async () => {
186
+ // Regression guard (#818 cross-cutting): a parent folder holding child git
187
+ // repos need not be a git repo itself. Doctor must not crash and must not
188
+ // treat the parent as a missing/non-repo child.
189
+ const base = mkdtempSync(join(tmpdir(), "gsd-doctor-ws-nongitparent-"));
190
+ try {
191
+ mkdirSync(join(base, ".gsd"), { recursive: true });
192
+ // NOTE: no gitInit(base) — parent is a plain folder.
193
+ mkdirSync(join(base, "frontend"), { recursive: true });
194
+ gitInit(join(base, "frontend"));
195
+ writeParentPrefs(base, { frontend: { path: "frontend" } });
196
+
197
+ const report = await runGSDDoctor(base);
198
+ // The declared child repo is valid; the non-git parent must not produce a
199
+ // child-repo issue or crash the run.
200
+ const wsIssues = report.issues.filter(
201
+ (i) => i.code === "workspace_repo_path_missing" || i.code === "workspace_repo_not_a_repo",
202
+ );
203
+ assert.deepEqual(wsIssues, []);
204
+ } finally {
205
+ rmSync(base, { recursive: true, force: true });
206
+ }
207
+ });
@@ -2,12 +2,13 @@
2
2
  // File Purpose: Tests the one-time migration from nested to flat-phase layout.
3
3
  import test, { afterEach } from "node:test";
4
4
  import assert from "node:assert/strict";
5
- import { mkdtempSync, mkdirSync, rmSync, writeFileSync, existsSync, readdirSync, readFileSync, utimesSync } from "node:fs";
5
+ import { mkdtempSync, mkdirSync, renameSync, rmSync, writeFileSync, existsSync, readdirSync, readFileSync, utimesSync } from "node:fs";
6
6
  import { join } from "node:path";
7
7
  import { tmpdir } from "node:os";
8
8
  import { randomUUID } from "node:crypto";
9
9
 
10
10
  import {
11
+ _setFlatPhaseMigrationFsOpsForTest,
11
12
  migrateToFlatPhase,
12
13
  needsFlatPhaseMigration,
13
14
  pruneStaleFlatPhaseBackups,
@@ -64,6 +65,42 @@ test("migrateToFlatPhase moves content from milestones/ to phases/", async () =>
64
65
  assert.ok(!existsSync(join(base, ".gsd", "milestones")), "milestones/ should be removed");
65
66
  });
66
67
 
68
+ test("migrateToFlatPhase ignores and removes stale phase dirs from prior aborted runs", async () => {
69
+ const base = makeTmp();
70
+ const stalePhaseDir = join(base, ".gsd", "phases", "99-stale-aborted-run");
71
+ mkdirSync(stalePhaseDir, { recursive: true });
72
+
73
+ await migrateToFlatPhase(base);
74
+
75
+ assert.ok(existsSync(join(base, ".gsd", "phases", "01-foundation")), "current DB phase should render");
76
+ assert.equal(existsSync(stalePhaseDir), false, "stale pre-existing phase dir should be removed");
77
+ });
78
+
79
+ test("migrateToFlatPhase falls back to copy-delete when legacy rename is blocked", async (t) => {
80
+ const base = makeTmp();
81
+ const milestonesPath = join(base, ".gsd", "milestones");
82
+ const migratingPath = join(base, ".gsd", "milestones.migrating");
83
+ let blockedRenameAttempts = 0;
84
+
85
+ const restoreFsOps = _setFlatPhaseMigrationFsOpsForTest({
86
+ renameSync(src, dst) {
87
+ if (src === milestonesPath && dst === migratingPath) {
88
+ blockedRenameAttempts++;
89
+ throw Object.assign(new Error("simulated busy handle"), { code: "EPERM" });
90
+ }
91
+ return renameSync(src, dst);
92
+ },
93
+ });
94
+ t.after(restoreFsOps);
95
+
96
+ await migrateToFlatPhase(base);
97
+
98
+ assert.equal(blockedRenameAttempts, 1, "test should exercise the rename fallback path");
99
+ assert.ok(existsSync(join(base, ".gsd", "phases", "01-foundation")), "flat phase should render");
100
+ assert.equal(existsSync(milestonesPath), false, "legacy milestones dir should be removed");
101
+ assert.equal(existsSync(migratingPath), false, "staging dir should be removed after success");
102
+ });
103
+
67
104
  test("migrateToFlatPhase creates a backup", async () => {
68
105
  const base = makeTmp();
69
106
  await migrateToFlatPhase(base);
@@ -1,7 +1,6 @@
1
1
  /**
2
- * Regression test for #3453: dynamic model routing must be disabled for
3
- * flat-rate providers like GitHub Copilot where all models cost the same
4
- * per request — routing only degrades quality with no cost benefit.
2
+ * Regression tests for flat-rate provider detection and the explicit opt-out
3
+ * that suppresses dynamic routing for subscription providers.
5
4
  */
6
5
 
7
6
  import { describe, test } from "node:test";
@@ -9,7 +8,7 @@ import assert from "node:assert/strict";
9
8
  import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from "node:fs";
10
9
  import { tmpdir } from "node:os";
11
10
  import { join } from "node:path";
12
- import { buildFlatRateContext, isFlatRateProvider, resolvePreferredModelConfig } from "../auto-model-selection.ts";
11
+ import { buildFlatRateContext, isFlatRateProvider, resolvePreferredModelConfig, selectAndApplyModel } from "../auto-model-selection.ts";
13
12
 
14
13
  describe("flat-rate provider routing guard (#3453)", () => {
15
14
 
@@ -35,23 +34,18 @@ describe("flat-rate provider routing guard (#3453)", () => {
35
34
  assert.equal(isFlatRateProvider("openai"), false);
36
35
  });
37
36
 
38
- test("resolvePreferredModelConfig returns undefined for copilot start model", () => {
37
+ test("resolvePreferredModelConfig synthesizes routing config for copilot start model when routing is enabled", () => {
39
38
  const originalCwd = process.cwd();
40
39
  const originalGsdHome = process.env.GSD_HOME;
41
40
  const tempProject = mkdtempSync(join(tmpdir(), "gsd-flat-rate-project-"));
42
41
  const tempGsdHome = mkdtempSync(join(tmpdir(), "gsd-flat-rate-home-"));
43
42
 
44
- // When the user's start model is on a flat-rate provider,
45
- // resolvePreferredModelConfig should not synthesize a routing
46
- // config from tier_models — it should return undefined so the
47
- // user's selected model is preserved.
48
43
  try {
49
44
  mkdirSync(join(tempProject, ".gsd"), { recursive: true });
50
45
  writeFileSync(
51
46
  join(tempProject, ".gsd", "PREFERENCES.md"),
52
47
  [
53
48
  "---",
54
- "token_profile: burn-max",
55
49
  "dynamic_routing:",
56
50
  " enabled: true",
57
51
  " tier_models:",
@@ -70,10 +64,83 @@ describe("flat-rate provider routing guard (#3453)", () => {
70
64
  id: "claude-sonnet-4",
71
65
  });
72
66
 
73
- // Should be undefined (no routing config created for flat-rate)
74
- // Note: this only tests the synthesis guard — explicit per-unit config
75
- // still takes precedence when the user configured one.
76
- assert.equal(result, undefined, "Should not create routing config for copilot");
67
+ assert.deepEqual(result, {
68
+ primary: "claude-opus-4-6",
69
+ fallbacks: [],
70
+ source: "synthesized",
71
+ });
72
+ } finally {
73
+ process.chdir(originalCwd);
74
+ if (originalGsdHome === undefined) delete process.env.GSD_HOME;
75
+ else process.env.GSD_HOME = originalGsdHome;
76
+ rmSync(tempProject, { recursive: true, force: true });
77
+ rmSync(tempGsdHome, { recursive: true, force: true });
78
+ }
79
+ });
80
+
81
+ test("selectAndApplyModel runs synthesized dynamic routing for flat-rate start model", async () => {
82
+ const originalCwd = process.cwd();
83
+ const originalGsdHome = process.env.GSD_HOME;
84
+ const tempProject = mkdtempSync(join(tmpdir(), "gsd-flat-rate-project-"));
85
+ const tempGsdHome = mkdtempSync(join(tmpdir(), "gsd-flat-rate-home-"));
86
+ const setModelCalls: string[] = [];
87
+
88
+ try {
89
+ mkdirSync(join(tempProject, ".gsd"), { recursive: true });
90
+ writeFileSync(
91
+ join(tempProject, ".gsd", "PREFERENCES.md"),
92
+ [
93
+ "---",
94
+ "dynamic_routing:",
95
+ " enabled: true",
96
+ " hooks: false",
97
+ " budget_pressure: false",
98
+ " tier_models:",
99
+ " light: claude-haiku-4-5",
100
+ " standard: claude-sonnet-4-6",
101
+ " heavy: claude-opus-4-6",
102
+ "---",
103
+ ].join("\n"),
104
+ "utf-8",
105
+ );
106
+ process.env.GSD_HOME = tempGsdHome;
107
+ process.chdir(tempProject);
108
+
109
+ const availableModels = [
110
+ { id: "claude-haiku-4-5", provider: "claude-code", api: "anthropic-messages" },
111
+ { id: "claude-sonnet-4-6", provider: "claude-code", api: "anthropic-messages" },
112
+ { id: "claude-opus-4-6", provider: "claude-code", api: "anthropic-messages" },
113
+ ];
114
+
115
+ const result = await selectAndApplyModel(
116
+ {
117
+ modelRegistry: { getAvailable: () => availableModels },
118
+ sessionManager: { getSessionId: () => "test-session" },
119
+ ui: { notify: () => {} },
120
+ model: { provider: "claude-code", id: "claude-opus-4-6", api: "anthropic-messages" },
121
+ } as any,
122
+ {
123
+ setModel: async (model: { provider: string; id: string }) => {
124
+ setModelCalls.push(`${model.provider}/${model.id}`);
125
+ return true;
126
+ },
127
+ emitBeforeModelSelect: async () => undefined,
128
+ getActiveTools: () => [],
129
+ emitAdjustToolSet: async () => undefined,
130
+ setActiveTools: () => {},
131
+ } as any,
132
+ "execute-task",
133
+ "M001/S01/T01",
134
+ tempProject,
135
+ undefined,
136
+ false,
137
+ { provider: "claude-code", id: "claude-opus-4-6" },
138
+ undefined,
139
+ true,
140
+ );
141
+
142
+ assert.deepEqual(setModelCalls, ["claude-code/claude-sonnet-4-6"]);
143
+ assert.deepEqual(result.routing, { tier: "standard", modelDowngraded: true });
77
144
  } finally {
78
145
  process.chdir(originalCwd);
79
146
  if (originalGsdHome === undefined) delete process.env.GSD_HOME;
@@ -220,9 +287,9 @@ describe("buildFlatRateContext()", () => {
220
287
  });
221
288
  });
222
289
 
223
- // ─── #4386: allow_flat_rate_providers opt-in ────────────────────────────────
290
+ // ─── #4386/#1115: allow_flat_rate_providers override ────────────────────────
224
291
 
225
- describe("flat-rate routing opt-in (#4386)", () => {
292
+ describe("flat-rate routing override (#4386/#1115)", () => {
226
293
  function withPrefs(prefsYaml: string, fn: () => void): void {
227
294
  const originalCwd = process.cwd();
228
295
  const originalGsdHome = process.env.GSD_HOME;
@@ -247,7 +314,7 @@ describe("flat-rate routing opt-in (#4386)", () => {
247
314
  }
248
315
  }
249
316
 
250
- test("default (opt-in absent): flat-rate start model still returns undefined", () => {
317
+ test("default (allow absent): flat-rate start model still synthesizes routing config", () => {
251
318
  withPrefs(
252
319
  [
253
320
  "dynamic_routing:",
@@ -260,12 +327,13 @@ describe("flat-rate routing opt-in (#4386)", () => {
260
327
  provider: "claude-code",
261
328
  id: "claude-opus-4-6",
262
329
  });
263
- assert.equal(result, undefined, "default must preserve #3453 bypass");
330
+ assert.ok(result, "routing config should be synthesized");
331
+ assert.equal(result!.primary, "claude-opus-4-6");
264
332
  },
265
333
  );
266
334
  });
267
335
 
268
- test("opt-in: synthesizes a routing config for flat-rate start model", () => {
336
+ test("explicit allow: synthesizes a routing config for flat-rate start model", () => {
269
337
  withPrefs(
270
338
  [
271
339
  "dynamic_routing:",
@@ -14,6 +14,7 @@ import { tmpdir } from "node:os";
14
14
 
15
15
  import {
16
16
  checkAutoStartAfterDiscuss,
17
+ _getPendingAutoStart,
17
18
  setPendingAutoStart,
18
19
  clearPendingAutoStart,
19
20
  } from "../guided-flow.ts";
@@ -21,6 +22,7 @@ import { drainLogs } from "../workflow-logger.ts";
21
22
  import {
22
23
  openDatabase,
23
24
  closeDatabase,
25
+ getMilestone,
24
26
  insertMilestone,
25
27
  } from "../gsd-db.ts";
26
28
 
@@ -59,6 +61,12 @@ function mkBase(): string {
59
61
  return base;
60
62
  }
61
63
 
64
+ function mkFlatBase(): string {
65
+ const base = mkdtempSync(join(tmpdir(), "gsd-gate1b-flat-"));
66
+ mkdirSync(join(base, ".gsd", "phases", "01-m001"), { recursive: true });
67
+ return base;
68
+ }
69
+
62
70
  function writeContext(base: string): void {
63
71
  writeFileSync(
64
72
  join(base, ".gsd", "milestones", "M001", "M001-CONTEXT.md"),
@@ -66,6 +74,13 @@ function writeContext(base: string): void {
66
74
  );
67
75
  }
68
76
 
77
+ function writeFlatContext(base: string): void {
78
+ writeFileSync(
79
+ join(base, ".gsd", "phases", "01-m001", "01-CONTEXT.md"),
80
+ "# M001: Test Milestone\n\nContext written by discuss phase.\n",
81
+ );
82
+ }
83
+
69
84
  describe("Gate 1b discussion handoff in checkAutoStartAfterDiscuss", () => {
70
85
  let base: string;
71
86
  let cap: MockCapture;
@@ -142,4 +157,78 @@ describe("Gate 1b discussion handoff in checkAutoStartAfterDiscuss", () => {
142
157
  );
143
158
  assert.equal(gate1bLog, undefined, "Gate 1b must not log when CONTEXT.md is absent");
144
159
  });
160
+
161
+ test("flat-phase CONTEXT.md accepts handoff without a legacy milestone context file", () => {
162
+ base = mkFlatBase();
163
+ openDatabase(":memory:");
164
+ insertMilestone({ id: "M001", title: "Test Milestone", status: "queued" });
165
+ writeFlatContext(base);
166
+
167
+ cap = mkCapture();
168
+ setPendingAutoStart(base, {
169
+ basePath: base,
170
+ milestoneId: "M001",
171
+ startAuto: false,
172
+ ctx: mkCtx(cap),
173
+ pi: mkPi(cap),
174
+ });
175
+
176
+ const result = checkAutoStartAfterDiscuss();
177
+
178
+ assert.equal(result, true, "flat-phase context must be found by the handoff");
179
+ assert.equal(cap.messages.length, 0, "startAuto=false must suppress scheduling");
180
+ assert.deepEqual(cap.notifies[0], {
181
+ msg: "Milestone M001 context captured. Continuing the planning pipeline.",
182
+ level: "success",
183
+ });
184
+ });
185
+
186
+ test("DB-unavailable recovery blocks handoff instead of reporting success", () => {
187
+ base = mkFlatBase();
188
+ writeFlatContext(base);
189
+
190
+ cap = mkCapture();
191
+ setPendingAutoStart(base, {
192
+ basePath: base,
193
+ milestoneId: "M001",
194
+ startAuto: false,
195
+ ctx: mkCtx(cap),
196
+ pi: mkPi(cap),
197
+ });
198
+
199
+ const result = checkAutoStartAfterDiscuss();
200
+
201
+ assert.equal(result, false, "handoff must fail closed when the DB row cannot be confirmed");
202
+ assert.equal(cap.messages.length, 0, "must not schedule auto-start when DB is unavailable");
203
+ assert.equal(
204
+ cap.notifies.some(n => n.level === "success"),
205
+ false,
206
+ "must not report a successful handoff when DB recovery was skipped",
207
+ );
208
+ assert.ok(_getPendingAutoStart(base), "pending handoff should remain for a later recovery attempt");
209
+ });
210
+
211
+ test("missing DB row with CONTEXT.md inserts a queued row and accepts handoff", () => {
212
+ base = mkFlatBase();
213
+ openDatabase(":memory:");
214
+ writeFlatContext(base);
215
+
216
+ cap = mkCapture();
217
+ setPendingAutoStart(base, {
218
+ basePath: base,
219
+ milestoneId: "M001",
220
+ startAuto: false,
221
+ ctx: mkCtx(cap),
222
+ pi: mkPi(cap),
223
+ });
224
+
225
+ const result = checkAutoStartAfterDiscuss();
226
+
227
+ assert.equal(result, true, "successful insert should recover the missing row");
228
+ assert.equal(getMilestone("M001")?.status, "queued");
229
+ assert.deepEqual(cap.notifies[0], {
230
+ msg: "Milestone M001 context captured. Continuing the planning pipeline.",
231
+ level: "success",
232
+ });
233
+ });
145
234
  });
@@ -20,6 +20,7 @@ import {
20
20
  clearPendingAutoStart,
21
21
  checkAutoStartAfterDiscuss,
22
22
  } from "../guided-flow.ts";
23
+ import { closeDatabase, openDatabase } from "../gsd-db.ts";
23
24
 
24
25
  function pendingInput(basePath: string, milestoneId: string) {
25
26
  return {
@@ -34,6 +35,7 @@ function pendingInput(basePath: string, milestoneId: string) {
34
35
 
35
36
  describe("#2985 Bug 3 — concurrent discuss sessions must be independent", () => {
36
37
  beforeEach(() => {
38
+ closeDatabase();
37
39
  clearPendingAutoStart();
38
40
  });
39
41
 
@@ -75,6 +77,7 @@ describe("#2985 Bug 3 — concurrent discuss sessions must be independent", () =
75
77
 
76
78
  describe("#2985 Bug 4 — getDiscussionMilestoneId must be keyed by basePath", () => {
77
79
  beforeEach(() => {
80
+ closeDatabase();
78
81
  clearPendingAutoStart();
79
82
  });
80
83
 
@@ -110,6 +113,7 @@ describe("#2985 Bug 4 — getDiscussionMilestoneId must be keyed by basePath", (
110
113
  test("checkAutoStartAfterDiscuss ignores missing manifest for single-milestone discuss on established project", () => {
111
114
  const base = mkdtempSync(join(tmpdir(), "gsd-auto-start-manifest-"));
112
115
  try {
116
+ openDatabase(":memory:");
113
117
  const gsdDir = join(base, ".gsd");
114
118
  const milestoneDir = join(gsdDir, "milestones", "M001");
115
119
  mkdirSync(milestoneDir, { recursive: true });
@@ -132,6 +136,7 @@ test("checkAutoStartAfterDiscuss ignores missing manifest for single-milestone d
132
136
  const started = checkAutoStartAfterDiscuss();
133
137
  assert.equal(started, true, "project history alone should not require a manifest");
134
138
  } finally {
139
+ closeDatabase();
135
140
  clearPendingAutoStart();
136
141
  rmSync(base, { recursive: true, force: true });
137
142
  }
@@ -151,6 +156,7 @@ test("checkAutoStartAfterDiscuss(basePath) selects the matching pending entry wh
151
156
  }
152
157
 
153
158
  try {
159
+ openDatabase(":memory:");
154
160
  clearPendingAutoStart();
155
161
  writeReadyArtifacts(projectA, "M001");
156
162
  writeReadyArtifacts(projectB, "M002");
@@ -172,6 +178,7 @@ test("checkAutoStartAfterDiscuss(basePath) selects the matching pending entry wh
172
178
  assert.equal(getDiscussionMilestoneId(projectA), "M001", "projectA should remain pending");
173
179
  assert.equal(getDiscussionMilestoneId(projectB), null, "projectB should be cleared after start");
174
180
  } finally {
181
+ closeDatabase();
175
182
  clearPendingAutoStart();
176
183
  rmSync(projectA, { recursive: true, force: true });
177
184
  rmSync(projectB, { recursive: true, force: true });
@@ -183,6 +190,7 @@ test("checkAutoStartAfterDiscuss can accept context handoff without scheduling a
183
190
  let waitForIdleCalls = 0;
184
191
  const notifications: string[] = [];
185
192
  try {
193
+ openDatabase(":memory:");
186
194
  const gsdDir = join(base, ".gsd");
187
195
  const milestoneDir = join(gsdDir, "milestones", "M001");
188
196
  mkdirSync(milestoneDir, { recursive: true });
@@ -215,6 +223,7 @@ test("checkAutoStartAfterDiscuss can accept context handoff without scheduling a
215
223
  assert.equal(waitForIdleCalls, 0, "headless-owned auto start must not schedule guided-flow auto");
216
224
  assert.equal(getDiscussionMilestoneId(base), null, "accepted handoff should still be cleared");
217
225
  } finally {
226
+ closeDatabase();
218
227
  clearPendingAutoStart();
219
228
  rmSync(base, { recursive: true, force: true });
220
229
  }
@@ -90,6 +90,8 @@ import { clearPathCache } from "../../paths.ts";
90
90
  // Fixture Helpers
91
91
  // ═══════════════════════════════════════════════════════════════════════════
92
92
 
93
+ const UAT_VERDICT_GATE_RULE_NAME = "uat-verdict-gate (non-PASS observed; closeout enforces)";
94
+
93
95
  function makeTempDir(): string {
94
96
  return mkdtempSync(join(tmpdir(), "gsd-edge-cases-"));
95
97
  }
@@ -280,7 +282,7 @@ function buildDispatchCtx(
280
282
 
281
283
  function getUatVerdictGate(): import("../../auto-dispatch.ts").DispatchRule {
282
284
  const rule = DISPATCH_RULES.find(
283
- r => r.name === "uat-verdict-gate (non-PASS blocks progression)",
285
+ r => r.name === UAT_VERDICT_GATE_RULE_NAME,
284
286
  );
285
287
  assert.ok(rule, "uat-verdict-gate rule should be registered");
286
288
  return rule;
@@ -867,7 +869,7 @@ describe("dispatch failure modes", () => {
867
869
  // Verify critical ordering constraints
868
870
  const summarizeIdx = ruleNames.indexOf("summarizing → complete-slice");
869
871
  const runUatIdx = ruleNames.indexOf("run-uat (post-completion)");
870
- const uatGateIdx = ruleNames.indexOf("uat-verdict-gate (non-PASS blocks progression)");
872
+ const uatGateIdx = ruleNames.indexOf(UAT_VERDICT_GATE_RULE_NAME);
871
873
  const executeIdx = ruleNames.indexOf("executing → execute-task");
872
874
 
873
875
  // summarizing should come before execute-task