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
@@ -1,42 +1,107 @@
1
- // GSD-2 — Regression test for #3616: reload() must reset jiti extension loader cache
2
- // Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
1
+ // Regression test for #3616: DefaultResourceLoader.reload() must invalidate
2
+ // the jiti module cache before loading extensions, so that edits to
3
+ // extension source on disk are picked up on the next reload (not served
4
+ // stale from memory).
5
+ //
6
+ // Verified end-to-end behaviourally: we write a .ts extension that
7
+ // registers a tool whose NAME is a module-scope constant, load it,
8
+ // rewrite the source with a new constant value, reload(), and assert
9
+ // the observable tool name reflects the NEW source. Without jiti cache
10
+ // invalidation, the second reload would re-register the stale name.
3
11
 
4
- import test, { describe } from "node:test";
5
12
  import assert from "node:assert/strict";
6
- import { readFileSync } from "node:fs";
13
+ import { mkdtempSync, rmSync, utimesSync, writeFileSync } from "node:fs";
14
+ import { tmpdir } from "node:os";
7
15
  import { join } from "node:path";
16
+ import { afterEach, beforeEach, describe, it } from "node:test";
8
17
 
9
- const source = readFileSync(
10
- join(process.cwd(), "packages/pi-coding-agent/src/core/resource-loader.ts"),
11
- "utf-8",
12
- );
18
+ import { DefaultResourceLoader } from "./resource-loader.js";
19
+ import { SettingsManager } from "./settings-manager.js";
20
+ import { resetExtensionLoaderCache } from "./extensions/loader.js";
13
21
 
14
- describe("#3616 reload() must invalidate jiti module cache", () => {
15
- test("resource-loader imports resetExtensionLoaderCache from loader.js", () => {
16
- assert.ok(
17
- source.includes("resetExtensionLoaderCache"),
18
- "resource-loader.ts should import resetExtensionLoaderCache",
19
- );
20
- assert.ok(
21
- source.includes('from "./extensions/loader.js"'),
22
- "resetExtensionLoaderCache should be imported from extensions/loader.js",
23
- );
22
+ let testDir: string;
23
+
24
+ function writeExtensionWithToolName(extPath: string, toolName: string): void {
25
+ // Extension factory signature expected by the loader. Uses `any` so the
26
+ // test stays decoupled from the ExtensionAPI type shape.
27
+ writeFileSync(
28
+ extPath,
29
+ [
30
+ `const TOOL_NAME = "${toolName}";`,
31
+ `export default function activate(api: any) {`,
32
+ ` api.registerTool({`,
33
+ ` name: TOOL_NAME,`,
34
+ ` label: TOOL_NAME,`,
35
+ ` description: "test tool — source-generated name",`,
36
+ ` parameters: { type: "object", properties: {}, additionalProperties: false },`,
37
+ ` execute: async () => ({ content: [], details: undefined }),`,
38
+ ` });`,
39
+ `}`,
40
+ "",
41
+ ].join("\n"),
42
+ );
43
+ }
44
+
45
+ describe("#3616 — DefaultResourceLoader.reload() invalidates extension module cache", () => {
46
+ beforeEach(() => {
47
+ testDir = mkdtempSync(join(tmpdir(), "resource-loader-cache-reset-"));
48
+ // Ensure a clean jiti singleton — prior tests in this process may
49
+ // have populated it with unrelated entries.
50
+ resetExtensionLoaderCache();
51
+ });
52
+
53
+ afterEach(() => {
54
+ resetExtensionLoaderCache();
55
+ rmSync(testDir, { recursive: true, force: true });
24
56
  });
25
57
 
26
- test("reload() calls resetExtensionLoaderCache before loadExtensions", () => {
27
- const reloadStart = source.indexOf("async reload(): Promise<void>");
28
- assert.ok(reloadStart >= 0, "should find reload() method");
29
- const reloadBody = source.slice(reloadStart, reloadStart + 4000);
58
+ it("reload() picks up source edits after the extension has been loaded once", async () => {
59
+ const agentDir = join(testDir, "agent-home");
60
+ const extPath = join(testDir, "reload-probe.ts");
30
61
 
31
- const resetIdx = reloadBody.indexOf("resetExtensionLoaderCache()");
32
- assert.ok(resetIdx >= 0, "reload() should call resetExtensionLoaderCache()");
62
+ // v1 initial content
63
+ writeExtensionWithToolName(extPath, "probe_v1");
33
64
 
34
- const loadIdx = reloadBody.indexOf("loadExtensions(");
35
- assert.ok(loadIdx >= 0, "reload() should call loadExtensions");
65
+ const loader = new DefaultResourceLoader({
66
+ cwd: testDir,
67
+ agentDir,
68
+ settingsManager: SettingsManager.inMemory(),
69
+ noExtensions: true,
70
+ noPromptTemplates: true,
71
+ noThemes: true,
72
+ additionalExtensionPaths: [extPath],
73
+ });
36
74
 
75
+ await loader.reload();
76
+
77
+ const toolsV1 = [...loader.getExtensions().extensions.flatMap((e) => [...e.tools.keys()])];
78
+ assert.ok(
79
+ toolsV1.includes("probe_v1"),
80
+ `first reload should register probe_v1; got=${JSON.stringify(toolsV1)}`,
81
+ );
82
+
83
+ // v2 — overwrite the source on disk with a different tool name.
84
+ writeExtensionWithToolName(extPath, "probe_v2");
85
+ // Bump mtime explicitly so file-stat–based cache strategies (jiti's
86
+ // moduleCache consults Node's require.cache, which some runtimes
87
+ // tiebreak on mtime) always see "newer than last seen". On Windows
88
+ // NTFS + node the default fs mtime resolution can silently collapse
89
+ // two writes in the same tick to an identical mtime — that fooled
90
+ // the Windows CI run on #3616.
91
+ const futureTime = new Date(Date.now() + 5000);
92
+ utimesSync(extPath, futureTime, futureTime);
93
+
94
+ await loader.reload();
95
+
96
+ const toolsV2 = [...loader.getExtensions().extensions.flatMap((e) => [...e.tools.keys()])];
97
+ assert.ok(
98
+ toolsV2.includes("probe_v2"),
99
+ `second reload must observe the edited source (probe_v2) — if reload() ` +
100
+ `fails to reset the jiti cache, the stale module returns probe_v1. got=${JSON.stringify(toolsV2)}`,
101
+ );
37
102
  assert.ok(
38
- resetIdx < loadIdx,
39
- "resetExtensionLoaderCache() must be called BEFORE loadExtensions to ensure fresh modules",
103
+ !toolsV2.includes("probe_v1"),
104
+ `second reload must NOT still expose the stale probe_v1 name; got=${JSON.stringify(toolsV2)}`,
40
105
  );
41
106
  });
42
107
  });
@@ -281,7 +281,11 @@ describe("RetryHandler — long-context entitlement 429 (#2803)", () => {
281
281
  assert.equal(result, true, "retry should be initiated");
282
282
 
283
283
  handler.abortRetry();
284
- await new Promise((resolve) => setTimeout(resolve, 10));
284
+ // Yield the microtask queue so any synchronous continuation
285
+ // scheduled by abortRetry() settles before we assert. This is
286
+ // deterministic — no magic-sleep dependency (#4798 / #4784).
287
+ await Promise.resolve();
288
+ await Promise.resolve();
285
289
 
286
290
  assert.equal(continueFn.mock.calls.length, 0, "cancelled retry must not continue after explicit abort");
287
291
  const endEvents = emittedEvents.filter((e) => e.type === "auto_retry_end");
@@ -18,6 +18,7 @@ import type { ModelRegistry } from "./model-registry.js";
18
18
  import type { SettingsManager } from "./settings-manager.js";
19
19
  import { sleep } from "../utils/sleep.js";
20
20
  import type { AgentSessionEvent } from "./agent-session.js";
21
+ import { RETRYABLE_ERROR_RE } from "./retryable-error-regex.js";
21
22
 
22
23
  /** Dependencies injected from AgentSession into RetryHandler */
23
24
  export interface RetryHandlerDeps {
@@ -111,14 +112,7 @@ export class RetryHandler {
111
112
  const contextWindow = this._deps.getModel()?.contextWindow ?? 0;
112
113
  if (isContextOverflow(message, contextWindow)) return false;
113
114
 
114
- const err = message.errorMessage;
115
- // "temporarily backed off" is intentionally excluded: it is an internally-
116
- // generated error from getApiKey() when credentials are in a backoff window.
117
- // Re-entering the retry handler for that message creates a cascade of empty
118
- // error entries in the session file, breaking resume (#3429).
119
- return /overloaded|rate.?limit|too many requests|402|429|500|502|503|504|service.?unavailable|server.?error|internal.?error|connection.?error|connection.?refused|other side closed|fetch failed|upstream.?connect|reset before headers|terminated|retry delay|network.?(?:is\s+)?unavailable|credentials.*expired|requires more credits|can only afford|insufficient credits|not enough credits|extra usage is required|(?:out of|no) extra usage|third.party.*draw from extra|third.party.*not.*available/i.test(
120
- err,
121
- );
115
+ return RETRYABLE_ERROR_RE.test(message.errorMessage);
122
116
  }
123
117
 
124
118
  /**
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Regex matching retryable provider errors — overloaded, rate limits, transient
3
+ * server/connection failures, credential-backoff, quota/credit issues.
4
+ *
5
+ * Kept in its own zero-import module so tests can consume the live pattern
6
+ * without pulling in the full RetryHandler dependency graph (Agent /
7
+ * FallbackResolver / ModelRegistry / @gsd/pi-ai …). The test in
8
+ * `src/resources/extensions/gsd/tests/provider-errors.test.ts` previously
9
+ * redefined this regex inline, which meant runtime and test could drift
10
+ * silently on every edit (see #4837).
11
+ *
12
+ * "temporarily backed off" is intentionally excluded: it is an internally-
13
+ * generated error from getApiKey() when credentials are in a backoff window.
14
+ * Re-entering the retry handler for that message creates a cascade of empty
15
+ * error entries in the session file, breaking resume (#3429).
16
+ */
17
+ export const RETRYABLE_ERROR_RE =
18
+ /overloaded|rate.?limit|too many requests|402|429|500|502|503|504|service.?unavailable|server.?error|internal.?error|connection.?error|connection.?refused|other side closed|fetch failed|upstream.?connect|reset before headers|terminated|retry delay|network.?(?:is\s+)?unavailable|credentials.*expired|requires more credits|can only afford|insufficient credits|not enough credits|extra usage is required|(?:out of|no) extra usage|third.party.*draw from extra|third.party.*not.*available/i;
@@ -35,6 +35,26 @@ export interface BuildSystemPromptOptions {
35
35
  contextFiles?: Array<{ path: string; content: string }>;
36
36
  /** Pre-loaded skills. */
37
37
  skills?: Skill[];
38
+ /**
39
+ * Optional predicate applied to the `skills` list before rendering the
40
+ * <available_skills> catalog. Returning `false` omits a skill from the
41
+ * prompt (the skill remains loaded and invocable by name — only the
42
+ * catalog listing is suppressed).
43
+ *
44
+ * Intended for consumers that can narrow the relevant skill surface
45
+ * (e.g. per-unit-type manifests) to reduce cached system-prompt bloat.
46
+ * When omitted, all non-`disableModelInvocation` skills render — i.e.
47
+ * behavior is unchanged from before this option existed.
48
+ *
49
+ * Contract: the predicate must be **pure and synchronous**. It may be
50
+ * invoked on every system-prompt rebuild (tool-set changes and
51
+ * runtime resource-loader extensions both trigger one), so any state
52
+ * the closure captures should be stable across the rebuild window.
53
+ * If the predicate throws, `buildSystemPrompt` logs a warning and
54
+ * falls back to the unfiltered skill list — callers never see the
55
+ * exception and the session stays consistent.
56
+ */
57
+ skillFilter?: (skill: Skill) => boolean;
38
58
  }
39
59
 
40
60
  /** Build the system prompt with tools, guidelines, and context */
@@ -48,6 +68,7 @@ export function buildSystemPrompt(options: BuildSystemPromptOptions = {}): strin
48
68
  cwd,
49
69
  contextFiles: providedContextFiles,
50
70
  skills: providedSkills,
71
+ skillFilter,
51
72
  } = options;
52
73
  const resolvedCwd = toPosixPath(cwd ?? process.cwd());
53
74
 
@@ -66,7 +87,20 @@ export function buildSystemPrompt(options: BuildSystemPromptOptions = {}): strin
66
87
  const appendSection = appendSystemPrompt ? `\n\n${appendSystemPrompt}` : "";
67
88
 
68
89
  const contextFiles = providedContextFiles ?? [];
69
- const skills = providedSkills ?? [];
90
+ const skillsBase = providedSkills ?? [];
91
+ let skills = skillsBase;
92
+ if (skillFilter) {
93
+ try {
94
+ skills = skillsBase.filter(skillFilter);
95
+ } catch (error) {
96
+ // A consumer's predicate threw. Fall back to the unfiltered list so
97
+ // the session stays consistent — callers (e.g. AgentSession.setTools)
98
+ // must not be left with updated tools but a stale system prompt.
99
+ const message = error instanceof Error ? error.message : String(error);
100
+ console.warn(`buildSystemPrompt: skillFilter threw; falling back to unfiltered skills. Error: ${message}`);
101
+ skills = skillsBase;
102
+ }
103
+ }
70
104
 
71
105
  if (customPrompt) {
72
106
  let prompt = customPrompt;
@@ -188,6 +188,7 @@ export type { PackageCommand, PackageCommandOptions, PackageCommandRunnerOptions
188
188
  export { getPackageCommandUsage, parsePackageCommand, runPackageCommand } from "./core/package-commands.js";
189
189
  export type { ResourceCollision, ResourceDiagnostic, ResourceLoader } from "./core/resource-loader.js";
190
190
  export { DefaultResourceLoader } from "./core/resource-loader.js";
191
+ export { RETRYABLE_ERROR_RE } from "./core/retryable-error-regex.js";
191
192
  // SDK for programmatic usage
192
193
  export {
193
194
  type CreateAgentSessionOptions,
@@ -14,12 +14,13 @@ function renderTool(
14
14
  isError: boolean;
15
15
  details?: Record<string, unknown>;
16
16
  },
17
+ toolDefinition?: { label?: string },
17
18
  ): string {
18
19
  const component = new ToolExecutionComponent(
19
20
  toolName,
20
21
  args,
21
22
  {},
22
- undefined,
23
+ toolDefinition as any,
23
24
  { requestRender() {} } as any,
24
25
  );
25
26
  component.setExpanded(true);
@@ -35,12 +36,13 @@ function renderToolCollapsed(
35
36
  isError: boolean;
36
37
  details?: Record<string, unknown>;
37
38
  },
39
+ toolDefinition?: { label?: string },
38
40
  ): string {
39
41
  const component = new ToolExecutionComponent(
40
42
  toolName,
41
43
  args,
42
44
  {},
43
- undefined,
45
+ toolDefinition as any,
44
46
  { requestRender() {} } as any,
45
47
  );
46
48
  if (result) component.updateResult(result);
@@ -110,13 +112,57 @@ describe("ToolExecutionComponent", () => {
110
112
  { count: 3, enabled: true, label: "hello" },
111
113
  );
112
114
 
113
- assert.match(rendered, /some_unknown_tool/);
115
+ assert.match(rendered, /Some Unknown Tool/);
114
116
  assert.match(rendered, /count=3/);
115
117
  assert.match(rendered, /enabled=true/);
116
118
  assert.match(rendered, /label="hello"/);
117
119
  assert.doesNotMatch(rendered, /^\{$/m);
118
120
  });
119
121
 
122
+ test("frame header prefers toolDefinition.label over raw tool name", () => {
123
+ const rendered = renderToolCollapsed(
124
+ "gsd_slice_complete",
125
+ { sliceId: "S03" },
126
+ undefined,
127
+ { label: "Complete Slice" },
128
+ );
129
+
130
+ assert.match(rendered, /Tool Complete Slice/);
131
+ assert.doesNotMatch(rendered, /gsd_slice_complete/);
132
+ });
133
+
134
+ test("frame header strips gsd_ prefix and title-cases when no label is registered", () => {
135
+ const rendered = renderToolCollapsed("gsd_requirement_update", { id: "R005" });
136
+
137
+ assert.match(rendered, /Tool Requirement Update/);
138
+ assert.doesNotMatch(rendered, /gsd_requirement_update/);
139
+ });
140
+
141
+ test("formatCompactArgs truncates long string values inline instead of dumping JSON", () => {
142
+ const longPath = "/Users/alice/.gsd/projects/4dce7b775013/worktrees/slice-S03-some-long-path-that-exceeds-limit";
143
+ const rendered = renderToolCollapsed("gsd_slice_complete", {
144
+ sliceId: "S03",
145
+ milestoneId: "M001",
146
+ worktree: longPath,
147
+ });
148
+
149
+ assert.match(rendered, /sliceId="S03"/);
150
+ assert.match(rendered, /milestoneId="M001"/);
151
+ assert.match(rendered, /worktree=".*…"/);
152
+ assert.doesNotMatch(rendered, /"sliceId":\s*"S03"/);
153
+ });
154
+
155
+ test("formatCompactArgs shows full string values when expanded", () => {
156
+ const longPath = "/Users/alice/.gsd/projects/4dce7b775013/worktrees/slice-S03-some-long-path-that-exceeds-limit";
157
+ const rendered = renderTool("gsd_slice_complete", {
158
+ sliceId: "S03",
159
+ worktree: longPath,
160
+ });
161
+
162
+ assert.match(rendered, new RegExp(longPath.replace(/\//g, "\\/")));
163
+ assert.doesNotMatch(rendered, /…/);
164
+ });
165
+
120
166
  test("generic fallback truncates long output when collapsed", () => {
121
167
  const longOutput = Array.from({ length: 25 }, (_, i) => `line ${i + 1}`).join("\n");
122
168
  const rendered = renderToolCollapsed(
@@ -37,26 +37,32 @@ describe("DynamicBorder spinner", () => {
37
37
  border.stopSpinner();
38
38
  });
39
39
 
40
- it("triggers standalone render when no external render occurred recently", async () => {
41
- const border = new DynamicBorder((s) => s);
42
- const tui = makeTUI();
43
-
44
- // Set lastExternalRender to a time well in the past
45
- const anyBorder = border as any;
46
- anyBorder.lastExternalRender = 0;
47
-
48
- border.startSpinner(tui as any, (s) => s);
49
- const initialCount = tui.renderCount;
50
-
51
- // Wait for one spinner tick (200ms interval + buffer)
52
- await new Promise((r) => setTimeout(r, 250));
53
-
54
- assert.ok(
55
- tui.renderCount > initialCount,
56
- "spinner should trigger requestRender when no recent external render",
57
- );
58
-
59
- border.stopSpinner();
40
+ it("triggers standalone render when no external render occurred recently", () => {
41
+ mock.timers.enable({ apis: ["setInterval"], now: 0 });
42
+ try {
43
+ const border = new DynamicBorder((s) => s);
44
+ const tui = makeTUI();
45
+
46
+ // Set lastExternalRender to a time well in the past
47
+ const anyBorder = border as any;
48
+ anyBorder.lastExternalRender = 0;
49
+
50
+ border.startSpinner(tui as any, (s) => s);
51
+ const initialCount = tui.renderCount;
52
+
53
+ // Advance exactly one spinner interval (200ms). With mocked timers
54
+ // this is deterministic — no flake under CI load.
55
+ mock.timers.tick(200);
56
+
57
+ assert.ok(
58
+ tui.renderCount > initialCount,
59
+ "spinner should trigger requestRender after one 200ms interval when no recent external render",
60
+ );
61
+
62
+ border.stopSpinner();
63
+ } finally {
64
+ mock.timers.reset();
65
+ }
60
66
  });
61
67
 
62
68
  it("updates lastExternalRender on each render() call", () => {
@@ -66,6 +66,21 @@ function parseMcpToolName(name: string): { server: string; tool: string } | null
66
66
  return { server: rest.slice(0, delim), tool: rest.slice(delim + 2) };
67
67
  }
68
68
 
69
+ /**
70
+ * Prettify a raw tool name for display. Prefers the registered `label`
71
+ * ("Complete Slice") when available; otherwise strips a leading `gsd_`
72
+ * prefix and converts snake_case to Title Case.
73
+ */
74
+ function prettifyToolName(name: string, label?: string): string {
75
+ if (label && label.trim().length > 0) return label;
76
+ const stripped = name.replace(/^gsd_/, "");
77
+ if (stripped.length === 0) return name;
78
+ return stripped
79
+ .split("_")
80
+ .map((word) => (word.length === 0 ? word : word[0].toUpperCase() + word.slice(1)))
81
+ .join(" ");
82
+ }
83
+
69
84
  type ToolFrameTone = "pending" | "success" | "error";
70
85
 
71
86
  function trimOuterBlankLines(lines: string[]): string[] {
@@ -131,15 +146,19 @@ function formatCompactArgs(args: unknown, expanded: boolean): string {
131
146
 
132
147
  const allPrimitive = entries.every(([, value]) => {
133
148
  const t = typeof value;
134
- if (t === "number" || t === "boolean") return true;
135
- if (t === "string") return (value as string).length <= COMPACT_ARG_VALUE_LIMIT;
136
- return value == null;
149
+ return t === "number" || t === "boolean" || t === "string" || value == null;
137
150
  });
138
151
 
139
152
  if (allPrimitive) {
140
153
  return entries
141
154
  .map(([key, value]) => {
142
- if (typeof value === "string") return `${key}=${JSON.stringify(value)}`;
155
+ if (typeof value === "string") {
156
+ const truncated =
157
+ !expanded && value.length > COMPACT_ARG_VALUE_LIMIT
158
+ ? `${value.slice(0, COMPACT_ARG_VALUE_LIMIT - 1)}…`
159
+ : value;
160
+ return `${key}=${JSON.stringify(truncated)}`;
161
+ }
143
162
  if (value == null) return `${key}=null`;
144
163
  return `${key}=${String(value)}`;
145
164
  })
@@ -526,7 +545,7 @@ export class ToolExecutionComponent extends Container {
526
545
  const parsed = parseMcpToolName(this.toolName);
527
546
  const frameLabel = parsed
528
547
  ? `Tool ${parsed.server}·${parsed.tool}`
529
- : `Tool ${this.normalizedToolName || this.toolName || "unknown"}`;
548
+ : `Tool ${prettifyToolName(this.toolName, this.toolDefinition?.label) || "unknown"}`;
530
549
  const framed = renderToolFrame(lines, frameWidth, {
531
550
  label: frameLabel,
532
551
  status: frameStatus,
@@ -570,12 +589,30 @@ export class ToolExecutionComponent extends Container {
570
589
  }
571
590
  } catch {
572
591
  // Fall back to default on error
573
- this.contentBox.addChild(new Text(theme.fg("toolTitle", theme.bold(this.toolName)), 0, 0));
592
+ this.contentBox.addChild(
593
+ new Text(
594
+ theme.fg(
595
+ "toolTitle",
596
+ theme.bold(prettifyToolName(this.toolName, this.toolDefinition?.label)),
597
+ ),
598
+ 0,
599
+ 0,
600
+ ),
601
+ );
574
602
  customRendererHasContent = true;
575
603
  }
576
604
  } else {
577
- // No custom renderCall, show tool name
578
- this.contentBox.addChild(new Text(theme.fg("toolTitle", theme.bold(this.toolName)), 0, 0));
605
+ // No custom renderCall, show prettified tool name
606
+ this.contentBox.addChild(
607
+ new Text(
608
+ theme.fg(
609
+ "toolTitle",
610
+ theme.bold(prettifyToolName(this.toolName, this.toolDefinition?.label)),
611
+ ),
612
+ 0,
613
+ 0,
614
+ ),
615
+ );
579
616
  customRendererHasContent = true;
580
617
  }
581
618
 
@@ -1126,7 +1163,9 @@ export class ToolExecutionComponent extends Container {
1126
1163
  // cleanly. GSD-registered MCP tools have already had their prefix
1127
1164
  // stripped upstream in partial-builder.ts and won't reach this branch.
1128
1165
  const parsed = parseMcpToolName(this.toolName);
1129
- const displayName = parsed ? parsed.tool : this.toolName;
1166
+ const displayName = parsed
1167
+ ? parsed.tool
1168
+ : prettifyToolName(this.toolName, this.toolDefinition?.label);
1130
1169
  const serverPrefix = parsed ? theme.fg("muted", `${parsed.server}\u00b7`) : "";
1131
1170
  text = serverPrefix + theme.fg("toolTitle", theme.bold(displayName));
1132
1171
 
@@ -0,0 +1,157 @@
1
+ // @gsd/pi-coding-agent + system-prompt-skill-filter.test — coverage for the
2
+ // optional `skillFilter` option added to buildSystemPrompt (RFC #4779). The
3
+ // filter lets consumers narrow the <available_skills> catalog rendered into
4
+ // the cached system prompt without touching skill loading or invocation.
5
+
6
+ import test from "node:test";
7
+ import assert from "node:assert/strict";
8
+
9
+ import { buildSystemPrompt } from "../core/system-prompt.js";
10
+ import type { Skill } from "../core/skills.js";
11
+
12
+ function makeSkill(name: string, description = `description for ${name}`): Skill {
13
+ return {
14
+ name,
15
+ description,
16
+ filePath: `/tmp/${name}/SKILL.md`,
17
+ baseDir: `/tmp/${name}`,
18
+ source: "project",
19
+ disableModelInvocation: false,
20
+ };
21
+ }
22
+
23
+ function extractAvailableSkills(prompt: string): string {
24
+ const start = prompt.indexOf("<available_skills>");
25
+ const end = prompt.indexOf("</available_skills>");
26
+ if (start === -1 || end === -1) return "";
27
+ return prompt.slice(start, end + "</available_skills>".length);
28
+ }
29
+
30
+ // ─── Default branch (no customPrompt) ──────────────────────────────────────
31
+
32
+ test("buildSystemPrompt: skillFilter omits filtered-out skills from <available_skills>", () => {
33
+ const skills = [makeSkill("alpha"), makeSkill("beta"), makeSkill("gamma")];
34
+ const prompt = buildSystemPrompt({
35
+ skills,
36
+ selectedTools: ["read", "Skill"],
37
+ skillFilter: skill => skill.name !== "beta",
38
+ });
39
+
40
+ const section = extractAvailableSkills(prompt);
41
+ assert.ok(section.length > 0, "catalog section should render");
42
+ assert.match(section, /<name>alpha<\/name>/);
43
+ assert.match(section, /<name>gamma<\/name>/);
44
+ assert.doesNotMatch(section, /<name>beta<\/name>/);
45
+ });
46
+
47
+ test("buildSystemPrompt: skillFilter omitted preserves pre-filter behavior (all skills render)", () => {
48
+ const skills = [makeSkill("alpha"), makeSkill("beta")];
49
+ const prompt = buildSystemPrompt({
50
+ skills,
51
+ selectedTools: ["read", "Skill"],
52
+ });
53
+
54
+ const section = extractAvailableSkills(prompt);
55
+ assert.match(section, /<name>alpha<\/name>/);
56
+ assert.match(section, /<name>beta<\/name>/);
57
+ });
58
+
59
+ test("buildSystemPrompt: skillFilter that rejects every skill suppresses the <available_skills> block", () => {
60
+ const skills = [makeSkill("alpha"), makeSkill("beta")];
61
+ const prompt = buildSystemPrompt({
62
+ skills,
63
+ selectedTools: ["read", "Skill"],
64
+ skillFilter: () => false,
65
+ });
66
+
67
+ // With zero visible skills, formatSkillsForPrompt returns an empty string,
68
+ // so the opening tag should not appear anywhere.
69
+ assert.ok(!prompt.includes("<available_skills>"));
70
+ });
71
+
72
+ // ─── Custom-prompt branch ──────────────────────────────────────────────────
73
+
74
+ test("buildSystemPrompt (customPrompt): skillFilter applies to the catalog appended onto a custom prompt", () => {
75
+ const skills = [makeSkill("alpha"), makeSkill("beta"), makeSkill("gamma")];
76
+ const prompt = buildSystemPrompt({
77
+ customPrompt: "CUSTOM BASE",
78
+ skills,
79
+ selectedTools: ["read", "Skill"],
80
+ skillFilter: skill => skill.name === "alpha",
81
+ });
82
+
83
+ const section = extractAvailableSkills(prompt);
84
+ assert.match(section, /<name>alpha<\/name>/);
85
+ assert.doesNotMatch(section, /<name>beta<\/name>/);
86
+ assert.doesNotMatch(section, /<name>gamma<\/name>/);
87
+ });
88
+
89
+ // ─── Interaction with disableModelInvocation ──────────────────────────────
90
+
91
+ test("buildSystemPrompt: skillFilter composes with disableModelInvocation (both must pass)", () => {
92
+ // A skill already hidden from the catalog by disableModelInvocation must
93
+ // remain hidden even if skillFilter would otherwise admit it. The filter
94
+ // narrows, it does not override the existing invisibility contract.
95
+ const skills: Skill[] = [
96
+ { ...makeSkill("visible"), disableModelInvocation: false },
97
+ { ...makeSkill("hidden"), disableModelInvocation: true },
98
+ ];
99
+ const prompt = buildSystemPrompt({
100
+ skills,
101
+ selectedTools: ["read", "Skill"],
102
+ skillFilter: () => true,
103
+ });
104
+
105
+ const section = extractAvailableSkills(prompt);
106
+ assert.match(section, /<name>visible<\/name>/);
107
+ assert.doesNotMatch(section, /<name>hidden<\/name>/);
108
+ });
109
+
110
+ // ─── Pass-through of non-filtered fields ──────────────────────────────────
111
+
112
+ test("buildSystemPrompt: skillFilter does not affect context files or cwd rendering", () => {
113
+ const skills = [makeSkill("alpha")];
114
+ const prompt = buildSystemPrompt({
115
+ skills,
116
+ cwd: "/tmp/example",
117
+ contextFiles: [{ path: "CLAUDE.md", content: "project instructions" }],
118
+ selectedTools: ["read", "Skill"],
119
+ skillFilter: () => false,
120
+ });
121
+
122
+ assert.ok(prompt.includes("/tmp/example"), "cwd should still render");
123
+ assert.ok(prompt.includes("project instructions"), "context files should still render");
124
+ assert.ok(!prompt.includes("<available_skills>"), "no skill catalog when filter rejects all");
125
+ });
126
+
127
+ // ─── Exception safety ─────────────────────────────────────────────────────
128
+
129
+ test("buildSystemPrompt: skillFilter that throws falls back to unfiltered list and does not propagate", (t) => {
130
+ // A buggy consumer predicate must not bubble out of buildSystemPrompt.
131
+ // If it did, _rebuildSystemPrompt could unwind mid-setTools() and leave
132
+ // the session with updated tools but a stale system prompt.
133
+ const skills = [makeSkill("alpha"), makeSkill("beta")];
134
+
135
+ // Suppress the console.warn the fallback emits so test output stays clean.
136
+ const originalWarn = console.warn;
137
+ const warnings: string[] = [];
138
+ console.warn = (...args: unknown[]) => { warnings.push(args.join(" ")); };
139
+ t.after(() => { console.warn = originalWarn; });
140
+
141
+ let prompt = "";
142
+ assert.doesNotThrow(() => {
143
+ prompt = buildSystemPrompt({
144
+ skills,
145
+ selectedTools: ["read", "Skill"],
146
+ skillFilter: () => { throw new Error("consumer bug"); },
147
+ });
148
+ });
149
+
150
+ const section = extractAvailableSkills(prompt);
151
+ assert.match(section, /<name>alpha<\/name>/, "alpha should render (fallback to unfiltered)");
152
+ assert.match(section, /<name>beta<\/name>/, "beta should render (fallback to unfiltered)");
153
+ assert.ok(
154
+ warnings.some(w => w.includes("skillFilter threw") && w.includes("consumer bug")),
155
+ "fallback should emit an identifying warning",
156
+ );
157
+ });