@herbertgao/pi-extensions 2026.8.6 → 2026.8.8

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 (385) hide show
  1. package/README.md +8 -6
  2. package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +2 -2
  3. package/node_modules/@herbertgao/pi-cc-extensions/README.md +2 -2
  4. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/config.ts +19 -7
  5. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +30 -9
  6. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +320 -22
  7. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +94 -77
  8. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/message-display.ts +79 -17
  9. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/hover.ts +27 -0
  10. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/interaction.ts +66 -17
  11. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/layout.ts +41 -2
  12. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-renderer.ts +75 -6
  13. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/types.ts +7 -3
  14. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/grouping.ts +64 -1
  15. package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
  16. package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +6 -0
  17. package/node_modules/@herbertgao/pi-subagents/README.md +30 -22
  18. package/node_modules/@herbertgao/pi-subagents/examples/agent-tool-description.md +5 -5
  19. package/node_modules/@herbertgao/pi-subagents/package.json +7 -7
  20. package/node_modules/@herbertgao/pi-subagents/src/agent-file-toggle.ts +7 -1
  21. package/node_modules/@herbertgao/pi-subagents/src/agent-manager.ts +48 -19
  22. package/node_modules/@herbertgao/pi-subagents/src/agent-runner.ts +17 -13
  23. package/node_modules/@herbertgao/pi-subagents/src/custom-agents.ts +26 -2
  24. package/node_modules/@herbertgao/pi-subagents/src/index.ts +219 -40
  25. package/node_modules/@herbertgao/pi-subagents/src/invocation-config.ts +101 -3
  26. package/node_modules/@herbertgao/pi-subagents/src/nested-tools.ts +18 -4
  27. package/node_modules/@herbertgao/pi-subagents/src/settings.ts +99 -0
  28. package/node_modules/@herbertgao/pi-subagents/src/types.ts +22 -3
  29. package/node_modules/@herbertgao/pi-subagents/src/ui/agent-widget.ts +51 -3
  30. package/node_modules/@herbertgao/pi-subagents/src/ui/conversation-viewer.ts +20 -2
  31. package/node_modules/@herbertgao/pi-subagents/src/ui/fleet-list.ts +21 -6
  32. package/node_modules/@herbertgao/pi-subagents/src/usage.ts +129 -1
  33. package/node_modules/@herbertgao/pi-subagents/src/worktree.ts +20 -0
  34. package/node_modules/@juicesharp/rpiv-ask-user-question/README.md +5 -4
  35. package/node_modules/@juicesharp/rpiv-ask-user-question/ask-user-question.ts +174 -100
  36. package/node_modules/@juicesharp/rpiv-ask-user-question/config.ts +25 -0
  37. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/configuration.md +13 -4
  38. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/hosts.md +5 -1
  39. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/keyboard.md +10 -2
  40. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/localization.md +10 -6
  41. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/tool-schema.md +11 -5
  42. package/node_modules/@juicesharp/rpiv-ask-user-question/locales/en.json +5 -2
  43. package/node_modules/@juicesharp/rpiv-ask-user-question/locales/zh.json +2 -2
  44. package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +3 -2
  45. package/node_modules/@juicesharp/rpiv-ask-user-question/state/build-questionnaire.ts +10 -0
  46. package/node_modules/@juicesharp/rpiv-ask-user-question/state/key-router.ts +140 -97
  47. package/node_modules/@juicesharp/rpiv-ask-user-question/state/questionnaire-session.ts +63 -28
  48. package/node_modules/@juicesharp/rpiv-ask-user-question/state/selectors/projections.ts +22 -15
  49. package/node_modules/@juicesharp/rpiv-ask-user-question/state/state-reducer.ts +11 -1
  50. package/node_modules/@juicesharp/rpiv-ask-user-question/state/state.ts +14 -6
  51. package/node_modules/@juicesharp/rpiv-ask-user-question/tool/response-envelope.ts +11 -0
  52. package/node_modules/@juicesharp/rpiv-ask-user-question/tool/types.ts +11 -0
  53. package/node_modules/@juicesharp/rpiv-ask-user-question/view/components/multi-select-view.ts +31 -17
  54. package/node_modules/@juicesharp/rpiv-ask-user-question/view/components/preview/preview-block-renderer.ts +15 -6
  55. package/node_modules/@juicesharp/rpiv-ask-user-question/view/components/wrapping-select.ts +15 -6
  56. package/node_modules/@juicesharp/rpiv-ask-user-question/view/dialog-builder.ts +86 -38
  57. package/node_modules/@juicesharp/rpiv-ask-user-question/view/tab-content-strategy.ts +76 -9
  58. package/node_modules/@narumitw/pi-btw/README.md +120 -111
  59. package/node_modules/@narumitw/pi-btw/dist/index.ts +2898 -0
  60. package/node_modules/@narumitw/pi-btw/dist/index.ts.map +7 -0
  61. package/node_modules/@narumitw/pi-btw/package.json +10 -6
  62. package/node_modules/@narumitw/pi-btw/src/btw.ts +36 -40
  63. package/node_modules/@narumitw/pi-btw/src/fullscreen-ui.ts +55 -10
  64. package/node_modules/@narumitw/pi-btw/src/main-tree-picker.ts +390 -0
  65. package/node_modules/@narumitw/pi-btw/src/menu.ts +45 -2
  66. package/node_modules/@narumitw/pi-btw/src/transcript-pager.ts +136 -45
  67. package/node_modules/@pi-plugins/fast-mode/dist/index.mjs +44 -1856
  68. package/node_modules/@pi-plugins/fast-mode/dist/index.mjs.map +1 -1
  69. package/node_modules/@pi-plugins/fast-mode/package.json +4 -6
  70. package/node_modules/pi-lens/CHANGELOG.md +999 -0
  71. package/node_modules/pi-lens/README.md +117 -3
  72. package/node_modules/pi-lens/config/ruff/core.toml +42 -0
  73. package/node_modules/pi-lens/dist/clients/actionable-warnings.js +130 -27
  74. package/node_modules/pi-lens/dist/clients/advisory-provenance.js +126 -13
  75. package/node_modules/pi-lens/dist/clients/agent-nudge.js +67 -11
  76. package/node_modules/pi-lens/dist/clients/ast-grep-client.js +9 -3
  77. package/node_modules/pi-lens/dist/clients/atomic-write.js +9 -3
  78. package/node_modules/pi-lens/dist/clients/bash-file-access.js +381 -13
  79. package/node_modules/pi-lens/dist/clients/biome-client.js +61 -30
  80. package/node_modules/pi-lens/dist/clients/blocker-freshness.js +428 -0
  81. package/node_modules/pi-lens/dist/clients/blocker-past-eof.js +129 -0
  82. package/node_modules/pi-lens/dist/clients/bootstrap.js +12 -1
  83. package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +12 -3
  84. package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +206 -0
  85. package/node_modules/pi-lens/dist/clients/cache-observability.js +353 -76
  86. package/node_modules/pi-lens/dist/clients/cascade-budget.js +196 -0
  87. package/node_modules/pi-lens/dist/clients/cascade-format.js +20 -1
  88. package/node_modules/pi-lens/dist/clients/cascade-types.js +9 -1
  89. package/node_modules/pi-lens/dist/clients/child-unref.js +54 -6
  90. package/node_modules/pi-lens/dist/clients/code-quality-warnings.js +76 -24
  91. package/node_modules/pi-lens/dist/clients/dead-code-client.js +123 -31
  92. package/node_modules/pi-lens/dist/clients/deadline-utils.js +8 -1
  93. package/node_modules/pi-lens/dist/clients/debug-handles.js +5 -0
  94. package/node_modules/pi-lens/dist/clients/degradation-ledger.js +84 -14
  95. package/node_modules/pi-lens/dist/clients/demoted-finding-render.js +155 -0
  96. package/node_modules/pi-lens/dist/clients/dependency-checker.js +25 -3
  97. package/node_modules/pi-lens/dist/clients/deps/pi-tui.js +8 -2
  98. package/node_modules/pi-lens/dist/clients/deps/typebox.js +8 -3
  99. package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +147 -33
  100. package/node_modules/pi-lens/dist/clients/diagnostic-line-freshness.js +298 -0
  101. package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +52 -2
  102. package/node_modules/pi-lens/dist/clients/dispatch/integration.js +256 -88
  103. package/node_modules/pi-lens/dist/clients/dispatch/lazy.js +7 -4
  104. package/node_modules/pi-lens/dist/clients/dispatch/plan.js +5 -0
  105. package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +234 -18
  106. package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +187 -19
  107. package/node_modules/pi-lens/dist/clients/dispatch/runners/cpp-check.js +6 -3
  108. package/node_modules/pi-lens/dist/clients/dispatch/runners/credo.js +11 -9
  109. package/node_modules/pi-lens/dist/clients/dispatch/runners/cue-vet.js +398 -0
  110. package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +9 -6
  111. package/node_modules/pi-lens/dist/clients/dispatch/runners/elixir-check.js +11 -3
  112. package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +12 -7
  113. package/node_modules/pi-lens/dist/clients/dispatch/runners/go-vet.js +18 -0
  114. package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +9 -7
  115. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +895 -0
  116. package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +8 -6
  117. package/node_modules/pi-lens/dist/clients/dispatch/runners/index.js +4 -0
  118. package/node_modules/pi-lens/dist/clients/dispatch/runners/javac.js +16 -3
  119. package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +60 -53
  120. package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +16 -2
  121. package/node_modules/pi-lens/dist/clients/dispatch/runners/mypy.js +25 -2
  122. package/node_modules/pi-lens/dist/clients/dispatch/runners/oxlint.js +20 -5
  123. package/node_modules/pi-lens/dist/clients/dispatch/runners/phpstan.js +44 -11
  124. package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +331 -55
  125. package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +38 -5
  126. package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +97 -19
  127. package/node_modules/pi-lens/dist/clients/dispatch/runners/spellcheck.js +17 -8
  128. package/node_modules/pi-lens/dist/clients/dispatch/runners/sqlfluff.js +32 -4
  129. package/node_modules/pi-lens/dist/clients/dispatch/runners/stylelint.js +47 -3
  130. package/node_modules/pi-lens/dist/clients/dispatch/runners/swiftlint.js +14 -4
  131. package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +127 -28
  132. package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +12 -7
  133. package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +73 -12
  134. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/availability-policy.js +275 -13
  135. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/candidate-probe.js +21 -6
  136. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/lazy-installer.js +260 -27
  137. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +734 -64
  138. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +86 -11
  139. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/tool-failure.js +188 -0
  140. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +9 -0
  141. package/node_modules/pi-lens/dist/clients/dispatch/runners/vale.js +21 -10
  142. package/node_modules/pi-lens/dist/clients/dispatch/runners/yamllint.js +18 -2
  143. package/node_modules/pi-lens/dist/clients/dispatch/runners/zig-check.js +4 -2
  144. package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +28 -5
  145. package/node_modules/pi-lens/dist/clients/extension-log.js +22 -1
  146. package/node_modules/pi-lens/dist/clients/file-kinds.js +8 -2
  147. package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +316 -0
  148. package/node_modules/pi-lens/dist/clients/finding-identity.js +88 -0
  149. package/node_modules/pi-lens/dist/clients/formatters-lazy.js +3 -2
  150. package/node_modules/pi-lens/dist/clients/formatters.js +500 -115
  151. package/node_modules/pi-lens/dist/clients/generation-guard.js +216 -0
  152. package/node_modules/pi-lens/dist/clients/git-guard.js +31 -0
  153. package/node_modules/pi-lens/dist/clients/gitleaks-client.js +181 -8
  154. package/node_modules/pi-lens/dist/clients/govulncheck-client.js +109 -9
  155. package/node_modules/pi-lens/dist/clients/grammar-source.js +333 -10
  156. package/node_modules/pi-lens/dist/clients/host-ports.js +8 -0
  157. package/node_modules/pi-lens/dist/clients/installer/index.js +1114 -100
  158. package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh-session.js +58 -0
  159. package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh.js +715 -0
  160. package/node_modules/pi-lens/dist/clients/instance-reaper.js +643 -97
  161. package/node_modules/pi-lens/dist/clients/instance-registry.js +87 -17
  162. package/node_modules/pi-lens/dist/clients/jscpd-client.js +87 -8
  163. package/node_modules/pi-lens/dist/clients/knip-client.js +310 -10
  164. package/node_modules/pi-lens/dist/clients/language-policy.js +8 -2
  165. package/node_modules/pi-lens/dist/clients/language-profile.js +9 -2
  166. package/node_modules/pi-lens/dist/clients/latency-logger.js +440 -11
  167. package/node_modules/pi-lens/dist/clients/lazy-import.js +21 -0
  168. package/node_modules/pi-lens/dist/clients/ledger-bounds.js +21 -0
  169. package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +8 -0
  170. package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +128 -8
  171. package/node_modules/pi-lens/dist/clients/lsp/client.js +1660 -164
  172. package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +83 -1
  173. package/node_modules/pi-lens/dist/clients/lsp/document-drift.js +389 -0
  174. package/node_modules/pi-lens/dist/clients/lsp/index.js +2423 -151
  175. package/node_modules/pi-lens/dist/clients/lsp/inferred-project.js +215 -0
  176. package/node_modules/pi-lens/dist/clients/lsp/jvm-runtime.js +146 -14
  177. package/node_modules/pi-lens/dist/clients/lsp/language.js +48 -4
  178. package/node_modules/pi-lens/dist/clients/lsp/server.js +277 -16
  179. package/node_modules/pi-lens/dist/clients/lsp/spawn-history.js +22 -0
  180. package/node_modules/pi-lens/dist/clients/lsp/sync-kind.js +43 -0
  181. package/node_modules/pi-lens/dist/clients/lsp/tsserver-sync.js +79 -3
  182. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/strategies.js +43 -0
  183. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +422 -28
  184. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-session.js +30 -0
  185. package/node_modules/pi-lens/dist/clients/lsp/workspace-sweep-hold.js +175 -0
  186. package/node_modules/pi-lens/dist/clients/lsp-lazy.js +3 -2
  187. package/node_modules/pi-lens/dist/clients/mcp/session.js +10 -2
  188. package/node_modules/pi-lens/dist/clients/module-report.js +22 -1
  189. package/node_modules/pi-lens/dist/clients/ndjson-logger.js +114 -23
  190. package/node_modules/pi-lens/dist/clients/opengrep-client.js +15 -1
  191. package/node_modules/pi-lens/dist/clients/package-manager.js +170 -14
  192. package/node_modules/pi-lens/dist/clients/path-attribution-telemetry.js +27 -0
  193. package/node_modules/pi-lens/dist/clients/path-utils.js +160 -0
  194. package/node_modules/pi-lens/dist/clients/pipeline.js +154 -33
  195. package/node_modules/pi-lens/dist/clients/post-autofix-notice.js +37 -0
  196. package/node_modules/pi-lens/dist/clients/project-diagnostics/cache.js +17 -3
  197. package/node_modules/pi-lens/dist/clients/project-diagnostics/extractors.js +89 -22
  198. package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +84 -14
  199. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/gitleaks.js +15 -3
  200. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/trivy.js +50 -2
  201. package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +23 -9
  202. package/node_modules/pi-lens/dist/clients/project-lens-config.js +3 -0
  203. package/node_modules/pi-lens/dist/clients/project-report.js +62 -27
  204. package/node_modules/pi-lens/dist/clients/project-snapshot.js +299 -0
  205. package/node_modules/pi-lens/dist/clients/quiet-window-config.js +47 -0
  206. package/node_modules/pi-lens/dist/clients/quiet-window.js +11 -25
  207. package/node_modules/pi-lens/dist/clients/read-guard-logger.js +8 -3
  208. package/node_modules/pi-lens/dist/clients/read-guard.js +253 -10
  209. package/node_modules/pi-lens/dist/clients/resource-sampler.js +49 -17
  210. package/node_modules/pi-lens/dist/clients/reverse-deps.js +50 -11
  211. package/node_modules/pi-lens/dist/clients/review-graph/builder.js +194 -31
  212. package/node_modules/pi-lens/dist/clients/review-graph/query.js +52 -4
  213. package/node_modules/pi-lens/dist/clients/review-graph/revision-drift.js +21 -0
  214. package/node_modules/pi-lens/dist/clients/review-graph/workspace-modules.js +6 -3
  215. package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +96 -7
  216. package/node_modules/pi-lens/dist/clients/runtime-context.js +8 -1
  217. package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +352 -45
  218. package/node_modules/pi-lens/dist/clients/runtime-session.js +373 -9
  219. package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +217 -22
  220. package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +212 -79
  221. package/node_modules/pi-lens/dist/clients/runtime-turn.js +620 -67
  222. package/node_modules/pi-lens/dist/clients/safe-spawn.js +220 -7
  223. package/node_modules/pi-lens/dist/clients/scratch-tree-policy.js +161 -0
  224. package/node_modules/pi-lens/dist/clients/search-read-registration.js +34 -6
  225. package/node_modules/pi-lens/dist/clients/security-scan-client.js +157 -24
  226. package/node_modules/pi-lens/dist/clients/session-event-guard.js +184 -0
  227. package/node_modules/pi-lens/dist/clients/sg-runner.js +116 -22
  228. package/node_modules/pi-lens/dist/clients/single-flight.js +158 -0
  229. package/node_modules/pi-lens/dist/clients/source-filter.js +6 -2
  230. package/node_modules/pi-lens/dist/clients/stale-marker.js +17 -0
  231. package/node_modules/pi-lens/dist/clients/startup-scan.js +5 -2
  232. package/node_modules/pi-lens/dist/clients/startup-timing.js +19 -1
  233. package/node_modules/pi-lens/dist/clients/test-runner-client.js +151 -16
  234. package/node_modules/pi-lens/dist/clients/tool-event.js +56 -5
  235. package/node_modules/pi-lens/dist/clients/tool-policy.js +125 -20
  236. package/node_modules/pi-lens/dist/clients/tree-sitter-cache.js +75 -1
  237. package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +769 -64
  238. package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +1 -0
  239. package/node_modules/pi-lens/dist/clients/tree-sitter-shared.js +11 -0
  240. package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +90 -0
  241. package/node_modules/pi-lens/dist/clients/trivy-client.js +19 -1
  242. package/node_modules/pi-lens/dist/clients/widget-state.js +336 -36
  243. package/node_modules/pi-lens/dist/clients/zizmor-config.js +214 -14
  244. package/node_modules/pi-lens/dist/index.js +61407 -48577
  245. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +476 -57
  246. package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +132 -43
  247. package/node_modules/pi-lens/dist/tools/lsp-navigation.js +46 -7
  248. package/node_modules/pi-lens/docs/agent-guide.md +6 -1
  249. package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +515 -506
  250. package/node_modules/pi-lens/docs/dispositions.md +21 -0
  251. package/node_modules/pi-lens/docs/features.md +19 -5
  252. package/node_modules/pi-lens/docs/globalconfig.md +3 -0
  253. package/node_modules/pi-lens/docs/language-coverage.md +71 -5
  254. package/node_modules/pi-lens/docs/mcp.md +2 -2
  255. package/node_modules/pi-lens/docs/servercapabilities.md +4 -1
  256. package/node_modules/pi-lens/docs/settings.md +19 -1
  257. package/node_modules/pi-lens/docs/usage.md +1 -0
  258. package/node_modules/pi-lens/package.json +27 -10
  259. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/__snapshots__/no-reflect-get-js-snapshot.yml +23 -0
  260. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/__snapshots__/no-reflect-get-snapshot.yml +23 -0
  261. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/__snapshots__/ts-void-zero-snapshot.yml +23 -0
  262. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-bare-object-param-test.yml +16 -0
  263. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-chained-type-assertions-test.yml +15 -1
  264. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-compile-call-test.yml +29 -1
  265. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-dupe-class-members-test.yml +33 -0
  266. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-inner-html-js-test.yml +7 -1
  267. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-inner-html-test.yml +7 -1
  268. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-known-value-widening-test.yml +9 -0
  269. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-non-null-assertion-test.yml +216 -0
  270. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-js-test.yml +2 -0
  271. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-test.yml +2 -0
  272. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-js-test.yml +2 -1
  273. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-test.yml +2 -1
  274. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-return-value-in-generator-test.yml +12 -0
  275. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-runtime-typeof-test.yml +33 -0
  276. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-server-bind-wildcard-test.yml +9 -0
  277. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-shape-in-symbol-names-test.yml +13 -0
  278. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-sql-in-code-js-test.yml +5 -1
  279. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-sql-in-code-test.yml +5 -1
  280. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unknown-parameters-test.yml +12 -0
  281. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unknown-returns-test.yml +16 -0
  282. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unsafe-dictionary-any-test.yml +10 -0
  283. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unsafe-dictionary-unknown-test.yml +8 -0
  284. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/redundant-unsafe-function-test.yml +35 -0
  285. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/require-safety-comment-for-as-unknown-as-test.yml +177 -0
  286. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/ts-void-zero-test.yml +1 -0
  287. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-await-expression-member-js.yml +13 -2
  288. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-await-expression-member.yml +13 -2
  289. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-host-path-in-win32-branch.yml +2 -0
  290. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-object-param.yml +60 -0
  291. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-chained-type-assertions.yml +57 -12
  292. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-compile-call.yml +63 -5
  293. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-dupe-class-members.yml +52 -8
  294. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-inner-html-js.yml +27 -2
  295. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-inner-html.yml +27 -2
  296. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-known-value-widening.yml +71 -0
  297. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-non-null-assertion.yml +316 -0
  298. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-raw-json-store-write.yml +7 -0
  299. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply-js.yml +22 -2
  300. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply.yml +23 -2
  301. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get-js.yml +33 -5
  302. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get.yml +34 -8
  303. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-return-value-in-generator.yml +22 -5
  304. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-runtime-typeof.yml +121 -0
  305. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-server-bind-wildcard.yml +23 -7
  306. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-shape-in-symbol-names.yml +68 -0
  307. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-sql-in-code-js.yml +41 -2
  308. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-sql-in-code.yml +41 -2
  309. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-laundering.yml +6 -1
  310. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-parameters.yml +67 -0
  311. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-returns.yml +106 -0
  312. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unsafe-dictionary-any.yml +50 -0
  313. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unsafe-dictionary-unknown.yml +56 -0
  314. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-win32-isabsolute-for-qualification.yml +2 -0
  315. package/node_modules/pi-lens/rules/ast-grep-rules/rules/redundant-unsafe-function.yml +32 -0
  316. package/node_modules/pi-lens/rules/ast-grep-rules/rules/require-safety-comment-for-as-unknown-as.yml +184 -0
  317. package/node_modules/pi-lens/rules/ast-grep-rules/rules/ts-void-zero.yml +5 -0
  318. package/node_modules/pi-lens/rules/ast-grep-rules/self-scan-baseline.json +5 -0
  319. package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +53 -1
  320. package/node_modules/pi-lens/scripts/download-grammars.js +25 -0
  321. package/node_modules/pi-lens/scripts/grammars.lock.json +9 -0
  322. package/node_modules/pi-lens/scripts/install-selftest.mjs +99 -11
  323. package/node_modules/pi-lens/scripts/lib/host-provided-deps.mjs +75 -0
  324. package/node_modules/pi-lens/scripts/lib/warm-loader-cache.mjs +285 -0
  325. package/node_modules/pi-lens/scripts/warm-loader-cache.mjs +233 -0
  326. package/node_modules/pi-lens/skills/pi-lens-ast-grep/SKILL.md +2 -0
  327. package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/SKILL.md +80 -3
  328. package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/reference.md +41 -0
  329. package/node_modules/pi-lens/vendor/grammars/tree-sitter-cue.wasm +0 -0
  330. package/node_modules/pi-mcp-adapter/CHANGELOG.md +36 -0
  331. package/node_modules/pi-mcp-adapter/README.md +47 -5
  332. package/node_modules/pi-mcp-adapter/cli.js +163 -1
  333. package/node_modules/pi-mcp-adapter/commands.ts +128 -22
  334. package/node_modules/pi-mcp-adapter/config.ts +25 -7
  335. package/node_modules/pi-mcp-adapter/direct-tools.ts +5 -4
  336. package/node_modules/pi-mcp-adapter/index.ts +219 -32
  337. package/node_modules/pi-mcp-adapter/init.ts +36 -4
  338. package/node_modules/pi-mcp-adapter/lifecycle.ts +315 -24
  339. package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +39 -6
  340. package/node_modules/pi-mcp-adapter/mcp-bearer-store.ts +387 -0
  341. package/node_modules/pi-mcp-adapter/mcp-callback-server.ts +27 -3
  342. package/node_modules/pi-mcp-adapter/mcp-code.ts +4 -0
  343. package/node_modules/pi-mcp-adapter/mcp-panel.ts +42 -13
  344. package/node_modules/pi-mcp-adapter/metadata-cache.ts +32 -21
  345. package/node_modules/pi-mcp-adapter/package-mcp-loader.ts +168 -0
  346. package/node_modules/pi-mcp-adapter/package.json +3 -1
  347. package/node_modules/pi-mcp-adapter/proxy-modes.ts +5 -4
  348. package/node_modules/pi-mcp-adapter/request-headers-command.ts +31 -37
  349. package/node_modules/pi-mcp-adapter/search-ranking.ts +92 -22
  350. package/node_modules/pi-mcp-adapter/server-manager.ts +140 -2
  351. package/node_modules/pi-mcp-adapter/session-recovery.ts +7 -0
  352. package/node_modules/pi-mcp-adapter/tool-approval.ts +15 -2
  353. package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +44 -4
  354. package/node_modules/pi-mcp-adapter/types.ts +2 -0
  355. package/node_modules/pi-mcp-adapter/ui-server.ts +18 -16
  356. package/node_modules/pi-mcp-adapter/utils.ts +70 -0
  357. package/node_modules/pi-web-access/CHANGELOG.md +37 -0
  358. package/node_modules/pi-web-access/README.md +32 -13
  359. package/node_modules/pi-web-access/anysearch.ts +4 -2
  360. package/node_modules/pi-web-access/brave.ts +16 -4
  361. package/node_modules/pi-web-access/chrome-cookies.ts +86 -15
  362. package/node_modules/pi-web-access/curator-page.ts +6 -3
  363. package/node_modules/pi-web-access/curator-server.ts +4 -1
  364. package/node_modules/pi-web-access/data-uri-sanitize.ts +406 -0
  365. package/node_modules/pi-web-access/exa.ts +18 -7
  366. package/node_modules/pi-web-access/extract.ts +40 -5
  367. package/node_modules/pi-web-access/firecrawl.ts +18 -2
  368. package/node_modules/pi-web-access/gemini-search.ts +16 -4
  369. package/node_modules/pi-web-access/github-extract.ts +65 -36
  370. package/node_modules/pi-web-access/index.ts +60 -61
  371. package/node_modules/pi-web-access/openai-search.ts +17 -6
  372. package/node_modules/pi-web-access/package.json +1 -1
  373. package/node_modules/pi-web-access/page-query.ts +4 -2
  374. package/node_modules/pi-web-access/parallel-mcp.ts +249 -0
  375. package/node_modules/pi-web-access/parallel.ts +2 -2
  376. package/node_modules/pi-web-access/pdf-extract.ts +18 -5
  377. package/node_modules/pi-web-access/query-rewrite.ts +51 -0
  378. package/node_modules/pi-web-access/serper.ts +211 -0
  379. package/node_modules/pi-web-access/ssrf-protection.ts +12 -2
  380. package/node_modules/pi-web-access/summary-model-scope.ts +12 -4
  381. package/node_modules/pi-web-access/summary-review.ts +44 -22
  382. package/node_modules/pi-web-access/tavily.ts +16 -4
  383. package/node_modules/pi-web-access/utils.ts +87 -0
  384. package/node_modules/pi-web-access/valyu.ts +199 -0
  385. package/package.json +14 -14
@@ -1,11 +1,13 @@
1
1
  import * as fs from "node:fs/promises";
2
2
  import * as os from "node:os";
3
3
  import * as path from "node:path";
4
- import { spawn } from "node:child_process";
4
+ import { incrementDegradationCount } from "../../degradation-ledger.js";
5
+ import { safeSpawnAsync } from "../../safe-spawn.js";
5
6
  import { PRIORITY } from "../priorities.js";
6
- // The PS script written to a temp file each run — avoids cmd.exe quoting issues
7
- // safeSpawnAsync uses shell:true on Windows, which mangles -Command strings.
8
- // Using -File with a temp script sidesteps all escaping problems.
7
+ import { classifyProbeFailure, createAvailabilityLatch, isLatchingOutcome, logAvailabilityDecision, startHostStallSampler, } from "./utils/availability-policy.js";
8
+ // The PS script written to a temp file each run. `-File` with a temp script
9
+ // keeps the analysis argv free of embedded script text; safe-spawn itself never
10
+ // uses a shell (#817), so nothing here is quoting-sensitive.
9
11
  const PS_SCRIPT = `
10
12
  param([string]$FilePath)
11
13
  Import-Module PSScriptAnalyzer -ErrorAction Stop
@@ -13,82 +15,229 @@ $results = @(Invoke-ScriptAnalyzer -Path $FilePath | Select-Object RuleName,@{N=
13
15
  if ($results.Count -eq 0) { Write-Output '[]'; exit 0 }
14
16
  $results | ConvertTo-Json -Depth 3 -Compress
15
17
  `.trim();
16
- // Cache resolved powershell binary and module availability per process
17
- let psCmd = undefined;
18
- let psAnalyzerAvailable = undefined;
19
18
  const PS_TIMEOUT_MS = 30000;
19
+ /**
20
+ * PowerShell spawns go through `safeSpawnAsync` (#1490).
21
+ *
22
+ * The hand-rolled version returned `status: null` for a timeout, a synchronous
23
+ * `spawn UNKNOWN` and a missing binary alike, so the call site had nothing to
24
+ * classify and remembered every one of them as "PowerShell analysis is not
25
+ * available". `safeSpawnAsync` carries the standard taxonomy — `failure`,
26
+ * `spawnFailure.kind` and the errno on `error` — which is what lets the
27
+ * availability policy tell a stall from an absence.
28
+ */
20
29
  function spawnPs(cmd, args, timeoutMs = PS_TIMEOUT_MS) {
21
- return new Promise((resolve) => {
22
- let child;
23
- try {
24
- child = spawn(cmd, args, { windowsHide: true, shell: false });
25
- }
26
- catch {
27
- // SYNCHRONOUS spawn throw (Windows `spawn UNKNOWN`/EINVAL, the pidusage
28
- // bug class, #533) — this best-effort runner must resolve, not reject.
29
- resolve({ stdout: "", stderr: "", status: null });
30
- return;
31
- }
32
- let stdout = "";
33
- let stderr = "";
34
- let settled = false;
35
- const done = (status) => {
36
- if (settled)
37
- return;
38
- settled = true;
39
- clearTimeout(timer);
40
- resolve({ stdout, stderr, status });
41
- };
42
- const timer = setTimeout(() => {
43
- if (!settled) {
44
- child.kill("SIGTERM");
45
- setTimeout(() => { if (!settled)
46
- child.kill("SIGKILL"); }, 1000);
47
- done(null);
48
- }
49
- }, timeoutMs);
50
- child.stdout?.setEncoding("utf-8");
51
- child.stderr?.setEncoding("utf-8");
52
- child.stdout?.on("data", (d) => (stdout += d));
53
- child.stderr?.on("data", (d) => (stderr += d));
54
- child.on("close", (code) => done(code));
55
- child.on("error", () => done(null));
30
+ return safeSpawnAsync(cmd, args, {
31
+ timeout: timeoutMs,
32
+ resourceLabel: "psscriptanalyzer",
56
33
  });
57
34
  }
35
+ /**
36
+ * The two verdicts this runner memoizes, both owned by the shared policy.
37
+ *
38
+ * The availability-policy coverage gate never flagged either of them, and the
39
+ * reason is not a bug in the gate: it recognises a probe by a quoted VERSION
40
+ * FLAG, and neither probe below asks for a version. One runs `exit 0` to see if
41
+ * an interpreter answers; the other asks `Get-Module -ListAvailable`. A
42
+ * capability probe with no version flag is invisible to that pre-filter, so this
43
+ * file was skipped before any unit was analysed. The residual gate blindness is
44
+ * tracked with #1489's gate work; the migration here does not depend on it.
45
+ */
46
+ const psCmdLatch = createAvailabilityLatch();
47
+ const psAnalyzerLatchByCmd = new Map();
48
+ /**
49
+ * Whether `-File` actually executes, keyed by interpreter (#1540).
50
+ *
51
+ * Neither probe above asks `-File` a question -- both use `-Command`, which
52
+ * PowerShell's execution policy does not gate. Under `Restricted`/`AllSigned`
53
+ * that lets both probes report "available" while the real analysis spawn
54
+ * (which DOES go through `-File`) gets a policy `SecurityError` on stderr and
55
+ * exits nonzero with empty stdout. This latch is deliberately populated by
56
+ * the real analysis run rather than a third `-Command`-shaped probe, so the
57
+ * verdict it records is direct evidence about `-File`, not an inference from
58
+ * a proxy that has already been shown to disagree with it.
59
+ */
60
+ const psExecLatchByCmd = new Map();
61
+ let psCmd = null;
62
+ /** Record one decision, and report whether the caller may retry later. */
63
+ function notePsDecision(latch, tool, verdict) {
64
+ let retryAfterMs;
65
+ if (verdict.available) {
66
+ latch.noteAvailable();
67
+ }
68
+ else {
69
+ const delay = latch.noteUnavailable(verdict.outcome, verdict.cause);
70
+ if (delay > 0)
71
+ retryAfterMs = delay;
72
+ }
73
+ logAvailabilityDecision({
74
+ tool,
75
+ verdict: verdict.available ? "available" : "unavailable",
76
+ outcome: verdict.available ? "success" : verdict.outcome,
77
+ cause: verdict.available ? "ok" : verdict.cause,
78
+ elapsedMs: verdict.elapsedMs,
79
+ latched: verdict.available || isLatchingOutcome(verdict.outcome),
80
+ hostStallMs: verdict.hostStallMs,
81
+ ...(retryAfterMs !== undefined && { retryAfterMs }),
82
+ budgetMs: PS_TIMEOUT_MS,
83
+ });
84
+ }
85
+ /**
86
+ * Drop both verdicts at the session boundary (#1490 review).
87
+ *
88
+ * Every other runner gets this for free through
89
+ * `resetDispatchAvailabilityState`'s generation counter; these latches are local
90
+ * to the module, so `session_start` clears them explicitly. Without it, a
91
+ * PowerShell or module install performed mid-process is invisible until the host
92
+ * restarts — the same "suppression outliving its session" defect #1222 fixed for
93
+ * install attempts.
94
+ */
95
+ export function resetPsScriptAnalyzerAvailability() {
96
+ psCmdLatch.reset();
97
+ psAnalyzerLatchByCmd.clear();
98
+ psExecLatchByCmd.clear();
99
+ psCmd = null;
100
+ }
101
+ /**
102
+ * Resolve the PowerShell binary, once per session per verdict.
103
+ *
104
+ * A candidate that times out is NOT evidence that PowerShell is absent, so the
105
+ * loop only concludes "missing" when every candidate answered with a durable
106
+ * failure. One transient candidate makes the whole verdict transient, and the
107
+ * next turn after the cooldown probes again.
108
+ */
58
109
  async function resolvePowerShellCmd() {
59
- if (psCmd !== undefined)
60
- return psCmd;
110
+ const memo = psCmdLatch.read();
111
+ if (memo !== null)
112
+ return memo ? psCmd : null;
113
+ // Rank what the candidates said, rather than collapsing every failure to
114
+ // "not-found" (#1490 review). A stall outranks everything: it means we never
115
+ // got an answer. A present-but-rejecting interpreter (nonzero exit, EACCES)
116
+ // is durable but is NOT a missing install, and reporting it as one both sends
117
+ // the user to install PowerShell they already have and fabricates the cause in
118
+ // `availability_decision`. Only an all-candidates ENOENT sweep is `missing`.
119
+ let transient = null;
120
+ let rejected = null;
121
+ let elapsedTotalMs = 0;
122
+ let stallTotalMs = 0;
61
123
  for (const candidate of ["pwsh", "powershell"]) {
62
- const r = await spawnPs(candidate, ["-NoProfile", "-NonInteractive", "-Command", "exit 0"]);
63
- if (r.status === 0) {
124
+ const sampler = startHostStallSampler();
125
+ const startedAt = Date.now();
126
+ const result = await spawnPs(candidate, [
127
+ "-NoProfile",
128
+ "-NonInteractive",
129
+ "-Command",
130
+ "exit 0",
131
+ ]);
132
+ const hostStallMs = sampler.stop();
133
+ const elapsedMs = Date.now() - startedAt;
134
+ elapsedTotalMs += elapsedMs;
135
+ stallTotalMs += hostStallMs;
136
+ if (!result.error && result.status === 0) {
64
137
  psCmd = candidate;
65
- return psCmd;
138
+ notePsDecision(psCmdLatch, "powershell", {
139
+ available: true,
140
+ elapsedMs,
141
+ hostStallMs,
142
+ });
143
+ return candidate;
66
144
  }
145
+ const classified = classifyProbeFailure(result, { hostStallMs });
146
+ if (classified.outcome === "transient")
147
+ transient ??= classified;
148
+ else if (classified.outcome !== "missing")
149
+ rejected ??= classified;
67
150
  }
68
151
  psCmd = null;
152
+ const verdict = transient ??
153
+ rejected ?? {
154
+ outcome: "missing",
155
+ cause: "not-found",
156
+ };
157
+ notePsDecision(psCmdLatch, "powershell", {
158
+ available: false,
159
+ outcome: verdict.outcome,
160
+ cause: verdict.cause,
161
+ elapsedMs: elapsedTotalMs,
162
+ hostStallMs: stallTotalMs,
163
+ });
69
164
  return null;
70
165
  }
71
166
  async function checkModuleAvailable(cmd) {
72
- if (psAnalyzerAvailable !== undefined)
73
- return psAnalyzerAvailable;
74
- const r = await spawnPs(cmd, [
167
+ let latch = psAnalyzerLatchByCmd.get(cmd);
168
+ if (!latch) {
169
+ latch = createAvailabilityLatch();
170
+ psAnalyzerLatchByCmd.set(cmd, latch);
171
+ }
172
+ const memo = latch.read();
173
+ if (memo !== null)
174
+ return memo;
175
+ const sampler = startHostStallSampler();
176
+ const startedAt = Date.now();
177
+ const result = await spawnPs(cmd, [
75
178
  "-NoProfile",
76
179
  "-NonInteractive",
77
180
  "-Command",
78
181
  "if (Get-Module -ListAvailable PSScriptAnalyzer) { exit 0 } else { exit 1 }",
79
182
  ]);
80
- psAnalyzerAvailable = r.status === 0;
81
- return psAnalyzerAvailable;
183
+ const hostStallMs = sampler.stop();
184
+ const elapsedMs = Date.now() - startedAt;
185
+ if (!result.error && result.status === 0) {
186
+ notePsDecision(latch, "psscriptanalyzer", {
187
+ available: true,
188
+ elapsedMs,
189
+ hostStallMs,
190
+ });
191
+ return true;
192
+ }
193
+ // A CLEAN `exit 1` is the module genuinely not being installed — the one
194
+ // outcome an install fixes, so that alone earns the `missing` override.
195
+ //
196
+ // Everything else did not answer the question (#1490 review). A `spawn
197
+ // UNKNOWN`, an EMFILE, an EACCES, or a PowerShell that vanished between the
198
+ // two probes all arrive with an error and no status, and calling those
199
+ // "PSScriptAnalyzer is not installed" latched PowerShell analysis off for the
200
+ // session on hosts where the module was present the whole time.
201
+ const moduleAbsent = result.status === 1 && !result.failure && !result.error;
202
+ const classified = classifyProbeFailure(result, {
203
+ hostStallMs,
204
+ ...(moduleAbsent && { unclassifiedFailureOutcome: "missing" }),
205
+ });
206
+ let { outcome, cause } = classified;
207
+ if (!moduleAbsent && outcome !== "transient") {
208
+ // Nothing here is evidence about the module, so the verdict must expire.
209
+ outcome = "transient";
210
+ cause = "probe-rejected";
211
+ }
212
+ notePsDecision(latch, "psscriptanalyzer", {
213
+ available: false,
214
+ outcome,
215
+ cause,
216
+ elapsedMs,
217
+ hostStallMs,
218
+ });
219
+ return false;
82
220
  }
221
+ /**
222
+ * `null` means "unreadable" (#1598): empty stdout, or stdout that fails to
223
+ * parse as JSON. `PS_SCRIPT` always writes either the literal `[]` marker or
224
+ * a `ConvertTo-Json` array/object on a genuine run, so anything else on an
225
+ * exit-0 run is not a clean-file answer -- it is a lost or truncated write
226
+ * between the child and us. The caller must not read `null` the same way it
227
+ * reads a real `[]`.
228
+ */
83
229
  function parsePSAnalyzerOutput(raw, filePath) {
84
- if (!raw.trim() || raw.trim() === "[]")
230
+ const trimmed = raw.trim();
231
+ if (!trimmed)
232
+ return null;
233
+ if (trimmed === "[]")
85
234
  return [];
86
235
  let parsed;
87
236
  try {
88
- parsed = JSON.parse(raw);
237
+ parsed = JSON.parse(trimmed);
89
238
  }
90
239
  catch {
91
- return [];
240
+ return null;
92
241
  }
93
242
  const items = Array.isArray(parsed) ? parsed : [parsed];
94
243
  return items
@@ -124,22 +273,149 @@ const psScriptAnalyzerRunner = {
124
273
  if (!(await checkModuleAvailable(cmd))) {
125
274
  return { status: "skipped", diagnostics: [], semantic: "none" };
126
275
  }
276
+ let execLatch = psExecLatchByCmd.get(cmd);
277
+ if (!execLatch) {
278
+ execLatch = createAvailabilityLatch();
279
+ psExecLatchByCmd.set(cmd, execLatch);
280
+ }
281
+ // A durably blocked `-File` execution (an execution policy, typically) is
282
+ // a fact about the interpreter, not about any one file -- re-spawning on
283
+ // every save would just re-confirm the same policy every time (#1540).
284
+ if (execLatch.read() === false) {
285
+ return { status: "skipped", diagnostics: [], semantic: "none" };
286
+ }
127
287
  const cwd = ctx.cwd || process.cwd();
128
288
  const absPath = path.resolve(cwd, ctx.filePath);
129
289
  // Write script to temp file so we avoid cmd.exe quoting entirely
130
290
  const tmpScript = path.join(os.tmpdir(), `pi-lens-psa-${process.pid}.ps1`);
131
291
  await fs.writeFile(tmpScript, PS_SCRIPT, "utf-8");
132
292
  try {
293
+ const sampler = startHostStallSampler();
294
+ const startedAt = Date.now();
133
295
  const result = await spawnPs(cmd, [
134
296
  "-NoProfile",
135
297
  "-NonInteractive",
136
298
  "-File", tmpScript,
137
299
  "-FilePath", absPath,
138
300
  ]);
301
+ const hostStallMs = sampler.stop();
302
+ const elapsedMs = Date.now() - startedAt;
303
+ // The two probes above use `-Command`, which PowerShell's execution
304
+ // policy does not gate; this run uses `-File`, which it does. A
305
+ // crashed/signal-killed process (#1540 second gap) or a blocked/
306
+ // erroring run (#1540 primary gap) must both produce a recorded,
307
+ // legible availability_decision -- never a silent "clean" read of
308
+ // empty stdout, and never a silent skip.
139
309
  if (result.status === null) {
310
+ const classified = classifyProbeFailure(result, { hostStallMs });
311
+ let { outcome, cause } = classified;
312
+ // A `spawn UNKNOWN`/EMFILE/crash never ran the script, so it is not
313
+ // evidence that `-File` is broken (mirrors the nonzero-exit guard
314
+ // below, and checkModuleAvailable's own guard against over-latching
315
+ // an unclassified failure). Latching an unspawnable prober's
316
+ // verdict would durably disable analysis for the whole session on
317
+ // a host where `-File` works fine.
318
+ if (outcome !== "transient") {
319
+ outcome = "transient";
320
+ cause = "probe-rejected";
321
+ }
322
+ notePsDecision(execLatch, "psscriptanalyzer-exec", {
323
+ available: false,
324
+ outcome,
325
+ cause,
326
+ elapsedMs,
327
+ hostStallMs,
328
+ });
329
+ incrementDegradationCount({
330
+ kind: "grammar-blocked",
331
+ subject: "psscriptanalyzer",
332
+ reason: `PowerShell (${cmd}) -File analysis exited with no status -- crash or signal-killed process`,
333
+ });
334
+ return { status: "skipped", diagnostics: [], semantic: "none" };
335
+ }
336
+ if (result.status !== 0) {
337
+ const stderrText = result.stderr ?? "";
338
+ // Require the SecurityError/UnauthorizedAccess pairing that a real
339
+ // script-load block produces, not a bare "unauthorizedaccess"
340
+ // match (#1556 review F4) -- that word alone also appears in
341
+ // unrelated file-permission and access-denied stderr that has
342
+ // nothing to do with the execution policy, and misclassifying it
343
+ // as `policy-denied` would latch analysis off durably for a
344
+ // transient, per-file permission error.
345
+ const policyDenied = /securityerror/i.test(stderrText) &&
346
+ (/execution polic(y|ies)/i.test(stderrText) ||
347
+ /running scripts is disabled/i.test(stderrText) ||
348
+ /unauthorizedaccess/i.test(stderrText));
349
+ const classified = classifyProbeFailure(result, { hostStallMs });
350
+ let { outcome, cause } = classified;
351
+ if (policyDenied) {
352
+ // Direct, unambiguous evidence: PowerShell's own execution
353
+ // policy refused to load the script. Durable until the policy
354
+ // changes, so this is the one case allowed to latch.
355
+ outcome = "non-installable";
356
+ cause = "policy-denied";
357
+ }
358
+ else if (outcome !== "transient") {
359
+ // A single nonzero exit is not durable evidence that `-File`
360
+ // is broken for every file (mirrors checkModuleAvailable's own
361
+ // guard against over-latching an unclassified failure).
362
+ outcome = "transient";
363
+ cause = "probe-rejected";
364
+ }
365
+ notePsDecision(execLatch, "psscriptanalyzer-exec", {
366
+ available: false,
367
+ outcome,
368
+ cause,
369
+ elapsedMs,
370
+ hostStallMs,
371
+ });
372
+ incrementDegradationCount({
373
+ kind: "grammar-blocked",
374
+ subject: "psscriptanalyzer",
375
+ reason: policyDenied
376
+ ? `PowerShell execution policy blocked -File analysis for ${cmd}: ${stderrText.trim().split("\n")[0] || "SecurityError"}`
377
+ : `PowerShell -File analysis for ${cmd} exited ${result.status}: ${stderrText.trim().slice(0, 200) || "no stderr"}`,
378
+ });
140
379
  return { status: "skipped", diagnostics: [], semantic: "none" };
141
380
  }
381
+ // `-File` ran and exited 0: direct proof it works. Log the decision
382
+ // once (memoized by the latch) rather than on every save.
383
+ if (execLatch.read() === null) {
384
+ notePsDecision(execLatch, "psscriptanalyzer-exec", {
385
+ available: true,
386
+ elapsedMs,
387
+ hostStallMs,
388
+ });
389
+ }
142
390
  const diagnostics = parsePSAnalyzerOutput(result.stdout, ctx.filePath);
391
+ if (diagnostics === null) {
392
+ // Exit 0 proves `-File` itself ran, so this does NOT reuse the
393
+ // exec latch above -- one bad read says nothing durable about the
394
+ // interpreter (mirrors the nonzero-exit branch's own guard against
395
+ // over-latching an unclassified failure), and it is logged rather
396
+ // than latched. It must still never be read as a clean file
397
+ // (#1598): empty/malformed stdout on a successful exit is a lost
398
+ // or truncated write, not evidence of zero diagnostics.
399
+ const unreadableReason = result.stdout.trim()
400
+ ? "unparseable"
401
+ : "empty";
402
+ logAvailabilityDecision({
403
+ tool: "psscriptanalyzer-stdout",
404
+ verdict: "unavailable",
405
+ outcome: "transient",
406
+ cause: "empty-result",
407
+ elapsedMs,
408
+ latched: false,
409
+ hostStallMs,
410
+ budgetMs: PS_TIMEOUT_MS,
411
+ });
412
+ incrementDegradationCount({
413
+ kind: "grammar-blocked",
414
+ subject: "psscriptanalyzer",
415
+ reason: `PowerShell -File analysis for ${cmd} exited 0 but stdout was ${unreadableReason}`,
416
+ });
417
+ return { status: "skipped", diagnostics: [], semantic: "none" };
418
+ }
143
419
  if (diagnostics.length === 0) {
144
420
  return { status: "succeeded", diagnostics: [], semantic: "none" };
145
421
  }
@@ -99,20 +99,53 @@ const pyrightRunner = {
99
99
  }
100
100
  },
101
101
  };
102
- function parsePyrightOutput(data, _filePath) {
102
+ /**
103
+ * Map pyright's own severity vocabulary onto the four-tier `Diagnostic.severity`
104
+ * (clients/dispatch/types.ts), the same way `normalizeBiomeSeverity` does for
105
+ * biome-check (#1791) and `normalizeRuleSeverity` does for ast-grep-napi
106
+ * (#1787). Pyright names its info tier `"information"`; `"error"`/`"warning"`
107
+ * pass through as-is. An unrecognized value falls back to `"warning"` — the
108
+ * tier every pyright diagnostic reported at before this fix, so reviving the
109
+ * info tier never silently demotes an existing finding. Pyright has no
110
+ * `"hint"` tier in its own vocabulary, so that tier is unreachable here.
111
+ */
112
+ export function normalizePyrightSeverity(raw) {
113
+ switch (raw) {
114
+ case "error":
115
+ return "error";
116
+ case "information":
117
+ return "info";
118
+ // "warning" and any unrecognized value fall back below — the tier
119
+ // every pyright diagnostic reported at before this fix, so reviving
120
+ // the info tier never silently demotes an existing finding. There is
121
+ // no separate `case "warning"` branch: it would be redundant with
122
+ // this default and unprovable as its own branch.
123
+ default:
124
+ return "warning";
125
+ }
126
+ }
127
+ export function parsePyrightOutput(data, _filePath) {
103
128
  const diagnostics = [];
104
129
  // Pyright JSON output has generalDiagnostics array
105
130
  const generalDiags = data.generalDiagnostics || [];
106
131
  for (const diag of generalDiags) {
107
132
  // Skip if not for this file (pyright may output diagnostics for imports)
108
133
  // For now, include all - caller will filter if needed
134
+ // pyright's `range.start.line`/`character` are zero-based (see the
135
+ // `PyrightDiagnostic` note above); `Diagnostic.line`/`column` are
136
+ // one-based, the same convention ast-grep-napi and taplo already
137
+ // convert to (`range.start.line + 1`). `range` itself is omitted when
138
+ // pyright has nothing to point at, so both fall back to line 1.
139
+ const start = diag.range?.start;
109
140
  diagnostics.push({
110
- id: `pyright-${diag.rule || diag.start?.line || "unknown"}`,
141
+ id: `pyright-${diag.rule || start?.line || "unknown"}`,
111
142
  message: diag.message || "Type error",
112
143
  filePath: diag.file || _filePath,
113
- line: diag.start?.line || 0,
114
- column: diag.start?.column || 0,
115
- severity: diag.severity === "error" ? "error" : "warning",
144
+ line: (start?.line ?? 0) + 1,
145
+ column: (start?.character ?? 0) + 1,
146
+ severity: normalizePyrightSeverity(diag.severity),
147
+ // Blocking classification stays error-only — reviving the info tier
148
+ // must never widen what fails a turn.
116
149
  semantic: diag.severity === "error" ? "blocking" : "warning",
117
150
  tool: "pyright",
118
151
  rule: diag.rule,
@@ -8,7 +8,8 @@ import { findNearestContaining } from "../../path-utils.js";
8
8
  import { RustClient } from "../../rust-client.js";
9
9
  import { safeSpawnAsync } from "../../safe-spawn.js";
10
10
  import { stripAnsi } from "../../sanitize.js";
11
- import { tryLazyInstall } from "./utils/lazy-installer.js";
11
+ import { getLazyInstallAttempt, tryLazyInstall, } from "./utils/lazy-installer.js";
12
+ import { describeInstallAttempt, logAvailabilityDecision, } from "./utils/availability-policy.js";
12
13
  import { PRIORITY } from "../priorities.js";
13
14
  import { createCwdCachedProbe } from "./utils/runner-helpers.js";
14
15
  const rustClient = new RustClient();
@@ -21,20 +22,22 @@ const rustClient = new RustClient();
21
22
  // after install. We can't `delete` a Promise mid-flight, so the safe path
22
23
  // is: if the cached probe resolves false AND the install just succeeded,
23
24
  // fall back to a one-shot fresh probe rather than reusing the cached false.
24
- const probeClippy = (cargoExe) => createCwdCachedProbe(async (cwd) => {
25
- const r = await safeSpawnAsync(cargoExe, ["clippy", "--version"], {
26
- timeout: 8000,
27
- cwd,
28
- });
29
- return !r.error && r.status === 0;
30
- });
31
- const clippyProbeByCargo = new Map();
25
+ //
26
+ // The verdict is governed by the shared availability policy (#1494): a probe
27
+ // timeout expires on a cooldown, so only a genuinely absent clippy latches.
28
+ const CLIPPY_PROBE_BUDGET_MS = 8000;
29
+ const makeClippyProbe = (cargoExe) => createCwdCachedProbe((cwd) => safeSpawnAsync(cargoExe, ["clippy", "--version"], {
30
+ timeout: CLIPPY_PROBE_BUDGET_MS,
31
+ cwd,
32
+ }), { tool: "clippy", budgetMs: CLIPPY_PROBE_BUDGET_MS });
33
+ const clippyAvailabilityByCargo = new Map();
32
34
  function getClippyProbe(cargoExe) {
33
- const existing = clippyProbeByCargo.get(cargoExe);
34
- if (existing)
35
- return existing;
36
- const created = probeClippy(cargoExe);
37
- clippyProbeByCargo.set(cargoExe, created);
35
+ return clippyAvailabilityByCargo.get(cargoExe) ?? refreshClippyProbe(cargoExe);
36
+ }
37
+ /** Replace the cached probe so a post-install state is observed. */
38
+ function refreshClippyProbe(cargoExe) {
39
+ const created = makeClippyProbe(cargoExe);
40
+ clippyAvailabilityByCargo.set(cargoExe, created);
38
41
  return created;
39
42
  }
40
43
  const rustClippyRunner = {
@@ -51,10 +54,35 @@ const rustClippyRunner = {
51
54
  }
52
55
  const clippyProbe = getClippyProbe(cargoExe);
53
56
  if (!(await clippyProbe(ctx.cwd))) {
57
+ // A timed-out probe is not evidence that clippy is missing, so it must
58
+ // not drive an install. Skip this turn and let the policy re-probe once
59
+ // its cooldown expires (#1494).
60
+ if (clippyProbe.getVerdict(ctx.cwd).outcome === "transient") {
61
+ return { status: "skipped", diagnostics: [], semantic: "none" };
62
+ }
54
63
  await tryLazyInstall("rust-clippy", ctx.cwd);
55
- // Bust the cwd-keyed cache so the post-install state is observed.
56
- clippyProbeByCargo.set(cargoExe, probeClippy(cargoExe));
57
- if (!(await getClippyProbe(cargoExe)(ctx.cwd))) {
64
+ // Bust the cwd-keyed cache so the post-install state is observed. Held in
65
+ // a local: `refreshClippyProbe` REPLACES the cached probe, so calling it
66
+ // again to read the verdict would spend a second probe.
67
+ const refreshedProbe = refreshClippyProbe(cargoExe);
68
+ if (!(await refreshedProbe(ctx.cwd))) {
69
+ // #1537: clippy is still absent, and the interesting question is WHY —
70
+ // "we tried `rustup component add` and the network failed" is a
71
+ // different fact from "this machine has no rustup", and until now the
72
+ // lazy installers recorded neither. Put the attempt beside the verdict
73
+ // it produced (#1500), so a reader does not have to infer the install
74
+ // from a runner that silently skipped.
75
+ const verdict = refreshedProbe.getVerdict(ctx.cwd);
76
+ logAvailabilityDecision({
77
+ tool: "rust-clippy",
78
+ verdict: "unavailable",
79
+ outcome: verdict.outcome ?? "missing",
80
+ cause: verdict.cause ?? "not-found",
81
+ classifiedBy: "caller",
82
+ evidence: describeInstallAttempt(getLazyInstallAttempt("rust-clippy", ctx.cwd)),
83
+ elapsedMs: 0,
84
+ latched: false,
85
+ });
58
86
  return { status: "skipped", diagnostics: [], semantic: "none" };
59
87
  }
60
88
  }
@@ -107,6 +135,49 @@ function findCargoToml(filePath) {
107
135
  const dir = findNearestContaining(dirname(filePath), ["Cargo.toml"]);
108
136
  return dir ? join(dir, "Cargo.toml") : undefined;
109
137
  }
138
+ /**
139
+ * #1802 fix round: the original review claimed rustc/clippy's top-level
140
+ * `compiler-message.level` is genuinely two-valued (error/warning). A live
141
+ * `cargo clippy --message-format=json` repro falsified that: rustc_errors's
142
+ * `Level` serializes SIX values, and top-level messages with a real primary
143
+ * span are NOT limited to error/warning —
144
+ *
145
+ * - `"error"` — a hard compiler/lint error.
146
+ * - `"warning"` — the common clippy-lint case.
147
+ * - `"note"` — DOES appear as a top-level message with a non-empty primary
148
+ * span (repro: an erroneous-constant note pointing at the offending
149
+ * expression), not only as a `message.children[].level` annotation.
150
+ * - `"help"` — a top-level suggestion-carrying message.
151
+ * - `"failure-note"` — observed with an empty `spans` array, so it is
152
+ * already filtered out by the `if (!span) continue` guard below; no
153
+ * diagnostic is ever built from it. Verified, not assumed.
154
+ * - `"error: internal compiler error"` — an ICE. The old `level === "error"`
155
+ * exact-match ternary silently mapped this to `"warning"`, because the
156
+ * string doesn't equal `"error"` — an ICE must never be under-reported,
157
+ * so it is normalized to `"error"` here explicitly.
158
+ *
159
+ * `note`/`help` don't have a `"blocking"`-worthy tier of their own in the
160
+ * four-valued `Diagnostic.severity`, so they land on the two quiet tiers:
161
+ * `note` → `hint`, `help` → `info`. Blocking stays derived from the
162
+ * *normalized* severity (not the raw string), which is what makes the ICE
163
+ * case blocking too instead of silently passing.
164
+ */
165
+ export function normalizeClippyLevel(raw) {
166
+ if (raw === "error")
167
+ return "error";
168
+ if (typeof raw === "string" &&
169
+ raw.startsWith("error: internal compiler error")) {
170
+ return "error";
171
+ }
172
+ if (raw === "note")
173
+ return "hint";
174
+ if (raw === "help")
175
+ return "info";
176
+ // "warning", "failure-note" (unreachable here, see above), and anything
177
+ // unrecognized fall back to "warning" — the tier every clippy diagnostic
178
+ // reported at before this fix.
179
+ return "warning";
180
+ }
110
181
  /**
111
182
  * Find a machine-applicable suggested replacement across the message's spans.
112
183
  * Clippy emits one diagnostic per warning but can attach the auto-fix to a
@@ -145,14 +216,21 @@ export function parseClippyOutput(raw, fallbackPath, cargoDir) {
145
216
  ? resolve(cargoDir, rawFile)
146
217
  : rawFile
147
218
  : fallbackPath;
219
+ // #1802: preserve clippy's real six-level vocabulary instead of
220
+ // collapsing it. See `normalizeClippyLevel` above for the verified
221
+ // mapping and the repro that falsified the original "two-valued"
222
+ // claim. Blocking is derived from the NORMALIZED severity so an ICE
223
+ // (`"error: internal compiler error"`) is blocking too, not just an
224
+ // exact-match `"error"` string.
225
+ const normalizedSeverity = normalizeClippyLevel(message.level);
148
226
  diagnostics.push({
149
227
  id: `clippy-${message.code?.code || "unknown"}`,
150
228
  message: message.message || "Clippy warning",
151
229
  filePath,
152
230
  line: span.line_start || 0,
153
231
  column: span.column_start || 0,
154
- severity: message.level === "error" ? "error" : "warning",
155
- semantic: message.level === "error" ? "blocking" : "warning",
232
+ severity: normalizedSeverity,
233
+ semantic: normalizedSeverity === "error" ? "blocking" : "warning",
156
234
  tool: "rust-clippy",
157
235
  rule: message.code?.code,
158
236
  defectClass: "correctness",