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
@@ -11,8 +11,10 @@
11
11
  */
12
12
  import * as fs from "node:fs";
13
13
  import { createFileTime } from "./file-time.js";
14
+ import { hashDiagnosticContent } from "./lsp/diagnostic-binding.js";
14
15
  import { normalizeFilePath } from "./path-utils.js";
15
16
  import { logReadGuardEvent } from "./read-guard-logger.js";
17
+ export const READ_GUARD_STATE_VERSION = 1;
16
18
  // --- Constants ---
17
19
  const DEFAULT_CONFIG = {
18
20
  enabled: true,
@@ -27,6 +29,66 @@ const DEFAULT_CONFIG = {
27
29
  const OWN_EDIT_STALE_GRACE_MS = Math.max(0, Number.parseInt(process.env.PI_LENS_READ_GUARD_OWN_EDIT_GRACE_MS ?? "120000", 10) || 120000);
28
30
  /** Avoid hashing very large reads in the hot path. */
29
31
  const READ_HASH_MAX_LINES = Math.max(0, Number.parseInt(process.env.PI_LENS_READ_GUARD_HASH_MAX_LINES ?? "3000", 10) || 3000);
32
+ /**
33
+ * Content bindings are defense in depth on a read-adjacent hot path. Cap the
34
+ * synchronous disk read so bridge registration never hashes an unbounded file.
35
+ */
36
+ const READ_BINDING_MAX_BYTES = 4 * 1024 * 1024;
37
+ const READ_GUARD_MAX_FILES = 256;
38
+ // Unconsumed reads remain valid until edit or session end, but this high
39
+ // sanity cap prevents a read-only session from growing without bound.
40
+ const READ_GUARD_MAX_UNCONSUMED_FILES = 4096;
41
+ const READ_GUARD_IDLE_EVICT_MS_DEFAULT = 30 * 60_000;
42
+ export function captureReadContentBinding(filePath, offset, limit) {
43
+ try {
44
+ if (fs.statSync(filePath).size > READ_BINDING_MAX_BYTES)
45
+ return undefined;
46
+ const content = fs.readFileSync(filePath, "utf-8");
47
+ // Allocate at most the classification prefix first. For range bindings,
48
+ // splitting then stops at the requested range end rather than the EOF.
49
+ const lines = splitLinesThrough(content, READ_HASH_MAX_LINES + 1);
50
+ if (lines.length <= READ_HASH_MAX_LINES) {
51
+ return {
52
+ hash: hashDiagnosticContent(content),
53
+ fullFile: true,
54
+ offset: 1,
55
+ limit: lines.length,
56
+ };
57
+ }
58
+ const scopedOffset = Math.max(1, offset);
59
+ const scopedLimit = Math.min(Math.max(0, limit), READ_HASH_MAX_LINES);
60
+ if (scopedLimit === 0)
61
+ return undefined;
62
+ const rangeLines = splitLinesThrough(content, scopedOffset - 1 + scopedLimit);
63
+ const scopedContent = rangeLines
64
+ .slice(scopedOffset - 1, scopedOffset - 1 + scopedLimit)
65
+ .join("\n");
66
+ return {
67
+ hash: hashDiagnosticContent(scopedContent),
68
+ fullFile: false,
69
+ offset: scopedOffset,
70
+ limit: scopedLimit,
71
+ };
72
+ }
73
+ catch {
74
+ return undefined;
75
+ }
76
+ }
77
+ export function _currentContentMatchesBindingForTests(filePath, binding) {
78
+ try {
79
+ const content = fs.readFileSync(filePath, "utf-8");
80
+ const boundContent = binding.fullFile
81
+ ? content
82
+ : splitLines(content)
83
+ .slice(binding.offset - 1, binding.offset - 1 + binding.limit)
84
+ .join("\n");
85
+ return hashDiagnosticContent(boundContent) === binding.hash;
86
+ }
87
+ catch {
88
+ return false;
89
+ }
90
+ }
91
+ const currentContentMatchesBinding = _currentContentMatchesBindingForTests;
30
92
  // Adaptive relocation window (findRelocation). A globally-unique hash-sequence
31
93
  // match always wins; when the content is duplicated elsewhere, we fall back to
32
94
  // a match that is unique WITHIN this window of the original position. The window
@@ -39,6 +101,28 @@ const RELOCATION_WINDOW_MAX = Math.max(RELOCATION_WINDOW_MIN, Number.parseInt(pr
39
101
  function splitLines(text) {
40
102
  return text.split(/\r?\n/);
41
103
  }
104
+ /** `splitLines` semantics, but without scanning/allocating beyond `maxLines`. */
105
+ function splitLinesThrough(text, maxLines) {
106
+ if (maxLines <= 0)
107
+ return [];
108
+ const lines = [];
109
+ let start = 0;
110
+ while (lines.length < maxLines) {
111
+ const newline = text.indexOf("\n", start);
112
+ if (newline === -1) {
113
+ lines.push(text.slice(start));
114
+ break;
115
+ }
116
+ const end = newline > start && text[newline - 1] === "\r" ? newline - 1 : newline;
117
+ lines.push(text.slice(start, end));
118
+ start = newline + 1;
119
+ if (start === text.length && lines.length < maxLines) {
120
+ lines.push("");
121
+ break;
122
+ }
123
+ }
124
+ return lines;
125
+ }
42
126
  export function lineContentHash(line) {
43
127
  // FNV-1a over whitespace-stripped content. This treats no-op formatter/touch
44
128
  // changes as still-valid context while detecting semantic line changes.
@@ -117,6 +201,10 @@ export class ReadGuard {
117
201
  config;
118
202
  reads = new Map();
119
203
  edits = new Map();
204
+ fileLastUsed = new Map();
205
+ fileIdleTimers = new Map();
206
+ /** Reads remain behavior-gating until the corresponding edit is published. */
207
+ consumedReadFiles = new Set();
120
208
  fileTime;
121
209
  exemptions = new Set(); // One-time exemptions via /lens-allow-edit
122
210
  pendingCreations = new Map();
@@ -146,6 +234,66 @@ export class ReadGuard {
146
234
  key(filePath) {
147
235
  return normalizeFilePath(filePath);
148
236
  }
237
+ idleEvictMs() {
238
+ const value = Number.parseInt(process.env.PI_LENS_READ_GUARD_IDLE_EVICT_MS ?? "", 10);
239
+ return Number.isSafeInteger(value) && value > 0 ? value : READ_GUARD_IDLE_EVICT_MS_DEFAULT;
240
+ }
241
+ clearFileTimer(filePath) {
242
+ const timer = this.fileIdleTimers.get(filePath);
243
+ if (timer)
244
+ clearTimeout(timer);
245
+ this.fileIdleTimers.delete(filePath);
246
+ }
247
+ evictFile(filePath) {
248
+ this.clearFileTimer(filePath);
249
+ this.reads.delete(filePath);
250
+ this.edits.delete(filePath);
251
+ this.fileLastUsed.delete(filePath);
252
+ this.consumedReadFiles.delete(filePath);
253
+ this.writtenThisSession.delete(filePath);
254
+ }
255
+ touchFile(filePath) {
256
+ const now = Date.now();
257
+ this.fileLastUsed.set(filePath, now);
258
+ this.clearFileTimer(filePath);
259
+ // An outstanding read is enforcement state, not a rebuildable cache entry.
260
+ // It must survive idle time and file-cap pressure until the edit consumes it.
261
+ if (this.reads.has(filePath) && !this.consumedReadFiles.has(filePath))
262
+ return;
263
+ const stamp = now;
264
+ const timer = setTimeout(() => {
265
+ if (this.fileLastUsed.get(filePath) !== stamp)
266
+ return;
267
+ // Never turn an outstanding read into a zero-read block through idle
268
+ // eviction. Only consumed reads and rebuildable edit history may expire.
269
+ if (this.reads.has(filePath) && !this.consumedReadFiles.has(filePath))
270
+ return;
271
+ this.evictFile(filePath);
272
+ }, this.idleEvictMs());
273
+ timer.unref?.();
274
+ this.fileIdleTimers.set(filePath, timer);
275
+ }
276
+ enforceFileCap() {
277
+ while (this.reads.size > READ_GUARD_MAX_FILES) {
278
+ const victim = [...this.reads.keys()]
279
+ .filter((filePath) => this.consumedReadFiles.has(filePath))
280
+ .sort((a, b) => (this.fileLastUsed.get(a) ?? 0) - (this.fileLastUsed.get(b) ?? 0))[0];
281
+ if (!victim)
282
+ break;
283
+ this.evictFile(victim);
284
+ }
285
+ while (this.reads.size > READ_GUARD_MAX_UNCONSUMED_FILES) {
286
+ const victim = [...this.reads.keys()]
287
+ .filter((filePath) => !this.consumedReadFiles.has(filePath))
288
+ .sort((a, b) => (this.fileLastUsed.get(a) ?? 0) -
289
+ (this.fileLastUsed.get(b) ?? 0))[0];
290
+ if (!victim)
291
+ break;
292
+ // This is a normal read miss: a later edit must require a fresh read,
293
+ // never silently allow and never become a permanent hard-block.
294
+ this.evictFile(victim);
295
+ }
296
+ }
149
297
  // --- Public API ---
150
298
  /**
151
299
  * Record that a file was read.
@@ -160,8 +308,11 @@ export class ReadGuard {
160
308
  captureLineHashes(filePath, record.effectiveOffset, record.effectiveLimit),
161
309
  };
162
310
  const arr = this.reads.get(storedRecord.filePath) ?? [];
311
+ this.consumedReadFiles.delete(storedRecord.filePath);
163
312
  arr.push(storedRecord);
164
313
  this.reads.set(storedRecord.filePath, arr);
314
+ this.touchFile(storedRecord.filePath);
315
+ this.enforceFileCap();
165
316
  logReadGuardEvent({
166
317
  event: "read_recorded",
167
318
  sessionId: this.sessionId,
@@ -180,6 +331,9 @@ export class ReadGuard {
180
331
  writeIndex: storedRecord.writeIndex,
181
332
  readCountForFile: arr.length,
182
333
  hashLineCount: Object.keys(storedRecord.lineHashes ?? {}).length,
334
+ ...(storedRecord.source !== undefined && {
335
+ source: storedRecord.source,
336
+ }),
183
337
  },
184
338
  });
185
339
  // Also update FileTime stamp for this file
@@ -227,6 +381,8 @@ export class ReadGuard {
227
381
  // Canonicalize once: every map lookup below (and every private helper this
228
382
  // passes filePath to) must agree with how recordRead keyed the read.
229
383
  filePath = this.key(filePath);
384
+ if (this.reads.has(filePath) || this.edits.has(filePath))
385
+ this.touchFile(filePath);
230
386
  // Check exemptions
231
387
  if (this.exemptions.has(filePath)) {
232
388
  this.exemptions.delete(filePath); // One-time use
@@ -270,6 +426,19 @@ export class ReadGuard {
270
426
  });
271
427
  return verdict;
272
428
  }
429
+ const lastBoundRead = [...fileReads]
430
+ .reverse()
431
+ .find((read) => read.contentBinding !== undefined);
432
+ if (lastBoundRead?.contentBinding &&
433
+ !currentContentMatchesBinding(filePath, lastBoundRead.contentBinding)) {
434
+ const verdict = this.blockOrWarn("file-modified", `🔄 RETRYABLE — File modified since read\n\nYou last read \`${filePath}\` at ${new Date(lastBoundRead.timestamp).toISOString()}.\nThe file content no longer matches the bridge-recorded read.\n\nYour mental model is out of sync with the actual file content.\nTo proceed:\n 1. Re-read the file: \`read path="${filePath}"\``, undefined, effectiveMode);
435
+ this.recordVerdict(filePath, "edit", touchedLines, verdict, {
436
+ reasonKind: "file_modified",
437
+ lastReadTimestamp: lastBoundRead.timestamp,
438
+ contentBindingMismatch: true,
439
+ });
440
+ return verdict;
441
+ }
273
442
  // 2. FileTime check (actual staleness)
274
443
  let ignoredOwnEditStaleness = false;
275
444
  let ignoredHashStaleness = false;
@@ -314,7 +483,9 @@ export class ReadGuard {
314
483
  // If oldText was resolved (content-verified), the model demonstrably
315
484
  // knew the content it's replacing — line drift from prior edits in
316
485
  // the session is the likely cause. Downgrade to warn rather than block.
317
- const outOfRangeMode = options?.oldTextResolved ? "warn" : effectiveMode;
486
+ const outOfRangeMode = options?.oldTextResolved
487
+ ? "warn"
488
+ : effectiveMode;
318
489
  const verdict = this.blockOrWarn("out-of-range", `🔄 RETRYABLE — Edit outside read range\n\nYou read \`${filePath}\` lines ${lastRead.effectiveOffset}-${lastReadEnd}${lastRead.enclosingSymbol ? ` (${lastRead.enclosingSymbol.kind} \`${lastRead.enclosingSymbol.name}\`)` : ""}, but your edit touches lines ${editStart}-${editEnd}.\n\nRead the relevant section first, then retry the edit:\n \`read path="${filePath}" offset=${Math.max(1, editStart - 5)} limit=${Math.min(30, editEnd - editStart + 10)}\``, {
319
490
  editRange: range,
320
491
  readRanges: fileReads.map((r) => ({
@@ -434,6 +605,10 @@ export class ReadGuard {
434
605
  filePath = this.key(filePath);
435
606
  this.fileTime.read(filePath);
436
607
  this.writtenThisSession.add(filePath);
608
+ if (this.reads.has(filePath))
609
+ this.consumedReadFiles.add(filePath);
610
+ this.touchFile(filePath);
611
+ this.enforceFileCap();
437
612
  const creation = this.pendingCreations.get(filePath);
438
613
  if (creation) {
439
614
  this.pendingCreations.delete(filePath);
@@ -495,13 +670,96 @@ export class ReadGuard {
495
670
  * Get all read records for a file (for debugging).
496
671
  */
497
672
  getReadHistory(filePath) {
498
- return this.reads.get(this.key(filePath)) ?? [];
673
+ const key = this.key(filePath);
674
+ if (this.reads.has(key))
675
+ this.touchFile(key);
676
+ return this.reads.get(key) ?? [];
677
+ }
678
+ /**
679
+ * Snapshot the read-set for persistence across a session resume (#1041).
680
+ * Mirrors widget-state's `exportWidgetState`: the Map is emitted as
681
+ * `[key, records]` tuples, keys already in `normalizeFilePath` form. Only
682
+ * `reads` is serialized — it is the payload `checkEdit`'s zero-read/coverage
683
+ * checks consult. Safe to call even when the guard is disabled (an empty or
684
+ * never-populated `reads` simply exports zero entries).
685
+ */
686
+ exportState() {
687
+ return {
688
+ version: READ_GUARD_STATE_VERSION,
689
+ reads: [...this.reads.entries()].map(([key, records]) => [
690
+ key,
691
+ records.map((record) => ({ ...record })),
692
+ ]),
693
+ };
694
+ }
695
+ /**
696
+ * Rehydrate a persisted read-set (#1041) into this (fresh, post-resume)
697
+ * guard, with mandatory staleness reconciliation: each read is re-verified
698
+ * against the CURRENT on-disk content via its recorded `lineHashes`, and any
699
+ * read whose file changed (or no longer exists, or that carries no verifiable
700
+ * hashes) is DROPPED. A rehydrated read must never mask a real staleness — a
701
+ * resume must not let the agent edit a file that changed on disk while it
702
+ * believed it held a fresh read. Kept reads are replayed through
703
+ * {@link recordRead}, which re-keys through {@link key} (idempotent — the
704
+ * exported keys are already normalized) and re-stamps FileTime so the next
705
+ * `checkEdit` sees a consistent baseline. Version-guarded and null-safe:
706
+ * `undefined` / a mismatched version / a missing field loads as "no prior
707
+ * reads". Returns a count of imported vs dropped reads for logging.
708
+ */
709
+ importState(state) {
710
+ const result = { imported: 0, dropped: 0 };
711
+ if (!state || state.version !== READ_GUARD_STATE_VERSION)
712
+ return result;
713
+ // A corrupt/hand-edited sidecar must degrade to "no prior reads", never
714
+ // throw: loadSessionState validates only version/widget, so a malformed
715
+ // `reads` reaches here. If importState threw, the session_start try/catch
716
+ // would abort the ENTIRE rehydration (incl. widget + mountLensWidget)
717
+ // rather than just skipping the read-set.
718
+ if (!Array.isArray(state.reads))
719
+ return result;
720
+ for (const entry of state.reads) {
721
+ // Skip anything that isn't a well-formed [key, records] tuple.
722
+ if (!Array.isArray(entry) || entry.length !== 2)
723
+ continue;
724
+ const [rawPath, records] = entry;
725
+ if (typeof rawPath !== "string")
726
+ continue;
727
+ if (!Array.isArray(records) || records.length === 0)
728
+ continue;
729
+ const filePath = this.key(rawPath);
730
+ let lines;
731
+ try {
732
+ lines = splitLines(fs.readFileSync(filePath, "utf-8"));
733
+ }
734
+ catch {
735
+ // File gone since it was read → drop every read for it.
736
+ result.dropped += records.length;
737
+ continue;
738
+ }
739
+ for (const record of records) {
740
+ const rehydrated = { ...record, filePath };
741
+ // readHashesStillMatch returns false when the recorded hashes no
742
+ // longer match disk OR when the read captured no hashes — both
743
+ // unverifiable, so both drop (safety over convenience).
744
+ if (this.readHashesStillMatch(rehydrated, lines)) {
745
+ this.recordRead(rehydrated);
746
+ result.imported += 1;
747
+ }
748
+ else {
749
+ result.dropped += 1;
750
+ }
751
+ }
752
+ }
753
+ return result;
499
754
  }
500
755
  /**
501
756
  * Get all edit records for a file (for debugging).
502
757
  */
503
758
  getEditHistory(filePath) {
504
- return this.edits.get(this.key(filePath)) ?? [];
759
+ const key = this.key(filePath);
760
+ if (this.edits.has(key))
761
+ this.touchFile(key);
762
+ return this.edits.get(key) ?? [];
505
763
  }
506
764
  // --- Private helpers ---
507
765
  injectCreationRead(filePath, turnIndex, writeIndex) {
@@ -848,6 +1106,7 @@ export class ReadGuard {
848
1106
  return { action: "allow" };
849
1107
  }
850
1108
  recordEdit(filePath, tool, touchedLines, verdict) {
1109
+ this.touchFile(filePath);
851
1110
  const arr = this.edits.get(filePath) ?? [];
852
1111
  arr.push({
853
1112
  filePath,