gsd-pi 2.77.0-dev.1d17f366c → 2.77.0-dev.2daa994b6

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 (368) hide show
  1. package/dist/headless.js +25 -4
  2. package/dist/resource-loader.d.ts +40 -0
  3. package/dist/resource-loader.js +32 -13
  4. package/dist/resources/extensions/browser-tools/capture.js +9 -0
  5. package/dist/resources/extensions/browser-tools/tests/browser-tools-integration.test.mjs +8 -59
  6. package/dist/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +36 -24
  7. package/dist/resources/extensions/browser-tools/tests/capture-sharp-optional.test.cjs +69 -71
  8. package/dist/resources/extensions/browser-tools/tools/forms.js +5 -1
  9. package/dist/resources/extensions/browser-tools/tools/intent.js +5 -1
  10. package/dist/resources/extensions/gsd/auto/phases.js +5 -18
  11. package/dist/resources/extensions/gsd/auto/session.js +6 -0
  12. package/dist/resources/extensions/gsd/auto-dispatch.js +37 -8
  13. package/dist/resources/extensions/gsd/auto-post-unit.js +79 -0
  14. package/dist/resources/extensions/gsd/auto-prompts.js +372 -104
  15. package/dist/resources/extensions/gsd/auto-start.js +75 -24
  16. package/dist/resources/extensions/gsd/auto.js +34 -0
  17. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +9 -1
  18. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +7 -1
  19. package/dist/resources/extensions/gsd/component-loader.js +447 -0
  20. package/dist/resources/extensions/gsd/component-types.js +69 -0
  21. package/dist/resources/extensions/gsd/context-store.js +23 -7
  22. package/dist/resources/extensions/gsd/detection.js +49 -1
  23. package/dist/resources/extensions/gsd/docs/preferences-reference.md +1 -1
  24. package/dist/resources/extensions/gsd/forensics.js +106 -0
  25. package/dist/resources/extensions/gsd/gsd-db.js +1 -1
  26. package/dist/resources/extensions/gsd/guided-flow.js +2 -4
  27. package/dist/resources/extensions/gsd/memory-extractor.js +7 -1
  28. package/dist/resources/extensions/gsd/milestone-scope-classifier.js +299 -0
  29. package/dist/resources/extensions/gsd/model-cost-table.js +3 -0
  30. package/dist/resources/extensions/gsd/model-router.js +6 -0
  31. package/dist/resources/extensions/gsd/preferences-validation.js +23 -0
  32. package/dist/resources/extensions/gsd/prompt-cache-optimizer.js +4 -0
  33. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +5 -1
  34. package/dist/resources/extensions/gsd/prompts/plan-slice.md +15 -2
  35. package/dist/resources/extensions/gsd/service-tier.js +5 -2
  36. package/dist/resources/extensions/gsd/skill-manifest.js +168 -0
  37. package/dist/resources/extensions/gsd/slice-cadence.js +238 -0
  38. package/dist/resources/extensions/gsd/tools/validate-milestone.js +7 -2
  39. package/dist/resources/extensions/gsd/unit-context-composer.js +147 -0
  40. package/dist/resources/extensions/gsd/unit-context-manifest.js +334 -0
  41. package/dist/resources/extensions/gsd/worktree-manager.js +51 -0
  42. package/dist/resources/extensions/gsd/worktree-resolver.js +86 -7
  43. package/dist/resources/extensions/gsd/worktree-telemetry.js +198 -0
  44. package/dist/resources/extensions/mcp-client/index.js +3 -1
  45. package/dist/resources/extensions/ollama/index.js +5 -1
  46. package/dist/resources/extensions/remote-questions/manager.js +11 -5
  47. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  48. package/dist/web/standalone/.next/BUILD_ID +1 -1
  49. package/dist/web/standalone/.next/app-path-routes-manifest.json +17 -17
  50. package/dist/web/standalone/.next/build-manifest.json +2 -2
  51. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  52. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  53. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  54. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  55. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  56. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  57. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  58. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  59. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  60. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  61. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  62. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  63. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  64. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  65. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  66. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  67. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  68. package/dist/web/standalone/.next/server/app/index.html +1 -1
  69. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  70. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  71. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  72. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  73. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  74. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  75. package/dist/web/standalone/.next/server/app-paths-manifest.json +17 -17
  76. package/dist/web/standalone/.next/server/chunks/6897.js +1 -1
  77. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  78. package/dist/web/standalone/.next/server/middleware-manifest.json +5 -5
  79. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  80. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  81. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  82. package/package.json +1 -3
  83. package/packages/mcp-server/src/mcp-server.test.ts +25 -3
  84. package/packages/mcp-server/src/readers/graph.test.ts +87 -15
  85. package/packages/mcp-server/src/workflow-tools.test.ts +80 -39
  86. package/packages/native/package.json +1 -1
  87. package/packages/native/src/__tests__/_test-coverage-guard.test.mjs +98 -0
  88. package/packages/native/src/__tests__/module-compat.test.mjs +59 -27
  89. package/packages/native/src/__tests__/ps.test.mjs +14 -8
  90. package/packages/native/src/__tests__/stream-process.test.mjs +23 -2
  91. package/packages/native/src/__tests__/truncate.test.mjs +17 -2
  92. package/packages/pi-agent-core/src/agent-loop.test.ts +5 -15
  93. package/packages/pi-agent-core/src/agent.test.ts +96 -102
  94. package/packages/pi-agent-core/tsconfig.tsbuildinfo +1 -1
  95. package/packages/pi-ai/dist/models/generated/index.d.ts +34 -0
  96. package/packages/pi-ai/dist/models/generated/index.d.ts.map +1 -1
  97. package/packages/pi-ai/dist/models/generated/openai-codex.d.ts +17 -0
  98. package/packages/pi-ai/dist/models/generated/openai-codex.d.ts.map +1 -1
  99. package/packages/pi-ai/dist/models/generated/openai-codex.js +17 -0
  100. package/packages/pi-ai/dist/models/generated/openai-codex.js.map +1 -1
  101. package/packages/pi-ai/dist/models/generated/openai.d.ts +17 -0
  102. package/packages/pi-ai/dist/models/generated/openai.d.ts.map +1 -1
  103. package/packages/pi-ai/dist/models/generated/openai.js +17 -0
  104. package/packages/pi-ai/dist/models/generated/openai.js.map +1 -1
  105. package/packages/pi-ai/dist/models.generated.test.js +43 -70
  106. package/packages/pi-ai/dist/models.generated.test.js.map +1 -1
  107. package/packages/pi-ai/dist/models.test.js +29 -11
  108. package/packages/pi-ai/dist/models.test.js.map +1 -1
  109. package/packages/pi-ai/scripts/generate-models.ts +44 -0
  110. package/packages/pi-ai/src/models/generated/openai-codex.ts +17 -0
  111. package/packages/pi-ai/src/models/generated/openai.ts +17 -0
  112. package/packages/pi-ai/src/models.generated.test.ts +46 -73
  113. package/packages/pi-ai/src/models.test.ts +39 -11
  114. package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
  115. package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js +96 -32
  116. package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js.map +1 -1
  117. package/packages/pi-coding-agent/dist/core/agent-session-model-switch.test.js +75 -12
  118. package/packages/pi-coding-agent/dist/core/agent-session-model-switch.test.js.map +1 -1
  119. package/packages/pi-coding-agent/dist/core/agent-session-tool-refresh.test.js +99 -31
  120. package/packages/pi-coding-agent/dist/core/agent-session-tool-refresh.test.js.map +1 -1
  121. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts +5 -0
  122. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  123. package/packages/pi-coding-agent/dist/core/extensions/loader.js +61 -0
  124. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  125. package/packages/pi-coding-agent/dist/core/lsp/lsp-integration.test.js +30 -4
  126. package/packages/pi-coding-agent/dist/core/lsp/lsp-integration.test.js.map +1 -1
  127. package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js +17 -0
  128. package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js.map +1 -1
  129. package/packages/pi-coding-agent/dist/core/resource-loader-cache-reset.test.js +76 -18
  130. package/packages/pi-coding-agent/dist/core/resource-loader-cache-reset.test.js.map +1 -1
  131. package/packages/pi-coding-agent/dist/core/retry-handler.d.ts.map +1 -1
  132. package/packages/pi-coding-agent/dist/core/retry-handler.js +2 -6
  133. package/packages/pi-coding-agent/dist/core/retry-handler.js.map +1 -1
  134. package/packages/pi-coding-agent/dist/core/retry-handler.test.js +5 -1
  135. package/packages/pi-coding-agent/dist/core/retry-handler.test.js.map +1 -1
  136. package/packages/pi-coding-agent/dist/core/retryable-error-regex.d.ts +18 -0
  137. package/packages/pi-coding-agent/dist/core/retryable-error-regex.d.ts.map +1 -0
  138. package/packages/pi-coding-agent/dist/core/retryable-error-regex.js +18 -0
  139. package/packages/pi-coding-agent/dist/core/retryable-error-regex.js.map +1 -0
  140. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts +20 -0
  141. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
  142. package/packages/pi-coding-agent/dist/core/system-prompt.js +16 -2
  143. package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
  144. package/packages/pi-coding-agent/dist/index.d.ts +1 -0
  145. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  146. package/packages/pi-coding-agent/dist/index.js +1 -0
  147. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  148. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js +36 -5
  149. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js.map +1 -1
  150. package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.test.js +20 -13
  151. package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.test.js.map +1 -1
  152. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  153. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +30 -12
  154. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  155. package/packages/pi-coding-agent/dist/tests/system-prompt-skill-filter.test.d.ts +2 -0
  156. package/packages/pi-coding-agent/dist/tests/system-prompt-skill-filter.test.d.ts.map +1 -0
  157. package/packages/pi-coding-agent/dist/tests/system-prompt-skill-filter.test.js +130 -0
  158. package/packages/pi-coding-agent/dist/tests/system-prompt-skill-filter.test.js.map +1 -0
  159. package/packages/pi-coding-agent/src/core/agent-session-abort-order.test.ts +113 -37
  160. package/packages/pi-coding-agent/src/core/agent-session-model-switch.test.ts +89 -17
  161. package/packages/pi-coding-agent/src/core/agent-session-tool-refresh.test.ts +112 -43
  162. package/packages/pi-coding-agent/src/core/extensions/loader.ts +58 -0
  163. package/packages/pi-coding-agent/src/core/lsp/lsp-integration.test.ts +35 -4
  164. package/packages/pi-coding-agent/src/core/model-registry-auth-mode.test.ts +20 -0
  165. package/packages/pi-coding-agent/src/core/resource-loader-cache-reset.test.ts +93 -28
  166. package/packages/pi-coding-agent/src/core/retry-handler.test.ts +5 -1
  167. package/packages/pi-coding-agent/src/core/retry-handler.ts +2 -8
  168. package/packages/pi-coding-agent/src/core/retryable-error-regex.ts +18 -0
  169. package/packages/pi-coding-agent/src/core/system-prompt.ts +35 -1
  170. package/packages/pi-coding-agent/src/index.ts +1 -0
  171. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-execution.test.ts +49 -3
  172. package/packages/pi-coding-agent/src/modes/interactive/components/dynamic-border.test.ts +26 -20
  173. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +48 -9
  174. package/packages/pi-coding-agent/src/tests/system-prompt-skill-filter.test.ts +157 -0
  175. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  176. package/packages/pi-tui/dist/__tests__/autocomplete.test.js +18 -8
  177. package/packages/pi-tui/dist/__tests__/autocomplete.test.js.map +1 -1
  178. package/packages/pi-tui/dist/__tests__/overlay-layout.test.js +128 -17
  179. package/packages/pi-tui/dist/__tests__/overlay-layout.test.js.map +1 -1
  180. package/packages/pi-tui/dist/__tests__/stdin-buffer.test.js +36 -12
  181. package/packages/pi-tui/dist/__tests__/stdin-buffer.test.js.map +1 -1
  182. package/packages/pi-tui/dist/__tests__/tui.test.js +18 -30
  183. package/packages/pi-tui/dist/__tests__/tui.test.js.map +1 -1
  184. package/packages/pi-tui/dist/components/__tests__/input.test.js +10 -3
  185. package/packages/pi-tui/dist/components/__tests__/input.test.js.map +1 -1
  186. package/packages/pi-tui/dist/components/__tests__/loader.test.js +53 -9
  187. package/packages/pi-tui/dist/components/__tests__/loader.test.js.map +1 -1
  188. package/packages/pi-tui/dist/components/__tests__/markdown-maxlines.test.js +6 -2
  189. package/packages/pi-tui/dist/components/__tests__/markdown-maxlines.test.js.map +1 -1
  190. package/packages/pi-tui/dist/components/image.test.js +6 -5
  191. package/packages/pi-tui/dist/components/image.test.js.map +1 -1
  192. package/packages/pi-tui/src/__tests__/autocomplete.test.ts +24 -8
  193. package/packages/pi-tui/src/__tests__/overlay-layout.test.ts +140 -17
  194. package/packages/pi-tui/src/__tests__/stdin-buffer.test.ts +41 -12
  195. package/packages/pi-tui/src/__tests__/tui.test.ts +18 -37
  196. package/packages/pi-tui/src/components/__tests__/input.test.ts +19 -3
  197. package/packages/pi-tui/src/components/__tests__/loader.test.ts +112 -35
  198. package/packages/pi-tui/src/components/__tests__/markdown-maxlines.test.ts +9 -2
  199. package/packages/pi-tui/src/components/image.test.ts +10 -5
  200. package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
  201. package/packages/rpc-client/dist/rpc-client.test.js +101 -51
  202. package/packages/rpc-client/dist/rpc-client.test.js.map +1 -1
  203. package/packages/rpc-client/src/rpc-client.test.ts +109 -52
  204. package/packages/rpc-client/tsconfig.tsbuildinfo +1 -1
  205. package/scripts/install.js +15 -1
  206. package/src/resources/extensions/browser-tools/capture.ts +12 -0
  207. package/src/resources/extensions/browser-tools/tests/browser-tools-integration.test.mjs +8 -59
  208. package/src/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +36 -24
  209. package/src/resources/extensions/browser-tools/tests/capture-sharp-optional.test.cjs +69 -71
  210. package/src/resources/extensions/browser-tools/tools/forms.ts +5 -1
  211. package/src/resources/extensions/browser-tools/tools/intent.ts +5 -1
  212. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +80 -72
  213. package/src/resources/extensions/github-sync/tests/cli.test.ts +76 -7
  214. package/src/resources/extensions/github-sync/tests/templates.test.ts +33 -1
  215. package/src/resources/extensions/gsd/auto/phases.ts +6 -17
  216. package/src/resources/extensions/gsd/auto/session.ts +7 -0
  217. package/src/resources/extensions/gsd/auto-dispatch.ts +40 -8
  218. package/src/resources/extensions/gsd/auto-post-unit.ts +81 -0
  219. package/src/resources/extensions/gsd/auto-prompts.ts +385 -93
  220. package/src/resources/extensions/gsd/auto-start.ts +97 -4
  221. package/src/resources/extensions/gsd/auto.ts +37 -0
  222. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +9 -1
  223. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +7 -1
  224. package/src/resources/extensions/gsd/component-loader.ts +598 -0
  225. package/src/resources/extensions/gsd/component-types.ts +362 -0
  226. package/src/resources/extensions/gsd/context-store.ts +25 -8
  227. package/src/resources/extensions/gsd/detection.ts +58 -1
  228. package/src/resources/extensions/gsd/docs/preferences-reference.md +1 -1
  229. package/src/resources/extensions/gsd/forensics.ts +118 -1
  230. package/src/resources/extensions/gsd/git-service.ts +16 -0
  231. package/src/resources/extensions/gsd/gsd-db.ts +1 -1
  232. package/src/resources/extensions/gsd/guided-flow.ts +2 -4
  233. package/src/resources/extensions/gsd/journal.ts +11 -1
  234. package/src/resources/extensions/gsd/memory-extractor.ts +11 -3
  235. package/src/resources/extensions/gsd/milestone-scope-classifier.ts +366 -0
  236. package/src/resources/extensions/gsd/model-cost-table.ts +3 -0
  237. package/src/resources/extensions/gsd/model-router.ts +6 -0
  238. package/src/resources/extensions/gsd/preferences-validation.ts +21 -0
  239. package/src/resources/extensions/gsd/prompt-cache-optimizer.ts +4 -0
  240. package/src/resources/extensions/gsd/prompts/complete-milestone.md +5 -1
  241. package/src/resources/extensions/gsd/prompts/plan-slice.md +15 -2
  242. package/src/resources/extensions/gsd/service-tier.ts +5 -2
  243. package/src/resources/extensions/gsd/skill-manifest.ts +175 -0
  244. package/src/resources/extensions/gsd/slice-cadence.ts +299 -0
  245. package/src/resources/extensions/gsd/tests/artifacts-table-preserved-on-cache-invalidate.test.ts +2 -1
  246. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +25 -292
  247. package/src/resources/extensions/gsd/tests/auto-remediate-slice-status.test.ts +4 -1
  248. package/src/resources/extensions/gsd/tests/auto-retry-mcp-churn-fixes.test.ts +8 -194
  249. package/src/resources/extensions/gsd/tests/auto-start-clean-runtime-db-gated.test.ts +3 -2
  250. package/src/resources/extensions/gsd/tests/auto-start-cold-db-bootstrap.test.ts +2 -2
  251. package/src/resources/extensions/gsd/tests/auto-start-needs-discussion.test.ts +15 -58
  252. package/src/resources/extensions/gsd/tests/auto-start-worktree-db-path.test.ts +2 -2
  253. package/src/resources/extensions/gsd/tests/auto-thinking-restore.test.ts +3 -2
  254. package/src/resources/extensions/gsd/tests/auto-warning-noise-regression.test.ts +3 -2
  255. package/src/resources/extensions/gsd/tests/bootstrap-derive-state-db-open.test.ts +2 -1
  256. package/src/resources/extensions/gsd/tests/cache-staleness-regression.test.ts +17 -21
  257. package/src/resources/extensions/gsd/tests/canonical-milestone-root.test.ts +108 -0
  258. package/src/resources/extensions/gsd/tests/complete-milestone-excerpt.test.ts +263 -0
  259. package/src/resources/extensions/gsd/tests/complete-slice-composer.test.ts +192 -0
  260. package/src/resources/extensions/gsd/tests/complete-slice-verification-gate.test.ts +2 -1
  261. package/src/resources/extensions/gsd/tests/complete-task.test.ts +8 -4
  262. package/src/resources/extensions/gsd/tests/component-loader.test.ts +589 -0
  263. package/src/resources/extensions/gsd/tests/component-types.test.ts +127 -0
  264. package/src/resources/extensions/gsd/tests/context-store.test.ts +79 -0
  265. package/src/resources/extensions/gsd/tests/copy-planning-artifacts-samepath.test.ts +2 -1
  266. package/src/resources/extensions/gsd/tests/discuss-slice-structured-questions.test.ts +2 -1
  267. package/src/resources/extensions/gsd/tests/discuss-tool-scope-leak.test.ts +2 -1
  268. package/src/resources/extensions/gsd/tests/double-merge-guard.test.ts +4 -3
  269. package/src/resources/extensions/gsd/tests/empty-content-abort-loop.test.ts +4 -3
  270. package/src/resources/extensions/gsd/tests/extension-bootstrap-isolation.test.ts +139 -129
  271. package/src/resources/extensions/gsd/tests/finalize-timeout-guard.test.ts +8 -104
  272. package/src/resources/extensions/gsd/tests/hook-key-parsing.test.ts +4 -55
  273. package/src/resources/extensions/gsd/tests/integration/all-milestones-complete-merge.test.ts +7 -56
  274. package/src/resources/extensions/gsd/tests/integration/doctor-proactive.test.ts +18 -2
  275. package/src/resources/extensions/gsd/tests/integration/queue-completed-milestone-perf.test.ts +10 -4
  276. package/src/resources/extensions/gsd/tests/integration/state-machine-edge-cases.test.ts +1 -1
  277. package/src/resources/extensions/gsd/tests/interactive-routing-bypass.test.ts +9 -3
  278. package/src/resources/extensions/gsd/tests/interrupted-session-ui.test.ts +6 -9
  279. package/src/resources/extensions/gsd/tests/knowledge.test.ts +93 -1
  280. package/src/resources/extensions/gsd/tests/mcp-client-security.test.ts +8 -37
  281. package/src/resources/extensions/gsd/tests/memory-extractor.test.ts +5 -15
  282. package/src/resources/extensions/gsd/tests/merge-conflict-stops-loop.test.ts +227 -55
  283. package/src/resources/extensions/gsd/tests/milestone-scope-classifier.test.ts +187 -0
  284. package/src/resources/extensions/gsd/tests/model-cost-table.test.ts +9 -1
  285. package/src/resources/extensions/gsd/tests/model-router.test.ts +1 -1
  286. package/src/resources/extensions/gsd/tests/native-git-bridge-exec-fallback.test.ts +6 -48
  287. package/src/resources/extensions/gsd/tests/notification-widget.test.ts +6 -3
  288. package/src/resources/extensions/gsd/tests/orphaned-worktree-audit.test.ts +59 -2
  289. package/src/resources/extensions/gsd/tests/parallel-research-dispatch.test.ts +273 -130
  290. package/src/resources/extensions/gsd/tests/pipeline-variant-dispatch.test.ts +301 -0
  291. package/src/resources/extensions/gsd/tests/preferences-worktree-sync.test.ts +2 -1
  292. package/src/resources/extensions/gsd/tests/prompt-cache-optimizer.test.ts +12 -0
  293. package/src/resources/extensions/gsd/tests/prompt-step-ordering.test.ts +15 -4
  294. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +22 -16
  295. package/src/resources/extensions/gsd/tests/queue-draft-detection.test.ts +3 -2
  296. package/src/resources/extensions/gsd/tests/queued-discuss-fast-path.test.ts +4 -5
  297. package/src/resources/extensions/gsd/tests/reassess-default-optin.test.ts +132 -0
  298. package/src/resources/extensions/gsd/tests/recovery-attempts-reset.test.ts +8 -40
  299. package/src/resources/extensions/gsd/tests/regex-hardening.test.ts +136 -256
  300. package/src/resources/extensions/gsd/tests/research-milestone-composer.test.ts +114 -0
  301. package/src/resources/extensions/gsd/tests/restore-tools-after-discuss.test.ts +6 -3
  302. package/src/resources/extensions/gsd/tests/run-uat-composer.test.ts +148 -0
  303. package/src/resources/extensions/gsd/tests/service-tier.test.ts +4 -0
  304. package/src/resources/extensions/gsd/tests/sidecar-queue.test.ts +3 -2
  305. package/src/resources/extensions/gsd/tests/silent-catch-diagnostics.test.ts +55 -95
  306. package/src/resources/extensions/gsd/tests/skill-activation.test.ts +120 -1
  307. package/src/resources/extensions/gsd/tests/skill-manifest.test.ts +112 -0
  308. package/src/resources/extensions/gsd/tests/slice-cadence.test.ts +242 -0
  309. package/src/resources/extensions/gsd/tests/slice-context-injection.test.ts +3 -2
  310. package/src/resources/extensions/gsd/tests/smart-entry-draft.test.ts +2 -1
  311. package/src/resources/extensions/gsd/tests/stop-auto-race-null-unit.test.ts +3 -3
  312. package/src/resources/extensions/gsd/tests/structured-data-formatter.test.ts +11 -92
  313. package/src/resources/extensions/gsd/tests/subagent-model-dispatch.test.ts +7 -6
  314. package/src/resources/extensions/gsd/tests/survivor-branch-complete.test.ts +102 -101
  315. package/src/resources/extensions/gsd/tests/sync-worktree-skip-current.test.ts +4 -3
  316. package/src/resources/extensions/gsd/tests/test-helpers.test.ts +98 -0
  317. package/src/resources/extensions/gsd/tests/test-helpers.ts +153 -0
  318. package/src/resources/extensions/gsd/tests/token-profile.test.ts +8 -1
  319. package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +355 -0
  320. package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +203 -0
  321. package/src/resources/extensions/gsd/tests/uok-gitops-wiring.test.ts +49 -26
  322. package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +1 -0
  323. package/src/resources/extensions/gsd/tests/verify-artifact-tightened.test.ts +144 -80
  324. package/src/resources/extensions/gsd/tests/visualizer-critical-path.test.ts +20 -54
  325. package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +342 -277
  326. package/src/resources/extensions/gsd/tests/worker-model-override.test.ts +37 -29
  327. package/src/resources/extensions/gsd/tests/worktree-db.test.ts +226 -266
  328. package/src/resources/extensions/gsd/tests/worktree-health-monorepo.test.ts +103 -67
  329. package/src/resources/extensions/gsd/tests/worktree-nested-git-safety.test.ts +92 -90
  330. package/src/resources/extensions/gsd/tests/worktree-submodule-safety.test.ts +238 -59
  331. package/src/resources/extensions/gsd/tests/worktree-sync-overwrite-loop.test.ts +113 -161
  332. package/src/resources/extensions/gsd/tests/worktree-telemetry.test.ts +210 -0
  333. package/src/resources/extensions/gsd/tests/zombie-gsd-state.test.ts +80 -96
  334. package/src/resources/extensions/gsd/tools/validate-milestone.ts +8 -2
  335. package/src/resources/extensions/gsd/unit-context-composer.ts +218 -0
  336. package/src/resources/extensions/gsd/unit-context-manifest.ts +492 -0
  337. package/src/resources/extensions/gsd/worktree-manager.ts +53 -0
  338. package/src/resources/extensions/gsd/worktree-resolver.ts +96 -9
  339. package/src/resources/extensions/gsd/worktree-telemetry.ts +322 -0
  340. package/src/resources/extensions/mcp-client/index.ts +3 -1
  341. package/src/resources/extensions/mcp-client/tests/server-name-spaces.test.ts +70 -36
  342. package/src/resources/extensions/ollama/index.ts +5 -1
  343. package/src/resources/extensions/ollama/ollama-auth-mode.test.ts +123 -15
  344. package/src/resources/extensions/ollama/ollama-status-indicator.test.ts +206 -19
  345. package/src/resources/extensions/remote-questions/manager.ts +36 -4
  346. package/src/resources/extensions/remote-questions/tests/command-polling.test.ts +200 -190
  347. package/src/resources/extensions/shared/tests/interview-preview.test.ts +11 -3
  348. package/src/resources/extensions/voice/tests/linux-ready.test.ts +129 -113
  349. package/packages/pi-ai/dist/utils/oauth/oauth-providers.test.d.ts +0 -2
  350. package/packages/pi-ai/dist/utils/oauth/oauth-providers.test.d.ts.map +0 -1
  351. package/packages/pi-ai/dist/utils/oauth/oauth-providers.test.js +0 -289
  352. package/packages/pi-ai/dist/utils/oauth/oauth-providers.test.js.map +0 -1
  353. package/packages/pi-ai/src/utils/oauth/oauth-providers.test.ts +0 -363
  354. package/src/resources/extensions/gsd/tests/auto-start-model-capture.test.ts +0 -143
  355. package/src/resources/extensions/gsd/tests/complete-milestone-false-merge.test.ts +0 -157
  356. package/src/resources/extensions/gsd/tests/dashboard-model-label-ordering.test.ts +0 -107
  357. package/src/resources/extensions/gsd/tests/find-missing-summaries-closed.test.ts +0 -48
  358. package/src/resources/extensions/gsd/tests/forensics-context-persist.test.ts +0 -159
  359. package/src/resources/extensions/gsd/tests/forensics-db-completion.test.ts +0 -96
  360. package/src/resources/extensions/gsd/tests/forensics-dedup.test.ts +0 -79
  361. package/src/resources/extensions/gsd/tests/forensics-hook-key-parse.test.ts +0 -74
  362. package/src/resources/extensions/gsd/tests/forensics-journal.test.ts +0 -162
  363. package/src/resources/extensions/gsd/tests/gitignore-bg-shell.test.ts +0 -38
  364. package/src/resources/extensions/gsd/tests/gsd-no-project-error.test.ts +0 -73
  365. package/src/resources/extensions/gsd/tests/idle-watchdog-stall-override.test.ts +0 -125
  366. package/src/resources/extensions/gsd/tests/import-done-milestones.test.ts +0 -42
  367. /package/dist/web/standalone/.next/static/{vidAVJkURvTJ0_V2-64ro → gYYky7yfxW8txb9vU2TrJ}/_buildManifest.js +0 -0
  368. /package/dist/web/standalone/.next/static/{vidAVJkURvTJ0_V2-64ro → gYYky7yfxW8txb9vU2TrJ}/_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,203 @@
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
+ // ─── Budget floor: run-uat + gate-evaluate hit the smallest budget tier ──
191
+
192
+ test("#4782 phase 2: run-uat and gate-evaluate use the smallest budget tier", () => {
193
+ const uatBudget = UNIT_MANIFESTS["run-uat"].maxSystemPromptChars;
194
+ const gateBudget = UNIT_MANIFESTS["gate-evaluate"].maxSystemPromptChars;
195
+ assert.strictEqual(uatBudget, gateBudget, "run-uat and gate-evaluate both use COMMON_BUDGET_SMALL");
196
+ // They should be the tightest (or tied for tightest) across all manifests
197
+ for (const [unitType, other] of Object.entries(UNIT_MANIFESTS)) {
198
+ assert.ok(
199
+ uatBudget <= other.maxSystemPromptChars,
200
+ `run-uat budget (${uatBudget}) should be ≤ ${unitType} budget (${other.maxSystemPromptChars})`,
201
+ );
202
+ }
203
+ });
@@ -1,35 +1,58 @@
1
+ /**
2
+ * UOK gitops wiring — post-unit pre-verification policy.
3
+ *
4
+ * Tests the pure policy bit that selects the turn-level git action from
5
+ * resolved UOK flags. The integration wiring (postUnitPreVerification
6
+ * calling runTurnGitAction, writeTurnGitTransaction, the UokGateRunner
7
+ * closeout pathway, and buildSnapshotOpts' trace/turn ID plumbing) is
8
+ * exercised end-to-end by the auto-loop and UOK kernel integration tests;
9
+ * the earlier source-grep assertions duplicated that coverage without
10
+ * actually exercising the code, and have been removed.
11
+ */
12
+
1
13
  import test from "node:test";
2
14
  import assert from "node:assert/strict";
3
- import { readFileSync } from "node:fs";
4
- import { join, dirname } from "node:path";
5
- import { fileURLToPath } from "node:url";
6
15
 
7
- const __dirname = dirname(fileURLToPath(import.meta.url));
8
- const gsdDir = join(__dirname, "..");
16
+ import { resolveUokFlags } from "../uok/flags.ts";
17
+
18
+ test("turn action defaults to commit when uok.gitops is enabled with no override", () => {
19
+ const flags = resolveUokFlags({ uok: { gitops: { enabled: true } } } as any);
20
+ assert.equal(flags.gitops, true);
21
+ assert.equal(flags.gitopsTurnAction, "commit");
22
+ });
23
+
24
+ test("turn action reflects uok.gitops.turn_action when set to snapshot", () => {
25
+ const flags = resolveUokFlags({
26
+ uok: { gitops: { enabled: true, turn_action: "snapshot" } },
27
+ } as any);
28
+ assert.equal(flags.gitops, true);
29
+ assert.equal(flags.gitopsTurnAction, "snapshot");
30
+ });
9
31
 
10
- test("post-unit pre-verification selects turn git action from UOK gitops flags", () => {
11
- const source = readFileSync(join(gsdDir, "auto-post-unit.ts"), "utf-8");
12
- assert.ok(
13
- source.includes("const turnAction: TurnGitActionMode = uokFlags.gitops ? uokFlags.gitopsTurnAction : \"commit\""),
14
- "postUnitPreVerification should derive turn action from uok.gitops.turn_action when enabled",
15
- );
32
+ test("turn action reflects uok.gitops.turn_action when set to status-only", () => {
33
+ const flags = resolveUokFlags({
34
+ uok: { gitops: { enabled: true, turn_action: "status-only" } },
35
+ } as any);
36
+ assert.equal(flags.gitops, true);
37
+ assert.equal(flags.gitopsTurnAction, "status-only");
16
38
  });
17
39
 
18
- test("post-unit pre-verification routes git failures through closeout gate", () => {
19
- const source = readFileSync(join(gsdDir, "auto-post-unit.ts"), "utf-8");
20
- assert.ok(
21
- source.includes('id: "closeout-git-action"') &&
22
- source.includes('type: "closeout"') &&
23
- source.includes('failureClass: "git"'),
24
- "git failures should be persisted via a closeout gate with failureClass=git",
25
- );
40
+ test("turn_push flag round-trips through resolveUokFlags", () => {
41
+ const on = resolveUokFlags({
42
+ uok: { gitops: { enabled: true, turn_push: true } },
43
+ } as any);
44
+ const off = resolveUokFlags({
45
+ uok: { gitops: { enabled: true, turn_push: false } },
46
+ } as any);
47
+ assert.equal(on.gitopsTurnPush, true);
48
+ assert.equal(off.gitopsTurnPush, false);
26
49
  });
27
50
 
28
- test("auto snapshot opts carry trace/turn IDs for turn closeout records", () => {
29
- const source = readFileSync(join(gsdDir, "auto.ts"), "utf-8");
30
- assert.ok(
31
- source.includes("traceId: s.currentTraceId ?? undefined") &&
32
- source.includes("turnId: s.currentTurnId ?? undefined"),
33
- "buildSnapshotOpts should pass trace/turn IDs into closeout options",
34
- );
51
+ test("gitops disabled when uok.gitops.enabled is explicitly false", () => {
52
+ const flags = resolveUokFlags({
53
+ uok: { gitops: { enabled: false, turn_action: "snapshot" } },
54
+ } as any);
55
+ assert.equal(flags.gitops, false);
56
+ // Turn_action is still surfaced so callers can read their policy cleanly.
57
+ assert.equal(flags.gitopsTurnAction, "snapshot");
35
58
  });
@@ -271,6 +271,7 @@ Test
271
271
  assert.match(prompt, /S01 Summary/i, "prompt should inline non-skipped slice summaries");
272
272
  assert.doesNotMatch(prompt, /### S02 Summary/i, "prompt should not inline skipped slice summaries");
273
273
  assert.doesNotMatch(prompt, /not found — file does not exist yet/i, "prompt should not emit skipped-slice missing-file placeholders");
274
+ assert.doesNotMatch(prompt, /S02-SUMMARY\.md/, "skipped slice must not appear in on-demand path list (#4780)");
274
275
  } finally {
275
276
  cleanup(base);
276
277
  }