gsd-pi 2.78.0-dev.aeeb2ca00 → 2.78.1-dev.84a383f51

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 (383) hide show
  1. package/README.md +7 -7
  2. package/dist/claude-cli-check.js +64 -37
  3. package/dist/cli-policy.d.ts +13 -0
  4. package/dist/cli-policy.js +17 -0
  5. package/dist/cli.js +95 -55
  6. package/dist/headless-query.d.ts +22 -0
  7. package/dist/headless-query.js +24 -4
  8. package/dist/headless.d.ts +10 -0
  9. package/dist/headless.js +16 -1
  10. package/dist/loader.js +7 -10
  11. package/dist/onboarding.d.ts +10 -0
  12. package/dist/onboarding.js +2 -2
  13. package/dist/provider-migrations.d.ts +2 -2
  14. package/dist/provider-migrations.js +5 -2
  15. package/dist/resource-loader.d.ts +5 -2
  16. package/dist/resource-loader.js +28 -5
  17. package/dist/resources/.managed-resources-content-hash +1 -0
  18. package/dist/resources/extensions/claude-code-cli/readiness.js +77 -45
  19. package/dist/resources/extensions/gsd/auto/loop.js +23 -0
  20. package/dist/resources/extensions/gsd/auto/phases.js +2 -2
  21. package/dist/resources/extensions/gsd/auto/run-unit.js +3 -1
  22. package/dist/resources/extensions/gsd/auto/session.js +3 -0
  23. package/dist/resources/extensions/gsd/auto-recovery.js +43 -4
  24. package/dist/resources/extensions/gsd/auto-runtime-state.js +31 -0
  25. package/dist/resources/extensions/gsd/auto-start.js +1 -1
  26. package/dist/resources/extensions/gsd/auto-tool-tracking.js +2 -2
  27. package/dist/resources/extensions/gsd/auto-worktree.js +30 -0
  28. package/dist/resources/extensions/gsd/auto.js +14 -5
  29. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +14 -2
  30. package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +7 -5
  31. package/dist/resources/extensions/gsd/bootstrap/query-tools.js +2 -2
  32. package/dist/resources/extensions/gsd/bootstrap/register-extension.js +5 -4
  33. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +94 -31
  34. package/dist/resources/extensions/gsd/bootstrap/register-shortcuts.js +11 -6
  35. package/dist/resources/extensions/gsd/bootstrap/system-context.js +34 -8
  36. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +38 -2
  37. package/dist/resources/extensions/gsd/commands/catalog.js +69 -5
  38. package/dist/resources/extensions/gsd/commands/handlers/core.js +22 -1
  39. package/dist/resources/extensions/gsd/commands-mcp-status.js +3 -1
  40. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +10 -1
  41. package/dist/resources/extensions/gsd/dashboard-overlay.js +1 -1
  42. package/dist/resources/extensions/gsd/docs/preferences-reference.md +4 -0
  43. package/dist/resources/extensions/gsd/doctor-runtime-checks.js +39 -1
  44. package/dist/resources/extensions/gsd/error-classifier.js +1 -1
  45. package/dist/resources/extensions/gsd/forensics.js +2 -2
  46. package/dist/resources/extensions/gsd/git-service.js +12 -5
  47. package/dist/resources/extensions/gsd/gsd-db.js +11 -2
  48. package/dist/resources/extensions/gsd/guided-flow.js +23 -23
  49. package/dist/resources/extensions/gsd/memory-store.js +66 -31
  50. package/dist/resources/extensions/gsd/milestone-id-reservation.js +36 -0
  51. package/dist/resources/extensions/gsd/model-router.js +114 -9
  52. package/dist/resources/extensions/gsd/native-git-bridge.js +7 -1
  53. package/dist/resources/extensions/gsd/preferences-models.js +91 -15
  54. package/dist/resources/extensions/gsd/preferences-types.js +2 -0
  55. package/dist/resources/extensions/gsd/preferences-validation.js +32 -0
  56. package/dist/resources/extensions/gsd/preferences.js +5 -3
  57. package/dist/resources/extensions/gsd/prompt-loader.js +23 -12
  58. package/dist/resources/extensions/gsd/slice-parallel-orchestrator.js +9 -3
  59. package/dist/resources/extensions/gsd/state.js +42 -0
  60. package/dist/resources/extensions/gsd/templates/PREFERENCES.md +1 -0
  61. package/dist/resources/extensions/gsd/tools/memory-tools.js +18 -1
  62. package/dist/resources/extensions/gsd/visualizer-overlay.js +1 -1
  63. package/dist/resources/extensions/gsd/watch/header-renderer.js +3 -1
  64. package/dist/resources/extensions/gsd/worktree-command.js +26 -46
  65. package/dist/resources/extensions/gsd/worktree-session-state.js +33 -0
  66. package/dist/resources/extensions/mcp-client/index.js +6 -3
  67. package/dist/resources/extensions/slash-commands/create-extension.js +36 -22
  68. package/dist/resources/skills/create-gsd-extension/SKILL.md +9 -5
  69. package/dist/resources/skills/create-gsd-extension/references/custom-commands.md +1 -1
  70. package/dist/resources/skills/create-gsd-extension/references/custom-rendering.md +5 -5
  71. package/dist/resources/skills/create-gsd-extension/references/custom-tools.md +4 -4
  72. package/dist/resources/skills/create-gsd-extension/references/custom-ui.md +6 -6
  73. package/dist/resources/skills/create-gsd-extension/references/events-reference.md +3 -3
  74. package/dist/resources/skills/create-gsd-extension/references/packaging-distribution.md +1 -1
  75. package/dist/resources/skills/create-gsd-extension/references/remote-execution-overrides.md +3 -3
  76. package/dist/resources/skills/create-gsd-extension/workflows/create-extension.md +32 -12
  77. package/dist/rtk-shared.d.ts +3 -0
  78. package/dist/rtk-shared.js +17 -0
  79. package/dist/rtk.d.ts +2 -5
  80. package/dist/rtk.js +3 -20
  81. package/dist/runtime-checks.d.ts +27 -0
  82. package/dist/runtime-checks.js +38 -0
  83. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  84. package/dist/web/standalone/.next/BUILD_ID +1 -1
  85. package/dist/web/standalone/.next/app-path-routes-manifest.json +12 -12
  86. package/dist/web/standalone/.next/build-manifest.json +3 -3
  87. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  88. package/dist/web/standalone/.next/react-loadable-manifest.json +44 -4
  89. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  90. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  91. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  92. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  93. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  94. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  95. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  96. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  97. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  98. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  99. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  100. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  101. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  102. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  103. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  104. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  105. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  106. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  107. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  108. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  109. package/dist/web/standalone/.next/server/app/api/session/events/route.js +4 -2
  110. package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
  111. package/dist/web/standalone/.next/server/app/index.html +1 -1
  112. package/dist/web/standalone/.next/server/app/index.rsc +2 -2
  113. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  114. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +2 -2
  115. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  116. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  117. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  118. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  119. package/dist/web/standalone/.next/server/app-paths-manifest.json +12 -12
  120. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  121. package/dist/web/standalone/.next/server/middleware-manifest.json +5 -5
  122. package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
  123. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  124. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  125. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  126. package/dist/web/standalone/.next/server/webpack-runtime.js +1 -1
  127. package/dist/web/standalone/.next/static/chunks/2556.0527fea66e123b7f.js +1 -0
  128. package/dist/web/standalone/.next/static/chunks/2824.08296bc2f9654698.js +1 -0
  129. package/dist/web/standalone/.next/static/chunks/3026.3af53b279375f082.js +1 -0
  130. package/dist/web/standalone/.next/static/chunks/315.6f68ae79b67d25cf.js +1 -0
  131. package/dist/web/standalone/.next/static/chunks/3497.4bfc60a3b3dea717.js +1 -0
  132. package/dist/web/standalone/.next/static/chunks/5516.4a07c872b5c3a663.js +1 -0
  133. package/dist/web/standalone/.next/static/chunks/8336.31b019697882acfb.js +10 -0
  134. package/dist/web/standalone/.next/static/chunks/8845.c9702695e8c5a9c5.js +2 -0
  135. package/dist/web/standalone/.next/static/chunks/9058.01ef3a463bda88f1.js +20 -0
  136. package/dist/web/standalone/.next/static/chunks/9441.1081da1125d1764f.js +1 -0
  137. package/dist/web/standalone/.next/static/chunks/app/{page-5b113fd32bc2a1c3.js → page-9bf2e0c50fb2ca05.js} +1 -1
  138. package/dist/web/standalone/.next/static/chunks/webpack-f9f0dc45e4f3ac10.js +1 -0
  139. package/dist/web/standalone/package.json +2 -1
  140. package/dist/worktree-status-banner.d.ts +1 -0
  141. package/dist/worktree-status-banner.js +132 -0
  142. package/package.json +1 -1
  143. package/packages/daemon/package.json +2 -2
  144. package/packages/mcp-server/dist/alias-telemetry.d.ts +8 -0
  145. package/packages/mcp-server/dist/alias-telemetry.d.ts.map +1 -0
  146. package/packages/mcp-server/dist/alias-telemetry.js +30 -0
  147. package/packages/mcp-server/dist/alias-telemetry.js.map +1 -0
  148. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  149. package/packages/mcp-server/dist/workflow-tools.js +74 -46
  150. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  151. package/packages/mcp-server/package.json +2 -2
  152. package/packages/mcp-server/src/alias-telemetry.test.ts +78 -0
  153. package/packages/mcp-server/src/alias-telemetry.ts +30 -0
  154. package/packages/mcp-server/src/workflow-tools.test.ts +26 -0
  155. package/packages/mcp-server/src/workflow-tools.ts +93 -58
  156. package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
  157. package/packages/native/package.json +1 -1
  158. package/packages/pi-agent-core/package.json +1 -1
  159. package/packages/pi-agent-core/tsconfig.tsbuildinfo +1 -1
  160. package/packages/pi-ai/dist/providers/anthropic-shared.cache-breakpoint.test.d.ts +2 -0
  161. package/packages/pi-ai/dist/providers/anthropic-shared.cache-breakpoint.test.d.ts.map +1 -0
  162. package/packages/pi-ai/dist/providers/anthropic-shared.cache-breakpoint.test.js +231 -0
  163. package/packages/pi-ai/dist/providers/anthropic-shared.cache-breakpoint.test.js.map +1 -0
  164. package/packages/pi-ai/dist/providers/anthropic-shared.d.ts.map +1 -1
  165. package/packages/pi-ai/dist/providers/anthropic-shared.js +48 -19
  166. package/packages/pi-ai/dist/providers/anthropic-shared.js.map +1 -1
  167. package/packages/pi-ai/dist/types.d.ts +13 -0
  168. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  169. package/packages/pi-ai/dist/types.js.map +1 -1
  170. package/packages/pi-ai/dist/utils/repair-tool-json.d.ts.map +1 -1
  171. package/packages/pi-ai/dist/utils/repair-tool-json.js +24 -3
  172. package/packages/pi-ai/dist/utils/repair-tool-json.js.map +1 -1
  173. package/packages/pi-ai/dist/utils/tests/repair-tool-json.test.js +26 -0
  174. package/packages/pi-ai/dist/utils/tests/repair-tool-json.test.js.map +1 -1
  175. package/packages/pi-ai/package.json +1 -1
  176. package/packages/pi-ai/src/providers/anthropic-shared.cache-breakpoint.test.ts +289 -0
  177. package/packages/pi-ai/src/providers/anthropic-shared.ts +52 -20
  178. package/packages/pi-ai/src/types.ts +13 -0
  179. package/packages/pi-ai/src/utils/repair-tool-json.ts +24 -3
  180. package/packages/pi-ai/src/utils/tests/repair-tool-json.test.ts +32 -0
  181. package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
  182. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  183. package/packages/pi-coding-agent/dist/core/agent-session.js +6 -0
  184. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  185. package/packages/pi-coding-agent/dist/core/messages.d.ts.map +1 -1
  186. package/packages/pi-coding-agent/dist/core/messages.js +4 -0
  187. package/packages/pi-coding-agent/dist/core/messages.js.map +1 -1
  188. package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js +19 -2
  189. package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js.map +1 -1
  190. package/packages/pi-coding-agent/dist/core/model-registry.d.ts +10 -0
  191. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  192. package/packages/pi-coding-agent/dist/core/model-registry.js +18 -0
  193. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  194. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts +13 -0
  195. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
  196. package/packages/pi-coding-agent/dist/core/system-prompt.js +20 -16
  197. package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
  198. package/packages/pi-coding-agent/dist/core/token-telemetry.d.ts +37 -0
  199. package/packages/pi-coding-agent/dist/core/token-telemetry.d.ts.map +1 -0
  200. package/packages/pi-coding-agent/dist/core/token-telemetry.js +49 -0
  201. package/packages/pi-coding-agent/dist/core/token-telemetry.js.map +1 -0
  202. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.d.ts +2 -0
  203. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.d.ts.map +1 -0
  204. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.js +133 -0
  205. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.js.map +1 -0
  206. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js +1 -1
  207. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js.map +1 -1
  208. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js +14 -1
  209. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js.map +1 -1
  210. package/packages/pi-coding-agent/dist/tests/system-prompt-cache-stability.test.d.ts +2 -0
  211. package/packages/pi-coding-agent/dist/tests/system-prompt-cache-stability.test.d.ts.map +1 -0
  212. package/packages/pi-coding-agent/dist/tests/system-prompt-cache-stability.test.js +78 -0
  213. package/packages/pi-coding-agent/dist/tests/system-prompt-cache-stability.test.js.map +1 -0
  214. package/packages/pi-coding-agent/dist/tests/token-telemetry.test.d.ts +2 -0
  215. package/packages/pi-coding-agent/dist/tests/token-telemetry.test.d.ts.map +1 -0
  216. package/packages/pi-coding-agent/dist/tests/token-telemetry.test.js +181 -0
  217. package/packages/pi-coding-agent/dist/tests/token-telemetry.test.js.map +1 -0
  218. package/packages/pi-coding-agent/package.json +1 -1
  219. package/packages/pi-coding-agent/src/core/agent-session.ts +7 -0
  220. package/packages/pi-coding-agent/src/core/messages.ts +4 -0
  221. package/packages/pi-coding-agent/src/core/model-registry-auth-mode.test.ts +32 -2
  222. package/packages/pi-coding-agent/src/core/model-registry.ts +21 -0
  223. package/packages/pi-coding-agent/src/core/system-prompt.ts +33 -15
  224. package/packages/pi-coding-agent/src/core/token-telemetry.ts +77 -0
  225. package/packages/pi-coding-agent/src/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.ts +212 -0
  226. package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.test.ts +17 -1
  227. package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.ts +1 -1
  228. package/packages/pi-coding-agent/src/tests/system-prompt-cache-stability.test.ts +102 -0
  229. package/packages/pi-coding-agent/src/tests/token-telemetry.test.ts +200 -0
  230. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  231. package/packages/pi-tui/dist/__tests__/autocomplete.test.js +17 -3
  232. package/packages/pi-tui/dist/__tests__/autocomplete.test.js.map +1 -1
  233. package/packages/pi-tui/dist/components/__tests__/leak-fixes-runtime.test.d.ts +2 -0
  234. package/packages/pi-tui/dist/components/__tests__/leak-fixes-runtime.test.d.ts.map +1 -0
  235. package/packages/pi-tui/dist/components/__tests__/leak-fixes-runtime.test.js +161 -0
  236. package/packages/pi-tui/dist/components/__tests__/leak-fixes-runtime.test.js.map +1 -0
  237. package/packages/pi-tui/package.json +1 -1
  238. package/packages/pi-tui/src/__tests__/autocomplete.test.ts +20 -3
  239. package/packages/pi-tui/src/components/__tests__/leak-fixes-runtime.test.ts +219 -0
  240. package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
  241. package/packages/rpc-client/package.json +1 -1
  242. package/pkg/package.json +1 -1
  243. package/src/resources/extensions/claude-code-cli/readiness.ts +78 -46
  244. package/src/resources/extensions/gsd/auto/loop.ts +24 -2
  245. package/src/resources/extensions/gsd/auto/phases.ts +3 -3
  246. package/src/resources/extensions/gsd/auto/run-unit.ts +3 -1
  247. package/src/resources/extensions/gsd/auto/session.ts +3 -0
  248. package/src/resources/extensions/gsd/auto/types.ts +1 -0
  249. package/src/resources/extensions/gsd/auto-recovery.ts +46 -8
  250. package/src/resources/extensions/gsd/auto-runtime-state.ts +51 -0
  251. package/src/resources/extensions/gsd/auto-start.ts +1 -1
  252. package/src/resources/extensions/gsd/auto-tool-tracking.ts +2 -4
  253. package/src/resources/extensions/gsd/auto-worktree.ts +38 -0
  254. package/src/resources/extensions/gsd/auto.ts +14 -4
  255. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +15 -13
  256. package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +8 -7
  257. package/src/resources/extensions/gsd/bootstrap/query-tools.ts +2 -2
  258. package/src/resources/extensions/gsd/bootstrap/register-extension.ts +10 -9
  259. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +102 -31
  260. package/src/resources/extensions/gsd/bootstrap/register-shortcuts.ts +12 -6
  261. package/src/resources/extensions/gsd/bootstrap/system-context.ts +39 -8
  262. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +39 -11
  263. package/src/resources/extensions/gsd/commands/catalog.ts +75 -5
  264. package/src/resources/extensions/gsd/commands/handlers/core.ts +22 -1
  265. package/src/resources/extensions/gsd/commands-mcp-status.ts +3 -1
  266. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +15 -1
  267. package/src/resources/extensions/gsd/dashboard-overlay.ts +1 -1
  268. package/src/resources/extensions/gsd/docs/preferences-reference.md +4 -0
  269. package/src/resources/extensions/gsd/doctor-runtime-checks.ts +39 -1
  270. package/src/resources/extensions/gsd/doctor-types.ts +3 -1
  271. package/src/resources/extensions/gsd/error-classifier.ts +1 -1
  272. package/src/resources/extensions/gsd/forensics.ts +2 -2
  273. package/src/resources/extensions/gsd/git-service.ts +13 -5
  274. package/src/resources/extensions/gsd/gsd-db.ts +12 -2
  275. package/src/resources/extensions/gsd/guided-flow.ts +25 -25
  276. package/src/resources/extensions/gsd/memory-store.ts +81 -28
  277. package/src/resources/extensions/gsd/milestone-id-reservation.ts +47 -0
  278. package/src/resources/extensions/gsd/model-router.ts +172 -9
  279. package/src/resources/extensions/gsd/native-git-bridge.ts +7 -1
  280. package/src/resources/extensions/gsd/preferences-models.ts +101 -15
  281. package/src/resources/extensions/gsd/preferences-types.ts +6 -0
  282. package/src/resources/extensions/gsd/preferences-validation.ts +35 -0
  283. package/src/resources/extensions/gsd/preferences.ts +16 -2
  284. package/src/resources/extensions/gsd/prompt-loader.ts +26 -12
  285. package/src/resources/extensions/gsd/slice-parallel-orchestrator.ts +9 -3
  286. package/src/resources/extensions/gsd/state.ts +42 -0
  287. package/src/resources/extensions/gsd/templates/PREFERENCES.md +1 -0
  288. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +178 -1
  289. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +58 -0
  290. package/src/resources/extensions/gsd/tests/auto-session-encapsulation.test.ts +9 -5
  291. package/src/resources/extensions/gsd/tests/auto-supervisor.test.mjs +21 -4
  292. package/src/resources/extensions/gsd/tests/bootstrap-derive-state-db-open.test.ts +1 -1
  293. package/src/resources/extensions/gsd/tests/budget-prediction.test.ts +138 -211
  294. package/src/resources/extensions/gsd/tests/complete-slice-verification-gate.test.ts +142 -59
  295. package/src/resources/extensions/gsd/tests/complete-slice.test.ts +7 -4
  296. package/src/resources/extensions/gsd/tests/completed-at-reconcile.test.ts +89 -32
  297. package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +41 -23
  298. package/src/resources/extensions/gsd/tests/db-path-worktree-symlink.test.ts +3 -43
  299. package/src/resources/extensions/gsd/tests/debug-logger.test.ts +5 -3
  300. package/src/resources/extensions/gsd/tests/deferred-milestone-dir-4996.test.ts +116 -0
  301. package/src/resources/extensions/gsd/tests/discuss-empty-db-fallback.test.ts +22 -87
  302. package/src/resources/extensions/gsd/tests/discuss-queued-milestones.test.ts +7 -118
  303. package/src/resources/extensions/gsd/tests/discuss-tool-scope-leak.test.ts +18 -60
  304. package/src/resources/extensions/gsd/tests/doctor-orphan-milestone-4996.test.ts +100 -0
  305. package/src/resources/extensions/gsd/tests/double-merge-guard.test.ts +14 -76
  306. package/src/resources/extensions/gsd/tests/ensure-preconditions-guard-4996.test.ts +93 -0
  307. package/src/resources/extensions/gsd/tests/false-degraded-mode-warning.test.ts +22 -83
  308. package/src/resources/extensions/gsd/tests/finalize-timeout-guard.test.ts +1 -63
  309. package/src/resources/extensions/gsd/tests/find-missing-summaries-closed-runtime.test.ts +47 -0
  310. package/src/resources/extensions/gsd/tests/forensics-stuck-loops.test.ts +26 -1
  311. package/src/resources/extensions/gsd/tests/gitignore-bg-shell-runtime.test.ts +63 -0
  312. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +30 -0
  313. package/src/resources/extensions/gsd/tests/gsd-no-project-error-runtime.test.ts +81 -0
  314. package/src/resources/extensions/gsd/tests/headless-answers.test.ts +14 -4
  315. package/src/resources/extensions/gsd/tests/health-widget.test.ts +22 -12
  316. package/src/resources/extensions/gsd/tests/help-menu-coverage.test.ts +57 -0
  317. package/src/resources/extensions/gsd/tests/import-done-milestones-runtime.test.ts +145 -0
  318. package/src/resources/extensions/gsd/tests/init-prefs-routing.test.ts +64 -1
  319. package/src/resources/extensions/gsd/tests/integration/auto-worktree.test.ts +22 -0
  320. package/src/resources/extensions/gsd/tests/integration/token-savings.test.ts +0 -23
  321. package/src/resources/extensions/gsd/tests/memory-store.test.ts +128 -0
  322. package/src/resources/extensions/gsd/tests/memory-tools.test.ts +33 -1
  323. package/src/resources/extensions/gsd/tests/merge-self-branch-guard.test.ts +124 -0
  324. package/src/resources/extensions/gsd/tests/milestone-id-gap-reuse-4996.test.ts +152 -0
  325. package/src/resources/extensions/gsd/tests/model-router.test.ts +169 -8
  326. package/src/resources/extensions/gsd/tests/native-git-infra-errors.test.ts +50 -0
  327. package/src/resources/extensions/gsd/tests/orphaned-worktree-audit.test.ts +8 -0
  328. package/src/resources/extensions/gsd/tests/parallel-crash-recovery.test.ts +32 -43
  329. package/src/resources/extensions/gsd/tests/phases-merge-error-stops-auto.test.ts +4 -10
  330. package/src/resources/extensions/gsd/tests/preferences.test.ts +127 -0
  331. package/src/resources/extensions/gsd/tests/prompt-step-ordering.test.ts +16 -0
  332. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +7 -0
  333. package/src/resources/extensions/gsd/tests/quick-turn-end-cleanup.test.ts +6 -6
  334. package/src/resources/extensions/gsd/tests/register-hooks-compaction-checkpoint.test.ts +93 -0
  335. package/src/resources/extensions/gsd/tests/session-start-footer.test.ts +168 -19
  336. package/src/resources/extensions/gsd/tests/slice-parallel-orchestrator.test.ts +7 -1
  337. package/src/resources/extensions/gsd/tests/smart-entry-complete.test.ts +23 -1
  338. package/src/resources/extensions/gsd/tests/system-context-message-routing.test.ts +101 -0
  339. package/src/resources/extensions/gsd/tests/token-profile.test.ts +51 -4
  340. package/src/resources/extensions/gsd/tests/turn-epoch.test.ts +7 -16
  341. package/src/resources/extensions/gsd/tests/unstructured-continue-context-injection.test.ts +5 -7
  342. package/src/resources/extensions/gsd/tests/uok-gitops-turn-action.test.ts +15 -1
  343. package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +6 -6
  344. package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +15 -0
  345. package/src/resources/extensions/gsd/tools/memory-tools.ts +17 -1
  346. package/src/resources/extensions/gsd/unit-context-manifest.ts +8 -8
  347. package/src/resources/extensions/gsd/visualizer-overlay.ts +1 -1
  348. package/src/resources/extensions/gsd/watch/header-renderer.ts +3 -1
  349. package/src/resources/extensions/gsd/workflow-logger.ts +1 -0
  350. package/src/resources/extensions/gsd/worktree-command.ts +31 -44
  351. package/src/resources/extensions/gsd/worktree-session-state.ts +35 -0
  352. package/src/resources/extensions/mcp-client/index.ts +6 -3
  353. package/src/resources/extensions/mcp-client/tests/global-config.test.ts +91 -0
  354. package/src/resources/extensions/slash-commands/create-extension.ts +38 -24
  355. package/src/resources/skills/create-gsd-extension/SKILL.md +9 -5
  356. package/src/resources/skills/create-gsd-extension/references/custom-commands.md +1 -1
  357. package/src/resources/skills/create-gsd-extension/references/custom-rendering.md +5 -5
  358. package/src/resources/skills/create-gsd-extension/references/custom-tools.md +4 -4
  359. package/src/resources/skills/create-gsd-extension/references/custom-ui.md +6 -6
  360. package/src/resources/skills/create-gsd-extension/references/events-reference.md +3 -3
  361. package/src/resources/skills/create-gsd-extension/references/packaging-distribution.md +1 -1
  362. package/src/resources/skills/create-gsd-extension/references/remote-execution-overrides.md +3 -3
  363. package/src/resources/skills/create-gsd-extension/templates/extension-skeleton.ts +2 -2
  364. package/src/resources/skills/create-gsd-extension/templates/stateful-tool-skeleton.ts +3 -3
  365. package/src/resources/skills/create-gsd-extension/templates/templates.test.ts +58 -0
  366. package/src/resources/skills/create-gsd-extension/workflows/create-extension.md +32 -12
  367. package/dist/resources/extensions/browser-tools/tests/browser-tools-integration.test.mjs +0 -601
  368. package/dist/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +0 -651
  369. package/dist/resources/extensions/browser-tools/tests/capture-sharp-optional.test.cjs +0 -91
  370. package/dist/resources/extensions/gsd/tests/auto-supervisor.test.mjs +0 -53
  371. package/dist/resources/extensions/gsd/tests/dist-redirect.mjs +0 -112
  372. package/dist/resources/extensions/gsd/tests/resolve-ts-hooks.mjs +0 -23
  373. package/dist/resources/extensions/gsd/tests/resolve-ts.mjs +0 -5
  374. package/dist/resources/skills/github-workflows/references/gh/tests/__init__.py +0 -0
  375. package/dist/resources/skills/github-workflows/references/gh/tests/test_github_project_setup.py +0 -608
  376. package/dist/web/standalone/.next/static/chunks/2826.e9f5195e91f9cad2.js +0 -11
  377. package/dist/web/standalone/.next/static/chunks/3621.fc7480022c972438.js +0 -20
  378. package/dist/web/standalone/.next/static/chunks/webpack-2e68521d7c82f7c2.js +0 -1
  379. package/src/resources/extensions/gsd/tests/copy-planning-artifacts-samepath.test.ts +0 -22
  380. package/src/resources/extensions/gsd/tests/discuss-slice-structured-questions.test.ts +0 -47
  381. package/src/resources/extensions/gsd/tests/empty-content-abort-loop.test.ts +0 -75
  382. /package/dist/web/standalone/.next/static/{cAJH99yNS1UPbeSEiNRrV → UF5VF4F1tB0miEtJS7LyX}/_buildManifest.js +0 -0
  383. /package/dist/web/standalone/.next/static/{cAJH99yNS1UPbeSEiNRrV → UF5VF4F1tB0miEtJS7LyX}/_ssgManifest.js +0 -0
@@ -5,24 +5,16 @@ import { isToolCallEventType } from "@gsd/pi-coding-agent";
5
5
 
6
6
  import type { GSDEcosystemBeforeAgentStartHandler } from "../ecosystem/gsd-extension-api.js";
7
7
  import { updateSnapshot } from "../ecosystem/gsd-extension-api.js";
8
- import { getEcosystemReadyPromise } from "../ecosystem/loader.js";
9
8
 
10
9
  import { buildMilestoneFileName, resolveMilestonePath, resolveSliceFile, resolveSlicePath } from "../paths.js";
11
- import { buildBeforeAgentStartResult } from "./system-context.js";
12
- import { handleAgentEnd } from "./agent-end-recovery.js";
13
- import { clearDiscussionFlowState, isDepthConfirmationAnswer, isQueuePhaseActive, markDepthVerified, resetWriteGateState, shouldBlockContextWrite, shouldBlockQueueExecution, isGateQuestionId, setPendingGate, clearPendingGate, getPendingGate, shouldBlockPendingGate, shouldBlockPendingGateBash, extractDepthVerificationMilestoneId } from "./write-gate.js";
10
+ import { clearDiscussionFlowState, isDepthConfirmationAnswer, isQueuePhaseActive, markDepthVerified, resetWriteGateState, shouldBlockContextWrite, shouldBlockPlanningUnit, shouldBlockQueueExecution, isGateQuestionId, setPendingGate, clearPendingGate, getPendingGate, shouldBlockPendingGate, shouldBlockPendingGateBash, extractDepthVerificationMilestoneId } from "./write-gate.js";
11
+ import { resolveManifest } from "../unit-context-manifest.js";
14
12
  import { isBlockedStateFile, isBashWriteToStateFile, BLOCKED_WRITE_ERROR } from "../write-intercept.js";
15
- import { cleanupQuickBranch } from "../quick.js";
16
- import { getDiscussionMilestoneId } from "../guided-flow.js";
17
- import { loadToolApiKeys } from "../commands-config.js";
18
13
  import { loadFile, saveFile, formatContinue } from "../files.js";
19
- import { deriveState } from "../state.js";
20
- import { getAutoDashboardData, isAutoActive, isAutoPaused, markToolEnd, markToolStart, recordToolInvocationError } from "../auto.js";
14
+ import { getAutoRuntimeSnapshot, isAutoActive, isAutoPaused, markToolEnd, markToolStart, recordToolInvocationError } from "../auto-runtime-state.js";
21
15
 
22
- import { isParallelActive, shutdownParallel } from "../parallel-orchestrator.js";
23
16
  import { checkToolCallLoop, resetToolCallLoopGuard } from "./tool-call-loop-guard.js";
24
17
  import { saveActivityLog } from "../activity-log.js";
25
- import { resetAskUserQuestionsCache } from "../../ask-user-questions.js";
26
18
  import { recordToolCall as safetyRecordToolCall, recordToolResult as safetyRecordToolResult, saveEvidenceToDisk } from "../safety/evidence-collector.js";
27
19
  import { parseUnitId } from "../unit-id.js";
28
20
  import { classifyCommand } from "../safety/destructive-guard.js";
@@ -30,17 +22,45 @@ import { logWarning as safetyLogWarning } from "../workflow-logger.js";
30
22
  import { installNotifyInterceptor } from "./notify-interceptor.js";
31
23
  import { initNotificationStore } from "../notification-store.js";
32
24
  import { initNotificationWidget } from "../notification-widget.js";
33
- import { initHealthWidget } from "../health-widget.js";
34
25
 
35
26
  // Skip the welcome screen on the very first session_start — cli.ts already
36
27
  // printed it before the TUI launched. Only re-print on /clear (subsequent sessions).
37
28
  let isFirstSession = true;
38
29
 
30
+ async function deriveGsdState(basePath: string) {
31
+ const { deriveState } = await import("../state.js");
32
+ return deriveState(basePath);
33
+ }
34
+
35
+ async function getDiscussionMilestoneIdFor(basePath: string): Promise<string | null> {
36
+ const { getDiscussionMilestoneId } = await import("../guided-flow.js");
37
+ return getDiscussionMilestoneId(basePath);
38
+ }
39
+
40
+ async function loadToolApiKeysForSession(): Promise<void> {
41
+ const { loadToolApiKeys } = await import("../commands-config.js");
42
+ loadToolApiKeys();
43
+ }
44
+
45
+ async function resetAskUserQuestionsTurnCache(): Promise<void> {
46
+ const { resetAskUserQuestionsCache } = await import("../../ask-user-questions.js");
47
+ resetAskUserQuestionsCache();
48
+ }
49
+
39
50
  async function syncServiceTierStatus(ctx: ExtensionContext): Promise<void> {
40
51
  const { getEffectiveServiceTier, formatServiceTierFooterStatus } = await import("../service-tier.js");
41
52
  ctx.ui.setStatus("gsd-fast", formatServiceTierFooterStatus(getEffectiveServiceTier(), ctx.model?.id));
42
53
  }
43
54
 
55
+ async function applyDisabledModelProviderPolicy(ctx: ExtensionContext): Promise<void> {
56
+ try {
57
+ const { resolveDisabledModelProvidersFromPreferences } = await import("../preferences.js");
58
+ ctx.modelRegistry.setDisabledModelProviders(resolveDisabledModelProvidersFromPreferences());
59
+ } catch {
60
+ // Non-fatal: keep default provider visibility if preferences cannot be loaded.
61
+ }
62
+ }
63
+
44
64
  export function registerHooks(
45
65
  pi: ExtensionAPI,
46
66
  ecosystemHandlers: GSDEcosystemBeforeAgentStartHandler[],
@@ -50,12 +70,14 @@ export function registerHooks(
50
70
  installNotifyInterceptor(ctx);
51
71
  initNotificationWidget(ctx);
52
72
  if (!isAutoActive()) {
73
+ const { initHealthWidget } = await import("../health-widget.js");
53
74
  initHealthWidget(ctx);
54
75
  }
55
76
  resetWriteGateState();
56
77
  resetToolCallLoopGuard();
57
- resetAskUserQuestionsCache();
78
+ await resetAskUserQuestionsTurnCache();
58
79
  await syncServiceTierStatus(ctx);
80
+ await applyDisabledModelProviderPolicy(ctx);
59
81
  // Skip MCP auto-prep when running inside an auto-worktree (see session_switch below).
60
82
  const { isInAutoWorktree } = await import("../auto-worktree.js");
61
83
  if (!isInAutoWorktree(process.cwd())) {
@@ -91,7 +113,7 @@ export function registerHooks(
91
113
  }
92
114
  } catch { /* non-fatal */ }
93
115
  }
94
- loadToolApiKeys();
116
+ await loadToolApiKeysForSession();
95
117
  if (isAutoActive()) {
96
118
  ctx.ui.setWidget("gsd-health", undefined);
97
119
  }
@@ -102,9 +124,10 @@ export function registerHooks(
102
124
  installNotifyInterceptor(ctx);
103
125
  resetWriteGateState();
104
126
  resetToolCallLoopGuard();
105
- resetAskUserQuestionsCache();
127
+ await resetAskUserQuestionsTurnCache();
106
128
  clearDiscussionFlowState();
107
129
  await syncServiceTierStatus(ctx);
130
+ await applyDisabledModelProviderPolicy(ctx);
108
131
  // Skip MCP auto-prep when running inside an auto-worktree. The worktree
109
132
  // already has .mcp.json from createAutoWorktree, and re-running the writer
110
133
  // post-chdir rewrites the file mid-run (non-idempotent due to cwd-relative
@@ -114,23 +137,28 @@ export function registerHooks(
114
137
  const { prepareWorkflowMcpForProject } = await import("../workflow-mcp-auto-prep.js");
115
138
  prepareWorkflowMcpForProject(ctx, process.cwd());
116
139
  }
117
- loadToolApiKeys();
118
- if (isAutoActive()) {
140
+ await loadToolApiKeysForSession();
141
+ if (!isAutoActive()) {
142
+ const { initHealthWidget } = await import("../health-widget.js");
143
+ initHealthWidget(ctx);
144
+ } else {
119
145
  ctx.ui.setWidget("gsd-health", undefined);
120
146
  }
121
147
  });
122
148
 
123
149
  pi.on("before_agent_start", async (event, ctx: ExtensionContext) => {
124
150
  // Wait for ecosystem loader to finish (no-op after first turn).
151
+ const { getEcosystemReadyPromise } = await import("../ecosystem/loader.js");
125
152
  await getEcosystemReadyPromise();
126
153
 
127
154
  // GSD's own context injection (existing behavior — unchanged).
155
+ const { buildBeforeAgentStartResult } = await import("./system-context.js");
128
156
  const gsdResult = await buildBeforeAgentStartResult(event, ctx);
129
157
 
130
158
  // Refresh the snapshot used by ecosystem getPhase()/getActiveUnit().
131
159
  // deriveState has its own ~100ms cache so this is cheap on repeat calls.
132
160
  try {
133
- const state = await deriveState(process.cwd());
161
+ const state = await deriveGsdState(process.cwd());
134
162
  updateSnapshot(state);
135
163
  } catch {
136
164
  updateSnapshot(null);
@@ -169,7 +197,8 @@ export function registerHooks(
169
197
 
170
198
  pi.on("agent_end", async (event, ctx: ExtensionContext) => {
171
199
  resetToolCallLoopGuard();
172
- resetAskUserQuestionsCache();
200
+ await resetAskUserQuestionsTurnCache();
201
+ const { handleAgentEnd } = await import("./agent-end-recovery.js");
173
202
  await handleAgentEnd(pi, event, ctx);
174
203
  });
175
204
 
@@ -178,6 +207,7 @@ export function registerHooks(
178
207
  // quick-return state is pending, so this is safe to call on every turn.
179
208
  pi.on("turn_end", async () => {
180
209
  try {
210
+ const { cleanupQuickBranch } = await import("../quick.js");
181
211
  cleanupQuickBranch();
182
212
  } catch {
183
213
  // Best-effort: don't break the turn lifecycle if cleanup fails.
@@ -194,8 +224,8 @@ export function registerHooks(
194
224
  const basePath = process.cwd();
195
225
  const { ensureDbOpen } = await import("./dynamic-tools.js");
196
226
  await ensureDbOpen();
197
- const state = await deriveState(basePath);
198
- if (!state.activeMilestone || !state.activeSlice || !state.activeTask) return;
227
+ const state = await deriveGsdState(basePath);
228
+ if (!state.activeMilestone || !state.activeSlice) return;
199
229
  // Write checkpoint for ALL phases, not just "executing" — discuss, research,
200
230
  // and planning also carry in-memory state (user answers, gate verification)
201
231
  // that would be lost on compaction (#4258).
@@ -210,21 +240,31 @@ export function registerHooks(
210
240
  if (await loadFile(legacyContinue)) return;
211
241
 
212
242
  const continuePath = join(sliceDir, `${state.activeSlice.id}-CONTINUE.md`);
243
+ const taskId = state.activeTask?.id ?? "none";
244
+ const taskTitle = state.activeTask?.title ?? "";
245
+ const phaseLabel = state.phase.replace(/-/g, " ");
246
+
213
247
  await saveFile(continuePath, formatContinue({
214
248
  frontmatter: {
215
249
  milestone: state.activeMilestone.id,
216
250
  slice: state.activeSlice.id,
217
- task: state.activeTask.id,
251
+ task: taskId,
218
252
  step: 0,
219
253
  totalSteps: 0,
220
254
  status: "compacted" as const,
221
255
  savedAt: new Date().toISOString(),
222
256
  },
223
- completedWork: `Task ${state.activeTask.id} (${state.activeTask.title}) was in progress when compaction occurred.`,
224
- remainingWork: "Check the task plan for remaining steps.",
257
+ completedWork: state.activeTask
258
+ ? `Task ${taskId} (${taskTitle}) was in progress when compaction occurred.`
259
+ : `Slice ${state.activeSlice.id} was in ${phaseLabel} phase when compaction occurred.`,
260
+ remainingWork: state.activeTask
261
+ ? "Check the task plan for remaining steps."
262
+ : "Continue this slice from the latest planning/research/discussion artifacts.",
225
263
  decisions: "Check task summary files for prior decisions.",
226
264
  context: "Session was auto-compacted by Pi. Resume with /gsd.",
227
- nextAction: `Resume task ${state.activeTask.id}: ${state.activeTask.title}.`,
265
+ nextAction: state.activeTask
266
+ ? `Resume task ${taskId}: ${taskTitle}.`
267
+ : `Resume ${phaseLabel} work for slice ${state.activeSlice.id}.`,
228
268
  }));
229
269
  });
230
270
 
@@ -246,7 +286,7 @@ export function registerHooks(
246
286
  const basePath = process.cwd();
247
287
  let activeContext: string | null = null;
248
288
  try {
249
- const state = await deriveState(basePath);
289
+ const state = await deriveGsdState(basePath);
250
290
  if (state.activeMilestone && state.activeSlice && state.activeTask) {
251
291
  activeContext =
252
292
  `Active: ${state.activeMilestone.id} / ${state.activeSlice.id} / ${state.activeTask.id}` +
@@ -265,6 +305,7 @@ export function registerHooks(
265
305
  });
266
306
 
267
307
  pi.on("session_shutdown", async (_event, ctx: ExtensionContext) => {
308
+ const { isParallelActive, shutdownParallel } = await import("../parallel-orchestrator.js");
268
309
  if (isParallelActive()) {
269
310
  try {
270
311
  await shutdownParallel(process.cwd());
@@ -273,7 +314,7 @@ export function registerHooks(
273
314
  }
274
315
  }
275
316
  if (!isAutoActive() && !isAutoPaused()) return;
276
- const dash = getAutoDashboardData();
317
+ const dash = getAutoRuntimeSnapshot();
277
318
  if (dash.currentUnit) {
278
319
  saveActivityLog(ctx, dash.basePath, dash.currentUnit.type, dash.currentUnit.id);
279
320
  }
@@ -302,7 +343,7 @@ export function registerHooks(
302
343
  // If ask_user_questions was called with a gate ID but hasn't been confirmed,
303
344
  // block all non-read-only tool calls to prevent the model from skipping gates.
304
345
  if (getPendingGate()) {
305
- const milestoneId = getDiscussionMilestoneId(discussionBasePath);
346
+ const milestoneId = await getDiscussionMilestoneIdFor(discussionBasePath);
306
347
  if (isToolCallEventType("bash", event)) {
307
348
  const bashGuard = shouldBlockPendingGateBash(
308
349
  event.input.command,
@@ -337,6 +378,36 @@ export function registerHooks(
337
378
  if (queueGuard.block) return queueGuard;
338
379
  }
339
380
 
381
+ // ── Planning-unit tools-policy enforcement (#4934): runtime half ─────
382
+ // The active auto-mode unit's manifest declares a ToolsPolicy. For
383
+ // planning/docs/read-only modes, deny writes outside .gsd/ (or the
384
+ // manifest's allowedPathGlobs), bash that isn't read-only, and
385
+ // subagent dispatch. Closes the b23 bug class where a discuss-milestone
386
+ // turn used the host Edit tool to modify user source files.
387
+ const dash = getAutoRuntimeSnapshot();
388
+ const activeUnitType = dash.currentUnit?.type;
389
+ if (activeUnitType) {
390
+ const manifest = resolveManifest(activeUnitType);
391
+ if (manifest) {
392
+ let planningInput = "";
393
+ if (isToolCallEventType("write", event)) {
394
+ planningInput = event.input.path;
395
+ } else if (isToolCallEventType("edit", event)) {
396
+ planningInput = event.input.path;
397
+ } else if (isToolCallEventType("bash", event)) {
398
+ planningInput = event.input.command;
399
+ }
400
+ const planningGuard = shouldBlockPlanningUnit(
401
+ event.toolName,
402
+ planningInput,
403
+ dash.basePath || discussionBasePath,
404
+ activeUnitType,
405
+ manifest.tools,
406
+ );
407
+ if (planningGuard.block) return planningGuard;
408
+ }
409
+ }
410
+
340
411
  // ── Single-writer engine: block direct writes to STATE.md ──────────
341
412
  // Covers write, edit, and bash tools to prevent bypass vectors.
342
413
  if (isToolCallEventType("write", event)) {
@@ -362,7 +433,7 @@ export function registerHooks(
362
433
  const result = shouldBlockContextWrite(
363
434
  event.toolName,
364
435
  event.input.path,
365
- getDiscussionMilestoneId(discussionBasePath),
436
+ await getDiscussionMilestoneIdFor(discussionBasePath),
366
437
  isQueuePhaseActive(),
367
438
  );
368
439
  if (result.block) return result;
@@ -407,7 +478,7 @@ export function registerHooks(
407
478
  recordToolInvocationError(event.toolName, errorText);
408
479
  }
409
480
  if (event.toolName !== "ask_user_questions") return;
410
- const milestoneId = getDiscussionMilestoneId(process.cwd());
481
+ const milestoneId = await getDiscussionMilestoneIdFor(process.cwd());
411
482
  const queueActive = isQueuePhaseActive();
412
483
 
413
484
  const details = event.details as any;
@@ -506,7 +577,7 @@ export function registerHooks(
506
577
  safetyRecordToolResult(event.toolCallId, event.toolName, event.result, event.isError);
507
578
  // Persist evidence to disk after each tool result so it survives a session
508
579
  // restart mid-unit (Bug #4385 — non-persisted evidence false positives).
509
- const dash = getAutoDashboardData();
580
+ const dash = getAutoRuntimeSnapshot();
510
581
  if (dash.basePath && dash.currentUnit?.type === "execute-task") {
511
582
  const { milestone: pMid, slice: pSid, task: pTid } = parseUnitId(dash.currentUnit.id);
512
583
  if (pMid && pSid && pTid) {
@@ -4,13 +4,14 @@ import { join } from "node:path";
4
4
  import type { ExtensionAPI, ExtensionContext } from "@gsd/pi-coding-agent";
5
5
  import { Key } from "@gsd/pi-tui";
6
6
 
7
- import { GSDDashboardOverlay } from "../dashboard-overlay.js";
8
- import { GSDNotificationOverlay } from "../notification-overlay.js";
9
- import { ParallelMonitorOverlay } from "../parallel-monitor-overlay.js";
10
7
  import { GSD_SHORTCUTS } from "../shortcut-defs.js";
11
- import { projectRoot } from "../commands/context.js";
12
8
  import { shortcutDesc } from "../../shared/mod.js";
13
9
 
10
+ async function getProjectRoot(): Promise<string> {
11
+ const { projectRoot } = await import("../commands/context.js");
12
+ return projectRoot();
13
+ }
14
+
14
15
  export function registerShortcuts(pi: ExtensionAPI): void {
15
16
  const overlayOptions = {
16
17
  width: "90%",
@@ -20,7 +21,10 @@ export function registerShortcuts(pi: ExtensionAPI): void {
20
21
  } as const;
21
22
 
22
23
  const openDashboardOverlay = async (ctx: ExtensionContext) => {
23
- const basePath = projectRoot();
24
+ const [{ GSDDashboardOverlay }, basePath] = await Promise.all([
25
+ import("../dashboard-overlay.js"),
26
+ getProjectRoot(),
27
+ ]);
24
28
  if (!existsSync(join(basePath, ".gsd"))) {
25
29
  ctx.ui.notify("No .gsd/ directory found. Run /gsd to start.", "info");
26
30
  return;
@@ -35,6 +39,7 @@ export function registerShortcuts(pi: ExtensionAPI): void {
35
39
  };
36
40
 
37
41
  const openNotificationsOverlay = async (ctx: ExtensionContext) => {
42
+ const { GSDNotificationOverlay } = await import("../notification-overlay.js");
38
43
  await ctx.ui.custom<boolean>(
39
44
  (tui, theme, _kb, done) => new GSDNotificationOverlay(tui, theme, () => done(true)),
40
45
  {
@@ -51,12 +56,13 @@ export function registerShortcuts(pi: ExtensionAPI): void {
51
56
  };
52
57
 
53
58
  const openParallelOverlay = async (ctx: ExtensionContext) => {
54
- const basePath = projectRoot();
59
+ const basePath = await getProjectRoot();
55
60
  const parallelDir = join(basePath, ".gsd", "parallel");
56
61
  if (!existsSync(parallelDir)) {
57
62
  ctx.ui.notify("No parallel workers found. Run /gsd parallel start first.", "info");
58
63
  return;
59
64
  }
65
+ const { ParallelMonitorOverlay } = await import("../parallel-monitor-overlay.js");
60
66
  await ctx.ui.custom<boolean>(
61
67
  (tui, theme, _kb, done) => new ParallelMonitorOverlay(tui, theme, () => done(true), basePath),
62
68
  {
@@ -15,7 +15,7 @@ import { resolveGsdRootFile, resolveSliceFile, resolveSlicePath, resolveTaskFile
15
15
  import { ensureCodebaseMapFresh, readCodebaseMap } from "../codebase-generator.js";
16
16
  import { hasSkillSnapshot, detectNewSkills, formatSkillsXml } from "../skill-discovery.js";
17
17
  import { getActiveAutoWorktreeContext } from "../auto-worktree.js";
18
- import { getActiveWorktreeName, getWorktreeOriginalCwd } from "../worktree-command.js";
18
+ import { getActiveWorktreeName, getWorktreeOriginalCwd } from "../worktree-session-state.js";
19
19
  import { deriveState } from "../state.js";
20
20
  import { formatOverridesSection, formatShortcut, loadActiveOverrides, loadFile, parseContinue, parseSummary } from "../files.js";
21
21
  import { toPosixPath } from "../../shared/mod.js";
@@ -207,7 +207,14 @@ export async function buildBeforeAgentStartResult(
207
207
  ? `\n\n## Subagent Model\n\nWhen spawning subagents via the \`subagent\` tool, always pass \`model: "${subagentModelConfig.primary}"\` in the tool call parameters. Never omit this — always specify it explicitly.`
208
208
  : "";
209
209
 
210
- const fullSystem = `${event.systemPrompt}\n\n[SYSTEM CONTEXT GSD]\n\n${systemContent}${preferenceBlock}${knowledgeBlock}${codebaseBlock}${memoryBlock}${newSkillsBlock}${worktreeBlock}${subagentModelBlock}`;
210
+ // memoryBlock is FTS-queried against the user prompt and changes per call.
211
+ // Removing it from `fullSystem` keeps the system-prompt cache breakpoint
212
+ // stable across calls — the only scoped goal of this fix. The pi-ai
213
+ // Anthropic adapter additionally cache-marks the last user turn, so the
214
+ // memoryBlock injected via the context message may itself be cached up to
215
+ // that boundary; that's orthogonal and unchanged from prior behavior. The
216
+ // load-bearing win here is preserving the system+tools cache hit. (#5019)
217
+ const fullSystem = `${event.systemPrompt}\n\n[SYSTEM CONTEXT — GSD]\n\n${systemContent}${preferenceBlock}${knowledgeBlock}${codebaseBlock}${newSkillsBlock}${worktreeBlock}${subagentModelBlock}`;
211
218
 
212
219
  stopContextTimer({
213
220
  systemPromptSize: fullSystem.length,
@@ -216,12 +223,7 @@ export async function buildBeforeAgentStartResult(
216
223
  hasNewSkills: newSkillsBlock.length > 0,
217
224
  });
218
225
 
219
- // Determine which context message to inject (guided execute takes priority)
220
- const contextMessage = injection
221
- ? { customType: "gsd-guided-context", content: injection, display: false as const }
222
- : forensicsInjection
223
- ? { customType: "gsd-forensics", content: forensicsInjection, display: false as const }
224
- : null;
226
+ const contextMessage = buildContextMessage({ memoryBlock, injection, forensicsInjection });
225
227
 
226
228
  return {
227
229
  systemPrompt: fullSystem,
@@ -229,6 +231,35 @@ export async function buildBeforeAgentStartResult(
229
231
  };
230
232
  }
231
233
 
234
+ /**
235
+ * Route the per-call dynamic blocks (memory, guided-execute, forensics) into a
236
+ * single user-message context payload so they ride the volatile suffix instead
237
+ * of the cached system prefix. Priority when both memory and an injection are
238
+ * present: guided > forensics > memory-only. (#5019)
239
+ *
240
+ * Exported for direct unit testing — the surrounding bootstrap has too many
241
+ * filesystem and DB dependencies to exercise this routing logic in-place.
242
+ */
243
+ export function buildContextMessage(opts: {
244
+ memoryBlock: string;
245
+ injection: string | null;
246
+ forensicsInjection: string | null;
247
+ }): { customType: string; content: string; display: false } | null {
248
+ const memoryContent = opts.memoryBlock.trim();
249
+ if (opts.injection) {
250
+ const content = memoryContent ? `${memoryContent}\n\n${opts.injection}` : opts.injection;
251
+ return { customType: "gsd-guided-context", content, display: false as const };
252
+ }
253
+ if (opts.forensicsInjection) {
254
+ const content = memoryContent ? `${memoryContent}\n\n${opts.forensicsInjection}` : opts.forensicsInjection;
255
+ return { customType: "gsd-forensics", content, display: false as const };
256
+ }
257
+ if (memoryContent) {
258
+ return { customType: "gsd-memory", content: memoryContent, display: false as const };
259
+ }
260
+ return null;
261
+ }
262
+
232
263
  /**
233
264
  * ADR-013 step 4 — auto-injection parity for the memories table.
234
265
  *
@@ -3,15 +3,7 @@ import { isAbsolute, join, relative, resolve, sep } from "node:path";
3
3
 
4
4
  import { minimatch } from "minimatch";
5
5
 
6
- /**
7
- * Declarative tools-policy for a unit. Inlined here because the worktree
8
- * branch predates the full unit-context-manifest export (#4934).
9
- */
10
- export type ToolsPolicy =
11
- | { mode: "all" }
12
- | { mode: "read-only" }
13
- | { mode: "planning" }
14
- | { mode: "docs"; allowedPathGlobs: readonly string[] };
6
+ import type { ToolsPolicy } from "../unit-context-manifest.js";
15
7
 
16
8
  /**
17
9
  * Regex matching milestone CONTEXT.md file names in both legacy M001
@@ -535,10 +527,33 @@ export function shouldBlockQueueExecutionInSnapshot(
535
527
  }
536
528
 
537
529
  // ─── Planning-unit tools-policy enforcement (#4934) ───────────────────────
530
+ //
531
+ // Runtime half of the declarative ToolsPolicy on UnitContextManifest. The
532
+ // manifest assigns each unit type a tools mode; this predicate is what
533
+ // actually rejects a tool call that violates it.
534
+ //
535
+ // Forensics: a discuss-milestone LLM turn used the host Edit tool to modify
536
+ // index.html in test app b23 (~/Github/test-apps/b23). With this predicate
537
+ // wired into the tool_call hook, the same call returns block=true with a
538
+ // HARD BLOCK reason that the model cannot rationalize past.
539
+ //
540
+ // Activation: the hook supplies the policy resolved from the active unit's
541
+ // manifest. When no unit is active (interactive sessions, unknown unit
542
+ // types), the hook passes null and this predicate is a no-op — falling
543
+ // through to the existing pendingGate / queue-execution / context-write
544
+ // guards.
538
545
 
539
546
  const PLANNING_WRITE_TOOLS = new Set(["write", "edit", "multi_edit", "notebook_edit"]);
540
547
  const PLANNING_SUBAGENT_TOOLS = new Set(["subagent", "task"]);
541
548
 
549
+ /**
550
+ * Read-only / planning-safe tools that any non-"all" mode allows. Mirrors
551
+ * QUEUE_SAFE_TOOLS / GATE_SAFE_TOOLS but is the inclusive default for
552
+ * planning units (which need their full discussion + research surface).
553
+ *
554
+ * gsd_* MCP tools are passed through unconditionally — they have their own
555
+ * domain validation (e.g. depth-verification gate, single-writer DB).
556
+ */
542
557
  const PLANNING_SAFE_TOOLS = new Set([
543
558
  "read", "grep", "find", "ls", "glob",
544
559
  "ask_user_questions",
@@ -555,6 +570,7 @@ function isPathUnderGsd(absPath: string, basePath: string): boolean {
555
570
  function matchesAllowedGlob(absPath: string, basePath: string, globs: readonly string[]): boolean {
556
571
  const rel = relative(basePath, absPath);
557
572
  if (rel.startsWith("..") || isAbsolute(rel)) return false;
573
+ // Normalize Windows separators for minimatch.
558
574
  const posix = rel.split(sep).join("/");
559
575
  return globs.some(g => minimatch(posix, g, { dot: false, nocase: false }));
560
576
  }
@@ -579,7 +595,12 @@ function blockReason(unitType: string, mode: string, what: string): string {
579
595
  * - "docs" → like "planning" but also allows writes to paths
580
596
  * matching `allowedPathGlobs` relative to basePath.
581
597
  *
582
- * `policy` of null means "no manifest resolved" pass-through.
598
+ * `pathOrCommand` is the file path for write/edit-shaped tools and the
599
+ * shell command for bash. Other tools ignore this argument.
600
+ *
601
+ * `policy` of null means "no manifest resolved" — pass-through. Callers
602
+ * that have no active unit (interactive sessions) pass null and this
603
+ * predicate is a no-op.
583
604
  */
584
605
  export function shouldBlockPlanningUnit(
585
606
  toolName: string,
@@ -593,16 +614,18 @@ export function shouldBlockPlanningUnit(
593
614
 
594
615
  const tool = toolName;
595
616
 
617
+ // Read-only mode: only Read-class tools are permitted.
596
618
  if (policy.mode === "read-only") {
597
619
  if (PLANNING_SAFE_TOOLS.has(tool)) return { block: false };
598
620
  if (tool.startsWith("gsd_")) return { block: false };
599
621
  if (PLANNING_WRITE_TOOLS.has(tool) || tool === "bash" || PLANNING_SUBAGENT_TOOLS.has(tool)) {
600
622
  return { block: true, reason: blockReason(unitType, policy.mode, `${tool} is not permitted (read-only)`) };
601
623
  }
624
+ // Unknown tool in read-only mode — block by default.
602
625
  return { block: true, reason: blockReason(unitType, policy.mode, `tool "${tool}" is not on the read-only allowlist`) };
603
626
  }
604
627
 
605
- // planning / docs modes
628
+ // planning / docs modes share the same surface for safe tools, bash, and subagent.
606
629
  if (PLANNING_SAFE_TOOLS.has(tool)) return { block: false };
607
630
  if (tool.startsWith("gsd_")) return { block: false };
608
631
 
@@ -628,8 +651,10 @@ export function shouldBlockPlanningUnit(
628
651
  }
629
652
  const absPath = isAbsolute(pathOrCommand) ? pathOrCommand : resolve(basePath, pathOrCommand);
630
653
 
654
+ // Always allow .gsd/ writes — that's where planning artifacts live.
631
655
  if (isPathUnderGsd(absPath, basePath)) return { block: false };
632
656
 
657
+ // docs mode additionally allows the manifest's allowedPathGlobs.
633
658
  if (policy.mode === "docs" && matchesAllowedGlob(absPath, basePath, policy.allowedPathGlobs)) {
634
659
  return { block: false };
635
660
  }
@@ -644,5 +669,8 @@ export function shouldBlockPlanningUnit(
644
669
  };
645
670
  }
646
671
 
672
+ // Unknown tool name — pass through. Other layers (queue, pending-gate,
673
+ // CONTEXT.md write) catch known mutating shapes; defaulting to allow here
674
+ // avoids breaking gsd_* MCP tools or future safe additions.
647
675
  return { block: false };
648
676
  }
@@ -1,9 +1,8 @@
1
1
  import { existsSync, readFileSync, readdirSync } from "node:fs";
2
2
  import { homedir } from "node:os";
3
- import { join } from "node:path";
3
+ import { join, resolve } from "node:path";
4
4
 
5
5
  import { loadRegistry } from "../workflow-templates.js";
6
- import { resolveProjectRoot } from "../worktree.js";
7
6
 
8
7
  const gsdHome = process.env.GSD_HOME || join(homedir(), ".gsd");
9
8
 
@@ -15,7 +14,7 @@ export interface GsdCommandDefinition {
15
14
  type CompletionMap = Record<string, readonly GsdCommandDefinition[]>;
16
15
 
17
16
  export const GSD_COMMAND_DESCRIPTION =
18
- "GSD — Get Shit Done: /gsd help|start|templates|next|auto|stop|pause|status|widget|visualize|queue|quick|discuss|capture|triage|dispatch|history|undo|undo-task|reset-slice|rate|skip|export|cleanup|model|mode|prefs|config|keys|hooks|run-hook|skill-health|doctor|debug|logs|forensics|changelog|migrate|remote|steer|knowledge|new-milestone|parallel|cmux|park|unpark|init|setup|onboarding|inspect|extensions|update|fast|mcp|rethink|codebase|notifications|ship|do|session-report|backlog|pr-branch|add-tests|scan|language";
17
+ "GSD — Get Shit Done: /gsd help|start|templates|next|auto|stop|pause|status|widget|visualize|queue|quick|discuss|capture|triage|dispatch|history|undo|undo-task|reset-slice|rate|skip|export|cleanup|model|mode|prefs|config|keys|hooks|run-hook|skill-health|doctor|debug|logs|forensics|changelog|migrate|remote|steer|knowledge|new-milestone|parallel|cmux|park|unpark|init|setup|onboarding|inspect|extensions|update|fast|mcp|rethink|workflow|codebase|notifications|ship|do|session-report|backlog|pr-branch|add-tests|scan|language";
19
18
 
20
19
  export const TOP_LEVEL_SUBCOMMANDS: readonly GsdCommandDefinition[] = [
21
20
  { cmd: "help", desc: "Categorized command reference with descriptions" },
@@ -346,6 +345,77 @@ function getExtensionCompletions(prefix: string, action: string) {
346
345
  }
347
346
  }
348
347
 
348
+ function normalizePathForCompare(path: string): string {
349
+ return path.replaceAll("\\", "/").replace(/\/+$/, "");
350
+ }
351
+
352
+ function findWorktreeSegment(normalizedPath: string): { gsdIdx: number; afterWorktrees: number } | null {
353
+ const directMarker = "/.gsd/worktrees/";
354
+ const directIdx = normalizedPath.indexOf(directMarker);
355
+ if (directIdx !== -1) {
356
+ return { gsdIdx: directIdx, afterWorktrees: directIdx + directMarker.length };
357
+ }
358
+
359
+ const symlinkMatch = normalizedPath.match(/\/\.gsd\/projects\/[a-f0-9]+\/worktrees\//);
360
+ if (symlinkMatch?.index !== undefined) {
361
+ return { gsdIdx: symlinkMatch.index, afterWorktrees: symlinkMatch.index + symlinkMatch[0].length };
362
+ }
363
+
364
+ return null;
365
+ }
366
+
367
+ function resolveProjectRootFromGitFile(worktreePath: string): string | null {
368
+ try {
369
+ let dir = worktreePath;
370
+ for (let i = 0; i < 30; i++) {
371
+ const gitPath = join(dir, ".git");
372
+ if (existsSync(gitPath)) {
373
+ const content = readFileSync(gitPath, "utf8").trim();
374
+ if (content.startsWith("gitdir: ")) {
375
+ const gitDir = resolve(dir, content.slice(8));
376
+ const dotGitDir = resolve(gitDir, "..", "..");
377
+ if (dotGitDir.endsWith(".git") || dotGitDir.endsWith(".git/") || dotGitDir.endsWith(".git\\")) {
378
+ return resolve(dotGitDir, "..");
379
+ }
380
+ const commonDirPath = join(gitDir, "commondir");
381
+ if (existsSync(commonDirPath)) {
382
+ const commonDir = readFileSync(commonDirPath, "utf8").trim();
383
+ return resolve(resolve(gitDir, commonDir), "..");
384
+ }
385
+ }
386
+ break;
387
+ }
388
+ const parent = resolve(dir, "..");
389
+ if (parent === dir) break;
390
+ dir = parent;
391
+ }
392
+ } catch {
393
+ // Completion must stay best-effort.
394
+ }
395
+ return null;
396
+ }
397
+
398
+ function resolveProjectRootForCompletion(basePath: string): string {
399
+ if (process.env.GSD_PROJECT_ROOT) return process.env.GSD_PROJECT_ROOT;
400
+
401
+ const normalizedPath = normalizePathForCompare(basePath);
402
+ const segment = findWorktreeSegment(normalizedPath);
403
+ if (!segment) return basePath;
404
+
405
+ const separator = basePath.includes("\\") ? "\\" : "/";
406
+ const gsdMarker = `${separator}.gsd${separator}`;
407
+ const gsdIdx = basePath.indexOf(gsdMarker);
408
+ const candidate = gsdIdx !== -1 ? basePath.slice(0, gsdIdx) : basePath.slice(0, segment.gsdIdx);
409
+
410
+ const normalizedGsdHome = normalizePathForCompare(gsdHome);
411
+ const candidateGsdPath = normalizePathForCompare(join(candidate, ".gsd"));
412
+ if (candidateGsdPath === normalizedGsdHome || candidateGsdPath.startsWith(`${normalizedGsdHome}/`)) {
413
+ return resolveProjectRootFromGitFile(basePath) ?? basePath;
414
+ }
415
+
416
+ return candidate;
417
+ }
418
+
349
419
  export function getGsdArgumentCompletions(prefix: string) {
350
420
  const hasTrailingSpace = prefix.endsWith(" ");
351
421
  const parts = prefix.trim().split(/\s+/);
@@ -406,7 +476,7 @@ export function getGsdArgumentCompletions(prefix: string) {
406
476
  // Workflow definition-name completion for `workflow run <name>` and `workflow validate <name>`
407
477
  if (command === "workflow" && (subcommand === "run" || subcommand === "validate") && parts.length <= 3) {
408
478
  try {
409
- const defsDir = join(resolveProjectRoot(process.cwd()), ".gsd", "workflow-defs");
479
+ const defsDir = join(resolveProjectRootForCompletion(process.cwd()), ".gsd", "workflow-defs");
410
480
  if (existsSync(defsDir)) {
411
481
  return readdirSync(defsDir)
412
482
  .filter((f) => f.endsWith(".yaml") && f.startsWith(third))
@@ -443,7 +513,7 @@ export function getGsdArgumentCompletions(prefix: string) {
443
513
  } catch { /* ignore */ }
444
514
  };
445
515
  try {
446
- const base = resolveProjectRoot(process.cwd());
516
+ const base = resolveProjectRootForCompletion(process.cwd());
447
517
  scanDir(join(base, ".gsd", "workflows"), "project");
448
518
  scanDir(join(base, ".gsd", "workflow-defs"), "project-legacy");
449
519
  scanDir(join(gsdHome, "workflows"), "global");