@duckmind/dm-darwin-x64 0.66.4 → 0.66.6

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 (1115) hide show
  1. package/README.md +4 -0
  2. package/dm +2 -2
  3. package/extensions/.dm-extensions.json +755 -37
  4. package/extensions/dm-lens/clients/actionable-warnings.js +63 -11
  5. package/extensions/dm-lens/clients/advisory-provenance.js +5 -3
  6. package/extensions/dm-lens/clients/agent-nudge.js +106 -2
  7. package/extensions/dm-lens/clients/analysis-root.js +103 -0
  8. package/extensions/dm-lens/clients/ast-grep-client.js +18 -7
  9. package/extensions/dm-lens/clients/ast-grep-rule-manager.js +35 -12
  10. package/extensions/dm-lens/clients/biome-client.js +18 -12
  11. package/extensions/dm-lens/clients/blocker-freshness.js +1 -9
  12. package/extensions/dm-lens/clients/bounded-pid-file-lock.js +141 -30
  13. package/extensions/dm-lens/clients/bounded-telemetry.js +3 -0
  14. package/extensions/dm-lens/clients/cache/rule-cache.js +2 -17
  15. package/extensions/dm-lens/clients/cache-manager.js +105 -17
  16. package/extensions/dm-lens/clients/cargo-manifest.js +23 -3
  17. package/extensions/dm-lens/clients/complexity-client.js +1 -1
  18. package/extensions/dm-lens/clients/config-core/normalize.js +9 -0
  19. package/extensions/dm-lens/clients/config-core/schema.js +3 -0
  20. package/extensions/dm-lens/clients/config-schema.js +105 -2
  21. package/extensions/dm-lens/clients/confirmed-delete.js +11 -0
  22. package/extensions/dm-lens/clients/custom-rule-locations.js +28 -0
  23. package/extensions/dm-lens/clients/dead-code-client.js +81 -12
  24. package/extensions/dm-lens/clients/deferred-runner-blockers.js +108 -0
  25. package/extensions/dm-lens/clients/deferred-test-targets.js +31 -0
  26. package/extensions/dm-lens/clients/degradation-ledger.js +21 -3
  27. package/extensions/dm-lens/clients/demoted-finding-render.js +5 -2
  28. package/extensions/dm-lens/clients/dependency-checker.js +13 -4
  29. package/extensions/dm-lens/clients/diagnostic-line-freshness.js +3 -5
  30. package/extensions/dm-lens/clients/dispatch/adopted-root-runners.js +22 -0
  31. package/extensions/dm-lens/clients/dispatch/dispatcher.js +105 -27
  32. package/extensions/dm-lens/clients/dispatch/facts/comment-facts.js +5 -2
  33. package/extensions/dm-lens/clients/dispatch/facts/function-facts.js +1 -1
  34. package/extensions/dm-lens/clients/dispatch/facts/import-facts.js +2 -2
  35. package/extensions/dm-lens/clients/dispatch/facts/tree-sitter-facts.js +2 -2
  36. package/extensions/dm-lens/clients/dispatch/facts/try-catch-facts.js +1 -1
  37. package/extensions/dm-lens/clients/dispatch/finding-policy.js +12 -4
  38. package/extensions/dm-lens/clients/dispatch/integration.js +60 -15
  39. package/extensions/dm-lens/clients/dispatch/known-runner-ids.js +13 -0
  40. package/extensions/dm-lens/clients/dispatch/pending-runner-findings.js +66 -11
  41. package/extensions/dm-lens/clients/dispatch/plan.js +6 -0
  42. package/extensions/dm-lens/clients/dispatch/rule-id-normalize.js +9 -2
  43. package/extensions/dm-lens/clients/dispatch/rule-policy.js +33 -4
  44. package/extensions/dm-lens/clients/dispatch/runners/ast-grep-napi.js +9 -5
  45. package/extensions/dm-lens/clients/dispatch/runners/biome-check.js +5 -2
  46. package/extensions/dm-lens/clients/dispatch/runners/cue-vet.js +7 -3
  47. package/extensions/dm-lens/clients/dispatch/runners/detekt.js +3 -3
  48. package/extensions/dm-lens/clients/dispatch/runners/fact-rules.js +3 -3
  49. package/extensions/dm-lens/clients/dispatch/runners/fish-indent.js +10 -6
  50. package/extensions/dm-lens/clients/dispatch/runners/gleam-check.js +8 -5
  51. package/extensions/dm-lens/clients/dispatch/runners/go-vet.js +2 -1
  52. package/extensions/dm-lens/clients/dispatch/runners/lsp.js +28 -8
  53. package/extensions/dm-lens/clients/dispatch/runners/oxlint.js +2 -2
  54. package/extensions/dm-lens/clients/dispatch/runners/php-lint.js +6 -7
  55. package/extensions/dm-lens/clients/dispatch/runners/prisma-validate.js +18 -8
  56. package/extensions/dm-lens/clients/dispatch/runners/psscriptanalyzer.js +3 -3
  57. package/extensions/dm-lens/clients/dispatch/runners/pyright.js +1 -1
  58. package/extensions/dm-lens/clients/dispatch/runners/rust-clippy.js +25 -6
  59. package/extensions/dm-lens/clients/dispatch/runners/shellcheck.js +71 -4
  60. package/extensions/dm-lens/clients/dispatch/runners/shfmt.js +5 -1
  61. package/extensions/dm-lens/clients/dispatch/runners/spellcheck.js +3 -3
  62. package/extensions/dm-lens/clients/dispatch/runners/swiftlint.js +3 -3
  63. package/extensions/dm-lens/clients/dispatch/runners/taplo.js +17 -5
  64. package/extensions/dm-lens/clients/dispatch/runners/tree-sitter.js +12 -5
  65. package/extensions/dm-lens/clients/dispatch/runners/trivy-config.js +3 -3
  66. package/extensions/dm-lens/clients/dispatch/runners/utils/runner-helpers.js +127 -46
  67. package/extensions/dm-lens/clients/dispatch/runners/utils/tool-failure.js +11 -6
  68. package/extensions/dm-lens/clients/dispatch/runners/vale.js +3 -3
  69. package/extensions/dm-lens/clients/dispatch/runners/yaml-rule-parser.js +8 -1
  70. package/extensions/dm-lens/clients/dispatch/types.js +21 -1
  71. package/extensions/dm-lens/clients/dispatch/utils/format-utils.js +13 -0
  72. package/extensions/dm-lens/clients/effective-config.js +14 -3
  73. package/extensions/dm-lens/clients/ephemeral-root.js +71 -0
  74. package/extensions/dm-lens/clients/file-kinds.js +9 -0
  75. package/extensions/dm-lens/clients/file-mutation-queue.js +95 -0
  76. package/extensions/dm-lens/clients/file-utils.js +87 -8
  77. package/extensions/dm-lens/clients/finding-delivery-gate.js +5 -2
  78. package/extensions/dm-lens/clients/fix-run-restore.js +416 -0
  79. package/extensions/dm-lens/clients/format-events-publish.js +82 -2
  80. package/extensions/dm-lens/clients/format-service.js +10 -5
  81. package/extensions/dm-lens/clients/formatters.js +29 -12
  82. package/extensions/dm-lens/clients/generation-guard.js +50 -8
  83. package/extensions/dm-lens/clients/generation-lock.js +184 -0
  84. package/extensions/dm-lens/clients/git-guard.js +19 -1
  85. package/extensions/dm-lens/clients/gitleaks-client.js +13 -4
  86. package/extensions/dm-lens/clients/grammar-source.js +5 -0
  87. package/extensions/dm-lens/clients/grammar-wasm-imports.js +48 -0
  88. package/extensions/dm-lens/clients/gzip-stage-write.js +15 -7
  89. package/extensions/dm-lens/clients/hard-failure-summary.js +28 -0
  90. package/extensions/dm-lens/clients/hook-budgets.js +1 -0
  91. package/extensions/dm-lens/clients/inline-blocker-fields.js +8 -0
  92. package/extensions/dm-lens/clients/install-diagnostics.js +31 -16
  93. package/extensions/dm-lens/clients/installer/index.js +238 -58
  94. package/extensions/dm-lens/clients/instance-reaper.js +164 -52
  95. package/extensions/dm-lens/clients/instance-registry-lock.js +117 -76
  96. package/extensions/dm-lens/clients/instance-registry-tail.js +17 -0
  97. package/extensions/dm-lens/clients/instance-registry.js +248 -91
  98. package/extensions/dm-lens/clients/io-bridge-contract.js +7 -0
  99. package/extensions/dm-lens/clients/io-bridge.js +372 -0
  100. package/extensions/dm-lens/clients/jscpd-client.js +83 -35
  101. package/extensions/dm-lens/clients/knip-client.js +75 -6
  102. package/extensions/dm-lens/clients/knip-scan-roots.js +29 -0
  103. package/extensions/dm-lens/clients/language-policy.js +2 -0
  104. package/extensions/dm-lens/clients/language-profile.js +4 -1
  105. package/extensions/dm-lens/clients/language-registry.js +4 -1
  106. package/extensions/dm-lens/clients/latency-logger.js +11 -1
  107. package/extensions/dm-lens/clients/lens-config.js +10 -1
  108. package/extensions/dm-lens/clients/lens-engine.js +1 -1
  109. package/extensions/dm-lens/clients/lens-flag-registry.js +14 -24
  110. package/extensions/dm-lens/clients/lsp/aggregation.js +9 -8
  111. package/extensions/dm-lens/clients/lsp/auxiliary-lifecycle.js +23 -0
  112. package/extensions/dm-lens/clients/lsp/capabilities.js +98 -0
  113. package/extensions/dm-lens/clients/lsp/cascade-tier.js +8 -3
  114. package/extensions/dm-lens/clients/lsp/client.js +282 -48
  115. package/extensions/dm-lens/clients/lsp/config.js +247 -15
  116. package/extensions/dm-lens/clients/lsp/document-drift.js +23 -11
  117. package/extensions/dm-lens/clients/lsp/edits.js +137 -85
  118. package/extensions/dm-lens/clients/lsp/grouping.js +38 -0
  119. package/extensions/dm-lens/clients/lsp/index.js +484 -275
  120. package/extensions/dm-lens/clients/lsp/launch.js +106 -6
  121. package/extensions/dm-lens/clients/lsp/pending-aux-coverage.js +37 -7
  122. package/extensions/dm-lens/clients/lsp/resolved-config.js +188 -0
  123. package/extensions/dm-lens/clients/lsp/server-covers.js +8 -0
  124. package/extensions/dm-lens/clients/lsp/server-traits.js +39 -0
  125. package/extensions/dm-lens/clients/lsp/server.js +259 -34
  126. package/extensions/dm-lens/clients/lsp/service-singleton.js +59 -0
  127. package/extensions/dm-lens/clients/lsp/session-roots.js +1 -1
  128. package/extensions/dm-lens/clients/lsp/wait-policy/classification.js +2 -1
  129. package/extensions/dm-lens/clients/lsp/wait-policy/strategies.js +25 -4
  130. package/extensions/dm-lens/clients/lsp/workspace-diagnostics-cache.js +6 -3
  131. package/extensions/dm-lens/clients/lsp-budget.js +9 -5
  132. package/extensions/dm-lens/clients/lsp-document-symbols.js +1 -1
  133. package/extensions/dm-lens/clients/lsp-lazy.js +1 -1
  134. package/extensions/dm-lens/clients/lsp-mutation.js +44 -8
  135. package/extensions/dm-lens/clients/lsp-status.js +5 -7
  136. package/extensions/dm-lens/clients/mcp/analyze.js +24 -11
  137. package/extensions/dm-lens/clients/mcp/ipc.js +26 -7
  138. package/extensions/dm-lens/clients/mcp/session.js +1 -1
  139. package/extensions/dm-lens/clients/memory-sampler.js +48 -3
  140. package/extensions/dm-lens/clients/module-report-lsp.js +1 -1
  141. package/extensions/dm-lens/clients/module-report.js +9 -3
  142. package/extensions/dm-lens/clients/mutating-tool.js +12 -3
  143. package/extensions/dm-lens/clients/mutation-attribution.js +8 -0
  144. package/extensions/dm-lens/clients/mutation-bridge.js +181 -48
  145. package/extensions/dm-lens/clients/ndjson-logger.js +33 -0
  146. package/extensions/dm-lens/clients/observed-mutation-sources.js +1 -1
  147. package/extensions/dm-lens/clients/observed-mutation.js +40 -20
  148. package/extensions/dm-lens/clients/opaque-mutation-scan.js +40 -13
  149. package/extensions/dm-lens/clients/opengrep-client.js +6 -3
  150. package/extensions/dm-lens/clients/package-manager.js +61 -6
  151. package/extensions/dm-lens/clients/path-utils.js +77 -18
  152. package/extensions/dm-lens/clients/performance-report.js +1 -1
  153. package/extensions/dm-lens/clients/persist-worker-stats.js +8 -0
  154. package/extensions/dm-lens/clients/persistent-reverify.js +5 -1
  155. package/extensions/dm-lens/clients/pipeline.js +310 -114
  156. package/extensions/dm-lens/clients/probe-home-state.js +6 -1
  157. package/extensions/dm-lens/clients/process-bridge.js +28 -0
  158. package/extensions/dm-lens/clients/process-snapshot.js +115 -2
  159. package/extensions/dm-lens/clients/project-changes.js +81 -19
  160. package/extensions/dm-lens/clients/project-diagnostics/fresh-fetch.js +18 -13
  161. package/extensions/dm-lens/clients/project-lens-config.js +32 -5
  162. package/extensions/dm-lens/clients/project-scale.js +1 -1
  163. package/extensions/dm-lens/clients/project-snapshot-fingerprint.js +17 -12
  164. package/extensions/dm-lens/clients/project-snapshot-persist-worker.js +2 -3
  165. package/extensions/dm-lens/clients/project-snapshot.js +229 -64
  166. package/extensions/dm-lens/clients/python-environment.js +44 -2
  167. package/extensions/dm-lens/clients/quiet-window.js +6 -3
  168. package/extensions/dm-lens/clients/read-bridge.js +28 -20
  169. package/extensions/dm-lens/clients/read-expansion.js +1 -1
  170. package/extensions/dm-lens/clients/read-guard-branch.js +129 -0
  171. package/extensions/dm-lens/clients/read-guard-logger.js +11 -3
  172. package/extensions/dm-lens/clients/read-guard-tool-lines.js +16 -8
  173. package/extensions/dm-lens/clients/read-guard.js +516 -176
  174. package/extensions/dm-lens/clients/review-graph/builder.js +132 -54
  175. package/extensions/dm-lens/clients/review-graph/git-identity.js +59 -1
  176. package/extensions/dm-lens/clients/review-graph/import-resolvers.js +6 -8
  177. package/extensions/dm-lens/clients/runtime-agent-end.js +271 -68
  178. package/extensions/dm-lens/clients/runtime-coordinator.js +386 -74
  179. package/extensions/dm-lens/clients/runtime-session.js +174 -31
  180. package/extensions/dm-lens/clients/runtime-tool-call.js +92 -17
  181. package/extensions/dm-lens/clients/runtime-tool-result.js +477 -137
  182. package/extensions/dm-lens/clients/runtime-turn.js +949 -355
  183. package/extensions/dm-lens/clients/safe-spawn.js +12 -4
  184. package/extensions/dm-lens/clients/scanner-temp-root.js +8 -0
  185. package/extensions/dm-lens/clients/scratch-tree-policy.js +9 -0
  186. package/extensions/dm-lens/clients/search-read-registration.js +2 -1
  187. package/extensions/dm-lens/clients/session-lifecycle.js +120 -18
  188. package/extensions/dm-lens/clients/session-scope.js +456 -0
  189. package/extensions/dm-lens/clients/session-state-store.js +23 -20
  190. package/extensions/dm-lens/clients/sg-runner.js +24 -6
  191. package/extensions/dm-lens/clients/sgconfig.js +35 -5
  192. package/extensions/dm-lens/clients/startup-scan.js +10 -2
  193. package/extensions/dm-lens/clients/test-runner-client.js +258 -48
  194. package/extensions/dm-lens/clients/test-runner-delivery.js +46 -9
  195. package/extensions/dm-lens/clients/test-target-roots.js +20 -0
  196. package/extensions/dm-lens/clients/tool-agreement.js +282 -19
  197. package/extensions/dm-lens/clients/tool-config.js +1 -0
  198. package/extensions/dm-lens/clients/tool-event.js +6 -0
  199. package/extensions/dm-lens/clients/tool-probe.js +8 -0
  200. package/extensions/dm-lens/clients/tool-set-policy.js +36 -26
  201. package/extensions/dm-lens/clients/tree-sitter-client.js +304 -34
  202. package/extensions/dm-lens/clients/tree-sitter-query-loader.js +76 -2
  203. package/extensions/dm-lens/clients/tree-sitter-shared.js +2 -2
  204. package/extensions/dm-lens/clients/tree-sitter-symbol-extractor.js +3 -1
  205. package/extensions/dm-lens/clients/trivy-client.js +40 -7
  206. package/extensions/dm-lens/clients/turn-end/delivery-holds.js +59 -0
  207. package/extensions/dm-lens/clients/widget-state.js +36 -2
  208. package/extensions/dm-lens/clients/word-index.js +73 -5
  209. package/extensions/dm-lens/clients/workspace-topology.js +11 -4
  210. package/extensions/dm-lens/clients/write-ordering-guard.js +4 -0
  211. package/extensions/dm-lens/config/dependency-cruiser-eager-allowlist.json +9 -3
  212. package/extensions/dm-lens/dist/clients/actionable-warnings-logger.js +24 -0
  213. package/extensions/dm-lens/dist/clients/actionable-warnings.js +1062 -0
  214. package/extensions/dm-lens/dist/clients/advisory-provenance.js +387 -0
  215. package/extensions/dm-lens/dist/clients/agent-behavior-client.js +94 -0
  216. package/extensions/dm-lens/dist/clients/agent-nudge.js +253 -0
  217. package/extensions/dm-lens/dist/clients/analysed-root.js +1 -0
  218. package/extensions/dm-lens/dist/clients/analysis-root.js +100 -0
  219. package/extensions/dm-lens/dist/clients/ast-grep-client.js +587 -0
  220. package/extensions/dm-lens/dist/clients/ast-grep-rule-manager.js +137 -0
  221. package/extensions/dm-lens/dist/clients/ast-grep-tool-logger.js +85 -0
  222. package/extensions/dm-lens/dist/clients/ast-grep-types.js +1 -0
  223. package/extensions/dm-lens/dist/clients/ast-grep-yaml-synth.js +109 -0
  224. package/extensions/dm-lens/dist/clients/atomic-write-staging.js +66 -0
  225. package/extensions/dm-lens/dist/clients/atomic-write.js +37 -0
  226. package/extensions/dm-lens/dist/clients/availability-probe-flight.js +11 -0
  227. package/extensions/dm-lens/dist/clients/bash-file-access.js +830 -0
  228. package/extensions/dm-lens/dist/clients/biome-client.js +223 -0
  229. package/extensions/dm-lens/dist/clients/blocker-freshness.js +377 -0
  230. package/extensions/dm-lens/dist/clients/blocker-past-eof.js +49 -0
  231. package/extensions/dm-lens/dist/clients/bootstrap.js +246 -0
  232. package/extensions/dm-lens/dist/clients/bounded-cache.js +105 -0
  233. package/extensions/dm-lens/dist/clients/bounded-pid-file-lock.js +275 -0
  234. package/extensions/dm-lens/dist/clients/bounded-telemetry.js +107 -0
  235. package/extensions/dm-lens/dist/clients/build-identity.js +45 -0
  236. package/extensions/dm-lens/dist/clients/bundled-resource-health.js +41 -0
  237. package/extensions/dm-lens/dist/clients/bus-events-logger.js +48 -0
  238. package/extensions/dm-lens/dist/clients/bus-publish.js +117 -0
  239. package/extensions/dm-lens/dist/clients/cache/rule-cache.js +87 -0
  240. package/extensions/dm-lens/dist/clients/cache-manager.js +428 -0
  241. package/extensions/dm-lens/dist/clients/cache-observability.js +847 -0
  242. package/extensions/dm-lens/dist/clients/call-graph.js +628 -0
  243. package/extensions/dm-lens/dist/clients/cargo-manifest.js +171 -0
  244. package/extensions/dm-lens/dist/clients/cascade-budget.js +52 -0
  245. package/extensions/dm-lens/dist/clients/cascade-format.js +160 -0
  246. package/extensions/dm-lens/dist/clients/cascade-logger.js +22 -0
  247. package/extensions/dm-lens/dist/clients/cascade-types.js +1 -0
  248. package/extensions/dm-lens/dist/clients/child-unref.js +59 -0
  249. package/extensions/dm-lens/dist/clients/code-quality-warnings.js +192 -0
  250. package/extensions/dm-lens/dist/clients/codebase-model.js +110 -0
  251. package/extensions/dm-lens/dist/clients/collateral-test-role.js +8 -0
  252. package/extensions/dm-lens/dist/clients/complexity-client.js +398 -0
  253. package/extensions/dm-lens/dist/clients/config-core/deny.js +117 -0
  254. package/extensions/dm-lens/dist/clients/config-core/index.js +2 -0
  255. package/extensions/dm-lens/dist/clients/config-core/merge.js +228 -0
  256. package/extensions/dm-lens/dist/clients/config-core/normalize.js +226 -0
  257. package/extensions/dm-lens/dist/clients/config-core/process-spec.js +147 -0
  258. package/extensions/dm-lens/dist/clients/config-core/provenance.js +54 -0
  259. package/extensions/dm-lens/dist/clients/config-core/records.js +79 -0
  260. package/extensions/dm-lens/dist/clients/config-core/resolve.js +48 -0
  261. package/extensions/dm-lens/dist/clients/config-core/safe-object.js +22 -0
  262. package/extensions/dm-lens/dist/clients/config-core/schema.js +93 -0
  263. package/extensions/dm-lens/dist/clients/config-diagnostic-codes.js +90 -0
  264. package/extensions/dm-lens/dist/clients/config-locations.js +175 -0
  265. package/extensions/dm-lens/dist/clients/config-resolve.js +359 -0
  266. package/extensions/dm-lens/dist/clients/config-schema.js +230 -0
  267. package/extensions/dm-lens/dist/clients/config-warn.js +146 -0
  268. package/extensions/dm-lens/dist/clients/confirmed-delete.js +11 -0
  269. package/extensions/dm-lens/dist/clients/console-guard-install.js +4 -0
  270. package/extensions/dm-lens/dist/clients/cooperative-budget.js +36 -0
  271. package/extensions/dm-lens/dist/clients/custom-rule-locations.js +28 -0
  272. package/extensions/dm-lens/dist/clients/dead-code-client.js +408 -0
  273. package/extensions/dm-lens/dist/clients/dead-code-logger.js +18 -0
  274. package/extensions/dm-lens/dist/clients/deadline-utils.js +117 -0
  275. package/extensions/dm-lens/dist/clients/debug-handles.js +121 -0
  276. package/extensions/dm-lens/dist/clients/debug-heap.js +80 -0
  277. package/extensions/dm-lens/dist/clients/deferred-lsp-work.js +33 -0
  278. package/extensions/dm-lens/dist/clients/deferred-runner-blockers.js +106 -0
  279. package/extensions/dm-lens/dist/clients/deferred-test-targets.js +31 -0
  280. package/extensions/dm-lens/dist/clients/degradation-ledger.js +289 -0
  281. package/extensions/dm-lens/dist/clients/demoted-finding-render.js +64 -0
  282. package/extensions/dm-lens/dist/clients/dependency-checker.js +534 -0
  283. package/extensions/dm-lens/dist/clients/deps/ast-grep-napi.js +17 -0
  284. package/extensions/dm-lens/dist/clients/deps/dm-host-deps.js +10 -0
  285. package/extensions/dm-lens/dist/clients/deps/dm-tui-compat.js +58 -0
  286. package/extensions/dm-lens/dist/clients/deps/js-yaml.js +7 -0
  287. package/extensions/dm-lens/dist/clients/deps/minimatch.js +6 -0
  288. package/extensions/dm-lens/dist/clients/deps/pidusage.js +5 -0
  289. package/extensions/dm-lens/dist/clients/deps/typebox.js +5 -0
  290. package/extensions/dm-lens/dist/clients/deps/vscode-jsonrpc.js +8 -0
  291. package/extensions/dm-lens/dist/clients/deps/web-tree-sitter.js +11 -0
  292. package/extensions/dm-lens/dist/clients/diagnostic-dispositions.js +318 -0
  293. package/extensions/dm-lens/dist/clients/diagnostic-line-freshness.js +142 -0
  294. package/extensions/dm-lens/dist/clients/diagnostic-logger.js +60 -0
  295. package/extensions/dm-lens/dist/clients/diagnostic-tracker.js +89 -0
  296. package/extensions/dm-lens/dist/clients/diagnostics-publish.js +134 -0
  297. package/extensions/dm-lens/dist/clients/dispatch/adopted-root-runners.js +22 -0
  298. package/extensions/dm-lens/dist/clients/dispatch/ast-grep-catalog.js +16 -0
  299. package/extensions/dm-lens/dist/clients/dispatch/auxiliary-lsp.js +158 -0
  300. package/extensions/dm-lens/dist/clients/dispatch/collect-later-tier.js +25 -0
  301. package/extensions/dm-lens/dist/clients/dispatch/diagnostic-taxonomy.js +58 -0
  302. package/extensions/dm-lens/dist/clients/dispatch/dispatcher.js +1017 -0
  303. package/extensions/dm-lens/dist/clients/dispatch/fact-provider-types.js +1 -0
  304. package/extensions/dm-lens/dist/clients/dispatch/fact-rule-runner.js +17 -0
  305. package/extensions/dm-lens/dist/clients/dispatch/fact-runner.js +18 -0
  306. package/extensions/dm-lens/dist/clients/dispatch/fact-scheduler.js +52 -0
  307. package/extensions/dm-lens/dist/clients/dispatch/fact-store.js +199 -0
  308. package/extensions/dm-lens/dist/clients/dispatch/facts/comment-facts.js +23 -0
  309. package/extensions/dm-lens/dist/clients/dispatch/facts/file-content.js +18 -0
  310. package/extensions/dm-lens/dist/clients/dispatch/facts/function-facts.js +281 -0
  311. package/extensions/dm-lens/dist/clients/dispatch/facts/import-facts.js +169 -0
  312. package/extensions/dm-lens/dist/clients/dispatch/facts/tree-sitter-facts.js +19 -0
  313. package/extensions/dm-lens/dist/clients/dispatch/facts/try-catch-facts.js +95 -0
  314. package/extensions/dm-lens/dist/clients/dispatch/finding-policy.js +164 -0
  315. package/extensions/dm-lens/dist/clients/dispatch/indent-detect.js +227 -0
  316. package/extensions/dm-lens/dist/clients/dispatch/inline-suppressions.js +52 -0
  317. package/extensions/dm-lens/dist/clients/dispatch/integration.js +1502 -0
  318. package/extensions/dm-lens/dist/clients/dispatch/known-runner-ids.js +13 -0
  319. package/extensions/dm-lens/dist/clients/dispatch/lazy.js +8 -0
  320. package/extensions/dm-lens/dist/clients/dispatch/pending-runner-findings.js +130 -0
  321. package/extensions/dm-lens/dist/clients/dispatch/plan.js +284 -0
  322. package/extensions/dm-lens/dist/clients/dispatch/priorities.js +14 -0
  323. package/extensions/dm-lens/dist/clients/dispatch/rule-id-normalize.js +49 -0
  324. package/extensions/dm-lens/dist/clients/dispatch/rule-ignores.js +21 -0
  325. package/extensions/dm-lens/dist/clients/dispatch/rule-policy.js +102 -0
  326. package/extensions/dm-lens/dist/clients/dispatch/rules/async-noise.js +31 -0
  327. package/extensions/dm-lens/dist/clients/dispatch/rules/async-unnecessary-wrapper.js +27 -0
  328. package/extensions/dm-lens/dist/clients/dispatch/rules/cors-wildcard.js +35 -0
  329. package/extensions/dm-lens/dist/clients/dispatch/rules/error-obscuring.js +29 -0
  330. package/extensions/dm-lens/dist/clients/dispatch/rules/error-swallowing.js +29 -0
  331. package/extensions/dm-lens/dist/clients/dispatch/rules/framework-call-noise.js +31 -0
  332. package/extensions/dm-lens/dist/clients/dispatch/rules/high-complexity.js +56 -0
  333. package/extensions/dm-lens/dist/clients/dispatch/rules/high-fan-out.js +45 -0
  334. package/extensions/dm-lens/dist/clients/dispatch/rules/high-import-coupling.js +31 -0
  335. package/extensions/dm-lens/dist/clients/dispatch/rules/missing-error-propagation.js +45 -0
  336. package/extensions/dm-lens/dist/clients/dispatch/rules/no-commented-credentials.js +46 -0
  337. package/extensions/dm-lens/dist/clients/dispatch/rules/pass-through-wrappers.js +38 -0
  338. package/extensions/dm-lens/dist/clients/dispatch/rules/placeholder-comments.js +40 -0
  339. package/extensions/dm-lens/dist/clients/dispatch/rules/unsafe-boundary.js +101 -0
  340. package/extensions/dm-lens/dist/clients/dispatch/runner-context.js +46 -0
  341. package/extensions/dm-lens/dist/clients/dispatch/runners/actionlint.js +98 -0
  342. package/extensions/dm-lens/dist/clients/dispatch/runners/ast-grep-napi.js +694 -0
  343. package/extensions/dm-lens/dist/clients/dispatch/runners/biome-check.js +205 -0
  344. package/extensions/dm-lens/dist/clients/dispatch/runners/cpp-check.js +190 -0
  345. package/extensions/dm-lens/dist/clients/dispatch/runners/credo.js +66 -0
  346. package/extensions/dm-lens/dist/clients/dispatch/runners/cue-vet.js +171 -0
  347. package/extensions/dm-lens/dist/clients/dispatch/runners/dart-analyze.js +151 -0
  348. package/extensions/dm-lens/dist/clients/dispatch/runners/detekt.js +157 -0
  349. package/extensions/dm-lens/dist/clients/dispatch/runners/dotnet-build.js +157 -0
  350. package/extensions/dm-lens/dist/clients/dispatch/runners/elixir-check.js +153 -0
  351. package/extensions/dm-lens/dist/clients/dispatch/runners/eslint.js +99 -0
  352. package/extensions/dm-lens/dist/clients/dispatch/runners/fact-rules.js +20 -0
  353. package/extensions/dm-lens/dist/clients/dispatch/runners/fish-indent.js +66 -0
  354. package/extensions/dm-lens/dist/clients/dispatch/runners/gleam-check.js +110 -0
  355. package/extensions/dm-lens/dist/clients/dispatch/runners/go-vet.js +52 -0
  356. package/extensions/dm-lens/dist/clients/dispatch/runners/golangci-lint.js +89 -0
  357. package/extensions/dm-lens/dist/clients/dispatch/runners/hadolint.js +83 -0
  358. package/extensions/dm-lens/dist/clients/dispatch/runners/helm-lint.js +144 -0
  359. package/extensions/dm-lens/dist/clients/dispatch/runners/helm-render.js +593 -0
  360. package/extensions/dm-lens/dist/clients/dispatch/runners/htmlhint.js +103 -0
  361. package/extensions/dm-lens/dist/clients/dispatch/runners/index.js +100 -0
  362. package/extensions/dm-lens/dist/clients/dispatch/runners/javac.js +82 -0
  363. package/extensions/dm-lens/dist/clients/dispatch/runners/ktlint.js +93 -0
  364. package/extensions/dm-lens/dist/clients/dispatch/runners/lsp.js +226 -0
  365. package/extensions/dm-lens/dist/clients/dispatch/runners/markdownlint.js +129 -0
  366. package/extensions/dm-lens/dist/clients/dispatch/runners/mypy.js +70 -0
  367. package/extensions/dm-lens/dist/clients/dispatch/runners/oxlint.js +305 -0
  368. package/extensions/dm-lens/dist/clients/dispatch/runners/php-lint.js +66 -0
  369. package/extensions/dm-lens/dist/clients/dispatch/runners/phpstan.js +90 -0
  370. package/extensions/dm-lens/dist/clients/dispatch/runners/prisma-validate.js +66 -0
  371. package/extensions/dm-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +307 -0
  372. package/extensions/dm-lens/dist/clients/dispatch/runners/pyright.js +118 -0
  373. package/extensions/dm-lens/dist/clients/dispatch/runners/rubocop.js +79 -0
  374. package/extensions/dm-lens/dist/clients/dispatch/runners/ruff.js +69 -0
  375. package/extensions/dm-lens/dist/clients/dispatch/runners/rust-clippy.js +166 -0
  376. package/extensions/dm-lens/dist/clients/dispatch/runners/shellcheck.js +177 -0
  377. package/extensions/dm-lens/dist/clients/dispatch/runners/shfmt.js +85 -0
  378. package/extensions/dm-lens/dist/clients/dispatch/runners/spellcheck.js +74 -0
  379. package/extensions/dm-lens/dist/clients/dispatch/runners/spotbugs.js +208 -0
  380. package/extensions/dm-lens/dist/clients/dispatch/runners/sqlfluff.js +136 -0
  381. package/extensions/dm-lens/dist/clients/dispatch/runners/stylelint.js +118 -0
  382. package/extensions/dm-lens/dist/clients/dispatch/runners/swiftlint.js +143 -0
  383. package/extensions/dm-lens/dist/clients/dispatch/runners/taplo.js +108 -0
  384. package/extensions/dm-lens/dist/clients/dispatch/runners/terragrunt.js +110 -0
  385. package/extensions/dm-lens/dist/clients/dispatch/runners/tflint.js +104 -0
  386. package/extensions/dm-lens/dist/clients/dispatch/runners/tree-sitter.js +521 -0
  387. package/extensions/dm-lens/dist/clients/dispatch/runners/trivy-config.js +155 -0
  388. package/extensions/dm-lens/dist/clients/dispatch/runners/utils/availability-policy.js +285 -0
  389. package/extensions/dm-lens/dist/clients/dispatch/runners/utils/candidate-probe.js +61 -0
  390. package/extensions/dm-lens/dist/clients/dispatch/runners/utils/diagnostic-parsers.js +61 -0
  391. package/extensions/dm-lens/dist/clients/dispatch/runners/utils/lazy-installer.js +130 -0
  392. package/extensions/dm-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +1143 -0
  393. package/extensions/dm-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +40 -0
  394. package/extensions/dm-lens/dist/clients/dispatch/runners/utils/tool-failure.js +120 -0
  395. package/extensions/dm-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +88 -0
  396. package/extensions/dm-lens/dist/clients/dispatch/runners/utils.js +8 -0
  397. package/extensions/dm-lens/dist/clients/dispatch/runners/vale.js +106 -0
  398. package/extensions/dm-lens/dist/clients/dispatch/runners/yaml-rule-parser.js +170 -0
  399. package/extensions/dm-lens/dist/clients/dispatch/runners/yamllint.js +83 -0
  400. package/extensions/dm-lens/dist/clients/dispatch/runners/zig-check.js +72 -0
  401. package/extensions/dm-lens/dist/clients/dispatch/suppress-writer.js +57 -0
  402. package/extensions/dm-lens/dist/clients/dispatch/tool-profile.js +30 -0
  403. package/extensions/dm-lens/dist/clients/dispatch/types.js +27 -0
  404. package/extensions/dm-lens/dist/clients/dispatch/utils/format-utils.js +58 -0
  405. package/extensions/dm-lens/dist/clients/dispatch/utils/lsp-diagnostics.js +31 -0
  406. package/extensions/dm-lens/dist/clients/disposition-logger.js +19 -0
  407. package/extensions/dm-lens/dist/clients/disposition-publish.js +88 -0
  408. package/extensions/dm-lens/dist/clients/durable-store.js +77 -0
  409. package/extensions/dm-lens/dist/clients/effective-config.js +170 -0
  410. package/extensions/dm-lens/dist/clients/env-utils.js +26 -0
  411. package/extensions/dm-lens/dist/clients/ephemeral-root.js +67 -0
  412. package/extensions/dm-lens/dist/clients/error-class.js +3 -0
  413. package/extensions/dm-lens/dist/clients/eval-timestamp.js +2 -0
  414. package/extensions/dm-lens/dist/clients/event-loop-hold.js +119 -0
  415. package/extensions/dm-lens/dist/clients/event-loop-monitor.js +84 -0
  416. package/extensions/dm-lens/dist/clients/extension-log.js +230 -0
  417. package/extensions/dm-lens/dist/clients/extension-mode.js +20 -0
  418. package/extensions/dm-lens/dist/clients/feature-hints.js +88 -0
  419. package/extensions/dm-lens/dist/clients/file-kinds.js +313 -0
  420. package/extensions/dm-lens/dist/clients/file-mutation-queue.js +95 -0
  421. package/extensions/dm-lens/dist/clients/file-role.js +50 -0
  422. package/extensions/dm-lens/dist/clients/file-time.js +112 -0
  423. package/extensions/dm-lens/dist/clients/file-utils.js +706 -0
  424. package/extensions/dm-lens/dist/clients/finding-delivery-gate.js +132 -0
  425. package/extensions/dm-lens/dist/clients/finding-identity.js +23 -0
  426. package/extensions/dm-lens/dist/clients/fix-run-restore.js +413 -0
  427. package/extensions/dm-lens/dist/clients/fix-worklog.js +75 -0
  428. package/extensions/dm-lens/dist/clients/format-events-publish.js +310 -0
  429. package/extensions/dm-lens/dist/clients/format-service.js +159 -0
  430. package/extensions/dm-lens/dist/clients/formatters-lazy.js +8 -0
  431. package/extensions/dm-lens/dist/clients/formatters.js +1367 -0
  432. package/extensions/dm-lens/dist/clients/freshness-cadence.js +1 -0
  433. package/extensions/dm-lens/dist/clients/freshness.js +8 -0
  434. package/extensions/dm-lens/dist/clients/generated-artifacts.js +209 -0
  435. package/extensions/dm-lens/dist/clients/generation-guard.js +147 -0
  436. package/extensions/dm-lens/dist/clients/generation-lock.js +181 -0
  437. package/extensions/dm-lens/dist/clients/git-guard.js +786 -0
  438. package/extensions/dm-lens/dist/clients/git-tracked-ignore.js +120 -0
  439. package/extensions/dm-lens/dist/clients/gitleaks-client.js +290 -0
  440. package/extensions/dm-lens/dist/clients/go-client.js +47 -0
  441. package/extensions/dm-lens/dist/clients/govulncheck-client.js +411 -0
  442. package/extensions/dm-lens/dist/clients/gradle-ktfmt-style.js +177 -0
  443. package/extensions/dm-lens/dist/clients/grammar-source.js +229 -0
  444. package/extensions/dm-lens/dist/clients/grammar-wasm-imports.js +48 -0
  445. package/extensions/dm-lens/dist/clients/gzip-stage-write.js +93 -0
  446. package/extensions/dm-lens/dist/clients/hard-failure-summary.js +28 -0
  447. package/extensions/dm-lens/dist/clients/hashline-anchor.js +222 -0
  448. package/extensions/dm-lens/dist/clients/hook-budgets.js +17 -0
  449. package/extensions/dm-lens/dist/clients/host-edit-normalize.js +87 -0
  450. package/extensions/dm-lens/dist/clients/host-ports.js +35 -0
  451. package/extensions/dm-lens/dist/clients/indent-retarget.js +102 -0
  452. package/extensions/dm-lens/dist/clients/inline-blocker-dispositions.js +103 -0
  453. package/extensions/dm-lens/dist/clients/inline-blocker-fields.js +8 -0
  454. package/extensions/dm-lens/dist/clients/install-diagnostics.js +135 -0
  455. package/extensions/dm-lens/dist/clients/installer/index.js +3935 -0
  456. package/extensions/dm-lens/dist/clients/installer/managed-tool-refresh-session.js +17 -0
  457. package/extensions/dm-lens/dist/clients/installer/managed-tool-refresh.js +522 -0
  458. package/extensions/dm-lens/dist/clients/instance-reaper-state.js +3 -0
  459. package/extensions/dm-lens/dist/clients/instance-reaper.js +682 -0
  460. package/extensions/dm-lens/dist/clients/instance-registry-lock.js +166 -0
  461. package/extensions/dm-lens/dist/clients/instance-registry-tail.js +17 -0
  462. package/extensions/dm-lens/dist/clients/instance-registry.js +595 -0
  463. package/extensions/dm-lens/dist/clients/io-bridge-contract.js +7 -0
  464. package/extensions/dm-lens/dist/clients/io-bridge.js +357 -0
  465. package/extensions/dm-lens/dist/clients/jscpd-client.js +300 -0
  466. package/extensions/dm-lens/dist/clients/json-cache-read.js +25 -0
  467. package/extensions/dm-lens/dist/clients/knip-client.js +534 -0
  468. package/extensions/dm-lens/dist/clients/knip-scan-roots.js +26 -0
  469. package/extensions/dm-lens/dist/clients/language-policy.js +244 -0
  470. package/extensions/dm-lens/dist/clients/language-profile.js +263 -0
  471. package/extensions/dm-lens/dist/clients/language-registry.js +427 -0
  472. package/extensions/dm-lens/dist/clients/latency-logger.js +202 -0
  473. package/extensions/dm-lens/dist/clients/lazy-import.js +14 -0
  474. package/extensions/dm-lens/dist/clients/ledger-bounds.js +21 -0
  475. package/extensions/dm-lens/dist/clients/lens-config.js +246 -0
  476. package/extensions/dm-lens/dist/clients/lens-engine.js +181 -0
  477. package/extensions/dm-lens/dist/clients/lens-events.js +105 -0
  478. package/extensions/dm-lens/dist/clients/lens-flag-registry.js +292 -0
  479. package/extensions/dm-lens/dist/clients/lens-map.js +980 -0
  480. package/extensions/dm-lens/dist/clients/live-bus-emitter.js +53 -0
  481. package/extensions/dm-lens/dist/clients/log-cleanup.js +138 -0
  482. package/extensions/dm-lens/dist/clients/lsp/aggregation.js +89 -0
  483. package/extensions/dm-lens/dist/clients/lsp/auxiliary-lifecycle.js +23 -0
  484. package/extensions/dm-lens/dist/clients/lsp/capabilities.js +94 -0
  485. package/extensions/dm-lens/dist/clients/lsp/cascade-tier.js +208 -0
  486. package/extensions/dm-lens/dist/clients/lsp/client.js +2943 -0
  487. package/extensions/dm-lens/dist/clients/lsp/config.js +471 -0
  488. package/extensions/dm-lens/dist/clients/lsp/content-limits.js +21 -0
  489. package/extensions/dm-lens/dist/clients/lsp/diagnostic-binding.js +79 -0
  490. package/extensions/dm-lens/dist/clients/lsp/document-drift.js +324 -0
  491. package/extensions/dm-lens/dist/clients/lsp/edits.js +982 -0
  492. package/extensions/dm-lens/dist/clients/lsp/grouping.js +38 -0
  493. package/extensions/dm-lens/dist/clients/lsp/index.js +4639 -0
  494. package/extensions/dm-lens/dist/clients/lsp/inferred-project.js +121 -0
  495. package/extensions/dm-lens/dist/clients/lsp/jvm-runtime.js +194 -0
  496. package/extensions/dm-lens/dist/clients/lsp/language.js +23 -0
  497. package/extensions/dm-lens/dist/clients/lsp/launch.js +564 -0
  498. package/extensions/dm-lens/dist/clients/lsp/lombok.js +146 -0
  499. package/extensions/dm-lens/dist/clients/lsp/path-utils.js +1 -0
  500. package/extensions/dm-lens/dist/clients/lsp/pending-aux-coverage.js +120 -0
  501. package/extensions/dm-lens/dist/clients/lsp/position-encoding.js +28 -0
  502. package/extensions/dm-lens/dist/clients/lsp/resolved-config.js +183 -0
  503. package/extensions/dm-lens/dist/clients/lsp/ruby-drive-dirs.js +42 -0
  504. package/extensions/dm-lens/dist/clients/lsp/server-covers.js +8 -0
  505. package/extensions/dm-lens/dist/clients/lsp/server-traits.js +39 -0
  506. package/extensions/dm-lens/dist/clients/lsp/server.js +2640 -0
  507. package/extensions/dm-lens/dist/clients/lsp/service-singleton.js +59 -0
  508. package/extensions/dm-lens/dist/clients/lsp/session-roots.js +52 -0
  509. package/extensions/dm-lens/dist/clients/lsp/spawn-history.js +15 -0
  510. package/extensions/dm-lens/dist/clients/lsp/sync-kind.js +30 -0
  511. package/extensions/dm-lens/dist/clients/lsp/tsserver-sync.js +156 -0
  512. package/extensions/dm-lens/dist/clients/lsp/wait-policy/capability-snapshot.js +1 -0
  513. package/extensions/dm-lens/dist/clients/lsp/wait-policy/classification.js +37 -0
  514. package/extensions/dm-lens/dist/clients/lsp/wait-policy/index.js +3 -0
  515. package/extensions/dm-lens/dist/clients/lsp/wait-policy/strategies.js +196 -0
  516. package/extensions/dm-lens/dist/clients/lsp/watch-queue.js +40 -0
  517. package/extensions/dm-lens/dist/clients/lsp/workspace-diagnostics-cache.js +270 -0
  518. package/extensions/dm-lens/dist/clients/lsp/workspace-diagnostics-session.js +7 -0
  519. package/extensions/dm-lens/dist/clients/lsp/workspace-sweep-hold.js +93 -0
  520. package/extensions/dm-lens/dist/clients/lsp-budget.js +87 -0
  521. package/extensions/dm-lens/dist/clients/lsp-document-symbols.js +93 -0
  522. package/extensions/dm-lens/dist/clients/lsp-lazy.js +8 -0
  523. package/extensions/dm-lens/dist/clients/lsp-mutation.js +309 -0
  524. package/extensions/dm-lens/dist/clients/lsp-status.js +35 -0
  525. package/extensions/dm-lens/dist/clients/map-with-concurrency.js +20 -0
  526. package/extensions/dm-lens/dist/clients/mcp/analyze.js +265 -0
  527. package/extensions/dm-lens/dist/clients/mcp/host-shim.js +14 -0
  528. package/extensions/dm-lens/dist/clients/mcp/ipc.js +321 -0
  529. package/extensions/dm-lens/dist/clients/mcp/review.js +137 -0
  530. package/extensions/dm-lens/dist/clients/mcp/session.js +305 -0
  531. package/extensions/dm-lens/dist/clients/memory-sampler.js +145 -0
  532. package/extensions/dm-lens/dist/clients/message-end-attribution.js +18 -0
  533. package/extensions/dm-lens/dist/clients/metrics-client.js +80 -0
  534. package/extensions/dm-lens/dist/clients/metrics-history.js +255 -0
  535. package/extensions/dm-lens/dist/clients/middle-man-analysis.js +161 -0
  536. package/extensions/dm-lens/dist/clients/model-provider.js +31 -0
  537. package/extensions/dm-lens/dist/clients/module-report-lsp.js +140 -0
  538. package/extensions/dm-lens/dist/clients/module-report.js +1648 -0
  539. package/extensions/dm-lens/dist/clients/mutating-tool.js +386 -0
  540. package/extensions/dm-lens/dist/clients/mutation-attribution.js +176 -0
  541. package/extensions/dm-lens/dist/clients/mutation-bridge.js +231 -0
  542. package/extensions/dm-lens/dist/clients/ndjson-logger.js +504 -0
  543. package/extensions/dm-lens/dist/clients/observed-mutation-sources.js +43 -0
  544. package/extensions/dm-lens/dist/clients/observed-mutation.js +729 -0
  545. package/extensions/dm-lens/dist/clients/oldtext-autopatch.js +100 -0
  546. package/extensions/dm-lens/dist/clients/opaque-mutation-scan.js +340 -0
  547. package/extensions/dm-lens/dist/clients/opengrep-client.js +219 -0
  548. package/extensions/dm-lens/dist/clients/opengrep-config.js +52 -0
  549. package/extensions/dm-lens/dist/clients/package-manager.js +358 -0
  550. package/extensions/dm-lens/dist/clients/package-root.js +25 -0
  551. package/extensions/dm-lens/dist/clients/partial-edit-apply.js +337 -0
  552. package/extensions/dm-lens/dist/clients/path-attribution-telemetry.js +31 -0
  553. package/extensions/dm-lens/dist/clients/path-keyed-map.js +57 -0
  554. package/extensions/dm-lens/dist/clients/path-utils.js +535 -0
  555. package/extensions/dm-lens/dist/clients/performance-report.js +291 -0
  556. package/extensions/dm-lens/dist/clients/persist-debounce.js +37 -0
  557. package/extensions/dm-lens/dist/clients/persist-worker-stats.js +8 -0
  558. package/extensions/dm-lens/dist/clients/persistent-reverify.js +166 -0
  559. package/extensions/dm-lens/dist/clients/php-cs-fixer-config.js +15 -0
  560. package/extensions/dm-lens/dist/clients/pipeline.js +1279 -0
  561. package/extensions/dm-lens/dist/clients/post-autofix-notice.js +19 -0
  562. package/extensions/dm-lens/dist/clients/print-mode.js +3 -0
  563. package/extensions/dm-lens/dist/clients/probe-home-state.js +67 -0
  564. package/extensions/dm-lens/dist/clients/process-bridge.js +48 -0
  565. package/extensions/dm-lens/dist/clients/process-singletons.js +69 -0
  566. package/extensions/dm-lens/dist/clients/process-snapshot.js +130 -0
  567. package/extensions/dm-lens/dist/clients/project-changes.js +174 -0
  568. package/extensions/dm-lens/dist/clients/project-conventions.js +152 -0
  569. package/extensions/dm-lens/dist/clients/project-diagnostics/cache.js +91 -0
  570. package/extensions/dm-lens/dist/clients/project-diagnostics/extractors.js +53 -0
  571. package/extensions/dm-lens/dist/clients/project-diagnostics/fresh-fetch.js +413 -0
  572. package/extensions/dm-lens/dist/clients/project-diagnostics/runner-adapters/call-graph-impact.js +64 -0
  573. package/extensions/dm-lens/dist/clients/project-diagnostics/runner-adapters/dead-code.js +38 -0
  574. package/extensions/dm-lens/dist/clients/project-diagnostics/runner-adapters/gitleaks.js +21 -0
  575. package/extensions/dm-lens/dist/clients/project-diagnostics/runner-adapters/govulncheck.js +23 -0
  576. package/extensions/dm-lens/dist/clients/project-diagnostics/runner-adapters/jscpd.js +28 -0
  577. package/extensions/dm-lens/dist/clients/project-diagnostics/runner-adapters/knip.js +45 -0
  578. package/extensions/dm-lens/dist/clients/project-diagnostics/runner-adapters/madge.js +34 -0
  579. package/extensions/dm-lens/dist/clients/project-diagnostics/runner-adapters/opengrep.js +32 -0
  580. package/extensions/dm-lens/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +79 -0
  581. package/extensions/dm-lens/dist/clients/project-diagnostics/runner-adapters/trivy.js +41 -0
  582. package/extensions/dm-lens/dist/clients/project-diagnostics/scanner.js +349 -0
  583. package/extensions/dm-lens/dist/clients/project-diagnostics/types.js +1 -0
  584. package/extensions/dm-lens/dist/clients/project-lens-config.js +464 -0
  585. package/extensions/dm-lens/dist/clients/project-report.js +527 -0
  586. package/extensions/dm-lens/dist/clients/project-scale.js +60 -0
  587. package/extensions/dm-lens/dist/clients/project-scan-policy.js +4 -0
  588. package/extensions/dm-lens/dist/clients/project-snapshot-fingerprint.js +44 -0
  589. package/extensions/dm-lens/dist/clients/project-snapshot-persist-worker.js +13 -0
  590. package/extensions/dm-lens/dist/clients/project-snapshot.js +1333 -0
  591. package/extensions/dm-lens/dist/clients/project-trust.js +104 -0
  592. package/extensions/dm-lens/dist/clients/python-environment.js +162 -0
  593. package/extensions/dm-lens/dist/clients/python-provenance.js +551 -0
  594. package/extensions/dm-lens/dist/clients/quiet-window-config.js +16 -0
  595. package/extensions/dm-lens/dist/clients/quiet-window.js +129 -0
  596. package/extensions/dm-lens/dist/clients/read-bridge.js +53 -0
  597. package/extensions/dm-lens/dist/clients/read-expansion.js +218 -0
  598. package/extensions/dm-lens/dist/clients/read-guard-branch.js +126 -0
  599. package/extensions/dm-lens/dist/clients/read-guard-logger.js +186 -0
  600. package/extensions/dm-lens/dist/clients/read-guard-tool-lines.js +1050 -0
  601. package/extensions/dm-lens/dist/clients/read-guard.js +1429 -0
  602. package/extensions/dm-lens/dist/clients/recent-touches.js +126 -0
  603. package/extensions/dm-lens/dist/clients/redact/secrets.js +386 -0
  604. package/extensions/dm-lens/dist/clients/resource-sampler.js +344 -0
  605. package/extensions/dm-lens/dist/clients/reverse-deps.js +227 -0
  606. package/extensions/dm-lens/dist/clients/review-graph/builder.js +3708 -0
  607. package/extensions/dm-lens/dist/clients/review-graph/format.js +30 -0
  608. package/extensions/dm-lens/dist/clients/review-graph/git-identity.js +168 -0
  609. package/extensions/dm-lens/dist/clients/review-graph/import-resolvers.js +323 -0
  610. package/extensions/dm-lens/dist/clients/review-graph/persist-worker.js +8 -0
  611. package/extensions/dm-lens/dist/clients/review-graph/query.js +195 -0
  612. package/extensions/dm-lens/dist/clients/review-graph/revision-drift.js +3 -0
  613. package/extensions/dm-lens/dist/clients/review-graph/service.js +60 -0
  614. package/extensions/dm-lens/dist/clients/review-graph/shared-extraction-ir.js +54 -0
  615. package/extensions/dm-lens/dist/clients/review-graph/symbol-id.js +29 -0
  616. package/extensions/dm-lens/dist/clients/review-graph/tsconfig-paths.js +253 -0
  617. package/extensions/dm-lens/dist/clients/review-graph/types.js +1 -0
  618. package/extensions/dm-lens/dist/clients/review-graph/workspace-modules.js +374 -0
  619. package/extensions/dm-lens/dist/clients/review-graph-logger.js +51 -0
  620. package/extensions/dm-lens/dist/clients/ruff-client.js +109 -0
  621. package/extensions/dm-lens/dist/clients/rules-scanner.js +113 -0
  622. package/extensions/dm-lens/dist/clients/run-duration.js +9 -0
  623. package/extensions/dm-lens/dist/clients/runtime-agent-end.js +800 -0
  624. package/extensions/dm-lens/dist/clients/runtime-config.js +39 -0
  625. package/extensions/dm-lens/dist/clients/runtime-context.js +155 -0
  626. package/extensions/dm-lens/dist/clients/runtime-coordinator.js +1117 -0
  627. package/extensions/dm-lens/dist/clients/runtime-session.js +1768 -0
  628. package/extensions/dm-lens/dist/clients/runtime-tool-call.js +935 -0
  629. package/extensions/dm-lens/dist/clients/runtime-tool-result.js +1630 -0
  630. package/extensions/dm-lens/dist/clients/runtime-turn.js +2900 -0
  631. package/extensions/dm-lens/dist/clients/rust-client.js +48 -0
  632. package/extensions/dm-lens/dist/clients/safe-spawn.js +1275 -0
  633. package/extensions/dm-lens/dist/clients/sanitize.js +174 -0
  634. package/extensions/dm-lens/dist/clients/scan-utils.js +7 -0
  635. package/extensions/dm-lens/dist/clients/scanner-temp-root.js +8 -0
  636. package/extensions/dm-lens/dist/clients/scratch-tree-policy.js +63 -0
  637. package/extensions/dm-lens/dist/clients/search-read-registration.js +60 -0
  638. package/extensions/dm-lens/dist/clients/secret-findings.js +69 -0
  639. package/extensions/dm-lens/dist/clients/security-scan-client.js +208 -0
  640. package/extensions/dm-lens/dist/clients/session-event-guard.js +111 -0
  641. package/extensions/dm-lens/dist/clients/session-lifecycle.js +269 -0
  642. package/extensions/dm-lens/dist/clients/session-scope.js +451 -0
  643. package/extensions/dm-lens/dist/clients/session-start-observability.js +47 -0
  644. package/extensions/dm-lens/dist/clients/session-state-store.js +66 -0
  645. package/extensions/dm-lens/dist/clients/session-summary.js +8 -0
  646. package/extensions/dm-lens/dist/clients/sessionstart-logger.js +22 -0
  647. package/extensions/dm-lens/dist/clients/sg-runner.js +645 -0
  648. package/extensions/dm-lens/dist/clients/sgconfig.js +306 -0
  649. package/extensions/dm-lens/dist/clients/shared-checkout-guard.js +177 -0
  650. package/extensions/dm-lens/dist/clients/single-flight.js +52 -0
  651. package/extensions/dm-lens/dist/clients/situational-tool-telemetry.js +85 -0
  652. package/extensions/dm-lens/dist/clients/skills-resolver.js +38 -0
  653. package/extensions/dm-lens/dist/clients/slow-fs.js +77 -0
  654. package/extensions/dm-lens/dist/clients/smells-rollup.js +118 -0
  655. package/extensions/dm-lens/dist/clients/source-filter.js +320 -0
  656. package/extensions/dm-lens/dist/clients/source-walker.js +96 -0
  657. package/extensions/dm-lens/dist/clients/spawn-output-cap.js +35 -0
  658. package/extensions/dm-lens/dist/clients/spawn-timeout-cooldown.js +51 -0
  659. package/extensions/dm-lens/dist/clients/stale-marker.js +1 -0
  660. package/extensions/dm-lens/dist/clients/startup-scan.js +230 -0
  661. package/extensions/dm-lens/dist/clients/startup-timing.js +67 -0
  662. package/extensions/dm-lens/dist/clients/string-utils.js +6 -0
  663. package/extensions/dm-lens/dist/clients/subagent-mode.js +40 -0
  664. package/extensions/dm-lens/dist/clients/symbol-containment.js +16 -0
  665. package/extensions/dm-lens/dist/clients/symbol-types.js +1 -0
  666. package/extensions/dm-lens/dist/clients/test-runner-client.js +1697 -0
  667. package/extensions/dm-lens/dist/clients/test-runner-delivery.js +293 -0
  668. package/extensions/dm-lens/dist/clients/test-target-roots.js +17 -0
  669. package/extensions/dm-lens/dist/clients/todo-scanner.js +164 -0
  670. package/extensions/dm-lens/dist/clients/tool-agreement.js +640 -0
  671. package/extensions/dm-lens/dist/clients/tool-config.js +219 -0
  672. package/extensions/dm-lens/dist/clients/tool-cwd.js +235 -0
  673. package/extensions/dm-lens/dist/clients/tool-definition.js +22 -0
  674. package/extensions/dm-lens/dist/clients/tool-event.js +24 -0
  675. package/extensions/dm-lens/dist/clients/tool-policy.js +2393 -0
  676. package/extensions/dm-lens/dist/clients/tool-probe.js +13 -0
  677. package/extensions/dm-lens/dist/clients/tool-render.js +79 -0
  678. package/extensions/dm-lens/dist/clients/tool-set-policy.js +77 -0
  679. package/extensions/dm-lens/dist/clients/tree-sitter-cache.js +206 -0
  680. package/extensions/dm-lens/dist/clients/tree-sitter-client.js +3057 -0
  681. package/extensions/dm-lens/dist/clients/tree-sitter-logger.js +77 -0
  682. package/extensions/dm-lens/dist/clients/tree-sitter-navigator.js +198 -0
  683. package/extensions/dm-lens/dist/clients/tree-sitter-query-loader.js +389 -0
  684. package/extensions/dm-lens/dist/clients/tree-sitter-shared.js +87 -0
  685. package/extensions/dm-lens/dist/clients/tree-sitter-symbol-extractor.js +959 -0
  686. package/extensions/dm-lens/dist/clients/trivy-client.js +335 -0
  687. package/extensions/dm-lens/dist/clients/tui-fit.js +26 -0
  688. package/extensions/dm-lens/dist/clients/turn-context.js +48 -0
  689. package/extensions/dm-lens/dist/clients/turn-end/delivery-holds.js +59 -0
  690. package/extensions/dm-lens/dist/clients/turn-end/lane.js +1 -0
  691. package/extensions/dm-lens/dist/clients/turn-end/lanes/govulncheck.js +54 -0
  692. package/extensions/dm-lens/dist/clients/turn-end/lanes/secrets.js +131 -0
  693. package/extensions/dm-lens/dist/clients/turn-summary-render.js +56 -0
  694. package/extensions/dm-lens/dist/clients/turn-summary.js +84 -0
  695. package/extensions/dm-lens/dist/clients/typos-config.js +9 -0
  696. package/extensions/dm-lens/dist/clients/user-notify.js +15 -0
  697. package/extensions/dm-lens/dist/clients/vanished-instance-marker.js +18 -0
  698. package/extensions/dm-lens/dist/clients/warm-attach.js +210 -0
  699. package/extensions/dm-lens/dist/clients/widget-state.js +997 -0
  700. package/extensions/dm-lens/dist/clients/word-index-logger.js +21 -0
  701. package/extensions/dm-lens/dist/clients/word-index-store.js +222 -0
  702. package/extensions/dm-lens/dist/clients/word-index.js +1494 -0
  703. package/extensions/dm-lens/dist/clients/workspace-topology.js +268 -0
  704. package/extensions/dm-lens/dist/clients/write-ordering-guard.js +28 -0
  705. package/extensions/dm-lens/dist/clients/zizmor-config.js +160 -0
  706. package/extensions/dm-lens/dist/i18n.js +67 -0
  707. package/extensions/dm-lens/dist/index.js +1973 -0
  708. package/extensions/dm-lens/dist/mcp/analyze-cli.js +205 -0
  709. package/extensions/dm-lens/dist/mcp/build-staleness.js +55 -0
  710. package/extensions/dm-lens/dist/mcp/cli.js +96 -0
  711. package/extensions/dm-lens/dist/mcp/server.js +1239 -0
  712. package/extensions/dm-lens/dist/mcp/tool-arguments.js +134 -0
  713. package/extensions/dm-lens/dist/mcp/worker.js +41 -0
  714. package/extensions/dm-lens/dist/scripts/lib/process-scan.mjs +375 -0
  715. package/extensions/dm-lens/dist/scripts/lib/skills-predicate.mjs +53 -0
  716. package/extensions/dm-lens/dist/scripts/lib/web-tree-sitter-dir.mjs +51 -0
  717. package/extensions/dm-lens/dist/tools/activate-tools.js +62 -0
  718. package/extensions/dm-lens/dist/tools/ast-grep-outline.js +126 -0
  719. package/extensions/dm-lens/dist/tools/ast-grep-replace.js +227 -0
  720. package/extensions/dm-lens/dist/tools/ast-grep-search.js +687 -0
  721. package/extensions/dm-lens/dist/tools/effective-config.js +59 -0
  722. package/extensions/dm-lens/dist/tools/lens-diagnostic-mark.js +251 -0
  723. package/extensions/dm-lens/dist/tools/lens-diagnostics.js +1518 -0
  724. package/extensions/dm-lens/dist/tools/lsp-diagnostics.js +1198 -0
  725. package/extensions/dm-lens/dist/tools/lsp-navigation.js +1249 -0
  726. package/extensions/dm-lens/dist/tools/lsp-structured-output.js +277 -0
  727. package/extensions/dm-lens/dist/tools/module-report.js +332 -0
  728. package/extensions/dm-lens/dist/tools/project-report.js +72 -0
  729. package/extensions/dm-lens/dist/tools/render-compact.js +272 -0
  730. package/extensions/dm-lens/dist/tools/scan-progress.js +31 -0
  731. package/extensions/dm-lens/dist/tools/shared.js +32 -0
  732. package/extensions/dm-lens/dist/tools/symbol-search.js +133 -0
  733. package/extensions/dm-lens/i18n.js +3 -3
  734. package/extensions/dm-lens/index.js +431 -214
  735. package/extensions/dm-lens/mcp/analyze-cli.js +63 -36
  736. package/extensions/dm-lens/mcp/cli.js +20 -0
  737. package/extensions/dm-lens/mcp/server.js +40 -3
  738. package/extensions/dm-lens/mcp/tool-arguments.js +134 -0
  739. package/extensions/dm-lens/mcp/worker.js +8 -0
  740. package/extensions/dm-lens/package.json +1 -1
  741. package/extensions/dm-lens/rules/ast-grep-rules/rule-tests/advisory-registry-subset-test.yml +41 -0
  742. package/extensions/dm-lens/rules/ast-grep-rules/rule-tests/no-identity-operator-on-literals-test.yml +12 -2
  743. package/extensions/dm-lens/rules/ast-grep-rules/rule-tests/throw-new-error-js-test.yml +2 -0
  744. package/extensions/dm-lens/rules/ast-grep-rules/rule-tests/throw-new-error-test.yml +2 -0
  745. package/extensions/dm-lens/rules/ast-grep-rules/rules/advisory-registry-subset.yml +50 -0
  746. package/extensions/dm-lens/rules/ast-grep-rules/rules/no-identity-operator-on-literals.yml +20 -12
  747. package/extensions/dm-lens/rules/ast-grep-rules/rules/throw-new-error-js.yml +2 -2
  748. package/extensions/dm-lens/rules/ast-grep-rules/rules/throw-new-error.yml +2 -2
  749. package/extensions/dm-lens/scripts/lib/process-scan.mjs +126 -7
  750. package/extensions/dm-lens/scripts/lib/web-tree-sitter-dir.mjs +6 -0
  751. package/extensions/dm-lens/tools/activate-tools.js +3 -2
  752. package/extensions/dm-lens/tools/ast-grep-outline.js +2 -3
  753. package/extensions/dm-lens/tools/ast-grep-replace.js +6 -5
  754. package/extensions/dm-lens/tools/ast-grep-search.js +2 -2
  755. package/extensions/dm-lens/tools/lens-diagnostic-mark.js +12 -1
  756. package/extensions/dm-lens/tools/lens-diagnostics.js +74 -37
  757. package/extensions/dm-lens/tools/lsp-diagnostics.js +140 -25
  758. package/extensions/dm-lens/tools/lsp-navigation.js +64 -7
  759. package/extensions/dm-lens/tools/module-report.js +6 -6
  760. package/extensions/dm-lens/tools/shared.js +6 -0
  761. package/extensions/dm-mcp-adapter/THIRD_PARTY_NOTICES.md +1 -1
  762. package/extensions/dm-mcp-adapter/cli.js +265 -23
  763. package/extensions/dm-mcp-adapter/commands.js +83 -17
  764. package/extensions/dm-mcp-adapter/config.js +517 -92
  765. package/extensions/dm-mcp-adapter/direct-tool-surface.js +10 -8
  766. package/extensions/dm-mcp-adapter/direct-tools.js +54 -36
  767. package/extensions/dm-mcp-adapter/dist/abort.js +35 -0
  768. package/extensions/dm-mcp-adapter/dist/agent-dir.js +45 -0
  769. package/extensions/dm-mcp-adapter/dist/agent-plugin-loader.js +392 -0
  770. package/extensions/dm-mcp-adapter/dist/agent-plugin-provenance.js +26 -0
  771. package/extensions/dm-mcp-adapter/dist/bearer-command-resolver.js +182 -0
  772. package/extensions/dm-mcp-adapter/dist/claude-plugin-loader.js +350 -0
  773. package/extensions/dm-mcp-adapter/dist/config.js +1735 -0
  774. package/extensions/dm-mcp-adapter/dist/consent-manager.js +84 -0
  775. package/extensions/dm-mcp-adapter/dist/elicitation-handler.js +370 -0
  776. package/extensions/dm-mcp-adapter/dist/errors.js +225 -0
  777. package/extensions/dm-mcp-adapter/dist/http-ca.js +75 -0
  778. package/extensions/dm-mcp-adapter/dist/jev-client.js +359 -0
  779. package/extensions/dm-mcp-adapter/dist/jev-contracts.js +1 -0
  780. package/extensions/dm-mcp-adapter/dist/jev-key-store.js +189 -0
  781. package/extensions/dm-mcp-adapter/dist/json-schema-validator.js +70 -0
  782. package/extensions/dm-mcp-adapter/dist/lifecycle.js +387 -0
  783. package/extensions/dm-mcp-adapter/dist/logger.js +115 -0
  784. package/extensions/dm-mcp-adapter/dist/mcp-auth-fetch.js +86 -0
  785. package/extensions/dm-mcp-adapter/dist/mcp-auth-flow.js +956 -0
  786. package/extensions/dm-mcp-adapter/dist/mcp-auth.js +998 -0
  787. package/extensions/dm-mcp-adapter/dist/mcp-bearer-store.js +182 -0
  788. package/extensions/dm-mcp-adapter/dist/mcp-callback-server.js +380 -0
  789. package/extensions/dm-mcp-adapter/dist/mcp-oauth-provider.js +525 -0
  790. package/extensions/dm-mcp-adapter/dist/mcp-probe.js +151 -0
  791. package/extensions/dm-mcp-adapter/dist/mcp-tasks.js +300 -0
  792. package/extensions/dm-mcp-adapter/dist/mcp-trace.js +220 -0
  793. package/extensions/dm-mcp-adapter/dist/metadata-cache.js +323 -0
  794. package/extensions/dm-mcp-adapter/dist/npx-resolver.js +465 -0
  795. package/extensions/dm-mcp-adapter/dist/output-shape.js +312 -0
  796. package/extensions/dm-mcp-adapter/dist/package-mcp-loader.js +144 -0
  797. package/extensions/dm-mcp-adapter/dist/project-server-trust.js +186 -0
  798. package/extensions/dm-mcp-adapter/dist/request-headers-command.js +294 -0
  799. package/extensions/dm-mcp-adapter/dist/resource-tools.js +7 -0
  800. package/extensions/dm-mcp-adapter/dist/runtime-owner.js +94 -0
  801. package/extensions/dm-mcp-adapter/dist/sampling-handler.js +213 -0
  802. package/extensions/dm-mcp-adapter/dist/secure-keyring.js +191 -0
  803. package/extensions/dm-mcp-adapter/dist/server-manager.js +1594 -0
  804. package/extensions/dm-mcp-adapter/dist/session-approvals.js +127 -0
  805. package/extensions/dm-mcp-adapter/dist/session-recovery.js +83 -0
  806. package/extensions/dm-mcp-adapter/dist/state.js +1 -0
  807. package/extensions/dm-mcp-adapter/dist/ts-shape.js +157 -0
  808. package/extensions/dm-mcp-adapter/dist/types.js +286 -0
  809. package/extensions/dm-mcp-adapter/dist/ui-app-bridge-helpers.js +36 -0
  810. package/extensions/dm-mcp-adapter/dist/ui-resource-handler.js +204 -0
  811. package/extensions/dm-mcp-adapter/dist/ui-stream-types.js +58 -0
  812. package/extensions/dm-mcp-adapter/dist/ui-tool-visibility.js +26 -0
  813. package/extensions/dm-mcp-adapter/dist/unix-socket-transport.js +83 -0
  814. package/extensions/dm-mcp-adapter/dist/utils.js +418 -0
  815. package/extensions/dm-mcp-adapter/elicitation-handler.js +67 -2
  816. package/extensions/dm-mcp-adapter/failure-backoff.js +9 -0
  817. package/extensions/dm-mcp-adapter/host-managed.js +400 -0
  818. package/extensions/dm-mcp-adapter/http-ca.js +4 -0
  819. package/extensions/dm-mcp-adapter/index.js +347 -57
  820. package/extensions/dm-mcp-adapter/init.js +92 -44
  821. package/extensions/dm-mcp-adapter/jev-client.js +5 -5
  822. package/extensions/dm-mcp-adapter/jev-key-store.js +15 -2
  823. package/extensions/dm-mcp-adapter/mcp-auth-flow.js +10 -2
  824. package/extensions/dm-mcp-adapter/mcp-auth.js +2 -1
  825. package/extensions/dm-mcp-adapter/mcp-callback-server.js +1 -1
  826. package/extensions/dm-mcp-adapter/mcp-code.js +174 -87
  827. package/extensions/dm-mcp-adapter/mcp-keyring-helper.cjs +1 -1
  828. package/extensions/dm-mcp-adapter/mcp-oauth-provider.js +3 -2
  829. package/extensions/dm-mcp-adapter/mcp-output-guard.js +19 -5
  830. package/extensions/dm-mcp-adapter/mcp-panel.js +59 -12
  831. package/extensions/dm-mcp-adapter/mcp-script-wasm.js +22 -0
  832. package/extensions/dm-mcp-adapter/mcp-script-worker.mjs +227 -111
  833. package/extensions/dm-mcp-adapter/mcp-setup-panel.js +500 -258
  834. package/extensions/dm-mcp-adapter/mcp-status.js +9 -2
  835. package/extensions/dm-mcp-adapter/mcp-trace.js +5 -0
  836. package/extensions/dm-mcp-adapter/metadata-cache.js +68 -11
  837. package/extensions/dm-mcp-adapter/namespace-tools.js +20 -5
  838. package/extensions/dm-mcp-adapter/onboarding-state.js +11 -1
  839. package/extensions/dm-mcp-adapter/output-shape.js +312 -0
  840. package/extensions/dm-mcp-adapter/package-mcp-loader.js +15 -6
  841. package/extensions/dm-mcp-adapter/package.json +6 -15
  842. package/extensions/dm-mcp-adapter/panel-keys.js +13 -0
  843. package/extensions/dm-mcp-adapter/pi-builtin-mcp.js +26 -0
  844. package/extensions/dm-mcp-adapter/pi-signin-import.js +121 -0
  845. package/extensions/dm-mcp-adapter/project-server-trust.js +186 -0
  846. package/extensions/dm-mcp-adapter/prompts.js +80 -7
  847. package/extensions/dm-mcp-adapter/proxy-modes.js +219 -158
  848. package/extensions/dm-mcp-adapter/runtime-protocol.js +342 -0
  849. package/extensions/dm-mcp-adapter/search-ranking.js +8 -6
  850. package/extensions/dm-mcp-adapter/semantic-search.js +5 -4
  851. package/extensions/dm-mcp-adapter/server-manager.js +108 -26
  852. package/extensions/dm-mcp-adapter/session-recovery.js +3 -2
  853. package/extensions/dm-mcp-adapter/skills/mcp-scripting/SKILL.md +24 -28
  854. package/extensions/dm-mcp-adapter/skills/mcp-scripting/references/jev.md +3 -0
  855. package/extensions/dm-mcp-adapter/test-runner.mjs +1 -1
  856. package/extensions/dm-mcp-adapter/tool-approval.js +17 -7
  857. package/extensions/dm-mcp-adapter/tool-metadata.js +4 -2
  858. package/extensions/dm-mcp-adapter/tool-parameters.js +4 -0
  859. package/extensions/dm-mcp-adapter/tool-result-renderer.js +68 -7
  860. package/extensions/dm-mcp-adapter/ts-shape.js +1 -1
  861. package/extensions/dm-mcp-adapter/ui-server.js +24 -20
  862. package/extensions/dm-mcp-adapter/unix-socket-transport.js +1 -1
  863. package/extensions/dm-mcp-adapter/utils.js +37 -1
  864. package/extensions/dm-ponytail/extensions/ponytail.mjs +37 -17
  865. package/extensions/dm-ponytail/hooks/claude-codex-hooks.json +3 -3
  866. package/extensions/dm-ponytail/hooks/copilot-hooks.json +4 -4
  867. package/extensions/dm-ponytail/hooks/ponytail-activate.js +46 -13
  868. package/extensions/dm-ponytail/hooks/ponytail-config.js +6 -5
  869. package/extensions/dm-ponytail/hooks/ponytail-instructions.js +29 -33
  870. package/extensions/dm-ponytail/hooks/ponytail-map.js +116 -0
  871. package/extensions/dm-ponytail/hooks/ponytail-mode-tracker.js +26 -13
  872. package/extensions/dm-ponytail/hooks/ponytail-runtime.js +23 -9
  873. package/extensions/dm-ponytail/hooks/ponytail-subagent.js +15 -3
  874. package/extensions/dm-ponytail/hooks/qoder-hooks.json +3 -3
  875. package/extensions/dm-ponytail/package.json +1 -1
  876. package/extensions/dm-ponytail/pi-extension/index.js +37 -17
  877. package/extensions/dm-ponytail/pi-extension/package.json +1 -1
  878. package/extensions/dm-ponytail/skills/plugin.json +5 -0
  879. package/extensions/dm-ponytail/skills/ponytail/SKILL.md +33 -101
  880. package/extensions/dm-ponytail/skills/ponytail-audit/SKILL.md +89 -27
  881. package/extensions/dm-ponytail/skills/ponytail-debt/SKILL.md +13 -12
  882. package/extensions/dm-ponytail/skills/ponytail-gain/SKILL.md +21 -18
  883. package/extensions/dm-ponytail/skills/ponytail-help/SKILL.md +13 -14
  884. package/extensions/dm-ponytail/skills/ponytail-review/SKILL.md +96 -52
  885. package/extensions/dm-subagents/agents/oracle.md +8 -19
  886. package/extensions/dm-subagents/agents/researcher.md +0 -3
  887. package/extensions/dm-subagents/agents/reviewer.md +17 -61
  888. package/extensions/dm-subagents/agents/scout.md +1 -4
  889. package/extensions/dm-subagents/agents/worker.md +8 -25
  890. package/extensions/dm-subagents/index.js +5 -0
  891. package/extensions/dm-subagents/install.mjs +47 -62
  892. package/extensions/dm-subagents/package.json +6 -5
  893. package/extensions/dm-subagents/prompts/council.md +1 -1
  894. package/extensions/dm-subagents/prompts/review-loop.md +1 -1
  895. package/extensions/dm-subagents/skills/council-mode/SKILL.md +1 -1
  896. package/extensions/dm-subagents/skills/dm-subagents/SKILL.md +13 -6
  897. package/extensions/dm-subagents/skills/dm-subagents/references/constraints-and-recipes.md +3 -3
  898. package/extensions/dm-subagents/skills/dm-subagents/references/execution-controls.md +79 -78
  899. package/extensions/dm-subagents/skills/dm-subagents/references/management-authoring-rpc.md +18 -14
  900. package/extensions/dm-subagents/skills/dm-subagents/references/multi-lane-orchestration.md +1 -1
  901. package/extensions/dm-subagents/skills/dm-subagents/references/prompting-and-roles.md +46 -49
  902. package/extensions/dm-subagents/src/agents/advertised-agent-prompt.js +18 -32
  903. package/extensions/dm-subagents/src/agents/agent-management.js +7 -2
  904. package/extensions/dm-subagents/src/agents/agent-memory.js +3 -3
  905. package/extensions/dm-subagents/src/agents/agent-scope.js +3 -0
  906. package/extensions/dm-subagents/src/agents/agent-serializer.js +12 -1
  907. package/extensions/dm-subagents/src/agents/agents.js +217 -120
  908. package/extensions/dm-subagents/src/api/inspectors.js +10 -0
  909. package/extensions/dm-subagents/src/api/preflight.js +43 -6
  910. package/extensions/dm-subagents/src/extension/config.js +35 -2
  911. package/extensions/dm-subagents/src/extension/control-notices.js +3 -7
  912. package/extensions/dm-subagents/src/extension/declaration-pinning.js +127 -0
  913. package/extensions/dm-subagents/src/extension/doctor.js +4 -7
  914. package/extensions/dm-subagents/src/extension/fanout-child.js +15 -7
  915. package/extensions/dm-subagents/src/extension/herdr-dm-bridge.js +2 -0
  916. package/extensions/dm-subagents/src/extension/index.js +231 -332
  917. package/extensions/dm-subagents/src/extension/public-execution.js +63 -33
  918. package/extensions/dm-subagents/src/extension/reply-workflow-script.js +65 -0
  919. package/extensions/dm-subagents/src/extension/rpc.js +42 -16
  920. package/extensions/dm-subagents/src/extension/schemas.js +73 -152
  921. package/extensions/dm-subagents/src/extension/subagent-guide.js +92 -7
  922. package/extensions/dm-subagents/src/extension/subagent-options.js +47 -0
  923. package/extensions/dm-subagents/src/extension/tool-activation.js +57 -89
  924. package/extensions/dm-subagents/src/extension/tool-description.js +56 -47
  925. package/extensions/dm-subagents/src/extension/upgrade-notice.js +36 -0
  926. package/extensions/dm-subagents/src/inspectors/actions.js +34 -11
  927. package/extensions/dm-subagents/src/inspectors/plugins.js +71 -2
  928. package/extensions/dm-subagents/src/inspectors/tmux/actions.js +173 -0
  929. package/extensions/dm-subagents/src/inspectors/tmux/client.js +63 -0
  930. package/extensions/dm-subagents/src/inspectors/tmux/plugin.js +14 -0
  931. package/extensions/dm-subagents/src/integrations/herdr-status.js +26 -49
  932. package/extensions/dm-subagents/src/integrations/program-status.js +179 -0
  933. package/extensions/dm-subagents/src/intercom/native-supervisor-channel.js +77 -4
  934. package/extensions/dm-subagents/src/intercom/result-intercom.js +3 -2
  935. package/extensions/dm-subagents/src/intercom/supervisor-ui.js +2 -152
  936. package/extensions/dm-subagents/src/missions/goal-driver.js +47 -34
  937. package/extensions/dm-subagents/src/missions/store.js +33 -4
  938. package/extensions/dm-subagents/src/profiles/profiles.js +22 -15
  939. package/extensions/dm-subagents/src/runs/background/active-async-capacity.js +49 -6
  940. package/extensions/dm-subagents/src/runs/background/async-execution.js +301 -139
  941. package/extensions/dm-subagents/src/runs/background/async-job-tracker.js +65 -24
  942. package/extensions/dm-subagents/src/runs/background/async-resume.js +9 -3
  943. package/extensions/dm-subagents/src/runs/background/async-retention.js +40 -15
  944. package/extensions/dm-subagents/src/runs/background/await-async-run.js +68 -0
  945. package/extensions/dm-subagents/src/runs/background/binary-bootstrap.js +2 -1
  946. package/extensions/dm-subagents/src/runs/background/calendar-schedule.js +90 -0
  947. package/extensions/dm-subagents/src/runs/background/chain-root-attachment.js +11 -8
  948. package/extensions/dm-subagents/src/runs/background/control-channel.js +89 -5
  949. package/extensions/dm-subagents/src/runs/background/fleet-view.js +5 -5
  950. package/extensions/dm-subagents/src/runs/background/notify.js +170 -11
  951. package/extensions/dm-subagents/src/runs/background/pid-namespace.js +17 -0
  952. package/extensions/dm-subagents/src/runs/background/result-files.js +80 -6
  953. package/extensions/dm-subagents/src/runs/background/result-watcher.js +92 -65
  954. package/extensions/dm-subagents/src/runs/background/resume-guidance.js +2 -2
  955. package/extensions/dm-subagents/src/runs/background/run-child-session.js +34 -10
  956. package/extensions/dm-subagents/src/runs/background/run-id-resolver.js +8 -6
  957. package/extensions/dm-subagents/src/runs/background/run-status.js +25 -15
  958. package/extensions/dm-subagents/src/runs/background/runner-aliases.js +6 -4
  959. package/extensions/dm-subagents/src/runs/background/runner-child-launch.js +3 -0
  960. package/extensions/dm-subagents/src/runs/background/runner-http-dispatcher.js +2 -1
  961. package/extensions/dm-subagents/src/runs/background/scheduled-runs.js +270 -79
  962. package/extensions/dm-subagents/src/runs/background/stale-run-reconciler.js +18 -3
  963. package/extensions/dm-subagents/src/runs/background/subagent-runner-bootstrap.js +4 -1
  964. package/extensions/dm-subagents/src/runs/background/subagent-runner.js +123 -33
  965. package/extensions/dm-subagents/src/runs/background/subagent-wait.js +33 -20
  966. package/extensions/dm-subagents/src/runs/background/terminal-run-index.js +42 -1
  967. package/extensions/dm-subagents/src/runs/background/wait-completions.js +1 -0
  968. package/extensions/dm-subagents/src/runs/background/wait-tool.js +36 -26
  969. package/extensions/dm-subagents/src/runs/background/workflow-terminal-proof.js +39 -26
  970. package/extensions/dm-subagents/src/runs/foreground/async-stop-action.js +58 -26
  971. package/extensions/dm-subagents/src/runs/foreground/command-action.js +37 -0
  972. package/extensions/dm-subagents/src/runs/foreground/execution.js +132 -32
  973. package/extensions/dm-subagents/src/runs/foreground/foreground-history.js +1 -0
  974. package/extensions/dm-subagents/src/runs/foreground/prompt-audit.js +5 -8
  975. package/extensions/dm-subagents/src/runs/foreground/subagent-executor.js +683 -325
  976. package/extensions/dm-subagents/src/runs/shared/async-status-projection.js +3 -0
  977. package/extensions/dm-subagents/src/runs/shared/child-commands.js +252 -0
  978. package/extensions/dm-subagents/src/runs/shared/child-hooks.js +6 -5
  979. package/extensions/dm-subagents/src/runs/shared/child-launch.js +13 -1
  980. package/extensions/dm-subagents/src/runs/shared/child-session.js +175 -28
  981. package/extensions/dm-subagents/src/runs/shared/child-tool-plan.js +17 -14
  982. package/extensions/dm-subagents/src/runs/shared/claude-code-adapter.js +87 -1
  983. package/extensions/dm-subagents/src/runs/shared/dm-spawn.js +11 -85
  984. package/extensions/dm-subagents/src/runs/shared/dynamic-fanout.js +3 -0
  985. package/extensions/dm-subagents/src/runs/shared/effective-system-prompt.js +1 -2
  986. package/extensions/dm-subagents/src/runs/shared/external-cli-preflight.js +57 -6
  987. package/extensions/dm-subagents/src/runs/shared/external-cli-runner.js +6 -3
  988. package/extensions/dm-subagents/src/runs/shared/herdr-placed-run.js +13 -6
  989. package/extensions/dm-subagents/src/runs/shared/mcp-config-sources.js +1 -1
  990. package/extensions/dm-subagents/src/runs/shared/mcp-direct-tool-allowlist.js +66 -14
  991. package/extensions/dm-subagents/src/runs/shared/model-resolution-diagnostic.js +25 -5
  992. package/extensions/dm-subagents/src/runs/shared/model-resolution.js +55 -11
  993. package/extensions/dm-subagents/src/runs/shared/model-scope.js +23 -10
  994. package/extensions/dm-subagents/src/runs/shared/nested-events.js +3 -0
  995. package/extensions/dm-subagents/src/runs/shared/parallel-handoff.js +55 -7
  996. package/extensions/dm-subagents/src/runs/shared/partial-output.js +26 -0
  997. package/extensions/dm-subagents/src/runs/shared/run-history.js +41 -0
  998. package/extensions/dm-subagents/src/runs/shared/runner-launcher.js +21 -0
  999. package/extensions/dm-subagents/src/runs/shared/single-output.js +67 -26
  1000. package/extensions/dm-subagents/src/runs/shared/structured-output.js +24 -7
  1001. package/extensions/dm-subagents/src/runs/shared/subagent-control.js +32 -20
  1002. package/extensions/dm-subagents/src/runs/shared/subagent-prompt-runtime.js +68 -29
  1003. package/extensions/dm-subagents/src/runs/shared/tool-availability.js +1 -1
  1004. package/extensions/dm-subagents/src/runs/shared/workflow-async-child-guidance.js +1 -1
  1005. package/extensions/dm-subagents/src/runs/shared/worktree-cleanup-apply.js +169 -0
  1006. package/extensions/dm-subagents/src/runs/shared/worktree-cleanup-plan.js +41 -20
  1007. package/extensions/dm-subagents/src/runs/shared/worktree-lock.js +38 -0
  1008. package/extensions/dm-subagents/src/runs/shared/worktree.js +10 -1
  1009. package/extensions/dm-subagents/src/shared/atomic-json.js +2 -1
  1010. package/extensions/dm-subagents/src/shared/disabled-features.js +101 -0
  1011. package/extensions/dm-subagents/src/shared/dm-declarations.js +14 -0
  1012. package/extensions/dm-subagents/src/shared/dm-safeguards.js +41 -0
  1013. package/extensions/dm-subagents/src/shared/dm-settlement.js +48 -0
  1014. package/extensions/dm-subagents/src/shared/edit-distance.js +23 -0
  1015. package/extensions/dm-subagents/src/shared/extension-context.js +4 -0
  1016. package/extensions/dm-subagents/src/shared/file-lease.js +89 -0
  1017. package/extensions/dm-subagents/src/shared/fork-context.js +0 -1
  1018. package/extensions/dm-subagents/src/shared/formatters.js +1 -6
  1019. package/extensions/dm-subagents/src/shared/model-info.js +23 -0
  1020. package/extensions/dm-subagents/src/shared/package-version.js +12 -0
  1021. package/extensions/dm-subagents/src/shared/parent-wake.js +67 -0
  1022. package/extensions/dm-subagents/src/shared/pruned-fork.js +6 -2
  1023. package/extensions/dm-subagents/src/shared/required-child-extensions.js +35 -5
  1024. package/extensions/dm-subagents/src/shared/runtime-module-path.js +9 -0
  1025. package/extensions/dm-subagents/src/shared/settings-file-lease.js +31 -0
  1026. package/extensions/dm-subagents/src/shared/types.js +3 -1
  1027. package/extensions/dm-subagents/src/shared/utils.js +7 -7
  1028. package/extensions/dm-subagents/src/slash/prompt-template-bridge.js +2 -2
  1029. package/extensions/dm-subagents/src/slash/slash-commands.js +25 -15
  1030. package/extensions/dm-subagents/src/slash/subagent-cost.js +38 -7
  1031. package/extensions/dm-subagents/src/slash/subagents-admin.js +1 -1
  1032. package/extensions/dm-subagents/src/tui/dm-mouse-region.js +12 -0
  1033. package/extensions/dm-subagents/src/tui/fleet-status.js +12 -6
  1034. package/extensions/dm-subagents/src/tui/fleet-transcript.js +6 -2
  1035. package/extensions/dm-subagents/src/tui/fleet.js +20 -6
  1036. package/extensions/dm-subagents/src/tui/render.js +126 -72
  1037. package/extensions/dm-subagents/src/tui/running-tone.js +20 -0
  1038. package/extensions/dm-subagents/src/tui/subagent-messages.js +249 -0
  1039. package/extensions/dm-subagents/src/watchdog/diff-tool.js +19 -11
  1040. package/extensions/dm-subagents/src/watchdog/permission-arbiter.js +3 -11
  1041. package/extensions/dm-subagents/src/watchdog/register-child.js +2 -2
  1042. package/extensions/dm-subagents/src/watchdog/register-main.js +4 -24
  1043. package/extensions/dm-subagents/src/watchdog/render.js +1 -19
  1044. package/extensions/dm-subagents/src/watchdog/review.js +4 -7
  1045. package/extensions/dm-subagents/src/watchdog/runtime.js +6 -2
  1046. package/extensions/dm-subagents/src/watchdog/settings.js +22 -17
  1047. package/extensions/dm-subagents/src/watchdog/tool-actions.js +2 -2
  1048. package/extensions/dm-subagents/src/watchdog/turn-delta.js +1 -1
  1049. package/extensions/dm-subagents/src/workflows/chat-progress.js +2 -2
  1050. package/extensions/dm-subagents/src/workflows/scripted-workflow.js +116 -45
  1051. package/extensions/dm-subagents/src/workflows/structured-workflow-scripts.js +154 -0
  1052. package/extensions/dm-subagents/src/workflows/workflow-checklist.js +3 -0
  1053. package/extensions/dm-subagents/src/workflows/workflow-receipt.js +7 -4
  1054. package/extensions/dm-subagents/src/workflows/workflow-resources.js +52 -17
  1055. package/extensions/dm-subagents/src/workflows/workflow-reuse.js +87 -0
  1056. package/extensions/dm-subagents/src/workflows/workflow-revival.js +80 -0
  1057. package/extensions/dm-tty/control.js +150 -0
  1058. package/extensions/dm-tty/index.js +122 -0
  1059. package/extensions/dm-tty/package.json +20 -0
  1060. package/extensions/dm-tty/pty.js +213 -0
  1061. package/extensions/dm-tty/pty_bridge.py +266 -0
  1062. package/extensions/dm-tty/server.js +438 -0
  1063. package/extensions/dm-tty/sessions.js +97 -0
  1064. package/extensions/dm-tty/standalone.js +75 -0
  1065. package/extensions/dm-tty/web/client.js +1141 -0
  1066. package/extensions/dm-tty/web/index.html +117 -0
  1067. package/extensions/dm-tty/web/style.css +73 -0
  1068. package/extensions/dm-web-access/brave-rate-limit.js +160 -0
  1069. package/extensions/dm-web-access/brave.js +62 -11
  1070. package/extensions/dm-web-access/ceramic.js +197 -0
  1071. package/extensions/dm-web-access/chrome-cookies.js +69 -21
  1072. package/extensions/dm-web-access/curator-page.js +12 -2
  1073. package/extensions/dm-web-access/curator-server.js +10 -0
  1074. package/extensions/dm-web-access/declared-web-links.js +1 -1
  1075. package/extensions/dm-web-access/degoog.js +288 -0
  1076. package/extensions/dm-web-access/dist/index.js +28404 -0
  1077. package/extensions/dm-web-access/dist/mcp-cli.js +28186 -0
  1078. package/extensions/dm-web-access/duckduckgo.js +6 -3
  1079. package/extensions/dm-web-access/endpoint-pdf-extract.js +210 -0
  1080. package/extensions/dm-web-access/exa.js +17 -35
  1081. package/extensions/dm-web-access/extract.js +63 -10
  1082. package/extensions/dm-web-access/firecrawl.js +30 -4
  1083. package/extensions/dm-web-access/gemini-search.js +117 -37
  1084. package/extensions/dm-web-access/gemini-web.js +7 -7
  1085. package/extensions/dm-web-access/github-extract.js +28 -2
  1086. package/extensions/dm-web-access/github-issue-pr.js +88 -73
  1087. package/extensions/dm-web-access/index.js +230 -1161
  1088. package/extensions/dm-web-access/keenable.js +197 -0
  1089. package/extensions/dm-web-access/mcp-cli.js +30 -0
  1090. package/extensions/dm-web-access/mcp-server.js +51 -0
  1091. package/extensions/dm-web-access/mcp-tool-definitions.js +93 -0
  1092. package/extensions/dm-web-access/openai-search.js +70 -20
  1093. package/extensions/dm-web-access/opencode-session-headers.js +1 -1
  1094. package/extensions/dm-web-access/package.json +4 -1
  1095. package/extensions/dm-web-access/page-query.js +5 -10
  1096. package/extensions/dm-web-access/parallel-mcp.js +2 -1
  1097. package/extensions/dm-web-access/pdf-extract.js +34 -1
  1098. package/extensions/dm-web-access/perplexity.js +68 -51
  1099. package/extensions/dm-web-access/query-rewrite.js +3 -4
  1100. package/extensions/dm-web-access/scripts/build.js +8 -0
  1101. package/extensions/dm-web-access/ssrf-protection.js +10 -8
  1102. package/extensions/dm-web-access/storage.js +79 -76
  1103. package/extensions/dm-web-access/summary-model-scope.js +5 -64
  1104. package/extensions/dm-web-access/summary-review.js +35 -9
  1105. package/extensions/dm-web-access/tavily.js +69 -22
  1106. package/extensions/dm-web-access/tool-activation.js +44 -26
  1107. package/extensions/dm-web-access/tool-arguments.js +21 -0
  1108. package/extensions/dm-web-access/utils.js +17 -7
  1109. package/extensions/dm-web-access/video-extract.js +1 -1
  1110. package/extensions/dm-web-access/web-tool-contract.js +0 -0
  1111. package/extensions/dm-web-access/web-tool-core.js +1312 -0
  1112. package/extensions/dm-web-access/you.js +155 -0
  1113. package/extensions/dm-web-access/youtube-extract.js +2 -2
  1114. package/extensions/dm-web-access/zai.js +159 -0
  1115. package/package.json +13 -9
@@ -0,0 +1,4639 @@
1
+ import { CASCADE_DIAGNOSTICS_TTL_MS } from "../cascade-types.js";
2
+ import * as nodeFs from "node:fs";
3
+ import fs from "node:fs/promises";
4
+ import path from "node:path";
5
+ import { pathToFileURL, URL } from "node:url";
6
+ import { BoundedFifoMap } from "../bounded-cache.js";
7
+ import { getProjectIgnoreMatcher, isExcludedDirName } from "../file-utils.js";
8
+ import { recordLsp } from "../widget-state.js";
9
+ import { applyAuxiliarySuppressions } from "../dispatch/auxiliary-lsp.js";
10
+ import { detectFileRole } from "../file-role.js";
11
+ import { emitBounded } from "../bounded-telemetry.js";
12
+ import { logLatency, phaseFinished, phaseStarted } from "../latency-logger.js";
13
+ import { logSessionStart } from "../sessionstart-logger.js";
14
+ import { incrementDegradationCount, recordDegradation, recordDegradationOnce } from "../degradation-ledger.js";
15
+ import { assertInstallAllowed } from "../project-trust.js";
16
+ import { isLspSpawnAllowedByTrust, projectTrustDenialReason } from "../project-trust.js";
17
+ import { shouldPreferPullOnlyDiagnostics } from "../lsp-budget.js";
18
+ import { sampleProcessTreeCpuPercent } from "../resource-sampler.js";
19
+ import { bounded, withDeadline, withTimeout } from "../deadline-utils.js";
20
+ import { HOOK_WALL_BUDGET_MS } from "../hook-budgets.js";
21
+ import { getAmbientAbortSignal } from "../safe-spawn.js";
22
+ import { abortDeferredLspWork } from "../deferred-lsp-work.js";
23
+ import { acquireWorkspaceSweepHold, clearWorkspaceSweepHoldForSessionStart } from "./workspace-sweep-hold.js";
24
+ import { DocumentDriftTracker, fingerprintDocumentContent } from "./document-drift.js";
25
+ import { captureAuxPublicationBacklog, markPendingAuxiliaryCoverage, napiFallbackCoveredSince } from "./pending-aux-coverage.js";
26
+ import { isAtOrAboveHomeDir, isWindowsPath, normalizeMapKey, uriToPath } from "../path-utils.js";
27
+ import { recordLspMutation } from "../lsp-mutation.js";
28
+ import { createLSPClient } from "./client.js";
29
+ import { auxiliaryCoverageGap, auxiliaryPublicationEvidence, bindingStateLabel, composeBoundToCurrentDisk, createDiskBindingCache, hashDiagnosticContent, resolveTouchVerdict, touchCoverageGap } from "./diagnostic-binding.js";
30
+ import { getServersForFileWithConfig, getServerInitOverride } from "./config.js";
31
+ import { getSessionRootsForTelemetry, isOutsideAllSessionRoots } from "./session-roots.js";
32
+ import { findLocalBinUpwards, isProjectLocalBinPath } from "../package-manager.js";
33
+ import { resolveAnalysisRoot, resolveAnalysisRootPath } from "../analysis-root.js";
34
+ import { configureLspServiceLifecycle, getOwnedLspService, peekOwnedLspService, resetOwnedLspService } from "./service-singleton.js";
35
+ import { groupFilesByPrimaryServer, runPerServerGroups } from "./grouping.js";
36
+ export { groupFilesByPrimaryServer, runPerServerGroups } from "./grouping.js";
37
+ import { isEphemeralCheckoutRoot } from "../ephemeral-root.js";
38
+ import { getLanguageId } from "./language.js";
39
+ import { getReverseDepsFromIndex, loadReverseDependencyIndexFromSnapshot } from "../reverse-deps.js";
40
+ import { isSameOrWithin } from "./server.js";
41
+ import { refuseUntrustedLspExecution, withLspProjectCodePermission } from "./launch.js";
42
+ import { enforceLspRootCeiling, getServerById, hasProjectBoundaryMarker, isDirectLspCommandTemporarilyUnavailable, resetClassicTsRepairGuard, resetLspLaunchAvailabilityGeneration, resolveLspServerCwd } from "./server.js";
43
+ import { isAuxiliary, serverTraits } from "./server-traits.js";
44
+ import { auxWaitBudgetMs, readEnvAuxGraceMs, DEFAULT_AUX_GRACE_CEILING_MS } from "./auxiliary-lifecycle.js";
45
+ import { classifyCascadeWaitTier, classifyServerWaitTier, getStrategy } from "./wait-policy/index.js";
46
+ function textDocumentSaveOf(client) {
47
+ if (typeof client.getSaveOptions !== "function")
48
+ return;
49
+ const save = client.getSaveOptions();
50
+ if (!save)
51
+ return "none";
52
+ return save.includeText ? "save+text" : "save";
53
+ }
54
+ const WORKSPACE_ATTRIBUTION_CLIENT_CAP = 16;
55
+ const AUX_WAIT_DEMOTION_THRESHOLD = 5;
56
+ const AUX_WAIT_DEMOTION_RATIO = 0.9;
57
+ const AUX_WAIT_REPROMOTION_THRESHOLD = 5;
58
+ const AUX_WAIT_REPROMOTION_RATIO = 0.5;
59
+ const ADOPTED_ROOT_CAP = 2;
60
+ const ADOPTED_ROOT_IDLE_EVICT_MS = 60000;
61
+ import { raceToCompletion } from "./aggregation.js";
62
+ import { applyWorkspaceEdit, mergeWorkspaceTextEditsByPriority, summarizeWorkspaceEdit, validateWorkspaceEdit } from "./edits.js";
63
+ import { buildScopeKey, createWorkspaceDiagnosticsCacheContext } from "./workspace-diagnostics-cache.js";
64
+ import { attemptTsserverSyncDiagnostics } from "./tsserver-sync.js";
65
+ import { isWarmAttached, tryWarmAttachedDiagnostics } from "../warm-attach.js";
66
+ import { getSuccessfulLspSpawnDurationMs, recordSuccessfulLspSpawn } from "./spawn-history.js";
67
+ function destinationUriPreservingSpelling(oldUri, oldFilePath, newFilePath) {
68
+ const canonical = pathToFileURL(newFilePath);
69
+ try {
70
+ const original = new URL(oldUri);
71
+ const authority = /^file:\/\/([^/]*)/i.exec(oldUri)?.[1] ?? "";
72
+ if (original.protocol !== "file:" || original.host !== "" && original.hostname !== "localhost") {
73
+ return canonical.href;
74
+ }
75
+ const canonicalDestination = () => `${original.protocol}//${authority}${canonical.pathname}`;
76
+ const oldParent = path.resolve(path.dirname(oldFilePath));
77
+ const newParent = path.resolve(path.dirname(newFilePath));
78
+ if (normalizeMapKey(oldParent) !== normalizeMapKey(newParent)) {
79
+ return canonicalDestination();
80
+ }
81
+ const slash = original.pathname.lastIndexOf("/");
82
+ if (slash < 0)
83
+ return canonicalDestination();
84
+ let rawName = canonical.pathname.slice(canonical.pathname.lastIndexOf("/") + 1);
85
+ const oldRawName = original.pathname.slice(slash + 1);
86
+ const oldName = decodeURIComponent(oldRawName);
87
+ const expectedOldName = path.basename(oldFilePath);
88
+ if (oldName.toLowerCase() !== expectedOldName.toLowerCase()) {
89
+ return canonicalDestination();
90
+ }
91
+ for (const match of oldRawName.matchAll(/%([0-9a-f]{2})/gi)) {
92
+ const encoded = match[0];
93
+ const decoded = String.fromCharCode(Number.parseInt(match[1], 16));
94
+ rawName = rawName.split(decoded).join(encoded);
95
+ }
96
+ return `${original.protocol}//${authority}${original.pathname.slice(0, slash + 1)}${rawName}`;
97
+ } catch {
98
+ return canonical.href;
99
+ }
100
+ }
101
+ function deepMergeObjects(base, override) {
102
+ const result = { ...base };
103
+ for (const [key, val] of Object.entries(override)) {
104
+ if (val !== null && typeof val === "object" && !Array.isArray(val) && result[key] !== null && typeof result[key] === "object" && !Array.isArray(result[key])) {
105
+ result[key] = deepMergeObjects(result[key], val);
106
+ } else {
107
+ result[key] = val;
108
+ }
109
+ }
110
+ return result;
111
+ }
112
+ export function mergeInitializationOptions(base, override) {
113
+ if (!override)
114
+ return base;
115
+ if (!base)
116
+ return override;
117
+ return deepMergeObjects(base, override);
118
+ }
119
+ const BROKEN_BASE_COOLDOWN_MS = 15000;
120
+ const BROKEN_MAX_COOLDOWN_MS = 5 * 60000;
121
+ const BROKEN_PERMANENT_AFTER = 5;
122
+ const RUNTIME_EXIT_UPTIME_THRESHOLD_MS = 60000;
123
+ const RUNTIME_EXIT_WINDOW_MS = 15 * 60000;
124
+ const RUNTIME_EXIT_WINDOW_TRIP_COUNT = BROKEN_PERMANENT_AFTER;
125
+ const RUNTIME_EXIT_WINDOW_UPTIME_CEILING_MS = 10 * 60000;
126
+ const NOTIFY_BACKPRESSURE_BROKEN_AFTER = 3;
127
+ const OPTIONAL_LSP_RETRY_COOLDOWN_MS = 5 * 60000;
128
+ const OPTIONAL_LSP_SERVER_IDS = new Set;
129
+ const NAV_CLIENT_WAIT_TIMEOUT_MS = Math.max(0, Number.parseInt(process.env.DM_LENS_LSP_NAV_CLIENT_WAIT_MS ?? "1500", 10) || 1500);
130
+ const TOUCH_DEBOUNCE_MS = Math.max(0, Number.parseInt(process.env.DM_LENS_LSP_TOUCH_DEBOUNCE_MS ?? "1500", 10) || 1500);
131
+ const SCOPED_DEPENDENCY_TOUCH_MAX = 32;
132
+ const RENAME_NOTIFY_TIMEOUT_MS = Math.max(50, Number.parseInt(process.env.DM_LENS_LSP_RENAME_NOTIFY_TIMEOUT_MS ?? "1500", 10) || 1500);
133
+ async function runRenameNotify(send, timeoutMs) {
134
+ try {
135
+ await withTimeout(send(), timeoutMs);
136
+ return { ok: true };
137
+ } catch (err) {
138
+ const timedOut = err instanceof Error && err.message.startsWith("Timeout after");
139
+ return {
140
+ ok: false,
141
+ error: err instanceof Error ? err.message : String(err),
142
+ disposition: timedOut ? "timedOut" : "rejected"
143
+ };
144
+ }
145
+ }
146
+ const DEFAULT_LSP_CLIENT_CEILING = 24;
147
+ const DEFAULT_IDLE_EVICT_MS = 20 * 60000;
148
+ const DEFAULT_EPHEMERAL_IDLE_EVICT_MS = 60000;
149
+ export function getLspIdleEvictMs() {
150
+ for (const name of [
151
+ "DM_LENS_LSP_IDLE_EVICT_MS",
152
+ "DM_LENS_TS_IDLE_EVICT_MS"
153
+ ]) {
154
+ const parsed = Number.parseInt(process.env[name] ?? "", 10);
155
+ if (Number.isSafeInteger(parsed) && parsed > 0)
156
+ return parsed;
157
+ }
158
+ return DEFAULT_IDLE_EVICT_MS;
159
+ }
160
+ function getEphemeralLspIdleEvictMs() {
161
+ const parsed = Number.parseInt(process.env.DM_LENS_EPHEMERAL_LSP_IDLE_EVICT_MS ?? "", 10);
162
+ return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : DEFAULT_EPHEMERAL_IDLE_EVICT_MS;
163
+ }
164
+ export function getLspIdleEvictMsForRoot(root) {
165
+ return isEphemeralCheckoutRoot(root) ? getEphemeralLspIdleEvictMs() : getLspIdleEvictMs();
166
+ }
167
+ export function getLspClientCeiling() {
168
+ const parsed = Number.parseInt(process.env.DM_LENS_LSP_CLIENT_CEILING ?? "", 10);
169
+ return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : DEFAULT_LSP_CLIENT_CEILING;
170
+ }
171
+ function documentIsOpenOn(client, filePath) {
172
+ const probe = client.isDocumentOpen;
173
+ if (typeof probe !== "function")
174
+ return false;
175
+ try {
176
+ return probe.call(client, filePath) === true;
177
+ } catch {
178
+ return false;
179
+ }
180
+ }
181
+ function tsserverSyncChannel(client) {
182
+ return {
183
+ getAdvertisedCommands: async () => client.getAdvertisedCommands(),
184
+ executeCommand: (_filePath, command, args) => client.executeCommand(command, args)
185
+ };
186
+ }
187
+ function warmupTimeoutMs() {
188
+ const raw = Number.parseInt(process.env.DM_LENS_LSP_WARMUP_TIMEOUT_MS ?? "", 10);
189
+ return Number.isFinite(raw) && raw > 0 ? raw : 20000;
190
+ }
191
+ function warmupRetryBackoffMs() {
192
+ const raw = Number.parseInt(process.env.DM_LENS_LSP_WARMUP_RETRY_BACKOFF_MS ?? "", 10);
193
+ return Number.isFinite(raw) && raw >= 0 ? raw : 500;
194
+ }
195
+ function readEnvDiagnosticsWaitMs() {
196
+ const raw = process.env.DM_LENS_LSP_DIAGNOSTICS_MAX_WAIT_MS;
197
+ if (raw === undefined)
198
+ return;
199
+ const parsed = Number.parseInt(raw, 10);
200
+ if (!Number.isFinite(parsed) || parsed <= 0)
201
+ return;
202
+ return parsed;
203
+ }
204
+ function readTsserverSyncGraceMs() {
205
+ const raw = process.env.DM_LENS_TSSERVER_SYNC_GRACE_MS;
206
+ if (raw === undefined)
207
+ return 300;
208
+ const parsed = Number.parseInt(raw, 10);
209
+ if (!Number.isFinite(parsed) || parsed < 0)
210
+ return 300;
211
+ return parsed;
212
+ }
213
+ const MAX_TOUCH_CANDIDATES = 8;
214
+ const DIAGNOSTICS_SEMANTIC_SETTLE_THRESHOLD_MS = Math.max(0, Number.parseInt(process.env.DM_LENS_LSP_DIAGNOSTICS_SEMANTIC_THRESHOLD_MS ?? "250", 10) || 250);
215
+ const DIAGNOSTICS_SEMANTIC_SETTLE_WAIT_MS = Math.max(0, Number.parseInt(process.env.DM_LENS_LSP_DIAGNOSTICS_SEMANTIC_SETTLE_MS ?? "400", 10) || 400);
216
+ const EARLY_UNBLOCK_GRACE_MS = Math.max(0, Number.parseInt(process.env.DM_LENS_LSP_EARLY_UNBLOCK_GRACE_MS ?? "400", 10) || 400);
217
+ function mergeLspDiagnostics(diagnostics) {
218
+ const merged = [];
219
+ const seen = new Set;
220
+ for (const diagnostic of diagnostics) {
221
+ const key = [
222
+ diagnostic.range.start.line,
223
+ diagnostic.range.start.character,
224
+ diagnostic.message
225
+ ].join(":");
226
+ if (seen.has(key))
227
+ continue;
228
+ seen.add(key);
229
+ merged.push(diagnostic);
230
+ }
231
+ return merged;
232
+ }
233
+ function attributeDiagnostics(serverId, diagnostics) {
234
+ return diagnostics.map((diagnostic) => ({ ...diagnostic, serverId }));
235
+ }
236
+ export function createSweepIndexGate() {
237
+ const seen = new Set;
238
+ return {
239
+ consumeFirstTouch(serverId) {
240
+ if (seen.has(serverId))
241
+ return false;
242
+ seen.add(serverId);
243
+ return true;
244
+ }
245
+ };
246
+ }
247
+ const WORKSPACE_DIAGNOSTICS_CONCURRENCY = 8;
248
+ const WORKSPACE_SWEEP_PREOPEN_CHUNK_SIZE = (() => {
249
+ const raw = Number(process.env.DM_LENS_LSP_WORKSPACE_PREOPEN_CHUNK);
250
+ return Number.isFinite(raw) && raw > 0 ? Math.floor(raw) : 8;
251
+ })();
252
+ const WORKSPACE_SWEEP_EXCLUDED_SERVER_IDS = new Set([
253
+ "opengrep"
254
+ ]);
255
+ function notifyWriteBudgetMs() {
256
+ const raw = Number(process.env.DM_LENS_LSP_NOTIFY_BUDGET_MS);
257
+ return Number.isFinite(raw) && raw > 0 ? raw : 2000;
258
+ }
259
+ function primaryServerWaitFloorMs(filePath, maxWaitMs) {
260
+ const serverWaitOverrideMs = getServersForFileWithConfig(filePath).filter((s) => !isAuxiliary(s)).reduce((max, server) => Math.max(max, server.clientWaitTimeoutMs ?? 0), 0);
261
+ return Math.max(maxWaitMs ?? 0, serverWaitOverrideMs);
262
+ }
263
+ const NOTIFY_WEDGED_BUDGET_MULTIPLIER = 5;
264
+ function notifyWedgedMs() {
265
+ return notifyWriteBudgetMs() * NOTIFY_WEDGED_BUDGET_MULTIPLIER;
266
+ }
267
+ const NOTIFY_WEDGED_EWMA_MULTIPLIER = 2;
268
+ const NOTIFY_WEDGED_CAP_MS = 60000;
269
+ function notifyWedgedCapMs() {
270
+ const raw = Number(process.env.DM_LENS_LSP_NOTIFY_WEDGED_CAP_MS);
271
+ return Number.isFinite(raw) && raw > 0 ? raw : NOTIFY_WEDGED_CAP_MS;
272
+ }
273
+ function notifyStallCpuSampleMs() {
274
+ const raw = Number(process.env.DM_LENS_LSP_NOTIFY_STALL_CPU_SAMPLE_MS);
275
+ return Number.isFinite(raw) && raw > 0 ? raw : 1200;
276
+ }
277
+ const NOTIFY_STALL_CPU_BUSY_FLOOR_PERCENT = 10;
278
+ function workspacePullBudgetMs() {
279
+ const raw = Number(process.env.DM_LENS_LSP_WORKSPACE_PULL_MS);
280
+ return Number.isFinite(raw) && raw > 0 ? raw : 30000;
281
+ }
282
+ const DEFAULT_MAX_WORKSPACE_DIAGNOSTIC_FILES = 5000;
283
+ function getMaxWorkspaceDiagnosticFiles() {
284
+ const override = Number.parseInt(process.env.DM_LENS_LSP_WORKSPACE_MAX_FILES ?? "", 10);
285
+ return Number.isFinite(override) && override > 0 ? override : DEFAULT_MAX_WORKSPACE_DIAGNOSTIC_FILES;
286
+ }
287
+ async function collectWorkspaceDiagnosticFiles(root, maxFiles = getMaxWorkspaceDiagnosticFiles(), signal, homeDir) {
288
+ const files = [];
289
+ if (isAtOrAboveHomeDir(root, homeDir))
290
+ return files;
291
+ const ignoreMatcher = getProjectIgnoreMatcher(root);
292
+ await ignoreMatcher.ensureTrackedIndex();
293
+ async function walk(current) {
294
+ if (signal?.aborted || files.length >= maxFiles)
295
+ return;
296
+ let entries;
297
+ try {
298
+ entries = await nodeFs.promises.readdir(current, {
299
+ withFileTypes: true
300
+ });
301
+ } catch {
302
+ return;
303
+ }
304
+ for (const entry of entries) {
305
+ if (signal?.aborted || files.length >= maxFiles)
306
+ return;
307
+ if (entry.isSymbolicLink())
308
+ continue;
309
+ const full = path.join(current, entry.name);
310
+ if (entry.isDirectory()) {
311
+ if (isExcludedDirName(entry.name))
312
+ continue;
313
+ if (ignoreMatcher.isIgnored(full, true))
314
+ continue;
315
+ await walk(full);
316
+ } else if (entry.isFile() && getServersForFileWithConfig(full).length > 0 && !ignoreMatcher.isIgnored(full, false)) {
317
+ files.push(full);
318
+ }
319
+ }
320
+ }
321
+ await walk(root);
322
+ return files;
323
+ }
324
+
325
+ export class LSPService {
326
+ static MAX_SIBLING_ANNOUNCEMENTS = 1024;
327
+ sessionCwd;
328
+ state;
329
+ siblingAnnouncementHashes = new BoundedFifoMap(LSPService.MAX_SIBLING_ANNOUNCEMENTS);
330
+ workspaceProbeLogged = new Set;
331
+ projectBoundaryCache = new Map;
332
+ warmStartLogged = new Set;
333
+ optionalFailureLogged = new Set;
334
+ unavailableLogged = new Set;
335
+ optionalDisabled = new Set;
336
+ firstContactWaits = new Map;
337
+ auxWaitPressureStreak = new Map;
338
+ auxWaitFastAnswerStreak = new Map;
339
+ demotedAuxiliaryServerIds = new Set;
340
+ demotedAuxiliaryBudgets = new Map;
341
+ lastSpawnVerdict = new Map;
342
+ failureCounts = new Map;
343
+ runtimeExitCounts = new Map;
344
+ runtimeExitWindow = new Map;
345
+ permanentlyBroken = new Set;
346
+ lastKnownDiagnostics = new Map;
347
+ lastKnownContentHash = new Map;
348
+ documentDrift = new DocumentDriftTracker;
349
+ diskBindingCache = createDiskBindingCache();
350
+ lastDiagnosticsHealth = new Map;
351
+ recentTouches = new Map;
352
+ notifyWriteBackpressureStreak = new Map;
353
+ auxNotifyDrainLatencyEwma = new Map;
354
+ auxNotifyInflight = new Map;
355
+ outstandingAuxNotifyWrites = new Map;
356
+ static MAX_NOTIFY_STALL_DEMOTIONS = 50;
357
+ notifyStallDemotions = new BoundedFifoMap(LSPService.MAX_NOTIFY_STALL_DEMOTIONS);
358
+ clientLastUsedAt = new Map;
359
+ adoptedRootLastUsedAt = new Map;
360
+ adoptedClientKeys = new Set;
361
+ clientLeases = new Map;
362
+ idleEvictionTimers = new Map;
363
+ clientSpawnGate = Promise.resolve();
364
+ isDestroyed = false;
365
+ shutdownController = new AbortController;
366
+ generationHandoff;
367
+ constructor(generationHandoff, sessionCwd) {
368
+ this.generationHandoff = generationHandoff;
369
+ this.sessionCwd = sessionCwd;
370
+ this.state = {
371
+ clients: new Map,
372
+ servers: new Map,
373
+ broken: new Map,
374
+ inFlight: new Map,
375
+ clientSpawnedAt: new Map,
376
+ demonstratedReady: new Set,
377
+ demonstratedCold: new Set,
378
+ diagnosticsPublished: new Set,
379
+ diagnosticsUnsupported: new Set
380
+ };
381
+ }
382
+ isAuxiliaryWaitDemoted(serverId, root) {
383
+ return this.demotedAuxiliaryServerIds.has(`${serverId}:${normalizeMapKey(root ?? "")}`);
384
+ }
385
+ noteAuxiliaryWait(key, budgetMs, elapsedMs) {
386
+ if (this.demotedAuxiliaryServerIds.has(key))
387
+ return;
388
+ if (elapsedMs < AUX_WAIT_DEMOTION_RATIO * budgetMs) {
389
+ this.auxWaitPressureStreak.delete(key);
390
+ return;
391
+ }
392
+ const streak = (this.auxWaitPressureStreak.get(key) ?? 0) + 1;
393
+ if (streak >= AUX_WAIT_DEMOTION_THRESHOLD) {
394
+ this.demotedAuxiliaryServerIds.add(key);
395
+ this.demotedAuxiliaryBudgets.set(key, budgetMs);
396
+ this.auxWaitPressureStreak.delete(key);
397
+ this.auxWaitFastAnswerStreak.delete(key);
398
+ recordDegradationOnce({
399
+ kind: "aux_wait_demoted",
400
+ subject: key,
401
+ reason: "auxiliary reached at least 90% of its declared wait budget for five consecutive dispatches"
402
+ });
403
+ return;
404
+ }
405
+ this.auxWaitPressureStreak.set(key, streak);
406
+ }
407
+ noteAuxiliaryLateAnswer(key, elapsedMs) {
408
+ if (!this.demotedAuxiliaryServerIds.has(key))
409
+ return;
410
+ const budgetMs = this.demotedAuxiliaryBudgets.get(key);
411
+ if (budgetMs === undefined || elapsedMs >= AUX_WAIT_REPROMOTION_RATIO * budgetMs) {
412
+ this.auxWaitFastAnswerStreak.delete(key);
413
+ return;
414
+ }
415
+ const streak = (this.auxWaitFastAnswerStreak.get(key) ?? 0) + 1;
416
+ if (streak < AUX_WAIT_REPROMOTION_THRESHOLD) {
417
+ this.auxWaitFastAnswerStreak.set(key, streak);
418
+ return;
419
+ }
420
+ this.demotedAuxiliaryServerIds.delete(key);
421
+ this.demotedAuxiliaryBudgets.delete(key);
422
+ this.auxWaitFastAnswerStreak.delete(key);
423
+ recordDegradationOnce({
424
+ kind: "aux_wait_repromoted",
425
+ subject: key,
426
+ reason: "auxiliary produced five consecutive late answers below half its declared wait budget"
427
+ });
428
+ }
429
+ analysisRootModeForFile(filePath) {
430
+ const roots = getSessionRootsForTelemetry(128);
431
+ if (this.sessionCwd)
432
+ roots.push(this.sessionCwd);
433
+ let adopted = false;
434
+ for (const root of new Set(roots)) {
435
+ const mode = resolveAnalysisRoot(filePath, root);
436
+ if (mode === "session" || mode === "linked-worktree")
437
+ return mode;
438
+ adopted ||= mode === "adopted";
439
+ }
440
+ return adopted ? "adopted" : "none";
441
+ }
442
+ fileIsOutsideRegisteredRoots(filePath) {
443
+ const roots = getSessionRootsForTelemetry(128);
444
+ if (roots.length === 0)
445
+ return isOutsideAllSessionRoots(filePath);
446
+ return !roots.some((root) => isSameOrWithin(root, filePath));
447
+ }
448
+ admitAdoptedRoot(root, filePath) {
449
+ if (this.analysisRootModeForFile(filePath) !== "adopted")
450
+ return false;
451
+ const normalized = normalizeMapKey(root);
452
+ const liveRoots = new Set;
453
+ for (const key of this.state.clients.keys()) {
454
+ if (this.adoptedClientKeys.has(key))
455
+ liveRoots.add(key.slice(key.indexOf(":") + 1));
456
+ }
457
+ for (const root of this.adoptedRootLastUsedAt.keys()) {
458
+ if (!liveRoots.has(root))
459
+ this.adoptedRootLastUsedAt.delete(root);
460
+ }
461
+ if (liveRoots.has(normalized)) {
462
+ this.adoptedRootLastUsedAt.set(normalized, Date.now());
463
+ return true;
464
+ }
465
+ if (this.adoptedRootLastUsedAt.size < ADOPTED_ROOT_CAP) {
466
+ this.adoptedRootLastUsedAt.set(normalized, Date.now());
467
+ return true;
468
+ }
469
+ this.adoptedRootLastUsedAt.set(normalized, Date.now());
470
+ const victim = [...this.adoptedRootLastUsedAt.keys()].sort((a, b) => (this.adoptedRootLastUsedAt.get(a) ?? 0) - (this.adoptedRootLastUsedAt.get(b) ?? 0)).find((candidate) => candidate !== normalized);
471
+ if (!victim)
472
+ return true;
473
+ this.adoptedRootLastUsedAt.delete(victim);
474
+ for (const [key, client] of this.state.clients) {
475
+ if (!this.adoptedClientKeys.has(key) || !key.endsWith(`:${victim}`))
476
+ continue;
477
+ if ((this.clientLeases.get(key) ?? 0) > 0)
478
+ continue;
479
+ this.retireClient(key);
480
+ client.shutdown({ reason: "adopted-root-lru" }).catch(() => {});
481
+ }
482
+ incrementDegradationCount({
483
+ kind: "lsp-adopted-root-evicted",
484
+ subject: victim,
485
+ reason: `adopted root cap ${ADOPTED_ROOT_CAP} evicted ${victim}`
486
+ });
487
+ return true;
488
+ }
489
+ async resolveServerRoot(server, filePath, onRootFailure) {
490
+ const candidate = await resolveLspServerCwd(server, filePath, this.sessionCwd ?? process.cwd(), onRootFailure);
491
+ if (!candidate)
492
+ return;
493
+ const mode = this.analysisRootModeForFile(filePath);
494
+ if (mode === "adopted") {
495
+ const classifierRoot = resolveAnalysisRootPath(filePath, this.sessionCwd ?? process.cwd());
496
+ if (!classifierRoot || normalizeMapKey(classifierRoot) !== normalizeMapKey(candidate))
497
+ return;
498
+ }
499
+ if (this.fileIsOutsideRegisteredRoots(filePath) && this.analysisRootModeForFile(filePath) !== "adopted")
500
+ return;
501
+ const root = enforceLspRootCeiling(candidate, process.cwd(), filePath);
502
+ if (normalizeMapKey(root) === normalizeMapKey(process.cwd()))
503
+ return root;
504
+ const rootKey = normalizeMapKey(root);
505
+ const prefix = `${server.id}:`;
506
+ let nearestAncestor;
507
+ for (const key of new Set([
508
+ ...this.state.clients.keys(),
509
+ ...this.state.inFlight.keys()
510
+ ])) {
511
+ if (!key.startsWith(prefix))
512
+ continue;
513
+ const ancestorKey = key.slice(prefix.length);
514
+ const pathApi = isWindowsPath(ancestorKey) || isWindowsPath(rootKey) ? path.win32 : path;
515
+ const relative = pathApi.relative(ancestorKey, rootKey);
516
+ if (relative === "" || relative.startsWith("..") || pathApi.isAbsolute(relative)) {
517
+ continue;
518
+ }
519
+ if (!nearestAncestor || ancestorKey.length > nearestAncestor.length) {
520
+ nearestAncestor = ancestorKey;
521
+ }
522
+ }
523
+ if (!nearestAncestor)
524
+ return root;
525
+ let boundary = this.projectBoundaryCache.get(rootKey);
526
+ if (!boundary) {
527
+ boundary = hasProjectBoundaryMarker(root);
528
+ this.projectBoundaryCache.set(rootKey, boundary);
529
+ }
530
+ if (await boundary)
531
+ return root;
532
+ return this.state.clients.get(`${server.id}:${nearestAncestor}`)?.root ?? nearestAncestor;
533
+ }
534
+ checkDestroyed() {
535
+ return this.isDestroyed;
536
+ }
537
+ announceToSiblings(filePath, content, contentHash = this.hashContent(content)) {
538
+ const normalizedPath = normalizeMapKey(filePath);
539
+ if (this.siblingAnnouncementHashes.get(normalizedPath) === contentHash) {
540
+ return Promise.resolve();
541
+ }
542
+ return Promise.all(getServersForFileWithConfig(filePath).map((server) => this.resolveServerRoot(server, filePath).then((root) => {
543
+ if (!root)
544
+ return false;
545
+ const ownKey = `${server.id}:${normalizeMapKey(root)}`;
546
+ return this.notifyLiveClientsForServer(server.id, filePath, 2, ownKey, false);
547
+ }))).then(() => {
548
+ this.siblingAnnouncementHashes.set(normalizedPath, contentHash);
549
+ });
550
+ }
551
+ notifyLiveClientsForServer(serverId, filePath, type, ownKey, includeOwn) {
552
+ let notified = false;
553
+ const prefix = `${serverId}:`;
554
+ for (const [key, client] of this.state.clients) {
555
+ if (!key.startsWith(prefix) || !includeOwn && key === ownKey || !client.isAlive()) {
556
+ continue;
557
+ }
558
+ client.notify.watchedFileChange(filePath, type);
559
+ notified = true;
560
+ }
561
+ return notified;
562
+ }
563
+ async withClientSpawnGate(operation) {
564
+ const previous = this.clientSpawnGate;
565
+ let release;
566
+ this.clientSpawnGate = new Promise((resolve) => {
567
+ release = resolve;
568
+ });
569
+ await previous;
570
+ try {
571
+ return await operation();
572
+ } finally {
573
+ release();
574
+ }
575
+ }
576
+ async clientKeyFor(entry, filePath) {
577
+ const root = await this.resolveServerRoot(entry.info, filePath);
578
+ return root ? `${entry.info.id}:${normalizeMapKey(root)}` : undefined;
579
+ }
580
+ async acquireClientLease(entry, filePath) {
581
+ const key = await this.clientKeyFor(entry, filePath);
582
+ if (!key)
583
+ return;
584
+ return this.withClientSpawnGate(async () => {
585
+ if (this.isDestroyed || this.state.clients.get(key) !== entry.client) {
586
+ return;
587
+ }
588
+ this.clientLeases.set(key, (this.clientLeases.get(key) ?? 0) + 1);
589
+ return key;
590
+ });
591
+ }
592
+ async acquireClientLeases(entries, filePath) {
593
+ const keys = await Promise.all(entries.map((entry) => this.clientKeyFor(entry, filePath)));
594
+ if (keys.some((key) => key === undefined))
595
+ return;
596
+ const keyed = entries.map((entry, index) => [keys[index], entry]);
597
+ return this.withClientSpawnGate(async () => {
598
+ if (this.isDestroyed || keyed.some(([key, entry]) => this.state.clients.get(key) !== entry.client)) {
599
+ return;
600
+ }
601
+ for (const [key] of keyed) {
602
+ this.clientLeases.set(key, (this.clientLeases.get(key) ?? 0) + 1);
603
+ }
604
+ return keyed.map(([key, entry]) => ({ key, server: entry.info }));
605
+ });
606
+ }
607
+ releaseClientLease(key, server) {
608
+ const remaining = (this.clientLeases.get(key) ?? 1) - 1;
609
+ if (remaining > 0) {
610
+ this.clientLeases.set(key, remaining);
611
+ return;
612
+ }
613
+ this.clientLeases.delete(key);
614
+ if (this.state.clients.has(key)) {
615
+ this.clientLastUsedAt.set(key, Date.now());
616
+ this.scheduleIdleEviction(key, server);
617
+ }
618
+ }
619
+ async withClientForFileUse(filePath, maxWaitMs, hardCapMs, use) {
620
+ while (!this.isDestroyed) {
621
+ const entry = await this.getClientForFile(filePath, maxWaitMs, hardCapMs);
622
+ if (!entry)
623
+ return;
624
+ const leaseKey = await this.acquireClientLease(entry, filePath);
625
+ if (!leaseKey)
626
+ continue;
627
+ try {
628
+ return await use(entry);
629
+ } finally {
630
+ this.releaseClientLease(leaseKey, entry.info);
631
+ }
632
+ }
633
+ return;
634
+ }
635
+ async makeCapacityForClient(key) {
636
+ const occupiedKeys = new Set(this.state.inFlight.keys());
637
+ for (const [clientKey, client] of this.state.clients) {
638
+ if (client.isAlive())
639
+ occupiedKeys.add(clientKey);
640
+ }
641
+ const occupied = occupiedKeys.size;
642
+ if (occupied < getLspClientCeiling())
643
+ return true;
644
+ const idle = [...this.state.clients.entries()].filter(([candidateKey, client]) => candidateKey !== key && client.isAlive() && client.isBusy?.() !== true && (this.clientLeases.get(candidateKey) ?? 0) === 0).sort(([a], [b]) => (this.clientLastUsedAt.get(a) ?? this.state.clientSpawnedAt.get(a) ?? 0) - (this.clientLastUsedAt.get(b) ?? this.state.clientSpawnedAt.get(b) ?? 0));
645
+ const victim = idle[0];
646
+ if (!victim) {
647
+ logSessionStart(`lsp client ceiling ${getLspClientCeiling()}: spawn declined; all clients are in use`);
648
+ return false;
649
+ }
650
+ const [victimKey, victimClient] = victim;
651
+ this.releaseOutstandingAuxNotifyWrite(victimKey);
652
+ await victimClient.shutdown({ reason: "client_ceiling_lru" });
653
+ this.retireClient(victimKey);
654
+ logSessionStart(`lsp client ceiling ${getLspClientCeiling()}: evicted idle LRU ${victimKey}`);
655
+ return true;
656
+ }
657
+ clearIdleEvictionTimer(key) {
658
+ const timer = this.idleEvictionTimers.get(key);
659
+ if (timer)
660
+ clearTimeout(timer);
661
+ this.idleEvictionTimers.delete(key);
662
+ }
663
+ retireClient(key) {
664
+ this.releaseOutstandingAuxNotifyWrite(key);
665
+ this.state.clients.delete(key);
666
+ this.state.clientSpawnedAt.delete(key);
667
+ this.forgetReadiness(key);
668
+ this.clientLastUsedAt.delete(key);
669
+ if (this.adoptedClientKeys.delete(key)) {
670
+ const root = key.slice(key.indexOf(":") + 1);
671
+ if (![...this.adoptedClientKeys].some((candidate) => candidate.endsWith(`:${root}`)))
672
+ this.adoptedRootLastUsedAt.delete(root);
673
+ }
674
+ this.clearIdleEvictionTimer(key);
675
+ this.auxNotifyInflight.delete(key);
676
+ this.auxNotifyDrainLatencyEwma.delete(key);
677
+ }
678
+ releaseOutstandingAuxNotifyWrite(key, token) {
679
+ const current = this.outstandingAuxNotifyWrites.get(key);
680
+ if (!current || token !== undefined && current !== token)
681
+ return;
682
+ clearTimeout(current.wedgeTimer);
683
+ this.outstandingAuxNotifyWrites.delete(key);
684
+ current.resolveSettled();
685
+ }
686
+ scheduleIdleEviction(key, server) {
687
+ if (server.idleEviction !== "transparent" && !this.adoptedClientKeys.has(key))
688
+ return;
689
+ this.clearIdleEvictionTimer(key);
690
+ const lastUsedAt = this.clientLastUsedAt.get(key) ?? Date.now();
691
+ const root = key.slice(key.indexOf(":") + 1);
692
+ const idleMs = this.adoptedClientKeys.has(key) ? ADOPTED_ROOT_IDLE_EVICT_MS : getLspIdleEvictMsForRoot(root);
693
+ const timer = setTimeout(() => {
694
+ this.idleEvictionTimers.delete(key);
695
+ this.withClientSpawnGate(async () => {
696
+ if (this.isDestroyed)
697
+ return;
698
+ const client = this.state.clients.get(key);
699
+ if (!client?.isAlive())
700
+ return;
701
+ if (client.isBusy?.() === true || (this.clientLeases.get(key) ?? 0) > 0 || (this.clientLastUsedAt.get(key) ?? 0) !== lastUsedAt) {
702
+ this.scheduleIdleEviction(key, server);
703
+ return;
704
+ }
705
+ this.retireClient(key);
706
+ try {
707
+ await client.shutdown({ reason: "idle_eviction" });
708
+ } catch {}
709
+ logSessionStart(`lsp idle eviction: released ${key}`);
710
+ recordDegradation({
711
+ kind: "lsp-idle-eviction",
712
+ subject: key,
713
+ reason: "idle LSP client released to bound memory"
714
+ });
715
+ }).catch(() => {});
716
+ }, idleMs);
717
+ timer.unref?.();
718
+ this.idleEvictionTimers.set(key, timer);
719
+ }
720
+ shouldSkipTouch(filePath, contentFingerprint, clientScope, waitForDiagnostics, spawned) {
721
+ if (waitForDiagnostics)
722
+ return false;
723
+ if (spawned.length === 0)
724
+ return false;
725
+ return spawned.every((entry) => this.shouldSkipNotify(filePath, contentFingerprint, clientScope, entry.info.id, entry.client));
726
+ }
727
+ shouldSkipNotify(filePath, contentFingerprint, clientScope, serverId, client) {
728
+ if (TOUCH_DEBOUNCE_MS <= 0)
729
+ return false;
730
+ const previous = this.recentTouches.get(this.recentTouchKey(filePath, clientScope, serverId));
731
+ if (!previous)
732
+ return false;
733
+ const now = Date.now();
734
+ if (now - previous.touchedAt > TOUCH_DEBOUNCE_MS)
735
+ return false;
736
+ if (previous.client.deref() !== client)
737
+ return false;
738
+ return previous.fingerprint === contentFingerprint();
739
+ }
740
+ recentTouchKey(filePath, clientScope, serverId) {
741
+ return `${normalizeMapKey(filePath)}:${clientScope}:${serverId}`;
742
+ }
743
+ markTouched(filePath, contentFingerprint, clientScope, serverId, client) {
744
+ const key = this.recentTouchKey(filePath, clientScope, serverId);
745
+ const now = Date.now();
746
+ this.recentTouches.set(key, {
747
+ fingerprint: contentFingerprint,
748
+ touchedAt: now,
749
+ clientScope,
750
+ client: new WeakRef(client)
751
+ });
752
+ if (this.recentTouches.size > 800) {
753
+ for (const [k, v] of this.recentTouches) {
754
+ if (now - v.touchedAt > TOUCH_DEBOUNCE_MS) {
755
+ this.recentTouches.delete(k);
756
+ }
757
+ }
758
+ }
759
+ }
760
+ async demonstratedReadyKeyFor(server, filePath) {
761
+ const root = await this.resolveServerRoot(server, filePath);
762
+ if (!root)
763
+ return;
764
+ return `${server.id}:${normalizeMapKey(root)}`;
765
+ }
766
+ forgetReadiness(key) {
767
+ this.state.demonstratedReady.delete(key);
768
+ this.state.demonstratedCold.delete(key);
769
+ this.notifyWriteBackpressureStreak.delete(key);
770
+ }
771
+ markDemonstratedReadyKey(key) {
772
+ this.state.demonstratedReady.add(key);
773
+ this.state.demonstratedCold.delete(key);
774
+ }
775
+ recordBreaker(key, reason) {
776
+ recordDegradationOnce({ kind: "lsp-breaker", subject: key, reason });
777
+ }
778
+ recordNotifyWriteBackpressure(key, entry, filePath) {
779
+ if (!key)
780
+ return;
781
+ if (this.state.clients.get(key) !== entry.client)
782
+ return;
783
+ const streak = (this.notifyWriteBackpressureStreak.get(key) ?? 0) + 1;
784
+ if (streak < NOTIFY_BACKPRESSURE_BROKEN_AFTER) {
785
+ this.notifyWriteBackpressureStreak.set(key, streak);
786
+ return;
787
+ }
788
+ this.notifyWriteBackpressureStreak.delete(key);
789
+ this.demoteNotifyStallCpuGuarded(key, entry, filePath, {
790
+ consecutiveTimeouts: NOTIFY_BACKPRESSURE_BROKEN_AFTER
791
+ });
792
+ }
793
+ async demoteNotifyStallCpuGuarded(key, entry, filePath, reason) {
794
+ if (this.state.clients.get(key) !== entry.client)
795
+ return;
796
+ this.clearIdleEvictionTimer(key);
797
+ const verdict = await this.notifyStallCpuVerdict(entry);
798
+ if (verdict.cpuVerdict === "busy") {
799
+ if (this.state.clients.get(key) === entry.client) {
800
+ this.scheduleIdleEviction(key, entry.info);
801
+ }
802
+ this.logNotifyStallCpuBusy(key, entry, filePath, 0, verdict);
803
+ return;
804
+ }
805
+ if (this.state.clients.get(key) !== entry.client)
806
+ return;
807
+ this.demoteForNotifyStall(key, entry, filePath, {
808
+ ...reason,
809
+ discriminator: "consecutive-timeouts",
810
+ cpuVerdict: verdict.cpuVerdict
811
+ });
812
+ }
813
+ async notifyStallCpuVerdict(entry) {
814
+ const pid = entry.client.getProcessPid?.();
815
+ if (pid === undefined || !(Number.isFinite(pid) && pid > 0)) {
816
+ return { cpuVerdict: "unmeasured", cpuPercent: null };
817
+ }
818
+ const sample = await sampleProcessTreeCpuPercent(pid, notifyStallCpuSampleMs(), NOTIFY_STALL_CPU_BUSY_FLOOR_PERCENT);
819
+ if (!sample.measured) {
820
+ return { cpuVerdict: "unmeasured", cpuPercent: null };
821
+ }
822
+ if (sample.busy) {
823
+ return { cpuVerdict: "busy", cpuPercent: sample.cpuPercent };
824
+ }
825
+ return { cpuVerdict: "flat", cpuPercent: sample.cpuPercent };
826
+ }
827
+ logNotifyStallCpuBusy(key, entry, filePath, outstandingMs, verdict, extra = {}) {
828
+ emitBounded("lsp_notify_stall_cpu_busy", `${key}:${normalizeMapKey(filePath)}`, {
829
+ type: "phase",
830
+ durationMs: outstandingMs,
831
+ metadata: {
832
+ serverId: entry.info.id,
833
+ clientKey: key,
834
+ outstandingMs,
835
+ cpuPercent: verdict.cpuPercent ?? null,
836
+ ...extra
837
+ }
838
+ }, {
839
+ ledgerKind: "lsp-notify-stall-cpu-busy",
840
+ risingEdgePer: "identity"
841
+ });
842
+ }
843
+ auxNotifyWedgeBudgetMs(key) {
844
+ let budgetMs = notifyWedgedMs();
845
+ const ewma = this.auxNotifyDrainLatencyEwma.get(key);
846
+ if (ewma !== undefined && ewma > 0) {
847
+ const unacked = this.auxNotifyInflight.get(key)?.unacked ?? 0;
848
+ budgetMs = Math.max(budgetMs, NOTIFY_WEDGED_EWMA_MULTIPLIER * ewma * unacked);
849
+ }
850
+ return Math.min(budgetMs, notifyWedgedCapMs());
851
+ }
852
+ demoteForNotifyStall(key, entry, filePath, reason) {
853
+ if (this.state.clients.get(key) !== entry.client)
854
+ return;
855
+ this.state.broken.set(key, Date.now() + BROKEN_BASE_COOLDOWN_MS);
856
+ this.notifyStallDemotions.set(key, Date.now());
857
+ entry.client.shutdown().catch(() => {});
858
+ this.retireClient(key);
859
+ logLatency({
860
+ type: "phase",
861
+ phase: "lsp_notify_backpressure_broken",
862
+ filePath: normalizeMapKey(filePath),
863
+ durationMs: 0,
864
+ metadata: {
865
+ serverId: entry.info.id,
866
+ cooldownMs: BROKEN_BASE_COOLDOWN_MS,
867
+ ...reason
868
+ }
869
+ });
870
+ }
871
+ retractNotifyWriteBackpressure(key, serverId, filePath, outstandingMs, client) {
872
+ if (this.state.clients.get(key) !== client)
873
+ return;
874
+ const streak = this.notifyWriteBackpressureStreak.get(key);
875
+ if (!streak)
876
+ return;
877
+ const streakAfter = streak - 1;
878
+ if (streakAfter <= 0)
879
+ this.notifyWriteBackpressureStreak.delete(key);
880
+ else
881
+ this.notifyWriteBackpressureStreak.set(key, streakAfter);
882
+ logLatency({
883
+ type: "phase",
884
+ phase: "lsp_notify_write_late_landed",
885
+ filePath: normalizeMapKey(filePath),
886
+ durationMs: outstandingMs,
887
+ metadata: { serverId, outstandingMs, streakAfter }
888
+ });
889
+ }
890
+ noteAuxNotifyIssued(key, client) {
891
+ const record = this.auxNotifyInflight.get(key);
892
+ if (record && record.client === client) {
893
+ record.unacked += 1;
894
+ return;
895
+ }
896
+ this.auxNotifyInflight.set(key, { client, unacked: 1 });
897
+ }
898
+ noteAuxNotifyDrainLatency(key, durationMs, outstanding, record) {
899
+ if (!(outstanding > 0 && durationMs > 0))
900
+ return;
901
+ if (record && this.auxNotifyInflight.get(key) !== record)
902
+ return;
903
+ const perWriteMs = durationMs / outstanding;
904
+ const prev = this.auxNotifyDrainLatencyEwma.get(key);
905
+ this.auxNotifyDrainLatencyEwma.set(key, prev === undefined ? perWriteMs : 0.5 * perWriteMs + 0.5 * prev);
906
+ }
907
+ async decideNotifyStallTeardown(key, entry, filePath, token) {
908
+ if (this.state.clients.get(key) !== entry.client || this.outstandingAuxNotifyWrites.get(key) !== token) {
909
+ throw new Error("stale notify generation");
910
+ }
911
+ const outstandingMs = Date.now() - token.startedAt;
912
+ const armedBudgetMs = token.armedBudgetMs;
913
+ if (outstandingMs >= notifyWedgedCapMs()) {
914
+ return {
915
+ action: "demote",
916
+ reason: {
917
+ outstandingMs,
918
+ discriminator: "cap-exceeded",
919
+ budgetMs: armedBudgetMs
920
+ }
921
+ };
922
+ }
923
+ const verdict = await this.notifyStallCpuVerdict(entry);
924
+ if (this.state.clients.get(key) !== entry.client || this.outstandingAuxNotifyWrites.get(key) !== token) {
925
+ throw new Error("stale notify generation");
926
+ }
927
+ if (verdict.cpuVerdict === "busy") {
928
+ this.logNotifyStallCpuBusy(key, entry, filePath, outstandingMs, verdict, {
929
+ budgetMs: armedBudgetMs
930
+ });
931
+ return { action: "rearm", budgetMs: this.auxNotifyWedgeBudgetMs(key) };
932
+ }
933
+ return {
934
+ action: "demote",
935
+ reason: {
936
+ outstandingMs,
937
+ discriminator: verdict.cpuVerdict === "unmeasured" ? "budget-exceeded" : "budget-exceeded-cpu-flat",
938
+ budgetMs: armedBudgetMs,
939
+ ewmaInputMs: this.auxNotifyDrainLatencyEwma.get(key),
940
+ unackedDepth: this.auxNotifyInflight.get(key)?.unacked ?? 0,
941
+ cpuVerdict: verdict.cpuVerdict,
942
+ cpuPercent: verdict.cpuPercent ?? null
943
+ }
944
+ };
945
+ }
946
+ auxNotifyBacklogAtCeiling(key, entry) {
947
+ const record = this.auxNotifyInflight.get(key);
948
+ if (!record || record.client !== entry.client)
949
+ return false;
950
+ if (record.gateOpen)
951
+ return false;
952
+ return record.unacked >= serverTraits(entry.info).notifyInflightLimit;
953
+ }
954
+ async paceAuxNotify(key, entry, filePath, waitMs, context) {
955
+ const record = this.auxNotifyInflight.get(key);
956
+ if (!record)
957
+ return;
958
+ if (record.client !== entry.client) {
959
+ this.auxNotifyInflight.delete(key);
960
+ return;
961
+ }
962
+ if (record.gateOpen)
963
+ return;
964
+ const traits = serverTraits(entry.info);
965
+ const limit = traits.notifyInflightLimit;
966
+ if (record.unacked < limit)
967
+ return;
968
+ const ping = traits.replyOrdering === "threaded" ? undefined : entry.client.pingLiveness;
969
+ if (!ping) {
970
+ record.unacked = 0;
971
+ return;
972
+ }
973
+ if (waitMs <= 0) {
974
+ this.openAuxNotifyGate(key, entry, filePath, context, {
975
+ unacked: record.unacked,
976
+ limit,
977
+ waitMs,
978
+ durationMs: 0
979
+ });
980
+ return;
981
+ }
982
+ if (!record.drain) {
983
+ const startedAt = Date.now();
984
+ const outstanding = record.unacked;
985
+ const client = entry.client;
986
+ const barrier = (async () => {
987
+ let drained = false;
988
+ try {
989
+ drained = await ping.call(client, waitMs);
990
+ } catch {
991
+ drained = false;
992
+ }
993
+ const current = this.auxNotifyInflight.get(key);
994
+ if (current === record && current.client === client) {
995
+ current.drain = undefined;
996
+ if (drained)
997
+ current.unacked = Math.max(0, current.unacked - outstanding);
998
+ }
999
+ const durationMs = Date.now() - startedAt;
1000
+ if (drained)
1001
+ this.noteAuxNotifyDrainLatency(key, durationMs, outstanding, record);
1002
+ this.noteDrainBarrierOutcome(key, entry, filePath, context, {
1003
+ unacked: outstanding,
1004
+ limit,
1005
+ waitMs,
1006
+ durationMs,
1007
+ outcome: drained ? "drained" : "stalled"
1008
+ });
1009
+ return drained;
1010
+ })();
1011
+ barrier.catch(() => {});
1012
+ record.drain = barrier;
1013
+ record.stallLogged = false;
1014
+ }
1015
+ const waitStartedAt = Date.now();
1016
+ const drained = await withDeadline(record.drain, {
1017
+ ms: waitMs,
1018
+ onTimeout: "undefined",
1019
+ onReject: "undefined"
1020
+ });
1021
+ if (drained !== true) {
1022
+ this.openAuxNotifyGate(key, entry, filePath, context, {
1023
+ unacked: record.unacked,
1024
+ limit,
1025
+ waitMs,
1026
+ durationMs: Date.now() - waitStartedAt
1027
+ });
1028
+ }
1029
+ }
1030
+ openAuxNotifyGate(key, entry, filePath, context, detail) {
1031
+ const record = this.auxNotifyInflight.get(key);
1032
+ if (record && record.client === entry.client) {
1033
+ record.gateOpen = true;
1034
+ }
1035
+ this.noteDrainBarrierOutcome(key, entry, filePath, context, {
1036
+ ...detail,
1037
+ outcome: "stalled"
1038
+ });
1039
+ }
1040
+ noteDrainBarrierOutcome(key, entry, filePath, context, detail) {
1041
+ const record = this.auxNotifyInflight.get(key);
1042
+ if (detail.outcome === "stalled" && record) {
1043
+ if (record.stallLogged)
1044
+ return;
1045
+ record.stallLogged = true;
1046
+ }
1047
+ this.logDrainBarrier(key, entry, filePath, context, detail);
1048
+ }
1049
+ logDrainBarrier(key, entry, filePath, context, detail) {
1050
+ if (detail.outcome === "stalled") {
1051
+ incrementDegradationCount({
1052
+ kind: "lsp-notify-inflight-stall",
1053
+ subject: `${entry.info.id}:${normalizeMapKey(filePath)}`,
1054
+ reason: `notify barrier stalled with ${detail.unacked} unacknowledged writes`
1055
+ });
1056
+ }
1057
+ logLatency({
1058
+ type: "phase",
1059
+ phase: "lsp_notify_inflight_barrier",
1060
+ filePath: normalizeMapKey(filePath),
1061
+ durationMs: detail.durationMs,
1062
+ metadata: {
1063
+ serverId: entry.info.id,
1064
+ clientKey: key,
1065
+ source: context.source,
1066
+ clientScope: context.clientScope,
1067
+ ...detail
1068
+ }
1069
+ });
1070
+ }
1071
+ async claimAuxNotifySlot(clientKey, entry, filePath, budgetMs) {
1072
+ const deadline = Date.now() + budgetMs;
1073
+ for (;; ) {
1074
+ const outstanding = this.outstandingAuxNotifyWrites.get(clientKey);
1075
+ if (outstanding && outstanding.client !== entry.client) {
1076
+ this.releaseOutstandingAuxNotifyWrite(clientKey, outstanding);
1077
+ } else if (outstanding) {
1078
+ const outstandingMs = Date.now() - outstanding.startedAt;
1079
+ const remainingMs = deadline - Date.now();
1080
+ if (remainingMs <= 0)
1081
+ return { outstandingMs };
1082
+ await withDeadline(outstanding.settled, {
1083
+ ms: remainingMs,
1084
+ onTimeout: "undefined",
1085
+ onReject: "undefined"
1086
+ });
1087
+ continue;
1088
+ }
1089
+ const current = this.state.clients.get(clientKey);
1090
+ if (current !== entry.client) {
1091
+ return { outstandingMs: 0 };
1092
+ }
1093
+ let resolveSettled;
1094
+ const settled = new Promise((resolve) => {
1095
+ resolveSettled = resolve;
1096
+ });
1097
+ const token = {
1098
+ startedAt: Date.now(),
1099
+ client: entry.client,
1100
+ settled,
1101
+ armedBudgetMs: 0,
1102
+ wedgeTimer: undefined,
1103
+ resolveSettled: () => resolveSettled?.()
1104
+ };
1105
+ const fireWedge = async () => {
1106
+ if (this.outstandingAuxNotifyWrites.get(clientKey) !== token || this.state.clients.get(clientKey) !== entry.client) {
1107
+ this.releaseOutstandingAuxNotifyWrite(clientKey, token);
1108
+ return;
1109
+ }
1110
+ let decision;
1111
+ try {
1112
+ decision = await this.decideNotifyStallTeardown(clientKey, entry, filePath, token);
1113
+ } catch {
1114
+ decision = {
1115
+ action: "demote",
1116
+ reason: {
1117
+ outstandingMs: Date.now() - token.startedAt,
1118
+ discriminator: "budget-exceeded",
1119
+ budgetMs: token.armedBudgetMs
1120
+ }
1121
+ };
1122
+ }
1123
+ if (this.outstandingAuxNotifyWrites.get(clientKey) !== token || this.state.clients.get(clientKey) !== entry.client) {
1124
+ this.releaseOutstandingAuxNotifyWrite(clientKey, token);
1125
+ return;
1126
+ }
1127
+ if (decision.action === "demote") {
1128
+ this.demoteForNotifyStall(clientKey, entry, filePath, decision.reason);
1129
+ resolveSettled?.();
1130
+ return;
1131
+ }
1132
+ token.wedgeTimer = setTimeout(() => void fireWedge(), decision.budgetMs);
1133
+ token.wedgeTimer.unref?.();
1134
+ };
1135
+ token.armedBudgetMs = this.auxNotifyWedgeBudgetMs(clientKey);
1136
+ token.wedgeTimer = setTimeout(() => void fireWedge(), token.armedBudgetMs);
1137
+ token.wedgeTimer.unref?.();
1138
+ this.outstandingAuxNotifyWrites.set(clientKey, token);
1139
+ return {
1140
+ release: () => {
1141
+ this.releaseOutstandingAuxNotifyWrite(clientKey, token);
1142
+ }
1143
+ };
1144
+ }
1145
+ }
1146
+ async brokenSkippedAuxiliaryServerIds(filePath, clientScope, options, spawned) {
1147
+ if (clientScope === "primary")
1148
+ return [];
1149
+ const enabledAuxiliaries = clientScope === "with-auxiliary" ? new Set(options.auxiliaryServerIds ?? []) : undefined;
1150
+ const attached = new Set(spawned.map((entry) => entry.info.id));
1151
+ const skipped = [];
1152
+ for (const server of getServersForFileWithConfig(filePath)) {
1153
+ if (!isAuxiliary(server))
1154
+ continue;
1155
+ if (attached.has(server.id))
1156
+ continue;
1157
+ if (options.excludeServerIds?.has(server.id))
1158
+ continue;
1159
+ if (enabledAuxiliaries && !enabledAuxiliaries.has(server.id))
1160
+ continue;
1161
+ const key = await this.demonstratedReadyKeyFor(server, filePath);
1162
+ if (!key)
1163
+ continue;
1164
+ const brokenUntil = this.state.broken.get(key);
1165
+ if (this.permanentlyBroken.has(key) || typeof brokenUntil === "number" && brokenUntil > Date.now()) {
1166
+ skipped.push(server.id);
1167
+ }
1168
+ }
1169
+ return skipped;
1170
+ }
1171
+ activeClientsForCwd(cwd, priorityServerIds = []) {
1172
+ const normalizedCwd = normalizeMapKey(cwd);
1173
+ const priority = new Map(priorityServerIds.map((serverId, index) => [serverId, index]));
1174
+ const entries = [];
1175
+ for (const [key, client] of this.state.clients) {
1176
+ if (!client.isAlive())
1177
+ continue;
1178
+ const separator = key.indexOf(":");
1179
+ const serverId = separator >= 0 ? key.slice(0, separator) : key;
1180
+ const root = normalizeMapKey(client.root);
1181
+ const sameOrNested = root === normalizedCwd || root.startsWith(`${normalizedCwd}/`) || normalizedCwd.startsWith(`${root}/`);
1182
+ if (!sameOrNested)
1183
+ continue;
1184
+ entries.push({ serverId, client });
1185
+ }
1186
+ return entries.sort((a, b) => (priority.get(a.serverId) ?? Number.MAX_SAFE_INTEGER) - (priority.get(b.serverId) ?? Number.MAX_SAFE_INTEGER));
1187
+ }
1188
+ getOpenDocumentPaths(cwd) {
1189
+ const paths = new Set;
1190
+ for (const { client } of this.activeClientsForCwd(cwd)) {
1191
+ try {
1192
+ for (const filePath of client.openDocumentPaths?.() ?? [])
1193
+ paths.add(filePath);
1194
+ } catch {}
1195
+ }
1196
+ return [...paths];
1197
+ }
1198
+ getTrackedContent(filePath, cwd) {
1199
+ const priorityServerIds = getServersForFileWithConfig(filePath).map((server) => server.id);
1200
+ for (const { client } of this.activeClientsForCwd(cwd, priorityServerIds)) {
1201
+ const sent = client.getSentContent?.(filePath);
1202
+ if (sent !== undefined)
1203
+ return sent;
1204
+ }
1205
+ return;
1206
+ }
1207
+ async getClientForFile(filePath, maxWaitMs, hardCapMs, resolvedRoots, waitSkipReasons) {
1208
+ if (this.checkDestroyed())
1209
+ return;
1210
+ const servers = getServersForFileWithConfig(filePath).filter((s) => !isAuxiliary(s));
1211
+ const rootMemo = new Map;
1212
+ const partitionCandidates = async () => {
1213
+ const resolved = await Promise.all(servers.map(async (server) => {
1214
+ let rootFailureReason;
1215
+ try {
1216
+ return {
1217
+ server,
1218
+ root: await this.resolveServerRootOnce(server, filePath, rootMemo, (reason) => {
1219
+ rootFailureReason = reason;
1220
+ }),
1221
+ reason: rootFailureReason
1222
+ };
1223
+ } catch (error) {
1224
+ return {
1225
+ server,
1226
+ root: undefined,
1227
+ reason: error instanceof Error ? error.message : String(error)
1228
+ };
1229
+ }
1230
+ }));
1231
+ const unrootedReasons = {};
1232
+ for (const entry of resolved) {
1233
+ if (entry.root === undefined && entry.reason !== undefined) {
1234
+ unrootedReasons[entry.server.id] = entry.reason;
1235
+ }
1236
+ }
1237
+ return {
1238
+ rooted: resolved.filter((entry) => entry.root !== undefined).map(({ server, root }) => ({ server, root })),
1239
+ unrootedIds: resolved.filter((entry) => entry.root === undefined).map((entry) => entry.server.id),
1240
+ unrootedReasons
1241
+ };
1242
+ };
1243
+ const serverBaseMs = primaryServerWaitFloorMs(filePath, maxWaitMs);
1244
+ const effectiveMaxWaitMs = hardCapMs !== undefined ? serverBaseMs > 0 ? Math.min(serverBaseMs, hardCapMs) : hardCapMs : serverBaseMs;
1245
+ let knownSlowResolve;
1246
+ const knownSlowSentinel = Symbol("lsp-client-wait-known-slow");
1247
+ const knownSlow = new Promise((resolve) => {
1248
+ knownSlowResolve = () => resolve(knownSlowSentinel);
1249
+ });
1250
+ const noteSpawnInFlight = (serverId) => {
1251
+ const knownDurationMs = getSuccessfulLspSpawnDurationMs(serverId);
1252
+ if (knownDurationMs !== undefined && knownDurationMs > effectiveMaxWaitMs * 2) {
1253
+ queueMicrotask(() => knownSlowResolve?.());
1254
+ }
1255
+ };
1256
+ const withBudget = async () => {
1257
+ if (servers.length === 0)
1258
+ return;
1259
+ let erroredServerId;
1260
+ let erroredOutcome;
1261
+ for (const server of servers) {
1262
+ const acquisition = {
1263
+ outcome: "declined"
1264
+ };
1265
+ const spawned = await this.ensureClientForServer(filePath, server, resolvedRoots, noteSpawnInFlight, (reported) => {
1266
+ acquisition.outcome = reported;
1267
+ }, rootMemo);
1268
+ if (spawned) {
1269
+ logLatency({
1270
+ type: "phase",
1271
+ phase: "lsp_client_selected",
1272
+ filePath,
1273
+ durationMs: 0,
1274
+ metadata: {
1275
+ serverId: server.id,
1276
+ candidateCount: servers.length,
1277
+ outcome: acquisition.outcome
1278
+ }
1279
+ });
1280
+ return spawned;
1281
+ }
1282
+ if (acquisition.outcome === "spawn-failure" || acquisition.outcome === "spawn-failure-joined") {
1283
+ if (erroredServerId === undefined) {
1284
+ erroredServerId = server.id;
1285
+ erroredOutcome = acquisition.outcome;
1286
+ }
1287
+ }
1288
+ }
1289
+ if (erroredServerId !== undefined) {
1290
+ logLatency({
1291
+ type: "phase",
1292
+ phase: "lsp_client_selected",
1293
+ filePath,
1294
+ durationMs: 0,
1295
+ metadata: {
1296
+ serverId: erroredServerId,
1297
+ candidateCount: servers.length,
1298
+ outcome: erroredOutcome ?? "spawn-failure"
1299
+ }
1300
+ });
1301
+ }
1302
+ const unavailable = (await Promise.all(servers.map(async (server) => {
1303
+ const root = await this.resolveServerRootOnce(server, filePath, rootMemo);
1304
+ return {
1305
+ server,
1306
+ key: `${server.id}:${root ? normalizeMapKey(root) : "<unresolved>"}`
1307
+ };
1308
+ }))).filter(({ key }) => !this.unavailableLogged.has(key));
1309
+ for (const { key } of unavailable)
1310
+ this.unavailableLogged.add(key);
1311
+ if (unavailable.length === 0)
1312
+ return;
1313
+ logLatency({
1314
+ type: "phase",
1315
+ phase: "lsp_client_unavailable",
1316
+ filePath,
1317
+ durationMs: 0,
1318
+ metadata: {
1319
+ candidateCount: unavailable.length,
1320
+ servers: unavailable.map(({ server }) => server.id)
1321
+ }
1322
+ });
1323
+ return;
1324
+ };
1325
+ if (!effectiveMaxWaitMs || effectiveMaxWaitMs <= 0) {
1326
+ return withBudget();
1327
+ }
1328
+ const timeoutSentinel = Symbol("lsp-client-wait-timeout");
1329
+ let waitTimer;
1330
+ let waitResult;
1331
+ try {
1332
+ waitResult = await Promise.race([
1333
+ withBudget(),
1334
+ knownSlow,
1335
+ new Promise((resolve) => {
1336
+ waitTimer = setTimeout(() => resolve(timeoutSentinel), effectiveMaxWaitMs);
1337
+ })
1338
+ ]);
1339
+ } finally {
1340
+ if (waitTimer)
1341
+ clearTimeout(waitTimer);
1342
+ }
1343
+ if (waitResult === knownSlowSentinel) {
1344
+ const { rooted, unrootedIds, unrootedReasons } = await partitionCandidates();
1345
+ for (const { server, root } of rooted) {
1346
+ const client = this.state.clients.get(`${server.id}:${normalizeMapKey(root)}`);
1347
+ if (client?.isAlive())
1348
+ return { client, info: server };
1349
+ }
1350
+ waitSkipReasons?.add("budget_skipped_known_slow");
1351
+ logLatency({
1352
+ type: "phase",
1353
+ phase: "lsp_client_wait_skipped",
1354
+ filePath,
1355
+ durationMs: 0,
1356
+ metadata: {
1357
+ maxWaitMs: effectiveMaxWaitMs,
1358
+ serverIds: rooted.map(({ server }) => server.id),
1359
+ unrootedCandidates: {
1360
+ count: unrootedIds.length,
1361
+ ids: unrootedIds,
1362
+ ...Object.keys(unrootedReasons).length > 0 ? { reasons: unrootedReasons } : {}
1363
+ },
1364
+ reason: "budget_skipped_known_slow"
1365
+ }
1366
+ });
1367
+ return;
1368
+ }
1369
+ if (waitResult === timeoutSentinel) {
1370
+ const partitionBudgetMs = hardCapMs !== undefined ? Math.min(250, hardCapMs / 4) : 250;
1371
+ const partitioned = await withDeadline(partitionCandidates(), {
1372
+ ms: partitionBudgetMs,
1373
+ onTimeout: "undefined"
1374
+ });
1375
+ const { rooted, unrootedIds, unrootedReasons } = partitioned ?? {
1376
+ rooted: [],
1377
+ unrootedIds: [],
1378
+ unrootedReasons: {}
1379
+ };
1380
+ const knownHealth = [...this.state.clients.entries()].filter(([k]) => rooted.some(({ server }) => k.startsWith(`${server.id}:`))).map(([k, c]) => ({
1381
+ serverId: k.split(":")[0],
1382
+ alive: c.isAlive(),
1383
+ spawnedAt: this.state.clientSpawnedAt.get(k) ?? null
1384
+ }));
1385
+ logLatency({
1386
+ type: "phase",
1387
+ phase: "lsp_client_wait_timeout",
1388
+ filePath,
1389
+ durationMs: effectiveMaxWaitMs,
1390
+ metadata: {
1391
+ maxWaitMs: effectiveMaxWaitMs,
1392
+ serverIds: rooted.map(({ server }) => server.id),
1393
+ unrootedCandidates: {
1394
+ count: unrootedIds.length,
1395
+ ids: unrootedIds,
1396
+ ...Object.keys(unrootedReasons).length > 0 ? { reasons: unrootedReasons } : {}
1397
+ },
1398
+ knownClientHealth: knownHealth,
1399
+ ...partitioned === undefined ? { partition: "timed-out" } : {}
1400
+ }
1401
+ });
1402
+ return;
1403
+ }
1404
+ return waitResult;
1405
+ }
1406
+ async getClientsForFile(filePath, excludeServerIds, resolvedRoots, auxiliaryRoots) {
1407
+ const allServers = getServersForFileWithConfig(filePath);
1408
+ const servers = excludeServerIds && excludeServerIds.size > 0 ? allServers.filter((s) => !excludeServerIds.has(s.id)) : allServers;
1409
+ if (servers.length === 0)
1410
+ return { clients: [], serverCountAttempted: 0 };
1411
+ const rootedServers = (await Promise.all(servers.map(async (server) => ({
1412
+ server,
1413
+ root: await this.resolveServerRoot(server, filePath)
1414
+ })))).filter((entry) => entry.root !== undefined);
1415
+ for (const { server, root } of rootedServers)
1416
+ if (isAuxiliary(server))
1417
+ auxiliaryRoots?.set(server.id, normalizeMapKey(root));
1418
+ let serverCountAttempted = 0;
1419
+ const acquisitions = new Map;
1420
+ const acquire = (server) => {
1421
+ serverCountAttempted += 1;
1422
+ return this.ensureClientForServer(filePath, server, resolvedRoots);
1423
+ };
1424
+ for (const { server } of rootedServers) {
1425
+ const preferred = server.fallbackFor ? acquisitions.get(server.fallbackFor) : undefined;
1426
+ const acquisition = preferred ? preferred.then((entry) => entry === undefined ? acquire(server) : undefined) : acquire(server);
1427
+ acquisitions.set(server.id, acquisition);
1428
+ }
1429
+ const results = await Promise.all(acquisitions.values());
1430
+ const clients = results.filter((entry) => entry !== undefined);
1431
+ return { clients, serverCountAttempted };
1432
+ }
1433
+ async getAuxiliaryClientsForFile(filePath, enabledIds, onOutcome, maxWaitMs, signal, hook) {
1434
+ if (this.checkDestroyed() || enabledIds.size === 0)
1435
+ return [];
1436
+ const servers = getServersForFileWithConfig(filePath).filter((s) => isAuxiliary(s) && enabledIds.has(s.id));
1437
+ if (servers.length === 0)
1438
+ return [];
1439
+ const rootMemo = new Map;
1440
+ const effectiveSignal = signal ?? getAmbientAbortSignal();
1441
+ const effectiveHook = hook ?? "tool_result_edit";
1442
+ const spawned = await Promise.all(servers.map(async (server) => {
1443
+ const acquisition = this.ensureClientForServer(filePath, server, undefined, undefined, (reported) => onOutcome?.(server.id, reported), rootMemo);
1444
+ if (!maxWaitMs || maxWaitMs <= 0) {
1445
+ return acquisition;
1446
+ }
1447
+ return bounded(acquisition, {
1448
+ ms: maxWaitMs,
1449
+ signal: effectiveSignal,
1450
+ hook: effectiveHook,
1451
+ label: `auxiliary-spawn:${server.id}`
1452
+ });
1453
+ }));
1454
+ return spawned.filter((entry) => Boolean(entry));
1455
+ }
1456
+ async getWarmClientForFile(filePath) {
1457
+ if (this.checkDestroyed())
1458
+ return;
1459
+ const servers = getServersForFileWithConfig(filePath);
1460
+ const missed = [];
1461
+ for (const server of servers) {
1462
+ const root = await this.resolveServerRoot(server, filePath);
1463
+ if (!root)
1464
+ continue;
1465
+ const key = `${server.id}:${normalizeMapKey(root)}`;
1466
+ const existing = this.state.clients.get(key);
1467
+ if (existing?.isAlive()) {
1468
+ return { client: existing, info: server };
1469
+ }
1470
+ missed.push(key);
1471
+ }
1472
+ if (missed.length > 0) {
1473
+ emitBounded("lsp_warm_client_missing", missed.join(","), {
1474
+ filePath,
1475
+ durationMs: 0,
1476
+ metadata: {
1477
+ serverIds: missed.map((key) => key.slice(0, key.indexOf(":"))),
1478
+ roots: missed.map((key) => key.slice(key.indexOf(":") + 1))
1479
+ }
1480
+ }, {
1481
+ ledgerKind: "lsp-warm-client-missing",
1482
+ risingEdgePer: "identity",
1483
+ reason: `no warm client for ${missed.join(",")}`
1484
+ });
1485
+ }
1486
+ return;
1487
+ }
1488
+ async hasServerPublishedForFileRoot(serverId, filePath) {
1489
+ if (this.checkDestroyed())
1490
+ return false;
1491
+ for (const server of getServersForFileWithConfig(filePath)) {
1492
+ if (server.id !== serverId)
1493
+ continue;
1494
+ const root = await this.resolveServerRoot(server, filePath);
1495
+ if (!root)
1496
+ continue;
1497
+ const key = `${server.id}:${normalizeMapKey(root)}`;
1498
+ const client = this.state.clients.get(key);
1499
+ if (client?.isAlive() && client.getDiagnosticsVersionForPath(filePath) > 0) {
1500
+ return true;
1501
+ }
1502
+ }
1503
+ return false;
1504
+ }
1505
+ async readCachedDiagnosticsForServers(filePath, serverIds) {
1506
+ const out = new Map;
1507
+ if (this.checkDestroyed() || serverIds.size === 0)
1508
+ return out;
1509
+ for (const server of getServersForFileWithConfig(filePath)) {
1510
+ if (!serverIds.has(server.id) || out.has(server.id))
1511
+ continue;
1512
+ const root = await this.resolveServerRoot(server, filePath);
1513
+ if (!root)
1514
+ continue;
1515
+ const key = `${server.id}:${normalizeMapKey(root)}`;
1516
+ const client = this.state.clients.get(key);
1517
+ if (!client?.isAlive()) {
1518
+ const demotedAt = this.notifyStallDemotions.get(key);
1519
+ if (demotedAt !== undefined) {
1520
+ out.set(server.id, {
1521
+ diags: [],
1522
+ notifyStallDemoted: true,
1523
+ demotedAt
1524
+ });
1525
+ }
1526
+ continue;
1527
+ }
1528
+ this.notifyStallDemotions.delete(key);
1529
+ const entry = client.getAllDiagnostics().get(normalizeMapKey(filePath));
1530
+ out.set(server.id, {
1531
+ diags: entry?.diags ?? [],
1532
+ publishedAt: entry?.ts
1533
+ });
1534
+ }
1535
+ return out;
1536
+ }
1537
+ async observeLateAuxiliaryAnswer(filePath, serverId, elapsedMs) {
1538
+ for (const server of getServersForFileWithConfig(filePath)) {
1539
+ if (server.id !== serverId)
1540
+ continue;
1541
+ const root = await this.resolveServerRoot(server, filePath);
1542
+ if (root === undefined)
1543
+ return;
1544
+ this.noteAuxiliaryLateAnswer(`${serverId}:${normalizeMapKey(root)}`, elapsedMs);
1545
+ return;
1546
+ }
1547
+ }
1548
+ async notifyExternalFileChange(filePath, type) {
1549
+ if (this.checkDestroyed())
1550
+ return;
1551
+ for (const server of getServersForFileWithConfig(filePath)) {
1552
+ const root = await this.resolveServerRoot(server, filePath);
1553
+ if (!root)
1554
+ continue;
1555
+ this.notifyLiveClientsForServer(server.id, filePath, type, `${server.id}:${normalizeMapKey(root)}`, true);
1556
+ }
1557
+ }
1558
+ async resyncGitChangedFiles(changedPaths, options = {}) {
1559
+ const dispositions = new Map;
1560
+ if (this.checkDestroyed() || changedPaths.length === 0) {
1561
+ return dispositions;
1562
+ }
1563
+ const targets = new Set;
1564
+ const changed = new Map;
1565
+ for (const changedPath of changedPaths) {
1566
+ const resolved = path.resolve(changedPath);
1567
+ for (const server of getServersForFileWithConfig(resolved)) {
1568
+ const root = await this.resolveServerRoot(server, resolved);
1569
+ if (!root)
1570
+ continue;
1571
+ const index = loadReverseDependencyIndexFromSnapshot({ cwd: root });
1572
+ for (const importer of index ? getReverseDepsFromIndex(index, resolved) : []) {
1573
+ targets.add(importer);
1574
+ }
1575
+ }
1576
+ targets.add(resolved);
1577
+ changed.set(changedPath, resolved);
1578
+ }
1579
+ const openTargets = [...targets].filter((filePath) => this.hasLiveClientHoldingDocument(filePath));
1580
+ const written = new Set(changed.values());
1581
+ this.documentDrift.enqueueResync(openTargets.filter((filePath) => !written.has(filePath)));
1582
+ this.documentDrift.enqueueResync(openTargets.filter((filePath) => written.has(filePath)), { saved: options.saved === true });
1583
+ const pass = await this.sweepDocumentDrift({ force: true });
1584
+ logLatency({
1585
+ type: "phase",
1586
+ phase: "lsp_external_change_resync",
1587
+ filePath: changedPaths[0] ?? "",
1588
+ durationMs: 0,
1589
+ metadata: {
1590
+ changedCount: changedPaths.length,
1591
+ enqueued: openTargets.length,
1592
+ deferred: pass?.deferred ?? this.documentDrift.pendingResyncCount
1593
+ }
1594
+ });
1595
+ for (const [changedPath, resolved] of changed) {
1596
+ dispositions.set(changedPath, openTargets.includes(resolved) ? pass?.queued.get(normalizeMapKey(resolved)) ?? "deferred" : "unheld");
1597
+ }
1598
+ return dispositions;
1599
+ }
1600
+ async sweepDocumentDrift(options = {}) {
1601
+ if (this.checkDestroyed())
1602
+ return;
1603
+ return this.documentDrift.sweep({
1604
+ resync: async (filePath, content, _driftAgeMs, readStamp, saved) => {
1605
+ await this.touchFile(filePath, content, {
1606
+ diagnostics: "none",
1607
+ source: "drift_resync",
1608
+ clientScope: "all",
1609
+ excludeServerIds: await this.serverIdsNotHoldingDocument(filePath),
1610
+ readStamp,
1611
+ saved: saved === true
1612
+ });
1613
+ return this.documentDrift.peek(filePath)?.fingerprint === fingerprintDocumentContent(content);
1614
+ },
1615
+ holdsDocument: (filePath) => this.hasLiveClientHoldingDocument(filePath),
1616
+ onDrift: (event) => {
1617
+ if (event.disposition === "unchanged" || event.disposition === "vanished" || event.disposition === "unheld") {
1618
+ return;
1619
+ }
1620
+ incrementDegradationCount({
1621
+ kind: "lsp-document-drift",
1622
+ subject: event.filePath,
1623
+ reason: event.disposition === "deferred" ? `disk drift deferred by resync pacing after ${event.driftAgeMs}ms` : event.disposition === "failed" ? `resync FAILED after ${event.driftAgeMs}ms of untracked disk drift; view still stale (${event.syncedSize}->${event.diskSize} bytes)` : `resynced after ${event.driftAgeMs}ms of untracked disk drift (${event.syncedSize}->${event.diskSize} bytes)`
1624
+ });
1625
+ logLatency({
1626
+ type: "phase",
1627
+ phase: "lsp_document_drift",
1628
+ filePath: event.filePath,
1629
+ durationMs: event.driftAgeMs,
1630
+ metadata: {
1631
+ disposition: event.disposition,
1632
+ driftAgeMs: event.driftAgeMs,
1633
+ syncedSize: event.syncedSize,
1634
+ diskSize: event.diskSize
1635
+ }
1636
+ });
1637
+ }
1638
+ }, options);
1639
+ }
1640
+ _driftTrackedCountForTests() {
1641
+ return this.documentDrift.size;
1642
+ }
1643
+ hasLiveClientHoldingDocument(filePath) {
1644
+ for (const client of this.state.clients.values()) {
1645
+ if (!client.isAlive())
1646
+ continue;
1647
+ if (documentIsOpenOn(client, filePath))
1648
+ return true;
1649
+ }
1650
+ return false;
1651
+ }
1652
+ async serverIdsNotHoldingDocument(filePath) {
1653
+ const exclude = new Set;
1654
+ for (const server of getServersForFileWithConfig(filePath)) {
1655
+ const root = await this.resolveServerRoot(server, filePath);
1656
+ const existing = root ? this.state.clients.get(`${server.id}:${normalizeMapKey(root)}`) : undefined;
1657
+ if (existing?.isAlive() && documentIsOpenOn(existing, filePath))
1658
+ continue;
1659
+ exclude.add(server.id);
1660
+ }
1661
+ return exclude;
1662
+ }
1663
+ recordFullyCoveredSync(filePath, content, targeted, allWritesLanded, at, contentFingerprint) {
1664
+ if (!allWritesLanded || targeted.length === 0)
1665
+ return;
1666
+ const targetedClients = new Set(targeted.map((entry) => entry.client));
1667
+ for (const client of this.state.clients.values()) {
1668
+ if (targetedClients.has(client))
1669
+ continue;
1670
+ if (!client.isAlive())
1671
+ continue;
1672
+ if (documentIsOpenOn(client, filePath))
1673
+ return;
1674
+ }
1675
+ this.documentDrift.recordSynced(filePath, content, at, contentFingerprint?.() ?? fingerprintDocumentContent(content));
1676
+ }
1677
+ noteSpawnVerdict(key, verdict) {
1678
+ this.lastSpawnVerdict.set(key, verdict);
1679
+ }
1680
+ resolveServerRootOnce(server, filePath, memo, onRootFailure) {
1681
+ if (!memo)
1682
+ return this.resolveServerRoot(server, filePath, onRootFailure);
1683
+ const pending = memo.get(server.id);
1684
+ if (pending !== undefined)
1685
+ return pending;
1686
+ const started = this.resolveServerRoot(server, filePath, onRootFailure);
1687
+ memo.set(server.id, started);
1688
+ return started;
1689
+ }
1690
+ async ensureClientForServer(filePath, server, resolvedRoots, onSpawnInFlight, onOutcome, rootMemo) {
1691
+ const handoff = this.generationHandoff;
1692
+ if (handoff) {
1693
+ await handoff;
1694
+ if (this.generationHandoff === handoff) {
1695
+ this.generationHandoff = undefined;
1696
+ }
1697
+ if (this.checkDestroyed())
1698
+ return;
1699
+ }
1700
+ const root = await this.resolveServerRootOnce(server, filePath, rootMemo);
1701
+ if (!root || this.checkDestroyed())
1702
+ return;
1703
+ if (!isAuxiliary(server)) {
1704
+ resolvedRoots?.set(server.id, normalizeMapKey(root));
1705
+ }
1706
+ const allowInstall = this.shouldAllowInstall(server.id);
1707
+ const normalizedRoot = normalizeMapKey(root);
1708
+ const key = `${server.id}:${normalizedRoot}`;
1709
+ const adopted = this.analysisRootModeForFile(filePath) === "adopted";
1710
+ if (adopted) {
1711
+ const localCommand = server.command ?? server.projectLocalCommand ?? server.availabilityKey;
1712
+ const localBinary = localCommand ? path.isAbsolute(localCommand) ? isProjectLocalBinPath(localCommand, root) ? localCommand : undefined : findLocalBinUpwards(localCommand, root) : undefined;
1713
+ if (localBinary) {
1714
+ recordDegradationOnce({
1715
+ kind: "lsp-registry-decision",
1716
+ subject: `${server.id}:${normalizeMapKey(root)}`,
1717
+ reason: "project-local LSP binary refused for adopted root"
1718
+ });
1719
+ return;
1720
+ }
1721
+ this.adoptedClientKeys.add(key);
1722
+ if (!this.admitAdoptedRoot(root, filePath))
1723
+ return;
1724
+ }
1725
+ const isOptionalServer = OPTIONAL_LSP_SERVER_IDS.has(server.id);
1726
+ if (server.availabilityKey && isDirectLspCommandTemporarilyUnavailable(server.availabilityKey)) {
1727
+ emitBounded("lsp_client_skipped_unavailable_command", `${server.availabilityKey}:${normalizeMapKey(filePath)}`, {
1728
+ filePath,
1729
+ durationMs: 0,
1730
+ metadata: {
1731
+ serverId: server.id,
1732
+ command: server.availabilityKey
1733
+ }
1734
+ }, {
1735
+ ledgerKind: "lsp-client-skipped-unavailable-command",
1736
+ risingEdgePer: "identity",
1737
+ reason: `command ${server.availabilityKey} temporarily unavailable`
1738
+ });
1739
+ return;
1740
+ }
1741
+ if (isOptionalServer && this.optionalDisabled.has(key)) {
1742
+ return;
1743
+ }
1744
+ if (this.permanentlyBroken.has(key)) {
1745
+ emitBounded("lsp_client_skipped_broken", `${server.id}:${normalizeMapKey(filePath)}`, {
1746
+ filePath,
1747
+ durationMs: 0,
1748
+ metadata: {
1749
+ serverId: server.id,
1750
+ permanent: true
1751
+ }
1752
+ }, {
1753
+ ledgerKind: "lsp-client-skipped-broken",
1754
+ risingEdgePer: "identity",
1755
+ reason: `${server.id} latched permanently broken`
1756
+ });
1757
+ return;
1758
+ }
1759
+ const existing = this.state.clients.get(key);
1760
+ if (existing) {
1761
+ if (existing.isAlive()) {
1762
+ this.unavailableLogged.delete(key);
1763
+ this.clientLastUsedAt.set(key, Date.now());
1764
+ this.scheduleIdleEviction(key, server);
1765
+ if (!this.warmStartLogged.has(key)) {
1766
+ logSessionStart(`lsp warm-start ${server.id}: reused root=${root} file=${filePath}`);
1767
+ this.warmStartLogged.add(key);
1768
+ }
1769
+ onOutcome?.("warm-reuse");
1770
+ return { client: existing, info: server };
1771
+ }
1772
+ const spawnedAt = this.state.clientSpawnedAt.get(key);
1773
+ const wasIntentional = existing.wasShutdownIntentional();
1774
+ const exitedAt = existing.getExitedAt();
1775
+ const uptimeMs = exitedAt != null && spawnedAt != null ? exitedAt - spawnedAt : spawnedAt != null ? Date.now() - spawnedAt : null;
1776
+ logLatency({
1777
+ type: "phase",
1778
+ phase: "lsp_server_respawn",
1779
+ filePath,
1780
+ durationMs: 0,
1781
+ metadata: {
1782
+ serverId: server.id,
1783
+ root,
1784
+ uptimeMs,
1785
+ intentional: wasIntentional
1786
+ }
1787
+ });
1788
+ this.releaseOutstandingAuxNotifyWrite(key);
1789
+ try {
1790
+ await existing.shutdown();
1791
+ } catch {}
1792
+ this.retireClient(key);
1793
+ this.state.broken.delete(key);
1794
+ if (wasIntentional) {} else {
1795
+ if (this.recordRuntimeExitWindow(key, exitedAt, uptimeMs)) {
1796
+ const rate = `${RUNTIME_EXIT_WINDOW_TRIP_COUNT} deaths within ${Math.round(RUNTIME_EXIT_WINDOW_MS / 60000)}m`;
1797
+ this.state.broken.set(key, Math.max(this.state.broken.get(key) ?? 0, Date.now() + (isOptionalServer ? OPTIONAL_LSP_RETRY_COOLDOWN_MS : BROKEN_MAX_COOLDOWN_MS)));
1798
+ if (isOptionalServer) {
1799
+ this.optionalDisabled.add(key);
1800
+ this.runtimeExitWindow.delete(key);
1801
+ logSessionStart(`lsp respawn ${server.id}: optional server cooled down (windowed-rate trip: ${rate})`);
1802
+ } else {
1803
+ this.permanentlyBroken.add(key);
1804
+ this.recordBreaker(key, `windowed runtime-exit trip: ${rate}`);
1805
+ logSessionStart(`lsp respawn ${server.id}: permanently disabled (windowed-rate trip: ${rate}, uptimeMs=${uptimeMs})`);
1806
+ }
1807
+ }
1808
+ if (uptimeMs != null && uptimeMs < RUNTIME_EXIT_UPTIME_THRESHOLD_MS) {
1809
+ const rCount = (this.runtimeExitCounts.get(key) ?? 0) + 1;
1810
+ this.runtimeExitCounts.set(key, rCount);
1811
+ const rCooldown = Math.min(BROKEN_BASE_COOLDOWN_MS * 2 ** (rCount - 1), BROKEN_MAX_COOLDOWN_MS);
1812
+ this.state.broken.set(key, Date.now() + rCooldown);
1813
+ if (!isOptionalServer && rCount >= BROKEN_PERMANENT_AFTER) {
1814
+ this.permanentlyBroken.add(key);
1815
+ this.recordBreaker(key, `permanently disabled after ${rCount} early post-init exits`);
1816
+ logSessionStart(`lsp respawn ${server.id}: permanently disabled after ${rCount} early post-init exits (uptimeMs=${uptimeMs})`);
1817
+ } else {
1818
+ logSessionStart(`lsp respawn ${server.id}: early post-init exit ${rCount}/${BROKEN_PERMANENT_AFTER} (uptimeMs=${uptimeMs}), cooldown=${rCooldown}ms`);
1819
+ }
1820
+ } else {
1821
+ this.runtimeExitCounts.delete(key);
1822
+ const windowDeaths = this.runtimeExitWindow.get(key);
1823
+ if (windowDeaths) {
1824
+ const windowNow = exitedAt ?? Date.now();
1825
+ if (!windowDeaths.some((t) => t >= windowNow - RUNTIME_EXIT_WINDOW_MS)) {
1826
+ this.runtimeExitWindow.delete(key);
1827
+ }
1828
+ }
1829
+ }
1830
+ }
1831
+ }
1832
+ const brokenUntil = this.state.broken.get(key);
1833
+ if (typeof brokenUntil === "number" && brokenUntil > Date.now()) {
1834
+ emitBounded("lsp_client_skipped_broken", `${server.id}:${normalizeMapKey(filePath)}`, {
1835
+ filePath,
1836
+ durationMs: 0,
1837
+ metadata: {
1838
+ serverId: server.id,
1839
+ retryInMs: Math.max(0, brokenUntil - Date.now())
1840
+ }
1841
+ }, {
1842
+ ledgerKind: "lsp-client-skipped-broken",
1843
+ risingEdgePer: "identity",
1844
+ reason: `${server.id} in breaker cooldown`
1845
+ });
1846
+ return;
1847
+ }
1848
+ if (typeof brokenUntil === "number" && brokenUntil <= Date.now()) {
1849
+ this.state.broken.delete(key);
1850
+ if (isOptionalServer)
1851
+ this.optionalDisabled.delete(key);
1852
+ }
1853
+ let startedSpawn = false;
1854
+ let spawnPromise = this.state.inFlight.get(key);
1855
+ if (!spawnPromise) {
1856
+ const started = await this.withClientSpawnGate(async () => {
1857
+ const raced = this.state.inFlight.get(key);
1858
+ if (raced)
1859
+ return { promise: raced, startedSpawn: false };
1860
+ if (this.checkDestroyed())
1861
+ return;
1862
+ if (!await this.makeCapacityForClient(key))
1863
+ return;
1864
+ const promise = this.spawnClient(server, root, key, filePath, allowInstall);
1865
+ this.state.inFlight.set(key, promise);
1866
+ return { promise, startedSpawn: true };
1867
+ });
1868
+ if (!started)
1869
+ return;
1870
+ spawnPromise = started.promise;
1871
+ startedSpawn = started.startedSpawn;
1872
+ }
1873
+ onSpawnInFlight?.(server.id);
1874
+ try {
1875
+ const spawned = await spawnPromise;
1876
+ onOutcome?.(spawned ? startedSpawn ? "cold-spawn" : "cold-spawn-joined" : this.lastSpawnVerdict.get(key) === "failed" ? startedSpawn ? "spawn-failure" : "spawn-failure-joined" : "declined");
1877
+ return spawned;
1878
+ } catch (err) {
1879
+ onOutcome?.("spawn-failure");
1880
+ throw err;
1881
+ } finally {
1882
+ if (this.state.inFlight.get(key) === spawnPromise) {
1883
+ this.state.inFlight.delete(key);
1884
+ }
1885
+ }
1886
+ }
1887
+ shouldAllowInstall(serverId) {
1888
+ if (!assertInstallAllowed(`lsp install: ${serverId}`))
1889
+ return false;
1890
+ return process.env.DM_LENS_DISABLE_LSP_INSTALL !== "1";
1891
+ }
1892
+ async spawnClient(server, root, key, filePath, allowInstall) {
1893
+ try {
1894
+ if (server.executesProjectCode === true) {
1895
+ refuseUntrustedLspExecution({
1896
+ kind: "project-code-server",
1897
+ serverId: server.id,
1898
+ root
1899
+ });
1900
+ }
1901
+ } catch (error) {
1902
+ logSessionStart(`lsp spawn ${server.id}: refused — ${error instanceof Error ? error.message : String(error)}`);
1903
+ this.noteSpawnVerdict(key, "failed");
1904
+ return;
1905
+ }
1906
+ if (server.trustAllowed !== true && !isLspSpawnAllowedByTrust()) {
1907
+ logSessionStart(`lsp spawn ${server.id}: refused — ${projectTrustDenialReason()}`);
1908
+ this.noteSpawnVerdict(key, "declined");
1909
+ return;
1910
+ }
1911
+ const isOptionalServer = OPTIONAL_LSP_SERVER_IDS.has(server.id);
1912
+ const startedAt = Date.now();
1913
+ logSessionStart(`lsp spawn ${server.id}: start root=${root} install=${allowInstall ? "enabled" : "disabled"} file=${filePath}`);
1914
+ recordLsp(server.id, root, "spawn_start");
1915
+ try {
1916
+ const projectCodeAllowed = this.analysisRootModeForFile(filePath) !== "adopted";
1917
+ const spawned = await withLspProjectCodePermission(projectCodeAllowed, () => server.spawn(root, {
1918
+ allowInstall,
1919
+ projectCodeAllowed,
1920
+ sessionRoot: this.sessionCwd ?? process.cwd()
1921
+ }));
1922
+ if (this.isDestroyed) {
1923
+ try {
1924
+ spawned?.process?.process?.kill();
1925
+ } catch {}
1926
+ logSessionStart(`lsp spawn ${server.id}: aborted (service shut down mid-spawn)`);
1927
+ this.noteSpawnVerdict(key, "declined");
1928
+ return;
1929
+ }
1930
+ if (!spawned) {
1931
+ logSessionStart(`lsp spawn ${server.id}: unavailable (${Date.now() - startedAt}ms)`);
1932
+ recordLsp(server.id, root, "spawn_failed", Date.now() - startedAt);
1933
+ if (!allowInstall) {
1934
+ logSessionStart(`lsp spawn ${server.id}: unavailable with install disabled; temporary cooldown only`);
1935
+ this.state.broken.set(key, Date.now() + BROKEN_BASE_COOLDOWN_MS);
1936
+ this.noteSpawnVerdict(key, "declined");
1937
+ return;
1938
+ }
1939
+ const uCount = (this.failureCounts.get(key) ?? 0) + 1;
1940
+ this.failureCounts.set(key, uCount);
1941
+ const uCooldown = Math.min(BROKEN_BASE_COOLDOWN_MS * 2 ** (uCount - 1), BROKEN_MAX_COOLDOWN_MS);
1942
+ this.state.broken.set(key, Date.now() + uCooldown);
1943
+ if (uCount >= BROKEN_PERMANENT_AFTER) {
1944
+ this.permanentlyBroken.add(key);
1945
+ this.recordBreaker(key, `permanently disabled after ${uCount} unavailable spawns`);
1946
+ logSessionStart(`lsp spawn ${server.id}: permanently disabled after ${uCount} failures`);
1947
+ }
1948
+ this.noteSpawnVerdict(key, "failed");
1949
+ return;
1950
+ }
1951
+ const override = getServerInitOverride(server.id, filePath);
1952
+ const mergedInit = spawned.launchVariant === "classic" ? mergeInitializationOptions(override?.initializationOptions, spawned.initialization) : mergeInitializationOptions(spawned.initialization, override?.initializationOptions);
1953
+ this.state.diagnosticsPublished.delete(server.id);
1954
+ this.state.diagnosticsUnsupported.delete(server.id);
1955
+ this.firstContactWaits.delete(server.id);
1956
+ const client = await createLSPClient({
1957
+ serverId: server.id,
1958
+ process: spawned.process,
1959
+ root,
1960
+ sessionCwd: this.sessionCwd,
1961
+ initialization: mergedInit,
1962
+ initializeTimeoutMs: server.initializeTimeoutMs,
1963
+ launchVariant: spawned.launchVariant,
1964
+ onDiagnosticsPublished: (serverId) => {
1965
+ this.state.diagnosticsPublished.add(serverId);
1966
+ }
1967
+ });
1968
+ if (this.isDestroyed) {
1969
+ client.shutdown({ fast: true }).catch(() => {});
1970
+ logSessionStart(`lsp spawn ${server.id}: aborted (service shut down mid-initialize)`);
1971
+ this.noteSpawnVerdict(key, "declined");
1972
+ return;
1973
+ }
1974
+ const wsDiag = typeof client.getWorkspaceDiagnosticsSupport === "function" ? client.getWorkspaceDiagnosticsSupport() : {
1975
+ advertised: false,
1976
+ mode: "push-only",
1977
+ diagnosticProviderKind: "unavailable"
1978
+ };
1979
+ this.state.clients.set(key, client);
1980
+ this.forgetReadiness(key);
1981
+ this.notifyStallDemotions.delete(key);
1982
+ this.unavailableLogged.delete(key);
1983
+ this.lastSpawnVerdict.delete(key);
1984
+ this.state.clientSpawnedAt.set(key, Date.now());
1985
+ this.clientLastUsedAt.set(key, Date.now());
1986
+ this.scheduleIdleEviction(key, server);
1987
+ this.failureCounts.delete(key);
1988
+ if (isOptionalServer) {
1989
+ this.optionalDisabled.delete(key);
1990
+ this.optionalFailureLogged.delete(key);
1991
+ }
1992
+ logSessionStart(`lsp spawn ${server.id}: success source=${spawned.source ?? "unknown"} (${Date.now() - startedAt}ms)`);
1993
+ const spawnDurationMs = Date.now() - startedAt;
1994
+ recordLsp(server.id, root, "spawn_success", spawnDurationMs);
1995
+ recordSuccessfulLspSpawn(server.id, spawnDurationMs);
1996
+ logLatency({
1997
+ type: "phase",
1998
+ phase: "lsp_server_spawned",
1999
+ filePath: root,
2000
+ durationMs: spawnDurationMs,
2001
+ metadata: {
2002
+ serverId: server.id,
2003
+ source: spawned.source ?? "unknown",
2004
+ launchVariant: spawned.launchVariant ?? "default",
2005
+ triggerFilePath: filePath
2006
+ }
2007
+ });
2008
+ if (!this.workspaceProbeLogged.has(key)) {
2009
+ logSessionStart(`lsp workspace-diag probe ${server.id}: advertised=${wsDiag.advertised} mode=${wsDiag.mode} provider=${wsDiag.diagnosticProviderKind}`);
2010
+ this.workspaceProbeLogged.add(key);
2011
+ }
2012
+ return { client, info: server };
2013
+ } catch (err) {
2014
+ recordLsp(server.id, root, "spawn_failed", Date.now() - startedAt);
2015
+ if (!isOptionalServer || !this.optionalFailureLogged.has(key)) {
2016
+ logSessionStart(`lsp spawn ${server.id}: failed (${Date.now() - startedAt}ms) error=${err instanceof Error ? err.message : String(err)}`);
2017
+ if (isOptionalServer) {
2018
+ this.optionalFailureLogged.add(key);
2019
+ }
2020
+ }
2021
+ const eCount = (this.failureCounts.get(key) ?? 0) + 1;
2022
+ this.failureCounts.set(key, eCount);
2023
+ const eCooldown = isOptionalServer ? OPTIONAL_LSP_RETRY_COOLDOWN_MS : Math.min(BROKEN_BASE_COOLDOWN_MS * 2 ** (eCount - 1), BROKEN_MAX_COOLDOWN_MS);
2024
+ this.state.broken.set(key, Date.now() + eCooldown);
2025
+ if (!isOptionalServer && eCount >= BROKEN_PERMANENT_AFTER) {
2026
+ this.permanentlyBroken.add(key);
2027
+ this.recordBreaker(key, `permanently disabled after ${eCount} spawn/initialize failures`);
2028
+ logSessionStart(`lsp spawn ${server.id}: permanently disabled after ${eCount} failures`);
2029
+ }
2030
+ if (isOptionalServer) {
2031
+ this.optionalDisabled.add(key);
2032
+ }
2033
+ this.noteSpawnVerdict(key, "failed");
2034
+ return;
2035
+ }
2036
+ }
2037
+ async openFile(filePath, content, options) {
2038
+ if (this.checkDestroyed())
2039
+ return;
2040
+ const startedAt = Date.now();
2041
+ await this.withClientForFileUse(filePath, undefined, options?.spawnBudgetMs, async (spawned) => {
2042
+ const languageId = getLanguageId(filePath) ?? "plaintext";
2043
+ const sent = await spawned.client.notify.open(filePath, content, languageId, options?.preserveDiagnostics);
2044
+ this.recordFullyCoveredSync(filePath, content, [spawned], sent !== false, startedAt);
2045
+ });
2046
+ }
2047
+ async updateFile(filePath, content) {
2048
+ if (this.checkDestroyed())
2049
+ return;
2050
+ await this.withClientForFileUse(filePath, undefined, undefined, (spawned) => spawned.client.notify.change(filePath, content));
2051
+ }
2052
+ async touchFile(filePath, content, options = {}) {
2053
+ if (this.checkDestroyed()) {
2054
+ const destroyedDiagnosticsMode = options.collectDiagnostics ? options.diagnostics ?? "document" : options.diagnostics ?? "none";
2055
+ logLatency({
2056
+ type: "phase",
2057
+ phase: "lsp_touch_file",
2058
+ filePath: normalizeMapKey(filePath),
2059
+ durationMs: 0,
2060
+ metadata: {
2061
+ serverCountAttempted: 0,
2062
+ serverCountReady: 0,
2063
+ clientScope: options.clientScope ?? (destroyedDiagnosticsMode === "full" ? "all" : "primary"),
2064
+ diagnosticsMode: destroyedDiagnosticsMode,
2065
+ source: options.source ?? "unknown",
2066
+ failureKind: "destroyed"
2067
+ }
2068
+ });
2069
+ return;
2070
+ }
2071
+ const contentHash = this.hashContent(content);
2072
+ if (typeof content === "string") {
2073
+ this.announceToSiblings(filePath, content, contentHash).catch((cause) => {
2074
+ incrementDegradationCount({
2075
+ kind: "lsp-sibling-announcement",
2076
+ subject: normalizeMapKey(filePath),
2077
+ reason: `sibling notification failed: ${String(cause)}`
2078
+ });
2079
+ });
2080
+ }
2081
+ const startedAt = Date.now();
2082
+ let fingerprintMemo;
2083
+ const contentFingerprint = () => fingerprintMemo ??= fingerprintDocumentContent(content);
2084
+ const hookDeadlineAt = options.hook !== undefined && Object.hasOwn(HOOK_WALL_BUDGET_MS, options.hook) ? startedAt + HOOK_WALL_BUDGET_MS[options.hook] : undefined;
2085
+ const normalizedPath = normalizeMapKey(filePath);
2086
+ const outsideRoot = await this.findOutsideProjectRoot(filePath);
2087
+ if (outsideRoot) {
2088
+ this.recordOutsideRootDecline(filePath, outsideRoot, "touchFile");
2089
+ return { diags: [], skipReason: "outside-project-root" };
2090
+ }
2091
+ this.sweepDocumentDrift().catch(() => {});
2092
+ const diagnosticsMode = options.collectDiagnostics ? options.diagnostics ?? "document" : options.diagnostics ?? "none";
2093
+ const source = options.source ?? "unknown";
2094
+ const clientScope = options.clientScope ?? (diagnosticsMode === "full" ? "all" : "primary");
2095
+ const useAllClients = clientScope === "all";
2096
+ const resolvedPrimaryRoots = new Map;
2097
+ const attemptedAuxiliaryRoots = new Map;
2098
+ const waitSkipReasons = new Set;
2099
+ const coldAuxiliaryServerIds = new Set;
2100
+ const noteColdAuxiliary = (serverId, outcome) => {
2101
+ if (outcome === "cold-spawn" || outcome === "cold-spawn-joined") {
2102
+ coldAuxiliaryServerIds.add(serverId);
2103
+ }
2104
+ };
2105
+ let spawned;
2106
+ let serverCountAttempted;
2107
+ if (useAllClients) {
2108
+ const result = await this.getClientsForFile(filePath, options.excludeServerIds, resolvedPrimaryRoots, attemptedAuxiliaryRoots);
2109
+ spawned = result.clients;
2110
+ serverCountAttempted = result.serverCountAttempted;
2111
+ } else if (clientScope === "with-auxiliary") {
2112
+ const auxStartedAt = Date.now();
2113
+ const [entry, aux] = await Promise.all([
2114
+ this.getClientForFile(filePath, options.maxClientWaitMs, undefined, resolvedPrimaryRoots, waitSkipReasons),
2115
+ this.getAuxiliaryClientsForFile(filePath, new Set(options.auxiliaryServerIds ?? []), noteColdAuxiliary, options.maxClientWaitMs, undefined, options.hook)
2116
+ ]);
2117
+ const auxDurationMs = Date.now() - auxStartedAt;
2118
+ if (options.auxiliaryServerIds && options.auxiliaryServerIds.length > 0) {
2119
+ logLatency({
2120
+ type: "phase",
2121
+ phase: "auxiliary_readiness",
2122
+ filePath: normalizedPath,
2123
+ durationMs: auxDurationMs,
2124
+ metadata: {
2125
+ serverIds: aux.map((s) => s.info.id),
2126
+ attemptedCount: options.auxiliaryServerIds.length,
2127
+ readyCount: aux.length,
2128
+ coldServerIds: [...coldAuxiliaryServerIds],
2129
+ source
2130
+ }
2131
+ });
2132
+ }
2133
+ spawned = entry ? [entry, ...aux] : aux;
2134
+ serverCountAttempted = spawned.length;
2135
+ } else {
2136
+ const entry = await this.getClientForFile(filePath, options.maxClientWaitMs, undefined, resolvedPrimaryRoots, waitSkipReasons);
2137
+ spawned = entry ? [entry] : [];
2138
+ serverCountAttempted = spawned.length > 0 ? 1 : getServersForFileWithConfig(filePath).length > 0 ? 1 : 0;
2139
+ }
2140
+ if (spawned.length === 0) {
2141
+ const failureKind = this.isSpawnInFlight(filePath, resolvedPrimaryRoots) ? "spawn_in_flight_budget_elapsed" : "no_clients_none_spawning";
2142
+ logLatency({
2143
+ type: "phase",
2144
+ phase: "lsp_touch_file",
2145
+ filePath: normalizedPath,
2146
+ durationMs: Date.now() - startedAt,
2147
+ metadata: {
2148
+ serverCountAttempted,
2149
+ serverCountReady: 0,
2150
+ clientScope,
2151
+ diagnosticsMode,
2152
+ source,
2153
+ maxClientWaitMs: options.maxClientWaitMs,
2154
+ failureKind,
2155
+ ...waitSkipReasons.size > 0 ? { reason: [...waitSkipReasons][0] } : {},
2156
+ candidates: this.describeTouchCandidates(filePath, resolvedPrimaryRoots, attemptedAuxiliaryRoots)
2157
+ }
2158
+ });
2159
+ return;
2160
+ }
2161
+ const leaseKeys = await this.acquireClientLeases(spawned, filePath);
2162
+ if (!leaseKeys) {
2163
+ if (this.checkDestroyed()) {
2164
+ const primaryServerIds = spawned.filter((entry) => !isAuxiliary(entry.info)).map((entry) => entry.info.id);
2165
+ return {
2166
+ diags: [],
2167
+ inconclusive: true,
2168
+ ...primaryServerIds.length > 0 && {
2169
+ inconclusiveServerIds: primaryServerIds
2170
+ },
2171
+ inconclusiveReason: "diagnostics-wait"
2172
+ };
2173
+ }
2174
+ return this.touchFile(filePath, content, options);
2175
+ }
2176
+ try {
2177
+ if (this.shouldSkipTouch(filePath, contentFingerprint, clientScope, diagnosticsMode !== "none", spawned)) {
2178
+ logLatency({
2179
+ type: "phase",
2180
+ phase: "lsp_touch_file",
2181
+ filePath: normalizedPath,
2182
+ durationMs: Date.now() - startedAt,
2183
+ metadata: {
2184
+ serverCountReady: spawned.length,
2185
+ clientScope,
2186
+ diagnosticsMode,
2187
+ source,
2188
+ failureKind: "success",
2189
+ skipped: true,
2190
+ reason: "debounced_unchanged_content"
2191
+ }
2192
+ });
2193
+ return { diags: [] };
2194
+ }
2195
+ const languageId = getLanguageId(filePath) ?? "plaintext";
2196
+ const silent = options.silent ?? false;
2197
+ const notifySkippedServerIds = new Set(spawned.filter((entry) => this.shouldSkipNotify(filePath, contentFingerprint, clientScope, entry.info.id, entry.client)).map((entry) => entry.info.id));
2198
+ const notifySkipped = spawned.length > 0 && notifySkippedServerIds.size === spawned.length;
2199
+ const readPathVersion = (client) => client.getDiagnosticsVersionForPath?.(filePath);
2200
+ const diagnosticBaselines = new Map(spawned.map((entry) => [entry.client, readPathVersion(entry.client)]));
2201
+ const markedAtMs = Date.now();
2202
+ const touchContentHash = contentHash;
2203
+ const bindingMatchesTouchContent = (binding) => binding?.contentHash === touchContentHash;
2204
+ const carriedAuxiliary = options.collectDiagnostics ? spawned.flatMap((entry) => {
2205
+ if (!isAuxiliary(entry.info))
2206
+ return [];
2207
+ const binding = entry.client.getDiagnosticBinding?.(filePath);
2208
+ if (!bindingMatchesTouchContent(binding))
2209
+ return [];
2210
+ const diags = entry.client.getDiagnostics(filePath);
2211
+ return diags.length > 0 ? [{ serverId: entry.info.id, diags, binding }] : [];
2212
+ }) : [];
2213
+ const auxPublishedThisContent = new Set(spawned.flatMap((entry) => isAuxiliary(entry.info) && bindingMatchesTouchContent(entry.client.getDiagnosticBinding?.(filePath)) ? [entry.info.id] : []));
2214
+ const spawnedByServerId = new Map(spawned.map((entry) => [entry.info.id, entry]));
2215
+ const auxCoversThisContent = (serverId) => {
2216
+ const entry = spawnedByServerId.get(serverId);
2217
+ if (!entry || !isAuxiliary(entry.info))
2218
+ return false;
2219
+ return auxPublishedThisContent.has(serverId) || bindingMatchesTouchContent(entry.client.getDiagnosticBinding?.(filePath));
2220
+ };
2221
+ const notifyWriteTimedOutServerIds = [];
2222
+ const notifyDeferredServerIds = [];
2223
+ const supersededServerIds = [];
2224
+ if (!notifySkipped) {
2225
+ const budget = notifyWriteBudgetMs();
2226
+ const queueWaitMs = options.maxClientWaitMs !== undefined ? Math.min(budget, Math.max(0, primaryServerWaitFloorMs(filePath, options.maxClientWaitMs) - (Date.now() - startedAt))) : budget;
2227
+ await Promise.all(spawned.map(async (entry) => {
2228
+ if (notifySkippedServerIds.has(entry.info.id))
2229
+ return;
2230
+ const clientKey = await this.demonstratedReadyKeyFor(entry.info, filePath);
2231
+ const gated = isAuxiliary(entry.info) && clientKey !== undefined;
2232
+ let slot;
2233
+ if (gated && clientKey) {
2234
+ const barrierStartedAt = Date.now();
2235
+ await this.paceAuxNotify(clientKey, entry, filePath, queueWaitMs, {
2236
+ source,
2237
+ clientScope
2238
+ });
2239
+ const slotWaitMs = Math.max(0, queueWaitMs - (Date.now() - barrierStartedAt));
2240
+ const claim = await this.claimAuxNotifySlot(clientKey, entry, filePath, slotWaitMs);
2241
+ if ("outstandingMs" in claim) {
2242
+ notifyDeferredServerIds.push(entry.info.id);
2243
+ logLatency({
2244
+ type: "phase",
2245
+ phase: "lsp_notify_resync_deferred",
2246
+ filePath: normalizedPath,
2247
+ durationMs: claim.outstandingMs,
2248
+ metadata: {
2249
+ serverId: entry.info.id,
2250
+ source,
2251
+ clientScope,
2252
+ reason: "outstanding_write",
2253
+ outstandingMs: claim.outstandingMs,
2254
+ queueWaitMs
2255
+ }
2256
+ });
2257
+ return;
2258
+ }
2259
+ slot = claim;
2260
+ }
2261
+ let wrote;
2262
+ let rejected = false;
2263
+ try {
2264
+ const writeStartedAt = Date.now();
2265
+ const writePromise = entry.client.notify.open(filePath, content, languageId, undefined, silent, options.saved === true, options.readStamp).then((sent) => sent !== false);
2266
+ if (gated && clientKey) {
2267
+ this.noteAuxNotifyIssued(clientKey, entry.client);
2268
+ }
2269
+ if (slot && clientKey) {
2270
+ const client = entry.client;
2271
+ const release = slot.release;
2272
+ writePromise.then(() => {
2273
+ release();
2274
+ const outstandingMs = Date.now() - writeStartedAt;
2275
+ if (outstandingMs > budget && outstandingMs <= this.auxNotifyWedgeBudgetMs(clientKey)) {
2276
+ this.retractNotifyWriteBackpressure(clientKey, entry.info.id, filePath, outstandingMs, client);
2277
+ }
2278
+ }, release);
2279
+ }
2280
+ wrote = await withDeadline(writePromise, {
2281
+ ms: budget,
2282
+ onTimeout: "undefined",
2283
+ onReject: "propagate"
2284
+ });
2285
+ } catch {
2286
+ rejected = true;
2287
+ slot?.release();
2288
+ }
2289
+ if (wrote === true) {
2290
+ if (clientKey)
2291
+ this.notifyWriteBackpressureStreak.delete(clientKey);
2292
+ this.markTouched(filePath, contentFingerprint(), clientScope, entry.info.id, entry.client);
2293
+ } else if (wrote === false) {
2294
+ supersededServerIds.push(entry.info.id);
2295
+ } else {
2296
+ notifyWriteTimedOutServerIds.push(entry.info.id);
2297
+ if (!rejected) {
2298
+ this.recordNotifyWriteBackpressure(clientKey, entry, filePath);
2299
+ }
2300
+ }
2301
+ }));
2302
+ this.recordFullyCoveredSync(filePath, content, spawned, notifyWriteTimedOutServerIds.length === 0 && notifyDeferredServerIds.length === 0 && supersededServerIds.length === 0, startedAt, contentFingerprint);
2303
+ if (notifyWriteTimedOutServerIds.length > 0) {
2304
+ logLatency({
2305
+ type: "phase",
2306
+ phase: "lsp_notify_timeout",
2307
+ filePath: normalizedPath,
2308
+ durationMs: Date.now() - startedAt,
2309
+ metadata: {
2310
+ source,
2311
+ clientScope,
2312
+ serverCount: spawned.length,
2313
+ timedOutServerIds: notifyWriteTimedOutServerIds
2314
+ }
2315
+ });
2316
+ }
2317
+ }
2318
+ const notifyWriteTimedOut = notifyWriteTimedOutServerIds.length > 0;
2319
+ const primaryEntries = spawned.filter((entry) => !isAuxiliary(entry.info));
2320
+ const primaryServerIds = new Set(primaryEntries.map((e) => e.info.id));
2321
+ const primaryNotifyWriteTimedOutServerIds = notifyWriteTimedOutServerIds.filter((id) => primaryServerIds.has(id));
2322
+ const primaryNotifyWriteTimedOut = primaryNotifyWriteTimedOutServerIds.length > 0;
2323
+ const deferredResyncServerIds = new Set(notifyDeferredServerIds);
2324
+ let diagnosticsTimedOut = false;
2325
+ let diagnosticsUnansweredServerIds = [];
2326
+ let diagnosticsUnansweredPrimaryServerIds = [];
2327
+ const retractPrimaryTimeoutAttribution = () => {
2328
+ diagnosticsUnansweredPrimaryServerIds = [];
2329
+ diagnosticsUnansweredServerIds = diagnosticsUnansweredServerIds.filter((id) => !primaryServerIds.has(id));
2330
+ };
2331
+ let auxCutOffServerIds;
2332
+ let auxUnconfirmedServerIds;
2333
+ let lateDeliveryServerIds;
2334
+ let tsserverSyncEligible = false;
2335
+ const isWarmupTouch = source === "lsp_sweep_warmup";
2336
+ let tsserverSyncConfirmed;
2337
+ let diagnosticsUnsupportedServerIds = [];
2338
+ if (diagnosticsMode !== "none") {
2339
+ const envWait = readEnvDiagnosticsWaitMs();
2340
+ const callerCap = options.maxDiagnosticsWaitMs ?? options.maxClientWaitMs;
2341
+ const modeFloor = diagnosticsMode === "full" ? 3000 : 1200;
2342
+ const sweepFirstTouch = new Map;
2343
+ if (options.sweepIndexGate) {
2344
+ for (const entry of spawned) {
2345
+ const strategy = getStrategy(entry.client.serverId, entry.client.getLaunchVariant?.());
2346
+ if (strategy.workspaceIndexing) {
2347
+ sweepFirstTouch.set(entry.client.serverId, options.sweepIndexGate.consumeFirstTouch(entry.client.serverId));
2348
+ }
2349
+ }
2350
+ }
2351
+ const silentCleanWarmupServers = new Set;
2352
+ if (options.warmupOverride && (options.warmupAttempt ?? 1) <= 1) {
2353
+ for (const entry of spawned) {
2354
+ const strategy = getStrategy(entry.client.serverId, entry.client.getLaunchVariant?.());
2355
+ if (strategy.workspaceIndexing !== true)
2356
+ continue;
2357
+ try {
2358
+ const snapshot = {
2359
+ serverId: entry.client.serverId,
2360
+ root: entry.client.root,
2361
+ operationSupport: entry.client.getOperationSupport(),
2362
+ workspaceDiagnosticsSupport: entry.client.getWorkspaceDiagnosticsSupport(),
2363
+ customServer: entry.info.custom,
2364
+ diagnosticsPublished: this.state.diagnosticsPublished.has(entry.info.id),
2365
+ diagnosticsUnsupported: this.state.diagnosticsUnsupported.has(entry.info.id),
2366
+ advertisedCommands: entry.client.getAdvertisedCommands(),
2367
+ rawCapabilityKeys: entry.client.getRawCapabilityKeys?.() ?? [],
2368
+ launchVariant: entry.client.getLaunchVariant?.()
2369
+ };
2370
+ if (classifyServerWaitTier(entry.client.serverId, snapshot) === "tier3-silent") {
2371
+ silentCleanWarmupServers.add(entry.client.serverId);
2372
+ }
2373
+ } catch {}
2374
+ }
2375
+ }
2376
+ const perServerTimeout = (serverId) => {
2377
+ const launchVariant = spawned.find((entry) => entry.client.serverId === serverId)?.client.getLaunchVariant?.();
2378
+ const strategy = getStrategy(serverId, launchVariant);
2379
+ let strategyWait = strategy.aggregateWaitMs;
2380
+ const isFirstTouch = sweepFirstTouch.get(serverId);
2381
+ if (isFirstTouch === false && strategy.workspaceIndexing) {
2382
+ strategyWait = strategy.workspaceIndexingWarmWaitMs ?? Math.min(300, strategyWait);
2383
+ }
2384
+ if (callerCap !== undefined) {
2385
+ if (options.warmupOverride) {
2386
+ if (silentCleanWarmupServers.has(serverId)) {
2387
+ return Math.min(callerCap, strategyWait > 0 ? strategyWait : callerCap);
2388
+ }
2389
+ if ((options.warmupAttempt ?? 1) > 1) {
2390
+ return Math.min(callerCap, strategyWait > 0 ? strategyWait : callerCap);
2391
+ }
2392
+ return Math.max(callerCap, strategyWait > 0 ? strategyWait : 0);
2393
+ }
2394
+ return Math.min(callerCap, strategyWait > 0 ? strategyWait : callerCap);
2395
+ }
2396
+ return strategyWait > 0 ? strategyWait : modeFloor;
2397
+ };
2398
+ let timeoutFor;
2399
+ if (envWait !== undefined) {
2400
+ timeoutFor = () => envWait;
2401
+ } else if (!useAllClients && spawned.length === 1 || clientScope === "with-auxiliary" || clientScope === "all") {
2402
+ timeoutFor = perServerTimeout;
2403
+ } else {
2404
+ timeoutFor = () => callerCap ?? modeFloor;
2405
+ }
2406
+ const timeoutMs = Math.max(0, ...spawned.filter((e) => !deferredResyncServerIds.has(e.info.id)).map((e) => timeoutFor(e.client.serverId)));
2407
+ if (!notifyWriteTimedOut && (options.collectDiagnostics === true || isWarmupTouch) && clientScope === "primary" && spawned.length === 1 && spawned[0].client.serverId === "typescript" && getStrategy(spawned[0].client.serverId, spawned[0].client.getLaunchVariant?.()).silentOnClean === true) {
2408
+ try {
2409
+ const snapshots = await this.getCapabilitySnapshots(filePath);
2410
+ tsserverSyncEligible = classifyCascadeWaitTier(this, filePath, snapshots) === "tier3-silent";
2411
+ } catch {}
2412
+ }
2413
+ const waitStartedAt = Date.now();
2414
+ const hasTouchAuxiliaries = clientScope === "with-auxiliary" && options.collectDiagnostics === true && spawned.some((e) => isAuxiliary(e.info));
2415
+ if (spawned.some((entry) => entry.info.custom === true)) {
2416
+ try {
2417
+ const snapshotHook = options.hook ?? "tool_result_edit";
2418
+ const snapshotBudget = hookDeadlineAt === undefined ? HOOK_WALL_BUDGET_MS.tool_result_edit : Math.max(0, hookDeadlineAt - Date.now());
2419
+ const snapshots = await bounded(this.getCapabilitySnapshots(filePath), {
2420
+ ms: snapshotBudget,
2421
+ signal: getAmbientAbortSignal(),
2422
+ shutdownSignal: this.shutdownController.signal,
2423
+ hook: snapshotHook,
2424
+ label: `getCapabilitySnapshots:${filePath}`
2425
+ });
2426
+ if (snapshots !== undefined) {
2427
+ diagnosticsUnsupportedServerIds = spawned.filter((entry) => classifyServerWaitTier(entry.info.id, snapshots.find((s) => s.serverId === entry.info.id)) === "diagnostics-unsupported").map((entry) => entry.info.id);
2428
+ }
2429
+ } catch {}
2430
+ }
2431
+ let pressureSnapshots = [];
2432
+ if (shouldPreferPullOnlyDiagnostics()) {
2433
+ try {
2434
+ pressureSnapshots = await this.getCapabilitySnapshots(filePath);
2435
+ } catch {}
2436
+ }
2437
+ const configuredAuxCeilingMs = readEnvAuxGraceMs();
2438
+ const perServerDeclaredTimeouts = spawned.map((entry) => timeoutFor(entry.client.serverId));
2439
+ const perServerWaitTimeouts = spawned.map((entry, entryIndex) => {
2440
+ const declaredServerTimeout = perServerDeclaredTimeouts[entryIndex];
2441
+ const observedSpawnMs = getSuccessfulLspSpawnDurationMs(entry.client.serverId);
2442
+ return hasTouchAuxiliaries && isAuxiliary(entry.info) && coldAuxiliaryServerIds.has(entry.client.serverId) && (configuredAuxCeilingMs !== undefined || observedSpawnMs !== undefined && observedSpawnMs > 0) ? auxWaitBudgetMs(entry.client.serverId, true, configuredAuxCeilingMs, declaredServerTimeout) : perServerDeclaredTimeouts[entryIndex];
2443
+ });
2444
+ const firstContactCutOffServerIds = new Set;
2445
+ const firstContactErroredServerIds = new Set;
2446
+ const answeredForThisTouch = (entry) => {
2447
+ const baseline = diagnosticBaselines.get(entry.client);
2448
+ const currentPathVersion = readPathVersion(entry.client);
2449
+ if (auxiliaryPublicationEvidence({
2450
+ bindingMatchesContent: false,
2451
+ baseline,
2452
+ currentPathVersion
2453
+ })) {
2454
+ return true;
2455
+ }
2456
+ try {
2457
+ return entry.client.getAllDiagnostics?.().has(normalizedPath) === true;
2458
+ } catch {
2459
+ return false;
2460
+ }
2461
+ };
2462
+ const perServerRaceBudgets = spawned.map((entry, entryIndex) => {
2463
+ return hasTouchAuxiliaries && isAuxiliary(entry.info) ? auxWaitBudgetMs(entry.client.serverId, coldAuxiliaryServerIds.has(entry.client.serverId), configuredAuxCeilingMs, perServerDeclaredTimeouts[entryIndex]) : perServerDeclaredTimeouts[entryIndex];
2464
+ });
2465
+ const perServerWaits = spawned.map((entry, entryIndex) => {
2466
+ if (hasTouchAuxiliaries && isAuxiliary(entry.info) && this.isAuxiliaryWaitDemoted(entry.info.id, entry.client.root ?? filePath)) {
2467
+ return Promise.resolve(undefined);
2468
+ }
2469
+ if (this.state.diagnosticsUnsupported.has(entry.info.id) || diagnosticsUnsupportedServerIds.includes(entry.info.id)) {
2470
+ return Promise.resolve(undefined);
2471
+ }
2472
+ if (deferredResyncServerIds.has(entry.info.id)) {
2473
+ return Promise.resolve(undefined);
2474
+ }
2475
+ const serverTimeout = perServerWaitTimeouts[entryIndex];
2476
+ const baseline = diagnosticBaselines.get(entry.client);
2477
+ const pullOnly = classifyServerWaitTier(entry.client.serverId, pressureSnapshots.find((snapshot) => snapshot.serverId === entry.client.serverId)) === "pull-capable";
2478
+ const waitForDiagnostics = () => !notifySkippedServerIds.has(entry.info.id) && Number.isFinite(baseline) ? entry.client.waitForDiagnostics(filePath, serverTimeout, {
2479
+ minVersion: baseline,
2480
+ ...pullOnly && { pullOnly: true },
2481
+ ...isWarmupTouch && { pullSettleSource: "pull-warmup" }
2482
+ }) : pullOnly ? entry.client.waitForDiagnostics(filePath, serverTimeout, {
2483
+ pullOnly: true,
2484
+ ...isWarmupTouch && { pullSettleSource: "pull-warmup" }
2485
+ }) : isWarmupTouch ? entry.client.waitForDiagnostics(filePath, serverTimeout, {
2486
+ pullSettleSource: "pull-warmup"
2487
+ }) : entry.client.waitForDiagnostics(filePath, serverTimeout);
2488
+ const firstContact = entry.info.custom === true && !this.state.diagnosticsPublished.has(entry.info.id) && !this.state.diagnosticsUnsupported.has(entry.info.id) && entry.client.getWorkspaceDiagnosticsSupport().mode !== "pull";
2489
+ if (!firstContact)
2490
+ return waitForDiagnostics().catch(() => {
2491
+ return;
2492
+ });
2493
+ let shared = this.firstContactWaits.get(entry.info.id);
2494
+ if (!shared) {
2495
+ let removeShutdownListener;
2496
+ const shutdown = new Promise((resolve) => {
2497
+ const onShutdown = () => resolve("shutdown");
2498
+ if (this.shutdownController.signal.aborted) {
2499
+ onShutdown();
2500
+ return;
2501
+ }
2502
+ this.shutdownController.signal.addEventListener("abort", onShutdown, {
2503
+ once: true
2504
+ });
2505
+ removeShutdownListener = () => this.shutdownController.signal.removeEventListener("abort", onShutdown);
2506
+ });
2507
+ const probe = Promise.race([
2508
+ waitForDiagnostics().then(() => "completed", () => "errored"),
2509
+ shutdown
2510
+ ]);
2511
+ const completed = withDeadline(probe, {
2512
+ ms: serverTimeout,
2513
+ onTimeout: "undefined"
2514
+ });
2515
+ shared = completed.then((outcome) => {
2516
+ if (outcome === "errored" || outcome === "shutdown") {
2517
+ return "errored";
2518
+ }
2519
+ if (answeredForThisTouch(entry))
2520
+ return "answered";
2521
+ this.state.diagnosticsUnsupported.add(entry.info.id);
2522
+ recordDegradationOnce({
2523
+ kind: "lsp-diagnostics-unsupported",
2524
+ subject: entry.info.id,
2525
+ reason: "custom server has no pull provider and no publish evidence after first contact wait"
2526
+ });
2527
+ return "silent";
2528
+ }).catch(() => "errored").finally(() => {
2529
+ removeShutdownListener?.();
2530
+ if (this.firstContactWaits.get(entry.info.id) === shared) {
2531
+ this.firstContactWaits.delete(entry.info.id);
2532
+ }
2533
+ });
2534
+ this.firstContactWaits.set(entry.info.id, shared);
2535
+ }
2536
+ const callerWait = hookDeadlineAt === undefined ? shared : withDeadline(shared, {
2537
+ ms: Math.max(0, hookDeadlineAt - Date.now()),
2538
+ onTimeout: "undefined"
2539
+ });
2540
+ return callerWait.then((outcome) => {
2541
+ if (outcome === undefined) {
2542
+ firstContactCutOffServerIds.add(entry.info.id);
2543
+ } else if (outcome === "errored") {
2544
+ firstContactErroredServerIds.add(entry.info.id);
2545
+ }
2546
+ return;
2547
+ });
2548
+ });
2549
+ let pushWaitSettled = false;
2550
+ const pushWait = hasTouchAuxiliaries ? (() => {
2551
+ const primaryWaits = perServerWaits.filter((_, i) => !isAuxiliary(spawned[i].info));
2552
+ const auxWaits = perServerWaits.map((p, i) => isAuxiliary(spawned[i].info) ? {
2553
+ promise: p,
2554
+ serverId: spawned[i].info.id,
2555
+ client: spawned[i].client,
2556
+ baseline: diagnosticBaselines.get(spawned[i].client),
2557
+ budgetMs: perServerRaceBudgets[i],
2558
+ demoted: this.isAuxiliaryWaitDemoted(spawned[i].info.id, spawned[i].client.root ?? filePath),
2559
+ root: spawned[i].client.root ?? filePath
2560
+ } : null).filter((x) => x !== null);
2561
+ return Promise.all(primaryWaits).then(async () => {
2562
+ if (auxWaits.length === 0)
2563
+ return;
2564
+ const auxWaitStartedAt = Date.now();
2565
+ const outcomes = await Promise.all(auxWaits.map(async (aux) => {
2566
+ const { budgetMs } = aux;
2567
+ if (aux.demoted) {
2568
+ return {
2569
+ serverId: aux.serverId,
2570
+ outcome: deferredResyncServerIds.has(aux.serverId) ? "deferred" : "demoted",
2571
+ publishedThisContent: auxiliaryPublicationEvidence({
2572
+ bindingMatchesContent: auxCoversThisContent(aux.serverId),
2573
+ baseline: aux.baseline,
2574
+ currentPathVersion: readPathVersion(aux.client)
2575
+ }),
2576
+ budgetMs,
2577
+ elapsedMs: 0,
2578
+ elapsedSinceNotifyMs: 0
2579
+ };
2580
+ }
2581
+ let timer;
2582
+ const timeout = new Promise((resolve) => {
2583
+ timer = setTimeout(() => resolve(false), budgetMs);
2584
+ if (typeof timer === "object" && "unref" in timer) {
2585
+ timer.unref?.();
2586
+ }
2587
+ });
2588
+ const raced = await Promise.race([
2589
+ aux.promise.then(() => true),
2590
+ timeout
2591
+ ]);
2592
+ if (timer)
2593
+ clearTimeout(timer);
2594
+ const currentPathVersion = readPathVersion(aux.client);
2595
+ const publishedEvidence = auxiliaryPublicationEvidence({
2596
+ bindingMatchesContent: false,
2597
+ baseline: aux.baseline,
2598
+ currentPathVersion,
2599
+ raced
2600
+ });
2601
+ const outcome = deferredResyncServerIds.has(aux.serverId) ? "deferred" : !raced ? "cut_off" : publishedEvidence ? "answered" : "silent";
2602
+ const elapsedMs = Date.now() - auxWaitStartedAt;
2603
+ if (outcome !== "deferred") {
2604
+ this.noteAuxiliaryWait(`${aux.serverId}:${normalizeMapKey(aux.root)}`, budgetMs, elapsedMs);
2605
+ }
2606
+ return {
2607
+ serverId: aux.serverId,
2608
+ outcome,
2609
+ publishedThisContent: auxCoversThisContent(aux.serverId),
2610
+ budgetMs,
2611
+ elapsedMs,
2612
+ elapsedSinceNotifyMs: Date.now() - waitStartedAt
2613
+ };
2614
+ }));
2615
+ const unfinished = outcomes.filter((outcome) => outcome.outcome === "cut_off").map((outcome) => outcome.serverId);
2616
+ if (unfinished.length > 0)
2617
+ auxCutOffServerIds = unfinished;
2618
+ const uncovered = auxiliaryCoverageGap(outcomes);
2619
+ if (uncovered.length > 0)
2620
+ auxUnconfirmedServerIds = uncovered;
2621
+ const collectLaterServerIds = outcomes.filter((o) => !o.publishedThisContent && (o.outcome === "cut_off" || o.outcome === "silent" || o.outcome === "demoted") && !(o.serverId === "ast-grep" && napiFallbackCoveredSince(filePath, startedAt))).map((o) => o.serverId);
2622
+ if (collectLaterServerIds.length > 0) {
2623
+ lateDeliveryServerIds = collectLaterServerIds;
2624
+ for (const serverId of collectLaterServerIds) {
2625
+ markPendingAuxiliaryCoverage(filePath, [serverId], startedAt, undefined, undefined, captureAuxPublicationBacklog(auxWaits.find((aux) => aux.serverId === serverId)?.client, filePath));
2626
+ }
2627
+ }
2628
+ logLatency({
2629
+ type: "phase",
2630
+ phase: "lsp_aux_wait_outcome",
2631
+ filePath: normalizedPath,
2632
+ durationMs: Date.now() - auxWaitStartedAt,
2633
+ metadata: { clientScope, waitShape: "aux_grace", outcomes }
2634
+ });
2635
+ });
2636
+ })() : Promise.all(perServerWaits).then(() => {});
2637
+ const markPushWaitSettled = () => {
2638
+ pushWaitSettled = true;
2639
+ };
2640
+ pushWait.then(markPushWaitSettled, markPushWaitSettled);
2641
+ if (tsserverSyncEligible) {
2642
+ const graceMs = readTsserverSyncGraceMs();
2643
+ const primaryClient = spawned[0].client;
2644
+ const syncRacer = (async () => {
2645
+ await new Promise((resolve) => {
2646
+ const timer = setTimeout(resolve, graceMs);
2647
+ timer.unref?.();
2648
+ });
2649
+ const publishedAt = primaryClient.getAllDiagnostics?.().get(normalizedPath)?.ts;
2650
+ if (pushWaitSettled || publishedAt !== undefined && publishedAt > markedAtMs) {
2651
+ return new Promise(() => {});
2652
+ }
2653
+ try {
2654
+ const result = await attemptTsserverSyncDiagnostics(filePath, tsserverSyncChannel(primaryClient));
2655
+ if (result === undefined || pushWaitSettled) {
2656
+ return new Promise(() => {});
2657
+ }
2658
+ return result;
2659
+ } catch {
2660
+ return new Promise(() => {});
2661
+ }
2662
+ })();
2663
+ const raceOutcome = await Promise.race([
2664
+ pushWait.then(() => {
2665
+ return;
2666
+ }),
2667
+ syncRacer
2668
+ ]);
2669
+ if (raceOutcome !== undefined) {
2670
+ tsserverSyncConfirmed = raceOutcome;
2671
+ }
2672
+ } else {
2673
+ await pushWait;
2674
+ }
2675
+ const waitedMs = Date.now() - waitStartedAt;
2676
+ const firstContactCustomServerIds = spawned.filter((entry) => entry.info.custom === true && !this.state.diagnosticsPublished.has(entry.info.id) && !this.state.diagnosticsUnsupported.has(entry.info.id) && !firstContactCutOffServerIds.has(entry.info.id) && !firstContactErroredServerIds.has(entry.info.id) && entry.client.getWorkspaceDiagnosticsSupport().mode !== "pull").map((entry) => entry.info.id);
2677
+ for (const serverId of firstContactCustomServerIds) {
2678
+ const entry = spawned.find((candidate) => candidate.info.id === serverId);
2679
+ if (!entry || answeredForThisTouch(entry))
2680
+ continue;
2681
+ this.state.diagnosticsUnsupported.add(serverId);
2682
+ diagnosticsUnsupportedServerIds.push(serverId);
2683
+ recordDegradationOnce({
2684
+ kind: "lsp-diagnostics-unsupported",
2685
+ subject: serverId,
2686
+ reason: "custom server has no pull provider and no publish evidence after first contact wait"
2687
+ });
2688
+ }
2689
+ for (const entry of spawned) {
2690
+ if (entry.info.custom === true && this.state.diagnosticsUnsupported.has(entry.info.id)) {
2691
+ diagnosticsUnsupportedServerIds.push(entry.info.id);
2692
+ }
2693
+ }
2694
+ diagnosticsUnsupportedServerIds = [
2695
+ ...new Set(diagnosticsUnsupportedServerIds)
2696
+ ];
2697
+ if (!hasTouchAuxiliaries && options.collectDiagnostics === true) {
2698
+ const auxEntries = spawned.filter((entry) => isAuxiliary(entry.info));
2699
+ if (auxEntries.length > 0) {
2700
+ const outcomes = auxEntries.map((entry) => {
2701
+ const baseline = diagnosticBaselines.get(entry.client);
2702
+ const currentPathVersion = readPathVersion(entry.client);
2703
+ const publishedEvidence = auxiliaryPublicationEvidence({
2704
+ bindingMatchesContent: false,
2705
+ baseline,
2706
+ currentPathVersion
2707
+ });
2708
+ return {
2709
+ serverId: entry.info.id,
2710
+ outcome: deferredResyncServerIds.has(entry.info.id) ? "deferred" : publishedEvidence ? "answered" : "silent",
2711
+ publishedThisContent: auxCoversThisContent(entry.info.id),
2712
+ budgetMs: timeoutFor(entry.client.serverId),
2713
+ elapsedMs: waitedMs,
2714
+ elapsedSinceNotifyMs: waitedMs
2715
+ };
2716
+ });
2717
+ const uncovered = auxiliaryCoverageGap(outcomes);
2718
+ if (uncovered.length > 0)
2719
+ auxUnconfirmedServerIds = uncovered;
2720
+ logLatency({
2721
+ type: "phase",
2722
+ phase: "lsp_aux_wait_outcome",
2723
+ filePath: normalizedPath,
2724
+ durationMs: waitedMs,
2725
+ metadata: { clientScope, waitShape: "aggregate", outcomes }
2726
+ });
2727
+ }
2728
+ }
2729
+ if (tsserverSyncConfirmed !== undefined) {
2730
+ logLatency({
2731
+ type: "phase",
2732
+ phase: "lsp_tsserver_sync_confirm",
2733
+ filePath: normalizedPath,
2734
+ durationMs: waitedMs,
2735
+ metadata: {
2736
+ source,
2737
+ serverId: spawned[0]?.client.serverId,
2738
+ clientScope,
2739
+ diagnosticsMode,
2740
+ mode: "race",
2741
+ confirmedDiagnosticCount: tsserverSyncConfirmed.length,
2742
+ budgetMs: timeoutMs,
2743
+ savedVsBudgetMs: Math.max(0, timeoutMs - waitedMs)
2744
+ }
2745
+ });
2746
+ } else if (waitedMs + 20 >= timeoutMs || firstContactErroredServerIds.size > 0) {
2747
+ const waited = spawned.filter((entry) => !deferredResyncServerIds.has(entry.info.id) && !diagnosticsUnsupportedServerIds.includes(entry.info.id));
2748
+ const unanswered = waited.filter((entry) => !answeredForThisTouch(entry));
2749
+ diagnosticsUnansweredServerIds = unanswered.map((e) => e.info.id);
2750
+ diagnosticsUnansweredPrimaryServerIds = unanswered.filter((entry) => !isAuxiliary(entry.info)).map((e) => e.info.id);
2751
+ const hasWaitedPrimary = waited.some((entry) => !isAuxiliary(entry.info));
2752
+ const hasUnsupportedPrimary = diagnosticsUnsupportedServerIds.some((serverId) => spawned.some((entry) => entry.info.id === serverId && !isAuxiliary(entry.info)));
2753
+ diagnosticsTimedOut = !hasWaitedPrimary && !hasUnsupportedPrimary || diagnosticsUnansweredPrimaryServerIds.length > 0;
2754
+ for (const entry of unanswered) {
2755
+ incrementDegradationCount({
2756
+ kind: "lsp-diagnostics-timeout",
2757
+ subject: entry.info.id,
2758
+ reason: "diagnostics wait timed out"
2759
+ });
2760
+ }
2761
+ logLatency({
2762
+ type: "phase",
2763
+ phase: "lsp_diagnostics_timeout",
2764
+ filePath: normalizedPath,
2765
+ durationMs: waitedMs,
2766
+ metadata: {
2767
+ source,
2768
+ serverIds: spawned.map((e) => e.info.id),
2769
+ clientScope,
2770
+ diagnosticsMode,
2771
+ timeoutMs,
2772
+ unansweredServerIds: diagnosticsUnansweredServerIds,
2773
+ attributedToPrimary: diagnosticsTimedOut
2774
+ }
2775
+ });
2776
+ }
2777
+ if (diagnosticsTimedOut && !primaryNotifyWriteTimedOut && clientScope === "all") {
2778
+ try {
2779
+ const outstanding = primaryEntries.filter((entry) => !notifyWriteTimedOutServerIds.includes(entry.info.id) && !entry.client.getAllDiagnostics().has(normalizedPath));
2780
+ if (outstanding.length > 0) {
2781
+ const snapshots = await this.getCapabilitySnapshots(filePath);
2782
+ const allSilent = outstanding.every((entry) => classifyServerWaitTier(entry.client.serverId, snapshots.find((s) => s.serverId === entry.client.serverId)) === "tier3-silent");
2783
+ if (allSilent) {
2784
+ const liveness = await Promise.all(outstanding.map((entry) => (entry.client.pingLiveness?.() ?? Promise.resolve(true)).catch(() => false)));
2785
+ if (liveness.every(Boolean)) {
2786
+ diagnosticsTimedOut = false;
2787
+ retractPrimaryTimeoutAttribution();
2788
+ logLatency({
2789
+ type: "phase",
2790
+ phase: "lsp_silent_clean_confirm",
2791
+ filePath: normalizedPath,
2792
+ durationMs: Date.now() - startedAt,
2793
+ metadata: {
2794
+ source,
2795
+ clientScope,
2796
+ diagnosticsMode,
2797
+ aggregate: true,
2798
+ serverIds: outstanding.map((entry) => entry.client.serverId)
2799
+ }
2800
+ });
2801
+ }
2802
+ }
2803
+ }
2804
+ } catch {}
2805
+ }
2806
+ }
2807
+ const auxCoveredAtMerge = new Set(spawned.filter((entry) => auxCoversThisContent(entry.info.id)).map((entry) => entry.info.id));
2808
+ const uncoveredDeferredServerIds = notifyDeferredServerIds.filter((serverId) => !auxCoveredAtMerge.has(serverId));
2809
+ const staleWriteAuxiliaryServerIds = spawned.filter((entry) => isAuxiliary(entry.info) && notifyWriteTimedOutServerIds.includes(entry.info.id) && !auxCoveredAtMerge.has(entry.info.id)).map((entry) => entry.info.id);
2810
+ const droppedAuxiliaryServerIds = new Set([
2811
+ ...uncoveredDeferredServerIds,
2812
+ ...staleWriteAuxiliaryServerIds
2813
+ ]);
2814
+ let collected = options.collectDiagnostics ? tsserverSyncConfirmed !== undefined ? mergeLspDiagnostics(attributeDiagnostics(spawned[0]?.info.id ?? "unknown", tsserverSyncConfirmed)) : mergeLspDiagnostics([
2815
+ ...spawned.flatMap((entry) => droppedAuxiliaryServerIds.has(entry.info.id) ? [] : attributeDiagnostics(entry.info.id, entry.client.getDiagnostics(filePath))),
2816
+ ...carriedAuxiliary.flatMap((entry) => attributeDiagnostics(entry.serverId, entry.diags))
2817
+ ]) : undefined;
2818
+ let syncConfirmed = tsserverSyncConfirmed !== undefined;
2819
+ const syncClient = spawned[0]?.client;
2820
+ if (diagnosticsTimedOut && tsserverSyncEligible && syncClient !== undefined && collected !== undefined && collected.length === 0) {
2821
+ try {
2822
+ const syncResult = await attemptTsserverSyncDiagnostics(filePath, tsserverSyncChannel(syncClient));
2823
+ if (syncResult !== undefined) {
2824
+ diagnosticsTimedOut = false;
2825
+ retractPrimaryTimeoutAttribution();
2826
+ syncConfirmed = true;
2827
+ collected = syncResult.length > 0 ? mergeLspDiagnostics(attributeDiagnostics(spawned[0]?.info.id ?? "unknown", syncResult)) : [];
2828
+ logLatency({
2829
+ type: "phase",
2830
+ phase: "lsp_tsserver_sync_confirm",
2831
+ filePath: normalizedPath,
2832
+ durationMs: Date.now() - startedAt,
2833
+ metadata: {
2834
+ source,
2835
+ clientScope,
2836
+ diagnosticsMode,
2837
+ mode: "end-of-wait",
2838
+ confirmedDiagnosticCount: collected.length
2839
+ }
2840
+ });
2841
+ }
2842
+ } catch {}
2843
+ }
2844
+ if (diagnosticsTimedOut && !primaryNotifyWriteTimedOut && (!tsserverSyncEligible || isWarmupTouch) && clientScope === "primary" && spawned.length === 1 && getStrategy(spawned[0].client.serverId, spawned[0].client.getLaunchVariant?.()).silentOnClean === true) {
2845
+ try {
2846
+ const snapshots = await this.getCapabilitySnapshots(filePath);
2847
+ if (classifyCascadeWaitTier(this, filePath, snapshots) === "tier3-silent") {
2848
+ const alive = await (spawned[0].client.pingLiveness?.() ?? Promise.resolve(true)).catch(() => false);
2849
+ if (alive) {
2850
+ diagnosticsTimedOut = false;
2851
+ retractPrimaryTimeoutAttribution();
2852
+ if (collected !== undefined)
2853
+ collected = mergeLspDiagnostics([]);
2854
+ logLatency({
2855
+ type: "phase",
2856
+ phase: "lsp_silent_clean_confirm",
2857
+ filePath: normalizedPath,
2858
+ durationMs: Date.now() - startedAt,
2859
+ metadata: {
2860
+ source,
2861
+ clientScope,
2862
+ diagnosticsMode,
2863
+ serverId: spawned[0].client.serverId
2864
+ }
2865
+ });
2866
+ }
2867
+ }
2868
+ } catch {}
2869
+ }
2870
+ const verdict = resolveTouchVerdict({
2871
+ primaryNotifyWriteTimedOutServerIds,
2872
+ diagnosticsTimedOut,
2873
+ diagnosticsUnansweredServerIds: diagnosticsUnansweredPrimaryServerIds
2874
+ });
2875
+ const inconclusive = verdict.inconclusive;
2876
+ const brokenSkippedServerIds = collected !== undefined ? await this.brokenSkippedAuxiliaryServerIds(filePath, clientScope, options, spawned) : [];
2877
+ const auxNoAnswerServerIds = spawned.filter((entry) => isAuxiliary(entry.info) && (diagnosticsUnansweredServerIds.includes(entry.info.id) || notifyWriteTimedOutServerIds.includes(entry.info.id)) && !auxCoveredAtMerge.has(entry.info.id)).map((entry) => entry.info.id);
2878
+ const unconfirmedServerIds = [
2879
+ ...new Set([
2880
+ ...auxUnconfirmedServerIds ?? [],
2881
+ ...auxNoAnswerServerIds,
2882
+ ...uncoveredDeferredServerIds,
2883
+ ...brokenSkippedServerIds
2884
+ ])
2885
+ ].filter((serverId) => !auxCoveredAtMerge.has(serverId));
2886
+ const coverageGap = unconfirmedServerIds.length > 0;
2887
+ if (brokenSkippedServerIds.length > 0 || uncoveredDeferredServerIds.length > 0 || auxNoAnswerServerIds.length > 0) {
2888
+ for (const serverId of unconfirmedServerIds) {
2889
+ const reasons = [
2890
+ brokenSkippedServerIds.includes(serverId) && "breaker skip",
2891
+ uncoveredDeferredServerIds.includes(serverId) && "deferred resync",
2892
+ auxNoAnswerServerIds.includes(serverId) && "no diagnostics answer"
2893
+ ].filter(Boolean);
2894
+ incrementDegradationCount({
2895
+ kind: "lsp-scanner-coverage-gap",
2896
+ subject: `${serverId}:${normalizedPath}`,
2897
+ reason: reasons.join(", ") || "scanner coverage gap"
2898
+ });
2899
+ }
2900
+ emitBounded("lsp_scanner_coverage_gap", `${source}:${normalizedPath}`, {
2901
+ filePath: normalizedPath,
2902
+ durationMs: Date.now() - startedAt,
2903
+ metadata: {
2904
+ source,
2905
+ clientScope,
2906
+ ...brokenSkippedServerIds.length > 0 && {
2907
+ brokenSkippedServerIds
2908
+ },
2909
+ ...uncoveredDeferredServerIds.length > 0 && {
2910
+ deferredResyncServerIds: uncoveredDeferredServerIds
2911
+ },
2912
+ ...auxNoAnswerServerIds.length > 0 && { auxNoAnswerServerIds }
2913
+ }
2914
+ });
2915
+ }
2916
+ const notifyTimedOutServerIds = new Set(notifyWriteTimedOutServerIds);
2917
+ const uncoveredServerIds = new Set(unconfirmedServerIds);
2918
+ if (diagnosticsMode !== "none" && !diagnosticsTimedOut) {
2919
+ for (const entry of spawned) {
2920
+ if (notifyTimedOutServerIds.has(entry.info.id))
2921
+ continue;
2922
+ if (uncoveredServerIds.has(entry.info.id))
2923
+ continue;
2924
+ const key = await this.demonstratedReadyKeyFor(entry.info, filePath);
2925
+ if (key && this.state.clients.get(key) === entry.client)
2926
+ this.markDemonstratedReadyKey(key);
2927
+ }
2928
+ }
2929
+ if (collected !== undefined && !inconclusive && !coverageGap) {
2930
+ const normalizedKey = normalizeMapKey(filePath);
2931
+ if (collected.length > 0) {
2932
+ this.lastKnownDiagnostics.set(normalizedKey, collected);
2933
+ this.lastKnownContentHash.set(normalizedKey, this.hashContent(content));
2934
+ } else {
2935
+ this.lastKnownDiagnostics.delete(normalizedKey);
2936
+ this.lastKnownContentHash.delete(normalizedKey);
2937
+ }
2938
+ }
2939
+ const result = { diags: collected ?? [] };
2940
+ const primaryDiagnosticsUnsupported = diagnosticsUnsupportedServerIds.some((serverId) => spawned.some((entry) => entry.info.id === serverId && !isAuxiliary(entry.info)));
2941
+ if (diagnosticsUnsupportedServerIds.length > 0) {
2942
+ result.diagnosticsUnsupportedServerIds = diagnosticsUnsupportedServerIds;
2943
+ }
2944
+ if (collected !== undefined && inconclusive) {
2945
+ result.inconclusive = true;
2946
+ if (verdict.inconclusiveServerIds) {
2947
+ result.inconclusiveServerIds = verdict.inconclusiveServerIds;
2948
+ }
2949
+ result.inconclusiveReason = verdict.inconclusiveReason;
2950
+ } else if (collected !== undefined && primaryDiagnosticsUnsupported) {
2951
+ if (unconfirmedServerIds.length > 0) {
2952
+ result.unconfirmedServerIds = [...unconfirmedServerIds];
2953
+ }
2954
+ } else if (collected !== undefined && coverageGap) {
2955
+ result.confirmation = "partial";
2956
+ result.unconfirmedServerIds = [...unconfirmedServerIds];
2957
+ } else if (collected !== undefined && !primaryDiagnosticsUnsupported) {
2958
+ result.confirmation = "confirmed";
2959
+ }
2960
+ let binding;
2961
+ if (collected !== undefined) {
2962
+ binding = syncConfirmed ? { boundToCurrentDisk: "unknown" } : this.mergeBinding(filePath, [
2963
+ ...spawned.filter((entry) => !droppedAuxiliaryServerIds.has(entry.info.id)).map((entry) => entry.client.getDiagnosticBinding?.(filePath)),
2964
+ ...carriedAuxiliary.map((entry) => entry.binding)
2965
+ ]);
2966
+ result.binding = binding;
2967
+ }
2968
+ const lateDeliveryPending = unconfirmedServerIds.filter((serverId) => (lateDeliveryServerIds ?? []).includes(serverId));
2969
+ if (lateDeliveryPending.length > 0) {
2970
+ result.deferredServerIds = lateDeliveryPending;
2971
+ }
2972
+ if (supersededServerIds.length > 0) {
2973
+ result.supersededServerIds = supersededServerIds;
2974
+ }
2975
+ logLatency({
2976
+ type: "phase",
2977
+ phase: "lsp_touch_file",
2978
+ filePath: normalizedPath,
2979
+ durationMs: Date.now() - startedAt,
2980
+ metadata: {
2981
+ serverCountReady: spawned.length,
2982
+ clientScope,
2983
+ diagnosticsMode,
2984
+ source,
2985
+ failureKind: "success",
2986
+ collectedDiagnostics: collected?.length,
2987
+ ...binding !== undefined && {
2988
+ bindingState: bindingStateLabel(binding.boundToCurrentDisk)
2989
+ },
2990
+ notifySkipped,
2991
+ notifyWriteTimedOut,
2992
+ ...notifyWriteTimedOutServerIds.length > 0 && {
2993
+ notifyWriteTimedOutServerIds
2994
+ },
2995
+ ...supersededServerIds.length > 0 && { supersededServerIds },
2996
+ diagnosticsTimedOut,
2997
+ inconclusive,
2998
+ ...verdict.inconclusiveServerIds && {
2999
+ inconclusiveServerIds: verdict.inconclusiveServerIds
3000
+ },
3001
+ ...verdict.inconclusiveReason && {
3002
+ inconclusiveReason: verdict.inconclusiveReason
3003
+ },
3004
+ ...result.confirmation !== undefined && {
3005
+ confirmation: result.confirmation
3006
+ },
3007
+ ...auxCutOffServerIds !== undefined && { auxCutOffServerIds },
3008
+ ...auxUnconfirmedServerIds !== undefined && {
3009
+ auxUnconfirmedServerIds
3010
+ },
3011
+ ...brokenSkippedServerIds.length > 0 && { brokenSkippedServerIds },
3012
+ ...uncoveredDeferredServerIds.length > 0 && {
3013
+ deferredResyncServerIds: uncoveredDeferredServerIds
3014
+ },
3015
+ ...lateDeliveryPending.length > 0 && {
3016
+ lateDeliveryServerIds: lateDeliveryPending
3017
+ },
3018
+ ...auxNoAnswerServerIds.length > 0 && { auxNoAnswerServerIds }
3019
+ }
3020
+ });
3021
+ return result;
3022
+ } finally {
3023
+ for (const lease of leaseKeys)
3024
+ this.releaseClientLease(lease.key, lease.server);
3025
+ }
3026
+ }
3027
+ async findOutsideProjectRoot(filePath) {
3028
+ if (!this.fileIsOutsideRegisteredRoots(filePath))
3029
+ return;
3030
+ if (this.analysisRootModeForFile(filePath) === "adopted")
3031
+ return;
3032
+ const candidates = await Promise.all(getServersForFileWithConfig(filePath).map((server) => server.root(filePath)));
3033
+ return candidates.find((candidate) => Boolean(candidate));
3034
+ }
3035
+ recordOutsideRootDecline(filePath, nearestRoot, operation) {
3036
+ const root = normalizeMapKey(nearestRoot);
3037
+ const sessionRoots = getSessionRootsForTelemetry().map((sessionRoot) => normalizeMapKey(sessionRoot)).join(", ");
3038
+ emitBounded("lsp_capability_skip", root, {
3039
+ filePath: normalizeMapKey(filePath),
3040
+ durationMs: 0,
3041
+ metadata: {
3042
+ operation,
3043
+ nearestMarkerRoot: root,
3044
+ sessionRoots,
3045
+ identity: root
3046
+ }
3047
+ }, {
3048
+ ledgerKind: "lsp-capability-skip",
3049
+ risingEdgePer: "identity",
3050
+ reason: `declined LSP root outside session cwd: ${root}`
3051
+ });
3052
+ }
3053
+ getDiagnosticsHealth(filePath) {
3054
+ return this.lastDiagnosticsHealth.get(normalizeMapKey(filePath));
3055
+ }
3056
+ getLastKnownDiagnostics(filePath, expectedContentHash) {
3057
+ const normalizedKey = normalizeMapKey(filePath);
3058
+ if (expectedContentHash !== undefined) {
3059
+ const knownHash = this.lastKnownContentHash.get(normalizedKey);
3060
+ if (knownHash === undefined || knownHash !== expectedContentHash) {
3061
+ return;
3062
+ }
3063
+ }
3064
+ return this.lastKnownDiagnostics.get(normalizedKey);
3065
+ }
3066
+ async getDiagnostics(filePath, diagnosticsMode = "full") {
3067
+ const normalizedPath = normalizeMapKey(filePath);
3068
+ const outsideRoot = await this.findOutsideProjectRoot(filePath);
3069
+ if (outsideRoot) {
3070
+ this.recordOutsideRootDecline(filePath, outsideRoot, "getDiagnostics");
3071
+ return [];
3072
+ }
3073
+ if (this.checkDestroyed()) {
3074
+ this.lastDiagnosticsHealth.set(normalizedPath, {
3075
+ health: "destroyed",
3076
+ failureKind: "destroyed",
3077
+ serverCountAttempted: 0,
3078
+ serverCountReady: 0,
3079
+ candidateServerIds: getServersForFileWithConfig(filePath).map((s) => s.id),
3080
+ mergedCount: 0,
3081
+ dedupDroppedCount: 0,
3082
+ checkedAt: new Date().toISOString()
3083
+ });
3084
+ return [];
3085
+ }
3086
+ const startedAt = Date.now();
3087
+ const candidateServerIds = getServersForFileWithConfig(filePath).map((s) => s.id);
3088
+ const { clients: spawned, serverCountAttempted } = await this.getClientsForFile(filePath);
3089
+ if (spawned.length === 0) {
3090
+ const stale = this.lastKnownDiagnostics.get(normalizedPath);
3091
+ const failureKind = stale?.length ? "no_clients_stale" : "no_clients";
3092
+ this.lastDiagnosticsHealth.set(normalizedPath, {
3093
+ health: failureKind,
3094
+ failureKind,
3095
+ serverCountAttempted,
3096
+ serverCountReady: 0,
3097
+ candidateServerIds,
3098
+ mergedCount: stale?.length ?? 0,
3099
+ dedupDroppedCount: 0,
3100
+ checkedAt: new Date().toISOString()
3101
+ });
3102
+ logLatency({
3103
+ type: "phase",
3104
+ phase: "lsp_diagnostics_aggregate",
3105
+ filePath: normalizedPath,
3106
+ durationMs: Date.now() - startedAt,
3107
+ metadata: {
3108
+ serverCountAttempted,
3109
+ serverCountReady: 0,
3110
+ mergedCount: stale?.length ?? 0,
3111
+ dedupDroppedCount: 0,
3112
+ failureKind,
3113
+ health: failureKind,
3114
+ servers: []
3115
+ }
3116
+ });
3117
+ return stale ?? [];
3118
+ }
3119
+ const clientWaits = spawned.map(async (entry) => {
3120
+ const waitStart = Date.now();
3121
+ const launchVariant = entry.client.getLaunchVariant?.();
3122
+ const strategy = getStrategy(entry.info.id, launchVariant);
3123
+ await entry.client.waitForDiagnostics(filePath, strategy.aggregateWaitMs);
3124
+ let diagnostics = entry.client.getDiagnostics(filePath);
3125
+ const firstWaitMs = Date.now() - waitStart;
3126
+ if (strategy.expectSemanticSecondPush && diagnostics.length === 0 && firstWaitMs < DIAGNOSTICS_SEMANTIC_SETTLE_THRESHOLD_MS) {
3127
+ await entry.client.waitForDiagnostics(filePath, DIAGNOSTICS_SEMANTIC_SETTLE_WAIT_MS);
3128
+ diagnostics = entry.client.getDiagnostics(filePath);
3129
+ }
3130
+ return {
3131
+ serverId: entry.info.id,
3132
+ launchVariant,
3133
+ waitMs: Date.now() - waitStart,
3134
+ diagnosticCount: diagnostics.length,
3135
+ diagnostics
3136
+ };
3137
+ });
3138
+ const graceMs = diagnosticsMode === "document" ? 0 : EARLY_UNBLOCK_GRACE_MS;
3139
+ const diagDescriptors = spawned.map((entry) => {
3140
+ if (!isAuxiliary(entry.info))
3141
+ return { role: entry.info.role };
3142
+ const strategy = getStrategy(entry.info.id, entry.client.getLaunchVariant?.());
3143
+ return { role: entry.info.role, budgetMs: strategy.aggregateWaitMs };
3144
+ });
3145
+ const perServer = await raceToCompletion(clientWaits, (results) => results.some((r) => r.diagnosticCount > 0 || getStrategy(r.serverId, r.launchVariant).seedFirstPush), {
3146
+ timeoutMs: Math.max(...spawned.map((entry) => getStrategy(entry.info.id, entry.client.getLaunchVariant?.()).aggregateWaitMs)),
3147
+ graceMs,
3148
+ descriptors: diagDescriptors,
3149
+ auxGraceMs: readEnvAuxGraceMs() ?? DEFAULT_AUX_GRACE_CEILING_MS
3150
+ });
3151
+ const earlyUnblockedCount = spawned.length - perServer.length;
3152
+ const perServerFull = spawned.map((entry) => {
3153
+ const found = perServer.find((r) => r.serverId === entry.info.id);
3154
+ return found ?? {
3155
+ serverId: entry.info.id,
3156
+ launchVariant: entry.client.getLaunchVariant?.(),
3157
+ waitMs: getStrategy(entry.info.id, entry.client.getLaunchVariant?.()).aggregateWaitMs,
3158
+ diagnosticCount: 0,
3159
+ diagnostics: []
3160
+ };
3161
+ });
3162
+ const merged = [];
3163
+ const seen = new Set;
3164
+ for (const entry of perServerFull) {
3165
+ for (const diagnostic of entry.diagnostics) {
3166
+ const key = [
3167
+ diagnostic.range.start.line,
3168
+ diagnostic.range.start.character,
3169
+ diagnostic.message
3170
+ ].join(":");
3171
+ if (seen.has(key))
3172
+ continue;
3173
+ seen.add(key);
3174
+ merged.push({ ...diagnostic, serverId: entry.serverId });
3175
+ }
3176
+ }
3177
+ const rawCount = perServerFull.reduce((sum, entry) => sum + entry.diagnosticCount, 0);
3178
+ const serversWithDiagnostics = perServerFull.filter((entry) => entry.diagnosticCount > 0).length;
3179
+ const failureKind = merged.length === 0 ? "ok_empty" : "success";
3180
+ this.lastDiagnosticsHealth.set(normalizedPath, {
3181
+ health: failureKind === "success" ? "ok" : "ok_empty",
3182
+ failureKind,
3183
+ serverCountAttempted,
3184
+ serverCountReady: perServerFull.length,
3185
+ candidateServerIds,
3186
+ mergedCount: merged.length,
3187
+ dedupDroppedCount: rawCount - merged.length,
3188
+ checkedAt: new Date().toISOString()
3189
+ });
3190
+ logLatency({
3191
+ type: "phase",
3192
+ phase: "lsp_diagnostics_aggregate",
3193
+ filePath: normalizedPath,
3194
+ durationMs: Date.now() - startedAt,
3195
+ metadata: {
3196
+ serverCountAttempted,
3197
+ serverCountReady: perServerFull.length,
3198
+ serverCountWithDiagnostics: serversWithDiagnostics,
3199
+ mergedCount: merged.length,
3200
+ dedupDroppedCount: rawCount - merged.length,
3201
+ earlyUnblockedCount,
3202
+ diagnosticsMode,
3203
+ failureKind,
3204
+ health: failureKind === "success" ? "ok" : "ok_empty",
3205
+ servers: perServerFull.map((entry) => ({
3206
+ id: entry.serverId,
3207
+ waitMs: entry.waitMs,
3208
+ diagnosticCount: entry.diagnosticCount
3209
+ }))
3210
+ }
3211
+ });
3212
+ if (merged.length > 0) {
3213
+ this.lastKnownDiagnostics.set(normalizedPath, merged);
3214
+ } else {
3215
+ this.lastKnownDiagnostics.delete(normalizedPath);
3216
+ }
3217
+ this.lastKnownContentHash.delete(normalizedPath);
3218
+ return merged;
3219
+ }
3220
+ hashContent(content) {
3221
+ return hashDiagnosticContent(content);
3222
+ }
3223
+ mergeBinding(filePath, stored) {
3224
+ const verdicts = [];
3225
+ let version;
3226
+ let contentHash;
3227
+ for (const entry of stored) {
3228
+ verdicts.push(this.diskBindingCache.boundToCurrentDisk(filePath, entry ?? {}));
3229
+ if (version === undefined && entry?.version !== undefined) {
3230
+ version = entry.version;
3231
+ }
3232
+ if (contentHash === undefined && entry?.contentHash !== undefined) {
3233
+ contentHash = entry.contentHash;
3234
+ }
3235
+ }
3236
+ return {
3237
+ version,
3238
+ contentHash,
3239
+ boundToCurrentDisk: composeBoundToCurrentDisk(verdicts)
3240
+ };
3241
+ }
3242
+ async definition(filePath, line, character) {
3243
+ const spawned = await this.getClientForFile(filePath, NAV_CLIENT_WAIT_TIMEOUT_MS);
3244
+ if (!spawned)
3245
+ return [];
3246
+ if (!spawned.client.getOperationSupport().definition) {
3247
+ throw new Error("__UNSUPPORTED__ Active LSP server does not advertise support for definition");
3248
+ }
3249
+ return spawned.client.definition(filePath, line, character);
3250
+ }
3251
+ async typeDefinition(filePath, line, character) {
3252
+ const spawned = await this.getClientForFile(filePath, NAV_CLIENT_WAIT_TIMEOUT_MS);
3253
+ if (!spawned)
3254
+ return [];
3255
+ if (!spawned.client.getOperationSupport().typeDefinition) {
3256
+ throw new Error("__UNSUPPORTED__ Active LSP server does not advertise support for typeDefinition");
3257
+ }
3258
+ return spawned.client.typeDefinition(filePath, line, character);
3259
+ }
3260
+ async declaration(filePath, line, character) {
3261
+ const spawned = await this.getClientForFile(filePath, NAV_CLIENT_WAIT_TIMEOUT_MS);
3262
+ if (!spawned)
3263
+ return [];
3264
+ if (!spawned.client.getOperationSupport().declaration) {
3265
+ throw new Error("__UNSUPPORTED__ Active LSP server does not advertise support for declaration");
3266
+ }
3267
+ return spawned.client.declaration(filePath, line, character);
3268
+ }
3269
+ async references(filePath, line, character, includeDeclaration = true) {
3270
+ const spawned = await this.getClientForFile(filePath, NAV_CLIENT_WAIT_TIMEOUT_MS);
3271
+ if (!spawned)
3272
+ return [];
3273
+ if (!spawned.client.getOperationSupport().references) {
3274
+ throw new Error("__UNSUPPORTED__ Active LSP server does not advertise support for references");
3275
+ }
3276
+ return spawned.client.references(filePath, line, character, includeDeclaration);
3277
+ }
3278
+ async hover(filePath, line, character) {
3279
+ const spawned = await this.getClientForFile(filePath, NAV_CLIENT_WAIT_TIMEOUT_MS);
3280
+ if (!spawned)
3281
+ return null;
3282
+ if (!spawned.client.getOperationSupport().hover) {
3283
+ throw new Error("__UNSUPPORTED__ Active LSP server does not advertise support for hover");
3284
+ }
3285
+ return spawned.client.hover(filePath, line, character);
3286
+ }
3287
+ async signatureHelp(filePath, line, character) {
3288
+ const spawned = await this.getClientForFile(filePath, NAV_CLIENT_WAIT_TIMEOUT_MS);
3289
+ if (!spawned)
3290
+ return null;
3291
+ if (!spawned.client.getOperationSupport().signatureHelp) {
3292
+ throw new Error("__UNSUPPORTED__ Active LSP server does not advertise support for signatureHelp");
3293
+ }
3294
+ return spawned.client.signatureHelp(filePath, line, character);
3295
+ }
3296
+ async documentSymbol(filePath) {
3297
+ const spawned = await this.getClientForFile(filePath, NAV_CLIENT_WAIT_TIMEOUT_MS);
3298
+ if (!spawned)
3299
+ return [];
3300
+ if (!spawned.client.getOperationSupport().documentSymbol) {
3301
+ throw new Error("__UNSUPPORTED__ Active LSP server does not advertise support for documentSymbol");
3302
+ }
3303
+ return spawned.client.documentSymbol(filePath);
3304
+ }
3305
+ selectWorkspaceScopeClient(predicate) {
3306
+ let auxFallback;
3307
+ for (const [key, client] of this.state.clients) {
3308
+ if (!client.isAlive())
3309
+ continue;
3310
+ if (predicate && !predicate(client))
3311
+ continue;
3312
+ const separator = key.indexOf(":");
3313
+ const serverId = separator >= 0 ? key.slice(0, separator) : key;
3314
+ if (isAuxiliary(getServerById(serverId))) {
3315
+ if (!auxFallback)
3316
+ auxFallback = { client, serverId };
3317
+ continue;
3318
+ }
3319
+ return { client, serverId };
3320
+ }
3321
+ return auxFallback;
3322
+ }
3323
+ async workspaceSymbol(query, filePath, attribution) {
3324
+ if (filePath) {
3325
+ const spawned = await this.getClientForFile(filePath, NAV_CLIENT_WAIT_TIMEOUT_MS);
3326
+ if (!spawned)
3327
+ return [];
3328
+ if (!spawned.client.getOperationSupport().workspaceSymbol)
3329
+ return [];
3330
+ return spawned.client.workspaceSymbol(query);
3331
+ }
3332
+ const target = this.selectWorkspaceScopeClient((client) => client.getOperationSupport().workspaceSymbol);
3333
+ if (!target)
3334
+ return [];
3335
+ if (attribution)
3336
+ attribution.workspaceSymbol = target.serverId;
3337
+ return target.client.workspaceSymbol(query);
3338
+ }
3339
+ async getAdvertisedCommands(filePath, attribution) {
3340
+ if (filePath) {
3341
+ const spawned = await this.getClientForFile(filePath, NAV_CLIENT_WAIT_TIMEOUT_MS);
3342
+ if (!spawned)
3343
+ return [];
3344
+ return spawned.client.getAdvertisedCommands();
3345
+ }
3346
+ const first = this.selectWorkspaceScopeClient();
3347
+ if (!first)
3348
+ return [];
3349
+ if (attribution)
3350
+ attribution.getAdvertisedCommands = first.serverId;
3351
+ return first.client.getAdvertisedCommands();
3352
+ }
3353
+ async executeCommand(filePath, command, args, mutationContext, attribution) {
3354
+ if (filePath) {
3355
+ const spawned = await this.getClientForFile(filePath, NAV_CLIENT_WAIT_TIMEOUT_MS);
3356
+ if (!spawned) {
3357
+ return { executed: false, reason: "no LSP server for file" };
3358
+ }
3359
+ return spawned.client.executeCommand(command, args, mutationContext);
3360
+ }
3361
+ const first = this.selectWorkspaceScopeClient();
3362
+ if (!first)
3363
+ return { executed: false, reason: "no active LSP server" };
3364
+ if (attribution)
3365
+ attribution.executeCommand = first.serverId;
3366
+ return first.client.executeCommand(command, args, mutationContext);
3367
+ }
3368
+ async executeReadOnlyCommandOnLiveClient(filePath, command, args) {
3369
+ if (this.checkDestroyed()) {
3370
+ return { executed: false, reason: "lsp service destroyed" };
3371
+ }
3372
+ for (const server of getServersForFileWithConfig(filePath)) {
3373
+ const root = await this.resolveServerRoot(server, filePath);
3374
+ if (!root)
3375
+ continue;
3376
+ const entry = this.state.clients.get(`${server.id}:${normalizeMapKey(root)}`);
3377
+ if (!entry?.isAlive())
3378
+ continue;
3379
+ const run = entry.executeReadOnlyCommand;
3380
+ if (typeof run !== "function")
3381
+ continue;
3382
+ return run.call(entry, command, args);
3383
+ }
3384
+ return { executed: false, reason: "no live LSP server for file" };
3385
+ }
3386
+ async getOperationSupport(filePath, attribution) {
3387
+ if (filePath) {
3388
+ const spawned = await this.getClientForFile(filePath);
3389
+ if (!spawned)
3390
+ return null;
3391
+ const getter = spawned.client.getOperationSupport;
3392
+ if (typeof getter !== "function")
3393
+ return null;
3394
+ return getter();
3395
+ }
3396
+ const readable = (client) => typeof client.getOperationSupport === "function";
3397
+ const first = this.selectWorkspaceScopeClient(readable);
3398
+ if (!first)
3399
+ return null;
3400
+ const aggregate = { ...first.client.getOperationSupport() };
3401
+ const contributors = {};
3402
+ for (const capability of Object.keys(aggregate)) {
3403
+ if (aggregate[capability]) {
3404
+ contributors[capability] = first.serverId;
3405
+ continue;
3406
+ }
3407
+ const supporter = this.selectWorkspaceScopeClient((client) => readable(client) && Boolean(client.getOperationSupport()[capability]));
3408
+ if (supporter) {
3409
+ aggregate[capability] = true;
3410
+ contributors[capability] = supporter.serverId;
3411
+ }
3412
+ }
3413
+ if (attribution) {
3414
+ attribution.getOperationSupport = {
3415
+ baseClientId: first.serverId,
3416
+ contributors
3417
+ };
3418
+ }
3419
+ return aggregate;
3420
+ }
3421
+ async getCapabilitySnapshots(filePath, attribution) {
3422
+ if (this.checkDestroyed())
3423
+ return [];
3424
+ const snapshots = [];
3425
+ if (filePath) {
3426
+ const servers = getServersForFileWithConfig(filePath);
3427
+ for (const server of servers) {
3428
+ const root = await this.resolveServerRoot(server, filePath);
3429
+ if (!root)
3430
+ continue;
3431
+ const client = this.state.clients.get(`${server.id}:${normalizeMapKey(root)}`);
3432
+ if (!client?.isAlive())
3433
+ continue;
3434
+ snapshots.push({
3435
+ serverId: server.id,
3436
+ root,
3437
+ operationSupport: client.getOperationSupport(),
3438
+ workspaceDiagnosticsSupport: client.getWorkspaceDiagnosticsSupport(),
3439
+ customServer: server.custom,
3440
+ diagnosticsPublished: this.state.diagnosticsPublished.has(server.id),
3441
+ diagnosticsUnsupported: this.state.diagnosticsUnsupported.has(server.id),
3442
+ advertisedCommands: client.getAdvertisedCommands(),
3443
+ rawCapabilityKeys: client.getRawCapabilityKeys?.() ?? [],
3444
+ textDocumentSave: textDocumentSaveOf(client),
3445
+ launchVariant: client.getLaunchVariant?.()
3446
+ });
3447
+ }
3448
+ return snapshots;
3449
+ }
3450
+ for (const [key, client] of this.state.clients) {
3451
+ if (!client.isAlive())
3452
+ continue;
3453
+ const separator = key.indexOf(":");
3454
+ const serverId = separator >= 0 ? key.slice(0, separator) : key;
3455
+ snapshots.push({
3456
+ serverId,
3457
+ root: client.root,
3458
+ operationSupport: client.getOperationSupport(),
3459
+ workspaceDiagnosticsSupport: client.getWorkspaceDiagnosticsSupport(),
3460
+ customServer: this.state.servers.get(serverId)?.custom,
3461
+ diagnosticsPublished: this.state.diagnosticsPublished.has(serverId),
3462
+ diagnosticsUnsupported: this.state.diagnosticsUnsupported.has(serverId),
3463
+ advertisedCommands: client.getAdvertisedCommands(),
3464
+ rawCapabilityKeys: client.getRawCapabilityKeys?.() ?? [],
3465
+ textDocumentSave: textDocumentSaveOf(client),
3466
+ launchVariant: client.getLaunchVariant?.()
3467
+ });
3468
+ }
3469
+ if (attribution) {
3470
+ attribution.getCapabilitySnapshots = {
3471
+ clientIds: snapshots.slice(0, WORKSPACE_ATTRIBUTION_CLIENT_CAP).map((snapshot) => snapshot.serverId),
3472
+ clientCount: snapshots.length
3473
+ };
3474
+ }
3475
+ return snapshots;
3476
+ }
3477
+ async getWorkspaceDiagnosticsSupport(filePath, attribution) {
3478
+ if (filePath) {
3479
+ const spawned = await this.getClientForFile(filePath);
3480
+ if (!spawned)
3481
+ return null;
3482
+ const getter = spawned.client.getWorkspaceDiagnosticsSupport;
3483
+ if (typeof getter !== "function")
3484
+ return null;
3485
+ return getter();
3486
+ }
3487
+ const first = this.selectWorkspaceScopeClient();
3488
+ if (!first)
3489
+ return null;
3490
+ const getter = first.client.getWorkspaceDiagnosticsSupport;
3491
+ if (typeof getter !== "function")
3492
+ return null;
3493
+ if (attribution)
3494
+ attribution.getWorkspaceDiagnosticsSupport = first.serverId;
3495
+ return getter.call(first.client);
3496
+ }
3497
+ async codeAction(filePath, line, character, endLine, endCharacter) {
3498
+ const spawned = await this.getClientForFile(filePath, NAV_CLIENT_WAIT_TIMEOUT_MS);
3499
+ if (!spawned)
3500
+ return [];
3501
+ if (!spawned.client.getOperationSupport().codeAction) {
3502
+ throw new Error("__UNSUPPORTED__ Active LSP server does not advertise support for codeAction");
3503
+ }
3504
+ return spawned.client.codeAction(filePath, line, character, endLine, endCharacter);
3505
+ }
3506
+ async rename(filePath, line, character, newName) {
3507
+ const spawned = await this.getClientForFile(filePath, NAV_CLIENT_WAIT_TIMEOUT_MS);
3508
+ if (!spawned)
3509
+ return null;
3510
+ if (!spawned.client.getOperationSupport().rename) {
3511
+ throw new Error("__UNSUPPORTED__ Active LSP server does not advertise support for rename");
3512
+ }
3513
+ return spawned.client.rename(filePath, line, character, newName);
3514
+ }
3515
+ async renameFile(oldFilePath, newFilePath, options) {
3516
+ const cwd = options.cwd;
3517
+ const apply = options.apply ?? false;
3518
+ await validateWorkspaceEdit({
3519
+ documentChanges: [
3520
+ {
3521
+ kind: "rename",
3522
+ oldUri: pathToFileURL(oldFilePath).href,
3523
+ newUri: pathToFileURL(newFilePath).href
3524
+ }
3525
+ ]
3526
+ }, cwd);
3527
+ const priorityServerIds = getServersForFileWithConfig(oldFilePath).map((server) => server.id);
3528
+ const activeClients = this.activeClientsForCwd(cwd, priorityServerIds);
3529
+ const willRenameFailures = [];
3530
+ const didRenameFailures = [];
3531
+ const willRenameClients = activeClients.filter(({ serverId, client }) => {
3532
+ if (client.getOperationSupport().willRenameFiles === true)
3533
+ return true;
3534
+ recordDegradationOnce({
3535
+ kind: "lsp-capability-skip",
3536
+ subject: `${serverId}:workspace/willRenameFiles`,
3537
+ reason: client.getMalformedFileOperationRegistrations().has("willRename") ? "malformed-registration" : "no-registration"
3538
+ });
3539
+ return false;
3540
+ });
3541
+ const willResults = await Promise.all(willRenameClients.map(async ({ serverId, client }) => {
3542
+ try {
3543
+ return {
3544
+ serverId,
3545
+ edit: await client.willRenameFiles(oldFilePath, newFilePath)
3546
+ };
3547
+ } catch (err) {
3548
+ willRenameFailures.push({
3549
+ serverId,
3550
+ error: err instanceof Error ? err.message : String(err)
3551
+ });
3552
+ return { serverId, edit: null };
3553
+ }
3554
+ }));
3555
+ const successfulWillResults = willResults.filter((result) => !willRenameFailures.some((failure) => failure.serverId === result.serverId));
3556
+ if (willRenameClients.length > 0 && successfulWillResults.length === 0) {
3557
+ throw new Error(`workspace/willRenameFiles failed for all active LSP servers: ${willRenameFailures.map((failure) => `${failure.serverId}: ${failure.error}`).join("; ")}`);
3558
+ }
3559
+ const merged = mergeWorkspaceTextEditsByPriority(successfulWillResults);
3560
+ const summary = summarizeWorkspaceEdit(merged.edit, cwd);
3561
+ if (!apply) {
3562
+ return {
3563
+ applied: false,
3564
+ serverIds: activeClients.map((entry) => entry.serverId),
3565
+ willRenameFailures,
3566
+ didRenameFailures,
3567
+ droppedConflicts: merged.droppedConflicts,
3568
+ inputEditCount: merged.inputEditCount,
3569
+ summary
3570
+ };
3571
+ }
3572
+ let applied;
3573
+ try {
3574
+ applied = await applyWorkspaceEdit(merged.edit, cwd, {
3575
+ mutationContext: options.mutationContext,
3576
+ observe: false
3577
+ });
3578
+ } catch (err) {
3579
+ if (options.mutationContext) {
3580
+ const partial = err.appliedWorkspaceEdit;
3581
+ if (partial) {
3582
+ recordLspMutation(options.mutationContext, {
3583
+ results: [partial],
3584
+ status: "failed"
3585
+ });
3586
+ }
3587
+ }
3588
+ throw err;
3589
+ }
3590
+ const openDocuments = activeClients.filter(({ client }) => client.isDocumentOpen(oldFilePath)).map(({ serverId, client }) => ({
3591
+ serverId,
3592
+ client,
3593
+ oldUri: client.getDocumentUri(oldFilePath)
3594
+ }));
3595
+ const closeFailures = [];
3596
+ await Promise.all(activeClients.map(async ({ serverId, client }) => {
3597
+ const result = await runRenameNotify(() => client.closeDocument(oldFilePath), RENAME_NOTIFY_TIMEOUT_MS);
3598
+ if (!result.ok) {
3599
+ closeFailures.push({
3600
+ serverId,
3601
+ error: result.error,
3602
+ disposition: result.disposition
3603
+ });
3604
+ }
3605
+ }));
3606
+ if (closeFailures.length > 0) {
3607
+ if (options.mutationContext) {
3608
+ recordLspMutation(options.mutationContext, {
3609
+ results: [applied],
3610
+ status: "failed"
3611
+ });
3612
+ }
3613
+ const content = await fs.readFile(oldFilePath, "utf-8");
3614
+ const languageId = getLanguageId(oldFilePath) ?? "plaintext";
3615
+ const resyncFailures = [];
3616
+ await Promise.all(openDocuments.map(async ({ serverId, client }) => {
3617
+ const resyncResult = await runRenameNotify(() => client.notify.open(oldFilePath, content, languageId, true, true).then((sent) => {
3618
+ if (sent === false) {
3619
+ throw new Error(client.isAlive() ? "re-open not sent: the close is still queued" : "re-open not sent: the client is dead");
3620
+ }
3621
+ }), RENAME_NOTIFY_TIMEOUT_MS);
3622
+ if (!resyncResult.ok) {
3623
+ resyncFailures.push({
3624
+ serverId,
3625
+ error: resyncResult.error,
3626
+ disposition: resyncResult.disposition
3627
+ });
3628
+ }
3629
+ }));
3630
+ if (resyncFailures.length > 0) {
3631
+ logLatency({
3632
+ type: "phase",
3633
+ phase: "lsp_rename_resync_failed",
3634
+ filePath: oldFilePath,
3635
+ durationMs: 0,
3636
+ metadata: {
3637
+ failures: resyncFailures.map((failure) => ({
3638
+ serverId: failure.serverId,
3639
+ disposition: failure.disposition,
3640
+ error: failure.error
3641
+ }))
3642
+ }
3643
+ });
3644
+ }
3645
+ const closeFailureSummary = closeFailures.map((failure) => `${failure.serverId} (${failure.disposition}): ${failure.error}`).join("; ");
3646
+ const resyncFailureSummary = resyncFailures.length > 0 ? ` (resync also failed: ${resyncFailures.map((failure) => `${failure.serverId} (${failure.disposition}): ${failure.error}`).join("; ")})` : "";
3647
+ throw new Error(`workspace/didClose failed; rename aborted: ${closeFailureSummary}${resyncFailureSummary}`);
3648
+ }
3649
+ let renameApplied;
3650
+ try {
3651
+ renameApplied = await applyWorkspaceEdit({
3652
+ documentChanges: [
3653
+ {
3654
+ kind: "rename",
3655
+ oldUri: pathToFileURL(oldFilePath).href,
3656
+ newUri: pathToFileURL(newFilePath).href
3657
+ }
3658
+ ]
3659
+ }, cwd, { observe: false });
3660
+ } catch (err) {
3661
+ if (options.mutationContext) {
3662
+ recordLspMutation(options.mutationContext, {
3663
+ results: [applied],
3664
+ status: "failed"
3665
+ });
3666
+ }
3667
+ throw err;
3668
+ }
3669
+ const relOld = path.relative(cwd, oldFilePath).replace(/\\/g, "/") || path.basename(oldFilePath);
3670
+ const relNew = path.relative(cwd, newFilePath).replace(/\\/g, "/") || path.basename(newFilePath);
3671
+ const renameDescription = `Renamed ${relOld} → ${relNew}`;
3672
+ await Promise.all(activeClients.map(async ({ serverId, client }) => {
3673
+ if (client.getOperationSupport().didRenameFiles !== true) {
3674
+ recordDegradationOnce({
3675
+ kind: "lsp-capability-skip",
3676
+ subject: `${serverId}:workspace/didRenameFiles`,
3677
+ reason: client.getMalformedFileOperationRegistrations().has("didRename") ? "malformed-registration" : "no-registration"
3678
+ });
3679
+ return;
3680
+ }
3681
+ const opened = openDocuments.find((entry) => entry.serverId === serverId);
3682
+ const result = await runRenameNotify(() => opened?.oldUri ? client.didRenameFiles(oldFilePath, newFilePath, opened.oldUri, destinationUriPreservingSpelling(opened.oldUri, oldFilePath, newFilePath)) : client.didRenameFiles(oldFilePath, newFilePath), RENAME_NOTIFY_TIMEOUT_MS);
3683
+ if (!result.ok) {
3684
+ didRenameFailures.push({
3685
+ serverId,
3686
+ error: result.error,
3687
+ disposition: result.disposition
3688
+ });
3689
+ }
3690
+ }));
3691
+ const files = [...new Set([...applied.files, oldFilePath, newFilePath])];
3692
+ if (options.mutationContext) {
3693
+ recordLspMutation(options.mutationContext, {
3694
+ results: [
3695
+ {
3696
+ ...applied,
3697
+ files,
3698
+ operationTotal: applied.operationTotal + renameApplied.operationTotal,
3699
+ appliedOperationTotal: applied.appliedOperationTotal + renameApplied.appliedOperationTotal,
3700
+ appliedOperationIndexes: [
3701
+ ...applied.appliedOperationIndexes,
3702
+ ...renameApplied.appliedOperationIndexes.map((index) => applied.operationTotal + index)
3703
+ ],
3704
+ operationCounts: {
3705
+ textEdits: applied.operationCounts.textEdits + renameApplied.operationCounts.textEdits,
3706
+ create: applied.operationCounts.create + renameApplied.operationCounts.create,
3707
+ rename: applied.operationCounts.rename + renameApplied.operationCounts.rename,
3708
+ delete: applied.operationCounts.delete + renameApplied.operationCounts.delete
3709
+ },
3710
+ fileDetails: [...applied.fileDetails, ...renameApplied.fileDetails]
3711
+ }
3712
+ ],
3713
+ status: "success"
3714
+ });
3715
+ }
3716
+ return {
3717
+ applied: true,
3718
+ serverIds: activeClients.map((entry) => entry.serverId),
3719
+ willRenameFailures,
3720
+ didRenameFailures,
3721
+ droppedConflicts: merged.droppedConflicts,
3722
+ inputEditCount: merged.inputEditCount,
3723
+ summary,
3724
+ descriptions: [...applied.descriptions, renameDescription],
3725
+ files
3726
+ };
3727
+ }
3728
+ async implementation(filePath, line, character) {
3729
+ const spawned = await this.getClientForFile(filePath, NAV_CLIENT_WAIT_TIMEOUT_MS);
3730
+ if (!spawned)
3731
+ return [];
3732
+ if (!spawned.client.getOperationSupport().implementation) {
3733
+ throw new Error("__UNSUPPORTED__ Active LSP server does not advertise support for implementation");
3734
+ }
3735
+ return spawned.client.implementation(filePath, line, character);
3736
+ }
3737
+ async prepareCallHierarchy(filePath, line, character) {
3738
+ const spawned = await this.getClientForFile(filePath, NAV_CLIENT_WAIT_TIMEOUT_MS);
3739
+ if (!spawned)
3740
+ return [];
3741
+ if (!spawned.client.getOperationSupport().callHierarchy) {
3742
+ throw new Error("__UNSUPPORTED__ Active LSP server does not advertise support for prepareCallHierarchy");
3743
+ }
3744
+ return spawned.client.prepareCallHierarchy(filePath, line, character);
3745
+ }
3746
+ async incomingCalls(item) {
3747
+ const spawned = await this.getClientForFile(uriToPath(item.uri), NAV_CLIENT_WAIT_TIMEOUT_MS);
3748
+ if (!spawned)
3749
+ return [];
3750
+ if (!spawned.client.getOperationSupport().callHierarchy)
3751
+ return [];
3752
+ return spawned.client.incomingCalls(item);
3753
+ }
3754
+ async outgoingCalls(item) {
3755
+ const spawned = await this.getClientForFile(uriToPath(item.uri), NAV_CLIENT_WAIT_TIMEOUT_MS);
3756
+ if (!spawned)
3757
+ return [];
3758
+ if (!spawned.client.getOperationSupport().callHierarchy)
3759
+ return [];
3760
+ return spawned.client.outgoingCalls(item);
3761
+ }
3762
+ async ensureWarmForSweep(representativeFile, options = {}) {
3763
+ if (this.checkDestroyed() || options.signal?.aborted) {
3764
+ return { performedWarmup: false, failedServerIds: [] };
3765
+ }
3766
+ const servers = getServersForFileWithConfig(representativeFile).filter((s) => !isAuxiliary(s));
3767
+ if (servers.length === 0) {
3768
+ return { performedWarmup: false, failedServerIds: [] };
3769
+ }
3770
+ const keys = await Promise.all(servers.map((server) => this.demonstratedReadyKeyFor(server, representativeFile)));
3771
+ const headOf = new Map;
3772
+ const familyHead = servers.map((server) => {
3773
+ const preferredHead = server.fallbackFor === undefined ? undefined : headOf.get(server.fallbackFor);
3774
+ const head = preferredHead ?? server.id;
3775
+ headOf.set(server.id, head);
3776
+ return head;
3777
+ });
3778
+ const isColdAt = (i) => {
3779
+ const key = keys[i];
3780
+ if (key === undefined || this.state.demonstratedReady.has(key)) {
3781
+ return false;
3782
+ }
3783
+ return !servers.some((_, j) => {
3784
+ const peerKey = keys[j];
3785
+ return peerKey !== undefined && familyHead[j] === familyHead[i] && this.state.demonstratedReady.has(peerKey);
3786
+ });
3787
+ };
3788
+ const alreadyWarm = servers.every((_, i) => !isColdAt(i));
3789
+ if (alreadyWarm)
3790
+ return { performedWarmup: false, failedServerIds: [] };
3791
+ const cachedColdServerIds = [];
3792
+ let allNonWarmCached = true;
3793
+ for (let i = 0;i < servers.length; i++) {
3794
+ const key = keys[i];
3795
+ if (key === undefined || !isColdAt(i))
3796
+ continue;
3797
+ if (this.state.demonstratedCold.has(key)) {
3798
+ cachedColdServerIds.push(servers[i].id);
3799
+ } else {
3800
+ allNonWarmCached = false;
3801
+ }
3802
+ }
3803
+ if (allNonWarmCached && cachedColdServerIds.length > 0) {
3804
+ logLatency({
3805
+ type: "phase",
3806
+ phase: "lsp_sweep_warmup_cached_cold",
3807
+ filePath: representativeFile,
3808
+ durationMs: 0,
3809
+ metadata: { serverIds: cachedColdServerIds }
3810
+ });
3811
+ return {
3812
+ performedWarmup: false,
3813
+ failedServerIds: cachedColdServerIds,
3814
+ skippedFromCache: true
3815
+ };
3816
+ }
3817
+ let content;
3818
+ try {
3819
+ content = await nodeFs.promises.readFile(representativeFile, "utf-8");
3820
+ } catch {
3821
+ return { performedWarmup: false, failedServerIds: [] };
3822
+ }
3823
+ if (options.signal?.aborted) {
3824
+ return { performedWarmup: false, failedServerIds: [] };
3825
+ }
3826
+ const timeoutMs = options.timeoutMs ?? warmupTimeoutMs();
3827
+ const rootedServerIds = [];
3828
+ const unrootedServerIds = [];
3829
+ for (let i = 0;i < servers.length; i++) {
3830
+ if (keys[i] !== undefined)
3831
+ rootedServerIds.push(servers[i].id);
3832
+ else
3833
+ unrootedServerIds.push(servers[i].id);
3834
+ }
3835
+ const unrootedCandidates = {
3836
+ count: unrootedServerIds.length,
3837
+ ids: unrootedServerIds
3838
+ };
3839
+ const stillColdServerIds = () => {
3840
+ const cold = [];
3841
+ for (let i = 0;i < servers.length; i++) {
3842
+ if (isColdAt(i))
3843
+ cold.push(servers[i].id);
3844
+ }
3845
+ return cold;
3846
+ };
3847
+ const runWarmupTouch = async (attempt) => {
3848
+ if (options.signal?.aborted)
3849
+ return;
3850
+ const startedAt = Date.now();
3851
+ logLatency({
3852
+ type: "phase",
3853
+ phase: "lsp_sweep_warmup_start",
3854
+ filePath: representativeFile,
3855
+ durationMs: 0,
3856
+ metadata: {
3857
+ serverIds: rootedServerIds,
3858
+ unrootedCandidates,
3859
+ timeoutMs,
3860
+ attempt
3861
+ }
3862
+ });
3863
+ const warmupAttempt = this.touchFile(representativeFile, content, {
3864
+ diagnostics: "document",
3865
+ collectDiagnostics: false,
3866
+ clientScope: "primary",
3867
+ source: "lsp_sweep_warmup",
3868
+ maxClientWaitMs: timeoutMs,
3869
+ maxDiagnosticsWaitMs: timeoutMs,
3870
+ warmupOverride: true,
3871
+ warmupAttempt: attempt
3872
+ });
3873
+ await (options.signal ? Promise.race([
3874
+ withDeadline(warmupAttempt, {
3875
+ ms: timeoutMs,
3876
+ onTimeout: "undefined"
3877
+ }),
3878
+ new Promise((resolve) => {
3879
+ if (options.signal.aborted) {
3880
+ resolve();
3881
+ return;
3882
+ }
3883
+ options.signal.addEventListener("abort", () => resolve(), {
3884
+ once: true
3885
+ });
3886
+ })
3887
+ ]) : withDeadline(warmupAttempt, {
3888
+ ms: timeoutMs,
3889
+ onTimeout: "undefined"
3890
+ }));
3891
+ const coldServerIds = stillColdServerIds();
3892
+ logLatency({
3893
+ type: "phase",
3894
+ phase: options.signal?.aborted ? "lsp_sweep_warmup_aborted" : coldServerIds.length > 0 ? "lsp_sweep_warmup_failed" : "lsp_sweep_warmup_done",
3895
+ filePath: representativeFile,
3896
+ durationMs: Date.now() - startedAt,
3897
+ metadata: {
3898
+ serverIds: rootedServerIds,
3899
+ unrootedCandidates,
3900
+ timeoutMs,
3901
+ attempt,
3902
+ coldServerIds
3903
+ }
3904
+ });
3905
+ };
3906
+ await runWarmupTouch(1);
3907
+ let failedServerIds = stillColdServerIds();
3908
+ const warmedClients = keys.map((key) => key === undefined ? undefined : this.state.clients.get(key));
3909
+ if (failedServerIds.length > 0 && !options.signal?.aborted) {
3910
+ const backoffMs = warmupRetryBackoffMs();
3911
+ if (backoffMs > 0) {
3912
+ await new Promise((resolve) => {
3913
+ const timer = setTimeout(resolve, backoffMs);
3914
+ timer.unref?.();
3915
+ options.signal?.addEventListener("abort", () => resolve(), {
3916
+ once: true
3917
+ });
3918
+ });
3919
+ }
3920
+ if (!options.signal?.aborted) {
3921
+ await runWarmupTouch(2);
3922
+ failedServerIds = stillColdServerIds();
3923
+ }
3924
+ }
3925
+ if (failedServerIds.length > 0) {
3926
+ for (let i = 0;i < servers.length; i++) {
3927
+ const key = keys[i];
3928
+ if (key !== undefined && failedServerIds.includes(servers[i].id) && this.state.clients.get(key) === warmedClients[i]) {
3929
+ this.state.demonstratedCold.add(key);
3930
+ }
3931
+ }
3932
+ }
3933
+ return { performedWarmup: true, failedServerIds };
3934
+ }
3935
+ async runWorkspaceDiagnostics(cwd, options = {}) {
3936
+ const releaseSweepHold = acquireWorkspaceSweepHold();
3937
+ try {
3938
+ return await this.runWorkspaceDiagnosticsSwept(cwd, options);
3939
+ } finally {
3940
+ releaseSweepHold();
3941
+ }
3942
+ }
3943
+ async runWorkspaceDiagnosticsSwept(cwd, options = {}) {
3944
+ const startedAt = Date.now();
3945
+ const root = path.resolve(cwd);
3946
+ const { signal } = options;
3947
+ this.sweepDocumentDrift().catch(() => {});
3948
+ const maxFiles = typeof options.maxFiles === "number" && Number.isFinite(options.maxFiles) && options.maxFiles > 0 ? Math.floor(options.maxFiles) : getMaxWorkspaceDiagnosticFiles();
3949
+ const files = options.files ? options.files.slice(0, maxFiles) : await collectWorkspaceDiagnosticFiles(root, maxFiles, signal);
3950
+ const perFileMs = (() => {
3951
+ const raw = Number(process.env.DM_LENS_LSP_WORKSPACE_PER_FILE_MS);
3952
+ return Number.isFinite(raw) && raw > 0 ? raw : 15000;
3953
+ })();
3954
+ const results = [];
3955
+ let completed = 0;
3956
+ let timedOutFiles = 0;
3957
+ let lastHeartbeat = Date.now();
3958
+ const workspaceDiagnosticsCacheCtx = createWorkspaceDiagnosticsCacheContext(root);
3959
+ const workspaceSweepScopeKey = buildScopeKey("all", [
3960
+ ...WORKSPACE_SWEEP_EXCLUDED_SERVER_IDS
3961
+ ]);
3962
+ const touchScopedOpenDependencies = async (filePath) => {
3963
+ if (options.files === undefined)
3964
+ return true;
3965
+ const imports = workspaceDiagnosticsCacheCtx.importsFor(filePath);
3966
+ if (!imports)
3967
+ return false;
3968
+ const openImports = imports.filter((dependency) => this.hasLiveClientHoldingDocument(dependency));
3969
+ const capped = openImports.length > SCOPED_DEPENDENCY_TOUCH_MAX;
3970
+ if (capped) {
3971
+ recordDegradationOnce({
3972
+ kind: "lsp_dependency_touch_capped",
3973
+ subject: filePath,
3974
+ reason: `scoped full scan limited open import touches to ${SCOPED_DEPENDENCY_TOUCH_MAX}`
3975
+ });
3976
+ }
3977
+ await Promise.all(openImports.slice(0, SCOPED_DEPENDENCY_TOUCH_MAX).map(async (dependency) => {
3978
+ let content;
3979
+ try {
3980
+ content = await fs.readFile(dependency, "utf8");
3981
+ } catch {
3982
+ return;
3983
+ }
3984
+ await this.touchFile(dependency, content, {
3985
+ diagnostics: "none",
3986
+ source: "lens_diagnostics_full_dependency",
3987
+ clientScope: "all",
3988
+ excludeServerIds: await this.serverIdsNotHoldingDocument(dependency)
3989
+ });
3990
+ }));
3991
+ return !capped;
3992
+ };
3993
+ const cachedResults = [];
3994
+ const filesToTouch = [];
3995
+ const writeIndexByPath = new Map;
3996
+ for (const filePath of files) {
3997
+ writeIndexByPath.set(normalizeMapKey(filePath), options.nextWriteIndex?.());
3998
+ const cached = workspaceDiagnosticsCacheCtx.lookup(filePath, workspaceSweepScopeKey);
3999
+ if (cached && cached.binding.boundToCurrentDisk !== false) {
4000
+ const dependenciesConfirmed = await touchScopedOpenDependencies(filePath);
4001
+ if (dependenciesConfirmed)
4002
+ cachedResults.push({
4003
+ filePath,
4004
+ diagnostics: cached.diagnostics,
4005
+ count: cached.count,
4006
+ observedAt: cached.scannedAt,
4007
+ contentHash: cached.binding.contentHash,
4008
+ boundToCurrentDisk: cached.binding.boundToCurrentDisk,
4009
+ writeIndex: writeIndexByPath.get(normalizeMapKey(filePath))
4010
+ });
4011
+ else
4012
+ filesToTouch.push(filePath);
4013
+ } else {
4014
+ filesToTouch.push(filePath);
4015
+ }
4016
+ }
4017
+ completed = cachedResults.length;
4018
+ if (cachedResults.length > 0) {
4019
+ options.onProgress?.(completed, files.length);
4020
+ }
4021
+ const cacheServedKeys = new Set(cachedResults.map((result) => normalizeMapKey(result.filePath)));
4022
+ const supersededCacheKeys = new Set;
4023
+ const pullAnsweredFiles = new Set;
4024
+ const scannedMtimeByFile = new Map;
4025
+ const scannedSizeByFile = new Map;
4026
+ const scannedAtByFile = new Map;
4027
+ const noteScanStat = (filePath) => {
4028
+ scannedAtByFile.set(filePath, Date.now());
4029
+ try {
4030
+ const scanStat = nodeFs.statSync(filePath);
4031
+ scannedMtimeByFile.set(filePath, scanStat.mtimeMs);
4032
+ scannedSizeByFile.set(filePath, scanStat.size);
4033
+ } catch {}
4034
+ };
4035
+ const groups = groupFilesByPrimaryServer(filesToTouch);
4036
+ const sweepIndexGate = createSweepIndexGate();
4037
+ const workspacePullEnabled = process.env.DM_LENS_LSP_WORKSPACE_PULL === "1";
4038
+ logLatency({
4039
+ type: "phase",
4040
+ phase: "lsp_workspace_diagnostics_start",
4041
+ filePath: root,
4042
+ durationMs: 0,
4043
+ metadata: {
4044
+ fileCount: files.length,
4045
+ cacheHits: cachedResults.length,
4046
+ filesToTouch: filesToTouch.length,
4047
+ maxFiles,
4048
+ perFileMs,
4049
+ serverGroups: groups.length
4050
+ }
4051
+ });
4052
+ const contentCache = new Map;
4053
+ const preOpenGroupFiles = async (groupFiles) => {
4054
+ if (isWarmAttached())
4055
+ return;
4056
+ for (const filePath of groupFiles) {
4057
+ if (signal?.aborted)
4058
+ return;
4059
+ let content;
4060
+ noteScanStat(filePath);
4061
+ try {
4062
+ content = await nodeFs.promises.readFile(filePath, "utf-8");
4063
+ } catch {
4064
+ continue;
4065
+ }
4066
+ contentCache.set(filePath, content);
4067
+ const languageId = getLanguageId(filePath) ?? "plaintext";
4068
+ const preOpenAttempt = withDeadline((async () => {
4069
+ const { clients } = await this.getClientsForFile(filePath, WORKSPACE_SWEEP_EXCLUDED_SERVER_IDS);
4070
+ for (const entry of clients) {
4071
+ let auxKey;
4072
+ if (isAuxiliary(entry.info)) {
4073
+ auxKey = await this.demonstratedReadyKeyFor(entry.info, filePath);
4074
+ if (auxKey && this.auxNotifyBacklogAtCeiling(auxKey, entry)) {
4075
+ continue;
4076
+ }
4077
+ }
4078
+ try {
4079
+ const sent = await entry.client.notify.open(filePath, content, languageId);
4080
+ if (auxKey && sent !== false) {
4081
+ this.noteAuxNotifyIssued(auxKey, entry.client);
4082
+ }
4083
+ } catch {}
4084
+ }
4085
+ })(), { ms: perFileMs, onTimeout: "undefined" });
4086
+ await (signal ? Promise.race([
4087
+ preOpenAttempt,
4088
+ new Promise((resolve) => {
4089
+ if (signal.aborted) {
4090
+ resolve();
4091
+ return;
4092
+ }
4093
+ signal.addEventListener("abort", () => resolve(), {
4094
+ once: true
4095
+ });
4096
+ })
4097
+ ]) : preOpenAttempt);
4098
+ }
4099
+ };
4100
+ const markServiceDestroyed = (remainingFiles) => {
4101
+ for (const filePath of remainingFiles) {
4102
+ results.push({
4103
+ filePath,
4104
+ diagnostics: [],
4105
+ count: 0,
4106
+ timedOut: true,
4107
+ unconfirmedReason: "service_destroyed",
4108
+ writeIndex: writeIndexByPath.get(normalizeMapKey(filePath))
4109
+ });
4110
+ timedOutFiles += 1;
4111
+ completed += 1;
4112
+ }
4113
+ options.onProgress?.(completed, files.length);
4114
+ };
4115
+ const processFile = async (filePath) => {
4116
+ try {
4117
+ let content = contentCache.get(filePath);
4118
+ if (content === undefined) {
4119
+ noteScanStat(filePath);
4120
+ content = await nodeFs.promises.readFile(filePath, "utf-8");
4121
+ }
4122
+ const attached = isWarmAttached() ? await tryWarmAttachedDiagnostics(filePath, content, perFileMs, "sweep") : undefined;
4123
+ if (attached && !attached.available) {
4124
+ await this.ensureWarmForSweep(filePath, { signal });
4125
+ }
4126
+ const touchResult = attached?.available ? {
4127
+ diags: attached.response.diagnostics,
4128
+ ...attached.response.unconfirmedServerIds !== undefined && {
4129
+ unconfirmedServerIds: attached.response.unconfirmedServerIds
4130
+ }
4131
+ } : await withDeadline(this.touchFile(filePath, content, {
4132
+ diagnostics: "document",
4133
+ collectDiagnostics: true,
4134
+ clientScope: "all",
4135
+ source: "lens_diagnostics_full",
4136
+ excludeServerIds: WORKSPACE_SWEEP_EXCLUDED_SERVER_IDS,
4137
+ sweepIndexGate
4138
+ }), { ms: perFileMs, onTimeout: "undefined" });
4139
+ const diagnostics = touchResult?.diags;
4140
+ const inconclusive = touchResult?.inconclusive === true;
4141
+ const unconfirmedServerIds = touchCoverageGap(touchResult);
4142
+ const coverageGap = unconfirmedServerIds.length > 0;
4143
+ const declinedOutsideRoot = touchResult?.skipReason === "outside-project-root";
4144
+ const timedOut = touchResult === undefined || inconclusive || declinedOutsideRoot;
4145
+ if (timedOut)
4146
+ timedOutFiles += 1;
4147
+ const unconfirmedReason = touchResult === undefined ? "budget" : declinedOutsideRoot ? "outside_project_root" : inconclusive ? "inconclusive" : undefined;
4148
+ const rawBinding = touchResult?.binding;
4149
+ const filteredDiagnostics = diagnostics ? applyAuxiliarySuppressions(diagnostics, content, {
4150
+ fileRole: detectFileRole(filePath, content),
4151
+ filePath,
4152
+ scanRoot: root
4153
+ }) : diagnostics;
4154
+ results.push({
4155
+ filePath,
4156
+ diagnostics: filteredDiagnostics ?? [],
4157
+ count: filteredDiagnostics?.length ?? 0,
4158
+ ...timedOut && { timedOut: true, unconfirmedReason },
4159
+ ...coverageGap && {
4160
+ unconfirmedServerIds: [...unconfirmedServerIds]
4161
+ },
4162
+ contentHash: rawBinding?.contentHash,
4163
+ boundToCurrentDisk: rawBinding?.boundToCurrentDisk,
4164
+ writeIndex: writeIndexByPath.get(normalizeMapKey(filePath))
4165
+ });
4166
+ } catch (err) {
4167
+ results.push({
4168
+ filePath,
4169
+ diagnostics: [],
4170
+ count: 0,
4171
+ error: err instanceof Error ? err.message : String(err),
4172
+ timedOut: true,
4173
+ unconfirmedReason: "error",
4174
+ writeIndex: writeIndexByPath.get(normalizeMapKey(filePath))
4175
+ });
4176
+ }
4177
+ completed += 1;
4178
+ options.onProgress?.(completed, files.length);
4179
+ if (Date.now() - lastHeartbeat >= 1e4) {
4180
+ lastHeartbeat = Date.now();
4181
+ logLatency({
4182
+ type: "phase",
4183
+ phase: "lsp_workspace_diagnostics_progress",
4184
+ filePath: root,
4185
+ durationMs: Date.now() - startedAt,
4186
+ metadata: {
4187
+ completed,
4188
+ total: files.length,
4189
+ timedOutFiles,
4190
+ aborted: signal?.aborted ?? false
4191
+ }
4192
+ });
4193
+ }
4194
+ };
4195
+ const groupWorkers = Math.min(WORKSPACE_DIAGNOSTICS_CONCURRENCY, groups.length);
4196
+ const sweepPhase = phaseStarted("lsp_workspace_diagnostics_touch");
4197
+ try {
4198
+ await runPerServerGroups(groups, groupWorkers, async (group) => {
4199
+ if (signal?.aborted)
4200
+ return;
4201
+ if (this.checkDestroyed()) {
4202
+ markServiceDestroyed(group.files);
4203
+ return;
4204
+ }
4205
+ if (!isWarmAttached() && workspacePullEnabled && !group.multiServer) {
4206
+ const pullStartedAt = Date.now();
4207
+ const pulled = await this.tryWorkspacePull(group.files, perFileMs, cacheServedKeys);
4208
+ if (pulled) {
4209
+ for (const clean of pulled.extraClean) {
4210
+ supersededCacheKeys.add(normalizeMapKey(clean.filePath));
4211
+ }
4212
+ for (const result of [...pulled.results, ...pulled.extraClean]) {
4213
+ results.push({
4214
+ ...result,
4215
+ writeIndex: writeIndexByPath.get(normalizeMapKey(result.filePath))
4216
+ });
4217
+ pullAnsweredFiles.add(result.filePath);
4218
+ try {
4219
+ const pullStat = nodeFs.statSync(result.filePath);
4220
+ scannedMtimeByFile.set(result.filePath, pullStat.mtimeMs);
4221
+ scannedSizeByFile.set(result.filePath, pullStat.size);
4222
+ scannedAtByFile.set(result.filePath, pullStartedAt);
4223
+ } catch {}
4224
+ }
4225
+ completed += group.files.length;
4226
+ options.onProgress?.(completed, files.length);
4227
+ return;
4228
+ }
4229
+ }
4230
+ const first = group.files[0];
4231
+ if (first && !isWarmAttached()) {
4232
+ const warmup = await this.ensureWarmForSweep(first, { signal });
4233
+ if (signal?.aborted)
4234
+ return;
4235
+ if (warmup.failedServerIds.length > 0) {
4236
+ logLatency({
4237
+ type: "phase",
4238
+ phase: "lsp_sweep_group_skipped_warmup",
4239
+ filePath: first,
4240
+ durationMs: 0,
4241
+ metadata: {
4242
+ failedServerIds: warmup.failedServerIds,
4243
+ skippedFromCache: warmup.skippedFromCache ?? false,
4244
+ skippedFiles: group.files.length
4245
+ }
4246
+ });
4247
+ for (const filePath of group.files) {
4248
+ results.push({
4249
+ filePath,
4250
+ diagnostics: [],
4251
+ count: 0,
4252
+ timedOut: true,
4253
+ unconfirmedReason: "inconclusive",
4254
+ skippedWarmupFailure: true
4255
+ });
4256
+ timedOutFiles += 1;
4257
+ completed += 1;
4258
+ }
4259
+ options.onProgress?.(completed, files.length);
4260
+ return;
4261
+ }
4262
+ if (warmup.performedWarmup)
4263
+ options.onServerReady?.();
4264
+ }
4265
+ for (let chunkStart = 0;chunkStart < group.files.length; chunkStart += WORKSPACE_SWEEP_PREOPEN_CHUNK_SIZE) {
4266
+ if (signal?.aborted)
4267
+ return;
4268
+ if (this.checkDestroyed()) {
4269
+ markServiceDestroyed(group.files.slice(chunkStart));
4270
+ return;
4271
+ }
4272
+ const chunk = group.files.slice(chunkStart, chunkStart + WORKSPACE_SWEEP_PREOPEN_CHUNK_SIZE);
4273
+ await preOpenGroupFiles(chunk);
4274
+ for (const filePath of chunk) {
4275
+ if (signal?.aborted)
4276
+ return;
4277
+ if (this.checkDestroyed()) {
4278
+ markServiceDestroyed(group.files.slice(group.files.indexOf(filePath)));
4279
+ return;
4280
+ }
4281
+ await processFile(filePath);
4282
+ }
4283
+ }
4284
+ }, signal);
4285
+ } finally {
4286
+ phaseFinished(sweepPhase);
4287
+ }
4288
+ const unconfirmedByReason = {};
4289
+ for (const result of results) {
4290
+ if (!result.timedOut)
4291
+ continue;
4292
+ const reason = result.unconfirmedReason ?? "budget";
4293
+ unconfirmedByReason[reason] = (unconfirmedByReason[reason] ?? 0) + 1;
4294
+ }
4295
+ const partiallyCoveredFiles = results.filter((result) => (result.unconfirmedServerIds?.length ?? 0) > 0).length;
4296
+ const unconfirmedServerIds = [
4297
+ ...new Set(results.flatMap((result) => result.unconfirmedServerIds ?? []))
4298
+ ].sort((a, b) => Number(a > b) - Number(a < b));
4299
+ let pullUnbound = 0;
4300
+ for (const result of results) {
4301
+ if (pullAnsweredFiles.has(result.filePath) && result.contentHash === undefined)
4302
+ pullUnbound += 1;
4303
+ }
4304
+ logLatency({
4305
+ type: "phase",
4306
+ phase: "lsp_workspace_diagnostics",
4307
+ filePath: root,
4308
+ durationMs: Date.now() - startedAt,
4309
+ metadata: {
4310
+ filesChecked: files.length,
4311
+ cacheHits: cachedResults.length,
4312
+ diagnosticCount: results.reduce((sum, result) => sum + (result?.count ?? 0), 0),
4313
+ serverGroups: groups.length,
4314
+ concurrency: groupWorkers,
4315
+ maxFiles,
4316
+ timedOutFiles,
4317
+ unconfirmedByReason,
4318
+ partiallyCoveredFiles,
4319
+ pullUnbound,
4320
+ ...unconfirmedServerIds.length > 0 && { unconfirmedServerIds },
4321
+ aborted: signal?.aborted ?? false
4322
+ }
4323
+ });
4324
+ for (const result of results) {
4325
+ const scannedAt = scannedMtimeByFile.get(result.filePath);
4326
+ if (result.error || result.timedOut || (result.unconfirmedServerIds?.length ?? 0) > 0 || scannedAt === undefined) {
4327
+ continue;
4328
+ }
4329
+ if (pullAnsweredFiles.has(result.filePath) && result.contentHash === undefined) {
4330
+ workspaceDiagnosticsCacheCtx.forget(result.filePath);
4331
+ continue;
4332
+ }
4333
+ workspaceDiagnosticsCacheCtx.record(result.filePath, workspaceSweepScopeKey, result.diagnostics, scannedAt, result.contentHash, scannedSizeByFile.get(result.filePath), scannedAtByFile.get(result.filePath));
4334
+ }
4335
+ workspaceDiagnosticsCacheCtx.persist();
4336
+ const servedCacheResults = supersededCacheKeys.size === 0 ? cachedResults : cachedResults.filter((result) => !supersededCacheKeys.has(normalizeMapKey(result.filePath)));
4337
+ const freshResults = results.map((result) => ({
4338
+ ...result,
4339
+ observedAt: scannedAtByFile.get(result.filePath)
4340
+ }));
4341
+ return [...servedCacheResults, ...freshResults].filter(Boolean);
4342
+ }
4343
+ async tryWorkspacePull(groupFiles, perFileMs, reanswerFor) {
4344
+ try {
4345
+ const first = groupFiles[0];
4346
+ if (!first)
4347
+ return;
4348
+ const spawned = await this.getClientForFile(first, perFileMs);
4349
+ if (!spawned)
4350
+ return;
4351
+ if (!spawned.client.getWorkspaceDiagnosticsSupport().workspaceDiagnostics) {
4352
+ return;
4353
+ }
4354
+ const report = await spawned.client.requestWorkspaceDiagnostics(Math.max(perFileMs, workspacePullBudgetMs()));
4355
+ if (!report)
4356
+ return;
4357
+ const byPath = new Map;
4358
+ for (const entry of report) {
4359
+ byPath.set(normalizeMapKey(entry.filePath), entry);
4360
+ }
4361
+ const results = groupFiles.map((filePath) => {
4362
+ const entry = byPath.get(normalizeMapKey(filePath));
4363
+ const diagnostics = entry?.diagnostics ?? [];
4364
+ return {
4365
+ filePath,
4366
+ diagnostics,
4367
+ count: diagnostics.length,
4368
+ timedOut: false,
4369
+ contentHash: entry?.contentHash
4370
+ };
4371
+ });
4372
+ const extraClean = [];
4373
+ if (reanswerFor && reanswerFor.size > 0) {
4374
+ const asked = new Set(groupFiles.map((f) => normalizeMapKey(f)));
4375
+ const withFindings = new Set;
4376
+ for (const entry of report) {
4377
+ if (entry.diagnostics.length > 0) {
4378
+ withFindings.add(normalizeMapKey(entry.filePath));
4379
+ }
4380
+ }
4381
+ for (const [key, entry] of byPath) {
4382
+ if (asked.has(key) || !reanswerFor.has(key))
4383
+ continue;
4384
+ if (entry.diagnostics.length > 0 || withFindings.has(key))
4385
+ continue;
4386
+ extraClean.push({
4387
+ filePath: entry.filePath,
4388
+ diagnostics: [],
4389
+ count: 0,
4390
+ timedOut: false,
4391
+ contentHash: entry.contentHash
4392
+ });
4393
+ }
4394
+ }
4395
+ return { results, extraClean };
4396
+ } catch {
4397
+ return;
4398
+ }
4399
+ }
4400
+ async getAllDiagnostics() {
4401
+ const all = new Map;
4402
+ const bindingsByPath = new Map;
4403
+ const now = Date.now();
4404
+ for (const [_key, client] of this.state.clients) {
4405
+ const trackedPaths = client.getTrackedDiagnosticPaths();
4406
+ const existingPaths = new Set;
4407
+ await Promise.all(trackedPaths.map(async (filePath) => {
4408
+ try {
4409
+ await nodeFs.promises.access(filePath);
4410
+ existingPaths.add(filePath);
4411
+ } catch {}
4412
+ return true;
4413
+ }));
4414
+ client.pruneDiagnostics((filePath, ts) => !existingPaths.has(filePath) || now - ts > CASCADE_DIAGNOSTICS_TTL_MS);
4415
+ const clientDiags = client.getAllDiagnostics();
4416
+ for (const [filePath, entry] of clientDiags) {
4417
+ const attributed = attributeDiagnostics(client.serverId, entry.diags);
4418
+ const existing = all.get(filePath);
4419
+ if (existing) {
4420
+ existing.diags = mergeLspDiagnostics([
4421
+ ...existing.diags,
4422
+ ...attributed
4423
+ ]);
4424
+ existing.ts = Math.max(existing.ts, entry.ts);
4425
+ } else {
4426
+ all.set(filePath, { diags: attributed, ts: entry.ts });
4427
+ }
4428
+ const list = bindingsByPath.get(filePath) ?? [];
4429
+ list.push(entry.binding);
4430
+ bindingsByPath.set(filePath, list);
4431
+ }
4432
+ }
4433
+ for (const [filePath, entry] of all) {
4434
+ const stored = bindingsByPath.get(filePath) ?? [];
4435
+ let memo;
4436
+ Object.defineProperty(entry, "binding", {
4437
+ enumerable: false,
4438
+ configurable: true,
4439
+ get: () => memo ??= this.mergeBinding(filePath, stored)
4440
+ });
4441
+ }
4442
+ return all;
4443
+ }
4444
+ supportsLSP(filePath) {
4445
+ return getServersForFileWithConfig(filePath).length > 0;
4446
+ }
4447
+ isSpawnInFlight(_filePath, resolvedRoots = new Map) {
4448
+ for (const [serverId, root] of resolvedRoots) {
4449
+ if (this.state.inFlight.has(`${serverId}:${root}`))
4450
+ return true;
4451
+ }
4452
+ return false;
4453
+ }
4454
+ describeTouchCandidates(filePath, resolvedRoots, auxiliaryRoots = new Map) {
4455
+ const candidates = [];
4456
+ for (const server of getServersForFileWithConfig(filePath)) {
4457
+ const auxiliary = isAuxiliary(server);
4458
+ if (auxiliary && !auxiliaryRoots.has(server.id))
4459
+ continue;
4460
+ if (candidates.length >= MAX_TOUCH_CANDIDATES)
4461
+ break;
4462
+ const root = (auxiliary ? auxiliaryRoots : resolvedRoots).get(server.id);
4463
+ const key = root === undefined ? undefined : `${server.id}:${root}`;
4464
+ const client = key === undefined ? undefined : this.state.clients.get(key);
4465
+ candidates.push({
4466
+ serverId: server.id,
4467
+ rooted: root !== undefined,
4468
+ clientFound: client ? client.isAlive() ? "alive" : "dead" : "none",
4469
+ generation: key === undefined ? undefined : this.state.clientSpawnedAt.get(key)
4470
+ });
4471
+ }
4472
+ return candidates;
4473
+ }
4474
+ async hasWarmLSP(filePath) {
4475
+ const spawned = await this.getWarmClientForFile(filePath);
4476
+ return Boolean(spawned);
4477
+ }
4478
+ async hasLSP(filePath) {
4479
+ const spawned = await this.getClientForFile(filePath);
4480
+ return Boolean(spawned);
4481
+ }
4482
+ async shutdown(options = {}) {
4483
+ const resetStartedAt = Date.now();
4484
+ if (this.checkDestroyed())
4485
+ return;
4486
+ this.isDestroyed = true;
4487
+ this.shutdownController.abort();
4488
+ for (const [key, token] of this.outstandingAuxNotifyWrites) {
4489
+ this.releaseOutstandingAuxNotifyWrite(key, token);
4490
+ }
4491
+ for (const key of this.idleEvictionTimers.keys()) {
4492
+ this.clearIdleEvictionTimer(key);
4493
+ }
4494
+ if (!options.processExiting && this.state.inFlight.size > 0) {
4495
+ const pending = Array.from(this.state.inFlight.values());
4496
+ this.state.inFlight.clear();
4497
+ const settled = await Promise.allSettled(pending);
4498
+ for (const result of settled) {
4499
+ if (result.status === "fulfilled" && result.value?.client) {
4500
+ result.value.client.shutdown({ fast: true }).catch(() => {});
4501
+ }
4502
+ }
4503
+ } else {
4504
+ this.state.inFlight.clear();
4505
+ }
4506
+ const aliveClients = this.getAliveClientCount();
4507
+ await Promise.allSettled(Array.from(this.state.clients.values(), (client) => Promise.resolve().then(() => client.shutdown(options))));
4508
+ logLatency({
4509
+ type: "phase",
4510
+ phase: "lsp_service_reset",
4511
+ filePath: "",
4512
+ startedAt: new Date(resetStartedAt).toISOString(),
4513
+ durationMs: Date.now() - resetStartedAt,
4514
+ metadata: {
4515
+ reason: options.reason ?? null,
4516
+ aliveClients,
4517
+ fast: !!options.fast,
4518
+ processExiting: !!options.processExiting
4519
+ }
4520
+ });
4521
+ this.state.clients.clear();
4522
+ this.state.broken.clear();
4523
+ this.firstContactWaits.clear();
4524
+ this.lastSpawnVerdict.clear();
4525
+ for (const [key, token] of this.outstandingAuxNotifyWrites) {
4526
+ this.releaseOutstandingAuxNotifyWrite(key, token);
4527
+ }
4528
+ this.notifyStallDemotions.clear();
4529
+ this.auxNotifyDrainLatencyEwma.clear();
4530
+ this.auxNotifyInflight.clear();
4531
+ this.documentDrift.clear();
4532
+ this.workspaceProbeLogged.clear();
4533
+ this.warmStartLogged.clear();
4534
+ }
4535
+ getStatus() {
4536
+ return Array.from(this.state.clients.entries()).map(([key, client]) => {
4537
+ const [serverId, root] = key.split(":");
4538
+ return {
4539
+ serverId,
4540
+ root,
4541
+ connected: client.isAlive(),
4542
+ pullFailureHistory: (client.getPullFailureHistory?.() ?? []).map((entry) => ({
4543
+ ...entry,
4544
+ message: entry.message.slice(0, 200)
4545
+ }))
4546
+ };
4547
+ });
4548
+ }
4549
+ recordRuntimeExitWindow(key, exitedAt, uptimeMs) {
4550
+ if (exitedAt == null || uptimeMs == null || uptimeMs > RUNTIME_EXIT_WINDOW_UPTIME_CEILING_MS) {
4551
+ return false;
4552
+ }
4553
+ const deaths = this.runtimeExitWindow.get(key) ?? [];
4554
+ deaths.push(exitedAt);
4555
+ const windowRef = Math.max(...deaths);
4556
+ const windowStart = windowRef - RUNTIME_EXIT_WINDOW_MS;
4557
+ let pruned = deaths.filter((t) => t >= windowStart);
4558
+ if (pruned.length > RUNTIME_EXIT_WINDOW_TRIP_COUNT) {
4559
+ pruned = pruned.slice(pruned.length - RUNTIME_EXIT_WINDOW_TRIP_COUNT);
4560
+ }
4561
+ this.runtimeExitWindow.set(key, pruned);
4562
+ return pruned.length >= RUNTIME_EXIT_WINDOW_TRIP_COUNT;
4563
+ }
4564
+ getBrokenStatus() {
4565
+ const keys = new Set([
4566
+ ...this.state.broken.keys(),
4567
+ ...this.permanentlyBroken
4568
+ ]);
4569
+ return [...keys].map((key) => {
4570
+ const separator = key.indexOf(":");
4571
+ return {
4572
+ serverId: separator >= 0 ? key.slice(0, separator) : key,
4573
+ root: separator >= 0 ? key.slice(separator + 1) : "",
4574
+ failures: (this.failureCounts.get(key) ?? 0) + Math.max(this.runtimeExitCounts.get(key) ?? 0, this.runtimeExitWindow.get(key)?.length ?? 0),
4575
+ permanentlyBroken: this.permanentlyBroken.has(key),
4576
+ cooldownUntil: this.state.broken.get(key) ?? 0
4577
+ };
4578
+ });
4579
+ }
4580
+ getAliveClientCount() {
4581
+ let count = 0;
4582
+ for (const client of this.state.clients.values()) {
4583
+ if (client.isAlive())
4584
+ count++;
4585
+ }
4586
+ return count;
4587
+ }
4588
+ getAliveServerIds() {
4589
+ const primary = [];
4590
+ const aux = [];
4591
+ const seen = new Set;
4592
+ for (const client of this.state.clients.values()) {
4593
+ if (!client.isAlive())
4594
+ continue;
4595
+ const id = client.serverId;
4596
+ if (seen.has(id))
4597
+ continue;
4598
+ seen.add(id);
4599
+ (isAuxiliary(getServerById(id)) ? aux : primary).push(id);
4600
+ }
4601
+ return [...primary, ...aux];
4602
+ }
4603
+ }
4604
+ configureLspServiceLifecycle({
4605
+ create: (generationHandoff) => new LSPService(generationHandoff, process.cwd()),
4606
+ beforeReset: (options) => {
4607
+ abortDeferredLspWork(`lsp service reset: ${options.reason ?? "unspecified"}`);
4608
+ resetLspLaunchAvailabilityGeneration();
4609
+ if (options.reason === "session_start") {
4610
+ resetClassicTsRepairGuard();
4611
+ }
4612
+ },
4613
+ destroy: (service, options) => service.shutdown(options)
4614
+ });
4615
+ export function getLSPService() {
4616
+ return getOwnedLspService();
4617
+ }
4618
+ export function peekLSPService() {
4619
+ return peekOwnedLspService();
4620
+ }
4621
+ export async function hasAuxiliaryLspPublishedForRoot(serverId, filePath) {
4622
+ return getLSPService().hasServerPublishedForFileRoot(serverId, filePath);
4623
+ }
4624
+ export async function notifyExternalFileChange(filePath, type) {
4625
+ return getLSPService().notifyExternalFileChange(filePath, type);
4626
+ }
4627
+ export async function resyncGitChangedFiles(changedPaths) {
4628
+ return getLSPService().resyncGitChangedFiles(changedPaths);
4629
+ }
4630
+ export function resetLSPService(options = {}) {
4631
+ if (options.reason === "session_start") {
4632
+ resetClassicTsRepairGuard();
4633
+ clearWorkspaceSweepHoldForSessionStart();
4634
+ }
4635
+ resetOwnedLspService(options);
4636
+ }
4637
+ export function __collectWorkspaceDiagnosticFilesForTest(root, maxFiles, signal, homeDir) {
4638
+ return collectWorkspaceDiagnosticFiles(path.resolve(root), maxFiles ?? getMaxWorkspaceDiagnosticFiles(), signal, homeDir);
4639
+ }