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,293 @@
1
+ /**
2
+ * THE single declarative source of truth for every pi-lens toggle (#166).
3
+ *
4
+ * One entry per flag drives all four consumers that previously kept their own
5
+ * hand-maintained list and drifted apart:
6
+ *
7
+ * - `index.ts` — `pi.registerFlag` for the CLI surface (name, description,
8
+ * default), as a loop over this array.
9
+ * - `clients/lens-config.ts` — parsing the key out of `~/.pi-lens/config.json`
10
+ * AND resolving the precedence chain, both keyed by `configKey`.
11
+ * - `clients/project-lens-config.ts` — the closest-wins nested walk for the
12
+ * `scope: "project"` entries.
13
+ * - `tests/index-wiring.test.ts` — the registration contract, derived rather
14
+ * than restated.
15
+ *
16
+ * Every entry is settable BOTH ways: `--<name>` on the CLI and `configKey` in
17
+ * `~/.pi-lens/config.json`. Adding a toggle means adding one entry here; there
18
+ * is no second place to remember.
19
+ */
20
+ export const LENS_FLAGS = [
21
+ {
22
+ name: "no-lens",
23
+ description: "Start pi-lens disabled for this session. Re-enable with /lens-toggle. Also via lens.enabled=false in ~/.pi-lens/config.json.",
24
+ configKey: "lens.enabled",
25
+ negated: true,
26
+ default: false,
27
+ scope: "global",
28
+ },
29
+ {
30
+ name: "no-lsp",
31
+ description: "Disable unified LSP diagnostics and use language-specific fallbacks (for example pyright). Also via lsp.enabled=false in ~/.pi-lens/config.json.",
32
+ configKey: "lsp.enabled",
33
+ negated: true,
34
+ default: false,
35
+ scope: "global",
36
+ },
37
+ {
38
+ name: "no-autoformat",
39
+ description: "Disable automatic formatting entirely (deferred format runs at agent_end by default). Also via format.enabled=false in config.",
40
+ configKey: "format.enabled",
41
+ negated: true,
42
+ default: false,
43
+ scope: "project",
44
+ },
45
+ {
46
+ name: "immediate-format",
47
+ description: 'Run automatic formatting immediately after each write/edit instead of deferring to agent_end. Also via format.mode="immediate" in config.',
48
+ configKey: "format.mode",
49
+ negated: false,
50
+ default: false,
51
+ scope: "global",
52
+ readGlobal: (config) => {
53
+ const format = config.format;
54
+ if (!format || typeof format !== "object")
55
+ return undefined;
56
+ const mode = format.mode;
57
+ if (mode !== "immediate" && mode !== "deferred")
58
+ return undefined;
59
+ return mode === "immediate";
60
+ },
61
+ },
62
+ {
63
+ name: "no-autofix",
64
+ description: "Disable auto-fixing of lint issues (Biome, Ruff, ESLint). Also via autofix.enabled=false in config.",
65
+ configKey: "autofix.enabled",
66
+ negated: true,
67
+ default: false,
68
+ scope: "project",
69
+ },
70
+ {
71
+ name: "no-tests",
72
+ description: "Disable test runner on write. Also via tests.enabled=false in ~/.pi-lens/config.json.",
73
+ configKey: "tests.enabled",
74
+ negated: true,
75
+ default: false,
76
+ scope: "global",
77
+ },
78
+ {
79
+ name: "no-delta",
80
+ description: "Disable delta mode (show all diagnostics, not just new ones). Also via delta.enabled=false in ~/.pi-lens/config.json.",
81
+ configKey: "delta.enabled",
82
+ negated: true,
83
+ default: false,
84
+ scope: "global",
85
+ },
86
+ {
87
+ name: "lens-guard",
88
+ description: "Experimental: block git commit/push when unresolved pi-lens blockers exist. Also via guard.enabled=true in ~/.pi-lens/config.json.",
89
+ configKey: "guard.enabled",
90
+ negated: false,
91
+ default: false,
92
+ scope: "global",
93
+ },
94
+ {
95
+ name: "no-opengrep",
96
+ description: "Disable the Opengrep security scanner (a default-on auxiliary LSP; auto-installs, uses repo rules if present else the login-free 'auto' ruleset). Also via opengrep.enabled=false in ~/.pi-lens/config.json.",
97
+ configKey: "opengrep.enabled",
98
+ negated: true,
99
+ default: false,
100
+ scope: "global",
101
+ },
102
+ {
103
+ name: "no-read-guard",
104
+ description: "Disable read-before-edit behavior monitor. Also via readGuard.enabled=false in ~/.pi-lens/config.json.",
105
+ configKey: "readGuard.enabled",
106
+ negated: true,
107
+ default: false,
108
+ scope: "global",
109
+ },
110
+ {
111
+ name: "no-lens-context",
112
+ description: "Disable automatic context injection (session-start guidance, turn-end & test findings) while keeping tools, LSP, read-guard, and formatting active. Toggle with /lens-context-toggle. Also via contextInjection.enabled=false in config or PI_LENS_NO_CONTEXT_INJECTION=1.",
113
+ configKey: "contextInjection.enabled",
114
+ negated: true,
115
+ default: false,
116
+ scope: "global",
117
+ env: "PI_LENS_NO_CONTEXT_INJECTION",
118
+ },
119
+ {
120
+ name: "lens-turn-summary",
121
+ description: "Opt-in: persist a per-turn transcript entry summarizing diagnostics found, autofixes applied, and autoformats applied this turn (#484). Collapsed one-line, expandable in place. Default off. Also via turnSummary.enabled=true in ~/.pi-lens/config.json.",
122
+ configKey: "turnSummary.enabled",
123
+ negated: false,
124
+ default: false,
125
+ scope: "global",
126
+ },
127
+ {
128
+ name: "lens-actionable-warnings",
129
+ description: "Write turn-delta fixable warning reports and inject a short advisory. Also via actionableWarnings.enabled=true in ~/.pi-lens/config.json.",
130
+ configKey: "actionableWarnings.enabled",
131
+ negated: false,
132
+ default: false,
133
+ scope: "global",
134
+ },
135
+ {
136
+ name: "lens-actionable-warning-actions",
137
+ description: "Enrich actionable-warning reports with LSP code-action titles (requires an active language server). Also via actionableWarnings.includeLspCodeActions=true in ~/.pi-lens/config.json.",
138
+ configKey: "actionableWarnings.includeLspCodeActions",
139
+ negated: false,
140
+ default: false,
141
+ scope: "global",
142
+ },
143
+ {
144
+ name: "lens-actionable-warning-autofix",
145
+ description: "Experimental: apply conservative LSP quickfixes for actionable warnings at agent_end. Also via actionableWarnings.autoFix.enabled=true in config.",
146
+ configKey: "actionableWarnings.autoFix.enabled",
147
+ negated: false,
148
+ default: false,
149
+ scope: "project",
150
+ },
151
+ {
152
+ name: "lens-actionable-warning-all",
153
+ description: "Report every actionable warning, not just those introduced this turn. Also via actionableWarnings.deltaOnly=false in ~/.pi-lens/config.json.",
154
+ configKey: "actionableWarnings.deltaOnly",
155
+ negated: true,
156
+ default: false,
157
+ scope: "global",
158
+ },
159
+ {
160
+ name: "lens-compact-tool-line",
161
+ description: "Opt-in (#1327): collapse a pi-lens tool's call+result rows into ONE theme-aware line (status glyph + name + summary) instead of two. Preserves expand-to-view-full-output. Default off. Also via ui.compactToolLine=true in ~/.pi-lens/config.json.",
162
+ configKey: "ui.compactToolLine",
163
+ negated: false,
164
+ default: false,
165
+ scope: "global",
166
+ },
167
+ ];
168
+ const byName = new Map(LENS_FLAGS.map((spec) => [spec.name, spec]));
169
+ export function getLensFlagSpec(name) {
170
+ return byName.get(name);
171
+ }
172
+ /** The `scope: "project"` subset — the flags a `.pi-lens.json` may also set. */
173
+ export const PROJECT_SCOPED_LENS_FLAGS = LENS_FLAGS.filter((spec) => spec.scope === "project");
174
+ /**
175
+ * Recognized TOP-LEVEL sections of `~/.pi-lens/config.json` that are NOT
176
+ * derived from the flag registry — the non-flag namespaces the global loader
177
+ * parses by hand (`ignore`, `dispatch`, `widget`) plus `$schema` for editor
178
+ * JSON-schema association. Declared here, beside {@link LENS_FLAGS}, so the
179
+ * global unknown-key scan has ONE catalog to consult (#883) instead of a
180
+ * second hand-maintained literal set inline in the loader. Adding a future
181
+ * top-level namespace is a one-line edit HERE; adding a flag needs no edit at
182
+ * all (its section is registry-derived via {@link flagConfigSectionKeys}).
183
+ * Do NOT speculatively pre-add keys no loader reads yet (e.g. `languages` is
184
+ * #195's job) — this is an extension point, not a wishlist.
185
+ *
186
+ * `actionableWarnings` is intentionally absent: it is already a flag section
187
+ * (`actionableWarnings.enabled`, `.autoFix.enabled`, ...) so it is covered by
188
+ * the registry-derived set.
189
+ */
190
+ export const GLOBAL_NON_FLAG_CONFIG_SECTIONS = [
191
+ "ignore",
192
+ "dispatch",
193
+ "widget",
194
+ "$schema",
195
+ ];
196
+ /**
197
+ * Foreign top-level namespaces that legitimately appear in a SHARED
198
+ * `.pi-lens.json` but belong to a DIFFERENT loader: the LSP config loader
199
+ * (`clients/lsp/config.ts`) reads `servers` / `serverOverrides` /
200
+ * `disabledServers` / `warmFiles` out of the very same file. The project-lens
201
+ * loader must TOLERATE these (never warn "unknown key") — they are not typos,
202
+ * just another subsystem's namespace. `$schema` is allowed for editor
203
+ * JSON-schema association. Declared once, here, for the same #883 reason as
204
+ * {@link GLOBAL_NON_FLAG_CONFIG_SECTIONS}.
205
+ */
206
+ export const PROJECT_FOREIGN_CONFIG_NAMESPACES = [
207
+ "servers",
208
+ "serverOverrides",
209
+ "disabledServers",
210
+ "warmFiles",
211
+ "$schema",
212
+ ];
213
+ /**
214
+ * The distinct TOP-LEVEL section keys a set of flags lives under (the first
215
+ * dotted segment of each `configKey`). Both config loaders derive their
216
+ * recognized-key catalogs from this rather than restating flag sections, so a
217
+ * new flag never needs an unknown-key-scan edit (#166/#883).
218
+ */
219
+ export function flagConfigSectionKeys(flags) {
220
+ return [...new Set(flags.map((spec) => spec.configKey.split(".")[0]))];
221
+ }
222
+ function asConfigObject(value) {
223
+ return value && typeof value === "object" && !Array.isArray(value)
224
+ ? value
225
+ : undefined;
226
+ }
227
+ function resolveFlagConfigPath(raw, configKey, warnInvalid) {
228
+ const segments = configKey.split(".");
229
+ let source = raw;
230
+ for (let i = 0; i < segments.length - 1; i++) {
231
+ const object = asConfigObject(source);
232
+ if (!object)
233
+ return undefined;
234
+ const segment = segments[i];
235
+ if (!(segment in object))
236
+ return undefined;
237
+ const next = asConfigObject(object[segment]);
238
+ if (!next) {
239
+ if (warnInvalid) {
240
+ warnInvalid(`${segments.slice(0, i + 1).join(".")} must be an object`);
241
+ }
242
+ return undefined;
243
+ }
244
+ source = next;
245
+ }
246
+ const finalSource = asConfigObject(source);
247
+ if (!finalSource)
248
+ return undefined;
249
+ return { source: finalSource, segments };
250
+ }
251
+ /**
252
+ * Read the boolean at a spec's dotted `configKey` out of an already-parsed
253
+ * config object. Returns undefined when any segment is missing or the leaf is
254
+ * not a boolean — callers treat that as "this tier does not decide".
255
+ */
256
+ export function readFlagConfigValue(config, configKey) {
257
+ const path = resolveFlagConfigPath(config, configKey);
258
+ if (!path)
259
+ return undefined;
260
+ const leaf = path.segments[path.segments.length - 1];
261
+ const value = path.source[leaf];
262
+ return typeof value === "boolean" ? value : undefined;
263
+ }
264
+ /** Turn a config-tier boolean into the flag's value, honoring `negated`. */
265
+ export function flagValueFromConfig(spec, configValue) {
266
+ return spec.negated ? !configValue : configValue;
267
+ }
268
+ /**
269
+ * Copy the boolean at one flag's dotted `configKey` from a raw parsed config
270
+ * into `out`, materializing intermediate objects only when they actually
271
+ * exist in `raw` — an absent section stays absent, a present-but-empty one is
272
+ * materialized with an undefined leaf. Shared by the global
273
+ * (`lens-config.ts`) and project (`project-lens-config.ts`) loaders so both
274
+ * parse and warn identically; it lives here rather than in either loader
275
+ * because this module imports nothing and cannot form a cycle.
276
+ */
277
+ export function assignFlagConfigSection(raw, out, configKey, warnInvalid) {
278
+ const path = resolveFlagConfigPath(raw, configKey, warnInvalid);
279
+ if (!path)
280
+ return;
281
+ let target = out;
282
+ for (const segment of path.segments.slice(0, -1)) {
283
+ target[segment] ??= {};
284
+ target = target[segment];
285
+ }
286
+ const leaf = path.segments[path.segments.length - 1];
287
+ if (leaf in path.source && typeof path.source[leaf] !== "boolean") {
288
+ warnInvalid(`${configKey} must be a boolean`);
289
+ target[leaf] = undefined;
290
+ return;
291
+ }
292
+ target[leaf] = path.source[leaf];
293
+ }
@@ -20,7 +20,8 @@ import * as path from "node:path";
20
20
  import { FactStore } from "./dispatch/fact-store.js";
21
21
  import { getProjectDataDir } from "./file-utils.js";
22
22
  import { collectUntrackedIgnoredIds } from "./git-tracked-ignore.js";
23
- import { normalizeMapKey } from "./path-utils.js";
23
+ import { normalizeMapKey, toProjectRelativePath } from "./path-utils.js";
24
+ import { canonicalSourceTwin } from "./source-filter.js";
24
25
  import { buildOrUpdateGraph } from "./review-graph/service.js";
25
26
  import { detectFileRole } from "./file-role.js";
26
27
  export const DEFAULT_MAX_MAP_NODES = 500;
@@ -92,11 +93,10 @@ export function aggregateGraphToFiles(graph, options) {
92
93
  // import edges). Deterministic sibling families: .js → .ts (preferred)
93
94
  // then .tsx; .mjs → .mts; .cjs → .cts. A compiled-looking file with no
94
95
  // source twin (vendored deps, pure-JS projects) is untouched.
95
- const COMPILED_TWIN_SOURCES = [
96
- [".js", [".ts", ".tsx"]],
97
- [".mjs", [".mts"]],
98
- [".cjs", [".cts"]],
99
- ];
96
+ // Keep this pure aggregation layer on the shared source-filter policy. It
97
+ // cannot call findSourceSibling because synthetic graph fixtures and cached
98
+ // nodes are not required to exist on disk.
99
+ const compiledExtensions = new Set([".js", ".jsx", ".mjs", ".cjs"]);
100
100
  // Interplay with the untracked-gitignored exclusion (`excludeIds`):
101
101
  // twin canonicalization takes PRECEDENCE over exclusion. An excluded
102
102
  // compiled file whose source twin survives is merged onto that twin
@@ -109,19 +109,11 @@ export function aggregateGraphToFiles(graph, options) {
109
109
  const excludeIds = options?.excludeIds;
110
110
  const canonicalOf = new Map();
111
111
  for (const id of allFileIds) {
112
- for (const [compiledExt, sourceExts] of COMPILED_TWIN_SOURCES) {
113
- if (!id.endsWith(compiledExt))
114
- continue;
115
- const stem = id.slice(0, id.length - compiledExt.length);
116
- for (const sourceExt of sourceExts) {
117
- const candidate = stem + sourceExt;
118
- if (allFileIds.has(candidate) && !excludeIds?.has(candidate)) {
119
- canonicalOf.set(id, candidate);
120
- break;
121
- }
122
- }
123
- break;
124
- }
112
+ if (!compiledExtensions.has(path.extname(id).toLowerCase()))
113
+ continue;
114
+ const canonical = canonicalSourceTwin(id, allFileIds, excludeIds);
115
+ if (canonical !== id)
116
+ canonicalOf.set(id, canonical);
125
117
  }
126
118
  const compiledTwinCount = canonicalOf.size;
127
119
  // Excluded ids that actually drop: in the graph, untracked-gitignored,
@@ -137,6 +129,77 @@ export function aggregateGraphToFiles(graph, options) {
137
129
  }
138
130
  }
139
131
  const ignoredFileCount = droppedIgnoredIds.size;
132
+ // Build logical symbol identities before counting symbols or edges. A compiled
133
+ // twin can carry the same declaration and edge evidence as its source; those
134
+ // are one logical observation after canonicalization. Match source declarations
135
+ // in stable insertion order so overloads with the same name/kind stay distinct.
136
+ const logicalSymbolByNode = new Map();
137
+ const sourceSymbolsByFile = new Map();
138
+ // A symbol kind is not stable across extractor surfaces: a declaration can
139
+ // be a `function` in a JS/TS fact and a `variable` in a compiled twin's
140
+ // syntax extraction. Keep a name-only index for the safe case where there is
141
+ // exactly one source declaration with that name; this prevents equivalent
142
+ // twins from inflating the preferred source while preserving overloads and
143
+ // genuinely ambiguous same-name declarations.
144
+ const sourceSymbolsByName = new Map();
145
+ for (const node of graph.nodes.values()) {
146
+ if (node.kind !== "symbol" || !node.filePath || !node.symbolName)
147
+ continue;
148
+ const raw = normalizeMapKey(node.filePath);
149
+ const canonical = canonicalOf.get(raw) ?? raw;
150
+ if (raw !== canonical)
151
+ continue;
152
+ const key = `${node.symbolName}\u0000${node.symbolKind ?? ""}`;
153
+ const byName = sourceSymbolsByFile.get(canonical) ?? new Map();
154
+ const ids = byName.get(key) ?? [];
155
+ ids.push(node.id);
156
+ byName.set(key, ids);
157
+ sourceSymbolsByFile.set(canonical, byName);
158
+ const bySymbolName = sourceSymbolsByName.get(canonical) ?? new Map();
159
+ const nameIds = bySymbolName.get(node.symbolName) ?? [];
160
+ nameIds.push(node.id);
161
+ bySymbolName.set(node.symbolName, nameIds);
162
+ sourceSymbolsByName.set(canonical, bySymbolName);
163
+ logicalSymbolByNode.set(node.id, node.id);
164
+ }
165
+ // Assign each twin's declarations by occurrence within THAT twin file, not
166
+ // through one process-wide "used" set. A source can have several compiled
167
+ // siblings (`x.ts`, `x.js`, `x.mjs`, ...); every sibling's first `run` must
168
+ // map to the source's first `run`, while overloads with the same name/kind
169
+ // remain distinct by occurrence.
170
+ const twinOccurrencesByFile = new Map();
171
+ for (const node of graph.nodes.values()) {
172
+ if (node.kind !== "symbol" || !node.filePath || !node.symbolName)
173
+ continue;
174
+ const raw = normalizeMapKey(node.filePath);
175
+ const canonical = canonicalOf.get(raw) ?? raw;
176
+ if (raw === canonical)
177
+ continue;
178
+ const key = `${node.symbolName}\u0000${node.symbolKind ?? ""}`;
179
+ const exactSourceIds = sourceSymbolsByFile.get(canonical)?.get(key) ?? [];
180
+ const nameSourceIds = sourceSymbolsByName.get(canonical)?.get(node.symbolName) ?? [];
181
+ // Prefer the kind-qualified mapping. Fall back only when the source name
182
+ // is unique; never guess among overloads or same-name declarations.
183
+ const sourceIds = exactSourceIds.length > 0
184
+ ? exactSourceIds
185
+ : nameSourceIds.length === 1
186
+ ? nameSourceIds
187
+ : [];
188
+ const occurrenceKey = exactSourceIds.length > 0 ? key : node.symbolName;
189
+ const occurrences = twinOccurrencesByFile.get(raw) ?? new Map();
190
+ const occurrence = occurrences.get(occurrenceKey) ?? 0;
191
+ occurrences.set(occurrenceKey, occurrence + 1);
192
+ twinOccurrencesByFile.set(raw, occurrences);
193
+ const logical = sourceIds[occurrence];
194
+ if (logical) {
195
+ logicalSymbolByNode.set(node.id, logical);
196
+ }
197
+ else {
198
+ // A compiled-only declaration remains real evidence.
199
+ logicalSymbolByNode.set(node.id, node.id);
200
+ }
201
+ }
202
+ const countedLogicalSymbols = new Set();
140
203
  // ── Pass 3: aggregate nodes under their canonical file identity ─────────
141
204
  for (const node of graph.nodes.values()) {
142
205
  if (node.kind === "external")
@@ -173,9 +236,13 @@ export function aggregateGraphToFiles(graph, options) {
173
236
  continue;
174
237
  const id = canonicalOf.get(raw) ?? raw;
175
238
  nodeToFile.set(node.id, id);
239
+ const logicalId = logicalSymbolByNode.get(node.id) ?? node.id;
176
240
  const existing = files.get(id);
177
241
  if (existing) {
178
- existing.symbolCount += 1;
242
+ if (!countedLogicalSymbols.has(logicalId)) {
243
+ existing.symbolCount += 1;
244
+ countedLogicalSymbols.add(logicalId);
245
+ }
179
246
  }
180
247
  else {
181
248
  files.set(id, {
@@ -184,6 +251,7 @@ export function aggregateGraphToFiles(graph, options) {
184
251
  language: node.language || "",
185
252
  symbolCount: 1,
186
253
  });
254
+ countedLogicalSymbols.add(logicalId);
187
255
  }
188
256
  continue;
189
257
  }
@@ -197,11 +265,18 @@ export function aggregateGraphToFiles(graph, options) {
197
265
  // becomes self-referential only AFTER canonicalization (e.g. x.js → x.ts)
198
266
  // collapses through the same fromFile === toFile check.
199
267
  const edgeWeights = new Map();
268
+ const seenLogicalEdges = new Set();
200
269
  for (const edge of graph.edges) {
201
270
  const fromFile = nodeToFile.get(edge.from);
202
271
  const toFile = nodeToFile.get(edge.to);
203
272
  if (!fromFile || !toFile || fromFile === toFile)
204
273
  continue;
274
+ const logicalFrom = logicalSymbolByNode.get(edge.from) ?? fromFile;
275
+ const logicalTo = logicalSymbolByNode.get(edge.to) ?? toFile;
276
+ const logicalEdgeKey = `${logicalFrom}\u0000${logicalTo}\u0000${edge.kind}`;
277
+ if (seenLogicalEdges.has(logicalEdgeKey))
278
+ continue;
279
+ seenLogicalEdges.add(logicalEdgeKey);
205
280
  // NUL separator (same idiom as review-graph/builder.ts): the one byte
206
281
  // that can never appear in a file path, written as an escape sequence so
207
282
  // this source file stays text for grep (a literal NUL makes ripgrep
@@ -1005,12 +1080,7 @@ export { parseUntrackedIgnoredOutput } from "./git-tracked-ignore.js";
1005
1080
  // sits under the project root, else the absolute (slash-normalized) path.
1006
1081
  // Mirrors module-report.ts's toDisplayPath convention.
1007
1082
  function toDisplayPath(p, projectRoot) {
1008
- if (!path.isAbsolute(p))
1009
- return p.replace(/\\/g, "/");
1010
- const rel = path.relative(projectRoot, p);
1011
- return rel && !rel.startsWith("..")
1012
- ? rel.replace(/\\/g, "/")
1013
- : p.replace(/\\/g, "/");
1083
+ return toProjectRelativePath(p, projectRoot);
1014
1084
  }
1015
1085
  /**
1016
1086
  * Builds (or refreshes) the review graph, aggregates it to a file-level map,
@@ -0,0 +1,30 @@
1
+ import { recordDegradation } from "./degradation-ledger.js";
2
+ const STALE_CTX_MESSAGE = "This extension ctx is stale after session replacement";
3
+ /** Ledger a dead activation once, at the publisher's occurrence-window guard. */
4
+ export function recordStaleBusFailure(subject, error) {
5
+ const reason = String(error);
6
+ if (!reason.includes(STALE_CTX_MESSAGE))
7
+ return;
8
+ recordDegradation({ kind: "bus-stale", subject, reason });
9
+ }
10
+ export function createLiveBusEmitter() {
11
+ let emit;
12
+ let getter;
13
+ return {
14
+ wire(next) {
15
+ emit = next;
16
+ getter = undefined;
17
+ },
18
+ wireGetter(next) {
19
+ getter = next;
20
+ emit = undefined;
21
+ },
22
+ get() {
23
+ return getter?.() ?? emit;
24
+ },
25
+ reset() {
26
+ emit = undefined;
27
+ getter = undefined;
28
+ },
29
+ };
30
+ }
@@ -24,20 +24,9 @@ import * as fs from "node:fs";
24
24
  import * as path from "node:path";
25
25
  import { getGlobalPiLensDir } from "./file-utils.js";
26
26
  import { getRegisteredLogFiles } from "./ndjson-logger.js";
27
+ import { pathsEqual } from "./path-utils.js";
27
28
  const LOG_DIR = getGlobalPiLensDir();
28
29
  const LOGS_SUBDIR = path.join(LOG_DIR, "logs");
29
- /**
30
- * Logs that write via some mechanism OTHER than the shared
31
- * `createNdjsonLogger` (clients/ndjson-logger.ts), so they can't self-register
32
- * into its file registry. Keep this list as small as possible — anything that
33
- * can reasonably move onto the shared writer should, so there's only one
34
- * place left to remember by hand.
35
- *
36
- * - sessionstart.log — written by a handful of modules (installer/index.ts,
37
- * lsp/index.ts, lsp/launch.ts, lsp/server.ts, index.ts) via a bespoke
38
- * `fs.appendFile`, predating ndjson-logger.
39
- */
40
- const UNMANAGED_STRAGGLER_LOG_FILES = ["sessionstart.log"];
41
30
  /**
42
31
  * Every global `.log` pi-lens writes under ~/.pi-lens, resolved dynamically so
43
32
  * rotation and the storage summary can't drift out of sync with what's
@@ -58,16 +47,13 @@ const UNMANAGED_STRAGGLER_LOG_FILES = ["sessionstart.log"];
58
47
  * a file has content on disk, this catches it regardless of import
59
48
  * timing; an unregistered file that doesn't exist yet has nothing to
60
49
  * clean up anyway.
61
- * 3. `UNMANAGED_STRAGGLER_LOG_FILES` — the handful of logs that don't go
62
- * through `createNdjsonLogger` at all and so can never self-register.
63
- *
64
50
  * `dir` is overridable for tests; production callers use the default
65
51
  * (real `~/.pi-lens`).
66
52
  */
67
53
  export function getManagedLogFiles(dir = LOG_DIR) {
68
- const names = new Set(UNMANAGED_STRAGGLER_LOG_FILES);
54
+ const names = new Set();
69
55
  for (const absPath of getRegisteredLogFiles()) {
70
- if (path.dirname(absPath) === dir) {
56
+ if (pathsEqual(path.dirname(absPath), dir)) {
71
57
  names.add(path.basename(absPath));
72
58
  }
73
59
  }
@@ -93,10 +79,14 @@ export function getManagedLogFiles(dir = LOG_DIR) {
93
79
  * timestamp segment and no trailing suffix after `.log`, so it never matches.
94
80
  */
95
81
  export const ROTATED_BACKUP_RE = /(\.\d{4}-\d{2}-\d{2}T.*\.log|\.log\..+)$/;
82
+ /** Rotation threshold for every active `~/.pi-lens/*.log`, in MB. */
83
+ export function getMaxLogSizeMB() {
84
+ return Math.max(1, Number.parseInt(process.env.PI_LENS_MAX_LOG_SIZE_MB ?? "10", 10) || 10);
85
+ }
96
86
  function getConfig() {
97
87
  return {
98
88
  retentionDays: Math.max(1, Number.parseInt(process.env.PI_LENS_LOG_RETENTION_DAYS ?? "7", 10) || 7),
99
- maxSizeMB: Math.max(1, Number.parseInt(process.env.PI_LENS_MAX_LOG_SIZE_MB ?? "10", 10) || 10),
89
+ maxSizeMB: getMaxLogSizeMB(),
100
90
  };
101
91
  }
102
92
  function getFileAgeDays(filePath) {
@@ -26,7 +26,7 @@
26
26
  * snapshot's `workspaceDiagnosticsSupport.mode` (from
27
27
  * `detectWorkspaceDiagnosticsSupport`, cached at `initialize`) and combines it
28
28
  * with the `silentOnClean` marker on that server's `DiagnosticStrategy`
29
- * (`server-strategies.ts`) — the same per-server behavioral-knowledge table
29
+ * (`wait-policy/strategies.ts`) — the same per-server behavioral-knowledge table
30
30
  * the rest of the LSP layer already uses. A server with no live snapshot yet,
31
31
  * or whose mode isn't `push-only`, or that isn't marked `silentOnClean`, is
32
32
  * NOT tier-3 — the caller keeps today's full in-lane wait. Fail-safe is
@@ -151,12 +151,15 @@ export async function reconcileOutstandingCascadeTouches(lspService) {
151
151
  });
152
152
  continue;
153
153
  }
154
+ const found = entry.diags.length > 0;
154
155
  outcomes.push({
155
156
  filePath: touch.filePath,
156
157
  serverId: touch.serverId,
157
- outcome: entry.diags.length > 0 ? "resolved-found" : "resolved-clean",
158
+ outcome: found ? "resolved-found" : "resolved-clean",
158
159
  ageMs,
159
160
  diagnosticCount: entry.diags.length,
161
+ // #1023: carry the diagnostics so the task can re-surface them.
162
+ ...(found && { diagnostics: entry.diags }),
160
163
  });
161
164
  }
162
165
  catch (err) {
@@ -183,7 +186,7 @@ let _reconcileTaskRegistered = false;
183
186
  * (`clients/quiet-window.ts`). Idempotent — safe to call more than once
184
187
  * (e.g. multiple extension activations in tests).
185
188
  */
186
- export function registerCascadeTierReconcileTask(getLspService) {
189
+ export function registerCascadeTierReconcileTask(getLspService, options = {}) {
187
190
  if (_reconcileTaskRegistered)
188
191
  return;
189
192
  _reconcileTaskRegistered = true;
@@ -195,6 +198,23 @@ export function registerCascadeTierReconcileTask(getLspService) {
195
198
  const outcomes = await reconcileOutstandingCascadeTouches(getLspService());
196
199
  if (outcomes.length === 0)
197
200
  return;
201
+ // #1023: re-inject each resolved-found neighbor error so it reaches the
202
+ // agent (previously logs-only). Isolated per-outcome — a throwing callback
203
+ // must not drop the log line or the sibling re-injections.
204
+ for (const o of outcomes) {
205
+ if (o.outcome !== "resolved-found" || !o.diagnostics?.length)
206
+ continue;
207
+ try {
208
+ options.onResolvedFound?.({
209
+ filePath: o.filePath,
210
+ serverId: o.serverId,
211
+ diagnostics: o.diagnostics,
212
+ });
213
+ }
214
+ catch {
215
+ // best-effort surfacing; the log below is the durable record.
216
+ }
217
+ }
198
218
  let resolvedFound = 0;
199
219
  let resolvedClean = 0;
200
220
  let unresolved = 0;