@opengsd/gsd-pi 1.3.0-dev.65546769 → 1.3.0-dev.738160b9

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 (488) hide show
  1. package/README.md +1 -1
  2. package/dist/cli.js +17 -4
  3. package/dist/headless-answers.js +1 -1
  4. package/dist/headless-ui.d.ts +1 -1
  5. package/dist/headless-ui.js +1 -1
  6. package/dist/headless.js +1 -1
  7. package/dist/resources/.managed-resources-content-hash +1 -1
  8. package/dist/resources/extensions/claude-code-cli/models.js +9 -0
  9. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +11 -2
  10. package/dist/resources/extensions/google-cli/stream-adapter.js +82 -15
  11. package/dist/resources/extensions/gsd/artifact-verification.js +427 -0
  12. package/dist/resources/extensions/gsd/auto/orchestrator.js +12 -3
  13. package/dist/resources/extensions/gsd/auto/session.js +11 -0
  14. package/dist/resources/extensions/gsd/auto/verification-retry-policy.js +1 -1
  15. package/dist/resources/extensions/gsd/auto-artifact-paths.js +28 -1
  16. package/dist/resources/extensions/gsd/auto-budget.js +5 -1
  17. package/dist/resources/extensions/gsd/auto-dispatch.js +34 -20
  18. package/dist/resources/extensions/gsd/auto-post-unit.js +1 -1
  19. package/dist/resources/extensions/gsd/auto-prompts.js +29 -14
  20. package/dist/resources/extensions/gsd/auto-recovery.js +9 -510
  21. package/dist/resources/extensions/gsd/auto-runtime-state.js +4 -5
  22. package/dist/resources/extensions/gsd/auto-timeout-recovery.js +3 -3
  23. package/dist/resources/extensions/gsd/auto-verification.js +5 -2
  24. package/dist/resources/extensions/gsd/auto.js +19 -10
  25. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +103 -13
  26. package/dist/resources/extensions/gsd/bootstrap/core-session-tools.js +39 -0
  27. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +11 -5
  28. package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +2 -0
  29. package/dist/resources/extensions/gsd/bootstrap/register-extension.js +2 -0
  30. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +41 -21
  31. package/dist/resources/extensions/gsd/bootstrap/system-context.js +46 -19
  32. package/dist/resources/extensions/gsd/bootstrap/tool-call-loop-guard.js +96 -10
  33. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +1 -1
  34. package/dist/resources/extensions/gsd/closeout-consistency-gate.js +3 -1
  35. package/dist/resources/extensions/gsd/commands/handlers/auto.js +1 -1
  36. package/dist/resources/extensions/gsd/commands-context.js +19 -1
  37. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +17 -11
  38. package/dist/resources/extensions/gsd/commands-usage.js +1 -1
  39. package/dist/resources/extensions/gsd/commands-worktree.js +12 -10
  40. package/dist/resources/extensions/gsd/dashboard-overlay.js +32 -3
  41. package/dist/resources/extensions/gsd/db/engine.js +4 -1
  42. package/dist/resources/extensions/gsd/db/queries.js +60 -0
  43. package/dist/resources/extensions/gsd/db-memory-fts-schema.js +34 -0
  44. package/dist/resources/extensions/gsd/db-workspace.js +55 -3
  45. package/dist/resources/extensions/gsd/delegation-policy.js +1 -1
  46. package/dist/resources/extensions/gsd/docs/preferences-reference.md +2 -2
  47. package/dist/resources/extensions/gsd/doctor-engine-checks.js +192 -4
  48. package/dist/resources/extensions/gsd/doctor-providers.js +92 -8
  49. package/dist/resources/extensions/gsd/doctor-runtime-checks.js +3 -0
  50. package/dist/resources/extensions/gsd/exec-sandbox.js +53 -11
  51. package/dist/resources/extensions/gsd/flat-phase-migration.js +45 -0
  52. package/dist/resources/extensions/gsd/forensics.js +2 -32
  53. package/dist/resources/extensions/gsd/git-service.js +61 -4
  54. package/dist/resources/extensions/gsd/gitignore.js +4 -1
  55. package/dist/resources/extensions/gsd/guided-flow-queue.js +66 -5
  56. package/dist/resources/extensions/gsd/health-widget.js +55 -29
  57. package/dist/resources/extensions/gsd/json-persistence.js +20 -0
  58. package/dist/resources/extensions/gsd/knowledge-backfill.js +18 -11
  59. package/dist/resources/extensions/gsd/layout-policy.js +3 -1
  60. package/dist/resources/extensions/gsd/markdown-renderer.js +8 -9
  61. package/dist/resources/extensions/gsd/memory-backfill.js +28 -21
  62. package/dist/resources/extensions/gsd/memory-consolidation-scanner.js +44 -21
  63. package/dist/resources/extensions/gsd/metrics.js +4 -4
  64. package/dist/resources/extensions/gsd/migrate-external.js +11 -2
  65. package/dist/resources/extensions/gsd/migration-auto-check.js +22 -0
  66. package/dist/resources/extensions/gsd/milestone-ids.js +32 -2
  67. package/dist/resources/extensions/gsd/milestone-implementation-evidence.js +26 -20
  68. package/dist/resources/extensions/gsd/orphan-stash-audit.js +92 -16
  69. package/dist/resources/extensions/gsd/prompts/code-review.md +6 -4
  70. package/dist/resources/extensions/gsd/prompts/plan-milestone.md +2 -2
  71. package/dist/resources/extensions/gsd/prompts/plan-slice.md +3 -3
  72. package/dist/resources/extensions/gsd/quick.js +45 -2
  73. package/dist/resources/extensions/gsd/root-write-leak-guard.js +7 -1
  74. package/dist/resources/extensions/gsd/safety/destructive-guard.js +8 -1
  75. package/dist/resources/extensions/gsd/safety/evidence-collector.js +16 -1
  76. package/dist/resources/extensions/gsd/safety/file-change-validator.js +5 -2
  77. package/dist/resources/extensions/gsd/session-forensics.js +11 -1
  78. package/dist/resources/extensions/gsd/skills/gsd-headless/references/commands.md +1 -1
  79. package/dist/resources/extensions/gsd/state/derive/cache.js +28 -0
  80. package/dist/resources/extensions/gsd/state/derive/db-open.js +39 -0
  81. package/dist/resources/extensions/gsd/state/derive/from-db.js +452 -0
  82. package/dist/resources/extensions/gsd/state/derive/index.js +75 -0
  83. package/dist/resources/extensions/gsd/state/derive/interrupted-work.js +21 -0
  84. package/dist/resources/extensions/gsd/state-reconciliation/drift/stale-render.js +45 -2
  85. package/dist/resources/extensions/gsd/state-reconciliation/index.js +48 -23
  86. package/dist/resources/extensions/gsd/state-reconciliation/registry.js +32 -28
  87. package/dist/resources/extensions/gsd/state.js +12 -611
  88. package/dist/resources/extensions/gsd/tools/complete-milestone.js +4 -2
  89. package/dist/resources/extensions/gsd/tools/complete-slice.js +2 -2
  90. package/dist/resources/extensions/gsd/tools/complete-task.js +43 -14
  91. package/dist/resources/extensions/gsd/tools/exec-tool.js +7 -2
  92. package/dist/resources/extensions/gsd/tools/plan-slice.js +59 -44
  93. package/dist/resources/extensions/gsd/tools/plan-task.js +28 -5
  94. package/dist/resources/extensions/gsd/undo.js +43 -6
  95. package/dist/resources/extensions/gsd/unit-context-composer.js +23 -7
  96. package/dist/resources/extensions/gsd/unit-registry.js +36 -6
  97. package/dist/resources/extensions/gsd/unmerged-milestone-guard.js +33 -3
  98. package/dist/resources/extensions/gsd/validation-block-guard.js +9 -4
  99. package/dist/resources/extensions/gsd/verification-gate.js +7 -0
  100. package/dist/resources/extensions/gsd/workflow-manifest.js +126 -13
  101. package/dist/resources/extensions/gsd/workflow-projections.js +19 -14
  102. package/dist/resources/extensions/gsd/workspace-git-preflight.js +30 -1
  103. package/dist/resources/extensions/gsd/worktree-manager.js +44 -2
  104. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  105. package/dist/web/standalone/.next/BUILD_ID +1 -1
  106. package/dist/web/standalone/.next/app-path-routes-manifest.json +10 -10
  107. package/dist/web/standalone/.next/build-manifest.json +3 -3
  108. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  109. package/dist/web/standalone/.next/react-loadable-manifest.json +9 -9
  110. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  111. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  112. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  113. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  114. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  115. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  116. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  117. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  118. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  119. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  120. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  121. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  122. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  123. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  124. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  125. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  126. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  127. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  128. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  129. package/dist/web/standalone/.next/server/app/index.html +1 -1
  130. package/dist/web/standalone/.next/server/app/index.rsc +2 -2
  131. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  132. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +2 -2
  133. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  134. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  135. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  136. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  137. package/dist/web/standalone/.next/server/app-paths-manifest.json +10 -10
  138. package/dist/web/standalone/.next/server/chunks/8357.js +1 -1
  139. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  140. package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
  141. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  142. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  143. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  144. package/dist/web/standalone/.next/static/chunks/{2659.b7b129ee6a769448.js → 2659.58e950899a9bb82f.js} +1 -1
  145. package/dist/web/standalone/.next/static/chunks/2772.a7c1fcc69a4685ef.js +1 -0
  146. package/dist/web/standalone/.next/static/chunks/{3616.3c60753b8ffcbd2e.js → 3616.61a2af74bb8833c8.js} +1 -1
  147. package/dist/web/standalone/.next/static/chunks/{4283.8e446784528ed9dc.js → 4283.d0d9e0a955e441cb.js} +1 -1
  148. package/dist/web/standalone/.next/static/chunks/{5826.a46ecdd1cfe8dabc.js → 5826.5421d66c72b9f34e.js} +1 -1
  149. package/dist/web/standalone/.next/static/chunks/{8785.481aa5869991b760.js → 8785.e29b3134cab1d153.js} +1 -1
  150. package/dist/web/standalone/.next/static/chunks/8937.640dc9c2aaa1dfad.js +10 -0
  151. package/dist/web/standalone/.next/static/chunks/app/{page-6644fc6ee8ca1247.js → page-72a856634ad14c10.js} +1 -1
  152. package/dist/web/standalone/.next/static/chunks/webpack-9c401904f87ded16.js +1 -0
  153. package/dist/web/standalone/node_modules/node-pty/build/Makefile +1 -1
  154. package/package.json +1 -1
  155. package/packages/cloud-mcp-gateway/package.json +2 -2
  156. package/packages/contracts/dist/workflow.d.ts +1 -0
  157. package/packages/contracts/dist/workflow.d.ts.map +1 -1
  158. package/packages/contracts/dist/workflow.js +2 -0
  159. package/packages/contracts/dist/workflow.js.map +1 -1
  160. package/packages/contracts/package.json +1 -1
  161. package/packages/daemon/package.json +4 -4
  162. package/packages/gsd-agent-core/dist/agent-session.d.ts +1 -0
  163. package/packages/gsd-agent-core/dist/agent-session.d.ts.map +1 -1
  164. package/packages/gsd-agent-core/dist/agent-session.js +3 -0
  165. package/packages/gsd-agent-core/dist/agent-session.js.map +1 -1
  166. package/packages/gsd-agent-core/dist/extension-ui-snapshot.d.ts +41 -0
  167. package/packages/gsd-agent-core/dist/extension-ui-snapshot.d.ts.map +1 -0
  168. package/packages/gsd-agent-core/dist/extension-ui-snapshot.js +62 -0
  169. package/packages/gsd-agent-core/dist/extension-ui-snapshot.js.map +1 -0
  170. package/packages/gsd-agent-core/dist/index.d.ts +2 -0
  171. package/packages/gsd-agent-core/dist/index.d.ts.map +1 -1
  172. package/packages/gsd-agent-core/dist/index.js +2 -0
  173. package/packages/gsd-agent-core/dist/index.js.map +1 -1
  174. package/packages/gsd-agent-core/dist/session/agent-session-events.js +1 -1
  175. package/packages/gsd-agent-core/dist/session/agent-session-events.js.map +1 -1
  176. package/packages/gsd-agent-core/dist/session/agent-session-host.d.ts +1 -0
  177. package/packages/gsd-agent-core/dist/session/agent-session-host.d.ts.map +1 -1
  178. package/packages/gsd-agent-core/dist/session/agent-session-host.js.map +1 -1
  179. package/packages/gsd-agent-core/dist/session/agent-session-prompt.d.ts +5 -0
  180. package/packages/gsd-agent-core/dist/session/agent-session-prompt.d.ts.map +1 -1
  181. package/packages/gsd-agent-core/dist/session/agent-session-prompt.js +60 -3
  182. package/packages/gsd-agent-core/dist/session/agent-session-prompt.js.map +1 -1
  183. package/packages/gsd-agent-core/dist/transcript-store.d.ts +58 -0
  184. package/packages/gsd-agent-core/dist/transcript-store.d.ts.map +1 -0
  185. package/packages/gsd-agent-core/dist/transcript-store.js +132 -0
  186. package/packages/gsd-agent-core/dist/transcript-store.js.map +1 -0
  187. package/packages/gsd-agent-core/package.json +5 -5
  188. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts +2 -0
  189. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  190. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js +25 -11
  191. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js.map +1 -1
  192. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller-latency.d.ts +4 -0
  193. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller-latency.d.ts.map +1 -0
  194. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller-latency.js +7 -0
  195. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller-latency.js.map +1 -0
  196. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts +3 -24
  197. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  198. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js +26 -829
  199. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  200. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-handoff-filter.d.ts +58 -0
  201. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-handoff-filter.d.ts.map +1 -0
  202. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-handoff-filter.js +312 -0
  203. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-handoff-filter.js.map +1 -0
  204. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-pinned-zone.d.ts +31 -0
  205. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-pinned-zone.d.ts.map +1 -0
  206. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-pinned-zone.js +130 -0
  207. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-pinned-zone.js.map +1 -0
  208. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-segment-walker.d.ts +15 -0
  209. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-segment-walker.d.ts.map +1 -0
  210. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-segment-walker.js +258 -0
  211. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-segment-walker.js.map +1 -0
  212. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-tool-rollup.d.ts +13 -0
  213. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-tool-rollup.d.ts.map +1 -0
  214. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-tool-rollup.js +118 -0
  215. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-tool-rollup.js.map +1 -0
  216. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-state.d.ts +9 -0
  217. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-state.d.ts.map +1 -1
  218. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-state.js +1 -1
  219. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-state.js.map +1 -1
  220. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-ui-state.d.ts +54 -0
  221. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-ui-state.d.ts.map +1 -0
  222. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-ui-state.js +20 -0
  223. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-ui-state.js.map +1 -0
  224. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.d.ts +4 -1
  225. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  226. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.js +9 -0
  227. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.js.map +1 -1
  228. package/packages/gsd-agent-modes/dist/modes/interactive/streaming-render-state.d.ts +56 -0
  229. package/packages/gsd-agent-modes/dist/modes/interactive/streaming-render-state.d.ts.map +1 -0
  230. package/packages/gsd-agent-modes/dist/modes/interactive/streaming-render-state.js +44 -0
  231. package/packages/gsd-agent-modes/dist/modes/interactive/streaming-render-state.js.map +1 -0
  232. package/packages/gsd-agent-modes/dist/modes/interactive/tui-transcript-tracker.d.ts +5 -0
  233. package/packages/gsd-agent-modes/dist/modes/interactive/tui-transcript-tracker.d.ts.map +1 -0
  234. package/packages/gsd-agent-modes/dist/modes/interactive/tui-transcript-tracker.js +77 -0
  235. package/packages/gsd-agent-modes/dist/modes/interactive/tui-transcript-tracker.js.map +1 -0
  236. package/packages/gsd-agent-modes/dist/modes/rpc/rpc-client.d.ts +2 -0
  237. package/packages/gsd-agent-modes/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  238. package/packages/gsd-agent-modes/dist/modes/rpc/rpc-client.js +23 -5
  239. package/packages/gsd-agent-modes/dist/modes/rpc/rpc-client.js.map +1 -1
  240. package/packages/gsd-agent-modes/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  241. package/packages/gsd-agent-modes/dist/modes/rpc/rpc-mode.js +18 -0
  242. package/packages/gsd-agent-modes/dist/modes/rpc/rpc-mode.js.map +1 -1
  243. package/packages/gsd-agent-modes/package.json +7 -7
  244. package/packages/mcp-server/README.md +1 -1
  245. package/packages/mcp-server/dist/server.d.ts +1 -1
  246. package/packages/mcp-server/dist/server.d.ts.map +1 -1
  247. package/packages/mcp-server/dist/server.js +3 -3
  248. package/packages/mcp-server/dist/server.js.map +1 -1
  249. package/packages/mcp-server/dist/workflow-tools.d.ts +13 -1
  250. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  251. package/packages/mcp-server/dist/workflow-tools.js +35 -21
  252. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  253. package/packages/mcp-server/package.json +4 -4
  254. package/packages/native/package.json +1 -1
  255. package/packages/pi-agent-core/dist/agent-loop.js +3 -2
  256. package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
  257. package/packages/pi-agent-core/package.json +1 -1
  258. package/packages/pi-ai/dist/models.generated.d.ts +46 -0
  259. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  260. package/packages/pi-ai/dist/models.generated.js +38 -0
  261. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  262. package/packages/pi-ai/package.json +1 -1
  263. package/packages/pi-coding-agent/README.md +3 -2
  264. package/packages/pi-coding-agent/dist/core/session-manager-context.d.ts +9 -0
  265. package/packages/pi-coding-agent/dist/core/session-manager-context.d.ts.map +1 -0
  266. package/packages/pi-coding-agent/dist/core/session-manager-context.js +94 -0
  267. package/packages/pi-coding-agent/dist/core/session-manager-context.js.map +1 -0
  268. package/packages/pi-coding-agent/dist/core/session-manager-list.d.ts +8 -0
  269. package/packages/pi-coding-agent/dist/core/session-manager-list.d.ts.map +1 -0
  270. package/packages/pi-coding-agent/dist/core/session-manager-list.js +244 -0
  271. package/packages/pi-coding-agent/dist/core/session-manager-list.js.map +1 -0
  272. package/packages/pi-coding-agent/dist/core/session-manager-migration.d.ts +12 -0
  273. package/packages/pi-coding-agent/dist/core/session-manager-migration.d.ts.map +1 -0
  274. package/packages/pi-coding-agent/dist/core/session-manager-migration.js +84 -0
  275. package/packages/pi-coding-agent/dist/core/session-manager-migration.js.map +1 -0
  276. package/packages/pi-coding-agent/dist/core/session-manager-types.d.ts +135 -0
  277. package/packages/pi-coding-agent/dist/core/session-manager-types.d.ts.map +1 -0
  278. package/packages/pi-coding-agent/dist/core/session-manager-types.js +2 -0
  279. package/packages/pi-coding-agent/dist/core/session-manager-types.js.map +1 -0
  280. package/packages/pi-coding-agent/dist/core/session-manager.d.ts +6 -154
  281. package/packages/pi-coding-agent/dist/core/session-manager.d.ts.map +1 -1
  282. package/packages/pi-coding-agent/dist/core/session-manager.js +22 -459
  283. package/packages/pi-coding-agent/dist/core/session-manager.js.map +1 -1
  284. package/packages/pi-coding-agent/dist/theme/theme-schema.d.ts +75 -75
  285. package/packages/pi-coding-agent/dist/theme/theme-schema.d.ts.map +1 -1
  286. package/packages/pi-coding-agent/dist/theme/theme-schema.js +1 -1
  287. package/packages/pi-coding-agent/dist/theme/theme-schema.js.map +1 -1
  288. package/packages/pi-coding-agent/dist/theme/theme.d.ts.map +1 -1
  289. package/packages/pi-coding-agent/dist/theme/theme.js +11 -7
  290. package/packages/pi-coding-agent/dist/theme/theme.js.map +1 -1
  291. package/packages/pi-coding-agent/package.json +7 -7
  292. package/packages/pi-tui/package.json +2 -2
  293. package/packages/rpc-client/package.json +2 -2
  294. package/pkg/dist/theme/theme-schema.d.ts +75 -75
  295. package/pkg/dist/theme/theme-schema.d.ts.map +1 -1
  296. package/pkg/dist/theme/theme-schema.js +1 -1
  297. package/pkg/dist/theme/theme-schema.js.map +1 -1
  298. package/pkg/dist/theme/theme.d.ts.map +1 -1
  299. package/pkg/dist/theme/theme.js +11 -7
  300. package/pkg/dist/theme/theme.js.map +1 -1
  301. package/pkg/package.json +1 -1
  302. package/src/resources/extensions/claude-code-cli/models.ts +9 -0
  303. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +20 -2
  304. package/src/resources/extensions/claude-code-cli/tests/index.test.ts +14 -0
  305. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +80 -0
  306. package/src/resources/extensions/google-cli/stream-adapter.ts +106 -19
  307. package/src/resources/extensions/gsd/artifact-verification.ts +464 -0
  308. package/src/resources/extensions/gsd/auto/loop-deps.ts +1 -0
  309. package/src/resources/extensions/gsd/auto/orchestrator.ts +25 -11
  310. package/src/resources/extensions/gsd/auto/session.ts +14 -0
  311. package/src/resources/extensions/gsd/auto/verification-retry-policy.ts +3 -1
  312. package/src/resources/extensions/gsd/auto-artifact-paths.ts +47 -1
  313. package/src/resources/extensions/gsd/auto-budget.ts +12 -1
  314. package/src/resources/extensions/gsd/auto-dispatch.ts +35 -24
  315. package/src/resources/extensions/gsd/auto-post-unit.ts +1 -1
  316. package/src/resources/extensions/gsd/auto-prompts.ts +41 -15
  317. package/src/resources/extensions/gsd/auto-recovery.ts +13 -511
  318. package/src/resources/extensions/gsd/auto-runtime-state.ts +4 -5
  319. package/src/resources/extensions/gsd/auto-timeout-recovery.ts +3 -2
  320. package/src/resources/extensions/gsd/auto-verification.ts +5 -1
  321. package/src/resources/extensions/gsd/auto.ts +23 -8
  322. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +125 -12
  323. package/src/resources/extensions/gsd/bootstrap/core-session-tools.ts +44 -0
  324. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +11 -5
  325. package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +2 -0
  326. package/src/resources/extensions/gsd/bootstrap/register-extension.ts +2 -0
  327. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +52 -21
  328. package/src/resources/extensions/gsd/bootstrap/system-context.ts +52 -18
  329. package/src/resources/extensions/gsd/bootstrap/tool-call-loop-guard.ts +104 -10
  330. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +1 -1
  331. package/src/resources/extensions/gsd/closeout-consistency-gate.ts +2 -1
  332. package/src/resources/extensions/gsd/commands/handlers/auto.ts +1 -1
  333. package/src/resources/extensions/gsd/commands-context.ts +18 -1
  334. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +15 -10
  335. package/src/resources/extensions/gsd/commands-usage.ts +1 -1
  336. package/src/resources/extensions/gsd/commands-worktree.ts +12 -10
  337. package/src/resources/extensions/gsd/dashboard-overlay.ts +32 -3
  338. package/src/resources/extensions/gsd/db/engine.ts +8 -1
  339. package/src/resources/extensions/gsd/db/queries.ts +79 -0
  340. package/src/resources/extensions/gsd/db-memory-fts-schema.ts +45 -0
  341. package/src/resources/extensions/gsd/db-workspace.ts +61 -3
  342. package/src/resources/extensions/gsd/delegation-policy.ts +1 -1
  343. package/src/resources/extensions/gsd/docs/preferences-reference.md +2 -2
  344. package/src/resources/extensions/gsd/doctor-engine-checks.ts +233 -5
  345. package/src/resources/extensions/gsd/doctor-providers.ts +103 -9
  346. package/src/resources/extensions/gsd/doctor-runtime-checks.ts +3 -0
  347. package/src/resources/extensions/gsd/doctor-types.ts +3 -0
  348. package/src/resources/extensions/gsd/exec-sandbox.ts +56 -11
  349. package/src/resources/extensions/gsd/flat-phase-migration.ts +44 -0
  350. package/src/resources/extensions/gsd/forensics.ts +2 -33
  351. package/src/resources/extensions/gsd/git-service.ts +69 -5
  352. package/src/resources/extensions/gsd/gitignore.ts +4 -1
  353. package/src/resources/extensions/gsd/guided-flow-queue.ts +89 -4
  354. package/src/resources/extensions/gsd/health-widget.ts +69 -32
  355. package/src/resources/extensions/gsd/json-persistence.ts +20 -0
  356. package/src/resources/extensions/gsd/knowledge-backfill.ts +16 -10
  357. package/src/resources/extensions/gsd/layout-policy.ts +2 -1
  358. package/src/resources/extensions/gsd/markdown-renderer.ts +8 -11
  359. package/src/resources/extensions/gsd/memory-backfill.ts +26 -20
  360. package/src/resources/extensions/gsd/memory-consolidation-scanner.ts +51 -19
  361. package/src/resources/extensions/gsd/metrics.ts +4 -4
  362. package/src/resources/extensions/gsd/migrate-external.ts +8 -2
  363. package/src/resources/extensions/gsd/migration-auto-check.ts +23 -0
  364. package/src/resources/extensions/gsd/milestone-ids.ts +31 -2
  365. package/src/resources/extensions/gsd/milestone-implementation-evidence.ts +35 -21
  366. package/src/resources/extensions/gsd/orphan-stash-audit.ts +108 -20
  367. package/src/resources/extensions/gsd/prompts/code-review.md +6 -4
  368. package/src/resources/extensions/gsd/prompts/plan-milestone.md +2 -2
  369. package/src/resources/extensions/gsd/prompts/plan-slice.md +3 -3
  370. package/src/resources/extensions/gsd/quick.ts +43 -2
  371. package/src/resources/extensions/gsd/root-write-leak-guard.ts +13 -1
  372. package/src/resources/extensions/gsd/safety/destructive-guard.ts +9 -1
  373. package/src/resources/extensions/gsd/safety/evidence-collector.ts +16 -1
  374. package/src/resources/extensions/gsd/safety/file-change-validator.ts +8 -2
  375. package/src/resources/extensions/gsd/session-forensics.ts +11 -1
  376. package/src/resources/extensions/gsd/skills/gsd-headless/references/commands.md +1 -1
  377. package/src/resources/extensions/gsd/state/derive/cache.ts +46 -0
  378. package/src/resources/extensions/gsd/state/derive/db-open.ts +45 -0
  379. package/src/resources/extensions/gsd/state/derive/from-db.ts +561 -0
  380. package/src/resources/extensions/gsd/state/derive/index.ts +104 -0
  381. package/src/resources/extensions/gsd/state/derive/interrupted-work.ts +31 -0
  382. package/src/resources/extensions/gsd/state-reconciliation/drift/stale-render.ts +81 -7
  383. package/src/resources/extensions/gsd/state-reconciliation/index.ts +50 -24
  384. package/src/resources/extensions/gsd/state-reconciliation/registry.ts +43 -28
  385. package/src/resources/extensions/gsd/state.ts +32 -732
  386. package/src/resources/extensions/gsd/tests/artifact-retry-cap.test.ts +4 -3
  387. package/src/resources/extensions/gsd/tests/auto-artifact-paths.test.ts +98 -1
  388. package/src/resources/extensions/gsd/tests/auto-budget-alerts.test.ts +14 -5
  389. package/src/resources/extensions/gsd/tests/auto-pause-double-entry-guard.test.ts +36 -0
  390. package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +182 -4
  391. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +146 -1
  392. package/src/resources/extensions/gsd/tests/auto-retry-mcp-churn-fixes.test.ts +30 -0
  393. package/src/resources/extensions/gsd/tests/commands-context.test.ts +26 -0
  394. package/src/resources/extensions/gsd/tests/commands-gsd-core.test.ts +1 -0
  395. package/src/resources/extensions/gsd/tests/commands-worktree-clean.test.ts +80 -0
  396. package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +28 -0
  397. package/src/resources/extensions/gsd/tests/complete-slice-reopen-handoff.test.ts +69 -0
  398. package/src/resources/extensions/gsd/tests/complete-slice.test.ts +11 -0
  399. package/src/resources/extensions/gsd/tests/complete-task-rollback-evidence.test.ts +48 -8
  400. package/src/resources/extensions/gsd/tests/complete-task.test.ts +75 -0
  401. package/src/resources/extensions/gsd/tests/dashboard-overlay.test.ts +55 -2
  402. package/src/resources/extensions/gsd/tests/db-memory-fts-schema.test.ts +58 -3
  403. package/src/resources/extensions/gsd/tests/decisions-projection-from-memories.test.ts +51 -0
  404. package/src/resources/extensions/gsd/tests/destructive-guard.test.ts +44 -0
  405. package/src/resources/extensions/gsd/tests/dispatch-missing-task-plans.test.ts +143 -0
  406. package/src/resources/extensions/gsd/tests/dispatch-rule-coverage.test.ts +26 -1
  407. package/src/resources/extensions/gsd/tests/doctor-forensics-db-open-regression.test.ts +70 -2
  408. package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +107 -0
  409. package/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts +239 -2
  410. package/src/resources/extensions/gsd/tests/exec-graceful-kill.test.ts +38 -0
  411. package/src/resources/extensions/gsd/tests/exec-sandbox.test.ts +16 -0
  412. package/src/resources/extensions/gsd/tests/exec-tool.test.ts +45 -1
  413. package/src/resources/extensions/gsd/tests/file-change-validator.test.ts +40 -0
  414. package/src/resources/extensions/gsd/tests/flat-phase-migration.test.ts +37 -2
  415. package/src/resources/extensions/gsd/tests/forensics-error-filter.test.ts +88 -0
  416. package/src/resources/extensions/gsd/tests/gitignore-bg-shell-runtime.test.ts +4 -0
  417. package/src/resources/extensions/gsd/tests/guided-discuss-milestone-prompt-rendering.test.ts +42 -0
  418. package/src/resources/extensions/gsd/tests/health-widget.test.ts +268 -3
  419. package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +130 -3
  420. package/src/resources/extensions/gsd/tests/integration/queue-active-milestone-context-budget.test.ts +93 -0
  421. package/src/resources/extensions/gsd/tests/integration/quick-branch-lifecycle.test.ts +56 -9
  422. package/src/resources/extensions/gsd/tests/json-persistence-atomic.test.ts +47 -0
  423. package/src/resources/extensions/gsd/tests/knowledge-backfill-projection.test.ts +35 -0
  424. package/src/resources/extensions/gsd/tests/knowledge-cold-start.test.ts +14 -0
  425. package/src/resources/extensions/gsd/tests/memory-consolidation-scanner.test.ts +78 -0
  426. package/src/resources/extensions/gsd/tests/memory-store.test.ts +48 -0
  427. package/src/resources/extensions/gsd/tests/merge-closeout-consistency-gate.test.ts +31 -1
  428. package/src/resources/extensions/gsd/tests/metrics-prune-cache-invalidation.test.ts +6 -0
  429. package/src/resources/extensions/gsd/tests/metrics.test.ts +1 -0
  430. package/src/resources/extensions/gsd/tests/migrate-external-worktree.test.ts +57 -0
  431. package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +70 -0
  432. package/src/resources/extensions/gsd/tests/orchestrator-logs.test.ts +43 -1
  433. package/src/resources/extensions/gsd/tests/orphan-stash-audit.test.ts +91 -7
  434. package/src/resources/extensions/gsd/tests/parallel-research-dispatch.test.ts +26 -0
  435. package/src/resources/extensions/gsd/tests/parsers-legacy-importers.test.ts +4 -1
  436. package/src/resources/extensions/gsd/tests/pipeline-variant-dispatch.test.ts +1 -1
  437. package/src/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +10 -0
  438. package/src/resources/extensions/gsd/tests/plan-slice.test.ts +103 -4
  439. package/src/resources/extensions/gsd/tests/plan-task.test.ts +44 -1
  440. package/src/resources/extensions/gsd/tests/post-mutation-hook.test.ts +4 -2
  441. package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +54 -1
  442. package/src/resources/extensions/gsd/tests/progressive-planning.test.ts +50 -14
  443. package/src/resources/extensions/gsd/tests/prompt-budget-enforcement.test.ts +58 -0
  444. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +8 -6
  445. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +195 -1
  446. package/src/resources/extensions/gsd/tests/read-uat-gate-verdict.test.ts +185 -0
  447. package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +87 -0
  448. package/src/resources/extensions/gsd/tests/register-hooks-loop-guard-auto.test.ts +103 -0
  449. package/src/resources/extensions/gsd/tests/root-write-leak-guard.test.ts +20 -1
  450. package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +15 -3
  451. package/src/resources/extensions/gsd/tests/stalled-tool-recovery.test.ts +56 -0
  452. package/src/resources/extensions/gsd/tests/state-reconciliation-drift.test.ts +191 -0
  453. package/src/resources/extensions/gsd/tests/tool-call-loop-guard.test.ts +208 -0
  454. package/src/resources/extensions/gsd/tests/tool-param-optionality.test.ts +32 -3
  455. package/src/resources/extensions/gsd/tests/undo.test.ts +74 -0
  456. package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +193 -14
  457. package/src/resources/extensions/gsd/tests/unmerged-milestone-guard.test.ts +25 -0
  458. package/src/resources/extensions/gsd/tests/uok-gitops-turn-action.test.ts +38 -0
  459. package/src/resources/extensions/gsd/tests/validation-block-guard.test.ts +79 -0
  460. package/src/resources/extensions/gsd/tests/verification-retry-policy.test.ts +69 -4
  461. package/src/resources/extensions/gsd/tests/verify-artifact-tightened.test.ts +66 -0
  462. package/src/resources/extensions/gsd/tests/workflow-manifest.test.ts +96 -15
  463. package/src/resources/extensions/gsd/tests/workflow-mcp-preflight.test.ts +1 -0
  464. package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +8 -4
  465. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +6 -5
  466. package/src/resources/extensions/gsd/tests/workspace-git-preflight.test.ts +151 -2
  467. package/src/resources/extensions/gsd/tests/worktree-manager.test.ts +39 -0
  468. package/src/resources/extensions/gsd/tools/complete-milestone.ts +3 -2
  469. package/src/resources/extensions/gsd/tools/complete-slice.ts +2 -2
  470. package/src/resources/extensions/gsd/tools/complete-task.ts +53 -15
  471. package/src/resources/extensions/gsd/tools/exec-tool.ts +7 -3
  472. package/src/resources/extensions/gsd/tools/plan-slice.ts +60 -47
  473. package/src/resources/extensions/gsd/tools/plan-task.ts +30 -5
  474. package/src/resources/extensions/gsd/undo.ts +47 -6
  475. package/src/resources/extensions/gsd/unit-context-composer.ts +33 -7
  476. package/src/resources/extensions/gsd/unit-registry.ts +36 -6
  477. package/src/resources/extensions/gsd/unmerged-milestone-guard.ts +41 -5
  478. package/src/resources/extensions/gsd/validation-block-guard.ts +13 -7
  479. package/src/resources/extensions/gsd/verification-gate.ts +8 -0
  480. package/src/resources/extensions/gsd/workflow-manifest.ts +141 -7
  481. package/src/resources/extensions/gsd/workflow-projections.ts +20 -14
  482. package/src/resources/extensions/gsd/workspace-git-preflight.ts +31 -0
  483. package/src/resources/extensions/gsd/worktree-manager.ts +41 -1
  484. package/dist/web/standalone/.next/static/chunks/2772.bfa657f49f955239.js +0 -1
  485. package/dist/web/standalone/.next/static/chunks/796.e0bdc932325d7e03.js +0 -10
  486. package/dist/web/standalone/.next/static/chunks/webpack-f46ea08200a0227e.js +0 -1
  487. /package/dist/web/standalone/.next/static/{BTKtGFF1Y-hvVJEGhBRo9 → 2A_WqqCS-EoYZxN9_v3d2}/_buildManifest.js +0 -0
  488. /package/dist/web/standalone/.next/static/{BTKtGFF1Y-hvVJEGhBRo9 → 2A_WqqCS-EoYZxN9_v3d2}/_ssgManifest.js +0 -0
@@ -7,9 +7,11 @@ import { test } from 'node:test';
7
7
  import assert from 'node:assert/strict';
8
8
  import {
9
9
  checkToolCallLoop,
10
+ recordToolCallLoopMutation,
10
11
  resetToolCallLoopGuard,
11
12
  disableToolCallLoopGuard,
12
13
  getToolCallLoopCount,
14
+ getToolCallCountForTool,
13
15
  } from '../bootstrap/tool-call-loop-guard.ts';
14
16
 
15
17
 
@@ -177,3 +179,209 @@ console.log('\n── Loop guard: nested key order is normalized ──');
177
179
  }
178
180
 
179
181
  // ═══════════════════════════════════════════════════════════════════════════
182
+ // Per-tool-name cap (#783 Brief C) — catches improvisation loops with varied args
183
+ // ═══════════════════════════════════════════════════════════════════════════
184
+
185
+ console.log('\n── Loop guard: per-tool cap blocks varied-args improvisation (#783) ──');
186
+
187
+ {
188
+ resetToolCallLoopGuard();
189
+
190
+ // A one-shot workflow tool called with DIFFERENT args each time (the reported
191
+ // improvisation pattern). The identical-signature streak alone would reset
192
+ // every call; the per-tool cap must catch it.
193
+ for (let i = 1; i <= 6; i++) {
194
+ const result = checkToolCallLoop('gsd_complete_milestone', { milestone: `M${i}` });
195
+ assert.ok(result.block === false, `one-shot call ${i} (varied args) should be allowed`);
196
+ assert.deepStrictEqual(getToolCallCountForTool('gsd_complete_milestone'), i, `per-tool count should be ${i}`);
197
+ }
198
+ // 7th call (cap 6 + 1) must be blocked by the per-tool guard.
199
+ const blocked = checkToolCallLoop('gsd_complete_milestone', { milestone: 'M7' });
200
+ assert.ok(blocked.block === true, '7th one-shot call (varied args) should be blocked by per-tool cap');
201
+ assert.ok(blocked.reason!.includes('repeated tool'), 'reason should identify the per-tool guard');
202
+ assert.ok(blocked.reason!.includes('gsd_complete_milestone'), 'reason should name the tool');
203
+ assert.ok(blocked.reason!.includes('7'), 'reason should mention the count');
204
+ }
205
+
206
+ // ═══════════════════════════════════════════════════════════════════════════
207
+ // Repeatable tools get the higher cap
208
+ // ═══════════════════════════════════════════════════════════════════════════
209
+
210
+ console.log('\n── Loop guard: repeatable tools get the higher cap (#783) ──');
211
+
212
+ {
213
+ resetToolCallLoopGuard();
214
+
215
+ // bash is repeatable: varied commands are legitimate up to the higher cap.
216
+ for (let i = 1; i <= 15; i++) {
217
+ const result = checkToolCallLoop('bash', { command: `echo ${i}` });
218
+ assert.ok(result.block === false, `bash call ${i} (varied args) should be allowed`);
219
+ }
220
+ // 16th call (cap 15 + 1) is blocked by the per-tool guard — this is the
221
+ // improvisation-through-bash case from the forensics (~51 calls).
222
+ const blocked = checkToolCallLoop('bash', { command: 'echo 16' });
223
+ assert.ok(blocked.block === true, '16th bash call (varied args) should be blocked by per-tool cap');
224
+ assert.ok(blocked.reason!.includes('cap 15'), 'reason should mention the repeatable cap');
225
+ }
226
+
227
+ // ═══════════════════════════════════════════════════════════════════════════
228
+ // Mutation progress decays per-tool counts (#1092)
229
+ // ═══════════════════════════════════════════════════════════════════════════
230
+
231
+ console.log('\n── Loop guard: mutation progress decays per-tool counts (#1092) ──');
232
+
233
+ {
234
+ resetToolCallLoopGuard();
235
+
236
+ // bash is called more than the repeatable cap, but each count is separated
237
+ // by a successful file mutation. Guard 2 should treat that as progress and
238
+ // avoid blocking the progressing unit.
239
+ for (let i = 1; i <= 20; i++) {
240
+ const result = checkToolCallLoop('bash', { command: `echo ${i}` });
241
+ assert.ok(result.block === false, `bash call ${i} after mutation progress should be allowed`);
242
+ assert.deepStrictEqual(getToolCallCountForTool('bash'), 1, `bash count should decay before call ${i}`);
243
+
244
+ const mutationTool = i % 2 === 0 ? 'write' : 'edit';
245
+ const mutation = checkToolCallLoop(mutationTool, {
246
+ path: `file-${i}.ts`,
247
+ content: `content ${i}`,
248
+ });
249
+ assert.ok(mutation.block === false, `${mutationTool} mutation ${i} should be allowed`);
250
+ recordToolCallLoopMutation(mutationTool);
251
+ }
252
+ }
253
+
254
+ // ═══════════════════════════════════════════════════════════════════════════
255
+ // Distinct read-only navigation calls are context gathering, not repeated-tool loops.
256
+ // ═══════════════════════════════════════════════════════════════════════════
257
+
258
+ console.log('\n── Loop guard: distinct read-only navigation calls are not capped by per-tool count ──');
259
+
260
+ {
261
+ const exemptTools = ['find', 'glob', 'grep', 'ls', 'read', 'search_and_read'];
262
+
263
+ for (const toolName of exemptTools) {
264
+ resetToolCallLoopGuard();
265
+
266
+ for (let i = 1; i <= 20; i++) {
267
+ const result = checkToolCallLoop(toolName, { path: `file-${i}.ts` });
268
+ assert.ok(result.block === false, `distinct ${toolName} call ${i} should be allowed`);
269
+ }
270
+
271
+ resetToolCallLoopGuard();
272
+ for (let i = 1; i <= 4; i++) {
273
+ const result = checkToolCallLoop(toolName, { path: 'same-file.ts' });
274
+ assert.ok(result.block === false, `identical ${toolName} call ${i} should be allowed`);
275
+ }
276
+
277
+ const blocked = checkToolCallLoop(toolName, { path: 'same-file.ts' });
278
+ assert.ok(blocked.block === true, `5th identical ${toolName} call should still be blocked`);
279
+ assert.ok(blocked.reason?.includes('identical args'), `${toolName} loops should be caught by Guard 1`);
280
+ }
281
+ }
282
+
283
+ // ═══════════════════════════════════════════════════════════════════════════
284
+ // Per-tool counts are independent per tool and reset together
285
+ // ═══════════════════════════════════════════════════════════════════════════
286
+
287
+ console.log('\n── Loop guard: per-tool counts are independent and reset together (#783) ──');
288
+
289
+ {
290
+ resetToolCallLoopGuard();
291
+
292
+ // Two different tools tracked separately.
293
+ for (let i = 0; i < 3; i++) checkToolCallLoop('read', { path: `f${i}` });
294
+ for (let i = 0; i < 3; i++) checkToolCallLoop('write', { path: `g${i}` });
295
+ assert.deepStrictEqual(getToolCallCountForTool('read'), 3, 'read tracked separately');
296
+ assert.deepStrictEqual(getToolCallCountForTool('write'), 3, 'write tracked separately');
297
+ assert.deepStrictEqual(getToolCallCountForTool('edit'), 0, 'uncalled tool reports 0');
298
+
299
+ resetToolCallLoopGuard();
300
+ assert.deepStrictEqual(getToolCallCountForTool('read'), 0, 'per-tool counts cleared on reset');
301
+ assert.deepStrictEqual(getToolCallCountForTool('write'), 0, 'per-tool counts cleared on reset');
302
+ }
303
+
304
+ // ═══════════════════════════════════════════════════════════════════════════
305
+ // Non-exempt repeatable tools from core-session-tools.ts get the higher cap.
306
+ // Read-only navigation tools are exempted earlier; mutating/execution repeatable
307
+ // tools (e.g. gsd_exec) must still trip the per-tool cap.
308
+ // ═══════════════════════════════════════════════════════════════════════════
309
+
310
+ console.log('\n── Loop guard: non-exempt repeatable tools get high cap ──');
311
+
312
+ {
313
+ for (const toolName of ['bg_shell', 'gsd_exec', 'write']) {
314
+ resetToolCallLoopGuard();
315
+
316
+ // Should allow up to 15 varied calls without blocking.
317
+ for (let i = 1; i <= 15; i++) {
318
+ const result = checkToolCallLoop(toolName, { arg: `v${i}` });
319
+ assert.ok(result.block === false, `${toolName} call ${i} (varied args) should be allowed`);
320
+ }
321
+ // 16th call with varied args must be blocked by the per-tool repeatable cap.
322
+ const blocked = checkToolCallLoop(toolName, { arg: 'v16' });
323
+ assert.ok(blocked.block === true, `${toolName}: 16th call must be blocked by repeatable cap`);
324
+ assert.ok(blocked.reason?.includes('cap 15'), `${toolName}: reason must mention cap 15`);
325
+ }
326
+ }
327
+
328
+ // ═══════════════════════════════════════════════════════════════════════════
329
+ // Non-repeatable tools excluded from INHERENTLY_REPEATABLE_TOOL_SET use the
330
+ // default cap (6), not the repeatable cap (15).
331
+ // ═══════════════════════════════════════════════════════════════════════════
332
+
333
+ console.log('\n── Loop guard: ToolSearch (excluded from repeatable set) hits default cap ──');
334
+
335
+ {
336
+ // ToolSearch is in MINIMAL_AUTO_BASE but explicitly excluded from repeatable.
337
+ // It should hit the default cap of 6, not 15.
338
+ resetToolCallLoopGuard();
339
+ for (let i = 1; i <= 6; i++) {
340
+ const result = checkToolCallLoop('ToolSearch', { query: `q${i}` });
341
+ assert.ok(result.block === false, `ToolSearch call ${i} should be allowed`);
342
+ }
343
+ const blocked = checkToolCallLoop('ToolSearch', { query: 'q7' });
344
+ assert.ok(blocked.block === true, 'ToolSearch: 7th varied call must be blocked by default cap');
345
+ assert.ok(!blocked.reason?.includes('cap 15'), 'ToolSearch must NOT use the repeatable cap');
346
+ }
347
+
348
+ // ═══════════════════════════════════════════════════════════════════════════
349
+ // Block reasons instruct the model to stop tooling this turn
350
+ // ═══════════════════════════════════════════════════════════════════════════
351
+
352
+ console.log('\n── Loop guard: block reasons tell model to respond in text ──');
353
+
354
+ {
355
+ resetToolCallLoopGuard();
356
+
357
+ for (let i = 0; i < 4; i++) {
358
+ checkToolCallLoop('web_search', { query: 'same query' });
359
+ }
360
+ const identicalBlocked = checkToolCallLoop('web_search', { query: 'same query' });
361
+ assert.ok(identicalBlocked.block === true, 'identical-args guard should block');
362
+ assert.ok(
363
+ identicalBlocked.reason!.includes('respond to the user in text'),
364
+ 'identical-args reason should tell the model to stop tooling',
365
+ );
366
+ assert.ok(
367
+ !identicalBlocked.reason!.includes('Try a different approach'),
368
+ 'identical-args reason should not suggest retrying another tool',
369
+ );
370
+
371
+ resetToolCallLoopGuard();
372
+ for (let i = 0; i < 6; i++) {
373
+ checkToolCallLoop('gsd_complete_milestone', { milestone: `M${i}` });
374
+ }
375
+ const perToolBlocked = checkToolCallLoop('gsd_complete_milestone', { milestone: 'M7' });
376
+ assert.ok(perToolBlocked.block === true, 'per-tool guard should block');
377
+ assert.ok(
378
+ perToolBlocked.reason!.includes('respond to the user in text'),
379
+ 'per-tool reason should tell the model to stop tooling',
380
+ );
381
+ assert.ok(
382
+ perToolBlocked.reason!.includes('Do not retry this tool'),
383
+ 'per-tool reason should forbid retrying the blocked tool',
384
+ );
385
+ }
386
+
387
+ // ═══════════════════════════════════════════════════════════════════════════
@@ -13,6 +13,7 @@
13
13
 
14
14
  import { test } from "node:test";
15
15
  import assert from "node:assert/strict";
16
+ import { SUMMARY_SAVE_CONTENT_MAX_LENGTH } from "@opengsd/contracts";
16
17
  import { registerDbTools } from "../bootstrap/db-tools.ts";
17
18
  import AjvModule from "ajv";
18
19
 
@@ -86,6 +87,31 @@ test("gsd_summary_save — validates UAT assessment params", () => {
86
87
  assert.strictEqual(valid, true, `UAT assessment params should validate but got errors: ${JSON.stringify(validate.errors)}`);
87
88
  });
88
89
 
90
+ test("gsd_summary_save — content has a provider-safe maxLength", () => {
91
+ const tool = getTool("gsd_summary_save");
92
+ assert.ok(tool, "gsd_summary_save must be registered");
93
+
94
+ const contentSchema = tool.parameters.properties.content;
95
+ assert.strictEqual(contentSchema.maxLength, SUMMARY_SAVE_CONTENT_MAX_LENGTH);
96
+
97
+ const validAtLimit = validateSchema(tool, {
98
+ milestone_id: "M001",
99
+ artifact_type: "CONTEXT-DRAFT",
100
+ content: "x".repeat(SUMMARY_SAVE_CONTENT_MAX_LENGTH),
101
+ });
102
+ assert.deepEqual(validAtLimit, []);
103
+
104
+ const overLimit = validateSchema(tool, {
105
+ milestone_id: "M001",
106
+ artifact_type: "CONTEXT-DRAFT",
107
+ content: "x".repeat(SUMMARY_SAVE_CONTENT_MAX_LENGTH + 1),
108
+ });
109
+ assert.ok(
110
+ overLimit.some((error) => error.includes(`must NOT have more than ${SUMMARY_SAVE_CONTENT_MAX_LENGTH} characters`)),
111
+ `expected maxLength validation error, got: ${overLimit.join("; ")}`,
112
+ );
113
+ });
114
+
89
115
  // ─── gsd_slice_complete: enrichment arrays must be optional ──────────────────
90
116
 
91
117
  test("gsd_slice_complete — enrichment arrays are optional", () => {
@@ -359,14 +385,17 @@ test("gsd_plan_slice — enrichment fields are optional", () => {
359
385
 
360
386
  const required = new Set(getRequiredProps(tool));
361
387
 
362
- // Core fields
363
- const coreRequired = ["milestoneId", "sliceId", "goal", "tasks"];
388
+ // Core fields. `tasks` is intentionally NOT here: incremental planning
389
+ // (#1027) lets gsd_plan_slice persist slice metadata only, then add tasks
390
+ // one at a time via gsd_plan_task, so tasks must be optional.
391
+ const coreRequired = ["milestoneId", "sliceId", "goal"];
364
392
  for (const field of coreRequired) {
365
393
  assert.ok(required.has(field), `core field "${field}" must be required`);
366
394
  }
367
395
 
368
- // Enrichment fields
396
+ // Enrichment fields (plus tasks, optional for incremental planning).
369
397
  const enrichmentFields = [
398
+ "tasks",
370
399
  "successCriteria",
371
400
  "proofLevel",
372
401
  "integrationClosure",
@@ -10,6 +10,7 @@ import {
10
10
  handleUndo,
11
11
  handleUndoTask,
12
12
  handleResetSlice,
13
+ parseActivityLogFilename,
13
14
  uncheckTaskInPlan,
14
15
  } from "../undo.ts";
15
16
  import {
@@ -67,6 +68,37 @@ test("handleUndo without --force only warns and leaves completed units intact",
67
68
  }
68
69
  });
69
70
 
71
+ test("handleUndo execute-task with --force reopens the task row and re-renders plan", async () => {
72
+ const base = makeTempDir("gsd-undo-execute-task");
73
+ try {
74
+ setupTaskFixture(base);
75
+ mkdirSync(join(base, ".gsd", "activity"), { recursive: true });
76
+ writeFileSync(
77
+ join(base, ".gsd", "activity", "001-execute-task-M001-S01-T01.jsonl"),
78
+ "",
79
+ "utf-8",
80
+ );
81
+
82
+ const { notifications, ctx } = makeCtx();
83
+ await handleUndo("--force", ctx, {} as any, base);
84
+
85
+ const task = getTask("M001", "S01", "T01");
86
+ assert.equal(task?.status, "pending");
87
+
88
+ const planContent = readFileSync(
89
+ join(base, ".gsd", "phases", "01-test", "01-01-PLAN.md"),
90
+ "utf-8",
91
+ );
92
+ assert.match(planContent, /\[ \] \*\*T01\*\*:/);
93
+
94
+ assert.equal(notifications[0]?.level, "success");
95
+ assert.match(notifications[0]?.message ?? "", /Unchecked task in PLAN/);
96
+ } finally {
97
+ closeDatabase();
98
+ rmSync(base, { recursive: true, force: true });
99
+ }
100
+ });
101
+
70
102
  test("uncheckTaskInPlan flips a checked task back to unchecked", () => {
71
103
  const base = makeTempDir("gsd-undo-plan");
72
104
  try {
@@ -154,6 +186,48 @@ test("extractCommitShas ignores malformed commit tokens", () => {
154
186
  assert.deepEqual(extractCommitShas(content), ["1234567"]);
155
187
  });
156
188
 
189
+ test("parseActivityLogFilename splits hyphenated unit types from their IDs", () => {
190
+ // Task-level: unit type and ID both contain hyphens.
191
+ assert.deepEqual(parseActivityLogFilename("001-execute-task-M001-S01-T01.jsonl"), {
192
+ unitType: "execute-task",
193
+ unitId: "M001-S01-T01",
194
+ });
195
+ // Variant must win over its shorter prefix ("execute-task").
196
+ assert.deepEqual(parseActivityLogFilename("002-execute-task-simple-M001-S02-T03.jsonl"), {
197
+ unitType: "execute-task-simple",
198
+ unitId: "M001-S02-T03",
199
+ });
200
+ // Milestone- and slice-shaped IDs.
201
+ assert.deepEqual(parseActivityLogFilename("003-research-milestone-M001.jsonl"), {
202
+ unitType: "research-milestone",
203
+ unitId: "M001",
204
+ });
205
+ assert.deepEqual(parseActivityLogFilename("004-plan-slice-M001-S01.jsonl"), {
206
+ unitType: "plan-slice",
207
+ unitId: "M001-S01",
208
+ });
209
+ });
210
+
211
+ test("parseActivityLogFilename accepts non-milestone-shaped unit IDs", () => {
212
+ // Regression (#1057): project-level units use IDs that do not start with
213
+ // "M<digit>". A milestone-shaped regex made /gsd undo bail out with
214
+ // "could not parse latest activity log" when one of these was most recent.
215
+ assert.deepEqual(parseActivityLogFilename("005-discuss-project-PROJECT.jsonl"), {
216
+ unitType: "discuss-project",
217
+ unitId: "PROJECT",
218
+ });
219
+ assert.deepEqual(parseActivityLogFilename("006-workflow-preferences-WORKFLOW-PREFS.jsonl"), {
220
+ unitType: "workflow-preferences",
221
+ unitId: "WORKFLOW-PREFS",
222
+ });
223
+ });
224
+
225
+ test("parseActivityLogFilename returns null for unrecognized names", () => {
226
+ assert.equal(parseActivityLogFilename("007-not-a-real-unit-M001.jsonl"), null);
227
+ assert.equal(parseActivityLogFilename("no-sequence-prefix.jsonl"), null);
228
+ assert.equal(parseActivityLogFilename("001-execute-task-M001-S01-T01.txt"), null);
229
+ });
230
+
157
231
  // ─── handleUndoTask tests ────────────────────────────────────────────────────
158
232
 
159
233
  function makeCtx(): { notifications: Array<{ message: string; level: string }>; ctx: any } {
@@ -8,6 +8,7 @@ import { join } from "node:path";
8
8
  import { tmpdir } from "node:os";
9
9
 
10
10
  import {
11
+ CONTEXT_MODE_GUIDANCE_BY_UNIT,
11
12
  composeContractedUnitContext,
12
13
  composeContextModeInstructions,
13
14
  composeInlinedContext,
@@ -141,9 +142,13 @@ test("Context Mode composer: nested output is compact single sentence", () => {
141
142
  assert.ok(!out.startsWith("## Context Mode"));
142
143
  assert.match(out, /^Context Mode \(verification lane\): /);
143
144
  assert.strictEqual(out.split(/\n/).length, 1);
144
- assert.match(out, /`gsd_exec`/);
145
- assert.match(out, /`gsd_exec_search`/);
146
- assert.match(out, /`gsd_resume`/);
145
+ // Nested guidance is embedded into tester subagent prompts — it must instruct the tester
146
+ // to run verification and call gsd_save_gate_result, NOT to dispatch further subagents.
147
+ assert.doesNotMatch(out, /`subagent`/, "tester prompts must not be told to dispatch subagents");
148
+ assert.match(out, /`gsd_save_gate_result`/);
149
+ assert.doesNotMatch(out, /`gsd_exec`/);
150
+ assert.doesNotMatch(out, /`gsd_exec_search`/);
151
+ assert.doesNotMatch(out, /`gsd_resume`/);
147
152
  assert.ok(out.length < 240, `nested guidance should stay compact, got ${out.length} chars`);
148
153
  });
149
154
 
@@ -158,6 +163,53 @@ const laneLabelByMode: Record<string, string> = {
158
163
  triage: "triage",
159
164
  };
160
165
 
166
+ const contextModeGuidanceOverrideExpectedTools: Record<string, readonly string[]> = {
167
+ "discuss-milestone": [
168
+ "ask_user_questions",
169
+ "gsd_summary_save",
170
+ "gsd_decision_save",
171
+ "gsd_requirement_save",
172
+ "gsd_requirement_update",
173
+ "gsd_plan_milestone",
174
+ "gsd_milestone_generate_id",
175
+ ],
176
+ "discuss-project": [
177
+ "ask_user_questions",
178
+ "gsd_summary_save",
179
+ "gsd_decision_save",
180
+ "gsd_requirement_save",
181
+ ],
182
+ "discuss-requirements": [
183
+ "ask_user_questions",
184
+ "gsd_requirement_save",
185
+ "gsd_summary_save",
186
+ ],
187
+ "discuss-slice": [
188
+ "ask_user_questions",
189
+ "gsd_summary_save",
190
+ "gsd_decision_save",
191
+ ],
192
+ "replan-slice": [
193
+ "gsd_replan_slice",
194
+ "gsd_decision_save",
195
+ ],
196
+ "reassess-roadmap": [
197
+ "gsd_milestone_status",
198
+ "gsd_reassess_roadmap",
199
+ ],
200
+ "run-uat": [
201
+ "gsd_uat_exec",
202
+ "gsd_resume",
203
+ ],
204
+ // research-project uses scout subagents that write .gsd/research/ files directly;
205
+ // the parent dispatches Task calls and verifies file outputs — no GSD save tools.
206
+ "research-project": [],
207
+ "gate-evaluate": [
208
+ "subagent",
209
+ "gsd_save_gate_result",
210
+ ],
211
+ };
212
+
161
213
  test("Context Mode composer: every known eligible unit renders its configured lane and required tools", () => {
162
214
  for (const unitType of KNOWN_UNIT_TYPES) {
163
215
  const manifest = UNIT_MANIFESTS[unitType];
@@ -170,18 +222,65 @@ test("Context Mode composer: every known eligible unit renders its configured la
170
222
  assert.ok(out.startsWith("## Context Mode"), `${unitType} should render standalone Context Mode heading`);
171
223
  assert.match(out, new RegExp(`Lane: \\*\\*${laneLabelByMode[manifest.contextMode]} lane\\*\\*\\.`, "i"));
172
224
  const forbidden = getUnitToolSurfaceContract(unitType)?.forbiddenGsdTools ?? {};
173
- if ("gsd_exec" in forbidden) {
174
- // Units that forbid gsd_exec (run-uat) have it stripped from their
175
- // Claude Code dispatch surface; guidance steering to it produces
176
- // "No such tool available" loops in the dispatched agent.
177
- assert.doesNotMatch(out, /`gsd_exec`/, `${unitType} forbids gsd_exec; guidance must not steer to it`);
225
+ const overrideExpectedTools = contextModeGuidanceOverrideExpectedTools[unitType];
226
+ if ("gsd_exec" in forbidden || overrideExpectedTools) {
227
+ // Unit overrides are the contract-specific exception to lane defaults.
228
+ // Steering to the lane default here can produce unavailable-tool loops.
229
+ assert.doesNotMatch(out, /`gsd_exec`/, `${unitType} guidance must not steer to gsd_exec`);
178
230
  assert.doesNotMatch(out, /`gsd_exec_search`/, `${unitType} guidance must not steer to gsd_exec_search`);
179
- assert.match(out, /`gsd_uat_exec`/, `${unitType} guidance should steer to gsd_uat_exec instead`);
231
+ for (const toolName of overrideExpectedTools ?? []) {
232
+ assert.match(out, new RegExp(`\`${toolName}\``), `${unitType} guidance should mention ${toolName}`);
233
+ }
180
234
  } else {
181
235
  assert.match(out, /`gsd_exec`/, `${unitType} should mention gsd_exec`);
182
236
  assert.match(out, /`gsd_exec_search`/, `${unitType} should mention gsd_exec_search`);
183
237
  }
184
- assert.match(out, /`gsd_resume`/, `${unitType} should mention gsd_resume`);
238
+ if (!overrideExpectedTools || overrideExpectedTools.includes("gsd_resume")) {
239
+ assert.match(out, /`gsd_resume`/, `${unitType} should mention gsd_resume`);
240
+ } else {
241
+ assert.doesNotMatch(out, /`gsd_resume`/, `${unitType} guidance must not steer to gsd_resume`);
242
+ }
243
+ }
244
+ });
245
+
246
+ test("Context Mode composer: discuss interview overrides stay within unit contracts", () => {
247
+ const discussUnits = [
248
+ "discuss-milestone",
249
+ "discuss-project",
250
+ "discuss-requirements",
251
+ "discuss-slice",
252
+ ];
253
+
254
+ for (const unitType of discussUnits) {
255
+ const guidance = CONTEXT_MODE_GUIDANCE_BY_UNIT[unitType];
256
+ assert.ok(guidance, `${unitType} should have a Context Mode override`);
257
+ assert.doesNotMatch(guidance, /`gsd_exec`/, `${unitType} guidance must not mention gsd_exec`);
258
+ assert.doesNotMatch(guidance, /`gsd_exec_search`/, `${unitType} guidance must not mention gsd_exec_search`);
259
+ assert.doesNotMatch(guidance, /`gsd_resume`/, `${unitType} guidance must not mention gsd_resume`);
260
+
261
+ const expectedTools = contextModeGuidanceOverrideExpectedTools[unitType] ?? [];
262
+ assert.ok(expectedTools.length > 0, `${unitType} should declare expected override tools`);
263
+ const contract = getUnitToolSurfaceContract(unitType);
264
+ assert.ok(contract, `${unitType} should have a tool contract`);
265
+ const contractTools = new Set([
266
+ ...contract.allowedGsdTools,
267
+ ...contract.requiredWorkflowTools,
268
+ ]);
269
+
270
+ for (const toolName of expectedTools) {
271
+ assert.match(guidance, new RegExp(`\`${toolName}\``), `${unitType} guidance should mention ${toolName}`);
272
+ assert.ok(contractTools.has(toolName as UnitGsdToolName), `${unitType} contract should allow ${toolName}`);
273
+ const scope = shouldBlockAutoUnitToolCall(unitType, toolName);
274
+ assert.equal(scope.block, false, `${unitType} should not hard-block ${toolName}: ${scope.reason ?? ""}`);
275
+ }
276
+
277
+ const out = composeContextModeInstructions(unitType, { enabled: true, renderMode: "standalone" });
278
+ if (out) {
279
+ assert.match(out, /interview lane/i);
280
+ assert.doesNotMatch(out, /`gsd_exec`/);
281
+ assert.doesNotMatch(out, /`gsd_exec_search`/);
282
+ assert.doesNotMatch(out, /`gsd_resume`/);
283
+ }
185
284
  }
186
285
  });
187
286
 
@@ -195,8 +294,67 @@ test("Context Mode composer: run-uat guidance steers to gsd_uat_exec in both ren
195
294
  assert.doesNotMatch(standalone, /`gsd_exec`/);
196
295
  });
197
296
 
198
- test("Context Mode composer: slice planning and research guidance tools pass unit contracts", () => {
199
- const affectedUnits = ["research-slice", "plan-slice", "refine-slice"];
297
+ test("Context Mode composer: research-project guidance steers to scout orchestration", () => {
298
+ for (const renderMode of ["nested", "standalone"] as const) {
299
+ const out = composeContextModeInstructions("research-project", { enabled: true, renderMode });
300
+ assert.match(out, /research lane/i);
301
+ assert.match(out, /scout subagents/i);
302
+ assert.match(out, /\.gsd\/research\//);
303
+ assert.match(out, /STACK\.md/);
304
+ assert.match(out, /PITFALLS\.md/);
305
+ assert.doesNotMatch(out, /`gsd_summary_save`/);
306
+ assert.doesNotMatch(out, /`gsd_decision_save`/);
307
+ assert.doesNotMatch(out, /`gsd_exec`/);
308
+ assert.doesNotMatch(out, /`gsd_exec_search`/);
309
+ assert.doesNotMatch(out, /`gsd_resume`/);
310
+ }
311
+
312
+ const contract = getUnitToolSurfaceContract("research-project");
313
+ assert.deepEqual(contract?.allowedGsdTools, []);
314
+ assert.deepEqual(contract?.requiredWorkflowTools, []);
315
+ for (const toolName of ["gsd_summary_save", "gsd_decision_save"]) {
316
+ const scope = shouldBlockAutoUnitToolCall("research-project", toolName);
317
+ assert.equal(scope.block, true, `research-project should not allow ${toolName}`);
318
+ }
319
+ });
320
+
321
+ test("Context Mode composer: narrow planning guidance steers only to contracted tools", () => {
322
+ const cases = [
323
+ {
324
+ unitType: "replan-slice",
325
+ expectedTools: ["gsd_replan_slice", "gsd_decision_save"],
326
+ },
327
+ {
328
+ unitType: "reassess-roadmap",
329
+ expectedTools: ["gsd_milestone_status", "gsd_reassess_roadmap"],
330
+ },
331
+ ];
332
+ const disallowedTools = ["gsd_exec", "gsd_exec_search", "gsd_resume"];
333
+
334
+ for (const { unitType, expectedTools } of cases) {
335
+ for (const renderMode of ["nested", "standalone"] as const) {
336
+ const out = composeContextModeInstructions(unitType, { enabled: true, renderMode });
337
+ assert.match(out, /planning lane/i, `${unitType} should still render planning lane guidance`);
338
+ for (const toolName of expectedTools) {
339
+ assert.ok(out.includes(`\`${toolName}\``), `${unitType} guidance should mention ${toolName}`);
340
+ }
341
+ for (const toolName of disallowedTools) {
342
+ assert.ok(!out.includes(`\`${toolName}\``), `${unitType} guidance must not mention ${toolName}`);
343
+ }
344
+ }
345
+ }
346
+ });
347
+
348
+ test("Context Mode composer: lane guidance tools pass unit contracts", () => {
349
+ const affectedUnits = [
350
+ "research-milestone",
351
+ "research-slice",
352
+ "plan-slice",
353
+ "refine-slice",
354
+ "complete-slice",
355
+ "validate-milestone",
356
+ "complete-milestone",
357
+ ];
200
358
  const contextModeTools: UnitGsdToolName[] = ["gsd_exec", "gsd_exec_search", "gsd_resume"];
201
359
  const readOnlyOrientationTools: UnitGsdToolName[] = ["gsd_milestone_status", ...contextModeTools];
202
360
 
@@ -207,7 +365,10 @@ test("Context Mode composer: slice planning and research guidance tools pass uni
207
365
  for (const toolName of contextModeTools) {
208
366
  assert.ok(out.includes(`\`${toolName}\``), `${unitType} guidance should mention ${toolName}`);
209
367
  }
210
- for (const toolName of readOnlyOrientationTools) {
368
+ const expectedContractTools = unitType === "research-milestone"
369
+ ? contextModeTools
370
+ : readOnlyOrientationTools;
371
+ for (const toolName of expectedContractTools) {
211
372
  assert.ok(allowed.has(toolName), `${unitType} contract should allow ${toolName}`);
212
373
  const scope = shouldBlockAutoUnitToolCall(unitType, toolName);
213
374
  assert.equal(scope.block, false, `${unitType} should not hard-block ${toolName}: ${scope.reason ?? ""}`);
@@ -357,13 +518,31 @@ test("#4782 phase 2: buildReassessRoadmapPrompt emits composer-shaped context wi
357
518
  assert.ok(!prompt.includes("Slice Context (from discussion)"));
358
519
  });
359
520
 
360
- test("execute-task prompt surfaces contract-declared on-demand slice research", async (t) => {
521
+ test("execute-task prompt omits on-demand slice research when the artifact is absent", async (t) => {
522
+ const base = makeFixtureBase();
523
+ t.after(() => cleanup(base));
524
+ invalidateAllCaches();
525
+
526
+ seed(base, "M001");
527
+ writeArtifacts(base);
528
+
529
+ const prompt = await buildExecuteTaskPrompt("M001", "S01", "First", "T01", "Task", base);
530
+
531
+ assert.doesNotMatch(prompt, /## On-demand Context/);
532
+ assert.doesNotMatch(prompt, /\.gsd\/milestones\/M001\/slices\/S01\/S01-RESEARCH\.md/);
533
+ });
534
+
535
+ test("execute-task prompt surfaces on-demand slice research when the artifact exists", async (t) => {
361
536
  const base = makeFixtureBase();
362
537
  t.after(() => cleanup(base));
363
538
  invalidateAllCaches();
364
539
 
365
540
  seed(base, "M001");
366
541
  writeArtifacts(base);
542
+ writeFileSync(
543
+ join(base, ".gsd", "milestones", "M001", "slices", "S01", "S01-RESEARCH.md"),
544
+ "# S01 Research\n",
545
+ );
367
546
 
368
547
  const prompt = await buildExecuteTaskPrompt("M001", "S01", "First", "T01", "Task", base);
369
548