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
@@ -24,13 +24,14 @@ import * as path from "node:path";
24
24
  import { fileURLToPath } from "node:url";
25
25
  import { AstGrepClient } from "../clients/ast-grep-client.js";
26
26
  import { CacheManager } from "../clients/cache-manager.js";
27
- import { computeBuildStamp, STALE_SERVED_BY_FRESH, STALE_WARN_ONLY, StalenessGate, stalenessCheckEnabled, } from "./build-staleness.js";
28
- import { analyzeFile, analyzeFileFresh, createMcpHost, diagnosticStats, ensureLspConfig, ipcPathForCwd, lspStatus, moduleReport, projectReport, projectScan, readEnclosing, readSymbol, recentLatency, renderCompactModuleReport, renderCompactProjectReport, resolveRebuildScript, resourceFootprint, runRebuild, runSessionStart, runTurnEnd, scanTruncationNotice, summarizeScan, symbolSearch, } from "../clients/lens-engine.js";
27
+ import { getDegradationSummary, renderDegradationLines, } from "../clients/degradation-ledger.js";
28
+ import { acknowledgeTurnEnd, analyzeFile, analyzeFileFresh, canRebuildPiLens, createMcpHost, createWarmIpcLineReader, createWarmIpcRequestQueue, diagnosticStats, ensureLspConfig, generatedSkipNotice, ipcPathForCwd, lspStatus, moduleReport, projectReport, projectScan, readEnclosing, readSymbol, readTurnEndStatus, recentLatency, renderCompactModuleReport, renderCompactProjectReport, renderLspBrokenStatusLines, resolveRebuildScript, resourceFootprint, runRebuild, runSessionStart, runTurnEnd, runTurnEndForIpc, scanTruncationNotice, summarizeScan, symbolSearch, treeSitterRuntimeStatus, WARM_TURN_END_SCHEMA_VERSION, } from "../clients/lens-engine.js";
29
29
  import { createAstGrepReplaceTool } from "../tools/ast-grep-replace.js";
30
30
  import { createAstGrepSearchTool } from "../tools/ast-grep-search.js";
31
31
  import { createLensDiagnosticsTool } from "../tools/lens-diagnostics.js";
32
32
  import { createLspDiagnosticsTool } from "../tools/lsp-diagnostics.js";
33
33
  import { createLspNavigationTool } from "../tools/lsp-navigation.js";
34
+ import { computeBuildStamp, STALE_SERVED_BY_FRESH, STALE_WARN_ONLY, StalenessGate, stalenessCheckEnabled, } from "./build-staleness.js";
34
35
  // Any stray stdout write corrupts the JSON-RPC stream; force it onto stderr.
35
36
  console.log = (...args) => {
36
37
  console.error(...args);
@@ -75,7 +76,21 @@ const REBUILD_SCRIPT = resolveRebuildScript(SERVER_FILE);
75
76
  // under parallel vitest execution.
76
77
  const STALENESS_STAT_PATH = process.env.PI_LENS_MCP_STALENESS_STAT_PATH ?? SERVER_FILE;
77
78
  const BUILD_STAMP = computeBuildStamp(STALENESS_STAT_PATH);
78
- const STALENESS_GATE = new StalenessGate(BUILD_STAMP);
79
+ // `PI_LENS_MCP_STALENESS_INTERVAL_MS` (test-only override): shrinks the
80
+ // gate's re-stat throttle below its 1000ms default so a staleness smoke test
81
+ // doesn't have to sleep out a full second per assertion. Parsed explicitly
82
+ // rather than `Number(raw) || undefined` — that idiom would coerce a real
83
+ // "0" override (disable the throttle entirely) back to the 1000ms default.
84
+ const stalenessIntervalOverride = (() => {
85
+ const raw = process.env.PI_LENS_MCP_STALENESS_INTERVAL_MS;
86
+ if (raw === undefined)
87
+ return undefined;
88
+ const parsed = Number(raw);
89
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : undefined;
90
+ })();
91
+ const STALENESS_GATE = new StalenessGate(BUILD_STAMP, {
92
+ checkIntervalMs: stalenessIntervalOverride,
93
+ });
79
94
  /**
80
95
  * True when the warm server's loaded code is older than what's on disk right
81
96
  * now. Mtime-gated (at most one `fs.stat` per second, like the #492
@@ -108,7 +123,11 @@ function findRepoRoot(start) {
108
123
  }
109
124
  return path.resolve(start, "..", "..");
110
125
  }
111
- const REPO_ROOT = findRepoRoot(SERVER_DIR);
126
+ // Test-only override lets the stdio smoke exercise the published-package shape
127
+ // without copying the whole compiled server tree under node_modules.
128
+ const REPO_ROOT = process.env.PI_LENS_MCP_REPO_ROOT
129
+ ? path.resolve(process.env.PI_LENS_MCP_REPO_ROOT)
130
+ : findRepoRoot(SERVER_DIR);
112
131
  async function ensureReady(cwd) {
113
132
  const normalized = path.resolve(cwd);
114
133
  if (lspReadyCwds.has(normalized))
@@ -173,6 +192,32 @@ function getAutoSessionStatus() {
173
192
  // runs in THIS warm process (LSP-complete) instead of a cold hook process.
174
193
  // Responses go over the socket — never stdout — so the MCP stream is untouched.
175
194
  const IPC_PATH = ipcPathForCwd(DEFAULT_CWD);
195
+ /**
196
+ * #1273: the turn-end route runs the TARGET directory's configured test runner
197
+ * and spins up an LSP for it, from a client-supplied `cwd`. Every legitimate
198
+ * client derived the socket path from that same cwd, so a workspace check costs
199
+ * nothing legitimate — but without it, anything that can reach this endpoint
200
+ * (same-uid processes, a permissive-umask shared host, a Windows named pipe's
201
+ * more generous default DACL) can make the server execute an arbitrary
202
+ * directory's test suite. "Lint one file" and "run this directory's test suite"
203
+ * are different blast radii, so the guard is on turn-end and its ack.
204
+ */
205
+ function rejectForeignTurnCwd(route, cwd) {
206
+ const message = `${route} cwd outside this server's workspace: ${path.resolve(cwd)} not within ${DEFAULT_CWD}`;
207
+ console.error(`[pi-lens-mcp] rejected ${message}`);
208
+ return message;
209
+ }
210
+ function isWithinServerWorkspace(cwd) {
211
+ const target = path.resolve(cwd);
212
+ // `path.relative` applies the platform's own path semantics (including
213
+ // win32 case-insensitivity), which is what "inside the workspace" means
214
+ // here — do NOT hand-roll a case fold. An empty result means `target`
215
+ // IS `DEFAULT_CWD` (win32 can reach that via a pure case/drive-letter
216
+ // difference, since path.relative there is case-insensitive) and must
217
+ // be accepted, not rejected — do NOT reintroduce a `rel !== ""` guard.
218
+ const rel = path.relative(DEFAULT_CWD, target);
219
+ return !rel.startsWith("..") && !path.isAbsolute(rel);
220
+ }
176
221
  function startIpcServer() {
177
222
  // POSIX: a stale socket file blocks listen; remove it first. (Named pipes on
178
223
  // Windows don't need this.)
@@ -184,33 +229,77 @@ function startIpcServer() {
184
229
  // no stale socket — fine
185
230
  }
186
231
  }
232
+ const requestQueue = createWarmIpcRequestQueue();
187
233
  const ipc = net.createServer((socket) => {
188
234
  socket.setEncoding("utf8");
189
- let buffer = "";
190
- socket.on("data", (chunk) => {
191
- buffer += chunk;
192
- const newline = buffer.indexOf("\n");
193
- if (newline === -1)
194
- return;
195
- const line = buffer.slice(0, newline);
196
- void (async () => {
235
+ // One-shot per connection (#1219): clients write exactly one request and
236
+ // read one reply, so a handler that kept re-reading the same buffered
237
+ // line re-dispatched the request on stray bytes. The reader consumes the
238
+ // line and ignores anything after it.
239
+ socket.on("data", createWarmIpcLineReader((line) => {
240
+ void requestQueue.enqueue(async () => {
197
241
  try {
198
- // #535: the PostToolUse hook bin (mcp/analyze-cli.ts) already treats
199
- // ANY error response as "no usable warm server" and falls back to
200
- // its own cold, load-fresh-from-disk analysis path — so on a stale
201
- // warm build, replying with an error IS the fresh-fork behavior for
202
- // this channel, for free. No separate fresh-fork plumbing needed
203
- // here: the client-side fallback already loads current code.
204
242
  if (isWarmBuildStale()) {
205
- console.error("[pi-lens-mcp] warm analyze: build stale, replying error so the hook falls back cold");
206
- socket.end(`${JSON.stringify({ error: "warm build stale — falling back to cold analysis" })}\n`);
243
+ console.error("[pi-lens-mcp] warm request: build stale, replying with an error");
244
+ socket.end(`${JSON.stringify({ error: "warm build stale" })}\n`);
207
245
  return;
208
246
  }
209
- const req = JSON.parse(line);
247
+ const parsed = JSON.parse(line);
248
+ if (parsed.route === "turn-end-ack") {
249
+ if (parsed.version !== WARM_TURN_END_SCHEMA_VERSION ||
250
+ typeof parsed.deliveryId !==
251
+ "string") {
252
+ socket.end(`${JSON.stringify({ error: `turn-end ack schema ${parsed.version} != ${WARM_TURN_END_SCHEMA_VERSION}` })}\n`);
253
+ return;
254
+ }
255
+ const turnCwd = parsed.cwd ?? DEFAULT_CWD;
256
+ if (!isWithinServerWorkspace(turnCwd)) {
257
+ socket.end(`${JSON.stringify({ error: rejectForeignTurnCwd("turn-end-ack", turnCwd) })}\n`);
258
+ return;
259
+ }
260
+ const acknowledged = acknowledgeTurnEnd(turnCwd, parsed.deliveryId);
261
+ socket.end(`${JSON.stringify({
262
+ result: {
263
+ route: "turn-end-ack",
264
+ version: WARM_TURN_END_SCHEMA_VERSION,
265
+ acknowledged,
266
+ },
267
+ })}\n`);
268
+ return;
269
+ }
270
+ if (parsed.route === "turn-end") {
271
+ if (parsed.version !== WARM_TURN_END_SCHEMA_VERSION) {
272
+ socket.end(`${JSON.stringify({ error: `turn-end schema ${parsed.version} != ${WARM_TURN_END_SCHEMA_VERSION}` })}\n`);
273
+ return;
274
+ }
275
+ const turnCwd = parsed.cwd ?? DEFAULT_CWD;
276
+ if (!isWithinServerWorkspace(turnCwd)) {
277
+ socket.end(`${JSON.stringify({ error: rejectForeignTurnCwd("turn-end", turnCwd) })}\n`);
278
+ return;
279
+ }
280
+ // #1274: a client that already gave up (hook timeout, killed
281
+ // Claude Code) leaves a destroyed socket. Starting a heavy
282
+ // pass whose reply nobody can read is pure waste; the findings
283
+ // stay in the cache for the next Stop either way.
284
+ if (socket.destroyed) {
285
+ console.error(`[pi-lens-mcp] warm turn-end: client gone before the pass started (${turnCwd})`);
286
+ return;
287
+ }
288
+ console.error(`[pi-lens-mcp] warm turn-end: ${turnCwd}`);
289
+ await ensureReady(turnCwd);
290
+ const delivery = await runTurnEndForIpc(turnCwd);
291
+ const result = {
292
+ route: "turn-end",
293
+ version: WARM_TURN_END_SCHEMA_VERSION,
294
+ turnEnd: delivery.outcome.turnEnd,
295
+ tests: delivery.outcome.tests,
296
+ deliveryId: delivery.deliveryId,
297
+ };
298
+ socket.end(`${JSON.stringify({ result })}\n`);
299
+ return;
300
+ }
301
+ const req = parsed;
210
302
  console.error(`[pi-lens-mcp] warm analyze: ${req.file}`);
211
- // Warm = full LSP + an edit-detection path (register turn-state) +
212
- // review-graph maintenance (#536 — this is an in-process, long-lived
213
- // path, unlike the ephemeral `fresh` worker).
214
303
  const result = await analyzeFile(req.file, req.cwd, {
215
304
  registerTurnState: true,
216
305
  updateGraph: true,
@@ -220,8 +309,8 @@ function startIpcServer() {
220
309
  catch (err) {
221
310
  socket.end(`${JSON.stringify({ error: String(err) })}\n`);
222
311
  }
223
- })();
224
- });
312
+ });
313
+ }));
225
314
  socket.on("error", () => socket.destroy());
226
315
  });
227
316
  ipc.on("error", (err) => {
@@ -352,7 +441,7 @@ function schemaWithCwd(parameters) {
352
441
  ...(p.required ? { required: p.required } : {}),
353
442
  };
354
443
  }
355
- const TOOLS = [
444
+ const ALL_TOOLS = [
356
445
  {
357
446
  name: "pilens_analyze",
358
447
  description: "Run pi-lens's per-edit dispatch pipeline (LSP + linters + structural " +
@@ -377,7 +466,7 @@ const TOOLS = [
377
466
  },
378
467
  flags: {
379
468
  type: "object",
380
- description: "Optional pi-lens flag overrides for this run, e.g. {\"no-lsp\": true} to bench the non-LSP path.",
469
+ description: 'Optional pi-lens flag overrides for this run, e.g. {"no-lsp": true} to bench the non-LSP path.',
381
470
  },
382
471
  },
383
472
  required: ["file"],
@@ -397,8 +486,14 @@ const TOOLS = [
397
486
  inputSchema: {
398
487
  type: "object",
399
488
  properties: {
400
- limit: { type: "number", description: "Max reports to return (default 5)." },
401
- file: { type: "string", description: "Only reports whose path ends with this." },
489
+ limit: {
490
+ type: "number",
491
+ description: "Max reports to return (default 5).",
492
+ },
493
+ file: {
494
+ type: "string",
495
+ description: "Only reports whose path ends with this.",
496
+ },
402
497
  },
403
498
  },
404
499
  },
@@ -421,6 +516,13 @@ const TOOLS = [
421
516
  properties: {
422
517
  cwd: { type: "string" },
423
518
  maxFiles: { type: "number", description: "Cap files scanned." },
519
+ includeGenerated: {
520
+ type: "boolean",
521
+ description: "Scan WITHOUT the generated/artifact NAME-heuristic filter " +
522
+ "(lockfiles, gen.ts-style names, generated/ dirs, …). Default " +
523
+ "false. Use when a scan's 'excluded by generated-name " +
524
+ "heuristics' notice suggests a real file was skipped.",
525
+ },
424
526
  },
425
527
  },
426
528
  },
@@ -477,7 +579,9 @@ const TOOLS = [
477
579
  "(this path never calls LSP). `semantic.source` reports whether graph " +
478
580
  "data was used. Pass `blastRadius: true` for the cross-file blast radius " +
479
581
  "(transitive dependents as ranked file reads, read-only over the cached " +
480
- 'graph). `view: "compact"` returns a line-oriented text rendering ' +
582
+ "graph). Pass `callGraph: true` for bounded derived callers/callees; a " +
583
+ "cold or stale call-graph cache is reported explicitly, never as zero calls. " +
584
+ '`view: "compact"` returns a line-oriented text rendering ' +
481
585
  "(cheapest option) instead of JSON. An outline shows shape, not bodies.",
482
586
  inputSchema: {
483
587
  type: "object",
@@ -508,6 +612,14 @@ const TOOLS = [
508
612
  type: "number",
509
613
  description: "Max hops for the blast-radius walk (default 3). Only used with blastRadius.",
510
614
  },
615
+ callGraph: {
616
+ type: "boolean",
617
+ description: "Include bounded derived callers/callees from the cached FunctionCallGraph; cold or stale cache state is explicit.",
618
+ },
619
+ maxCallGraphEntries: {
620
+ type: "number",
621
+ description: "Per-direction cap for call-graph relations (default 20).",
622
+ },
511
623
  },
512
624
  required: ["file"],
513
625
  },
@@ -667,8 +779,11 @@ const TOOLS = [
667
779
  {
668
780
  name: "pilens_ast_grep_search",
669
781
  description: "Structural (AST) code search via ast-grep — match by code structure, not " +
670
- "text. Use meta-variables ($X) and AST context (e.g. 'console.log($MSG)', " +
671
- "'function $NAME() { $$$ }'). Far more precise than grep for code shapes.",
782
+ "text. Use complete patterns with arguments (e.g. 'console.log($MSG)', " +
783
+ "'function $NAME($$$ARGS) { $$$BODY }'), or use nodeKind to find every " +
784
+ "node of a known grammar kind. Metavariables do not match text inside " +
785
+ "quoted string literals; use an exact string or grep for wildcard text. " +
786
+ "This is the same schema and synthesis path as the pi tool.",
672
787
  inputSchema: schemaWithCwd(astGrepSearchTool.parameters),
673
788
  },
674
789
  {
@@ -695,6 +810,12 @@ const TOOLS = [
695
810
  inputSchema: schemaWithCwd(lspDiagnosticsTool.parameters),
696
811
  },
697
812
  ];
813
+ // #920: published packages cannot rebuild themselves safely because their
814
+ // build config is intentionally not shipped. Do not advertise the destructive
815
+ // capability to clients/subagent allowlists; callTool retains its own guard.
816
+ const TOOLS = canRebuildPiLens(REPO_ROOT)
817
+ ? ALL_TOOLS
818
+ : ALL_TOOLS.filter((tool) => tool.name !== "pilens_rebuild");
698
819
  function formatAnalyze(result, cwd, mode, servedBy) {
699
820
  // Surface the LSP outcome so a cold/indexing server's "0" is never silently
700
821
  // read as "clean" — a known limit on large projects (warm mode / re-run once
@@ -715,7 +836,10 @@ async function callTool(name, args) {
715
836
  if (name === "pilens_analyze") {
716
837
  const file = args.file;
717
838
  if (typeof file !== "string" || file.length === 0) {
718
- return { ...toolText("pilens_analyze requires a 'file' string."), isError: true };
839
+ return {
840
+ ...toolText("pilens_analyze requires a 'file' string."),
841
+ isError: true,
842
+ };
719
843
  }
720
844
  const cwd = typeof args.cwd === "string" ? args.cwd : DEFAULT_CWD;
721
845
  const requestedMode = args.mode === "fresh" ? "fresh" : "warm";
@@ -759,6 +883,16 @@ async function callTool(name, args) {
759
883
  }
760
884
  if (name === "pilens_rebuild") {
761
885
  const outcome = await runRebuild(REPO_ROOT, REBUILD_SCRIPT);
886
+ if (!outcome.packageManager) {
887
+ return {
888
+ ...toolText(outcome.output, {
889
+ ok: false,
890
+ script: outcome.script,
891
+ repoRoot: REPO_ROOT,
892
+ }),
893
+ isError: true,
894
+ };
895
+ }
762
896
  const runCmd = `${outcome.packageManager} run ${outcome.script}`;
763
897
  const headline = outcome.ok
764
898
  ? `✓ rebuild succeeded (${runCmd}, ${outcome.durationMs}ms). Fresh analyses now reflect the latest build.`
@@ -780,7 +914,8 @@ async function callTool(name, args) {
780
914
  const maxFiles = typeof args.maxFiles === "number" && Number.isFinite(args.maxFiles)
781
915
  ? Math.max(1, Math.floor(args.maxFiles))
782
916
  : undefined;
783
- const snapshot = await projectScan(cwd, maxFiles);
917
+ const includeGenerated = args.includeGenerated === true;
918
+ const snapshot = await projectScan(cwd, maxFiles, includeGenerated);
784
919
  // #747: a cwd at/above $HOME refuses to walk — say so instead of letting
785
920
  // "Scanned 0 file(s) → 0 diagnostics" read as a clean project.
786
921
  if (snapshot.unsafeRoot) {
@@ -806,6 +941,11 @@ async function callTool(name, args) {
806
941
  const truncationNotice = scanTruncationNotice(snapshot);
807
942
  if (truncationNotice)
808
943
  summaryLines.push(truncationNotice);
944
+ // #1107 phase 2: same "reached the seam but nothing rendered it" gap as
945
+ // #784's scanTruncationNotice, for the generated-name skip counters.
946
+ const skipNotice = generatedSkipNotice(snapshot);
947
+ if (skipNotice)
948
+ summaryLines.push(skipNotice);
809
949
  return toolText(summaryLines.join("\n"), {
810
950
  filesScanned: snapshot.filesScanned,
811
951
  runners: snapshot.runners,
@@ -815,6 +955,18 @@ async function callTool(name, args) {
815
955
  topFiles,
816
956
  sample: deduped.slice(0, 40),
817
957
  ...(snapshot.scanTruncated ? { scanTruncated: true } : {}),
958
+ ...(snapshot.treeSitterStatus
959
+ ? { treeSitterStatus: snapshot.treeSitterStatus }
960
+ : {}),
961
+ ...(snapshot.generatedFileSkips
962
+ ? { generatedFileSkips: snapshot.generatedFileSkips }
963
+ : {}),
964
+ ...(snapshot.generatedNameOnlySkips
965
+ ? { generatedNameOnlySkips: snapshot.generatedNameOnlySkips }
966
+ : {}),
967
+ ...(snapshot.generatedDirSkips
968
+ ? { generatedDirSkips: snapshot.generatedDirSkips }
969
+ : {}),
818
970
  });
819
971
  }
820
972
  if (name === "pilens_symbol_search") {
@@ -829,16 +981,22 @@ async function callTool(name, args) {
829
981
  ? args.paths.filter((p) => typeof p === "string")
830
982
  : undefined;
831
983
  const lang = typeof args.lang === "string" ? args.lang : undefined;
832
- const { available, results, hint, snapshotGeneratedAt } = await symbolSearch(query, cwd, limit, { paths, lang });
984
+ const { available, results, hint, unavailableReason, coverage, snapshotGeneratedAt, } = await symbolSearch(query, cwd, limit, { paths, lang });
833
985
  if (!available) {
834
- return toolText(hint ?? "No word index for this workspace yet — run pilens_session_start first.", { available: false, query, hint }, true);
986
+ return toolText(hint ??
987
+ "No word index for this workspace yet — run pilens_session_start first.", { available: false, query, hint, unavailableReason }, true);
835
988
  }
836
989
  const stalenessNote = graphStalenessNote(snapshotGeneratedAt, "Project snapshot");
837
990
  if (results.length === 0) {
838
- return toolText(`No files matched "${query}".` + (stalenessNote ? `\n\n${stalenessNote}` : ""), {
991
+ return toolText(`No files matched "${query}".` +
992
+ (coverage
993
+ ? `\nIndex covers ${coverage.files} files${coverage.truncated ? " (capped — results may be incomplete)" : ""}.`
994
+ : "") +
995
+ (stalenessNote ? `\n\n${stalenessNote}` : ""), {
839
996
  available: true,
840
997
  query,
841
998
  results: [],
999
+ coverage,
842
1000
  ...(stalenessNote ? { staleness: stalenessNote } : {}),
843
1001
  }, true);
844
1002
  }
@@ -848,6 +1006,11 @@ async function callTool(name, args) {
848
1006
  `(score ${result.score.toFixed(2)}, ${result.hits} hit(s), ` +
849
1007
  `line ${result.startLine})`),
850
1008
  ...(stalenessNote ? ["", stalenessNote] : []),
1009
+ ...(coverage
1010
+ ? [
1011
+ `Index covers ${coverage.files} files${coverage.truncated ? " (capped — results may be incomplete)" : ""}.`,
1012
+ ]
1013
+ : []),
851
1014
  ];
852
1015
  // Compact (unindented) JSON — matches the module_report / read_symbol
853
1016
  // convention (#517): an agent parses this payload, it doesn't read it
@@ -855,6 +1018,7 @@ async function callTool(name, args) {
855
1018
  // `read` block — startLine/endLine already derive offset/limit.
856
1019
  return toolText(lines.join("\n"), {
857
1020
  query,
1021
+ coverage,
858
1022
  results: results.map((result) => {
859
1023
  const relFile = path.relative(cwd, result.file);
860
1024
  return {
@@ -886,12 +1050,21 @@ async function callTool(name, args) {
886
1050
  Number.isFinite(args.blastRadiusDepth)
887
1051
  ? Math.max(1, Math.floor(args.blastRadiusDepth))
888
1052
  : undefined;
889
- const view = args.view === "summary" || args.view === "compact" ? args.view : undefined;
1053
+ const callGraph = args.callGraph === true;
1054
+ const maxCallGraphEntries = typeof args.maxCallGraphEntries === "number" &&
1055
+ Number.isFinite(args.maxCallGraphEntries)
1056
+ ? Math.max(1, Math.floor(args.maxCallGraphEntries))
1057
+ : undefined;
1058
+ const view = args.view === "summary" || args.view === "compact"
1059
+ ? args.view
1060
+ : undefined;
890
1061
  const focus = typeof args.focus === "string" ? args.focus : undefined;
891
1062
  const report = await moduleReport(file, cwd, {
892
1063
  maxRefsPerSymbol,
893
1064
  blastRadius,
894
1065
  blastRadiusDepth,
1066
+ callGraph,
1067
+ maxCallGraphEntries,
895
1068
  view,
896
1069
  focus,
897
1070
  });
@@ -912,14 +1085,18 @@ async function callTool(name, args) {
912
1085
  content: [
913
1086
  {
914
1087
  type: "text",
915
- text: graphStaleness ? `${compactText}\n\n${graphStaleness}` : compactText,
1088
+ text: graphStaleness
1089
+ ? `${compactText}\n\n${graphStaleness}`
1090
+ : compactText,
916
1091
  },
917
1092
  ],
918
1093
  };
919
1094
  }
920
1095
  // Compact (unindented) JSON — matches the pi tool's mirror (#512); an
921
1096
  // agent parses this payload, it doesn't read it formatted.
922
- return toolText(summary, graphStaleness ? { ...report, graphStalenessNote: graphStaleness } : report, true);
1097
+ return toolText(summary, graphStaleness
1098
+ ? { ...report, graphStalenessNote: graphStaleness }
1099
+ : report, true);
923
1100
  }
924
1101
  if (name === "pilens_project_report") {
925
1102
  const cwd = typeof args.cwd === "string" ? args.cwd : DEFAULT_CWD;
@@ -944,7 +1121,9 @@ async function callTool(name, args) {
944
1121
  content: [
945
1122
  {
946
1123
  type: "text",
947
- text: graphStaleness ? `${compactText}\n\n${graphStaleness}` : compactText,
1124
+ text: graphStaleness
1125
+ ? `${compactText}\n\n${graphStaleness}`
1126
+ : compactText,
948
1127
  },
949
1128
  ],
950
1129
  };
@@ -953,7 +1132,9 @@ async function callTool(name, args) {
953
1132
  `${report.entryPoints?.length ?? 0} entry point(s), ` +
954
1133
  `${report.riskHotspots?.length ?? 0} risk hotspot(s)` +
955
1134
  (graphStaleness ? `\n\n${graphStaleness}` : "");
956
- return toolText(summary, graphStaleness ? { ...report, graphStalenessNote: graphStaleness } : report, true);
1135
+ return toolText(summary, graphStaleness
1136
+ ? { ...report, graphStalenessNote: graphStaleness }
1137
+ : report, true);
957
1138
  }
958
1139
  if (name === "pilens_read_symbol") {
959
1140
  const file = typeof args.file === "string" ? args.file : "";
@@ -984,13 +1165,20 @@ async function callTool(name, args) {
984
1165
  ? ` (${result.ambiguous.count} matches — returned the ${result.kind}; pass \`kind\` to disambiguate: ${result.ambiguous.kinds.join(", ")})`
985
1166
  : "";
986
1167
  const header = `${result.kind} ${result.name}${ambiguityNote}${sigSuffix} ${path.relative(cwd, result.path)}:${result.startLine}-${result.endLine}`;
987
- return { content: [{ type: "text", text: `${header}\n\n${result.source ?? ""}` }] };
1168
+ return {
1169
+ content: [
1170
+ { type: "text", text: `${header}\n\n${result.source ?? ""}` },
1171
+ ],
1172
+ };
988
1173
  }
989
1174
  if (name === "pilens_read_enclosing") {
990
1175
  const file = typeof args.file === "string" ? args.file : "";
991
1176
  const line = typeof args.line === "number" ? args.line : Number.NaN;
992
1177
  if (!file.trim() || !Number.isFinite(line)) {
993
- return { ...toolText("Provide `file` and a numeric `line`."), isError: true };
1178
+ return {
1179
+ ...toolText("Provide `file` and a numeric `line`."),
1180
+ isError: true,
1181
+ };
994
1182
  }
995
1183
  const cwd = typeof args.cwd === "string" ? args.cwd : DEFAULT_CWD;
996
1184
  const kinds = Array.isArray(args.kinds)
@@ -1034,19 +1222,39 @@ async function callTool(name, args) {
1034
1222
  ? `${result.startLine}-${result.endLine} (partial of ${result.enclosingStartLine}-${result.enclosingEndLine})`
1035
1223
  : `${result.startLine}-${result.endLine}`;
1036
1224
  const header = `${result.kind} ${result.name} ${path.relative(cwd, result.path)}:${range}`;
1037
- return { content: [{ type: "text", text: `${header}\n\n${result.source ?? ""}` }] };
1225
+ return {
1226
+ content: [
1227
+ { type: "text", text: `${header}\n\n${result.source ?? ""}` },
1228
+ ],
1229
+ };
1038
1230
  }
1039
1231
  if (name === "pilens_health") {
1040
- const { aliveClients, servers } = lspStatus();
1232
+ const { aliveClients, servers, brokenServers } = lspStatus();
1041
1233
  const last = recentLatency(1)[0];
1042
1234
  const stats = diagnosticStats();
1043
1235
  const autoSession = getAutoSessionStatus();
1236
+ const treeSitter = treeSitterRuntimeStatus();
1237
+ const degradations = getDegradationSummary();
1238
+ // #1272, following the #544 precedent: the Stop hook is a separate,
1239
+ // short-lived process, so a turn-end it skipped left no trace here at all
1240
+ // — a dead integration looked exactly like a clean turn, forever. The
1241
+ // hook records its outcome per workspace; this is where it becomes
1242
+ // visible without `claude --debug` log spelunking.
1243
+ const turnEnd = readTurnEndStatus(DEFAULT_CWD) ?? null;
1044
1244
  // #620: best-effort — a footprint read failure must never break the rest
1045
1245
  // of pilens_health's (much older, more load-bearing) reporting.
1046
1246
  const footprint = await resourceFootprint().catch(() => null);
1047
1247
  const lines = [
1248
+ treeSitter.wasmAborted
1249
+ ? `Tree-sitter: DEGRADED — WASM runtime aborted${treeSitter.abortedAt ? ` at ${treeSitter.abortedAt}` : ""}; restart this server to recover`
1250
+ : "Tree-sitter: available",
1048
1251
  `LSP: ${aliveClients} alive client(s)`,
1049
- ...servers.map((server) => ` ${server.connected ? "✓" : "✗"} ${server.serverId} (${server.root})`),
1252
+ ...servers.flatMap((server) => [
1253
+ ` ${server.connected ? "✓" : "✗"} ${server.serverId} (${server.root})`,
1254
+ ...server.pullFailureHistory.slice(-1).map((failure) => ` ⚠ diagnostics pull failed: ${failure.method}${failure.code !== undefined ? ` (${failure.code})` : ""} — ${failure.message.length > 200 ? `${failure.message.slice(0, 200)}…` : failure.message}`),
1255
+ ]),
1256
+ ...renderLspBrokenStatusLines(brokenServers),
1257
+ ...renderDegradationLines(degradations),
1050
1258
  last
1051
1259
  ? `Last dispatch: ${path.basename(last.filePath)} — ${last.totalDurationMs}ms, ${last.totalDiagnostics} diagnostic(s)`
1052
1260
  : "Last dispatch: none yet",
@@ -1054,6 +1262,11 @@ async function callTool(name, args) {
1054
1262
  autoSession
1055
1263
  ? `Auto session_start: ${autoSession.succeeded ? "succeeded" : autoSession.error ? "FAILED" : autoSession.attempted ? "in progress" : "not yet attempted"}${autoSession.firedAt ? ` (fired ${autoSession.firedAt})` : ""}${autoSession.error ? ` — ${autoSession.error}` : ""}`
1056
1264
  : "Auto session_start: disabled (PI_LENS_MCP_AUTO_SESSION not set)",
1265
+ turnEnd
1266
+ ? `Stop-hook turn-end: ${turnEnd.ran} ran · ${turnEnd.skipped} skipped` +
1267
+ `${turnEnd.lastRunAt ? ` (last ran ${turnEnd.lastRunAt})` : ""}` +
1268
+ `${turnEnd.lastSkipReason ? ` — last skip: ${turnEnd.lastSkipReason}${turnEnd.lastSkipAt ? ` at ${turnEnd.lastSkipAt}` : ""}` : ""}`
1269
+ : "Stop-hook turn-end: no activity recorded (hook not installed, or no Stop yet)",
1057
1270
  footprint
1058
1271
  ? `Resource footprint: ${footprint.instanceCount} pi-lens instance(s) · ` +
1059
1272
  `${(footprint.totalRssBytes / 1024 / 1024).toFixed(0)}MB RSS · ` +
@@ -1064,6 +1277,7 @@ async function callTool(name, args) {
1064
1277
  return toolText(lines.join("\n"), {
1065
1278
  aliveClients,
1066
1279
  servers,
1280
+ brokenServers,
1067
1281
  lastDispatch: last
1068
1282
  ? {
1069
1283
  filePath: last.filePath,
@@ -1077,7 +1291,10 @@ async function callTool(name, args) {
1077
1291
  unresolved: stats.totalUnresolved,
1078
1292
  },
1079
1293
  autoSession,
1294
+ treeSitter,
1295
+ turnEnd,
1080
1296
  resourceFootprint: footprint,
1297
+ degradations,
1081
1298
  });
1082
1299
  }
1083
1300
  if (name === "pilens_diagnostics") {
@@ -1131,7 +1348,9 @@ async function callTool(name, args) {
1131
1348
  }
1132
1349
  if (name === "pilens_ast_grep_search" || name === "pilens_ast_grep_replace") {
1133
1350
  const cwd = typeof args.cwd === "string" ? args.cwd : DEFAULT_CWD;
1134
- const tool = name === "pilens_ast_grep_search" ? astGrepSearchTool : astGrepReplaceTool;
1351
+ const tool = name === "pilens_ast_grep_search"
1352
+ ? astGrepSearchTool
1353
+ : astGrepReplaceTool;
1135
1354
  const out = (await tool.execute("mcp", args, new AbortController().signal, undefined, { cwd }));
1136
1355
  return { content: out.content };
1137
1356
  }
@@ -1215,7 +1434,10 @@ function withStaleWarning(result) {
1215
1434
  ...result,
1216
1435
  content: [
1217
1436
  ...result.content.slice(0, -1),
1218
- { ...last, text: `${last.text}\n\nwarmCodeStale: true\n${STALE_WARN_ONLY}` },
1437
+ {
1438
+ ...last,
1439
+ text: `${last.text}\n\nwarmCodeStale: true\n${STALE_WARN_ONLY}`,
1440
+ },
1219
1441
  ],
1220
1442
  };
1221
1443
  }