@herbertgao/pi-extensions 2026.8.13 → 2026.8.15

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 (263) hide show
  1. package/README.md +19 -14
  2. package/THIRD_PARTY_NOTICES.md +0 -1
  3. package/node_modules/@czottmann/pi-automode/CHANGELOG.md +19 -0
  4. package/node_modules/@czottmann/pi-automode/README.md +5 -1
  5. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/classifier.ts +126 -13
  6. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/config.ts +7 -3
  7. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/constants.ts +2 -0
  8. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/hard-deny.ts +134 -23
  9. package/node_modules/@czottmann/pi-automode/package.json +1 -1
  10. package/node_modules/@herbertgao/{pi-stash → pi-bark}/LICENSE +4 -5
  11. package/node_modules/@herbertgao/pi-bark/README.md +44 -0
  12. package/node_modules/@herbertgao/pi-bark/assets/pi-icon.png +0 -0
  13. package/node_modules/@herbertgao/pi-bark/package.json +53 -0
  14. package/node_modules/@herbertgao/pi-bark/src/index.ts +178 -0
  15. package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +1 -1
  16. package/node_modules/@herbertgao/pi-cc-extensions/README.md +1 -1
  17. package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
  18. package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +6 -0
  19. package/node_modules/@herbertgao/pi-subagents/package.json +2 -1
  20. package/node_modules/@herbertgao/pi-subagents/src/ui/conversation-viewer.ts +8 -1
  21. package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +2 -2
  22. package/node_modules/@narumitw/pi-btw/README.md +125 -88
  23. package/node_modules/@narumitw/pi-btw/dist/index.ts +140 -45
  24. package/node_modules/@narumitw/pi-btw/dist/index.ts.map +2 -2
  25. package/node_modules/@narumitw/pi-btw/package.json +5 -8
  26. package/node_modules/@narumitw/pi-btw/src/btw.ts +31 -26
  27. package/node_modules/@narumitw/pi-btw/src/fullscreen-ui.ts +106 -17
  28. package/node_modules/@narumitw/pi-btw/src/menu.ts +34 -2
  29. package/node_modules/@narumitw/pi-btw/src/settings.ts +16 -0
  30. package/node_modules/@tifan/pi-handoff/README.md +36 -13
  31. package/node_modules/@tifan/pi-handoff/package.json +2 -2
  32. package/node_modules/@tifan/pi-handoff/src/index.ts +160 -4
  33. package/node_modules/@tifan/pi-preferred-thinking/README.md +1 -1
  34. package/node_modules/@tifan/pi-preferred-thinking/package.json +1 -1
  35. package/node_modules/@tifan/pi-preferred-thinking/src/index.ts +15 -2
  36. package/node_modules/@tifan/pi-recap/README.md +1 -1
  37. package/node_modules/@tifan/pi-recap/package.json +2 -2
  38. package/node_modules/@tifan/pi-recap/src/index.ts +15 -0
  39. package/node_modules/@tifan/pi-rename/README.md +42 -21
  40. package/node_modules/@tifan/pi-rename/package.json +1 -1
  41. package/node_modules/@tifan/pi-rename/src/index.ts +62 -11
  42. package/node_modules/@tifan/pi-rename/src/language.ts +18 -0
  43. package/node_modules/@tifan/pi-rename/src/models.ts +69 -13
  44. package/node_modules/@tifan/pi-rename/src/naming.ts +36 -7
  45. package/node_modules/@tifan/pi-rename/src/sanitize.ts +22 -14
  46. package/node_modules/pi-lens/CHANGELOG.md +320 -0
  47. package/node_modules/pi-lens/config/dependency-cruiser-eager-allowlist.json +79 -0
  48. package/node_modules/pi-lens/dist/clients/actionable-warnings-logger.js +16 -1
  49. package/node_modules/pi-lens/dist/clients/ast-grep-client.js +13 -6
  50. package/node_modules/pi-lens/dist/clients/availability-probe-flight.js +12 -0
  51. package/node_modules/pi-lens/dist/clients/biome-client.js +7 -0
  52. package/node_modules/pi-lens/dist/clients/blocker-freshness.js +20 -0
  53. package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +56 -19
  54. package/node_modules/pi-lens/dist/clients/build-identity.js +95 -0
  55. package/node_modules/pi-lens/dist/clients/bus-publish.js +3 -0
  56. package/node_modules/pi-lens/dist/clients/cache/rule-cache.js +2 -1
  57. package/node_modules/pi-lens/dist/clients/cache-observability.js +2 -1
  58. package/node_modules/pi-lens/dist/clients/cascade-logger.js +15 -1
  59. package/node_modules/pi-lens/dist/clients/dead-code-client.js +14 -11
  60. package/node_modules/pi-lens/dist/clients/degradation-ledger.js +56 -4
  61. package/node_modules/pi-lens/dist/clients/demoted-finding-render.js +14 -0
  62. package/node_modules/pi-lens/dist/clients/dependency-checker.js +20 -3
  63. package/node_modules/pi-lens/dist/clients/deps/minimatch.js +1 -1
  64. package/node_modules/pi-lens/dist/clients/dispatch/collect-later-tier.js +39 -0
  65. package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +150 -9
  66. package/node_modules/pi-lens/dist/clients/dispatch/fact-store.js +231 -9
  67. package/node_modules/pi-lens/dist/clients/dispatch/integration.js +205 -100
  68. package/node_modules/pi-lens/dist/clients/dispatch/pending-runner-findings.js +87 -0
  69. package/node_modules/pi-lens/dist/clients/dispatch/plan.js +21 -5
  70. package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +174 -55
  71. package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +5 -1
  72. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-lint.js +16 -3
  73. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +54 -9
  74. package/node_modules/pi-lens/dist/clients/dispatch/runners/index.js +1 -3
  75. package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +37 -1
  76. package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +4 -1
  77. package/node_modules/pi-lens/dist/clients/dispatch/runners/oxlint.js +11 -4
  78. package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +4 -0
  79. package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +8 -3
  80. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +76 -25
  81. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +4 -0
  82. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/tool-failure.js +19 -7
  83. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +7 -13
  84. package/node_modules/pi-lens/dist/clients/dispatch/runners/yaml-rule-parser.js +51 -17
  85. package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +66 -1
  86. package/node_modules/pi-lens/dist/clients/file-utils.js +157 -7
  87. package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +5 -0
  88. package/node_modules/pi-lens/dist/clients/formatters.js +28 -3
  89. package/node_modules/pi-lens/dist/clients/git-guard.js +197 -47
  90. package/node_modules/pi-lens/dist/clients/git-tracked-ignore.js +32 -1
  91. package/node_modules/pi-lens/dist/clients/govulncheck-client.js +5 -1
  92. package/node_modules/pi-lens/dist/clients/gzip-stage-write.js +7 -0
  93. package/node_modules/pi-lens/dist/clients/installer/index.js +131 -34
  94. package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh.js +3 -1
  95. package/node_modules/pi-lens/dist/clients/instance-reaper.js +4 -13
  96. package/node_modules/pi-lens/dist/clients/instance-registry-lock.js +145 -0
  97. package/node_modules/pi-lens/dist/clients/instance-registry.js +432 -122
  98. package/node_modules/pi-lens/dist/clients/jscpd-client.js +6 -1
  99. package/node_modules/pi-lens/dist/clients/language-profile.js +4 -2
  100. package/node_modules/pi-lens/dist/clients/latency-logger.js +19 -1
  101. package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +8 -0
  102. package/node_modules/pi-lens/dist/clients/lens-map.js +3 -1
  103. package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +4 -0
  104. package/node_modules/pi-lens/dist/clients/lsp/client.js +516 -31
  105. package/node_modules/pi-lens/dist/clients/lsp/config.js +25 -2
  106. package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +9 -4
  107. package/node_modules/pi-lens/dist/clients/lsp/index.js +509 -182
  108. package/node_modules/pi-lens/dist/clients/lsp/jvm-runtime.js +5 -0
  109. package/node_modules/pi-lens/dist/clients/lsp/launch.js +36 -8
  110. package/node_modules/pi-lens/dist/clients/lsp/pending-aux-coverage.js +116 -4
  111. package/node_modules/pi-lens/dist/clients/lsp/server.js +142 -13
  112. package/node_modules/pi-lens/dist/clients/lsp/session-roots.js +99 -0
  113. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +16 -6
  114. package/node_modules/pi-lens/dist/clients/lsp/workspace-sweep-hold.js +32 -23
  115. package/node_modules/pi-lens/dist/clients/lsp-mutation.js +16 -2
  116. package/node_modules/pi-lens/dist/clients/mcp/analyze.js +12 -6
  117. package/node_modules/pi-lens/dist/clients/mcp/session.js +14 -1
  118. package/node_modules/pi-lens/dist/clients/memory-sampler.js +23 -1
  119. package/node_modules/pi-lens/dist/clients/message-end-attribution.js +30 -0
  120. package/node_modules/pi-lens/dist/clients/metrics-history.js +21 -4
  121. package/node_modules/pi-lens/dist/clients/opaque-mutation-scan.js +232 -18
  122. package/node_modules/pi-lens/dist/clients/package-manager.js +6 -18
  123. package/node_modules/pi-lens/dist/clients/path-keyed-map.js +14 -1
  124. package/node_modules/pi-lens/dist/clients/path-utils.js +32 -0
  125. package/node_modules/pi-lens/dist/clients/pipeline.js +0 -8
  126. package/node_modules/pi-lens/dist/clients/process-singletons.js +210 -0
  127. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/madge.js +2 -1
  128. package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +10 -4
  129. package/node_modules/pi-lens/dist/clients/project-snapshot.js +33 -11
  130. package/node_modules/pi-lens/dist/clients/read-guard-logger.js +24 -2
  131. package/node_modules/pi-lens/dist/clients/read-guard.js +74 -7
  132. package/node_modules/pi-lens/dist/clients/recent-touches.js +17 -5
  133. package/node_modules/pi-lens/dist/clients/review-graph/builder.js +639 -62
  134. package/node_modules/pi-lens/dist/clients/review-graph/tsconfig-paths.js +5 -1
  135. package/node_modules/pi-lens/dist/clients/review-graph-logger.js +21 -1
  136. package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +68 -0
  137. package/node_modules/pi-lens/dist/clients/runtime-session.js +30 -12
  138. package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +15 -0
  139. package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +106 -7
  140. package/node_modules/pi-lens/dist/clients/runtime-turn.js +242 -27
  141. package/node_modules/pi-lens/dist/clients/safe-spawn.js +158 -18
  142. package/node_modules/pi-lens/dist/clients/security-scan-client.js +48 -17
  143. package/node_modules/pi-lens/dist/clients/session-lifecycle.js +238 -42
  144. package/node_modules/pi-lens/dist/clients/sg-runner.js +27 -15
  145. package/node_modules/pi-lens/dist/clients/shared-checkout-guard.js +338 -0
  146. package/node_modules/pi-lens/dist/clients/spawn-output-cap.js +37 -0
  147. package/node_modules/pi-lens/dist/clients/spawn-timeout-cooldown.js +15 -1
  148. package/node_modules/pi-lens/dist/clients/startup-scan.js +6 -7
  149. package/node_modules/pi-lens/dist/clients/startup-timing.js +69 -4
  150. package/node_modules/pi-lens/dist/clients/string-utils.js +20 -0
  151. package/node_modules/pi-lens/dist/clients/test-runner-client.js +419 -88
  152. package/node_modules/pi-lens/dist/clients/tool-policy.js +1 -1
  153. package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +11 -5
  154. package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +17 -1
  155. package/node_modules/pi-lens/dist/clients/warm-attach.js +8 -10
  156. package/node_modules/pi-lens/dist/clients/word-index-logger.js +13 -1
  157. package/node_modules/pi-lens/dist/clients/word-index-store.js +448 -0
  158. package/node_modules/pi-lens/dist/clients/word-index.js +582 -104
  159. package/node_modules/pi-lens/dist/clients/workspace-topology.js +17 -0
  160. package/node_modules/pi-lens/dist/clients/zizmor-config.js +26 -6
  161. package/node_modules/pi-lens/dist/index.js +11243 -6651
  162. package/node_modules/pi-lens/dist/mcp/build-staleness.js +8 -7
  163. package/node_modules/pi-lens/dist/mcp/cli.js +3 -1
  164. package/node_modules/pi-lens/dist/mcp/server.js +6 -1
  165. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +4 -0
  166. package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +14 -5
  167. package/node_modules/pi-lens/dist/tools/lsp-navigation.js +3 -1
  168. package/node_modules/pi-lens/docs/agent-guide.md +4 -2
  169. package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +41 -10
  170. package/node_modules/pi-lens/docs/astplayground.md +26 -17
  171. package/node_modules/pi-lens/package.json +5 -1
  172. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/c-no-malloc-free-test.yml +20 -0
  173. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/cpp-no-malloc-free-test.yml +25 -0
  174. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/cpp-no-printf-test.yml +19 -0
  175. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/go-no-fmt-println-test.yml +19 -0
  176. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/go-no-panic-in-lib-test.yml +17 -0
  177. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/go-no-underscore-func-name-test.yml +18 -0
  178. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/go-prefer-errors-is-test.yml +15 -0
  179. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/go-prefer-string-builder-test.yml +21 -0
  180. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/hyphenated-svg-attribute-test.yml +11 -0
  181. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-no-lateinit-test.yml +17 -0
  182. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-no-nullable-boolean-test.yml +15 -0
  183. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-no-println-test.yml +19 -0
  184. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-no-unnecessary-let-test.yml +7 -0
  185. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-no-unsafe-cast-test.yml +7 -0
  186. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-no-var-test.yml +7 -0
  187. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-prefer-data-class-test.yml +25 -0
  188. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-prefer-is-empty-test.yml +11 -0
  189. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-important-test.yml +21 -0
  190. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-raw-types-test.yml +30 -0
  191. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-string-concat-in-loop-test.yml +100 -0
  192. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-system-out-println-test.yml +7 -0
  193. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/prefer-string-is-empty-test.yml +7 -0
  194. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/python-optional-type-test.yml +9 -0
  195. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/reducible-list-comprehension-test.yml +11 -0
  196. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/ruby-symbol-to-proc-candidate-test.yml +10 -0
  197. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/rust-no-chars-enumerate-test.yml +7 -0
  198. package/node_modules/pi-lens/rules/ast-grep-rules/rules/c-no-malloc-free.yml +39 -0
  199. package/node_modules/pi-lens/rules/ast-grep-rules/rules/cpp-no-malloc-free.yml +45 -0
  200. package/node_modules/pi-lens/rules/ast-grep-rules/rules/cpp-no-printf.yml +41 -0
  201. package/node_modules/pi-lens/rules/ast-grep-rules/rules/go-no-fmt-println.yml +43 -0
  202. package/node_modules/pi-lens/rules/ast-grep-rules/rules/go-no-panic-in-lib.yml +41 -0
  203. package/node_modules/pi-lens/rules/ast-grep-rules/rules/go-no-underscore-func-name.yml +35 -0
  204. package/node_modules/pi-lens/rules/ast-grep-rules/rules/go-prefer-errors-is.yml +40 -0
  205. package/node_modules/pi-lens/rules/ast-grep-rules/rules/go-prefer-string-builder.yml +43 -0
  206. package/node_modules/pi-lens/rules/ast-grep-rules/rules/hyphenated-svg-attribute.yml +32 -0
  207. package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-no-lateinit.yml +37 -0
  208. package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-no-nullable-boolean.yml +33 -0
  209. package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-no-println.yml +42 -0
  210. package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-no-unnecessary-let.yml +28 -0
  211. package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-no-unsafe-cast.yml +28 -0
  212. package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-no-var.yml +35 -0
  213. package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-prefer-data-class.yml +66 -0
  214. package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-prefer-is-empty.yml +35 -0
  215. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-important.yml +21 -0
  216. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-raw-types.yml +33 -0
  217. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-string-concat-in-loop.yml +90 -0
  218. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-system-out-println.yml +16 -0
  219. package/node_modules/pi-lens/rules/ast-grep-rules/rules/prefer-string-is-empty.yml +11 -0
  220. package/node_modules/pi-lens/rules/ast-grep-rules/rules/python-optional-type.yml +22 -0
  221. package/node_modules/pi-lens/rules/ast-grep-rules/rules/reducible-list-comprehension.yml +29 -0
  222. package/node_modules/pi-lens/rules/ast-grep-rules/rules/ruby-symbol-to-proc-candidate.yml +25 -0
  223. package/node_modules/pi-lens/rules/ast-grep-rules/rules/rust-no-chars-enumerate.yml +19 -0
  224. package/node_modules/pi-mcp-adapter/CHANGELOG.md +35 -0
  225. package/node_modules/pi-mcp-adapter/README.md +5 -2
  226. package/node_modules/pi-mcp-adapter/cli.js +4 -4
  227. package/node_modules/pi-mcp-adapter/commands.ts +1 -1
  228. package/node_modules/pi-mcp-adapter/config.ts +1 -0
  229. package/node_modules/pi-mcp-adapter/dist/config.js +1 -0
  230. package/node_modules/pi-mcp-adapter/dist/config.js.map +1 -1
  231. package/node_modules/pi-mcp-adapter/dist/mcp-bearer-store.d.ts +24 -0
  232. package/node_modules/pi-mcp-adapter/dist/mcp-bearer-store.js +336 -0
  233. package/node_modules/pi-mcp-adapter/dist/mcp-bearer-store.js.map +1 -0
  234. package/node_modules/pi-mcp-adapter/dist/types.d.ts +3 -1
  235. package/node_modules/pi-mcp-adapter/dist/types.js.map +1 -1
  236. package/node_modules/pi-mcp-adapter/index.ts +90 -6
  237. package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +124 -32
  238. package/node_modules/pi-mcp-adapter/mcp-bearer-store.ts +0 -2
  239. package/node_modules/pi-mcp-adapter/mcp-oauth-provider.ts +89 -0
  240. package/node_modules/pi-mcp-adapter/mcp-references.ts +9 -1
  241. package/node_modules/pi-mcp-adapter/package.json +1 -1
  242. package/node_modules/pi-mcp-adapter/proxy-modes.ts +62 -22
  243. package/node_modules/pi-mcp-adapter/request-headers-command.ts +1 -1
  244. package/node_modules/pi-mcp-adapter/server-manager.ts +6 -0
  245. package/node_modules/pi-mcp-adapter/types.ts +3 -1
  246. package/node_modules/pi-web-access/CHANGELOG.md +42 -0
  247. package/node_modules/pi-web-access/README.md +26 -10
  248. package/node_modules/pi-web-access/curator-page.ts +12 -3
  249. package/node_modules/pi-web-access/curator-server.ts +3 -1
  250. package/node_modules/pi-web-access/extract.ts +88 -13
  251. package/node_modules/pi-web-access/gemini-search.ts +10 -5
  252. package/node_modules/pi-web-access/github-extract.ts +47 -3
  253. package/node_modules/pi-web-access/index.ts +40 -40
  254. package/node_modules/pi-web-access/package.json +1 -1
  255. package/node_modules/pi-web-access/page-query.ts +61 -8
  256. package/node_modules/pi-web-access/xcrawl.ts +264 -0
  257. package/package.json +19 -22
  258. package/node_modules/@herbertgao/pi-stash/README.md +0 -34
  259. package/node_modules/@herbertgao/pi-stash/package.json +0 -51
  260. package/node_modules/@herbertgao/pi-stash/src/index.ts +0 -118
  261. package/node_modules/@tifan/pi-titlebar-spinner/README.md +0 -24
  262. package/node_modules/@tifan/pi-titlebar-spinner/package.json +0 -39
  263. package/node_modules/@tifan/pi-titlebar-spinner/src/index.ts +0 -49
@@ -2,7 +2,9 @@
2
2
  * ast-grep NAPI runner for dispatch system
3
3
  *
4
4
  * Uses @ast-grep/napi for programmatic parsing instead of CLI.
5
- * Handles TypeScript/JavaScript/CSS/HTML files with YAML rule support.
5
+ * The languages it can serve are exactly the grammars that addon bundles —
6
+ * see `NAPI_LANGUAGE_BINDINGS` for the matrix and for what the other twelve
7
+ * catalog languages deliver through instead.
6
8
  *
7
9
  * Replaces CLI-based runners for faster performance (100x speedup).
8
10
  */
@@ -15,9 +17,9 @@ import { logLatency } from "../../latency-logger.js";
15
17
  import { hasEslintConfig } from "../../tool-policy.js";
16
18
  import { enabledAuxiliaryLspServerIds } from "../auxiliary-lsp.js";
17
19
  import { classifyDefect } from "../diagnostic-taxonomy.js";
18
- import { recordDegradationOnce } from "../../degradation-ledger.js";
19
- import { isAuxiliaryLspAlive } from "../../lsp/index.js";
20
- import { resolveAstGrepNativeExe } from "../../lsp/wait-policy/index.js";
20
+ import { incrementDegradationCount, recordDegradationOnce, } from "../../degradation-ledger.js";
21
+ import { hasAuxiliaryLspPublishedForRoot } from "../../lsp/index.js";
22
+ import { clearPendingAuxiliaryCoverage, hasPendingAuxiliaryCoverage, recordNapiFallbackCoverage, } from "../../lsp/pending-aux-coverage.js";
21
23
  import { PRIORITY } from "../priorities.js";
22
24
  import { calculateRuleComplexity, isOverlyBroadPattern, isStructuredRule, loadYamlRules, loadYamlRulesFresh, MAX_BLOCKING_RULE_COMPLEXITY, } from "./yaml-rule-parser.js";
23
25
  const defaultUnsupportedLanguageLog = new Set();
@@ -200,8 +202,85 @@ export function resetAstGrepNapiLoadState() {
200
202
  sgSessionHold = false;
201
203
  sgHoldReason = undefined;
202
204
  }
203
- // Supported extensions for NAPI
204
- const SUPPORTED_EXTS = [".ts", ".tsx", ".js", ".jsx", ".css", ".html", ".htm"];
205
+ const NAPI_LANGUAGE_BINDINGS = [
206
+ // `.mts`/`.cts` and `.mjs`/`.cjs` are the same two grammars under a
207
+ // module-system-flavored extension; leaving them off the old hand list meant
208
+ // the whole catalog went dark on every ES/CommonJS module file (#2215).
209
+ {
210
+ ruleLanguage: "typescript",
211
+ napiExport: "ts",
212
+ extensions: [".ts", ".mts", ".cts"],
213
+ },
214
+ { ruleLanguage: "tsx", napiExport: "tsx", extensions: [".tsx"] },
215
+ {
216
+ ruleLanguage: "javascript",
217
+ napiExport: "js",
218
+ // `.jsx` stays on the `js` grammar it has always used here. The addon
219
+ // also exports a separate `jsx`, but switching to it is a matching
220
+ // change, not a coverage one, and belongs with its own fixtures.
221
+ extensions: [".js", ".jsx", ".mjs", ".cjs"],
222
+ },
223
+ { ruleLanguage: "css", napiExport: "css", extensions: [".css"] },
224
+ { ruleLanguage: "html", napiExport: "html", extensions: [".html", ".htm"] },
225
+ ];
226
+ const BINDING_BY_EXTENSION = new Map(NAPI_LANGUAGE_BINDINGS.flatMap((binding) => binding.extensions.map((ext) => [ext, binding])));
227
+ const SUPPORTED_RULE_LANGUAGES = NAPI_LANGUAGE_BINDINGS.map((binding) => binding.ruleLanguage);
228
+ /**
229
+ * Rule `language:` tokens the shipped catalog carries that NO bundled napi
230
+ * grammar can parse. Their rules deliver through the ast-grep LSP/CLI, which
231
+ * ships its own grammar set — never in-process. One shared reason, so this is
232
+ * a list of decisions rather than twelve copies of a sentence: the addon
233
+ * bundles six grammars (see `NAPI_LANGUAGE_BINDINGS`) and nothing registers
234
+ * more at runtime.
235
+ *
236
+ * The LSP/CLI half of that claim is measured, not assumed: every entry below
237
+ * was run through the real `ast-grep run -l <lang>` on 2026-08-26 and parsed,
238
+ * with `cobol` as the negative control (`cobol is not supported!`, exit 2). So
239
+ * no catalog language is unreachable by BOTH engines — the issue's
240
+ * "genuinely missing" bucket is empty, and this list is a routing fact rather
241
+ * than a coverage hole.
242
+ *
243
+ * This is the deliberate-exclusion half of the #2215 contract; the served half
244
+ * is derived from the addon itself. `ast-grep-napi-language-coverage.test.ts`
245
+ * reds when a catalog language with enabled rules is in neither half.
246
+ */
247
+ export const AST_GREP_LSP_ONLY_RULE_LANGUAGES = [
248
+ "c",
249
+ "cpp",
250
+ "csharp",
251
+ "go",
252
+ "java",
253
+ "kotlin",
254
+ "php",
255
+ "python",
256
+ "ruby",
257
+ "rust",
258
+ "scala",
259
+ "swift",
260
+ ];
261
+ const LSP_ONLY_RULE_LANGUAGES = new Set(AST_GREP_LSP_ONLY_RULE_LANGUAGES);
262
+ /**
263
+ * How a rule's declared language reaches the user. `unclassified` is the
264
+ * runtime signal that the #2215 class regrew: rules ship for a language nobody
265
+ * decided a delivery route for, so they may run nowhere at all.
266
+ */
267
+ export function deliveryRouteForRuleLanguage(ruleLanguage) {
268
+ if (SUPPORTED_RULE_LANGUAGES.includes(ruleLanguage))
269
+ return "napi";
270
+ return LSP_ONLY_RULE_LANGUAGES.has(ruleLanguage)
271
+ ? "ast-grep-lsp-cli"
272
+ : "unclassified";
273
+ }
274
+ /**
275
+ * Delivery route for one aggregated skip key. A `mismatch:<rule>-><file>` key
276
+ * is a rule whose language this engine DOES serve and that simply isn't this
277
+ * file's grammar, so it still routes through napi on its own files.
278
+ */
279
+ function skipRouteFor(key) {
280
+ return key.startsWith("mismatch:")
281
+ ? "napi"
282
+ : deliveryRouteForRuleLanguage(key);
283
+ }
205
284
  /** Maximum matches per rule to prevent excessive false positives */
206
285
  const MAX_MATCHES_PER_RULE = 10;
207
286
  /** Maximum total diagnostics per file to prevent output spam */
@@ -302,7 +381,7 @@ function matchesRuleIgnores(filePath, root, patterns) {
302
381
  return patterns.some((pattern) => minimatch(rel, pattern, { dot: true }));
303
382
  }
304
383
  export function canHandle(filePath) {
305
- return SUPPORTED_EXTS.includes(path.extname(filePath).toLowerCase());
384
+ return BINDING_BY_EXTENSION.has(path.extname(filePath).toLowerCase());
306
385
  }
307
386
  /**
308
387
  * The TypeScript grammar is a syntactic superset of JavaScript, so a
@@ -327,41 +406,35 @@ export function canHandle(filePath) {
327
406
  * parsed as tsx (ast-grep-tsx-coverage.test.ts) rather than assumed.
328
407
  * Without this exception the entire TS ruleset silently never runs on
329
408
  * `.tsx` files. `TSX`-tagged rules stay `.tsx`-exclusive; the exception
330
- * is TS→TSX only. Returns undefined for extensions this scoping doesn't
331
- * apply to (css/html), where no filtering is added.
409
+ * is TS→TSX only. Returns undefined for an extension outside the matrix.
332
410
  */
333
411
  export function ruleLanguageForFile(filePath) {
334
- const ext = path.extname(filePath).toLowerCase();
335
- switch (ext) {
336
- case ".ts":
337
- return "typescript";
338
- case ".tsx":
339
- return "tsx";
340
- case ".js":
341
- case ".jsx":
342
- return "javascript";
343
- default:
344
- return undefined;
345
- }
412
+ return BINDING_BY_EXTENSION.get(path.extname(filePath).toLowerCase())
413
+ ?.ruleLanguage;
346
414
  }
415
+ /**
416
+ * The grammar for `filePath` on the addon that actually loaded, or undefined
417
+ * when there is none.
418
+ *
419
+ * Both callers (this file's runner and clients/project-diagnostics/scanner.ts)
420
+ * gate on `canHandle` first, so an undefined return HERE means the matrix
421
+ * admitted the file and the addon then dropped it — the silent skip #2215 is
422
+ * about, which is why it records instead of just returning. An extension
423
+ * outside the matrix was never admitted, so it records nothing.
424
+ */
347
425
  export function getLang(filePath, sgModule) {
348
- const ext = path.extname(filePath).toLowerCase();
349
- switch (ext) {
350
- case ".ts":
351
- return sgModule.ts;
352
- case ".tsx":
353
- return sgModule.tsx;
354
- case ".js":
355
- case ".jsx":
356
- return sgModule.js;
357
- case ".css":
358
- return sgModule.css;
359
- case ".html":
360
- case ".htm":
361
- return sgModule.html;
362
- default:
363
- return undefined;
426
+ const binding = BINDING_BY_EXTENSION.get(path.extname(filePath).toLowerCase());
427
+ if (!binding)
428
+ return undefined;
429
+ const lang = sgModule[binding.napiExport];
430
+ if (!lang) {
431
+ recordDegradationOnce({
432
+ kind: "ast-grep-napi-language-unavailable",
433
+ subject: binding.ruleLanguage,
434
+ reason: `@ast-grep/napi exposes no "${binding.napiExport}" grammar; every ${binding.ruleLanguage} rule is skipped in-process this session`,
435
+ });
364
436
  }
437
+ return lang;
365
438
  }
366
439
  function duplicateRuleIds(rules) {
367
440
  const counts = new Map();
@@ -467,6 +540,13 @@ export function evaluateAstGrepRules(filePath, rootNode, cwd, kind, options = {}
467
540
  {
468
541
  count: ruleIds.length,
469
542
  ruleIds: ruleIds.slice(0, UNSUPPORTED_RULE_ID_SAMPLE_SIZE),
543
+ // #2215: a skip count alone says a rule did not run and
544
+ // nothing about whether it runs anywhere else. `route` names
545
+ // the delivery path (`ast-grep-lsp-cli` for the twelve
546
+ // grammar-less catalog languages); `unclassified` means rules
547
+ // ship for a language nobody decided a route for, which is the
548
+ // class this issue closed regrowing.
549
+ route: skipRouteFor(language),
470
550
  },
471
551
  ])),
472
552
  },
@@ -520,11 +600,14 @@ export function evaluateAstGrepRules(filePath, rootNode, cwd, kind, options = {}
520
600
  !isStructuredRule(rule)) {
521
601
  continue;
522
602
  }
603
+ // CSS and HTML rules are scoped to their parsed roots.
604
+ // Without this scope, language-tagged rules scan unrelated roots.
605
+ // The file-language mismatch check below enforces this scope.
606
+ // CSS rules therefore run only on CSS roots.
607
+ // This preserves CSS rule findings while avoiding unrelated scans.
608
+ // The fallback and LSP paths share the same parsed-language scope.
523
609
  const lang = rule.language?.toLowerCase();
524
- if (lang &&
525
- lang !== "typescript" &&
526
- lang !== "tsx" &&
527
- lang !== "javascript") {
610
+ if (lang && !SUPPORTED_RULE_LANGUAGES.includes(lang)) {
528
611
  if (!unsupportedLanguageLog.has(lang)) {
529
612
  const ids = newlyUnsupported.get(lang) ?? [];
530
613
  ids.push(rule.id);
@@ -640,7 +723,7 @@ export function evaluateAstGrepRules(filePath, rootNode, cwd, kind, options = {}
640
723
  // --- Runner Definition ---
641
724
  const astGrepNapiRunner = {
642
725
  id: "ast-grep-napi",
643
- appliesTo: ["jsts"],
726
+ appliesTo: ["jsts", "css", "html"],
644
727
  priority: PRIORITY.SPECIALIZED_ANALYSIS,
645
728
  enabledByDefault: true,
646
729
  skipTestFiles: true,
@@ -654,20 +737,31 @@ const astGrepNapiRunner = {
654
737
  // report against the LSP's `tool: ast-grep` diagnostics. Resume ONLY as the
655
738
  // fallback when the binary is absent / can't spawn (Gate B).
656
739
  const astGrepLspEnabled = enabledAuxiliaryLspServerIds((f) => ctx.pi?.getFlag?.(f)).includes("ast-grep");
657
- // Gate B asks whether the LSP will handle this file, not whether a bare
658
- // `ast-grep` command happens to be on PATH. The launcher first tries the
659
- // platform-native package binary, then PATH; mirror that resolution here.
660
- // A live client covers the already-warm case, while a resolvable binary
661
- // covers the cold case before the LSP has spawned for this root.
662
- const astGrepLspAlive = astGrepLspEnabled
663
- ? await isAuxiliaryLspAlive("ast-grep", ctx.filePath)
740
+ // Gate B asks whether the LSP has proved it can handle this root, not
741
+ // whether an ast-grep process or binary merely exists.
742
+ // A first publication covers the warm case. Process liveness and binary
743
+ // resolution do not: both precede proof that this root can publish findings.
744
+ const astGrepLspPublished = astGrepLspEnabled
745
+ ? await hasAuxiliaryLspPublishedForRoot("ast-grep", ctx.filePath)
664
746
  : false;
665
- let astGrepBinaryResolvable = false;
666
- if (astGrepLspEnabled && !astGrepLspAlive) {
667
- astGrepBinaryResolvable =
668
- Boolean(resolveAstGrepNativeExe()) || (await ctx.hasTool("ast-grep"));
669
- }
670
- if (astGrepLspEnabled && (astGrepLspAlive || astGrepBinaryResolvable)) {
747
+ if (astGrepLspEnabled && astGrepLspPublished) {
748
+ // #2324 F2/R2-C: "has this server EVER published for this file" can go
749
+ // stale — a LATER touch's aux-grace wait can find the server silent
750
+ // for the CURRENT content while an OLDER revision's publication still
751
+ // satisfies this per-file gate. Any pending late-aux entry visible
752
+ // HERE is necessarily a LEFTOVER from an earlier touch, never this
753
+ // one: the wait that marks a pair for THIS touch runs to completion
754
+ // (up to its own grace budget, ~1800ms) strictly AFTER this
755
+ // synchronous Gate-B check returns, so it cannot have marked
756
+ // anything yet. Re-running napi here would risk the F3 duplicate
757
+ // this fix closes, so the loss is made observable instead.
758
+ if (hasPendingAuxiliaryCoverage(ctx.filePath, "ast-grep")) {
759
+ incrementDegradationCount({
760
+ kind: "aux-runner-findings-lost",
761
+ subject: "ast-grep",
762
+ reason: `Gate B skipped napi for ${ctx.filePath}: a pending late-auxiliary pair from an EARLIER touch is still undelivered while a prior publication satisfies the per-file gate`,
763
+ });
764
+ }
671
765
  return { status: "skipped", diagnostics: [], semantic: "none" };
672
766
  }
673
767
  const sgModule = await loadSg();
@@ -718,6 +812,31 @@ const astGrepNapiRunner = {
718
812
  catch {
719
813
  return { status: "skipped", diagnostics: [], semantic: "none" };
720
814
  }
815
+ if (astGrepLspEnabled && !astGrepLspPublished) {
816
+ // #2324 F3/R2-A/R2-B: napi is about to ACTUALLY EVALUATE RULES —
817
+ // every early-return skip above (load failure, missing file,
818
+ // unresolved language, stat/size/read/parse failure) is now behind
819
+ // us, so this run genuinely covers the file rather than reporting a
820
+ // zero-finding no-op. Placing this here (not at Gate-B's decision
821
+ // point) matters twice over:
822
+ // - R2-B: a napi run that never reached rule evaluation must NOT
823
+ // consume anything — an unparseable .html file, say, would
824
+ // otherwise silence the LSP's legitimate late delivery for a
825
+ // file napi never actually covered.
826
+ // - R2-A: recording coverage HERE, keyed by timestamp, lets the
827
+ // aux-grace wait (clients/lsp/index.ts) — which decides whether
828
+ // to mark a pending pair for THIS touch strictly AFTER this
829
+ // synchronous call returns — see that napi already delivered
830
+ // and skip marking, instead of racing a clear against a mark
831
+ // that has not been written yet.
832
+ // The clear below only ever removes a LEFTOVER pair from an
833
+ // EARLIER touch (this touch's own pair, if the wait decides to
834
+ // mark one, is marked strictly later) — that pair describes a
835
+ // PREVIOUS revision this fresh evaluation supersedes, so dropping
836
+ // it is safe. Unknown pairs are a no-op.
837
+ recordNapiFallbackCoverage(ctx.filePath);
838
+ clearPendingAuxiliaryCoverage(ctx.filePath, "ast-grep");
839
+ }
721
840
  const diagnostics = evaluateAstGrepRules(ctx.filePath, rootNode, ctx.cwd, ctx.kind, {
722
841
  blockingOnly: ctx.blockingOnly,
723
842
  projectRoot: ctx.projectRoot,
@@ -24,7 +24,11 @@ function makeEslintProbe(cmd) {
24
24
  return createCwdCachedProbe((cwd) => safeSpawnAsync(cmd, ["--version"], {
25
25
  timeout: ESLINT_PROBE_BUDGET_MS,
26
26
  cwd,
27
- }), { tool: "eslint", budgetMs: ESLINT_PROBE_BUDGET_MS });
27
+ }), {
28
+ tool: "eslint",
29
+ budgetMs: ESLINT_PROBE_BUDGET_MS,
30
+ flightKeyComponent: cmd,
31
+ });
28
32
  }
29
33
  const eslintAvailabilityByCmd = new Map();
30
34
  function getEslintProbe(cmd) {
@@ -2,12 +2,18 @@ import * as path from "node:path";
2
2
  import { PathKeyedMap } from "../../path-keyed-map.js";
3
3
  import { normalizeMapKey } from "../../path-utils.js";
4
4
  import { safeSpawnAsync } from "../../safe-spawn.js";
5
+ import { truncatedByOutputCap } from "../../spawn-output-cap.js";
5
6
  import { findNearestDirWithMarker } from "../../workspace-topology.js";
6
7
  import { PRIORITY } from "../priorities.js";
7
8
  import { createAvailabilityChecker, resolveAvailableOrInstall, } from "./utils/runner-helpers.js";
8
9
  const helm = createAvailabilityChecker("helm", ".exe");
9
10
  const inFlightByChartRoot = new PathKeyedMap(normalizeMapKey);
10
11
  const HELM_LINT_TIMEOUT_MS = 30_000;
12
+ // `helm lint` prints one `[LEVEL]` line per finding for one chart, so nothing
13
+ // legitimate approaches 8 MiB: this bounds a wedged or runaway helm rather than
14
+ // real lint output, and it is what makes `outputTruncated` reachable for this
15
+ // runner at all (#2100). Same value as the sibling report caps.
16
+ const MAX_HELM_LINT_OUTPUT_BYTES = 8 * 1024 * 1024;
11
17
  const SKIPPED = {
12
18
  status: "skipped",
13
19
  diagnostics: [],
@@ -93,7 +99,17 @@ async function lintChart(chartRoot, cwd) {
93
99
  timeout: HELM_LINT_TIMEOUT_MS,
94
100
  deadlineAt: Date.now() + HELM_LINT_TIMEOUT_MS,
95
101
  resourceLabel: "helm-lint",
102
+ maxOutputBytes: MAX_HELM_LINT_OUTPUT_BYTES,
96
103
  });
104
+ // FIRST (#2100): the cap kill is OUR SIGTERM, so it also sets
105
+ // `spawnFailure.kind === "killed"` and `failure === "signal"`. Read after
106
+ // those two, a truncated lint was reported as a server_error — helm blamed
107
+ // for output we threw away. A timed-out or aborted run carries
108
+ // `outputTruncated` too, and `truncatedByOutputCap` leaves those to the
109
+ // typed-failure block below so they keep reporting timeout/aborted.
110
+ if (truncatedByOutputCap(result)) {
111
+ return failedResult("parser_error", "helm lint output was truncated before parsing completed");
112
+ }
97
113
  const output = [result.stdout, result.stderr].filter(Boolean).join("\n");
98
114
  const diagnostics = parseHelmLintOutput(output, chartRoot);
99
115
  const typedFailure = result.spawnFailure?.kind;
@@ -110,9 +126,6 @@ async function lintChart(chartRoot, cwd) {
110
126
  if (result.failure) {
111
127
  return failedResult(result.failure === "timeout" ? "timeout" : result.failure, result.error?.message || `helm lint ${result.failure}`);
112
128
  }
113
- if (result.outputTruncated) {
114
- return failedResult("parser_error", "helm lint output was truncated before parsing completed");
115
- }
116
129
  const hasErrorDiagnostic = diagnostics.some((diagnostic) => diagnostic.severity === "error");
117
130
  if (result.status !== 0 && !hasErrorDiagnostic) {
118
131
  return failedResult(output.trim() ? "parser_error" : "server_error", output.trim() || "helm lint exited without an error diagnostic");
@@ -78,6 +78,7 @@ import { normalizeMapKey } from "../../path-utils.js";
78
78
  import { loadPiLensProjectConfig } from "../../project-lens-config.js";
79
79
  import { getProjectTrustState, projectTrustDenialReason, } from "../../project-trust.js";
80
80
  import { safeSpawnAsync } from "../../safe-spawn.js";
81
+ import { killedForOutputCap, truncatedByOutputCap, } from "../../spawn-output-cap.js";
81
82
  import { isTrivyEnabled, resolveSeverityFloor } from "../../trivy-client.js";
82
83
  import { findNearestDirWithMarker } from "../../workspace-topology.js";
83
84
  import { PRIORITY } from "../priorities.js";
@@ -100,6 +101,15 @@ const TRIVY_TIMEOUT_MS = 45_000;
100
101
  const MAX_RENDERED_FILES = 400;
101
102
  /** Bytes of trivy report we retain. Truncation is reported, never parsed past. */
102
103
  const MAX_REPORT_BYTES = 8 * 1024 * 1024;
104
+ /**
105
+ * Bytes of `helm template` output we retain. The manifests go to `--output-dir`,
106
+ * so stdout is one short "wrote <path>" line per file — bounded near
107
+ * {@link MAX_RENDERED_FILES} — and stderr carries template errors. 8 MiB is
108
+ * therefore a blast-radius bound on a chart that loops or a helm that wedges,
109
+ * not a working limit, and it is what makes `render.outputTruncated` reachable
110
+ * at all (#2100).
111
+ */
112
+ const MAX_RENDER_OUTPUT_BYTES = 8 * 1024 * 1024;
103
113
  const SKIPPED = {
104
114
  status: "skipped",
105
115
  diagnostics: [],
@@ -661,6 +671,16 @@ export async function runIacPass(options) {
661
671
  // hold in memory rather than trusting the chart's size (shape 9).
662
672
  maxOutputBytes: MAX_REPORT_BYTES,
663
673
  });
674
+ // FIRST (#2100): hitting the cap makes safe-spawn SIGTERM trivy, so a
675
+ // truncated report also arrives as a killed spawn with a null status. Read
676
+ // after the gate below, this said "the IaC pass failed" when what actually
677
+ // happened is that WE stopped reading. A timed-out or aborted scan carries
678
+ // the flag too and stays with the failure gate, which names its real cause.
679
+ if (truncatedByOutputCap(scan)) {
680
+ return [
681
+ coverageGap(chartRoot, "iac-report-unreadable", `Rendered-manifest IaC checks did not run: the trivy report exceeded ${MAX_REPORT_BYTES} bytes and was truncated before parsing.`),
682
+ ];
683
+ }
664
684
  const scanFailure = spawnFailureKind(scan);
665
685
  // `trivy config` is not given `--exit-code`, so it exits 0 whenever it
666
686
  // completed. ANY nonzero status is therefore a real error, with or without
@@ -673,11 +693,6 @@ export async function runIacPass(options) {
673
693
  coverageGap(chartRoot, "iac-pass-failed", `Rendered-manifest IaC checks failed to complete (${why}): ${detail}`),
674
694
  ];
675
695
  }
676
- if (scan.outputTruncated) {
677
- return [
678
- coverageGap(chartRoot, "iac-report-unreadable", `Rendered-manifest IaC checks did not run: the trivy report exceeded ${MAX_REPORT_BYTES} bytes and was truncated before parsing.`),
679
- ];
680
- }
681
696
  const sourceByRendered = new PathKeyedMap(normalizeMapKey);
682
697
  for (const manifest of options.manifests) {
683
698
  sourceByRendered.set(manifest.renderedPath, {
@@ -754,7 +769,30 @@ async function renderAndValidate(chartRoot, cwd, filePath) {
754
769
  timeout: RENDER_TIMEOUT_MS,
755
770
  deadlineAt: Date.now() + RENDER_TIMEOUT_MS,
756
771
  resourceLabel: "helm-render",
772
+ maxOutputBytes: MAX_RENDER_OUTPUT_BYTES,
757
773
  });
774
+ const renderOutput = [render.stdout, render.stderr]
775
+ .filter(Boolean)
776
+ .join("\n");
777
+ // `killedForOutputCap` is platform-neutral. POSIX usually reports SIGTERM,
778
+ // but Windows reports status 1 with no failure or signal. A timed-out or
779
+ // aborted render retains its own classification.
780
+ const renderCapped = truncatedByOutputCap(render);
781
+ if (killedForOutputCap(render)) {
782
+ // helm was cut off mid-render, so the scratch tree is a PREFIX —
783
+ // reading it back would report the manifests we never saw as clean
784
+ // (shape 10). Not parsed as a chart failure either: helm never reported
785
+ // a verdict, and `parseHelmTemplateFailure` synthesizes a BLOCKING
786
+ // "helm template failed" finding when it sees no `Error:` line, which
787
+ // would blame the chart for our own cap (the #1487 inversion).
788
+ logRenderPass(filePath, chartRoot, startedAt, {
789
+ outcome: "render-truncated",
790
+ diagnostics: 1,
791
+ });
792
+ return summarize([
793
+ coverageGap(chartRoot, "render-truncated", `helm template produced more than ${MAX_RENDER_OUTPUT_BYTES} bytes and was stopped mid-render, so the rendered manifests are UNCHECKED rather than clean.`),
794
+ ]);
795
+ }
758
796
  const startupFailure = spawnFailureKind(render);
759
797
  if (startupFailure) {
760
798
  // The runner never got a verdict about the chart. Reporting this as a
@@ -766,9 +804,6 @@ async function renderAndValidate(chartRoot, cwd, filePath) {
766
804
  });
767
805
  return failedResult(startupFailure, render.error?.message || `helm template ${startupFailure}`);
768
806
  }
769
- const renderOutput = [render.stdout, render.stderr]
770
- .filter(Boolean)
771
- .join("\n");
772
807
  if (render.status !== 0) {
773
808
  // Before blaming the chart: did helm reject OUR OWN command line? An old
774
809
  // helm (v2 has no `helm template --output-dir`) or a wrapper shim exits
@@ -785,7 +820,9 @@ async function renderAndValidate(chartRoot, cwd, filePath) {
785
820
  }
786
821
  // A failed RENDER is a real diagnostic: the chart cannot be installed.
787
822
  const diagnostics = parseHelmTemplateFailure(renderOutput, chartRoot);
788
- if (render.outputTruncated) {
823
+ if (renderCapped) {
824
+ // helm reported this failure itself, but we kept only a prefix of
825
+ // what it said, so the report above may be missing errors.
789
826
  diagnostics.push(coverageGap(chartRoot, "render-truncated", "helm template output was truncated, so this failure report may be incomplete."));
790
827
  }
791
828
  logRenderPass(filePath, chartRoot, startedAt, {
@@ -804,6 +841,14 @@ async function renderAndValidate(chartRoot, cwd, filePath) {
804
841
  sourceMapped: manifest.sourceMapped,
805
842
  }));
806
843
  }
844
+ if (renderCapped) {
845
+ // helm exited 0, so `--output-dir` holds the COMPLETE tree and every
846
+ // manifest above was validated from disk — the cap only cost us the
847
+ // "wrote <path>" lines on stdout, which this runner never parses.
848
+ // Recorded rather than silent (shape 8), but deliberately not an
849
+ // UNCHECKED claim: nothing was skipped (review F1).
850
+ diagnostics.push(coverageGap(chartRoot, "render-output-truncated", `helm template printed more than ${MAX_RENDER_OUTPUT_BYTES} bytes and its stdout was truncated. The rendered manifests were still validated from disk; only helm's own progress output was lost.`));
851
+ }
807
852
  if (tree.truncated) {
808
853
  // A partly-validated chart reported as clean is the shape-10 trap.
809
854
  diagnostics.push(coverageGap(chartRoot, "render-truncated", `This chart rendered more than ${MAX_RENDERED_FILES} manifests; only the first ${MAX_RENDERED_FILES} were validated, so the rest are UNCHECKED rather than clean.`));
@@ -57,9 +57,7 @@ export function registerDefaultRunners(registry) {
57
57
  registry.register(lspRunner); // Unified LSP type-checking for all languages (priority 4)
58
58
  registry.register(pyrightRunner); // Python type-checking (priority 5) - fallback when --lens-lsp disabled
59
59
  registry.register(biomeCheckJsonRunner); // Biome check with JSON output for diagnostic capture (priority 9)
60
- // DISABLED in post-write dispatch - ast-grep-napi can crash. Runs in the
61
- // project-wide pass via lens_diagnostics mode=full (refreshRunners) instead.
62
- registry.register(astGrepNapiRunner); // TS/JS structural analysis via NAPI (priority 15, post-write disabled)
60
+ registry.register(astGrepNapiRunner); // JS/TS, CSS, and HTML structural analysis via NAPI (priority 15)
63
61
  registry.register(treeSitterRunner); // Tree-sitter structural analysis (priority 14)
64
62
  registry.register(ruffRunner); // Python linting (priority 10)
65
63
  registry.register(shellcheckRunner); // Shell script linting (priority 20)
@@ -15,6 +15,7 @@ import { logExtension } from "../../extension-log.js";
15
15
  import { getLspCapableKinds } from "../../language-policy.js";
16
16
  import { touchCoverageGap } from "../../lsp/diagnostic-binding.js";
17
17
  import { getLSPService } from "../../lsp/index.js";
18
+ import { LSP_SERVERS } from "../../lsp/server.js";
18
19
  import { RUNTIME_CONFIG } from "../../runtime-config.js";
19
20
  import { PRIORITY } from "../priorities.js";
20
21
  import { resolveRunnerPath } from "../runner-context.js";
@@ -32,8 +33,42 @@ const LSP_SPAWN_BUDGET_MS = RUNTIME_CONFIG.pipeline.lspSpawnBudgetMs;
32
33
  // can't dominate the per-edit pipeline budget. Diagnostics that arrive
33
34
  // after the cap still land in the client's cache and surface on the
34
35
  // next edit. Overridable via PI_LENS_LSP_DIAGNOSTICS_MAX_WAIT_MS.
35
- const LSP_DIAGNOSTICS_WAIT_MS = 2500;
36
+ export const LSP_DIAGNOSTICS_WAIT_MS = 2500;
36
37
  const MAX_CODE_ACTION_TITLES = 3;
38
+ /**
39
+ * Fixed margin above the worst-case cold-spawn-plus-diagnostics wait, so
40
+ * scheduling jitter alone can't reopen the F2 race below.
41
+ */
42
+ const LSP_COLD_SPAWN_MARGIN_MS = 5_000;
43
+ /**
44
+ * This runner's own dispatch wall-clock budget. `runRunner` in
45
+ * `dispatcher.ts` races `runner.timeoutMs ?? RUNNER_TIMEOUT_MS` (30s)
46
+ * against `runner.run()` for the WHOLE call — reading the file, waiting for
47
+ * a client (bounded by `LSP_SPAWN_BUDGET_MS`, raised per-server via
48
+ * `LSPServerInfo.clientWaitTimeoutMs`), then waiting for diagnostics
49
+ * (`LSP_DIAGNOSTICS_WAIT_MS`).
50
+ *
51
+ * Left undeclared, this runner inherited the shared 30s default. A server
52
+ * whose `clientWaitTimeoutMs` exceeds that — Prisma's 40s, Vue's 30s
53
+ * (#2169, #2176) — could never actually use its own raised wait: the outer
54
+ * race always fired first, at 30s, so raising a server's floor without also
55
+ * raising this ceiling was a no-op on the real dispatch path (fix-round F2,
56
+ * #2233). Derive the ceiling from the registry instead of a second
57
+ * hand-picked literal, so the next slow server's `clientWaitTimeoutMs` keeps
58
+ * this budget correct automatically: the highest declared
59
+ * `clientWaitTimeoutMs` (or the shared 5s default, whichever is larger),
60
+ * plus the diagnostics wait that still runs after a cold spawn succeeds,
61
+ * plus a fixed margin.
62
+ *
63
+ * This raises the failure-detection latency for every OTHER language's
64
+ * hung-server case too (30s -> the value below), not just the five servers
65
+ * that need the headroom — the tradeoff is stated in PR #2233's body. It
66
+ * remains well inside the spread other dispatch runners already use
67
+ * (pyright 75s, golangci-lint/dotnet-build/rust-clippy 90s).
68
+ */
69
+ export const LSP_RUNNER_TIMEOUT_MS = Math.max(LSP_SPAWN_BUDGET_MS, ...LSP_SERVERS.map((server) => server.clientWaitTimeoutMs ?? 0)) +
70
+ LSP_DIAGNOSTICS_WAIT_MS +
71
+ LSP_COLD_SPAWN_MARGIN_MS;
37
72
  function normalizeActionTitle(title) {
38
73
  return title.replace(/\s+/g, " ").trim();
39
74
  }
@@ -59,6 +94,7 @@ const lspRunner = {
59
94
  appliesTo: getLspCapableKinds(),
60
95
  priority: PRIORITY.LSP_PRIMARY,
61
96
  enabledByDefault: true,
97
+ timeoutMs: LSP_RUNNER_TIMEOUT_MS,
62
98
  async run(ctx) {
63
99
  const diagnosticPath = resolveRunnerPath(ctx.cwd, ctx.filePath);
64
100
  // Only run if LSP is not disabled via --no-lsp
@@ -5,7 +5,10 @@ import { createAvailabilityChecker, resolveToolCommandWithInstallFallback, } fro
5
5
  import { parseToolRun } from "./utils/tool-failure.js";
6
6
  import { isInSpawnTimeoutCooldown, noteSpawnTimeout, } from "../../spawn-timeout-cooldown.js";
7
7
  import { finishParsedRun } from "./utils/tool-failure.js";
8
- const markdownlint = createAvailabilityChecker("markdownlint-cli2", ".cmd");
8
+ const markdownlint = createAvailabilityChecker("markdownlint-cli2", ".cmd", [
9
+ "--no-globs",
10
+ "-",
11
+ ]);
9
12
  // markdownlint-cli2 exit codes (its README's "Exit Codes" section): 0 = no
10
13
  // findings, 1 = lint findings, 2 = unexpected error (unreadable config, bad
11
14
  // glob, crash). Only 2 is a rejected invocation.
@@ -10,10 +10,16 @@ import * as fs from "node:fs";
10
10
  import * as path from "node:path";
11
11
  import { walkUpDirs } from "../../path-utils.js";
12
12
  import { safeSpawnAsync } from "../../safe-spawn.js";
13
+ import { truncatedByOutputCap } from "../../spawn-output-cap.js";
13
14
  import { getJstsLintPolicyForCwd, hasVitePlusConfig, } from "../../tool-policy.js";
14
15
  import { PRIORITY } from "../priorities.js";
15
16
  import { resolveToolCommand, resolveToolCommandWithInstallFallback, } from "./utils/runner-helpers.js";
16
17
  import { finishParsedRun, parseToolRun } from "./utils/tool-failure.js";
18
+ // One file's JSON report. Nothing legitimate approaches 8 MiB here, so this is
19
+ // a blast-radius bound on a runaway or wedged oxlint rather than a working
20
+ // limit — the same value MAX_SG_OUTPUT_BYTES and MAX_REPORT_BYTES use, and what
21
+ // makes `outputTruncated` reachable for this runner at all (#2100).
22
+ const MAX_OXLINT_OUTPUT_BYTES = 8 * 1024 * 1024;
17
23
  const OXLINT_NO_FILES = Symbol("oxlint-no-files");
18
24
  const OXLINT_NO_FILES_UNCONFIRMED = Symbol("oxlint-no-files-unconfirmed");
19
25
  const OXLINT_NO_FILES_BANNER = "No files found to lint. Please check your paths and ignore patterns.";
@@ -86,6 +92,7 @@ const oxlintRunner = {
86
92
  // Run oxlint (or Vite+'s vp lint wrapper) on the file.
87
93
  const result = await safeSpawnAsync(cmd, args, {
88
94
  timeout: 30000,
95
+ maxOutputBytes: MAX_OXLINT_OUTPUT_BYTES,
89
96
  });
90
97
  // Oxlint exits 0 whenever nothing at ERROR severity was found — that
91
98
  // includes a run that found only warnings, its own default severity
@@ -194,16 +201,16 @@ const oxlintRunner = {
194
201
  };
195
202
  function oxlintProcessState(result) {
196
203
  const failureKind = result.spawnFailure?.kind;
197
- if (result.signal || result.failure === "signal")
198
- return "signal";
204
+ if (truncatedByOutputCap(result))
205
+ return "truncated";
199
206
  if (result.failure === "timeout" || failureKind === "timeout")
200
207
  return "timeout";
201
208
  if (result.failure === "aborted" || failureKind === "killed")
202
209
  return "killed";
210
+ if (result.signal || result.failure === "signal")
211
+ return "signal";
203
212
  if (result.failure === "spawn" || result.error)
204
213
  return "spawn";
205
- if (result.outputTruncated)
206
- return "truncated";
207
214
  return "normal";
208
215
  }
209
216
  function isNonnegativeInteger(value) {
@@ -77,6 +77,7 @@ function notePsDecision(latch, tool, verdict) {
77
77
  cause: verdict.available ? "ok" : verdict.cause,
78
78
  elapsedMs: verdict.elapsedMs,
79
79
  latched: verdict.available || isLatchingOutcome(verdict.outcome),
80
+ classifiedBy: "probe",
80
81
  hostStallMs: verdict.hostStallMs,
81
82
  ...(retryAfterMs !== undefined && { retryAfterMs }),
82
83
  budgetMs: PS_TIMEOUT_MS,
@@ -412,6 +413,9 @@ const psScriptAnalyzerRunner = {
412
413
  latched: false,
413
414
  hostStallMs,
414
415
  budgetMs: PS_TIMEOUT_MS,
416
+ // A read of the process's own stdout, not classifyProbeFailure, is
417
+ // what decided this (#2209).
418
+ classifiedBy: "caller",
415
419
  });
416
420
  incrementDegradationCount({
417
421
  kind: "grammar-blocked",