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,148 @@
1
+ // GSD-2 — #4782 phase 3: run-uat migrated to compose context via manifest.
2
+ // Regression test: prompt still carries the declared artifacts in the
3
+ // expected shape after the migration.
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 { buildRunUatPrompt } from "../auto-prompts.ts";
12
+ import { invalidateAllCaches } from "../cache.ts";
13
+ import {
14
+ openDatabase,
15
+ closeDatabase,
16
+ insertMilestone,
17
+ upsertMilestonePlanning,
18
+ insertSlice,
19
+ insertArtifact,
20
+ } from "../gsd-db.ts";
21
+
22
+ function makeBase(): string {
23
+ const base = mkdtempSync(join(tmpdir(), "gsd-runuat-composer-"));
24
+ mkdirSync(join(base, ".gsd", "milestones", "M001", "slices", "S01", "tasks"), { recursive: true });
25
+ return base;
26
+ }
27
+
28
+ function cleanup(base: string): void {
29
+ try { closeDatabase(); } catch { /* noop */ }
30
+ invalidateAllCaches();
31
+ rmSync(base, { recursive: true, force: true });
32
+ }
33
+
34
+ function seed(base: string, mid: string): void {
35
+ openDatabase(join(base, ".gsd", "gsd.db"));
36
+ insertMilestone({ id: mid, title: "Test", status: "active", depends_on: [] });
37
+ upsertMilestonePlanning(mid, {
38
+ title: "Test Milestone",
39
+ status: "active",
40
+ vision: "Demo the composer migration",
41
+ successCriteria: ["Prompt compiles", "UAT passes"],
42
+ keyRisks: [],
43
+ proofStrategy: [],
44
+ verificationContract: "",
45
+ verificationIntegration: "",
46
+ verificationOperational: "",
47
+ verificationUat: "",
48
+ definitionOfDone: [],
49
+ requirementCoverage: "",
50
+ boundaryMapMarkdown: "",
51
+ });
52
+ insertSlice({
53
+ id: "S01",
54
+ milestoneId: mid,
55
+ title: "First",
56
+ status: "complete",
57
+ risk: "low",
58
+ depends: [],
59
+ demo: "",
60
+ sequence: 1,
61
+ });
62
+ // Seed PROJECT.md so inlineProjectFromDb resolves — the run-uat manifest
63
+ // declares "project" as the third inline artifact (#4925 review).
64
+ insertArtifact({
65
+ path: "PROJECT.md",
66
+ artifact_type: "project",
67
+ milestone_id: null,
68
+ slice_id: null,
69
+ task_id: null,
70
+ full_content: "# Project\n\nRun-UAT composer fixture project.\n",
71
+ });
72
+ }
73
+
74
+ test("#4782 phase 3: buildRunUatPrompt inlines slice UAT, slice summary, project via composer", async (t) => {
75
+ const base = makeBase();
76
+ t.after(() => cleanup(base));
77
+ invalidateAllCaches();
78
+
79
+ seed(base, "M001");
80
+
81
+ // Write UAT + SUMMARY files. Deliberately diverge the on-disk UAT body
82
+ // from the in-memory uatContent the caller passes — if the resolver
83
+ // ever re-reads disk (the bug fixed in fcf3bfbe), this test fails
84
+ // because the prompt would contain "stale on-disk body" instead of
85
+ // "fresh in-memory snapshot" (#4925 follow-up review).
86
+ const uatRel = ".gsd/milestones/M001/slices/S01/S01-UAT.md";
87
+ writeFileSync(join(base, uatRel), "# S01 UAT\n\n- stale on-disk body\n");
88
+ writeFileSync(
89
+ join(base, ".gsd", "milestones", "M001", "slices", "S01", "S01-SUMMARY.md"),
90
+ "---\nid: S01\nparent: M001\n---\n# S01 Summary\n**One-liner**\n\n## What Happened\nShip.\n",
91
+ );
92
+
93
+ const uatContent = "# S01 UAT\n\n- Check X\n- Check Y\n (fresh in-memory snapshot)\n";
94
+ const prompt = await buildRunUatPrompt("M001", "S01", uatRel, uatContent, base);
95
+
96
+ // Context wrapper present
97
+ assert.match(prompt, /## Inlined Context \(preloaded — do not re-read these files\)/);
98
+
99
+ // Artifacts from the manifest inline list, in declared order:
100
+ // slice-uat → slice-summary → project (#4925 review).
101
+ const uatIdx = prompt.indexOf("### S01 UAT");
102
+ const summaryIdx = prompt.indexOf("### S01 Summary");
103
+ const projectIdx = prompt.indexOf("### Project");
104
+ assert.ok(uatIdx > -1, "slice UAT block missing");
105
+ assert.ok(summaryIdx > -1, "slice summary block missing");
106
+ assert.ok(projectIdx > -1, "project block missing — manifest declares project as 3rd inline");
107
+ assert.ok(
108
+ uatIdx < summaryIdx && summaryIdx < projectIdx,
109
+ `manifest order violated: uat (${uatIdx}) < summary (${summaryIdx}) < project (${projectIdx})`,
110
+ );
111
+
112
+ // In-memory uatContent inlined — drift assertion: stale disk content
113
+ // must NOT appear, fresh snapshot MUST appear (#4925 follow-up review).
114
+ assert.match(prompt, /fresh in-memory snapshot/);
115
+ assert.ok(!prompt.includes("stale on-disk body"), "resolver re-read disk instead of using uatContent snapshot");
116
+
117
+ // Summary body content inlined
118
+ assert.match(prompt, /What Happened[\s\S]*Ship/);
119
+
120
+ // Project body content inlined
121
+ assert.match(prompt, /Run-UAT composer fixture project/);
122
+ });
123
+
124
+ test("#4782 phase 3: buildRunUatPrompt omits optional slice summary when file is missing", async (t) => {
125
+ const base = makeBase();
126
+ t.after(() => cleanup(base));
127
+ invalidateAllCaches();
128
+
129
+ seed(base, "M001");
130
+
131
+ const uatRel = ".gsd/milestones/M001/slices/S01/S01-UAT.md";
132
+ writeFileSync(join(base, uatRel), "# S01 UAT\n");
133
+ // No SUMMARY.md written — composer should skip the slice-summary key.
134
+
135
+ const prompt = await buildRunUatPrompt("M001", "S01", uatRel, "# S01 UAT\n", base);
136
+
137
+ // UAT still present
138
+ assert.match(prompt, /### S01 UAT/);
139
+ // No empty "S01 Summary" section — section body would be blank without a file
140
+ assert.ok(!prompt.includes("### S01 Summary"));
141
+ // Project still present (third inline artifact, not optional) and follows
142
+ // UAT directly with the skipped summary collapsed (#4925 review).
143
+ const uatIdx = prompt.indexOf("### S01 UAT");
144
+ const projectIdx = prompt.indexOf("### Project");
145
+ assert.ok(projectIdx > uatIdx, `project must follow UAT when summary is omitted (uat=${uatIdx}, project=${projectIdx})`);
146
+ // No double separator from a skipped block
147
+ assert.ok(!prompt.includes("---\n\n---"));
148
+ });
@@ -31,6 +31,10 @@ describe("supportsServiceTier", () => {
31
31
  assert.equal(supportsServiceTier("vibeproxy-openai/gpt-5.4"), true);
32
32
  });
33
33
 
34
+ test("returns false for gpt-5.5 until service_tier payload support is verified", () => {
35
+ assert.equal(supportsServiceTier("gpt-5.5"), false);
36
+ });
37
+
34
38
  test("returns false for provider-only identifier without gpt-5.4 model suffix", () => {
35
39
  assert.equal(supportsServiceTier("vibeproxy-openai"), false);
36
40
  });
@@ -11,6 +11,7 @@ import assert from "node:assert/strict";
11
11
  import { readFileSync } from "node:fs";
12
12
  import { join, dirname } from "node:path";
13
13
  import { fileURLToPath } from "node:url";
14
+ import { extractSourceRegion } from "./test-helpers.ts";
14
15
 
15
16
  const __dirname = dirname(fileURLToPath(import.meta.url));
16
17
  const SESSION_TS_PATH = join(__dirname, "..", "auto", "session.ts");
@@ -52,7 +53,7 @@ test("SidecarItem type is exported from session.ts", () => {
52
53
  test("SidecarItem has required kind field with hook/triage/quick-task union", () => {
53
54
  const source = getSessionTsSource();
54
55
  const ifaceIdx = source.indexOf("export interface SidecarItem");
55
- const ifaceBlock = source.slice(ifaceIdx, ifaceIdx + 500);
56
+ const ifaceBlock = extractSourceRegion(source, "export interface SidecarItem");
56
57
  assert.ok(
57
58
  ifaceBlock.includes('"hook"') && ifaceBlock.includes('"triage"') && ifaceBlock.includes('"quick-task"'),
58
59
  "SidecarItem.kind must be a union of 'hook' | 'triage' | 'quick-task'",
@@ -77,7 +78,7 @@ test("AutoSession resets sidecarQueue in reset()", () => {
77
78
  const source = getSessionTsSource();
78
79
  const resetIdx = source.indexOf("reset(): void");
79
80
  assert.ok(resetIdx > -1, "AutoSession must have a reset() method");
80
- const resetBlock = source.slice(resetIdx, resetIdx + 3000);
81
+ const resetBlock = extractSourceRegion(source, "reset(): void");
81
82
  assert.ok(
82
83
  resetBlock.includes("sidecarQueue"),
83
84
  "reset() must clear sidecarQueue",
@@ -6,6 +6,13 @@
6
6
  * Two tests:
7
7
  * 1. Auto-mode files must have zero empty catch blocks (fully migrated).
8
8
  * 2. All GSD files must not use raw stderr/console in catch blocks.
9
+ *
10
+ * Implementation note (#4836): the previous implementation walked every
11
+ * `{` / `}` character in the source to infer catch-block boundaries. That
12
+ * ignored string literals, template interpolations, regexes, and comments,
13
+ * producing both false positives and false negatives. The current
14
+ * implementation uses the TypeScript compiler API to walk real
15
+ * `CatchClause` nodes, so lexical accidents cannot flip the verdict.
9
16
  */
10
17
 
11
18
  import { describe, test } from "node:test";
@@ -13,6 +20,7 @@ import assert from "node:assert/strict";
13
20
  import { readFileSync, readdirSync, statSync } from "node:fs";
14
21
  import { join, dirname, relative } from "node:path";
15
22
  import { fileURLToPath } from "node:url";
23
+ import ts from "typescript";
16
24
 
17
25
  const __dirname = dirname(fileURLToPath(import.meta.url));
18
26
  const gsdDir = join(__dirname, "..");
@@ -73,12 +81,6 @@ const MIGRATED_FILES = new Set([
73
81
  "auto-verification.ts",
74
82
  ]);
75
83
 
76
- /** Patterns that indicate a catch block already uses workflow-logger */
77
- const LOGGER_PATTERNS = [
78
- /logWarning\s*\(/,
79
- /logError\s*\(/,
80
- ];
81
-
82
84
  function getAutoModeFiles(): string[] {
83
85
  const files: string[] = [];
84
86
 
@@ -124,103 +126,61 @@ function getGsdSourceFiles(): string[] {
124
126
  return files;
125
127
  }
126
128
 
127
- /**
128
- * Scan a file for empty catch blocks — catches whose body contains
129
- * only whitespace and/or comments but no executable statements.
130
- */
131
- function findEmptyCatches(filePath: string): Array<{ line: number; text: string }> {
129
+ function parseSourceFile(filePath: string): ts.SourceFile {
132
130
  const content = readFileSync(filePath, "utf-8");
133
- const lines = content.split("\n");
134
- const results: Array<{ line: number; text: string }> = [];
135
-
136
- for (let i = 0; i < lines.length; i++) {
137
- const line = lines[i];
138
-
139
- // Match catch block opening
140
- if (!/\}\s*catch\s*(\([^)]*\))?\s*\{/.test(line)) continue;
141
-
142
- // Inline single-line catch: } catch { ... }
143
- const inlineMatch = line.match(/\}\s*catch\s*(\([^)]*\))?\s*\{(.*)\}\s*;?\s*$/);
144
- if (inlineMatch) {
145
- const body = inlineMatch[2].trim();
146
- const stripped = body.replace(/\/\*.*?\*\//g, "").replace(/\/\/.*/g, "").trim();
147
- if (!stripped) {
148
- results.push({ line: i + 1, text: line.trim() });
149
- }
150
- continue;
151
- }
152
-
153
- // Multi-line catch — scan until matching }
154
- let j = i + 1;
155
- let depth = 1;
156
- const bodyLines: string[] = [];
157
- while (j < lines.length && depth > 0) {
158
- for (const ch of lines[j]) {
159
- if (ch === "{") depth++;
160
- else if (ch === "}") depth--;
161
- }
162
- bodyLines.push(lines[j].trim());
163
- j++;
164
- }
131
+ return ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, /*setParentNodes*/ true, ts.ScriptKind.TS);
132
+ }
165
133
 
166
- const meaningful = bodyLines.slice(0, -1).filter(
167
- (l) => l && !l.startsWith("//") && !l.startsWith("/*") && !l.startsWith("*") && l !== "}",
168
- );
134
+ function forEachCatchClause(sf: ts.SourceFile, visit: (cc: ts.CatchClause) => void): void {
135
+ const walk = (node: ts.Node): void => {
136
+ if (ts.isCatchClause(node)) visit(node);
137
+ ts.forEachChild(node, walk);
138
+ };
139
+ walk(sf);
140
+ }
169
141
 
170
- if (meaningful.length === 0) {
171
- results.push({ line: i + 1, text: line.trim() });
142
+ /**
143
+ * A catch block is "empty" if its Block has zero statements. Comments
144
+ * inside the block are trivia and are not Statement nodes, so a
145
+ * comment-only body still counts as empty — matching the intent of the
146
+ * old regex check but without its lexical blind spots.
147
+ */
148
+ function findEmptyCatches(filePath: string): Array<{ line: number }> {
149
+ const sf = parseSourceFile(filePath);
150
+ const results: Array<{ line: number }> = [];
151
+ forEachCatchClause(sf, (cc) => {
152
+ if (cc.block.statements.length === 0) {
153
+ const { line } = sf.getLineAndCharacterOfPosition(cc.getStart(sf));
154
+ results.push({ line: line + 1 });
172
155
  }
173
- }
174
-
156
+ });
175
157
  return results;
176
158
  }
177
159
 
178
160
  /**
179
- * Scan a file for catch blocks that use raw process.stderr.write or
180
- * console.error/warn instead of workflow-logger.
161
+ * A catch block uses "raw stderr/console" if its body text calls
162
+ * process.stderr.write or console.error/warn *and* does NOT also call
163
+ * logWarning / logError.
164
+ *
165
+ * We test against the block's statement subtree text — derived from the
166
+ * AST node range, not a naive substring of the whole file — so string
167
+ * literals outside the block can never leak into the decision.
181
168
  */
182
- function findRawStderrCatches(filePath: string): Array<{ line: number; text: string }> {
183
- const content = readFileSync(filePath, "utf-8");
184
- const lines = content.split("\n");
185
- const results: Array<{ line: number; text: string }> = [];
186
-
187
- for (let i = 0; i < lines.length; i++) {
188
- const line = lines[i];
189
- if (!/\}\s*catch\s*(\([^)]*\))?\s*\{/.test(line)) continue;
190
-
191
- // Inline single-line catch
192
- const inlineMatch = line.match(/\}\s*catch\s*(\([^)]*\))?\s*\{(.*)\}\s*;?\s*$/);
193
- if (inlineMatch) {
194
- const body = inlineMatch[2];
195
- if (!LOGGER_PATTERNS.some((p) => p.test(body))) {
196
- if (/process\.stderr\.write/.test(body) || /console\.(error|warn)/.test(body)) {
197
- results.push({ line: i + 1, text: line.trim() });
198
- }
199
- }
200
- continue;
169
+ function findRawStderrCatches(filePath: string): Array<{ line: number }> {
170
+ const sf = parseSourceFile(filePath);
171
+ const results: Array<{ line: number }> = [];
172
+ forEachCatchClause(sf, (cc) => {
173
+ const bodyText = cc.block.getText(sf);
174
+ const usesLogger = /\blogWarning\s*\(|\blogError\s*\(/.test(bodyText);
175
+ if (usesLogger) return;
176
+ if (
177
+ /\bprocess\.stderr\.write\b/.test(bodyText) ||
178
+ /\bconsole\.(?:error|warn)\b/.test(bodyText)
179
+ ) {
180
+ const { line } = sf.getLineAndCharacterOfPosition(cc.getStart(sf));
181
+ results.push({ line: line + 1 });
201
182
  }
202
-
203
- // Multi-line catch
204
- let j = i + 1;
205
- let depth = 1;
206
- const bodyLines: string[] = [];
207
- while (j < lines.length && depth > 0) {
208
- for (const ch of lines[j]) {
209
- if (ch === "{") depth++;
210
- else if (ch === "}") depth--;
211
- }
212
- bodyLines.push(lines[j]);
213
- j++;
214
- }
215
-
216
- const bodyText = bodyLines.slice(0, -1).join("\n");
217
- if (!LOGGER_PATTERNS.some((p) => p.test(bodyText))) {
218
- if (/process\.stderr\.write/.test(bodyText) || /console\.(error|warn)/.test(bodyText)) {
219
- results.push({ line: i + 1, text: line.trim() });
220
- }
221
- }
222
- }
223
-
183
+ });
224
184
  return results;
225
185
  }
226
186
 
@@ -248,7 +208,7 @@ describe("workflow-logger coverage (#3348)", () => {
248
208
 
249
209
  const empties = findEmptyCatches(file);
250
210
  for (const empty of empties) {
251
- violations.push(`${rel}:${empty.line} — ${empty.text}`);
211
+ violations.push(`${rel}:${empty.line}`);
252
212
  }
253
213
  }
254
214
 
@@ -271,7 +231,7 @@ describe("workflow-logger coverage (#3348)", () => {
271
231
 
272
232
  const issues = findRawStderrCatches(file);
273
233
  for (const issue of issues) {
274
- violations.push(`${rel}:${issue.line} — ${issue.text}`);
234
+ violations.push(`${rel}:${issue.line}`);
275
235
  }
276
236
  }
277
237
 
@@ -4,7 +4,13 @@ import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
4
4
  import { join } from "node:path";
5
5
  import { tmpdir } from "node:os";
6
6
  import { loadSkills } from "@gsd/pi-coding-agent";
7
- import { buildSkillActivationBlock } from "../auto-prompts.js";
7
+ import {
8
+ buildPlanMilestonePrompt,
9
+ buildResearchMilestonePrompt,
10
+ buildSkillActivationBlock,
11
+ } from "../auto-prompts.js";
12
+ import { warnIfManifestHasMissingSkills } from "../skill-manifest.js";
13
+ import { _resetLogs, drainLogs, setStderrLoggingEnabled } from "../workflow-logger.js";
8
14
  import type { GSDPreferences } from "../preferences.js";
9
15
 
10
16
  function makeTempBase(): string {
@@ -25,6 +31,11 @@ function loadOnlyTestSkills(base: string): void {
25
31
  loadSkills({ cwd: base, includeDefaults: false, skillPaths: [join(base, "skills")] });
26
32
  }
27
33
 
34
+ function writeProjectPreferences(base: string, preferences: string): void {
35
+ mkdirSync(join(base, ".gsd"), { recursive: true });
36
+ writeFileSync(join(base, ".gsd", "PREFERENCES.md"), `---\n${preferences}---\n`);
37
+ }
38
+
28
39
  function buildBlock(
29
40
  base: string,
30
41
  params: Partial<Parameters<typeof buildSkillActivationBlock>[0]> = {},
@@ -231,3 +242,111 @@ test("buildSkillActivationBlock allows valid skill names and rejects invalid one
231
242
  cleanup(base);
232
243
  }
233
244
  });
245
+
246
+ // ─── Per-unit-type skill manifest (RFC #4779) ─────────────────────────────────
247
+
248
+ test("buildSkillActivationBlock: explicit always_use_skills bypass the unit-type manifest", () => {
249
+ const base = makeTempBase();
250
+ try {
251
+ // write-docs is in the research-milestone manifest; swiftui is not.
252
+ // Both are in always_use_skills — a user-explicit source — so BOTH
253
+ // should activate regardless of the manifest. User intent wins over
254
+ // unit-type defaults. See RFC #4779 and skill-manifest.ts rationale.
255
+ writeSkill(base, "write-docs", "Use when writing docs or RFCs.");
256
+ writeSkill(base, "swiftui", "Use for SwiftUI views.");
257
+ loadOnlyTestSkills(base);
258
+
259
+ const result = buildBlock(base, { unitType: "research-milestone" }, {
260
+ always_use_skills: ["write-docs", "swiftui"],
261
+ });
262
+
263
+ assert.match(result, /Call Skill\(\{ skill: 'write-docs' \}\)/);
264
+ assert.match(result, /Call Skill\(\{ skill: 'swiftui' \}\)/);
265
+ } finally {
266
+ cleanup(base);
267
+ }
268
+ });
269
+
270
+ test("buildSkillActivationBlock falls through to all skills for unknown unit type", () => {
271
+ const base = makeTempBase();
272
+ try {
273
+ writeSkill(base, "swiftui", "Use for SwiftUI views.");
274
+ loadOnlyTestSkills(base);
275
+
276
+ const result = buildBlock(base, { unitType: "unknown-unit-type" }, {
277
+ always_use_skills: ["swiftui"],
278
+ });
279
+
280
+ // Unknown unit type = wildcard fallback (pre-manifest behavior).
281
+ assert.match(result, /Call Skill\(\{ skill: 'swiftui' \}\)/);
282
+ } finally {
283
+ cleanup(base);
284
+ }
285
+ });
286
+
287
+ test("buildSkillActivationBlock without unitType preserves pre-manifest behavior", () => {
288
+ const base = makeTempBase();
289
+ try {
290
+ writeSkill(base, "swiftui", "Use for SwiftUI views.");
291
+ loadOnlyTestSkills(base);
292
+
293
+ // No unitType param — filter should no-op.
294
+ const result = buildBlock(base, {}, {
295
+ always_use_skills: ["swiftui"],
296
+ });
297
+
298
+ assert.match(result, /Call Skill\(\{ skill: 'swiftui' \}\)/);
299
+ } finally {
300
+ cleanup(base);
301
+ }
302
+ });
303
+
304
+ test("milestone prompt builders propagate always_use_skills through buildSkillActivationBlock", async () => {
305
+ const base = makeTempBase();
306
+ try {
307
+ // Both skills are in always_use_skills — explicit user intent bypasses
308
+ // the unit-type manifest, so both activate in both milestone flows.
309
+ writeSkill(base, "write-docs", "Use when writing docs or RFCs.");
310
+ writeSkill(base, "swiftui", "Use for SwiftUI views.");
311
+ writeProjectPreferences(base, "always_use_skills:\n - write-docs\n - swiftui\n");
312
+ loadOnlyTestSkills(base);
313
+
314
+ const researchPrompt = await buildResearchMilestonePrompt("M001", "Test", base);
315
+ assert.match(researchPrompt, /Call Skill\(\{ skill: 'write-docs' \}\)/);
316
+ assert.match(researchPrompt, /Call Skill\(\{ skill: 'swiftui' \}\)/);
317
+
318
+ const planPrompt = await buildPlanMilestonePrompt("M001", "Test", base);
319
+ assert.match(planPrompt, /Call Skill\(\{ skill: 'write-docs' \}\)/);
320
+ assert.match(planPrompt, /Call Skill\(\{ skill: 'swiftui' \}\)/);
321
+ } finally {
322
+ cleanup(base);
323
+ }
324
+ });
325
+
326
+ test("skill manifest strict warnings require GSD_SKILL_MANIFEST_STRICT=1", (t) => {
327
+ const previousStrict = process.env.GSD_SKILL_MANIFEST_STRICT;
328
+ const previousStderr = setStderrLoggingEnabled(false);
329
+ t.after(() => {
330
+ if (previousStrict === undefined) {
331
+ delete process.env.GSD_SKILL_MANIFEST_STRICT;
332
+ } else {
333
+ process.env.GSD_SKILL_MANIFEST_STRICT = previousStrict;
334
+ }
335
+ setStderrLoggingEnabled(previousStderr);
336
+ _resetLogs();
337
+ });
338
+
339
+ process.env.GSD_SKILL_MANIFEST_STRICT = "0";
340
+ _resetLogs();
341
+ warnIfManifestHasMissingSkills("research-milestone", new Set());
342
+ assert.equal(drainLogs().length, 0, "strict=0 must preserve silent behavior");
343
+
344
+ process.env.GSD_SKILL_MANIFEST_STRICT = "1";
345
+ _resetLogs();
346
+ warnIfManifestHasMissingSkills("research-milestone", new Set());
347
+ const logs = drainLogs();
348
+ assert.ok(
349
+ logs.some(log => log.message.includes("skill-manifest: references uninstalled skill")),
350
+ "strict=1 should warn about missing manifest entries",
351
+ );
352
+ });
@@ -0,0 +1,112 @@
1
+ // GSD2 + skill-manifest.test — unit coverage for the skill manifest resolver
2
+ //
3
+ // Focused tests for `resolveSkillManifest` and `filterSkillsByManifest`.
4
+ // Covers the wildcard semantics, the newly seeded unit-type entries
5
+ // (complete-milestone, validate-milestone, reassess-roadmap, research-slice,
6
+ // plan-slice, refine-slice, replan-slice, run-uat), and the deliberate
7
+ // wildcard fallback for the execute-task hot path (RFC #4779).
8
+
9
+ import test from "node:test";
10
+ import assert from "node:assert/strict";
11
+
12
+ import {
13
+ resolveSkillManifest,
14
+ filterSkillsByManifest,
15
+ } from "../skill-manifest.js";
16
+
17
+ const NEWLY_WIRED_UNIT_TYPES = [
18
+ "complete-milestone",
19
+ "validate-milestone",
20
+ "reassess-roadmap",
21
+ "research-slice",
22
+ "plan-slice",
23
+ "refine-slice",
24
+ "replan-slice",
25
+ "run-uat",
26
+ ] as const;
27
+
28
+ test("resolveSkillManifest returns null for undefined unit type (wildcard)", () => {
29
+ assert.equal(resolveSkillManifest(undefined), null);
30
+ });
31
+
32
+ test("resolveSkillManifest returns null for unknown unit types (wildcard fallback)", () => {
33
+ assert.equal(resolveSkillManifest("nonexistent-unit-type"), null);
34
+ });
35
+
36
+ test("resolveSkillManifest returns null for execute-task (intentional wildcard)", () => {
37
+ // execute-task is the implementation hot path; allowlisting it requires
38
+ // per-task skill hints from task-plan frontmatter. Documented in
39
+ // skill-manifest.ts — regression guard.
40
+ assert.equal(resolveSkillManifest("execute-task"), null);
41
+ });
42
+
43
+ for (const unitType of NEWLY_WIRED_UNIT_TYPES) {
44
+ test(`resolveSkillManifest returns a non-empty allowlist for '${unitType}'`, () => {
45
+ const allowlist = resolveSkillManifest(unitType);
46
+ assert.ok(allowlist !== null, `${unitType} should resolve to an allowlist, not wildcard`);
47
+ assert.ok(allowlist.length > 0, `${unitType} allowlist should not be empty`);
48
+ // Every entry must be lowercase (normalized).
49
+ for (const name of allowlist) {
50
+ assert.equal(name, name.toLowerCase(), `${unitType} entry '${name}' should be lowercase`);
51
+ }
52
+ });
53
+ }
54
+
55
+ test("resolveSkillManifest: slice-level manifests include decompose-into-slices", () => {
56
+ // Planning-shaped slice flows all benefit from the decomposition skill.
57
+ // Sanity-check a representative entry from each.
58
+ for (const unitType of ["research-slice", "plan-slice", "refine-slice", "replan-slice"] as const) {
59
+ const allowlist = resolveSkillManifest(unitType);
60
+ assert.ok(
61
+ allowlist?.includes("decompose-into-slices"),
62
+ `${unitType} should list decompose-into-slices`,
63
+ );
64
+ }
65
+ });
66
+
67
+ test("resolveSkillManifest: validation / completion flows include verify-before-complete", () => {
68
+ for (const unitType of ["complete-milestone", "validate-milestone", "run-uat"] as const) {
69
+ const allowlist = resolveSkillManifest(unitType);
70
+ assert.ok(
71
+ allowlist?.includes("verify-before-complete"),
72
+ `${unitType} should list verify-before-complete`,
73
+ );
74
+ }
75
+ });
76
+
77
+ test("filterSkillsByManifest: pass-through when unit type is unknown", () => {
78
+ const skills = [{ name: "swiftui" }, { name: "solidity-security" }];
79
+ const result = filterSkillsByManifest(skills, "nonexistent-unit-type");
80
+ assert.deepEqual(result, skills);
81
+ });
82
+
83
+ test("filterSkillsByManifest: pass-through when unitType is undefined", () => {
84
+ const skills = [{ name: "swiftui" }];
85
+ const result = filterSkillsByManifest(skills, undefined);
86
+ assert.deepEqual(result, skills);
87
+ });
88
+
89
+ test("filterSkillsByManifest: restricts to allowlisted names for known unit type", () => {
90
+ // research-slice allowlists include decompose-into-slices but not swiftui.
91
+ const skills = [
92
+ { name: "decompose-into-slices" },
93
+ { name: "swiftui" },
94
+ { name: "write-docs" },
95
+ ];
96
+ const result = filterSkillsByManifest(skills, "research-slice");
97
+ const names = result.map(s => s.name);
98
+ assert.ok(names.includes("decompose-into-slices"));
99
+ assert.ok(names.includes("write-docs"));
100
+ assert.ok(!names.includes("swiftui"));
101
+ });
102
+
103
+ test("filterSkillsByManifest: matching is case-insensitive via normalize", () => {
104
+ const skills = [
105
+ { name: "Write-Docs" }, // different case from manifest entry
106
+ { name: "SWIFTUI" },
107
+ ];
108
+ const result = filterSkillsByManifest(skills, "research-milestone");
109
+ const names = result.map(s => s.name);
110
+ assert.ok(names.includes("Write-Docs"));
111
+ assert.ok(!names.includes("SWIFTUI"));
112
+ });