@mrclrchtr/supi-code-intelligence 4.10.0 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (269) hide show
  1. package/README.md +4 -3
  2. package/node_modules/@mrclrchtr/supi-code-runtime/README.md +2 -0
  3. package/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
  4. package/node_modules/@mrclrchtr/supi-code-runtime/src/api.ts +9 -0
  5. package/node_modules/@mrclrchtr/supi-code-runtime/src/capability/types.ts +53 -13
  6. package/node_modules/@mrclrchtr/supi-code-runtime/src/request-control.ts +47 -0
  7. package/node_modules/@mrclrchtr/supi-code-runtime/src/types.ts +13 -1
  8. package/node_modules/@mrclrchtr/supi-core/README.md +2 -0
  9. package/node_modules/@mrclrchtr/supi-core/package.json +2 -2
  10. package/node_modules/@mrclrchtr/supi-core/src/api.ts +1 -1
  11. package/node_modules/@mrclrchtr/supi-core/src/config/config.ts +31 -0
  12. package/node_modules/@mrclrchtr/supi-core/src/config.ts +2 -0
  13. package/node_modules/@mrclrchtr/supi-core/src/debug-registry.ts +19 -6
  14. package/node_modules/@mrclrchtr/supi-core/src/debug.ts +9 -0
  15. package/node_modules/@mrclrchtr/supi-core/src/index.ts +1 -1
  16. package/node_modules/@mrclrchtr/supi-core/src/settings/settings-registry.ts +3 -0
  17. package/node_modules/@mrclrchtr/supi-lsp/README.md +111 -5
  18. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/README.md +2 -0
  19. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
  20. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/src/api.ts +9 -0
  21. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/src/capability/types.ts +53 -13
  22. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/src/request-control.ts +47 -0
  23. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/src/types.ts +13 -1
  24. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/README.md +2 -0
  25. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/package.json +2 -2
  26. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/api.ts +1 -1
  27. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/config/config.ts +31 -0
  28. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/config.ts +2 -0
  29. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/debug-registry.ts +19 -6
  30. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/debug.ts +9 -0
  31. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/index.ts +1 -1
  32. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/settings/settings-registry.ts +3 -0
  33. package/node_modules/@mrclrchtr/supi-lsp/package.json +7 -6
  34. package/node_modules/@mrclrchtr/supi-lsp/src/api.ts +22 -1
  35. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-cache.ts +245 -0
  36. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-capabilities.ts +78 -0
  37. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-collection.ts +171 -0
  38. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-evidence.ts +413 -0
  39. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-host.ts +15 -0
  40. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-publication.ts +214 -0
  41. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-pull.ts +62 -0
  42. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-refresh.ts +690 -0
  43. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-request.ts +9 -0
  44. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-timing.ts +97 -16
  45. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-waiters.ts +206 -0
  46. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostics.ts +448 -298
  47. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-document-state.ts +70 -0
  48. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-document-sync.ts +231 -0
  49. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-file-state.ts +25 -0
  50. package/node_modules/@mrclrchtr/supi-lsp/src/client/client.ts +529 -119
  51. package/node_modules/@mrclrchtr/supi-lsp/src/client/transport.ts +161 -33
  52. package/node_modules/@mrclrchtr/supi-lsp/src/config/capabilities.ts +12 -3
  53. package/node_modules/@mrclrchtr/supi-lsp/src/config/config.ts +5 -5
  54. package/node_modules/@mrclrchtr/supi-lsp/src/config/defaults.json +1 -1
  55. package/node_modules/@mrclrchtr/supi-lsp/src/config/server-config.ts +3 -0
  56. package/node_modules/@mrclrchtr/supi-lsp/src/config/tsconfig-extends.ts +68 -0
  57. package/node_modules/@mrclrchtr/supi-lsp/src/config/tsconfig-path.ts +8 -0
  58. package/node_modules/@mrclrchtr/supi-lsp/src/config/tsconfig-scope.ts +176 -24
  59. package/node_modules/@mrclrchtr/supi-lsp/src/debug-telemetry.ts +45 -0
  60. package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/evidence.ts +42 -0
  61. package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/workspace-sentinels.ts +46 -10
  62. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-client-state.ts +79 -9
  63. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-diagnostics.ts +17 -7
  64. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-workspace-recovery.ts +350 -26
  65. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-workspace-symbol.ts +5 -2
  66. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager.ts +586 -96
  67. package/node_modules/@mrclrchtr/supi-lsp/src/provider/lsp-refactor-provider.ts +130 -0
  68. package/node_modules/@mrclrchtr/supi-lsp/src/provider/lsp-semantic-provider.ts +45 -329
  69. package/node_modules/@mrclrchtr/supi-lsp/src/provider/refactor-planning.ts +57 -120
  70. package/node_modules/@mrclrchtr/supi-lsp/src/provider/semantic-edit-normalizer.ts +320 -0
  71. package/node_modules/@mrclrchtr/supi-lsp/src/provider/semantic-symbol-mapper.ts +207 -0
  72. package/node_modules/@mrclrchtr/supi-lsp/src/session/readiness.ts +72 -2
  73. package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-controller.ts +151 -26
  74. package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-diagnostic-surface.ts +42 -0
  75. package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-diagnostics.ts +92 -0
  76. package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-registry.ts +165 -118
  77. package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-transition-debug.ts +49 -0
  78. package/node_modules/@mrclrchtr/supi-lsp/src/session/workspace-lsp-runtime.ts +102 -0
  79. package/node_modules/@mrclrchtr/supi-tree-sitter/README.md +17 -7
  80. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/README.md +2 -0
  81. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
  82. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/src/api.ts +9 -0
  83. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/src/capability/types.ts +53 -13
  84. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/src/request-control.ts +47 -0
  85. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/src/types.ts +13 -1
  86. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/README.md +2 -0
  87. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/package.json +2 -2
  88. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/api.ts +1 -1
  89. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/config/config.ts +31 -0
  90. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/config.ts +2 -0
  91. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/debug-registry.ts +19 -6
  92. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/debug.ts +9 -0
  93. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/index.ts +1 -1
  94. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/settings/settings-registry.ts +3 -0
  95. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/LICENSE +21 -0
  96. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/README.md +258 -0
  97. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/dist/babel.cjs +257 -0
  98. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/dist/jiti.cjs +1 -0
  99. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/lib/jiti-cli.mjs +34 -0
  100. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/lib/jiti-hooks.mjs +124 -0
  101. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/lib/jiti-native.mjs +121 -0
  102. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/lib/jiti-register.d.mts +1 -0
  103. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/lib/jiti-register.mjs +4 -0
  104. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/lib/jiti-static.mjs +23 -0
  105. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/lib/jiti.cjs +30 -0
  106. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/lib/jiti.d.cts +8 -0
  107. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/lib/jiti.d.mts +8 -0
  108. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/lib/jiti.mjs +29 -0
  109. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/lib/types.d.ts +420 -0
  110. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/package.json +146 -0
  111. package/node_modules/@mrclrchtr/supi-tree-sitter/package.json +6 -3
  112. package/node_modules/@mrclrchtr/supi-tree-sitter/src/api.ts +0 -9
  113. package/node_modules/@mrclrchtr/supi-tree-sitter/src/index.ts +0 -8
  114. package/node_modules/@mrclrchtr/supi-tree-sitter/src/provider/tree-sitter-provider.ts +16 -12
  115. package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/runtime-controller.ts +65 -87
  116. package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/session.ts +47 -95
  117. package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/structural-timing.ts +147 -0
  118. package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/structural-worker-client-lifecycle.ts +39 -0
  119. package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/structural-worker-client.ts +561 -0
  120. package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/structural-worker-message-size.ts +24 -0
  121. package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/structural-worker-protocol.ts +452 -0
  122. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/call-sites.ts +4 -2
  123. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/callees.ts +12 -4
  124. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/exports.ts +4 -2
  125. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/imports.ts +4 -2
  126. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/node-at.ts +5 -3
  127. package/node_modules/@mrclrchtr/supi-tree-sitter/src/types.ts +33 -12
  128. package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/blocking-test-bootstrap.mjs +34 -0
  129. package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/bootstrap.mjs +8 -0
  130. package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/parsed-file-store-helpers.ts +23 -0
  131. package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/parsed-file-store.ts +400 -0
  132. package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/request-control.ts +48 -0
  133. package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/runtime-parser-helpers.ts +24 -0
  134. package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/runtime-query-helpers.ts +43 -0
  135. package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/runtime.ts +421 -0
  136. package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/service.ts +116 -0
  137. package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/worker-main.ts +201 -0
  138. package/package.json +5 -5
  139. package/src/analysis/health/diagnostics.ts +221 -77
  140. package/src/analysis/health/file-scope.ts +184 -0
  141. package/src/analysis/health/recovery.ts +14 -3
  142. package/src/analysis/provider.ts +58 -0
  143. package/src/analysis/readiness.ts +34 -9
  144. package/src/analysis/refactor/apply.ts +45 -1
  145. package/src/analysis/refactor/mutation-authority.ts +143 -0
  146. package/src/analysis/relations/callees.ts +9 -1
  147. package/src/analysis/relations/types.ts +5 -2
  148. package/src/analysis/search/ast-scan-timing.ts +9 -2
  149. package/src/analysis/search/ast-scan.ts +5 -2
  150. package/src/analysis/search/deadline.ts +2 -2
  151. package/src/analysis/search/pattern-analysis.ts +54 -33
  152. package/src/analysis/search/pattern.ts +18 -3
  153. package/src/analysis/target/anchored.ts +20 -11
  154. package/src/analysis/target/file.ts +18 -11
  155. package/src/analysis/target/identity.ts +19 -3
  156. package/src/analysis/target/symbol.ts +36 -15
  157. package/src/api.ts +4 -0
  158. package/src/config.ts +35 -1
  159. package/src/diagnostics/evidence.ts +51 -0
  160. package/src/extension.ts +57 -14
  161. package/src/overview/overview-data.ts +9 -2
  162. package/src/overview/overview.ts +49 -30
  163. package/src/overview/types.ts +2 -0
  164. package/src/session/capability-adapter.ts +12 -3
  165. package/src/session/find-types.ts +2 -2
  166. package/src/session/find-workflow.ts +20 -9
  167. package/src/session/graph/collect.ts +18 -2
  168. package/src/session/graph-workflow.ts +19 -12
  169. package/src/session/health-refresh.ts +175 -0
  170. package/src/session/health-types.ts +43 -15
  171. package/src/session/health-workflow.ts +69 -54
  172. package/src/session/input/workflows.ts +2 -2
  173. package/src/session/inspect/collect.ts +20 -3
  174. package/src/session/inspect-workflow.ts +12 -6
  175. package/src/session/orientation/collect.ts +12 -3
  176. package/src/session/orientation/context-facts.ts +3 -1
  177. package/src/session/orientation/context-sections.ts +20 -7
  178. package/src/session/orientation/gather.ts +17 -11
  179. package/src/session/orientation-types.ts +4 -1
  180. package/src/session/orientation-workflow.ts +23 -10
  181. package/src/session/refactor-plans.ts +2 -0
  182. package/src/session/refactor-workflow.ts +61 -15
  183. package/src/session/session.ts +29 -2
  184. package/src/session/target-workflow.ts +54 -23
  185. package/src/session/workflow-control.ts +7 -3
  186. package/src/substrate/lsp/lifecycle.ts +26 -46
  187. package/src/substrate/lsp/maintenance.ts +284 -28
  188. package/src/substrate/lsp/recovery.ts +11 -5
  189. package/src/substrate/lsp/state.ts +2 -2
  190. package/src/substrate/workspace-provider-host.ts +25 -3
  191. package/src/tool/{find → code_find}/execute.ts +2 -16
  192. package/src/tool/code_find/guidance.ts +8 -0
  193. package/src/tool/{find → code_find}/render.ts +21 -4
  194. package/src/tool/{result/find.ts → code_find/result.ts} +78 -9
  195. package/src/tool/code_find/spec.ts +37 -0
  196. package/src/tool/code_find/tui.ts +41 -0
  197. package/src/tool/code_graph/execute.ts +24 -0
  198. package/src/tool/code_graph/guidance.ts +6 -0
  199. package/src/tool/{graph → code_graph}/markdown.ts +1 -1
  200. package/src/tool/{result/graph.ts → code_graph/result.ts} +111 -3
  201. package/src/tool/code_graph/spec.ts +39 -0
  202. package/src/tool/code_graph/tui.ts +117 -0
  203. package/src/tool/{health → code_health}/execute.ts +2 -13
  204. package/src/tool/code_health/guidance.ts +8 -0
  205. package/src/tool/{health → code_health}/markdown.ts +119 -27
  206. package/src/tool/code_health/refresh-status.ts +105 -0
  207. package/src/tool/{result/health.ts → code_health/result.ts} +80 -3
  208. package/src/tool/code_health/spec.ts +41 -0
  209. package/src/tool/{health → code_health}/tui.ts +95 -98
  210. package/src/tool/{inspect → code_inspect}/execute.ts +2 -16
  211. package/src/tool/code_inspect/guidance.ts +6 -0
  212. package/src/tool/{inspect → code_inspect}/markdown.ts +1 -1
  213. package/src/tool/{result/inspect.ts → code_inspect/result.ts} +143 -2
  214. package/src/tool/code_inspect/spec.ts +25 -0
  215. package/src/tool/code_inspect/tui.ts +44 -0
  216. package/src/tool/code_orientation/execute.ts +25 -0
  217. package/src/tool/code_orientation/guidance.ts +9 -0
  218. package/src/tool/{orientation → code_orientation}/markdown.ts +1 -1
  219. package/src/tool/code_orientation/result.ts +248 -0
  220. package/src/tool/code_orientation/spec.ts +25 -0
  221. package/src/tool/{orientation → code_orientation}/tui.ts +51 -35
  222. package/src/tool/code_refactor_apply/execute.ts +17 -0
  223. package/src/tool/code_refactor_apply/guidance.ts +5 -0
  224. package/src/tool/{refactor-apply/execute.ts → code_refactor_apply/result.ts} +8 -11
  225. package/src/tool/code_refactor_apply/spec.ts +26 -0
  226. package/src/tool/code_refactor_apply/tui.ts +73 -0
  227. package/src/tool/code_refactor_plan/execute.ts +27 -0
  228. package/src/tool/code_refactor_plan/guidance.ts +8 -0
  229. package/src/tool/{refactor-plan/execute.ts → code_refactor_plan/result.ts} +13 -22
  230. package/src/tool/code_refactor_plan/spec.ts +25 -0
  231. package/src/tool/code_refactor_plan/tui.ts +54 -0
  232. package/src/tool/code_resolve/execute.ts +19 -0
  233. package/src/tool/code_resolve/guidance.ts +8 -0
  234. package/src/tool/{resolve → code_resolve}/markdown.ts +1 -1
  235. package/src/tool/{result/resolve.ts → code_resolve/result.ts} +79 -29
  236. package/src/tool/code_resolve/spec.ts +25 -0
  237. package/src/tool/code_resolve/tui.ts +60 -0
  238. package/src/tool/guidance.ts +71 -52
  239. package/src/tool/infra/truncate.ts +40 -0
  240. package/src/tool/infra/workflow-control.ts +8 -0
  241. package/src/tool/{refactor-plan/markdown.ts → refactor-markdown.ts} +4 -7
  242. package/src/tool/register.ts +105 -88
  243. package/src/tool/result/display.ts +107 -0
  244. package/src/tool/result/errors.ts +20 -17
  245. package/src/tool/result/refactor.ts +37 -8
  246. package/src/tool/result/types.ts +20 -0
  247. package/src/tool/schemas.ts +26 -157
  248. package/src/tool/specs.ts +40 -126
  249. package/src/types/execution.ts +18 -5
  250. package/src/types/index.ts +8 -1
  251. package/src/ui/status-command.ts +5 -3
  252. package/src/ui/status-overlay.ts +4 -0
  253. package/src/ui/tui/common.ts +108 -10
  254. package/src/ui/tui/display.ts +48 -0
  255. package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/runtime.ts +0 -351
  256. package/src/tool/find/tui.ts +0 -39
  257. package/src/tool/graph/execute.ts +0 -62
  258. package/src/tool/graph/tui.ts +0 -104
  259. package/src/tool/inspect/tui.ts +0 -36
  260. package/src/tool/orientation/execute.ts +0 -62
  261. package/src/tool/refactor-apply/tui.ts +0 -55
  262. package/src/tool/refactor-plan/tui.ts +0 -41
  263. package/src/tool/resolve/execute.ts +0 -36
  264. package/src/tool/resolve/tui.ts +0 -42
  265. package/src/tool/result/orientation.ts +0 -132
  266. /package/src/tool/{find → code_find}/ast-kinds.ts +0 -0
  267. /package/src/tool/{find → code_find}/markdown.ts +0 -0
  268. /package/src/tool/{find → code_find}/modes.ts +0 -0
  269. /package/src/tool/{health → code_health}/semantic-state.ts +0 -0
@@ -1,99 +1,135 @@
1
- // LSP client document synchronization and diagnostic state.
2
-
3
- import { existsSync, readFileSync } from "node:fs";
4
- import type {
5
- Diagnostic,
6
- DocumentDiagnosticReport,
7
- PublishDiagnosticsParams,
8
- TextDocumentIdentifier,
9
- TextDocumentItem,
10
- VersionedTextDocumentIdentifier,
11
- } from "../config/types.ts";
1
+ // biome-ignore-all lint/style/noExcessiveLinesPerFile: one client's document sync, diagnostics, refresh, and sync-file flow stay in one cohesive class.
2
+ import * as path from "node:path";
3
+ import {
4
+ type CodeQueryResult,
5
+ type CodeRequestControl,
6
+ completedCodeQuery,
7
+ throwIfCodeRequestInterrupted,
8
+ unavailableCodeQuery,
9
+ } from "@mrclrchtr/supi-code-runtime/api";
10
+ import type { Diagnostic, TextDocumentItem } from "../config/types.ts";
11
+ import type { DiagnosticEvidenceSummary } from "../diagnostics/evidence.ts";
12
12
  import { detectLanguageId, fileToUri, uriToFile } from "../utils.ts";
13
+ import { applyPushDiagnostics, buildClientDiagnosticSnapshot } from "./client-diagnostic-cache.ts";
14
+ import { collectSynchronizedFileDiagnostics } from "./client-diagnostic-collection.ts";
15
+ import {
16
+ type DiagnosticCacheEntry,
17
+ type DiagnosticSynchronization,
18
+ hasCurrentEvidence,
19
+ hasFreshPush,
20
+ isCurrentSynchronization,
21
+ isValidPublishDiagnosticsParams,
22
+ nextDocumentVersion,
23
+ } from "./client-diagnostic-evidence.ts";
24
+ import type { ClientDiagnosticsHost } from "./client-diagnostic-host.ts";
25
+ import {
26
+ DiagnosticPublicationTracker,
27
+ trackerFileIdentityFor,
28
+ } from "./client-diagnostic-publication.ts";
13
29
  import {
14
- DiagnosticObserver,
15
- DiagnosticPullError,
16
- type DiagnosticPushWaitOutcome,
17
- type DiagnosticSettleResult,
18
- isDiagnosticTimeout,
19
- } from "./client-diagnostic-timing.ts";
30
+ pullClientDiagnosticEvidenceFromHost,
31
+ refreshClientOpenDiagnostics,
32
+ sendDidCloseNotification,
33
+ } from "./client-diagnostic-refresh.ts";
34
+ import { DiagnosticObserver, type DiagnosticPushWaitOutcome } from "./client-diagnostic-timing.ts";
35
+ import { DiagnosticWaitRegistry } from "./client-diagnostic-waiters.ts";
36
+ import {
37
+ type ClientDiagnosticSnapshot,
38
+ type DiagnosticEntry,
39
+ fingerprintDocumentContent,
40
+ hasConfirmedDiagnosticEvidence,
41
+ type OpenDocumentState,
42
+ } from "./client-document-state.ts";
43
+ import {
44
+ clearTrackedDocumentState,
45
+ reopenDocument,
46
+ synchronizeTrackedDocument,
47
+ } from "./client-document-sync.ts";
48
+ import { getDiagnosticFileState } from "./client-file-state.ts";
20
49
 
21
50
  const DIAGNOSTIC_WAIT_MS = 3_000;
22
-
23
- type OpenDocument = { version: number };
24
- type DiagnosticWaiter = (outcome: "published" | "released") => void;
25
-
26
- type DiagnosticCacheEntry = {
27
- diagnostics: Diagnostic[];
28
- receivedAt: number;
29
- version?: number;
30
- resultId?: string;
31
- };
32
-
33
- /** One file's stored diagnostics as consumed by manager-level collection. */
34
- export interface DiagnosticEntry {
35
- uri: string;
36
- diagnostics: Diagnostic[];
37
- }
38
-
39
- interface ClientDiagnosticsHost {
40
- isOperational(): boolean;
41
- supportsPullDiagnostics(): boolean;
42
- sendNotification(method: string, params: unknown): void;
43
- pullDocumentDiagnostics(
44
- uri: string,
45
- previousResultId: string | undefined,
46
- timeoutMs: number,
47
- ): Promise<DocumentDiagnosticReport | null>;
48
- }
49
-
50
- /**
51
- * Owns one LSP client's open documents, diagnostic cache, waiters, and refresh policy.
52
- *
53
- * The host supplies transport and readiness operations. Callers use `LspClient` as
54
- * the external seam and do not access this state directly.
55
- */
51
+ /** Bounded push wait after a reopen-resync fallback, in milliseconds. */
52
+ const REOPEN_EVIDENCE_WAIT_MS = 1_000;
53
+ /** Own one client's document and diagnostic evidence; revisions prevent stale reuse. */
56
54
  export class ClientDiagnostics {
57
- readonly #openDocs = new Map<string, OpenDocument>();
55
+ readonly #openDocs = new Map<string, OpenDocumentState>();
58
56
  readonly #diagnosticStore = new Map<string, DiagnosticCacheEntry>();
59
- readonly #diagnosticWaiters = new Map<string, DiagnosticWaiter[]>();
60
-
61
- constructor(private readonly host: ClientDiagnosticsHost) {}
57
+ readonly #waiters = new DiagnosticWaitRegistry();
58
+ readonly #versionHistory = new Map<string, number>();
59
+ readonly #failedUris = new Set<string>();
60
+ /** Bounded push-publication telemetry for one client's diagnostic state. */
61
+ readonly #publications: DiagnosticPublicationTracker;
62
+ /** Client-side sync moment per URI: unversioned pushes before it stay rejected. */
63
+ readonly #unversionedPushSyncMoments = new Map<string, number>();
64
+ /** URIs closed by a lifecycle operation: versioned pushes stay fail-closed. */
65
+ readonly #closedVersionedBarrier = new Set<string>();
66
+ #evidenceRevision = 0;
67
+ #nextSynchronizationId = 0;
68
+
69
+ constructor(private readonly host: ClientDiagnosticsHost) {
70
+ this.#publications = new DiagnosticPublicationTracker(
71
+ { server: host.server, cwd: host.cwd },
72
+ trackerFileIdentityFor(host.cwd),
73
+ );
74
+ }
62
75
 
63
76
  get openFiles(): string[] {
64
77
  return Array.from(this.#openDocs.keys()).map(uriToFile);
65
78
  }
66
79
 
67
- /** Release all waiters and remove all document and diagnostic state. */
68
- clear(): void {
80
+ clear(options: { preserveFailedDocuments?: boolean } = {}): void {
81
+ if (options.preserveFailedDocuments) {
82
+ for (const uri of [...this.#openDocs.keys(), ...this.#diagnosticStore.keys()]) {
83
+ this.#failedUris.add(uri);
84
+ this.#closedVersionedBarrier.add(uri);
85
+ }
86
+ } else {
87
+ this.#failedUris.clear();
88
+ this.#closedVersionedBarrier.clear();
89
+ }
69
90
  this.#openDocs.clear();
70
91
  this.#diagnosticStore.clear();
71
- this.#releaseAllDiagnosticWaiters();
92
+ this.#versionHistory.clear();
93
+ this.#evidenceRevision++;
94
+ this.#unversionedPushSyncMoments.clear();
95
+ this.#waiters.releaseAll();
96
+ this.#waiters.cancelSettle();
72
97
  }
73
98
 
74
- /** Open a document, or update it when it is already open. */
75
99
  didOpen(filePath: string, content: string): void {
76
100
  if (!this.host.isOperational()) return;
77
101
 
78
102
  const uri = fileToUri(filePath);
103
+ this.#failedUris.delete(uri);
79
104
  if (this.#openDocs.has(uri)) {
80
105
  this.didChange(filePath, content);
81
106
  return;
82
107
  }
83
108
 
84
109
  const languageId = detectLanguageId(filePath);
85
- this.#openDocs.set(uri, { version: 1 });
110
+ this.#waiters.cancelSettle();
111
+ // A didOpen is a synchronization: pushes the server sends after it are
112
+ // responses to the open and can become fresh evidence (ADR 0020).
113
+ this.#unversionedPushSyncMoments.set(uri, Date.now());
114
+ this.#closedVersionedBarrier.delete(uri);
115
+ const version = nextDocumentVersion(this.#versionHistory, uri);
116
+ this.#openDocs.set(uri, {
117
+ version,
118
+ synchronizationId: ++this.#nextSynchronizationId,
119
+ evidenceRevision: this.#evidenceRevision,
120
+ content,
121
+ contentFingerprint: fingerprintDocumentContent(content),
122
+ });
86
123
  this.host.sendNotification("textDocument/didOpen", {
87
124
  textDocument: {
88
125
  uri,
89
126
  languageId,
90
- version: 1,
127
+ version,
91
128
  text: content,
92
129
  } satisfies TextDocumentItem,
93
130
  });
94
131
  }
95
132
 
96
- /** Update a document, or open it when it is not tracked yet. */
97
133
  didChange(filePath: string, content: string): void {
98
134
  if (!this.host.isOperational()) return;
99
135
 
@@ -103,295 +139,409 @@ export class ClientDiagnostics {
103
139
  this.didOpen(filePath, content);
104
140
  return;
105
141
  }
106
-
107
- doc.version++;
108
- this.#sendDidChange(uri, doc.version, content);
142
+ const nextFingerprint = fingerprintDocumentContent(content);
143
+ if (doc.contentFingerprint === nextFingerprint) return;
144
+ synchronizeTrackedDocument({
145
+ uri,
146
+ content,
147
+ document: doc,
148
+ nextVersion: () => nextDocumentVersion(this.#versionHistory, uri),
149
+ nextSynchronizationId: () => ++this.#nextSynchronizationId,
150
+ evidenceRevision: this.#evidenceRevision,
151
+ incrementalSync: this.host.usesIncrementalDocumentSync(),
152
+ waiters: this.#waiters,
153
+ sendNotification: (method, params) => this.host.sendNotification(method, params),
154
+ markUnversionedSyncMoment: () => this.#unversionedPushSyncMoments.set(uri, Date.now()),
155
+ clearFailedFile: () => this.#failedUris.delete(uri),
156
+ open: () => this.didOpen(filePath, content),
157
+ });
109
158
  }
110
159
 
111
- /** Close a document and remove its cached diagnostic state. */
112
160
  didClose(filePath: string): void {
113
161
  const uri = fileToUri(filePath);
114
162
  const wasOpen = this.#openDocs.has(uri);
115
- this.#clearFileState(uri);
163
+ this.#failedUris.delete(uri);
164
+ this.#unversionedPushSyncMoments.delete(uri);
165
+ this.#closedVersionedBarrier.add(uri);
166
+ clearTrackedDocumentState(this.#openDocs, this.#diagnosticStore, this.#waiters, uri);
116
167
 
117
168
  if (wasOpen && this.host.isOperational()) {
118
- this.#sendDidClose(uri);
169
+ sendDidCloseNotification(this.host, uri);
119
170
  }
120
171
  }
121
172
 
122
- /** Remove missing open documents and diagnostics, and return their file paths. */
123
173
  pruneMissingFiles(): string[] {
124
- const uris = new Set([...this.#openDocs.keys(), ...this.#diagnosticStore.keys()]);
174
+ const uris = new Set([
175
+ ...this.#openDocs.keys(),
176
+ ...this.#diagnosticStore.keys(),
177
+ ...this.#failedUris,
178
+ ]);
125
179
  const removedFiles: string[] = [];
126
180
 
127
181
  for (const uri of uris) {
128
182
  const filePath = uriToFile(uri);
129
- if (existsSync(filePath)) continue;
183
+ if (getDiagnosticFileState(filePath) !== "removed") continue;
130
184
 
131
185
  const wasOpen = this.#openDocs.has(uri);
132
- this.#clearFileState(uri);
186
+ this.#failedUris.delete(uri);
187
+ this.#unversionedPushSyncMoments.delete(uri);
188
+ this.#closedVersionedBarrier.add(uri);
189
+ clearTrackedDocumentState(this.#openDocs, this.#diagnosticStore, this.#waiters, uri);
133
190
  removedFiles.push(filePath);
134
- if (wasOpen && this.host.isOperational()) this.#sendDidClose(uri);
191
+ if (wasOpen && this.host.isOperational()) sendDidCloseNotification(this.host, uri);
135
192
  }
136
193
 
137
194
  return removedFiles;
138
195
  }
196
+ /** Retain a failed document outcome when a replacement cannot reopen it. */
197
+ markFailedFile(filePath: string): void {
198
+ const uri = fileToUri(filePath);
199
+ this.#failedUris.add(uri);
200
+ this.#unversionedPushSyncMoments.delete(uri);
201
+ this.#closedVersionedBarrier.add(uri);
202
+ }
139
203
 
204
+ getOpenDocumentVersion(filePath: string): number | null {
205
+ return this.#openDocs.get(fileToUri(filePath))?.version ?? null;
206
+ }
140
207
  getDiagnostics(filePath: string): Diagnostic[] {
141
208
  return this.#diagnosticStore.get(fileToUri(filePath))?.diagnostics ?? [];
142
209
  }
143
-
144
- /** Return non-empty diagnostics for files that still exist. */
210
+ getDiagnosticSnapshot(): ClientDiagnosticSnapshot {
211
+ return buildClientDiagnosticSnapshot({
212
+ store: this.#diagnosticStore,
213
+ openDocuments: this.#openDocs,
214
+ failedDocuments: this.#failedUris,
215
+ evidenceRevision: this.#evidenceRevision,
216
+ });
217
+ }
145
218
  getAllDiagnostics(): DiagnosticEntry[] {
146
- const result: DiagnosticEntry[] = [];
147
- for (const [uri, entry] of this.#diagnosticStore) {
148
- if (entry.diagnostics.length === 0 || !existsSync(uriToFile(uri))) continue;
149
- result.push({ uri, diagnostics: entry.diagnostics });
150
- }
151
- return result;
219
+ return this.getDiagnosticSnapshot().entries;
152
220
  }
153
-
154
- /** Force the next pull refresh to request complete diagnostic reports. */
155
221
  clearPullResultIds(): void {
156
222
  for (const entry of this.#diagnosticStore.values()) delete entry.resultId;
157
223
  }
158
-
159
- /** Apply diagnostics received through `textDocument/publishDiagnostics`. */
160
- handlePublishDiagnostics(params: PublishDiagnosticsParams): void {
161
- if (params.version !== undefined && params.version !== null) {
162
- const openDoc = this.#openDocs.get(params.uri);
163
- if (openDoc && params.version < openDoc.version) return;
224
+ /** Invalidate cache proof while retaining its data as partial fallback. */
225
+ invalidateCachedEvidence(): void {
226
+ this.#evidenceRevision++;
227
+ const knownUris = new Set([
228
+ ...this.#openDocs.keys(),
229
+ ...this.#diagnosticStore.keys(),
230
+ ...this.#versionHistory.keys(),
231
+ ...this.#failedUris,
232
+ ]);
233
+ // A watched-file notification is sent immediately after this call, so the
234
+ // recorded moment gates unversioned pushes around the invalidation: only
235
+ // pushes the server sends after the change can become fresh evidence.
236
+ const moment = Date.now();
237
+ for (const uri of knownUris) {
238
+ this.#unversionedPushSyncMoments.set(uri, moment);
239
+ this.#closedVersionedBarrier.add(uri);
164
240
  }
241
+ }
165
242
 
166
- this.#diagnosticStore.set(params.uri, {
167
- diagnostics: params.diagnostics,
168
- receivedAt: Date.now(),
169
- version: params.version ?? undefined,
243
+ handlePublishDiagnostics(params: unknown): void {
244
+ if (!isValidPublishDiagnosticsParams(params)) return;
245
+ const result = applyPushDiagnostics({
246
+ store: this.#diagnosticStore,
247
+ openDocuments: this.#openDocs,
248
+ params,
249
+ evidenceRevision: this.#evidenceRevision,
250
+ unversionedSyncMoment: this.#unversionedPushSyncMoments.get(params.uri),
251
+ closedVersionedBarrier: this.#closedVersionedBarrier.has(params.uri),
170
252
  });
171
- this.#releaseDiagnosticWaiters(params.uri, "published");
253
+ if (!result.accepted) return;
254
+ const entry = this.#diagnosticStore.get(params.uri);
255
+ if (entry?.synchronizationId !== undefined && entry.evidenceRevision !== undefined) {
256
+ this.#publications.record(
257
+ params.uri,
258
+ entry.synchronizationId,
259
+ entry.evidenceRevision,
260
+ entry.receivedAt,
261
+ );
262
+ if (result.promoted) {
263
+ this.#publications.promoted(
264
+ params.uri,
265
+ entry.synchronizationId,
266
+ entry.evidenceRevision,
267
+ entry.receivedAt,
268
+ );
269
+ }
270
+ }
271
+ this.#waiters.releaseFile(params.uri, "published");
272
+ this.#waiters.notifySettle();
172
273
  }
173
-
174
- /** Re-read open documents, then collect pull diagnostics or wait for push diagnostics. */
175
274
  async refreshOpenDiagnostics(
176
- options: { maxWaitMs?: number; quietMs?: number } = {},
177
- ): Promise<void> {
178
- const supportsPull = this.host.supportsPullDiagnostics();
179
- const observer = new DiagnosticObserver("refresh-open", supportsPull);
180
- if (!this.host.isOperational()) {
181
- observer.skipped(0);
182
- return;
183
- }
184
-
185
- const maxWaitMs = options.maxWaitMs ?? 3_000;
186
- const quietMs = options.quietMs ?? 200;
187
- const syncStart = Date.now();
188
- this.#resyncOpenDocuments();
189
- observer.synchronized();
190
- const documentCount = this.#openDocs.size;
191
- if (documentCount === 0) {
192
- observer.skipped(0);
193
- return;
194
- }
275
+ options: { maxWaitMs?: number; quietMs?: number } & CodeRequestControl = {},
276
+ ): Promise<DiagnosticEvidenceSummary> {
277
+ return this.#refreshOpenDiagnostics(options, false);
278
+ }
195
279
 
196
- if (supportsPull) {
197
- try {
198
- await this.#pullDiagnosticsForOpenDocuments(syncStart, maxWaitMs);
199
- observer.pullCompleted(documentCount);
200
- return;
201
- } catch (error) {
202
- observer.pullFailed(error);
203
- }
204
- }
280
+ /** Force a full document resynchronization for a server refresh request. */
281
+ async refreshForServerRequest(): Promise<DiagnosticEvidenceSummary> {
282
+ return this.#refreshOpenDiagnostics({}, true);
283
+ }
205
284
 
206
- const settle = await this.#waitForDiagnosticSettle(syncStart, maxWaitMs, quietMs);
207
- observer.pushSettled(documentCount, settle);
285
+ async #refreshOpenDiagnostics(
286
+ options: { maxWaitMs?: number; quietMs?: number } & CodeRequestControl,
287
+ forceResynchronize: boolean,
288
+ ): Promise<DiagnosticEvidenceSummary> {
289
+ const requestedFiles = Array.from(
290
+ new Set([...this.#openDocs.keys(), ...this.#diagnosticStore.keys(), ...this.#failedUris]),
291
+ ).map(uriToFile);
292
+ return refreshClientOpenDiagnostics({
293
+ host: this.host,
294
+ openDocuments: this.#openDocs,
295
+ diagnosticStore: this.#diagnosticStore,
296
+ waiters: this.#waiters,
297
+ versionHistory: this.#versionHistory,
298
+ requestedFiles,
299
+ evidenceRevision: () => this.#evidenceRevision,
300
+ failedFiles: () => new Set(Array.from(this.#failedUris).map(uriToFile)),
301
+ isRelatedUriTracked: (uri) => this.#openDocs.has(uri) || this.#versionHistory.has(uri),
302
+ nextSynchronizationId: () => ++this.#nextSynchronizationId,
303
+ invalidateEvidence: (uri) => {
304
+ this.#failedUris.add(uri);
305
+ const document = this.#openDocs.get(uri);
306
+ if (document) document.evidenceRevision = -1;
307
+ },
308
+ clearFile: (uri) => {
309
+ this.#failedUris.delete(uri);
310
+ this.#unversionedPushSyncMoments.delete(uri);
311
+ this.#closedVersionedBarrier.add(uri);
312
+ clearTrackedDocumentState(this.#openDocs, this.#diagnosticStore, this.#waiters, uri);
313
+ },
314
+ markUnversionedSyncMoment: (uri) => this.#unversionedPushSyncMoments.set(uri, Date.now()),
315
+ clearFailedFile: (uri) => this.#failedUris.delete(uri),
316
+ forceResynchronize,
317
+ options,
318
+ publications: {
319
+ emitSummary: (summaryOptions) =>
320
+ this.#publications.emitSummary({
321
+ ...summaryOptions,
322
+ identity: {
323
+ server: this.host.server,
324
+ cwd: this.host.cwd,
325
+ ...summaryOptions.identity,
326
+ },
327
+ }),
328
+ },
329
+ });
208
330
  }
209
331
 
210
- /** Sync one file and return its diagnostics after pull or push collection. */
211
- async syncAndWaitForDiagnostics(filePath: string, content: string): Promise<Diagnostic[]> {
332
+ async syncAndWaitForDiagnostics(
333
+ filePath: string,
334
+ content: string,
335
+ control?: CodeRequestControl,
336
+ ): Promise<CodeQueryResult<Diagnostic[]>> {
337
+ // Reject immediately when the request was already cancelled: no document
338
+ // synchronization or protocol traffic may start for a caller that no
339
+ // longer awaits a result.
340
+ throwIfCodeRequestInterrupted(control);
212
341
  const supportsPull = this.host.supportsPullDiagnostics();
213
- const observer = new DiagnosticObserver("sync-file", supportsPull);
342
+ const observer = new DiagnosticObserver("sync-file", supportsPull, control, {
343
+ server: this.host.server,
344
+ cwd: this.host.cwd,
345
+ file: relativeDiagnosticFile(this.host.cwd, filePath),
346
+ });
214
347
  const uri = fileToUri(filePath);
348
+ const cached = this.#diagnosticStore.get(uri);
349
+ const cachedDiagnostics = cached ? [...cached.diagnostics] : null;
215
350
  const syncStart = Date.now();
216
- this.didChange(filePath, content);
351
+ const openDocument = this.#openDocs.get(uri);
352
+ const contentUnchanged =
353
+ openDocument?.contentFingerprint === fingerprintDocumentContent(content);
354
+ const synchronizationCurrent = openDocument?.evidenceRevision === this.#evidenceRevision;
355
+ // Equivalent content joins the current synchronization. Cached evidence
356
+ // cannot force a no-op didChange that cancels an in-flight push or pull.
357
+ if (!contentUnchanged || !synchronizationCurrent) {
358
+ synchronizeTrackedDocument({
359
+ uri,
360
+ content,
361
+ document: this.#openDocs.get(uri),
362
+ nextVersion: () => nextDocumentVersion(this.#versionHistory, uri),
363
+ nextSynchronizationId: () => ++this.#nextSynchronizationId,
364
+ evidenceRevision: this.#evidenceRevision,
365
+ incrementalSync: this.host.usesIncrementalDocumentSync(),
366
+ waiters: this.#waiters,
367
+ sendNotification: (method, params) => this.host.sendNotification(method, params),
368
+ markUnversionedSyncMoment: () => this.#unversionedPushSyncMoments.set(uri, Date.now()),
369
+ clearFailedFile: () => this.#failedUris.delete(uri),
370
+ open: () => this.didOpen(filePath, content),
371
+ });
372
+ }
373
+ const synchronization = this.#openDocs.get(uri);
217
374
  observer.synchronized();
218
-
219
- if (supportsPull) {
220
- const remaining = DIAGNOSTIC_WAIT_MS - (Date.now() - syncStart);
221
- try {
222
- if (remaining <= 0) observer.pullTimedOut();
223
- else if (await this.#pullDiagnosticsForUri(uri, remaining)) {
224
- observer.pullCompleted(1);
225
- return this.getDiagnostics(filePath);
226
- } else observer.pullFailed(undefined);
227
- } catch (error) {
228
- observer.pullFailed(error);
229
- }
375
+ if (!synchronization) {
376
+ return unavailableCodeQuery("The document could not be synchronized for diagnostics.");
230
377
  }
231
-
232
- const push = await this.#waitForDiagnostics(
378
+ if (
379
+ contentUnchanged &&
380
+ hasConfirmedDiagnosticEvidence(synchronization, cached, this.#evidenceRevision)
381
+ ) {
382
+ observer.cacheReused(1);
383
+ return completedCodeQuery(cachedDiagnostics ?? []);
384
+ }
385
+ const request = {
233
386
  uri,
234
- Math.max(0, DIAGNOSTIC_WAIT_MS - (Date.now() - syncStart)),
387
+ synchronizationId: synchronization.synchronizationId,
388
+ evidenceRevision: synchronization.evidenceRevision,
389
+ };
390
+ return this.#collectFileDiagnosticsWithReopenRetry(
391
+ {
392
+ filePath,
393
+ content,
394
+ uri,
395
+ supportsPull,
396
+ syncStart,
397
+ request,
398
+ cachedDiagnostics,
399
+ observer,
400
+ },
401
+ control,
235
402
  );
236
- observer.pushWaitCompleted(1, push);
237
- return this.getDiagnostics(filePath);
238
403
  }
239
404
 
240
- #resyncOpenDocuments(): void {
241
- for (const [uri, doc] of this.#openDocs) {
242
- const filePath = uriToFile(uri);
243
- try {
244
- if (!existsSync(filePath)) {
245
- this.#clearFileState(uri);
246
- this.#sendDidClose(uri);
247
- continue;
248
- }
249
- const content = readFileSync(filePath, "utf-8");
250
- doc.version++;
251
- this.#sendDidChange(uri, doc.version, content);
252
- } catch {
253
- // Keep the document open when a transient read fails.
405
+ /**
406
+ * Collect diagnostics for one synchronized document, retrying once through
407
+ * a reopen-resync on push-only routes when the first wait times out.
408
+ */
409
+ async #collectFileDiagnosticsWithReopenRetry(
410
+ options: {
411
+ filePath: string;
412
+ content: string;
413
+ uri: string;
414
+ supportsPull: boolean;
415
+ syncStart: number;
416
+ request: DiagnosticSynchronization;
417
+ cachedDiagnostics: Diagnostic[] | null;
418
+ observer: DiagnosticObserver;
419
+ },
420
+ control?: CodeRequestControl,
421
+ ): Promise<CodeQueryResult<Diagnostic[]>> {
422
+ const {
423
+ filePath,
424
+ content,
425
+ uri,
426
+ supportsPull,
427
+ syncStart,
428
+ request,
429
+ cachedDiagnostics,
430
+ observer,
431
+ } = options;
432
+ let pushOutcome: DiagnosticPushWaitOutcome | undefined;
433
+ let attemptRequest = request;
434
+ let attemptSyncStart = syncStart;
435
+ let attemptMaxWaitMs = DIAGNOSTIC_WAIT_MS;
436
+ let reopenedOnce = false;
437
+ let result: CodeQueryResult<Diagnostic[]>;
438
+ for (;;) {
439
+ result = await collectSynchronizedFileDiagnostics(
440
+ {
441
+ supportsPull,
442
+ syncStart: attemptSyncStart,
443
+ maxWaitMs: attemptMaxWaitMs,
444
+ request: attemptRequest,
445
+ cachedDiagnostics,
446
+ observer,
447
+ waiters: this.#waiters,
448
+ current: () => isCurrentSynchronization(this.#openDocs, attemptRequest),
449
+ freshPush: () =>
450
+ hasFreshPush(this.#diagnosticStore, attemptRequest, this.#evidenceRevision),
451
+ currentPushReceivedAt: () => {
452
+ const entry = this.#diagnosticStore.get(attemptRequest.uri);
453
+ return entry?.source === "push" &&
454
+ hasCurrentEvidence(this.#diagnosticStore, attemptRequest, this.#evidenceRevision)
455
+ ? entry.receivedAt
456
+ : undefined;
457
+ },
458
+ diagnostics: () => this.getDiagnostics(filePath),
459
+ pullDiagnostics: (timeoutMs, signal) =>
460
+ pullClientDiagnosticEvidenceFromHost({
461
+ host: this.host,
462
+ store: this.#diagnosticStore,
463
+ openDocuments: this.#openDocs,
464
+ currentEvidenceRevision: () => this.#evidenceRevision,
465
+ isRelatedUriTracked: (relatedUri) =>
466
+ this.#openDocs.has(relatedUri) || this.#versionHistory.has(relatedUri),
467
+ request: {
468
+ uri,
469
+ timeoutMs,
470
+ synchronizationId: attemptRequest.synchronizationId,
471
+ evidenceRevision: attemptRequest.evidenceRevision ?? this.#evidenceRevision,
472
+ signal,
473
+ deadline: control?.deadline,
474
+ operationId: control?.operationId,
475
+ },
476
+ }),
477
+ onPushWait: (outcome) => {
478
+ pushOutcome = outcome;
479
+ },
480
+ },
481
+ control,
482
+ );
483
+ // Reopen-resync fallback (R2): on push-only routes a document that
484
+ // timed out with no push stays unconfirmed — a clean file gets no
485
+ // push on didChange at all. Close and reopen it over the protocol so
486
+ // the server publishes on didOpen, then wait once more with a bounded
487
+ // budget. The cache entry and version history survive the reopen.
488
+ const document = this.#openDocs.get(uri);
489
+ if (
490
+ supportsPull ||
491
+ reopenedOnce ||
492
+ result.kind === "completed" ||
493
+ pushOutcome !== "timed-out" ||
494
+ !document ||
495
+ !isCurrentSynchronization(this.#openDocs, attemptRequest)
496
+ ) {
497
+ break;
254
498
  }
255
- }
256
- }
257
-
258
- async #pullDiagnosticsForOpenDocuments(syncStart: number, maxWaitMs: number): Promise<void> {
259
- const deadline = syncStart + maxWaitMs;
260
- const uris = Array.from(this.#openDocs.keys());
261
- const results = await Promise.allSettled(
262
- uris.map(async (uri) => {
263
- const remaining = deadline - Date.now();
264
- if (remaining <= 0) throw new Error("pull diagnostic timeout");
265
- return this.#pullDiagnosticsForUri(uri, remaining);
266
- }),
267
- );
268
-
269
- const anySuccess = results.some((result) => result.status === "fulfilled" && result.value);
270
- const failures = results.filter((result) => result.status === "rejected");
271
- if ((failures.length > 0 || !anySuccess) && uris.length > 0) {
272
- throw new DiagnosticPullError(failures.some((result) => isDiagnosticTimeout(result.reason)));
273
- }
274
- }
275
-
276
- async #pullDiagnosticsForUri(uri: string, timeoutMs: number): Promise<boolean> {
277
- const previousResultId = this.#diagnosticStore.get(uri)?.resultId;
278
- const report = await this.host.pullDocumentDiagnostics(uri, previousResultId, timeoutMs);
279
- if (!report) return false;
280
- this.#applyPullReport(uri, report);
281
- return true;
282
- }
283
-
284
- #applyPullReport(uri: string, report: DocumentDiagnosticReport): void {
285
- if (report.kind === "full") {
286
- this.#diagnosticStore.set(uri, {
287
- diagnostics: report.items,
288
- receivedAt: Date.now(),
289
- resultId: report.resultId,
290
- });
291
- } else {
292
- const current = this.#diagnosticStore.get(uri);
293
- if (current) current.resultId = report.resultId;
294
- }
295
-
296
- for (const [relatedUri, relatedReport] of Object.entries(report.relatedDocuments ?? {})) {
297
- if (relatedReport.kind !== "full") continue;
298
- this.#diagnosticStore.set(relatedUri, {
299
- diagnostics: relatedReport.items,
300
- receivedAt: Date.now(),
301
- resultId: relatedReport.resultId,
499
+ reopenDocument({
500
+ uri,
501
+ content,
502
+ document,
503
+ languageId: detectLanguageId(filePath),
504
+ nextVersion: () => nextDocumentVersion(this.#versionHistory, uri),
505
+ nextSynchronizationId: () => ++this.#nextSynchronizationId,
506
+ evidenceRevision: this.#evidenceRevision,
507
+ waiters: this.#waiters,
508
+ sendNotification: (method, params) => this.host.sendNotification(method, params),
509
+ markUnversionedSyncMoment: () => this.#unversionedPushSyncMoments.set(uri, Date.now()),
302
510
  });
303
- }
304
- }
305
-
306
- async #waitForDiagnosticSettle(
307
- syncStart: number,
308
- maxWaitMs: number,
309
- quietMs: number,
310
- ): Promise<DiagnosticSettleResult> {
311
- const deadline = syncStart + maxWaitMs;
312
- while (Date.now() < deadline) {
313
- const observedAt = this.#lastDiagnosticReceivedAfter(syncStart);
314
- const elapsed = Date.now() - (observedAt || syncStart);
315
- if (elapsed >= quietMs) {
316
- return {
317
- outcome: "quiet",
318
- freshness: observedAt > 0 ? "observed" : "not-observed",
319
- };
320
- }
321
- await new Promise((resolve) =>
322
- setTimeout(resolve, Math.min(quietMs - elapsed, deadline - Date.now(), 50)),
323
- );
324
- }
325
- return {
326
- outcome: "timed-out",
327
- freshness: this.#lastDiagnosticReceivedAfter(syncStart) > 0 ? "observed" : "not-observed",
328
- };
329
- }
330
-
331
- #lastDiagnosticReceivedAfter(afterTime: number): number {
332
- let latest = 0;
333
- for (const entry of this.#diagnosticStore.values()) {
334
- if (entry.receivedAt > afterTime && entry.receivedAt > latest) latest = entry.receivedAt;
335
- }
336
- return latest;
337
- }
338
-
339
- #sendDidChange(uri: string, version: number, content: string): void {
340
- this.host.sendNotification("textDocument/didChange", {
341
- textDocument: { uri, version } satisfies VersionedTextDocumentIdentifier,
342
- contentChanges: [{ text: content }],
343
- });
344
- }
345
-
346
- #sendDidClose(uri: string): void {
347
- this.host.sendNotification("textDocument/didClose", {
348
- textDocument: { uri } satisfies TextDocumentIdentifier,
349
- });
350
- }
351
-
352
- #clearFileState(uri: string): void {
353
- this.#openDocs.delete(uri);
354
- this.#diagnosticStore.delete(uri);
355
- this.#releaseDiagnosticWaiters(uri);
356
- }
357
-
358
- #waitForDiagnostics(uri: string, timeoutMs: number): Promise<DiagnosticPushWaitOutcome> {
359
- if (timeoutMs <= 0) return Promise.resolve("timed-out");
360
-
361
- return new Promise<DiagnosticPushWaitOutcome>((resolve) => {
362
- const waiter: DiagnosticWaiter = (outcome) => {
363
- clearTimeout(timer);
364
- this.#removeDiagnosticWaiter(uri, waiter);
365
- resolve(outcome);
511
+ observer.reopened(1);
512
+ reopenedOnce = true;
513
+ attemptRequest = {
514
+ uri,
515
+ synchronizationId: document.synchronizationId,
516
+ evidenceRevision: document.evidenceRevision,
366
517
  };
367
- const timer = setTimeout(() => {
368
- this.#removeDiagnosticWaiter(uri, waiter);
369
- resolve("timed-out");
370
- }, timeoutMs);
371
- const waiters = this.#diagnosticWaiters.get(uri) ?? [];
372
- waiters.push(waiter);
373
- this.#diagnosticWaiters.set(uri, waiters);
374
- });
375
- }
376
-
377
- #removeDiagnosticWaiter(uri: string, waiter: DiagnosticWaiter): void {
378
- const waiters = this.#diagnosticWaiters.get(uri);
379
- if (!waiters) return;
380
- const next = waiters.filter((entry) => entry !== waiter);
381
- if (next.length > 0) this.#diagnosticWaiters.set(uri, next);
382
- else this.#diagnosticWaiters.delete(uri);
383
- }
384
-
385
- #releaseAllDiagnosticWaiters(): void {
386
- for (const uri of Array.from(this.#diagnosticWaiters.keys())) {
387
- this.#releaseDiagnosticWaiters(uri);
518
+ attemptSyncStart = Date.now();
519
+ attemptMaxWaitMs = REOPEN_EVIDENCE_WAIT_MS;
388
520
  }
521
+ observer.pushWaitCompleted(1, pushOutcome ?? "timed-out");
522
+ this.#publications.emitSummary({
523
+ operation: "sync-file",
524
+ identity: {
525
+ server: this.host.server,
526
+ cwd: this.host.cwd,
527
+ file: relativeDiagnosticFile(this.host.cwd, filePath),
528
+ },
529
+ synchronizations: [
530
+ {
531
+ uri,
532
+ synchronizationId: attemptRequest.synchronizationId,
533
+ evidenceRevision: attemptRequest.evidenceRevision ?? this.#evidenceRevision,
534
+ confirmed: result.kind === "completed",
535
+ },
536
+ ],
537
+ operationId: control?.operationId,
538
+ });
539
+ return result;
389
540
  }
541
+ }
390
542
 
391
- #releaseDiagnosticWaiters(uri: string, outcome: "published" | "released" = "released"): void {
392
- const waiters = this.#diagnosticWaiters.get(uri);
393
- if (!waiters) return;
394
- this.#diagnosticWaiters.delete(uri);
395
- for (const waiter of waiters) waiter(outcome);
396
- }
543
+ /** Return a workspace-relative diagnostic file path for telemetry identity. */
544
+ function relativeDiagnosticFile(cwd: string | undefined, filePath: string): string | undefined {
545
+ if (cwd === undefined) return undefined;
546
+ return path.relative(cwd, path.resolve(cwd, filePath));
397
547
  }