pi-lens 3.8.50 → 3.8.51

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 (472) hide show
  1. package/CHANGELOG.md +101 -1
  2. package/dist/clients/actionable-warnings-logger.js +51 -0
  3. package/dist/clients/actionable-warnings.js +542 -0
  4. package/dist/clients/agent-behavior-client.js +119 -0
  5. package/dist/clients/ast-grep-client.js +391 -0
  6. package/dist/clients/ast-grep-parser.js +86 -0
  7. package/dist/clients/ast-grep-rule-manager.js +91 -0
  8. package/dist/clients/ast-grep-tool-logger.js +150 -0
  9. package/dist/clients/ast-grep-types.js +9 -0
  10. package/dist/clients/ast-grep-yaml-synth.js +103 -0
  11. package/dist/clients/bash-file-access.js +360 -0
  12. package/dist/clients/biome-client.js +183 -0
  13. package/dist/clients/bootstrap.js +40 -0
  14. package/dist/clients/cache/rule-cache.js +75 -0
  15. package/dist/clients/cache-manager.js +273 -0
  16. package/dist/clients/call-graph.js +325 -0
  17. package/dist/clients/cascade-format.js +21 -0
  18. package/dist/clients/cascade-logger.js +25 -0
  19. package/dist/clients/cascade-types.js +1 -0
  20. package/dist/clients/code-quality-warnings.js +189 -0
  21. package/dist/clients/codebase-model.js +136 -0
  22. package/dist/clients/complexity-client.js +676 -0
  23. package/dist/clients/dependency-checker.js +359 -0
  24. package/dist/clients/diagnostic-logger.js +87 -0
  25. package/dist/clients/diagnostic-tracker.js +105 -0
  26. package/dist/clients/dispatch/diagnostic-taxonomy.js +66 -0
  27. package/dist/clients/dispatch/dispatcher.js +758 -0
  28. package/dist/clients/dispatch/fact-provider-types.js +1 -0
  29. package/dist/clients/dispatch/fact-rule-runner.js +17 -0
  30. package/dist/clients/dispatch/fact-runner.js +19 -0
  31. package/dist/clients/dispatch/fact-scheduler.js +68 -0
  32. package/dist/clients/dispatch/fact-store.js +47 -0
  33. package/dist/clients/dispatch/facts/comment-facts.js +35 -0
  34. package/dist/clients/dispatch/facts/file-content.js +19 -0
  35. package/dist/clients/dispatch/facts/function-facts.js +204 -0
  36. package/dist/clients/dispatch/facts/import-facts.js +163 -0
  37. package/dist/clients/dispatch/facts/try-catch-facts.js +112 -0
  38. package/dist/clients/dispatch/integration.js +1054 -0
  39. package/dist/clients/dispatch/plan.js +295 -0
  40. package/dist/clients/dispatch/priorities.js +21 -0
  41. package/dist/clients/dispatch/rules/async-noise.js +39 -0
  42. package/dist/clients/dispatch/rules/async-unnecessary-wrapper.js +27 -0
  43. package/dist/clients/dispatch/rules/error-obscuring.js +31 -0
  44. package/dist/clients/dispatch/rules/error-swallowing.js +28 -0
  45. package/dist/clients/dispatch/rules/high-complexity.js +36 -0
  46. package/dist/clients/dispatch/rules/high-fan-out.js +43 -0
  47. package/dist/clients/dispatch/rules/missing-error-propagation.js +62 -0
  48. package/dist/clients/dispatch/rules/pass-through-wrappers.js +38 -0
  49. package/dist/clients/dispatch/rules/placeholder-comments.js +40 -0
  50. package/dist/clients/dispatch/rules/quality-rules.js +297 -0
  51. package/dist/clients/dispatch/rules/sonar-rules.js +493 -0
  52. package/dist/clients/dispatch/rules/unsafe-boundary.js +95 -0
  53. package/dist/clients/dispatch/runner-context.js +52 -0
  54. package/dist/clients/dispatch/runners/actionlint.js +110 -0
  55. package/dist/clients/dispatch/runners/ast-grep-napi.js +302 -0
  56. package/dist/clients/dispatch/runners/biome-check.js +120 -0
  57. package/dist/clients/dispatch/runners/biome.js +69 -0
  58. package/dist/clients/dispatch/runners/cpp-check.js +225 -0
  59. package/dist/clients/dispatch/runners/credo.js +71 -0
  60. package/dist/clients/dispatch/runners/dart-analyze.js +202 -0
  61. package/dist/clients/dispatch/runners/detekt.js +165 -0
  62. package/dist/clients/dispatch/runners/dotnet-build.js +159 -0
  63. package/dist/clients/dispatch/runners/elixir-check.js +136 -0
  64. package/dist/clients/dispatch/runners/eslint.js +126 -0
  65. package/dist/clients/dispatch/runners/fact-rules.js +30 -0
  66. package/dist/clients/dispatch/runners/fish-indent.js +69 -0
  67. package/dist/clients/dispatch/runners/gleam-check.js +87 -0
  68. package/dist/clients/dispatch/runners/go-vet.js +50 -0
  69. package/dist/clients/dispatch/runners/golangci-lint.js +118 -0
  70. package/dist/clients/dispatch/runners/hadolint.js +71 -0
  71. package/dist/clients/dispatch/runners/htmlhint.js +90 -0
  72. package/dist/clients/dispatch/runners/index.js +105 -0
  73. package/dist/clients/dispatch/runners/javac.js +76 -0
  74. package/dist/clients/dispatch/runners/ktlint.js +136 -0
  75. package/dist/clients/dispatch/runners/lsp.js +217 -0
  76. package/dist/clients/dispatch/runners/markdownlint.js +113 -0
  77. package/dist/clients/dispatch/runners/mypy.js +68 -0
  78. package/dist/clients/dispatch/runners/oxlint.js +178 -0
  79. package/dist/clients/dispatch/runners/php-lint.js +62 -0
  80. package/dist/clients/dispatch/runners/phpstan.js +73 -0
  81. package/dist/clients/dispatch/runners/prisma-validate.js +62 -0
  82. package/dist/clients/dispatch/runners/psscriptanalyzer.js +149 -0
  83. package/dist/clients/dispatch/runners/pyright.js +115 -0
  84. package/dist/clients/dispatch/runners/python-slop.js +106 -0
  85. package/dist/clients/dispatch/runners/rubocop.js +90 -0
  86. package/dist/clients/dispatch/runners/ruff.js +94 -0
  87. package/dist/clients/dispatch/runners/rust-clippy.js +149 -0
  88. package/dist/clients/dispatch/runners/semgrep.js +197 -0
  89. package/dist/clients/dispatch/runners/shellcheck.js +160 -0
  90. package/dist/clients/dispatch/runners/shfmt.js +86 -0
  91. package/dist/clients/dispatch/runners/spellcheck.js +111 -0
  92. package/dist/clients/dispatch/runners/sqlfluff.js +146 -0
  93. package/dist/clients/dispatch/runners/stylelint.js +116 -0
  94. package/dist/clients/dispatch/runners/swiftlint.js +144 -0
  95. package/dist/clients/dispatch/runners/taplo.js +61 -0
  96. package/dist/clients/dispatch/runners/tflint.js +68 -0
  97. package/dist/clients/dispatch/runners/tree-sitter.js +690 -0
  98. package/dist/clients/dispatch/runners/ts-lsp.js +109 -0
  99. package/dist/clients/dispatch/runners/utils/diagnostic-parsers.js +149 -0
  100. package/dist/clients/dispatch/runners/utils/lazy-installer.js +38 -0
  101. package/dist/clients/dispatch/runners/utils/runner-helpers.js +385 -0
  102. package/dist/clients/dispatch/runners/utils.js +36 -0
  103. package/dist/clients/dispatch/runners/vale.js +105 -0
  104. package/dist/clients/dispatch/runners/yaml-rule-parser.js +167 -0
  105. package/dist/clients/dispatch/runners/yamllint.js +70 -0
  106. package/dist/clients/dispatch/runners/zig-check.js +87 -0
  107. package/dist/clients/dispatch/tool-profile.js +30 -0
  108. package/dist/clients/dispatch/types.js +13 -0
  109. package/dist/clients/dispatch/utils/format-utils.js +45 -0
  110. package/dist/clients/dispatch/utils/lsp-diagnostics.js +28 -0
  111. package/{clients/env-utils.ts → dist/clients/env-utils.js} +9 -9
  112. package/dist/clients/event-loop-monitor.js +56 -0
  113. package/dist/clients/feature-hints.js +49 -0
  114. package/dist/clients/file-kinds.js +401 -0
  115. package/dist/clients/file-role.js +102 -0
  116. package/dist/clients/file-time.js +155 -0
  117. package/dist/clients/file-utils.js +454 -0
  118. package/dist/clients/fix-worklog.js +87 -0
  119. package/dist/clients/format-service.js +184 -0
  120. package/dist/clients/formatters.js +917 -0
  121. package/dist/clients/generated-artifacts.js +146 -0
  122. package/dist/clients/git-guard.js +32 -0
  123. package/dist/clients/gitleaks-client.js +317 -0
  124. package/dist/clients/go-client.js +84 -0
  125. package/dist/clients/govulncheck-client.js +378 -0
  126. package/dist/clients/indent-retarget.js +84 -0
  127. package/dist/clients/installer/index.js +1997 -0
  128. package/dist/clients/jscpd-client.js +282 -0
  129. package/dist/clients/knip-client.js +371 -0
  130. package/dist/clients/language-policy.js +232 -0
  131. package/dist/clients/language-profile.js +329 -0
  132. package/dist/clients/latency-logger.js +44 -0
  133. package/dist/clients/lens-config.js +135 -0
  134. package/dist/clients/lens-engine.js +91 -0
  135. package/dist/clients/lens-events.js +70 -0
  136. package/dist/clients/log-cleanup.js +203 -0
  137. package/dist/clients/lsp/aggregation.js +83 -0
  138. package/dist/clients/lsp/client.js +1053 -0
  139. package/dist/clients/lsp/config.js +165 -0
  140. package/dist/clients/lsp/edits.js +228 -0
  141. package/dist/clients/lsp/index.js +1405 -0
  142. package/dist/clients/lsp/interactive-install.js +355 -0
  143. package/dist/clients/lsp/language.js +175 -0
  144. package/dist/clients/lsp/launch.js +755 -0
  145. package/{clients/lsp/lsp-index.ts → dist/clients/lsp/lsp-index.js} +1 -2
  146. package/dist/clients/lsp/path-utils.js +5 -0
  147. package/dist/clients/lsp/server-strategies.js +59 -0
  148. package/dist/clients/lsp/server.js +1600 -0
  149. package/dist/clients/mcp/analyze.js +170 -0
  150. package/dist/clients/mcp/host-shim.js +27 -0
  151. package/dist/clients/mcp/ipc.js +84 -0
  152. package/dist/clients/mcp/review.js +115 -0
  153. package/dist/clients/mcp/session.js +136 -0
  154. package/dist/clients/metrics-client.js +108 -0
  155. package/dist/clients/metrics-history.js +388 -0
  156. package/dist/clients/oldtext-autopatch.js +127 -0
  157. package/dist/clients/package-root.js +38 -0
  158. package/dist/clients/partial-edit-apply.js +43 -0
  159. package/dist/clients/path-utils.js +205 -0
  160. package/dist/clients/pipeline.js +835 -0
  161. package/dist/clients/production-readiness.js +517 -0
  162. package/dist/clients/project-changes.js +68 -0
  163. package/dist/clients/project-conventions.js +177 -0
  164. package/dist/clients/project-diagnostics/cache.js +51 -0
  165. package/dist/clients/project-diagnostics/runner-adapters/knip.js +44 -0
  166. package/dist/clients/project-diagnostics/scanner.js +159 -0
  167. package/dist/clients/project-diagnostics/types.js +1 -0
  168. package/dist/clients/project-metadata.js +710 -0
  169. package/dist/clients/project-scan-policy.js +49 -0
  170. package/dist/clients/project-snapshot.js +137 -0
  171. package/dist/clients/read-expansion.js +289 -0
  172. package/dist/clients/read-guard-logger.js +77 -0
  173. package/dist/clients/read-guard-tool-lines.js +1002 -0
  174. package/dist/clients/read-guard.js +855 -0
  175. package/dist/clients/reverse-deps.js +182 -0
  176. package/dist/clients/review-graph/builder.js +984 -0
  177. package/dist/clients/review-graph/format.js +33 -0
  178. package/dist/clients/review-graph/query.js +166 -0
  179. package/dist/clients/review-graph/service.js +44 -0
  180. package/dist/clients/review-graph/types.js +1 -0
  181. package/dist/clients/review-graph/workspace-modules.js +445 -0
  182. package/dist/clients/ruff-client.js +159 -0
  183. package/dist/clients/rules-scanner.js +118 -0
  184. package/dist/clients/runner-tracker.js +153 -0
  185. package/dist/clients/runtime-agent-end.js +227 -0
  186. package/dist/clients/runtime-config.js +73 -0
  187. package/dist/clients/runtime-context.js +42 -0
  188. package/dist/clients/runtime-coordinator.js +365 -0
  189. package/dist/clients/runtime-session.js +868 -0
  190. package/dist/clients/runtime-tool-result.js +509 -0
  191. package/dist/clients/runtime-turn.js +602 -0
  192. package/dist/clients/rust-client.js +83 -0
  193. package/dist/clients/safe-spawn.js +301 -0
  194. package/dist/clients/sanitize.js +291 -0
  195. package/dist/clients/scan-utils.js +80 -0
  196. package/dist/clients/search-read-registration.js +66 -0
  197. package/dist/clients/secrets-scanner.js +181 -0
  198. package/dist/clients/semgrep-config.js +157 -0
  199. package/dist/clients/session-state-store.js +97 -0
  200. package/dist/clients/session-summary.js +37 -0
  201. package/dist/clients/sg-runner.js +496 -0
  202. package/dist/clients/source-filter.js +274 -0
  203. package/dist/clients/source-groups.js +96 -0
  204. package/dist/clients/startup-scan.js +255 -0
  205. package/dist/clients/startup-timing.js +32 -0
  206. package/dist/clients/symbol-types.js +5 -0
  207. package/dist/clients/test-runner-client.js +766 -0
  208. package/dist/clients/todo-scanner.js +198 -0
  209. package/{clients/tool-event.ts → dist/clients/tool-event.js} +4 -9
  210. package/dist/clients/tool-policy.js +1856 -0
  211. package/dist/clients/tree-sitter-cache.js +244 -0
  212. package/dist/clients/tree-sitter-client.js +1235 -0
  213. package/dist/clients/tree-sitter-fixer.js +127 -0
  214. package/dist/clients/tree-sitter-logger.js +25 -0
  215. package/dist/clients/tree-sitter-navigator.js +269 -0
  216. package/dist/clients/tree-sitter-query-loader.js +428 -0
  217. package/{clients/tree-sitter-symbol-extractor.ts → dist/clients/tree-sitter-symbol-extractor.js} +262 -299
  218. package/dist/clients/ts-service.js +130 -0
  219. package/dist/clients/type-coverage-client.js +128 -0
  220. package/dist/clients/types.js +11 -0
  221. package/dist/clients/typescript-client.js +509 -0
  222. package/dist/clients/widget-state.js +533 -0
  223. package/dist/clients/word-index.js +250 -0
  224. package/dist/commands/booboo.js +1412 -0
  225. package/dist/i18n.js +61 -0
  226. package/dist/index.js +1743 -0
  227. package/dist/mcp/analyze-cli.js +105 -0
  228. package/dist/mcp/server.js +745 -0
  229. package/dist/mcp/worker.js +46 -0
  230. package/{tools → dist/tools}/ast-grep-replace.js +15 -3
  231. package/{tools → dist/tools}/ast-grep-search.js +17 -3
  232. package/dist/tools/lens-diagnostics.js +469 -0
  233. package/{tools → dist/tools}/lsp-navigation.js +92 -8
  234. package/package.json +12 -11
  235. package/rules/ast-grep-rules/rules/nested-ternary-js.yml +2 -0
  236. package/rules/ast-grep-rules/rules/nested-ternary.yml +2 -0
  237. package/rules/ast-grep-rules/rules/no-constant-condition-js.yml +11 -18
  238. package/rules/ast-grep-rules/rules/no-constant-condition.yml +11 -18
  239. package/rules/ast-grep-rules/rules/switch-without-default.yml +3 -0
  240. package/rules/ast-grep-rules/{rules → rules-disabled}/constructor-super-js.yml +2 -0
  241. package/rules/ast-grep-rules/{rules → rules-disabled}/constructor-super.yml +3 -0
  242. package/rules/ast-grep-rules/{rules → rules-disabled}/no-hardcoded-secrets-js.yml +3 -0
  243. package/rules/ast-grep-rules/{rules → rules-disabled}/no-hardcoded-secrets.yml +3 -0
  244. package/rules/ast-grep-rules/{rules → rules-disabled}/no-process-env.yml +3 -0
  245. package/rules/ast-grep-rules/{rules → rules-disabled}/unchecked-sync-fs-js.yml +3 -0
  246. package/rules/ast-grep-rules/{rules → rules-disabled}/unchecked-sync-fs.yml +3 -0
  247. package/rules/ast-grep-rules/slop-patterns.yml +26 -35
  248. package/rules/rule-catalog.json +3 -5
  249. package/rules/tree-sitter-queries/java/{infinite-loop.yml → infinite-loop-java.yml} +1 -1
  250. package/scripts/analyze-pi-lens-logs.mjs +211 -7
  251. package/skills/write-ast-grep-rule/SKILL.md +66 -14
  252. package/clients/actionable-warnings-logger.ts +0 -65
  253. package/clients/actionable-warnings.ts +0 -764
  254. package/clients/agent-behavior-client.ts +0 -164
  255. package/clients/ast-grep-client.ts +0 -603
  256. package/clients/ast-grep-parser.ts +0 -130
  257. package/clients/ast-grep-rule-manager.ts +0 -104
  258. package/clients/ast-grep-tool-logger.ts +0 -174
  259. package/clients/ast-grep-types.ts +0 -121
  260. package/clients/ast-grep-yaml-synth.ts +0 -122
  261. package/clients/bash-file-access.ts +0 -220
  262. package/clients/biome-client.ts +0 -657
  263. package/clients/bootstrap.ts +0 -93
  264. package/clients/cache/rule-cache.ts +0 -110
  265. package/clients/cache-manager.ts +0 -361
  266. package/clients/call-graph.ts +0 -444
  267. package/clients/cascade-format.ts +0 -27
  268. package/clients/cascade-logger.ts +0 -78
  269. package/clients/cascade-types.ts +0 -36
  270. package/clients/code-quality-warnings.ts +0 -313
  271. package/clients/codebase-model.ts +0 -194
  272. package/clients/complexity-client.ts +0 -922
  273. package/clients/dependency-checker.ts +0 -466
  274. package/clients/diagnostic-logger.ts +0 -151
  275. package/clients/diagnostic-tracker.ts +0 -163
  276. package/clients/dispatch/diagnostic-taxonomy.ts +0 -81
  277. package/clients/dispatch/dispatcher.ts +0 -985
  278. package/clients/dispatch/fact-provider-types.ts +0 -22
  279. package/clients/dispatch/fact-rule-runner.ts +0 -22
  280. package/clients/dispatch/fact-runner.ts +0 -28
  281. package/clients/dispatch/fact-scheduler.ts +0 -79
  282. package/clients/dispatch/fact-store.ts +0 -65
  283. package/clients/dispatch/facts/comment-facts.ts +0 -59
  284. package/clients/dispatch/facts/file-content.ts +0 -20
  285. package/clients/dispatch/facts/function-facts.ts +0 -256
  286. package/clients/dispatch/facts/import-facts.ts +0 -218
  287. package/clients/dispatch/facts/try-catch-facts.ts +0 -177
  288. package/clients/dispatch/integration.ts +0 -1326
  289. package/clients/dispatch/plan.ts +0 -329
  290. package/clients/dispatch/priorities.ts +0 -21
  291. package/clients/dispatch/rules/async-noise.ts +0 -50
  292. package/clients/dispatch/rules/async-unnecessary-wrapper.ts +0 -35
  293. package/clients/dispatch/rules/error-obscuring.ts +0 -40
  294. package/clients/dispatch/rules/error-swallowing.ts +0 -35
  295. package/clients/dispatch/rules/high-complexity.ts +0 -44
  296. package/clients/dispatch/rules/high-fan-out.ts +0 -55
  297. package/clients/dispatch/rules/missing-error-propagation.ts +0 -71
  298. package/clients/dispatch/rules/pass-through-wrappers.ts +0 -52
  299. package/clients/dispatch/rules/placeholder-comments.ts +0 -47
  300. package/clients/dispatch/rules/quality-rules.ts +0 -375
  301. package/clients/dispatch/rules/sonar-rules.ts +0 -508
  302. package/clients/dispatch/rules/unsafe-boundary.ts +0 -104
  303. package/clients/dispatch/runner-context.ts +0 -61
  304. package/clients/dispatch/runners/actionlint.ts +0 -145
  305. package/clients/dispatch/runners/ast-grep-napi.ts +0 -576
  306. package/clients/dispatch/runners/biome-check.ts +0 -166
  307. package/clients/dispatch/runners/biome.ts +0 -86
  308. package/clients/dispatch/runners/cpp-check.ts +0 -267
  309. package/clients/dispatch/runners/credo.ts +0 -106
  310. package/clients/dispatch/runners/dart-analyze.ts +0 -226
  311. package/clients/dispatch/runners/detekt.ts +0 -192
  312. package/clients/dispatch/runners/dotnet-build.ts +0 -195
  313. package/clients/dispatch/runners/elixir-check.ts +0 -151
  314. package/clients/dispatch/runners/eslint.ts +0 -177
  315. package/clients/dispatch/runners/fact-rules.ts +0 -44
  316. package/clients/dispatch/runners/fish-indent.ts +0 -83
  317. package/clients/dispatch/runners/gleam-check.ts +0 -108
  318. package/clients/dispatch/runners/go-vet.ts +0 -66
  319. package/clients/dispatch/runners/golangci-lint.ts +0 -175
  320. package/clients/dispatch/runners/hadolint.ts +0 -103
  321. package/clients/dispatch/runners/htmlhint.ts +0 -118
  322. package/clients/dispatch/runners/index.ts +0 -108
  323. package/clients/dispatch/runners/javac.ts +0 -99
  324. package/clients/dispatch/runners/ktlint.ts +0 -173
  325. package/clients/dispatch/runners/lsp.ts +0 -243
  326. package/clients/dispatch/runners/markdownlint.ts +0 -132
  327. package/clients/dispatch/runners/mypy.ts +0 -89
  328. package/clients/dispatch/runners/oxlint.ts +0 -214
  329. package/clients/dispatch/runners/php-lint.ts +0 -82
  330. package/clients/dispatch/runners/phpstan.ts +0 -113
  331. package/clients/dispatch/runners/prisma-validate.ts +0 -89
  332. package/clients/dispatch/runners/psscriptanalyzer.ts +0 -177
  333. package/clients/dispatch/runners/pyright.ts +0 -143
  334. package/clients/dispatch/runners/python-slop.ts +0 -133
  335. package/clients/dispatch/runners/rubocop.ts +0 -144
  336. package/clients/dispatch/runners/ruff.ts +0 -133
  337. package/clients/dispatch/runners/rust-clippy.ts +0 -204
  338. package/clients/dispatch/runners/semgrep.ts +0 -271
  339. package/clients/dispatch/runners/shellcheck.ts +0 -195
  340. package/clients/dispatch/runners/shfmt.ts +0 -100
  341. package/clients/dispatch/runners/spellcheck.ts +0 -145
  342. package/clients/dispatch/runners/sqlfluff.ts +0 -174
  343. package/clients/dispatch/runners/stylelint.ts +0 -155
  344. package/clients/dispatch/runners/swiftlint.ts +0 -199
  345. package/clients/dispatch/runners/taplo.ts +0 -93
  346. package/clients/dispatch/runners/tflint.ts +0 -100
  347. package/clients/dispatch/runners/tree-sitter.ts +0 -819
  348. package/clients/dispatch/runners/ts-lsp.ts +0 -136
  349. package/clients/dispatch/runners/utils/diagnostic-parsers.ts +0 -223
  350. package/clients/dispatch/runners/utils/lazy-installer.ts +0 -54
  351. package/clients/dispatch/runners/utils/runner-helpers.ts +0 -598
  352. package/clients/dispatch/runners/utils.ts +0 -58
  353. package/clients/dispatch/runners/vale.ts +0 -175
  354. package/clients/dispatch/runners/yaml-rule-parser.ts +0 -417
  355. package/clients/dispatch/runners/yamllint.ts +0 -92
  356. package/clients/dispatch/runners/zig-check.ts +0 -113
  357. package/clients/dispatch/tool-profile.ts +0 -40
  358. package/clients/dispatch/types.ts +0 -185
  359. package/clients/dispatch/utils/format-utils.ts +0 -56
  360. package/clients/dispatch/utils/lsp-diagnostics.ts +0 -42
  361. package/clients/feature-hints.ts +0 -79
  362. package/clients/file-kinds.ts +0 -467
  363. package/clients/file-role.ts +0 -145
  364. package/clients/file-time.ts +0 -208
  365. package/clients/file-utils.ts +0 -503
  366. package/clients/fix-worklog.ts +0 -121
  367. package/clients/format-service.ts +0 -276
  368. package/clients/formatters.ts +0 -1039
  369. package/clients/generated-artifacts.ts +0 -140
  370. package/clients/git-guard.ts +0 -41
  371. package/clients/gitleaks-client.ts +0 -363
  372. package/clients/go-client.ts +0 -242
  373. package/clients/govulncheck-client.ts +0 -476
  374. package/clients/indent-retarget.ts +0 -90
  375. package/clients/installer/index.ts +0 -2349
  376. package/clients/jscpd-client.ts +0 -361
  377. package/clients/knip-client.ts +0 -437
  378. package/clients/language-policy.ts +0 -263
  379. package/clients/language-profile.ts +0 -258
  380. package/clients/latency-logger.ts +0 -74
  381. package/clients/lens-config.ts +0 -209
  382. package/clients/lens-events.ts +0 -151
  383. package/clients/log-cleanup.ts +0 -257
  384. package/clients/lsp/aggregation.ts +0 -91
  385. package/clients/lsp/client.ts +0 -1619
  386. package/clients/lsp/config.ts +0 -216
  387. package/clients/lsp/edits.ts +0 -359
  388. package/clients/lsp/index.ts +0 -1653
  389. package/clients/lsp/interactive-install.ts +0 -424
  390. package/clients/lsp/language.ts +0 -223
  391. package/clients/lsp/launch.ts +0 -928
  392. package/clients/lsp/path-utils.ts +0 -12
  393. package/clients/lsp/server-strategies.ts +0 -81
  394. package/clients/lsp/server.ts +0 -2051
  395. package/clients/metrics-client.ts +0 -153
  396. package/clients/metrics-history.ts +0 -510
  397. package/clients/oldtext-autopatch.ts +0 -160
  398. package/clients/package-root.ts +0 -44
  399. package/clients/partial-edit-apply.ts +0 -76
  400. package/clients/path-utils.ts +0 -223
  401. package/clients/pipeline.ts +0 -1129
  402. package/clients/production-readiness.ts +0 -552
  403. package/clients/project-changes.ts +0 -112
  404. package/clients/project-conventions.ts +0 -215
  405. package/clients/project-metadata.ts +0 -809
  406. package/clients/project-scan-policy.ts +0 -79
  407. package/clients/project-snapshot.ts +0 -209
  408. package/clients/read-expansion.ts +0 -369
  409. package/clients/read-guard-logger.ts +0 -101
  410. package/clients/read-guard-tool-lines.ts +0 -825
  411. package/clients/read-guard.ts +0 -1044
  412. package/clients/reverse-deps.ts +0 -244
  413. package/clients/review-graph/builder.ts +0 -1005
  414. package/clients/review-graph/format.ts +0 -51
  415. package/clients/review-graph/query.ts +0 -162
  416. package/clients/review-graph/service.ts +0 -69
  417. package/clients/review-graph/types.ts +0 -46
  418. package/clients/review-graph/workspace-modules.ts +0 -497
  419. package/clients/ruff-client.ts +0 -511
  420. package/clients/rules-scanner.ts +0 -149
  421. package/clients/runner-tracker.ts +0 -215
  422. package/clients/runtime-agent-end.ts +0 -331
  423. package/clients/runtime-config.ts +0 -77
  424. package/clients/runtime-context.ts +0 -79
  425. package/clients/runtime-coordinator.ts +0 -463
  426. package/clients/runtime-session.ts +0 -894
  427. package/clients/runtime-tool-result.ts +0 -699
  428. package/clients/runtime-turn.ts +0 -732
  429. package/clients/rust-client.ts +0 -270
  430. package/clients/safe-spawn.ts +0 -343
  431. package/clients/sanitize.ts +0 -356
  432. package/clients/scan-utils.ts +0 -75
  433. package/clients/search-read-registration.ts +0 -89
  434. package/clients/secrets-scanner.ts +0 -214
  435. package/clients/semgrep-config.ts +0 -203
  436. package/clients/session-summary.ts +0 -53
  437. package/clients/sg-runner.ts +0 -679
  438. package/clients/source-filter.ts +0 -263
  439. package/clients/source-groups.ts +0 -140
  440. package/clients/startup-scan.ts +0 -150
  441. package/clients/subprocess-client.ts +0 -101
  442. package/clients/symbol-types.ts +0 -77
  443. package/clients/test-runner-client.ts +0 -1134
  444. package/clients/todo-scanner.ts +0 -243
  445. package/clients/tool-availability.ts +0 -250
  446. package/clients/tool-policy.ts +0 -2104
  447. package/clients/tree-sitter-cache.ts +0 -316
  448. package/clients/tree-sitter-client.ts +0 -1554
  449. package/clients/tree-sitter-fixer.ts +0 -217
  450. package/clients/tree-sitter-logger.ts +0 -51
  451. package/clients/tree-sitter-navigator.ts +0 -329
  452. package/clients/tree-sitter-query-loader.ts +0 -521
  453. package/clients/ts-service.ts +0 -154
  454. package/clients/type-coverage-client.ts +0 -164
  455. package/clients/types.ts +0 -59
  456. package/clients/typescript-client.ts +0 -698
  457. package/clients/widget-state.ts +0 -597
  458. package/commands/booboo.ts +0 -1714
  459. package/i18n.ts +0 -66
  460. package/index.ts +0 -2028
  461. package/tools/ast-dump.ts +0 -79
  462. package/tools/ast-grep-replace.ts +0 -233
  463. package/tools/ast-grep-search.ts +0 -421
  464. package/tools/lens-diagnostics.js +0 -194
  465. package/tools/lens-diagnostics.ts +0 -248
  466. package/tools/lsp-diagnostics.ts +0 -706
  467. package/tools/lsp-navigation.ts +0 -1492
  468. package/tools/shared.ts +0 -31
  469. package/tsconfig.json +0 -18
  470. /package/{tools → dist/tools}/ast-dump.js +0 -0
  471. /package/{tools → dist/tools}/lsp-diagnostics.js +0 -0
  472. /package/{tools → dist/tools}/shared.js +0 -0
package/CHANGELOG.md CHANGED
@@ -2,7 +2,107 @@
2
2
 
3
3
  All notable changes to pi-lens will be documented in this file.
4
4
 
5
- ## [Unreleased]
5
+ ## [3.8.51] - 2026-06-14
6
+
7
+ ### Added
8
+
9
+ - **Interrupting the agent (Esc) now cancels in-flight linter/formatter/type-check child processes (refs #197)** — pi-lens runs its dispatch tools via `safeSpawnAsync`, which already supported an `AbortSignal`, but nothing was feeding pi's per-turn `ctx.signal` into it, so an interrupted turn left its linters running until they hit their own timeout (up to 10–15s of wasted work, and on Windows orphaned process trees). The lifecycle handlers (`tool_result`, `agent_end`, `turn_end`) now publish the turn's `ctx.signal` as an ambient default that every `safeSpawnAsync` falls back to (`setAmbientAbortSignal`, cleared in each handler's `finally`), so Esc/abort tears down the in-flight children — process-tree kill on Windows. Threading the signal through every dispatch→runner→spawn call site would have been invasive; the ambient default captures the signal at spawn time, so clearing it after a handler returns only affects future spawns, never work already in flight. An explicit `options.signal` still takes precedence. Guarded by `tests/clients/safe-spawn-ambient-signal.test.ts`.
10
+
11
+ - **Resumed sessions rehydrate their diagnostics instead of starting empty (#190 Phase 1)** — quitting and resuming a session (`pi --session <id>`) made `lens_diagnostics` return nothing: pi-lens kept widget/diagnostic state in-memory only and reset on every `session_start`, treating resume as new. Root cause: it took the session id from the `session_start` event (which has none) and fell back to a fresh **per-process random id**, so nothing could be keyed across a resume. Now pi-lens reads pi's **stable** session id via `ctx.sessionManager.getSessionId()` and the `session_start.reason` (`new`/`resume`/`fork`/`reload`/`startup`): it persists the per-file widget diagnostics to disk at each `turn_end` (under `getProjectDataDir(cwd)/sessions/<id>.json`, atomic write, best-effort) and **rehydrates** that session's snapshot when one exists so `lens_diagnostics mode=all` and the widget show the prior findings; `reload` keeps in-memory state; an explicit `new` session starts clean. The rehydrate trigger is *"a persisted snapshot exists for this stable id"*, **not** `reason === "resume"` — a `pi --session <id>` launch fires `reason: "startup"` (only an in-process `switchSession` is `"resume"`), so gating on `"resume"` alone missed the common resume path; the reason→action mapping is now a unit-tested pure function (`sessionStartMode`). A brand-new session at startup has a fresh id with no snapshot → clean. Process-bound `lspServers` are deliberately not persisted (they re-spawn fresh).
12
+
13
+ **Phase 2** adds: (a) **fork branching** — `session_before_fork` stashes the source session's diagnostics in-memory and the forked session's `session_start` (reason="fork") adopts them, then persists under the new session id, so a `/fork` starts from the fork point's findings instead of empty (in-memory hand-off avoids deriving the source id from a file path, since pi stores the id in the session-file header, not the filename); (b) **freshness reconciliation (#180)** — on resume, files whose on-disk mtime is newer than the snapshot (edited between sessions) or that no longer exist are dropped before rehydration, so a resume never surfaces stale diagnostics; dropped files re-scan on their next edit. Still deferred on #190: `delta`-mode rehydration (gated by the `projectSeq`-reset freshness check, intertwined with #180's seq semantics) and tree-navigation (`/tree` doesn't change files on disk). Guarded by `tests/clients/session-state-store.test.ts` (export/import, save/load, end-to-end resume, fork hand-off, `dropStaleFiles`) and `tests/clients/runtime-session-lifecycle.test.ts` (stable-id pinning). Investigation closed the other two transitions as no-ops: `delta` mode is current-turn-scoped and its caches already persist per-project (no rehydration belongs there), and `/tree` navigation doesn't change files on disk. As discoverability for the turn-scoped default, `lens_diagnostics mode=delta` now appends a one-line hint when it's empty but the session-wide view has carried-over findings (e.g. just after a resume): "N findings across M files carried over — use mode=all".
14
+
15
+ - **`/lens-health` surfaces event-loop occupancy (#192)** — pi-lens now monitors event-loop delay in production (Node's native `monitorEventLoopDelay`, enabled at extension load, no per-event overhead) and `/lens-health` reports the worst synchronous block, p99, and mean for the session — flagging a >100ms block that can stutter the TUI. This is the dimension our duration-only logs were blind to (the one that let the ~1.5s scan freeze through, #188/#191). `latency.log` also records a `loop_block` entry for each new worst freeze, attributed to its turn, so blocks are queryable across sessions. Paired with the at-scale occupancy **test** harness (`tests/support/perf-harness.ts` — `measureMaxSyncBlockMs` + `generateSourceTree`) and CI budget guards. A dedicated `/lens-perf` view remains (#192).
16
+
17
+ - **Extension-wiring test harness + mock consolidation (closes #171)** — a single dependency-free mock of the host `ExtensionAPI` (`tests/support/pi-mock.ts`) that records everything `index.ts` registers (flags/commands/tools/lifecycle hooks) and lets a test drive a hook (`emit`) or command (`runCommand`) through the *real* entry, with `makeCtx()` capturing `ui.notify`/`setStatus`/`setWidget`. New `index-wiring` tests assert the full registration contract and that `context` injection is gated by `--no-lens-context` and flipped by `/lens-context-toggle` — glue that was previously untested and that the dist-packaging breakage showed we need. Consolidated the three parallel pi mocks onto this one: migrated `lens-toggle-command.test.ts` (template) and `index-integration.test.ts`, removed the duplicate `tests/support/mock-pi.ts`, and deleted `extension-hooks.test.ts` (its assertions never invoked the real entry — they registered on the mock and asserted the mock, so they were tautological and used stale flag names; the real registration contract is now covered by `index-wiring`). Dispatch-runner `RunnerContext` tests are a separate harness concern, out of scope here.
18
+
19
+ - **Startup-time logging (makes the #182 win measurable)** — pi-lens now records how long pi took to load it: `performance.now()` captured as the first statement in the extension entry (after all imports = full jiti transpile paid) gives ms from pi's process start to pi-lens load-complete. Emitted once per load as a human line in `sessionstart.log` (`pi-lens loaded: <ms>ms after process start (from dist|source)`) and a structured `latency.log` entry (`phase: "extension_loaded"`, `metadata.loadedFrom`). The `loadedFrom` tag distinguishes the precompiled `dist/` path from `source`/jiti, so the transpile-on-startup cost is now quantified rather than guessed (`clients/startup-timing.ts`).
20
+
21
+ - **Runner failures carry a `failureKind`, and the log-smell analyzer tells breakage from found-errors (refs #207)** — the dispatch latency log recorded `status:"failed"` both when a runner genuinely broke *and* when it simply found blocking diagnostics (the LSP runner reports `failed` for a file with type errors), so `scripts/analyze-pi-lens-logs.mjs` counted all of them as crashes — a false "98 runner failures" alarm over 24h where the real infra-failure count was **zero**. `RunnerResult` now carries `failureKind`/`failureMessage`: the LSP runner tags `server_error` (spawn/exit/JSON-RPC) vs `blocking_diagnostics` (found type errors — not a fault), and the central `runRunner` catch tags `timeout` vs `exception` (covering every runner's crash path); the dispatcher logs `metadata.failureKind` on the runner line. The analyzer reclassifies accordingly — only genuine breakage (`timeout`/`exception`/`server_error`) counts as the `runner-failures` smell, found-errors go to a separate per-runner tally, and legacy logs without the field fall back to a "failed + has diagnostics = found-errors" heuristic. It also now reads two live logs it was previously blind to — `actionable-warnings*.log` (advisory inject/suppress pipeline) and `ast-grep-tools*.log` (MCP search/replace telemetry) — with new `ast-grep-tool-errors` / `actionable-warning-errors` smells and per-source report sections. Guarded by `tests/scripts/analyze-pi-lens-logs.test.ts` (fixture-driven subprocess run: source discovery, the infra-vs-found-errors split, advisory aggregation, the ast-grep error smell).
22
+
23
+ - **ast-grep `search`/`replace` surface a remediation hint to the agent on error (refs #207)** — the tools already classified each failure (`classifyAstGrepError`) for telemetry, but only the two highest-frequency categories (`multiple_ast_nodes`, `cannot_parse_query`, curated by `sg-runner.ts`) reached the agent with guidance; the other four (`timeout`, `tool_not_found`, `json_parse_failed`, `other`) came back as raw stderr, and the rich `getPatternHint()` self-correction only fired on the *zero-matches* path, never on a hard error. `astGrepRemediationHint(kind)` now reuses that same classification to append a one-line fix on the error path (returns `null` for the already-curated categories so it never doubles up) — e.g. an empty `--rewrite` (previously raw clap CLI noise) now gets "verify the pattern is a single valid AST node … or fall back to grep". Guarded by `ast-grep-tool-logger.test.ts` (hint map incl. the real empty-`--rewrite` log case + a wrapped multiple-nodes error → no extra hint) and error-path tests in both tool suites (hint appended for raw errors, *not* for curated ones).
24
+
25
+ ### Fixed
26
+
27
+ - **Read-guard autopatch recovers Unicode-punctuation drift (Tier C)** — the autopatch ladder (`tryCorrectIndentationMismatchFromContent`) gained a tier that tolerates the punctuation models routinely swap: smart quotes ↔ straight (`“”‘’` ↔ `"'`), em/en-dash ↔ hyphen, and non-breaking / typographic spaces ↔ a regular space (common when `oldText` is pasted from rendered Markdown or the model "tidies" punctuation). Previously such an edit failed the exact + whitespace tiers and was blocked; now the tier matches on a Unicode-folded, whitespace-collapsed signature and — like Tiers A/B — **recovers and returns the verbatim file span** (the file's real characters), so the applied edit stays exact. Same safety contract: the folded signature must match exactly once, anchored on ≥2 non-blank lines. Borrowed from the fuzzy matcher in mitsuhiko/agent-stuff's multi-edit, but kept to pi-lens's verify-don't-guess discipline (no blind "closest match" fallthrough). Guarded by `read-guard-tool-lines.test.ts` (smart-quote / em-dash / NBSP recovery + single-line, ambiguous, and absent-content negatives).
28
+
29
+ - **Skills now load from the compiled `dist/` build (closes #205, reported by @feoh)** — the `resources_discover` handler resolved the skills directory relative to the module's own location (`path.dirname(import.meta.url) + "/skills"`). Under the `dist/` layout (#182) the module is `dist/index.js`, so that landed on the nonexistent `dist/skills/` and pi logged `skill path does not exist` while silently loading none of pi-lens's skills (ast-grep, lsp-navigation, write-ast-grep-rule, write-tree-sitter-rule). Now it uses `resolvePackagePath(import.meta.url, "skills")`, which walks up to the nearest `package.json` and lands on `<pkg>/skills/` in both the source and dist layouts. (The issue's suggested `path.join(extensionDir, "..", "skills")` would have fixed dist but broken source, where the module already sits at the package root.) Guarded by an `index-wiring` test that invokes the handler and asserts the path exists, ends in `skills`, and is not `dist/skills`.
30
+
31
+ - **SonarCloud security/reliability fixes (new-code period)** — (1) **`S5850` reliability bug** in `clients/word-index.ts`: the `TEST_VENDOR_RE` regex mixed anchors with a top-level `|`, leaving operator precedence ambiguous; wrapped the two alternatives in explicit non-capturing groups (behaviour verified identical, capture groups unchanged). (2) **`S4790` weak-hash** ×2: `clients/mcp/ipc.ts` (IPC socket/pipe name) and `clients/review-graph/builder.ts` (content fingerprint for change detection) used `sha1` for non-security hashing — switched to `sha256` (functionally equivalent here, silences the flag). (3) **`S7637`**: pinned the third-party `softprops/action-gh-release` GitHub Action to a full commit SHA (`b430933…` # v3) in `release.yml`. The remaining 48 `S5852` (ReDoS) and 4 `S4036` (PATH lookup) hotspots were reviewed and are safe by context — every flagged regex is single-quantifier or polynomial-at-worst over bounded, trusted input (source lines / tool output), with zero nested-quantifier (`(x+)+`) patterns in the codebase; PATH-based tool resolution is core to how pi-lens finds the user's installed linters/LSPs. These are review hotspots to mark *Safe* in SonarCloud, not code defects.
32
+
33
+ - **`audit:rule-catalog` no longer fails on duplicate rule_ids** — three catalog entries violated the registry's globally-unique-`rule_id` invariant (which the `-java`/`-js`/`-cobol` suffix convention exists to maintain). `infinite-loop` had genuine java *and* typescript rule files sharing one id, so the java variant was renamed to `infinite-loop-java` (id + file, matching the `unnecessary-bit-ops-java` precedent); `no-octal-values` and `short-circuit-logic` had phantom `typescript` catalog entries with no backing rule file (TS octal coverage already exists via the ast-grep `no-octal-literal` rule), so those were removed. The audit now reports 0 errors.
34
+
35
+ - **ast-grep-napi runner migrated to napi's native rule engine; hand-rolled interpreter deleted (closes #206)** — the runner used a ~240-line hand-rolled rule interpreter (`nodeMatchesCondition`/`findMatchingNodes`/`findByKind`/`legacyRuleMatches`) over a hand-rolled YAML parser (`parseSimpleYaml`). That parser could not faithfully serialize the ast-grep grammar — it flattened nested `any`/`has`, kept quotes inside `kind: "true"`, and dropped the metavariable key from `constraints` — so relational/`field`/`constraints` rules were silently skipped, and the interpreter's `has` both recursed AND matched the node itself (a self-referential `kind: X` has `kind: X` flagged **every** X: `nested-ternary` reported 720 of which ~678 were false). Now: (1) `parseSimpleYaml` is a thin `js-yaml` wrapper — the full grammar survives intact and is fed straight to `root.findAll({rule, constraints})`; one malformed document skips only itself, not its whole file; (2) the runner always uses napi (the `ast-grep-native-rules` flag and the entire legacy interpreter are gone); a rule napi rejects is skipped, never partially evaluated. Corpus changes to land the migration cleanly: quoted three rule `message:` scalars that began with `!!`/contained `:` (js-yaml threw on them, silently dropping the rules); rewrote five rules that used non-existent tree-sitter kinds (`element_access_expression`→`subscript_expression`, `property_access_expression`→`member_expression`, `block`→`statement_block`, `for_of_statement`→pattern) — they had been dead in both engines; added `stopBy: end` to `switch-without-default` and `nested-ternary`(+js) (their `has` targets a non-direct descendant — `switch_default` lives under `switch_body`); left direct-child `has` rules (`no-throw-string`, `no-discarded-error`, `else-return`, `redundant-state`/`follows`) at napi's neighbour default so they don't over-report. Earlier de-risking (this batch): `no-constant-condition`(+js) rewritten to a flat pattern any-list; `constructor-super`(+js)/`no-process-env`/`no-hardcoded-secrets`(+js)/`unchecked-sync-fs`(+js) moved to `rules-disabled/` (constraint/relational rules that were never actually running; secrets entries marked `deprecated` in the catalog). Net on this repo: 233 files, 0 errors, ~632 diagnostics with the `nested-ternary` false-positive bomb gone and previously-dead relational rules now correctly active. Guarded by `ast-grep-sonar-rules.test.ts` (native `has`/`stopBy` semantics: nested vs single ternary, switch with/without default, rewritten relational rules) and `yaml-rule-parser.test.ts` (faithful nested-`any`/`has` + `constraints` survive the parse; malformed doc returns null).
36
+
37
+ - **LSP registry-consistency guard (follow-up to #208)** — #208 was a server pi-lens wires that never actually came up, so we added a deterministic per-PR guard: `tests/clients/lsp/lsp-registry-consistency.test.ts` validates every `LSP_SERVERS` entry is well-formed — globally-unique ids, required `spawn`/`root`/`extensions`, clean extension tokens, sane optional timeouts — catching half-wired or duplicated entries cheaply. The complementary *live* end-to-end install→launch→`initialize` smoke (across all install strategies) is tracked in #209: a first cut produced false failures on Windows (a hand-rolled handshake that bypassed `vscode-jsonrpc` framing missed the flagship typescript server, which actually responds in ~120 ms), so it's being reworked to drive pi-lens's real LSP client before it lands as a nightly job.
38
+
39
+ - **LSP auto-install no longer rejects stdio servers that fail `--version` (closes #208, reported by a Fedora Silverblue user)** — `verifyToolBinary` confirmed a freshly-installed binary by running `<bin> --version` and requiring exit 0. Servers built on `vscode-languageserver-node` — the `vscode-langservers-extracted` family (JSON/CSS/HTML/ESLint) — reject a bare `--version`: `createConnection()` throws `Connection input stream is not set … '--node-ipc', '--stdio' or '--socket={number}'` and exits 1. So every install "verified as broken," got cleaned up, and those LSPs were never available (lost diagnostics/hover/format, plus repeated wasted install attempts at startup). Verification now treats that specific transport-required error as success — it is positive proof the binary loaded and is a working LSP server that simply needs `--stdio` to run. A genuinely broken install still fails, because it errors with a *different* message (`ERR_MODULE_NOT_FOUND`, `SyntaxError`, …) that doesn't match the pattern, so the broken-install guard is preserved. **Smoke-tested against the real `vscode-langservers-extracted@4.10.0` binaries**: JSON/CSS/HTML/ESLint all emit the transport error and now verify — these are exactly the four LSP servers pi-lens wires (`clients/lsp/server.ts`, each spawned with `--stdio` and auto-installed via `managedToolId`). pi-lens does **not** configure a Markdown LSP, so the package's Markdown binary is irrelevant here; it additionally fails to load under Node ≥24 (unrelated upstream `vscode-uri` ESM-interop `SyntaxError`, before the transport check), which verification correctly continues to reject — so it is neither used by pi-lens nor force-verified. The check is a small exported predicate (`isLspTransportRequiredError`) guarded by `tests/clients/installer/lsp-transport-verify.test.ts` (the exact vscode transport error → pass; the real Markdown `vscode-uri` crash / module-not-found / syntax-error / unknown-flag → still fail).
40
+
41
+ - **LSP last-known diagnostics cache is content-hash guarded — no more stale actionable-warnings (refs #207)** — the actionable-warnings turn_end read reused `getLastKnownDiagnostics` (keyed by path only) on the premise that dispatch's `touchFile` primed it that turn — but `touchFile` never wrote `lastKnownDiagnostics` (only the service-level `getDiagnostics` did, called by the *fresh* branch and the agent lsp tools). So once an earlier turn's fresh branch cached diagnostics, later turns on the same file served those **prior-turn** results as `"cache"` with no content guard — genuine staleness, worst when LSP is cold (the entry can't be refreshed that turn). This was the ~40% `lspSource:cache` seen in the log review. Now `touchFile` primes `lastKnownDiagnostics` together with a sha256 of the synced content (gated on `collectDiagnostics`), `getLastKnownDiagnostics(path, expectedContentHash)` returns the entry only on a hash match (the content-less service merge clears the hash, so those entries never pose as current; the unguarded widget read still gets last-known for display), and actionable-warnings hashes the on-disk bytes and passes them: match → verified-current reuse, mismatch/absent → fresh open+wait. `lspSource:"cache"` now means **verified-current reuse**. Guarded by `actionable-warnings-lsp-cache.test.ts` (passes the correct hash; reuses on match with no fresh read; rejects a stale entry on hash mismatch → forces a fresh read).
42
+
43
+ ### Changed
44
+
45
+ - **Rust/Go/type-coverage availability probes are now async (refs #197)** — `RustClient.findCargoPath`/`isAvailable`, `GoClient.findGoPath`/`isGoAvailable`, and `TypeCoverageClient.isAvailable`/`scan` were sync `safeSpawn` `--version`/path probes that blocked the event loop on first use; they're now `findCargoPathAsync`/`isAvailableAsync` etc. on `safeSpawnAsync`, with their callers (the `rust-clippy`/`go-vet` dispatch runners, the `session_start` active-tools list, and `/lens-booboo`) awaiting them. The unused `GoClient.isGoplsAvailable` was deleted outright. One intentionally-sync probe remains: `TestRunnerClient.detectRunner`'s `which pytest` check — it's cached per (cwd, runner) and only fires once for a Python project with no config-file runner, and converting it would ripple async through five methods into the per-edit turn path for no real gain.
46
+
47
+ - **Dispatch availability probes are now async-only (refs #197)** — the runner availability layer carried parallel sync/async probes; the sync ones blocked the event loop on first use. `createAvailabilityChecker` now exposes only `isAvailableAsync` (the never-taken sync `isAvailable` fallback is gone, and all ~25 runners + `resolveAvailableOrInstall` use the async path directly), the ast-grep availability chain collapsed to its async form (`AstGrepClient.runTempScanAsync` now `await`s `ensureAvailable()`, retiring the dead sync `AstGrepClient.isAvailable` → `SgRunner.isAvailable` → `isSgAvailable` → `probeAstGrepCommand` cascade), and the unused sync `isCommandAvailable` in `dispatch/runners/utils.ts` was deleted. No remaining sync spawn in the dispatch availability layer; behaviour unchanged (full suite green).
48
+
49
+ - **Tool installs and formatter probes no longer block the event loop (refs #197)** — converted the last event-loop-reachable synchronous spawns to `safeSpawnAsync`: the LSP runtime-install actions (`tryGoInstallGopls` `go install`, `tryDotnetToolInstall` `dotnet tool install`/`update`, `tryGemInstall` `gem install` — previously raw `spawnSync` that could freeze the TUI for the whole install, and `go install` had *no* timeout at all), and every formatter probe/install in `formatters.ts` (`gem install rubocop`, `rustup component add rustfmt`, `which`, `go env GOROOT`, `dotnet csharpier --version`, the PSScriptAnalyzer check). On Windows this also fixes a latent bug — `gem`/`dotnet` are often `.cmd` shims that bare `spawnSync(…, { shell:false })` can't launch, whereas `safeSpawnAsync` uses shell mode. Installs pass a new `ignoreAmbientSignal` option so they run to completion even if the agent turn is interrupted (matching the old uncancellable sync behaviour — an Esc can't strand a half-finished `gem install`); the quick probes stay cancellable. Equivalence-tested in `tests/clients/install-actions.test.ts` (same command/args, same success-on-exit-0 semantics, the dotnet NuGet-missing and update-fallback branches, the gem PATH update, and the formatter lazy-install dedupe guard) plus a `safeSpawnAsync` `ignoreAmbientSignal` unit test.
50
+
51
+ ### Performance
52
+
53
+ - **Collapsed the redundant post-edit LSP double-push that discarded in-flight diagnostics (#203)** — on every edit pi-lens pushed the final post-format content to the language server twice: once in the pipeline `lsp_sync` phase (via `resyncLspFile` → `LSPService.openFile`) and again ~80ms later in the `dispatch-lsp-runner`. `openFile` never registered the push in the touch-debounce map (`markTouched`), so the dispatch runner's `shouldSkipNotify` always returned false and its `didChange` **cleared the diagnostics the first push had just set the server computing**, forcing a from-scratch recompute and a multi-second wait. Latency-log evidence (`~/.pi-lens/latency.log`, ~18k events): the notify-skip dedup fired on just 2 of ~465 dispatch touches, and ~280 of ~700 document-diagnostics waits timed out — **136 of 142 on TypeScript** (`typescript-language-server` is push-only, so the timeouts were us throwing away a push that did arrive, not waiting on one that never came). `resyncLspFile` now routes through `touchFile({ diagnostics: "none", source: "lsp_sync", clientScope: "primary" })`, so the sync push registers via `markTouched`; the dispatch touch moments later then hits `shouldSkipNotify=true`, reuses those diagnostics instead of re-clearing, and `waitForDiagnostics` fast-paths. Expected `dispatch_lint` p50 ~3.1s → ~2.2s on every LSP edit, with the `.ts` timeout population largely eliminated. The old `formatChanged`/`preserveDiagnostics` branch is dropped — `didChange` triggers a server recompute regardless, so letting the cache clear yields fresh, correctly-positioned diagnostics rather than stale pre-edit ones. Regression-tested in `tests/clients/pipeline.test.ts` (the sync routes through `touchFile` with the registering options, not `openFile`); the touch→touch dedup itself is already covered by `service-touch-collect.test.ts` (#116).
54
+
55
+ - **Per-server diagnostics-wait budget on the LSP hot path (#203)** — `touchFile` resolved its diagnostics-wait timeout from a flat default (the dispatch runner's 2500ms / a 1200ms floor), ignoring the per-server budgets already defined in `server-strategies.ts`. On the single-server primary path it now uses that server's `aggregateWaitMs` (TypeScript 1000ms, rust-analyzer 3000ms, python 1500ms, …), bounded by any caller ceiling — so a fast server isn't held to a flat multi-second wait while a slow one still gets the time it needs. Env override (`PI_LENS_LSP_DIAGNOSTICS_MAX_WAIT_MS`) still wins, and the multi-server `full`/cascade path keeps its flat resolution. Covered by `tests/clients/lsp/service-touch-collect.test.ts`.
56
+
57
+ - **Auto-warm the dominant language's LSP at `session_start` (#203)** — first-edit-of-session cold-spawn stalls (`lsp_client_wait_timeout`, observed up to 5s on TypeScript/Deno) happened because servers only pre-warmed when a project explicitly listed `warmFiles`. When none are configured, pi-lens now uses the language detection it already does to pre-spawn just the **dominant** language's server (highest source-file count) by opening one representative file — backgrounded off the interactive path. Only one server is warmed by design: launching every detected language's server at once (rust-analyzer + gopls + tsserver …) would spike the event loop at startup, working against the latency it protects. The scan is directory-reads-only (`inspectGeneratedHeaders:false`, no per-file opens). Covered by `tests/clients/runtime-session-warm.test.ts`.
58
+
59
+ - **Document-version-coherent diagnostics freshness (#203)** — `waitForDiagnostics` judged freshness off a monotonic push counter, so a stale `publishDiagnostics` for a superseded document version could satisfy a wait for the current one (a latent correctness gap exposed once the double-push above stops clearing the cache pre-wait). The client now records the LSP document version each push was computed against (`publishDiagnostics.version`) and rejects cached results that lag the latest `didChange`. Servers that omit a version are treated as current, so version-less servers are unaffected and the timeout remains the backstop. Covered by `tests/clients/lsp/client-internals.test.ts`.
60
+
61
+ ### Removed
62
+
63
+ - **Deleted the dead synchronous linter/formatter methods from `BiomeClient`/`RuffClient` (refs #197)** — both clients carried a full legacy *sync* surface (`checkFile`, `checkFormatting`, `fixFile`, `fixFiles`, `formatFile`, `formatDiagnostics`, biome's `getFormatDiff`/`withValidatedPath`/`spawnBiome`, plus the now-orphaned private `parseDiagnostics`/`computeDiff`) built on the event-loop-blocking sync `safeSpawn`. An audit of the live dispatch path confirmed **none of these had any caller** — every per-edit path already runs async: the dispatch runners (`biome-check.ts`, `ruff.ts`) use `safeSpawnAsync`, autofix-on-write uses `fixFileAsync` (`pipeline.ts`), and format-on-write uses the async `formatService`. The audit that flagged "autofix-on-write blocks the loop" had conflated this dead sync code with the live `fixFileAsync` sitting next to it. Removing it deletes the most alarming sync-spawn call sites outright (-719 lines; biome-client 657→233, ruff-client 511→218) with zero behavior change (full suite green). The remaining sync `safeSpawn` sites are the cached availability probes and one-shot install actions tracked in #197.
64
+
65
+ - **Deleted the remaining dead legacy-sync methods + an unused module (refs #197)** — continuing the sync-`safeSpawn` cleanup: removed `TestRunnerClient.runTestFile` (sync; the live per-write path uses `runTestFileAsync`), `AstGrepClient.scanFile` → `SgRunner.execSync` → `SgRunner.tempScan`/`scanWithRule` (a fully dead sync ast-grep scan cascade; the ast-grep tools and temp-scans all use the async `exec`/`tempScanAsync` paths), and the entire `clients/subprocess-client.ts` (a 101-line abstract `SubprocessClient` base with **zero** importers). Orphaned helpers/imports went with them (`mapSeverity`, the `AstGrepParser` import, `sg-runner`'s now-unused sync `safeSpawn` import) and the obsolete `execSync` tests were removed (the co-located `formatMatches` tests were re-parented, not lost). ~360 fewer lines, zero behavior change (full suite green). Kept by design: `findCargoPath`/`findGoPath` and the `detectRunner` pytest probe (bounded, cached) and the `booboo` command-path probes (user-invoked).
66
+
67
+ - **Deleted the dead synchronous check methods from `RustClient`/`GoClient` (refs #197)** — same legacy pattern as the Biome/Ruff cleanup: the per-edit Rust/Go diagnostics already run through the async dispatch runners (`rust-clippy.ts`/`go-vet.ts`, which call `findCargoPath`/`findGoPath` + their own `safeSpawnAsync`), so the clients' sync `checkFile`/`clippyCheck`/`buildCheck`/`formatDiagnostics` methods (built on blocking `safeSpawn`) and their now-orphaned private `parseJsonOutput`/`parseOutput` + `CargoMessage` type had **no callers**. Removed (rust-client 270→107, go-client 242→126). The still-live probes are intentionally kept: `findCargoPath`/`findGoPath` (a bounded, cached, one-time `--version` fallback only hit when the tool isn't at a standard absolute path) and the status-list `isAvailable`/`isGoAvailable` (command/`runtime-session` path) — tracked as the residual in #197.
68
+
69
+ - **Deleted two more dead sync modules/functions (refs #197)** — the entire `clients/tool-availability.ts` module (a 251-line cached tool-availability layer — `isToolAvailable`/`getToolVersion`/`ToolAvailabilityChecker`/`TOOL_REGISTRY`) had **zero importers** anywhere in source or tests, and the sync `resolveLocalFirst()` in `runner-helpers.ts` was superseded by its live async twin `resolveLocalFirstAsync()` and likewise had no callers. Both built on sync `safeSpawn`; deleting them removes three more event-loop-blocking probe sites at zero risk. The genuinely *live* remaining sync probes — `createAvailabilityChecker`'s sync `isAvailable` fallback and `isSgAvailable()` (reached via the clients' legacy sync `isAvailable()` methods, e.g. `ast-grep-client`/`rust-client`/`type-coverage-client`) — are a cross-client availability-contract change tracked as the remaining (B) work in #197, not a deletion.
70
+
71
+ ### Security
72
+
73
+ - **Patched a moderate ReDoS-class advisory in a transitive dep and added a CI audit gate** — `brace-expansion` (pulled via our direct `minimatch@^10`) resolved to a version under GHSA-jxxr-4gwj-5jf2 (a large numeric range defeats its documented `max` DoS protection). Bumped to `5.0.6` (lockfile-only; `npm audit` clean for both prod and full trees). It slipped through because Dependabot's weekly *version* updates only bump direct deps, and `minimatch@^10` was already satisfied — nothing was watching the transitive tree. CI now runs `npm audit --omit=dev --audit-level=high` in the lint job, so a known-vulnerable **production** dependency (what ships to users via `--omit=dev`) fails the build at PR time instead of being noticed by chance; the gate is scoped to high/critical to avoid blocking on fix-less moderate advisories, which Dependabot security updates can handle separately.
74
+
75
+ ### Fixed
76
+
77
+ - **Production install no longer fails to build `dist/` under `npm install --omit=dev` (#193, thanks @feoh; guarded by #194)** — `prepare`/`build:dist` inherited `types: ["node"]` from the base tsconfig, so under pi's `--omit=dev` git install (dev-only `@types/node` absent) `tsc` failed with TS2688 *before* type-checking — `--noCheck` doesn't suppress a program-construction error, contrary to what #182 assumed. `tsconfig.dist.json` now sets `types: []` (the transpile-only dist build needs no ambient node types). A new CI job (`prod-install-build`) installs `--omit=dev` and builds `dist/` from source, so this can't regress — the tarball-based install-test never re-ran the build under `--omit=dev` (#194).
78
+
79
+ - **Faster startup: ship precompiled JS instead of transpiling on every launch (closes #182)** — pi-lens was distributed as TypeScript source (`main: index.ts`, `pi.extensions: ["./index.ts"]`), so pi's jiti loader transpiled ~215 `.ts` files on every cold start (including `/new`), adding ~3.5s. The package now ships a precompiled `dist/` and points `main` + `pi.extensions` at `./dist/index.js`, which pi loads directly (~1.5s). A `prepare` step (`tsconfig.dist.json` → `dist/`, transpile-only via `--noCheck`) builds it **on install — including `git:` installs, which run `npm install` not `npm pack` — and before publish**, so both install paths get the compiled output with no rebuild script. pi-lens's own asset resolution is unaffected: `rules/`, `config/`, and grammars resolve via `getPackageRoot()` (walks up to `package.json`), not module depth. Guarded by `tests/packaging.test.ts` (entry/`files` contract), an upgraded `scripts/check-extensions.mjs` (validates compiled `.js` imports resolve), and CI install-test steps that verify the tarball ships `dist/index.js` with no `.ts` source and that the compiled entry loads. The dev/test loop still uses the in-place `npm run build`.
80
+
81
+ - **Skills now actually load under the moved entry (closes #199)** — pi resolves each `pi.skills` entry relative to the extension entry's **file path** (`path.resolve(entryFile, skillEntry)`), not its directory. Once the entry moved to `./dist/index.js` (#182), `pi.skills: ["../skills"]` resolved to `<root>/dist/skills` — the `../` only cancels `index.js` and stays in `dist/` — which doesn't exist, so pi-lens's skills silently stopped loading and pi warned `[Skill conflicts] … skill path does not exist`. Reaching the real root `skills/` from `dist/index.js` needs to climb **two** levels, so `pi.skills` is now `["../../skills"]`. The earlier value was off by one and the CI/tarball check never caught it (it only verifies `skills/` *ships*, not that pi *resolves* it); `tests/packaging.test.ts` now statically replicates `resolve(entryFile, skillEntry)` and asserts it lands on the package's own root `skills/`.
82
+
83
+ - **`ast_grep_replace` apply no longer falsely reports "no matches" on a successful replacement (closes #178)** — the apply path counted matches *after* writing the fix, so any content-changing replacement reported `[APPLIED] No changes made (no matches found)` despite succeeding, misleading agents into thinking the edit failed. Both replace paths (pattern and rule) now report the **pre-apply** match count, and the apply-zero display message is unambiguous (`[NOT APPLIED] No matches found …`).
84
+
85
+ - **Reliable alphabetical sort of project-diagnostic sources** — `[...sources].sort()` relied on default UTF-16 ordering, which SonarCloud flags as unreliable (`typescript:S2871`); now uses an explicit `String.localeCompare` comparator.
86
+
87
+ - **Multi-line diagnostic messages no longer break TUI rendering (closes #189)** — diagnostics with multi-line messages (e.g. TS2769 "no overload matches this call") spilled across several widget rows and broke the layout (and the `L<line>: <message>` inline-blocker format), because `fitLine` clips by visible width but embedded newlines survive. `recordDiagnostics` now collapses whitespace runs to a single space at storage, so the widget, `lens_diagnostics`, and summaries all get single-line messages.
88
+
89
+ - **`session_start` no longer freezes TUI input on cold boot / `/new` (closes #188)** — the synchronous `session_start` walks (scan-context, language profile, todo / call-graph scheduling) ran O(N) without yielding, starving the stdin macrotask queue for 3–6s on large projects. Fixed with an `ignoreMatcher` path-memo (mtime-invalidated), process-lifetime memos for scan-context and language-profile, async chunked-yield walk variants, background scans deferred past the typing window, a per-file chunked todo scan, and a cold-start forced-quick + delayed-warmup. `session_start` total drops from 3000–6000ms to ~3ms on a 1832-file project. Env knobs: `PI_LENS_COLD_START_QUICK`, `PI_LENS_WARMUP_DELAY_MS`, `PI_LENS_STARTUP_MODE`. Together with #182 this fixes both halves of startup latency (jiti transpile + scan). Thanks @amit-gshe.
90
+
91
+ - **Source-file enumeration no longer blocks the event loop (perf hardening, follows #188)** — the file walk under the deferred todo / project-diagnostics scans (`collectSourceFiles`) was still a single ~1.5s synchronous burst on a 2k-file project (≈70% of it the per-file 4 KB generated-header read), blocking TUI input even though #188 had made the *callers* yield. Added a chunked-yield `collectSourceFilesAsync` (shares the filter logic with the sync collector via an extracted `classifyEntry`, so results are identical), memoized the generated-header verdict (keyed on path+mtime+size, self-invalidating on edit), and routed the background callers (todo, project-diagnostics) to the async path. Longest synchronous block during enumeration: **~1576ms → ≤38ms cold / 5.9ms warm**; returned file set asserted identical. Guarded by `tests/clients/source-filter-async.test.ts`; remaining (riskier) source-walk hardening tracked in #191.
92
+
93
+ - **Per-edit cascade graph rebuild no longer freezes the TUI (perf hardening, follows #188/#191)** — `buildOrUpdateGraph` runs on **every** write/edit (via `computeCascadeForFile`), and even on a pure cache hit it re-derived the workspace source-file list (sync tree walk + per-file 4 KB generated-header read) and re-statted every project file just to compute the cache-validity signature — the same sync-FS-over-all-files class #188 fixed for startup, here on the path that runs on every keystroke-triggered edit. Made the walk (`getGraphSourceFiles`) and the signature/stat loop (`sourceSignatureMapAsync`) async + chunked-yield, reusing the existing `collectSourceFilesAsync` (byte-identical file list via the shared `classifyEntry`) and producing the identical `file → "size:mtimeMs"` signature; `_doBuildGraph` already awaited the builder so the call contract is unchanged. Longest synchronous block on a 1,200-file project: **warm cache-hit ~770ms → ~47ms; cold full derivation ~2,215ms → ~46ms** (total FS work unchanged — the loop now yields instead of freezing). Verified behavior-preserving: the async sections touch only local accumulation (no shared cache/fact mutation), and concurrent different-file builds were already interleavable at the existing `await` points, so no new race. Guarded by `tests/clients/cascade-graph-occupancy.test.ts`. The walk still runs each edit (now yielding), but we deliberately stop here: the expensive work (tree-sitter parse + graph construction) is already cached, so this walk is only the cache-*validation* step, and memoizing it would trade always-fresh impact analysis for tens of ms of yielded work on an accuracy-critical path — with no FS watcher to catch out-of-band file changes. Closed #196 won't-do with that rationale.
94
+
95
+ - **`lens_diagnostics` no longer lists findings the agent already fixed this session (read-your-writes, closes #180)** — `mode=all` reads the widget's per-file diagnostic state, which only refreshes a file when that file is re-dispatched. Because per-edit dispatches are **debounced** (flushed at `turn_end`), an agent that fixed files and then queried `lens_diagnostics` in the same turn saw the **pre-fix** diagnostics still pending in the debounce window. Now the tool **flushes pending dispatches before reporting** (`flushDebouncedToolResults`, injected) so just-fixed files are re-dispatched and reflected, and then **reconciles the live widget against the filesystem** (`reconcileStaleWidgetFiles`): entries whose file changed on disk after their diagnostics were recorded (`mtime > touchedAt`, e.g. an external edit) or that were deleted are dropped — and `mode=all` notes how many were omitted ("N changed files omitted as stale — use mode=full to rescan") so a changed-but-unscanned file reads as *stale*, not falsely clean. Cross-file staleness (a neighbor whose own content is unchanged but whose diagnostic an edit elsewhere invalidated) is a separate follow-up. Guarded by `tests/tools/lens-diagnostics.test.ts` (flush invoked, stale note) and `tests/clients/session-state-store.test.ts` (`reconcileStaleWidgetFiles` drops edited/deleted, keeps unchanged).
96
+
97
+ - **`rust-analyzer` no longer spawns one process per directory while scaffolding (closes #201 for Rust)** — `RustServer.root` was `RootWithFallback(RustWorkspaceRoot())`, whose default fallback is `FileDirRoot` (the file's own directory). Before a `Cargo.toml` exists, `RustWorkspaceRoot()` returns `undefined`, so every `.rs` file fell back to its own directory as the root — and since LSP clients dedup by `` `${serverId}:${root}` ``, each directory spawned a **separate `rust-analyzer`** (the active-LSP count climbed one-per-file during project creation, and each server was rooted at a manifest-less dir where rust-analyzer can't function). Dropped the fallback for Rust: no `Cargo.toml` ⇒ `undefined` ⇒ the server is skipped (no spawn) until a manifest gives a stable, shared crate root, after which all files share one server. The with-manifest behavior is unchanged. (C# `csharp-ls` has the same fallback trap but a compounding bug — `createRootDetector` matches markers by exact filename, so `.csproj` never matches a real `Foo.csproj` and C# currently depends on the fallback entirely; fixing it needs extension/glob marker support, tracked on #201.)
98
+
99
+ - **Read-guard autopatch now tolerates mid-block blank-line drift in `oldText` (Tier A of #200)** — when an agent's `Edit` `oldText` differed from the file only by a blank line added/removed *inside* the block, the autopatch's fixed-length window matchers couldn't bridge it (any interior blank-line delta breaks 1:1 line alignment), so the edit failed `oldtext_not_found` and the agent had to re-read/retry. A new blank-line-insensitive matcher (`findBlankLineInsensitiveCandidate`) matches the `oldText`'s non-blank lines (indentation-insensitive) against consecutive content, skipping interior blanks, and — critically — **recovers and returns the real file span verbatim** so the applied `oldText` is actual file bytes. Safety-gated: anchored on ≥2 non-blank lines, requires the signature to match **exactly once** (refuses on 0 or ≥2), and inherits the caller's existing `correctedMatchCount === 1` check — it prefers a no-patch over ever patching the wrong span. Internal-whitespace tolerance (string-literal-sensitive, riskier) remains tracked as Tier B on #200.
100
+
101
+ - **Tests can no longer silently run against a stale in-place build (closes #198)** — `npm run build` emits compiled `.js` next to each `.ts`, and vitest resolves a test's `.js` import specifier to that literal compiled file. Editing a source `.ts` and running the suite without rebuilding therefore exercised the *previous* build — the change was silently untested while `npm run lint` (which type-checks the `.ts`) stayed green. A vitest `globalSetup` (`tests/support/check-build-freshness.ts`) now fails fast — for any launch, including a direct `npx vitest run` that a `pretest` hook would miss — when a compiled-source `.ts` under `clients/`/`commands/`/`tools/` (or root `index.ts`/`i18n.ts`) is newer than its `.js` or has none, with an actionable `⛔ Stale build … run npm run build` message. The detection logic is unit-tested against a temp fixture (`tests/build-freshness-guard.test.ts`). This is the guard for the gotcha that nearly mis-calibrated the cascade occupancy test.
102
+
103
+ - **LSP workspace-diagnostics + warm-path FS calls no longer block the event loop (perf hardening, follows #188/#191)** — four synchronous filesystem calls on LSP hot paths were converted to their async equivalents, all behavior-preserving: (1) `collectWorkspaceDiagnosticFiles` (the `lsp_diagnostics` project-wide enumeration) walked the tree with a non-yielding `readdirSync` recursion — **~44.5ms → 0.7ms** longest sync block at ~1,400 files, scaling linearly on monorepos — now an `fs.promises.readdir` yielding walk; (2) its per-file `readFileSync` worker reads → `await readFile`; (3) `handleNotifyOpen`'s document-open existence probe `existsSync` → `await access` (the `didChangeWatchedFiles` Created/Changed type is unchanged); (4) `isOnPath` (the runtime-install gate on the spawn fall-through) `spawnSync("where"/"which")` → the shared `isCommandAvailableAsync` (`safeSpawnAsync`, 5s timeout, same `status === 0` semantics) so a stalled finder can't freeze the loop. The spawn-dedup invariant (one in-flight launch per `serverId:root`) was verified already correct and left untouched. Guarded by `tests/clients/lsp/workspace-diagnostics-occupancy.test.ts`.
104
+
105
+ - **`lsp_diagnostics` cascade cleanup no longer stats files synchronously (perf hardening, partial #197)** — `LSPService.getAllDiagnostics` (the cascade-checking path) pruned tracked diagnostics with a blocking `existsSync` per file *inside* the prune predicate, holding the event loop across every tracked file. Existence is now resolved in an async pre-pass (`fs.promises.access`, concurrent) and pruning stays a synchronous in-memory map operation — same semantics (a file is pruned iff it's missing **or** older than the cascade TTL), via a new `client.getTrackedDiagnosticPaths()`. Guarded by `tests/clients/lsp/get-all-diagnostics-prune.test.ts`. The remaining sync calls under #197 (the `go`/`dotnet`/`gem` install `spawnSync` and the single-shot `launch.ts`/root-detection stats) are deliberately left: they run once per tool/launch, off the typing window, and the install conversion needs equivalence testing of real install side-effects + reconciling `safeSpawnAsync`'s forced `shell` / timeout against the install commands' `shell:false`.
6
106
 
7
107
  ## [3.8.50] - 2026-06-07
8
108
 
@@ -0,0 +1,51 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import { isTestMode } from "./env-utils.js";
4
+ import { getGlobalPiLensDir } from "./file-utils.js";
5
+ const AW_LOG_DIR = getGlobalPiLensDir();
6
+ const AW_LOG_FILE = path.join(AW_LOG_DIR, "actionable-warnings.log");
7
+ const AW_LOG_BACKUP_FILE = path.join(AW_LOG_DIR, "actionable-warnings.log.1");
8
+ const MAX_LOG_BYTES = Math.max(128 * 1024, Number.parseInt(process.env.PI_LENS_AW_LOG_MAX_BYTES ?? "1048576", 10) || 1048576);
9
+ try {
10
+ if (!fs.existsSync(AW_LOG_DIR)) {
11
+ fs.mkdirSync(AW_LOG_DIR, { recursive: true });
12
+ }
13
+ }
14
+ catch (err) {
15
+ void err;
16
+ }
17
+ function rotateIfNeeded() {
18
+ try {
19
+ if (!fs.existsSync(AW_LOG_FILE))
20
+ return;
21
+ const size = fs.statSync(AW_LOG_FILE).size;
22
+ if (size < MAX_LOG_BYTES)
23
+ return;
24
+ try {
25
+ fs.rmSync(AW_LOG_BACKUP_FILE, { force: true });
26
+ }
27
+ catch (err) {
28
+ void err;
29
+ }
30
+ fs.renameSync(AW_LOG_FILE, AW_LOG_BACKUP_FILE);
31
+ }
32
+ catch (err) {
33
+ void err;
34
+ }
35
+ }
36
+ export function logActionableWarningsEvent(entry) {
37
+ if (isTestMode()) {
38
+ return;
39
+ }
40
+ const line = `${JSON.stringify({ ts: new Date().toISOString(), ...entry })}\n`;
41
+ try {
42
+ rotateIfNeeded();
43
+ fs.appendFileSync(AW_LOG_FILE, line);
44
+ }
45
+ catch (err) {
46
+ void err;
47
+ }
48
+ }
49
+ export function getActionableWarningsLogPath() {
50
+ return AW_LOG_FILE;
51
+ }