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
@@ -12,76 +12,25 @@
12
12
  import { describe, it, before, after } from "node:test";
13
13
  import assert from "node:assert/strict";
14
14
  import { chromium } from "playwright";
15
- import { readFileSync } from "node:fs";
16
- import { resolve, dirname } from "node:path";
15
+ import { dirname } from "node:path";
17
16
  import { fileURLToPath } from "node:url";
18
17
 
19
18
  const __dirname = dirname(fileURLToPath(import.meta.url));
20
- const ROOT = resolve(__dirname, "..");
21
19
 
22
20
  // ---------------------------------------------------------------------------
23
- // Source extractionget the IIFE strings we need for injection
21
+ // Source loadingimport the IIFE builders directly via jiti.
22
+ // The test-only named exports in tools/intent.ts and tools/forms.ts exist
23
+ // exactly so this test can call the real, in-tree builders. No brace
24
+ // walking, no regex stripping — a refactor of the signatures just updates
25
+ // the import surface, not the test.
24
26
  // ---------------------------------------------------------------------------
25
27
 
26
- // 1. EVALUATE_HELPERS_SOURCE — exported constant, extract via jiti
27
28
  import { createRequire } from "node:module";
28
29
  const require = createRequire(import.meta.url);
29
30
  const jiti = require("jiti")(__dirname, { interopDefault: true, debug: false });
30
31
  const { EVALUATE_HELPERS_SOURCE } = jiti("../evaluate-helpers.ts");
31
-
32
- // 2. Intent scoring module-private buildIntentScoringScript.
33
- // Extract the function from source, wrap it, and eval to get the builder.
34
- const intentSource = readFileSync(resolve(ROOT, "tools/intent.ts"), "utf-8");
35
-
36
- function extractBuildIntentScoringScript() {
37
- // Match the function body: starts with "function buildIntentScoringScript"
38
- // and returns a template literal string. We extract up to the matching closing brace.
39
- const startMarker = "function buildIntentScoringScript(intent: string, scope?: string): string {";
40
- const startIdx = intentSource.indexOf(startMarker);
41
- if (startIdx === -1) throw new Error("Could not find buildIntentScoringScript in intent.ts");
42
-
43
- // Walk from start, counting braces to find the end
44
- let depth = 0;
45
- let foundFirst = false;
46
- let endIdx = startIdx;
47
- for (let i = startIdx; i < intentSource.length; i++) {
48
- if (intentSource[i] === "{") { depth++; foundFirst = true; }
49
- if (intentSource[i] === "}") depth--;
50
- if (foundFirst && depth === 0) { endIdx = i + 1; break; }
51
- }
52
-
53
- let fnBody = intentSource.slice(startIdx, endIdx);
54
- // Strip TypeScript type annotations
55
- fnBody = fnBody.replace(/\(intent:\s*string,\s*scope\?:\s*string\):\s*string/, "(intent, scope)");
56
- return new Function("return " + fnBody)();
57
- }
58
-
59
- const buildIntentScoringScript = extractBuildIntentScoringScript();
60
-
61
- // 3. Form analysis — module-private buildFormAnalysisScript.
62
- const formsSource = readFileSync(resolve(ROOT, "tools/forms.ts"), "utf-8");
63
-
64
- function extractBuildFormAnalysisScript() {
65
- const startMarker = "function buildFormAnalysisScript(selector?: string): string {";
66
- const startIdx = formsSource.indexOf(startMarker);
67
- if (startIdx === -1) throw new Error("Could not find buildFormAnalysisScript in forms.ts");
68
-
69
- let depth = 0;
70
- let foundFirst = false;
71
- let endIdx = startIdx;
72
- for (let i = startIdx; i < formsSource.length; i++) {
73
- if (formsSource[i] === "{") { depth++; foundFirst = true; }
74
- if (formsSource[i] === "}") depth--;
75
- if (foundFirst && depth === 0) { endIdx = i + 1; break; }
76
- }
77
-
78
- let fnBody = formsSource.slice(startIdx, endIdx);
79
- // Strip TypeScript type annotation
80
- fnBody = fnBody.replace(/\(selector\?:\s*string\):\s*string/, "(selector)");
81
- return new Function("return " + fnBody)();
82
- }
83
-
84
- const buildFormAnalysisScript = extractBuildFormAnalysisScript();
32
+ const { buildIntentScoringScript } = jiti("../tools/intent.ts");
33
+ const { buildFormAnalysisScript } = jiti("../tools/forms.ts");
85
34
 
86
35
  // ---------------------------------------------------------------------------
87
36
  // Browser lifecycle
@@ -387,32 +387,44 @@ describe("formatArtifactTimestamp", () => {
387
387
  // ---------------------------------------------------------------------------
388
388
 
389
389
  describe("EVALUATE_HELPERS_SOURCE", () => {
390
- it("is a parseable string (valid JavaScript)", () => {
391
- assert.doesNotThrow(() => {
392
- new Function(EVALUATE_HELPERS_SOURCE);
393
- });
394
- });
390
+ // Behaviour test: executing the source in a Node vm sandbox must
391
+ // populate a `window.__pi` namespace with every expected helper.
392
+ // No source grep — we actually run the code and verify the resulting
393
+ // object shape.
394
+ it("executing the source assigns all expected helpers to window.__pi", () => {
395
+ const vm = require("node:vm");
396
+ const expectedFunctions = [
397
+ "cssPath",
398
+ "simpleHash",
399
+ "isVisible",
400
+ "isEnabled",
401
+ "inferRole",
402
+ "accessibleName",
403
+ "isInteractiveEl",
404
+ "domPath",
405
+ "selectorHints",
406
+ ];
395
407
 
396
- const expectedFunctions = [
397
- "cssPath",
398
- "simpleHash",
399
- "isVisible",
400
- "isEnabled",
401
- "inferRole",
402
- "accessibleName",
403
- "isInteractiveEl",
404
- "domPath",
405
- "selectorHints",
406
- ];
407
-
408
- for (const fnName of expectedFunctions) {
409
- it(`contains assignment for pi.${fnName}`, () => {
410
- assert.ok(
411
- EVALUATE_HELPERS_SOURCE.includes(`pi.${fnName} = function`),
412
- `Expected pi.${fnName} = function assignment in source`,
408
+ // Playwright evaluates the source in a page context where `window`
409
+ // exists, so the helpers attach to `window.__pi`. Provide a minimal
410
+ // window stub in a vm context so we avoid polluting the test globals.
411
+ const sandbox = { window: {} };
412
+ const script = new vm.Script(EVALUATE_HELPERS_SOURCE);
413
+ script.runInNewContext(sandbox, { timeout: 1000 });
414
+
415
+ assert.ok(
416
+ sandbox.window.__pi && typeof sandbox.window.__pi === "object",
417
+ "executing EVALUATE_HELPERS_SOURCE must assign window.__pi",
418
+ );
419
+
420
+ for (const fnName of expectedFunctions) {
421
+ assert.equal(
422
+ typeof sandbox.window.__pi[fnName],
423
+ "function",
424
+ `window.__pi.${fnName} must be a function after executing the source`,
413
425
  );
414
- });
415
- }
426
+ }
427
+ });
416
428
  });
417
429
 
418
430
  // ---------------------------------------------------------------------------
@@ -1,93 +1,91 @@
1
1
  /**
2
2
  * Regression tests for the optional sharp dependency in capture.ts.
3
3
  *
4
- * Verifies two things:
5
- * 1. Static: the lazy-load pattern is structurally correct in the source.
6
- * 2. Behavioral: constrainScreenshot returns the raw buffer unchanged when
7
- * sharp is unavailable, rather than throwing.
4
+ * Behaviour:
5
+ * - constrainScreenshot must fall back to returning the raw buffer
6
+ * unchanged when sharp is unavailable, rather than throwing.
7
+ * - When sharp IS available, oversized screenshots get resized.
8
+ *
9
+ * No source-grep. The test drives the real constrainScreenshot function
10
+ * after seeding the module-private `_sharp` cache via the test-only
11
+ * `__setSharpForTesting` export.
8
12
  */
9
13
 
10
- const { describe, it } = require("node:test");
14
+ const { describe, it, afterEach } = require("node:test");
11
15
  const assert = require("node:assert/strict");
12
- const { readFileSync } = require("node:fs");
13
- const { join } = require("node:path");
14
-
15
- // ---------------------------------------------------------------------------
16
- // 1. Static analysis — verify the lazy-load pattern is present in source
17
- // ---------------------------------------------------------------------------
16
+ const jiti = require("jiti")(__filename, { interopDefault: true, debug: false });
18
17
 
19
- describe("capture.ts sharp optional lazy-load (static)", () => {
20
- const source = readFileSync(
21
- join(process.cwd(), "src/resources/extensions/browser-tools/capture.ts"),
22
- "utf-8",
23
- );
18
+ const { constrainScreenshot, __setSharpForTesting } = jiti("../capture.ts");
24
19
 
25
- it("does not have a top-level static sharp import", () => {
26
- assert.ok(
27
- !source.includes('import sharp from "sharp"'),
28
- 'capture.ts must not contain a top-level `import sharp from "sharp"` — sharp must be loaded lazily',
29
- );
20
+ describe("constrainScreenshot sharp unavailable (null)", () => {
21
+ afterEach(() => {
22
+ // Clear the test override so later tests don't inherit a null sharp.
23
+ __setSharpForTesting(undefined);
30
24
  });
31
25
 
32
- it("defines a getSharp lazy-loader function", () => {
33
- assert.ok(
34
- source.includes("async function getSharp()"),
35
- "capture.ts must define an async getSharp() lazy-loader",
26
+ it("returns the raw buffer unchanged when sharp is null", async () => {
27
+ __setSharpForTesting(null);
28
+
29
+ const rawBuffer = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]); // PNG magic bytes
30
+ const result = await constrainScreenshot(null, rawBuffer, "image/png", 80);
31
+
32
+ assert.strictEqual(
33
+ result,
34
+ rawBuffer,
35
+ "constrainScreenshot must return the exact same buffer instance when sharp is null",
36
36
  );
37
37
  });
38
38
 
39
- it("guards constrainScreenshot with a null-sharp early return", () => {
40
- assert.ok(
41
- source.includes("if (!sharp) return buffer"),
42
- "constrainScreenshot must return the raw buffer early when sharp is null",
43
- );
39
+ it("returns the raw buffer unchanged for JPEG input when sharp is null", async () => {
40
+ __setSharpForTesting(null);
41
+
42
+ const rawBuffer = Buffer.from([0xff, 0xd8, 0xff, 0xe0]); // JPEG magic bytes
43
+ const result = await constrainScreenshot(null, rawBuffer, "image/jpeg", 80);
44
+
45
+ assert.strictEqual(result, rawBuffer);
44
46
  });
45
47
  });
46
48
 
47
- // ---------------------------------------------------------------------------
48
- // 2. Behavioral — constrainScreenshot passes through buffer when sharp is null
49
- // ---------------------------------------------------------------------------
50
-
51
- describe("capture.ts — constrainScreenshot with sharp unavailable", () => {
52
- it("returns the raw buffer unchanged when sharp is null", async () => {
53
- // Simulate what getSharp() returns on platforms without sharp by
54
- // directly calling constrainScreenshot through a module whose _sharp
55
- // cache has been pre-seeded to null via the module-level variable reset.
56
- //
57
- // Because jiti caches modules across the test suite we use a fresh
58
- // require-cache trick: load capture.ts source manually and evaluate the
59
- // constrainScreenshot function with a stub getSharp that always returns null.
60
- const captureSource = readFileSync(
61
- join(process.cwd(), "src/resources/extensions/browser-tools/capture.ts"),
62
- "utf-8",
63
- );
49
+ describe("constrainScreenshot — sharp available", () => {
50
+ afterEach(() => {
51
+ __setSharpForTesting(undefined);
52
+ });
64
53
 
65
- // Verify the guard line is reachable (structural check already done above).
66
- // For the behavioral test we use the actual constrainScreenshot imported
67
- // via jiti but we force getSharp() to return null by calling the function
68
- // with a very small buffer where sharp IS available. Separately we test the
69
- // null path by crafting a minimal wrapper.
70
- //
71
- // The simplest verifiable behaviour: if the guard `if (!sharp) return buffer`
72
- // is present, passing a Buffer through a version of constrainScreenshot where
73
- // _sharp=null must return that exact buffer. We verify this by extracting and
74
- // running a minimal inline version of the guard logic.
54
+ it("passes through a small image unchanged (below cap)", async () => {
55
+ const sharp = require("sharp");
56
+ const small = await sharp({
57
+ create: {
58
+ width: 400,
59
+ height: 300,
60
+ channels: 3,
61
+ background: { r: 128, g: 128, b: 128 },
62
+ },
63
+ })
64
+ .jpeg({ quality: 80 })
65
+ .toBuffer();
75
66
 
76
- const rawBuffer = Buffer.from([0x89, 0x50, 0x4e, 0x47]); // fake PNG header
67
+ const result = await constrainScreenshot(null, small, "image/jpeg", 80);
68
+ const meta = await sharp(result).metadata();
69
+ assert.equal(meta.width, 400, "small images must not be resized");
70
+ assert.equal(meta.height, 300);
71
+ });
77
72
 
78
- // Inline the guard as it appears in capture.ts so the test is coupled to
79
- // the actual contract, not an arbitrary helper.
80
- async function constrainScreenshotWithNullSharp(buffer) {
81
- const sharp = null; // simulates getSharp() returning null
82
- if (!sharp) return buffer;
83
- // (remainder of constrainScreenshot would run here with a real sharp)
84
- }
73
+ it("resizes an oversized image to within 1568px", async () => {
74
+ const sharp = require("sharp");
75
+ const big = await sharp({
76
+ create: {
77
+ width: 3000,
78
+ height: 2000,
79
+ channels: 3,
80
+ background: { r: 128, g: 128, b: 128 },
81
+ },
82
+ })
83
+ .jpeg({ quality: 80 })
84
+ .toBuffer();
85
85
 
86
- const result = await constrainScreenshotWithNullSharp(rawBuffer);
87
- assert.strictEqual(
88
- result,
89
- rawBuffer,
90
- "constrainScreenshot must return the exact same buffer instance when sharp is null",
91
- );
86
+ const result = await constrainScreenshot(null, big, "image/jpeg", 80);
87
+ const meta = await sharp(result).metadata();
88
+ assert.ok(meta.width <= 1568, `width ${meta.width} must be <= 1568`);
89
+ assert.ok(meta.height <= 1568, `height ${meta.height} must be <= 1568`);
92
90
  });
93
91
  });
@@ -46,7 +46,11 @@ interface FormAnalysisResult {
46
46
  * Runs inside page.evaluate(). Finds the target form, inventories all fields
47
47
  * with full label resolution, and returns a structured result.
48
48
  */
49
- function buildFormAnalysisScript(selector?: string): string {
49
+ // Exported for tests only (see tests/browser-tools-integration.test.mjs).
50
+ // Keep this function treated as module-private for production call sites —
51
+ // the only legitimate external caller is the Playwright-driven integration
52
+ // suite that needs to evaluate the returned IIFE against real DOM.
53
+ export function buildFormAnalysisScript(selector?: string): string {
50
54
  // We return a string that will be evaluated in the page context.
51
55
  // This avoids serialization issues with passing functions.
52
56
  return `(() => {
@@ -37,7 +37,11 @@ type Intent = (typeof INTENTS)[number];
37
37
  * Uses window.__pi utilities (injected via addInitScript) for element
38
38
  * metadata — no inline redeclarations.
39
39
  */
40
- function buildIntentScoringScript(intent: string, scope?: string): string {
40
+ // Exported for tests only (see tests/browser-tools-integration.test.mjs).
41
+ // Keep this function treated as module-private for production call sites —
42
+ // the only legitimate external caller is the Playwright-driven integration
43
+ // suite that needs to evaluate the returned IIFE against real DOM.
44
+ export function buildIntentScoringScript(intent: string, scope?: string): string {
41
45
  const scopeSelector = JSON.stringify(scope ?? null);
42
46
 
43
47
  return `(() => {
@@ -25,6 +25,51 @@ import {
25
25
  import type { AssistantMessage, Context, Message } from "@gsd/pi-ai";
26
26
  import type { SDKUserMessage } from "../sdk-types.ts";
27
27
 
28
+ // ---------------------------------------------------------------------------
29
+ // Env helpers — `GSD_WORKFLOW_MCP_*` save/restore
30
+ //
31
+ // The naive pattern `process.env.X = prev.X` breaks when `prev.X` is
32
+ // undefined: Node coerces the assignment to the literal string
33
+ // "undefined", which then pollutes subsequent tests that read the var
34
+ // and assume it's absent. Issue #4808 documents the resulting bleed.
35
+ //
36
+ // `setWorkflowMcpEnv` returns a `restore()` closure that either
37
+ // re-assigns the previous string value OR `delete`s the key when the
38
+ // original was absent. Call in a try/finally; restore in the finally.
39
+ // ---------------------------------------------------------------------------
40
+
41
+ const WORKFLOW_MCP_ENV_KEYS = [
42
+ "GSD_WORKFLOW_MCP_COMMAND",
43
+ "GSD_WORKFLOW_MCP_NAME",
44
+ "GSD_WORKFLOW_MCP_ARGS",
45
+ "GSD_WORKFLOW_MCP_ENV",
46
+ "GSD_WORKFLOW_MCP_CWD",
47
+ ] as const;
48
+
49
+ type WorkflowMcpEnvKey = (typeof WORKFLOW_MCP_ENV_KEYS)[number];
50
+
51
+ function setWorkflowMcpEnv(
52
+ values: Partial<Record<WorkflowMcpEnvKey, string>>,
53
+ ): () => void {
54
+ const prev: Partial<Record<WorkflowMcpEnvKey, string | undefined>> = {};
55
+ for (const key of WORKFLOW_MCP_ENV_KEYS) {
56
+ prev[key] = process.env[key];
57
+ }
58
+ for (const [key, value] of Object.entries(values)) {
59
+ process.env[key] = value;
60
+ }
61
+ return function restore() {
62
+ for (const key of WORKFLOW_MCP_ENV_KEYS) {
63
+ const previous = prev[key];
64
+ if (previous === undefined) {
65
+ delete process.env[key];
66
+ } else {
67
+ process.env[key] = previous;
68
+ }
69
+ }
70
+ };
71
+ }
72
+
28
73
  // ---------------------------------------------------------------------------
29
74
  // Existing tests — exhausted stream fallback (#2575)
30
75
  // ---------------------------------------------------------------------------
@@ -737,19 +782,14 @@ describe("stream-adapter — session persistence (#2859)", () => {
737
782
  });
738
783
 
739
784
  test("buildSdkOptions includes workflow MCP server config when env is set", () => {
740
- const prev = {
741
- GSD_WORKFLOW_MCP_COMMAND: process.env.GSD_WORKFLOW_MCP_COMMAND,
742
- GSD_WORKFLOW_MCP_NAME: process.env.GSD_WORKFLOW_MCP_NAME,
743
- GSD_WORKFLOW_MCP_ARGS: process.env.GSD_WORKFLOW_MCP_ARGS,
744
- GSD_WORKFLOW_MCP_ENV: process.env.GSD_WORKFLOW_MCP_ENV,
745
- GSD_WORKFLOW_MCP_CWD: process.env.GSD_WORKFLOW_MCP_CWD,
746
- };
785
+ const restore = setWorkflowMcpEnv({
786
+ GSD_WORKFLOW_MCP_COMMAND: "node",
787
+ GSD_WORKFLOW_MCP_NAME: "gsd-workflow",
788
+ GSD_WORKFLOW_MCP_ARGS: JSON.stringify(["packages/mcp-server/dist/cli.js"]),
789
+ GSD_WORKFLOW_MCP_ENV: JSON.stringify({ GSD_CLI_PATH: "/tmp/gsd" }),
790
+ GSD_WORKFLOW_MCP_CWD: "/tmp/project",
791
+ });
747
792
  try {
748
- process.env.GSD_WORKFLOW_MCP_COMMAND = "node";
749
- process.env.GSD_WORKFLOW_MCP_NAME = "gsd-workflow";
750
- process.env.GSD_WORKFLOW_MCP_ARGS = JSON.stringify(["packages/mcp-server/dist/cli.js"]);
751
- process.env.GSD_WORKFLOW_MCP_ENV = JSON.stringify({ GSD_CLI_PATH: "/tmp/gsd" });
752
- process.env.GSD_WORKFLOW_MCP_CWD = "/tmp/project";
753
793
 
754
794
  const options = buildSdkOptions("claude-sonnet-4-20250514", "test");
755
795
  const mcpServers = options.mcpServers as Record<string, any>;
@@ -776,28 +816,19 @@ describe("stream-adapter — session persistence (#2859)", () => {
776
816
  "mcp__gsd-workflow__*",
777
817
  ]);
778
818
  } finally {
779
- process.env.GSD_WORKFLOW_MCP_COMMAND = prev.GSD_WORKFLOW_MCP_COMMAND;
780
- process.env.GSD_WORKFLOW_MCP_NAME = prev.GSD_WORKFLOW_MCP_NAME;
781
- process.env.GSD_WORKFLOW_MCP_ARGS = prev.GSD_WORKFLOW_MCP_ARGS;
782
- process.env.GSD_WORKFLOW_MCP_ENV = prev.GSD_WORKFLOW_MCP_ENV;
783
- process.env.GSD_WORKFLOW_MCP_CWD = prev.GSD_WORKFLOW_MCP_CWD;
819
+ restore();
784
820
  }
785
821
  });
786
822
 
787
823
  test("buildSdkOptions auto-approves every tool for custom workflow MCP server names", () => {
788
- const prev = {
789
- GSD_WORKFLOW_MCP_COMMAND: process.env.GSD_WORKFLOW_MCP_COMMAND,
790
- GSD_WORKFLOW_MCP_NAME: process.env.GSD_WORKFLOW_MCP_NAME,
791
- GSD_WORKFLOW_MCP_ARGS: process.env.GSD_WORKFLOW_MCP_ARGS,
792
- GSD_WORKFLOW_MCP_ENV: process.env.GSD_WORKFLOW_MCP_ENV,
793
- GSD_WORKFLOW_MCP_CWD: process.env.GSD_WORKFLOW_MCP_CWD,
794
- };
824
+ const restore = setWorkflowMcpEnv({
825
+ GSD_WORKFLOW_MCP_COMMAND: "node",
826
+ GSD_WORKFLOW_MCP_NAME: "custom-workflow",
827
+ GSD_WORKFLOW_MCP_ARGS: JSON.stringify(["packages/mcp-server/dist/cli.js"]),
828
+ GSD_WORKFLOW_MCP_ENV: JSON.stringify({ GSD_CLI_PATH: "/tmp/gsd" }),
829
+ GSD_WORKFLOW_MCP_CWD: "/tmp/project",
830
+ });
795
831
  try {
796
- process.env.GSD_WORKFLOW_MCP_COMMAND = "node";
797
- process.env.GSD_WORKFLOW_MCP_NAME = "custom-workflow";
798
- process.env.GSD_WORKFLOW_MCP_ARGS = JSON.stringify(["packages/mcp-server/dist/cli.js"]);
799
- process.env.GSD_WORKFLOW_MCP_ENV = JSON.stringify({ GSD_CLI_PATH: "/tmp/gsd" });
800
- process.env.GSD_WORKFLOW_MCP_CWD = "/tmp/project";
801
832
 
802
833
  const options = buildSdkOptions("claude-sonnet-4-20250514", "test");
803
834
  const mcpServers = options.mcpServers as Record<string, any>;
@@ -817,22 +848,16 @@ describe("stream-adapter — session persistence (#2859)", () => {
817
848
  "mcp__custom-workflow__*",
818
849
  ]);
819
850
  } finally {
820
- process.env.GSD_WORKFLOW_MCP_COMMAND = prev.GSD_WORKFLOW_MCP_COMMAND;
821
- process.env.GSD_WORKFLOW_MCP_NAME = prev.GSD_WORKFLOW_MCP_NAME;
822
- process.env.GSD_WORKFLOW_MCP_ARGS = prev.GSD_WORKFLOW_MCP_ARGS;
823
- process.env.GSD_WORKFLOW_MCP_ENV = prev.GSD_WORKFLOW_MCP_ENV;
824
- process.env.GSD_WORKFLOW_MCP_CWD = prev.GSD_WORKFLOW_MCP_CWD;
851
+ restore();
825
852
  }
826
853
  });
827
854
 
828
855
  test("buildSdkOptions auto-discovers bundled MCP server even without env hints", () => {
829
- const prev = {
830
- GSD_WORKFLOW_MCP_COMMAND: process.env.GSD_WORKFLOW_MCP_COMMAND,
831
- GSD_WORKFLOW_MCP_NAME: process.env.GSD_WORKFLOW_MCP_NAME,
832
- GSD_WORKFLOW_MCP_ARGS: process.env.GSD_WORKFLOW_MCP_ARGS,
833
- GSD_WORKFLOW_MCP_ENV: process.env.GSD_WORKFLOW_MCP_ENV,
834
- GSD_WORKFLOW_MCP_CWD: process.env.GSD_WORKFLOW_MCP_CWD,
835
- };
856
+ // Use setWorkflowMcpEnv with no values to save current state;
857
+ // restore() in finally will put it back correctly (including
858
+ // deleting any keys that started as undefined — the #4808 bug
859
+ // the naive `process.env.X = prev.X` pattern introduced).
860
+ const restore = setWorkflowMcpEnv({});
836
861
  try {
837
862
  delete process.env.GSD_WORKFLOW_MCP_COMMAND;
838
863
  delete process.env.GSD_WORKFLOW_MCP_NAME;
@@ -857,23 +882,15 @@ describe("stream-adapter — session persistence (#2859)", () => {
857
882
  }
858
883
  rmSync(emptyDir, { recursive: true, force: true });
859
884
  } finally {
860
- process.env.GSD_WORKFLOW_MCP_COMMAND = prev.GSD_WORKFLOW_MCP_COMMAND;
861
- process.env.GSD_WORKFLOW_MCP_NAME = prev.GSD_WORKFLOW_MCP_NAME;
862
- process.env.GSD_WORKFLOW_MCP_ARGS = prev.GSD_WORKFLOW_MCP_ARGS;
863
- process.env.GSD_WORKFLOW_MCP_ENV = prev.GSD_WORKFLOW_MCP_ENV;
864
- process.env.GSD_WORKFLOW_MCP_CWD = prev.GSD_WORKFLOW_MCP_CWD;
885
+ restore();
865
886
  }
866
887
  });
867
888
 
868
889
  test("buildSdkOptions auto-detects local workflow MCP dist CLI when present", () => {
869
- const prev = {
870
- GSD_WORKFLOW_MCP_COMMAND: process.env.GSD_WORKFLOW_MCP_COMMAND,
871
- GSD_WORKFLOW_MCP_NAME: process.env.GSD_WORKFLOW_MCP_NAME,
872
- GSD_WORKFLOW_MCP_ARGS: process.env.GSD_WORKFLOW_MCP_ARGS,
873
- GSD_WORKFLOW_MCP_ENV: process.env.GSD_WORKFLOW_MCP_ENV,
874
- GSD_WORKFLOW_MCP_CWD: process.env.GSD_WORKFLOW_MCP_CWD,
875
- GSD_CLI_PATH: process.env.GSD_CLI_PATH,
876
- };
890
+ // GSD_CLI_PATH isn't in WORKFLOW_MCP_ENV_KEYS, so save+restore it
891
+ // manually around setWorkflowMcpEnv which handles the MCP keys.
892
+ const prevCliPath = process.env.GSD_CLI_PATH;
893
+ const restore = setWorkflowMcpEnv({});
877
894
  const originalCwd = process.cwd();
878
895
  const repoDir = mkdtempSync(join(tmpdir(), "claude-mcp-detect-"));
879
896
  try {
@@ -904,23 +921,18 @@ describe("stream-adapter — session persistence (#2859)", () => {
904
921
  } finally {
905
922
  process.chdir(originalCwd);
906
923
  rmSync(repoDir, { recursive: true, force: true });
907
- process.env.GSD_WORKFLOW_MCP_COMMAND = prev.GSD_WORKFLOW_MCP_COMMAND;
908
- process.env.GSD_WORKFLOW_MCP_NAME = prev.GSD_WORKFLOW_MCP_NAME;
909
- process.env.GSD_WORKFLOW_MCP_ARGS = prev.GSD_WORKFLOW_MCP_ARGS;
910
- process.env.GSD_WORKFLOW_MCP_ENV = prev.GSD_WORKFLOW_MCP_ENV;
911
- process.env.GSD_WORKFLOW_MCP_CWD = prev.GSD_WORKFLOW_MCP_CWD;
912
- process.env.GSD_CLI_PATH = prev.GSD_CLI_PATH;
924
+ restore();
925
+ // GSD_CLI_PATH isn't in setWorkflowMcpEnv's scope — restore it here.
926
+ if (prevCliPath === undefined) {
927
+ delete process.env.GSD_CLI_PATH;
928
+ } else {
929
+ process.env.GSD_CLI_PATH = prevCliPath;
930
+ }
913
931
  }
914
932
  });
915
933
 
916
934
  test("buildSdkOptions preserves runtime callbacks such as onElicitation", () => {
917
- const prev = {
918
- GSD_WORKFLOW_MCP_COMMAND: process.env.GSD_WORKFLOW_MCP_COMMAND,
919
- GSD_WORKFLOW_MCP_NAME: process.env.GSD_WORKFLOW_MCP_NAME,
920
- GSD_WORKFLOW_MCP_ARGS: process.env.GSD_WORKFLOW_MCP_ARGS,
921
- GSD_WORKFLOW_MCP_ENV: process.env.GSD_WORKFLOW_MCP_ENV,
922
- GSD_WORKFLOW_MCP_CWD: process.env.GSD_WORKFLOW_MCP_CWD,
923
- };
935
+ const restore = setWorkflowMcpEnv({});
924
936
  const onElicitation = async () => ({ action: "decline" as const });
925
937
  try {
926
938
  delete process.env.GSD_WORKFLOW_MCP_COMMAND;
@@ -931,11 +943,7 @@ describe("stream-adapter — session persistence (#2859)", () => {
931
943
  const options = buildSdkOptions("claude-sonnet-4-20250514", "test", undefined, { onElicitation });
932
944
  assert.equal(options.onElicitation, onElicitation);
933
945
  } finally {
934
- process.env.GSD_WORKFLOW_MCP_COMMAND = prev.GSD_WORKFLOW_MCP_COMMAND;
935
- process.env.GSD_WORKFLOW_MCP_NAME = prev.GSD_WORKFLOW_MCP_NAME;
936
- process.env.GSD_WORKFLOW_MCP_ARGS = prev.GSD_WORKFLOW_MCP_ARGS;
937
- process.env.GSD_WORKFLOW_MCP_ENV = prev.GSD_WORKFLOW_MCP_ENV;
938
- process.env.GSD_WORKFLOW_MCP_CWD = prev.GSD_WORKFLOW_MCP_CWD;
946
+ restore();
939
947
  }
940
948
  });
941
949
  });