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
@@ -1,10 +1,14 @@
1
1
  import * as nodeFs from "node:fs";
2
2
  import * as path from "node:path";
3
3
  import { applyConservativeActionableWarningFixes, checkActionableWarningsReportFresh, } from "./actionable-warnings.js";
4
- import { logLatency } from "./latency-logger.js";
5
- import { resyncLspFile, runFormatPhase } from "./pipeline.js";
6
4
  import { publishFilesTouched } from "./bus-publish.js";
7
5
  import { publishAutofixStart, publishFormatStart, } from "./format-events-publish.js";
6
+ import { logLatency } from "./latency-logger.js";
7
+ import { isPathIgnoredByProject } from "./file-utils.js";
8
+ import { newLspMutationCorrelationId } from "./lsp-mutation.js";
9
+ import { getGlobalActionableWarningMaxFixes, } from "./lens-config.js";
10
+ import { resyncLspFile, runFormatPhase } from "./pipeline.js";
11
+ import { getAmbientAbortSignal } from "./safe-spawn.js";
8
12
  import { appendProjectChange, } from "./project-changes.js";
9
13
  /**
10
14
  * A queued file is claimed by any flush once it has sat unclaimed by its own
@@ -15,6 +19,7 @@ import { appendProjectChange, } from "./project-changes.js";
15
19
  * extra staleness for guaranteed eventual formatting. (#791)
16
20
  */
17
21
  export const DEFERRED_FORMAT_STALE_AFTER_MS = 10 * 60_000;
22
+ const DEFERRED_FORMAT_CONCURRENCY = 3;
18
23
  function recordProjectChange(args) {
19
24
  const bump = args.runtime.bumpFileSeq;
20
25
  if (!bump)
@@ -137,43 +142,63 @@ export async function handleAgentEnd({ ctxCwd, getFlag, getFlagSource, notify, d
137
142
  return !disabled;
138
143
  });
139
144
  if (formatRecords.length > 0) {
140
- // Run all formatter subprocesses concurrently — no shared state touched here.
141
- // bumpFileSeq / cacheManager mutations happen in the sequential pass below.
142
- const outcomes = await Promise.all(formatRecords.map(async (record) => {
143
- const fileStart = Date.now();
144
- const filePath = path.resolve(record.filePath);
145
- if (!nodeFs.existsSync(filePath)) {
146
- dbg(`agent_end deferred_format skipped missing file: ${filePath}`);
147
- return { kind: "skipped", filePath, reason: "missing" };
148
- }
149
- try {
150
- const result = await runFormatPhase(filePath, getFormatService, dbg);
151
- return { kind: "done", record, filePath, result, fileStart };
152
- }
153
- catch (err) {
154
- const message = err instanceof Error ? err.message : String(err);
155
- dbg(`agent_end deferred_format failed for ${filePath}: ${message}`);
156
- return { kind: "failed", filePath, message, fileStart };
145
+ const work = new Array(formatRecords.length);
146
+ const started = new Set();
147
+ let nextIndex = 0;
148
+ const ambientSignal = getAmbientAbortSignal();
149
+ const worker = async () => {
150
+ while (nextIndex < formatRecords.length) {
151
+ const index = nextIndex++;
152
+ if (ambientSignal?.aborted)
153
+ return;
154
+ const record = formatRecords[index];
155
+ const fileStart = Date.now();
156
+ const filePath = path.resolve(record.filePath);
157
+ started.add(index);
158
+ if (!nodeFs.existsSync(filePath)) {
159
+ work[index] = { record, filePath, fileStart, missing: true };
160
+ continue;
161
+ }
162
+ try {
163
+ work[index] = {
164
+ record,
165
+ filePath,
166
+ fileStart,
167
+ result: await runFormatPhase(filePath, getFormatService, dbg),
168
+ };
169
+ }
170
+ catch (err) {
171
+ work[index] = {
172
+ record,
173
+ filePath,
174
+ fileStart,
175
+ error: err instanceof Error ? err.message : String(err),
176
+ };
177
+ }
157
178
  }
158
- }));
159
- // Process results sequentially — bumpFileSeq and cacheManager mutations
160
- // must stay ordered to avoid sequence number races.
161
- for (const outcome of outcomes) {
162
- if (outcome.kind === "skipped") {
163
- summary.skipped.push({
164
- filePath: outcome.filePath,
165
- reason: outcome.reason,
166
- });
179
+ };
180
+ await Promise.all(Array.from({ length: Math.min(DEFERRED_FORMAT_CONCURRENCY, formatRecords.length) }, () => worker()));
181
+ if (ambientSignal?.aborted) {
182
+ runtime.requeueDeferredFormatFiles(formatRecords.filter((_, index) => !started.has(index)));
183
+ }
184
+ for (const entry of work) {
185
+ if (!entry)
186
+ continue;
187
+ await new Promise((resolve) => setImmediate(resolve));
188
+ const { record, filePath, fileStart } = entry;
189
+ if (entry.missing) {
190
+ dbg(`agent_end deferred_format skipped missing file: ${filePath}`);
191
+ summary.skipped.push({ filePath, reason: "missing" });
167
192
  continue;
168
193
  }
169
- if (outcome.kind === "failed") {
170
- summary.failed.push({
171
- filePath: outcome.filePath,
172
- errors: [outcome.message],
173
- });
194
+ if (entry.error) {
195
+ dbg(`agent_end deferred_format failed for ${filePath}: ${entry.error}`);
196
+ summary.failed.push({ filePath, errors: [entry.error] });
174
197
  continue;
175
198
  }
176
- const { record, filePath, result, fileStart } = outcome;
199
+ const result = entry.result;
200
+ if (!result)
201
+ continue;
177
202
  summary.formatted++;
178
203
  if (result.formatFailures.length > 0) {
179
204
  summary.failed.push({ filePath, errors: result.formatFailures });
@@ -201,7 +226,7 @@ export async function handleAgentEnd({ ctxCwd, getFlag, getFlagSource, notify, d
201
226
  const content = nodeFs.readFileSync(filePath, "utf-8");
202
227
  const lineCount = content.split("\n").length;
203
228
  const hasImports = /^import\s/m.test(content);
204
- cacheManager.addModifiedRange(filePath, { start: 1, end: lineCount }, hasImports, bookkeepingCwd);
229
+ cacheManager.addModifiedRange(filePath, { start: 1, end: lineCount }, hasImports, bookkeepingCwd, currentSessionId ?? runtime.telemetrySessionId, "pi");
205
230
  }
206
231
  catch (err) {
207
232
  dbg(`agent_end deferred_format modified-range tracking failed for ${filePath}: ${err}`);
@@ -250,6 +275,14 @@ export async function handleAgentEnd({ ctxCwd, getFlag, getFlagSource, notify, d
250
275
  }
251
276
  else {
252
277
  const enabledFiles = actionReport.data.files.filter((file) => {
278
+ // #1247: the per-edit dispatch surface gates ignored files before
279
+ // running; the turn-end autofix must apply the same filter or a
280
+ // `.pi-lens.json` `ignore` entry cannot protect a prose file
281
+ // (CHANGELOG.md / AGENTS.md) from being rewritten here.
282
+ if (isPathIgnoredByProject(file.filePath, ctxCwd ?? runtime.projectRoot)) {
283
+ dbg(`agent_end actionable_warnings_autofix: skipped ${file.filePath} (ignored by project)`);
284
+ return false;
285
+ }
253
286
  const enabled = !!getFlag("lens-actionable-warning-autofix", file.filePath);
254
287
  if (!enabled) {
255
288
  const source = getFlagSource?.("lens-actionable-warning-autofix", file.filePath);
@@ -280,48 +313,42 @@ export async function handleAgentEnd({ ctxCwd, getFlag, getFlagSource, notify, d
280
313
  if (!freshness.fresh) {
281
314
  dbg(`agent_end actionable_warnings_autofix: stale report (${freshness.reason}; reportProjectSeqEnd=${freshness.reportProjectSeqEnd ?? "missing"}; currentProjectSeq=${freshness.currentProjectSeq}${freshness.filePath ? `; file=${freshness.filePath}; reportFileSeq=${freshness.reportFileSeq}; currentFileSeq=${freshness.currentFileSeq}` : ""}), skipping fixes`);
282
315
  }
283
- else {
316
+ else if (eligibleReport.summary.autoFixEligible > 0) {
284
317
  // #684: same "genuine work about to happen" gate as
285
318
  // publishFormatStart — only fires when the report is fresh AND
286
319
  // has at least one autofix-eligible warning, right before
287
320
  // applyConservativeActionableWarningFixes actually starts.
288
- if (eligibleReport.summary.autoFixEligible > 0) {
289
- publishAutofixStart({
290
- cwd: ctxCwd ?? runtime.projectRoot,
291
- paths: eligibleReport.files
292
- .filter((file) => file.warnings.some((warning) => !warning.suppressed &&
293
- warning.actions.some((action) => action.autoFixEligible)))
294
- .map((file) => file.filePath),
295
- eligibleCount: eligibleReport.summary.autoFixEligible,
296
- dbg,
297
- });
298
- }
321
+ publishAutofixStart({
322
+ cwd: ctxCwd ?? runtime.projectRoot,
323
+ paths: eligibleReport.files
324
+ .filter((file) => file.warnings.some((warning) => !warning.suppressed &&
325
+ warning.actions.some((action) => action.autoFixEligible)))
326
+ .map((file) => file.filePath),
327
+ eligibleCount: eligibleReport.summary.autoFixEligible,
328
+ dbg,
329
+ });
299
330
  const fixStart = Date.now();
331
+ const fixCwd = ctxCwd ?? runtime.projectRoot;
332
+ const mutationContext = {
333
+ cwd: fixCwd,
334
+ correlationId: newLspMutationCorrelationId(),
335
+ tool: "lsp-quickfix",
336
+ source: "autofix",
337
+ runtime,
338
+ cacheManager,
339
+ readGuard: getFlag("no-read-guard") ? undefined : runtime.readGuard,
340
+ recordAutofix: getFlag("lens-turn-summary")
341
+ ? (filePath) => runtime.turnSummary.recordAutofix(filePath, { tool: "lsp-quickfix" })
342
+ : undefined,
343
+ dbg,
344
+ };
300
345
  const fixSummary = await applyConservativeActionableWarningFixes({
301
- cwd: ctxCwd ?? runtime.projectRoot,
346
+ cwd: fixCwd,
302
347
  report: eligibleReport,
348
+ maxFixes: getGlobalActionableWarningMaxFixes(),
303
349
  dbg,
350
+ mutationContext,
304
351
  });
305
- for (const changedFile of fixSummary.changedFiles) {
306
- if (!nodeFs.existsSync(changedFile))
307
- continue;
308
- recordProjectChange({
309
- runtime,
310
- cwd: ctxCwd ?? runtime.projectRoot,
311
- filePath: changedFile,
312
- source: "autofix",
313
- dbg,
314
- });
315
- if (!getFlag("no-read-guard"))
316
- runtime.readGuard.recordWritten(changedFile);
317
- try {
318
- const content = nodeFs.readFileSync(changedFile, "utf-8");
319
- cacheManager.addModifiedRange(changedFile, { start: 1, end: content.split("\n").length }, /^import\s/m.test(content), ctxCwd ?? runtime.projectRoot);
320
- }
321
- catch (err) {
322
- dbg(`agent_end actionable warning changed-file tracking failed for ${changedFile}: ${err}`);
323
- }
324
- }
325
352
  if (fixSummary.changedFiles.length > 0) {
326
353
  publishFilesTouched({
327
354
  reason: "autofix",
@@ -334,13 +361,6 @@ export async function handleAgentEnd({ ctxCwd, getFlag, getFlagSource, notify, d
334
361
  kind: "autofix",
335
362
  })),
336
363
  });
337
- if (getFlag("lens-turn-summary")) {
338
- for (const changedFile of fixSummary.changedFiles) {
339
- runtime.turnSummary.recordAutofix(changedFile, {
340
- tool: "lsp-quickfix",
341
- });
342
- }
343
- }
344
364
  }
345
365
  logLatency({
346
366
  type: "phase",
@@ -359,6 +379,9 @@ export async function handleAgentEnd({ ctxCwd, getFlag, getFlagSource, notify, d
359
379
  notify(`pi-lens applied ${fixSummary.applied} conservative LSP warning quickfix(es)`, "info");
360
380
  }
361
381
  }
382
+ else {
383
+ dbg("agent_end actionable_warnings_autofix: fresh report, 0 autofix-eligible warnings, skipping");
384
+ }
362
385
  }
363
386
  }
364
387
  logLatency({
@@ -375,6 +398,19 @@ export async function handleAgentEnd({ ctxCwd, getFlag, getFlagSource, notify, d
375
398
  skipped: summary.skipped.length,
376
399
  },
377
400
  });
401
+ logLatency({
402
+ type: "phase",
403
+ toolName: "agent_end",
404
+ filePath: ctxCwd ?? runtime.projectRoot,
405
+ phase: "agent_end_deferred_format_done",
406
+ durationMs: Date.now() - startedAt,
407
+ metadata: {
408
+ formatted: summary.formatted,
409
+ changed: summary.changed.length,
410
+ failed: summary.failed.length,
411
+ skipped: summary.skipped.length,
412
+ },
413
+ });
378
414
  dbg(`agent_end deferred_format complete: formatted=${summary.formatted} changed=${summary.changed.length} failed=${summary.failed.length} skipped=${summary.skipped.length}`);
379
415
  if (summary.failed.length > 0) {
380
416
  notify(`pi-lens deferred format: ${summary.changed.length} changed, ${summary.failed.length} failed`, "warning");
@@ -1,30 +1,54 @@
1
+ // Exported so the Stop-hook bin strips exactly what these bridges prepend.
2
+ export const AUTOMATION_FRAMING = "[pi-lens automated check — not a user request] ";
3
+ function turnEndMessage(content) {
4
+ return {
5
+ role: "user",
6
+ content: `${AUTOMATION_FRAMING}Address 🔴 blockers before continuing; ℹ️ advisories are informational only.\n\n${content}`,
7
+ };
8
+ }
9
+ /** Read a turn-end finding without changing its durable delivery state. */
10
+ export function peekTurnEndFindings(cacheManager, cwd) {
11
+ const findings = cacheManager.readCache("turn-end-findings", cwd);
12
+ if (!findings?.data?.content || findings.data.consumed === true)
13
+ return;
14
+ return { messages: [turnEndMessage(findings.data.content)] };
15
+ }
1
16
  export function consumeTurnEndFindings(cacheManager, cwd) {
2
17
  const findings = cacheManager.readCache("turn-end-findings", cwd);
18
+ if (!findings?.data?.content || findings.data.consumed === true)
19
+ return;
20
+ // A blocker record is also the opt-in commit gate's durable state. Mark the
21
+ // context message consumed without deleting the record; clean/advisory-only
22
+ // records retain the historical consume-and-clear behavior.
23
+ if (findings.data.hasBlockers === true &&
24
+ typeof findings.data.sessionId === "string") {
25
+ cacheManager.writeCache("turn-end-findings", { ...findings.data, consumed: true }, cwd);
26
+ }
27
+ else {
28
+ cacheManager.clearCache("turn-end-findings", cwd);
29
+ }
30
+ return { messages: [turnEndMessage(findings.data.content)] };
31
+ }
32
+ /** Read test findings without consuming them; used by acknowledged IPC delivery. */
33
+ export function peekTestFindings(cacheManager, cwd) {
34
+ const findings = cacheManager.readCache("test-runner-findings", cwd);
3
35
  if (!findings?.data?.content)
4
36
  return;
5
- cacheManager.writeCache("turn-end-findings", null, cwd);
6
37
  return {
7
38
  messages: [
8
39
  {
9
40
  role: "user",
10
- content: `[pi-lens automated check — not a user request] Address 🔴 blockers before continuing; ℹ️ advisories are informational only.\n\n${findings.data.content}`,
41
+ content: `${AUTOMATION_FRAMING}Test failures detected last turn — fix before continuing:\n\n${findings.data.content}`,
11
42
  },
12
43
  ],
13
44
  };
14
45
  }
15
46
  export function consumeTestFindings(cacheManager, cwd) {
16
- const findings = cacheManager.readCache("test-runner-findings", cwd);
17
- if (!findings?.data?.content)
47
+ const findings = peekTestFindings(cacheManager, cwd);
48
+ if (!findings)
18
49
  return;
19
50
  cacheManager.writeCache("test-runner-findings", null, cwd);
20
- return {
21
- messages: [
22
- {
23
- role: "user",
24
- content: `[pi-lens automated check — not a user request] Test failures detected last turn — fix before continuing:\n\n${findings.data.content}`,
25
- },
26
- ],
27
- };
51
+ return findings;
28
52
  }
29
53
  export function consumeSessionStartGuidance(cacheManager, cwd) {
30
54
  const guidance = cacheManager.readCache("session-start-guidance", cwd);
@@ -1,6 +1,8 @@
1
1
  import { randomBytes } from "node:crypto";
2
+ import * as fs from "node:fs";
2
3
  import * as path from "node:path";
3
4
  import { normalizeMapKey } from "./path-utils.js";
5
+ import { PathKeyedMap } from "./path-keyed-map.js";
4
6
  import { ReadGuard } from "./read-guard.js";
5
7
  import { RUNTIME_CONFIG } from "./runtime-config.js";
6
8
  import { TurnSummaryCollector } from "./turn-summary.js";
@@ -45,12 +47,13 @@ export class RuntimeCoordinator {
45
47
  _fileLastProjectSeq = new Map();
46
48
  _gitGuardHasBlockers = false;
47
49
  _gitGuardSummary = "";
50
+ _gitGuardCacheUnknownReason;
48
51
  callGraph = null;
49
52
  wordIndex = null;
50
53
  _readGuard = null;
51
54
  _pendingDeferredFormatFiles = new Map();
52
55
  _lspReadWarmState = new Map();
53
- _pendingInlineBlockers = new Map();
56
+ _pendingInlineBlockers = new PathKeyedMap(normalizeMapKey);
54
57
  _actionableWarningsThisTurn = new Map();
55
58
  _codeQualityWarningsThisTurn = new Map();
56
59
  // #484: opt-in per-RUN summary of diagnostics/autofixes/formats,
@@ -59,9 +62,9 @@ export class RuntimeCoordinator {
59
62
  // (cheap, empty Map) but callers gate recording behind the
60
63
  // `lens-turn-summary` flag so it's a true no-op when the feature is off.
61
64
  _turnSummary = new TurnSummaryCollector();
62
- resetForSession() {
65
+ resetForSession(startedAt = Date.now()) {
63
66
  this._sessionGeneration += 1;
64
- this._sessionStartedAt = Date.now();
67
+ this._sessionStartedAt = startedAt;
65
68
  this._complexityBaselines.clear();
66
69
  this._pipelineCrashCounts.clear();
67
70
  this._cachedExports.clear();
@@ -87,6 +90,7 @@ export class RuntimeCoordinator {
87
90
  this._fileLastProjectSeq.clear();
88
91
  this._gitGuardHasBlockers = false;
89
92
  this._gitGuardSummary = "";
93
+ this._gitGuardCacheUnknownReason = undefined;
90
94
  this._readGuard = null;
91
95
  this._pendingDeferredFormatFiles.clear();
92
96
  this._lspReadWarmState.clear();
@@ -107,8 +111,12 @@ export class RuntimeCoordinator {
107
111
  this._cascadeSessionStats.coldSnapshotTouches += coldSnapshotTouches;
108
112
  }
109
113
  updateGitGuardStatus(hasBlockers, output) {
110
- this._gitGuardHasBlockers = hasBlockers;
111
- if (!hasBlockers) {
114
+ // The status is an aggregate over the current per-file map. A clean B
115
+ // result must not erase an unresolved A result; the pipeline records/clears
116
+ // the edited file immediately before this method runs.
117
+ this._gitGuardHasBlockers =
118
+ hasBlockers || this.getInlineBlockersSnapshot().length > 0;
119
+ if (!this._gitGuardHasBlockers) {
112
120
  this._gitGuardSummary = "";
113
121
  return;
114
122
  }
@@ -116,7 +124,10 @@ export class RuntimeCoordinator {
116
124
  .split("\n")
117
125
  .map((line) => line.trim())
118
126
  .find((line) => line.length > 0);
119
- this._gitGuardSummary = (firstLine ?? "Unresolved blockers detected").slice(0, 160);
127
+ const summaries = this.getInlineBlockersSnapshot()
128
+ .map((entry) => entry.summary.trim())
129
+ .filter(Boolean);
130
+ this._gitGuardSummary = (summaries[0] ?? firstLine ?? "Unresolved blockers detected").slice(0, 160);
120
131
  }
121
132
  get gitGuardHasBlockers() {
122
133
  return this._gitGuardHasBlockers;
@@ -124,6 +135,15 @@ export class RuntimeCoordinator {
124
135
  get gitGuardSummary() {
125
136
  return this._gitGuardSummary;
126
137
  }
138
+ markGitGuardCacheUnknown(reason) {
139
+ this._gitGuardCacheUnknownReason = reason;
140
+ }
141
+ clearGitGuardCacheUnknown() {
142
+ this._gitGuardCacheUnknownReason = undefined;
143
+ }
144
+ get gitGuardCacheUnknownReason() {
145
+ return this._gitGuardCacheUnknownReason;
146
+ }
127
147
  beginTurn() {
128
148
  this._cascadeRuns = [];
129
149
  // _pendingCascadeRuns is deliberately NOT cleared here: a cascade compute
@@ -131,7 +151,9 @@ export class RuntimeCoordinator {
131
151
  // measured up to ~19s) must surface on the NEXT turn_end, not be dropped —
132
152
  // pre-#450 those findings were always awaited, never lost. Session reset
133
153
  // still clears it.
134
- this._pendingInlineBlockers.clear();
154
+ // Inline blockers are session-scoped per-file state. They are cleared only
155
+ // when that file is re-analyzed clean or the session resets; a new turn must
156
+ // not let a clean unrelated file erase an unresolved blocker.
135
157
  this._actionableWarningsThisTurn.clear();
136
158
  this._codeQualityWarningsThisTurn.clear();
137
159
  // _turnSummary is deliberately NOT cleared here (#484 rework): the
@@ -365,8 +387,45 @@ export class RuntimeCoordinator {
365
387
  clearInlineBlockers(filePath) {
366
388
  this._pendingInlineBlockers.delete(path.resolve(filePath));
367
389
  }
390
+ reconcileInlineBlockers() {
391
+ // Rebuild, never delete-in-place: `PathKeyedMap.delete()` re-normalizes
392
+ // the key, and `normalizeMapKey` realpaths a live file but lowercases
393
+ // the tail of a deleted one — so on Windows a mixed-case filename gets
394
+ // a DIFFERENT delete-time key than its set-time key and the delete
395
+ // misses (#1245, verified live: `MyCase.ts` survived reconcile).
396
+ // Existence-checking the display path and rebuilding survivors avoids
397
+ // the key-mismatch entirely; live survivors re-set to identical keys
398
+ // (both realpath), so only the stale entries are dropped.
399
+ const survivors = [];
400
+ for (const [displayPath, value] of this._pendingInlineBlockers.entries()) {
401
+ if (fs.existsSync(displayPath))
402
+ survivors.push([displayPath, value]);
403
+ }
404
+ if (survivors.length !== this._pendingInlineBlockers.size) {
405
+ this._pendingInlineBlockers.clear();
406
+ for (const [displayPath, value] of survivors) {
407
+ this._pendingInlineBlockers.set(displayPath, value);
408
+ }
409
+ }
410
+ }
411
+ /**
412
+ * Stale-entry reconcile (#1245): a blocker recorded for a file that has
413
+ * since been deleted can never be cleared — `clearInlineBlockers` fires
414
+ * only on a LATER dispatch of the same path, which a deleted file never
415
+ * gets. Every read of the blocker map (turn_end injection, git-guard
416
+ * size/summary, `syncGitGuardRecord`) therefore drops entries whose file no
417
+ * longer exists on disk: a blocker for a deleted file is stale by
418
+ * definition (the agent cannot fix it), so it must not re-surface every
419
+ * turn or gate a commit. The map is tiny (per-turn blockers) and reads are
420
+ * bounded (once per turn_end / tool_result), so the probe cost is
421
+ * negligible.
422
+ */
423
+ getInlineBlockersSnapshot() {
424
+ this.reconcileInlineBlockers();
425
+ return [...this._pendingInlineBlockers.values()];
426
+ }
368
427
  consumeInlineBlockers() {
369
- const entries = [...this._pendingInlineBlockers.values()];
428
+ const entries = this.getInlineBlockersSnapshot();
370
429
  this._pendingInlineBlockers.clear();
371
430
  return entries;
372
431
  }
@@ -500,6 +559,15 @@ export class RuntimeCoordinator {
500
559
  }
501
560
  return { claimed, staleClaimed, deferredToOwner };
502
561
  }
562
+ /** Return claimed records that were never started by an aborted drain. */
563
+ requeueDeferredFormatFiles(records) {
564
+ for (const record of records) {
565
+ const key = path.resolve(record.filePath);
566
+ if (!this._pendingDeferredFormatFiles.has(key)) {
567
+ this._pendingDeferredFormatFiles.set(key, record);
568
+ }
569
+ }
570
+ }
503
571
  shouldWarmLspOnRead(filePath, maxAgeMs = 120_000) {
504
572
  const state = this._lspReadWarmState.get(path.resolve(filePath));
505
573
  if (!state)