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
@@ -1,8 +1,8 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as net from "node:net";
3
3
  import * as path from "node:path";
4
- import { readInstanceRegistry, } from "./instance-registry.js";
5
- import { realIsPidAlive, STALE_HEARTBEAT_MS } from "./instance-reaper.js";
4
+ import { readInstanceRegistry, selectLivePeerInstances, } from "./instance-registry.js";
5
+ import { realIsPidAlive } from "./instance-reaper.js";
6
6
  import { logLatency } from "./latency-logger.js";
7
7
  import { touchCoverageGap } from "./lsp/diagnostic-binding.js";
8
8
  import { loadLspService } from "./lsp-lazy.js";
@@ -18,15 +18,13 @@ function record(event, cwd, reason, pid, source) {
18
18
  metadata: { event, reason, incumbentPid: pid, source },
19
19
  });
20
20
  }
21
+ /**
22
+ * The oldest live peer on this root, if any. The liveness rule itself lives
23
+ * in `selectLivePeerInstances` (#2007) so warm-attach and the shared-checkout
24
+ * guard cannot drift apart about what "another session is here" means.
25
+ */
21
26
  export function selectWarmAttachIncumbent(entries, cwd, now = Date.now(), isPidAlive = realIsPidAlive) {
22
- const root = normalizeFilePath(cwd);
23
- return entries
24
- .filter((entry) => entry.pid !== process.pid &&
25
- entry.projectRoot === root &&
26
- isPidAlive(entry.pid) &&
27
- Number.isFinite(Date.parse(entry.heartbeatAt)) &&
28
- now - Date.parse(entry.heartbeatAt) <= STALE_HEARTBEAT_MS)
29
- .sort((a, b) => Date.parse(a.startedAt) - Date.parse(b.startedAt))[0];
27
+ return selectLivePeerInstances(entries, cwd, now, isPidAlive)[0];
30
28
  }
31
29
  async function serveRequest(req) {
32
30
  if (req.route === "code-actions") {
@@ -1,6 +1,7 @@
1
1
  import { stat } from "node:fs/promises";
2
2
  import * as path from "node:path";
3
3
  import { pathToFileURL } from "node:url";
4
+ import { BoundedFifoMap } from "./bounded-cache.js";
4
5
  import { demotePastEofDiagnostics, } from "./diagnostic-line-freshness.js";
5
6
  import { visibleWidth } from "./deps/pi-tui.js";
6
7
  import { normalizeEphemeralMapKey, normalizeMapKey } from "./path-utils.js";
@@ -58,7 +59,6 @@ export function isBlocking(d) {
58
59
  }
59
60
  // ── Module state ─────────────────────────────────────────────────────────────
60
61
  const files = new Map();
61
- const lspServers = new Map();
62
62
  let sessionLanguages = [];
63
63
  let requestRenderFn = null;
64
64
  /**
@@ -81,7 +81,8 @@ const runnerWriteGuard = new WriteOrderingGuard();
81
81
  const MAX_STORED_DIAGNOSTICS_PER_FILE = 12;
82
82
  const MAX_INACTIVE_FILE_RECORDS = 1024;
83
83
  const ACTIVE_FILE_IDLE_MS = 30 * 60_000;
84
- const MAX_LSP_SERVER_RECORDS = 128;
84
+ export const MAX_LSP_SERVER_RECORDS = 128;
85
+ const lspServers = new BoundedFifoMap(MAX_LSP_SERVER_RECORDS);
85
86
  // Pruning is a cold-size-boundary operation. Do not walk the whole file map
86
87
  // for every record in a large diagnostics reconciliation; the full-scan path
87
88
  // can legitimately create thousands of records in one synchronous batch.
@@ -110,8 +111,23 @@ function maybePruneInactiveFileRecords() {
110
111
  export function setRenderCallback(fn) {
111
112
  requestRenderFn = fn;
112
113
  }
114
+ /**
115
+ * #2275 review F1: files whose dependency-drift-demoted rows the footer has
116
+ * ACTUALLY DRAWN since the last turn end — the delivery population
117
+ * `runtime-turn.ts`'s cap step drains.
118
+ *
119
+ * The footer is not a broadcast of the whole store: `renderWidget` picks ONE
120
+ * record (`withBlocking[0]`) and its first five qualifying entries, and it
121
+ * may not be drawn at all (headless, or a turn with no repaint). Counting a
122
+ * delivery per turn end therefore charged the cap for rows the agent never
123
+ * saw, and retired them a delivery early. A Set (not a counter) is the right
124
+ * shape: the widget repaints many times per turn, and all of those repaints
125
+ * are the SAME one delivery.
126
+ */
127
+ const renderedDependencyDriftFiles = new Set();
113
128
  export function clearWidgetState() {
114
129
  files.clear();
130
+ renderedDependencyDriftFiles.clear();
115
131
  lspServers.clear();
116
132
  sessionLanguages = [];
117
133
  requestRenderFn = null;
@@ -174,7 +190,19 @@ export function exportWidgetState() {
174
190
  */
175
191
  function migrateEntryStamps(entries, recordTouchedAt) {
176
192
  return (entries ?? []).map((d) => {
177
- const { stale: _stale, ...rest } = d;
193
+ // #2275: strip staleDeliveryCount and footerRetired alongside stale — a
194
+ // delivery count (and the footer-hide it earned) without the demotion
195
+ // it counts is meaningless, and a resumed session re-evaluates
196
+ // dependency-drift from scratch (#1631 review F3) same as the `stale`
197
+ // bit itself. A restored footer has shown the row zero times.
198
+ // Fix-round 3 (#2275 review F2): `staleReason` left the round trip
199
+ // unstripped, so a resumed row could carry `staleReason:
200
+ // "dependency-drift"` with `stale` gone — `isDependencyDriftDemoted`
201
+ // reads `stale === true`, so that combination reads as clean today, but
202
+ // the stray reason is still a demotion label with no demotion behind
203
+ // it, the same "meaningless without the bit it counts" shape as the
204
+ // other three fields. Strip it alongside them.
205
+ const { stale: _stale, staleDeliveryCount: _staleDeliveryCount, footerRetired: _footerRetired, staleReason: _staleReason, ...rest } = d;
178
206
  return rest.observedAt == null
179
207
  ? { ...rest, observedAt: recordTouchedAt }
180
208
  : rest;
@@ -271,9 +299,9 @@ export function getFailedLspServerIds() {
271
299
  }
272
300
  return ids;
273
301
  }
274
- export function recordFormatter(filePath, formatter, changed, success) {
302
+ export function recordFormatter(filePath, formatter, changed, success, outcome) {
275
303
  const rec = getOrCreate(filePath);
276
- rec.formatters.set(formatter, { changed, success });
304
+ rec.formatters.set(formatter, { changed, success, outcome });
277
305
  rec.touchedAt = Date.now();
278
306
  files.set(fileMapKey(filePath), rec);
279
307
  requestRender();
@@ -381,7 +409,19 @@ function commitDiagnostics(rec, filePath, normalized, observedAt, key = fileMapK
381
409
  files.set(key, rec);
382
410
  requestRender();
383
411
  }
384
- /** Recompute the {blocking, errors, warnings} tally for a diagnostic set. */
412
+ export function classifyDiagnosticTier(d) {
413
+ if (d.severity === "error")
414
+ return "error";
415
+ if (d.severity === "warning")
416
+ return "warning";
417
+ if (d.severity === "hint" || d.severity === "info")
418
+ return "advisory";
419
+ return undefined;
420
+ }
421
+ /** Recompute the {blocking, errors, warnings} tally for a diagnostic set.
422
+ * `hint`/`info` tier findings are tallied separately — see
423
+ * {@link classifyDiagnosticTier} and {@link countAdvisories} — and never
424
+ * inflate `warnings` (#2414). */
385
425
  function countDiagnostics(diags) {
386
426
  let blocking = 0;
387
427
  let errors = 0;
@@ -398,22 +438,34 @@ function countDiagnostics(diags) {
398
438
  if (diagnostic.stale &&
399
439
  (diagnostic.staleReason ?? "past-eof") === "past-eof")
400
440
  continue;
401
- if (diagnostic.severity === "error")
441
+ const tier = classifyDiagnosticTier(diagnostic);
442
+ if (tier === "error")
402
443
  errors++;
403
- // #1777: `hint` and `info` tally alongside `warning`. The dispatch path
404
- // now preserves all four ast-grep tiers; before, the runner collapsed
405
- // them to "warning" here. An exact `=== "warning"` test would drop those
406
- // findings out of the footer entirely, so a file with real findings would
407
- // render clean. The footer stays a blocking/error/warning summary on
408
- // purpose — the tier distinction is rendered by the code-quality-warnings
409
- // advisory, not by these counters.
410
- else if (diagnostic.severity === "warning" ||
411
- diagnostic.severity === "hint" ||
412
- diagnostic.severity === "info")
444
+ else if (tier === "warning")
413
445
  warnings++;
446
+ // tier === "advisory" (hint/info): intentionally excluded from the
447
+ // footer's warning tally (#2414) — see `countAdvisories` for the
448
+ // parallel count `getFileDiagnosticSummaries` exposes to
449
+ // `lens_diagnostics` so a hint-only file still shows up as "has issues".
414
450
  }
415
451
  return { blocking, errors, warnings };
416
452
  }
453
+ /** Count `hint`/`info` tier findings using the same past-EOF exclusion as
454
+ * {@link countDiagnostics} (#2414). Kept as a standalone accessor rather than
455
+ * a new `FileRecord.diagnosticCounts` field — the footer never renders this
456
+ * number, only `getFileDiagnosticSummaries` (mode=all) needs it, to keep a
457
+ * hint-only file from being silently dropped from a detailed listing. */
458
+ function countAdvisories(diags) {
459
+ let advisories = 0;
460
+ for (const diagnostic of diags) {
461
+ if (diagnostic.stale &&
462
+ (diagnostic.staleReason ?? "past-eof") === "past-eof")
463
+ continue;
464
+ if (classifyDiagnosticTier(diagnostic) === "advisory")
465
+ advisories++;
466
+ }
467
+ return advisories;
468
+ }
417
469
  /**
418
470
  * Apply the #1641 past-EOF gate to `rec` in place: demote (never drop) any
419
471
  * stored diagnostic whose cited line exceeds the file's CURRENT on-disk line
@@ -804,6 +856,102 @@ export function markWidgetFileBlockersStale(filePath, reason) {
804
856
  }
805
857
  return changed;
806
858
  }
859
+ /**
860
+ * #2275: a dependency-drift demotion the footer is still allowed to draw —
861
+ * the one predicate the render marker, the delivery counter and the
862
+ * footer-hide all ask, so "which rows does this cap govern" cannot drift
863
+ * apart between them.
864
+ */
865
+ function isDependencyDriftDemoted(d) {
866
+ return (d.stale === true &&
867
+ d.staleReason === "dependency-drift" &&
868
+ d.footerRetired !== true);
869
+ }
870
+ /**
871
+ * #2275 review F1: take (and clear) the files whose dependency-drift
872
+ * demotions the footer actually RENDERED since the last call — the delivery
873
+ * population `runtime-turn.ts`'s per-turn cap step walks at turn end.
874
+ *
875
+ * Deliberately NOT a store-wide query over everything currently demoted.
876
+ * `renderWidget` serves one record per pass, so a store-wide walk charged a
877
+ * delivery to every demoted file every turn — including files the footer had
878
+ * never shown and one demoted moments earlier by the same turn end, which
879
+ * also made the ledger's "capped after N deliveries" overstate N by one.
880
+ * Draining here is what makes many repaints within one turn count as the one
881
+ * delivery they are, and a turn with no repaint count as none.
882
+ */
883
+ export function drainRenderedDependencyDriftFilePaths() {
884
+ const out = [...renderedDependencyDriftFiles];
885
+ renderedDependencyDriftFiles.clear();
886
+ return out;
887
+ }
888
+ /**
889
+ * #2275: commit one more RENDERED delivery of `filePath`'s
890
+ * dependency-drift-demoted diagnostics. Every qualifying entry on the record
891
+ * advances together — `markWidgetFileBlockersStale` demotes them as one
892
+ * batch per file, so they share one delivery history, mirroring that
893
+ * write's own file-level scope. Returns the new count, or 0 when the file
894
+ * has nothing this cap governs (never demoted, or already footer-retired —
895
+ * a hidden row is delivered no more, so it must stop advancing).
896
+ */
897
+ export function incrementWidgetDependencyDriftDelivery(filePath) {
898
+ const rec = files.get(fileMapKey(filePath));
899
+ if (!rec)
900
+ return 0;
901
+ let next = 0;
902
+ for (const d of rec.allDiagnostics) {
903
+ if (isDependencyDriftDemoted(d)) {
904
+ next = (d.staleDeliveryCount ?? 0) + 1;
905
+ d.staleDeliveryCount = next;
906
+ }
907
+ }
908
+ return next;
909
+ }
910
+ /**
911
+ * #2275 review F2: retire every dependency-drift-demoted diagnostic on
912
+ * `filePath` once its delivery cap is reached — by HIDING it from the
913
+ * footer, not by dropping it.
914
+ *
915
+ * `RuntimeCoordinator.retireDemotedDependencyDriftBlocker` can delete its
916
+ * whole `_pendingInlineBlockers` record because that store backs exactly one
917
+ * surface (the turn-end advisory text). This store does not: `allDiagnostics`
918
+ * is also what `getFileDiagnosticSummaries` serves to `lens_diagnostics
919
+ * mode=all` and what `lens_diagnostic_mark` cross-checks to reanchor. Removing
920
+ * the entry there turned an unconfirmed LSP error into a CLEAN read and took
921
+ * it out of the error tally #1631 requires it to stay in. So the cap marks
922
+ * `footerRetired` and leaves everything else — severity, counts, the
923
+ * `stale`/`dependency-drift` demotion — exactly as it was; mode=all carries
924
+ * the "capped, still confirmable" wording in its own note. Returns true iff
925
+ * something changed (idempotent on a second call).
926
+ */
927
+ export function retireWidgetDependencyDriftBlockers(filePath) {
928
+ const rec = files.get(fileMapKey(filePath));
929
+ if (!rec)
930
+ return false;
931
+ let changed = false;
932
+ for (const d of rec.allDiagnostics) {
933
+ if (isDependencyDriftDemoted(d)) {
934
+ d.footerRetired = true;
935
+ changed = true;
936
+ }
937
+ }
938
+ if (!changed)
939
+ return false;
940
+ // The record's severity tallies are unchanged by design (the finding is
941
+ // still an error, still non-blocking) — only what the footer draws moves.
942
+ // Fix-round 3 (#2275 review F1): this delete is live only for the
943
+ // multi-file drain re-render case — `drainRenderedDependencyDriftFilePaths`
944
+ // already clears the whole Set before this loop starts, so by the time a
945
+ // given `wPath` reaches this call its own entry is normally long gone. It
946
+ // only does something when a render for a DIFFERENT file interleaves
947
+ // between the drain and this retire (adding `rec.filePath` back to the
948
+ // Set mid-loop) or a render fires between retirement and the caller's next
949
+ // drain — either way, defensive: it keeps a just-retired file from being
950
+ // double-drained (and double-charged) on the next pass.
951
+ renderedDependencyDriftFiles.delete(rec.filePath);
952
+ requestRenderFn?.();
953
+ return true;
954
+ }
807
955
  /**
808
956
  * Keep the TUI honest (#298 follow-up). `reconcileStaleWidgetFiles` drops
809
957
  * widget entries whose file changed on disk after they were last recorded
@@ -840,6 +988,7 @@ export function getFileDiagnosticSummaries() {
840
988
  blocking: rec.diagnosticCounts.blocking,
841
989
  errors: rec.diagnosticCounts.errors,
842
990
  warnings: rec.diagnosticCounts.warnings,
991
+ advisories: countAdvisories(rec.allDiagnostics),
843
992
  hasFinalSnapshot: rec.hasFinalDiagnosticsSnapshot,
844
993
  diagnostics: rec.allDiagnostics.map((d) => ({ ...d })),
845
994
  };
@@ -893,12 +1042,6 @@ export function recordLsp(serverId, root, status, durationMs) {
893
1042
  ? "ready"
894
1043
  : "failed";
895
1044
  lspServers.set(key, { serverId, root, status: mapped, durationMs });
896
- while (lspServers.size > MAX_LSP_SERVER_RECORDS) {
897
- const oldest = lspServers.keys().next().value;
898
- if (oldest === undefined)
899
- break;
900
- lspServers.delete(oldest);
901
- }
902
1045
  requestRender();
903
1046
  }
904
1047
  // ── Render ────────────────────────────────────────────────────────────────────
@@ -961,7 +1104,12 @@ export function renderWidget(width, theme) {
961
1104
  // the footer entirely instead of demoting visibly. `isBlockingOrDemoted` keeps
962
1105
  // it in the list; the per-entry render below distinguishes the two with a
963
1106
  // dimmed marker instead of the red dot.
964
- const isBlockingOrDemoted = (d) => isBlocking(d) || d.stale === true;
1107
+ //
1108
+ // #2275: a demotion that reached its delivery cap is hidden from THIS
1109
+ // surface only (`footerRetired`) — it stays in `allDiagnostics` for
1110
+ // mode=all and lens_diagnostic_mark, which is where the agent is told it
1111
+ // was capped and can still be confirmed by a re-run.
1112
+ const isBlockingOrDemoted = (d) => (isBlocking(d) || d.stale === true) && d.footerRetired !== true;
965
1113
  const withBlocking = recencySorted.filter((r) => r.diagnostics.some(isBlockingOrDemoted));
966
1114
  if (withBlocking.length > 0) {
967
1115
  const rec = withBlocking[0];
@@ -979,6 +1127,18 @@ export function renderWidget(width, theme) {
979
1127
  lines.push(fitLine(` ${dim(path.basename(rec.filePath))}`, w));
980
1128
  }
981
1129
  const blockers = rec.diagnostics.filter(isBlockingOrDemoted).slice(0, 5);
1130
+ // #2275 review F1: THIS is the point where a demoted row is delivered to
1131
+ // a human — after the `withBlocking[0]` pick and the top-five slice, not
1132
+ // before them. Mark the file so the next turn end can charge exactly one
1133
+ // delivery against its cap. File-level, matching
1134
+ // `markWidgetFileBlockersStale`'s own file-level demotion batch (all of a
1135
+ // file's drift demotions share one delivery history); a file holding more
1136
+ // than five demoted rows delivers the visible ones and counts the batch,
1137
+ // which is the conservative direction — it retires no LATER than the rows
1138
+ // the agent actually read.
1139
+ if (blockers.some(isDependencyDriftDemoted)) {
1140
+ renderedDependencyDriftFiles.add(rec.filePath);
1141
+ }
982
1142
  for (const d of blockers) {
983
1143
  // A past-EOF demotion's coordinate is untrustworthy — render the
984
1144
  // marker instead of the line (#1641); drift demotions keep theirs.
@@ -1062,7 +1222,10 @@ function formatFileRowVertical(rec, theme) {
1062
1222
  .filter(([, f]) => f.changed && f.success)
1063
1223
  .map(([name]) => name);
1064
1224
  const failedFormatters = [...rec.formatters.entries()]
1065
- .filter(([, f]) => !f.success)
1225
+ // An `unavailable` tool is not a code failure (#2413): it never earns a red
1226
+ // `fmt-failed` marker. Its result already carries success:true, so this
1227
+ // guard is a belt that keeps the intent explicit at the render seam.
1228
+ .filter(([, f]) => !f.success && f.outcome !== "unavailable")
1066
1229
  .map(([name]) => name);
1067
1230
  const formatMark = (failedFormatters.length > 0
1068
1231
  ? red(` fmt-failed:${failedFormatters.join(",")}`)
@@ -1205,7 +1368,9 @@ function hasChangedFormatter(rec) {
1205
1368
  return [...rec.formatters.values()].some((f) => f.changed && f.success);
1206
1369
  }
1207
1370
  function hasFailedFormatter(rec) {
1208
- return [...rec.formatters.values()].some((f) => !f.success);
1371
+ // `unavailable` (#2413) is success:true and must never count as a failure —
1372
+ // the explicit guard keeps that true even if a future path mislabels it.
1373
+ return [...rec.formatters.values()].some((f) => !f.success && f.outcome !== "unavailable");
1209
1374
  }
1210
1375
  function shouldRenderFile(rec) {
1211
1376
  return (rec.hasFinalDiagnosticsSnapshot ||
@@ -21,19 +21,31 @@
21
21
  */
22
22
  import * as path from "node:path";
23
23
  import { isTestMode } from "./env-utils.js";
24
- import { getGlobalPiLensDir } from "./file-utils.js";
24
+ import { getGlobalPiLensLogDir } from "./probe-home-state.js";
25
25
  import { getMaxLogSizeMB } from "./log-cleanup.js";
26
26
  import { createNdjsonLogger } from "./ndjson-logger.js";
27
- const WORD_INDEX_LOG_FILE = path.join(getGlobalPiLensDir(), "word-index.log");
27
+ import { normalizeFilePath } from "./path-utils.js";
28
+ const WORD_INDEX_LOG_FILE = path.join(getGlobalPiLensLogDir(), "word-index.log");
28
29
  const writer = createNdjsonLogger({
29
30
  filePath: WORD_INDEX_LOG_FILE,
30
31
  maxBytes: getMaxLogSizeMB() * 1024 * 1024,
31
32
  });
33
+ /**
34
+ * #2141 class sweep: mirrors review-graph-logger.ts's `cwd` normalization.
35
+ * Call sites here feed both raw roots (`snapshotRoot`, `path.resolve(cwd)`)
36
+ * and pre-normalized cache keys (`key`), so the same root could show up in
37
+ * two path forms. Normalizing once at this shared emit seam closes that for
38
+ * every call site, present and future.
39
+ */
32
40
  export function logWordIndex(entry) {
33
41
  if (isTestMode()) {
34
42
  return;
35
43
  }
36
- writer.log({ ts: new Date().toISOString(), ...entry });
44
+ writer.log({
45
+ ts: new Date().toISOString(),
46
+ ...entry,
47
+ cwd: normalizeFilePath(entry.cwd),
48
+ });
37
49
  }
38
50
  export function getWordIndexLogPath() {
39
51
  return WORD_INDEX_LOG_FILE;