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
@@ -26,6 +26,7 @@ import {
26
26
  import { detectStuck } from "./detect-stuck.js";
27
27
  import { runUnit } from "./run-unit.js";
28
28
  import { debugLog } from "../debug-logger.js";
29
+ import { resolveWorktreeProjectRoot } from "../worktree-root.js";
29
30
  import { PROJECT_FILES, hasProjectFileInAncestor } from "../detection.js";
30
31
  import { MergeConflictError } from "../git-service.js";
31
32
  import { setCurrentPhase, clearCurrentPhase } from "../../shared/gsd-phase-state.js";
@@ -81,11 +82,7 @@ export function resetSessionTimeoutState(): void {
81
82
  * Exported for testing as _resolveReportBasePath.
82
83
  */
83
84
  export function _resolveReportBasePath(s: Pick<AutoSession, "originalBasePath" | "basePath">): string {
84
- // Strip /.gsd/worktrees/ suffix when basePath is itself a worktree path and
85
- // originalBasePath is falsy — prevents reports landing in the worktree (#3729).
86
- const resolved = s.originalBasePath || s.basePath;
87
- const markerIdx = resolved.indexOf("/.gsd/worktrees/");
88
- return markerIdx !== -1 ? resolved.slice(0, markerIdx) : resolved;
85
+ return resolveWorktreeProjectRoot(s.basePath, s.originalBasePath);
89
86
  }
90
87
 
91
88
  /**
@@ -96,12 +93,7 @@ export function _resolveReportBasePath(s: Pick<AutoSession, "originalBasePath" |
96
93
  export function _resolveDispatchGuardBasePath(
97
94
  s: Pick<AutoSession, "originalBasePath" | "basePath">,
98
95
  ): string {
99
- // Strip /.gsd/worktrees/ suffix when basePath is itself a worktree path and
100
- // originalBasePath is falsy — prevents guard checks running against the
101
- // worktree instead of the project root (#3729).
102
- const resolved = s.originalBasePath || s.basePath;
103
- const markerIdx = resolved.indexOf("/.gsd/worktrees/");
104
- return markerIdx !== -1 ? resolved.slice(0, markerIdx) : resolved;
96
+ return resolveWorktreeProjectRoot(s.basePath, s.originalBasePath);
105
97
  }
106
98
 
107
99
  const PLAN_V2_GATE_PHASES: ReadonlySet<Phase> = new Set([
@@ -1342,7 +1334,7 @@ export async function runUnitPhase(
1342
1334
  iterData: IterationData,
1343
1335
  loopState: LoopState,
1344
1336
  sidecarItem?: SidecarItem,
1345
- ): Promise<PhaseResult<{ unitStartedAt: number }>> {
1337
+ ): Promise<PhaseResult<{ unitStartedAt?: number; requestDispatchedAt?: number }>> {
1346
1338
  const { ctx, pi, s, deps, prefs } = ic;
1347
1339
  const { unitType, unitId, prompt, state, mid } = iterData;
1348
1340
 
@@ -1860,7 +1852,7 @@ export async function runUnitPhase(
1860
1852
  );
1861
1853
  // Fall through to next iteration where dispatch will re-derive
1862
1854
  // and re-dispatch this unit.
1863
- return { action: "next", data: { unitStartedAt: s.currentUnit?.startedAt } };
1855
+ return { action: "next", data: { unitStartedAt: s.currentUnit?.startedAt, requestDispatchedAt: unitResult.requestDispatchedAt } };
1864
1856
  }
1865
1857
  }
1866
1858
  }
@@ -1924,7 +1916,7 @@ export async function runUnitPhase(
1924
1916
  s.checkpointSha = null;
1925
1917
  }
1926
1918
 
1927
- return { action: "next", data: { unitStartedAt: s.currentUnit?.startedAt } };
1919
+ return { action: "next", data: { unitStartedAt: s.currentUnit?.startedAt, requestDispatchedAt: unitResult.requestDispatchedAt } };
1928
1920
  }
1929
1921
 
1930
1922
  // ─── runFinalize ──────────────────────────────────────────────────────────────
@@ -40,6 +40,29 @@ export async function runUnit(
40
40
  ): Promise<UnitResult> {
41
41
  debugLog("runUnit", { phase: "start", unitType, unitId });
42
42
 
43
+ // Ensure cwd matches basePath BEFORE newSession() captures it. The new
44
+ // session reads process.cwd() during construction to anchor its tool
45
+ // runtime and system prompt; if cwd has drifted (async_bash, background
46
+ // jobs, prior unit cleanup), the session would otherwise be rooted to
47
+ // the wrong directory. Must be synchronous — no awaits between chdir
48
+ // and newSession (#1389, #4762 follow-up).
49
+ try {
50
+ if (process.cwd() !== s.basePath) {
51
+ process.chdir(s.basePath);
52
+ }
53
+ } catch (e) {
54
+ const msg = `Failed to chdir to basePath before newSession (basePath: ${s.basePath}): ${String(e)}`;
55
+ logWarning("engine", msg, { basePath: s.basePath, error: String(e) });
56
+ return {
57
+ status: "cancelled",
58
+ errorContext: {
59
+ message: msg,
60
+ category: "session-failed",
61
+ isTransient: true,
62
+ },
63
+ };
64
+ }
65
+
43
66
  // ── Session creation with timeout ──
44
67
  debugLog("runUnit", { phase: "session-create", unitType, unitId });
45
68
 
@@ -120,17 +143,6 @@ export async function runUnit(
120
143
  _setCurrentResolve(resolve);
121
144
  });
122
145
 
123
- // Ensure cwd matches basePath before dispatch (#1389).
124
- // async_bash and background jobs can drift cwd away from the worktree.
125
- // Realigning here prevents commits from landing on the wrong branch.
126
- try {
127
- if (process.cwd() !== s.basePath) {
128
- process.chdir(s.basePath);
129
- }
130
- } catch (e) {
131
- logWarning("engine", "Failed to chdir to basePath before dispatch", { basePath: s.basePath, error: String(e) });
132
- }
133
-
134
146
  // ── Provider request-readiness pre-check (#4555) ──
135
147
  // Verify the provider can accept requests before dispatching. If the token
136
148
  // has expired since bootstrap, return cancelled immediately so the unit is
@@ -171,6 +183,7 @@ export async function runUnit(
171
183
  // ── Send the prompt ──
172
184
  debugLog("runUnit", { phase: "send-message", unitType, unitId });
173
185
 
186
+ const requestDispatchedAt = Date.now();
174
187
  pi.sendMessage(
175
188
  { customType: "gsd-auto", content: prompt, display: s.verbose },
176
189
  { triggerTurn: true },
@@ -201,6 +214,7 @@ export async function runUnit(
201
214
  unitId,
202
215
  status: result.status,
203
216
  });
217
+ const finalResult: UnitResult = { ...result, requestDispatchedAt };
204
218
 
205
219
  // Discard trailing follow-up messages (e.g. async_job_result notifications)
206
220
  // from the completed unit. Without this, queued follow-ups trigger wasteful
@@ -216,5 +230,5 @@ export async function runUnit(
216
230
  logWarning("engine", "clearQueue failed after unit completion", { error: String(e) });
217
231
  }
218
232
 
219
- return result;
233
+ return finalResult;
220
234
  }
@@ -21,6 +21,7 @@ import type { ExtensionAPI, ExtensionCommandContext } from "@gsd/pi-coding-agent
21
21
  import type { GitServiceImpl } from "../git-service.js";
22
22
  import type { CaptureEntry } from "../captures.js";
23
23
  import type { BudgetAlertLevel } from "../auto-budget.js";
24
+ import { resolveWorktreeProjectRoot } from "../worktree-root.js";
24
25
 
25
26
  // ─── Exported Types ──────────────────────────────────────────────────────────
26
27
 
@@ -193,6 +194,8 @@ export class AutoSession {
193
194
  lastPromptCharCount: number | undefined;
194
195
  lastBaselineCharCount: number | undefined;
195
196
  pendingQuickTasks: CaptureEntry[] = [];
197
+ /** Timestamp of the last LLM request dispatch (ms since epoch). Used for proactive rate limiting. */
198
+ lastRequestTimestamp = 0;
196
199
 
197
200
  // ── Safety harness ───────────────────────────────────────────────────────
198
201
  /** SHA of the pre-unit git checkpoint ref. Cleared on success or rollback. */
@@ -224,12 +227,7 @@ export class AutoSession {
224
227
  }
225
228
 
226
229
  get lockBasePath(): string {
227
- // Prefer originalBasePath (project root); fall back to basePath.
228
- // Strip /.gsd/worktrees/ suffix if basePath is itself a worktree path
229
- // to avoid reading/writing the lock inside the worktree (#3729).
230
- const resolved = this.originalBasePath || this.basePath;
231
- const markerIdx = resolved.indexOf("/.gsd/worktrees/");
232
- return markerIdx !== -1 ? resolved.slice(0, markerIdx) : resolved;
230
+ return resolveWorktreeProjectRoot(this.basePath, this.originalBasePath);
233
231
  }
234
232
 
235
233
  reset(): void {
@@ -294,6 +292,7 @@ export class AutoSession {
294
292
  this.lastPromptCharCount = undefined;
295
293
  this.lastBaselineCharCount = undefined;
296
294
  this.pendingQuickTasks = [];
295
+ this.lastRequestTimestamp = 0;
297
296
  this.sidecarQueue = [];
298
297
  this.rewriteAttemptCount = 0;
299
298
  this.consecutiveCompleteBootstraps = 0;
@@ -66,6 +66,7 @@ export interface UnitResult {
66
66
  status: "completed" | "cancelled" | "error";
67
67
  event?: AgentEndEvent;
68
68
  errorContext?: ErrorContext;
69
+ requestDispatchedAt?: number;
69
70
  }
70
71
 
71
72
  // ─── Phase pipeline types ────────────────────────────────────────────────────
@@ -19,6 +19,7 @@ import { getActiveHook } from "./post-unit-hooks.js";
19
19
  import { getLedger, getProjectTotals } from "./metrics.js";
20
20
  import { getErrorMessage } from "./error-utils.js";
21
21
  import { nativeIsRepo } from "./native-git-bridge.js";
22
+ import { getHomeDir } from "./home-dir.js";
22
23
  import {
23
24
  resolveMilestoneFile,
24
25
  resolveSliceFile,
@@ -582,8 +583,8 @@ export function updateProgressWidget(
582
583
  let widgetPwd: string;
583
584
  {
584
585
  let fullPwd = process.cwd();
585
- const widgetHome = process.env.HOME || process.env.USERPROFILE;
586
- if (widgetHome && fullPwd.startsWith(widgetHome)) {
586
+ const widgetHome = getHomeDir();
587
+ if (widgetHome && (fullPwd === widgetHome || fullPwd.startsWith(widgetHome + "/") || fullPwd.startsWith(widgetHome + "\\"))) {
587
588
  fullPwd = `~${fullPwd.slice(widgetHome.length)}`;
588
589
  }
589
590
  const parts = fullPwd.split("/");
@@ -30,6 +30,8 @@ import {
30
30
  import { loadEffectiveGSDPreferences } from "./preferences.js";
31
31
  import type { MinimalModelRegistry } from "./context-budget.js";
32
32
  import { pauseAuto } from "./auto.js";
33
+ import { resolveCanonicalMilestoneRoot } from "./worktree-manager.js";
34
+ import { logWarning } from "./workflow-logger.js";
33
35
  import {
34
36
  getWorkflowTransportSupportError,
35
37
  getRequiredWorkflowToolsForAutoUnit,
@@ -50,6 +52,14 @@ export async function dispatchDirectPhase(
50
52
  return;
51
53
  }
52
54
 
55
+ const projectRoot = base;
56
+
57
+ // Switch the dispatch base to the canonical milestone worktree if one
58
+ // exists. Without this, /gsd dispatch invoked from the project root would
59
+ // build prompts and create a session anchored to the project root even
60
+ // though the milestone's actual code lives in the worktree.
61
+ const dispatchBase = resolveCanonicalMilestoneRoot(base, mid);
62
+
53
63
  const normalized = phase.toLowerCase();
54
64
  let unitType: string;
55
65
  let unitId: string;
@@ -70,7 +80,7 @@ export async function dispatchDirectPhase(
70
80
 
71
81
  // When require_slice_discussion is enabled, pause auto-mode before
72
82
  // each new slice so the user can discuss requirements first (#789).
73
- const sliceContextFile = resolveSliceFile(base, mid, sid, "CONTEXT");
83
+ const sliceContextFile = resolveSliceFile(dispatchBase, mid, sid, "CONTEXT");
74
84
  const requireDiscussion = loadEffectiveGSDPreferences()?.preferences?.phases?.require_slice_discussion;
75
85
  if (requireDiscussion && !sliceContextFile) {
76
86
  ctx.ui.notify(
@@ -83,11 +93,11 @@ export async function dispatchDirectPhase(
83
93
 
84
94
  unitType = "research-slice";
85
95
  unitId = `${mid}/${sid}`;
86
- prompt = await buildResearchSlicePrompt(mid, midTitle, sid, sTitle, base);
96
+ prompt = await buildResearchSlicePrompt(mid, midTitle, sid, sTitle, dispatchBase);
87
97
  } else {
88
98
  unitType = "research-milestone";
89
99
  unitId = mid;
90
- prompt = await buildResearchMilestonePrompt(mid, midTitle, base);
100
+ prompt = await buildResearchMilestonePrompt(mid, midTitle, dispatchBase);
91
101
  }
92
102
  break;
93
103
  }
@@ -106,7 +116,7 @@ export async function dispatchDirectPhase(
106
116
  unitType = "plan-slice";
107
117
  unitId = `${mid}/${sid}`;
108
118
  prompt = await buildPlanSlicePrompt(
109
- mid, midTitle, sid, sTitle, base, undefined,
119
+ mid, midTitle, sid, sTitle, dispatchBase, undefined,
110
120
  {
111
121
  sessionContextWindow: ctx.model?.contextWindow,
112
122
  modelRegistry: ctx.modelRegistry as MinimalModelRegistry | undefined,
@@ -115,7 +125,7 @@ export async function dispatchDirectPhase(
115
125
  } else {
116
126
  unitType = "plan-milestone";
117
127
  unitId = mid;
118
- prompt = await buildPlanMilestonePrompt(mid, midTitle, base);
128
+ prompt = await buildPlanMilestonePrompt(mid, midTitle, dispatchBase);
119
129
  }
120
130
  break;
121
131
  }
@@ -137,7 +147,7 @@ export async function dispatchDirectPhase(
137
147
  unitType = "execute-task";
138
148
  unitId = `${mid}/${sid}/${tid}`;
139
149
  prompt = await buildExecuteTaskPrompt(
140
- mid, sid, sTitle, tid, tTitle, base,
150
+ mid, sid, sTitle, tid, tTitle, dispatchBase,
141
151
  {
142
152
  sessionContextWindow: ctx.model?.contextWindow,
143
153
  modelRegistry: ctx.modelRegistry as MinimalModelRegistry | undefined,
@@ -159,11 +169,11 @@ export async function dispatchDirectPhase(
159
169
  }
160
170
  unitType = "complete-slice";
161
171
  unitId = `${mid}/${sid}`;
162
- prompt = await buildCompleteSlicePrompt(mid, midTitle, sid, sTitle, base);
172
+ prompt = await buildCompleteSlicePrompt(mid, midTitle, sid, sTitle, dispatchBase);
163
173
  } else {
164
174
  unitType = "complete-milestone";
165
175
  unitId = mid;
166
- prompt = await buildCompleteMilestonePrompt(mid, midTitle, base);
176
+ prompt = await buildCompleteMilestonePrompt(mid, midTitle, dispatchBase);
167
177
  }
168
178
  break;
169
179
  }
@@ -177,7 +187,7 @@ export async function dispatchDirectPhase(
177
187
  }
178
188
  if (completedSliceIds.length === 0) {
179
189
  // File-based fallback: parse roadmap checkboxes
180
- const roadmapPath = resolveMilestoneFile(base, mid, "ROADMAP");
190
+ const roadmapPath = resolveMilestoneFile(dispatchBase, mid, "ROADMAP");
181
191
  if (roadmapPath) {
182
192
  const roadmapContent = await loadFile(roadmapPath);
183
193
  if (roadmapContent) {
@@ -192,7 +202,7 @@ export async function dispatchDirectPhase(
192
202
  const completedSliceId = completedSliceIds[completedSliceIds.length - 1];
193
203
  unitType = "reassess-roadmap";
194
204
  unitId = `${mid}/${completedSliceId}`;
195
- prompt = await buildReassessRoadmapPrompt(mid, midTitle, completedSliceId, base);
205
+ prompt = await buildReassessRoadmapPrompt(mid, midTitle, completedSliceId, dispatchBase);
196
206
  break;
197
207
  }
198
208
 
@@ -208,7 +218,7 @@ export async function dispatchDirectPhase(
208
218
  }
209
219
  if (uatCompletedSliceIds.length === 0) {
210
220
  // File-based fallback: parse roadmap checkboxes
211
- const roadmapPath = resolveMilestoneFile(base, mid, "ROADMAP");
221
+ const roadmapPath = resolveMilestoneFile(dispatchBase, mid, "ROADMAP");
212
222
  if (roadmapPath) {
213
223
  const roadmapContent = await loadFile(roadmapPath);
214
224
  if (roadmapContent) {
@@ -221,7 +231,7 @@ export async function dispatchDirectPhase(
221
231
  return;
222
232
  }
223
233
  const sid = uatCompletedSliceIds[uatCompletedSliceIds.length - 1];
224
- const uatFile = resolveSliceFile(base, mid, sid, "UAT");
234
+ const uatFile = resolveSliceFile(dispatchBase, mid, sid, "UAT");
225
235
  if (!uatFile) {
226
236
  ctx.ui.notify("Cannot dispatch run-uat: no UAT file found.", "warning");
227
237
  return;
@@ -231,10 +241,10 @@ export async function dispatchDirectPhase(
231
241
  ctx.ui.notify("Cannot dispatch run-uat: UAT file is empty.", "warning");
232
242
  return;
233
243
  }
234
- const uatPath = relSliceFile(base, mid, sid, "UAT");
244
+ const uatPath = relSliceFile(dispatchBase, mid, sid, "UAT");
235
245
  unitType = "run-uat";
236
246
  unitId = `${mid}/${sid}`;
237
- prompt = await buildRunUatPrompt(mid, sid, uatPath, uatContent, base);
247
+ prompt = await buildRunUatPrompt(mid, sid, uatPath, uatContent, dispatchBase);
238
248
  break;
239
249
  }
240
250
 
@@ -248,7 +258,7 @@ export async function dispatchDirectPhase(
248
258
  }
249
259
  unitType = "replan-slice";
250
260
  unitId = `${mid}/${sid}`;
251
- prompt = await buildReplanSlicePrompt(mid, midTitle, sid, sTitle, base);
261
+ prompt = await buildReplanSlicePrompt(mid, midTitle, sid, sTitle, dispatchBase);
252
262
  break;
253
263
  }
254
264
 
@@ -264,7 +274,7 @@ export async function dispatchDirectPhase(
264
274
  ctx.model?.provider,
265
275
  getRequiredWorkflowToolsForAutoUnit(unitType),
266
276
  {
267
- projectRoot: base,
277
+ projectRoot,
268
278
  surface: "direct phase dispatch",
269
279
  unitType,
270
280
  authMode: ctx.model?.provider ? ctx.modelRegistry.getProviderAuthMode(ctx.model.provider) : undefined,
@@ -277,13 +287,39 @@ export async function dispatchDirectPhase(
277
287
  }
278
288
 
279
289
  ctx.ui.notify(`Dispatching ${unitType} for ${unitId}...`, "info");
280
- const result = await ctx.newSession();
281
- if (result.cancelled) {
282
- ctx.ui.notify("Session creation cancelled.", "warning");
283
- return;
290
+
291
+ const originalCwd = process.cwd();
292
+
293
+ try {
294
+ // Ensure cwd matches dispatchBase BEFORE newSession() captures it. Synchronous —
295
+ // no awaits between chdir and newSession.
296
+ try {
297
+ if (process.cwd() !== dispatchBase) {
298
+ process.chdir(dispatchBase);
299
+ }
300
+ } catch (err) {
301
+ const msg = `Failed to chdir before direct-dispatch newSession (basePath: ${dispatchBase}): ${err instanceof Error ? err.message : String(err)}`;
302
+ logWarning("engine", msg, { file: "auto-direct-dispatch.ts", basePath: dispatchBase, error: err instanceof Error ? err.message : String(err) });
303
+ ctx.ui.notify(`${msg}. Cancelling dispatch to avoid running in the wrong directory.`, "error");
304
+ return;
305
+ }
306
+
307
+ const result = await ctx.newSession();
308
+ if (result.cancelled) {
309
+ ctx.ui.notify("Session creation cancelled.", "warning");
310
+ return;
311
+ }
312
+ pi.sendMessage(
313
+ { customType: "gsd-dispatch", content: prompt, display: false },
314
+ { triggerTurn: true },
315
+ );
316
+ } finally {
317
+ try {
318
+ if (process.cwd() !== originalCwd) {
319
+ process.chdir(originalCwd);
320
+ }
321
+ } catch (err) {
322
+ logWarning("engine", `Failed to restore cwd after direct dispatch: ${err instanceof Error ? err.message : String(err)}`, { file: "auto-direct-dispatch.ts", basePath: originalCwd });
323
+ }
284
324
  }
285
- pi.sendMessage(
286
- { customType: "gsd-dispatch", content: prompt, display: false },
287
- { triggerTurn: true },
288
- );
289
325
  }
@@ -797,14 +797,25 @@ export const DISPATCH_RULES: DispatchRule[] = [
797
797
  if (state.phase !== "executing" || !state.activeTask) return null;
798
798
  if (!state.activeSlice) return null; // fall through
799
799
 
800
- // Only activate when reactive_execution is explicitly enabled
800
+ // Reactive dispatch is on by default when there are enough ready tasks to
801
+ // benefit from parallelism. Users opt out explicitly via
802
+ // `reactive_execution.enabled: false`. The downstream safety checks
803
+ // (graph ambiguity, ready-task count, conflict-free selection) still gate
804
+ // every actual dispatch, so the worst-case "default-on" outcome is the
805
+ // same fall-through to sequential execution as before.
801
806
  const reactiveConfig = prefs?.reactive_execution;
802
- if (!reactiveConfig?.enabled) return null;
807
+ if (reactiveConfig?.enabled === false) return null;
803
808
 
804
809
  const sid = state.activeSlice.id;
805
810
  const sTitle = state.activeSlice.title;
806
- const maxParallel = reactiveConfig.max_parallel ?? 2;
807
- const subagentModel = reactiveConfig.subagent_model ?? resolveModelWithFallbacksForUnit("subagent")?.primary;
811
+ const maxParallel = reactiveConfig?.max_parallel ?? 2;
812
+ const subagentModel = reactiveConfig?.subagent_model ?? resolveModelWithFallbacksForUnit("subagent")?.primary;
813
+ // Default-on safety threshold: only activate reactive dispatch when at
814
+ // least N tasks are ready. Users who explicitly enabled reactive_execution
815
+ // keep the legacy threshold of 2 (matches the prior "any parallelism is
816
+ // better than none" intent). Default-on installs require >=3 to avoid
817
+ // surprising users with parallelism on small slices.
818
+ const minReadyTasksForReactive = reactiveConfig?.enabled === true ? 2 : 3;
808
819
 
809
820
  // Dry-run mode: max_parallel=1 means graph is derived and logged but
810
821
  // execution remains sequential
@@ -831,8 +842,9 @@ export const DISPATCH_RULES: DispatchRule[] = [
831
842
  const completed = new Set(graph.filter((n) => n.done).map((n) => n.id));
832
843
  const readyIds = getReadyTasks(graph, completed, new Set());
833
844
 
834
- // Only activate reactive dispatch when >1 task is ready
835
- if (readyIds.length <= 1) return null;
845
+ // Only activate reactive dispatch when enough tasks are ready.
846
+ // Threshold is 2 when explicitly opted in, 3 when default-on.
847
+ if (readyIds.length < minReadyTasksForReactive) return null;
836
848
 
837
849
  const uokFlags = resolveUokFlags(prefs);
838
850
  const selected = uokFlags.executionGraph
@@ -37,7 +37,7 @@ import { composeInlinedContext, type ArtifactResolver } from "./unit-context-com
37
37
  import { logWarning } from "./workflow-logger.js";
38
38
  import { inlineGraphSubgraph } from "./graph-context.js";
39
39
  import { buildExtractionStepsBlock } from "./commands-extract-learnings.js";
40
- import { warnIfManifestHasMissingSkills } from "./skill-manifest.js";
40
+ import { resolveSkillManifest, warnIfManifestHasMissingSkills } from "./skill-manifest.js";
41
41
 
42
42
  // ─── Preamble Cap ─────────────────────────────────────────────────────────────
43
43
 
@@ -776,6 +776,25 @@ function formatSkillActivationBlock(skillNames: string[]): string {
776
776
  return `<skill_activation>${calls}.</skill_activation>`;
777
777
  }
778
778
 
779
+ /**
780
+ * Manifest-driven recommendations block — informational only, does NOT
781
+ * auto-invoke. Lists per-unit-type skills that are installed but not already
782
+ * activated by explicit user intent (always_use_skills / prefer_skills /
783
+ * skill_rules / task-plan skills_used). Surfaces relevant skills to the
784
+ * model so they can be invoked when the model judges them useful.
785
+ *
786
+ * This is the additive complement to the existing activation directive:
787
+ * activation force-invokes (explicit intent), recommendations remind
788
+ * (manifest defaults). User intent is preserved as the stronger signal
789
+ * (RFC #4779 design principle); this block only adds visibility.
790
+ */
791
+ function formatSkillRecommendationsBlock(unitType: string | undefined, skillNames: string[]): string {
792
+ if (!unitType) return "";
793
+ const safe = skillNames.filter(name => SAFE_SKILL_NAME.test(name));
794
+ if (safe.length === 0) return "";
795
+ return `<skill_recommendations unit="${unitType}">For this unit type, also consider invoking: ${safe.join(", ")}. Use Skill({ skill: 'name' }) when relevant — these are recommendations, not requirements.</skill_recommendations>`;
796
+ }
797
+
779
798
  export function buildSkillActivationBlock(params: {
780
799
  base: string;
781
800
  milestoneId: string;
@@ -846,10 +865,49 @@ export function buildSkillActivationBlock(params: {
846
865
  }
847
866
  }
848
867
 
868
+ // Heuristic auto-match (gated on skill_discovery: "auto").
869
+ // For each installed skill, check if its name or description appears in the
870
+ // unit's context tokens (milestone/slice/task titles). Only consider skills
871
+ // already on the unit-type manifest allowlist — this keeps the heuristic
872
+ // narrow and avoids wildly off-topic activations.
873
+ // Users who set `skill_discovery: "off"` or "suggest" do not get
874
+ // auto-matched skills (the recommendations block still surfaces manifest
875
+ // skills passively); only "auto" actually adds them to the activation
876
+ // directive set. Default `skill_discovery` is "suggest", so this is opt-in.
877
+ if ((prefs?.skill_discovery ?? "suggest") === "auto") {
878
+ const manifestAllow = resolveSkillManifest(params.unitType);
879
+ const allowSet = manifestAllow ? new Set(manifestAllow) : null;
880
+ for (const skill of visibleSkills) {
881
+ const normalized = normalizeSkillReference(skill.name);
882
+ if (matched.has(normalized) || avoided.has(normalized)) continue;
883
+ // Respect the manifest allowlist when present; wildcard (null) lets all
884
+ // installed skills compete for keyword match.
885
+ if (allowSet && !allowSet.has(normalized)) continue;
886
+ if (skillMatchesContext(skill, contextTokens)) {
887
+ matched.add(normalized);
888
+ }
889
+ }
890
+ }
891
+
849
892
  const ordered = [...matched]
850
893
  .filter(name => installedNames.has(name) && !avoided.has(name))
851
894
  .sort();
852
- return formatSkillActivationBlock(ordered);
895
+ const activationBlock = formatSkillActivationBlock(ordered);
896
+
897
+ // Manifest-driven recommendations (additive, does not override explicit intent).
898
+ // Only surface skills the manifest declares for this unit type that are
899
+ // installed and not already in matched/avoided.
900
+ const matchedSet = new Set(ordered);
901
+ const manifestList = resolveSkillManifest(params.unitType);
902
+ const recommendations = (manifestList ?? [])
903
+ .filter(name => installedNames.has(name) && !avoided.has(name) && !matchedSet.has(name))
904
+ .sort();
905
+ const recommendationsBlock = formatSkillRecommendationsBlock(params.unitType, recommendations);
906
+
907
+ if (!activationBlock && !recommendationsBlock) return "";
908
+ if (!activationBlock) return recommendationsBlock;
909
+ if (!recommendationsBlock) return activationBlock;
910
+ return `${activationBlock}\n${recommendationsBlock}`;
853
911
  }
854
912
 
855
913
  /**
@@ -1209,6 +1267,7 @@ export async function buildDiscussMilestonePrompt(
1209
1267
  const discussTemplates = inlineTemplate("context", "Context");
1210
1268
 
1211
1269
  const basePrompt = loadPrompt("guided-discuss-milestone", {
1270
+ workingDirectory: base,
1212
1271
  milestoneId: mid,
1213
1272
  milestoneTitle: midTitle,
1214
1273
  inlinedTemplates: discussTemplates,
@@ -2587,6 +2646,7 @@ export async function buildParallelResearchSlicesPrompt(
2587
2646
  }
2588
2647
 
2589
2648
  return loadPrompt("parallel-research-slices", {
2649
+ workingDirectory: basePath,
2590
2650
  mid,
2591
2651
  midTitle,
2592
2652
  sliceCount: String(slices.length),
@@ -2623,6 +2683,7 @@ export async function buildGateEvaluatePrompt(
2623
2683
 
2624
2684
  const subagentSections: string[] = [];
2625
2685
  const gateListLines: string[] = [];
2686
+ const normalizedBase = base.replaceAll("\\", "/");
2626
2687
 
2627
2688
  for (const def of gateDefs) {
2628
2689
  gateListLines.push(`- **${def.id}**: ${def.question}`);
@@ -2630,6 +2691,8 @@ export async function buildGateEvaluatePrompt(
2630
2691
  const subPrompt = [
2631
2692
  `You are evaluating quality gate **${def.id}** for slice ${sid} (${sTitle}).`,
2632
2693
  "",
2694
+ `**Working directory:** \`${normalizedBase}\`. All file reads, writes, and shell commands MUST operate relative to this directory. Do NOT \`cd\` to any other directory.`,
2695
+ "",
2633
2696
  `## Question: ${def.question}`,
2634
2697
  "",
2635
2698
  def.guidance,
@@ -2746,6 +2809,7 @@ export async function buildRewriteDocsPrompt(
2746
2809
  const documentList = docList.length > 0 ? docList.join("\n") : "- No active plan documents found.";
2747
2810
 
2748
2811
  return loadPrompt("rewrite-docs", {
2812
+ workingDirectory: base,
2749
2813
  milestoneId: mid,
2750
2814
  milestoneTitle: midTitle,
2751
2815
  sliceId: sid ?? "none",
@@ -9,6 +9,7 @@
9
9
 
10
10
  import type { ExtensionContext } from "@gsd/pi-coding-agent";
11
11
  import { parseUnitId } from "./unit-id.js";
12
+ import { MILESTONE_ID_RE } from "./milestone-ids.js";
12
13
  import { appendEvent } from "./workflow-events.js";
13
14
  import { atomicWriteSync } from "./atomic-write.js";
14
15
  import { clearParseCache } from "./files.js";
@@ -213,13 +214,40 @@ function getChangedFilesSinceBranch(basePath: string, targetBranch: string): { o
213
214
  function getChangedFilesFromMilestoneTaggedCommits(
214
215
  basePath: string,
215
216
  milestoneId: string,
217
+ ): { ok: boolean; matched: boolean; files: string[] } {
218
+ // Primary: path-scoped log against .gsd/milestones/<id>. Fast and unbounded
219
+ // by depth when .gsd/ is tracked in git.
220
+ const scoped = scanGsdTaggedCommits(basePath, milestoneId, [
221
+ "log", "--format=%H%x1f%B%x1e", "HEAD", "--", `.gsd/milestones/${milestoneId}`,
222
+ ]);
223
+ if (!scoped.ok) return scoped;
224
+ if (scoped.matched) return scoped;
225
+
226
+ // Fallback (#5033): when .gsd/ is gitignored / external / untracked, the
227
+ // path-scoped scan matches no commits even though GSD-tagged commits
228
+ // referencing the milestone exist on the integration branch. Re-scan all
229
+ // of HEAD's history and rely on commitMatchesMilestone to bind by
230
+ // explicit milestone mention in the message body.
231
+ //
232
+ // Intentionally unbounded — symmetric with the primary scan, and avoids
233
+ // reintroducing the rolling-depth failure class removed in #4699 where
234
+ // milestone evidence aged out behind unrelated activity.
235
+ return scanGsdTaggedCommits(basePath, milestoneId, [
236
+ "log", "--format=%H%x1f%B%x1e", "HEAD",
237
+ ]);
238
+ }
239
+
240
+ function scanGsdTaggedCommits(
241
+ basePath: string,
242
+ milestoneId: string,
243
+ gitArgs: readonly string[],
216
244
  ): { ok: boolean; matched: boolean; files: string[] } {
217
245
  try {
218
- const logOutput = execFileSync(
219
- "git",
220
- ["log", "--format=%H%x1f%B%x1e", "HEAD", "--", `.gsd/milestones/${milestoneId}`],
221
- { cwd: basePath, stdio: ["ignore", "pipe", "pipe"], encoding: "utf-8" },
222
- );
246
+ const logOutput = execFileSync("git", [...gitArgs], {
247
+ cwd: basePath,
248
+ stdio: ["ignore", "pipe", "pipe"],
249
+ encoding: "utf-8",
250
+ });
223
251
  const records = logOutput
224
252
  .split("\x1e")
225
253
  .map((record) => record.trim())
@@ -270,15 +298,25 @@ function commitMatchesMilestone(message: string, milestoneId: string, files: rea
270
298
  if (commitTrailerStartsWithMilestone(message, milestoneId)) return true;
271
299
 
272
300
  // Meaningful execute-task commits currently store task scope as Sxx/Tyy
273
- // rather than Mxx/Sxx/Tyy. Bind those commits back to the milestone only
274
- // when the commit also touched this milestone's artifacts.
301
+ // rather than Mxx/Sxx/Tyy. Bind those commits back to the milestone when
302
+ // either the commit touched this milestone's artifacts, or — for projects
303
+ // where .gsd/ is gitignored/external (#5033) — the message explicitly
304
+ // names the milestone.
275
305
  if (/^GSD-Task:\s*S[^/\s]+\/T\S+/m.test(message)) {
276
- return files.some((file) => isMilestoneArtifactPath(file, milestoneId));
306
+ if (files.some((file) => isMilestoneArtifactPath(file, milestoneId))) return true;
307
+ if (commitMessageMentionsMilestone(message, milestoneId)) return true;
277
308
  }
278
309
 
279
310
  return false;
280
311
  }
281
312
 
313
+ function commitMessageMentionsMilestone(message: string, milestoneId: string): boolean {
314
+ if (!MILESTONE_ID_RE.test(milestoneId)) return false;
315
+
316
+ const escapedMilestone = milestoneId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
317
+ return new RegExp(`\\b${escapedMilestone}\\b`).test(message);
318
+ }
319
+
282
320
  function commitTrailerStartsWithMilestone(message: string, milestoneId: string): boolean {
283
321
  const escapedMilestone = milestoneId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
284
322
  const trailerPattern = new RegExp(