gsd-pi 2.78.0-dev.aeeb2ca00 → 2.78.1-dev.0fdacd524

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 (471) hide show
  1. package/README.md +8 -7
  2. package/dist/bundled-resource-path.d.ts +7 -0
  3. package/dist/bundled-resource-path.js +34 -2
  4. package/dist/claude-cli-check.js +77 -38
  5. package/dist/cli-policy.d.ts +13 -0
  6. package/dist/cli-policy.js +17 -0
  7. package/dist/cli.js +95 -55
  8. package/dist/headless-query.d.ts +22 -0
  9. package/dist/headless-query.js +43 -8
  10. package/dist/headless.d.ts +10 -0
  11. package/dist/headless.js +16 -1
  12. package/dist/loader.js +9 -13
  13. package/dist/onboarding.d.ts +10 -0
  14. package/dist/onboarding.js +2 -2
  15. package/dist/provider-migrations.d.ts +2 -2
  16. package/dist/provider-migrations.js +5 -2
  17. package/dist/resource-loader.d.ts +5 -2
  18. package/dist/resource-loader.js +30 -13
  19. package/dist/resources/.managed-resources-content-hash +1 -0
  20. package/dist/resources/extensions/claude-code-cli/readiness.js +90 -46
  21. package/dist/resources/extensions/google-search/index.js +2 -6
  22. package/dist/resources/extensions/gsd/auto/loop.js +23 -0
  23. package/dist/resources/extensions/gsd/auto/phases.js +5 -13
  24. package/dist/resources/extensions/gsd/auto/run-unit.js +26 -12
  25. package/dist/resources/extensions/gsd/auto/session.js +5 -6
  26. package/dist/resources/extensions/gsd/auto-dashboard.js +3 -2
  27. package/dist/resources/extensions/gsd/auto-direct-dispatch.js +55 -21
  28. package/dist/resources/extensions/gsd/auto-dispatch.js +18 -6
  29. package/dist/resources/extensions/gsd/auto-prompts.js +69 -2
  30. package/dist/resources/extensions/gsd/auto-recovery.js +43 -4
  31. package/dist/resources/extensions/gsd/auto-runtime-state.js +31 -0
  32. package/dist/resources/extensions/gsd/auto-start.js +1 -1
  33. package/dist/resources/extensions/gsd/auto-tool-tracking.js +2 -2
  34. package/dist/resources/extensions/gsd/auto-worktree.js +60 -13
  35. package/dist/resources/extensions/gsd/auto.js +39 -14
  36. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +14 -2
  37. package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +7 -5
  38. package/dist/resources/extensions/gsd/bootstrap/query-tools.js +2 -2
  39. package/dist/resources/extensions/gsd/bootstrap/register-extension.js +5 -4
  40. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +112 -31
  41. package/dist/resources/extensions/gsd/bootstrap/register-shortcuts.js +11 -6
  42. package/dist/resources/extensions/gsd/bootstrap/subagent-input.js +22 -0
  43. package/dist/resources/extensions/gsd/bootstrap/system-context.js +45 -8
  44. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +121 -3
  45. package/dist/resources/extensions/gsd/commands/catalog.js +76 -5
  46. package/dist/resources/extensions/gsd/commands/handlers/core.js +23 -1
  47. package/dist/resources/extensions/gsd/commands/handlers/ops.js +8 -0
  48. package/dist/resources/extensions/gsd/commands-config.js +3 -2
  49. package/dist/resources/extensions/gsd/commands-extensions.js +46 -3
  50. package/dist/resources/extensions/gsd/commands-handlers.js +3 -2
  51. package/dist/resources/extensions/gsd/commands-mcp-status.js +3 -1
  52. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +10 -1
  53. package/dist/resources/extensions/gsd/commands-worktree.js +309 -0
  54. package/dist/resources/extensions/gsd/dashboard-overlay.js +1 -1
  55. package/dist/resources/extensions/gsd/docs/preferences-reference.md +10 -0
  56. package/dist/resources/extensions/gsd/doctor-providers.js +2 -1
  57. package/dist/resources/extensions/gsd/doctor-runtime-checks.js +39 -1
  58. package/dist/resources/extensions/gsd/error-classifier.js +1 -1
  59. package/dist/resources/extensions/gsd/forensics.js +10 -8
  60. package/dist/resources/extensions/gsd/git-service.js +12 -5
  61. package/dist/resources/extensions/gsd/gsd-db.js +11 -2
  62. package/dist/resources/extensions/gsd/guided-flow.js +25 -24
  63. package/dist/resources/extensions/gsd/home-dir.js +16 -0
  64. package/dist/resources/extensions/gsd/key-manager.js +2 -1
  65. package/dist/resources/extensions/gsd/memory-store.js +66 -31
  66. package/dist/resources/extensions/gsd/migrate/command.js +3 -2
  67. package/dist/resources/extensions/gsd/milestone-id-reservation.js +36 -0
  68. package/dist/resources/extensions/gsd/model-router.js +114 -9
  69. package/dist/resources/extensions/gsd/native-git-bridge.js +7 -1
  70. package/dist/resources/extensions/gsd/preferences-models.js +91 -15
  71. package/dist/resources/extensions/gsd/preferences-types.js +2 -0
  72. package/dist/resources/extensions/gsd/preferences-validation.js +32 -0
  73. package/dist/resources/extensions/gsd/preferences.js +5 -3
  74. package/dist/resources/extensions/gsd/prompt-loader.js +23 -12
  75. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +10 -0
  76. package/dist/resources/extensions/gsd/prompts/complete-slice.md +10 -0
  77. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +2 -0
  78. package/dist/resources/extensions/gsd/prompts/parallel-research-slices.md +2 -0
  79. package/dist/resources/extensions/gsd/prompts/plan-slice.md +10 -0
  80. package/dist/resources/extensions/gsd/prompts/refine-slice.md +10 -0
  81. package/dist/resources/extensions/gsd/prompts/rewrite-docs.md +2 -0
  82. package/dist/resources/extensions/gsd/slice-parallel-orchestrator.js +9 -3
  83. package/dist/resources/extensions/gsd/state.js +42 -0
  84. package/dist/resources/extensions/gsd/templates/PREFERENCES.md +1 -0
  85. package/dist/resources/extensions/gsd/tools/memory-tools.js +18 -1
  86. package/dist/resources/extensions/gsd/unit-context-manifest.js +29 -4
  87. package/dist/resources/extensions/gsd/visualizer-overlay.js +1 -1
  88. package/dist/resources/extensions/gsd/watch/header-renderer.js +3 -1
  89. package/dist/resources/extensions/gsd/worktree-command.js +26 -46
  90. package/dist/resources/extensions/gsd/worktree-manager.js +20 -1
  91. package/dist/resources/extensions/gsd/worktree-resolver.js +4 -13
  92. package/dist/resources/extensions/gsd/worktree-root.js +124 -0
  93. package/dist/resources/extensions/gsd/worktree-session-state.js +33 -0
  94. package/dist/resources/extensions/gsd/worktree.js +4 -115
  95. package/dist/resources/extensions/mcp-client/index.js +6 -9
  96. package/dist/resources/extensions/ollama/index.js +15 -2
  97. package/dist/resources/extensions/ollama/model-capabilities.js +31 -0
  98. package/dist/resources/extensions/ollama/ollama-client.js +40 -4
  99. package/dist/resources/extensions/slash-commands/create-extension.js +36 -22
  100. package/dist/resources/extensions/subagent/index.js +324 -178
  101. package/dist/resources/skills/create-gsd-extension/SKILL.md +9 -5
  102. package/dist/resources/skills/create-gsd-extension/references/custom-commands.md +1 -1
  103. package/dist/resources/skills/create-gsd-extension/references/custom-rendering.md +5 -5
  104. package/dist/resources/skills/create-gsd-extension/references/custom-tools.md +4 -4
  105. package/dist/resources/skills/create-gsd-extension/references/custom-ui.md +6 -6
  106. package/dist/resources/skills/create-gsd-extension/references/events-reference.md +3 -3
  107. package/dist/resources/skills/create-gsd-extension/references/packaging-distribution.md +1 -1
  108. package/dist/resources/skills/create-gsd-extension/references/remote-execution-overrides.md +3 -3
  109. package/dist/resources/skills/create-gsd-extension/workflows/create-extension.md +32 -12
  110. package/dist/resources/skills/lint/SKILL.md +4 -0
  111. package/dist/resources/skills/review/SKILL.md +4 -0
  112. package/dist/resources/skills/test/SKILL.md +3 -0
  113. package/dist/rtk-shared.d.ts +3 -0
  114. package/dist/rtk-shared.js +17 -0
  115. package/dist/rtk.d.ts +2 -5
  116. package/dist/rtk.js +3 -20
  117. package/dist/runtime-checks.d.ts +27 -0
  118. package/dist/runtime-checks.js +38 -0
  119. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  120. package/dist/web/standalone/.next/BUILD_ID +1 -1
  121. package/dist/web/standalone/.next/app-path-routes-manifest.json +13 -13
  122. package/dist/web/standalone/.next/build-manifest.json +3 -3
  123. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  124. package/dist/web/standalone/.next/react-loadable-manifest.json +44 -4
  125. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  126. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  127. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  128. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  129. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  130. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  131. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  132. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  133. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  134. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  135. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  136. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  137. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  138. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  139. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  140. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  141. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  142. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  143. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  144. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  145. package/dist/web/standalone/.next/server/app/api/session/events/route.js +4 -2
  146. package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
  147. package/dist/web/standalone/.next/server/app/index.html +1 -1
  148. package/dist/web/standalone/.next/server/app/index.rsc +2 -2
  149. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  150. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +2 -2
  151. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  152. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  153. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  154. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  155. package/dist/web/standalone/.next/server/app-paths-manifest.json +13 -13
  156. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  157. package/dist/web/standalone/.next/server/middleware-manifest.json +5 -5
  158. package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
  159. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  160. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  161. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  162. package/dist/web/standalone/.next/server/webpack-runtime.js +1 -1
  163. package/dist/web/standalone/.next/static/chunks/2556.0527fea66e123b7f.js +1 -0
  164. package/dist/web/standalone/.next/static/chunks/2824.08296bc2f9654698.js +1 -0
  165. package/dist/web/standalone/.next/static/chunks/3026.3af53b279375f082.js +1 -0
  166. package/dist/web/standalone/.next/static/chunks/315.6f68ae79b67d25cf.js +1 -0
  167. package/dist/web/standalone/.next/static/chunks/3497.4bfc60a3b3dea717.js +1 -0
  168. package/dist/web/standalone/.next/static/chunks/5516.4a07c872b5c3a663.js +1 -0
  169. package/dist/web/standalone/.next/static/chunks/8336.31b019697882acfb.js +10 -0
  170. package/dist/web/standalone/.next/static/chunks/8845.c9702695e8c5a9c5.js +2 -0
  171. package/dist/web/standalone/.next/static/chunks/9058.01ef3a463bda88f1.js +20 -0
  172. package/dist/web/standalone/.next/static/chunks/9441.1081da1125d1764f.js +1 -0
  173. package/dist/web/standalone/.next/static/chunks/app/{page-5b113fd32bc2a1c3.js → page-9bf2e0c50fb2ca05.js} +1 -1
  174. package/dist/web/standalone/.next/static/chunks/webpack-f9f0dc45e4f3ac10.js +1 -0
  175. package/dist/web/standalone/package.json +2 -1
  176. package/dist/welcome-screen.js +27 -1
  177. package/dist/worktree-cli.d.ts +1 -0
  178. package/dist/worktree-cli.js +9 -3
  179. package/dist/worktree-status-banner.d.ts +1 -0
  180. package/dist/worktree-status-banner.js +132 -0
  181. package/package.json +1 -3
  182. package/packages/daemon/package.json +2 -2
  183. package/packages/mcp-server/dist/alias-telemetry.d.ts +8 -0
  184. package/packages/mcp-server/dist/alias-telemetry.d.ts.map +1 -0
  185. package/packages/mcp-server/dist/alias-telemetry.js +30 -0
  186. package/packages/mcp-server/dist/alias-telemetry.js.map +1 -0
  187. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  188. package/packages/mcp-server/dist/workflow-tools.js +74 -46
  189. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  190. package/packages/mcp-server/package.json +2 -2
  191. package/packages/mcp-server/src/alias-telemetry.test.ts +78 -0
  192. package/packages/mcp-server/src/alias-telemetry.ts +30 -0
  193. package/packages/mcp-server/src/workflow-tools.test.ts +78 -0
  194. package/packages/mcp-server/src/workflow-tools.ts +93 -58
  195. package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
  196. package/packages/native/package.json +1 -1
  197. package/packages/native/tsconfig.tsbuildinfo +1 -1
  198. package/packages/pi-agent-core/package.json +1 -1
  199. package/packages/pi-agent-core/tsconfig.tsbuildinfo +1 -1
  200. package/packages/pi-ai/dist/providers/anthropic-shared.cache-breakpoint.test.d.ts +2 -0
  201. package/packages/pi-ai/dist/providers/anthropic-shared.cache-breakpoint.test.d.ts.map +1 -0
  202. package/packages/pi-ai/dist/providers/anthropic-shared.cache-breakpoint.test.js +231 -0
  203. package/packages/pi-ai/dist/providers/anthropic-shared.cache-breakpoint.test.js.map +1 -0
  204. package/packages/pi-ai/dist/providers/anthropic-shared.d.ts.map +1 -1
  205. package/packages/pi-ai/dist/providers/anthropic-shared.js +48 -19
  206. package/packages/pi-ai/dist/providers/anthropic-shared.js.map +1 -1
  207. package/packages/pi-ai/dist/types.d.ts +13 -0
  208. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  209. package/packages/pi-ai/dist/types.js.map +1 -1
  210. package/packages/pi-ai/dist/utils/repair-tool-json.d.ts.map +1 -1
  211. package/packages/pi-ai/dist/utils/repair-tool-json.js +24 -3
  212. package/packages/pi-ai/dist/utils/repair-tool-json.js.map +1 -1
  213. package/packages/pi-ai/dist/utils/tests/repair-tool-json.test.js +26 -0
  214. package/packages/pi-ai/dist/utils/tests/repair-tool-json.test.js.map +1 -1
  215. package/packages/pi-ai/package.json +1 -1
  216. package/packages/pi-ai/src/providers/anthropic-shared.cache-breakpoint.test.ts +289 -0
  217. package/packages/pi-ai/src/providers/anthropic-shared.ts +52 -20
  218. package/packages/pi-ai/src/types.ts +13 -0
  219. package/packages/pi-ai/src/utils/repair-tool-json.ts +24 -3
  220. package/packages/pi-ai/src/utils/tests/repair-tool-json.test.ts +32 -0
  221. package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
  222. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  223. package/packages/pi-coding-agent/dist/core/agent-session.js +6 -0
  224. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  225. package/packages/pi-coding-agent/dist/core/messages.d.ts.map +1 -1
  226. package/packages/pi-coding-agent/dist/core/messages.js +4 -0
  227. package/packages/pi-coding-agent/dist/core/messages.js.map +1 -1
  228. package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js +19 -2
  229. package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js.map +1 -1
  230. package/packages/pi-coding-agent/dist/core/model-registry.d.ts +10 -0
  231. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  232. package/packages/pi-coding-agent/dist/core/model-registry.js +18 -0
  233. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  234. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts +13 -0
  235. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
  236. package/packages/pi-coding-agent/dist/core/system-prompt.js +20 -16
  237. package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
  238. package/packages/pi-coding-agent/dist/core/token-telemetry.d.ts +37 -0
  239. package/packages/pi-coding-agent/dist/core/token-telemetry.d.ts.map +1 -0
  240. package/packages/pi-coding-agent/dist/core/token-telemetry.js +49 -0
  241. package/packages/pi-coding-agent/dist/core/token-telemetry.js.map +1 -0
  242. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.d.ts +2 -0
  243. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.d.ts.map +1 -0
  244. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.js +133 -0
  245. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.js.map +1 -0
  246. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js +1 -1
  247. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js.map +1 -1
  248. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js +14 -1
  249. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js.map +1 -1
  250. package/packages/pi-coding-agent/dist/tests/system-prompt-cache-stability.test.d.ts +2 -0
  251. package/packages/pi-coding-agent/dist/tests/system-prompt-cache-stability.test.d.ts.map +1 -0
  252. package/packages/pi-coding-agent/dist/tests/system-prompt-cache-stability.test.js +78 -0
  253. package/packages/pi-coding-agent/dist/tests/system-prompt-cache-stability.test.js.map +1 -0
  254. package/packages/pi-coding-agent/dist/tests/token-telemetry.test.d.ts +2 -0
  255. package/packages/pi-coding-agent/dist/tests/token-telemetry.test.d.ts.map +1 -0
  256. package/packages/pi-coding-agent/dist/tests/token-telemetry.test.js +181 -0
  257. package/packages/pi-coding-agent/dist/tests/token-telemetry.test.js.map +1 -0
  258. package/packages/pi-coding-agent/package.json +1 -1
  259. package/packages/pi-coding-agent/src/core/agent-session.ts +7 -0
  260. package/packages/pi-coding-agent/src/core/messages.ts +4 -0
  261. package/packages/pi-coding-agent/src/core/model-registry-auth-mode.test.ts +32 -2
  262. package/packages/pi-coding-agent/src/core/model-registry.ts +21 -0
  263. package/packages/pi-coding-agent/src/core/system-prompt.ts +33 -15
  264. package/packages/pi-coding-agent/src/core/token-telemetry.ts +77 -0
  265. package/packages/pi-coding-agent/src/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.ts +212 -0
  266. package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.test.ts +17 -1
  267. package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.ts +1 -1
  268. package/packages/pi-coding-agent/src/tests/system-prompt-cache-stability.test.ts +102 -0
  269. package/packages/pi-coding-agent/src/tests/token-telemetry.test.ts +200 -0
  270. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  271. package/packages/pi-tui/dist/__tests__/autocomplete.test.js +17 -3
  272. package/packages/pi-tui/dist/__tests__/autocomplete.test.js.map +1 -1
  273. package/packages/pi-tui/dist/components/__tests__/leak-fixes-runtime.test.d.ts +2 -0
  274. package/packages/pi-tui/dist/components/__tests__/leak-fixes-runtime.test.d.ts.map +1 -0
  275. package/packages/pi-tui/dist/components/__tests__/leak-fixes-runtime.test.js +161 -0
  276. package/packages/pi-tui/dist/components/__tests__/leak-fixes-runtime.test.js.map +1 -0
  277. package/packages/pi-tui/package.json +1 -1
  278. package/packages/pi-tui/src/__tests__/autocomplete.test.ts +20 -3
  279. package/packages/pi-tui/src/components/__tests__/leak-fixes-runtime.test.ts +219 -0
  280. package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
  281. package/packages/rpc-client/package.json +1 -1
  282. package/pkg/package.json +1 -1
  283. package/src/resources/extensions/claude-code-cli/readiness.ts +92 -47
  284. package/src/resources/extensions/google-search/index.ts +2 -9
  285. package/src/resources/extensions/gsd/auto/loop.ts +24 -2
  286. package/src/resources/extensions/gsd/auto/phases.ts +6 -14
  287. package/src/resources/extensions/gsd/auto/run-unit.ts +26 -12
  288. package/src/resources/extensions/gsd/auto/session.ts +5 -6
  289. package/src/resources/extensions/gsd/auto/types.ts +1 -0
  290. package/src/resources/extensions/gsd/auto-dashboard.ts +3 -2
  291. package/src/resources/extensions/gsd/auto-direct-dispatch.ts +60 -24
  292. package/src/resources/extensions/gsd/auto-dispatch.ts +18 -6
  293. package/src/resources/extensions/gsd/auto-prompts.ts +66 -2
  294. package/src/resources/extensions/gsd/auto-recovery.ts +46 -8
  295. package/src/resources/extensions/gsd/auto-runtime-state.ts +51 -0
  296. package/src/resources/extensions/gsd/auto-start.ts +1 -1
  297. package/src/resources/extensions/gsd/auto-tool-tracking.ts +2 -4
  298. package/src/resources/extensions/gsd/auto-worktree.ts +82 -12
  299. package/src/resources/extensions/gsd/auto.ts +37 -10
  300. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +15 -13
  301. package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +8 -7
  302. package/src/resources/extensions/gsd/bootstrap/query-tools.ts +2 -2
  303. package/src/resources/extensions/gsd/bootstrap/register-extension.ts +10 -9
  304. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +121 -31
  305. package/src/resources/extensions/gsd/bootstrap/register-shortcuts.ts +12 -6
  306. package/src/resources/extensions/gsd/bootstrap/subagent-input.ts +20 -0
  307. package/src/resources/extensions/gsd/bootstrap/system-context.ts +50 -8
  308. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +141 -11
  309. package/src/resources/extensions/gsd/commands/catalog.ts +82 -5
  310. package/src/resources/extensions/gsd/commands/handlers/core.ts +23 -1
  311. package/src/resources/extensions/gsd/commands/handlers/ops.ts +10 -0
  312. package/src/resources/extensions/gsd/commands-config.ts +3 -2
  313. package/src/resources/extensions/gsd/commands-extensions.ts +43 -3
  314. package/src/resources/extensions/gsd/commands-handlers.ts +3 -2
  315. package/src/resources/extensions/gsd/commands-mcp-status.ts +3 -1
  316. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +15 -1
  317. package/src/resources/extensions/gsd/commands-worktree.ts +383 -0
  318. package/src/resources/extensions/gsd/dashboard-overlay.ts +1 -1
  319. package/src/resources/extensions/gsd/docs/preferences-reference.md +10 -0
  320. package/src/resources/extensions/gsd/doctor-providers.ts +2 -1
  321. package/src/resources/extensions/gsd/doctor-runtime-checks.ts +39 -1
  322. package/src/resources/extensions/gsd/doctor-types.ts +3 -1
  323. package/src/resources/extensions/gsd/error-classifier.ts +1 -1
  324. package/src/resources/extensions/gsd/forensics.ts +12 -7
  325. package/src/resources/extensions/gsd/git-service.ts +13 -5
  326. package/src/resources/extensions/gsd/gsd-db.ts +12 -2
  327. package/src/resources/extensions/gsd/guided-flow.ts +27 -26
  328. package/src/resources/extensions/gsd/home-dir.ts +19 -0
  329. package/src/resources/extensions/gsd/journal.ts +4 -1
  330. package/src/resources/extensions/gsd/key-manager.ts +2 -1
  331. package/src/resources/extensions/gsd/memory-store.ts +81 -28
  332. package/src/resources/extensions/gsd/migrate/command.ts +3 -2
  333. package/src/resources/extensions/gsd/milestone-id-reservation.ts +47 -0
  334. package/src/resources/extensions/gsd/model-router.ts +172 -9
  335. package/src/resources/extensions/gsd/native-git-bridge.ts +7 -1
  336. package/src/resources/extensions/gsd/preferences-models.ts +101 -15
  337. package/src/resources/extensions/gsd/preferences-types.ts +6 -0
  338. package/src/resources/extensions/gsd/preferences-validation.ts +35 -0
  339. package/src/resources/extensions/gsd/preferences.ts +16 -2
  340. package/src/resources/extensions/gsd/prompt-loader.ts +26 -12
  341. package/src/resources/extensions/gsd/prompts/complete-milestone.md +10 -0
  342. package/src/resources/extensions/gsd/prompts/complete-slice.md +10 -0
  343. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +2 -0
  344. package/src/resources/extensions/gsd/prompts/parallel-research-slices.md +2 -0
  345. package/src/resources/extensions/gsd/prompts/plan-slice.md +10 -0
  346. package/src/resources/extensions/gsd/prompts/refine-slice.md +10 -0
  347. package/src/resources/extensions/gsd/prompts/rewrite-docs.md +2 -0
  348. package/src/resources/extensions/gsd/slice-parallel-orchestrator.ts +9 -3
  349. package/src/resources/extensions/gsd/state.ts +42 -0
  350. package/src/resources/extensions/gsd/templates/PREFERENCES.md +1 -0
  351. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +179 -1
  352. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +58 -0
  353. package/src/resources/extensions/gsd/tests/auto-session-encapsulation.test.ts +24 -5
  354. package/src/resources/extensions/gsd/tests/auto-supervisor.test.mjs +21 -4
  355. package/src/resources/extensions/gsd/tests/bootstrap-derive-state-db-open.test.ts +1 -1
  356. package/src/resources/extensions/gsd/tests/budget-prediction.test.ts +138 -211
  357. package/src/resources/extensions/gsd/tests/bundled-skill-triggers.test.ts +50 -27
  358. package/src/resources/extensions/gsd/tests/commands-extensions-version-compare.test.ts +58 -0
  359. package/src/resources/extensions/gsd/tests/commands-worktree-clean.test.ts +48 -0
  360. package/src/resources/extensions/gsd/tests/complete-slice-verification-gate.test.ts +142 -59
  361. package/src/resources/extensions/gsd/tests/complete-slice.test.ts +7 -4
  362. package/src/resources/extensions/gsd/tests/completed-at-reconcile.test.ts +89 -32
  363. package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +41 -23
  364. package/src/resources/extensions/gsd/tests/db-path-worktree-symlink.test.ts +3 -43
  365. package/src/resources/extensions/gsd/tests/debug-logger.test.ts +5 -3
  366. package/src/resources/extensions/gsd/tests/deferred-milestone-dir-4996.test.ts +116 -0
  367. package/src/resources/extensions/gsd/tests/discuss-empty-db-fallback.test.ts +22 -87
  368. package/src/resources/extensions/gsd/tests/discuss-queued-milestones.test.ts +7 -118
  369. package/src/resources/extensions/gsd/tests/discuss-tool-scope-leak.test.ts +18 -60
  370. package/src/resources/extensions/gsd/tests/doctor-orphan-milestone-4996.test.ts +100 -0
  371. package/src/resources/extensions/gsd/tests/double-merge-guard.test.ts +14 -76
  372. package/src/resources/extensions/gsd/tests/ensure-preconditions-guard-4996.test.ts +93 -0
  373. package/src/resources/extensions/gsd/tests/false-degraded-mode-warning.test.ts +22 -83
  374. package/src/resources/extensions/gsd/tests/finalize-timeout-guard.test.ts +1 -63
  375. package/src/resources/extensions/gsd/tests/find-missing-summaries-closed-runtime.test.ts +47 -0
  376. package/src/resources/extensions/gsd/tests/forensics-stuck-loops.test.ts +26 -1
  377. package/src/resources/extensions/gsd/tests/gitignore-bg-shell-runtime.test.ts +63 -0
  378. package/src/resources/extensions/gsd/tests/google-search-stub.test.ts +25 -65
  379. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +30 -0
  380. package/src/resources/extensions/gsd/tests/gsd-no-project-error-runtime.test.ts +81 -0
  381. package/src/resources/extensions/gsd/tests/headless-answers.test.ts +14 -4
  382. package/src/resources/extensions/gsd/tests/health-widget.test.ts +22 -12
  383. package/src/resources/extensions/gsd/tests/help-menu-coverage.test.ts +57 -0
  384. package/src/resources/extensions/gsd/tests/home-dir.test.ts +52 -0
  385. package/src/resources/extensions/gsd/tests/import-done-milestones-runtime.test.ts +145 -0
  386. package/src/resources/extensions/gsd/tests/init-prefs-routing.test.ts +64 -1
  387. package/src/resources/extensions/gsd/tests/integration/auto-worktree.test.ts +72 -1
  388. package/src/resources/extensions/gsd/tests/integration/token-savings.test.ts +0 -23
  389. package/src/resources/extensions/gsd/tests/memory-store.test.ts +128 -0
  390. package/src/resources/extensions/gsd/tests/memory-tools.test.ts +33 -1
  391. package/src/resources/extensions/gsd/tests/merge-self-branch-guard.test.ts +124 -0
  392. package/src/resources/extensions/gsd/tests/milestone-id-gap-reuse-4996.test.ts +152 -0
  393. package/src/resources/extensions/gsd/tests/milestone-report-path.test.ts +18 -1
  394. package/src/resources/extensions/gsd/tests/model-router.test.ts +169 -8
  395. package/src/resources/extensions/gsd/tests/native-git-infra-errors.test.ts +50 -0
  396. package/src/resources/extensions/gsd/tests/orphaned-worktree-audit.test.ts +8 -0
  397. package/src/resources/extensions/gsd/tests/parallel-crash-recovery.test.ts +32 -43
  398. package/src/resources/extensions/gsd/tests/phases-merge-error-stops-auto.test.ts +4 -10
  399. package/src/resources/extensions/gsd/tests/preferences.test.ts +127 -0
  400. package/src/resources/extensions/gsd/tests/prompt-step-ordering.test.ts +16 -0
  401. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +7 -0
  402. package/src/resources/extensions/gsd/tests/quick-turn-end-cleanup.test.ts +6 -6
  403. package/src/resources/extensions/gsd/tests/register-hooks-compaction-checkpoint.test.ts +93 -0
  404. package/src/resources/extensions/gsd/tests/safety-harness-false-positives.test.ts +34 -0
  405. package/src/resources/extensions/gsd/tests/session-start-footer.test.ts +168 -19
  406. package/src/resources/extensions/gsd/tests/slice-parallel-orchestrator.test.ts +7 -1
  407. package/src/resources/extensions/gsd/tests/smart-entry-complete.test.ts +23 -1
  408. package/src/resources/extensions/gsd/tests/steer-worktree-path.test.ts +17 -1
  409. package/src/resources/extensions/gsd/tests/system-context-message-routing.test.ts +101 -0
  410. package/src/resources/extensions/gsd/tests/token-profile.test.ts +51 -4
  411. package/src/resources/extensions/gsd/tests/turn-epoch.test.ts +7 -16
  412. package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +38 -3
  413. package/src/resources/extensions/gsd/tests/unstructured-continue-context-injection.test.ts +5 -7
  414. package/src/resources/extensions/gsd/tests/uok-gitops-turn-action.test.ts +15 -1
  415. package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +6 -6
  416. package/src/resources/extensions/gsd/tests/worktree-path-injection.test.ts +235 -0
  417. package/src/resources/extensions/gsd/tests/worktree-symlink-removal.test.ts +34 -33
  418. package/src/resources/extensions/gsd/tests/worktree.test.ts +8 -0
  419. package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +131 -1
  420. package/src/resources/extensions/gsd/tools/memory-tools.ts +17 -1
  421. package/src/resources/extensions/gsd/unit-context-manifest.ts +44 -12
  422. package/src/resources/extensions/gsd/visualizer-overlay.ts +1 -1
  423. package/src/resources/extensions/gsd/watch/header-renderer.ts +3 -1
  424. package/src/resources/extensions/gsd/workflow-logger.ts +1 -0
  425. package/src/resources/extensions/gsd/worktree-command.ts +31 -44
  426. package/src/resources/extensions/gsd/worktree-manager.ts +40 -1
  427. package/src/resources/extensions/gsd/worktree-resolver.ts +4 -14
  428. package/src/resources/extensions/gsd/worktree-root.ts +144 -0
  429. package/src/resources/extensions/gsd/worktree-session-state.ts +35 -0
  430. package/src/resources/extensions/gsd/worktree.ts +8 -119
  431. package/src/resources/extensions/mcp-client/index.ts +6 -10
  432. package/src/resources/extensions/mcp-client/tests/global-config.test.ts +91 -0
  433. package/src/resources/extensions/ollama/index.ts +16 -2
  434. package/src/resources/extensions/ollama/model-capabilities.ts +34 -0
  435. package/src/resources/extensions/ollama/ollama-client.ts +41 -4
  436. package/src/resources/extensions/ollama/tests/model-capabilities.test.ts +96 -0
  437. package/src/resources/extensions/ollama/tests/ollama-client-timeout-env.test.ts +147 -0
  438. package/src/resources/extensions/slash-commands/create-extension.ts +38 -24
  439. package/src/resources/extensions/subagent/index.ts +165 -7
  440. package/src/resources/skills/create-gsd-extension/SKILL.md +9 -5
  441. package/src/resources/skills/create-gsd-extension/references/custom-commands.md +1 -1
  442. package/src/resources/skills/create-gsd-extension/references/custom-rendering.md +5 -5
  443. package/src/resources/skills/create-gsd-extension/references/custom-tools.md +4 -4
  444. package/src/resources/skills/create-gsd-extension/references/custom-ui.md +6 -6
  445. package/src/resources/skills/create-gsd-extension/references/events-reference.md +3 -3
  446. package/src/resources/skills/create-gsd-extension/references/packaging-distribution.md +1 -1
  447. package/src/resources/skills/create-gsd-extension/references/remote-execution-overrides.md +3 -3
  448. package/src/resources/skills/create-gsd-extension/templates/extension-skeleton.ts +2 -2
  449. package/src/resources/skills/create-gsd-extension/templates/stateful-tool-skeleton.ts +3 -3
  450. package/src/resources/skills/create-gsd-extension/templates/templates.test.ts +58 -0
  451. package/src/resources/skills/create-gsd-extension/workflows/create-extension.md +32 -12
  452. package/src/resources/skills/lint/SKILL.md +4 -0
  453. package/src/resources/skills/review/SKILL.md +4 -0
  454. package/src/resources/skills/test/SKILL.md +3 -0
  455. package/dist/resources/extensions/browser-tools/tests/browser-tools-integration.test.mjs +0 -601
  456. package/dist/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +0 -651
  457. package/dist/resources/extensions/browser-tools/tests/capture-sharp-optional.test.cjs +0 -91
  458. package/dist/resources/extensions/gsd/tests/auto-supervisor.test.mjs +0 -53
  459. package/dist/resources/extensions/gsd/tests/dist-redirect.mjs +0 -112
  460. package/dist/resources/extensions/gsd/tests/resolve-ts-hooks.mjs +0 -23
  461. package/dist/resources/extensions/gsd/tests/resolve-ts.mjs +0 -5
  462. package/dist/resources/skills/github-workflows/references/gh/tests/__init__.py +0 -0
  463. package/dist/resources/skills/github-workflows/references/gh/tests/test_github_project_setup.py +0 -608
  464. package/dist/web/standalone/.next/static/chunks/2826.e9f5195e91f9cad2.js +0 -11
  465. package/dist/web/standalone/.next/static/chunks/3621.fc7480022c972438.js +0 -20
  466. package/dist/web/standalone/.next/static/chunks/webpack-2e68521d7c82f7c2.js +0 -1
  467. package/src/resources/extensions/gsd/tests/copy-planning-artifacts-samepath.test.ts +0 -22
  468. package/src/resources/extensions/gsd/tests/discuss-slice-structured-questions.test.ts +0 -47
  469. package/src/resources/extensions/gsd/tests/empty-content-abort-loop.test.ts +0 -75
  470. /package/dist/web/standalone/.next/static/{cAJH99yNS1UPbeSEiNRrV → 4iu6IYeYfxOq8OidlDqp6}/_buildManifest.js +0 -0
  471. /package/dist/web/standalone/.next/static/{cAJH99yNS1UPbeSEiNRrV → 4iu6IYeYfxOq8OidlDqp6}/_ssgManifest.js +0 -0
@@ -144,19 +144,10 @@ test("recoverTimedOutUnit: no top-level bumpTurnGeneration — steering branches
144
144
  );
145
145
  });
146
146
 
147
- test("recoverTimedOutUnit: bumpAndResolveSynthetic appears exactly four times (one per advance branch)", async () => {
148
- const fs = await import("node:fs");
149
- const path = await import("node:path");
150
- const url = await import("node:url");
151
- const here = path.dirname(url.fileURLToPath(import.meta.url));
152
- const src = fs.readFileSync(
153
- path.join(here, "..", "auto-timeout-recovery.ts"),
154
- "utf-8",
155
- );
156
- const matches = src.match(/bumpAndResolveSynthetic\s*\(/g) ?? [];
157
- assert.equal(
158
- matches.length,
159
- 4,
160
- `expected 4 advance-branch supersede sites (durableComplete, execute-task-exhausted, artifact-already-exists, non-execute-exhausted); found ${matches.length}`,
161
- );
162
- });
147
+ // Removed: source-grep count of `bumpAndResolveSynthetic\s*\(` occurrences.
148
+ // A literal 4 hardcodes the current branch shape, not behaviour. The
149
+ // behavioural invariant "advance branches supersede atomically; non-advance
150
+ // branches do not bump" — is enforced by the previous test (no direct
151
+ // bumpTurnGeneration calls) plus the per-branch behavioural tests above
152
+ // (`recoverTimedOutUnit: …`). Refactors that split a branch into two would
153
+ // trip a count test without affecting correctness. Refs #4851.
@@ -199,8 +199,8 @@ test("#4934: every manifest declares a tools policy", () => {
199
199
  }
200
200
  });
201
201
 
202
- test("#4934: tools.mode is one of the four declared policies", () => {
203
- const validModes = new Set(["all", "read-only", "planning", "docs"]);
202
+ test("#4934: tools.mode is one of the declared policies", () => {
203
+ const validModes = new Set(["all", "read-only", "planning", "planning-dispatch", "docs"]);
204
204
  for (const [unitType, manifest] of Object.entries(UNIT_MANIFESTS)) {
205
205
  const mode = (manifest as { tools: { mode: string } }).tools.mode;
206
206
  assert.ok(
@@ -219,7 +219,42 @@ test('#4934: only execute-task and reactive-execute may use tools.mode "all" (fu
219
219
  allowedAllUnits.has(unitType),
220
220
  `manifest "${unitType}" declares tools.mode = "all" but is not on the execute-track. ` +
221
221
  'Only execute-task and reactive-execute should have full source write access; ' +
222
- 'planning/discuss/research units must use "planning" (or "docs" for rewrite-docs).',
222
+ 'planning/discuss/research units must use "planning" or "planning-dispatch" (or "docs" for rewrite-docs).',
223
+ );
224
+ }
225
+ }
226
+ });
227
+
228
+ test('planning-dispatch mode is reserved for slice-level decomposition and completion units', () => {
229
+ const allowedDispatchUnits = new Set([
230
+ "plan-slice",
231
+ "refine-slice",
232
+ "complete-slice",
233
+ "complete-milestone",
234
+ ]);
235
+ for (const [unitType, manifest] of Object.entries(UNIT_MANIFESTS)) {
236
+ const mode = (manifest as { tools: { mode: string } }).tools.mode;
237
+ if (mode === "planning-dispatch") {
238
+ assert.ok(
239
+ allowedDispatchUnits.has(unitType),
240
+ `manifest "${unitType}" declares tools.mode = "planning-dispatch" but is not on the dispatch-allowed allowlist. ` +
241
+ 'planning-dispatch is intentionally narrow — extend the allowlist consciously when a new unit type genuinely benefits from subagent delegation.',
242
+ );
243
+ }
244
+ }
245
+ });
246
+
247
+ test('planning-dispatch manifests declare non-empty allowedSubagents lists', () => {
248
+ for (const [unitType, manifest] of Object.entries(UNIT_MANIFESTS)) {
249
+ if (manifest.tools.mode !== "planning-dispatch") continue;
250
+ assert.ok(
251
+ Array.isArray(manifest.tools.allowedSubagents) && manifest.tools.allowedSubagents.length > 0,
252
+ `manifest "${unitType}" has planning-dispatch policy but no allowedSubagents — explicit allowlist is required for runtime dispatch gating`,
253
+ );
254
+ for (const agent of manifest.tools.allowedSubagents) {
255
+ assert.ok(
256
+ typeof agent === "string" && agent.length > 0,
257
+ `manifest "${unitType}" has empty/invalid allowedSubagents entry: ${JSON.stringify(agent)}`,
223
258
  );
224
259
  }
225
260
  }
@@ -76,13 +76,11 @@ describe("#3615 — structural: fallback exists with correct guards", () => {
76
76
  );
77
77
  });
78
78
 
79
- test("only one return null at the end", () => {
80
- const returnNulls = fnBody.match(/return null;/g);
81
- assert.ok(
82
- returnNulls && returnNulls.length === 1,
83
- `expected exactly 1 'return null' (at end after fallback), got ${returnNulls?.length ?? 0}`,
84
- );
85
- });
79
+ // Removed: source-grep count of `return null;` occurrences. The behaviour
80
+ // we care about ("function returns null only when no auto-dispatch /
81
+ // guided-resume / fallback path matches") is exercised by the behavioural
82
+ // tests below counting literal `return null;` tokens encodes statement
83
+ // shape, not contract. Refs #4851.
86
84
  });
87
85
 
88
86
  // ── Behavioral tests: RESUME_INTENT_PATTERNS ────────────────────────
@@ -4,7 +4,7 @@ import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
4
4
  import { tmpdir } from "node:os";
5
5
  import { join } from "node:path";
6
6
  import { execSync } from "node:child_process";
7
- import { runTurnGitAction } from "../git-service.ts";
7
+ import { handleTurnGitActionError, runTurnGitAction } from "../git-service.ts";
8
8
 
9
9
  function run(cmd: string, cwd: string): string {
10
10
  return execSync(cmd, { cwd, stdio: "pipe", encoding: "utf-8" }).trim();
@@ -83,3 +83,17 @@ test("uok gitops turn action commit creates commit with unit trailer", () => {
83
83
  rmSync(repo, { recursive: true, force: true });
84
84
  }
85
85
  });
86
+
87
+ test("uok gitops turn action rethrows infrastructure failures", () => {
88
+ const err = Object.assign(new Error("ENFILE: file table overflow"), { code: "ENFILE" });
89
+
90
+ assert.throws(() => handleTurnGitActionError("commit", err), (thrown) => thrown === err);
91
+ });
92
+
93
+ test("uok gitops turn action keeps non-infrastructure git failures recoverable", () => {
94
+ const result = handleTurnGitActionError("commit", new Error("nothing to commit"));
95
+
96
+ assert.equal(result.action, "commit");
97
+ assert.equal(result.status, "failed");
98
+ assert.equal(result.error, "nothing to commit");
99
+ });
@@ -13,7 +13,7 @@
13
13
  import { test } from "node:test";
14
14
  import assert from "node:assert/strict";
15
15
 
16
- import { GSDVisualizerOverlay } from "../visualizer-overlay.ts";
16
+ import { GSDVisualizerOverlay, TAB_COUNT } from "../visualizer-overlay.ts";
17
17
 
18
18
  function makeTui() {
19
19
  const renders: number[] = [];
@@ -93,16 +93,16 @@ test("overlay switches tabs via 1–9,0 digit keys", (t) => {
93
93
 
94
94
  test("overlay Tab key cycles forward and wraps around at TAB_COUNT", (t) => {
95
95
  const { overlay } = makeOverlay(t);
96
- overlay.activeTab = 9;
96
+ overlay.activeTab = TAB_COUNT - 1;
97
97
  overlay.handleInput("\t");
98
- assert.equal(overlay.activeTab, 0, "Tab wraps from 9 back to 0");
98
+ assert.equal(overlay.activeTab, 0, `Tab wraps from ${TAB_COUNT - 1} back to 0`);
99
99
  });
100
100
 
101
101
  test("overlay Shift+Tab cycles backward and wraps", (t) => {
102
102
  const { overlay } = makeOverlay(t);
103
103
  overlay.activeTab = 0;
104
104
  overlay.handleInput("\u001b[Z");
105
- assert.equal(overlay.activeTab, 9, "Shift+Tab wraps from 0 to 9");
105
+ assert.equal(overlay.activeTab, TAB_COUNT - 1, `Shift+Tab wraps from 0 to ${TAB_COUNT - 1}`);
106
106
  });
107
107
 
108
108
  // ─── Filter mode ─────────────────────────────────────────────────────────
@@ -331,9 +331,9 @@ test("overlay footer hint mentions tab navigation, filter, scroll, and help", (t
331
331
 
332
332
  // ─── Scroll offsets array is sized to TAB_COUNT ──────────────────────────
333
333
 
334
- test("overlay scrollOffsets array has one slot per tab (10 tabs total)", (t) => {
334
+ test("overlay scrollOffsets array has one slot per tab", (t) => {
335
335
  const { overlay } = makeOverlay(t);
336
- assert.equal(overlay.scrollOffsets.length, 10, "scrollOffsets sized to TAB_COUNT=10");
336
+ assert.equal(overlay.scrollOffsets.length, TAB_COUNT, "scrollOffsets sized to TAB_COUNT");
337
337
  assert.ok(overlay.scrollOffsets.every((n: number) => n === 0), "initialized to zero");
338
338
  });
339
339
 
@@ -0,0 +1,235 @@
1
+ import test, { after } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { mkdirSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from "node:fs";
4
+ import { tmpdir } from "node:os";
5
+ import { join } from "node:path";
6
+
7
+ const ownsGsdHome = process.env.GSD_HOME_TEST_OVERRIDE === undefined;
8
+ const previousGsdHome = process.env.GSD_HOME;
9
+ const synthesizedGsdHome = join(tmpdir(), `gsd-test-home-${process.pid}-${Date.now()}`);
10
+ process.env.GSD_HOME = process.env.GSD_HOME_TEST_OVERRIDE
11
+ ?? synthesizedGsdHome;
12
+
13
+ after(() => {
14
+ if (ownsGsdHome) {
15
+ rmSync(synthesizedGsdHome, { recursive: true, force: true });
16
+ }
17
+ if (previousGsdHome === undefined) {
18
+ delete process.env.GSD_HOME;
19
+ } else {
20
+ process.env.GSD_HOME = previousGsdHome;
21
+ }
22
+ });
23
+
24
+ const { dispatchDirectPhase } = await import("../auto-direct-dispatch.ts");
25
+ const {
26
+ buildDiscussMilestonePrompt,
27
+ buildParallelResearchSlicesPrompt,
28
+ buildRewriteDocsPrompt,
29
+ } = await import("../auto-prompts.ts");
30
+ const { invalidateStateCache } = await import("../state.ts");
31
+ const { resolveAgentEnd, runUnit, _resetPendingResolve } = await import("../auto-loop.ts");
32
+
33
+ function writeMilestone(base: string, mid = "M001", title = "Worktree Path Injection"): void {
34
+ const milestoneDir = join(base, ".gsd", "milestones", mid);
35
+ mkdirSync(milestoneDir, { recursive: true });
36
+ writeFileSync(
37
+ join(milestoneDir, `${mid}-CONTEXT.md`),
38
+ `# ${mid}: ${title}\n\nContext.\n`,
39
+ "utf-8",
40
+ );
41
+ writeFileSync(
42
+ join(milestoneDir, `${mid}-ROADMAP.md`),
43
+ [
44
+ `# ${mid}: ${title}`,
45
+ "",
46
+ "## Slices",
47
+ "",
48
+ "- [ ] **S01: First slice** `risk:low` `depends:[]`",
49
+ "",
50
+ ].join("\n"),
51
+ "utf-8",
52
+ );
53
+ }
54
+
55
+ function makeLiveMilestoneWorktree(base: string, mid = "M001"): string {
56
+ const worktreeRoot = join(base, ".gsd", "worktrees", mid);
57
+ mkdirSync(worktreeRoot, { recursive: true });
58
+ writeFileSync(
59
+ join(worktreeRoot, ".git"),
60
+ `gitdir: ${join(base, ".git", "worktrees", mid)}\n`,
61
+ "utf-8",
62
+ );
63
+ writeMilestone(worktreeRoot, mid);
64
+ return worktreeRoot;
65
+ }
66
+
67
+ async function waitFor(condition: () => boolean, label: string): Promise<void> {
68
+ const rawTimeout = process.env.READABLE_WAIT_TIMEOUT_MS;
69
+ const parsedTimeout = rawTimeout === undefined ? NaN : Number.parseInt(rawTimeout, 10);
70
+ const timeoutMs = Number.isFinite(parsedTimeout) && parsedTimeout > 0 ? parsedTimeout : 1000;
71
+ const deadline = Date.now() + timeoutMs;
72
+
73
+ while (Date.now() < deadline) {
74
+ if (condition()) return;
75
+ await new Promise((resolve) => setTimeout(resolve, 5));
76
+ }
77
+ if (condition()) return;
78
+ assert.fail(`Timed out waiting for ${label} after ${timeoutMs}ms`);
79
+ }
80
+
81
+ test("runUnit changes cwd to basePath before creating a new session", async (t) => {
82
+ _resetPendingResolve();
83
+
84
+ const originalCwd = process.cwd();
85
+ const base = realpathSync(mkdtempSync(join(tmpdir(), "gsd-rununit-base-")));
86
+ const drifted = realpathSync(mkdtempSync(join(tmpdir(), "gsd-rununit-drift-")));
87
+ t.after(() => {
88
+ process.chdir(originalCwd);
89
+ rmSync(base, { recursive: true, force: true });
90
+ rmSync(drifted, { recursive: true, force: true });
91
+ });
92
+
93
+ process.chdir(drifted);
94
+
95
+ let cwdAtNewSession: string | undefined;
96
+ const session = {
97
+ active: true,
98
+ basePath: base,
99
+ verbose: false,
100
+ cmdCtx: {
101
+ newSession: () => {
102
+ cwdAtNewSession = process.cwd();
103
+ return Promise.resolve({ cancelled: false });
104
+ },
105
+ },
106
+ } as any;
107
+ const pi = {
108
+ calls: [] as unknown[],
109
+ sendMessage(...args: unknown[]) {
110
+ this.calls.push(args);
111
+ },
112
+ } as any;
113
+ const ctx = { ui: { notify: () => {} }, model: { id: "test-model" } } as any;
114
+
115
+ const resultPromise = runUnit(ctx, pi, session, "task", "T01", "prompt");
116
+ await waitFor(() => pi.calls.length === 1, "runUnit dispatch");
117
+ resolveAgentEnd({ messages: [{ role: "assistant" }] });
118
+
119
+ const result = await resultPromise;
120
+ assert.equal(result.status, "completed");
121
+ assert.equal(cwdAtNewSession, base);
122
+ });
123
+
124
+ test("runUnit cancels before creating a session when basePath chdir fails", async (t) => {
125
+ _resetPendingResolve();
126
+
127
+ const originalCwd = process.cwd();
128
+ const base = realpathSync(mkdtempSync(join(tmpdir(), "gsd-rununit-missing-base-")));
129
+ const drifted = realpathSync(mkdtempSync(join(tmpdir(), "gsd-rununit-missing-drift-")));
130
+ rmSync(base, { recursive: true, force: true });
131
+ t.after(() => {
132
+ process.chdir(originalCwd);
133
+ rmSync(drifted, { recursive: true, force: true });
134
+ });
135
+
136
+ process.chdir(drifted);
137
+
138
+ let newSessionCalled = false;
139
+ const session = {
140
+ active: true,
141
+ basePath: base,
142
+ verbose: false,
143
+ cmdCtx: {
144
+ newSession: () => {
145
+ newSessionCalled = true;
146
+ return Promise.resolve({ cancelled: false });
147
+ },
148
+ },
149
+ } as any;
150
+ const pi = {
151
+ calls: [] as unknown[],
152
+ sendMessage(...args: unknown[]) {
153
+ this.calls.push(args);
154
+ },
155
+ } as any;
156
+ const ctx = { ui: { notify: () => {} }, model: { id: "test-model" } } as any;
157
+
158
+ const result = await runUnit(ctx, pi, session, "task", "T01", "prompt");
159
+
160
+ assert.equal(result.status, "cancelled");
161
+ assert.equal(result.errorContext?.category, "session-failed");
162
+ assert.equal(result.errorContext?.isTransient, true);
163
+ assert.match(result.errorContext?.message ?? "", /Failed to chdir to basePath before newSession/);
164
+ assert.ok(result.errorContext?.message.includes(base), "error should include the failed basePath");
165
+ assert.equal(newSessionCalled, false, "newSession must not run after chdir failure");
166
+ assert.equal(pi.calls.length, 0, "unit must not dispatch after chdir failure");
167
+ });
168
+
169
+ test("direct dispatch redirects to the canonical milestone worktree before newSession", async (t) => {
170
+ invalidateStateCache();
171
+
172
+ const originalCwd = process.cwd();
173
+ const base = realpathSync(mkdtempSync(join(tmpdir(), "gsd-direct-base-")));
174
+ const drifted = realpathSync(mkdtempSync(join(tmpdir(), "gsd-direct-drift-")));
175
+ writeMilestone(base);
176
+ const worktreeRoot = makeLiveMilestoneWorktree(base);
177
+
178
+ t.after(() => {
179
+ process.chdir(originalCwd);
180
+ rmSync(base, { recursive: true, force: true });
181
+ rmSync(drifted, { recursive: true, force: true });
182
+ invalidateStateCache();
183
+ });
184
+
185
+ process.chdir(drifted);
186
+
187
+ let cwdAtNewSession: string | undefined;
188
+ let sentPrompt: string | undefined;
189
+ const ctx = {
190
+ ui: { notify: () => {} },
191
+ newSession: async () => {
192
+ cwdAtNewSession = process.cwd();
193
+ return { cancelled: false };
194
+ },
195
+ } as any;
196
+ const pi = {
197
+ sendMessage(message: { content: string }) {
198
+ sentPrompt = message.content;
199
+ },
200
+ } as any;
201
+
202
+ await dispatchDirectPhase(ctx, pi, "research-milestone", base);
203
+
204
+ assert.equal(cwdAtNewSession, worktreeRoot);
205
+ assert.equal(process.cwd(), drifted);
206
+ assert.ok(sentPrompt?.includes(worktreeRoot), "prompt should name the canonical worktree root");
207
+ });
208
+
209
+ test("worktree-aware prompt builders include the explicit working directory", async (t) => {
210
+ const base = realpathSync(mkdtempSync(join(tmpdir(), "gsd-prompt-base-")));
211
+ writeMilestone(base);
212
+ t.after(() => rmSync(base, { recursive: true, force: true }));
213
+
214
+ const prompts = await Promise.all([
215
+ buildDiscussMilestonePrompt("M001", "Worktree Path Injection", base),
216
+ buildParallelResearchSlicesPrompt(
217
+ "M001",
218
+ "Worktree Path Injection",
219
+ [{ id: "S01", title: "First slice" }],
220
+ base,
221
+ ),
222
+ buildRewriteDocsPrompt(
223
+ "M001",
224
+ "Worktree Path Injection",
225
+ null,
226
+ base,
227
+ [{ change: "Refresh docs", timestamp: "2026-04-27T00:00:00.000Z", appliedAt: "test" }] as any,
228
+ ),
229
+ ]);
230
+
231
+ for (const prompt of prompts) {
232
+ assert.match(prompt, /working directory/i);
233
+ assert.ok(prompt.includes(base), "prompt should include the provided working directory");
234
+ }
235
+ });
@@ -20,7 +20,7 @@ import {
20
20
  listWorktrees,
21
21
  worktreePath,
22
22
  } from "../worktree-manager.ts";
23
- import { describe, test } from 'node:test';
23
+ import { test } from 'node:test';
24
24
  import assert from 'node:assert/strict';
25
25
 
26
26
 
@@ -28,37 +28,41 @@ function run(command: string, cwd: string): string {
28
28
  return execSync(command, { cwd, stdio: ["ignore", "pipe", "pipe"], encoding: "utf-8" }).trim();
29
29
  }
30
30
 
31
- // Set up a test repo with .gsd/ as a symlink to an external directory,
32
- // mimicking the external state directory layout (~/.gsd/projects/<hash>/).
33
- // Resolve tmpdir to handle macOS /tmp -> /private/var/... symlink.
34
- const realTmp = realpathSync(tmpdir());
35
- const base = mkdtempSync(join(realTmp, "gsd-wt-symlink-test-"));
36
- const externalState = mkdtempSync(join(realTmp, "gsd-wt-symlink-ext-"));
37
-
38
- run("git init -b main", base);
39
- run('git config user.name "Test"', base);
40
- run('git config user.email "test@example.com"', base);
41
-
42
- // Create external state directory structure
43
- mkdirSync(join(externalState, "worktrees"), { recursive: true });
44
-
45
- // Create .gsd as a symlink to the external state directory
46
- symlinkSync(externalState, join(base, ".gsd"));
47
-
48
- // Verify the symlink is in place
49
- assert.ok(existsSync(join(base, ".gsd")), ".gsd symlink exists");
50
- assert.ok(
51
- realpathSync(join(base, ".gsd")) === externalState,
52
- ".gsd resolves to external state dir",
53
- );
31
+ test('worktree-symlink-removal removes the git-registered symlink target safely', (t) => {
32
+ console.log("\n=== #1852: removeWorktree with symlinked .gsd/ ===");
54
33
 
55
- // Create initial commit so we have a valid repo
56
- writeFileSync(join(base, "README.md"), "# Test\n", "utf-8");
57
- run("git add .", base);
58
- run('git commit -m "init"', base);
34
+ // Set up a test repo with .gsd/ as a symlink to an external directory,
35
+ // mimicking the external state directory layout (~/.gsd/projects/<hash>/).
36
+ // Resolve tmpdir to handle macOS /tmp -> /private/var/... symlink.
37
+ const realTmp = realpathSync(tmpdir());
38
+ const base = mkdtempSync(join(realTmp, "gsd-wt-symlink-test-"));
39
+ const externalState = mkdtempSync(join(realTmp, "gsd-wt-symlink-ext-"));
40
+ t.after(() => {
41
+ rmSync(base, { recursive: true, force: true });
42
+ rmSync(externalState, { recursive: true, force: true });
43
+ });
44
+
45
+ run("git init -b main", base);
46
+ run('git config user.name "Test"', base);
47
+ run('git config user.email "test@example.com"', base);
48
+
49
+ // Create external state directory structure
50
+ mkdirSync(join(externalState, "worktrees"), { recursive: true });
51
+
52
+ // Create .gsd as a symlink to the external state directory
53
+ symlinkSync(externalState, join(base, ".gsd"));
54
+
55
+ // Verify the symlink is in place
56
+ assert.ok(existsSync(join(base, ".gsd")), ".gsd symlink exists");
57
+ assert.ok(
58
+ realpathSync(join(base, ".gsd")) === externalState,
59
+ ".gsd resolves to external state dir",
60
+ );
59
61
 
60
- describe('worktree-symlink-removal', async () => {
61
- console.log("\n=== #1852: removeWorktree with symlinked .gsd/ ===");
62
+ // Create initial commit so we have a valid repo
63
+ writeFileSync(join(base, "README.md"), "# Test\n", "utf-8");
64
+ run("git add .", base);
65
+ run('git commit -m "init"', base);
62
66
 
63
67
  // Create a worktree — git will resolve the symlink and register
64
68
  // the worktree at the external path
@@ -127,7 +131,4 @@ describe('worktree-symlink-removal', async () => {
127
131
  const listed = listWorktrees(base);
128
132
  assert.deepStrictEqual(listed.length, 0, "no worktrees listed after removal");
129
133
 
130
- // Cleanup
131
- rmSync(base, { recursive: true, force: true });
132
- rmSync(externalState, { recursive: true, force: true });
133
134
  });
@@ -255,6 +255,14 @@ describe('worktree', async () => {
255
255
  "returns unchanged for non-worktree path",
256
256
  );
257
257
 
258
+ const nestedRepoDir = join(base, "packages", "demo");
259
+ mkdirSync(nestedRepoDir, { recursive: true });
260
+ assert.deepStrictEqual(
261
+ normalizePath(resolveProjectRoot(nestedRepoDir)),
262
+ normalizePath(base),
263
+ "resolves normal repo subdirectories to the project root",
264
+ );
265
+
258
266
  // Without GSD_PROJECT_ROOT, direct layout with nested subdirs
259
267
  assert.deepStrictEqual(
260
268
  resolveProjectRoot("/data/.gsd/worktrees/M003/nested"),
@@ -9,11 +9,21 @@ import test from 'node:test';
9
9
  import assert from 'node:assert/strict';
10
10
  import { join, sep } from 'node:path';
11
11
 
12
- import { shouldBlockPlanningUnit } from '../bootstrap/write-gate.ts';
12
+ import { ALLOWED_PLANNING_DISPATCH_AGENTS, shouldBlockPlanningUnit } from '../bootstrap/write-gate.ts';
13
+ import { extractSubagentAgentClasses } from '../bootstrap/subagent-input.ts';
14
+ import { isDeterministicPolicyError } from '../auto-tool-tracking.ts';
13
15
  import type { ToolsPolicy } from '../unit-context-manifest.ts';
14
16
 
15
17
  const BASE = join('/tmp', 'fake-project');
16
18
  const PLANNING: ToolsPolicy = { mode: 'planning' };
19
+ const PLANNING_DISPATCH: ToolsPolicy = {
20
+ mode: 'planning-dispatch',
21
+ allowedSubagents: [...ALLOWED_PLANNING_DISPATCH_AGENTS],
22
+ };
23
+ const PLANNING_DISPATCH_REVIEW: ToolsPolicy = {
24
+ mode: 'planning-dispatch',
25
+ allowedSubagents: ['reviewer', 'security', 'tester'],
26
+ };
17
27
  const READ_ONLY: ToolsPolicy = { mode: 'read-only' };
18
28
  const ALL: ToolsPolicy = { mode: 'all' };
19
29
  const DOCS: ToolsPolicy = {
@@ -36,6 +46,20 @@ test('planning-unit: blocks edit to user source (the b23 forensic)', () => {
36
46
  assert.match(r.reason!, /discuss-milestone/);
37
47
  });
38
48
 
49
+ test('planning-unit: deterministic block reason is suitable for retry short-circuiting', () => {
50
+ const r = shouldBlockPlanningUnit(
51
+ 'edit',
52
+ 'src/main.ts',
53
+ BASE,
54
+ 'discuss-milestone',
55
+ PLANNING,
56
+ );
57
+ assert.strictEqual(r.block, true);
58
+ assert.match(r.reason!, /HARD BLOCK/);
59
+ assert.match(r.reason!, /tools-policy/);
60
+ assert.strictEqual(isDeterministicPolicyError(r.reason!), true);
61
+ });
62
+
39
63
  test('planning-unit: blocks write to user source via relative path', () => {
40
64
  const r = shouldBlockPlanningUnit('write', 'src/main.ts', BASE, 'plan-milestone', PLANNING);
41
65
  assert.strictEqual(r.block, true);
@@ -128,6 +152,112 @@ test('planning-unit: blocks task tool (alt subagent name)', () => {
128
152
  assert.strictEqual(r.block, true);
129
153
  });
130
154
 
155
+ test('planning-dispatch: allows subagent dispatch (delegated recon/planner during slice planning)', () => {
156
+ const r = shouldBlockPlanningUnit('subagent', '', BASE, 'plan-slice', PLANNING_DISPATCH, ['scout']);
157
+ assert.strictEqual(r.block, false);
158
+ });
159
+
160
+ test('planning-dispatch: allows task dispatch (delegated recon/planner during slice planning)', () => {
161
+ const r = shouldBlockPlanningUnit('task', '', BASE, 'plan-slice', PLANNING_DISPATCH, ['planner']);
162
+ assert.strictEqual(r.block, false);
163
+ });
164
+
165
+ test('planning-dispatch: extracts subagent classes from single, parallel, and chain inputs', () => {
166
+ assert.deepEqual(extractSubagentAgentClasses({ agent: ' scout ' }), ['scout']);
167
+ assert.deepEqual(
168
+ extractSubagentAgentClasses({ tasks: [{ agent: 'planner' }, { agent: ' tester ' }] }),
169
+ ['planner', 'tester'],
170
+ );
171
+ assert.deepEqual(
172
+ extractSubagentAgentClasses({ chain: [{ agent: 'reviewer' }, { agent: 'security' }] }),
173
+ ['reviewer', 'security'],
174
+ );
175
+ });
176
+
177
+ test('planning-dispatch: blocks subagent dispatch when agentClasses is undefined (stale caller shim)', () => {
178
+ const r = shouldBlockPlanningUnit('subagent', '', BASE, 'plan-slice', PLANNING_DISPATCH, undefined);
179
+ assert.strictEqual(r.block, true);
180
+ assert.match(r.reason!, /stale caller/);
181
+ assert.match(r.reason!, /tools-policy "planning-dispatch"/);
182
+ });
183
+
184
+ test('planning-dispatch: allows explicitly empty agent classes for downstream validation', () => {
185
+ const emptyClasses = extractSubagentAgentClasses({});
186
+ assert.deepEqual(emptyClasses, []);
187
+ const empty = shouldBlockPlanningUnit('subagent', '', BASE, 'plan-slice', PLANNING_DISPATCH, emptyClasses);
188
+ assert.strictEqual(empty.block, false);
189
+ });
190
+
191
+ test('planning-dispatch: allows all globally allowed specialists when listed by policy', () => {
192
+ const policy: ToolsPolicy = {
193
+ mode: 'planning-dispatch',
194
+ allowedSubagents: [...ALLOWED_PLANNING_DISPATCH_AGENTS],
195
+ };
196
+ const r = shouldBlockPlanningUnit(
197
+ 'subagent',
198
+ '',
199
+ BASE,
200
+ 'complete-milestone',
201
+ policy,
202
+ [...ALLOWED_PLANNING_DISPATCH_AGENTS],
203
+ );
204
+ assert.strictEqual(r.block, false);
205
+ });
206
+
207
+ test('planning-dispatch: blocks implementation-tier agent', () => {
208
+ const r = shouldBlockPlanningUnit('subagent', '', BASE, 'plan-slice', PLANNING_DISPATCH, ['worker']);
209
+ assert.strictEqual(r.block, true);
210
+ assert.match(r.reason!, /"worker"/);
211
+ assert.match(r.reason!, /read-only specialists/);
212
+ });
213
+
214
+ test('planning-dispatch: blocks globally disallowed agent even if listed by policy', () => {
215
+ const policy: ToolsPolicy = {
216
+ mode: 'planning-dispatch',
217
+ allowedSubagents: ['refactorer'],
218
+ };
219
+ const r = shouldBlockPlanningUnit('subagent', '', BASE, 'refine-slice', policy, ['refactorer']);
220
+ assert.strictEqual(r.block, true);
221
+ assert.match(r.reason!, /"refactorer"/);
222
+ assert.match(r.reason!, /read-only specialists/);
223
+ assert.doesNotMatch(r.reason!, /ToolsPolicy\.allowedSubagents|permitted agents for this unit/);
224
+ });
225
+
226
+ test('planning-dispatch: blocks mixed batch containing a disallowed agent', () => {
227
+ const r = shouldBlockPlanningUnit('subagent', '', BASE, 'plan-slice', PLANNING_DISPATCH, ['scout', 'worker']);
228
+ assert.strictEqual(r.block, true);
229
+ assert.match(r.reason!, /"worker"/);
230
+ });
231
+
232
+ test('planning-dispatch: allows review-tier agent under closeout policy', () => {
233
+ const r = shouldBlockPlanningUnit('subagent', '', BASE, 'complete-slice', PLANNING_DISPATCH_REVIEW, ['reviewer']);
234
+ assert.strictEqual(r.block, false);
235
+ });
236
+
237
+ test('planning-dispatch: blocks recon agent under closeout policy', () => {
238
+ const r = shouldBlockPlanningUnit('subagent', '', BASE, 'complete-slice', PLANNING_DISPATCH_REVIEW, ['scout']);
239
+ assert.strictEqual(r.block, true);
240
+ assert.match(r.reason!, /"scout"/);
241
+ assert.match(r.reason!, /ToolsPolicy\.allowedSubagents|permitted agents for this unit/);
242
+ assert.doesNotMatch(r.reason!, /read-only specialists/);
243
+ });
244
+
245
+ test('planning-dispatch: still blocks writes to user source (write isolation preserved)', () => {
246
+ const r = shouldBlockPlanningUnit('write', join(BASE, 'src', 'main.ts'), BASE, 'plan-slice', PLANNING_DISPATCH);
247
+ assert.strictEqual(r.block, true);
248
+ });
249
+
250
+ test('planning-dispatch: still allows writes inside .gsd/', () => {
251
+ const r = shouldBlockPlanningUnit(
252
+ 'write',
253
+ join(BASE, '.gsd', 'milestones', 'M001', 'slices', 'S01', 'PLAN.md'),
254
+ BASE,
255
+ 'plan-slice',
256
+ PLANNING_DISPATCH,
257
+ );
258
+ assert.strictEqual(r.block, false);
259
+ });
260
+
131
261
  // ─── planning mode: pass-through tools ────────────────────────────────────
132
262
 
133
263
  test('planning-unit: allows read tool', () => {