@herbertgao/pi-extensions 2026.9.2 → 2026.9.4

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 (255) hide show
  1. package/README.md +4 -2
  2. package/THIRD_PARTY_NOTICES.md +49 -0
  3. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +3 -2
  4. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/default-mode.ts +16 -11
  5. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-renderer.ts +20 -6
  6. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/grouping.ts +12 -7
  7. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/result.ts +98 -0
  8. package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
  9. package/node_modules/@herbertgao/sol-pi/LICENSE +19 -0
  10. package/node_modules/@herbertgao/sol-pi/README.md +159 -0
  11. package/node_modules/@herbertgao/sol-pi/SECURITY.md +26 -0
  12. package/node_modules/@herbertgao/sol-pi/THIRD_PARTY_NOTICES.md +19 -0
  13. package/node_modules/@herbertgao/sol-pi/agents-install.md +150 -0
  14. package/node_modules/@herbertgao/sol-pi/assets/sol-pi-hero.png +0 -0
  15. package/node_modules/@herbertgao/sol-pi/docs/compatibility.md +69 -0
  16. package/node_modules/@herbertgao/sol-pi/docs/configuration.md +75 -0
  17. package/node_modules/@herbertgao/sol-pi/package.json +76 -0
  18. package/node_modules/@herbertgao/sol-pi/scripts/check-pi-compat.mjs +32 -0
  19. package/node_modules/@herbertgao/sol-pi/scripts/check-sol-pi-config.mjs +120 -0
  20. package/node_modules/@herbertgao/sol-pi/sol-pi.example.json +10 -0
  21. package/node_modules/@herbertgao/sol-pi/src/sol-pi/config.ts +135 -0
  22. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/action-fusion/file-queue.ts +74 -0
  23. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/action-fusion/index.ts +185 -0
  24. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/action-fusion/then-run.ts +128 -0
  25. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/archive.ts +53 -0
  26. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/candidate.ts +101 -0
  27. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/config.ts +71 -0
  28. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/index.ts +220 -0
  29. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/journal.ts +25 -0
  30. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/provider.ts +164 -0
  31. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/receipt.ts +177 -0
  32. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/observation-pack/index.ts +227 -0
  33. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/observation-pack/ledger.ts +20 -0
  34. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/observation-pack/observation.ts +252 -0
  35. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/economics.ts +237 -0
  36. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/extension.ts +455 -0
  37. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/index.ts +49 -0
  38. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/plan.ts +79 -0
  39. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/state.ts +208 -0
  40. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/tools.ts +100 -0
  41. package/node_modules/@herbertgao/sol-pi/src/sol-pi/index.ts +42 -0
  42. package/node_modules/@herbertgao/sol-pi/src/sol-pi/runtime-paths.ts +17 -0
  43. package/node_modules/@herbertgao/sol-pi/src/sol-pi/tui.ts +71 -0
  44. package/node_modules/@narumitw/pi-btw/dist/index.ts +12 -2
  45. package/node_modules/@narumitw/pi-btw/dist/index.ts.map +2 -2
  46. package/node_modules/@narumitw/pi-btw/package.json +1 -1
  47. package/node_modules/@narumitw/pi-btw/src/btw.ts +13 -2
  48. package/node_modules/pi-antigravity/LICENSE +21 -0
  49. package/node_modules/pi-antigravity/README.md +194 -0
  50. package/node_modules/pi-antigravity/package.json +67 -0
  51. package/node_modules/pi-antigravity/src/auth/index.ts +14 -0
  52. package/node_modules/pi-antigravity/src/auth/oauth.ts +442 -0
  53. package/node_modules/pi-antigravity/src/client/client.ts +561 -0
  54. package/node_modules/pi-antigravity/src/client/index.ts +1 -0
  55. package/node_modules/pi-antigravity/src/diagnostics/diagnostics.ts +96 -0
  56. package/node_modules/pi-antigravity/src/diagnostics/index.ts +1 -0
  57. package/node_modules/pi-antigravity/src/image/image.ts +336 -0
  58. package/node_modules/pi-antigravity/src/image/index.ts +1 -0
  59. package/node_modules/pi-antigravity/src/index.ts +280 -0
  60. package/node_modules/pi-antigravity/src/models/discovery.ts +154 -0
  61. package/node_modules/pi-antigravity/src/models/grouping.ts +424 -0
  62. package/node_modules/pi-antigravity/src/models/index.ts +3 -0
  63. package/node_modules/pi-antigravity/src/models/models.ts +500 -0
  64. package/node_modules/pi-antigravity/src/stream/index.ts +1 -0
  65. package/node_modules/pi-antigravity/src/stream/stream.ts +1460 -0
  66. package/node_modules/pi-antigravity/src/types/enums.ts +42 -0
  67. package/node_modules/pi-antigravity/src/types/index.ts +2 -0
  68. package/node_modules/pi-antigravity/src/types/types.ts +292 -0
  69. package/node_modules/pi-antigravity/src/usage/index.ts +1 -0
  70. package/node_modules/pi-antigravity/src/usage/usage.ts +371 -0
  71. package/node_modules/pi-antigravity/src/utils/http.ts +91 -0
  72. package/node_modules/pi-antigravity/src/utils/index.ts +3 -0
  73. package/node_modules/pi-antigravity/src/utils/security.ts +73 -0
  74. package/node_modules/pi-antigravity/src/utils/util.ts +132 -0
  75. package/node_modules/pi-antigravity/tsconfig.json +21 -0
  76. package/node_modules/pi-lens/CHANGELOG.md +176 -0
  77. package/node_modules/pi-lens/README.md +13 -8
  78. package/node_modules/pi-lens/config/dependency-cruiser-eager-allowlist.json +3 -1
  79. package/node_modules/pi-lens/dist/clients/analysed-root.js +1 -0
  80. package/node_modules/pi-lens/dist/clients/ast-grep-tool-logger.js +1 -1
  81. package/node_modules/pi-lens/dist/clients/biome-client.js +13 -2
  82. package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +2 -0
  83. package/node_modules/pi-lens/dist/clients/cache-observability.js +122 -1
  84. package/node_modules/pi-lens/dist/clients/complexity-client.js +5 -0
  85. package/node_modules/pi-lens/dist/clients/config-diagnostic-codes.js +2 -0
  86. package/node_modules/pi-lens/dist/clients/config-resolve.js +6 -3
  87. package/node_modules/pi-lens/dist/clients/config-schema.js +17 -0
  88. package/node_modules/pi-lens/dist/clients/config-warn.js +2 -2
  89. package/node_modules/pi-lens/dist/clients/dead-code-client.js +8 -1
  90. package/node_modules/pi-lens/dist/clients/dependency-checker.js +5 -1
  91. package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +38 -15
  92. package/node_modules/pi-lens/dist/clients/dispatch/facts/function-facts.js +1 -1
  93. package/node_modules/pi-lens/dist/clients/dispatch/facts/import-facts.js +5 -1
  94. package/node_modules/pi-lens/dist/clients/dispatch/runners/actionlint.js +2 -1
  95. package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +2 -3
  96. package/node_modules/pi-lens/dist/clients/dispatch/runners/cpp-check.js +4 -3
  97. package/node_modules/pi-lens/dist/clients/dispatch/runners/credo.js +2 -1
  98. package/node_modules/pi-lens/dist/clients/dispatch/runners/cue-vet.js +2 -1
  99. package/node_modules/pi-lens/dist/clients/dispatch/runners/dart-analyze.js +2 -1
  100. package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +2 -1
  101. package/node_modules/pi-lens/dist/clients/dispatch/runners/dotnet-build.js +2 -1
  102. package/node_modules/pi-lens/dist/clients/dispatch/runners/elixir-check.js +2 -1
  103. package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +2 -1
  104. package/node_modules/pi-lens/dist/clients/dispatch/runners/fish-indent.js +2 -1
  105. package/node_modules/pi-lens/dist/clients/dispatch/runners/gleam-check.js +2 -1
  106. package/node_modules/pi-lens/dist/clients/dispatch/runners/go-vet.js +2 -1
  107. package/node_modules/pi-lens/dist/clients/dispatch/runners/golangci-lint.js +2 -1
  108. package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +2 -1
  109. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-lint.js +3 -1
  110. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +2 -1
  111. package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +2 -1
  112. package/node_modules/pi-lens/dist/clients/dispatch/runners/javac.js +2 -1
  113. package/node_modules/pi-lens/dist/clients/dispatch/runners/ktlint.js +2 -1
  114. package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +19 -1
  115. package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +3 -2
  116. package/node_modules/pi-lens/dist/clients/dispatch/runners/mypy.js +2 -1
  117. package/node_modules/pi-lens/dist/clients/dispatch/runners/oxlint.js +2 -1
  118. package/node_modules/pi-lens/dist/clients/dispatch/runners/php-lint.js +2 -1
  119. package/node_modules/pi-lens/dist/clients/dispatch/runners/phpstan.js +2 -1
  120. package/node_modules/pi-lens/dist/clients/dispatch/runners/prisma-validate.js +2 -1
  121. package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +12 -12
  122. package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +2 -1
  123. package/node_modules/pi-lens/dist/clients/dispatch/runners/rubocop.js +2 -1
  124. package/node_modules/pi-lens/dist/clients/dispatch/runners/ruff.js +2 -2
  125. package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +12 -11
  126. package/node_modules/pi-lens/dist/clients/dispatch/runners/shellcheck.js +2 -1
  127. package/node_modules/pi-lens/dist/clients/dispatch/runners/shfmt.js +2 -1
  128. package/node_modules/pi-lens/dist/clients/dispatch/runners/spellcheck.js +2 -2
  129. package/node_modules/pi-lens/dist/clients/dispatch/runners/spotbugs.js +2 -1
  130. package/node_modules/pi-lens/dist/clients/dispatch/runners/sqlfluff.js +3 -2
  131. package/node_modules/pi-lens/dist/clients/dispatch/runners/stylelint.js +3 -2
  132. package/node_modules/pi-lens/dist/clients/dispatch/runners/swiftlint.js +2 -1
  133. package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +2 -1
  134. package/node_modules/pi-lens/dist/clients/dispatch/runners/terragrunt.js +2 -1
  135. package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +2 -1
  136. package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +2 -1
  137. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/availability-policy.js +5 -0
  138. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/candidate-probe.js +2 -2
  139. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +8 -2
  140. package/node_modules/pi-lens/dist/clients/dispatch/runners/vale.js +2 -1
  141. package/node_modules/pi-lens/dist/clients/dispatch/runners/yamllint.js +4 -3
  142. package/node_modules/pi-lens/dist/clients/dispatch/runners/zig-check.js +2 -1
  143. package/node_modules/pi-lens/dist/clients/effective-config.js +12 -2
  144. package/node_modules/pi-lens/dist/clients/extension-log.js +2 -0
  145. package/node_modules/pi-lens/dist/clients/file-role.js +18 -1
  146. package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +4 -0
  147. package/node_modules/pi-lens/dist/clients/formatters.js +54 -188
  148. package/node_modules/pi-lens/dist/clients/generation-guard.js +4 -0
  149. package/node_modules/pi-lens/dist/clients/gitleaks-client.js +42 -6
  150. package/node_modules/pi-lens/dist/clients/govulncheck-client.js +48 -7
  151. package/node_modules/pi-lens/dist/clients/installer/index.js +196 -52
  152. package/node_modules/pi-lens/dist/clients/jscpd-client.js +9 -1
  153. package/node_modules/pi-lens/dist/clients/knip-client.js +9 -2
  154. package/node_modules/pi-lens/dist/clients/language-profile.js +12 -2
  155. package/node_modules/pi-lens/dist/clients/latency-logger.js +2 -0
  156. package/node_modules/pi-lens/dist/clients/lens-config.js +4 -0
  157. package/node_modules/pi-lens/dist/clients/lsp/config.js +36 -7
  158. package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +24 -1
  159. package/node_modules/pi-lens/dist/clients/lsp/document-drift.js +78 -0
  160. package/node_modules/pi-lens/dist/clients/lsp/index.js +281 -45
  161. package/node_modules/pi-lens/dist/clients/lsp/server.js +62 -15
  162. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +37 -4
  163. package/node_modules/pi-lens/dist/clients/mcp/analyze.js +3 -0
  164. package/node_modules/pi-lens/dist/clients/mcp/session.js +2 -0
  165. package/node_modules/pi-lens/dist/clients/opengrep-client.js +2 -0
  166. package/node_modules/pi-lens/dist/clients/package-manager.js +2 -1
  167. package/node_modules/pi-lens/dist/clients/php-cs-fixer-config.js +8 -3
  168. package/node_modules/pi-lens/dist/clients/pipeline.js +3 -2
  169. package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +38 -10
  170. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/gitleaks.js +10 -9
  171. package/node_modules/pi-lens/dist/clients/project-lens-config.js +3 -0
  172. package/node_modules/pi-lens/dist/clients/read-guard-logger.js +2 -0
  173. package/node_modules/pi-lens/dist/clients/review-graph-logger.js +2 -0
  174. package/node_modules/pi-lens/dist/clients/ruff-client.js +18 -9
  175. package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +9 -0
  176. package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +5 -0
  177. package/node_modules/pi-lens/dist/clients/runtime-session.js +13 -4
  178. package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +4 -1
  179. package/node_modules/pi-lens/dist/clients/runtime-turn.js +48 -1
  180. package/node_modules/pi-lens/dist/clients/security-scan-client.js +2 -2
  181. package/node_modules/pi-lens/dist/clients/session-event-guard.js +69 -1
  182. package/node_modules/pi-lens/dist/clients/sg-runner.js +2 -1
  183. package/node_modules/pi-lens/dist/clients/situational-tool-telemetry.js +108 -0
  184. package/node_modules/pi-lens/dist/clients/test-runner-client.js +291 -76
  185. package/node_modules/pi-lens/dist/clients/tool-config.js +199 -0
  186. package/node_modules/pi-lens/dist/clients/tool-cwd.js +299 -0
  187. package/node_modules/pi-lens/dist/clients/tool-probe.js +53 -0
  188. package/node_modules/pi-lens/dist/clients/tool-set-policy.js +42 -0
  189. package/node_modules/pi-lens/dist/clients/trivy-client.js +9 -1
  190. package/node_modules/pi-lens/dist/clients/turn-context.js +52 -0
  191. package/node_modules/pi-lens/dist/clients/widget-state.js +4 -3
  192. package/node_modules/pi-lens/dist/index.js +21969 -20005
  193. package/node_modules/pi-lens/dist/mcp/analyze-cli.js +3 -2
  194. package/node_modules/pi-lens/dist/mcp/server.js +135 -159
  195. package/node_modules/pi-lens/dist/tools/activate-tools.js +10 -7
  196. package/node_modules/pi-lens/dist/tools/ast-grep-outline.js +2 -13
  197. package/node_modules/pi-lens/dist/tools/ast-grep-replace.js +8 -13
  198. package/node_modules/pi-lens/dist/tools/ast-grep-search.js +83 -39
  199. package/node_modules/pi-lens/dist/tools/effective-config.js +4 -3
  200. package/node_modules/pi-lens/dist/tools/lens-diagnostic-mark.js +1 -10
  201. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +353 -85
  202. package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +21 -110
  203. package/node_modules/pi-lens/dist/tools/lsp-navigation.js +20 -44
  204. package/node_modules/pi-lens/dist/tools/module-report.js +5 -9
  205. package/node_modules/pi-lens/dist/tools/project-report.js +9 -17
  206. package/node_modules/pi-lens/dist/tools/render-compact.js +280 -1
  207. package/node_modules/pi-lens/dist/tools/symbol-search.js +2 -2
  208. package/node_modules/pi-lens/docs/agent-guide.md +3 -4
  209. package/node_modules/pi-lens/docs/agent-tools.md +46 -22
  210. package/node_modules/pi-lens/docs/configuration.md +9 -0
  211. package/node_modules/pi-lens/docs/environment-variables.md +0 -5
  212. package/node_modules/pi-lens/docs/features.md +5 -5
  213. package/node_modules/pi-lens/docs/globalconfig.md +11 -1
  214. package/node_modules/pi-lens/docs/mcp.md +13 -1
  215. package/node_modules/pi-lens/docs/pi-lens-fixer.md +71 -0
  216. package/node_modules/pi-lens/docs/pi-lens-investigator.md +15 -0
  217. package/node_modules/pi-lens/docs/pi-lens-monitor.md +88 -0
  218. package/node_modules/pi-lens/docs/pi-lens-reviewer.md +35 -0
  219. package/node_modules/pi-lens/docs/pi-lens-subagent.md +12 -4
  220. package/node_modules/pi-lens/docs/public-api-stability.md +1 -0
  221. package/node_modules/pi-lens/docs/real-harness.md +46 -0
  222. package/node_modules/pi-lens/docs/release-qa-baseline.md +5 -1
  223. package/node_modules/pi-lens/docs/servercapabilities.md +1 -6
  224. package/node_modules/pi-lens/docs/settings.md +37 -2
  225. package/node_modules/pi-lens/docs/tools_improvement2.md +4 -4
  226. package/node_modules/pi-lens/docs/tree-sitter_rules_catalog.md +1 -1
  227. package/node_modules/pi-lens/docs/usage.md +13 -3
  228. package/node_modules/pi-lens/package.json +10 -1
  229. package/node_modules/pi-lens/rules/tree-sitter-queries/typescript/sql-injection.yml +7 -3
  230. package/node_modules/pi-lens/skills/pi-lens-ast-grep/SKILL.md +8 -6
  231. package/node_modules/pi-lens/skills/pi-lens-lsp-navigation/SKILL.md +19 -9
  232. package/node_modules/pi-web-access/CHANGELOG.md +29 -0
  233. package/node_modules/pi-web-access/README.md +33 -22
  234. package/node_modules/pi-web-access/abortable.ts +17 -0
  235. package/node_modules/pi-web-access/crawl4ai.ts +204 -0
  236. package/node_modules/pi-web-access/credential-source.ts +1 -0
  237. package/node_modules/pi-web-access/curator-page.ts +20 -2
  238. package/node_modules/pi-web-access/curator-run.ts +44 -0
  239. package/node_modules/pi-web-access/curator-server.ts +3 -1
  240. package/node_modules/pi-web-access/duckduckgo.ts +1 -1
  241. package/node_modules/pi-web-access/extract.ts +57 -12
  242. package/node_modules/pi-web-access/fetch-params.ts +1 -1
  243. package/node_modules/pi-web-access/gemini-search.ts +9 -5
  244. package/node_modules/pi-web-access/index.ts +25 -18
  245. package/node_modules/pi-web-access/openai-search.ts +35 -13
  246. package/node_modules/pi-web-access/package.json +2 -2
  247. package/node_modules/pi-web-access/page-query.ts +5 -2
  248. package/node_modules/pi-web-access/query-rewrite.ts +5 -2
  249. package/node_modules/pi-web-access/serpapi.ts +220 -0
  250. package/node_modules/pi-web-access/ssrf-protection.ts +5 -1
  251. package/node_modules/pi-web-access/storage.ts +10 -0
  252. package/node_modules/pi-web-access/summary-review.ts +34 -21
  253. package/node_modules/pi-web-access/utils.ts +6 -4
  254. package/package.json +11 -5
  255. package/node_modules/pi-lens/dist/tools/ast-dump.js +0 -103
@@ -34,7 +34,7 @@ import { markPendingAuxiliaryCoverage, napiFallbackCoveredSince, } from "./pendi
34
34
  import { isAtOrAboveHomeDir, isWindowsPath, normalizeMapKey, uriToPath, } from "../path-utils.js";
35
35
  import { recordLspMutation } from "../lsp-mutation.js";
36
36
  import { createLSPClient } from "./client.js";
37
- import { auxiliaryCoverageGap, bindingStateLabel, composeBoundToCurrentDisk, createDiskBindingCache, hashDiagnosticContent, resolveTouchVerdict, touchCoverageGap, } from "./diagnostic-binding.js";
37
+ import { auxiliaryCoverageGap, auxiliaryPublicationEvidence, bindingStateLabel, composeBoundToCurrentDisk, createDiskBindingCache, hashDiagnosticContent, resolveTouchVerdict, touchCoverageGap, } from "./diagnostic-binding.js";
38
38
  import { getServersForFileWithConfig, getServerInitOverride, } from "./config.js";
39
39
  // #2052: deliberately NOT taken from `config.js`. This module's import surface
40
40
  // from `config.js` is mirrored by explicit `vi.mock` factories in ~58 test
@@ -42,9 +42,14 @@ import { getServersForFileWithConfig, getServerInitOverride, } from "./config.js
42
42
  import { getSessionRootsForTelemetry, isOutsideAllSessionRoots, } from "./session-roots.js";
43
43
  import { getProcessSingleton } from "../process-singletons.js";
44
44
  import { getLanguageId } from "./language.js";
45
- import { LSP_SERVERS, enforceLspRootCeiling, hasProjectBoundaryMarker, isDirectLspCommandTemporarilyUnavailable, resetClassicTsRepairGuard, resetLspLaunchAvailabilityGeneration, } from "./server.js";
45
+ import { getReverseDepsFromIndex, loadReverseDependencyIndexFromSnapshot, } from "../reverse-deps.js";
46
+ import { LSP_SERVERS, enforceLspRootCeiling, hasProjectBoundaryMarker, isDirectLspCommandTemporarilyUnavailable, resetClassicTsRepairGuard, resetLspLaunchAvailabilityGeneration, resolveLspServerCwd, } from "./server.js";
46
47
  import { classifyCascadeWaitTier, classifyServerWaitTier, getStrategy, } from "./wait-policy/index.js";
47
48
  const WORKSPACE_ATTRIBUTION_CLIENT_CAP = 16;
49
+ const AUX_WAIT_DEMOTION_THRESHOLD = 5;
50
+ const AUX_WAIT_DEMOTION_RATIO = 0.9;
51
+ const AUX_WAIT_REPROMOTION_THRESHOLD = 5;
52
+ const AUX_WAIT_REPROMOTION_RATIO = 0.5;
48
53
  import { raceToCompletion } from "./aggregation.js";
49
54
  import { applyWorkspaceEdit, mergeWorkspaceTextEditsByPriority, summarizeWorkspaceEdit, validateWorkspaceEdit, } from "./edits.js";
50
55
  import { buildScopeKey, createWorkspaceDiagnosticsCacheContext, } from "./workspace-diagnostics-cache.js";
@@ -176,6 +181,7 @@ const NAV_CLIENT_WAIT_TIMEOUT_MS = Math.max(0, Number.parseInt(process.env.PI_LE
176
181
  1500);
177
182
  const TOUCH_DEBOUNCE_MS = Math.max(0, Number.parseInt(process.env.PI_LENS_LSP_TOUCH_DEBOUNCE_MS ?? "1500", 10) ||
178
183
  1500);
184
+ const SCOPED_DEPENDENCY_TOUCH_MAX = 32;
179
185
  // #1621: the rename-propagation notifies (`didClose` ahead of the rename,
180
186
  // `workspace/didRenameFiles` after) share the exit-notify defect #1620 fixed —
181
187
  // a notify write on a pipe that is not draining neither resolves nor rejects,
@@ -345,6 +351,9 @@ function mergeLspDiagnostics(diagnostics) {
345
351
  }
346
352
  return merged;
347
353
  }
354
+ function attributeDiagnostics(serverId, diagnostics) {
355
+ return diagnostics.map((diagnostic) => ({ ...diagnostic, serverId }));
356
+ }
348
357
  /**
349
358
  * Group files by their primary language server id (#387/#631 — extracted
350
359
  * from `runWorkspaceDiagnostics`'s inline grouping so other callers, e.g.
@@ -653,6 +662,11 @@ export class LSPService {
653
662
  * the navigation-only latch, so the next touch can try again.
654
663
  */
655
664
  firstContactWaits = new Map();
665
+ /** Session-scoped adaptive demotion for budget-hitting auxiliaries. */
666
+ auxWaitPressureStreak = new Map();
667
+ auxWaitFastAnswerStreak = new Map();
668
+ demotedAuxiliaryServerIds = new Set();
669
+ demotedAuxiliaryBudgets = new Map();
656
670
  /**
657
671
  * #1934 review F1: what the last COMPLETED `spawnClient` call for a
658
672
  * (server, root) key decided, written by that call at every point it
@@ -865,13 +879,61 @@ export class LSPService {
865
879
  diagnosticsUnsupported: new Set(),
866
880
  };
867
881
  }
882
+ isAuxiliaryWaitDemoted(serverId, root) {
883
+ return this.demotedAuxiliaryServerIds.has(`${serverId}:${normalizeMapKey(root ?? "")}`);
884
+ }
885
+ noteAuxiliaryWait(key, budgetMs, elapsedMs) {
886
+ if (this.demotedAuxiliaryServerIds.has(key))
887
+ return;
888
+ if (elapsedMs < AUX_WAIT_DEMOTION_RATIO * budgetMs) {
889
+ this.auxWaitPressureStreak.delete(key);
890
+ return;
891
+ }
892
+ const streak = (this.auxWaitPressureStreak.get(key) ?? 0) + 1;
893
+ if (streak >= AUX_WAIT_DEMOTION_THRESHOLD) {
894
+ this.demotedAuxiliaryServerIds.add(key);
895
+ this.demotedAuxiliaryBudgets.set(key, budgetMs);
896
+ this.auxWaitPressureStreak.delete(key);
897
+ this.auxWaitFastAnswerStreak.delete(key);
898
+ recordDegradationOnce({
899
+ kind: "aux_wait_demoted",
900
+ subject: key,
901
+ reason: "auxiliary reached at least 90% of its declared wait budget for five consecutive dispatches",
902
+ });
903
+ return;
904
+ }
905
+ this.auxWaitPressureStreak.set(key, streak);
906
+ }
907
+ noteAuxiliaryLateAnswer(key, elapsedMs) {
908
+ if (!this.demotedAuxiliaryServerIds.has(key))
909
+ return;
910
+ const budgetMs = this.demotedAuxiliaryBudgets.get(key);
911
+ if (budgetMs === undefined ||
912
+ elapsedMs >= AUX_WAIT_REPROMOTION_RATIO * budgetMs) {
913
+ this.auxWaitFastAnswerStreak.delete(key);
914
+ return;
915
+ }
916
+ const streak = (this.auxWaitFastAnswerStreak.get(key) ?? 0) + 1;
917
+ if (streak < AUX_WAIT_REPROMOTION_THRESHOLD) {
918
+ this.auxWaitFastAnswerStreak.set(key, streak);
919
+ return;
920
+ }
921
+ this.demotedAuxiliaryServerIds.delete(key);
922
+ this.demotedAuxiliaryBudgets.delete(key);
923
+ this.auxWaitFastAnswerStreak.delete(key);
924
+ recordDegradationOnce({
925
+ kind: "aux_wait_repromoted",
926
+ subject: key,
927
+ reason: "auxiliary produced five consecutive late answers below half its declared wait budget",
928
+ });
929
+ }
868
930
  /**
869
931
  * Resolve one server's client identity root. Root selection is hard-bounded
870
932
  * by the session cwd, then config-only nested roots reuse an already-hosted
871
933
  * same-server ancestor. Real nested projects retain independent clients.
872
934
  */
873
- async resolveServerRoot(server, filePath) {
874
- const candidate = await server.root(filePath);
935
+ async resolveServerRoot(server, filePath, onRootFailure) {
936
+ const candidate = await resolveLspServerCwd(server, filePath, this.sessionCwd ?? process.cwd(), onRootFailure);
875
937
  if (!candidate)
876
938
  return undefined;
877
939
  // #2052: a file outside EVERY initialized session cwd gets no client at
@@ -1995,11 +2057,14 @@ export class LSPService {
1995
2057
  */
1996
2058
  const partitionCandidates = async () => {
1997
2059
  const resolved = await Promise.all(servers.map(async (server) => {
2060
+ let rootFailureReason;
1998
2061
  try {
1999
2062
  return {
2000
2063
  server,
2001
- root: await this.resolveServerRootOnce(server, filePath, rootMemo),
2002
- reason: undefined,
2064
+ root: await this.resolveServerRootOnce(server, filePath, rootMemo, (reason) => {
2065
+ rootFailureReason = reason;
2066
+ }),
2067
+ reason: rootFailureReason,
2003
2068
  };
2004
2069
  }
2005
2070
  catch (error) {
@@ -2466,6 +2531,18 @@ export class LSPService {
2466
2531
  }
2467
2532
  return out;
2468
2533
  }
2534
+ /** Record a fast answer observed by the turn-end late-results path. */
2535
+ async observeLateAuxiliaryAnswer(filePath, serverId, elapsedMs) {
2536
+ for (const server of getServersForFileWithConfig(filePath)) {
2537
+ if (server.id !== serverId)
2538
+ continue;
2539
+ const root = await this.resolveServerRoot(server, filePath);
2540
+ if (root === undefined)
2541
+ return;
2542
+ this.noteAuxiliaryLateAnswer(`${serverId}:${normalizeMapKey(root)}`, elapsedMs);
2543
+ return;
2544
+ }
2545
+ }
2469
2546
  /**
2470
2547
  * #1668: deliver a `workspace/didChangeWatchedFiles` event for a disk
2471
2548
  * change the client did not author through open-document sync — a bash
@@ -2492,6 +2569,45 @@ export class LSPService {
2492
2569
  }
2493
2570
  }
2494
2571
  }
2572
+ /**
2573
+ * Re-sync the open views affected by one recovered Git tree change. The
2574
+ * recovery seam already owns the changed-path set, so this deliberately
2575
+ * reads only the cached reverse-import index and never runs another diff.
2576
+ */
2577
+ async resyncGitChangedFiles(changedPaths) {
2578
+ if (this.checkDestroyed() || changedPaths.length === 0)
2579
+ return;
2580
+ const targets = new Set();
2581
+ for (const changedPath of changedPaths) {
2582
+ const resolved = path.resolve(changedPath);
2583
+ for (const server of getServersForFileWithConfig(resolved)) {
2584
+ const root = await this.resolveServerRoot(server, resolved);
2585
+ if (!root)
2586
+ continue;
2587
+ const index = loadReverseDependencyIndexFromSnapshot({ cwd: root });
2588
+ for (const importer of index
2589
+ ? getReverseDepsFromIndex(index, resolved)
2590
+ : []) {
2591
+ targets.add(importer);
2592
+ }
2593
+ }
2594
+ targets.add(resolved);
2595
+ }
2596
+ const openTargets = [...targets].filter((filePath) => this.hasLiveClientHoldingDocument(filePath));
2597
+ this.documentDrift.enqueueResync(openTargets);
2598
+ const pass = await this.sweepDocumentDrift({ force: true });
2599
+ logLatency({
2600
+ type: "phase",
2601
+ phase: "lsp_external_change_resync",
2602
+ filePath: changedPaths[0] ?? "",
2603
+ durationMs: 0,
2604
+ metadata: {
2605
+ changedCount: changedPaths.length,
2606
+ enqueued: openTargets.length,
2607
+ deferred: pass?.deferred ?? this.documentDrift.pendingResyncCount,
2608
+ },
2609
+ });
2610
+ }
2495
2611
  /**
2496
2612
  * #1783: disk-drift backstop. Stat one batch of the documents a language
2497
2613
  * server currently holds, and re-push any whose bytes on disk no longer
@@ -2680,13 +2796,13 @@ export class LSPService {
2680
2796
  * instead of each taking their own. Without a `memo` this is a plain
2681
2797
  * passthrough, for callers that have no acquisition to share with.
2682
2798
  */
2683
- resolveServerRootOnce(server, filePath, memo) {
2799
+ resolveServerRootOnce(server, filePath, memo, onRootFailure) {
2684
2800
  if (!memo)
2685
- return this.resolveServerRoot(server, filePath);
2801
+ return this.resolveServerRoot(server, filePath, onRootFailure);
2686
2802
  const pending = memo.get(server.id);
2687
2803
  if (pending !== undefined)
2688
2804
  return pending;
2689
- const started = this.resolveServerRoot(server, filePath);
2805
+ const started = this.resolveServerRoot(server, filePath, onRootFailure);
2690
2806
  memo.set(server.id, started);
2691
2807
  return started;
2692
2808
  }
@@ -3513,7 +3629,9 @@ export class LSPService {
3513
3629
  if (!bindingMatchesTouchContent(binding))
3514
3630
  return [];
3515
3631
  const diags = entry.client.getDiagnostics(filePath);
3516
- return diags.length > 0 ? [{ diags, binding }] : [];
3632
+ return diags.length > 0
3633
+ ? [{ serverId: entry.info.id, diags, binding }]
3634
+ : [];
3517
3635
  })
3518
3636
  : [];
3519
3637
  // #1493: auxiliaries whose STORED publication already covers exactly the
@@ -3822,6 +3940,15 @@ export class LSPService {
3822
3940
  // confirmation; `auxCutOffServerIds` stays cut_off-only so the R8 latency
3823
3941
  // field keeps its original meaning.
3824
3942
  let auxUnconfirmedServerIds;
3943
+ // #2810: the auxiliaries this touch handed to the collect-later store —
3944
+ // exactly the servers whose findings a turn-end drain can still deliver.
3945
+ // Written by the ONE producer that marks them, from the same array it
3946
+ // marks, so the promise the agent is shown cannot drift from the store.
3947
+ // Round 3 derived the promise from the #1459 resync deferrals instead —
3948
+ // the one set `pending-aux-coverage.ts` never marks — so the notice
3949
+ // promised delivery that could not happen, while the demoted server,
3950
+ // which IS marked, was reported as an unexplained silence on every edit.
3951
+ let lateDeliveryServerIds;
3825
3952
  // #707: tsserver sync clean-confirm state. `tsserverSyncEligible` is the
3826
3953
  // full gate (evaluated once, before the wait); `tsserverSyncConfirmed`
3827
3954
  // holds the sync commands' answer when the racing confirm won the wait
@@ -4098,9 +4225,11 @@ export class LSPService {
4098
4225
  const answeredForThisTouch = (entry) => {
4099
4226
  const baseline = diagnosticBaselines.get(entry.client);
4100
4227
  const currentPathVersion = readPathVersion(entry.client);
4101
- if (Number.isFinite(baseline) &&
4102
- currentPathVersion !== undefined &&
4103
- currentPathVersion > baseline) {
4228
+ if (auxiliaryPublicationEvidence({
4229
+ bindingMatchesContent: false,
4230
+ baseline,
4231
+ currentPathVersion,
4232
+ })) {
4104
4233
  return true;
4105
4234
  }
4106
4235
  try {
@@ -4116,6 +4245,11 @@ export class LSPService {
4116
4245
  : perServerDeclaredTimeouts[entryIndex];
4117
4246
  });
4118
4247
  const perServerWaits = spawned.map((entry, entryIndex) => {
4248
+ if (hasTouchAuxiliaries &&
4249
+ entry.info.role === "auxiliary" &&
4250
+ this.isAuxiliaryWaitDemoted(entry.info.id, entry.client.root ?? filePath)) {
4251
+ return Promise.resolve(undefined);
4252
+ }
4119
4253
  if (this.state.diagnosticsUnsupported.has(entry.info.id) ||
4120
4254
  diagnosticsUnsupportedServerIds.includes(entry.info.id)) {
4121
4255
  return Promise.resolve(undefined);
@@ -4254,6 +4388,8 @@ export class LSPService {
4254
4388
  client: spawned[i].client,
4255
4389
  baseline: diagnosticBaselines.get(spawned[i].client),
4256
4390
  budgetMs: perServerRaceBudgets[i],
4391
+ demoted: this.isAuxiliaryWaitDemoted(spawned[i].info.id, spawned[i].client.root ?? filePath),
4392
+ root: spawned[i].client.root ?? filePath,
4257
4393
  }
4258
4394
  : null)
4259
4395
  .filter((x) => x !== null);
@@ -4273,6 +4409,22 @@ export class LSPService {
4273
4409
  const auxWaitStartedAt = Date.now();
4274
4410
  const outcomes = await Promise.all(auxWaits.map(async (aux) => {
4275
4411
  const { budgetMs } = aux;
4412
+ if (aux.demoted) {
4413
+ return {
4414
+ serverId: aux.serverId,
4415
+ outcome: deferredResyncServerIds.has(aux.serverId)
4416
+ ? "deferred"
4417
+ : "demoted",
4418
+ publishedThisContent: auxiliaryPublicationEvidence({
4419
+ bindingMatchesContent: auxCoversThisContent(aux.serverId),
4420
+ baseline: aux.baseline,
4421
+ currentPathVersion: readPathVersion(aux.client),
4422
+ }),
4423
+ budgetMs,
4424
+ elapsedMs: 0,
4425
+ elapsedSinceNotifyMs: 0,
4426
+ };
4427
+ }
4276
4428
  let timer;
4277
4429
  const timeout = new Promise((resolve) => {
4278
4430
  timer = setTimeout(() => resolve(false), budgetMs);
@@ -4311,10 +4463,12 @@ export class LSPService {
4311
4463
  // ignoring sibling paths — and it is the SAME axis `baseline`
4312
4464
  // was captured on above.
4313
4465
  const currentPathVersion = readPathVersion(aux.client);
4314
- const publishedEvidence = raced &&
4315
- Number.isFinite(aux.baseline) &&
4316
- currentPathVersion !== undefined &&
4317
- currentPathVersion > aux.baseline;
4466
+ const publishedEvidence = auxiliaryPublicationEvidence({
4467
+ bindingMatchesContent: false,
4468
+ baseline: aux.baseline,
4469
+ currentPathVersion,
4470
+ raced,
4471
+ });
4318
4472
  // #1459: a DEFERRED aux was never sent this content and is not
4319
4473
  // waited on at all, so its instantly-resolved placeholder
4320
4474
  // promise must not read as "silent". "Silent" is the reserved
@@ -4328,6 +4482,10 @@ export class LSPService {
4328
4482
  : publishedEvidence
4329
4483
  ? "answered"
4330
4484
  : "silent";
4485
+ const elapsedMs = Date.now() - auxWaitStartedAt;
4486
+ if (outcome !== "deferred") {
4487
+ this.noteAuxiliaryWait(`${aux.serverId}:${normalizeMapKey(aux.root)}`, budgetMs, elapsedMs);
4488
+ }
4331
4489
  return {
4332
4490
  serverId: aux.serverId,
4333
4491
  outcome,
@@ -4337,9 +4495,12 @@ export class LSPService {
4337
4495
  // did not narrow the touch.
4338
4496
  // #1586: through the one predicate, so this row and the merge
4339
4497
  // below cannot disagree about the same scanner.
4498
+ // #2914: binding-only by construction — the direct call
4499
+ // below is master's line; the stamp axis already decided
4500
+ // the outcome above and must not re-admit the row here.
4340
4501
  publishedThisContent: auxCoversThisContent(aux.serverId),
4341
4502
  budgetMs,
4342
- elapsedMs: Date.now() - auxWaitStartedAt,
4503
+ elapsedMs,
4343
4504
  // #1458 S3: elapsed measured from BEFORE the primary wait
4344
4505
  // (waitStartedAt), not just from auxWaitStartedAt — this is
4345
4506
  // what lets a latency row validate the ~1.3s warm-scanner
@@ -4372,7 +4533,9 @@ export class LSPService {
4372
4533
  // revision and probing it would replay stale findings.
4373
4534
  const collectLaterServerIds = outcomes
4374
4535
  .filter((o) => !o.publishedThisContent &&
4375
- (o.outcome === "cut_off" || o.outcome === "silent") &&
4536
+ (o.outcome === "cut_off" ||
4537
+ o.outcome === "silent" ||
4538
+ o.outcome === "demoted") &&
4376
4539
  // #2324 R2-A/R3-A: ast-grep's napi fallback is a
4377
4540
  // SECOND producer of coverage for this exact pair,
4378
4541
  // dispatched CONCURRENTLY with this whole touch
@@ -4392,7 +4555,8 @@ export class LSPService {
4392
4555
  napiFallbackCoveredSince(filePath, startedAt)))
4393
4556
  .map((o) => o.serverId);
4394
4557
  if (collectLaterServerIds.length > 0) {
4395
- markPendingAuxiliaryCoverage(filePath, collectLaterServerIds);
4558
+ lateDeliveryServerIds = collectLaterServerIds;
4559
+ markPendingAuxiliaryCoverage(filePath, collectLaterServerIds, Date.now());
4396
4560
  }
4397
4561
  logLatency({
4398
4562
  type: "phase",
@@ -4592,9 +4756,11 @@ export class LSPService {
4592
4756
  const outcomes = auxEntries.map((entry) => {
4593
4757
  const baseline = diagnosticBaselines.get(entry.client);
4594
4758
  const currentPathVersion = readPathVersion(entry.client);
4595
- const publishedEvidence = Number.isFinite(baseline) &&
4596
- currentPathVersion !== undefined &&
4597
- currentPathVersion > baseline;
4759
+ const publishedEvidence = auxiliaryPublicationEvidence({
4760
+ bindingMatchesContent: false,
4761
+ baseline,
4762
+ currentPathVersion,
4763
+ });
4598
4764
  return {
4599
4765
  serverId: entry.info.id,
4600
4766
  outcome: deferredResyncServerIds.has(entry.info.id)
@@ -4602,6 +4768,9 @@ export class LSPService {
4602
4768
  : publishedEvidence
4603
4769
  ? "answered"
4604
4770
  : "silent",
4771
+ // #2914: binding-only by construction — the direct call
4772
+ // below is master's line; the stamp axis already decided
4773
+ // the outcome above and must not re-admit the row here.
4605
4774
  publishedThisContent: auxCoversThisContent(entry.info.id),
4606
4775
  budgetMs: timeoutFor(entry.client.serverId),
4607
4776
  elapsedMs: waitedMs,
@@ -4866,7 +5035,7 @@ export class LSPService {
4866
5035
  // diagnostics from the client cache as always.
4867
5036
  let collected = options.collectDiagnostics
4868
5037
  ? tsserverSyncConfirmed !== undefined
4869
- ? mergeLspDiagnostics(tsserverSyncConfirmed)
5038
+ ? mergeLspDiagnostics(attributeDiagnostics(spawned[0]?.info.id ?? "unknown", tsserverSyncConfirmed))
4870
5039
  : mergeLspDiagnostics([
4871
5040
  // #1459: a DEFERRED server's cache still holds the PREVIOUS
4872
5041
  // content's findings — the resync that would have cleared it never
@@ -4878,8 +5047,8 @@ export class LSPService {
4878
5047
  // coverage naming and the merged binding read too.
4879
5048
  ...spawned.flatMap((entry) => droppedAuxiliaryServerIds.has(entry.info.id)
4880
5049
  ? []
4881
- : entry.client.getDiagnostics(filePath)),
4882
- ...carriedAuxiliary.flatMap((entry) => entry.diags),
5050
+ : attributeDiagnostics(entry.info.id, entry.client.getDiagnostics(filePath))),
5051
+ ...carriedAuxiliary.flatMap((entry) => attributeDiagnostics(entry.serverId, entry.diags)),
4883
5052
  ])
4884
5053
  : undefined;
4885
5054
  // #1095 (P3-b): whether `collected` came from a tsserver sync confirm
@@ -4919,7 +5088,9 @@ export class LSPService {
4919
5088
  retractPrimaryTimeoutAttribution(); // #1549
4920
5089
  syncConfirmed = true;
4921
5090
  collected =
4922
- syncResult.length > 0 ? mergeLspDiagnostics(syncResult) : [];
5091
+ syncResult.length > 0
5092
+ ? mergeLspDiagnostics(attributeDiagnostics(spawned[0]?.info.id ?? "unknown", syncResult))
5093
+ : [];
4923
5094
  logLatency({
4924
5095
  type: "phase",
4925
5096
  phase: "lsp_tsserver_sync_confirm",
@@ -5252,9 +5423,11 @@ export class LSPService {
5252
5423
  result.inconclusiveReason = verdict.inconclusiveReason;
5253
5424
  }
5254
5425
  else if (collected !== undefined && primaryDiagnosticsUnsupported) {
5255
- // A navigation-only primary has no diagnostic confirmation to report.
5256
- // Auxiliary coverage cannot turn that capability boundary into a clean
5257
- // or partial diagnostic verdict.
5426
+ // A navigation-only primary has no diagnostic confirmation to report,
5427
+ // but an uncovered auxiliary still needs to be named for delivery.
5428
+ if (unconfirmedServerIds.length > 0) {
5429
+ result.unconfirmedServerIds = [...unconfirmedServerIds];
5430
+ }
5258
5431
  }
5259
5432
  else if (collected !== undefined && coverageGap) {
5260
5433
  // #1470/#1493: narrowed, not collapsed. Reached for EITHER no-answer
@@ -5312,6 +5485,17 @@ export class LSPService {
5312
5485
  // fully delivered. Nothing to record here: a skipped server keeps its original
5313
5486
  // entry (and timestamp) so its window still expires naturally instead of being
5314
5487
  // extended by every reuse.
5488
+ // #2810: the half of the coverage gap that has a delivery path. Derived
5489
+ // from `unconfirmedServerIds` so the two can never disagree about the
5490
+ // same scanner, and intersected with the marked set so "findings will
5491
+ // arrive through the late path" is said only where a pending pair
5492
+ // exists. Everything else in the gap — a #1459 deferral, a breaker
5493
+ // skip, an ast-grep already covered by its napi fallback — keeps the
5494
+ // honest "silent, diagnostics are incomplete" half.
5495
+ const lateDeliveryPending = unconfirmedServerIds.filter((serverId) => (lateDeliveryServerIds ?? []).includes(serverId));
5496
+ if (lateDeliveryPending.length > 0) {
5497
+ result.deferredServerIds = lateDeliveryPending;
5498
+ }
5315
5499
  logLatency({
5316
5500
  type: "phase",
5317
5501
  phase: "lsp_touch_file",
@@ -5384,6 +5568,14 @@ export class LSPService {
5384
5568
  ...(uncoveredDeferredServerIds.length > 0 && {
5385
5569
  deferredResyncServerIds: uncoveredDeferredServerIds,
5386
5570
  }),
5571
+ // #2810: the scanners this touch promised late delivery for, so a
5572
+ // field query can join the promise to the pending pair that has to
5573
+ // honour it — and tell it apart from `deferredResyncServerIds`
5574
+ // above, which is the door that opened BEFORE any wait and has no
5575
+ // delivery path at all.
5576
+ ...(lateDeliveryPending.length > 0 && {
5577
+ lateDeliveryServerIds: lateDeliveryPending,
5578
+ }),
5387
5579
  // #1549: auxiliaries whose own deadline lapsed — the wait produced no
5388
5580
  // publication, or the notify write never landed. Distinct from the fields
5389
5581
  // above (a different door) and from `auxUnconfirmedServerIds` (derived
@@ -5620,7 +5812,7 @@ export class LSPService {
5620
5812
  if (seen.has(key))
5621
5813
  continue;
5622
5814
  seen.add(key);
5623
- merged.push(diagnostic);
5815
+ merged.push({ ...diagnostic, serverId: entry.serverId });
5624
5816
  }
5625
5817
  }
5626
5818
  const rawCount = perServerFull.reduce((sum, entry) => sum + entry.diagnosticCount, 0);
@@ -6788,6 +6980,42 @@ export class LSPService {
6788
6980
  const workspaceSweepScopeKey = buildScopeKey("all", [
6789
6981
  ...WORKSPACE_SWEEP_EXCLUDED_SERVER_IDS,
6790
6982
  ]);
6983
+ const touchScopedOpenDependencies = async (filePath) => {
6984
+ if (options.files === undefined)
6985
+ return true;
6986
+ // Import coverage is language-neutral. Undefined means this file was
6987
+ // not covered by the facts seam, so its cache entry is not eligible.
6988
+ const imports = workspaceDiagnosticsCacheCtx.importsFor(filePath);
6989
+ if (!imports)
6990
+ return false;
6991
+ const openImports = imports.filter((dependency) => this.hasLiveClientHoldingDocument(dependency));
6992
+ const capped = openImports.length > SCOPED_DEPENDENCY_TOUCH_MAX;
6993
+ if (capped) {
6994
+ recordDegradationOnce({
6995
+ kind: "lsp_dependency_touch_capped",
6996
+ subject: filePath,
6997
+ reason: `scoped full scan limited open import touches to ${SCOPED_DEPENDENCY_TOUCH_MAX}`,
6998
+ });
6999
+ }
7000
+ await Promise.all(openImports
7001
+ .slice(0, SCOPED_DEPENDENCY_TOUCH_MAX)
7002
+ .map(async (dependency) => {
7003
+ let content;
7004
+ try {
7005
+ content = await fs.readFile(dependency, "utf8");
7006
+ }
7007
+ catch {
7008
+ return;
7009
+ }
7010
+ await this.touchFile(dependency, content, {
7011
+ diagnostics: "none",
7012
+ source: "lens_diagnostics_full_dependency",
7013
+ clientScope: "all",
7014
+ excludeServerIds: await this.serverIdsNotHoldingDocument(dependency),
7015
+ });
7016
+ }));
7017
+ return !capped;
7018
+ };
6791
7019
  const cachedResults = [];
6792
7020
  const filesToTouch = [];
6793
7021
  const writeIndexByPath = new Map();
@@ -6804,18 +7032,22 @@ export class LSPService {
6804
7032
  // exists to detect) must NOT be replayed and reconciled as confirmed via
6805
7033
  // mode=full. On a mismatch, fall through to a fresh touch.
6806
7034
  if (cached && cached.binding.boundToCurrentDisk !== false) {
6807
- cachedResults.push({
6808
- filePath,
6809
- diagnostics: cached.diagnostics,
6810
- count: cached.count,
6811
- // #1093: a cache hit replays an older observation — carry its
6812
- // scan time so mode=full's footer reconcile stamps `touchedAt`
6813
- // with when the truth was seen, not now().
6814
- observedAt: cached.scannedAt,
6815
- contentHash: cached.binding.contentHash,
6816
- boundToCurrentDisk: cached.binding.boundToCurrentDisk,
6817
- writeIndex: writeIndexByPath.get(normalizeMapKey(filePath)),
6818
- });
7035
+ const dependenciesConfirmed = await touchScopedOpenDependencies(filePath);
7036
+ if (dependenciesConfirmed)
7037
+ cachedResults.push({
7038
+ filePath,
7039
+ diagnostics: cached.diagnostics,
7040
+ count: cached.count,
7041
+ // #1093: a cache hit replays an older observation — carry its
7042
+ // scan time so mode=full's footer reconcile stamps `touchedAt`
7043
+ // with when the truth was seen, not now().
7044
+ observedAt: cached.scannedAt,
7045
+ contentHash: cached.binding.contentHash,
7046
+ boundToCurrentDisk: cached.binding.boundToCurrentDisk,
7047
+ writeIndex: writeIndexByPath.get(normalizeMapKey(filePath)),
7048
+ });
7049
+ else
7050
+ filesToTouch.push(filePath);
6819
7051
  }
6820
7052
  else {
6821
7053
  filesToTouch.push(filePath);
@@ -7601,16 +7833,17 @@ export class LSPService {
7601
7833
  client.pruneDiagnostics((filePath, ts) => !existingPaths.has(filePath) || now - ts > CASCADE_DIAGNOSTICS_TTL_MS);
7602
7834
  const clientDiags = client.getAllDiagnostics();
7603
7835
  for (const [filePath, entry] of clientDiags) {
7836
+ const attributed = attributeDiagnostics(client.serverId, entry.diags);
7604
7837
  const existing = all.get(filePath);
7605
7838
  if (existing) {
7606
7839
  existing.diags = mergeLspDiagnostics([
7607
7840
  ...existing.diags,
7608
- ...entry.diags,
7841
+ ...attributed,
7609
7842
  ]);
7610
7843
  existing.ts = Math.max(existing.ts, entry.ts);
7611
7844
  }
7612
7845
  else {
7613
- all.set(filePath, { diags: [...entry.diags], ts: entry.ts });
7846
+ all.set(filePath, { diags: attributed, ts: entry.ts });
7614
7847
  }
7615
7848
  const list = bindingsByPath.get(filePath) ?? [];
7616
7849
  list.push(entry.binding);
@@ -7970,6 +8203,9 @@ export async function hasAuxiliaryLspPublishedForRoot(serverId, filePath) {
7970
8203
  export async function notifyExternalFileChange(filePath, type) {
7971
8204
  return getLSPService().notifyExternalFileChange(filePath, type);
7972
8205
  }
8206
+ export async function resyncGitChangedFiles(changedPaths) {
8207
+ return getLSPService().resyncGitChangedFiles(changedPaths);
8208
+ }
7973
8209
  export function resetLSPService(options = {}) {
7974
8210
  // #2504 review round 2 (F3): whatever the retiring service was still being
7975
8211
  // asked for off-hook must stop HERE, before any teardown and before the