@mrclrchtr/supi-code-intelligence 4.10.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 (203) 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/settings/settings-registry.ts +3 -0
  14. package/node_modules/@mrclrchtr/supi-lsp/README.md +80 -5
  15. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/README.md +2 -0
  16. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
  17. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/src/api.ts +9 -0
  18. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/src/capability/types.ts +53 -13
  19. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/src/request-control.ts +47 -0
  20. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/src/types.ts +13 -1
  21. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/README.md +2 -0
  22. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/package.json +1 -1
  23. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/config/config.ts +31 -0
  24. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/config.ts +2 -0
  25. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/debug-registry.ts +19 -3
  26. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/settings/settings-registry.ts +3 -0
  27. package/node_modules/@mrclrchtr/supi-lsp/package.json +7 -6
  28. package/node_modules/@mrclrchtr/supi-lsp/src/api.ts +10 -0
  29. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-cache.ts +206 -0
  30. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-capabilities.ts +78 -0
  31. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-collection.ts +114 -0
  32. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-evidence.ts +372 -0
  33. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-host.ts +14 -0
  34. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-pull.ts +62 -0
  35. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-refresh.ts +491 -0
  36. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-request.ts +9 -0
  37. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-timing.ts +71 -11
  38. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-waiters.ts +206 -0
  39. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostics.ts +365 -297
  40. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-document-state.ts +52 -0
  41. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-document-sync.ts +185 -0
  42. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-file-state.ts +25 -0
  43. package/node_modules/@mrclrchtr/supi-lsp/src/client/client.ts +467 -115
  44. package/node_modules/@mrclrchtr/supi-lsp/src/client/transport.ts +161 -33
  45. package/node_modules/@mrclrchtr/supi-lsp/src/config/capabilities.ts +10 -1
  46. package/node_modules/@mrclrchtr/supi-lsp/src/config/defaults.json +1 -1
  47. package/node_modules/@mrclrchtr/supi-lsp/src/debug-telemetry.ts +45 -0
  48. package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/evidence.ts +38 -0
  49. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-client-state.ts +79 -9
  50. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-diagnostics.ts +13 -4
  51. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-workspace-recovery.ts +300 -24
  52. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-workspace-symbol.ts +5 -2
  53. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager.ts +464 -96
  54. package/node_modules/@mrclrchtr/supi-lsp/src/provider/lsp-refactor-provider.ts +130 -0
  55. package/node_modules/@mrclrchtr/supi-lsp/src/provider/lsp-semantic-provider.ts +45 -329
  56. package/node_modules/@mrclrchtr/supi-lsp/src/provider/refactor-planning.ts +57 -120
  57. package/node_modules/@mrclrchtr/supi-lsp/src/provider/semantic-edit-normalizer.ts +320 -0
  58. package/node_modules/@mrclrchtr/supi-lsp/src/provider/semantic-symbol-mapper.ts +207 -0
  59. package/node_modules/@mrclrchtr/supi-lsp/src/session/readiness.ts +72 -2
  60. package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-controller.ts +151 -26
  61. package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-diagnostic-surface.ts +40 -0
  62. package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-diagnostics.ts +50 -0
  63. package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-registry.ts +136 -120
  64. package/node_modules/@mrclrchtr/supi-lsp/src/session/runtime-transition-debug.ts +49 -0
  65. package/node_modules/@mrclrchtr/supi-lsp/src/session/workspace-lsp-runtime.ts +102 -0
  66. package/node_modules/@mrclrchtr/supi-tree-sitter/README.md +17 -7
  67. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/README.md +2 -0
  68. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
  69. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/src/api.ts +9 -0
  70. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/src/capability/types.ts +53 -13
  71. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/src/request-control.ts +47 -0
  72. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/src/types.ts +13 -1
  73. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/README.md +2 -0
  74. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/package.json +1 -1
  75. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/config/config.ts +31 -0
  76. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/config.ts +2 -0
  77. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/debug-registry.ts +19 -3
  78. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/settings/settings-registry.ts +3 -0
  79. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/LICENSE +21 -0
  80. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/README.md +258 -0
  81. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/dist/babel.cjs +257 -0
  82. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/dist/jiti.cjs +1 -0
  83. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/lib/jiti-cli.mjs +34 -0
  84. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/lib/jiti-hooks.mjs +124 -0
  85. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/lib/jiti-native.mjs +121 -0
  86. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/lib/jiti-register.d.mts +1 -0
  87. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/lib/jiti-register.mjs +4 -0
  88. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/lib/jiti-static.mjs +23 -0
  89. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/lib/jiti.cjs +30 -0
  90. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/lib/jiti.d.cts +8 -0
  91. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/lib/jiti.d.mts +8 -0
  92. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/lib/jiti.mjs +29 -0
  93. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/lib/types.d.ts +420 -0
  94. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/jiti/package.json +146 -0
  95. package/node_modules/@mrclrchtr/supi-tree-sitter/package.json +6 -3
  96. package/node_modules/@mrclrchtr/supi-tree-sitter/src/api.ts +0 -9
  97. package/node_modules/@mrclrchtr/supi-tree-sitter/src/index.ts +0 -8
  98. package/node_modules/@mrclrchtr/supi-tree-sitter/src/provider/tree-sitter-provider.ts +16 -12
  99. package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/runtime-controller.ts +65 -87
  100. package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/session.ts +47 -95
  101. package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/structural-timing.ts +147 -0
  102. package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/structural-worker-client-lifecycle.ts +39 -0
  103. package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/structural-worker-client.ts +561 -0
  104. package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/structural-worker-message-size.ts +24 -0
  105. package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/structural-worker-protocol.ts +452 -0
  106. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/call-sites.ts +4 -2
  107. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/callees.ts +12 -4
  108. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/exports.ts +4 -2
  109. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/imports.ts +4 -2
  110. package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/node-at.ts +5 -3
  111. package/node_modules/@mrclrchtr/supi-tree-sitter/src/types.ts +33 -12
  112. package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/blocking-test-bootstrap.mjs +34 -0
  113. package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/bootstrap.mjs +8 -0
  114. package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/parsed-file-store-helpers.ts +23 -0
  115. package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/parsed-file-store.ts +400 -0
  116. package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/request-control.ts +48 -0
  117. package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/runtime-parser-helpers.ts +24 -0
  118. package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/runtime-query-helpers.ts +43 -0
  119. package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/runtime.ts +421 -0
  120. package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/service.ts +116 -0
  121. package/node_modules/@mrclrchtr/supi-tree-sitter/src/worker/worker-main.ts +201 -0
  122. package/package.json +5 -5
  123. package/src/analysis/health/diagnostics.ts +236 -24
  124. package/src/analysis/health/recovery.ts +6 -2
  125. package/src/analysis/provider.ts +58 -0
  126. package/src/analysis/readiness.ts +30 -9
  127. package/src/analysis/refactor/apply.ts +45 -1
  128. package/src/analysis/refactor/mutation-authority.ts +143 -0
  129. package/src/analysis/relations/callees.ts +9 -1
  130. package/src/analysis/relations/types.ts +5 -2
  131. package/src/analysis/search/ast-scan-timing.ts +9 -2
  132. package/src/analysis/search/ast-scan.ts +1 -1
  133. package/src/analysis/search/deadline.ts +2 -2
  134. package/src/analysis/search/pattern-analysis.ts +46 -32
  135. package/src/analysis/search/pattern.ts +12 -2
  136. package/src/analysis/target/anchored.ts +20 -11
  137. package/src/analysis/target/file.ts +18 -11
  138. package/src/analysis/target/identity.ts +19 -3
  139. package/src/analysis/target/symbol.ts +36 -15
  140. package/src/api.ts +4 -0
  141. package/src/diagnostics/evidence.ts +51 -0
  142. package/src/session/capability-adapter.ts +12 -3
  143. package/src/session/find-workflow.ts +17 -7
  144. package/src/session/graph/collect.ts +18 -2
  145. package/src/session/graph-workflow.ts +19 -12
  146. package/src/session/health-refresh.ts +148 -0
  147. package/src/session/health-types.ts +37 -15
  148. package/src/session/health-workflow.ts +37 -39
  149. package/src/session/inspect/collect.ts +20 -3
  150. package/src/session/inspect-workflow.ts +10 -5
  151. package/src/session/orientation/collect.ts +10 -1
  152. package/src/session/orientation/context-facts.ts +3 -1
  153. package/src/session/orientation/context-sections.ts +20 -7
  154. package/src/session/orientation/gather.ts +17 -11
  155. package/src/session/orientation-types.ts +2 -1
  156. package/src/session/orientation-workflow.ts +15 -8
  157. package/src/session/refactor-plans.ts +2 -0
  158. package/src/session/refactor-workflow.ts +59 -14
  159. package/src/session/session.ts +4 -1
  160. package/src/session/target-workflow.ts +54 -23
  161. package/src/session/workflow-control.ts +7 -3
  162. package/src/substrate/lsp/lifecycle.ts +26 -46
  163. package/src/substrate/lsp/maintenance.ts +199 -25
  164. package/src/substrate/lsp/state.ts +2 -2
  165. package/src/substrate/workspace-provider-host.ts +25 -3
  166. package/src/tool/find/execute.ts +7 -1
  167. package/src/tool/find/render.ts +20 -3
  168. package/src/tool/find/tui.ts +23 -21
  169. package/src/tool/graph/execute.ts +23 -3
  170. package/src/tool/graph/tui.ts +59 -46
  171. package/src/tool/health/execute.ts +7 -2
  172. package/src/tool/health/markdown.ts +56 -14
  173. package/src/tool/health/refresh-status.ts +105 -0
  174. package/src/tool/health/tui.ts +95 -98
  175. package/src/tool/infra/workflow-control.ts +8 -0
  176. package/src/tool/inspect/execute.ts +7 -1
  177. package/src/tool/inspect/tui.ts +26 -18
  178. package/src/tool/orientation/execute.ts +21 -3
  179. package/src/tool/orientation/tui.ts +51 -35
  180. package/src/tool/refactor-apply/execute.ts +3 -0
  181. package/src/tool/refactor-apply/tui.ts +43 -25
  182. package/src/tool/refactor-plan/execute.ts +4 -0
  183. package/src/tool/refactor-plan/tui.ts +33 -20
  184. package/src/tool/register.ts +127 -22
  185. package/src/tool/resolve/execute.ts +18 -1
  186. package/src/tool/resolve/tui.ts +42 -24
  187. package/src/tool/result/display.ts +107 -0
  188. package/src/tool/result/errors.ts +20 -17
  189. package/src/tool/result/find.ts +52 -8
  190. package/src/tool/result/graph.ts +44 -1
  191. package/src/tool/result/health.ts +53 -1
  192. package/src/tool/result/inspect.ts +113 -1
  193. package/src/tool/result/orientation.ts +66 -14
  194. package/src/tool/result/refactor.ts +36 -4
  195. package/src/tool/result/resolve.ts +44 -7
  196. package/src/tool/result/types.ts +20 -0
  197. package/src/types/execution.ts +18 -5
  198. package/src/types/index.ts +8 -1
  199. package/src/ui/status-command.ts +5 -3
  200. package/src/ui/status-overlay.ts +4 -0
  201. package/src/ui/tui/common.ts +108 -10
  202. package/src/ui/tui/display.ts +48 -0
  203. package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/runtime.ts +0 -351
@@ -0,0 +1,561 @@
1
+ // biome-ignore-all lint/style/noExcessiveLinesPerFile: one mailbox state machine owns all Worker lifecycle transitions
2
+ import { setImmediate as yieldImmediate } from "node:timers/promises";
3
+ import { Worker } from "node:worker_threads";
4
+ import {
5
+ type CodeRequestControl,
6
+ CodeRequestDeadlineError,
7
+ isCodeRequestDeadlineError,
8
+ isCodeRequestInterruption,
9
+ throwIfCodeRequestInterrupted,
10
+ } from "@mrclrchtr/supi-code-runtime/api";
11
+ import { isDebugOperationId, startDebugTimer } from "@mrclrchtr/supi-core/debug";
12
+ import type { TreeSitterResult } from "../types.ts";
13
+ import { publishStructuralTimingEvent } from "./structural-timing.ts";
14
+ import { StructuralWorkerLifecycle } from "./structural-worker-client-lifecycle.ts";
15
+ import { assertStructuralProtocolMessageSize } from "./structural-worker-message-size.ts";
16
+ import {
17
+ decodeStructuralResult,
18
+ type ParentToStructuralWorkerMessage,
19
+ STRUCTURAL_WORKER_LIMITS,
20
+ STRUCTURAL_WORKER_PROTOCOL_VERSION,
21
+ type StructuralWorkerOperation,
22
+ type StructuralWorkerToParentMessage,
23
+ validateWorkerToParentMessage,
24
+ } from "./structural-worker-protocol.ts";
25
+
26
+ interface StructuralWorkerLike {
27
+ postMessage(message: ParentToStructuralWorkerMessage): void;
28
+ on(event: "message", listener: (message: unknown) => void): this;
29
+ on(event: "error", listener: (error: Error) => void): this;
30
+ on(event: "exit", listener: (code: number) => void): this;
31
+ terminate(): Promise<number>;
32
+ }
33
+
34
+ export type StructuralWorkerFactory = (options: {
35
+ readonly cwd: string;
36
+ readonly generation: number;
37
+ }) => StructuralWorkerLike;
38
+
39
+ const TEST_WORKER_FACTORY = Symbol.for("supi-tree-sitter/test-worker-factory");
40
+
41
+ /** Install a process-local Worker factory for integration tests. */
42
+ export function setStructuralWorkerFactoryForTests(
43
+ factory: StructuralWorkerFactory | undefined,
44
+ ): void {
45
+ const state = globalThis as typeof globalThis & {
46
+ [TEST_WORKER_FACTORY]?: StructuralWorkerFactory;
47
+ };
48
+ if (factory) state[TEST_WORKER_FACTORY] = factory;
49
+ else delete state[TEST_WORKER_FACTORY];
50
+ }
51
+
52
+ interface PendingRequest<T> {
53
+ readonly id: string;
54
+ readonly input: StructuralWorkerOperation;
55
+ readonly control?: CodeRequestControl;
56
+ readonly cancellationBuffer: SharedArrayBuffer;
57
+ readonly cancellationFlag: Int32Array;
58
+ readonly resolve: (value: TreeSitterResult<T>) => void;
59
+ readonly reject: (error: unknown) => void;
60
+ cleanupControl(): void;
61
+ chunks: Uint8Array[];
62
+ nextSequence: number;
63
+ assembledBytes: number;
64
+ awaitingAck: number | null;
65
+ finalSequence: number | null;
66
+ settled: boolean;
67
+ hardStopTimer: NodeJS.Timeout | null;
68
+ }
69
+
70
+ interface ActiveWorker {
71
+ readonly generation: number;
72
+ readonly worker: StructuralWorkerLike;
73
+ ready: boolean;
74
+ startupSettled: boolean;
75
+ readonly settleStartupFailure: (message: string) => void;
76
+ }
77
+
78
+ /** Parent-owned asynchronous proxy for exactly one long-lived Structural Worker. */
79
+ export class StructuralWorkerClient {
80
+ readonly #cwd: string;
81
+ readonly #factory: StructuralWorkerFactory;
82
+ #worker: ActiveWorker | null = null;
83
+ #queue: PendingRequest<unknown>[] = [];
84
+ #active: PendingRequest<unknown> | null = null;
85
+ #generation = 0;
86
+ #requestSequence = 0;
87
+ #closed = false;
88
+ #unavailableReason: string | null = null;
89
+ #startupFailures = 0;
90
+ #starting: Promise<void> | null = null;
91
+ readonly #lifecycle = new StructuralWorkerLifecycle();
92
+ #disposePromise: Promise<void> | null = null;
93
+
94
+ constructor(cwd: string, factory?: StructuralWorkerFactory) {
95
+ this.#cwd = cwd;
96
+ this.#factory = factory ?? testWorkerFactory() ?? createProductionWorker;
97
+ }
98
+
99
+ /** Start and validate the owned Worker without running parser work on the parent. */
100
+ async start(): Promise<void> {
101
+ if (this.#closed) throw new Error("Structural Worker client is closed");
102
+ if (this.#worker?.ready) return;
103
+ if (this.#unavailableReason) throw new Error(this.#unavailableReason);
104
+ this.#starting ??= this.#lifecycle.run(() => this.#startWithRetry());
105
+ return this.#starting;
106
+ }
107
+
108
+ async #startWithRetry(): Promise<void> {
109
+ try {
110
+ await this.#startGeneration();
111
+ this.#startupFailures = 0;
112
+ } catch (error) {
113
+ await this.#startupFailure(errorMessage(error));
114
+ if (this.#unavailableReason) throw new Error(this.#unavailableReason, { cause: error });
115
+ } finally {
116
+ this.#starting = null;
117
+ }
118
+ }
119
+
120
+ /** Admit one operation to the fixed FIFO mailbox. */
121
+ async execute<T>(
122
+ input: StructuralWorkerOperation,
123
+ control?: CodeRequestControl,
124
+ ): Promise<TreeSitterResult<T>> {
125
+ throwIfCodeRequestInterrupted(control);
126
+ if (this.#closed) return runtimeError("Structural Worker is shut down");
127
+ if (control?.operationId !== undefined && !isDebugOperationId(control.operationId)) {
128
+ return runtimeError("Invalid Debug Operation ID");
129
+ }
130
+ if (this.#unavailableReason) return runtimeError(this.#unavailableReason);
131
+ if (this.#queue.length >= STRUCTURAL_WORKER_LIMITS.maxQueuedRequests) {
132
+ return runtimeError("Structural Worker mailbox is busy");
133
+ }
134
+
135
+ return new Promise<TreeSitterResult<T>>((resolve, reject) => {
136
+ const cancellationBuffer = new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT);
137
+ const pending: PendingRequest<T> = {
138
+ id: `structural-${++this.#requestSequence}`,
139
+ input,
140
+ control,
141
+ cancellationBuffer,
142
+ cancellationFlag: new Int32Array(cancellationBuffer),
143
+ resolve,
144
+ reject,
145
+ cleanupControl() {},
146
+ chunks: [],
147
+ nextSequence: 0,
148
+ assembledBytes: 0,
149
+ awaitingAck: null,
150
+ finalSequence: null,
151
+ settled: false,
152
+ hardStopTimer: null,
153
+ };
154
+ this.#attachControl(pending as PendingRequest<unknown>);
155
+ this.#queue.push(pending as PendingRequest<unknown>);
156
+ void this.start().then(
157
+ () => this.#dispatch(),
158
+ () => undefined,
159
+ );
160
+ });
161
+ }
162
+
163
+ /** Stop admission, settle requests, terminate the Worker, and await exit. */
164
+ dispose(): Promise<void> {
165
+ this.#disposePromise ??= this.#dispose();
166
+ return this.#disposePromise;
167
+ }
168
+
169
+ async #dispose(): Promise<void> {
170
+ this.#closed = true;
171
+ ++this.#generation;
172
+ this.#unavailableReason = "Structural Worker is shut down";
173
+ for (const request of this.#queue.splice(0))
174
+ this.#settle(request, runtimeError("Structural Worker is shut down"));
175
+ if (this.#active) {
176
+ const active = this.#active;
177
+ Atomics.store(active.cancellationFlag, 0, 1);
178
+ this.#active = null;
179
+ this.#settle(active, runtimeError("Structural Worker is shut down"));
180
+ }
181
+ const owned = this.#worker;
182
+ this.#worker = null;
183
+ owned?.settleStartupFailure("Structural Worker is shut down");
184
+ this.#starting = null;
185
+ await this.#lifecycle.terminate(owned?.worker ?? null);
186
+ await this.#lifecycle.settled();
187
+ }
188
+
189
+ async #startGeneration(): Promise<void> {
190
+ const generation = ++this.#generation;
191
+ const worker = this.#factory({ cwd: this.#cwd, generation });
192
+ let rejectStartup = (_message: string) => {};
193
+ const active: ActiveWorker = {
194
+ generation,
195
+ worker,
196
+ ready: false,
197
+ startupSettled: false,
198
+ settleStartupFailure: (message) => rejectStartup(message),
199
+ };
200
+ this.#worker = active;
201
+
202
+ await new Promise<void>((resolve, reject) => {
203
+ const settleReady = () => {
204
+ if (active.startupSettled) return;
205
+ active.startupSettled = true;
206
+ active.ready = true;
207
+ this.#startupFailures = 0;
208
+ resolve();
209
+ };
210
+ const settleFailure = (message: string) => {
211
+ if (active.startupSettled) return;
212
+ active.startupSettled = true;
213
+ reject(new Error(message));
214
+ };
215
+ rejectStartup = settleFailure;
216
+ // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: startup and active protocol states share one fenced Worker listener
217
+ worker.on("message", (value) => {
218
+ const validation = validateWorkerToParentMessage(value);
219
+ if (validation.kind === "invalid") {
220
+ settleFailure(validation.reason);
221
+ return;
222
+ }
223
+ const message = validation.message;
224
+ if (this.#worker !== active || message.generation !== generation) return;
225
+ if (message.kind === "ready") {
226
+ settleReady();
227
+ return;
228
+ }
229
+ if (message.kind === "startup-failure") {
230
+ settleFailure(message.message);
231
+ return;
232
+ }
233
+ if (!active.ready) {
234
+ settleFailure("Structural Worker sent work before readiness");
235
+ return;
236
+ }
237
+ void this.#handleMessage(message);
238
+ });
239
+ worker.on("error", (error) => {
240
+ settleFailure(error.message);
241
+ if (this.#worker === active && active.ready) {
242
+ void this.#lifecycle.run(() => this.#workerFailure(error.message));
243
+ }
244
+ });
245
+ worker.on("exit", (code) => {
246
+ settleFailure(`Structural Worker exited with code ${code}`);
247
+ if (!this.#closed && this.#worker === active && active.ready) {
248
+ void this.#lifecycle.run(() =>
249
+ this.#workerFailure(`Structural Worker exited with code ${code}`),
250
+ );
251
+ }
252
+ });
253
+ });
254
+ }
255
+
256
+ #dispatch(): void {
257
+ if (this.#closed || this.#active || !this.#worker?.ready) return;
258
+ while (this.#queue.length > 0) {
259
+ const request = this.#queue.shift();
260
+ if (!request) return;
261
+ try {
262
+ throwIfCodeRequestInterrupted(request.control);
263
+ } catch (error) {
264
+ this.#reject(request, error);
265
+ continue;
266
+ }
267
+ this.#active = request;
268
+ const message = {
269
+ kind: "request",
270
+ version: STRUCTURAL_WORKER_PROTOCOL_VERSION,
271
+ generation: this.#worker.generation,
272
+ requestId: request.id,
273
+ input: request.input,
274
+ ...(request.control?.operationId ? { operationId: request.control.operationId } : {}),
275
+ deadline: request.control?.deadline,
276
+ cancellationFlag: request.cancellationBuffer,
277
+ } as const;
278
+ try {
279
+ assertStructuralProtocolMessageSize(message);
280
+ this.#worker.worker.postMessage(message);
281
+ } catch (error) {
282
+ this.#active = null;
283
+ this.#settle(request, runtimeError(errorMessage(error)));
284
+ continue;
285
+ }
286
+ return;
287
+ }
288
+ }
289
+
290
+ async #handleMessage(message: StructuralWorkerToParentMessage): Promise<void> {
291
+ if (message.kind === "observation") {
292
+ if (this.#active?.id !== message.requestId)
293
+ return this.#protocolFailure("Unknown observation request id");
294
+ assertStructuralProtocolMessageSize(message);
295
+ if (message.observation.operationId !== this.#active.control?.operationId) {
296
+ return this.#protocolFailure("Structural observation ownership mismatch");
297
+ }
298
+ publishStructuralTimingEvent(message.observation);
299
+ return;
300
+ }
301
+ if (message.kind === "chunk") return this.#handleChunk(message);
302
+ if (message.kind === "terminal") return this.#handleTerminal(message);
303
+ }
304
+
305
+ async #handleChunk(
306
+ message: Extract<StructuralWorkerToParentMessage, { kind: "chunk" }>,
307
+ ): Promise<void> {
308
+ const active = this.#active;
309
+ if (!active || active.id !== message.requestId)
310
+ return this.#protocolFailure("Unknown chunk request id");
311
+ if (active.awaitingAck !== null) {
312
+ return this.#protocolFailure("Structural Worker sent more than one unacknowledged chunk");
313
+ }
314
+ if (message.sequence !== active.nextSequence)
315
+ return this.#protocolFailure("Invalid chunk sequence");
316
+ if (active.assembledBytes + message.encodedBytes > STRUCTURAL_WORKER_LIMITS.maxResultBytes) {
317
+ return this.#protocolFailure("Structural result exceeds the byte limit");
318
+ }
319
+ try {
320
+ throwIfCodeRequestInterrupted(active.control);
321
+ } catch (error) {
322
+ this.#interrupt(active, error);
323
+ return;
324
+ }
325
+ active.chunks.push(Uint8Array.from(message.payload));
326
+ active.nextSequence += 1;
327
+ active.assembledBytes += message.encodedBytes;
328
+ active.awaitingAck = message.sequence;
329
+ if (message.final) active.finalSequence = message.sequence;
330
+ await yieldImmediate();
331
+ if (this.#active !== active || !this.#worker) return;
332
+ active.awaitingAck = null;
333
+ this.#worker.worker.postMessage({
334
+ kind: "chunk-ack",
335
+ version: STRUCTURAL_WORKER_PROTOCOL_VERSION,
336
+ generation: this.#worker.generation,
337
+ requestId: active.id,
338
+ sequence: message.sequence,
339
+ });
340
+ }
341
+
342
+ async #handleTerminal(
343
+ message: Extract<StructuralWorkerToParentMessage, { kind: "terminal" }>,
344
+ ): Promise<void> {
345
+ const active = this.#active;
346
+ if (!active || active.id !== message.requestId)
347
+ return this.#protocolFailure("Unknown terminal request id");
348
+ if (active.settled) return this.#protocolFailure("Duplicate terminal message");
349
+ if (active.awaitingAck !== null || active.finalSequence !== active.nextSequence - 1) {
350
+ return this.#protocolFailure(
351
+ "Structural Worker terminal preceded the final chunk acknowledgement",
352
+ );
353
+ }
354
+ this.#active = null;
355
+ if (message.outcome === "cancelled") {
356
+ const reason = active.control?.signal?.reason ?? new DOMException("Aborted", "AbortError");
357
+ this.#reject(active, reason);
358
+ } else if (message.outcome === "timeout") {
359
+ this.#reject(active, new CodeRequestDeadlineError());
360
+ } else if (active.control?.deadline !== undefined && Date.now() >= active.control.deadline) {
361
+ this.#reject(active, new CodeRequestDeadlineError());
362
+ } else if (active.control?.signal?.aborted) {
363
+ this.#reject(
364
+ active,
365
+ active.control.signal.reason ?? new DOMException("Aborted", "AbortError"),
366
+ );
367
+ } else {
368
+ try {
369
+ this.#settle(active, decodeStructuralResult(active.chunks));
370
+ } catch (error) {
371
+ this.#reject(active, error);
372
+ }
373
+ }
374
+ this.#dispatch();
375
+ }
376
+
377
+ #attachControl(request: PendingRequest<unknown>): void {
378
+ const signal = request.control?.signal;
379
+ const deadline = request.control?.deadline;
380
+ const abort = () =>
381
+ this.#interrupt(request, signal?.reason ?? new DOMException("Aborted", "AbortError"));
382
+ signal?.addEventListener("abort", abort, { once: true });
383
+ let deadlineTimer: NodeJS.Timeout | null = null;
384
+ if (deadline !== undefined) {
385
+ deadlineTimer = setTimeout(
386
+ () => this.#interrupt(request, new CodeRequestDeadlineError()),
387
+ Math.max(0, deadline - Date.now()),
388
+ );
389
+ deadlineTimer.unref?.();
390
+ }
391
+ request.cleanupControl = () => {
392
+ signal?.removeEventListener("abort", abort);
393
+ if (deadlineTimer) clearTimeout(deadlineTimer);
394
+ if (request.hardStopTimer) clearTimeout(request.hardStopTimer);
395
+ };
396
+ }
397
+
398
+ #interrupt(request: PendingRequest<unknown>, error: unknown): void {
399
+ if (request.settled) return;
400
+ const queuedIndex = this.#queue.indexOf(request);
401
+ if (queuedIndex >= 0) {
402
+ this.#queue.splice(queuedIndex, 1);
403
+ this.#reject(request, error);
404
+ return;
405
+ }
406
+ if (this.#active !== request || !this.#worker) return;
407
+ Atomics.store(request.cancellationFlag, 0, 1);
408
+ this.#worker.worker.postMessage({
409
+ kind: "cancel",
410
+ version: STRUCTURAL_WORKER_PROTOCOL_VERSION,
411
+ generation: this.#worker.generation,
412
+ requestId: request.id,
413
+ });
414
+ request.hardStopTimer = setTimeout(
415
+ () => void this.#hardStop(request, error),
416
+ STRUCTURAL_WORKER_LIMITS.hardStopGraceMs,
417
+ );
418
+ request.hardStopTimer.unref?.();
419
+ }
420
+
421
+ async #hardStop(request: PendingRequest<unknown>, error: unknown): Promise<void> {
422
+ if (this.#active !== request || request.settled) return;
423
+ this.#active = null;
424
+ this.#reject(request, error);
425
+ await this.#restartOwned(request, "Structural Worker hard stop");
426
+ }
427
+
428
+ async #protocolFailure(reason: string): Promise<void> {
429
+ const active = this.#active;
430
+ this.#active = null;
431
+ if (active) this.#settle(active, runtimeError(`Structural Worker protocol failure: ${reason}`));
432
+ if (active) await this.#restartOwned(active, reason);
433
+ else await this.#restart(reason);
434
+ }
435
+
436
+ async #workerFailure(reason: string): Promise<void> {
437
+ const active = this.#active;
438
+ this.#active = null;
439
+ if (active) this.#settle(active, runtimeError(reason));
440
+ if (active) await this.#restartOwned(active, reason, true);
441
+ else await this.#restart(reason);
442
+ }
443
+
444
+ async #restartOwned(
445
+ request: PendingRequest<unknown>,
446
+ reason: string,
447
+ lifecycleAlreadyOwned = false,
448
+ ): Promise<void> {
449
+ const timer = startDebugTimer();
450
+ let outcome: "completed" | "failed" = "completed";
451
+ try {
452
+ if (lifecycleAlreadyOwned) await this.#restart(reason);
453
+ else await this.#lifecycle.run(() => this.#restart(reason));
454
+ if (this.#unavailableReason) outcome = "failed";
455
+ } catch {
456
+ outcome = "failed";
457
+ }
458
+ if (this.#closed) return;
459
+ timer.finish(
460
+ {
461
+ operationId: request.control?.operationId,
462
+ source: "tree-sitter",
463
+ level: "debug",
464
+ category: "structural.worker.restart.timing",
465
+ message: `Structural Worker restart ${outcome}`,
466
+ data: { outcome, cacheReset: true },
467
+ },
468
+ "restart",
469
+ );
470
+ }
471
+
472
+ async #startupFailure(reason: string): Promise<void> {
473
+ if (this.#closed) return;
474
+ this.#startupFailures += 1;
475
+ if (this.#startupFailures >= 2) {
476
+ this.#unavailableReason = `Structural capability unavailable: ${reason}`;
477
+ const failed = this.#worker;
478
+ this.#worker = null;
479
+ failed?.settleStartupFailure("Structural capability unavailable");
480
+ if (failed) await failed.worker.terminate().catch(() => 0);
481
+ this.#failQueued(this.#unavailableReason);
482
+ return;
483
+ }
484
+ const old = this.#worker;
485
+ this.#worker = null;
486
+ old?.settleStartupFailure("Structural Worker startup retry");
487
+ if (old) await old.worker.terminate().catch(() => 0);
488
+ if (this.#closed) return;
489
+ try {
490
+ await this.#startGeneration();
491
+ this.#dispatch();
492
+ } catch (error) {
493
+ await this.#startupFailure(errorMessage(error));
494
+ }
495
+ if (this.#unavailableReason) this.#failQueued(this.#unavailableReason);
496
+ }
497
+
498
+ async #restart(_reason: string): Promise<void> {
499
+ if (this.#closed) return;
500
+ const old = this.#worker;
501
+ const generation = ++this.#generation;
502
+ this.#worker = null;
503
+ old?.settleStartupFailure("Structural Worker restart");
504
+ this.#starting = (async () => {
505
+ if (old) await old.worker.terminate().catch(() => 0);
506
+ if (this.#closed || generation !== this.#generation) return;
507
+ await this.#startGeneration();
508
+ })();
509
+ try {
510
+ await this.#starting;
511
+ this.#startupFailures = 0;
512
+ this.#dispatch();
513
+ } catch (error) {
514
+ await this.#startupFailure(errorMessage(error));
515
+ }
516
+ }
517
+
518
+ #failQueued(message: string): void {
519
+ for (const request of this.#queue.splice(0)) this.#settle(request, runtimeError(message));
520
+ }
521
+
522
+ #settle<T>(request: PendingRequest<T>, result: TreeSitterResult<T>): void {
523
+ if (request.settled) return;
524
+ request.settled = true;
525
+ request.cleanupControl();
526
+ request.resolve(result);
527
+ }
528
+
529
+ #reject(request: PendingRequest<unknown>, error: unknown): void {
530
+ if (request.settled) return;
531
+ request.settled = true;
532
+ request.cleanupControl();
533
+ if (isCodeRequestInterruption(error, request.control) || isCodeRequestDeadlineError(error))
534
+ request.reject(error);
535
+ else request.reject(error);
536
+ }
537
+ }
538
+
539
+ function testWorkerFactory(): StructuralWorkerFactory | undefined {
540
+ return (globalThis as typeof globalThis & { [TEST_WORKER_FACTORY]?: StructuralWorkerFactory })[
541
+ TEST_WORKER_FACTORY
542
+ ];
543
+ }
544
+
545
+ function createProductionWorker(options: {
546
+ cwd: string;
547
+ generation: number;
548
+ }): StructuralWorkerLike {
549
+ return new Worker(new URL("../worker/bootstrap.mjs", import.meta.url), {
550
+ execArgv: [],
551
+ workerData: options,
552
+ });
553
+ }
554
+
555
+ function runtimeError<T = never>(message: string): TreeSitterResult<T> {
556
+ return { kind: "runtime-error", message };
557
+ }
558
+
559
+ function errorMessage(error: unknown): string {
560
+ return error instanceof Error ? error.message : "Structural Worker failed";
561
+ }
@@ -0,0 +1,24 @@
1
+ import { STRUCTURAL_WORKER_LIMITS } from "./structural-worker-protocol.ts";
2
+
3
+ /** Approximate encoded UTF-8 bytes for one plain protocol envelope. */
4
+ export function structuralProtocolMessageBytes(message: unknown): number {
5
+ try {
6
+ return Buffer.byteLength(
7
+ JSON.stringify(message, (_key, value) => {
8
+ if (value instanceof SharedArrayBuffer) return { sharedBytes: value.byteLength };
9
+ if (value instanceof Uint8Array) return { binaryBytes: value.byteLength };
10
+ return value;
11
+ }),
12
+ "utf8",
13
+ );
14
+ } catch {
15
+ return Number.POSITIVE_INFINITY;
16
+ }
17
+ }
18
+
19
+ /** Throw before an oversize protocol envelope crosses a thread boundary. */
20
+ export function assertStructuralProtocolMessageSize(message: unknown): void {
21
+ if (structuralProtocolMessageBytes(message) > STRUCTURAL_WORKER_LIMITS.maxMessageBytes) {
22
+ throw new Error("Structural Worker message exceeds the byte limit");
23
+ }
24
+ }