@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,201 @@
1
+ import type { MessagePort } from "node:worker_threads";
2
+ import { isCodeRequestDeadlineError } from "@mrclrchtr/supi-code-runtime/api";
3
+ import type { StructuralTimingEvent } from "../session/structural-timing.ts";
4
+ import { assertStructuralProtocolMessageSize } from "../session/structural-worker-message-size.ts";
5
+ import {
6
+ encodeStructuralResult,
7
+ type ParentToStructuralWorkerMessage,
8
+ STRUCTURAL_WORKER_PROTOCOL_VERSION,
9
+ type StructuralWorkerChunkAckMessage,
10
+ type StructuralWorkerRequestMessage,
11
+ validateStructuralWorkerRequest,
12
+ } from "../session/structural-worker-protocol.ts";
13
+ import type { TreeSitterResult } from "../types.ts";
14
+ import type { StructuralRequestControl } from "./request-control.ts";
15
+ import { TreeSitterRuntime } from "./runtime.ts";
16
+ import { StructuralWorkerService } from "./service.ts";
17
+
18
+ interface WorkerBootstrapData {
19
+ readonly cwd: string;
20
+ readonly generation: number;
21
+ }
22
+
23
+ interface ActiveWorkerRequest {
24
+ readonly message: StructuralWorkerRequestMessage;
25
+ readonly control: StructuralRequestControl;
26
+ readonly abortController: AbortController;
27
+ chunks: Uint8Array[];
28
+ nextSequence: number;
29
+ awaitingAck: boolean;
30
+ outcome: "completed" | "cancelled" | "timeout";
31
+ }
32
+
33
+ /** Start the package-private Structural Worker implementation. */
34
+ // biome-ignore lint/complexity/noExcessiveLinesPerFunction: one Worker loop owns active request and chunk acknowledgement state
35
+ export async function runStructuralWorker(
36
+ parentPort: MessagePort,
37
+ workerData: WorkerBootstrapData,
38
+ ): Promise<void> {
39
+ const runtime = new TreeSitterRuntime(workerData.cwd, (event) => forwardObservation(event));
40
+ const service = new StructuralWorkerService(runtime);
41
+ let active: ActiveWorkerRequest | null = null;
42
+
43
+ const post = (message: unknown) => {
44
+ assertStructuralProtocolMessageSize(message);
45
+ parentPort.postMessage(message);
46
+ };
47
+ const forwardObservation = (observation: StructuralTimingEvent) => {
48
+ if (!active) return;
49
+ post({
50
+ kind: "observation",
51
+ version: STRUCTURAL_WORKER_PROTOCOL_VERSION,
52
+ generation: workerData.generation,
53
+ requestId: active.message.requestId,
54
+ observation,
55
+ });
56
+ };
57
+
58
+ // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: protocol dispatch keeps request, cancel, and acknowledgement validation together
59
+ parentPort.on("message", (message: ParentToStructuralWorkerMessage) => {
60
+ if (message.version !== STRUCTURAL_WORKER_PROTOCOL_VERSION) return;
61
+ if (message.generation !== workerData.generation) return;
62
+ if (message.kind === "cancel") {
63
+ if (active?.message.requestId === message.requestId) active.abortController.abort();
64
+ return;
65
+ }
66
+ if (message.kind === "chunk-ack") {
67
+ if (active?.message.requestId === message.requestId) sendNextChunk(message);
68
+ return;
69
+ }
70
+ if (message.kind !== "request" || active) return;
71
+ const validation = validateStructuralWorkerRequest(message);
72
+ if (validation.kind === "invalid") return;
73
+ void execute(validation.message);
74
+ });
75
+
76
+ try {
77
+ await runtime.ensureGrammarParser("javascript");
78
+ post({
79
+ kind: "ready",
80
+ version: STRUCTURAL_WORKER_PROTOCOL_VERSION,
81
+ generation: workerData.generation,
82
+ });
83
+ } catch (error) {
84
+ post({
85
+ kind: "startup-failure",
86
+ version: STRUCTURAL_WORKER_PROTOCOL_VERSION,
87
+ generation: workerData.generation,
88
+ message: error instanceof Error ? error.message : "Structural Worker startup failed",
89
+ });
90
+ runtime.dispose();
91
+ }
92
+
93
+ // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: one terminal boundary classifies every operation result and interruption
94
+ async function execute(message: StructuralWorkerRequestMessage): Promise<void> {
95
+ const abortController = new AbortController();
96
+ const cancellationFlag = new Int32Array(message.cancellationFlag);
97
+ const control: StructuralRequestControl = {
98
+ operationId: message.operationId,
99
+ signal: abortController.signal,
100
+ deadline: message.deadline,
101
+ cancellationFlag,
102
+ };
103
+ active = {
104
+ message,
105
+ control,
106
+ abortController,
107
+ chunks: [],
108
+ nextSequence: 0,
109
+ awaitingAck: false,
110
+ outcome: "completed",
111
+ };
112
+ let result: TreeSitterResult<unknown>;
113
+ try {
114
+ result = await service.execute(message.input, control);
115
+ } catch (error) {
116
+ const timedOut =
117
+ isCodeRequestDeadlineError(error) ||
118
+ (message.deadline !== undefined && Date.now() >= message.deadline);
119
+ const cancelled = abortController.signal.aborted || Atomics.load(cancellationFlag, 0) !== 0;
120
+ active.outcome = timedOut ? "timeout" : cancelled ? "cancelled" : "completed";
121
+ result = {
122
+ kind: "runtime-error",
123
+ message: timedOut
124
+ ? "Structural request deadline exceeded"
125
+ : cancelled
126
+ ? "Structural request cancelled"
127
+ : error instanceof Error
128
+ ? error.message
129
+ : "Structural Worker operation failed",
130
+ };
131
+ }
132
+ if (!active || active.message.requestId !== message.requestId) return;
133
+ try {
134
+ active.chunks = encodeStructuralResult(result);
135
+ } catch (error) {
136
+ active.chunks = encodeStructuralResult({
137
+ kind: "runtime-error",
138
+ message: error instanceof Error ? error.message : "Structural result encoding failed",
139
+ });
140
+ }
141
+ sendChunk();
142
+ }
143
+
144
+ function sendNextChunk(message: StructuralWorkerChunkAckMessage): void {
145
+ if (!active?.awaitingAck || message.sequence !== active.nextSequence - 1) return;
146
+ active.awaitingAck = false;
147
+ if (active.nextSequence >= active.chunks.length) {
148
+ postTerminal();
149
+ return;
150
+ }
151
+ sendChunk();
152
+ }
153
+
154
+ function sendChunk(): void {
155
+ if (!active || active.awaitingAck) return;
156
+ updateInterruptionOutcome(active);
157
+ const sequence = active.nextSequence;
158
+ const payload = active.chunks[sequence];
159
+ if (!payload) {
160
+ postTerminal();
161
+ return;
162
+ }
163
+ active.nextSequence += 1;
164
+ active.awaitingAck = true;
165
+ post({
166
+ kind: "chunk",
167
+ version: STRUCTURAL_WORKER_PROTOCOL_VERSION,
168
+ generation: workerData.generation,
169
+ requestId: active.message.requestId,
170
+ sequence,
171
+ final: active.nextSequence === active.chunks.length,
172
+ encodedBytes: payload.byteLength,
173
+ payload,
174
+ });
175
+ }
176
+
177
+ function postTerminal(): void {
178
+ if (!active) return;
179
+ updateInterruptionOutcome(active);
180
+ post({
181
+ kind: "terminal",
182
+ version: STRUCTURAL_WORKER_PROTOCOL_VERSION,
183
+ generation: workerData.generation,
184
+ requestId: active.message.requestId,
185
+ outcome: active.outcome,
186
+ });
187
+ active = null;
188
+ }
189
+
190
+ function updateInterruptionOutcome(request: ActiveWorkerRequest): void {
191
+ if (request.message.deadline !== undefined && Date.now() >= request.message.deadline) {
192
+ request.outcome = "timeout";
193
+ } else if (
194
+ request.abortController.signal.aborted ||
195
+ (request.control.cancellationFlag !== undefined &&
196
+ Atomics.load(request.control.cancellationFlag, 0) !== 0)
197
+ ) {
198
+ request.outcome = "cancelled";
199
+ }
200
+ }
201
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrclrchtr/supi-code-intelligence",
3
- "version": "4.9.0",
3
+ "version": "5.0.0",
4
4
  "description": "LSP and AST navigation, search, diagnostics, and refactoring",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -35,10 +35,10 @@
35
35
  ],
36
36
  "dependencies": {
37
37
  "yaml": "^2.9.0",
38
- "@mrclrchtr/supi-code-runtime": "4.9.0",
39
- "@mrclrchtr/supi-core": "4.9.0",
40
- "@mrclrchtr/supi-tree-sitter": "4.9.0",
41
- "@mrclrchtr/supi-lsp": "4.9.0"
38
+ "@mrclrchtr/supi-core": "5.0.0",
39
+ "@mrclrchtr/supi-lsp": "5.0.0",
40
+ "@mrclrchtr/supi-tree-sitter": "5.0.0",
41
+ "@mrclrchtr/supi-code-runtime": "5.0.0"
42
42
  },
43
43
  "bundledDependencies": [
44
44
  "@mrclrchtr/supi-code-runtime",
@@ -2,9 +2,19 @@
2
2
  // Extracted from orchestrate.ts.
3
3
 
4
4
  import { existsSync, statSync } from "node:fs";
5
- import { resolve } from "node:path";
5
+ import { relative, resolve } from "node:path";
6
+ import {
7
+ type CodeRequestControl,
8
+ isCodeRequestInterruption,
9
+ } from "@mrclrchtr/supi-code-runtime/api";
6
10
  import { isWithinOrEqual } from "@mrclrchtr/supi-core/api";
7
- import type { Diagnostic, WorkspaceLspRuntime } from "@mrclrchtr/supi-lsp/api";
11
+ import type {
12
+ Diagnostic,
13
+ DiagnosticEvidenceDocument,
14
+ DiagnosticEvidenceSummary,
15
+ WorkspaceLspRuntime,
16
+ } from "@mrclrchtr/supi-lsp/api";
17
+ import { mergeDiagnosticEvidence } from "../../diagnostics/evidence.ts";
8
18
  import type {
9
19
  HealthDiagnosticEntry,
10
20
  HealthDiagnosticMessage,
@@ -28,60 +38,117 @@ export function diagnosticScope(scopeFilter: string | null): HealthDiagnosticSco
28
38
 
29
39
  interface CollectDiagnosticsOptions {
30
40
  readonly service: WorkspaceLspRuntime | null;
41
+ /** Runtime used for evidence snapshots when semantic operations are unavailable. */
42
+ readonly evidenceService?: WorkspaceLspRuntime | null;
31
43
  readonly included: readonly HealthSection[];
32
44
  readonly scope: HealthDiagnosticScope;
33
45
  readonly cwd: string;
34
46
  readonly unavailableReason: string;
35
47
  /** Collect individual messages per file (detailed level). */
36
48
  readonly detailed?: boolean;
49
+ readonly requestControl?: CodeRequestControl;
50
+ /** Final evidence from an explicit workspace refresh, when one ran. */
51
+ readonly refreshEvidence?: DiagnosticEvidenceSummary;
37
52
  }
38
53
 
39
54
  /** Collect diagnostics without widening the requested evidence scope. */
40
55
  export async function collectDiagnostics(
41
56
  options: CollectDiagnosticsOptions,
42
57
  ): Promise<HealthDiagnosticObservation> {
43
- const { service, included, scope, cwd, unavailableReason, detailed } = options;
58
+ const { service, included, scope, cwd, unavailableReason, detailed, requestControl } = options;
44
59
  if (!included.includes("diagnostics")) return { kind: "not-requested", entries: [] };
45
- if (!service) return unavailableDiagnostics(scope, unavailableReason);
60
+ if (!service) {
61
+ return unavailableDiagnostics(
62
+ scope,
63
+ unavailableReason,
64
+ unavailableScopeEvidence(scope, options.refreshEvidence, options.evidenceService, cwd),
65
+ );
66
+ }
46
67
 
47
68
  return scope.kind === "file"
48
- ? collectScopedFileDiagnostics(service, scope, detailed)
49
- : collectTrackedFileDiagnostics(service, scope, cwd, detailed);
69
+ ? collectScopedFileDiagnostics(service, scope, detailed, requestControl)
70
+ : collectTrackedFileDiagnostics({
71
+ service,
72
+ scope,
73
+ cwd,
74
+ detailed,
75
+ refreshEvidence: options.refreshEvidence,
76
+ });
50
77
  }
51
78
 
52
79
  async function collectScopedFileDiagnostics(
53
80
  service: WorkspaceLspRuntime,
54
81
  scope: Extract<HealthDiagnosticScope, { kind: "file" }>,
55
82
  detailed?: boolean,
83
+ requestControl?: CodeRequestControl,
56
84
  ): Promise<HealthDiagnosticObservation> {
57
85
  try {
58
- const result = await service.fileDiagnostics(scope.path, 4);
59
- if (result.kind === "unavailable") return unavailableDiagnostics(scope, result.reason);
86
+ const result = await service.fileDiagnostics(scope.path, 4, requestControl);
87
+ if (result.kind === "unavailable") {
88
+ return unavailableDiagnostics(scope, result.reason, unavailableFileEvidence(scope.path));
89
+ }
60
90
 
61
91
  const entries = toFileDiagnosticEntries(scope.path, result.data, detailed);
62
92
  return result.kind === "partial"
63
- ? { kind: "partial", scope, entries, reason: result.reason }
64
- : { kind: "completed", scope, entries };
93
+ ? {
94
+ kind: "partial",
95
+ scope,
96
+ entries,
97
+ evidence: singleFileEvidence(scope.path, "unconfirmed"),
98
+ reason: result.reason,
99
+ }
100
+ : {
101
+ kind: "completed",
102
+ scope,
103
+ entries,
104
+ evidence: singleFileEvidence(scope.path, "confirmed"),
105
+ };
65
106
  } catch (error) {
66
- return unavailableDiagnostics(scope, errorMessage(error, "File diagnostic request failed."));
107
+ if (isCodeRequestInterruption(error, requestControl)) throw error;
108
+ return unavailableDiagnostics(
109
+ scope,
110
+ errorMessage(error, "File diagnostic request failed."),
111
+ unavailableFileEvidence(scope.path),
112
+ );
67
113
  }
68
114
  }
69
115
 
116
+ interface TrackedFileDiagnosticOptions {
117
+ readonly service: WorkspaceLspRuntime;
118
+ readonly scope: Extract<HealthDiagnosticScope, { kind: "tracked-files" }>;
119
+ readonly cwd: string;
120
+ readonly detailed?: boolean;
121
+ readonly refreshEvidence?: DiagnosticEvidenceSummary;
122
+ }
123
+
70
124
  function collectTrackedFileDiagnostics(
71
- service: WorkspaceLspRuntime,
72
- scope: Extract<HealthDiagnosticScope, { kind: "tracked-files" }>,
73
- cwd: string,
74
- detailed?: boolean,
125
+ options: TrackedFileDiagnosticOptions,
75
126
  ): HealthDiagnosticObservation {
127
+ const { service, scope, cwd, detailed, refreshEvidence } = options;
76
128
  try {
77
- const entries = detailed
129
+ const snapshot = detailed
78
130
  ? collectWorkspaceDiagnosticsDetailed(service, scope.filter, cwd)
79
131
  : collectWorkspaceDiagnostics(service, scope.filter, cwd);
80
- return { kind: "completed", scope, entries };
132
+ const sourceEvidence = mergeEvidence(refreshEvidence, snapshot.evidence, cwd);
133
+ const scopedEvidence = selectTrackedFileEvidence(sourceEvidence, scope.filter, cwd);
134
+ const evidence = ensureEvidenceCoversEntries(scopedEvidence, snapshot.entries, cwd);
135
+ return evidenceIsComplete(evidence, snapshot.entries.length > 0, snapshot.current)
136
+ ? { kind: "completed", scope, entries: snapshot.entries, evidence }
137
+ : {
138
+ kind: "partial",
139
+ scope,
140
+ entries: snapshot.entries,
141
+ evidence,
142
+ reason: diagnosticEvidenceReason(evidence),
143
+ };
81
144
  } catch (error) {
145
+ const evidence = options.refreshEvidence
146
+ ? selectTrackedFileEvidence(options.refreshEvidence, scope.filter, cwd)
147
+ : emptyEvidence();
82
148
  return unavailableDiagnostics(
83
149
  scope,
84
150
  errorMessage(error, "Tracked-file diagnostic snapshot is unavailable."),
151
+ evidence,
85
152
  );
86
153
  }
87
154
  }
@@ -105,18 +172,22 @@ function collectWorkspaceDiagnostics(
105
172
  service: WorkspaceLspRuntime,
106
173
  scopeFilter: string | null,
107
174
  cwd: string,
108
- ): HealthDiagnosticEntry[] {
175
+ ): {
176
+ entries: HealthDiagnosticEntry[];
177
+ current: boolean;
178
+ evidence: DiagnosticEvidenceSummary;
179
+ } {
109
180
  const summary = service.getWorkspaceDiagnosticSummary();
110
181
  const result: HealthDiagnosticEntry[] = [];
111
182
 
112
- for (const entry of summary) {
183
+ for (const entry of summary.entries) {
113
184
  const filePath = resolve(cwd, entry.file);
114
185
  if (scopeFilter && !isWithinOrEqual(scopeFilter, filePath)) continue;
115
186
  if (!hasIssueCounts(entry.errors, entry.warnings)) continue;
116
187
  result.push({ file: filePath, errors: entry.errors, warnings: entry.warnings });
117
188
  }
118
189
 
119
- return result;
190
+ return { entries: result, current: summary.current, evidence: summary.evidence };
120
191
  }
121
192
 
122
193
  /** Detailed workspace path: full diagnostics with messages, capped per file. */
@@ -124,18 +195,22 @@ function collectWorkspaceDiagnosticsDetailed(
124
195
  service: WorkspaceLspRuntime,
125
196
  scopeFilter: string | null,
126
197
  cwd: string,
127
- ): HealthDiagnosticEntry[] {
198
+ ): {
199
+ entries: HealthDiagnosticEntry[];
200
+ current: boolean;
201
+ evidence: DiagnosticEvidenceSummary;
202
+ } {
128
203
  const outstanding = service.getOutstandingDiagnostics(2);
129
204
  const result: HealthDiagnosticEntry[] = [];
130
205
 
131
- for (const { file, diagnostics } of outstanding) {
206
+ for (const { file, diagnostics } of outstanding.entries) {
132
207
  const filePath = resolve(cwd, file);
133
208
  if (scopeFilter && !isWithinOrEqual(scopeFilter, filePath)) continue;
134
209
  const entries = toFileDiagnosticEntries(filePath, diagnostics, true);
135
210
  result.push(...entries);
136
211
  }
137
212
 
138
- return result;
213
+ return { entries: result, current: outstanding.current, evidence: outstanding.evidence };
139
214
  }
140
215
 
141
216
  // ── Message extraction ────────────────────────────────────────────────
@@ -159,11 +234,49 @@ function extractMessages(
159
234
  }));
160
235
  }
161
236
 
237
+ function unavailableScopeEvidence(
238
+ scope: HealthDiagnosticScope,
239
+ refreshEvidence: DiagnosticEvidenceSummary | undefined,
240
+ evidenceService: WorkspaceLspRuntime | null | undefined,
241
+ cwd: string,
242
+ ): DiagnosticEvidenceSummary {
243
+ const runtimeEvidence = readRuntimeEvidence(evidenceService);
244
+ const knownEvidence = refreshEvidence
245
+ ? mergeDiagnosticEvidence(refreshEvidence, runtimeEvidence, cwd, "conservative")
246
+ : runtimeEvidence;
247
+ if (knownEvidence.requested === 0 && knownEvidence.documents.length === 0) {
248
+ return scope.kind === "file" ? unavailableFileEvidence(scope.path) : emptyEvidence();
249
+ }
250
+ const scoped = selectTrackedFileEvidence(
251
+ knownEvidence,
252
+ scope.kind === "file" ? scope.path : scope.filter,
253
+ cwd,
254
+ );
255
+ if (scoped.requested > 0) return scoped;
256
+ return scope.kind === "file" ? unavailableFileEvidence(scope.path) : scoped;
257
+ }
258
+
259
+ function unavailableFileEvidence(file: string): DiagnosticEvidenceSummary {
260
+ return singleFileEvidence(file, existsSync(file) ? "failed" : "removed");
261
+ }
262
+
263
+ function readRuntimeEvidence(
264
+ service: WorkspaceLspRuntime | null | undefined,
265
+ ): DiagnosticEvidenceSummary {
266
+ if (!service) return emptyEvidence();
267
+ try {
268
+ return service.getWorkspaceDiagnosticSummary().evidence;
269
+ } catch {
270
+ return emptyEvidence();
271
+ }
272
+ }
273
+
162
274
  function unavailableDiagnostics(
163
275
  scope: HealthDiagnosticScope,
164
276
  reason: string,
277
+ evidence: DiagnosticEvidenceSummary = emptyEvidence(),
165
278
  ): HealthDiagnosticObservation {
166
- return { kind: "unavailable", scope, entries: [], reason };
279
+ return { kind: "unavailable", scope, entries: [], evidence, reason };
167
280
  }
168
281
 
169
282
  function errorMessage(error: unknown, fallback: string): string {
@@ -174,6 +287,105 @@ function hasIssueCounts(errors: number, warnings: number): boolean {
174
287
  return errors > 0 || warnings > 0;
175
288
  }
176
289
 
290
+ function emptyEvidence(): DiagnosticEvidenceSummary {
291
+ return {
292
+ requested: 0,
293
+ confirmed: 0,
294
+ unconfirmed: 0,
295
+ failed: 0,
296
+ removed: 0,
297
+ documents: [],
298
+ };
299
+ }
300
+
301
+ function singleFileEvidence(
302
+ file: string,
303
+ status: DiagnosticEvidenceDocument["status"],
304
+ ): DiagnosticEvidenceSummary {
305
+ return {
306
+ requested: 1,
307
+ confirmed: status === "confirmed" ? 1 : 0,
308
+ unconfirmed: status === "unconfirmed" ? 1 : 0,
309
+ failed: status === "failed" ? 1 : 0,
310
+ removed: status === "removed" ? 1 : 0,
311
+ documents: [{ file, status }],
312
+ };
313
+ }
314
+
315
+ function mergeEvidence(
316
+ refreshEvidence: DiagnosticEvidenceSummary | undefined,
317
+ snapshotEvidence: DiagnosticEvidenceSummary,
318
+ cwd: string,
319
+ ): DiagnosticEvidenceSummary {
320
+ return refreshEvidence
321
+ ? mergeDiagnosticEvidence(refreshEvidence, snapshotEvidence, cwd, "conservative")
322
+ : snapshotEvidence;
323
+ }
324
+
325
+ function selectTrackedFileEvidence(
326
+ evidence: DiagnosticEvidenceSummary,
327
+ scopeFilter: string | null,
328
+ cwd: string,
329
+ ): DiagnosticEvidenceSummary {
330
+ if (evidence.documents.length === 0) return evidence;
331
+
332
+ const documents = evidence.documents.filter((document) => {
333
+ if (scopeFilter === null) return true;
334
+ return isWithinOrEqual(scopeFilter, resolve(cwd, document.file));
335
+ });
336
+ return summarizeEvidence(documents);
337
+ }
338
+
339
+ function ensureEvidenceCoversEntries(
340
+ evidence: DiagnosticEvidenceSummary,
341
+ entries: readonly HealthDiagnosticEntry[],
342
+ cwd: string,
343
+ ): DiagnosticEvidenceSummary {
344
+ if (entries.length === 0) return evidence;
345
+ const documents = [...evidence.documents];
346
+ const knownFiles = new Set(documents.map((document) => resolve(cwd, document.file)));
347
+ for (const entry of entries) {
348
+ const file = resolve(cwd, entry.file);
349
+ if (knownFiles.has(file)) continue;
350
+ knownFiles.add(file);
351
+ documents.push({ file: relative(cwd, file), status: "unconfirmed" });
352
+ }
353
+ return summarizeEvidence(documents);
354
+ }
355
+
356
+ function summarizeEvidence(
357
+ documents: readonly DiagnosticEvidenceDocument[],
358
+ ): DiagnosticEvidenceSummary {
359
+ const counts = {
360
+ requested: documents.length,
361
+ confirmed: 0,
362
+ unconfirmed: 0,
363
+ failed: 0,
364
+ removed: 0,
365
+ };
366
+ for (const document of documents) counts[document.status]++;
367
+ return { ...counts, documents: [...documents] };
368
+ }
369
+
370
+ function evidenceIsComplete(
371
+ evidence: DiagnosticEvidenceSummary,
372
+ hasDiagnosticEntries: boolean,
373
+ snapshotCurrent: boolean,
374
+ ): boolean {
375
+ if (!snapshotCurrent && evidence.requested === 0) return false;
376
+ if (evidence.requested === 0) return !hasDiagnosticEntries;
377
+ return (
378
+ evidence.confirmed === evidence.requested &&
379
+ evidence.unconfirmed === 0 &&
380
+ evidence.failed === 0 &&
381
+ evidence.removed === 0
382
+ );
383
+ }
384
+
385
+ function diagnosticEvidenceReason(evidence: DiagnosticEvidenceSummary): string {
386
+ return `Diagnostic evidence is partial: ${evidence.requested} requested, ${evidence.confirmed} confirmed, ${evidence.unconfirmed} unconfirmed, ${evidence.failed} failed, ${evidence.removed} removed.`;
387
+ }
388
+
177
389
  export function isDirectory(filePath: string): boolean {
178
390
  try {
179
391
  return statSync(filePath).isDirectory();
@@ -1,7 +1,7 @@
1
1
  // Recovery and state description helpers for code_health.
2
2
  // Extracted from orchestrate.ts.
3
3
 
4
- import type { CapabilityState } from "@mrclrchtr/supi-code-runtime/api";
4
+ import type { CapabilityState, CodeRequestControl } from "@mrclrchtr/supi-code-runtime/api";
5
5
  import type { RecoverDiagnosticsResult, WorkspaceLspRuntime } from "@mrclrchtr/supi-lsp/api";
6
6
 
7
7
  // ── Recovery ──────────────────────────────────────────────────────────
@@ -9,6 +9,7 @@ import type { RecoverDiagnosticsResult, WorkspaceLspRuntime } from "@mrclrchtr/s
9
9
  interface RecoverOptions {
10
10
  service: WorkspaceLspRuntime;
11
11
  progress?: () => void;
12
+ control?: CodeRequestControl;
12
13
  }
13
14
 
14
15
  /** Run the runtime's best-effort recovery and preserve its established outcome. */
@@ -16,7 +17,10 @@ export async function recoverDiagnosticRuntime(
16
17
  opts: RecoverOptions,
17
18
  ): Promise<RecoverDiagnosticsResult> {
18
19
  opts.progress?.();
19
- return opts.service.recoverDiagnostics({ restartIfStillStale: true });
20
+ return opts.service.recoverDiagnostics({
21
+ restartIfStillStale: true,
22
+ control: opts.control,
23
+ });
20
24
  }
21
25
 
22
26
  // ── State description helpers ─────────────────────────────────────────
@@ -11,6 +11,7 @@
11
11
  */
12
12
 
13
13
  import {
14
+ type CodeRequestControl,
14
15
  getDefaultWorkspaceRuntime,
15
16
  type SemanticProvider,
16
17
  type StructuralProvider,
@@ -85,6 +86,54 @@ export function getCodeProviderState(cwd: string): CodeProviderState {
85
86
  /** Alias for getCodeProviderState — kept for backward compatibility. */
86
87
  export const getCodeProvider = getCodeProviderState;
87
88
 
89
+ /** Bind one exact request-control value to every semantic provider operation. */
90
+ export function withSemanticRequestControl<T extends SemanticProvider>(
91
+ provider: T | null,
92
+ control: CodeRequestControl | undefined,
93
+ ): T | null {
94
+ if (!provider || !control) return provider;
95
+ return {
96
+ ...provider,
97
+ references: (file, position) => provider.references(file, position, control),
98
+ implementation: (file, position) => provider.implementation(file, position, control),
99
+ documentSymbols: (file) => provider.documentSymbols(file, control),
100
+ workspaceSymbols: (query) => provider.workspaceSymbols(query, control),
101
+ hover: provider.hover
102
+ ? (file, position) => provider.hover?.(file, position, control)
103
+ : undefined,
104
+ definition: provider.definition
105
+ ? (file, position) => provider.definition?.(file, position, control)
106
+ : undefined,
107
+ refactor: provider.refactor ? (request) => provider.refactor?.(request, control) : undefined,
108
+ rename: provider.rename
109
+ ? (file, position, newName) => provider.rename?.(file, position, newName, control)
110
+ : undefined,
111
+ codeActions: provider.codeActions
112
+ ? (file, position) => provider.codeActions?.(file, position, control)
113
+ : undefined,
114
+ } as T;
115
+ }
116
+
117
+ /** Bind one exact request-control value to every structural provider operation. */
118
+ export function withStructuralRequestControl<T extends StructuralProvider>(
119
+ provider: T | null,
120
+ control: CodeRequestControl | undefined,
121
+ ): T | null {
122
+ if (!provider || !control) return provider;
123
+ return {
124
+ ...provider,
125
+ calleesAt: (file, line, character, depthOrOptions) => {
126
+ const depth = typeof depthOrOptions === "string" ? depthOrOptions : depthOrOptions?.depth;
127
+ return provider.calleesAt(file, line, character, { depth, control });
128
+ },
129
+ exports: (file) => provider.exports(file, control),
130
+ outline: (file) => provider.outline(file, control),
131
+ imports: (file) => provider.imports(file, control),
132
+ nodeAt: (file, line, character) => provider.nodeAt(file, line, character, control),
133
+ callSites: (file) => provider.callSites(file, control),
134
+ } as T;
135
+ }
136
+
88
137
  /**
89
138
  * Create a single CodeProvider that delegates semantic methods to the
90
139
  * semantic (LSP) provider and structural methods to the structural
@@ -138,6 +187,15 @@ function createCompositeProvider(
138
187
  semantic?.definition?.(...args) ?? unavailableCodeQuery("Semantic definition unavailable")
139
188
  );
140
189
  },
190
+ async refactor(...args: Parameters<NonNullable<SemanticProvider["refactor"]>>) {
191
+ const result = await semantic?.refactor?.(...args);
192
+ return (
193
+ result ?? {
194
+ kind: "unavailable" as const,
195
+ reason: "Refactor not available",
196
+ }
197
+ );
198
+ },
141
199
  async codeActions(...args: Parameters<NonNullable<SemanticProvider["codeActions"]>>) {
142
200
  return semantic?.codeActions?.(...args) ?? [];
143
201
  },