pi-lens 3.8.73 → 4.0.0

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 (346) hide show
  1. package/CHANGELOG.md +2614 -12
  2. package/README.md +50 -21
  3. package/dist/clients/actionable-warnings.js +73 -18
  4. package/dist/clients/agent-behavior-client.js +21 -4
  5. package/dist/clients/ast-grep-client.js +68 -16
  6. package/dist/clients/ast-grep-types.js +0 -1
  7. package/dist/clients/ast-grep-yaml-synth.js +37 -10
  8. package/dist/clients/atomic-write-staging.js +63 -0
  9. package/dist/clients/atomic-write.js +116 -16
  10. package/dist/clients/bash-file-access.js +193 -23
  11. package/dist/clients/biome-client.js +29 -25
  12. package/dist/clients/bootstrap.js +11 -2
  13. package/dist/clients/bounded-cache.js +34 -0
  14. package/dist/clients/bounded-pid-file-lock.js +192 -0
  15. package/dist/clients/bus-events-logger.js +5 -1
  16. package/dist/clients/bus-publish.js +10 -3
  17. package/dist/clients/cache/rule-cache.js +89 -6
  18. package/dist/clients/cache-manager.js +97 -9
  19. package/dist/clients/cache-observability.js +498 -0
  20. package/dist/clients/call-graph.js +460 -108
  21. package/dist/clients/cascade-logger.js +5 -1
  22. package/dist/clients/child-unref.js +85 -0
  23. package/dist/clients/codebase-model.js +52 -23
  24. package/dist/clients/collateral-test-role.js +40 -0
  25. package/dist/clients/complexity-client.js +6 -8
  26. package/dist/clients/console-guard-install.js +14 -0
  27. package/dist/clients/cooperative-budget.js +42 -0
  28. package/dist/clients/dead-code-client.js +2 -1
  29. package/dist/clients/debug-handles.js +206 -0
  30. package/dist/clients/debug-heap.js +167 -0
  31. package/dist/clients/degradation-ledger.js +153 -0
  32. package/dist/clients/dependency-checker.js +466 -63
  33. package/dist/clients/diagnostic-dispositions.js +83 -13
  34. package/dist/clients/diagnostics-publish.js +10 -3
  35. package/dist/clients/dispatch/auxiliary-lsp.js +34 -0
  36. package/dist/clients/dispatch/dispatcher.js +71 -13
  37. package/dist/clients/dispatch/fact-store.js +11 -0
  38. package/dist/clients/dispatch/facts/function-facts.js +37 -7
  39. package/dist/clients/dispatch/facts/import-facts.js +108 -119
  40. package/dist/clients/dispatch/facts/tree-sitter-facts.js +12 -12
  41. package/dist/clients/dispatch/facts/try-catch-facts.js +13 -0
  42. package/dist/clients/dispatch/indent-detect.js +35 -0
  43. package/dist/clients/dispatch/inline-suppressions.js +17 -8
  44. package/dist/clients/dispatch/integration.js +1094 -611
  45. package/dist/clients/dispatch/lazy.js +14 -0
  46. package/dist/clients/dispatch/plan.js +11 -1
  47. package/dist/clients/dispatch/rule-id-normalize.js +50 -0
  48. package/dist/clients/dispatch/rule-policy.js +135 -0
  49. package/dist/clients/dispatch/rules/async-noise.js +1 -0
  50. package/dist/clients/dispatch/rules/unsafe-boundary.js +7 -0
  51. package/dist/clients/dispatch/runners/ast-grep-napi.js +47 -6
  52. package/dist/clients/dispatch/runners/biome-check.js +5 -9
  53. package/dist/clients/dispatch/runners/detekt.js +3 -2
  54. package/dist/clients/dispatch/runners/hadolint.js +2 -1
  55. package/dist/clients/dispatch/runners/helm-lint.js +154 -0
  56. package/dist/clients/dispatch/runners/htmlhint.js +3 -2
  57. package/dist/clients/dispatch/runners/index.js +5 -1
  58. package/dist/clients/dispatch/runners/lsp.js +14 -6
  59. package/dist/clients/dispatch/runners/markdownlint.js +4 -9
  60. package/dist/clients/dispatch/runners/psscriptanalyzer.js +10 -1
  61. package/dist/clients/dispatch/runners/pyright.js +9 -5
  62. package/dist/clients/dispatch/runners/ruff.js +4 -8
  63. package/dist/clients/dispatch/runners/shellcheck.js +2 -3
  64. package/dist/clients/dispatch/runners/shfmt.js +2 -3
  65. package/dist/clients/dispatch/runners/spotbugs.js +2 -1
  66. package/dist/clients/dispatch/runners/taplo.js +2 -1
  67. package/dist/clients/dispatch/runners/terragrunt.js +163 -0
  68. package/dist/clients/dispatch/runners/tflint.js +37 -5
  69. package/dist/clients/dispatch/runners/tree-sitter.js +155 -166
  70. package/dist/clients/dispatch/runners/trivy-config.js +13 -7
  71. package/dist/clients/dispatch/runners/utils/lazy-installer.js +3 -0
  72. package/dist/clients/dispatch/runners/utils/runner-helpers.js +284 -34
  73. package/dist/clients/dispatch/runners/utils/spawn-outcome.js +22 -0
  74. package/dist/clients/dispatch/runners/yaml-rule-parser.js +5 -4
  75. package/dist/clients/disposition-publish.js +10 -3
  76. package/dist/clients/durable-store.js +97 -0
  77. package/dist/clients/event-loop-monitor.js +94 -3
  78. package/dist/clients/extension-log.js +139 -0
  79. package/dist/clients/extension-mode.js +73 -0
  80. package/dist/clients/feature-hints.js +53 -7
  81. package/dist/clients/file-kinds.js +143 -31
  82. package/dist/clients/file-role.js +18 -3
  83. package/dist/clients/file-utils.js +56 -34
  84. package/dist/clients/fix-worklog.js +14 -2
  85. package/dist/clients/format-events-publish.js +16 -3
  86. package/dist/clients/format-service.js +24 -8
  87. package/dist/clients/formatters-lazy.js +8 -0
  88. package/dist/clients/formatters.js +358 -61
  89. package/dist/clients/generated-artifacts.js +169 -15
  90. package/dist/clients/git-guard.js +681 -11
  91. package/dist/clients/git-tracked-ignore.js +4 -0
  92. package/dist/clients/go-client.js +2 -1
  93. package/dist/clients/govulncheck-client.js +8 -0
  94. package/dist/clients/grammar-source.js +12 -4
  95. package/dist/clients/gzip-stage-write.js +102 -0
  96. package/dist/clients/host-ports.js +31 -0
  97. package/dist/clients/installer/index.js +733 -213
  98. package/dist/clients/instance-reaper.js +142 -172
  99. package/dist/clients/jscpd-client.js +31 -54
  100. package/dist/clients/knip-client.js +78 -36
  101. package/dist/clients/language-policy.js +14 -2
  102. package/dist/clients/language-profile.js +72 -77
  103. package/dist/clients/latency-logger.js +28 -16
  104. package/dist/clients/lens-config.js +140 -166
  105. package/dist/clients/lens-engine.js +124 -21
  106. package/dist/clients/lens-events.js +27 -4
  107. package/dist/clients/lens-flag-registry.js +293 -0
  108. package/dist/clients/lens-map.js +96 -26
  109. package/dist/clients/live-bus-emitter.js +30 -0
  110. package/dist/clients/log-cleanup.js +8 -18
  111. package/dist/clients/lsp/cascade-tier.js +23 -3
  112. package/dist/clients/lsp/client.js +566 -57
  113. package/dist/clients/lsp/config.js +74 -10
  114. package/dist/clients/lsp/diagnostic-binding.js +86 -0
  115. package/dist/clients/lsp/edits.js +1012 -121
  116. package/dist/clients/lsp/index.js +1840 -803
  117. package/dist/clients/lsp/language.js +4 -1
  118. package/dist/clients/lsp/launch.js +40 -32
  119. package/dist/clients/lsp/path-utils.js +1 -1
  120. package/dist/clients/lsp/ruby-drive-dirs.js +83 -0
  121. package/dist/clients/lsp/server.js +279 -60
  122. package/dist/clients/lsp/workspace-diagnostics-cache.js +28 -4
  123. package/dist/clients/lsp-document-symbols.js +112 -0
  124. package/dist/clients/lsp-lazy.js +8 -0
  125. package/dist/clients/lsp-mutation.js +248 -0
  126. package/dist/clients/mcp/analyze.js +128 -36
  127. package/dist/clients/mcp/ipc.js +157 -24
  128. package/dist/clients/mcp/review.js +36 -3
  129. package/dist/clients/mcp/session.js +216 -8
  130. package/dist/clients/memory-sampler.js +115 -0
  131. package/dist/clients/metrics-client.js +2 -1
  132. package/dist/clients/metrics-history.js +2 -1
  133. package/dist/clients/module-report-lsp.js +5 -0
  134. package/dist/clients/module-report.js +267 -82
  135. package/dist/clients/ndjson-logger.js +334 -103
  136. package/dist/clients/opengrep-client.js +1 -1
  137. package/dist/clients/partial-edit-apply.js +120 -5
  138. package/dist/clients/path-keyed-map.js +76 -0
  139. package/dist/clients/path-utils.js +141 -5
  140. package/dist/clients/performance-report.js +327 -0
  141. package/dist/clients/pipeline.js +54 -13
  142. package/dist/clients/print-mode.js +21 -0
  143. package/dist/clients/project-changes.js +117 -4
  144. package/dist/clients/project-diagnostics/cache.js +3 -2
  145. package/dist/clients/project-diagnostics/extractors.js +26 -115
  146. package/dist/clients/project-diagnostics/fresh-fetch.js +149 -19
  147. package/dist/clients/project-diagnostics/runner-adapters/call-graph-impact.js +117 -0
  148. package/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +37 -5
  149. package/dist/clients/project-diagnostics/scanner.js +268 -128
  150. package/dist/clients/project-lens-config.js +138 -34
  151. package/dist/clients/project-report.js +64 -25
  152. package/dist/clients/project-scale.js +8 -10
  153. package/dist/clients/project-snapshot-persist-worker.js +6 -0
  154. package/dist/clients/project-snapshot.js +767 -8
  155. package/dist/clients/project-trust.js +181 -0
  156. package/dist/clients/quiet-window.js +27 -5
  157. package/dist/clients/read-bridge.js +147 -0
  158. package/dist/clients/read-expansion.js +22 -12
  159. package/dist/clients/read-guard-logger.js +159 -1
  160. package/dist/clients/read-guard-tool-lines.js +150 -29
  161. package/dist/clients/read-guard.js +262 -3
  162. package/dist/clients/redact/secrets.js +449 -0
  163. package/dist/clients/resource-sampler.js +144 -39
  164. package/dist/clients/reverse-deps.js +48 -0
  165. package/dist/clients/review-graph/builder.js +2570 -326
  166. package/dist/clients/review-graph/git-identity.js +2 -1
  167. package/dist/clients/review-graph/import-resolvers.js +21 -10
  168. package/dist/clients/review-graph/persist-worker.js +10 -0
  169. package/dist/clients/review-graph/query.js +5 -0
  170. package/dist/clients/review-graph/shared-extraction-ir.js +90 -0
  171. package/dist/clients/review-graph/symbol-id.js +46 -0
  172. package/dist/clients/review-graph/tsconfig-paths.js +57 -6
  173. package/dist/clients/review-graph/workspace-modules.js +51 -3
  174. package/dist/clients/review-graph-logger.js +53 -0
  175. package/dist/clients/ruff-client.js +27 -44
  176. package/dist/clients/runtime-agent-end.js +111 -75
  177. package/dist/clients/runtime-context.js +36 -12
  178. package/dist/clients/runtime-coordinator.js +76 -8
  179. package/dist/clients/runtime-session.js +799 -137
  180. package/dist/clients/runtime-tool-call.js +117 -62
  181. package/dist/clients/runtime-tool-result.js +218 -23
  182. package/dist/clients/runtime-turn.js +284 -40
  183. package/dist/clients/rust-client.js +2 -1
  184. package/dist/clients/safe-spawn.js +873 -71
  185. package/dist/clients/security-scan-client.js +2 -1
  186. package/dist/clients/session-start-observability.js +15 -0
  187. package/dist/clients/session-state-store.js +5 -3
  188. package/dist/clients/sessionstart-logger.js +18 -0
  189. package/dist/clients/sg-runner.js +284 -159
  190. package/dist/clients/slow-fs.js +2 -1
  191. package/dist/clients/smells-rollup.js +224 -0
  192. package/dist/clients/source-filter.js +231 -43
  193. package/dist/clients/source-walker.js +66 -10
  194. package/dist/clients/startup-scan.js +15 -16
  195. package/dist/clients/startup-timing.js +9 -0
  196. package/dist/clients/test-runner-client.js +38 -8
  197. package/dist/clients/todo-scanner.js +30 -3
  198. package/dist/clients/tool-event.js +35 -0
  199. package/dist/clients/tool-policy.js +388 -39
  200. package/dist/clients/tool-render.js +155 -0
  201. package/dist/clients/tree-sitter-cache.js +153 -140
  202. package/dist/clients/tree-sitter-client.js +1722 -80
  203. package/dist/clients/tree-sitter-logger.js +64 -1
  204. package/dist/clients/tree-sitter-query-loader.js +117 -12
  205. package/dist/clients/tree-sitter-shared.js +48 -19
  206. package/dist/clients/tree-sitter-symbol-extractor.js +115 -11
  207. package/dist/clients/user-notify.js +51 -0
  208. package/dist/clients/vanished-instance-marker.js +57 -0
  209. package/dist/clients/warm-attach.js +33 -13
  210. package/dist/clients/widget-state.js +342 -49
  211. package/dist/clients/word-index-logger.js +44 -0
  212. package/dist/clients/word-index.js +612 -68
  213. package/dist/clients/workspace-topology.js +100 -7
  214. package/dist/index.js +65923 -47161
  215. package/dist/mcp/analyze-cli.js +185 -29
  216. package/dist/mcp/cli.js +110 -0
  217. package/dist/mcp/server.js +272 -50
  218. package/dist/tools/ast-grep-search.js +93 -27
  219. package/dist/tools/lens-diagnostics.js +247 -41
  220. package/dist/tools/lsp-diagnostics.js +153 -40
  221. package/dist/tools/lsp-navigation.js +51 -31
  222. package/dist/tools/module-report.js +9 -0
  223. package/dist/tools/symbol-search.js +21 -2
  224. package/docs/agent-guide.md +247 -0
  225. package/docs/agent-tools.md +21 -10
  226. package/docs/analysisall.md +192 -0
  227. package/docs/api-ports-inventory.md +165 -0
  228. package/docs/ast-grep_rules_catalog.md +5 -2
  229. package/docs/astplayground.md +3 -4
  230. package/docs/audit1.md +5 -5
  231. package/docs/durable-store-audit-1202.md +32 -0
  232. package/docs/environment-variables.md +197 -11
  233. package/docs/fable.md +27 -0
  234. package/docs/features.md +35 -5
  235. package/docs/globalconfig.md +101 -5
  236. package/docs/language-coverage.md +6 -5
  237. package/docs/lsp-capability-matrix.md +2 -2
  238. package/docs/mcp.md +54 -20
  239. package/docs/servercapabilities.md +7 -4
  240. package/docs/settings.md +192 -0
  241. package/docs/tools.md +18 -1
  242. package/docs/tree-sitter_rules_catalog.md +11 -31
  243. package/docs/usage.md +31 -0
  244. package/package.json +139 -138
  245. package/rules/ast-grep-rules/rule-tests/hardcoded-url-js-test.yml +1 -0
  246. package/rules/ast-grep-rules/rule-tests/hardcoded-url-test.yml +2 -0
  247. package/rules/ast-grep-rules/rule-tests/no-bare-except-test.yml +49 -0
  248. package/rules/ast-grep-rules/rule-tests/no-bare-host-path-in-win32-branch-test.yml +87 -0
  249. package/rules/ast-grep-rules/rule-tests/no-case-declarations-js-test.yml +10 -0
  250. package/rules/ast-grep-rules/rule-tests/no-case-declarations-test.yml +10 -0
  251. package/rules/ast-grep-rules/rule-tests/no-javascript-url-js-test.yml +10 -0
  252. package/rules/ast-grep-rules/rule-tests/no-javascript-url-test.yml +10 -0
  253. package/rules/ast-grep-rules/rule-tests/no-nested-links-test.yml +11 -1
  254. package/rules/ast-grep-rules/rule-tests/no-raw-json-store-write-test.yml +17 -0
  255. package/rules/ast-grep-rules/rule-tests/no-win32-isabsolute-for-qualification-test.yml +16 -0
  256. package/rules/ast-grep-rules/rule-tests/strict-equality-js-test.yml +3 -0
  257. package/rules/ast-grep-rules/rule-tests/strict-equality-test.yml +3 -0
  258. package/rules/ast-grep-rules/rule-tests/strict-inequality-js-test.yml +3 -0
  259. package/rules/ast-grep-rules/rule-tests/strict-inequality-test.yml +4 -0
  260. package/rules/ast-grep-rules/rules/hardcoded-url-js.yml +22 -4
  261. package/rules/ast-grep-rules/rules/hardcoded-url.yml +22 -4
  262. package/rules/ast-grep-rules/rules/no-bare-except.yml +3 -15
  263. package/rules/ast-grep-rules/rules/no-bare-host-path-in-win32-branch.yml +25 -0
  264. package/rules/ast-grep-rules/rules/no-case-declarations-js.yml +4 -3
  265. package/rules/ast-grep-rules/rules/no-case-declarations.yml +4 -3
  266. package/rules/ast-grep-rules/rules/no-console-except-error-js.yml +10 -0
  267. package/rules/ast-grep-rules/rules/no-console-except-error.yml +10 -0
  268. package/rules/ast-grep-rules/rules/no-javascript-url-js.yml +42 -6
  269. package/rules/ast-grep-rules/rules/no-javascript-url.yml +42 -6
  270. package/rules/ast-grep-rules/rules/no-nested-links.yml +7 -0
  271. package/rules/ast-grep-rules/rules/no-raw-json-store-write.yml +30 -0
  272. package/rules/ast-grep-rules/rules/no-win32-isabsolute-for-qualification.yml +23 -0
  273. package/rules/ast-grep-rules/rules/strict-equality-js.yml +14 -1
  274. package/rules/ast-grep-rules/rules/strict-equality.yml +14 -1
  275. package/rules/ast-grep-rules/rules/strict-inequality-js.yml +14 -1
  276. package/rules/ast-grep-rules/rules/strict-inequality.yml +14 -1
  277. package/rules/tree-sitter-queries/cpp/no-memset-sensitive-data.yml +9 -3
  278. package/rules/tree-sitter-queries/cpp/no-scoped-lock-without-args.yml +6 -6
  279. package/rules/tree-sitter-queries/css/calc-spacing.yml +8 -6
  280. package/rules/tree-sitter-queries/go/go-mutex-copy.yml +21 -18
  281. package/rules/tree-sitter-queries/go/go-shared-map-write-goroutine.yml +3 -3
  282. package/rules/tree-sitter-queries/java/infinite-loop-java.yml +3 -3
  283. package/rules/tree-sitter-queries/java/junit-call-super.yml +6 -4
  284. package/rules/tree-sitter-queries/java/no-double-checked-locking.yml +10 -8
  285. package/rules/tree-sitter-queries/java/no-field-shadowing.yml +1 -1
  286. package/rules/tree-sitter-queries/java/prepared-statement-valid-indices.yml +2 -3
  287. package/rules/tree-sitter-queries/java/resources-closed.yml +2 -2
  288. package/rules/tree-sitter-queries/java/springboot-default-package.yml +6 -3
  289. package/rules/tree-sitter-queries/java/switch-fall-through.yml +6 -4
  290. package/rules/tree-sitter-queries/java/switch-non-case-labels.yml +4 -3
  291. package/rules/tree-sitter-queries/java/tests-include-assertions.yml +6 -5
  292. package/rules/tree-sitter-queries/javascript/switch-case-termination-js.yml +5 -8
  293. package/rules/tree-sitter-queries/javascript/switch-non-case-labels.yml +3 -2
  294. package/rules/tree-sitter-queries/kotlin/prepared-statement-indices.yml +9 -5
  295. package/rules/tree-sitter-queries/php/this-in-static-context.yml +5 -5
  296. package/rules/tree-sitter-queries/python/exit-signature-check.yml +2 -9
  297. package/rules/tree-sitter-queries/python/in-operator-unsupported.yml +3 -4
  298. package/rules/tree-sitter-queries/python/no-super-torchscript.yml +4 -8
  299. package/rules/tree-sitter-queries/python/notimplemented-boolean-context.yml +1 -5
  300. package/rules/tree-sitter-queries/python/python-empty-except.yml +1 -1
  301. package/rules/tree-sitter-queries/python/return-in-init.yml +0 -2
  302. package/rules/tree-sitter-queries/python/yield-return-outside-function.yml +0 -3
  303. package/rules/tree-sitter-queries/ruby/ruby-command-injection.yml +1 -12
  304. package/rules/tree-sitter-queries/ruby/ruby-eval.yml +1 -1
  305. package/rules/tree-sitter-queries/ruby/ruby-insecure-deserialization.yml +4 -4
  306. package/rules/tree-sitter-queries/ruby/ruby-insecure-random.yml +4 -11
  307. package/rules/tree-sitter-queries/ruby/ruby-open-struct.yml +1 -1
  308. package/rules/tree-sitter-queries/ruby/ruby-string-eval.yml +2 -3
  309. package/rules/tree-sitter-queries/ruby/ruby-weak-hash.yml +4 -4
  310. package/rules/tree-sitter-queries/rust/rust-lock-held-across-await.yml +4 -3
  311. package/rules/tree-sitter-queries/tsx/no-nested-links.yml +4 -6
  312. package/rules/tree-sitter-queries/typescript/console-statement.yml +11 -0
  313. package/rules/tree-sitter-queries/typescript/duplicate-function-arg.yml +4 -9
  314. package/rules/tree-sitter-queries/typescript/empty-switch-case.yml +13 -9
  315. package/rules/tree-sitter-queries/typescript/infinite-loop.yml +2 -2
  316. package/rules/tree-sitter-queries/typescript/mixed-async-styles.yml +10 -1
  317. package/rules/tree-sitter-queries/typescript/switch-case-termination.yml +5 -8
  318. package/rules/tree-sitter-queries/typescript/ts-insecure-random.yml +7 -10
  319. package/rules/tree-sitter-queries/typescript/unsafe-regex.yml +4 -2
  320. package/rules/typos/_typos.toml +17 -0
  321. package/scripts/analyze-pi-lens-logs.mjs +55 -2
  322. package/skills/pi-lens-write-tree-sitter-rule/SKILL.md +5 -0
  323. package/dist/clients/ast-grep-parser.js +0 -86
  324. package/dist/clients/config-enabled-shape.js +0 -39
  325. package/dist/clients/lsp/interactive-install.js +0 -367
  326. package/dist/clients/lsp/lsp-index.js +0 -10
  327. package/dist/clients/lsp/server-strategies.js +0 -5
  328. package/dist/clients/project-metadata.js +0 -690
  329. package/dist/clients/source-groups.js +0 -96
  330. package/dist/clients/types.js +0 -11
  331. package/docs/env_variables.md +0 -10
  332. package/rules/tree-sitter-queries/abap-disabled/delete-where.yml +0 -46
  333. package/rules/tree-sitter-queries/cpp/no-confused-move-forward.yml +0 -59
  334. package/rules/tree-sitter-queries/cpp/noexcept-functions.yml +0 -58
  335. package/rules/tree-sitter-queries/java/mockito-initialized.yml +0 -66
  336. package/rules/tree-sitter-queries/java/no-wait-notify-on-thread.yml +0 -58
  337. package/rules/tree-sitter-queries/java/short-circuit-logic.yml +0 -57
  338. package/rules/tree-sitter-queries/java/spring-session-attributes-setcomplete.yml +0 -75
  339. package/rules/tree-sitter-queries/plsql-disabled/delete-update-where.yml +0 -56
  340. package/rules/tree-sitter-queries/plsql-disabled/end-loop-semicolon.yml +0 -51
  341. package/rules/tree-sitter-queries/plsql-disabled/fetch-bulk-collect-limit.yml +0 -47
  342. package/rules/tree-sitter-queries/plsql-disabled/forallsave-exceptions.yml +0 -51
  343. package/rules/tree-sitter-queries/plsql-disabled/nchar-nvarchar2-bytes.yml +0 -54
  344. package/rules/tree-sitter-queries/plsql-disabled/not-null-initialization.yml +0 -59
  345. package/rules/tree-sitter-queries/plsql-disabled/raise-application-error-codes.yml +0 -50
  346. package/rules/tree-sitter-queries/typescript-disabled/ts-path-traversal.yml +0 -71
@@ -14,17 +14,21 @@ import * as path from "node:path";
14
14
  import { Type } from "../clients/deps/typebox.js";
15
15
  import { anchorsForDiagnostic, applyDispositions, applyWeakDispositions, getDisposition, } from "../clients/diagnostic-dispositions.js";
16
16
  import { applyInlineSuppressions } from "../clients/dispatch/inline-suppressions.js";
17
+ import { normalizeRuleId } from "../clients/dispatch/rule-id-normalize.js";
18
+ import { applyRulePolicy, rulePolicyMapFromConfig } from "../clients/dispatch/rule-policy.js";
19
+ import { loadPiLensProjectConfig } from "../clients/project-lens-config.js";
17
20
  import { compactRenderResult } from "./render-compact.js";
18
21
  import { combineAbortSignals } from "../clients/deadline-utils.js";
19
22
  import { getProjectIgnoreMatcher } from "../clients/file-utils.js";
20
23
  import { isAtOrAboveHomeDir, normalizeFilePath, } from "../clients/path-utils.js";
21
24
  import { getLSPService } from "../clients/lsp/index.js";
22
25
  import { primaryServerId } from "../clients/lsp/config.js";
26
+ import { hashDiagnosticContent, } from "../clients/lsp/diagnostic-binding.js";
23
27
  import { loadProjectDiagnosticsDeltaReport, loadProjectDiagnosticsSnapshot, PROJECT_DIAGNOSTICS_CACHE_VERSION, reconcileProjectDiagnosticsSnapshot, } from "../clients/project-diagnostics/cache.js";
24
28
  import { warmTriggerFor } from "../clients/project-diagnostics/extractors.js";
25
29
  import { fetchFreshProjectDiagnostics } from "../clients/project-diagnostics/fresh-fetch.js";
26
30
  import { loadBootstrapClients } from "../clients/bootstrap.js";
27
- import { scanTruncationNotice } from "../clients/lens-engine.js";
31
+ import { generatedSkipNotice, scanTruncationNotice, } from "../clients/lens-engine.js";
28
32
  import { scanProjectDiagnostics } from "../clients/project-diagnostics/scanner.js";
29
33
  import { getFileDiagnosticSummaries, reconcileScanDiagnostics, reconcileStaleWidgetFiles, } from "../clients/widget-state.js";
30
34
  import { convertLspDiagnostics } from "../clients/dispatch/utils/lsp-diagnostics.js";
@@ -50,6 +54,48 @@ const FULL_SCAN_WALL_CLOCK_MS = (() => {
50
54
  const raw = Number(process.env.PI_LENS_LENS_DIAGNOSTICS_FULL_TIMEOUT_MS);
51
55
  return Number.isFinite(raw) && raw > 0 ? raw : 300_000; // 5 min default
52
56
  })();
57
+ // Binding verification is a fallback for result producers that supplied a
58
+ // content hash without an already-computed disk verdict. Keep it one-file-at-a-
59
+ // time and yield periodically: a full scan can contain thousands of results,
60
+ // and a synchronous read loop here would freeze the host even though the LSP
61
+ // work itself was asynchronous (#1198 follow-up).
62
+ const FULL_SCAN_BINDING_YIELD_EVERY = 16;
63
+ async function findFullScanBindingMismatches(results) {
64
+ const mismatched = new WeakSet();
65
+ let sinceYield = 0;
66
+ for (const result of results) {
67
+ if (result.boundToCurrentDisk === false) {
68
+ mismatched.add(result);
69
+ }
70
+ else if (
71
+ // A `true` verdict came from the LSP/cache binding seam and has already
72
+ // compared its fingerprint with disk. Only legacy/partial producers need
73
+ // this fallback read; `unknown` remains unknown when no hash exists.
74
+ result.boundToCurrentDisk !== true &&
75
+ result.contentHash !== undefined) {
76
+ try {
77
+ // Promise-based, sequential reads bound both event-loop occupancy and
78
+ // outstanding file handles. A read failure deliberately leaves the
79
+ // verdict unknown (the historical fail-open behavior).
80
+ const content = await fs.readFile(result.filePath, "utf-8");
81
+ if (hashDiagnosticContent(content) !== result.contentHash) {
82
+ mismatched.add(result);
83
+ }
84
+ }
85
+ catch {
86
+ // Unreadable/deleted files cannot disprove the binding. Leave them
87
+ // unknown for the existing caller policy, rather than manufacturing a
88
+ // mismatch or a clean verdict here.
89
+ }
90
+ }
91
+ sinceYield += 1;
92
+ if (sinceYield >= FULL_SCAN_BINDING_YIELD_EVERY) {
93
+ sinceYield = 0;
94
+ await new Promise((resolve) => setImmediate(resolve));
95
+ }
96
+ }
97
+ return mismatched;
98
+ }
53
99
  export function createLensDiagnosticsTool(cacheManager, getCwd, getLspService = getLSPService,
54
100
  // Flush any debounced per-edit dispatches before reading, so files the agent
55
101
  // fixed earlier in the turn are re-dispatched and the widget reflects the
@@ -68,7 +114,11 @@ flushPending = async () => { },
68
114
  // write apart from a concurrent, genuinely newer per-edit write for the
69
115
  // same file. Optional/undefined in tests — an omitted writeIndex always
70
116
  // proceeds (see `reconcileScanDiagnostics`'s doc comment).
71
- nextWriteIndex) {
117
+ nextWriteIndex,
118
+ // #798/#338: capture host UI methods synchronously from the active tool event.
119
+ // The returned closure is safe for the later async sweep to invoke because it
120
+ // never dereferences the session-guarded ctx.ui getter.
121
+ captureLspStatusRepaint) {
72
122
  return {
73
123
  name: "lens_diagnostics",
74
124
  label: "Project Diagnostics",
@@ -113,22 +163,25 @@ nextWriteIndex) {
113
163
  const coldSuffix = details?.coldRunners && details.coldRunners.length > 0
114
164
  ? ` (${details.coldRunners.length} cold: ${details.coldRunners.join(", ")})`
115
165
  : "";
166
+ const failedSuffix = details?.failedAnalyzers && details.failedAnalyzers.length > 0
167
+ ? ` (${details.failedAnalyzers.length} failed: ${details.failedAnalyzers.map((item) => item.id).join(", ")})`
168
+ : "";
116
169
  if (mode === "delta") {
117
170
  const aw = details?.actionableWarnings ?? 0;
118
171
  const cq = details?.qualityIssues ?? 0;
119
172
  const pd = details?.projectDiagnostics ?? 0;
120
173
  if (aw + cq + pd === 0)
121
- return `lens_diagnostics delta — clean${coldSuffix}`;
122
- return `lens_diagnostics delta — ${aw} actionable · ${cq} quality · ${pd} project${coldSuffix}`;
174
+ return `lens_diagnostics delta — clean${coldSuffix}${failedSuffix}`;
175
+ return `lens_diagnostics delta — ${aw} actionable · ${cq} quality · ${pd} project${coldSuffix}${failedSuffix}`;
123
176
  }
124
177
  const b = details?.totalBlocking ?? 0;
125
178
  const e = details?.totalErrors ?? 0;
126
179
  const w = details?.totalWarnings ?? 0;
127
180
  const files = details?.filesWithIssues ?? details?.filesChecked ?? 0;
128
181
  if (b + e + w === 0) {
129
- return `lens_diagnostics ${mode} — clean (${files} files)${coldSuffix}`;
182
+ return `lens_diagnostics ${mode} — clean (${files} files)${coldSuffix}${failedSuffix}`;
130
183
  }
131
- return `lens_diagnostics ${mode} — ${b} blocking · ${e} errors · ${w} warnings (${files} files)${coldSuffix}`;
184
+ return `lens_diagnostics ${mode} — ${b} blocking · ${e} errors · ${w} warnings (${files} files)${coldSuffix}${failedSuffix}`;
132
185
  }),
133
186
  parameters: Type.Object({
134
187
  mode: Type.Optional(Type.String({
@@ -149,6 +202,9 @@ nextWriteIndex) {
149
202
  maxLspFiles: Type.Optional(Type.Number({
150
203
  description: "mode=full only: cap the number of files routed through the language server for the project-wide LSP sweep. On large projects (e.g. a Next.js app with thousands of source files) the uncapped sweep can take many minutes; set this to bound it. Default is generous (env PI_LENS_LSP_WORKSPACE_MAX_FILES, else 5000).",
151
204
  })),
205
+ includeGenerated: Type.Optional(Type.Boolean({
206
+ description: "mode=full refreshRunners=cheap/all only (no effect with refreshRunners=cached/none, since no project scan runs to apply it to): scan WITHOUT the generated/artifact NAME-heuristic filter (lockfiles, gen.ts-style names, generated/ dirs, …). Default false. Use when a scan's 'excluded by generated-name heuristics' notice suggests a real file was skipped.",
207
+ })),
152
208
  severity: Type.Optional(Type.String({
153
209
  enum: ["error", "warning", "all"],
154
210
  description: "Filter by severity (default: all).",
@@ -176,6 +232,7 @@ nextWriteIndex) {
176
232
  })),
177
233
  }),
178
234
  async execute(_toolCallId, params, signal, onUpdate, ctx) {
235
+ const repaintLspStatus = captureLspStatusRepaint?.(ctx);
179
236
  const mode = params.mode ?? "delta";
180
237
  const severity = params.severity ?? "all";
181
238
  const refreshRunners = params.refreshRunners;
@@ -184,6 +241,7 @@ nextWriteIndex) {
184
241
  : undefined;
185
242
  const maxProjectFiles = parsePositiveInt(params.maxProjectFiles);
186
243
  const maxLspFiles = parsePositiveInt(params.maxLspFiles);
244
+ const includeGenerated = params.includeGenerated === true;
187
245
  const cwd = ctx.cwd ?? getCwd();
188
246
  let pathsScope;
189
247
  try {
@@ -228,9 +286,11 @@ nextWriteIndex) {
228
286
  refreshRunners,
229
287
  maxProjectFiles,
230
288
  maxLspFiles,
289
+ includeGenerated,
231
290
  signal: fullSignal,
232
291
  wallClockMs: FULL_SCAN_WALL_CLOCK_MS,
233
292
  onProgress,
293
+ onServerReady: repaintLspStatus,
234
294
  pathsScope,
235
295
  nextWriteIndex,
236
296
  });
@@ -272,23 +332,46 @@ function appendProjectDiagnosticsDeltaLines(lines, cwd, report, severity, includ
272
332
  * delta mode re-serves them. Anchored via `projectDiagnosticToWidget` so the
273
333
  * (tool, rule) an agent's mark binds to matches mode=full's own project-runner
274
334
  * filter (`applyInlineSuppressionsToSummaries`), keeping the two paths in
275
- * agreement. Weak-anchored → no file read.
335
+ * agreement. Weak-anchored → no file read. Also applies the project's
336
+ * `.pi-lens.json` `rules.<id>.disable`/`select` policy so a project's policy
337
+ * overlays the same delta report cache; the cache is otherwise insensitive to
338
+ * a project-config edit (the per-edit path picks it up immediately, but a
339
+ * non-dispatch tool query would replay the pre-edit state).
276
340
  */
277
- function filterDeltaReportDispositions(report, cwd) {
341
+ function filterDeltaReportDispositions(report, cwd, policyMap) {
278
342
  if (!report?.diagnostics.length)
279
343
  return report;
280
344
  const kept = report.diagnostics.filter((d) => applyWeakDispositions([projectDiagnosticToWidget(d)], cwd, d.filePath)
281
345
  .length === 1);
282
- return kept.length === report.diagnostics.length
283
- ? report
284
- : { ...report, diagnostics: kept };
346
+ const policyKept = applyRulePolicy(kept, policyMap);
347
+ if (policyKept.length === report.diagnostics.length)
348
+ return report;
349
+ return { ...report, diagnostics: policyKept };
350
+ }
351
+ /**
352
+ * Load the rule-policy map from a project's `.pi-lens.json` — same source the
353
+ * per-edit dispatch path uses, so a project's policy applies consistently
354
+ * across every output surface. `loadPiLensProjectConfig` is mtime-cached, and
355
+ * the map is filtered to entries that actually have a `disable`/`select` list
356
+ * (thresholds are handled elsewhere), so the common case returns undefined and
357
+ * the filter step is skipped outright.
358
+ */
359
+ function loadProjectRulePolicyMap(cwd) {
360
+ return rulePolicyMapFromConfig(loadPiLensProjectConfig(cwd).rules);
285
361
  }
286
362
  function formatDeltaMode(cacheManager, cwd, severity, pathsScope) {
287
363
  const actionableEntry = cacheManager.readCache("actionable-warnings", cwd);
288
364
  const qualityEntry = cacheManager.readCache("code-quality-warnings", cwd);
289
365
  const actionable = actionableEntry?.data;
290
366
  const quality = qualityEntry?.data;
291
- const projectDelta = filterDeltaReportDispositions(loadProjectDiagnosticsDeltaReport(cwd), cwd);
367
+ // Project rule policy (`.pi-lens.json` `rules.<id>.disable` /
368
+ // `rules.<id>.select`) — output-only filtering applied consistently across
369
+ // every mode. The cache-only delta/all paths would otherwise leak project-
370
+ // policy oversight (the per-edit dispatch already filters, but the cache
371
+ // snapshots predate the user's project config edit and would replay
372
+ // filtered findings). Weak-anchored like weak disposition — zero I/O.
373
+ const policyMap = loadProjectRulePolicyMap(cwd);
374
+ const projectDelta = filterDeltaReportDispositions(loadProjectDiagnosticsDeltaReport(cwd), cwd, policyMap);
292
375
  const ignoreFile = createCurrentIgnoreFilter(cwd);
293
376
  const includeFile = (filePath) => ignoreFile(filePath) && (!pathsScope || pathsScope.includeFile(filePath));
294
377
  // #755: delta re-serves the actionable/quality caches verbatim, but those
@@ -296,20 +379,15 @@ function formatDeltaMode(cacheManager, cwd, severity, pathsScope) {
296
379
  // the weak disposition filter (suppress/defer) here so a mark converges
297
380
  // immediately, not only on the next edit. Weak-anchored → zero file I/O, so
298
381
  // this stays "instant" (see applyWeakDispositions).
299
- const actionableFiles = (actionable?.files ?? [])
382
+ const visibleWarningFiles = (files) => (files ?? [])
300
383
  .filter((file) => includeFile(file.filePath))
301
384
  .map((file) => ({
302
385
  filePath: file.filePath,
303
- warnings: applyWeakDispositions(file.warnings ?? [], cwd, file.filePath),
304
- }))
305
- .filter((file) => file.warnings.length > 0);
306
- const qualityFiles = (quality?.files ?? [])
307
- .filter((file) => includeFile(file.filePath))
308
- .map((file) => ({
309
- filePath: file.filePath,
310
- warnings: applyWeakDispositions(file.warnings ?? [], cwd, file.filePath),
386
+ warnings: applyRulePolicy(applyWeakDispositions(file.warnings ?? [], cwd, file.filePath), policyMap),
311
387
  }))
312
388
  .filter((file) => file.warnings.length > 0);
389
+ const actionableFiles = visibleWarningFiles(actionable?.files);
390
+ const qualityFiles = visibleWarningFiles(quality?.files);
313
391
  const lines = [];
314
392
  // Fixable warnings from actionable-warnings
315
393
  if (severity !== "error") {
@@ -344,7 +422,7 @@ function formatDeltaMode(cacheManager, cwd, severity, pathsScope) {
344
422
  .filter((f) => includeFile(f.filePath))
345
423
  .map((f) => ({
346
424
  ...f,
347
- diagnostics: applyWeakDispositions(f.diagnostics, cwd, f.filePath),
425
+ diagnostics: applyRulePolicy(applyWeakDispositions(f.diagnostics, cwd, f.filePath), policyMap),
348
426
  }))
349
427
  .filter((f) => f.diagnostics.length > 0);
350
428
  const carriedIssues = carried.reduce((n, f) => n + f.diagnostics.length, 0);
@@ -475,6 +553,20 @@ function filterProjectDiagnosticsDeltaReport(report, includeFile) {
475
553
  diagnostics: report.diagnostics.filter((d) => includeFile(d.filePath)),
476
554
  };
477
555
  }
556
+ /**
557
+ * Apply the project rule policy to a project-diagnostics snapshot/delta
558
+ * report's `diagnostics` list, in place of the collection's shape. Used by
559
+ * `formatFullMode` so `projectSnapshot.diagnostics.length` /
560
+ * `projectDelta.diagnostics.length` — which feed both the merged summaries
561
+ * and the `details.projectDiagnostics`/`details.projectDiagnosticsDelta`
562
+ * counts — already reflect the policy instead of reporting a pre-policy
563
+ * count that disagrees with the rendered text.
564
+ */
565
+ function applyProjectRulePolicy(value, policyMap) {
566
+ if (!value)
567
+ return undefined;
568
+ return { ...value, diagnostics: applyRulePolicy(value.diagnostics, policyMap) };
569
+ }
478
570
  /** A diagnostic counts as error-like when it blocks or has error severity. */
479
571
  function isErrorLike(d) {
480
572
  return d.semantic === "blocking" || d.severity === "error";
@@ -544,12 +636,12 @@ function projectDiagnosticToWidget(diagnostic) {
544
636
  // violation must collapse to one finding in mode=full's merge regardless of which
545
637
  // engine produced it. Strip the `ast-grep:` source prefix and the `-js` language
546
638
  // suffix (the napi runner already treats `<id>` / `<id>-js` as one rule) so the
547
- // LSP sweep and the napi scan don't double-report the same line.
548
- function normalizeRuleForDedup(ruleId) {
549
- return ruleId.replace(/^ast-grep:/, "").replace(/-js$/, "");
550
- }
639
+ // LSP sweep and the napi scan don't double-report the same line. The
640
+ // normalization itself now lives in `clients/dispatch/rule-id-normalize.ts` so the
641
+ // inline suppression parser, the project rule-policy matcher, and dedup all apply
642
+ // the same canonical form.
551
643
  function diagnosticDedupKey(filePath, diagnostic) {
552
- const ruleId = normalizeRuleForDedup(diagnostic.rule ?? diagnostic.tool ?? "");
644
+ const ruleId = normalizeRuleId(diagnostic.rule ?? diagnostic.tool ?? "");
553
645
  return [path.resolve(filePath), diagnostic.line ?? "?", ruleId].join(":");
554
646
  }
555
647
  function summarizeDiagnostics(filePath, diagnostics, hasFinalSnapshot) {
@@ -692,8 +784,14 @@ function mergeDiagnosticsWithWidgetSummaries(widgetSummaries, lspResults, projec
692
784
  * a read failure is fail-safe (keep the diagnostics rather than hide a finding on
693
785
  * an I/O error). Re-summarizes so the blocking/error/warning counts reflect the
694
786
  * suppression.
787
+ *
788
+ * Also applies the project's `.pi-lens.json` `rules.<id>.disable`/`select`
789
+ * policy AFTER inline suppression / disposition so the same set of findings
790
+ * the per-edit `dispatcher.ts` filters is what's rendered here. The policy
791
+ * map is passed in (computed once per `formatFullMode` call) rather than
792
+ * re-deriving per file — see `loadProjectRulePolicyMap(cwd)`.
695
793
  */
696
- async function applyInlineSuppressionsToSummaries(summaries, cwd) {
794
+ async function applyInlineSuppressionsToSummaries(summaries, cwd, policyMap) {
697
795
  return Promise.all(summaries.map(async (summary) => {
698
796
  if (!summary.diagnostics.length)
699
797
  return summary;
@@ -702,7 +800,13 @@ async function applyInlineSuppressionsToSummaries(summaries, cwd) {
702
800
  content = await fs.readFile(summary.filePath, "utf8");
703
801
  }
704
802
  catch {
705
- return summary; // never hide a finding on a read error
803
+ // Inline suppression/disposition remains fail-open when the file cannot
804
+ // be read, but project policy does not depend on content and must still
805
+ // be applied so a fresh full-mode result cannot bypass `disable`/`select`.
806
+ const policyKept = applyRulePolicy(summary.diagnostics, policyMap);
807
+ return policyKept.length === summary.diagnostics.length
808
+ ? summary
809
+ : summarizeDiagnostics(summary.filePath, policyKept, summary.hasFinalSnapshot);
706
810
  }
707
811
  const inlineKept = applyInlineSuppressions(summary.diagnostics, content);
708
812
  // #690: same false-positive/suppress/defer disposition filter the
@@ -710,11 +814,16 @@ async function applyInlineSuppressionsToSummaries(summaries, cwd) {
710
814
  // diagnostics from a fresh LSP sweep/project scan that never went
711
815
  // through that path, so without this a disposed finding reappears here.
712
816
  const kept = applyDispositions(inlineKept, cwd, summary.filePath, content);
817
+ // Project rule policy (`.pi-lens.json` `rules.<id>.disable`/`select`).
818
+ // Applied after inline suppression / disposition so the policy's
819
+ // output-only filtering affects the same surface the per-edit path
820
+ // produces (no double-counting, no leftover policy-rejected findings).
821
+ const policyKept = applyRulePolicy(kept, policyMap);
713
822
  // Tag `flagged` diagnostics for the render loop (formatAllMode). Content
714
823
  // is already in hand here (unlike mode=all/delta's cache-only path), so
715
824
  // this is the one place the tag can be computed without adding I/O to
716
825
  // the "instant" modes.
717
- for (const d of kept) {
826
+ for (const d of policyKept) {
718
827
  // flagged is weak-anchored (module doc, diagnostic-dispositions.ts) so
719
828
  // the tag survives incidental edits to the flagged line itself.
720
829
  const { weak } = anchorsForDiagnostic(cwd, summary.filePath, d, content);
@@ -722,9 +831,9 @@ async function applyInlineSuppressionsToSummaries(summaries, cwd) {
722
831
  d.flagged = true;
723
832
  }
724
833
  }
725
- if (kept.length === summary.diagnostics.length)
834
+ if (policyKept.length === summary.diagnostics.length)
726
835
  return summary;
727
- return summarizeDiagnostics(summary.filePath, kept, summary.hasFinalSnapshot);
836
+ return summarizeDiagnostics(summary.filePath, policyKept, summary.hasFinalSnapshot);
728
837
  }));
729
838
  }
730
839
  function shouldUseCachedProjectDiagnostics(value) {
@@ -772,6 +881,7 @@ async function getProjectDiagnosticsSnapshotForFullMode(cwd, options) {
772
881
  maxFiles: options.maxProjectFiles,
773
882
  signal: options.signal,
774
883
  files: options.files,
884
+ includeGenerated: options.includeGenerated,
775
885
  });
776
886
  }
777
887
  if (shouldUseCachedProjectDiagnostics(options.refreshRunners)) {
@@ -830,6 +940,7 @@ async function formatFullMode(cwd, severity, lspService, cacheManager, options =
830
940
  diagnostics: [],
831
941
  runners: [],
832
942
  cold: [],
943
+ failed: [],
833
944
  timings: {},
834
945
  });
835
946
  const [rawLspResults, rawProjectSnapshot, extracted] = await Promise.all([
@@ -837,6 +948,8 @@ async function formatFullMode(cwd, severity, lspService, cacheManager, options =
837
948
  maxFiles: options.maxLspFiles,
838
949
  signal,
839
950
  onProgress: options.onProgress,
951
+ onServerReady: options.onServerReady,
952
+ nextWriteIndex: options.nextWriteIndex,
840
953
  files: explicitFiles,
841
954
  }),
842
955
  getProjectDiagnosticsSnapshotForFullMode(cwd, {
@@ -847,6 +960,12 @@ async function formatFullMode(cwd, severity, lspService, cacheManager, options =
847
960
  ]);
848
961
  const aborted = signal?.aborted ?? false;
849
962
  const lspResults = rawLspResults.filter((result) => includeFile(result.filePath));
963
+ // A result bound to a different document must not replace the current
964
+ // widget state, even when it contains real diagnostics. Pull results may carry
965
+ // a content hash without the push binding verdict, so verify that hash against
966
+ // disk through the bounded async fallback above; unreadable files remain
967
+ // unknown and retain the existing fail-open policy.
968
+ const mismatchedLspResults = await findFullScanBindingMismatches(lspResults);
850
969
  // #630: `timedOut`/`error` means the per-file check never actually
851
970
  // completed or was inconclusive (#570's `touchFile().inconclusive`, or
852
971
  // this sweep's own outer per-file deadline/throw — see
@@ -858,8 +977,12 @@ async function formatFullMode(cwd, severity, lspService, cacheManager, options =
858
977
  // the unfiltered `lspResults` in, so a timed-out file's placeholder `[]`
859
978
  // read as "0 diagnostics" — false-clean — in the rendered/`details`
860
979
  // output (#630).
861
- const confirmedLspResults = lspResults.filter((result) => !result.timedOut && !result.error);
862
- const unconfirmedLspResults = lspResults.filter((result) => result.timedOut || result.error);
980
+ const confirmedLspResults = lspResults.filter((result) => !result.timedOut &&
981
+ !result.error &&
982
+ !mismatchedLspResults.has(result));
983
+ const unconfirmedLspResults = lspResults.filter((result) => result.timedOut ||
984
+ result.error ||
985
+ mismatchedLspResults.has(result));
863
986
  // #571: reconcile this scan's fresh, CONFIRMED per-file results into the
864
987
  // footer cache. A footer write is never allowed to fail the tool call, so
865
988
  // any unexpected throw is swallowed.
@@ -880,12 +1003,33 @@ async function formatFullMode(cwd, severity, lspService, cacheManager, options =
880
1003
  // (`applyAuxiliarySuppressions`), so an empty content string here is a
881
1004
  // safe no-op re-check rather than a behavior gap.
882
1005
  const retagged = retagAuxiliaryDiagnostics(diagnostics, result.diagnostics, "", { cwd, fileRole: detectFileRole(result.filePath) });
883
- reconcileScanDiagnostics(result.filePath, retagged, true, nextWriteIndex?.());
1006
+ reconcileScanDiagnostics(result.filePath, retagged, true,
1007
+ // The service reserves this token when the file enters the scan. The
1008
+ // fallback preserves compatibility with older test doubles/services.
1009
+ result.writeIndex ?? nextWriteIndex?.(),
1010
+ // #1093: `observedAt` is set only when this result was served from
1011
+ // the workspace-diagnostics cache (a replay of an older scan) —
1012
+ // stamp `touchedAt` with when it was scanned, not now(), so a
1013
+ // mode=full that only re-serves the cache can't keep a resolved
1014
+ // finding on screen by re-arming the mtime gate. Undefined for
1015
+ // freshly-touched results (observed now).
1016
+ result.observedAt);
884
1017
  }
885
1018
  catch {
886
1019
  // Never let a footer-reconciliation hiccup fail the scan itself.
887
1020
  }
888
1021
  }
1022
+ // Project rule policy (`.pi-lens.json` `rules.<id>.disable`/`select`) —
1023
+ // loaded once per mode=full call, BEFORE `projectSnapshot`/`projectDelta`
1024
+ // are built below, so `.diagnostics.length` on both (which feeds
1025
+ // `details.projectDiagnostics`/`details.projectDiagnosticsDelta` as well
1026
+ // as the merge into `summaries`) is the POST-policy count. Threaded into
1027
+ // `applyInlineSuppressionsToSummaries` too (idempotent re-apply there) so
1028
+ // the merged summaries honor the same policy the per-edit dispatch path
1029
+ // applies. A project config read failure is treated as "no policy"
1030
+ // (defensive, matches the `applyInlineSuppressionsToSummaries` read-error
1031
+ // discipline).
1032
+ const policyMap = loadProjectRulePolicyMap(cwd);
889
1033
  const scannedSnapshot = filterProjectDiagnosticsSnapshot(rawProjectSnapshot, includeFile);
890
1034
  // Heavyweight-analyzer findings (jscpd, madge, gitleaks, knip, govulncheck,
891
1035
  // trivy, dead-code) — a FRESH run of each (#585), not a cache-only read:
@@ -900,14 +1044,14 @@ async function formatFullMode(cwd, severity, lspService, cacheManager, options =
900
1044
  // computed above, in the SAME `Promise.all` as the LSP sweep (#613) — only
901
1045
  // when the caller opted into project-runner state (otherwise it's the
902
1046
  // `Promise.resolve({...})` stub from `analyzersPromise` above).
903
- const projectSnapshot = foldExtraDiagnosticsIntoSnapshot(scannedSnapshot, extracted.diagnostics.filter((d) => includeFile(d.filePath)), extracted.runners, cwd);
904
- const projectDelta = filterProjectDiagnosticsDeltaReport(loadProjectDiagnosticsDeltaReport(cwd), includeFile);
1047
+ const projectSnapshot = applyProjectRulePolicy(foldExtraDiagnosticsIntoSnapshot(scannedSnapshot, extracted.diagnostics.filter((d) => includeFile(d.filePath)), extracted.runners, cwd), policyMap);
1048
+ const projectDelta = applyProjectRulePolicy(filterProjectDiagnosticsDeltaReport(loadProjectDiagnosticsDeltaReport(cwd), includeFile), policyMap);
905
1049
  // #630: only the CONFIRMED LSP results contribute diagnostics to the merge
906
1050
  // — an unconfirmed (timed-out/errored) file's placeholder `[]` must not be
907
1051
  // read as "0 issues, clean" via its LSP contribution. It can still
908
1052
  // legitimately show diagnostics from widgetSummaries/project-runner state
909
1053
  // below if those independently have entries for it.
910
- const summaries = await applyInlineSuppressionsToSummaries(mergeDiagnosticsWithWidgetSummaries(getFileDiagnosticSummaries().filter((summary) => includeFile(summary.filePath)), confirmedLspResults, projectSnapshot, projectDelta), cwd);
1054
+ const summaries = await applyInlineSuppressionsToSummaries(mergeDiagnosticsWithWidgetSummaries(getFileDiagnosticSummaries().filter((summary) => includeFile(summary.filePath)), confirmedLspResults, projectSnapshot, projectDelta), cwd, policyMap);
911
1055
  const result = formatAllMode(cwd, severity, summaries, {
912
1056
  mode: "full",
913
1057
  lspFilesChecked: rawLspResults.length,
@@ -1001,6 +1145,30 @@ async function formatFullMode(cwd, severity, lspService, cacheManager, options =
1001
1145
  .map((id) => `${id} — ${warmTriggerFor(id)}`)
1002
1146
  .join(", ")}. These analyzers have not contributed to this result — absence of their findings is NOT a clean verdict.`
1003
1147
  : "";
1148
+ // #1004: unlike every other analyzer above (knip/jscpd/madge/gitleaks/
1149
+ // govulncheck/opengrep/trivy/dead-code all run a FRESH whole-project scan
1150
+ // per the fresh-fetch.ts header, so "clean" there really does mean "the
1151
+ // whole project was checked this call"), test-runner has no whole-project
1152
+ // run to trigger: its cache only ever reflects the (targeted,
1153
+ // cascade-aware) test file(s) touched by the MOST RECENT edit's turn_end
1154
+ // fire (`runtime-turn.ts`). Folding it into `runners`/`diagnostics` with no
1155
+ // distinguishing note — as opposed to when it's cold, which already gets
1156
+ // `coldNote` above — would let a caller mistake "test-runner ran
1157
+ // project-wide and is clean" for "it only ever saw the 2 files edited an
1158
+ // hour ago", exactly the false-empty/false-confidence #533 this whole
1159
+ // module exists to prevent. Fires whenever test-runner CONTRIBUTED
1160
+ // (`runners`), independent of whether it also has cold/failed entries this
1161
+ // call — a caller needs this caveat on every mode=full response where
1162
+ // test-runner findings are present, not only some.
1163
+ const testRunnerEditScopedNote = extracted.runners.includes("test-runner")
1164
+ ? `\n\ntest-runner coverage is edit-scoped, NOT a full-project run: its findings only reflect the test file(s) touched by the most recent edit's turn_end fire, not a fresh whole-project test suite run (mode=full never re-runs the suite — see fresh-fetch.ts). Absence of test-runner findings elsewhere in the project is NOT a clean verdict, and presence here does not mean "just checked" — it may be from several turns ago.`
1165
+ : "";
1166
+ const failedAnalyzers = extracted.failed ?? [];
1167
+ const failedNote = failedAnalyzers.length > 0
1168
+ ? `\n\nfailed (ran, but no trustworthy result): ${failedAnalyzers
1169
+ .map(({ id, summary }) => `${id} — ${summary}`)
1170
+ .join(", ")}. These analyzers were not cached and will be retried; absence of their findings is NOT a clean verdict.`
1171
+ : "";
1004
1172
  // #747/#250: the cheap project-diagnostics scan (scanProjectDiagnostics) and
1005
1173
  // the LSP workspace sweep (collectWorkspaceDiagnosticFiles) both refuse to
1006
1174
  // WALK from a cwd at/above $HOME — from there, walking would enumerate every
@@ -1022,6 +1190,14 @@ async function formatFullMode(cwd, severity, lspService, cacheManager, options =
1022
1190
  const scanTruncatedNote = scanTruncatedNoticeText
1023
1191
  ? `\n\n${scanTruncatedNoticeText}`
1024
1192
  : "";
1193
+ // #1107 phase 2: same "reached the seam, nothing rendered it" gap as #784
1194
+ // above, for the generated-name/dir skip counters.
1195
+ const generatedSkipNoticeText = projectSnapshot
1196
+ ? generatedSkipNotice(projectSnapshot)
1197
+ : undefined;
1198
+ const generatedSkipNote = generatedSkipNoticeText
1199
+ ? `\n\n${generatedSkipNoticeText}`
1200
+ : "";
1025
1201
  const abortedNote = abortedIds.size > 0
1026
1202
  ? `\n\nstopped mid-scan (still running in the background, not reflected in this result): ${[
1027
1203
  ...abortedIds,
@@ -1047,6 +1223,7 @@ async function formatFullMode(cwd, severity, lspService, cacheManager, options =
1047
1223
  details: {
1048
1224
  ...result.details,
1049
1225
  coldRunners: extracted.cold,
1226
+ failedAnalyzers,
1050
1227
  analyzerTimingsMs: extracted.timings,
1051
1228
  analyzersAborted: extracted.aborted ?? false,
1052
1229
  analyzersAbortedIds: extracted.abortedIds ?? [],
@@ -1074,6 +1251,19 @@ async function formatFullMode(cwd, severity, lspService, cacheManager, options =
1074
1251
  // #784: lets a caller check "was the cheap project scan truncated"
1075
1252
  // without parsing the text note.
1076
1253
  projectScanTruncated: projectSnapshot?.scanTruncated ?? false,
1254
+ // #1107 phase 2: same machine-readable convention, for the
1255
+ // generated-name/dir skip counters.
1256
+ ...(projectSnapshot?.generatedFileSkips
1257
+ ? { projectGeneratedFileSkips: projectSnapshot.generatedFileSkips }
1258
+ : {}),
1259
+ ...(projectSnapshot?.generatedNameOnlySkips
1260
+ ? {
1261
+ projectGeneratedNameOnlySkips: projectSnapshot.generatedNameOnlySkips,
1262
+ }
1263
+ : {}),
1264
+ ...(projectSnapshot?.generatedDirSkips
1265
+ ? { projectGeneratedDirSkips: projectSnapshot.generatedDirSkips }
1266
+ : {}),
1077
1267
  },
1078
1268
  };
1079
1269
  // Stopped mid-scan: the results above are whatever completed before the abort.
@@ -1098,8 +1288,11 @@ async function formatFullMode(cwd, severity, lspService, cacheManager, options =
1098
1288
  unconfirmedLspNote +
1099
1289
  lspPrimaryVsAuxiliaryNote +
1100
1290
  coldNote +
1291
+ testRunnerEditScopedNote +
1292
+ failedNote +
1101
1293
  walkUnsafeRootNote +
1102
1294
  scanTruncatedNote +
1295
+ generatedSkipNote +
1103
1296
  abortedNote +
1104
1297
  freshNote +
1105
1298
  missingNote,
@@ -1110,8 +1303,11 @@ async function formatFullMode(cwd, severity, lspService, cacheManager, options =
1110
1303
  }
1111
1304
  if (missingNote ||
1112
1305
  coldNote ||
1306
+ testRunnerEditScopedNote ||
1307
+ failedNote ||
1113
1308
  walkUnsafeRootNote ||
1114
1309
  scanTruncatedNote ||
1310
+ generatedSkipNote ||
1115
1311
  abortedNote ||
1116
1312
  freshNote ||
1117
1313
  unconfirmedLspNote ||
@@ -1124,8 +1320,11 @@ async function formatFullMode(cwd, severity, lspService, cacheManager, options =
1124
1320
  unconfirmedLspNote +
1125
1321
  lspPrimaryVsAuxiliaryNote +
1126
1322
  coldNote +
1323
+ testRunnerEditScopedNote +
1324
+ failedNote +
1127
1325
  walkUnsafeRootNote +
1128
1326
  scanTruncatedNote +
1327
+ generatedSkipNote +
1129
1328
  abortedNote +
1130
1329
  freshNote +
1131
1330
  missingNote,
@@ -1155,13 +1354,20 @@ function formatAllMode(cwd, severity, summaries = getFileDiagnosticSummaries(),
1155
1354
  // post-dispatch lens_diagnostic_mark (suppress/defer) would otherwise still
1156
1355
  // show here. Re-apply the weak filter (zero I/O) for the cache-only path and
1157
1356
  // re-summarize so blocking/error/warning counts reflect the drop.
1357
+ // Same project rule policy (`.pi-lens.json` `rules.<id>.disable`/`select`)
1358
+ // overlays both modes: mode=full re-applies it after inline suppression /
1359
+ // disposition (zero I/O), mode=all applies it here on the cache-only path.
1360
+ // The full path's policyMover is loaded below in `applyInlineSuppressionsToSummaries`.
1361
+ const policyMap = loadProjectRulePolicyMap(cwd);
1158
1362
  const dispositioned = isFullMode
1159
1363
  ? summaries
1160
1364
  : summaries.map((s) => {
1161
1365
  const kept = applyWeakDispositions(s.diagnostics ?? [], cwd, s.filePath);
1162
- return kept.length === (s.diagnostics?.length ?? 0)
1163
- ? s
1164
- : summarizeDiagnostics(s.filePath, kept, s.hasFinalSnapshot);
1366
+ const policyKept = applyRulePolicy(kept, policyMap);
1367
+ if (policyKept.length === kept.length &&
1368
+ kept.length === (s.diagnostics?.length ?? 0))
1369
+ return s;
1370
+ return summarizeDiagnostics(s.filePath, policyKept, s.hasFinalSnapshot);
1165
1371
  });
1166
1372
  const visibleSummaries = dispositioned.filter((s) => includeFile(s.filePath));
1167
1373
  // Filter to files with actual issues