gsd-pi 2.77.0-dev.eaa4973bc → 2.78.0-dev.aeeb2ca00

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 (545) hide show
  1. package/README.md +53 -17
  2. package/dist/claude-cli-check.js +46 -10
  3. package/dist/headless.js +49 -4
  4. package/dist/resource-loader.d.ts +40 -0
  5. package/dist/resource-loader.js +32 -13
  6. package/dist/resources/extensions/browser-tools/capture.js +9 -0
  7. package/dist/resources/extensions/browser-tools/tests/browser-tools-integration.test.mjs +8 -59
  8. package/dist/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +36 -24
  9. package/dist/resources/extensions/browser-tools/tests/capture-sharp-optional.test.cjs +69 -71
  10. package/dist/resources/extensions/browser-tools/tools/forms.js +5 -1
  11. package/dist/resources/extensions/browser-tools/tools/intent.js +5 -1
  12. package/dist/resources/extensions/claude-code-cli/readiness.js +72 -16
  13. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +481 -17
  14. package/dist/resources/extensions/github-sync/templates.js +103 -0
  15. package/dist/resources/extensions/google-search/index.js +3 -2
  16. package/dist/resources/extensions/gsd/auto/loop.js +124 -2
  17. package/dist/resources/extensions/gsd/auto/phases.js +57 -39
  18. package/dist/resources/extensions/gsd/auto/session.js +6 -2
  19. package/dist/resources/extensions/gsd/auto-dispatch.js +142 -29
  20. package/dist/resources/extensions/gsd/auto-model-selection.js +124 -4
  21. package/dist/resources/extensions/gsd/auto-post-unit.js +150 -64
  22. package/dist/resources/extensions/gsd/auto-prompts.js +372 -104
  23. package/dist/resources/extensions/gsd/auto-recovery.js +197 -48
  24. package/dist/resources/extensions/gsd/auto-start.js +107 -29
  25. package/dist/resources/extensions/gsd/auto-tool-tracking.js +47 -7
  26. package/dist/resources/extensions/gsd/auto-worktree.js +122 -26
  27. package/dist/resources/extensions/gsd/auto.js +76 -21
  28. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +19 -1
  29. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +209 -0
  30. package/dist/resources/extensions/gsd/bootstrap/provider-error-resume.js +3 -6
  31. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +7 -3
  32. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +127 -9
  33. package/dist/resources/extensions/gsd/component-loader.js +447 -0
  34. package/dist/resources/extensions/gsd/component-types.js +69 -0
  35. package/dist/resources/extensions/gsd/context-store.js +23 -7
  36. package/dist/resources/extensions/gsd/detection.js +49 -1
  37. package/dist/resources/extensions/gsd/dispatch-guard.js +2 -17
  38. package/dist/resources/extensions/gsd/docs/preferences-reference.md +1 -1
  39. package/dist/resources/extensions/gsd/forensics.js +106 -0
  40. package/dist/resources/extensions/gsd/gate-registry.js +2 -2
  41. package/dist/resources/extensions/gsd/git-constants.js +28 -1
  42. package/dist/resources/extensions/gsd/git-self-heal.js +27 -0
  43. package/dist/resources/extensions/gsd/git-service.js +126 -2
  44. package/dist/resources/extensions/gsd/gsd-db.js +6 -3
  45. package/dist/resources/extensions/gsd/guided-flow.js +39 -13
  46. package/dist/resources/extensions/gsd/memory-extractor.js +7 -1
  47. package/dist/resources/extensions/gsd/milestone-scope-classifier.js +299 -0
  48. package/dist/resources/extensions/gsd/milestone-summary-classifier.js +37 -0
  49. package/dist/resources/extensions/gsd/model-cost-table.js +3 -0
  50. package/dist/resources/extensions/gsd/model-router.js +6 -0
  51. package/dist/resources/extensions/gsd/native-git-bridge.js +34 -4
  52. package/dist/resources/extensions/gsd/preferences-validation.js +23 -0
  53. package/dist/resources/extensions/gsd/prompt-cache-optimizer.js +4 -0
  54. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +6 -2
  55. package/dist/resources/extensions/gsd/prompts/discuss-headless.md +23 -4
  56. package/dist/resources/extensions/gsd/prompts/doctor-heal.md +5 -4
  57. package/dist/resources/extensions/gsd/prompts/plan-slice.md +15 -2
  58. package/dist/resources/extensions/gsd/safety/git-checkpoint.js +11 -0
  59. package/dist/resources/extensions/gsd/service-tier.js +5 -2
  60. package/dist/resources/extensions/gsd/session-lock.js +19 -10
  61. package/dist/resources/extensions/gsd/skill-manifest.js +168 -0
  62. package/dist/resources/extensions/gsd/slice-cadence.js +238 -0
  63. package/dist/resources/extensions/gsd/slice-parallel-orchestrator.js +278 -8
  64. package/dist/resources/extensions/gsd/state-transition-matrix.js +118 -0
  65. package/dist/resources/extensions/gsd/state.js +69 -58
  66. package/dist/resources/extensions/gsd/sync-lock.js +98 -42
  67. package/dist/resources/extensions/gsd/tools/validate-milestone.js +7 -2
  68. package/dist/resources/extensions/gsd/unit-context-composer.js +147 -0
  69. package/dist/resources/extensions/gsd/unit-context-manifest.js +370 -0
  70. package/dist/resources/extensions/gsd/uok/dispatch-envelope.js +33 -0
  71. package/dist/resources/extensions/gsd/uok/execution-graph.js +10 -0
  72. package/dist/resources/extensions/gsd/uok/gate-runner.js +53 -5
  73. package/dist/resources/extensions/gsd/uok/gitops.js +2 -1
  74. package/dist/resources/extensions/gsd/uok/loop-adapter.js +37 -10
  75. package/dist/resources/extensions/gsd/uok/parity-report.js +58 -0
  76. package/dist/resources/extensions/gsd/uok/plan-v2.js +10 -4
  77. package/dist/resources/extensions/gsd/uok/writer.js +82 -0
  78. package/dist/resources/extensions/gsd/workflow-mcp.js +6 -0
  79. package/dist/resources/extensions/gsd/worktree-manager.js +85 -8
  80. package/dist/resources/extensions/gsd/worktree-resolver.js +86 -7
  81. package/dist/resources/extensions/gsd/worktree-telemetry.js +198 -0
  82. package/dist/resources/extensions/mcp-client/index.js +3 -1
  83. package/dist/resources/extensions/ollama/index.js +5 -1
  84. package/dist/resources/extensions/remote-questions/manager.js +11 -5
  85. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  86. package/dist/web/standalone/.next/BUILD_ID +1 -1
  87. package/dist/web/standalone/.next/app-path-routes-manifest.json +11 -11
  88. package/dist/web/standalone/.next/build-manifest.json +2 -2
  89. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  90. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  91. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  92. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  93. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  94. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  95. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  96. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  97. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  98. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  99. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  100. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  101. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  102. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  103. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  104. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  105. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  106. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  107. package/dist/web/standalone/.next/server/app/index.html +1 -1
  108. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  109. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  110. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  111. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  112. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  113. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  114. package/dist/web/standalone/.next/server/app-paths-manifest.json +11 -11
  115. package/dist/web/standalone/.next/server/chunks/1926.js +1 -1
  116. package/dist/web/standalone/.next/server/chunks/6897.js +1 -1
  117. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  118. package/dist/web/standalone/.next/server/middleware-manifest.json +5 -5
  119. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  120. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  121. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  122. package/package.json +2 -3
  123. package/packages/daemon/package.json +2 -2
  124. package/packages/daemon/src/logger.ts +4 -3
  125. package/packages/mcp-server/dist/server.d.ts +24 -0
  126. package/packages/mcp-server/dist/server.d.ts.map +1 -1
  127. package/packages/mcp-server/dist/server.js +88 -87
  128. package/packages/mcp-server/dist/server.js.map +1 -1
  129. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  130. package/packages/mcp-server/dist/workflow-tools.js +15 -6
  131. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  132. package/packages/mcp-server/package.json +2 -2
  133. package/packages/mcp-server/src/mcp-server.test.ts +25 -3
  134. package/packages/mcp-server/src/readers/graph.test.ts +87 -15
  135. package/packages/mcp-server/src/secure-env-collect.test.ts +232 -237
  136. package/packages/mcp-server/src/server.ts +131 -105
  137. package/packages/mcp-server/src/workflow-tools.test.ts +85 -0
  138. package/packages/mcp-server/src/workflow-tools.ts +19 -6
  139. package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
  140. package/packages/native/package.json +2 -2
  141. package/packages/native/src/__tests__/_test-coverage-guard.test.mjs +98 -0
  142. package/packages/native/src/__tests__/module-compat.test.mjs +59 -27
  143. package/packages/native/src/__tests__/ps.test.mjs +14 -8
  144. package/packages/native/src/__tests__/stream-process.test.mjs +23 -2
  145. package/packages/native/src/__tests__/truncate.test.mjs +17 -2
  146. package/packages/pi-agent-core/package.json +1 -1
  147. package/packages/pi-agent-core/src/agent-loop.test.ts +5 -15
  148. package/packages/pi-agent-core/src/agent.test.ts +96 -102
  149. package/packages/pi-agent-core/tsconfig.tsbuildinfo +1 -1
  150. package/packages/pi-ai/dist/models/capability-patches.d.ts.map +1 -1
  151. package/packages/pi-ai/dist/models/capability-patches.js +9 -2
  152. package/packages/pi-ai/dist/models/capability-patches.js.map +1 -1
  153. package/packages/pi-ai/dist/models/generated/index.d.ts +34 -0
  154. package/packages/pi-ai/dist/models/generated/index.d.ts.map +1 -1
  155. package/packages/pi-ai/dist/models/generated/openai-codex.d.ts +17 -0
  156. package/packages/pi-ai/dist/models/generated/openai-codex.d.ts.map +1 -1
  157. package/packages/pi-ai/dist/models/generated/openai-codex.js +17 -0
  158. package/packages/pi-ai/dist/models/generated/openai-codex.js.map +1 -1
  159. package/packages/pi-ai/dist/models/generated/openai.d.ts +17 -0
  160. package/packages/pi-ai/dist/models/generated/openai.d.ts.map +1 -1
  161. package/packages/pi-ai/dist/models/generated/openai.js +17 -0
  162. package/packages/pi-ai/dist/models/generated/openai.js.map +1 -1
  163. package/packages/pi-ai/dist/models.generated.test.js +43 -70
  164. package/packages/pi-ai/dist/models.generated.test.js.map +1 -1
  165. package/packages/pi-ai/dist/models.test.js +36 -11
  166. package/packages/pi-ai/dist/models.test.js.map +1 -1
  167. package/packages/pi-ai/package.json +1 -1
  168. package/packages/pi-ai/scripts/generate-models.ts +44 -0
  169. package/packages/pi-ai/src/models/capability-patches.ts +10 -2
  170. package/packages/pi-ai/src/models/generated/openai-codex.ts +17 -0
  171. package/packages/pi-ai/src/models/generated/openai.ts +17 -0
  172. package/packages/pi-ai/src/models.generated.test.ts +46 -73
  173. package/packages/pi-ai/src/models.test.ts +48 -11
  174. package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
  175. package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js +96 -32
  176. package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js.map +1 -1
  177. package/packages/pi-coding-agent/dist/core/agent-session-model-switch.test.js +75 -12
  178. package/packages/pi-coding-agent/dist/core/agent-session-model-switch.test.js.map +1 -1
  179. package/packages/pi-coding-agent/dist/core/agent-session-tool-refresh.test.js +99 -31
  180. package/packages/pi-coding-agent/dist/core/agent-session-tool-refresh.test.js.map +1 -1
  181. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts +5 -0
  182. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  183. package/packages/pi-coding-agent/dist/core/extensions/loader.js +61 -0
  184. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  185. package/packages/pi-coding-agent/dist/core/lsp/lsp-integration.test.js +30 -4
  186. package/packages/pi-coding-agent/dist/core/lsp/lsp-integration.test.js.map +1 -1
  187. package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js +17 -0
  188. package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js.map +1 -1
  189. package/packages/pi-coding-agent/dist/core/resource-loader-cache-reset.test.js +76 -18
  190. package/packages/pi-coding-agent/dist/core/resource-loader-cache-reset.test.js.map +1 -1
  191. package/packages/pi-coding-agent/dist/core/retry-handler.d.ts.map +1 -1
  192. package/packages/pi-coding-agent/dist/core/retry-handler.js +2 -6
  193. package/packages/pi-coding-agent/dist/core/retry-handler.js.map +1 -1
  194. package/packages/pi-coding-agent/dist/core/retry-handler.test.js +5 -1
  195. package/packages/pi-coding-agent/dist/core/retry-handler.test.js.map +1 -1
  196. package/packages/pi-coding-agent/dist/core/retryable-error-regex.d.ts +18 -0
  197. package/packages/pi-coding-agent/dist/core/retryable-error-regex.d.ts.map +1 -0
  198. package/packages/pi-coding-agent/dist/core/retryable-error-regex.js +18 -0
  199. package/packages/pi-coding-agent/dist/core/retryable-error-regex.js.map +1 -0
  200. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts +20 -0
  201. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
  202. package/packages/pi-coding-agent/dist/core/system-prompt.js +16 -2
  203. package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
  204. package/packages/pi-coding-agent/dist/index.d.ts +1 -0
  205. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  206. package/packages/pi-coding-agent/dist/index.js +1 -0
  207. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  208. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js +36 -5
  209. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js.map +1 -1
  210. package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.test.js +20 -13
  211. package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.test.js.map +1 -1
  212. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  213. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +30 -12
  214. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  215. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts.map +1 -1
  216. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js +18 -3
  217. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js.map +1 -1
  218. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js +125 -0
  219. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js.map +1 -1
  220. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts +2 -0
  221. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts.map +1 -1
  222. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.js.map +1 -1
  223. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +4 -0
  224. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  225. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +105 -13
  226. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  227. package/packages/pi-coding-agent/dist/tests/system-prompt-skill-filter.test.d.ts +2 -0
  228. package/packages/pi-coding-agent/dist/tests/system-prompt-skill-filter.test.d.ts.map +1 -0
  229. package/packages/pi-coding-agent/dist/tests/system-prompt-skill-filter.test.js +130 -0
  230. package/packages/pi-coding-agent/dist/tests/system-prompt-skill-filter.test.js.map +1 -0
  231. package/packages/pi-coding-agent/package.json +1 -1
  232. package/packages/pi-coding-agent/src/core/agent-session-abort-order.test.ts +113 -37
  233. package/packages/pi-coding-agent/src/core/agent-session-model-switch.test.ts +89 -17
  234. package/packages/pi-coding-agent/src/core/agent-session-tool-refresh.test.ts +112 -43
  235. package/packages/pi-coding-agent/src/core/extensions/loader.ts +58 -0
  236. package/packages/pi-coding-agent/src/core/lsp/lsp-integration.test.ts +35 -4
  237. package/packages/pi-coding-agent/src/core/model-registry-auth-mode.test.ts +20 -0
  238. package/packages/pi-coding-agent/src/core/resource-loader-cache-reset.test.ts +93 -28
  239. package/packages/pi-coding-agent/src/core/retry-handler.test.ts +5 -1
  240. package/packages/pi-coding-agent/src/core/retry-handler.ts +2 -8
  241. package/packages/pi-coding-agent/src/core/retryable-error-regex.ts +18 -0
  242. package/packages/pi-coding-agent/src/core/system-prompt.ts +35 -1
  243. package/packages/pi-coding-agent/src/index.ts +1 -0
  244. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-execution.test.ts +49 -3
  245. package/packages/pi-coding-agent/src/modes/interactive/components/dynamic-border.test.ts +26 -20
  246. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +48 -9
  247. package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.test.ts +146 -1
  248. package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.ts +20 -3
  249. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode-state.ts +2 -0
  250. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +119 -13
  251. package/packages/pi-coding-agent/src/tests/system-prompt-skill-filter.test.ts +157 -0
  252. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  253. package/packages/pi-tui/dist/__tests__/autocomplete.test.js +18 -8
  254. package/packages/pi-tui/dist/__tests__/autocomplete.test.js.map +1 -1
  255. package/packages/pi-tui/dist/__tests__/overlay-layout.test.js +128 -17
  256. package/packages/pi-tui/dist/__tests__/overlay-layout.test.js.map +1 -1
  257. package/packages/pi-tui/dist/__tests__/stdin-buffer.test.js +37 -11
  258. package/packages/pi-tui/dist/__tests__/stdin-buffer.test.js.map +1 -1
  259. package/packages/pi-tui/dist/__tests__/tui.test.js +18 -30
  260. package/packages/pi-tui/dist/__tests__/tui.test.js.map +1 -1
  261. package/packages/pi-tui/dist/components/__tests__/input.test.js +10 -3
  262. package/packages/pi-tui/dist/components/__tests__/input.test.js.map +1 -1
  263. package/packages/pi-tui/dist/components/__tests__/loader.test.js +53 -9
  264. package/packages/pi-tui/dist/components/__tests__/loader.test.js.map +1 -1
  265. package/packages/pi-tui/dist/components/__tests__/markdown-maxlines.test.js +6 -2
  266. package/packages/pi-tui/dist/components/__tests__/markdown-maxlines.test.js.map +1 -1
  267. package/packages/pi-tui/dist/components/editor.d.ts +14 -0
  268. package/packages/pi-tui/dist/components/editor.d.ts.map +1 -1
  269. package/packages/pi-tui/dist/components/editor.js +19 -0
  270. package/packages/pi-tui/dist/components/editor.js.map +1 -1
  271. package/packages/pi-tui/dist/components/image.test.js +6 -5
  272. package/packages/pi-tui/dist/components/image.test.js.map +1 -1
  273. package/packages/pi-tui/dist/editor-component.d.ts +2 -0
  274. package/packages/pi-tui/dist/editor-component.d.ts.map +1 -1
  275. package/packages/pi-tui/dist/editor-component.js.map +1 -1
  276. package/packages/pi-tui/package.json +1 -1
  277. package/packages/pi-tui/src/__tests__/autocomplete.test.ts +24 -8
  278. package/packages/pi-tui/src/__tests__/overlay-layout.test.ts +140 -17
  279. package/packages/pi-tui/src/__tests__/stdin-buffer.test.ts +42 -11
  280. package/packages/pi-tui/src/__tests__/tui.test.ts +18 -37
  281. package/packages/pi-tui/src/components/__tests__/input.test.ts +19 -3
  282. package/packages/pi-tui/src/components/__tests__/loader.test.ts +112 -35
  283. package/packages/pi-tui/src/components/__tests__/markdown-maxlines.test.ts +9 -2
  284. package/packages/pi-tui/src/components/editor.ts +22 -0
  285. package/packages/pi-tui/src/components/image.test.ts +10 -5
  286. package/packages/pi-tui/src/editor-component.ts +3 -0
  287. package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
  288. package/packages/rpc-client/dist/rpc-client.test.js +101 -51
  289. package/packages/rpc-client/dist/rpc-client.test.js.map +1 -1
  290. package/packages/rpc-client/package.json +1 -1
  291. package/packages/rpc-client/src/rpc-client.test.ts +109 -52
  292. package/packages/rpc-client/tsconfig.tsbuildinfo +1 -1
  293. package/pkg/package.json +1 -1
  294. package/scripts/install.js +15 -1
  295. package/src/resources/extensions/browser-tools/capture.ts +12 -0
  296. package/src/resources/extensions/browser-tools/tests/browser-tools-integration.test.mjs +8 -59
  297. package/src/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +36 -24
  298. package/src/resources/extensions/browser-tools/tests/capture-sharp-optional.test.cjs +69 -71
  299. package/src/resources/extensions/browser-tools/tools/forms.ts +5 -1
  300. package/src/resources/extensions/browser-tools/tools/intent.ts +5 -1
  301. package/src/resources/extensions/claude-code-cli/readiness.ts +75 -16
  302. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +518 -19
  303. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +919 -75
  304. package/src/resources/extensions/github-sync/templates.ts +151 -0
  305. package/src/resources/extensions/github-sync/tests/cli.test.ts +76 -7
  306. package/src/resources/extensions/github-sync/tests/templates.test.ts +92 -1
  307. package/src/resources/extensions/google-search/index.ts +3 -2
  308. package/src/resources/extensions/gsd/auto/loop.ts +142 -2
  309. package/src/resources/extensions/gsd/auto/phases.ts +62 -38
  310. package/src/resources/extensions/gsd/auto/session.ts +7 -2
  311. package/src/resources/extensions/gsd/auto-dispatch.ts +156 -29
  312. package/src/resources/extensions/gsd/auto-model-selection.ts +131 -4
  313. package/src/resources/extensions/gsd/auto-post-unit.ts +163 -73
  314. package/src/resources/extensions/gsd/auto-prompts.ts +385 -93
  315. package/src/resources/extensions/gsd/auto-recovery.ts +230 -51
  316. package/src/resources/extensions/gsd/auto-start.ts +127 -9
  317. package/src/resources/extensions/gsd/auto-tool-tracking.ts +51 -7
  318. package/src/resources/extensions/gsd/auto-worktree.ts +130 -26
  319. package/src/resources/extensions/gsd/auto.ts +90 -23
  320. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +20 -1
  321. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +221 -0
  322. package/src/resources/extensions/gsd/bootstrap/provider-error-resume.ts +3 -7
  323. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +7 -3
  324. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +158 -9
  325. package/src/resources/extensions/gsd/component-loader.ts +598 -0
  326. package/src/resources/extensions/gsd/component-types.ts +362 -0
  327. package/src/resources/extensions/gsd/context-store.ts +25 -8
  328. package/src/resources/extensions/gsd/detection.ts +58 -1
  329. package/src/resources/extensions/gsd/dispatch-guard.ts +2 -20
  330. package/src/resources/extensions/gsd/docs/preferences-reference.md +1 -1
  331. package/src/resources/extensions/gsd/forensics.ts +118 -1
  332. package/src/resources/extensions/gsd/gate-registry.ts +2 -2
  333. package/src/resources/extensions/gsd/git-constants.ts +30 -1
  334. package/src/resources/extensions/gsd/git-self-heal.ts +31 -0
  335. package/src/resources/extensions/gsd/git-service.ts +149 -2
  336. package/src/resources/extensions/gsd/gsd-db.ts +6 -3
  337. package/src/resources/extensions/gsd/guided-flow.ts +57 -14
  338. package/src/resources/extensions/gsd/journal.ts +11 -1
  339. package/src/resources/extensions/gsd/memory-extractor.ts +11 -3
  340. package/src/resources/extensions/gsd/milestone-scope-classifier.ts +366 -0
  341. package/src/resources/extensions/gsd/milestone-summary-classifier.ts +42 -0
  342. package/src/resources/extensions/gsd/model-cost-table.ts +3 -0
  343. package/src/resources/extensions/gsd/model-router.ts +6 -0
  344. package/src/resources/extensions/gsd/native-git-bridge.ts +34 -4
  345. package/src/resources/extensions/gsd/preferences-validation.ts +21 -0
  346. package/src/resources/extensions/gsd/prompt-cache-optimizer.ts +4 -0
  347. package/src/resources/extensions/gsd/prompts/complete-milestone.md +6 -2
  348. package/src/resources/extensions/gsd/prompts/discuss-headless.md +23 -4
  349. package/src/resources/extensions/gsd/prompts/doctor-heal.md +5 -4
  350. package/src/resources/extensions/gsd/prompts/plan-slice.md +15 -2
  351. package/src/resources/extensions/gsd/safety/git-checkpoint.ts +15 -0
  352. package/src/resources/extensions/gsd/service-tier.ts +5 -2
  353. package/src/resources/extensions/gsd/session-lock.ts +20 -10
  354. package/src/resources/extensions/gsd/skill-manifest.ts +175 -0
  355. package/src/resources/extensions/gsd/slice-cadence.ts +299 -0
  356. package/src/resources/extensions/gsd/slice-parallel-orchestrator.ts +309 -8
  357. package/src/resources/extensions/gsd/state-transition-matrix.ts +152 -0
  358. package/src/resources/extensions/gsd/state.ts +76 -66
  359. package/src/resources/extensions/gsd/sync-lock.ts +97 -39
  360. package/src/resources/extensions/gsd/tests/artifact-retry-cap.test.ts +270 -0
  361. package/src/resources/extensions/gsd/tests/artifacts-table-preserved-on-cache-invalidate.test.ts +2 -1
  362. package/src/resources/extensions/gsd/tests/auto-deterministic-error-classification-4973.test.ts +341 -0
  363. package/src/resources/extensions/gsd/tests/auto-discuss-milestone-deadlock-4973.test.ts +264 -0
  364. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +133 -292
  365. package/src/resources/extensions/gsd/tests/auto-model-selection-tool-poisoning.test.ts +742 -0
  366. package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +78 -0
  367. package/src/resources/extensions/gsd/tests/auto-phases-lifecycle.test.ts +61 -0
  368. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +93 -0
  369. package/src/resources/extensions/gsd/tests/auto-remediate-slice-status.test.ts +4 -1
  370. package/src/resources/extensions/gsd/tests/auto-retry-mcp-churn-fixes.test.ts +8 -194
  371. package/src/resources/extensions/gsd/tests/auto-start-clean-runtime-db-gated.test.ts +3 -2
  372. package/src/resources/extensions/gsd/tests/auto-start-cold-db-bootstrap.test.ts +2 -2
  373. package/src/resources/extensions/gsd/tests/auto-start-needs-discussion.test.ts +15 -58
  374. package/src/resources/extensions/gsd/tests/auto-start-worktree-db-path.test.ts +2 -2
  375. package/src/resources/extensions/gsd/tests/auto-thinking-restore.test.ts +3 -2
  376. package/src/resources/extensions/gsd/tests/auto-warning-noise-regression.test.ts +3 -2
  377. package/src/resources/extensions/gsd/tests/bootstrap-derive-state-db-open.test.ts +2 -1
  378. package/src/resources/extensions/gsd/tests/cache-staleness-regression.test.ts +17 -21
  379. package/src/resources/extensions/gsd/tests/canonical-milestone-root.test.ts +108 -0
  380. package/src/resources/extensions/gsd/tests/complete-milestone-excerpt.test.ts +263 -0
  381. package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +25 -0
  382. package/src/resources/extensions/gsd/tests/complete-slice-composer.test.ts +192 -0
  383. package/src/resources/extensions/gsd/tests/complete-slice-verification-gate.test.ts +2 -1
  384. package/src/resources/extensions/gsd/tests/complete-task.test.ts +16 -8
  385. package/src/resources/extensions/gsd/tests/component-loader.test.ts +589 -0
  386. package/src/resources/extensions/gsd/tests/component-types.test.ts +127 -0
  387. package/src/resources/extensions/gsd/tests/context-store.test.ts +79 -0
  388. package/src/resources/extensions/gsd/tests/copy-planning-artifacts-samepath.test.ts +2 -1
  389. package/src/resources/extensions/gsd/tests/crash-recovery.test.ts +50 -1
  390. package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +159 -0
  391. package/src/resources/extensions/gsd/tests/db-access-guardrails.test.ts +1 -0
  392. package/src/resources/extensions/gsd/tests/derive-state-crossval.test.ts +3 -3
  393. package/src/resources/extensions/gsd/tests/derive-state-db-disk-reconcile.test.ts +40 -0
  394. package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +91 -3
  395. package/src/resources/extensions/gsd/tests/derive-state.test.ts +4 -4
  396. package/src/resources/extensions/gsd/tests/discuss-slice-structured-questions.test.ts +2 -1
  397. package/src/resources/extensions/gsd/tests/discuss-tool-scope-leak.test.ts +2 -1
  398. package/src/resources/extensions/gsd/tests/dispatch-complete-milestone-guard.test.ts +5 -0
  399. package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +25 -0
  400. package/src/resources/extensions/gsd/tests/dispatch-missing-task-plans.test.ts +14 -0
  401. package/src/resources/extensions/gsd/tests/dispatcher-stuck-planning.test.ts +3 -2
  402. package/src/resources/extensions/gsd/tests/double-merge-guard.test.ts +4 -3
  403. package/src/resources/extensions/gsd/tests/empty-content-abort-loop.test.ts +4 -3
  404. package/src/resources/extensions/gsd/tests/execution-entry-missing-context-4671.test.ts +173 -0
  405. package/src/resources/extensions/gsd/tests/extension-bootstrap-isolation.test.ts +139 -129
  406. package/src/resources/extensions/gsd/tests/finalize-timeout-guard.test.ts +8 -104
  407. package/src/resources/extensions/gsd/tests/gate-state-canonicalization.test.ts +102 -0
  408. package/src/resources/extensions/gsd/tests/gate-storage.test.ts +1 -1
  409. package/src/resources/extensions/gsd/tests/google-search-stub.test.ts +14 -4
  410. package/src/resources/extensions/gsd/tests/headless-milestone-parity.test.ts +117 -0
  411. package/src/resources/extensions/gsd/tests/hook-key-parsing.test.ts +4 -55
  412. package/src/resources/extensions/gsd/tests/integration/all-milestones-complete-merge.test.ts +7 -56
  413. package/src/resources/extensions/gsd/tests/integration/auto-recovery.test.ts +20 -0
  414. package/src/resources/extensions/gsd/tests/integration/doctor-proactive.test.ts +18 -2
  415. package/src/resources/extensions/gsd/tests/integration/queue-completed-milestone-perf.test.ts +10 -4
  416. package/src/resources/extensions/gsd/tests/integration/state-machine-edge-cases.test.ts +144 -7
  417. package/src/resources/extensions/gsd/tests/integration/state-machine-live-validation.test.ts +4 -0
  418. package/src/resources/extensions/gsd/tests/integration/state-machine-runtime-failures.test.ts +2 -16
  419. package/src/resources/extensions/gsd/tests/interactive-routing-bypass.test.ts +9 -3
  420. package/src/resources/extensions/gsd/tests/interrupted-session-ui.test.ts +6 -9
  421. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +64 -0
  422. package/src/resources/extensions/gsd/tests/knowledge.test.ts +93 -1
  423. package/src/resources/extensions/gsd/tests/mcp-client-security.test.ts +8 -37
  424. package/src/resources/extensions/gsd/tests/memory-extractor.test.ts +5 -15
  425. package/src/resources/extensions/gsd/tests/merge-conflict-stops-loop.test.ts +227 -55
  426. package/src/resources/extensions/gsd/tests/milestone-scope-classifier.test.ts +187 -0
  427. package/src/resources/extensions/gsd/tests/milestone-summary-classifier.test.ts +30 -0
  428. package/src/resources/extensions/gsd/tests/model-cost-table.test.ts +9 -1
  429. package/src/resources/extensions/gsd/tests/model-router.test.ts +1 -1
  430. package/src/resources/extensions/gsd/tests/native-git-bridge-exec-fallback.test.ts +6 -48
  431. package/src/resources/extensions/gsd/tests/notification-widget.test.ts +6 -3
  432. package/src/resources/extensions/gsd/tests/orphaned-worktree-audit.test.ts +59 -2
  433. package/src/resources/extensions/gsd/tests/parallel-research-dispatch.test.ts +273 -130
  434. package/src/resources/extensions/gsd/tests/pipeline-variant-dispatch.test.ts +301 -0
  435. package/src/resources/extensions/gsd/tests/pre-execution-pause-wiring.test.ts +32 -1
  436. package/src/resources/extensions/gsd/tests/preferences-worktree-sync.test.ts +2 -1
  437. package/src/resources/extensions/gsd/tests/prompt-cache-optimizer.test.ts +12 -0
  438. package/src/resources/extensions/gsd/tests/prompt-step-ordering.test.ts +15 -4
  439. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +23 -24
  440. package/src/resources/extensions/gsd/tests/queue-auto-guard.test.ts +32 -0
  441. package/src/resources/extensions/gsd/tests/queue-draft-detection.test.ts +3 -2
  442. package/src/resources/extensions/gsd/tests/queued-discuss-fast-path.test.ts +4 -5
  443. package/src/resources/extensions/gsd/tests/ready-phrase-no-files-4573.test.ts +75 -2
  444. package/src/resources/extensions/gsd/tests/reassess-default-optin.test.ts +132 -0
  445. package/src/resources/extensions/gsd/tests/recovery-attempts-reset.test.ts +8 -40
  446. package/src/resources/extensions/gsd/tests/regex-hardening.test.ts +136 -256
  447. package/src/resources/extensions/gsd/tests/research-milestone-composer.test.ts +114 -0
  448. package/src/resources/extensions/gsd/tests/restore-tools-after-discuss.test.ts +6 -3
  449. package/src/resources/extensions/gsd/tests/run-uat-composer.test.ts +148 -0
  450. package/src/resources/extensions/gsd/tests/service-tier.test.ts +4 -0
  451. package/src/resources/extensions/gsd/tests/session-lock-regression.test.ts +29 -0
  452. package/src/resources/extensions/gsd/tests/sidecar-queue.test.ts +3 -2
  453. package/src/resources/extensions/gsd/tests/silent-catch-diagnostics.test.ts +55 -95
  454. package/src/resources/extensions/gsd/tests/single-writer-v3-tool-surface.test.ts +158 -0
  455. package/src/resources/extensions/gsd/tests/skill-activation.test.ts +120 -1
  456. package/src/resources/extensions/gsd/tests/skill-manifest.test.ts +112 -0
  457. package/src/resources/extensions/gsd/tests/slice-cadence.test.ts +242 -0
  458. package/src/resources/extensions/gsd/tests/slice-context-injection.test.ts +3 -2
  459. package/src/resources/extensions/gsd/tests/slice-parallel-orchestrator.test.ts +164 -1
  460. package/src/resources/extensions/gsd/tests/smart-entry-draft.test.ts +2 -1
  461. package/src/resources/extensions/gsd/tests/stale-dirlistcache-4648.test.ts +112 -0
  462. package/src/resources/extensions/gsd/tests/state-machine-full-walkthrough.test.ts +29 -5
  463. package/src/resources/extensions/gsd/tests/state-transition-matrix.test.ts +44 -0
  464. package/src/resources/extensions/gsd/tests/stop-auto-race-null-unit.test.ts +3 -3
  465. package/src/resources/extensions/gsd/tests/structured-data-formatter.test.ts +11 -92
  466. package/src/resources/extensions/gsd/tests/subagent-model-dispatch.test.ts +7 -6
  467. package/src/resources/extensions/gsd/tests/survivor-branch-complete.test.ts +102 -101
  468. package/src/resources/extensions/gsd/tests/sync-lock.test.ts +31 -0
  469. package/src/resources/extensions/gsd/tests/sync-worktree-skip-current.test.ts +4 -3
  470. package/src/resources/extensions/gsd/tests/test-helpers.test.ts +98 -0
  471. package/src/resources/extensions/gsd/tests/test-helpers.ts +153 -0
  472. package/src/resources/extensions/gsd/tests/token-profile.test.ts +8 -1
  473. package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +61 -1
  474. package/src/resources/extensions/gsd/tests/tool-naming.test.ts +8 -1
  475. package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +355 -0
  476. package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +258 -0
  477. package/src/resources/extensions/gsd/tests/uok-contracts.test.ts +51 -0
  478. package/src/resources/extensions/gsd/tests/uok-execution-graph.test.ts +16 -0
  479. package/src/resources/extensions/gsd/tests/uok-gate-runner.test.ts +75 -0
  480. package/src/resources/extensions/gsd/tests/uok-gitops-wiring.test.ts +49 -26
  481. package/src/resources/extensions/gsd/tests/uok-loop-adapter-writer.test.ts +65 -0
  482. package/src/resources/extensions/gsd/tests/uok-parity-report.test.ts +42 -0
  483. package/src/resources/extensions/gsd/tests/uok-plan-v2-wiring.test.ts +19 -2
  484. package/src/resources/extensions/gsd/tests/uok-writer.test.ts +75 -0
  485. package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +12 -0
  486. package/src/resources/extensions/gsd/tests/verify-artifact-tightened.test.ts +144 -80
  487. package/src/resources/extensions/gsd/tests/visualizer-critical-path.test.ts +20 -54
  488. package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +342 -277
  489. package/src/resources/extensions/gsd/tests/worker-model-override.test.ts +37 -29
  490. package/src/resources/extensions/gsd/tests/worktree-db.test.ts +226 -266
  491. package/src/resources/extensions/gsd/tests/worktree-health-monorepo.test.ts +103 -67
  492. package/src/resources/extensions/gsd/tests/worktree-nested-git-safety.test.ts +92 -90
  493. package/src/resources/extensions/gsd/tests/worktree-submodule-safety.test.ts +238 -59
  494. package/src/resources/extensions/gsd/tests/worktree-sync-overwrite-loop.test.ts +113 -161
  495. package/src/resources/extensions/gsd/tests/worktree-telemetry.test.ts +210 -0
  496. package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +262 -0
  497. package/src/resources/extensions/gsd/tests/write-gate-predicates.test.ts +186 -0
  498. package/src/resources/extensions/gsd/tests/write-gate.test.ts +7 -5
  499. package/src/resources/extensions/gsd/tests/zombie-gsd-state.test.ts +80 -96
  500. package/src/resources/extensions/gsd/tools/validate-milestone.ts +8 -2
  501. package/src/resources/extensions/gsd/types.ts +3 -3
  502. package/src/resources/extensions/gsd/unit-context-composer.ts +218 -0
  503. package/src/resources/extensions/gsd/unit-context-manifest.ts +574 -0
  504. package/src/resources/extensions/gsd/uok/contracts.ts +65 -0
  505. package/src/resources/extensions/gsd/uok/dispatch-envelope.ts +56 -0
  506. package/src/resources/extensions/gsd/uok/execution-graph.ts +22 -0
  507. package/src/resources/extensions/gsd/uok/gate-runner.ts +65 -5
  508. package/src/resources/extensions/gsd/uok/gitops.ts +6 -1
  509. package/src/resources/extensions/gsd/uok/loop-adapter.ts +45 -10
  510. package/src/resources/extensions/gsd/uok/parity-report.ts +84 -0
  511. package/src/resources/extensions/gsd/uok/plan-v2.ts +13 -5
  512. package/src/resources/extensions/gsd/uok/writer.ts +113 -0
  513. package/src/resources/extensions/gsd/workflow-mcp.ts +6 -0
  514. package/src/resources/extensions/gsd/worktree-manager.ts +108 -7
  515. package/src/resources/extensions/gsd/worktree-resolver.ts +96 -9
  516. package/src/resources/extensions/gsd/worktree-telemetry.ts +322 -0
  517. package/src/resources/extensions/mcp-client/index.ts +3 -1
  518. package/src/resources/extensions/mcp-client/tests/server-name-spaces.test.ts +70 -36
  519. package/src/resources/extensions/ollama/index.ts +5 -1
  520. package/src/resources/extensions/ollama/ollama-auth-mode.test.ts +123 -15
  521. package/src/resources/extensions/ollama/ollama-status-indicator.test.ts +206 -19
  522. package/src/resources/extensions/remote-questions/manager.ts +36 -4
  523. package/src/resources/extensions/remote-questions/tests/command-polling.test.ts +200 -190
  524. package/src/resources/extensions/shared/tests/interview-preview.test.ts +11 -3
  525. package/src/resources/extensions/voice/tests/linux-ready.test.ts +129 -113
  526. package/packages/pi-ai/dist/utils/oauth/oauth-providers.test.d.ts +0 -2
  527. package/packages/pi-ai/dist/utils/oauth/oauth-providers.test.d.ts.map +0 -1
  528. package/packages/pi-ai/dist/utils/oauth/oauth-providers.test.js +0 -289
  529. package/packages/pi-ai/dist/utils/oauth/oauth-providers.test.js.map +0 -1
  530. package/packages/pi-ai/src/utils/oauth/oauth-providers.test.ts +0 -363
  531. package/src/resources/extensions/gsd/tests/auto-start-model-capture.test.ts +0 -143
  532. package/src/resources/extensions/gsd/tests/complete-milestone-false-merge.test.ts +0 -157
  533. package/src/resources/extensions/gsd/tests/dashboard-model-label-ordering.test.ts +0 -107
  534. package/src/resources/extensions/gsd/tests/find-missing-summaries-closed.test.ts +0 -48
  535. package/src/resources/extensions/gsd/tests/forensics-context-persist.test.ts +0 -159
  536. package/src/resources/extensions/gsd/tests/forensics-db-completion.test.ts +0 -96
  537. package/src/resources/extensions/gsd/tests/forensics-dedup.test.ts +0 -79
  538. package/src/resources/extensions/gsd/tests/forensics-hook-key-parse.test.ts +0 -74
  539. package/src/resources/extensions/gsd/tests/forensics-journal.test.ts +0 -162
  540. package/src/resources/extensions/gsd/tests/gitignore-bg-shell.test.ts +0 -38
  541. package/src/resources/extensions/gsd/tests/gsd-no-project-error.test.ts +0 -73
  542. package/src/resources/extensions/gsd/tests/idle-watchdog-stall-override.test.ts +0 -125
  543. package/src/resources/extensions/gsd/tests/import-done-milestones.test.ts +0 -42
  544. /package/dist/web/standalone/.next/static/{5wbu35_C2_MQ3Jj1lEVDx → cAJH99yNS1UPbeSEiNRrV}/_buildManifest.js +0 -0
  545. /package/dist/web/standalone/.next/static/{5wbu35_C2_MQ3Jj1lEVDx → cAJH99yNS1UPbeSEiNRrV}/_ssgManifest.js +0 -0
@@ -0,0 +1,355 @@
1
+ // GSD-2 — #4782 phase 2 composer tests. Pure-function tests using mock
2
+ // resolvers plus an integration check that reassess-roadmap's migrated
3
+ // builder produces a prompt matching expectations.
4
+
5
+ import test 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 {
12
+ composeInlinedContext,
13
+ composeUnitContext,
14
+ manifestBudgetChars,
15
+ type ArtifactResolver,
16
+ type ExcerptResolver,
17
+ } from "../unit-context-composer.ts";
18
+ import type {
19
+ ArtifactKey,
20
+ BaseResolverContext,
21
+ ComputedArtifactRegistry,
22
+ UnitContextManifest,
23
+ } from "../unit-context-manifest.ts";
24
+ import { UNIT_MANIFESTS } from "../unit-context-manifest.ts";
25
+ import { buildReassessRoadmapPrompt } from "../auto-prompts.ts";
26
+ import { invalidateAllCaches } from "../cache.ts";
27
+ import {
28
+ openDatabase,
29
+ closeDatabase,
30
+ insertMilestone,
31
+ upsertMilestonePlanning,
32
+ insertSlice,
33
+ } from "../gsd-db.ts";
34
+
35
+ // ─── Pure composer tests ──────────────────────────────────────────────────
36
+
37
+ test("#4782 composer: returns empty string for unknown unit type", async () => {
38
+ const out = await composeInlinedContext("never-dispatched", async () => "body");
39
+ assert.strictEqual(out, "");
40
+ });
41
+
42
+ test("#4782 composer: walks the manifest's inline list in declared order", async () => {
43
+ // reassess-roadmap manifest: [roadmap, slice-context, slice-summary, project, requirements, decisions]
44
+ const calls: ArtifactKey[] = [];
45
+ const resolver: ArtifactResolver = async (key) => {
46
+ calls.push(key);
47
+ return `BODY:${key}`;
48
+ };
49
+ const out = await composeInlinedContext("reassess-roadmap", resolver);
50
+ assert.deepEqual(calls, [
51
+ "roadmap",
52
+ "slice-context",
53
+ "slice-summary",
54
+ "project",
55
+ "requirements",
56
+ "decisions",
57
+ ]);
58
+ // Output joins blocks with the "---" separator.
59
+ assert.match(out, /BODY:roadmap\n\n---\n\nBODY:slice-context/);
60
+ });
61
+
62
+ test("#4782 composer: null-returning resolvers are silently omitted", async () => {
63
+ const resolver: ArtifactResolver = async (key) => {
64
+ if (key === "slice-context" || key === "project") return null;
65
+ return `BODY:${key}`;
66
+ };
67
+ const out = await composeInlinedContext("reassess-roadmap", resolver);
68
+ // slice-context + project skipped — not in output, no empty blocks
69
+ assert.ok(!out.includes("BODY:slice-context"));
70
+ assert.ok(!out.includes("BODY:project"));
71
+ // Remaining keys still emitted in declared order
72
+ assert.match(out, /BODY:roadmap\n\n---\n\nBODY:slice-summary\n\n---\n\nBODY:requirements\n\n---\n\nBODY:decisions/);
73
+ });
74
+
75
+ test("#4782 composer: empty-string resolvers are omitted (treated as no-op)", async () => {
76
+ const resolver: ArtifactResolver = async (key) => {
77
+ if (key === "slice-context") return "";
78
+ if (key === "slice-summary") return null;
79
+ return `BODY:${key}`;
80
+ };
81
+ const out = await composeInlinedContext("reassess-roadmap", resolver);
82
+ assert.ok(!out.includes("BODY:slice-context"));
83
+ assert.ok(!out.includes("BODY:slice-summary"));
84
+ // Must not leave double-separators when blocks are skipped
85
+ assert.ok(!out.includes("---\n\n---"));
86
+ });
87
+
88
+ test("#4782 composer: resolver errors surface to caller", async () => {
89
+ const resolver: ArtifactResolver = async () => {
90
+ throw new Error("resolver boom");
91
+ };
92
+ await assert.rejects(
93
+ () => composeInlinedContext("reassess-roadmap", resolver),
94
+ /resolver boom/,
95
+ );
96
+ });
97
+
98
+ test("#4782 composer: manifestBudgetChars returns declared budget", () => {
99
+ const small = manifestBudgetChars("reassess-roadmap");
100
+ assert.ok(small !== null && small > 0);
101
+ assert.strictEqual(manifestBudgetChars("never-dispatched"), null);
102
+ });
103
+
104
+ // ─── Integration: migrated buildReassessRoadmapPrompt ─────────────────────
105
+
106
+ function makeFixtureBase(): string {
107
+ const base = mkdtempSync(join(tmpdir(), "gsd-composer-pilot-"));
108
+ mkdirSync(join(base, ".gsd", "milestones", "M001", "slices", "S01", "tasks"), { recursive: true });
109
+ return base;
110
+ }
111
+
112
+ function cleanup(base: string): void {
113
+ try { closeDatabase(); } catch { /* noop */ }
114
+ invalidateAllCaches();
115
+ rmSync(base, { recursive: true, force: true });
116
+ }
117
+
118
+ function seed(base: string, mid: string): void {
119
+ openDatabase(join(base, ".gsd", "gsd.db"));
120
+ insertMilestone({ id: mid, title: "Test", status: "active", depends_on: [] });
121
+ upsertMilestonePlanning(mid, {
122
+ title: "Test",
123
+ status: "active",
124
+ vision: "Ship it",
125
+ successCriteria: ["It ships"],
126
+ keyRisks: [],
127
+ proofStrategy: [],
128
+ verificationContract: "",
129
+ verificationIntegration: "",
130
+ verificationOperational: "",
131
+ verificationUat: "",
132
+ definitionOfDone: [],
133
+ requirementCoverage: "",
134
+ boundaryMapMarkdown: "",
135
+ });
136
+ insertSlice({
137
+ id: "S01",
138
+ milestoneId: mid,
139
+ title: "First",
140
+ status: "complete",
141
+ risk: "low",
142
+ depends: [],
143
+ demo: "",
144
+ sequence: 1,
145
+ });
146
+ }
147
+
148
+ function writeArtifacts(base: string): void {
149
+ writeFileSync(
150
+ join(base, ".gsd", "milestones", "M001", "M001-ROADMAP.md"),
151
+ "# M001\n## Slices\n- [x] **S01: First** `risk:low` `depends:[]`\n",
152
+ );
153
+ writeFileSync(
154
+ join(base, ".gsd", "milestones", "M001", "slices", "S01", "S01-SUMMARY.md"),
155
+ "---\nid: S01\nparent: M001\n---\n# S01 Summary\n**One-liner**\n\n## What Happened\nDone.\n",
156
+ );
157
+ }
158
+
159
+ test("#4782 phase 2: buildReassessRoadmapPrompt emits composer-shaped context with manifest-declared artifacts", async (t) => {
160
+ const base = makeFixtureBase();
161
+ t.after(() => cleanup(base));
162
+ invalidateAllCaches();
163
+
164
+ seed(base, "M001");
165
+ writeArtifacts(base);
166
+
167
+ const prompt = await buildReassessRoadmapPrompt("M001", "Test", "S01", base);
168
+
169
+ // Context block wrapper from capPreamble
170
+ assert.match(prompt, /## Inlined Context \(preloaded — do not re-read these files\)/);
171
+
172
+ // Roadmap inlined first (manifest order)
173
+ assert.match(prompt, /### Current Roadmap/);
174
+ assert.match(prompt, /S01: First/);
175
+
176
+ // Slice summary present
177
+ assert.match(prompt, /### S01 Summary/);
178
+ assert.match(prompt, /One-liner/);
179
+
180
+ // Slice context is optional and not present in this fixture — must not
181
+ // leave a stray empty section
182
+ assert.ok(!prompt.includes("Slice Context (from discussion)"));
183
+ });
184
+
185
+ // ─── v2 surface (#4924) ───────────────────────────────────────────────────
186
+
187
+ const fakeBase: BaseResolverContext = {
188
+ unitType: "reassess-roadmap",
189
+ basePath: "/tmp/fake",
190
+ milestoneId: "M001",
191
+ sliceId: "S01",
192
+ };
193
+
194
+ test("#4924 v2 composer: returns empty sections for unknown unit type", async () => {
195
+ const out = await composeUnitContext("never-dispatched", { base: fakeBase });
196
+ assert.deepEqual(out, { prepend: "", inline: "" });
197
+ });
198
+
199
+ test("#4924 v2 composer: omitting resolveArtifact skips inline keys without erroring", async () => {
200
+ const out = await composeUnitContext("reassess-roadmap", { base: fakeBase });
201
+ assert.strictEqual(out.inline, "");
202
+ assert.strictEqual(out.prepend, "");
203
+ });
204
+
205
+ test("#4924 v2 composer: walks inline + excerpt + computed sections in declared order", async () => {
206
+ // Reuse the run-uat manifest shape (small inline, no excerpt/computed) and
207
+ // synthesise a manifest-shape override via a temporary registration would
208
+ // require touching production data. Instead, drive the composer through
209
+ // the existing manifest plus mock resolvers and verify ordering against
210
+ // the declared sequence.
211
+ const calls: string[] = [];
212
+ const resolveArtifact: ArtifactResolver = async (key) => {
213
+ calls.push(`art:${key}`);
214
+ return `BODY:${key}`;
215
+ };
216
+ const out = await composeUnitContext("run-uat", { base: { ...fakeBase, unitType: "run-uat" }, resolveArtifact });
217
+ // run-uat manifest inline order: slice-uat, slice-summary, project
218
+ assert.deepEqual(calls, ["art:slice-uat", "art:slice-summary", "art:project"]);
219
+ assert.match(out.inline, /BODY:slice-uat\n\n---\n\nBODY:slice-summary\n\n---\n\nBODY:project/);
220
+ });
221
+
222
+ test("#4924 v2 composer: excerpt section calls resolveExcerpt for declared keys", async () => {
223
+ // complete-milestone declares slice-summary as excerpt — perfect target.
224
+ const inlineCalls: ArtifactKey[] = [];
225
+ const excerptCalls: ArtifactKey[] = [];
226
+ const resolveArtifact: ArtifactResolver = async (key) => {
227
+ inlineCalls.push(key);
228
+ return `INLINE:${key}`;
229
+ };
230
+ const resolveExcerpt: ExcerptResolver = async (key) => {
231
+ excerptCalls.push(key);
232
+ return `EXCERPT:${key}`;
233
+ };
234
+ const out = await composeUnitContext("complete-milestone", {
235
+ base: { ...fakeBase, unitType: "complete-milestone" },
236
+ resolveArtifact,
237
+ resolveExcerpt,
238
+ });
239
+ assert.ok(excerptCalls.includes("slice-summary"));
240
+ // Excerpt body appears in the composed inline section, after inline keys.
241
+ assert.match(out.inline, /EXCERPT:slice-summary/);
242
+ // The inline keys come first per the manifest order.
243
+ const cmManifest = UNIT_MANIFESTS["complete-milestone"];
244
+ const firstInlineKey = cmManifest.artifacts.inline[0]!;
245
+ const firstInlineIdx = out.inline.indexOf(`INLINE:${firstInlineKey}`);
246
+ const excerptIdx = out.inline.indexOf("EXCERPT:slice-summary");
247
+ assert.ok(firstInlineIdx >= 0 && excerptIdx > firstInlineIdx, "inline body should precede excerpt body");
248
+ });
249
+
250
+ test("#4924 v2 composer: prepend block is separate from inline section", async () => {
251
+ // No production manifest declares a prepend block yet (those land with
252
+ // each batched migration). Drive the composer through a synthetic
253
+ // manifest by patching UNIT_MANIFESTS just for this test.
254
+ const original = UNIT_MANIFESTS["run-uat"];
255
+ type Mutable<T> = { -readonly [P in keyof T]: T[P] };
256
+ const patched: UnitContextManifest = {
257
+ ...original,
258
+ prepend: ["test-banner"] as never[], // computed id not in production registry — typed via cast for the test
259
+ };
260
+ (UNIT_MANIFESTS as Mutable<typeof UNIT_MANIFESTS>)["run-uat"] = patched;
261
+ try {
262
+ const computed = {
263
+ "test-banner": {
264
+ build: async (_inputs: never, base: BaseResolverContext) => `BANNER for ${base.unitType}`,
265
+ inputs: undefined as never,
266
+ },
267
+ } as unknown as ComputedArtifactRegistry;
268
+ const out = await composeUnitContext("run-uat", {
269
+ base: { ...fakeBase, unitType: "run-uat" },
270
+ computed,
271
+ });
272
+ assert.strictEqual(out.prepend, "BANNER for run-uat");
273
+ assert.strictEqual(out.inline, "");
274
+ } finally {
275
+ (UNIT_MANIFESTS as Mutable<typeof UNIT_MANIFESTS>)["run-uat"] = original;
276
+ }
277
+ });
278
+
279
+ test("#4924 v2 composer: missing computed registry entry is skipped silently", async () => {
280
+ const original = UNIT_MANIFESTS["run-uat"];
281
+ type Mutable<T> = { -readonly [P in keyof T]: T[P] };
282
+ const patched: UnitContextManifest = {
283
+ ...original,
284
+ prepend: ["test-banner"] as never[],
285
+ };
286
+ (UNIT_MANIFESTS as Mutable<typeof UNIT_MANIFESTS>)["run-uat"] = patched;
287
+ try {
288
+ // No `computed` registry supplied — declared id should be skipped, not throw.
289
+ const out = await composeUnitContext("run-uat", { base: { ...fakeBase, unitType: "run-uat" } });
290
+ assert.strictEqual(out.prepend, "");
291
+ } finally {
292
+ (UNIT_MANIFESTS as Mutable<typeof UNIT_MANIFESTS>)["run-uat"] = original;
293
+ }
294
+ });
295
+
296
+ test("#4924 v2 composer: computed builder returning null omits the section (no empty separator)", async () => {
297
+ const original = UNIT_MANIFESTS["run-uat"];
298
+ type Mutable<T> = { -readonly [P in keyof T]: T[P] };
299
+ const patched: UnitContextManifest = {
300
+ ...original,
301
+ prepend: ["test-banner-a", "test-banner-b"] as never[],
302
+ };
303
+ (UNIT_MANIFESTS as Mutable<typeof UNIT_MANIFESTS>)["run-uat"] = patched;
304
+ try {
305
+ const computed = {
306
+ "test-banner-a": { build: async () => null, inputs: undefined as never },
307
+ "test-banner-b": { build: async () => "B", inputs: undefined as never },
308
+ } as unknown as ComputedArtifactRegistry;
309
+ const out = await composeUnitContext("run-uat", { base: { ...fakeBase, unitType: "run-uat" }, computed });
310
+ assert.strictEqual(out.prepend, "B");
311
+ assert.ok(!out.prepend.includes("---"));
312
+ } finally {
313
+ (UNIT_MANIFESTS as Mutable<typeof UNIT_MANIFESTS>)["run-uat"] = original;
314
+ }
315
+ });
316
+
317
+ test("#4924 v2 composer: backward-compat — composeInlinedContext still works for v1 callers", async () => {
318
+ const out = await composeInlinedContext("run-uat", async (key) => `BODY:${key}`);
319
+ assert.match(out, /BODY:slice-uat\n\n---\n\nBODY:slice-summary\n\n---\n\nBODY:project/);
320
+ });
321
+
322
+ test("#4926 review: computed builders see normalized base.unitType matching the resolved manifest", async () => {
323
+ // Caller passes one unitType to composeUnitContext but a different (stale)
324
+ // value in opts.base. Composer must normalize so builders observe the
325
+ // unitType the manifest was resolved against — preventing manifests and
326
+ // computed context from drifting.
327
+ const original = UNIT_MANIFESTS["run-uat"];
328
+ type Mutable<T> = { -readonly [P in keyof T]: T[P] };
329
+ const patched: UnitContextManifest = {
330
+ ...original,
331
+ prepend: ["test-banner"] as never[],
332
+ };
333
+ (UNIT_MANIFESTS as Mutable<typeof UNIT_MANIFESTS>)["run-uat"] = patched;
334
+ try {
335
+ let observedUnitType: string | undefined;
336
+ const computed = {
337
+ "test-banner": {
338
+ build: async (_inputs: never, base: BaseResolverContext) => {
339
+ observedUnitType = base.unitType;
340
+ return `BANNER for ${base.unitType}`;
341
+ },
342
+ inputs: undefined as never,
343
+ },
344
+ } as unknown as ComputedArtifactRegistry;
345
+ const out = await composeUnitContext("run-uat", {
346
+ // Deliberately mismatched: function arg "run-uat" vs. base.unitType "stale-other-unit".
347
+ base: { ...fakeBase, unitType: "stale-other-unit" },
348
+ computed,
349
+ });
350
+ assert.strictEqual(observedUnitType, "run-uat", "builder must see the unitType the manifest was resolved against");
351
+ assert.strictEqual(out.prepend, "BANNER for run-uat");
352
+ } finally {
353
+ (UNIT_MANIFESTS as Mutable<typeof UNIT_MANIFESTS>)["run-uat"] = original;
354
+ }
355
+ });
@@ -0,0 +1,258 @@
1
+ // GSD-2 — #4782 phase 1: schema tests + CI coverage guard for manifests.
2
+
3
+ import test from "node:test";
4
+ import assert from "node:assert/strict";
5
+ import { readFileSync } from "node:fs";
6
+ import { dirname, join } from "node:path";
7
+ import { fileURLToPath } from "node:url";
8
+
9
+ import {
10
+ ARTIFACT_KEYS,
11
+ KNOWN_UNIT_TYPES,
12
+ UNIT_MANIFESTS,
13
+ resolveManifest,
14
+ type ArtifactKey,
15
+ type SkillsPolicy,
16
+ type UnitContextManifest,
17
+ } from "../unit-context-manifest.ts";
18
+
19
+ // ─── Coverage: every known unit type has a manifest ──────────────────────
20
+
21
+ test("#4782 phase 1: every KNOWN_UNIT_TYPES entry has a UNIT_MANIFESTS entry", () => {
22
+ for (const unitType of KNOWN_UNIT_TYPES) {
23
+ assert.ok(
24
+ UNIT_MANIFESTS[unitType],
25
+ `unit type "${unitType}" is declared in KNOWN_UNIT_TYPES but has no manifest`,
26
+ );
27
+ }
28
+ });
29
+
30
+ test("#4782 phase 1: every UNIT_MANIFESTS entry corresponds to a known unit type", () => {
31
+ const known = new Set<string>(KNOWN_UNIT_TYPES as readonly string[]);
32
+ for (const unitType of Object.keys(UNIT_MANIFESTS)) {
33
+ assert.ok(
34
+ known.has(unitType),
35
+ `manifest entry "${unitType}" is not in KNOWN_UNIT_TYPES — add it there or remove the manifest`,
36
+ );
37
+ }
38
+ });
39
+
40
+ // ─── Coverage: every unitType stringly-typed in auto-dispatch.ts is known ─
41
+
42
+ test("#4782 phase 1: every unitType string in auto-dispatch.ts has a manifest", () => {
43
+ // Source-only coverage check — read the dispatcher and enumerate its
44
+ // unitType literals. This is a CI guard against manifest drift: if a
45
+ // new dispatch rule is added without a corresponding manifest entry,
46
+ // this test fails loudly. Read-only check of source text; the cheapest
47
+ // way to enumerate declared unit types without running the dispatcher.
48
+ // allow-source-grep: enumerate unitType literals for CI coverage guard
49
+ const __dirname = dirname(fileURLToPath(import.meta.url));
50
+ const dispatchSrc = readFileSync(join(__dirname, "..", "auto-dispatch.ts"), "utf-8");
51
+ const matches = Array.from(dispatchSrc.matchAll(/unitType:\s*"([^"]+)"/g));
52
+ const seen = new Set<string>();
53
+ for (const m of matches) {
54
+ const t = m[1];
55
+ if (!t) continue;
56
+ seen.add(t);
57
+ }
58
+ const missing: string[] = [];
59
+ for (const t of seen) {
60
+ if (!UNIT_MANIFESTS[t as keyof typeof UNIT_MANIFESTS]) {
61
+ missing.push(t);
62
+ }
63
+ }
64
+ assert.deepEqual(missing, [], `unit types dispatched in auto-dispatch.ts but missing from UNIT_MANIFESTS: ${missing.join(", ")}`);
65
+ });
66
+
67
+ // ─── Shape: every manifest conforms to the schema invariants ──────────────
68
+
69
+ test("#4782 phase 1: every manifest's artifacts reference known ArtifactKey values", () => {
70
+ const validKeys = new Set<string>(ARTIFACT_KEYS as readonly string[]);
71
+ for (const [unitType, manifest] of Object.entries(UNIT_MANIFESTS)) {
72
+ const all: ArtifactKey[] = [
73
+ ...manifest.artifacts.inline,
74
+ ...manifest.artifacts.excerpt,
75
+ ...manifest.artifacts.onDemand,
76
+ ];
77
+ for (const key of all) {
78
+ assert.ok(
79
+ validKeys.has(key),
80
+ `manifest "${unitType}" references unknown artifact key "${key}"`,
81
+ );
82
+ }
83
+ }
84
+ });
85
+
86
+ test("#4782 phase 1: no manifest has the same artifact key in inline AND excerpt (mutually exclusive)", () => {
87
+ for (const [unitType, manifest] of Object.entries(UNIT_MANIFESTS)) {
88
+ const inline = new Set<string>(manifest.artifacts.inline as readonly string[]);
89
+ const clashes = (manifest.artifacts.excerpt as readonly string[]).filter(k => inline.has(k));
90
+ assert.deepEqual(
91
+ clashes,
92
+ [],
93
+ `manifest "${unitType}" has overlapping inline+excerpt artifact keys: ${clashes.join(", ")}. Pick one.`,
94
+ );
95
+ }
96
+ });
97
+
98
+ test("#4782 phase 1: every manifest has a positive maxSystemPromptChars", () => {
99
+ for (const [unitType, manifest] of Object.entries(UNIT_MANIFESTS)) {
100
+ assert.ok(
101
+ typeof manifest.maxSystemPromptChars === "number" && manifest.maxSystemPromptChars > 0,
102
+ `manifest "${unitType}" has invalid maxSystemPromptChars: ${manifest.maxSystemPromptChars}`,
103
+ );
104
+ }
105
+ });
106
+
107
+ test("#4782 phase 1: skills policy shapes are valid discriminated-union members", () => {
108
+ for (const [unitType, manifest] of Object.entries(UNIT_MANIFESTS)) {
109
+ const p = manifest.skills as SkillsPolicy;
110
+ switch (p.mode) {
111
+ case "none":
112
+ case "all":
113
+ break;
114
+ case "allowlist":
115
+ assert.ok(
116
+ Array.isArray(p.skills) && p.skills.every(s => typeof s === "string"),
117
+ `manifest "${unitType}" has allowlist policy with invalid skills[]`,
118
+ );
119
+ break;
120
+ default: {
121
+ const _exhaustive: never = p;
122
+ void _exhaustive;
123
+ assert.fail(`manifest "${unitType}" has unrecognized skills.mode`);
124
+ }
125
+ }
126
+ }
127
+ });
128
+
129
+ // ─── Lookup helper ────────────────────────────────────────────────────────
130
+
131
+ test("#4782 phase 1: resolveManifest returns null for an unknown unit type", () => {
132
+ assert.strictEqual(resolveManifest("never-dispatched-unit-type"), null);
133
+ });
134
+
135
+ test("#4782 phase 1: resolveManifest returns a manifest for every known unit type", () => {
136
+ for (const unitType of KNOWN_UNIT_TYPES) {
137
+ const m = resolveManifest(unitType);
138
+ assert.ok(m, `resolveManifest("${unitType}") should return a manifest`);
139
+ // Identity check — the helper should return the exact object, not a copy.
140
+ assert.strictEqual(m, UNIT_MANIFESTS[unitType]);
141
+ }
142
+ });
143
+
144
+ // ─── Phase-2 target: complete-milestone manifest reflects #4780's excerpt shape ─
145
+
146
+ test("#4782 phase 1: complete-milestone manifest declares slice-summary as excerpt (matches #4780)", () => {
147
+ const m = UNIT_MANIFESTS["complete-milestone"];
148
+ assert.ok(
149
+ m.artifacts.excerpt.includes("slice-summary"),
150
+ "complete-milestone should declare slice-summary as excerpt (alignment with #4780)",
151
+ );
152
+ assert.ok(
153
+ !m.artifacts.inline.includes("slice-summary"),
154
+ "complete-milestone should NOT declare slice-summary as inline — that was the #4780 bloat",
155
+ );
156
+ });
157
+
158
+ // ─── v2 contract invariants (#4924) ──────────────────────────────────────
159
+
160
+ test("#4924: computed + prepend ids (when declared) are non-empty strings", () => {
161
+ for (const [unitType, manifest] of Object.entries(UNIT_MANIFESTS)) {
162
+ const ids: string[] = [
163
+ ...((manifest.artifacts as { computed?: readonly string[] }).computed ?? []),
164
+ ...((manifest as { prepend?: readonly string[] }).prepend ?? []),
165
+ ];
166
+ for (const id of ids) {
167
+ assert.ok(
168
+ typeof id === "string" && id.length > 0,
169
+ `manifest "${unitType}" has an empty/invalid computed/prepend id: ${JSON.stringify(id)}`,
170
+ );
171
+ }
172
+ }
173
+ });
174
+
175
+ test("#4924: no computed id appears in both artifacts.computed AND prepend (mutually exclusive position)", () => {
176
+ for (const [unitType, manifest] of Object.entries(UNIT_MANIFESTS)) {
177
+ const inlineComputed = new Set<string>(
178
+ ((manifest.artifacts as { computed?: readonly string[] }).computed ?? []),
179
+ );
180
+ const clashes = ((manifest as { prepend?: readonly string[] }).prepend ?? [])
181
+ .filter(id => inlineComputed.has(id));
182
+ assert.deepEqual(
183
+ clashes,
184
+ [],
185
+ `manifest "${unitType}" places computed id(s) in both prepend and inline-computed: ${clashes.join(", ")}. Pick one position.`,
186
+ );
187
+ }
188
+ });
189
+
190
+ // ─── Tools-policy invariants (#4934) ─────────────────────────────────────
191
+
192
+ test("#4934: every manifest declares a tools policy", () => {
193
+ for (const [unitType, manifest] of Object.entries(UNIT_MANIFESTS)) {
194
+ const policy = (manifest as { tools?: { mode?: string } }).tools;
195
+ assert.ok(
196
+ policy && typeof policy.mode === "string",
197
+ `manifest "${unitType}" is missing a tools policy — required to fail loud rather than default to "all" silently`,
198
+ );
199
+ }
200
+ });
201
+
202
+ test("#4934: tools.mode is one of the four declared policies", () => {
203
+ const validModes = new Set(["all", "read-only", "planning", "docs"]);
204
+ for (const [unitType, manifest] of Object.entries(UNIT_MANIFESTS)) {
205
+ const mode = (manifest as { tools: { mode: string } }).tools.mode;
206
+ assert.ok(
207
+ validModes.has(mode),
208
+ `manifest "${unitType}" has invalid tools.mode "${mode}" — must be one of ${[...validModes].join(", ")}`,
209
+ );
210
+ }
211
+ });
212
+
213
+ test('#4934: only execute-task and reactive-execute may use tools.mode "all" (full source-tree write access)', () => {
214
+ const allowedAllUnits = new Set(["execute-task", "reactive-execute"]);
215
+ for (const [unitType, manifest] of Object.entries(UNIT_MANIFESTS)) {
216
+ const mode = (manifest as { tools: { mode: string } }).tools.mode;
217
+ if (mode === "all") {
218
+ assert.ok(
219
+ allowedAllUnits.has(unitType),
220
+ `manifest "${unitType}" declares tools.mode = "all" but is not on the execute-track. ` +
221
+ 'Only execute-task and reactive-execute should have full source write access; ' +
222
+ 'planning/discuss/research units must use "planning" (or "docs" for rewrite-docs).',
223
+ );
224
+ }
225
+ }
226
+ });
227
+
228
+ test('#4934: tools.mode "docs" requires a non-empty allowedPathGlobs array', () => {
229
+ for (const [unitType, manifest] of Object.entries(UNIT_MANIFESTS)) {
230
+ const tools = (manifest as { tools: { mode: string; allowedPathGlobs?: readonly string[] } }).tools;
231
+ if (tools.mode !== "docs") continue;
232
+ assert.ok(
233
+ Array.isArray(tools.allowedPathGlobs) && tools.allowedPathGlobs.length > 0,
234
+ `manifest "${unitType}" has docs policy but no allowedPathGlobs — explicit allow-set is required so the enforcement layer doesn't fall back to a hardcoded default`,
235
+ );
236
+ for (const g of tools.allowedPathGlobs!) {
237
+ assert.ok(
238
+ typeof g === "string" && g.length > 0,
239
+ `manifest "${unitType}" has empty/invalid allowedPathGlobs entry: ${JSON.stringify(g)}`,
240
+ );
241
+ }
242
+ }
243
+ });
244
+
245
+ // ─── Budget floor: run-uat + gate-evaluate hit the smallest budget tier ──
246
+
247
+ test("#4782 phase 2: run-uat and gate-evaluate use the smallest budget tier", () => {
248
+ const uatBudget = UNIT_MANIFESTS["run-uat"].maxSystemPromptChars;
249
+ const gateBudget = UNIT_MANIFESTS["gate-evaluate"].maxSystemPromptChars;
250
+ assert.strictEqual(uatBudget, gateBudget, "run-uat and gate-evaluate both use COMMON_BUDGET_SMALL");
251
+ // They should be the tightest (or tied for tightest) across all manifests
252
+ for (const [unitType, other] of Object.entries(UNIT_MANIFESTS)) {
253
+ assert.ok(
254
+ uatBudget <= other.maxSystemPromptChars,
255
+ `run-uat budget (${uatBudget}) should be ≤ ${unitType} budget (${other.maxSystemPromptChars})`,
256
+ );
257
+ }
258
+ });
@@ -3,12 +3,16 @@ import assert from "node:assert/strict";
3
3
 
4
4
  import type {
5
5
  AuditEventEnvelope,
6
+ UokDispatchEnvelope,
6
7
  GateResult,
7
8
  TurnContract,
8
9
  TurnResult,
9
10
  UokNodeKind,
11
+ WriteRecord,
12
+ WriterToken,
10
13
  } from "../uok/contracts.ts";
11
14
  import { buildAuditEnvelope } from "../uok/audit.ts";
15
+ import { buildDispatchEnvelope, explainDispatch } from "../uok/dispatch-envelope.ts";
12
16
 
13
17
  test("uok contracts serialize/deserialize turn envelopes", () => {
14
18
  const contract: TurnContract = {
@@ -84,3 +88,50 @@ test("uok audit envelope includes trace/turn/causality fields", () => {
84
88
  assert.equal(event.causedBy, "turn-start");
85
89
  assert.equal(event.payload.status, "completed");
86
90
  });
91
+
92
+ test("uok dispatch envelope carries scheduler reason and constraints", () => {
93
+ const envelope: UokDispatchEnvelope = buildDispatchEnvelope({
94
+ action: "dispatch",
95
+ node: {
96
+ kind: "unit",
97
+ dependsOn: ["plan-gate"],
98
+ reads: ["M001-ROADMAP.md"],
99
+ writes: ["M001/S01/T01-SUMMARY.md"],
100
+ },
101
+ unitType: "execute-task",
102
+ unitId: "M001/S01/T01",
103
+ prompt: "do work",
104
+ reasonCode: "dependency",
105
+ summary: "all dependencies are closed and output path is available",
106
+ evidence: { readyTaskCount: 1 },
107
+ });
108
+
109
+ assert.equal(envelope.nodeKind, "unit");
110
+ assert.equal(envelope.reason.reasonCode, "dependency");
111
+ assert.deepEqual(envelope.constraints?.dependsOn, ["plan-gate"]);
112
+ assert.ok(explainDispatch(envelope).includes("execute-task M001/S01/T01"));
113
+ });
114
+
115
+ test("uok writer records serialize sequence metadata", () => {
116
+ const token: WriterToken = {
117
+ tokenId: "token-1",
118
+ traceId: "trace-1",
119
+ turnId: "turn-1",
120
+ acquiredAt: new Date().toISOString(),
121
+ owner: "uok",
122
+ };
123
+
124
+ const record: WriteRecord = {
125
+ writerToken: token,
126
+ sequence: { traceId: token.traceId, turnId: token.turnId, sequence: 7 },
127
+ category: "audit",
128
+ operation: "append",
129
+ path: ".gsd/audit/events.jsonl",
130
+ ts: new Date().toISOString(),
131
+ };
132
+
133
+ const roundTrip = JSON.parse(JSON.stringify(record)) as WriteRecord;
134
+ assert.equal(roundTrip.writerToken.tokenId, "token-1");
135
+ assert.equal(roundTrip.sequence.sequence, 7);
136
+ assert.equal(roundTrip.category, "audit");
137
+ });