pi-lens 4.1.2 → 4.1.4

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 (359) hide show
  1. package/CHANGELOG.md +1308 -0
  2. package/README.md +3 -0
  3. package/config/biome/core.jsonc +11 -2
  4. package/config/dependency-cruiser-eager-allowlist.json +91 -0
  5. package/dist/clients/actionable-warnings-logger.js +18 -3
  6. package/dist/clients/actionable-warnings.js +927 -80
  7. package/dist/clients/agent-behavior-client.js +13 -4
  8. package/dist/clients/ast-grep-client.js +136 -9
  9. package/dist/clients/ast-grep-rule-manager.js +60 -5
  10. package/dist/clients/ast-grep-tool-logger.js +2 -2
  11. package/dist/clients/availability-probe-flight.js +12 -0
  12. package/dist/clients/bash-file-access.js +1 -3
  13. package/dist/clients/biome-client.js +16 -2
  14. package/dist/clients/blocker-freshness.js +34 -0
  15. package/dist/clients/bootstrap.js +509 -73
  16. package/dist/clients/bounded-cache.js +152 -12
  17. package/dist/clients/bounded-telemetry.js +115 -23
  18. package/dist/clients/build-identity.js +95 -0
  19. package/dist/clients/bundled-resource-health.js +113 -0
  20. package/dist/clients/bus-events-logger.js +2 -2
  21. package/dist/clients/bus-publish.js +3 -0
  22. package/dist/clients/cache/rule-cache.js +33 -5
  23. package/dist/clients/cache-manager.js +105 -6
  24. package/dist/clients/cache-observability.js +46 -19
  25. package/dist/clients/cargo-manifest.js +422 -0
  26. package/dist/clients/cascade-logger.js +17 -3
  27. package/dist/clients/code-quality-warnings.js +13 -3
  28. package/dist/clients/complexity-client.js +16 -5
  29. package/dist/clients/config-core/deny.js +221 -0
  30. package/dist/clients/config-core/index.js +47 -0
  31. package/dist/clients/config-core/merge.js +357 -0
  32. package/dist/clients/config-core/normalize.js +340 -0
  33. package/dist/clients/config-core/process-spec.js +248 -0
  34. package/dist/clients/config-core/provenance.js +171 -0
  35. package/dist/clients/config-core/records.js +218 -0
  36. package/dist/clients/config-core/resolve.js +125 -0
  37. package/dist/clients/config-core/safe-object.js +78 -0
  38. package/dist/clients/config-core/schema.js +167 -0
  39. package/dist/clients/config-diagnostic-codes.js +281 -0
  40. package/dist/clients/config-locations.js +160 -0
  41. package/dist/clients/config-resolve.js +789 -0
  42. package/dist/clients/config-schema.js +197 -0
  43. package/dist/clients/config-warn.js +407 -0
  44. package/dist/clients/dead-code-client.js +33 -25
  45. package/dist/clients/dead-code-logger.js +2 -2
  46. package/dist/clients/deadline-utils.js +178 -0
  47. package/dist/clients/debug-handles.js +2 -2
  48. package/dist/clients/debug-heap.js +3 -3
  49. package/dist/clients/deferred-lsp-work.js +106 -0
  50. package/dist/clients/degradation-ledger.js +166 -8
  51. package/dist/clients/demoted-finding-render.js +14 -0
  52. package/dist/clients/dependency-checker.js +28 -22
  53. package/dist/clients/deps/minimatch.js +1 -1
  54. package/dist/clients/diagnostic-line-freshness.js +12 -9
  55. package/dist/clients/diagnostic-logger.js +14 -4
  56. package/dist/clients/dispatch/collect-later-tier.js +39 -0
  57. package/dist/clients/dispatch/dispatcher.js +304 -19
  58. package/dist/clients/dispatch/fact-store.js +328 -9
  59. package/dist/clients/dispatch/integration.js +214 -107
  60. package/dist/clients/dispatch/pending-runner-findings.js +87 -0
  61. package/dist/clients/dispatch/plan.js +21 -5
  62. package/dist/clients/dispatch/runners/actionlint.js +0 -1
  63. package/dist/clients/dispatch/runners/ast-grep-napi.js +550 -77
  64. package/dist/clients/dispatch/runners/biome-check.js +0 -1
  65. package/dist/clients/dispatch/runners/cpp-check.js +0 -1
  66. package/dist/clients/dispatch/runners/credo.js +0 -1
  67. package/dist/clients/dispatch/runners/cue-vet.js +0 -1
  68. package/dist/clients/dispatch/runners/dart-analyze.js +0 -1
  69. package/dist/clients/dispatch/runners/detekt.js +0 -1
  70. package/dist/clients/dispatch/runners/dotnet-build.js +0 -1
  71. package/dist/clients/dispatch/runners/elixir-check.js +0 -1
  72. package/dist/clients/dispatch/runners/eslint.js +5 -2
  73. package/dist/clients/dispatch/runners/fact-rules.js +0 -1
  74. package/dist/clients/dispatch/runners/fish-indent.js +0 -1
  75. package/dist/clients/dispatch/runners/gleam-check.js +0 -1
  76. package/dist/clients/dispatch/runners/go-vet.js +1 -3
  77. package/dist/clients/dispatch/runners/golangci-lint.js +0 -1
  78. package/dist/clients/dispatch/runners/hadolint.js +0 -1
  79. package/dist/clients/dispatch/runners/helm-lint.js +16 -4
  80. package/dist/clients/dispatch/runners/helm-render.js +54 -10
  81. package/dist/clients/dispatch/runners/htmlhint.js +0 -1
  82. package/dist/clients/dispatch/runners/index.js +1 -3
  83. package/dist/clients/dispatch/runners/javac.js +0 -1
  84. package/dist/clients/dispatch/runners/ktlint.js +0 -1
  85. package/dist/clients/dispatch/runners/lsp.js +37 -2
  86. package/dist/clients/dispatch/runners/markdownlint.js +4 -2
  87. package/dist/clients/dispatch/runners/mypy.js +0 -1
  88. package/dist/clients/dispatch/runners/oxlint.js +24 -22
  89. package/dist/clients/dispatch/runners/php-lint.js +0 -1
  90. package/dist/clients/dispatch/runners/phpstan.js +0 -1
  91. package/dist/clients/dispatch/runners/prisma-validate.js +0 -1
  92. package/dist/clients/dispatch/runners/psscriptanalyzer.js +4 -1
  93. package/dist/clients/dispatch/runners/pyright.js +7 -4
  94. package/dist/clients/dispatch/runners/rubocop.js +0 -1
  95. package/dist/clients/dispatch/runners/ruff.js +0 -1
  96. package/dist/clients/dispatch/runners/rust-clippy.js +9 -6
  97. package/dist/clients/dispatch/runners/shellcheck.js +0 -1
  98. package/dist/clients/dispatch/runners/shfmt.js +0 -1
  99. package/dist/clients/dispatch/runners/spellcheck.js +0 -1
  100. package/dist/clients/dispatch/runners/spotbugs.js +0 -1
  101. package/dist/clients/dispatch/runners/sqlfluff.js +0 -1
  102. package/dist/clients/dispatch/runners/stylelint.js +0 -1
  103. package/dist/clients/dispatch/runners/swiftlint.js +0 -1
  104. package/dist/clients/dispatch/runners/taplo.js +0 -1
  105. package/dist/clients/dispatch/runners/terragrunt.js +0 -1
  106. package/dist/clients/dispatch/runners/tflint.js +0 -1
  107. package/dist/clients/dispatch/runners/tree-sitter.js +0 -1
  108. package/dist/clients/dispatch/runners/trivy-config.js +0 -1
  109. package/dist/clients/dispatch/runners/utils/availability-policy.js +3 -0
  110. package/dist/clients/dispatch/runners/utils/diagnostic-parsers.js +3 -4
  111. package/dist/clients/dispatch/runners/utils/runner-helpers.js +270 -105
  112. package/dist/clients/dispatch/runners/utils/spawn-outcome.js +4 -0
  113. package/dist/clients/dispatch/runners/utils/tool-failure.js +19 -7
  114. package/dist/clients/dispatch/runners/utils/toolchain-availability.js +89 -26
  115. package/dist/clients/dispatch/runners/vale.js +0 -1
  116. package/dist/clients/dispatch/runners/yaml-rule-parser.js +51 -17
  117. package/dist/clients/dispatch/runners/yamllint.js +0 -1
  118. package/dist/clients/dispatch/runners/zig-check.js +0 -1
  119. package/dist/clients/disposition-logger.js +3 -3
  120. package/dist/clients/effective-config.js +403 -0
  121. package/dist/clients/error-class.js +23 -0
  122. package/dist/clients/event-loop-hold.js +274 -0
  123. package/dist/clients/event-loop-monitor.js +66 -1
  124. package/dist/clients/extension-log.js +2 -2
  125. package/dist/clients/feature-hints.js +2 -1
  126. package/dist/clients/file-time.js +6 -1
  127. package/dist/clients/file-utils.js +266 -13
  128. package/dist/clients/finding-delivery-gate.js +55 -13
  129. package/dist/clients/format-service.js +6 -1
  130. package/dist/clients/formatters.js +303 -104
  131. package/dist/clients/freshness-cadence.js +17 -0
  132. package/dist/clients/generated-artifacts.js +114 -19
  133. package/dist/clients/generation-guard.js +4 -9
  134. package/dist/clients/git-guard.js +198 -48
  135. package/dist/clients/git-tracked-ignore.js +32 -1
  136. package/dist/clients/go-client.js +39 -0
  137. package/dist/clients/govulncheck-client.js +5 -1
  138. package/dist/clients/gradle-ktfmt-style.js +252 -0
  139. package/dist/clients/gzip-stage-write.js +7 -0
  140. package/dist/clients/hashline-anchor.js +424 -0
  141. package/dist/clients/hook-budgets.js +76 -0
  142. package/dist/clients/host-edit-normalize.js +5 -2
  143. package/dist/clients/host-ports.js +1 -1
  144. package/dist/clients/installer/index.js +200 -84
  145. package/dist/clients/installer/managed-tool-refresh.js +3 -1
  146. package/dist/clients/instance-reaper.js +108 -243
  147. package/dist/clients/instance-registry-lock.js +145 -0
  148. package/dist/clients/instance-registry.js +435 -122
  149. package/dist/clients/jscpd-client.js +6 -1
  150. package/dist/clients/language-profile.js +4 -2
  151. package/dist/clients/language-registry.js +595 -0
  152. package/dist/clients/latency-logger.js +179 -3
  153. package/dist/clients/ledger-bounds.js +13 -0
  154. package/dist/clients/lens-config.js +141 -30
  155. package/dist/clients/lens-engine.js +30 -37
  156. package/dist/clients/lens-flag-registry.js +67 -6
  157. package/dist/clients/lens-map.js +3 -1
  158. package/dist/clients/log-cleanup.js +2 -2
  159. package/dist/clients/lsp/aggregation.js +3 -1
  160. package/dist/clients/lsp/cascade-tier.js +4 -0
  161. package/dist/clients/lsp/client.js +672 -49
  162. package/dist/clients/lsp/config.js +504 -156
  163. package/dist/clients/lsp/diagnostic-binding.js +10 -5
  164. package/dist/clients/lsp/edits.js +15 -2
  165. package/dist/clients/lsp/index.js +1179 -247
  166. package/dist/clients/lsp/jvm-runtime.js +5 -0
  167. package/dist/clients/lsp/language.js +18 -169
  168. package/dist/clients/lsp/launch.js +38 -9
  169. package/dist/clients/lsp/pending-aux-coverage.js +113 -12
  170. package/dist/clients/lsp/server.js +426 -160
  171. package/dist/clients/lsp/session-roots.js +192 -0
  172. package/dist/clients/lsp/spawn-history.js +2 -7
  173. package/dist/clients/lsp/tsserver-sync.js +9 -2
  174. package/dist/clients/lsp/workspace-diagnostics-cache.js +17 -7
  175. package/dist/clients/lsp/workspace-sweep-hold.js +32 -23
  176. package/dist/clients/lsp-mutation.js +179 -23
  177. package/dist/clients/map-with-concurrency.js +36 -0
  178. package/dist/clients/mcp/analyze.js +45 -10
  179. package/dist/clients/mcp/session.js +19 -17
  180. package/dist/clients/memory-sampler.js +28 -1
  181. package/dist/clients/message-end-attribution.js +30 -0
  182. package/dist/clients/metrics-history.js +21 -4
  183. package/dist/clients/middle-man-analysis.js +2 -4
  184. package/dist/clients/module-report.js +20 -45
  185. package/dist/clients/mutating-tool.js +651 -0
  186. package/dist/clients/mutation-attribution.js +368 -0
  187. package/dist/clients/mutation-bridge.js +240 -0
  188. package/dist/clients/ndjson-logger.js +247 -20
  189. package/dist/clients/observed-mutation-sources.js +101 -0
  190. package/dist/clients/observed-mutation.js +1215 -0
  191. package/dist/clients/opaque-mutation-scan.js +300 -40
  192. package/dist/clients/opengrep-config.js +4 -0
  193. package/dist/clients/package-manager.js +201 -44
  194. package/dist/clients/partial-edit-apply.js +359 -80
  195. package/dist/clients/path-attribution-telemetry.js +23 -7
  196. package/dist/clients/path-keyed-map.js +35 -3
  197. package/dist/clients/path-utils.js +460 -13
  198. package/dist/clients/persist-debounce.js +8 -1
  199. package/dist/clients/php-cs-fixer-config.js +114 -0
  200. package/dist/clients/pipeline.js +77 -24
  201. package/dist/clients/probe-home-state.js +228 -0
  202. package/dist/clients/process-bridge.js +66 -0
  203. package/dist/clients/process-singletons.js +210 -0
  204. package/dist/clients/process-snapshot.js +68 -0
  205. package/dist/clients/project-diagnostics/fresh-fetch.js +4 -3
  206. package/dist/clients/project-diagnostics/runner-adapters/madge.js +2 -1
  207. package/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +27 -3
  208. package/dist/clients/project-diagnostics/scanner.js +61 -24
  209. package/dist/clients/project-lens-config.js +393 -122
  210. package/dist/clients/project-snapshot.js +85 -35
  211. package/dist/clients/python-environment.js +217 -0
  212. package/dist/clients/python-provenance.js +639 -0
  213. package/dist/clients/quiet-window.js +6 -1
  214. package/dist/clients/read-bridge.js +13 -21
  215. package/dist/clients/read-expansion.js +9 -46
  216. package/dist/clients/read-guard-logger.js +39 -6
  217. package/dist/clients/read-guard-tool-lines.js +364 -212
  218. package/dist/clients/read-guard.js +95 -7
  219. package/dist/clients/recent-touches.js +17 -5
  220. package/dist/clients/resource-sampler.js +195 -96
  221. package/dist/clients/review-graph/builder.js +752 -132
  222. package/dist/clients/review-graph/service.js +55 -3
  223. package/dist/clients/review-graph/shared-extraction-ir.js +7 -15
  224. package/dist/clients/review-graph/tsconfig-paths.js +5 -1
  225. package/dist/clients/review-graph/workspace-modules.js +10 -74
  226. package/dist/clients/review-graph-logger.js +30 -4
  227. package/dist/clients/runtime-agent-end.js +18 -2
  228. package/dist/clients/runtime-context.js +49 -16
  229. package/dist/clients/runtime-coordinator.js +93 -6
  230. package/dist/clients/runtime-session.js +169 -34
  231. package/dist/clients/runtime-tool-call.js +183 -35
  232. package/dist/clients/runtime-tool-result.js +894 -241
  233. package/dist/clients/runtime-turn.js +1264 -74
  234. package/dist/clients/rust-client.js +31 -0
  235. package/dist/clients/safe-spawn.js +191 -28
  236. package/dist/clients/sanitize.js +7 -1
  237. package/dist/clients/scratch-tree-policy.js +1 -3
  238. package/dist/clients/security-scan-client.js +51 -17
  239. package/dist/clients/session-lifecycle.js +238 -42
  240. package/dist/clients/session-start-observability.js +79 -0
  241. package/dist/clients/sessionstart-logger.js +12 -3
  242. package/dist/clients/sg-runner.js +27 -15
  243. package/dist/clients/sgconfig.js +6 -1
  244. package/dist/clients/shared-checkout-guard.js +338 -0
  245. package/dist/clients/skills-resolver.js +105 -0
  246. package/dist/clients/smells-rollup.js +2 -2
  247. package/dist/clients/spawn-output-cap.js +37 -0
  248. package/dist/clients/spawn-timeout-cooldown.js +15 -1
  249. package/dist/clients/startup-scan.js +6 -7
  250. package/dist/clients/startup-timing.js +69 -4
  251. package/dist/clients/string-utils.js +33 -0
  252. package/dist/clients/subagent-mode.js +17 -4
  253. package/dist/clients/test-runner-client.js +586 -118
  254. package/dist/clients/test-runner-delivery.js +239 -0
  255. package/dist/clients/tool-definition.js +41 -1
  256. package/dist/clients/tool-policy.js +92 -22
  257. package/dist/clients/tree-sitter-cache.js +40 -40
  258. package/dist/clients/tree-sitter-client.js +97 -53
  259. package/dist/clients/tree-sitter-logger.js +19 -3
  260. package/dist/clients/tree-sitter-query-loader.js +81 -1
  261. package/dist/clients/tree-sitter-shared.js +51 -46
  262. package/dist/clients/tree-sitter-symbol-extractor.js +28 -0
  263. package/dist/clients/typos-config.js +5 -0
  264. package/dist/clients/user-notify.js +6 -2
  265. package/dist/clients/warm-attach.js +8 -10
  266. package/dist/clients/widget-state.js +191 -26
  267. package/dist/clients/word-index-logger.js +15 -3
  268. package/dist/clients/word-index-store.js +463 -0
  269. package/dist/clients/word-index.js +815 -125
  270. package/dist/clients/workspace-topology.js +25 -1
  271. package/dist/clients/zizmor-config.js +29 -6
  272. package/dist/index.js +68577 -53950
  273. package/dist/mcp/analyze-cli.js +3 -1
  274. package/dist/mcp/build-staleness.js +8 -7
  275. package/dist/mcp/cli.js +3 -1
  276. package/dist/mcp/server.js +133 -6
  277. package/dist/scripts/lib/process-scan.mjs +583 -0
  278. package/dist/scripts/lib/skills-predicate.mjs +129 -0
  279. package/dist/tools/effective-config.js +89 -0
  280. package/dist/tools/lens-diagnostics.js +99 -24
  281. package/dist/tools/lsp-diagnostics.js +81 -85
  282. package/dist/tools/lsp-navigation.js +40 -17
  283. package/dist/tools/shared.js +0 -1
  284. package/docs/agent-guide.md +68 -3
  285. package/docs/ast-grep_rules_catalog.md +41 -10
  286. package/docs/astplayground.md +26 -17
  287. package/docs/configuration.md +222 -0
  288. package/docs/dependencies.md +3 -3
  289. package/docs/features.md +44 -5
  290. package/docs/language-coverage.md +2 -2
  291. package/docs/pi-lens-fixer.md +25 -0
  292. package/docs/pi-lens-investigator.md +25 -0
  293. package/docs/pi-lens-reviewer.md +27 -0
  294. package/docs/pi-lens-subagent.md +38 -0
  295. package/docs/pi-lens-warden.md +55 -0
  296. package/docs/public-api-stability.md +359 -0
  297. package/docs/release-qa-baseline.md +182 -0
  298. package/docs/subagent-compat.md +110 -30
  299. package/docs/tree-sitter_rules_catalog.md +2 -2
  300. package/package.json +17 -21
  301. package/rules/ast-grep-rules/rule-tests/c-no-malloc-free-test.yml +20 -0
  302. package/rules/ast-grep-rules/rule-tests/cpp-no-malloc-free-test.yml +25 -0
  303. package/rules/ast-grep-rules/rule-tests/cpp-no-printf-test.yml +19 -0
  304. package/rules/ast-grep-rules/rule-tests/go-no-fmt-println-test.yml +19 -0
  305. package/rules/ast-grep-rules/rule-tests/go-no-panic-in-lib-test.yml +17 -0
  306. package/rules/ast-grep-rules/rule-tests/go-no-underscore-func-name-test.yml +18 -0
  307. package/rules/ast-grep-rules/rule-tests/go-prefer-errors-is-test.yml +15 -0
  308. package/rules/ast-grep-rules/rule-tests/go-prefer-string-builder-test.yml +21 -0
  309. package/rules/ast-grep-rules/rule-tests/hyphenated-svg-attribute-test.yml +11 -0
  310. package/rules/ast-grep-rules/rule-tests/kotlin-no-lateinit-test.yml +17 -0
  311. package/rules/ast-grep-rules/rule-tests/kotlin-no-nullable-boolean-test.yml +15 -0
  312. package/rules/ast-grep-rules/rule-tests/kotlin-no-println-test.yml +19 -0
  313. package/rules/ast-grep-rules/rule-tests/kotlin-no-unnecessary-let-test.yml +7 -0
  314. package/rules/ast-grep-rules/rule-tests/kotlin-no-unsafe-cast-test.yml +7 -0
  315. package/rules/ast-grep-rules/rule-tests/kotlin-no-var-test.yml +7 -0
  316. package/rules/ast-grep-rules/rule-tests/kotlin-prefer-data-class-test.yml +25 -0
  317. package/rules/ast-grep-rules/rule-tests/kotlin-prefer-is-empty-test.yml +11 -0
  318. package/rules/ast-grep-rules/rule-tests/no-important-test.yml +21 -0
  319. package/rules/ast-grep-rules/rule-tests/no-raw-types-test.yml +30 -0
  320. package/rules/ast-grep-rules/rule-tests/no-string-concat-in-loop-test.yml +100 -0
  321. package/rules/ast-grep-rules/rule-tests/no-system-out-println-test.yml +7 -0
  322. package/rules/ast-grep-rules/rule-tests/prefer-string-is-empty-test.yml +7 -0
  323. package/rules/ast-grep-rules/rule-tests/python-optional-type-test.yml +9 -0
  324. package/rules/ast-grep-rules/rule-tests/reducible-list-comprehension-test.yml +11 -0
  325. package/rules/ast-grep-rules/rule-tests/ruby-symbol-to-proc-candidate-test.yml +10 -0
  326. package/rules/ast-grep-rules/rule-tests/rust-no-chars-enumerate-test.yml +7 -0
  327. package/rules/ast-grep-rules/rules/c-no-malloc-free.yml +39 -0
  328. package/rules/ast-grep-rules/rules/cpp-no-malloc-free.yml +45 -0
  329. package/rules/ast-grep-rules/rules/cpp-no-printf.yml +41 -0
  330. package/rules/ast-grep-rules/rules/go-no-fmt-println.yml +43 -0
  331. package/rules/ast-grep-rules/rules/go-no-panic-in-lib.yml +41 -0
  332. package/rules/ast-grep-rules/rules/go-no-underscore-func-name.yml +35 -0
  333. package/rules/ast-grep-rules/rules/go-prefer-errors-is.yml +40 -0
  334. package/rules/ast-grep-rules/rules/go-prefer-string-builder.yml +43 -0
  335. package/rules/ast-grep-rules/rules/hyphenated-svg-attribute.yml +32 -0
  336. package/rules/ast-grep-rules/rules/kotlin-no-lateinit.yml +37 -0
  337. package/rules/ast-grep-rules/rules/kotlin-no-nullable-boolean.yml +33 -0
  338. package/rules/ast-grep-rules/rules/kotlin-no-println.yml +42 -0
  339. package/rules/ast-grep-rules/rules/kotlin-no-unnecessary-let.yml +28 -0
  340. package/rules/ast-grep-rules/rules/kotlin-no-unsafe-cast.yml +28 -0
  341. package/rules/ast-grep-rules/rules/kotlin-no-var.yml +35 -0
  342. package/rules/ast-grep-rules/rules/kotlin-prefer-data-class.yml +66 -0
  343. package/rules/ast-grep-rules/rules/kotlin-prefer-is-empty.yml +35 -0
  344. package/rules/ast-grep-rules/rules/no-important.yml +21 -0
  345. package/rules/ast-grep-rules/rules/no-raw-types.yml +33 -0
  346. package/rules/ast-grep-rules/rules/no-string-concat-in-loop.yml +90 -0
  347. package/rules/ast-grep-rules/rules/no-system-out-println.yml +16 -0
  348. package/rules/ast-grep-rules/rules/prefer-string-is-empty.yml +11 -0
  349. package/rules/ast-grep-rules/rules/python-optional-type.yml +22 -0
  350. package/rules/ast-grep-rules/rules/reducible-list-comprehension.yml +29 -0
  351. package/rules/ast-grep-rules/rules/ruby-symbol-to-proc-candidate.yml +25 -0
  352. package/rules/ast-grep-rules/rules/rust-no-chars-enumerate.yml +19 -0
  353. package/rules/tree-sitter-queries/python/python-cross-language-method.yml +3 -3
  354. package/rules/tree-sitter-queries/python/python-hallucinated-import.yml +2 -2
  355. package/rules/tree-sitter-queries/python/python-sql-injection.yml +12 -2
  356. package/scripts/analyze-pi-lens-logs.mjs +192 -1
  357. package/scripts/install-selftest.mjs +58 -2
  358. package/scripts/lib/skills-predicate.mjs +129 -0
  359. package/scripts/rpc-load-check.mjs +3 -1
@@ -10,7 +10,8 @@ import { detectFileKind } from "../file-kinds.js";
10
10
  import { getLspCapableKinds, getPrimaryDispatchGroup, } from "../language-policy.js";
11
11
  import { formatSlopScoreSummary, } from "../session-summary.js";
12
12
  import { clearCoverageNoticeState, clearLatencyReports, createDispatchContext, dispatchForFile, formatLatencyReport, getLatencyReports, RunnerRegistry, } from "./dispatcher.js";
13
- import { FactStore } from "./fact-store.js";
13
+ import { recordDegradationOnce } from "../degradation-ledger.js";
14
+ import { FactStore, setFactStoreEvictionReporter } from "./fact-store.js";
14
15
  import { TOOL_PLANS } from "./plan.js";
15
16
  // Re-export latency tracking types and functions
16
17
  export { clearLatencyReports, formatLatencyReport, getLatencyReports };
@@ -37,7 +38,7 @@ import { clearModuleGraphCache } from "../review-graph/workspace-modules.js";
37
38
  import { releaseWorkspaceTopologyIdleTimers } from "../workspace-topology.js";
38
39
  import { RUNTIME_CONFIG } from "../runtime-config.js";
39
40
  import { findCompiledClassesDir, hasJavaBuildDescriptor, } from "../tool-policy.js";
40
- import { removeWordIndexDocument, updateWordIndexDocument, WORD_INDEX_MAX_BYTES, } from "../word-index.js";
41
+ import { removeWordIndexDocumentAsync, updateWordIndexDocumentForEdit, WORD_INDEX_MAX_BYTES, } from "../word-index.js";
41
42
  import { reconcileCascadeNeighborLspErrors } from "../widget-state.js";
42
43
  import { findAuxiliaryProfileForSource } from "./auxiliary-lsp.js";
43
44
  // Register fact providers. All register eagerly here (the dispatch entry) — the
@@ -98,8 +99,46 @@ registerRule(commentedCredentialsRule);
98
99
  export function applyProjectLensConfig(cwd) {
99
100
  return loadPiLensProjectConfig(cwd);
100
101
  }
101
- const sessionFacts = new FactStore();
102
- const cascadeDiagnosticBaselines = new Map();
102
+ // "dispatch" labels this store's eviction telemetry (#2243 review round 3,
103
+ // F1) — five other production FactStore instances exist (mcp/analyze.ts,
104
+ // lens-map.ts, project-diagnostics/scanner.ts, runtime-session.ts's
105
+ // session-start call-graph task, mcp/cli.ts); a shared constant subject let
106
+ // runtime-session's session-start review-graph walk consume the once-per-
107
+ // session ledger slot before any dispatch ran, misattributing the record.
108
+ const sessionFacts = new FactStore("dispatch");
109
+ // #2243 item 4: wire the fact-store's capacity-eviction telemetry to the
110
+ // ledger from HERE, not from fact-store.ts. fact-store stays an import leaf so
111
+ // it cannot re-enter the safe-spawn ↔ degradation-ledger cycle (see
112
+ // `setFactStoreEvictionReporter`). The reporter forwards each store's own
113
+ // subject, so the ledger's per-kind+subject dedupe emits exactly ONE record
114
+ // per session PER STORE, re-arming when the ledger resets at session_start.
115
+ //
116
+ // #2247 review F1: the axis was previously dropped here, so
117
+ // `recordDegradationOnce`'s `${kind}\0${subject}` dedupe collapsed a store's
118
+ // count-axis and byte-axis evictions onto the SAME key — whichever axis
119
+ // tripped first (always count, since 1024 typical files land far under 64
120
+ // MiB) silently consumed the once-per-session slot and a later byte-axis
121
+ // eviction on that same store recorded nothing. Fold the axis into the
122
+ // subject (`<store>:<axis>`), matching this ledger's own `<store>:<file>`
123
+ // convention (see `demoted-finding-retired`'s doc comment) rather than
124
+ // growing the `kind` union per axis. `pinned-over-budget` (#2247 review F2)
125
+ // is a distinct condition, not an eviction, so it gets its own kind instead
126
+ // of sharing the eviction kind's subject convention.
127
+ setFactStoreEvictionReporter((subject, axis, reason) => {
128
+ if (axis === "pinned-over-budget") {
129
+ recordDegradationOnce({
130
+ kind: "fact-store-pinned-over-budget",
131
+ subject,
132
+ reason,
133
+ });
134
+ return;
135
+ }
136
+ recordDegradationOnce({
137
+ kind: "fact-store-capacity-eviction",
138
+ subject: `${subject}:${axis}`,
139
+ reason,
140
+ });
141
+ });
103
142
  const sessionRunnerRegistry = new RunnerRegistry();
104
143
  registerDefaultRunners(sessionRunnerRegistry);
105
144
  const LSP_CAPABLE_KINDS = new Set(getLspCapableKinds());
@@ -137,32 +176,62 @@ function scheduleAstGrepWarningScan(filePath, cwd, pi, logContext) {
137
176
  const existing = astGrepWarnDebounceTimers.get(filePath);
138
177
  if (existing)
139
178
  clearTimeout(existing);
140
- const timer = setTimeout(async () => {
179
+ const timer = setTimeout(() => {
141
180
  astGrepWarnDebounceTimers.delete(filePath);
142
- try {
143
- const ctx = createDispatchContext(filePath, cwd, pi, sessionFacts, false);
144
- if (ctx.kind !== "jsts")
145
- return;
146
- // Single-runner group: ast-grep only, warning mode (blockingOnly=false)
147
- const group = {
148
- mode: "all",
149
- runnerIds: ["ast-grep"],
150
- filterKinds: ["jsts"],
151
- };
152
- const result = await dispatchForFile(ctx, [group], sessionRunnerRegistry);
153
- if (result.diagnostics.length === 0)
154
- return;
155
- const logger = getDiagnosticLogger();
156
- for (const d of result.diagnostics) {
157
- logger.logCaught(d, logContext, false);
158
- }
159
- }
160
- catch {
181
+ void runAstGrepWarningScan(filePath, cwd, pi, logContext).catch(() => {
161
182
  // Non-critical background scan — swallow errors silently
162
- }
183
+ });
163
184
  }, AST_GREP_WARN_DEBOUNCE_MS);
164
185
  astGrepWarnDebounceTimers.set(filePath, timer);
165
186
  }
187
+ /**
188
+ * Run the debounced ast-grep warning scan for one jsts file.
189
+ *
190
+ * #2243 item 1: this is a dispatch entry point like dispatchLint*, so it must
191
+ * follow the same discipline. It fires ~2 s after the last write, and in that
192
+ * window the review-graph project walk (`buildOrUpdateGraph`) seeds facts for
193
+ * every file into the SAME `sessionFacts`, which can evict this file's
194
+ * `file.content`. `dispatcher.ts` reads that fact back with `?? ""` for inline
195
+ * suppressions, so a stale or evicted read silently stops applying
196
+ * `pi-lens-ignore`. Two guards, matching the other dispatch callers:
197
+ * 1. `beginDispatchFor` pins the file for the dispatch (released in the
198
+ * finally), so a concurrent walk cannot evict it mid-dispatch. This
199
+ * does NOT clear the file's existing facts the way `clearFileFactsFor`
200
+ * does for the other dispatch callers (#2243 review round 3, F5): this
201
+ * scan fires ~2s after the SAME edit's inline dispatch already
202
+ * re-derived every fact for this file, so a second clear+re-derive
203
+ * here bought nothing but ~51ms of redundant re-parsing across all
204
+ * five providers (measured; a warm-cache read is ~1ms).
205
+ * 2. `runProviders` still re-derives `file.content` when it's actually
206
+ * missing (a genuine miss — evicted, or never computed), so the read
207
+ * never depends on staleness.
208
+ * `facts` is injected only for tests; production always uses `sessionFacts`.
209
+ */
210
+ export async function runAstGrepWarningScan(filePath, cwd, pi, logContext, facts = sessionFacts) {
211
+ const ctx = createDispatchContext(filePath, cwd, pi, facts, false);
212
+ if (ctx.kind !== "jsts")
213
+ return;
214
+ facts.beginDispatchFor(ctx.filePath);
215
+ try {
216
+ // Single-runner group: ast-grep only, warning mode (blockingOnly=false)
217
+ const group = {
218
+ mode: "all",
219
+ runnerIds: ["ast-grep"],
220
+ filterKinds: ["jsts"],
221
+ };
222
+ await runProviders(ctx);
223
+ const result = await dispatchForFile(ctx, [group], sessionRunnerRegistry);
224
+ if (result.diagnostics.length === 0)
225
+ return;
226
+ const logger = getDiagnosticLogger();
227
+ for (const d of result.diagnostics) {
228
+ logger.logCaught(d, logContext, false);
229
+ }
230
+ }
231
+ finally {
232
+ facts.endDispatchFor(ctx.filePath);
233
+ }
234
+ }
166
235
  function resetSessionSlopScore() {
167
236
  sessionSlopRuleCounts.clear();
168
237
  sessionSlopDiagnosticCount = 0;
@@ -315,7 +384,6 @@ export function resetDispatchBaselines(cwd) {
315
384
  clearModuleGraphCache();
316
385
  recentlyCleanNeighborCache.clear();
317
386
  primaryFilesThisTurn.clear();
318
- cascadeDiagnosticBaselines.clear();
319
387
  cascadeSessionStats = {
320
388
  runs: 0,
321
389
  diagnosticsSurfaced: 0,
@@ -335,10 +403,16 @@ export function getCascadeSessionStats() {
335
403
  }
336
404
  const recentlyCleanNeighborCache = new Map();
337
405
  /** O(1) entry count of this module's turn-bounded cache (#1123 item 2
338
- * memory attribution) — a `Map.size` read, never iterated. */
406
+ * memory attribution) — a `Map.size` read, never iterated.
407
+ * `sessionFactEntries` (#2282) is the dispatch `FactStore`'s own
408
+ * `sessionFacts` footprint: the fixed-vocabulary map (small, unbounded by
409
+ * design) plus the bounded per-file map (capped at `MAX_SESSION_FACT_RECORDS`
410
+ * — see `fact-store.ts`), so this number is now itself bounded rather than
411
+ * invisible growth. */
339
412
  export function getDispatchCascadeCacheStats() {
340
413
  return {
341
414
  recentlyCleanNeighborCacheSize: recentlyCleanNeighborCache.size,
415
+ sessionFactEntries: sessionFacts.getSessionFactEntryCount(),
342
416
  };
343
417
  }
344
418
  const RECENTLY_CLEAN_TTL_TURNS = 5;
@@ -496,6 +570,11 @@ function touchReverseDepsEntry(key, entry, armIdleTimer = true) {
496
570
  entry.idleTimer.unref?.();
497
571
  }
498
572
  function setReverseDepsEntry(key, entry, armIdleTimer = true) {
573
+ const previous = reverseDepsIndexCache.get(key);
574
+ if (previous?.idleTimer !== undefined) {
575
+ clearTimeout(previous.idleTimer);
576
+ previous.idleTimer = undefined;
577
+ }
499
578
  const resident = { ...entry, lastUsedAt: Date.now() };
500
579
  reverseDepsIndexCache.set(key, resident);
501
580
  touchReverseDepsEntry(key, resident, armIdleTimer);
@@ -604,20 +683,28 @@ function isIgnoredCascadeNeighbor(filePath, cwd) {
604
683
  * debounced persist (never a synchronous write per edit — same #260
605
684
  * discipline as the graph).
606
685
  *
607
- * Race safety against a build-in-progress: this function body is entirely
608
- * synchronous (no `await` anywhere in it) and is called synchronously at
609
- * `computeCascadeForFile`'s entry, before its own `await buildOrUpdateGraph`.
610
- * Node is single-threaded, so two overlapping cascades (#450's unawaited
611
- * concurrency) can never interleave mid-mutation here — each call runs to
612
- * completion in one turn. The only cross-build hazard is a full session-start
613
- * rebuild REPLACING `runtime.wordIndex` with a new object between the caller
614
- * reading `runtime.wordIndex` (in runtime-tool-result.ts, also synchronous)
615
- * and this function receiving it — in that case this call simply mutates
616
- * whichever index object it was handed (old or new), and the other one is
617
- * abandoned/superseded, never corrupted. No queue, no lock: the simplest rule
618
- * that is still provably correct.
686
+ * Race safety against a build-in-progress: the update runs cooperatively, on
687
+ * an 8 ms budget, through the word index's own per-index operation queue
688
+ * (#2067 AC4). It used to run synchronously here, on the reasoning that two
689
+ * overlapping cascades (#450's unawaited concurrency) could not interleave
690
+ * mid-mutation if nothing ever yielded — the reviewer's 300-document probe had
691
+ * lost 1,719 postings during an 8 ms yield. That hazard is what the queue
692
+ * closed: every async operation on one index is chained behind the previous
693
+ * one in call order, so overlapping cascades are ordered rather than
694
+ * interleaved, and the cooperative arena compactor is separately proof against
695
+ * a synchronous edit landing mid-copy (#2117 review F2). What the synchronous
696
+ * variant could not fix is its own cost: one replacement on a 2,600-document
697
+ * corpus held the event loop for the whole replacement, which is the block
698
+ * #2067 measured at 1.1 s before interning and still tens of ms after it.
699
+ *
700
+ * The only cross-build hazard is a full session-start rebuild REPLACING
701
+ * `runtime.wordIndex` with a new object between the caller reading
702
+ * `runtime.wordIndex` (in runtime-tool-result.ts, synchronous) and this
703
+ * function receiving it — in that case this call simply mutates whichever
704
+ * index object it was handed (old or new), and the other one is
705
+ * abandoned/superseded, never corrupted.
619
706
  */
620
- function updateWordIndexForCascade(args) {
707
+ async function updateWordIndexForCascade(args) {
621
708
  const { wordIndex, filePath, content, onUpdated, dbg } = args;
622
709
  if (!wordIndex || !wordIndex.forward)
623
710
  return;
@@ -625,11 +712,14 @@ function updateWordIndexForCascade(args) {
625
712
  return;
626
713
  const byteLength = Buffer.byteLength(content, "utf-8");
627
714
  if (byteLength > WORD_INDEX_MAX_BYTES) {
628
- removeWordIndexDocument(wordIndex, filePath);
715
+ await removeWordIndexDocumentAsync(wordIndex, filePath);
629
716
  dbg?.(`word-index per-edit: dropped ${filePath} (over size cap)`);
630
717
  }
631
718
  else {
632
- updateWordIndexDocument(wordIndex, { path: filePath, content });
719
+ await updateWordIndexDocumentForEdit(wordIndex, {
720
+ path: filePath,
721
+ content,
722
+ });
633
723
  dbg?.(`word-index per-edit: updated ${filePath}`);
634
724
  }
635
725
  onUpdated?.(wordIndex);
@@ -689,7 +779,7 @@ export async function computeCascadeForFile(filePath, cwd, options = {}) {
689
779
  // The old hazard — this update silently orphaning a SECOND entry next to the
690
780
  // walker's original-cased one — is now structurally impossible at the map
691
781
  // layer, so this seam no longer has to hand-match the build path's key shape.
692
- updateWordIndexForCascade({
782
+ await updateWordIndexForCascade({
693
783
  wordIndex,
694
784
  filePath: nodePath.resolve(filePath),
695
785
  content: fileContent,
@@ -1854,10 +1944,8 @@ function diagnosticDeltaKey(diagnostic) {
1854
1944
  function applyCascadeDeltaBaselines(neighbors) {
1855
1945
  return neighbors.map((neighbor) => {
1856
1946
  const baselineKey = `session.baseline.cascade.${normalizeMapKey(neighbor.filePath)}`;
1857
- const previous = cascadeDiagnosticBaselines.get(baselineKey) ??
1858
- sessionFacts.getSessionFact(baselineKey);
1859
- cascadeDiagnosticBaselines.set(baselineKey, [...neighbor.diagnostics]);
1860
- sessionFacts.setSessionFact(baselineKey, [...neighbor.diagnostics]);
1947
+ const previous = sessionFacts.getBoundedSessionFact(baselineKey);
1948
+ sessionFacts.setBoundedSessionFact(baselineKey, [...neighbor.diagnostics]);
1861
1949
  if (!previous)
1862
1950
  return neighbor;
1863
1951
  const before = new Set(previous.map(diagnosticDeltaKey));
@@ -1982,16 +2070,23 @@ export async function dispatchLint(filePath, cwd, pi, modifiedRanges, projectRoo
1982
2070
  // pre-existing issues after the first write.
1983
2071
  const ctx = createDispatchContext(filePath, cwd, pi, sessionFacts, true, modifiedRanges, projectRoot);
1984
2072
  sessionFacts.clearFileFactsFor(ctx.filePath);
1985
- const kind = ctx.kind;
1986
- if (!kind)
1987
- return "";
1988
- const groups = withSpotbugsGroup(kind, getDispatchGroupsForKind(kind, pi), ctx);
1989
- if (groups.length === 0)
1990
- return "";
1991
- await runProviders(ctx);
1992
- const result = await dispatchForFile(ctx, groups, sessionRunnerRegistry);
1993
- trackSessionSlopStats(ctx, result.diagnostics);
1994
- return result.output;
2073
+ // #2243 item 2: release the pin when the dispatch settles, so the pin set
2074
+ // tracks in-flight dispatches rather than the last N files touched.
2075
+ try {
2076
+ const kind = ctx.kind;
2077
+ if (!kind)
2078
+ return "";
2079
+ const groups = withSpotbugsGroup(kind, getDispatchGroupsForKind(kind, pi), ctx);
2080
+ if (groups.length === 0)
2081
+ return "";
2082
+ await runProviders(ctx);
2083
+ const result = await dispatchForFile(ctx, groups, sessionRunnerRegistry);
2084
+ trackSessionSlopStats(ctx, result.diagnostics);
2085
+ return result.output;
2086
+ }
2087
+ finally {
2088
+ sessionFacts.endDispatchFor(ctx.filePath);
2089
+ }
1995
2090
  }
1996
2091
  /**
1997
2092
  * Run linting and return full result (including diagnostics)
@@ -2002,44 +2097,50 @@ export async function dispatchLintWithResult(filePath, cwd, pi, modifiedRanges,
2002
2097
  // facade, pass blockingOnly=false to run every runner.
2003
2098
  const ctx = createDispatchContext(filePath, cwd, pi, sessionFacts, options?.blockingOnly ?? true, modifiedRanges, options?.projectRoot, options?.writeIndex, options?.telemetryModel, options?.telemetryProvider);
2004
2099
  sessionFacts.clearFileFactsFor(ctx.filePath);
2005
- const kind = ctx.kind;
2006
- if (!kind) {
2007
- return {
2008
- diagnostics: [],
2009
- blockers: [],
2010
- warnings: [],
2011
- baselineWarningCount: 0,
2012
- fixed: [],
2013
- resolvedCount: 0,
2014
- output: "",
2015
- blockerOutput: "",
2016
- hasBlockers: false,
2017
- };
2018
- }
2019
- const groups = withSpotbugsGroup(kind, getDispatchGroupsForKind(kind, pi), ctx);
2020
- if (groups.length === 0) {
2021
- return {
2022
- diagnostics: [],
2023
- blockers: [],
2024
- warnings: [],
2025
- baselineWarningCount: 0,
2026
- fixed: [],
2027
- resolvedCount: 0,
2028
- output: "",
2029
- blockerOutput: "",
2030
- hasBlockers: false,
2031
- };
2100
+ // #2243 item 2: release the pin when the dispatch settles.
2101
+ try {
2102
+ const kind = ctx.kind;
2103
+ if (!kind) {
2104
+ return {
2105
+ diagnostics: [],
2106
+ blockers: [],
2107
+ warnings: [],
2108
+ baselineWarningCount: 0,
2109
+ fixed: [],
2110
+ resolvedCount: 0,
2111
+ output: "",
2112
+ blockerOutput: "",
2113
+ hasBlockers: false,
2114
+ };
2115
+ }
2116
+ const groups = withSpotbugsGroup(kind, getDispatchGroupsForKind(kind, pi), ctx);
2117
+ if (groups.length === 0) {
2118
+ return {
2119
+ diagnostics: [],
2120
+ blockers: [],
2121
+ warnings: [],
2122
+ baselineWarningCount: 0,
2123
+ fixed: [],
2124
+ resolvedCount: 0,
2125
+ output: "",
2126
+ blockerOutput: "",
2127
+ hasBlockers: false,
2128
+ };
2129
+ }
2130
+ await runProviders(ctx);
2131
+ const result = await dispatchForFile(ctx, groups, sessionRunnerRegistry);
2132
+ trackSessionSlopStats(ctx, result.diagnostics);
2133
+ // Schedule debounced ast-grep warning scan for jsts files.
2134
+ // Runs 2s after the last write — collapses rapid sequential edits into one scan.
2135
+ // Results are logged only, never surfaced to the agent.
2136
+ if (kind === "jsts" && logContext) {
2137
+ scheduleAstGrepWarningScan(filePath, cwd, pi, logContext);
2138
+ }
2139
+ return result;
2032
2140
  }
2033
- await runProviders(ctx);
2034
- const result = await dispatchForFile(ctx, groups, sessionRunnerRegistry);
2035
- trackSessionSlopStats(ctx, result.diagnostics);
2036
- // Schedule debounced ast-grep warning scan for jsts files.
2037
- // Runs 2s after the last write — collapses rapid sequential edits into one scan.
2038
- // Results are logged only, never surfaced to the agent.
2039
- if (kind === "jsts" && logContext) {
2040
- scheduleAstGrepWarningScan(filePath, cwd, pi, logContext);
2141
+ finally {
2142
+ sessionFacts.endDispatchFor(ctx.filePath);
2041
2143
  }
2042
- return result;
2043
2144
  }
2044
2145
  /**
2045
2146
  * Same real dispatch path as {@link dispatchLintWithResult} (real context, real
@@ -2064,20 +2165,26 @@ export async function dispatchLintDetailed(filePath, cwd, pi, options) {
2064
2165
  };
2065
2166
  const ctx = createDispatchContext(filePath, cwd, pi, sessionFacts, options?.blockingOnly ?? false, options?.modifiedRanges, options?.projectRoot);
2066
2167
  sessionFacts.clearFileFactsFor(ctx.filePath);
2067
- const kind = ctx.kind;
2068
- if (!kind)
2069
- return { result: empty, runners: [] };
2070
- const groups = withSpotbugsGroup(kind, getDispatchGroupsForKind(kind, pi), ctx);
2071
- if (groups.length === 0)
2072
- return { result: empty, runners: [] };
2073
- const runners = [];
2074
- const sink = (runnerId, result) => {
2075
- runners.push({ runnerId, result });
2076
- };
2077
- await runProviders(ctx);
2078
- const result = await dispatchForFile(ctx, groups, sessionRunnerRegistry, sink);
2079
- trackSessionSlopStats(ctx, result.diagnostics);
2080
- return { result, runners };
2168
+ // #2243 item 2: release the pin when the dispatch settles.
2169
+ try {
2170
+ const kind = ctx.kind;
2171
+ if (!kind)
2172
+ return { result: empty, runners: [] };
2173
+ const groups = withSpotbugsGroup(kind, getDispatchGroupsForKind(kind, pi), ctx);
2174
+ if (groups.length === 0)
2175
+ return { result: empty, runners: [] };
2176
+ const runners = [];
2177
+ const sink = (runnerId, result) => {
2178
+ runners.push({ runnerId, result });
2179
+ };
2180
+ await runProviders(ctx);
2181
+ const result = await dispatchForFile(ctx, groups, sessionRunnerRegistry, sink);
2182
+ trackSessionSlopStats(ctx, result.diagnostics);
2183
+ return { result, runners };
2184
+ }
2185
+ finally {
2186
+ sessionFacts.endDispatchFor(ctx.filePath);
2187
+ }
2081
2188
  }
2082
2189
  /**
2083
2190
  * Check if a file should be processed by the dispatcher
@@ -0,0 +1,87 @@
1
+ /** Turn-end handoff for runners moved off the post-write critical path. */
2
+ import { incrementDegradationCount } from "../degradation-ledger.js";
3
+ const pending = [];
4
+ export const MAX_PENDING_RUNNER_FINDINGS = 50;
5
+ export function deferRunnerFindings(entry) {
6
+ const tracked = { ...entry, settled: false };
7
+ // Attach exactly once at ownership time. Re-attaching at every turn end
8
+ // accumulates handlers on a promise that may never settle (#2122 F8).
9
+ void tracked.promise.then((result) => {
10
+ tracked.result = result;
11
+ tracked.settled = true;
12
+ }, (error) => {
13
+ tracked.result = {
14
+ status: "failed",
15
+ diagnostics: [],
16
+ semantic: "warning",
17
+ failureKind: "exception",
18
+ failureMessage: String(error).slice(0, 200),
19
+ };
20
+ tracked.settled = true;
21
+ });
22
+ pending.push(tracked);
23
+ if (pending.length > MAX_PENDING_RUNNER_FINDINGS) {
24
+ const evicted = pending.shift();
25
+ if (evicted) {
26
+ incrementDegradationCount({
27
+ kind: "runner-findings-evicted",
28
+ subject: `${evicted.runnerId}:${evicted.filePath}`,
29
+ reason: `pending runner cap ${MAX_PENDING_RUNNER_FINDINGS}`,
30
+ });
31
+ }
32
+ }
33
+ }
34
+ /**
35
+ * Resolve already-finished runner work for this turn. Unfinished work remains
36
+ * owned by the store and is retried at the next turn boundary.
37
+ */
38
+ export async function drainPendingRunnerFindings(maxWaitMs = 2_000) {
39
+ if (pending.length === 0)
40
+ return [];
41
+ const current = pending.splice(0, pending.length);
42
+ const results = [];
43
+ // Give already-resolved promises one microtask turn without introducing a
44
+ // wall-clock wait. This observes completed work while preserving the F5
45
+ // zero-budget contract for in-flight runners.
46
+ if (maxWaitMs === 0)
47
+ await Promise.resolve();
48
+ if (maxWaitMs > 0 && current.some((entry) => !entry.settled)) {
49
+ await new Promise((resolve) => {
50
+ const timer = setTimeout(resolve, maxWaitMs);
51
+ timer.unref?.();
52
+ });
53
+ }
54
+ for (const entry of current) {
55
+ if (entry.settled && entry.result) {
56
+ results.push({
57
+ filePath: entry.filePath,
58
+ cwd: entry.cwd,
59
+ projectRoot: entry.projectRoot,
60
+ runnerId: entry.runnerId,
61
+ markedAtMs: entry.markedAtMs,
62
+ writeIndex: entry.writeIndex,
63
+ result: entry.result,
64
+ });
65
+ }
66
+ else {
67
+ pending.push(entry);
68
+ }
69
+ }
70
+ return results;
71
+ }
72
+ /** Drop a stale answer and record the lost re-run coverage. */
73
+ export function dropStaleRunnerFindings(entry) {
74
+ if (!entry.result)
75
+ return;
76
+ incrementDegradationCount({
77
+ kind: "runner-findings-stale",
78
+ subject: `${entry.runnerId}:${entry.filePath}`,
79
+ reason: "completed result was older than the latest file edit",
80
+ });
81
+ }
82
+ export function resetPendingRunnerFindings() {
83
+ pending.length = 0;
84
+ }
85
+ export function pendingRunnerFindingsSize() {
86
+ return pending.length;
87
+ }
@@ -95,7 +95,10 @@ export const LANGUAGE_CAPABILITY_MATRIX = {
95
95
  json: {
96
96
  name: "JSON Processing",
97
97
  capabilities: ["format", "lint"],
98
- writeGroups: [primary("json")],
98
+ writeGroups: [
99
+ primary("json"),
100
+ { mode: "all", runnerIds: ["trivy-config"], filterKinds: ["json"] },
101
+ ],
99
102
  },
100
103
  markdown: {
101
104
  name: "Markdown Processing",
@@ -108,7 +111,11 @@ export const LANGUAGE_CAPABILITY_MATRIX = {
108
111
  css: {
109
112
  name: "CSS Processing",
110
113
  capabilities: ["format", "lint"],
111
- writeGroups: [primary("css")],
114
+ writeGroups: [
115
+ primary("css"),
116
+ { mode: "all", runnerIds: ["tree-sitter"], filterKinds: ["css"] },
117
+ { mode: "all", runnerIds: ["ast-grep-napi"], filterKinds: ["css"] },
118
+ ],
112
119
  },
113
120
  yaml: {
114
121
  name: "YAML Processing",
@@ -131,7 +138,10 @@ export const LANGUAGE_CAPABILITY_MATRIX = {
131
138
  html: {
132
139
  name: "HTML Linting",
133
140
  capabilities: ["lint"],
134
- writeGroups: [primary("html")],
141
+ writeGroups: [
142
+ primary("html"),
143
+ { mode: "all", runnerIds: ["ast-grep-napi"], filterKinds: ["html"] },
144
+ ],
135
145
  },
136
146
  docker: {
137
147
  name: "Dockerfile Linting",
@@ -141,7 +151,10 @@ export const LANGUAGE_CAPABILITY_MATRIX = {
141
151
  php: {
142
152
  name: "PHP Linting",
143
153
  capabilities: ["types", "lint"],
144
- writeGroups: [primary("php")],
154
+ writeGroups: [
155
+ primary("php"),
156
+ { mode: "all", runnerIds: ["tree-sitter"], filterKinds: ["php"] },
157
+ ],
145
158
  },
146
159
  powershell: {
147
160
  name: "PowerShell Linting",
@@ -156,7 +169,10 @@ export const LANGUAGE_CAPABILITY_MATRIX = {
156
169
  csharp: {
157
170
  name: "C# Linting",
158
171
  capabilities: ["types", "lint"],
159
- writeGroups: [primary("csharp")],
172
+ writeGroups: [
173
+ primary("csharp"),
174
+ { mode: "all", runnerIds: ["tree-sitter"], filterKinds: ["csharp"] },
175
+ ],
160
176
  },
161
177
  fsharp: {
162
178
  name: "F# Linting",
@@ -61,7 +61,6 @@ const actionlintRunner = {
61
61
  id: "actionlint",
62
62
  appliesTo: ["yaml"],
63
63
  priority: PRIORITY.YAML_LINT + 1,
64
- enabledByDefault: true,
65
64
  skipTestFiles: false,
66
65
  when(ctx) {
67
66
  return isGitHubWorkflowFile(ctx.filePath);