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
@@ -0,0 +1,113 @@
1
+ // GSD-2 + src/resources/extensions/gsd/tests/parallel-orchestrator-fast-forward.test.ts
2
+ // Regression: parallel-orchestrator's `_createMilestoneWorktree` must
3
+ // fast-forward a reused milestone branch onto integration before creating
4
+ // the worktree, matching the behavior added to the auto-mode path in
5
+ // commit 8996cb68e (#5549 post-merge audit, R3).
6
+
7
+ import { describe, test, beforeEach, afterEach } from "node:test";
8
+ import assert from "node:assert/strict";
9
+ import { execFileSync } from "node:child_process";
10
+ import { mkdtempSync, rmSync, writeFileSync, mkdirSync } from "node:fs";
11
+ import { tmpdir } from "node:os";
12
+ import { join } from "node:path";
13
+
14
+ import { _createMilestoneWorktree } from "../parallel-orchestrator.js";
15
+
16
+ const NO_PROMPT_ENV = {
17
+ ...process.env,
18
+ GIT_TERMINAL_PROMPT: "0",
19
+ GIT_AUTHOR_NAME: "test",
20
+ GIT_AUTHOR_EMAIL: "test@example.com",
21
+ GIT_COMMITTER_NAME: "test",
22
+ GIT_COMMITTER_EMAIL: "test@example.com",
23
+ };
24
+
25
+ function git(cwd: string, ...args: string[]): string {
26
+ return execFileSync("git", args, {
27
+ cwd,
28
+ stdio: ["ignore", "pipe", "pipe"],
29
+ encoding: "utf-8",
30
+ env: NO_PROMPT_ENV,
31
+ });
32
+ }
33
+
34
+ function rev(cwd: string, ref: string): string {
35
+ return git(cwd, "rev-parse", ref).trim();
36
+ }
37
+
38
+ describe("_createMilestoneWorktree fast-forwards reused milestone branches (#5549 R3)", () => {
39
+ let repo: string;
40
+
41
+ beforeEach(() => {
42
+ repo = mkdtempSync(join(tmpdir(), "parallel-orch-ff-"));
43
+ git(repo, "init", "-q", "-b", "main");
44
+ git(repo, "config", "user.email", "test@example.com");
45
+ git(repo, "config", "user.name", "test");
46
+ writeFileSync(join(repo, "seed.txt"), "seed\n");
47
+ git(repo, "add", "seed.txt");
48
+ git(repo, "commit", "-q", "-m", "initial");
49
+ // Minimal .gsd/ structure so syncGsdStateToWorktree doesn't crash on a
50
+ // bare repo. We don't care if it copies anything — only that the FF ran.
51
+ mkdirSync(join(repo, ".gsd"), { recursive: true });
52
+ });
53
+
54
+ afterEach(() => {
55
+ rmSync(repo, { recursive: true, force: true });
56
+ });
57
+
58
+ test("reused milestone branch behind main is fast-forwarded before worktree creation", () => {
59
+ // Worker N drained M001 in a previous run, leaving milestone/M001 forked
60
+ // from old main. Worker N+1 picks up M002 — but the milestone/M002 branch
61
+ // was created from old main in a sibling run, so it's now N commits behind.
62
+ git(repo, "branch", "milestone/M002");
63
+ const m002Initial = rev(repo, "milestone/M002");
64
+
65
+ writeFileSync(join(repo, "seed.txt"), "main moved forward\n");
66
+ git(repo, "add", "seed.txt");
67
+ git(repo, "commit", "-q", "-m", "main advanced");
68
+ const mainTip = rev(repo, "main");
69
+
70
+ assert.notEqual(m002Initial, mainTip, "main must be ahead before the test");
71
+
72
+ // _createMilestoneWorktree may throw inside createWorktree/syncGsdStateToWorktree
73
+ // (e.g. if the worktree-manager has stricter requirements than this minimal
74
+ // repo provides). The fast-forward runs BEFORE those calls, so the branch
75
+ // ref should have moved regardless. Catch and assert on observable state.
76
+ try {
77
+ _createMilestoneWorktree(repo, "M002");
78
+ } catch {
79
+ // Fine — we only care that FF executed.
80
+ }
81
+
82
+ assert.equal(
83
+ rev(repo, "milestone/M002"),
84
+ mainTip,
85
+ "milestone/M002 must be fast-forwarded to main's tip before worktree is built",
86
+ );
87
+ });
88
+
89
+ test("diverged milestone branch is NOT touched (would lose work)", () => {
90
+ git(repo, "checkout", "-q", "-b", "milestone/M002");
91
+ writeFileSync(join(repo, "wip.txt"), "milestone-only work\n");
92
+ git(repo, "add", "wip.txt");
93
+ git(repo, "commit", "-q", "-m", "M002 work");
94
+ const m002Tip = rev(repo, "milestone/M002");
95
+
96
+ git(repo, "checkout", "-q", "main");
97
+ writeFileSync(join(repo, "seed.txt"), "main moved forward\n");
98
+ git(repo, "add", "seed.txt");
99
+ git(repo, "commit", "-q", "-m", "main advanced");
100
+
101
+ try {
102
+ _createMilestoneWorktree(repo, "M002");
103
+ } catch {
104
+ // ignored
105
+ }
106
+
107
+ assert.equal(
108
+ rev(repo, "milestone/M002"),
109
+ m002Tip,
110
+ "diverged milestone branch must NOT be touched — would lose committed work",
111
+ );
112
+ });
113
+ });
@@ -1,3 +1,6 @@
1
+ // Project/App: GSD-2
2
+ // File Purpose: Unit tests for pre-execution validation checks.
3
+
1
4
  /**
2
5
  * pre-execution-checks.test.ts — Unit tests for pre-execution validation checks.
3
6
  *
@@ -1542,6 +1545,27 @@ describe("checkFilePathConsistency directory inputs (#4446)", () => {
1542
1545
  assert.equal(results[0].blocking, true);
1543
1546
  });
1544
1547
 
1548
+ test("runtime directory annotation is skipped as a pre-execution file dependency", (t) => {
1549
+ const tempDir = join(tmpdir(), `pre-exec-dir-runtime-${Date.now()}`);
1550
+ mkdirSync(tempDir, { recursive: true });
1551
+ t.after(() => rmSync(tempDir, { recursive: true, force: true }));
1552
+
1553
+ const tasks = [
1554
+ createTask({
1555
+ id: "T02",
1556
+ inputs: ["entries/ directory (runtime)"],
1557
+ expected_output: ["src/commands/delete.ts", "src/index.ts"],
1558
+ }),
1559
+ ];
1560
+
1561
+ const results = checkFilePathConsistency(tasks, tempDir);
1562
+ assert.deepEqual(
1563
+ results,
1564
+ [],
1565
+ "Runtime-only directory inputs are created during command execution, not required before the task starts",
1566
+ );
1567
+ });
1568
+
1545
1569
  test("tilde-prefixed input is matched against $HOME, not the project basePath", (t) => {
1546
1570
  const fakeHome = join(tmpdir(), `pre-exec-tilde-home-${Date.now()}`);
1547
1571
  const projectDir = join(tmpdir(), `pre-exec-tilde-proj-${Date.now()}`);
@@ -1597,6 +1621,20 @@ describe("checkTaskOrdering directory inputs (#4446)", () => {
1597
1621
  "Directory reference should not be treated as reading a file created later",
1598
1622
  );
1599
1623
  });
1624
+
1625
+ test("runtime directory annotation does not produce an ordering violation", () => {
1626
+ const tasks = [
1627
+ createTask({
1628
+ id: "T02",
1629
+ sequence: 0,
1630
+ inputs: ["entries/ directory (runtime)"],
1631
+ expected_output: [],
1632
+ }),
1633
+ ];
1634
+
1635
+ const results = checkTaskOrdering(tasks, "/tmp");
1636
+ assert.deepEqual(results, []);
1637
+ });
1600
1638
  });
1601
1639
 
1602
1640
  // ─── Regression Tests: checkTaskOrdering false positive for pre-execution refs (#4071) ──
@@ -1781,7 +1819,7 @@ describe("checkFilePathConsistency completed-task output exemption (#4071)", ()
1781
1819
  );
1782
1820
  });
1783
1821
 
1784
- test("pending task at higher index still causes a missing-file error", (t) => {
1822
+ test("pending task at higher index does NOT cause a duplicate consistency error (ordering check handles it)", (t) => {
1785
1823
  const tempDir = join(tmpdir(), `pre-exec-fc-pending-${Date.now()}`);
1786
1824
  mkdirSync(tempDir, { recursive: true });
1787
1825
  t.after(() => rmSync(tempDir, { recursive: true, force: true }));
@@ -1803,14 +1841,16 @@ describe("checkFilePathConsistency completed-task output exemption (#4071)", ()
1803
1841
  }),
1804
1842
  ];
1805
1843
 
1844
+ // checkFilePathConsistency suppresses the error here because checkTaskOrdering
1845
+ // will fire a more precise "sequence violation" error for the same file.
1846
+ // The combined output of runPreExecutionChecks still flags the issue — just
1847
+ // once, via the ordering check, instead of twice.
1806
1848
  const results = checkFilePathConsistency(tasks, tempDir);
1807
1849
  assert.equal(
1808
1850
  results.length,
1809
- 1,
1810
- "pending task at higher index must still be flagged the file is not available yet",
1851
+ 0,
1852
+ "consistency check must not duplicate what the ordering check already reports",
1811
1853
  );
1812
- assert.equal(results[0].blocking, true);
1813
- assert.equal(results[0].target, "artifacts/output.json");
1814
1854
  });
1815
1855
  });
1816
1856
 
@@ -0,0 +1,230 @@
1
+ // Project/App: GSD-2
2
+ // File Purpose: Verifies low-risk auto-prompt duplication cuts render through prompt builders.
3
+
4
+ import test from "node:test";
5
+ import type { TestContext } from "node:test";
6
+ import assert from "node:assert/strict";
7
+ import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
8
+ import { join } from "node:path";
9
+ import { tmpdir } from "node:os";
10
+
11
+ import { invalidateAllCaches } from "../cache.ts";
12
+ import {
13
+ closeDatabase,
14
+ insertMilestone,
15
+ insertSlice,
16
+ insertTask,
17
+ openDatabase,
18
+ upsertMilestonePlanning,
19
+ } from "../gsd-db.ts";
20
+
21
+ type AutoPromptBuilders = typeof import("../auto-prompts.ts");
22
+
23
+ function makeBase(prefix: string): string {
24
+ const base = mkdtempSync(join(tmpdir(), prefix));
25
+ mkdirSync(join(base, ".gsd", "milestones", "M001", "slices", "S01", "tasks"), { recursive: true });
26
+ return base;
27
+ }
28
+
29
+ function cleanup(base: string): void {
30
+ try { closeDatabase(); } catch { /* noop */ }
31
+ invalidateAllCaches();
32
+ rmSync(base, { recursive: true, force: true });
33
+ }
34
+
35
+ async function loadAutoPromptBuilders(t: TestContext): Promise<AutoPromptBuilders> {
36
+ const previousGsdHome = process.env.GSD_HOME;
37
+ const isolatedHome = mkdtempSync(join(tmpdir(), "gsd-prompt-loader-home-"));
38
+ process.env.GSD_HOME = isolatedHome;
39
+ t.after(() => {
40
+ if (previousGsdHome === undefined) delete process.env.GSD_HOME;
41
+ else process.env.GSD_HOME = previousGsdHome;
42
+ rmSync(isolatedHome, { recursive: true, force: true });
43
+ });
44
+ return import(`../auto-prompts.ts?promptDupCuts=${Date.now()}-${Math.random()}`) as Promise<AutoPromptBuilders>;
45
+ }
46
+
47
+ function seedDb(base: string, taskStatus = "complete"): void {
48
+ openDatabase(join(base, ".gsd", "gsd.db"));
49
+ insertMilestone({ id: "M001", title: "Prompt Cuts", status: "active", depends_on: [] });
50
+ upsertMilestonePlanning("M001", {
51
+ title: "Prompt Cuts",
52
+ status: "active",
53
+ vision: "Reduce duplicate prompt reads.",
54
+ successCriteria: ["Prompt builders render compact context."],
55
+ keyRisks: [],
56
+ proofStrategy: [],
57
+ verificationContract: "",
58
+ verificationIntegration: "",
59
+ verificationOperational: "",
60
+ verificationUat: "",
61
+ definitionOfDone: [],
62
+ requirementCoverage: "",
63
+ boundaryMapMarkdown: "",
64
+ });
65
+ insertSlice({
66
+ id: "S01",
67
+ milestoneId: "M001",
68
+ title: "Prompt Slice",
69
+ status: "active",
70
+ risk: "low",
71
+ depends: [],
72
+ demo: "",
73
+ sequence: 1,
74
+ });
75
+ insertTask({
76
+ id: "T01",
77
+ sliceId: "S01",
78
+ milestoneId: "M001",
79
+ title: "Task one",
80
+ status: taskStatus,
81
+ });
82
+ }
83
+
84
+ function writeRoadmapAndPlan(base: string): void {
85
+ writeFileSync(
86
+ join(base, ".gsd", "milestones", "M001", "M001-ROADMAP.md"),
87
+ [
88
+ "# M001 Roadmap",
89
+ "## Slices",
90
+ "- [ ] **S01: Prompt Slice** `risk:low` `depends:[]`",
91
+ ].join("\n"),
92
+ );
93
+ writeFileSync(
94
+ join(base, ".gsd", "milestones", "M001", "slices", "S01", "S01-PLAN.md"),
95
+ [
96
+ "# S01 Plan",
97
+ "",
98
+ "**Goal:** Reduce duplicate prompt reads.",
99
+ "",
100
+ "## Tasks",
101
+ "- [x] **T01: Task one** `est:15m`",
102
+ ].join("\n"),
103
+ );
104
+ }
105
+
106
+ function writeTaskSummary(base: string, options?: { blocker?: boolean; repeatedNarrative?: string }): void {
107
+ const narrative = options?.repeatedNarrative ?? "This full implementation narrative should stay out of closer prompts.";
108
+ writeFileSync(
109
+ join(base, ".gsd", "milestones", "M001", "slices", "S01", "tasks", "T01-SUMMARY.md"),
110
+ [
111
+ "---",
112
+ "id: T01",
113
+ "parent: S01",
114
+ "milestone: M001",
115
+ "provides:",
116
+ " - prompt context reduction",
117
+ "key_files:",
118
+ " - src/resources/extensions/gsd/auto-prompts.ts",
119
+ "key_decisions:",
120
+ " - use compact excerpts before full reads",
121
+ "patterns_established:",
122
+ " - excerpt-first complete-slice context",
123
+ "observability_surfaces: []",
124
+ "duration: 15m",
125
+ "verification_result: passed",
126
+ "completed_at: 2026-05-06T12:00:00Z",
127
+ `blocker_discovered: ${options?.blocker ? "true" : "false"}`,
128
+ "---",
129
+ "",
130
+ "# T01: Task one",
131
+ "**One-line result.**",
132
+ "",
133
+ "## What Happened",
134
+ narrative,
135
+ "",
136
+ "## Verification",
137
+ "node:test passed.",
138
+ "",
139
+ "## Diagnostics",
140
+ "Prompt size stayed bounded.",
141
+ ].join("\n"),
142
+ );
143
+ }
144
+
145
+ test("execute-task rendering makes memory_query and template disk reads fallback-only", async (t) => {
146
+ const base = makeBase("gsd-execute-dup-cuts-");
147
+ t.after(() => cleanup(base));
148
+ invalidateAllCaches();
149
+
150
+ seedDb(base, "pending");
151
+ writeRoadmapAndPlan(base);
152
+ writeFileSync(
153
+ join(base, ".gsd", "milestones", "M001", "slices", "S01", "tasks", "T01-PLAN.md"),
154
+ "# T01 Plan\n\nDo the prompt edit.\n",
155
+ );
156
+
157
+ const { buildExecuteTaskPrompt } = await loadAutoPromptBuilders(t);
158
+ const prompt = await buildExecuteTaskPrompt("M001", "S01", "Prompt Slice", "T01", "Task one", base);
159
+
160
+ assert.match(prompt, /Call `memory_query`.*only when no injected memory block exists or the inlined memory\/context is insufficient/s);
161
+ assert.doesNotMatch(prompt, /Call `memory_query` with 2-4 keywords from the task title and touched files unless this is purely mechanical/);
162
+ assert.match(prompt, /Use the inlined Task Summary template below/);
163
+ assert.match(prompt, /Read `.*task-summary\.md` only if the inlined template is absent or visibly truncated/);
164
+ assert.doesNotMatch(prompt, /Read the template at `.*task-summary\.md`/);
165
+ assert.match(prompt, /### Output Template: Task Summary/);
166
+ });
167
+
168
+ test("complete-slice renders task summary excerpts without full summary bodies", async (t) => {
169
+ const base = makeBase("gsd-complete-slice-excerpts-");
170
+ t.after(() => cleanup(base));
171
+ invalidateAllCaches();
172
+
173
+ seedDb(base);
174
+ writeRoadmapAndPlan(base);
175
+ const repeatedNarrative = "FULL_TASK_BODY_SHOULD_NOT_RENDER ".repeat(40);
176
+ writeTaskSummary(base, { repeatedNarrative });
177
+
178
+ const { buildCompleteSlicePrompt } = await loadAutoPromptBuilders(t);
179
+ const prompt = await buildCompleteSlicePrompt("M001", "Prompt Cuts", "S01", "Prompt Slice", base);
180
+
181
+ assert.match(prompt, /### Task Summary: T01 \(excerpt\)/);
182
+ assert.match(prompt, /On-demand.*read `\.gsd\/milestones\/M001\/slices\/S01\/tasks\/T01-SUMMARY\.md` only when this excerpt is absent\/truncated/s);
183
+ assert.doesNotMatch(prompt, /FULL_TASK_BODY_SHOULD_NOT_RENDER/);
184
+ assert.match(prompt, /Review the inlined task-summary excerpts/);
185
+ });
186
+
187
+ test("complete-slice caps malformed task summaries instead of inlining full bodies", async (t) => {
188
+ const base = makeBase("gsd-complete-slice-malformed-excerpts-");
189
+ t.after(() => cleanup(base));
190
+ invalidateAllCaches();
191
+
192
+ seedDb(base);
193
+ writeRoadmapAndPlan(base);
194
+ writeFileSync(
195
+ join(base, ".gsd", "milestones", "M001", "slices", "S01", "tasks", "T01-SUMMARY.md"),
196
+ [
197
+ "# Legacy summary without frontmatter id",
198
+ "LEGACY_FULL_BODY_SHOULD_BE_CAPPED ".repeat(200),
199
+ ].join("\n"),
200
+ );
201
+
202
+ const { buildCompleteSlicePrompt } = await loadAutoPromptBuilders(t);
203
+ const prompt = await buildCompleteSlicePrompt("M001", "Prompt Cuts", "S01", "Prompt Slice", base);
204
+
205
+ assert.match(prompt, /Truncated malformed summary/);
206
+ assert.ok(prompt.length < 20_000);
207
+ assert.ok((prompt.match(/LEGACY_FULL_BODY_SHOULD_BE_CAPPED/g) ?? []).length < 60);
208
+ });
209
+
210
+ test("replan-slice renders blocker summary excerpt and tells the agent to read full only on demand", async (t) => {
211
+ const base = makeBase("gsd-replan-excerpts-");
212
+ t.after(() => cleanup(base));
213
+ invalidateAllCaches();
214
+
215
+ seedDb(base);
216
+ writeRoadmapAndPlan(base);
217
+ writeTaskSummary(base, {
218
+ blocker: true,
219
+ repeatedNarrative: "FULL_BLOCKER_BODY_SHOULD_NOT_RENDER ".repeat(40),
220
+ });
221
+
222
+ const { buildReplanSlicePrompt } = await loadAutoPromptBuilders(t);
223
+ const prompt = await buildReplanSlicePrompt("M001", "Prompt Cuts", "S01", "Prompt Slice", base);
224
+
225
+ assert.match(prompt, /### Blocker Task Summary: T01 \(excerpt\)/);
226
+ assert.match(prompt, /Use the inlined blocker summary excerpt first/);
227
+ assert.match(prompt, /Read the full blocker task summary only if the excerpt is absent, marked truncated, or lacks the specific blocker evidence needed to replan/);
228
+ assert.doesNotMatch(prompt, /FULL_BLOCKER_BODY_SHOULD_NOT_RENDER/);
229
+ assert.doesNotMatch(prompt, /Read the blocker task summary carefully/);
230
+ });
@@ -65,6 +65,25 @@ describe('prompt step ordering (#3696)', () => {
65
65
  assert.ok(learningsIdx < completeMilestoneIdx, 'learnings extraction must happen before gsd_complete_milestone');
66
66
  });
67
67
 
68
+ test('complete-milestone duplicate guard checks milestone status before durable writes', () => {
69
+ const guardMatch = completeMilestoneMd.match(/^\d+\.\s.*gsd_milestone_status/m);
70
+ const reqUpdateMatch = completeMilestoneMd.match(/^\d+\.\s.*gsd_requirement_update/m);
71
+ assert.ok(guardMatch, 'complete-milestone must start with a gsd_milestone_status duplicate guard');
72
+ assert.ok(reqUpdateMatch, 'gsd_requirement_update should appear in a numbered step');
73
+
74
+ const guardIdx = completeMilestoneMd.indexOf(guardMatch![0]);
75
+ const reqUpdateIdx = completeMilestoneMd.indexOf(reqUpdateMatch![0]);
76
+ assert.ok(
77
+ guardIdx < reqUpdateIdx,
78
+ 'duplicate guard must run before requirement/project/learnings writes',
79
+ );
80
+ assert.match(
81
+ completeMilestoneMd,
82
+ /status(?:`|\*\*)?\s+(?:is\s+)?(?:`complete`|"complete")/i,
83
+ 'duplicate guard must tell the agent to stop when status is complete',
84
+ );
85
+ });
86
+
68
87
  test('complete-slice.md uses gsd_requirement_update', () => {
69
88
  assert.match(completeSliceMd, /gsd_requirement_update/,
70
89
  'complete-slice.md should reference gsd_requirement_update');
@@ -28,8 +28,8 @@ describe('query-tools ensureDbOpen usage (#3672)', () => {
28
28
  });
29
29
 
30
30
  test('calls ensureDbOpen() before DB queries', () => {
31
- assert.match(source, /await ensureDbOpen\(\)/,
32
- 'query-tools should call await ensureDbOpen()');
31
+ assert.match(source, /await ensureDbOpen\([^)]*\)/,
32
+ 'query-tools should call await ensureDbOpen(...)');
33
33
  });
34
34
 
35
35
  test('no longer imports isDbAvailable in the execute path', () => {
@@ -41,7 +41,7 @@ describe('query-tools ensureDbOpen usage (#3672)', () => {
41
41
  });
42
42
 
43
43
  test('uses dbAvailable result from ensureDbOpen', () => {
44
- assert.match(source, /dbAvailable\s*=\s*await ensureDbOpen\(\)/,
44
+ assert.match(source, /dbAvailable\s*=\s*await ensureDbOpen\([^)]*\)/,
45
45
  'should store ensureDbOpen result in dbAvailable');
46
46
  });
47
47
  });
@@ -24,7 +24,7 @@ describe('restore tools after discuss flow scoping (#3628)', () => {
24
24
  it('savedTools is declared before the discuss scoping block', () => {
25
25
  // savedTools must be declared before the discuss-* check
26
26
  const savedToolsDecl = src.indexOf('let savedTools')
27
- const discussCheck = src.indexOf('if (unitType?.startsWith("discuss-"))')
27
+ const discussCheck = src.indexOf('if (unitType?.startsWith("discuss-")')
28
28
  assert.ok(savedToolsDecl !== -1, 'savedTools variable must be declared')
29
29
  assert.ok(discussCheck !== -1, 'discuss-* type check must exist')
30
30
  assert.ok(
@@ -33,40 +33,61 @@ describe('restore tools after discuss flow scoping (#3628)', () => {
33
33
  )
34
34
  })
35
35
 
36
- it('savedTools captures current tools inside the discuss block', () => {
37
- const discussCheck = src.indexOf('if (unitType?.startsWith("discuss-"))')
36
+ it('savedTools captures current tools before scoping can mutate active state', () => {
37
+ const discussCheck = src.indexOf('if (unitType?.startsWith("discuss-")')
38
38
  assert.ok(discussCheck !== -1)
39
39
 
40
- // Look for savedTools assignment within the discuss block
41
- const blockAfter = extractSourceRegion(src, 'if (unitType?.startsWith("discuss-"))')
40
+ const currentToolsDecl = src.indexOf('const currentTools = pi.getActiveTools()')
41
+ const savedToolsAssign = src.indexOf('savedTools = {', currentToolsDecl)
42
+ const firstMutation = src.indexOf('pi.setActiveTools(scopedTools)')
42
43
  assert.ok(
43
- blockAfter.includes('savedTools = currentTools'),
44
- 'savedTools must be assigned from currentTools inside the discuss block',
44
+ currentToolsDecl !== -1 && savedToolsAssign !== -1 && firstMutation !== -1,
45
+ 'guided-flow.ts must capture current tools, save them, and then scope active tools',
46
+ )
47
+ assert.ok(
48
+ currentToolsDecl < savedToolsAssign && savedToolsAssign < firstMutation,
49
+ 'savedTools must capture currentTools before any discuss scoping mutation',
50
+ )
51
+ assert.ok(
52
+ src.slice(savedToolsAssign, firstMutation).includes('tools: currentTools'),
53
+ 'savedTools must include currentTools before the first scoping mutation',
45
54
  )
46
55
  })
47
56
 
57
+ it('scoping and workflow read happen inside the restore try block', () => {
58
+ const savedToolsDecl = src.indexOf('let savedTools')
59
+ const tryIdx = src.indexOf('try {', savedToolsDecl)
60
+ const firstMutation = src.indexOf('pi.setActiveTools(scopedTools)')
61
+ const workflowRead = src.indexOf('readFileSync(workflowPath')
62
+ const finallyIdx = src.indexOf('} finally {', tryIdx)
63
+
64
+ assert.ok(savedToolsDecl !== -1, 'savedTools variable must be declared')
65
+ assert.ok(tryIdx !== -1, 'restore try block must exist')
66
+ assert.ok(firstMutation !== -1, 'discuss scoping mutation must exist')
67
+ assert.ok(workflowRead !== -1, 'workflow file read must exist')
68
+ assert.ok(finallyIdx !== -1, 'restore finally block must exist')
69
+ assert.ok(tryIdx < firstMutation && firstMutation < finallyIdx, 'scoping mutation must be inside try/finally')
70
+ assert.ok(tryIdx < workflowRead && workflowRead < finallyIdx, 'workflow file read must be inside try/finally')
71
+ })
72
+
48
73
  it('savedTools is restored after sendMessage', () => {
49
74
  // #4573: guided-flow.ts now contains multiple `triggerTurn: true` calls
50
75
  // (ready-phrase and empty-turn recovery paths). The discuss-flow scoping
51
- // sendMessage is the one that follows `savedTools = currentTools`, so
76
+ // sendMessage is the one that follows `tools: currentTools`, so
52
77
  // anchor the search there rather than at the first `triggerTurn: true`.
53
- const savedToolsAssign = src.indexOf('savedTools = currentTools')
54
- assert.ok(savedToolsAssign !== -1, 'savedTools = currentTools must exist')
78
+ const savedToolsAssign = src.indexOf('tools: currentTools')
79
+ assert.ok(savedToolsAssign !== -1, 'savedTools must capture currentTools')
55
80
 
56
81
  const sendMsg = src.indexOf('triggerTurn: true', savedToolsAssign)
57
82
  assert.ok(sendMsg !== -1, 'discuss-flow sendMessage with triggerTurn must exist after savedTools capture')
58
83
 
59
- // After sendMessage, savedTools should be restored via setActiveTools.
84
+ // After sendMessage, savedTools should be restored via the shared helper.
60
85
  // Use fromIdx to anchor at the discuss-flow sendMessage, not the first
61
86
  // triggerTurn: true occurrence in the file.
62
87
  const afterSend = extractSourceRegion(src, 'triggerTurn: true', { fromIdx: savedToolsAssign })
63
88
  assert.ok(
64
- afterSend.includes('if (savedTools)'),
65
- 'savedTools restoration guard must exist after sendMessage',
66
- )
67
- assert.ok(
68
- afterSend.includes('setActiveTools(savedTools)'),
69
- 'setActiveTools(savedTools) must be called to restore the full tool set',
89
+ afterSend.includes('restoreGsdWorkflowTools(pi, savedTools)'),
90
+ 'restoreGsdWorkflowTools(pi, savedTools) must restore the full scoped state',
70
91
  )
71
92
  })
72
93
  })