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
@@ -0,0 +1,498 @@
1
+ /**
2
+ * Prompt-cache observability (#1018).
3
+ *
4
+ * Two provider-independent signals, both sinking into ~/.pi-lens/latency.log
5
+ * via {@link logLatency} (matching the neighboring `type: "phase"` records):
6
+ *
7
+ * 1. Response-side cache usage (`cache_usage`) — on each assistant
8
+ * `message_end`, record the provider-reported token/cost breakdown so a
9
+ * session's actual cacheRead/cacheWrite behavior is queryable after the
10
+ * fact. This is what the provider DID, not what we hoped it would do.
11
+ *
12
+ * 2. Request-side context observations (`cache_context`) — one bounded record
13
+ * for every `context` call, describing pi-lens's own before/after message
14
+ * transformation, placement, injection sources, and privacy-preserving
15
+ * hashes. This is what pi-lens observed locally, not the final provider
16
+ * request after other context handlers have run and not a provider cache
17
+ * result.
18
+ *
19
+ * 3. Request-side prefix stability (`cache_prefix_break`) — a content hash of
20
+ * `messages[0]` observed on every `context` call. After #1016 the first
21
+ * message must stay byte-stable across a whole session; a logged CHANGE
22
+ * flags that something (pi-lens or otherwise) broke the cache prefix. This
23
+ * remains a local observation, never a provider cache-miss claim.
24
+ *
25
+ * All paths are defensive: `usage` (and its fields) may be absent on older
26
+ * hosts or non-assistant messages, so every access is guarded and the handlers
27
+ * never throw — on error they `dbg(...)` and no-op, like the other index.ts
28
+ * event handlers.
29
+ */
30
+ import { createHash } from "node:crypto";
31
+ import { logLatency } from "./latency-logger.js";
32
+ /** RuntimeCoordinator's counter is process-global when sessions share a host. */
33
+ const PROCESS_TURN_SCOPE = "process-global-runtime";
34
+ const SECONDARY_TURN_SCOPE = "unavailable-concurrent-secondary";
35
+ /**
36
+ * Keep context telemetry bounded even when a tool result or user message is
37
+ * unexpectedly large. This is a hash input only; none of the sampled text is
38
+ * written to the log.
39
+ */
40
+ const MAX_HASHED_MESSAGES = 64;
41
+ const MAX_HASHED_CONTENT_CHARS = 2048;
42
+ const MAX_REPORTED_MESSAGES = 10_000;
43
+ const MAX_INJECTED_CHARS = 16_384;
44
+ const MAX_INJECTED_BYTES = 65_536;
45
+ let contextObservationCounter = 0;
46
+ function boundedHashScalar(value, state) {
47
+ if (value === null)
48
+ return "null";
49
+ if (value === undefined)
50
+ return "undefined";
51
+ if (typeof value === "string") {
52
+ const truncated = value.length > MAX_HASHED_CONTENT_CHARS;
53
+ if (truncated)
54
+ state.contentTruncated = true;
55
+ const suffix = truncated ? ":content-truncated" : "";
56
+ return `string:${value.length}:${value.slice(0, MAX_HASHED_CONTENT_CHARS)}${suffix}`;
57
+ }
58
+ if (typeof value === "number" ||
59
+ typeof value === "boolean" ||
60
+ typeof value === "bigint") {
61
+ return `${typeof value}:${String(value)}`;
62
+ }
63
+ return typeof value;
64
+ }
65
+ function boundedHashArray(value, depth, seen, state) {
66
+ if (value.length > 12)
67
+ state.contentTruncated = true;
68
+ const items = value
69
+ .slice(0, 12)
70
+ .map((item) => boundedHashValue(item, depth + 1, seen, state));
71
+ return `array:${value.length}:[${items.join(",")}]`;
72
+ }
73
+ function boundedHashObject(value, depth, seen, state) {
74
+ const keys = Object.keys(value).sort((left, right) => left.localeCompare(right));
75
+ if (keys.length > 24)
76
+ state.contentTruncated = true;
77
+ const fields = keys
78
+ .slice(0, 24)
79
+ .map((key) => `${key}:${boundedHashValue(value[key], depth + 1, seen, state)}`);
80
+ return `object:${keys.length}:{${fields.join(",")}}`;
81
+ }
82
+ function boundedHashValue(value, depth = 0, seen = new Set(), state) {
83
+ const hashState = state ?? { contentTruncated: false };
84
+ if (value === null || typeof value !== "object") {
85
+ return boundedHashScalar(value, hashState);
86
+ }
87
+ if (depth >= 3) {
88
+ hashState.contentTruncated = true;
89
+ return Object.prototype.toString.call(value);
90
+ }
91
+ if (seen.has(value))
92
+ return "[cycle]";
93
+ seen.add(value);
94
+ try {
95
+ if (Array.isArray(value)) {
96
+ return boundedHashArray(value, depth, seen, hashState);
97
+ }
98
+ return boundedHashObject(value, depth, seen, hashState);
99
+ }
100
+ catch {
101
+ return "[unreadable]";
102
+ }
103
+ finally {
104
+ seen.delete(value);
105
+ }
106
+ }
107
+ function hashMessageSequence(messages) {
108
+ const hash = createHash("sha256");
109
+ const state = { contentTruncated: false };
110
+ hash.update(`message-count:${messages.length};`);
111
+ const sampled = Math.min(messages.length, MAX_HASHED_MESSAGES);
112
+ for (let i = 0; i < sampled; i++) {
113
+ const message = messages[i];
114
+ const seen = new Set();
115
+ hash.update(`${i}|role:${boundedHashValue(message?.role, 0, seen, state)}|content:${boundedHashValue(message?.content, 0, seen, state)};`);
116
+ }
117
+ const truncated = messages.length > sampled;
118
+ if (truncated)
119
+ hash.update(`truncated-after:${sampled};`);
120
+ return {
121
+ hash: hash.digest("hex"),
122
+ truncated,
123
+ contentTruncated: state.contentTruncated,
124
+ };
125
+ }
126
+ function messageTextSize(content) {
127
+ if (typeof content === "string") {
128
+ const chars = Math.min(content.length, MAX_INJECTED_CHARS);
129
+ return {
130
+ chars,
131
+ bytes: Math.min(Buffer.byteLength(content.slice(0, chars), "utf8"), MAX_INJECTED_BYTES),
132
+ };
133
+ }
134
+ const bounded = boundedHashValue(content);
135
+ return {
136
+ chars: Math.min(bounded.length, MAX_INJECTED_CHARS),
137
+ bytes: Math.min(Buffer.byteLength(bounded.slice(0, MAX_INJECTED_CHARS), "utf8"), MAX_INJECTED_BYTES),
138
+ };
139
+ }
140
+ function measureInjectedMessages(messages) {
141
+ let chars = 0;
142
+ let bytes = 0;
143
+ const measuredCount = Math.min(messages.length, MAX_REPORTED_MESSAGES);
144
+ for (let i = 0; i < measuredCount; i++) {
145
+ const message = messages[i];
146
+ const size = messageTextSize(message?.content);
147
+ chars = Math.min(MAX_INJECTED_CHARS, chars + size.chars);
148
+ bytes = Math.min(MAX_INJECTED_BYTES, bytes + size.bytes);
149
+ if (chars === MAX_INJECTED_CHARS || bytes === MAX_INJECTED_BYTES)
150
+ break;
151
+ }
152
+ return {
153
+ chars,
154
+ bytes,
155
+ capped: chars >= MAX_INJECTED_CHARS || bytes >= MAX_INJECTED_BYTES,
156
+ };
157
+ }
158
+ function sessionKey(sessionId) {
159
+ return sessionId?.trim() ? sessionId.trim() : NO_SESSION_KEY;
160
+ }
161
+ function prefixLengthForPlacement(placement, messageCount) {
162
+ if (placement === "insert-before-final") {
163
+ return Math.max(0, messageCount - 1);
164
+ }
165
+ if (placement === "prepend")
166
+ return 0;
167
+ return messageCount;
168
+ }
169
+ function resolvePrefixObservation(truncated, observation) {
170
+ if (truncated)
171
+ return "unknown";
172
+ return observation ?? "empty";
173
+ }
174
+ function prefixBaselineForObservation(observation) {
175
+ if (observation === "baseline")
176
+ return true;
177
+ if (observation === "empty" || observation === "unknown")
178
+ return null;
179
+ return false;
180
+ }
181
+ function firstMessageChangeFor(truncated, before, after) {
182
+ if (truncated)
183
+ return "unknown";
184
+ return before !== after ? "changed" : "unchanged";
185
+ }
186
+ /**
187
+ * Log one bounded request-side observation for every `context` call. This is
188
+ * deliberately a separate phase from `cache_prefix_break`: it describes the
189
+ * messages pi-lens saw and returned from its own context handler, not the final
190
+ * provider request after other context handlers and not whether a provider
191
+ * reused or missed a prompt cache. `observationId` is local to this process; the
192
+ * host currently exposes no request id on ContextEvent/MessageEndEvent. A
193
+ * RuntimeCoordinator turn is process-global, so it is explicitly scoped as
194
+ * such and is omitted for concurrent secondary sessions.
195
+ */
196
+ export function observeCacheContext(args) {
197
+ try {
198
+ const existingMessages = args.existingMessages ?? [];
199
+ const resultMessages = args.resultMessages ?? existingMessages;
200
+ const injectedMessages = args.injectedMessages ?? [];
201
+ const placement = args.placement ?? "none";
202
+ const beforeSequence = hashMessageSequence(existingMessages);
203
+ const afterSequence = hashMessageSequence(resultMessages);
204
+ const beforePrefixLength = prefixLengthForPlacement(placement, existingMessages.length);
205
+ const afterPrefixLength = Math.min(beforePrefixLength, resultMessages.length);
206
+ const beforePrefix = hashMessageSequence(existingMessages.slice(0, beforePrefixLength));
207
+ const afterPrefix = hashMessageSequence(resultMessages.slice(0, afterPrefixLength));
208
+ const beforeFirstSequence = existingMessages.length
209
+ ? hashMessageSequence([existingMessages[0]])
210
+ : undefined;
211
+ const afterFirstSequence = resultMessages.length
212
+ ? hashMessageSequence([resultMessages[0]])
213
+ : undefined;
214
+ const beforeFirst = beforeFirstSequence?.hash ?? null;
215
+ const afterFirst = afterFirstSequence?.hash ?? null;
216
+ const firstMessageHashTruncated = beforeFirstSequence?.truncated === true ||
217
+ beforeFirstSequence?.contentTruncated === true ||
218
+ afterFirstSequence?.truncated === true ||
219
+ afterFirstSequence?.contentTruncated === true;
220
+ const prefixHashTruncated = beforePrefix.truncated ||
221
+ afterPrefix.truncated ||
222
+ beforePrefix.contentTruncated ||
223
+ afterPrefix.contentTruncated;
224
+ const prefixObservation = resolvePrefixObservation(prefixHashTruncated, args.prefixObservation);
225
+ const sizes = measureInjectedMessages(injectedMessages);
226
+ const messageCountCapped = existingMessages.length > MAX_REPORTED_MESSAGES ||
227
+ resultMessages.length > MAX_REPORTED_MESSAGES;
228
+ logLatency({
229
+ type: "phase",
230
+ filePath: "<pi-lens>",
231
+ phase: "cache_context",
232
+ durationMs: 0,
233
+ metadata: {
234
+ version: 1,
235
+ observedStage: "pi-lens-context-handler",
236
+ observationId: `ctx-${(++contextObservationCounter).toString(36)}`,
237
+ sessionId: sessionKey(args.sessionId),
238
+ sessionRole: args.sessionRole,
239
+ ...(args.sessionRole === "concurrent-secondary"
240
+ ? { turnScope: SECONDARY_TURN_SCOPE }
241
+ : { turnIndex: args.turnIndex, turnScope: PROCESS_TURN_SCOPE }),
242
+ injectionEnabled: args.injectionEnabled,
243
+ injectionSources: Array.from(args.injectionSources ?? []),
244
+ injectedMessageCount: Math.min(injectedMessages.length, MAX_REPORTED_MESSAGES),
245
+ injectedMessageCountCapped: injectedMessages.length > MAX_REPORTED_MESSAGES,
246
+ injectedChars: sizes.chars,
247
+ injectedBytes: sizes.bytes,
248
+ injectedCountsCapped: sizes.capped,
249
+ existingMessageCount: Math.min(existingMessages.length, MAX_REPORTED_MESSAGES),
250
+ resultMessageCount: Math.min(resultMessages.length, MAX_REPORTED_MESSAGES),
251
+ messageCountCapped,
252
+ placement,
253
+ prefixObservation,
254
+ prefixObservationUnknown: prefixObservation === "unknown",
255
+ prefixBaseline: prefixBaselineForObservation(prefixObservation),
256
+ firstMessageChanged: firstMessageHashTruncated
257
+ ? null
258
+ : beforeFirst !== afterFirst,
259
+ firstMessageChange: firstMessageChangeFor(firstMessageHashTruncated, beforeFirst, afterFirst),
260
+ firstMessageHashTruncated,
261
+ beforeFirstMessageHash: beforeFirst,
262
+ afterFirstMessageHash: afterFirst,
263
+ beforeSequenceHash: beforeSequence.hash,
264
+ afterSequenceHash: afterSequence.hash,
265
+ beforePrefixHash: beforePrefix.hash,
266
+ afterPrefixHash: afterPrefix.hash,
267
+ sequenceHashTruncated: beforeSequence.truncated ||
268
+ afterSequence.truncated ||
269
+ beforeSequence.contentTruncated ||
270
+ afterSequence.contentTruncated,
271
+ sequenceMessageCountTruncated: beforeSequence.truncated || afterSequence.truncated,
272
+ sequenceContentHashTruncated: beforeSequence.contentTruncated || afterSequence.contentTruncated,
273
+ prefixHashTruncated,
274
+ prefixMessageCountTruncated: beforePrefix.truncated || afterPrefix.truncated,
275
+ prefixContentHashTruncated: beforePrefix.contentTruncated || afterPrefix.contentTruncated,
276
+ },
277
+ });
278
+ }
279
+ catch (err) {
280
+ args.dbg?.(`cache-context: failed to log context observation: ${err}`);
281
+ }
282
+ }
283
+ /**
284
+ * Part 1 — log one `cache_usage` record for an assistant `message_end` that
285
+ * carries a `usage`. Provider/model come straight off the message itself
286
+ * (`AssistantMessage.provider` / `.model` in pi-ai) — no dependency on the
287
+ * runtime telemetry identity. Skips silently (no record) when the message is
288
+ * not an assistant message or has no usage; never logs a zeros-only record for
289
+ * a message that simply lacks usage.
290
+ */
291
+ export function logCacheUsage(message, dbg, context) {
292
+ try {
293
+ if (!message || typeof message !== "object")
294
+ return;
295
+ const msg = message;
296
+ // Only assistant messages carry LLM usage; tool-result / user messages
297
+ // (and unknown custom AgentMessage variants) are skipped.
298
+ if (msg.role !== "assistant")
299
+ return;
300
+ const usage = msg.usage;
301
+ if (!usage || typeof usage !== "object")
302
+ return;
303
+ const u = usage;
304
+ logLatency({
305
+ type: "phase",
306
+ filePath: "<pi-lens>",
307
+ phase: "cache_usage",
308
+ durationMs: 0,
309
+ metadata: {
310
+ provider: typeof msg.provider === "string" ? msg.provider : undefined,
311
+ model: typeof msg.model === "string" ? msg.model : undefined,
312
+ cacheRead: u.cacheRead,
313
+ cacheWrite: u.cacheWrite,
314
+ input: u.input,
315
+ output: u.output,
316
+ // `Usage.cost` is a breakdown object; the total is the headline number.
317
+ cost: u.cost?.total,
318
+ ...(context
319
+ ? {
320
+ // MessageEndEvent has no request/context id in the host API. These
321
+ // fields permit session correlation without inventing an exact
322
+ // provider-request linkage. The process-global turn is omitted
323
+ // for a concurrent secondary session.
324
+ sessionId: sessionKey(context.sessionId),
325
+ ...(context.sessionRole === "concurrent-secondary"
326
+ ? { turnScope: SECONDARY_TURN_SCOPE }
327
+ : {
328
+ turnIndex: context.turnIndex,
329
+ turnScope: PROCESS_TURN_SCOPE,
330
+ }),
331
+ contextCorrelation: context.sessionId
332
+ ? "session-only-no-request-id"
333
+ : "no-stable-session-id",
334
+ }
335
+ : {}),
336
+ },
337
+ });
338
+ }
339
+ catch (err) {
340
+ dbg?.(`cache-usage: failed to log message_end usage: ${err}`);
341
+ }
342
+ }
343
+ /**
344
+ * Content hash of the first transcript message. Stable for identical content
345
+ * (role + content are serialized in a fixed order), so a changing hash means
346
+ * `messages[0]` actually changed byte-for-byte.
347
+ */
348
+ function hashFirstMessage(first) {
349
+ // Keep the original full local hash semantics for the existing
350
+ // `cache_prefix_break` signal. The new `cache_context` hashes above are the
351
+ // bounded hashes; this signal must not silently miss a suffix-only change.
352
+ const serialized = JSON.stringify({
353
+ role: first.role,
354
+ content: first.content,
355
+ });
356
+ return createHash("sha256").update(serialized).digest("hex");
357
+ }
358
+ /**
359
+ * Per-session baseline hash of `messages[0]`, keyed by the STABLE pi session id
360
+ * (`ctx.sessionManager.getSessionId()`). A single module-scoped var was wrong:
361
+ * in ONE process, multiple logical conversations touch this module, and they
362
+ * must not share a baseline —
363
+ * - new / fork / a concurrent in-process subagent (#473) is a DIFFERENT id →
364
+ * its OWN independent baseline, never compared against another id's (else a
365
+ * benign session boundary logs a spurious `cache_prefix_break`, and a
366
+ * concurrent subagent + its parent stomp each other's baseline, each
367
+ * emitting a false positive — fatal for a signal whose value is trust);
368
+ * - an IN-PROCESS reload / resume reuses the SAME id → the baseline is still in
369
+ * this map, so a genuine break is caught (no blinding reset). NOTE: a full
370
+ * process restart (quit → `pi --session <id>`) starts a NEW process with an
371
+ * empty map, so the first post-restart `context` re-anchors a fresh baseline
372
+ * rather than comparing across the restart. That's acceptable here — this is a
373
+ * pure observability signal (at worst a missed `cache_prefix_break` log on the
374
+ * first post-restart turn, never a user-facing action) — unlike genuine
375
+ * session state (read guard #1041, widget #190) which IS rehydrated from the
376
+ * sidecar.
377
+ *
378
+ * Bounded as an insertion-ordered LRU (evict oldest-inserted past the cap) so a
379
+ * long-lived process cycling through many sessions can't grow this unbounded.
380
+ */
381
+ const MAX_TRACKED_SESSIONS = 32;
382
+ const prefixHashBySession = new Map();
383
+ /**
384
+ * Bucket key used when no session id is available (undefined/empty). Degrades
385
+ * gracefully to the old single-var semantics — one shared baseline — rather than
386
+ * throwing or dropping the signal.
387
+ */
388
+ const NO_SESSION_KEY = "<no-session>";
389
+ /**
390
+ * Store `hash` for `key`, refreshing its LRU recency (re-insert moves it to the
391
+ * newest position since `Map` preserves insertion order) and evicting the
392
+ * oldest-inserted entries once the cap is exceeded.
393
+ */
394
+ function recordSessionHash(key, hash) {
395
+ prefixHashBySession.delete(key);
396
+ prefixHashBySession.set(key, hash);
397
+ while (prefixHashBySession.size > MAX_TRACKED_SESSIONS) {
398
+ const oldest = prefixHashBySession.keys().next().value;
399
+ if (oldest === undefined)
400
+ break;
401
+ prefixHashBySession.delete(oldest);
402
+ }
403
+ }
404
+ /**
405
+ * Part 2 — observe `messages[0]` stability turn-over-turn, keyed by session id.
406
+ * Logs a baseline the first time it sees a non-empty transcript FOR A GIVEN
407
+ * session id, then logs a `cache_prefix_break` whenever that same session's
408
+ * first-message hash changes. Pure observation: it never inspects or mutates
409
+ * anything but its own per-session hash map, and never throws.
410
+ *
411
+ * `sessionId` should be pi's STABLE id (`ctx.sessionManager.getSessionId()`),
412
+ * which uniquely identifies the CURRENTLY-firing session: a concurrent
413
+ * in-process subagent runs its own `AgentSession`/`sessionManager`, so its
414
+ * `context` calls carry a DIFFERENT id than the parent's and get their own
415
+ * baseline. (`runtime.telemetrySessionId` cannot be used here: per the #473
416
+ * guard a concurrent secondary skips `updateRuntimeIdentityFromEvent`, so that
417
+ * process-global singleton stays pinned to the PARENT — it would collapse
418
+ * parent and subagent onto one baseline.) Residual limitation: if the host ever
419
+ * does NOT supply a stable id, all such sessions collapse onto `NO_SESSION_KEY`
420
+ * and behave like the old single-var (a concurrent subagent could then still
421
+ * cross-contaminate) — accepted as graceful degradation, not silently perfect.
422
+ *
423
+ * Does nothing on an empty transcript (nothing to anchor a prefix to yet).
424
+ */
425
+ export function observeCachePrefix(messages, turnIndex, sessionId, sessionRole, dbg) {
426
+ try {
427
+ if (!messages || messages.length === 0)
428
+ return "empty";
429
+ const first = messages[0];
430
+ if (!first || typeof first !== "object")
431
+ return "empty";
432
+ const key = sessionKey(sessionId);
433
+ const currentHash = hashFirstMessage(first);
434
+ const previousHash = prefixHashBySession.get(key);
435
+ if (previousHash === undefined) {
436
+ // Baseline: record this session's starting prefix so a later break has a
437
+ // reference point in the log. `previousHash: null` marks the baseline.
438
+ recordSessionHash(key, currentHash);
439
+ logLatency({
440
+ type: "phase",
441
+ filePath: "<pi-lens>",
442
+ phase: "cache_prefix_break",
443
+ durationMs: 0,
444
+ metadata: {
445
+ ...(sessionRole === "concurrent-secondary"
446
+ ? { turnScope: SECONDARY_TURN_SCOPE }
447
+ : { turnIndex, turnScope: PROCESS_TURN_SCOPE }),
448
+ previousHash: null,
449
+ currentHash,
450
+ baseline: true,
451
+ sessionId: key,
452
+ sessionRole,
453
+ },
454
+ });
455
+ return "baseline";
456
+ }
457
+ if (currentHash !== previousHash) {
458
+ logLatency({
459
+ type: "phase",
460
+ filePath: "<pi-lens>",
461
+ phase: "cache_prefix_break",
462
+ durationMs: 0,
463
+ metadata: {
464
+ ...(sessionRole === "concurrent-secondary"
465
+ ? { turnScope: SECONDARY_TURN_SCOPE }
466
+ : { turnIndex, turnScope: PROCESS_TURN_SCOPE }),
467
+ previousHash,
468
+ currentHash,
469
+ sessionId: key,
470
+ sessionRole,
471
+ },
472
+ });
473
+ }
474
+ // Refresh recency (and update the stored hash after a break) so an active
475
+ // session stays warm in the LRU and isn't evicted while still in use.
476
+ recordSessionHash(key, currentHash);
477
+ return currentHash === previousHash ? "unchanged" : "changed";
478
+ }
479
+ catch (err) {
480
+ dbg?.(`cache-prefix: failed to observe messages[0]: ${err}`);
481
+ return "empty";
482
+ }
483
+ }
484
+ /**
485
+ * Drop a single session's prefix baseline. Called from the `session_shutdown`
486
+ * handler (primary path only — the concurrent-secondary guard there returns
487
+ * first, and the LRU cap backstops any secondary entry left behind) so an ended
488
+ * conversation's entry is reclaimed promptly rather than only when the LRU
489
+ * evicts it. Idempotent and never throws.
490
+ */
491
+ export function clearCachePrefixSession(sessionId) {
492
+ const key = sessionKey(sessionId);
493
+ prefixHashBySession.delete(key);
494
+ }
495
+ /** Clear all per-session prefix hashes. For tests / session boundaries. */
496
+ export function resetCachePrefixObservation() {
497
+ prefixHashBySession.clear();
498
+ }