@mrclrchtr/supi-code-intelligence 4.9.0 → 5.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 (215) hide show
  1. package/README.md +2 -1
  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 +1 -1
  10. package/node_modules/@mrclrchtr/supi-core/src/config/config.ts +31 -0
  11. package/node_modules/@mrclrchtr/supi-core/src/config.ts +2 -0
  12. package/node_modules/@mrclrchtr/supi-core/src/debug-registry.ts +19 -3
  13. package/node_modules/@mrclrchtr/supi-core/src/debug-timing.ts +34 -21
  14. package/node_modules/@mrclrchtr/supi-core/src/settings/settings-registry.ts +3 -0
  15. package/node_modules/@mrclrchtr/supi-lsp/README.md +80 -5
  16. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/README.md +2 -0
  17. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
  18. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/src/api.ts +9 -0
  19. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/src/capability/types.ts +53 -13
  20. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/src/request-control.ts +47 -0
  21. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/src/types.ts +13 -1
  22. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/README.md +2 -0
  23. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/package.json +1 -1
  24. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/config/config.ts +31 -0
  25. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/config.ts +2 -0
  26. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/debug-registry.ts +19 -3
  27. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/debug-timing.ts +34 -21
  28. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/settings/settings-registry.ts +3 -0
  29. package/node_modules/@mrclrchtr/supi-lsp/package.json +7 -6
  30. package/node_modules/@mrclrchtr/supi-lsp/src/api.ts +10 -0
  31. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-cache.ts +206 -0
  32. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-capabilities.ts +78 -0
  33. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-collection.ts +114 -0
  34. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-evidence.ts +372 -0
  35. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-host.ts +14 -0
  36. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-pull.ts +62 -0
  37. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-refresh.ts +491 -0
  38. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-request.ts +9 -0
  39. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-timing.ts +215 -0
  40. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-waiters.ts +206 -0
  41. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostics.ts +370 -266
  42. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-document-state.ts +52 -0
  43. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-document-sync.ts +185 -0
  44. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-file-state.ts +25 -0
  45. package/node_modules/@mrclrchtr/supi-lsp/src/client/client.ts +467 -115
  46. package/node_modules/@mrclrchtr/supi-lsp/src/client/transport.ts +238 -11
  47. package/node_modules/@mrclrchtr/supi-lsp/src/config/capabilities.ts +10 -1
  48. package/node_modules/@mrclrchtr/supi-lsp/src/config/defaults.json +1 -1
  49. package/node_modules/@mrclrchtr/supi-lsp/src/debug-telemetry.ts +45 -0
  50. package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/evidence.ts +38 -0
  51. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-client-state.ts +79 -9
  52. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-diagnostics.ts +13 -4
  53. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-workspace-recovery.ts +300 -24
  54. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-workspace-symbol.ts +5 -2
  55. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager.ts +464 -96
  56. package/node_modules/@mrclrchtr/supi-lsp/src/provider/lsp-refactor-provider.ts +130 -0
  57. package/node_modules/@mrclrchtr/supi-lsp/src/provider/lsp-semantic-provider.ts +45 -329
  58. package/node_modules/@mrclrchtr/supi-lsp/src/provider/refactor-planning.ts +57 -120
  59. package/node_modules/@mrclrchtr/supi-lsp/src/provider/semantic-edit-normalizer.ts +320 -0
  60. package/node_modules/@mrclrchtr/supi-lsp/src/provider/semantic-symbol-mapper.ts +207 -0
  61. package/node_modules/@mrclrchtr/supi-lsp/src/session/readiness.ts +72 -2
  62. package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-controller.ts +151 -26
  63. package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-diagnostic-surface.ts +40 -0
  64. package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-diagnostics.ts +50 -0
  65. package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-registry.ts +136 -120
  66. package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-transition-debug.ts +49 -0
  67. package/node_modules/@mrclrchtr/supi-lsp/src/session/workspace-lsp-runtime.ts +102 -0
  68. package/node_modules/@mrclrchtr/supi-tree-sitter/README.md +26 -6
  69. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/README.md +2 -0
  70. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
  71. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/src/api.ts +9 -0
  72. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/src/capability/types.ts +53 -13
  73. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/src/request-control.ts +47 -0
  74. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/src/types.ts +13 -1
  75. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/README.md +2 -0
  76. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/package.json +1 -1
  77. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/config/config.ts +31 -0
  78. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/config.ts +2 -0
  79. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/debug-registry.ts +19 -3
  80. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/debug-timing.ts +34 -21
  81. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/settings/settings-registry.ts +3 -0
  82. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/LICENSE +21 -0
  83. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/README.md +258 -0
  84. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/dist/babel.cjs +257 -0
  85. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/dist/jiti.cjs +1 -0
  86. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/lib/jiti-cli.mjs +34 -0
  87. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/lib/jiti-hooks.mjs +124 -0
  88. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/lib/jiti-native.mjs +121 -0
  89. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/lib/jiti-register.d.mts +1 -0
  90. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/lib/jiti-register.mjs +4 -0
  91. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/lib/jiti-static.mjs +23 -0
  92. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/lib/jiti.cjs +30 -0
  93. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/lib/jiti.d.cts +8 -0
  94. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/lib/jiti.d.mts +8 -0
  95. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/lib/jiti.mjs +29 -0
  96. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/lib/types.d.ts +420 -0
  97. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/package.json +146 -0
  98. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/debug/web-tree-sitter.wasm +0 -0
  99. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/debug/web-tree-sitter.wasm.map +4 -4
  100. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/package.json +1 -1
  101. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/web-tree-sitter.wasm +0 -0
  102. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/web-tree-sitter.wasm.map +4 -4
  103. package/node_modules/@mrclrchtr/supi-tree-sitter/package.json +7 -3
  104. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/kotlin/tree-sitter-kotlin.wasm.json +1 -1
  105. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/sql/tree-sitter-sql.wasm.json +1 -1
  106. package/node_modules/@mrclrchtr/supi-tree-sitter/src/api.ts +0 -9
  107. package/node_modules/@mrclrchtr/supi-tree-sitter/src/index.ts +0 -8
  108. package/node_modules/@mrclrchtr/supi-tree-sitter/src/provider/tree-sitter-provider.ts +16 -12
  109. package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/runtime-controller.ts +65 -87
  110. package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/session.ts +47 -95
  111. package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/structural-timing.ts +147 -0
  112. package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/structural-worker-client-lifecycle.ts +39 -0
  113. package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/structural-worker-client.ts +561 -0
  114. package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/structural-worker-message-size.ts +24 -0
  115. package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/structural-worker-protocol.ts +452 -0
  116. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/call-sites.ts +4 -2
  117. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/callees.ts +12 -4
  118. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/exports.ts +4 -2
  119. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/imports.ts +4 -2
  120. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/node-at.ts +5 -3
  121. package/node_modules/@mrclrchtr/supi-tree-sitter/src/types.ts +33 -12
  122. package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/blocking-test-bootstrap.mjs +34 -0
  123. package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/bootstrap.mjs +8 -0
  124. package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/parsed-file-store-helpers.ts +23 -0
  125. package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/parsed-file-store.ts +400 -0
  126. package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/request-control.ts +48 -0
  127. package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/runtime-parser-helpers.ts +24 -0
  128. package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/runtime-query-helpers.ts +43 -0
  129. package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/runtime.ts +421 -0
  130. package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/service.ts +116 -0
  131. package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/worker-main.ts +201 -0
  132. package/package.json +5 -5
  133. package/src/analysis/health/diagnostics.ts +236 -24
  134. package/src/analysis/health/recovery.ts +6 -2
  135. package/src/analysis/provider.ts +58 -0
  136. package/src/analysis/readiness.ts +30 -9
  137. package/src/analysis/refactor/apply.ts +59 -20
  138. package/src/analysis/refactor/mutation-authority.ts +143 -0
  139. package/src/analysis/refactor/position.ts +47 -0
  140. package/src/analysis/refactor/safety.ts +18 -10
  141. package/src/analysis/relations/callees.ts +9 -1
  142. package/src/analysis/relations/types.ts +5 -2
  143. package/src/analysis/search/ast-scan-timing.ts +9 -4
  144. package/src/analysis/search/ast-scan.ts +1 -1
  145. package/src/analysis/search/deadline.ts +2 -2
  146. package/src/analysis/search/pattern-analysis.ts +46 -32
  147. package/src/analysis/search/pattern.ts +12 -2
  148. package/src/analysis/target/anchored.ts +20 -11
  149. package/src/analysis/target/file.ts +18 -11
  150. package/src/analysis/target/identity.ts +19 -3
  151. package/src/analysis/target/symbol.ts +36 -15
  152. package/src/api.ts +4 -0
  153. package/src/diagnostics/evidence.ts +51 -0
  154. package/src/session/capability-adapter.ts +12 -3
  155. package/src/session/find-workflow.ts +17 -7
  156. package/src/session/graph/collect.ts +18 -2
  157. package/src/session/graph-workflow.ts +19 -12
  158. package/src/session/health-refresh.ts +148 -0
  159. package/src/session/health-types.ts +37 -15
  160. package/src/session/health-workflow.ts +37 -39
  161. package/src/session/inspect/collect.ts +20 -3
  162. package/src/session/inspect-workflow.ts +10 -5
  163. package/src/session/orientation/collect.ts +10 -1
  164. package/src/session/orientation/context-facts.ts +3 -1
  165. package/src/session/orientation/context-sections.ts +20 -7
  166. package/src/session/orientation/gather.ts +17 -11
  167. package/src/session/orientation-types.ts +2 -1
  168. package/src/session/orientation-workflow.ts +15 -8
  169. package/src/session/refactor-plans.ts +2 -0
  170. package/src/session/refactor-workflow.ts +59 -14
  171. package/src/session/session.ts +4 -1
  172. package/src/session/target-workflow.ts +54 -23
  173. package/src/session/workflow-control.ts +7 -3
  174. package/src/substrate/lsp/lifecycle.ts +26 -46
  175. package/src/substrate/lsp/maintenance.ts +199 -25
  176. package/src/substrate/lsp/state.ts +2 -2
  177. package/src/substrate/workspace-provider-host.ts +25 -3
  178. package/src/tool/find/execute.ts +7 -1
  179. package/src/tool/find/render.ts +20 -3
  180. package/src/tool/find/tui.ts +23 -21
  181. package/src/tool/graph/execute.ts +23 -3
  182. package/src/tool/graph/tui.ts +59 -46
  183. package/src/tool/health/execute.ts +7 -2
  184. package/src/tool/health/markdown.ts +56 -14
  185. package/src/tool/health/refresh-status.ts +105 -0
  186. package/src/tool/health/tui.ts +95 -98
  187. package/src/tool/infra/workflow-control.ts +8 -0
  188. package/src/tool/inspect/execute.ts +7 -1
  189. package/src/tool/inspect/tui.ts +26 -18
  190. package/src/tool/orientation/execute.ts +21 -3
  191. package/src/tool/orientation/tui.ts +51 -35
  192. package/src/tool/refactor-apply/execute.ts +3 -0
  193. package/src/tool/refactor-apply/tui.ts +43 -25
  194. package/src/tool/refactor-plan/execute.ts +4 -0
  195. package/src/tool/refactor-plan/tui.ts +33 -20
  196. package/src/tool/register.ts +127 -22
  197. package/src/tool/resolve/execute.ts +18 -1
  198. package/src/tool/resolve/tui.ts +42 -24
  199. package/src/tool/result/display.ts +107 -0
  200. package/src/tool/result/errors.ts +20 -17
  201. package/src/tool/result/find.ts +52 -8
  202. package/src/tool/result/graph.ts +44 -1
  203. package/src/tool/result/health.ts +53 -1
  204. package/src/tool/result/inspect.ts +113 -1
  205. package/src/tool/result/orientation.ts +66 -14
  206. package/src/tool/result/refactor.ts +36 -4
  207. package/src/tool/result/resolve.ts +44 -7
  208. package/src/tool/result/types.ts +20 -0
  209. package/src/types/execution.ts +18 -5
  210. package/src/types/index.ts +8 -1
  211. package/src/ui/status-command.ts +5 -3
  212. package/src/ui/status-overlay.ts +4 -0
  213. package/src/ui/tui/common.ts +108 -10
  214. package/src/ui/tui/display.ts +48 -0
  215. package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/runtime.ts +0 -247
@@ -0,0 +1,206 @@
1
+ import type { PublishDiagnosticsParams } from "../config/types.ts";
2
+ import { uriToFile } from "../utils.ts";
3
+ import {
4
+ type DiagnosticCacheEntry,
5
+ isValidPublishDiagnosticsParams,
6
+ } from "./client-diagnostic-evidence.ts";
7
+ import type { ClientDiagnosticSnapshot, OpenDocumentState } from "./client-document-state.ts";
8
+ import { hasCurrentDiagnosticEvidence } from "./client-document-state.ts";
9
+ import { getDiagnosticFileState } from "./client-file-state.ts";
10
+
11
+ interface ApplyPushOptions {
12
+ store: Map<string, DiagnosticCacheEntry>;
13
+ openDocuments: ReadonlyMap<string, OpenDocumentState>;
14
+ params: PublishDiagnosticsParams;
15
+ evidenceRevision: number;
16
+ /** Client-side sync moment for the URI, or undefined when never synchronized. */
17
+ unversionedSyncMoment: number | undefined;
18
+ /** Whether a lifecycle close marked this URI fail-closed for versioned pushes. */
19
+ closedVersionedBarrier: boolean;
20
+ }
21
+
22
+ /**
23
+ * Apply one valid push publication and return whether it can release current waiters.
24
+ *
25
+ * Fail-closed policy (ADR 0020): unversioned pushes are rejected for closed
26
+ * and untracked URIs and for arrivals before the URI's sync moment; versioned
27
+ * pushes for a URI closed by a lifecycle operation are rejected because their
28
+ * version cannot be verified. An unversioned push that arrives after the sync
29
+ * moment of an open document is accepted and re-stamped with that document's
30
+ * current synchronization state.
31
+ */
32
+ export function applyPushDiagnostics(options: ApplyPushOptions): boolean {
33
+ if (!isValidPublishDiagnosticsParams(options.params)) return false;
34
+ const openDocument = options.openDocuments.get(options.params.uri);
35
+ if (options.params.version !== undefined) {
36
+ if (!Number.isInteger(options.params.version)) return false;
37
+ if (openDocument && options.params.version !== openDocument.version) return false;
38
+ if (!openDocument && options.closedVersionedBarrier) return false;
39
+ }
40
+ if (!Array.isArray(options.params.diagnostics)) return false;
41
+ if (!acceptUnversionedPush(options, openDocument)) return false;
42
+ options.store.set(options.params.uri, buildPushCacheEntry(options, openDocument));
43
+ return true;
44
+ }
45
+
46
+ /** Gate one unversioned push publication against the sync-moment policy. */
47
+ function acceptUnversionedPush(
48
+ options: ApplyPushOptions,
49
+ openDocument: OpenDocumentState | undefined,
50
+ ): boolean {
51
+ if (options.params.version !== undefined) return true;
52
+ if (!openDocument) return false;
53
+ return !(
54
+ options.unversionedSyncMoment !== undefined && Date.now() < options.unversionedSyncMoment
55
+ );
56
+ }
57
+
58
+ /**
59
+ * Build the stored entry for one accepted push publication.
60
+ *
61
+ * Time-gated unversioned acceptance re-stamps the entry with the open
62
+ * document's current synchronization state, so the push proves that
63
+ * document's synchronization. Versioned pushes keep the current-revision
64
+ * check: a version match alone does not prove a current generation.
65
+ */
66
+ function buildPushCacheEntry(
67
+ options: ApplyPushOptions,
68
+ openDocument: OpenDocumentState | undefined,
69
+ ): DiagnosticCacheEntry {
70
+ const currentRevision = openDocument?.evidenceRevision === options.evidenceRevision;
71
+ const unversioned = options.params.version === undefined && openDocument !== undefined;
72
+ return {
73
+ diagnostics: options.params.diagnostics,
74
+ receivedAt: Date.now(),
75
+ source: "push",
76
+ synchronizationId: unversioned || currentRevision ? openDocument?.synchronizationId : undefined,
77
+ evidenceRevision: openDocument?.evidenceRevision,
78
+ version: options.params.version,
79
+ };
80
+ }
81
+
82
+ /** Build one client snapshot without dropping empty-cache freshness. */
83
+ export function buildClientDiagnosticSnapshot(options: {
84
+ store: ReadonlyMap<string, DiagnosticCacheEntry>;
85
+ openDocuments: ReadonlyMap<string, OpenDocumentState>;
86
+ failedDocuments: ReadonlySet<string>;
87
+ evidenceRevision: number;
88
+ }): ClientDiagnosticSnapshot {
89
+ const documents = collectDocumentFreshness(options);
90
+ const cache = collectCachedDiagnostics(options);
91
+ return {
92
+ entries: cache.entries,
93
+ documents,
94
+ current: cache.current && documents.every((document) => document.current),
95
+ };
96
+ }
97
+
98
+ function collectDocumentFreshness(options: {
99
+ openDocuments: ReadonlyMap<string, OpenDocumentState>;
100
+ failedDocuments: ReadonlySet<string>;
101
+ store: ReadonlyMap<string, DiagnosticCacheEntry>;
102
+ evidenceRevision: number;
103
+ }): ClientDiagnosticSnapshot["documents"] {
104
+ const open = collectOpenDocumentFreshness(options);
105
+ return [
106
+ ...open.documents,
107
+ ...collectFailedDocumentFreshness(options.failedDocuments, open.uris),
108
+ ...collectUntrackedCacheFreshness(options.store, open.uris, options.failedDocuments),
109
+ ];
110
+ }
111
+
112
+ function collectOpenDocumentFreshness(options: {
113
+ openDocuments: ReadonlyMap<string, OpenDocumentState>;
114
+ failedDocuments: ReadonlySet<string>;
115
+ store: ReadonlyMap<string, DiagnosticCacheEntry>;
116
+ evidenceRevision: number;
117
+ }): { documents: ClientDiagnosticSnapshot["documents"]; uris: ReadonlySet<string> } {
118
+ const documents: ClientDiagnosticSnapshot["documents"] = [];
119
+ const uris = new Set<string>();
120
+ for (const [uri, document] of options.openDocuments) {
121
+ uris.add(uri);
122
+ const fileState = getDiagnosticFileState(uriToFile(uri));
123
+ const current = hasCurrentDiagnosticEvidence(
124
+ document,
125
+ options.store.get(uri),
126
+ options.evidenceRevision,
127
+ );
128
+ const failed = options.failedDocuments.has(uri);
129
+ documents.push({
130
+ uri,
131
+ current: fileState === "present" && current && !failed,
132
+ status: statusForDocument(fileState, current, failed),
133
+ });
134
+ }
135
+ return { documents, uris };
136
+ }
137
+
138
+ function collectFailedDocumentFreshness(
139
+ failedDocuments: ReadonlySet<string>,
140
+ openUris: ReadonlySet<string>,
141
+ ): ClientDiagnosticSnapshot["documents"] {
142
+ return Array.from(failedDocuments)
143
+ .filter((uri) => !openUris.has(uri))
144
+ .map((uri) => ({
145
+ uri,
146
+ current: false,
147
+ status:
148
+ getDiagnosticFileState(uriToFile(uri)) === "removed"
149
+ ? ("removed" as const)
150
+ : ("failed" as const),
151
+ }));
152
+ }
153
+
154
+ function collectUntrackedCacheFreshness(
155
+ store: ReadonlyMap<string, DiagnosticCacheEntry>,
156
+ openUris: ReadonlySet<string>,
157
+ failedDocuments: ReadonlySet<string>,
158
+ ): ClientDiagnosticSnapshot["documents"] {
159
+ return Array.from(store.keys())
160
+ .filter((uri) => !openUris.has(uri) && !failedDocuments.has(uri))
161
+ .map((uri) => ({
162
+ uri,
163
+ current: false,
164
+ status:
165
+ getDiagnosticFileState(uriToFile(uri)) === "removed"
166
+ ? ("removed" as const)
167
+ : getDiagnosticFileState(uriToFile(uri)) === "unreadable"
168
+ ? ("failed" as const)
169
+ : ("unconfirmed" as const),
170
+ }));
171
+ }
172
+
173
+ function statusForDocument(
174
+ fileState: ReturnType<typeof getDiagnosticFileState>,
175
+ current: boolean,
176
+ failed: boolean,
177
+ ): "confirmed" | "unconfirmed" | "failed" | "removed" {
178
+ if (fileState === "removed") return "removed";
179
+ if (fileState === "unreadable" || failed) return "failed";
180
+ return current ? "confirmed" : "unconfirmed";
181
+ }
182
+
183
+ function collectCachedDiagnostics(options: {
184
+ store: ReadonlyMap<string, DiagnosticCacheEntry>;
185
+ openDocuments: ReadonlyMap<string, OpenDocumentState>;
186
+ evidenceRevision: number;
187
+ }): {
188
+ entries: ClientDiagnosticSnapshot["entries"];
189
+ current: boolean;
190
+ } {
191
+ const entries: ClientDiagnosticSnapshot["entries"] = [];
192
+ let current = true;
193
+ for (const [uri, entry] of options.store) {
194
+ if (getDiagnosticFileState(uriToFile(uri)) === "removed") continue;
195
+ const entryCurrent = hasCurrentDiagnosticEvidence(
196
+ options.openDocuments.get(uri),
197
+ entry,
198
+ options.evidenceRevision,
199
+ );
200
+ current &&= entryCurrent;
201
+ if (entry.diagnostics.length > 0) {
202
+ entries.push({ uri, diagnostics: entry.diagnostics, current: entryCurrent });
203
+ }
204
+ }
205
+ return { entries, current };
206
+ }
@@ -0,0 +1,78 @@
1
+ // Client-side diagnostic capability state — static (initialize result) and
2
+ // dynamic (client/registerCapability) tracking with fail-closed validation.
3
+ //
4
+ // Static state lives on the client's initialize-result capabilities; dynamic
5
+ // state lives in the per-instance `ClientDynamicRegistrations` below. Pull
6
+ // support is enabled only while the static provider shape is valid or the
7
+ // dynamic set for `textDocument/diagnostic` is non-empty.
8
+
9
+ /** LSP method for document diagnostic pulls. */
10
+ export const DOCUMENT_DIAGNOSTIC_METHOD = "textDocument/diagnostic";
11
+
12
+ function isRecord(value: unknown): value is Record<string, unknown> {
13
+ return typeof value === "object" && value !== null && !Array.isArray(value);
14
+ }
15
+
16
+ /**
17
+ * Validate one untrusted `DiagnosticOptions` / `DiagnosticRegistrationOptions`
18
+ * value from a static initialize result or a dynamic registration.
19
+ *
20
+ * Required per the LSP specification: boolean `interFileDependencies` and
21
+ * `workspaceDiagnostics`. Optional: string `identifier`, and `documentSelector`
22
+ * (absent, null, or an array of string selectors / document-filter records).
23
+ * Unknown fields such as kotlin-lsp's `workDoneProgress` are tolerated.
24
+ */
25
+ export function isValidDiagnosticOptions(value: unknown): boolean {
26
+ if (!isRecord(value)) return false;
27
+ if (typeof value.interFileDependencies !== "boolean") return false;
28
+ if (typeof value.workspaceDiagnostics !== "boolean") return false;
29
+ if (value.identifier !== undefined && typeof value.identifier !== "string") return false;
30
+ if (value.documentSelector !== undefined && value.documentSelector !== null) {
31
+ if (!Array.isArray(value.documentSelector)) return false;
32
+ if (!value.documentSelector.every((entry) => typeof entry === "string" || isRecord(entry))) {
33
+ return false;
34
+ }
35
+ }
36
+ return true;
37
+ }
38
+
39
+ /**
40
+ * Dynamic capability registrations for one client instance.
41
+ *
42
+ * A replacement client starts with an empty instance, so late registrations
43
+ * on a superseded instance never affect the replacement. Unregistration
44
+ * removes one id; pull stays enabled until the last id is removed.
45
+ */
46
+ export class ClientDynamicRegistrations {
47
+ private readonly idsByMethod = new Map<string, Set<string>>();
48
+
49
+ /** Record one registration id for a method. Duplicate ids are harmless. */
50
+ register(method: string, id: string): void {
51
+ let ids = this.idsByMethod.get(method);
52
+ if (ids === undefined) {
53
+ ids = new Set();
54
+ this.idsByMethod.set(method, ids);
55
+ }
56
+ ids.add(id);
57
+ }
58
+
59
+ /** Remove one registration id; unknown ids are harmless no-ops. */
60
+ unregister(method: string, id: string): void {
61
+ this.idsByMethod.get(method)?.delete(id);
62
+ }
63
+
64
+ /** Whether any registration id remains active for a method. */
65
+ has(method: string): boolean {
66
+ return (this.idsByMethod.get(method)?.size ?? 0) > 0;
67
+ }
68
+
69
+ /** Snapshot of the active registration ids for a method. */
70
+ ids(method: string): readonly string[] {
71
+ return [...(this.idsByMethod.get(method) ?? [])];
72
+ }
73
+
74
+ /** Drop all registrations — capability loss on shutdown, crash, or disposal. */
75
+ clear(): void {
76
+ this.idsByMethod.clear();
77
+ }
78
+ }
@@ -0,0 +1,114 @@
1
+ import {
2
+ type CodeQueryResult,
3
+ type CodeRequestControl,
4
+ completedCodeQuery,
5
+ isCodeRequestInterruption,
6
+ throwIfCodeRequestInterrupted,
7
+ unavailableCodeQuery,
8
+ } from "@mrclrchtr/supi-code-runtime/api";
9
+ import type { Diagnostic } from "../config/types.ts";
10
+ import {
11
+ type DiagnosticSynchronization,
12
+ incompleteDiagnosticResult,
13
+ raceDiagnosticPull,
14
+ } from "./client-diagnostic-evidence.ts";
15
+ import type { DiagnosticObserver, DiagnosticPushWaitOutcome } from "./client-diagnostic-timing.ts";
16
+ import type { DiagnosticWaitRegistry } from "./client-diagnostic-waiters.ts";
17
+
18
+ interface FileDiagnosticCollectionOptions {
19
+ readonly supportsPull: boolean;
20
+ readonly syncStart: number;
21
+ readonly maxWaitMs: number;
22
+ readonly request: DiagnosticSynchronization;
23
+ readonly cachedDiagnostics: Diagnostic[] | null;
24
+ readonly observer: DiagnosticObserver;
25
+ readonly waiters: DiagnosticWaitRegistry;
26
+ readonly current: () => boolean;
27
+ readonly freshPush: () => boolean;
28
+ readonly diagnostics: () => Diagnostic[];
29
+ readonly pullDiagnostics: (timeoutMs: number, signal: AbortSignal) => Promise<boolean>;
30
+ /** Observe the push wait outcome without finishing the observer; the caller finishes once. */
31
+ readonly onPushWait?: (outcome: DiagnosticPushWaitOutcome) => void;
32
+ }
33
+
34
+ type PullCollectionOutcome = "failed" | "pull" | "push" | "released";
35
+
36
+ /** Collect fresh pull or push evidence for one synchronized document. */
37
+ export async function collectSynchronizedFileDiagnostics(
38
+ options: FileDiagnosticCollectionOptions,
39
+ control?: CodeRequestControl,
40
+ ): Promise<CodeQueryResult<Diagnostic[]>> {
41
+ throwIfCodeRequestInterrupted(control);
42
+ if (options.supportsPull) {
43
+ const pullOutcome = await collectPullEvidence(options, control);
44
+ if (pullOutcome === "pull" || pullOutcome === "push") {
45
+ return completedCodeQuery(options.diagnostics());
46
+ }
47
+ }
48
+
49
+ throwIfCodeRequestInterrupted(control);
50
+ if (!options.current()) {
51
+ options.observer.pushWaitCompleted(1, "released");
52
+ return unavailableCodeQuery(
53
+ "Diagnostic collection ended before the current document synchronization was confirmed.",
54
+ );
55
+ }
56
+ if (options.freshPush()) {
57
+ options.observer.pushWaitCompleted(1, "published");
58
+ return completedCodeQuery(options.diagnostics());
59
+ }
60
+
61
+ const push = await options.waiters.waitForPush(
62
+ options.request.uri,
63
+ Math.max(0, options.maxWaitMs - (Date.now() - options.syncStart)),
64
+ control,
65
+ );
66
+ if (options.onPushWait) options.onPushWait(push);
67
+ else options.observer.pushWaitCompleted(1, push);
68
+ if (push === "published" && options.freshPush()) {
69
+ return completedCodeQuery(options.diagnostics());
70
+ }
71
+ return incompleteDiagnosticResult(
72
+ options.cachedDiagnostics,
73
+ push === "released" ? "released" : "timed-out",
74
+ );
75
+ }
76
+
77
+ async function collectPullEvidence(
78
+ options: FileDiagnosticCollectionOptions,
79
+ control?: CodeRequestControl,
80
+ ): Promise<PullCollectionOutcome> {
81
+ const remaining = options.maxWaitMs - (Date.now() - options.syncStart);
82
+ if (remaining <= 0) {
83
+ options.observer.pullTimedOut();
84
+ return "failed";
85
+ }
86
+
87
+ try {
88
+ const controller = new AbortController();
89
+ // Link the caller's cancellation to the local pull controller so an
90
+ // in-flight pull receives protocol cancellation and stops promptly.
91
+ const onAbort = () => controller.abort(control?.signal?.reason);
92
+ if (control?.signal?.aborted) onAbort();
93
+ else control?.signal?.addEventListener("abort", onAbort, { once: true });
94
+ try {
95
+ const outcome = await raceDiagnosticPull({
96
+ pull: options.pullDiagnostics(remaining, controller.signal),
97
+ waitForChange: () => options.waiters.waitForChange(),
98
+ freshPush: options.freshPush,
99
+ current: options.current,
100
+ });
101
+ if (outcome !== "pull") controller.abort();
102
+ if (outcome === "pull") options.observer.pullCompleted(1);
103
+ else options.observer.pullFailed(undefined);
104
+ if (outcome === "push") options.observer.pushWaitCompleted(1, "published");
105
+ return outcome;
106
+ } finally {
107
+ control?.signal?.removeEventListener("abort", onAbort);
108
+ }
109
+ } catch (error) {
110
+ if (isCodeRequestInterruption(error, control)) throw error;
111
+ options.observer.pullFailed(error);
112
+ return "failed";
113
+ }
114
+ }