@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
@@ -0,0 +1,690 @@
1
+ // biome-ignore-all lint/style/noExcessiveLinesPerFile: refresh orchestration, pull collection, and the reopen fallback stay in one cohesive module.
2
+ import { readFileSync } from "node:fs";
3
+ import {
4
+ type CodeRequestControl,
5
+ isCodeRequestInterruption,
6
+ throwIfCodeRequestInterrupted,
7
+ } from "@mrclrchtr/supi-code-runtime/api";
8
+ import type { TextDocumentIdentifier } from "../config/types.ts";
9
+ import {
10
+ type DiagnosticEvidenceSummary,
11
+ summarizeDiagnosticEvidence,
12
+ } from "../diagnostics/evidence.ts";
13
+ import { detectLanguageId, fileToUri, uriToFile } from "../utils.ts";
14
+ import {
15
+ type DiagnosticCacheEntry,
16
+ type DiagnosticSynchronization,
17
+ hasCurrentEvidence,
18
+ hasFreshEvidence,
19
+ hasFreshPush,
20
+ isCurrentSynchronization,
21
+ latestCurrentEvidenceReceivedAt,
22
+ nextDocumentVersion,
23
+ raceDiagnosticPull,
24
+ } from "./client-diagnostic-evidence.ts";
25
+ import type { ClientDiagnosticsHost } from "./client-diagnostic-host.ts";
26
+ import type {
27
+ DiagnosticPublicationIdentity,
28
+ DiagnosticPublicationSynchronization,
29
+ } from "./client-diagnostic-publication.ts";
30
+ import { pullDiagnosticEvidence } from "./client-diagnostic-pull.ts";
31
+ import type { DiagnosticPullRequest } from "./client-diagnostic-request.ts";
32
+ import {
33
+ DiagnosticObserver,
34
+ DiagnosticPullError,
35
+ isDiagnosticTimeout,
36
+ } from "./client-diagnostic-timing.ts";
37
+ import type { DiagnosticStateWait, DiagnosticWaitRegistry } from "./client-diagnostic-waiters.ts";
38
+ import {
39
+ fingerprintDocumentContent,
40
+ hasConfirmedDiagnosticEvidence,
41
+ type OpenDocumentState,
42
+ } from "./client-document-state.ts";
43
+ import {
44
+ type ResynchronizeDocumentsResult,
45
+ reopenDocument,
46
+ resynchronizeOpenDocuments,
47
+ } from "./client-document-sync.ts";
48
+ import { getDiagnosticFileState } from "./client-file-state.ts";
49
+
50
+ /** Send the protocol close notification for one removed document. */
51
+ export function sendDidCloseNotification(
52
+ host: Pick<ClientDiagnosticsHost, "sendNotification">,
53
+ uri: string,
54
+ ): void {
55
+ host.sendNotification("textDocument/didClose", {
56
+ textDocument: { uri } satisfies TextDocumentIdentifier,
57
+ });
58
+ }
59
+
60
+ /** Build final document coverage after one client refresh attempt. */
61
+ export function buildDiagnosticRefreshEvidence(options: {
62
+ requestedFiles: readonly string[];
63
+ resynchronization: ResynchronizeDocumentsResult;
64
+ /** Synchronizations that prove evidence, after reopen-resync updates. */
65
+ synchronizations: readonly DiagnosticSynchronization[];
66
+ failedPullUris: ReadonlySet<string>;
67
+ failedFiles: ReadonlySet<string>;
68
+ failedResynchronizations: ReadonlySet<string>;
69
+ currentEvidenceRevision: number;
70
+ openDocuments: ReadonlyMap<string, unknown>;
71
+ diagnosticStore: ReadonlyMap<string, DiagnosticCacheEntry>;
72
+ /** Required quiet time after the latest push for this refresh result. */
73
+ pushQuietMs?: number;
74
+ }): DiagnosticEvidenceSummary {
75
+ const synchronizationByFile = new Map(
76
+ options.synchronizations.map((item) => [uriToFile(item.uri), item]),
77
+ );
78
+ const removedFiles = new Set(options.resynchronization.removedFiles);
79
+ const failedFiles = new Set(options.resynchronization.failedFiles);
80
+ const observedAt = Date.now();
81
+ const documents = options.requestedFiles.map((file) => {
82
+ const uri = fileToUri(file);
83
+ const synchronization = synchronizationByFile.get(file);
84
+ if (removedFiles.has(file) || getDiagnosticFileState(file) === "removed") {
85
+ return { file, status: "removed" as const };
86
+ }
87
+ if (
88
+ failedFiles.has(file) ||
89
+ options.failedFiles.has(file) ||
90
+ options.failedResynchronizations.has(file)
91
+ ) {
92
+ return { file, status: "failed" as const };
93
+ }
94
+ if (!options.openDocuments.has(uri)) {
95
+ return { file, status: "unconfirmed" as const };
96
+ }
97
+ if (
98
+ synchronization &&
99
+ hasSettledRefreshEvidence({
100
+ store: options.diagnosticStore,
101
+ synchronization,
102
+ currentEvidenceRevision: options.currentEvidenceRevision,
103
+ pushQuietMs: options.pushQuietMs,
104
+ observedAt,
105
+ })
106
+ ) {
107
+ return { file, status: "confirmed" as const };
108
+ }
109
+ return {
110
+ file,
111
+ status: options.failedPullUris.has(uri) ? ("failed" as const) : ("unconfirmed" as const),
112
+ };
113
+ });
114
+ return summarizeDiagnosticEvidence(documents);
115
+ }
116
+
117
+ /** Test whether fresh evidence can confirm this refresh result. */
118
+ function hasSettledRefreshEvidence(options: {
119
+ store: ReadonlyMap<string, DiagnosticCacheEntry>;
120
+ synchronization: DiagnosticSynchronization;
121
+ currentEvidenceRevision: number;
122
+ pushQuietMs: number | undefined;
123
+ observedAt: number;
124
+ }): boolean {
125
+ if (!hasFreshEvidence(options.store, options.synchronization, options.currentEvidenceRevision)) {
126
+ return false;
127
+ }
128
+ const entry = options.store.get(options.synchronization.uri);
129
+ return (
130
+ options.pushQuietMs === undefined ||
131
+ entry?.source !== "push" ||
132
+ options.observedAt - entry.receivedAt >= options.pushQuietMs
133
+ );
134
+ }
135
+
136
+ /** Pull one document and reject evidence from a stale client generation. */
137
+ export function pullClientDiagnosticEvidence(
138
+ options: Omit<DiagnosticPullRequest, "previousResultId"> & {
139
+ store: Map<string, DiagnosticCacheEntry>;
140
+ synchronizationId?: number;
141
+ evidenceRevision: number;
142
+ currentRevision(): number;
143
+ isCurrentSynchronization(): boolean;
144
+ isRelatedUriTracked(uri: string): boolean;
145
+ pull(
146
+ request: DiagnosticPullRequest,
147
+ ): Promise<import("../config/types.ts").DocumentDiagnosticReport | null>;
148
+ },
149
+ ): Promise<boolean> {
150
+ return pullDiagnosticEvidence(options);
151
+ }
152
+
153
+ /** Pull one document through a client's host and preserve generation checks. */
154
+ export function pullClientDiagnosticEvidenceFromHost(options: {
155
+ host: Pick<ClientDiagnosticsHost, "pullDocumentDiagnostics">;
156
+ store: Map<string, DiagnosticCacheEntry>;
157
+ openDocuments: ReadonlyMap<string, { synchronizationId: number; evidenceRevision?: number }>;
158
+ currentEvidenceRevision(): number;
159
+ isRelatedUriTracked(uri: string): boolean;
160
+ request: Omit<DiagnosticPullRequest, "previousResultId"> & {
161
+ synchronizationId?: number;
162
+ evidenceRevision?: number;
163
+ };
164
+ }): Promise<boolean> {
165
+ return pullClientDiagnosticEvidence({
166
+ store: options.store,
167
+ ...options.request,
168
+ evidenceRevision: options.request.evidenceRevision ?? options.currentEvidenceRevision(),
169
+ currentRevision: options.currentEvidenceRevision,
170
+ isCurrentSynchronization: () =>
171
+ options.request.synchronizationId === undefined ||
172
+ isCurrentSynchronization(options.openDocuments, {
173
+ uri: options.request.uri,
174
+ synchronizationId: options.request.synchronizationId,
175
+ evidenceRevision: options.request.evidenceRevision,
176
+ }),
177
+ isRelatedUriTracked: options.isRelatedUriTracked,
178
+ pull: (request) => options.host.pullDocumentDiagnostics(request),
179
+ });
180
+ }
181
+
182
+ /** Collect pull evidence for every synchronized document in one refresh. */
183
+ export async function pullDiagnosticsForOpenDocuments(options: {
184
+ requests: readonly DiagnosticSynchronization[];
185
+ syncStart: number;
186
+ maxWaitMs: number;
187
+ signal?: AbortSignal;
188
+ deadline?: number;
189
+ operationId?: string;
190
+ currentEvidenceRevision: () => number;
191
+ openDocuments: ReadonlyMap<string, { evidenceRevision: number; synchronizationId: number }>;
192
+ diagnosticStore: ReadonlyMap<string, DiagnosticCacheEntry>;
193
+ waitForChange: () => DiagnosticStateWait;
194
+ pullDiagnostics: (options: {
195
+ request: DiagnosticSynchronization;
196
+ timeoutMs: number;
197
+ signal: AbortSignal;
198
+ operationId?: string;
199
+ deadline?: number;
200
+ }) => Promise<boolean>;
201
+ }): Promise<void> {
202
+ const deadline = options.syncStart + options.maxWaitMs;
203
+ const results = await Promise.allSettled(
204
+ options.requests.map(async (request) => {
205
+ const remaining = deadline - Date.now();
206
+ if (remaining <= 0) throw new Error("pull diagnostic timeout");
207
+ const pullController = new AbortController();
208
+ // Link the caller's cancellation to this document's pull controller so
209
+ // an in-flight pull receives protocol cancellation and stops promptly.
210
+ const onAbort = () => pullController.abort(options.signal?.reason);
211
+ if (options.signal?.aborted) onAbort();
212
+ else options.signal?.addEventListener("abort", onAbort, { once: true });
213
+ try {
214
+ const outcome = await raceDiagnosticPull({
215
+ pull: options.pullDiagnostics({
216
+ request,
217
+ timeoutMs: remaining,
218
+ signal: pullController.signal,
219
+ operationId: options.operationId,
220
+ deadline: options.deadline,
221
+ }),
222
+ waitForChange: options.waitForChange,
223
+ freshPush: () =>
224
+ hasFreshPush(options.diagnosticStore, request, options.currentEvidenceRevision()),
225
+ current: () => isCurrentSynchronization(options.openDocuments, request),
226
+ });
227
+ if (outcome !== "pull") pullController.abort();
228
+ return outcome === "pull";
229
+ } finally {
230
+ options.signal?.removeEventListener("abort", onAbort);
231
+ }
232
+ }),
233
+ );
234
+
235
+ // A cancelled refresh must not degrade into failed coverage evidence:
236
+ // the caller no longer awaits a result.
237
+ throwIfCodeRequestInterrupted({ signal: options.signal, deadline: options.deadline });
238
+
239
+ const incomplete = results.some((result) => result.status === "rejected" || !result.value);
240
+ if (incomplete && options.requests.length > 0) {
241
+ const failedUris = options.requests.flatMap((request, index) => {
242
+ const result = results[index];
243
+ return result?.status === "rejected" && !isDiagnosticTimeout(result.reason)
244
+ ? [request.uri]
245
+ : [];
246
+ });
247
+ throw new DiagnosticPullError(
248
+ results.some((result) => result.status === "rejected" && isDiagnosticTimeout(result.reason)),
249
+ failedUris,
250
+ );
251
+ }
252
+ }
253
+
254
+ /**
255
+ * Classify open documents for a refresh by disk content.
256
+ *
257
+ * A document whose disk content still matches its open fingerprint stays in
258
+ * the server's current state:
259
+ * - with current evidence it is reusable without document synchronization;
260
+ * - without current evidence it is retained: it keeps its synchronization and
261
+ * collection waits for the server's existing pipeline instead of forcing a
262
+ * no-op didChange. Large push-only servers (typescript-language-server)
263
+ * skip empty-to-empty publishes, so a no-op didChange of a clean file can
264
+ * never confirm — it only invalidates in-progress evidence and restarts
265
+ * full-program checks (#344).
266
+ *
267
+ * Documents outside the current evidence revision were invalidated by a
268
+ * workspace change and must resynchronize: their stale revision cannot
269
+ * produce fresh evidence without an explicit sync (ADR 0020).
270
+ */
271
+ function classifyReusableDocuments(options: {
272
+ openDocuments: ReadonlyMap<string, OpenDocumentState>;
273
+ diagnosticStore: ReadonlyMap<string, DiagnosticCacheEntry>;
274
+ evidenceRevision: number;
275
+ failedFiles: ReadonlySet<string>;
276
+ }): {
277
+ reusableUris: Set<string>;
278
+ retainedUris: Set<string>;
279
+ preloadedContent: Map<string, string>;
280
+ } {
281
+ const reusableUris = new Set<string>();
282
+ const retainedUris = new Set<string>();
283
+ const preloadedContent = new Map<string, string>();
284
+ for (const [uri, document] of options.openDocuments) {
285
+ const filePath = uriToFile(uri);
286
+ if (options.failedFiles.has(filePath)) continue;
287
+ let content: string;
288
+ try {
289
+ content = readFileSync(filePath, "utf-8");
290
+ } catch {
291
+ // The resynchronization path classifies removed and unreadable files.
292
+ continue;
293
+ }
294
+ if (document.evidenceRevision !== options.evidenceRevision) {
295
+ // Invalidated generation: the resync didChange re-establishes proof.
296
+ preloadedContent.set(uri, content);
297
+ continue;
298
+ }
299
+ if (fingerprintDocumentContent(content) !== document.contentFingerprint) {
300
+ preloadedContent.set(uri, content);
301
+ continue;
302
+ }
303
+ if (
304
+ hasConfirmedDiagnosticEvidence(
305
+ document,
306
+ options.diagnosticStore.get(uri),
307
+ options.evidenceRevision,
308
+ )
309
+ ) {
310
+ reusableUris.add(uri);
311
+ } else {
312
+ retainedUris.add(uri);
313
+ }
314
+ }
315
+ return { reusableUris, retainedUris, preloadedContent };
316
+ }
317
+
318
+ interface ClientDiagnosticRefreshOptions {
319
+ readonly host: ClientDiagnosticsHost;
320
+ readonly openDocuments: Map<string, OpenDocumentState>;
321
+ readonly diagnosticStore: Map<string, DiagnosticCacheEntry>;
322
+ readonly waiters: DiagnosticWaitRegistry;
323
+ readonly versionHistory: Map<string, number>;
324
+ readonly requestedFiles: readonly string[];
325
+ readonly evidenceRevision: () => number;
326
+ readonly failedFiles: () => ReadonlySet<string>;
327
+ readonly isRelatedUriTracked: (uri: string) => boolean;
328
+ readonly nextSynchronizationId: () => number;
329
+ readonly clearFile: (uri: string) => void;
330
+ readonly invalidateEvidence: (uri: string) => void;
331
+ readonly markUnversionedSyncMoment: (uri: string) => void;
332
+ readonly clearFailedFile: (uri: string) => void;
333
+ /** Server-requested refreshes bypass normal push-only evidence reuse. */
334
+ readonly forceResynchronize?: boolean;
335
+ readonly options: { maxWaitMs?: number; quietMs?: number } & CodeRequestControl;
336
+ /** Push-publication telemetry surface for this client. */
337
+ readonly publications: {
338
+ emitSummary(options: {
339
+ readonly operation: "refresh-open";
340
+ readonly identity: DiagnosticPublicationIdentity;
341
+ readonly synchronizations: readonly DiagnosticPublicationSynchronization[];
342
+ readonly operationId?: string;
343
+ }): void;
344
+ };
345
+ }
346
+
347
+ interface PreparedRefreshDocuments {
348
+ readonly resynchronization: ResynchronizeDocumentsResult;
349
+ readonly synchronizations: DiagnosticSynchronization[];
350
+ readonly fullyReusable: boolean;
351
+ }
352
+
353
+ /** Classify reusable documents, then resynchronize only the remaining set. */
354
+ function prepareRefreshDocuments(
355
+ options: ClientDiagnosticRefreshOptions,
356
+ supportsPull: boolean,
357
+ evidenceRevision: number,
358
+ ): PreparedRefreshDocuments {
359
+ const reuseEnabled = !options.forceResynchronize;
360
+ const classification = reuseEnabled
361
+ ? classifyReusableDocuments({
362
+ openDocuments: options.openDocuments,
363
+ diagnosticStore: options.diagnosticStore,
364
+ evidenceRevision,
365
+ failedFiles: options.failedFiles(),
366
+ })
367
+ : undefined;
368
+ const reusableUris = classification?.reusableUris ?? new Set<string>();
369
+ const retainedUris = classification?.retainedUris ?? new Set<string>();
370
+ const documentsToResynchronize = new Map(
371
+ Array.from(options.openDocuments).filter(
372
+ ([uri]) => !reusableUris.has(uri) && !retainedUris.has(uri),
373
+ ),
374
+ );
375
+ const resynchronization = resynchronizeOpenDocuments({
376
+ openDocuments: documentsToResynchronize,
377
+ waiters: options.waiters,
378
+ nextVersion: (uri) => nextDocumentVersion(options.versionHistory, uri),
379
+ nextSynchronizationId: options.nextSynchronizationId,
380
+ evidenceRevision,
381
+ incrementalSync: options.host.usesIncrementalDocumentSync(),
382
+ sendNotification: (method, params) => options.host.sendNotification(method, params),
383
+ uriToFile,
384
+ preloadedContent: classification?.preloadedContent,
385
+ clearFile: options.clearFile,
386
+ invalidateEvidence: options.invalidateEvidence,
387
+ markUnversionedSyncMoment: options.markUnversionedSyncMoment,
388
+ clearFailedFile: options.clearFailedFile,
389
+ });
390
+ const reusableSynchronizations: DiagnosticSynchronization[] = [];
391
+ for (const uri of reusableUris) {
392
+ const document = options.openDocuments.get(uri);
393
+ if (!document) continue;
394
+ reusableSynchronizations.push({
395
+ uri,
396
+ synchronizationId: document.synchronizationId,
397
+ evidenceRevision: document.evidenceRevision,
398
+ });
399
+ }
400
+ // Retained documents keep their current synchronization: settle waits for
401
+ // the server's existing pipeline without any protocol work.
402
+ const retainedSynchronizations: DiagnosticSynchronization[] = [];
403
+ for (const uri of retainedUris) {
404
+ const document = options.openDocuments.get(uri);
405
+ if (!document) continue;
406
+ retainedSynchronizations.push({
407
+ uri,
408
+ synchronizationId: document.synchronizationId,
409
+ evidenceRevision: document.evidenceRevision,
410
+ });
411
+ }
412
+ const synchronizations = [
413
+ ...reusableSynchronizations,
414
+ ...retainedSynchronizations,
415
+ ...resynchronization.synchronizations,
416
+ ];
417
+ // Pull-capable routes still request current diagnostics even when every
418
+ // document synchronization is reusable.
419
+ const fullyReusable =
420
+ reuseEnabled &&
421
+ !supportsPull &&
422
+ options.openDocuments.size > 0 &&
423
+ reusableUris.size === options.openDocuments.size;
424
+ return { resynchronization, synchronizations, fullyReusable };
425
+ }
426
+
427
+ /** Refresh one client and return exact document evidence for that attempt. */
428
+ export async function refreshClientOpenDiagnostics(
429
+ options: ClientDiagnosticRefreshOptions,
430
+ ): Promise<DiagnosticEvidenceSummary> {
431
+ // Reject immediately when the request was already cancelled: no document
432
+ // resynchronization or protocol traffic may start for a pass the caller
433
+ // no longer awaits.
434
+ throwIfCodeRequestInterrupted(options.options);
435
+ const supportsPull = options.host.supportsPullDiagnostics();
436
+ const observer = new DiagnosticObserver("refresh-open", supportsPull, options.options, {
437
+ server: options.host.server,
438
+ cwd: options.host.cwd,
439
+ });
440
+ if (!options.host.isOperational()) {
441
+ observer.skipped(options.requestedFiles.length);
442
+ return summarizeDiagnosticEvidence(
443
+ options.requestedFiles.map((file) => ({
444
+ file,
445
+ status:
446
+ getDiagnosticFileState(file) === "removed" ? ("removed" as const) : ("failed" as const),
447
+ })),
448
+ );
449
+ }
450
+
451
+ const maxWaitMs = options.options.maxWaitMs ?? 3_000;
452
+ const quietMs = options.options.quietMs ?? 200;
453
+ const syncStart = Date.now();
454
+ const prepared = prepareRefreshDocuments(options, supportsPull, options.evidenceRevision());
455
+ const resynchronization = prepared.resynchronization;
456
+ let synchronizations = prepared.synchronizations;
457
+ let failedPullUris: ReadonlySet<string> = new Set();
458
+ const buildEvidence = (pushQuietMs?: number) =>
459
+ buildDiagnosticRefreshEvidence({
460
+ requestedFiles: options.requestedFiles,
461
+ resynchronization,
462
+ synchronizations,
463
+ failedPullUris,
464
+ failedFiles: options.failedFiles(),
465
+ failedResynchronizations: new Set(resynchronization.failedFiles),
466
+ currentEvidenceRevision: options.evidenceRevision(),
467
+ openDocuments: options.openDocuments,
468
+ diagnosticStore: options.diagnosticStore,
469
+ pushQuietMs,
470
+ });
471
+
472
+ // A fully reusable push-only refresh has no protocol work to collect.
473
+ // Preserve the existing cache timing event format and return current evidence.
474
+ if (prepared.fullyReusable) {
475
+ observer.cacheReused(synchronizations.length);
476
+ return buildEvidence();
477
+ }
478
+
479
+ const settleEpoch = options.waiters.settleEpoch;
480
+ observer.synchronized();
481
+ if (synchronizations.length === 0) {
482
+ observer.skipped(0);
483
+ return buildEvidence();
484
+ }
485
+
486
+ if (supportsPull) {
487
+ const pull = await collectPullEvidenceForRefresh({
488
+ options,
489
+ synchronizations,
490
+ syncStart,
491
+ maxWaitMs,
492
+ observer,
493
+ });
494
+ failedPullUris = new Set(pull.failedPullUris);
495
+ if (pull.completed) return buildEvidence();
496
+ }
497
+
498
+ const waitForDiagnosticSettle = (settleStart: number, settleGeneration: number) =>
499
+ options.waiters.waitForSettle(
500
+ {
501
+ syncStart: settleStart,
502
+ maxWaitMs,
503
+ quietMs,
504
+ settleEpoch: settleGeneration,
505
+ isComplete: () =>
506
+ synchronizations.every((item) =>
507
+ hasFreshEvidence(options.diagnosticStore, item, options.evidenceRevision()),
508
+ ),
509
+ latestReceived: () =>
510
+ latestCurrentEvidenceReceivedAt(
511
+ options.diagnosticStore,
512
+ synchronizations,
513
+ options.evidenceRevision(),
514
+ ),
515
+ },
516
+ options.options,
517
+ );
518
+
519
+ let finalSettle = await waitForDiagnosticSettle(syncStart, settleEpoch);
520
+ if (!supportsPull && finalSettle.outcome === "timed-out") {
521
+ const reopen = await reopenUnconfirmedDocuments({
522
+ options,
523
+ synchronizations,
524
+ reopenCandidates: resynchronization.resynchronizedUris,
525
+ observer,
526
+ });
527
+ if (reopen.performed) {
528
+ synchronizations = reopen.synchronizations;
529
+ // A large push-only project may still be processing the reopen batch.
530
+ // The replacement pass uses the same collection budget as the initial pass.
531
+ finalSettle = await waitForDiagnosticSettle(reopen.startedAt, options.waiters.settleEpoch);
532
+ }
533
+ }
534
+ observer.pushSettled(synchronizations.length, finalSettle);
535
+ // A cancelled settle must not publish evidence the caller no longer awaits.
536
+ throwIfCodeRequestInterrupted(options.options);
537
+ options.publications.emitSummary({
538
+ operation: "refresh-open",
539
+ identity: {
540
+ server: options.host.server,
541
+ cwd: options.host.cwd,
542
+ },
543
+ synchronizations: synchronizations.map((synchronization) => ({
544
+ uri: synchronization.uri,
545
+ synchronizationId: synchronization.synchronizationId,
546
+ evidenceRevision: synchronization.evidenceRevision ?? options.evidenceRevision(),
547
+ confirmed: hasFreshEvidence(
548
+ options.diagnosticStore,
549
+ synchronization,
550
+ options.evidenceRevision(),
551
+ ),
552
+ })),
553
+ operationId: options.options.operationId,
554
+ });
555
+ return buildEvidence(quietMs);
556
+ }
557
+
558
+ /**
559
+ * Pull diagnostic evidence for every synchronized document, or fall through
560
+ * to the push settle path when any pull fails. An interruption during the
561
+ * pull phase stops the refresh instead of degrading into failed coverage.
562
+ */
563
+ async function collectPullEvidenceForRefresh(options: {
564
+ options: ClientDiagnosticRefreshOptions;
565
+ synchronizations: readonly DiagnosticSynchronization[];
566
+ syncStart: number;
567
+ maxWaitMs: number;
568
+ observer: DiagnosticObserver;
569
+ }): Promise<{ completed: boolean; failedPullUris: ReadonlySet<string> }> {
570
+ const { options: refresh, synchronizations, syncStart, maxWaitMs, observer } = options;
571
+ try {
572
+ await pullDiagnosticsForOpenDocuments({
573
+ requests: synchronizations,
574
+ syncStart,
575
+ maxWaitMs,
576
+ signal: refresh.options.signal,
577
+ deadline: refresh.options.deadline,
578
+ operationId: refresh.options.operationId,
579
+ currentEvidenceRevision: refresh.evidenceRevision,
580
+ openDocuments: refresh.openDocuments,
581
+ diagnosticStore: refresh.diagnosticStore,
582
+ waitForChange: () => refresh.waiters.waitForChange(),
583
+ pullDiagnostics: (pullOptions) =>
584
+ pullClientDiagnosticEvidenceFromHost({
585
+ host: refresh.host,
586
+ store: refresh.diagnosticStore,
587
+ openDocuments: refresh.openDocuments,
588
+ currentEvidenceRevision: refresh.evidenceRevision,
589
+ isRelatedUriTracked: refresh.isRelatedUriTracked,
590
+ request: {
591
+ uri: pullOptions.request.uri,
592
+ timeoutMs: pullOptions.timeoutMs,
593
+ synchronizationId: pullOptions.request.synchronizationId,
594
+ evidenceRevision:
595
+ refresh.openDocuments.get(pullOptions.request.uri)?.evidenceRevision ??
596
+ refresh.evidenceRevision(),
597
+ signal: pullOptions.signal,
598
+ deadline: pullOptions.deadline,
599
+ operationId: pullOptions.operationId,
600
+ },
601
+ }),
602
+ });
603
+ observer.pullCompleted(synchronizations.length);
604
+ return { completed: true, failedPullUris: new Set() };
605
+ } catch (error) {
606
+ observer.pullFailed(error);
607
+ if (error instanceof DiagnosticPullError) {
608
+ return { completed: false, failedPullUris: new Set(error.failedUris) };
609
+ }
610
+ if (isCodeRequestInterruption(error, refresh.options)) throw error;
611
+ return { completed: false, failedPullUris: new Set() };
612
+ }
613
+ }
614
+
615
+ /**
616
+ * Reopen-resync fallback (R2): on push-only routes a document that was
617
+ * didChange-synchronized and stays unconfirmed after the settle window may
618
+ * have been skipped by the server — a clean file gets no push on didChange
619
+ * at all, but the server publishes on didOpen. Close and reopen each such
620
+ * document so the server publishes, then settle again within a bounded
621
+ * second window. The cache entry and version history survive the reopen;
622
+ * other documents keep their server state.
623
+ *
624
+ * Only documents this pass resynchronized are candidates: retained documents
625
+ * wait for the server's existing pipeline, and reopening them would cancel
626
+ * in-progress server work without fixing any publish gap (#344).
627
+ */
628
+ async function reopenUnconfirmedDocuments(options: {
629
+ options: ClientDiagnosticRefreshOptions;
630
+ synchronizations: readonly DiagnosticSynchronization[];
631
+ /** URIs that received a didChange in this pass and may need a reopen push. */
632
+ reopenCandidates: ReadonlySet<string>;
633
+ observer: DiagnosticObserver;
634
+ }): Promise<{
635
+ performed: boolean;
636
+ startedAt: number;
637
+ synchronizations: DiagnosticSynchronization[];
638
+ }> {
639
+ const { options: refresh, synchronizations, reopenCandidates, observer } = options;
640
+ const startedAt = Date.now();
641
+ const unconfirmed = synchronizations.filter(
642
+ (item) =>
643
+ reopenCandidates.has(item.uri) &&
644
+ // A document with any current publication — tentative included — is
645
+ // not a reopen candidate: its server pipeline is alive and a republish
646
+ // can still promote the retained cache (ADR 0021).
647
+ !hasCurrentEvidence(refresh.diagnosticStore, item, refresh.evidenceRevision()),
648
+ );
649
+ const reopenedSynchronizations: DiagnosticSynchronization[] = [];
650
+ for (const item of unconfirmed) {
651
+ const document = refresh.openDocuments.get(item.uri);
652
+ if (!document) continue;
653
+ const filePath = uriToFile(item.uri);
654
+ let content: string;
655
+ try {
656
+ content = readFileSync(filePath, "utf-8");
657
+ } catch {
658
+ // The file disappeared mid-refresh; keep the document as-is and
659
+ // report its current unconfirmed coverage.
660
+ continue;
661
+ }
662
+ reopenDocument({
663
+ uri: item.uri,
664
+ content,
665
+ document,
666
+ languageId: detectLanguageId(filePath),
667
+ nextVersion: () => nextDocumentVersion(refresh.versionHistory, item.uri),
668
+ nextSynchronizationId: refresh.nextSynchronizationId,
669
+ evidenceRevision: refresh.evidenceRevision(),
670
+ waiters: refresh.waiters,
671
+ sendNotification: (method, params) => refresh.host.sendNotification(method, params),
672
+ markUnversionedSyncMoment: () => refresh.markUnversionedSyncMoment(item.uri),
673
+ });
674
+ reopenedSynchronizations.push({
675
+ uri: item.uri,
676
+ synchronizationId: document.synchronizationId,
677
+ evidenceRevision: document.evidenceRevision,
678
+ });
679
+ }
680
+ if (reopenedSynchronizations.length === 0) {
681
+ return { performed: false, startedAt, synchronizations: [...synchronizations] };
682
+ }
683
+ observer.reopened(reopenedSynchronizations.length);
684
+ const reopenedByUri = new Map(reopenedSynchronizations.map((item) => [item.uri, item]));
685
+ return {
686
+ performed: true,
687
+ startedAt,
688
+ synchronizations: synchronizations.map((item) => reopenedByUri.get(item.uri) ?? item),
689
+ };
690
+ }
@@ -0,0 +1,9 @@
1
+ /** Sanitized ownership and transport control for one explicit diagnostic pull. */
2
+ export interface DiagnosticPullRequest {
3
+ readonly uri: string;
4
+ readonly previousResultId: string | undefined;
5
+ readonly timeoutMs: number;
6
+ readonly signal?: AbortSignal;
7
+ readonly deadline?: number;
8
+ readonly operationId?: string;
9
+ }