gsd-pi 2.80.0-dev.2be7caf18 → 2.80.0-dev.3065f7bfa

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 (268) hide show
  1. package/README.md +1 -0
  2. package/dist/resources/.managed-resources-content-hash +1 -1
  3. package/dist/resources/extensions/gsd/auto/phases.js +178 -108
  4. package/dist/resources/extensions/gsd/auto/resolve.js +17 -0
  5. package/dist/resources/extensions/gsd/auto/run-unit.js +20 -30
  6. package/dist/resources/extensions/gsd/auto/session.js +8 -0
  7. package/dist/resources/extensions/gsd/auto-direct-dispatch.js +5 -32
  8. package/dist/resources/extensions/gsd/auto-dispatch.js +16 -0
  9. package/dist/resources/extensions/gsd/auto-post-unit.js +17 -4
  10. package/dist/resources/extensions/gsd/auto-prompts.js +90 -15
  11. package/dist/resources/extensions/gsd/auto-recovery.js +43 -1
  12. package/dist/resources/extensions/gsd/auto-start.js +197 -6
  13. package/dist/resources/extensions/gsd/auto-supervisor.js +8 -1
  14. package/dist/resources/extensions/gsd/auto-timeout-recovery.js +2 -2
  15. package/dist/resources/extensions/gsd/auto-worktree.js +111 -1
  16. package/dist/resources/extensions/gsd/auto.js +79 -24
  17. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +42 -2
  18. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +49 -36
  19. package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +15 -5
  20. package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +9 -3
  21. package/dist/resources/extensions/gsd/bootstrap/journal-tools.js +7 -1
  22. package/dist/resources/extensions/gsd/bootstrap/memory-tools.js +9 -3
  23. package/dist/resources/extensions/gsd/bootstrap/query-tools.js +8 -2
  24. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +238 -28
  25. package/dist/resources/extensions/gsd/bootstrap/system-context.js +82 -23
  26. package/dist/resources/extensions/gsd/clean-root-preflight.js +24 -6
  27. package/dist/resources/extensions/gsd/commands-handlers.js +23 -9
  28. package/dist/resources/extensions/gsd/context-budget.js +37 -2
  29. package/dist/resources/extensions/gsd/db/unit-dispatches.js +39 -0
  30. package/dist/resources/extensions/gsd/db-base-schema.js +4 -2
  31. package/dist/resources/extensions/gsd/db-migration-steps.js +6 -0
  32. package/dist/resources/extensions/gsd/ecosystem/gsd-extension-api.js +2 -0
  33. package/dist/resources/extensions/gsd/git-service.js +36 -4
  34. package/dist/resources/extensions/gsd/gsd-db.js +46 -13
  35. package/dist/resources/extensions/gsd/guided-flow.js +66 -32
  36. package/dist/resources/extensions/gsd/memory-store.js +69 -12
  37. package/dist/resources/extensions/gsd/native-git-bridge.js +32 -8
  38. package/dist/resources/extensions/gsd/orphan-stash-audit.js +101 -0
  39. package/dist/resources/extensions/gsd/parallel-orchestrator.js +13 -3
  40. package/dist/resources/extensions/gsd/pre-execution-checks.js +22 -0
  41. package/dist/resources/extensions/gsd/prompt-loader.js +28 -2
  42. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +16 -13
  43. package/dist/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  44. package/dist/resources/extensions/gsd/prompts/execute-task.md +4 -2
  45. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  46. package/dist/resources/extensions/gsd/prompts/replan-slice.md +2 -2
  47. package/dist/resources/extensions/gsd/tools/memory-tools.js +1 -0
  48. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +1 -1
  49. package/dist/resources/extensions/gsd/unit-runtime.js +11 -0
  50. package/dist/resources/extensions/gsd/workflow-protocol.js +131 -0
  51. package/dist/resources/extensions/gsd/worktree-resolver.js +68 -21
  52. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  53. package/dist/web/standalone/.next/BUILD_ID +1 -1
  54. package/dist/web/standalone/.next/app-path-routes-manifest.json +15 -15
  55. package/dist/web/standalone/.next/build-manifest.json +2 -2
  56. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  57. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  58. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  59. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  60. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  61. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  62. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  63. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  64. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  65. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  66. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  67. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  68. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  69. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  70. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  71. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  72. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  73. package/dist/web/standalone/.next/server/app/index.html +1 -1
  74. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  75. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  77. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  78. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  79. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  80. package/dist/web/standalone/.next/server/app-paths-manifest.json +15 -15
  81. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  82. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  83. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  84. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  85. package/package.json +3 -3
  86. package/packages/mcp-server/src/workflow-tools.test.ts +9 -2
  87. package/packages/native/tsconfig.tsbuildinfo +1 -1
  88. package/packages/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
  89. package/packages/pi-agent-core/dist/agent-loop.js +4 -1
  90. package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
  91. package/packages/pi-agent-core/dist/agent.d.ts +5 -0
  92. package/packages/pi-agent-core/dist/agent.d.ts.map +1 -1
  93. package/packages/pi-agent-core/dist/agent.js +2 -0
  94. package/packages/pi-agent-core/dist/agent.js.map +1 -1
  95. package/packages/pi-agent-core/dist/index.d.ts +1 -0
  96. package/packages/pi-agent-core/dist/index.d.ts.map +1 -1
  97. package/packages/pi-agent-core/dist/index.js +2 -0
  98. package/packages/pi-agent-core/dist/index.js.map +1 -1
  99. package/packages/pi-agent-core/dist/token-audit.d.ts +47 -0
  100. package/packages/pi-agent-core/dist/token-audit.d.ts.map +1 -0
  101. package/packages/pi-agent-core/dist/token-audit.js +221 -0
  102. package/packages/pi-agent-core/dist/token-audit.js.map +1 -0
  103. package/packages/pi-agent-core/dist/types.d.ts +9 -0
  104. package/packages/pi-agent-core/dist/types.d.ts.map +1 -1
  105. package/packages/pi-agent-core/dist/types.js.map +1 -1
  106. package/packages/pi-agent-core/src/agent-loop.test.ts +128 -0
  107. package/packages/pi-agent-core/src/agent-loop.ts +4 -1
  108. package/packages/pi-agent-core/src/agent.ts +8 -0
  109. package/packages/pi-agent-core/src/index.ts +2 -0
  110. package/packages/pi-agent-core/src/token-audit.test.ts +189 -0
  111. package/packages/pi-agent-core/src/token-audit.ts +287 -0
  112. package/packages/pi-agent-core/src/types.ts +14 -0
  113. package/packages/pi-agent-core/tsconfig.tsbuildinfo +1 -1
  114. package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js +32 -0
  115. package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js.map +1 -1
  116. package/packages/pi-coding-agent/dist/core/agent-session-tool-refresh.test.js +18 -0
  117. package/packages/pi-coding-agent/dist/core/agent-session-tool-refresh.test.js.map +1 -1
  118. package/packages/pi-coding-agent/dist/core/agent-session.d.ts +12 -0
  119. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  120. package/packages/pi-coding-agent/dist/core/agent-session.js +41 -7
  121. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  122. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  123. package/packages/pi-coding-agent/dist/core/extensions/loader.js +8 -0
  124. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  125. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts +2 -0
  126. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
  127. package/packages/pi-coding-agent/dist/core/extensions/runner.js +3 -6
  128. package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
  129. package/packages/pi-coding-agent/dist/core/extensions/runner.test.js +3 -3
  130. package/packages/pi-coding-agent/dist/core/extensions/runner.test.js.map +1 -1
  131. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +32 -1
  132. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
  133. package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
  134. package/packages/pi-coding-agent/dist/core/hooks-runner.test.js +2 -0
  135. package/packages/pi-coding-agent/dist/core/hooks-runner.test.js.map +1 -1
  136. package/packages/pi-coding-agent/dist/core/sdk-tool-filter.test.d.ts +2 -0
  137. package/packages/pi-coding-agent/dist/core/sdk-tool-filter.test.d.ts.map +1 -0
  138. package/packages/pi-coding-agent/dist/core/sdk-tool-filter.test.js +46 -0
  139. package/packages/pi-coding-agent/dist/core/sdk-tool-filter.test.js.map +1 -0
  140. package/packages/pi-coding-agent/dist/core/sdk.d.ts +10 -2
  141. package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
  142. package/packages/pi-coding-agent/dist/core/sdk.js +74 -2
  143. package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
  144. package/packages/pi-coding-agent/dist/core/skill-tool.test.js +22 -0
  145. package/packages/pi-coding-agent/dist/core/skill-tool.test.js.map +1 -1
  146. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts +6 -7
  147. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
  148. package/packages/pi-coding-agent/dist/core/system-prompt.js +2 -3
  149. package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
  150. package/packages/pi-coding-agent/src/core/agent-session-abort-order.test.ts +36 -0
  151. package/packages/pi-coding-agent/src/core/agent-session-tool-refresh.test.ts +25 -0
  152. package/packages/pi-coding-agent/src/core/agent-session.ts +45 -7
  153. package/packages/pi-coding-agent/src/core/extensions/loader.ts +10 -0
  154. package/packages/pi-coding-agent/src/core/extensions/runner.test.ts +3 -3
  155. package/packages/pi-coding-agent/src/core/extensions/runner.ts +5 -5
  156. package/packages/pi-coding-agent/src/core/extensions/types.ts +35 -1
  157. package/packages/pi-coding-agent/src/core/hooks-runner.test.ts +2 -0
  158. package/packages/pi-coding-agent/src/core/sdk-tool-filter.test.ts +60 -0
  159. package/packages/pi-coding-agent/src/core/sdk.ts +85 -3
  160. package/packages/pi-coding-agent/src/core/skill-tool.test.ts +28 -0
  161. package/packages/pi-coding-agent/src/core/system-prompt.ts +8 -10
  162. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  163. package/src/resources/extensions/gsd/auto/loop-deps.ts +2 -2
  164. package/src/resources/extensions/gsd/auto/phases.ts +251 -172
  165. package/src/resources/extensions/gsd/auto/resolve.ts +23 -1
  166. package/src/resources/extensions/gsd/auto/run-unit.ts +25 -30
  167. package/src/resources/extensions/gsd/auto/session.ts +8 -0
  168. package/src/resources/extensions/gsd/auto-direct-dispatch.ts +8 -34
  169. package/src/resources/extensions/gsd/auto-dispatch.ts +16 -0
  170. package/src/resources/extensions/gsd/auto-post-unit.ts +18 -4
  171. package/src/resources/extensions/gsd/auto-prompts.ts +95 -14
  172. package/src/resources/extensions/gsd/auto-recovery.ts +54 -0
  173. package/src/resources/extensions/gsd/auto-start.ts +230 -9
  174. package/src/resources/extensions/gsd/auto-supervisor.ts +7 -0
  175. package/src/resources/extensions/gsd/auto-timeout-recovery.ts +2 -2
  176. package/src/resources/extensions/gsd/auto-worktree.ts +123 -0
  177. package/src/resources/extensions/gsd/auto.ts +87 -19
  178. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +49 -1
  179. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +50 -36
  180. package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +16 -5
  181. package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +10 -3
  182. package/src/resources/extensions/gsd/bootstrap/journal-tools.ts +8 -1
  183. package/src/resources/extensions/gsd/bootstrap/memory-tools.ts +10 -3
  184. package/src/resources/extensions/gsd/bootstrap/query-tools.ts +9 -2
  185. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +283 -28
  186. package/src/resources/extensions/gsd/bootstrap/system-context.ts +90 -22
  187. package/src/resources/extensions/gsd/clean-root-preflight.ts +32 -7
  188. package/src/resources/extensions/gsd/commands-handlers.ts +34 -15
  189. package/src/resources/extensions/gsd/context-budget.ts +44 -2
  190. package/src/resources/extensions/gsd/db/unit-dispatches.ts +41 -0
  191. package/src/resources/extensions/gsd/db-base-schema.ts +4 -2
  192. package/src/resources/extensions/gsd/db-migration-steps.ts +8 -0
  193. package/src/resources/extensions/gsd/ecosystem/gsd-extension-api.ts +3 -0
  194. package/src/resources/extensions/gsd/git-service.ts +46 -8
  195. package/src/resources/extensions/gsd/gsd-db.ts +50 -13
  196. package/src/resources/extensions/gsd/guided-flow.ts +84 -39
  197. package/src/resources/extensions/gsd/memory-store.ts +77 -12
  198. package/src/resources/extensions/gsd/native-git-bridge.ts +39 -6
  199. package/src/resources/extensions/gsd/orphan-stash-audit.ts +117 -0
  200. package/src/resources/extensions/gsd/parallel-orchestrator.ts +13 -3
  201. package/src/resources/extensions/gsd/pre-execution-checks.ts +23 -0
  202. package/src/resources/extensions/gsd/prompt-loader.ts +27 -2
  203. package/src/resources/extensions/gsd/prompts/complete-milestone.md +16 -13
  204. package/src/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  205. package/src/resources/extensions/gsd/prompts/execute-task.md +4 -2
  206. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  207. package/src/resources/extensions/gsd/prompts/replan-slice.md +2 -2
  208. package/src/resources/extensions/gsd/tests/artifact-retry-cap.test.ts +2 -2
  209. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +417 -10
  210. package/src/resources/extensions/gsd/tests/auto-phases-lifecycle.test.ts +56 -13
  211. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +14 -1
  212. package/src/resources/extensions/gsd/tests/auto-wrapup-inflight-guard.test.ts +168 -6
  213. package/src/resources/extensions/gsd/tests/clean-root-preflight.test.ts +15 -6
  214. package/src/resources/extensions/gsd/tests/compaction-snapshot.test.ts +3 -1
  215. package/src/resources/extensions/gsd/tests/complete-milestone-excerpt.test.ts +31 -0
  216. package/src/resources/extensions/gsd/tests/complete-slice-composer.test.ts +3 -2
  217. package/src/resources/extensions/gsd/tests/context-budget.test.ts +10 -1
  218. package/src/resources/extensions/gsd/tests/context-store.test.ts +7 -1
  219. package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +5 -1
  220. package/src/resources/extensions/gsd/tests/dispatch-rule-coverage.test.ts +313 -0
  221. package/src/resources/extensions/gsd/tests/execute-task-rendering.test.ts +5 -2
  222. package/src/resources/extensions/gsd/tests/fast-forward-reused-milestone-branch.test.ts +219 -0
  223. package/src/resources/extensions/gsd/tests/finalize-survivor-branch.test.ts +132 -0
  224. package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +54 -0
  225. package/src/resources/extensions/gsd/tests/isolation-none-branch-guard.test.ts +6 -3
  226. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +239 -1
  227. package/src/resources/extensions/gsd/tests/journal-query-tool.test.ts +32 -0
  228. package/src/resources/extensions/gsd/tests/knowledge.test.ts +47 -0
  229. package/src/resources/extensions/gsd/tests/memory-decay-factor.test.ts +90 -0
  230. package/src/resources/extensions/gsd/tests/merge-conflict-stops-loop.test.ts +1 -0
  231. package/src/resources/extensions/gsd/tests/milestone-merge-stash-restore.test.ts +242 -0
  232. package/src/resources/extensions/gsd/tests/native-git-bridge-exec-fallback.test.ts +34 -2
  233. package/src/resources/extensions/gsd/tests/originalbase-path-comparison.test.ts +3 -0
  234. package/src/resources/extensions/gsd/tests/orphan-merge-bootstrap.test.ts +133 -0
  235. package/src/resources/extensions/gsd/tests/orphan-stash-audit.test.ts +201 -0
  236. package/src/resources/extensions/gsd/tests/parallel-orchestrator-fast-forward.test.ts +113 -0
  237. package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +45 -5
  238. package/src/resources/extensions/gsd/tests/prompt-duplication-cuts.test.ts +230 -0
  239. package/src/resources/extensions/gsd/tests/prompt-step-ordering.test.ts +19 -0
  240. package/src/resources/extensions/gsd/tests/query-tools-db-open.test.ts +3 -3
  241. package/src/resources/extensions/gsd/tests/restore-tools-after-discuss.test.ts +38 -17
  242. package/src/resources/extensions/gsd/tests/schema-v27-v28-sequence.test.ts +156 -0
  243. package/src/resources/extensions/gsd/tests/select-resumable-milestone.test.ts +96 -0
  244. package/src/resources/extensions/gsd/tests/session-switch-abort-misclassification.test.ts +106 -0
  245. package/src/resources/extensions/gsd/tests/signal-handlers.test.ts +27 -0
  246. package/src/resources/extensions/gsd/tests/stalled-tool-recovery.test.ts +49 -1
  247. package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +38 -0
  248. package/src/resources/extensions/gsd/tests/state-corruption-2945.test.ts +1 -0
  249. package/src/resources/extensions/gsd/tests/system-context-memory.test.ts +112 -0
  250. package/src/resources/extensions/gsd/tests/system-context-message-routing.test.ts +7 -9
  251. package/src/resources/extensions/gsd/tests/token-tool-gating.test.ts +291 -0
  252. package/src/resources/extensions/gsd/tests/unit-dispatches.test.ts +30 -0
  253. package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +30 -0
  254. package/src/resources/extensions/gsd/tests/unstructured-continue-context-injection.test.ts +5 -4
  255. package/src/resources/extensions/gsd/tests/workflow-protocol-excerpt.test.ts +99 -0
  256. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +3 -0
  257. package/src/resources/extensions/gsd/tests/worktree-journal-events.test.ts +1 -0
  258. package/src/resources/extensions/gsd/tests/worktree-path-injection.test.ts +22 -19
  259. package/src/resources/extensions/gsd/tests/worktree-project-root-degrade.test.ts +66 -0
  260. package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +167 -4
  261. package/src/resources/extensions/gsd/tools/memory-tools.ts +1 -0
  262. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +1 -1
  263. package/src/resources/extensions/gsd/unit-runtime.ts +11 -0
  264. package/src/resources/extensions/gsd/workflow-protocol.ts +160 -0
  265. package/src/resources/extensions/gsd/worktree-resolver.ts +85 -19
  266. package/src/resources/extensions/gsd/tests/phases-merge-error-stops-auto.test.ts +0 -97
  267. /package/dist/web/standalone/.next/static/{3EDDd9ULaybSupPoA_vf- → O2WvnXROue6l7nAp_4dhC}/_buildManifest.js +0 -0
  268. /package/dist/web/standalone/.next/static/{3EDDd9ULaybSupPoA_vf- → O2WvnXROue6l7nAp_4dhC}/_ssgManifest.js +0 -0
@@ -3,13 +3,13 @@
3
3
  *
4
4
  * #2909: Adds a fast-path git status check before milestone completion merges.
5
5
  * When the working tree is dirty the user is warned and changes are auto-stashed
6
- * so the merge can proceed cleanly. After the merge completes, postflightPopStash
7
- * restores the stashed changes.
6
+ * so the merge can proceed cleanly. After the merge completes, postflightPopStash
7
+ * restores the stashed changes and reports whether manual recovery is needed.
8
8
  *
9
9
  * Design constraints (from Trek-e approval):
10
10
  * - Warn the user before stashing (no silent surprises)
11
11
  * - git stash push / git stash pop only — no custom stash management layer
12
- * - Stash/pop errors are logged but MUST NOT block the merge
12
+ * - Stash/pop errors are logged but MUST NOT block the merge itself
13
13
  * - Fast-path status check — clean trees pay no extra cost
14
14
  */
15
15
  import { execFileSync } from "node:child_process";
@@ -98,7 +98,8 @@ export function preflightCleanRoot(basePath, milestoneId, notify) {
98
98
  *
99
99
  * Only called when preflightCleanRoot returned stashPushed=true.
100
100
  * Any pop error (e.g. conflict) is logged and notified but does NOT throw —
101
- * the merge already completed successfully.
101
+ * the merge already completed successfully. Callers must treat
102
+ * needsManualRecovery=true as a dirty workspace stop, not a clean completion.
102
103
  */
103
104
  export function postflightPopStash(basePath, milestoneId, stashMarker, notify) {
104
105
  let stashRef = null;
@@ -108,7 +109,11 @@ export function postflightPopStash(basePath, milestoneId, stashMarker, notify) {
108
109
  const msg = `No matching GSD preflight stash found for milestone ${milestoneId}; leaving stash list untouched.`;
109
110
  logWarning("preflight", msg);
110
111
  notify(msg, "warning");
111
- return;
112
+ return {
113
+ restored: false,
114
+ needsManualRecovery: true,
115
+ message: msg,
116
+ };
112
117
  }
113
118
  execFileSync("git", ["stash", "pop", stashRef], {
114
119
  cwd: basePath,
@@ -116,7 +121,14 @@ export function postflightPopStash(basePath, milestoneId, stashMarker, notify) {
116
121
  encoding: "utf-8",
117
122
  env: GIT_NO_PROMPT_ENV,
118
123
  });
119
- notify(`Restored stashed changes after milestone ${milestoneId} merge.`, "info");
124
+ const msg = `Restored stashed changes after milestone ${milestoneId} merge.`;
125
+ notify(msg, "info");
126
+ return {
127
+ restored: true,
128
+ needsManualRecovery: false,
129
+ message: msg,
130
+ stashRef,
131
+ };
120
132
  }
121
133
  catch (err) {
122
134
  // Pop conflicts mean the merged code collides with the stashed changes.
@@ -127,5 +139,11 @@ export function postflightPopStash(basePath, milestoneId, stashMarker, notify) {
127
139
  const msg = `git stash pop ${stashRef ?? ""}`.trim() + ` failed after merge of milestone ${milestoneId}: ${err instanceof Error ? err.message : String(err)}. ${restoreHint}`;
128
140
  logWarning("preflight", msg);
129
141
  notify(msg, "warning");
142
+ return {
143
+ restored: false,
144
+ needsManualRecovery: true,
145
+ message: msg,
146
+ ...(stashRef ? { stashRef } : {}),
147
+ };
130
148
  }
131
149
  }
@@ -17,6 +17,8 @@ import { isAutoActive, checkRemoteAutoSession } from "./auto.js";
17
17
  import { getAutoWorktreePath } from "./auto-worktree.js";
18
18
  import { currentDirectoryRoot, projectRoot } from "./commands/context.js";
19
19
  import { loadPrompt } from "./prompt-loader.js";
20
+ import { buildDoctorHealIssuePayload, buildDoctorHealSummary, buildWorkflowDispatchContent, } from "./workflow-protocol.js";
21
+ import { restoreGsdWorkflowTools, scopeGsdWorkflowToolsForDispatch, } from "./bootstrap/register-hooks.js";
20
22
  const UPDATE_REGISTRY_URL = "https://registry.npmjs.org/gsd-pi/latest";
21
23
  const UPDATE_FETCH_TIMEOUT_MS = 5000;
22
24
  // Detects a bun-installed gsd via `process.argv[1]`. Mirrors isBunInstall in
@@ -64,13 +66,19 @@ export function dispatchDoctorHeal(pi, scope, reportText, structuredIssues) {
64
66
  const workflowPath = process.env.GSD_WORKFLOW_PATH ?? join(gsdHome(), "agent", "GSD-WORKFLOW.md");
65
67
  const workflow = readFileSync(workflowPath, "utf-8");
66
68
  const prompt = loadPrompt("doctor-heal", {
67
- doctorSummary: reportText,
68
- structuredIssues,
69
+ doctorSummary: buildDoctorHealSummary(reportText),
70
+ structuredIssues: buildDoctorHealIssuePayload(structuredIssues),
69
71
  scopeLabel: scope ?? "active milestone / blocking scope",
70
72
  doctorCommandSuffix: scope ? ` ${scope}` : "",
71
73
  });
72
- const content = `Read the following GSD workflow protocol and execute exactly.\n\n${workflow}\n\n## Your Task\n\n${prompt}`;
73
- pi.sendMessage({ customType: "gsd-doctor-heal", content, display: false }, { triggerTurn: true });
74
+ const content = buildWorkflowDispatchContent({ workflow, workflowPath, task: prompt });
75
+ const savedTools = scopeGsdWorkflowToolsForDispatch(pi);
76
+ try {
77
+ pi.sendMessage({ customType: "gsd-doctor-heal", content, display: false }, { triggerTurn: true });
78
+ }
79
+ finally {
80
+ restoreGsdWorkflowTools(pi, savedTools);
81
+ }
74
82
  }
75
83
  /** Parse doctor command args into structured flags and positionals (pure, no I/O). */
76
84
  export function parseDoctorArgs(args) {
@@ -213,11 +221,17 @@ export async function handleTriage(ctx, pi, basePath) {
213
221
  });
214
222
  const workflowPath = process.env.GSD_WORKFLOW_PATH ?? join(gsdHome(), "agent", "GSD-WORKFLOW.md");
215
223
  const workflow = readFileSync(workflowPath, "utf-8");
216
- pi.sendMessage({
217
- customType: "gsd-triage",
218
- content: `Read the following GSD workflow protocol and execute exactly.\n\n${workflow}\n\n## Your Task\n\n${prompt}`,
219
- display: false,
220
- }, { triggerTurn: true });
224
+ const savedTools = scopeGsdWorkflowToolsForDispatch(pi);
225
+ try {
226
+ pi.sendMessage({
227
+ customType: "gsd-triage",
228
+ content: buildWorkflowDispatchContent({ workflow, workflowPath, task: prompt }),
229
+ display: false,
230
+ }, { triggerTurn: true });
231
+ }
232
+ finally {
233
+ restoreGsdWorkflowTools(pi, savedTools);
234
+ }
221
235
  }
222
236
  export async function handleSteer(change, ctx, pi) {
223
237
  const basePath = currentDirectoryRoot();
@@ -7,7 +7,7 @@
7
7
  *
8
8
  * @see D001 (module location), D002 (200K fallback), D003 (section-boundary truncation)
9
9
  */
10
- import { getCharsPerToken } from "./token-counter.js";
10
+ import { getCharsPerToken, isAccurateCountingAvailable, countTokensSync, } from "./token-counter.js";
11
11
  // ─── Budget ratio constants ──────────────────────────────────────────────────
12
12
  // Percentages of total context window allocated to each budget category.
13
13
  // These are applied after tokens→chars conversion.
@@ -23,6 +23,22 @@ const CHARS_PER_TOKEN = 4;
23
23
  const DEFAULT_CONTEXT_WINDOW = 200_000;
24
24
  /** Conservative effective context for Claude Code subscription routing (#4676) */
25
25
  const CLAUDE_CODE_EFFECTIVE_CONTEXT_WINDOW = 200_000;
26
+ /**
27
+ * Cached empirical chars-per-token from a tiktoken probe, keyed by provider.
28
+ * countTokensSync's fallback path is provider-aware, so we cache per-provider
29
+ * to preserve that distinction once the encoder warms. The cl100k_base encoder
30
+ * itself gives a stable ratio for ASCII English so a single probe per provider
31
+ * key is sufficient. Empty map means "not yet probed" or "encoder unavailable".
32
+ */
33
+ const _empiricalCharsPerTokenByProvider = new Map();
34
+ /**
35
+ * Test hook — clears the empirical chars-per-token cache so test cases that
36
+ * assert against the static char-ratio fallback aren't polluted by a prior
37
+ * tiktoken-warmed run in the same process. Production code must not call this.
38
+ */
39
+ export function _resetEmpiricalCacheForTest() {
40
+ _empiricalCharsPerTokenByProvider.clear();
41
+ }
26
42
  /** Percentage of context consumed before suggesting a continue-here checkpoint */
27
43
  const CONTINUE_THRESHOLD_PERCENT = 70;
28
44
  // ─── Task count bounds ───────────────────────────────────────────────────────
@@ -46,7 +62,26 @@ const TASK_COUNT_TIERS = [
46
62
  export function computeBudgets(contextWindow, provider) {
47
63
  const effectiveWindow = contextWindow > 0 ? contextWindow : DEFAULT_CONTEXT_WINDOW;
48
64
  const charsPerToken = provider ? getCharsPerToken(provider) : CHARS_PER_TOKEN;
49
- const totalChars = effectiveWindow * charsPerToken;
65
+ // Prefer the tiktoken encoder for total-char estimation when it has been
66
+ // warmed (initTokenCounter resolved). The cl100k_base ratio is stable for
67
+ // ASCII English, so probe once per provider and cache — computeBudgets is
68
+ // called multiple times per prompt build and the probe encode is otherwise
69
+ // wasted work.
70
+ let totalChars;
71
+ if (isAccurateCountingAvailable()) {
72
+ const providerKey = provider ?? "__default__";
73
+ let empirical = _empiricalCharsPerTokenByProvider.get(providerKey);
74
+ if (empirical === undefined) {
75
+ const probe = "the quick brown fox jumps over the lazy dog ".repeat(64);
76
+ const probeTokens = countTokensSync(probe, provider);
77
+ empirical = probeTokens > 0 ? probe.length / probeTokens : charsPerToken;
78
+ _empiricalCharsPerTokenByProvider.set(providerKey, empirical);
79
+ }
80
+ totalChars = effectiveWindow * empirical;
81
+ }
82
+ else {
83
+ totalChars = effectiveWindow * charsPerToken;
84
+ }
50
85
  return {
51
86
  summaryBudgetChars: Math.floor(totalChars * SUMMARY_RATIO),
52
87
  inlineContextBudgetChars: Math.floor(totalChars * INLINE_CONTEXT_RATIO),
@@ -253,6 +253,45 @@ export function markCanceled(dispatchId, reason) {
253
253
  SET status = 'canceled', ended_at = :ended_at, exit_reason = :reason
254
254
  WHERE id = :id AND status IN ('pending','claimed','running')`).run({ ":id": dispatchId, ":ended_at": now, ":reason": reason });
255
255
  }
256
+ /**
257
+ * Best-effort signal/crash cleanup: cancel the latest active dispatch owned by
258
+ * a worker when the process is exiting before the normal loop can settle it.
259
+ */
260
+ export function markLatestActiveForWorkerCanceled(workerId, reason) {
261
+ if (!isDbAvailable())
262
+ return false;
263
+ const now = new Date().toISOString();
264
+ const db = _getAdapter();
265
+ const result = transaction(() => {
266
+ return db.prepare(`UPDATE unit_dispatches
267
+ SET status = 'canceled', ended_at = :ended_at, exit_reason = :reason
268
+ WHERE id = (
269
+ SELECT id FROM unit_dispatches
270
+ WHERE worker_id = :worker_id
271
+ AND status IN ('pending','claimed','running')
272
+ ORDER BY id DESC
273
+ LIMIT 1
274
+ )`).run({
275
+ ":ended_at": now,
276
+ ":reason": reason,
277
+ ":worker_id": workerId,
278
+ });
279
+ });
280
+ const changes = typeof result.changes === "number"
281
+ ? result.changes
282
+ : 0;
283
+ if (changes <= 0)
284
+ return false;
285
+ insertAuditEvent({
286
+ eventId: randomUUID(),
287
+ traceId: workerId,
288
+ category: "orchestration",
289
+ type: "dispatch-canceled",
290
+ ts: now,
291
+ payload: { workerId, reason },
292
+ });
293
+ return true;
294
+ }
256
295
  /**
257
296
  * Fetch the most recent N dispatches for a unit. Used by recordDispatchClaim
258
297
  * callers to compute attempt_n and by detect-stuck.ts (B3) to consult
@@ -46,7 +46,8 @@ export function createBaseSchemaObjects(db, hooks) {
46
46
  slice_id TEXT DEFAULT NULL,
47
47
  task_id TEXT DEFAULT NULL,
48
48
  full_content TEXT NOT NULL DEFAULT '',
49
- imported_at TEXT NOT NULL DEFAULT ''
49
+ imported_at TEXT NOT NULL DEFAULT '',
50
+ content_hash TEXT DEFAULT NULL
50
51
  )
51
52
  `);
52
53
  db.exec(`
@@ -64,7 +65,8 @@ export function createBaseSchemaObjects(db, hooks) {
64
65
  hit_count INTEGER NOT NULL DEFAULT 0,
65
66
  scope TEXT NOT NULL DEFAULT 'project',
66
67
  tags TEXT NOT NULL DEFAULT '[]',
67
- structured_fields TEXT DEFAULT NULL
68
+ structured_fields TEXT DEFAULT NULL,
69
+ last_hit_at TEXT DEFAULT NULL
68
70
  )
69
71
  `);
70
72
  db.exec(`
@@ -379,6 +379,12 @@ export function applyMigrationV26MilestoneCommitAttributions(db) {
379
379
  `);
380
380
  db.exec("CREATE INDEX IF NOT EXISTS idx_milestone_commit_attr_milestone ON milestone_commit_attributions(milestone_id)");
381
381
  }
382
+ export function applyMigrationV27ArtifactHash(db) {
383
+ ensureColumn(db, "artifacts", "content_hash", "ALTER TABLE artifacts ADD COLUMN content_hash TEXT DEFAULT NULL");
384
+ }
385
+ export function applyMigrationV28MemoryLastHitAt(db) {
386
+ ensureColumn(db, "memories", "last_hit_at", "ALTER TABLE memories ADD COLUMN last_hit_at TEXT DEFAULT NULL");
387
+ }
382
388
  export function applyMigrationV22QualityGateRepair(db, hooks) {
383
389
  const qgInfo = db.prepare("PRAGMA table_info(quality_gates)").all();
384
390
  const taskIdCol = qgInfo.find((r) => r["name"] === "task_id");
@@ -127,6 +127,8 @@ export function createGSDExtensionAPI(pi, sharedHandlers) {
127
127
  getActiveTools: () => pi.getActiveTools(),
128
128
  getAllTools: () => pi.getAllTools(),
129
129
  setActiveTools: (...args) => pi.setActiveTools(...args),
130
+ getVisibleSkills: () => pi.getVisibleSkills(),
131
+ setVisibleSkills: (...args) => pi.setVisibleSkills(...args),
130
132
  getCommands: () => pi.getCommands(),
131
133
  // ── Model & thinking ───────────────────────────────────────────────
132
134
  setModel: (...args) => pi.setModel(...args),
@@ -13,6 +13,7 @@ import { isAbsolute, join, normalize, relative, resolve, sep } from "node:path";
13
13
  import { gsdRoot } from "./paths.js";
14
14
  import { GIT_NO_PROMPT_ENV } from "./git-constants.js";
15
15
  import { loadEffectiveGSDPreferences } from "./preferences.js";
16
+ import { logWarning } from "./workflow-logger.js";
16
17
  import { detectWorktreeName, } from "./worktree.js";
17
18
  import { SLICE_BRANCH_RE, QUICK_BRANCH_RE, WORKFLOW_BRANCH_RE } from "./branch-patterns.js";
18
19
  import { nativeGetCurrentBranch, nativeDetectMainBranch, nativeBranchExists, nativeHasChanges, nativeAddAllWithExclusions, nativeHasStagedChanges, nativeCommit, nativeRmCached, nativeUpdateRef, nativeAddPaths, nativeResetSoft, nativeCommitSubject, _resetHasChangesCache, } from "./native-git-bridge.js";
@@ -535,14 +536,45 @@ export class GitServiceImpl {
535
536
  if (keyFiles.length === 0)
536
537
  return false;
537
538
  const allExclusions = [...RUNTIME_EXCLUSION_PATHS, ...extraExclusions];
538
- const paths = Array.from(new Set(keyFiles
539
+ const normalized = keyFiles
539
540
  .map(file => normalizeRepoRelativePath(this.basePath, file))
540
541
  .filter((file) => file !== null)
541
- .filter(file => !isExcludedScopedPath(file, allExclusions))));
542
+ .filter(file => !isExcludedScopedPath(file, allExclusions));
543
+ // Drop entries that don't exist on disk. The LLM occasionally lists files
544
+ // it intended to write but didn't (or names them with wrong casing/path).
545
+ // Pre-`b304f738b` `git add -A` swallowed these silently; the scoped
546
+ // pathspec form passes each path explicitly, so a single bad entry made
547
+ // the whole commit fail (see #5500). Filter so valid paths still commit.
548
+ const missing = [];
549
+ const existing = [];
550
+ for (const path of normalized) {
551
+ if (existsSync(join(this.basePath, path))) {
552
+ existing.push(path);
553
+ }
554
+ else {
555
+ missing.push(path);
556
+ }
557
+ }
558
+ if (missing.length > 0) {
559
+ logWarning("engine", `scoped stage: dropping ${missing.length} non-existent keyFile(s) from task commit: ${missing.join(", ")}`, { file: "git-service.ts" });
560
+ }
561
+ const paths = Array.from(new Set(existing));
542
562
  if (paths.length === 0)
543
563
  return false;
544
- nativeAddPaths(this.basePath, paths);
545
- return true;
564
+ try {
565
+ nativeAddPaths(this.basePath, paths);
566
+ return true;
567
+ }
568
+ catch (err) {
569
+ // Defense-in-depth: even after existence filtering, libgit2/git can
570
+ // still reject paths (gitignore matches, case-only differences on
571
+ // case-insensitive FS, submodule boundaries). Returning false lets
572
+ // autoCommit fall through to smartStage so the commit still goes out
573
+ // — restoring the resilience the unscoped path used to provide.
574
+ const msg = err instanceof Error ? err.message : String(err);
575
+ logWarning("engine", `scoped stage failed (${msg}); falling back to smartStage`, { file: "git-service.ts" });
576
+ return false;
577
+ }
546
578
  }
547
579
  /** Tracks whether runtime file cleanup has run this session. */
548
580
  _runtimeFilesCleanedUp = false;
@@ -22,6 +22,7 @@
22
22
  // intentionally independent store for cross-worktree claim races and is
23
23
  // excluded from this invariant.
24
24
  import { createRequire } from "node:module";
25
+ import { createHash } from "node:crypto";
25
26
  import { existsSync, copyFileSync, mkdirSync, realpathSync } from "node:fs";
26
27
  import { dirname } from "node:path";
27
28
  import { GSDError, GSD_STALE_STATE } from "./errors.js";
@@ -36,7 +37,7 @@ import { rowToActiveDecision, rowToActiveRequirement, rowToDecision, rowToRequir
36
37
  import { rowToGate } from "./db-gate-rows.js";
37
38
  import { rowToArtifact, rowToMilestone } from "./db-milestone-artifact-rows.js";
38
39
  import { backupDatabaseBeforeMigration } from "./db-migration-backup.js";
39
- import { applyMigrationV2Artifacts, applyMigrationV3Memories, applyMigrationV4DecisionMadeBy, applyMigrationV5HierarchyTables, applyMigrationV6SliceSummaries, applyMigrationV7Dependencies, applyMigrationV8PlanningFields, applyMigrationV9Ordering, applyMigrationV10ReplanTrigger, applyMigrationV11TaskPlanning, applyMigrationV12QualityGates, applyMigrationV13HotPathIndexes, applyMigrationV14SliceDependencies, applyMigrationV15AuditTables, applyMigrationV16EscalationSource, applyMigrationV17TaskEscalation, applyMigrationV18MemorySources, applyMigrationV19MemoryFts, applyMigrationV20MemoryRelations, applyMigrationV21StructuredMemories, applyMigrationV22QualityGateRepair, applyMigrationV23MilestoneQueue, applyMigrationV26MilestoneCommitAttributions, } from "./db-migration-steps.js";
40
+ import { applyMigrationV2Artifacts, applyMigrationV3Memories, applyMigrationV4DecisionMadeBy, applyMigrationV5HierarchyTables, applyMigrationV6SliceSummaries, applyMigrationV7Dependencies, applyMigrationV8PlanningFields, applyMigrationV9Ordering, applyMigrationV10ReplanTrigger, applyMigrationV11TaskPlanning, applyMigrationV12QualityGates, applyMigrationV13HotPathIndexes, applyMigrationV14SliceDependencies, applyMigrationV15AuditTables, applyMigrationV16EscalationSource, applyMigrationV17TaskEscalation, applyMigrationV18MemorySources, applyMigrationV19MemoryFts, applyMigrationV20MemoryRelations, applyMigrationV21StructuredMemories, applyMigrationV22QualityGateRepair, applyMigrationV23MilestoneQueue, applyMigrationV26MilestoneCommitAttributions, applyMigrationV27ArtifactHash, applyMigrationV28MemoryLastHitAt, } from "./db-migration-steps.js";
40
41
  import { isMemoriesFtsAvailableSchema, tryCreateMemoriesFtsSchema } from "./db-memory-fts-schema.js";
41
42
  import { createDbOpenState } from "./db-open-state.js";
42
43
  import { createRuntimeKvTableV25 } from "./db-runtime-kv-schema.js";
@@ -52,7 +53,7 @@ const providerLoader = createSqliteProviderLoader({
52
53
  nodeVersion: process.versions.node,
53
54
  writeStderr: (message) => process.stderr.write(message),
54
55
  });
55
- export const SCHEMA_VERSION = 26;
56
+ export const SCHEMA_VERSION = 28;
56
57
  function initSchema(db, fileBacked) {
57
58
  if (fileBacked)
58
59
  db.exec("PRAGMA journal_mode=WAL");
@@ -250,6 +251,14 @@ function migrateSchema(db) {
250
251
  applyMigrationV26MilestoneCommitAttributions(db);
251
252
  recordSchemaVersion(db, 26);
252
253
  }
254
+ if (currentVersion < 27) {
255
+ applyMigrationV27ArtifactHash(db);
256
+ recordSchemaVersion(db, 27);
257
+ }
258
+ if (currentVersion < 28) {
259
+ applyMigrationV28MemoryLastHitAt(db);
260
+ recordSchemaVersion(db, 28);
261
+ }
253
262
  db.exec("COMMIT");
254
263
  }
255
264
  catch (err) {
@@ -821,8 +830,9 @@ export function clearArtifacts() {
821
830
  export function insertArtifact(a) {
822
831
  if (!currentDb)
823
832
  throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
824
- currentDb.prepare(`INSERT OR REPLACE INTO artifacts (path, artifact_type, milestone_id, slice_id, task_id, full_content, imported_at)
825
- VALUES (:path, :artifact_type, :milestone_id, :slice_id, :task_id, :full_content, :imported_at)`).run({
833
+ const contentHash = createHash("sha256").update(a.full_content).digest("hex");
834
+ currentDb.prepare(`INSERT OR REPLACE INTO artifacts (path, artifact_type, milestone_id, slice_id, task_id, full_content, imported_at, content_hash)
835
+ VALUES (:path, :artifact_type, :milestone_id, :slice_id, :task_id, :full_content, :imported_at, :content_hash)`).run({
826
836
  ":path": a.path,
827
837
  ":artifact_type": a.artifact_type,
828
838
  ":milestone_id": a.milestone_id,
@@ -830,6 +840,7 @@ export function insertArtifact(a) {
830
840
  ":task_id": a.task_id,
831
841
  ":full_content": a.full_content,
832
842
  ":imported_at": new Date().toISOString(),
843
+ ":content_hash": contentHash,
833
844
  });
834
845
  }
835
846
  export function insertMilestone(m) {
@@ -1521,6 +1532,13 @@ export function reconcileWorktreeDb(mainDbPath, worktreeDbPath) {
1521
1532
  const hasEscalationAwaiting = wtTaskInfo.some((col) => col["name"] === "escalation_awaiting_review");
1522
1533
  const hasEscalationArtifact = wtTaskInfo.some((col) => col["name"] === "escalation_artifact_path");
1523
1534
  const hasEscalationOverride = wtTaskInfo.some((col) => col["name"] === "escalation_override_applied_at");
1535
+ const wtArtifactInfo = adapter.prepare("PRAGMA wt.table_info('artifacts')").all();
1536
+ const hasArtifactContentHash = wtArtifactInfo.some((col) => col["name"] === "content_hash");
1537
+ const wtMemoryInfo = adapter.prepare("PRAGMA wt.table_info('memories')").all();
1538
+ const hasMemoryScope = wtMemoryInfo.some((col) => col["name"] === "scope");
1539
+ const hasMemoryTags = wtMemoryInfo.some((col) => col["name"] === "tags");
1540
+ const hasMemoryStructuredFields = wtMemoryInfo.some((col) => col["name"] === "structured_fields");
1541
+ const hasMemoryLastHitAt = wtMemoryInfo.some((col) => col["name"] === "last_hit_at");
1524
1542
  const decConf = adapter.prepare(`SELECT m.id FROM decisions m INNER JOIN wt.decisions w ON m.id = w.id WHERE m.decision != w.decision OR m.choice != w.choice OR m.rationale != w.rationale OR ${hasMadeBy ? "m.made_by != w.made_by" : "'agent' != 'agent'"} OR m.superseded_by IS NOT w.superseded_by`).all();
1525
1543
  for (const row of decConf)
1526
1544
  conflicts.push(`decision ${row["id"]}: modified in both`);
@@ -1553,12 +1571,17 @@ export function reconcileWorktreeDb(mainDbPath, worktreeDbPath) {
1553
1571
  supporting_slices, validation, notes, full_content, superseded_by
1554
1572
  FROM wt.requirements
1555
1573
  `).run());
1574
+ // V27: preserve content_hash. If the worktree predates V27 (no column),
1575
+ // fall back to the main DB's existing hash so reconcile doesn't null
1576
+ // out integrity fingerprints on artifacts that were unchanged in wt.
1556
1577
  merged.artifacts = countChanges(adapter.prepare(`
1557
1578
  INSERT OR REPLACE INTO artifacts (
1558
- path, artifact_type, milestone_id, slice_id, task_id, full_content, imported_at
1579
+ path, artifact_type, milestone_id, slice_id, task_id, full_content, imported_at, content_hash
1559
1580
  )
1560
- SELECT path, artifact_type, milestone_id, slice_id, task_id, full_content, imported_at
1561
- FROM wt.artifacts
1581
+ SELECT w.path, w.artifact_type, w.milestone_id, w.slice_id, w.task_id, w.full_content, w.imported_at,
1582
+ ${hasArtifactContentHash ? "w.content_hash" : "m.content_hash"}
1583
+ FROM wt.artifacts w
1584
+ LEFT JOIN artifacts m ON m.path = w.path
1562
1585
  `).run());
1563
1586
  // Merge milestones — worktree may have updated status/planning fields.
1564
1587
  // Never downgrade status: complete > active > pre-planning (#4372).
@@ -1656,15 +1679,25 @@ export function reconcileWorktreeDb(mainDbPath, worktreeDbPath) {
1656
1679
  FROM wt.tasks w
1657
1680
  LEFT JOIN tasks m ON m.milestone_id = w.milestone_id AND m.slice_id = w.slice_id AND m.id = w.id
1658
1681
  `).run());
1659
- // Merge memories — keep worktree-learned insights
1682
+ // Merge memories — keep worktree-learned insights.
1683
+ // V18 (scope, tags), V21 (structured_fields), V28 (last_hit_at): for each
1684
+ // column the wt may not yet have (older worktree DB), fall back to the
1685
+ // main DB's existing value via LEFT JOIN so reconcile never silently
1686
+ // resets these fields to defaults on rows that already had them.
1660
1687
  merged.memories = countChanges(adapter.prepare(`
1661
1688
  INSERT OR REPLACE INTO memories (
1662
1689
  seq, id, category, content, confidence, source_unit_type, source_unit_id,
1663
- created_at, updated_at, superseded_by, hit_count
1690
+ created_at, updated_at, superseded_by, hit_count,
1691
+ scope, tags, structured_fields, last_hit_at
1664
1692
  )
1665
- SELECT seq, id, category, content, confidence, source_unit_type, source_unit_id,
1666
- created_at, updated_at, superseded_by, hit_count
1667
- FROM wt.memories
1693
+ SELECT w.seq, w.id, w.category, w.content, w.confidence, w.source_unit_type, w.source_unit_id,
1694
+ w.created_at, w.updated_at, w.superseded_by, w.hit_count,
1695
+ ${hasMemoryScope ? "w.scope" : "COALESCE(m.scope, 'project')"},
1696
+ ${hasMemoryTags ? "w.tags" : "COALESCE(m.tags, '[]')"},
1697
+ ${hasMemoryStructuredFields ? "w.structured_fields" : "m.structured_fields"},
1698
+ ${hasMemoryLastHitAt ? "w.last_hit_at" : "m.last_hit_at"}
1699
+ FROM wt.memories w
1700
+ LEFT JOIN memories m ON m.id = w.id
1668
1701
  `).run());
1669
1702
  // Merge verification evidence — append-only, use INSERT OR IGNORE to avoid duplicates
1670
1703
  merged.verification_evidence = countChanges(adapter.prepare(`
@@ -2423,7 +2456,7 @@ export function updateMemoryContentRow(id, content, confidence, updatedAt) {
2423
2456
  export function incrementMemoryHitCount(id, updatedAt) {
2424
2457
  if (!currentDb)
2425
2458
  throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
2426
- currentDb.prepare("UPDATE memories SET hit_count = hit_count + 1, updated_at = :updated_at WHERE id = :id").run({ ":updated_at": updatedAt, ":id": id });
2459
+ currentDb.prepare("UPDATE memories SET hit_count = hit_count + 1, updated_at = :updated_at, last_hit_at = :last_hit_at WHERE id = :id").run({ ":updated_at": updatedAt, ":last_hit_at": updatedAt, ":id": id });
2427
2460
  }
2428
2461
  export function supersedeMemoryRow(oldId, newId, updatedAt) {
2429
2462
  if (!currentDb)
@@ -51,6 +51,23 @@ export { showQueue, handleQueueReorder, showQueueAdd, buildExistingMilestonesCon
51
51
  import { logWarning } from "./workflow-logger.js";
52
52
  import { deleteRuntimeKv } from "./db/runtime-kv.js";
53
53
  import { PAUSED_SESSION_KV_KEY } from "./interrupted-session.js";
54
+ import { buildWorkflowDispatchContent } from "./workflow-protocol.js";
55
+ import { isFullGsdToolSurfaceRequested, restoreGsdWorkflowTools, scopeGsdWorkflowToolsForDispatch } from "./bootstrap/register-hooks.js";
56
+ function scheduleAutoStartAfterIdle(ctx, pi, basePath, verboseMode, options, launch = startAutoDetached) {
57
+ const waitForIdle = typeof ctx.waitForIdle === "function"
58
+ ? ctx.waitForIdle.bind(ctx)
59
+ : async () => { };
60
+ void waitForIdle()
61
+ .then(() => {
62
+ setTimeout(() => launch(ctx, pi, basePath, verboseMode, options), 0);
63
+ })
64
+ .catch((err) => {
65
+ const message = err instanceof Error ? err.message : String(err);
66
+ ctx.ui.notify(`Auto-start failed while waiting for the prior turn to settle: ${message}`, "error");
67
+ logWarning("guided", `auto-start idle wait failed: ${message}`);
68
+ });
69
+ }
70
+ export const _scheduleAutoStartAfterIdleForTest = scheduleAutoStartAfterIdle;
54
71
  // ─── Scope-based validator wrappers ──────────────────────────────────────────
55
72
  // These thin wrappers accept a MilestoneScope so callers that already hold a
56
73
  // pinned scope never have to re-derive (basePath, milestoneId) separately.
@@ -325,7 +342,7 @@ async function dispatchNextDeepProjectSetupStage(entry) {
325
342
  const { DISPATCH_RULES, hasPendingDeepStage } = await import("./auto-dispatch.js");
326
343
  if (!hasPendingDeepStage(prefs, entry.basePath)) {
327
344
  pendingDeepProjectSetupMap.delete(entry.basePath);
328
- startAutoDetached(entry.ctx, entry.pi, entry.basePath, false, { step: entry.step });
345
+ scheduleAutoStartAfterIdle(entry.ctx, entry.pi, entry.basePath, false, { step: entry.step });
329
346
  return true;
330
347
  }
331
348
  const state = await deriveState(entry.basePath);
@@ -359,14 +376,14 @@ async function dispatchNextDeepProjectSetupStage(entry) {
359
376
  }
360
377
  else if (hasPendingDeepStage(prefs, entry.basePath)) {
361
378
  pendingDeepProjectSetupMap.delete(entry.basePath);
362
- startAutoDetached(entry.ctx, entry.pi, entry.basePath, false, { step: entry.step });
379
+ scheduleAutoStartAfterIdle(entry.ctx, entry.pi, entry.basePath, false, { step: entry.step });
363
380
  return true;
364
381
  }
365
382
  return false;
366
383
  }
367
384
  if (!USER_DRIVEN_DEEP_SETUP_UNITS.has(result.unitType)) {
368
385
  pendingDeepProjectSetupMap.delete(entry.basePath);
369
- startAutoDetached(entry.ctx, entry.pi, entry.basePath, false, { step: entry.step });
386
+ scheduleAutoStartAfterIdle(entry.ctx, entry.pi, entry.basePath, false, { step: entry.step });
370
387
  return true;
371
388
  }
372
389
  entry.currentUnitType = result.unitType;
@@ -544,7 +561,7 @@ export function checkAutoStartAfterDiscuss() {
544
561
  }
545
562
  pendingAutoStartMap.delete(basePath);
546
563
  ctx.ui.notify(`Milestone ${milestoneId} ready.`, "success");
547
- startAutoDetached(ctx, pi, basePath, false, { step });
564
+ scheduleAutoStartAfterIdle(ctx, pi, basePath, false, { step });
548
565
  return true;
549
566
  }
550
567
  /**
@@ -830,38 +847,55 @@ async function dispatchWorkflow(pi, note, customType = "gsd-run", ctx, unitType)
830
847
  return;
831
848
  }
832
849
  }
833
- // Scope tools for discuss flows (#2949).
850
+ // Scope tools for guided workflow turns (#2949, token-consumption savings).
834
851
  // Providers with grammar-based constrained decoding (xAI/Grok) return
835
852
  // "Grammar is too complex" when the combined tool schema is too large.
836
- // Discuss flows only need a small subset of GSD tools — strip the heavy
837
- // planning/execution/completion tools to keep the grammar within limits.
853
+ // Guided workflow turns only need the active unit's tool surface; strip
854
+ // unrelated GSD tools and broad non-GSD tools for this queued turn, then
855
+ // restore so the narrowed surface does not leak into future dispatches.
838
856
  let savedTools = null;
839
- if (unitType?.startsWith("discuss-")) {
857
+ try {
840
858
  const currentTools = pi.getActiveTools();
841
- savedTools = currentTools;
842
- // Keep all non-GSD tools (builtins, other extensions) and only the
843
- // GSD tools on the discuss allowlist.
844
- const scopedTools = currentTools.filter((t) => !t.startsWith("gsd_") || DISCUSS_TOOLS_ALLOWLIST.includes(t));
845
- pi.setActiveTools(scopedTools);
846
- debugLog("discuss-tool-scoping", {
847
- unitType,
848
- before: currentTools.length,
849
- after: scopedTools.length,
850
- removed: currentTools.length - scopedTools.length,
851
- });
852
- }
853
- const workflowPath = process.env.GSD_WORKFLOW_PATH ?? join(gsdHome(), "agent", "GSD-WORKFLOW.md");
854
- const workflow = readFileSync(workflowPath, "utf-8");
855
- pi.sendMessage({
856
- customType,
857
- content: `Read the following GSD workflow protocol and execute exactly.\n\n${workflow}\n\n## Your Task\n\n${note}`,
858
- display: false,
859
- }, { triggerTurn: true });
860
- // Restore full tool set after the message is queued. The LLM turn has
861
- // already captured the scoped set — restoring prevents the narrowed
862
- // tools from leaking into subsequent dispatches (#3628).
863
- if (savedTools) {
864
- pi.setActiveTools(savedTools);
859
+ savedTools = {
860
+ tools: currentTools,
861
+ visibleSkills: typeof pi.getVisibleSkills === "function" ? pi.getVisibleSkills() : undefined,
862
+ restoreVisibleSkills: typeof pi.setVisibleSkills === "function",
863
+ };
864
+ if (unitType?.startsWith("discuss-") && !isFullGsdToolSurfaceRequested()) {
865
+ // Keep all non-GSD tools (builtins, other extensions) and only the
866
+ // GSD tools on the discuss allowlist.
867
+ const scopedTools = currentTools.filter((t) => !t.startsWith("gsd_") || DISCUSS_TOOLS_ALLOWLIST.includes(t));
868
+ pi.setActiveTools(scopedTools);
869
+ const scopedState = scopeGsdWorkflowToolsForDispatch(pi, unitType);
870
+ savedTools = {
871
+ tools: currentTools,
872
+ visibleSkills: scopedState?.visibleSkills ?? savedTools.visibleSkills,
873
+ restoreVisibleSkills: scopedState?.restoreVisibleSkills ?? savedTools.restoreVisibleSkills,
874
+ };
875
+ debugLog("discuss-tool-scoping", {
876
+ unitType,
877
+ before: currentTools.length,
878
+ after: pi.getActiveTools().length,
879
+ removed: currentTools.length - pi.getActiveTools().length,
880
+ });
881
+ }
882
+ else {
883
+ savedTools = scopeGsdWorkflowToolsForDispatch(pi, unitType) ?? savedTools;
884
+ }
885
+ const workflowPath = process.env.GSD_WORKFLOW_PATH ?? join(gsdHome(), "agent", "GSD-WORKFLOW.md");
886
+ const workflow = readFileSync(workflowPath, "utf-8");
887
+ pi.sendMessage({
888
+ customType,
889
+ content: buildWorkflowDispatchContent({ workflow, workflowPath, task: note }),
890
+ display: false,
891
+ }, { triggerTurn: true });
892
+ }
893
+ finally {
894
+ // Restore full tool set after the message is queued. The LLM turn has
895
+ // already captured the scoped set — restoring prevents the narrowed
896
+ // tools from leaking into subsequent dispatches (#3628). The finally
897
+ // block ensures restoration even if sendMessage throws.
898
+ restoreGsdWorkflowTools(pi, savedTools);
865
899
  }
866
900
  }
867
901
  function getStructuredQuestionsAvailability(pi, ctx) {