@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
@@ -3,16 +3,22 @@
3
3
  // and notification/request dispatching through vscode-jsonrpc's MessageConnection.
4
4
 
5
5
  import type { Readable, Writable } from "node:stream";
6
+ import {
7
+ type CodeRequestControl,
8
+ CodeRequestDeadlineError,
9
+ } from "@mrclrchtr/supi-code-runtime/api";
6
10
  import { startDebugTimer } from "@mrclrchtr/supi-core/debug";
7
11
  import {
8
12
  CancellationTokenSource,
9
13
  createMessageConnection,
14
+ ErrorCodes,
10
15
  type MessageConnection,
11
16
  NullLogger,
12
17
  ResponseError,
13
18
  StreamMessageReader,
14
19
  StreamMessageWriter,
15
20
  } from "vscode-jsonrpc/node";
21
+ import { boundCwd, LSP_REQUEST_TIMEOUT_ERROR_CODE, truncateIdentity } from "../debug-telemetry.ts";
16
22
 
17
23
  const DEFAULT_TIMEOUT_MS = 30_000;
18
24
 
@@ -37,13 +43,19 @@ export class JsonRpcClient {
37
43
  private requestHandler: RequestHandler | null = null;
38
44
  private closed = false;
39
45
  private readonly timeoutMs: number;
46
+ private readonly server: string | undefined;
47
+ private readonly cwd: string | undefined;
48
+ /** Failures that signal a stalled protocol: repeated errors and local timeouts. */
49
+ private protocolFailureCount = 0;
40
50
 
41
51
  constructor(
42
52
  private readonly input: Readable,
43
53
  private readonly output: Writable,
44
- options?: { timeoutMs?: number },
54
+ options?: { timeoutMs?: number; server?: string; cwd?: string },
45
55
  ) {
46
56
  this.timeoutMs = options?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
57
+ this.server = options?.server;
58
+ this.cwd = options?.cwd;
47
59
 
48
60
  const reader = new StreamMessageReader(this.input);
49
61
  const writer = new StreamMessageWriter(this.output);
@@ -55,6 +67,13 @@ export class JsonRpcClient {
55
67
  this.notificationHandler?.(method, params);
56
68
  });
57
69
 
70
+ // vscode-jsonrpc registers an internal $/progress handler that never
71
+ // reaches the catch-all handler. Route it explicitly so LSP progress
72
+ // notifications reach the client's readiness state machine.
73
+ this.connection.onNotification("$/progress", (params) => {
74
+ this.notificationHandler?.("$/progress", params);
75
+ });
76
+
58
77
  // Register catch-all request handler for server-initiated requests
59
78
  this.connection.onRequest(async (method, params, _token) => {
60
79
  if (!this.requestHandler) {
@@ -85,26 +104,53 @@ export class JsonRpcClient {
85
104
  sendRequest(
86
105
  method: string,
87
106
  params?: unknown,
88
- options?: { timeoutMs?: number },
107
+ options?: { timeoutMs?: number } & CodeRequestControl,
89
108
  ): Promise<unknown> {
90
109
  const timeoutMs = options?.timeoutMs ?? this.timeoutMs;
110
+ const signal = options?.signal;
111
+ const deadlineMs = options?.deadline === undefined ? undefined : options.deadline - Date.now();
91
112
  const methodClass = classifyRequestMethod(method);
113
+ const boundedMethod = truncateIdentity(method);
92
114
  const timer = startDebugTimer();
115
+ // An expired absolute deadline must not even send the request: the caller
116
+ // no longer awaits a result, so no protocol traffic may start.
117
+ if (deadlineMs !== undefined && deadlineMs <= 0) {
118
+ recordRequestTiming(
119
+ timer,
120
+ options?.operationId,
121
+ {
122
+ method: boundedMethod,
123
+ methodClass,
124
+ outcome: "timed-out",
125
+ errorCode: LSP_REQUEST_TIMEOUT_ERROR_CODE,
126
+ },
127
+ { server: this.server, cwd: this.cwd },
128
+ );
129
+ return Promise.reject(new CodeRequestDeadlineError());
130
+ }
93
131
  if (this.closed || !this.connection) {
94
- recordRequestTiming(timer, {
95
- methodClass,
96
- outcome: "cancelled",
97
- timeoutMs,
98
- timedOut: false,
99
- cancelled: true,
100
- });
132
+ recordRequestTiming(
133
+ timer,
134
+ options?.operationId,
135
+ {
136
+ method: boundedMethod,
137
+ methodClass,
138
+ outcome: "cancelled",
139
+ },
140
+ { server: this.server, cwd: this.cwd },
141
+ );
101
142
  return Promise.reject(new Error("JSON-RPC client is closed"));
102
143
  }
103
-
104
144
  const tokenSource = new CancellationTokenSource();
105
145
  let timeout: ReturnType<typeof setTimeout> | undefined;
106
146
  let timedOut = false;
107
- const timeoutError = new Error(`Request ${method} timed out after ${timeoutMs}ms`);
147
+ let aborted = false;
148
+ const timerDelayMs = deadlineMs === undefined ? timeoutMs : Math.min(timeoutMs, deadlineMs);
149
+ const timeoutError = Object.assign(
150
+ new Error(`Request ${method} timed out after ${timerDelayMs}ms`),
151
+ { code: LSP_REQUEST_TIMEOUT_ERROR_CODE },
152
+ );
153
+ const abortError = new Error(`Request ${method} was cancelled`);
108
154
 
109
155
  const request = this.connection.sendRequest(method, params, tokenSource.token);
110
156
  // Catch the raw request promise to prevent unhandled rejections when
@@ -115,46 +161,79 @@ export class JsonRpcClient {
115
161
  // the JSON-RPC token and rejects the caller. Using one timer avoids
116
162
  // a leak where the rejecting timer in a second Promise stays alive
117
163
  // after a successful response.
164
+ let abortHandler: (() => void) | undefined;
165
+ const abort = new Promise<never>((_resolve, reject) => {
166
+ abortHandler = () => {
167
+ aborted = true;
168
+ tokenSource.cancel();
169
+ // Reject with the caller's abort reason when one exists, matching the
170
+ // canonical throwIfCodeRequestInterrupted() behavior.
171
+ reject(signal?.reason ?? abortError);
172
+ };
173
+ if (signal?.aborted) abortHandler();
174
+ else signal?.addEventListener("abort", abortHandler, { once: true });
175
+ });
118
176
  const promise = Promise.race([
119
177
  request,
120
178
  new Promise<never>((_resolve, reject) => {
121
179
  timeout = setTimeout(() => {
122
180
  timedOut = true;
123
181
  tokenSource.cancel();
124
- reject(timeoutError);
125
- }, timeoutMs);
182
+ // A deadline that binds earlier than the timeout is a deadline
183
+ // outcome, distinct from an ordinary per-request timeout.
184
+ reject(
185
+ deadlineMs !== undefined && deadlineMs < timeoutMs
186
+ ? new CodeRequestDeadlineError()
187
+ : timeoutError,
188
+ );
189
+ }, timerDelayMs);
126
190
  }),
191
+ abort,
127
192
  ])
128
193
  .then(
129
194
  (result) => {
130
- recordRequestTiming(timer, {
131
- methodClass,
132
- outcome: "completed",
133
- timeoutMs,
134
- timedOut: false,
135
- cancelled: false,
136
- });
195
+ recordRequestTiming(
196
+ timer,
197
+ options?.operationId,
198
+ {
199
+ method: boundedMethod,
200
+ methodClass,
201
+ outcome: "completed",
202
+ },
203
+ { server: this.server, cwd: this.cwd },
204
+ );
137
205
  return result;
138
206
  },
139
207
  (error: unknown) => {
140
- const cancelled = timedOut || this.closed || isCancellationError(error);
208
+ const cancelled = timedOut || aborted || this.closed || isCancellationError(error);
141
209
  const outcome: RequestOutcome = timedOut
142
210
  ? "timed-out"
143
211
  : cancelled
144
212
  ? "cancelled"
145
213
  : "failed";
146
- recordRequestTiming(timer, {
147
- methodClass,
148
- outcome,
149
- timeoutMs,
150
- timedOut,
151
- cancelled,
152
- });
214
+ const errorCode = requestErrorCode(outcome, error);
215
+ this.countProtocolStallFailure(outcome, errorCode, error);
216
+ recordRequestTiming(
217
+ timer,
218
+ options?.operationId,
219
+ {
220
+ method: boundedMethod,
221
+ methodClass,
222
+ outcome,
223
+ // Failed requests record the server-reported JSON-RPC error code
224
+ // when the error carries one; timed-out requests record the
225
+ // defined timeout code (also for deadline expiries, whose error
226
+ // carries none); cancellations carry no error code.
227
+ errorCode,
228
+ },
229
+ { server: this.server, cwd: this.cwd },
230
+ );
153
231
  throw error;
154
232
  },
155
233
  )
156
234
  .finally(() => {
157
235
  if (timeout !== undefined) clearTimeout(timeout);
236
+ if (abortHandler) signal?.removeEventListener("abort", abortHandler);
158
237
  });
159
238
 
160
239
  // Prevent unhandled rejection when dispose() cancels requests
@@ -184,6 +263,30 @@ export class JsonRpcClient {
184
263
  this.connection = null;
185
264
  }
186
265
  }
266
+
267
+ /**
268
+ * Count one protocol-stall failure: a local timeout (not a caller-imposed
269
+ * deadline expiry) or a repeated server-reported protocol error. A deadline
270
+ * expiry is not a stall: the request may be perfectly healthy (ADR 0020).
271
+ */
272
+ private countProtocolStallFailure(
273
+ outcome: RequestOutcome,
274
+ errorCode: number | undefined,
275
+ error: unknown,
276
+ ): void {
277
+ if (
278
+ (outcome === "timed-out" && !(error instanceof CodeRequestDeadlineError)) ||
279
+ errorCode === ErrorCodes.ServerNotInitialized ||
280
+ errorCode === ErrorCodes.InvalidRequest
281
+ ) {
282
+ this.protocolFailureCount++;
283
+ }
284
+ }
285
+
286
+ /** Number of protocol-stall failures observed on this connection. */
287
+ getProtocolFailureCount(): number {
288
+ return this.protocolFailureCount;
289
+ }
187
290
  }
188
291
 
189
292
  const SEMANTIC_REQUESTS = new Set([
@@ -218,25 +321,50 @@ function isCancellationError(error: unknown): boolean {
218
321
  );
219
322
  }
220
323
 
324
+ /** One bounded request-timing observation with exact method identity. */
221
325
  interface RequestTimingObservation {
326
+ readonly method: string;
222
327
  readonly methodClass: RequestMethodClass;
223
328
  readonly outcome: RequestOutcome;
224
- readonly timeoutMs: number;
225
- readonly timedOut: boolean;
226
- readonly cancelled: boolean;
329
+ /** JSON-RPC error code reported by the server for a failed request. */
330
+ readonly errorCode?: number;
331
+ }
332
+
333
+ /** Return the JSON-RPC error code for one request outcome. */
334
+ function requestErrorCode(outcome: RequestOutcome, error: unknown): number | undefined {
335
+ if (outcome === "failed") return jsonRpcErrorCode(error);
336
+ if (outcome === "timed-out") return jsonRpcErrorCode(error) ?? LSP_REQUEST_TIMEOUT_ERROR_CODE;
337
+ return undefined;
338
+ }
339
+
340
+ /** Return the numeric JSON-RPC error code carried by an error, if any. */
341
+ function jsonRpcErrorCode(error: unknown): number | undefined {
342
+ if (typeof error !== "object" || error === null) return undefined;
343
+ const code = (error as { code?: unknown }).code;
344
+ return typeof code === "number" && Number.isInteger(code) ? code : undefined;
227
345
  }
228
346
 
229
347
  function recordRequestTiming(
230
348
  timer: ReturnType<typeof startDebugTimer>,
349
+ operationId: string | undefined,
231
350
  observation: RequestTimingObservation,
351
+ identity: { server?: string; cwd?: string },
232
352
  ): void {
233
353
  timer.finish(
234
354
  () => ({
355
+ operationId,
235
356
  source: "lsp",
236
357
  level: "debug",
237
358
  category: "request.timing",
238
- message: `LSP ${observation.methodClass} request ${observation.outcome}`,
239
- data: { ...observation },
359
+ message: `LSP ${observation.methodClass} request ${observation.outcome} for ${observation.method}`,
360
+ cwd: boundCwd(identity.cwd),
361
+ data: {
362
+ method: observation.method,
363
+ methodClass: observation.methodClass,
364
+ outcome: observation.outcome,
365
+ ...(identity.server !== undefined ? { server: identity.server } : {}),
366
+ ...(observation.errorCode !== undefined ? { errorCode: observation.errorCode } : {}),
367
+ },
240
368
  }),
241
369
  "request",
242
370
  );
@@ -3,6 +3,9 @@
3
3
  import type { ClientCapabilities } from "./types.ts";
4
4
 
5
5
  export const CLIENT_CAPABILITIES: ClientCapabilities = {
6
+ general: {
7
+ positionEncodings: ["utf-16"],
8
+ },
6
9
  textDocument: {
7
10
  synchronization: {
8
11
  didSave: true,
@@ -49,7 +52,10 @@ export const CLIENT_CAPABILITIES: ClientCapabilities = {
49
52
  versionSupport: true,
50
53
  },
51
54
  diagnostic: {
52
- dynamicRegistration: false,
55
+ // Dynamic registration lets servers such as pyright-langserver register
56
+ // `textDocument/diagnostic` support after initialization. Probes show
57
+ // only Pyright acts on this global flag among the built-in servers.
58
+ dynamicRegistration: true,
53
59
  relatedDocumentSupport: true,
54
60
  },
55
61
  },
@@ -58,6 +64,9 @@ export const CLIENT_CAPABILITIES: ClientCapabilities = {
58
64
  },
59
65
  workspace: {
60
66
  workspaceFolders: false,
67
+ workspaceEdit: {
68
+ documentChanges: true,
69
+ },
61
70
  diagnostics: {
62
71
  refreshSupport: false,
63
72
  },
@@ -50,7 +50,7 @@
50
50
  },
51
51
  "kotlin": {
52
52
  "command": "kotlin-lsp",
53
- "args": [],
53
+ "args": ["--stdio"],
54
54
  "fileTypes": ["kt", "kts"],
55
55
  "rootMarkers": ["build.gradle.kts", "pom.xml"]
56
56
  },
@@ -0,0 +1,45 @@
1
+ // Bounded identity vocabulary for LSP debug telemetry.
2
+ //
3
+ // LSP debug events may carry server, workspace, file, method, and root
4
+ // identity for local protocol diagnosis. Every emitted identity string is
5
+ // bounded to MAX_IDENTITY_STRING UTF-16 code units (truncation marker
6
+ // included) and every identity list is bounded to MAX_SERVERS entries.
7
+
8
+ /** Maximum number of server entries in one bounded LSP debug payload. */
9
+ export const MAX_SERVERS = 16;
10
+
11
+ /** Maximum UTF-16 code-unit length of one emitted identity string in an LSP debug payload. */
12
+ export const MAX_IDENTITY_STRING = 512;
13
+
14
+ /** Marker appended to an identity string truncated to MAX_IDENTITY_STRING. */
15
+ export const IDENTITY_TRUNCATION_MARKER = "…";
16
+
17
+ /**
18
+ * JSON-RPC error code recorded for client-local request timeouts.
19
+ *
20
+ * The JSON-RPC and LSP vocabularies define no timeout code. This constant
21
+ * uses the JSON-RPC implementation-defined error range (-32099..-32000) at a
22
+ * value that vscode-jsonrpc transport codes (-32099, -32098, -32097, -32096,
23
+ * -32002, -32001) and LSP error codes never use.
24
+ */
25
+ export const LSP_REQUEST_TIMEOUT_ERROR_CODE = -32_095;
26
+
27
+ /**
28
+ * Truncate one identity string so the emitted value (marker included) never
29
+ * exceeds MAX_IDENTITY_STRING UTF-16 code units.
30
+ */
31
+ export function truncateIdentity(value: string): string {
32
+ if (value.length <= MAX_IDENTITY_STRING) return value;
33
+ return `${value.slice(0, MAX_IDENTITY_STRING - 1)}${IDENTITY_TRUNCATION_MARKER}`;
34
+ }
35
+
36
+ /** Bound an optional workspace root for event-level identity; undefined stays absent. */
37
+ export function boundCwd(cwd: string | undefined): string | undefined {
38
+ if (cwd === undefined) return undefined;
39
+ return truncateIdentity(cwd);
40
+ }
41
+
42
+ /** Bound a server-name list to MAX_SERVERS entries, truncating each name. */
43
+ export function boundServerNames(names: readonly string[]): string[] {
44
+ return names.slice(0, MAX_SERVERS).map(truncateIdentity);
45
+ }
@@ -0,0 +1,38 @@
1
+ /** A final evidence state for one tracked document. */
2
+ export type DiagnosticEvidenceStatus = "confirmed" | "unconfirmed" | "failed" | "removed";
3
+
4
+ /** Freshness evidence for one tracked document. */
5
+ export interface DiagnosticEvidenceDocument {
6
+ readonly file: string;
7
+ readonly status: DiagnosticEvidenceStatus;
8
+ }
9
+
10
+ /** Exact document coverage from a diagnostic snapshot or refresh attempt. */
11
+ export interface DiagnosticEvidenceSummary {
12
+ readonly requested: number;
13
+ readonly confirmed: number;
14
+ readonly unconfirmed: number;
15
+ readonly failed: number;
16
+ readonly removed: number;
17
+ readonly documents: readonly DiagnosticEvidenceDocument[];
18
+ }
19
+
20
+ /** Build exact coverage counts from one document outcome per requested document. */
21
+ export function summarizeDiagnosticEvidence(
22
+ documents: readonly DiagnosticEvidenceDocument[],
23
+ ): DiagnosticEvidenceSummary {
24
+ const counts = {
25
+ requested: documents.length,
26
+ confirmed: 0,
27
+ unconfirmed: 0,
28
+ failed: 0,
29
+ removed: 0,
30
+ };
31
+ for (const document of documents) counts[document.status]++;
32
+ return { ...counts, documents: [...documents] };
33
+ }
34
+
35
+ /** Return an empty evidence summary for a refresh with no tracked documents. */
36
+ export function emptyDiagnosticEvidence(): DiagnosticEvidenceSummary {
37
+ return summarizeDiagnosticEvidence([]);
38
+ }
@@ -1,5 +1,16 @@
1
1
  import * as path from "node:path";
2
+ import {
3
+ type CodeRequestControl,
4
+ isCodeRequestInterruption,
5
+ } from "@mrclrchtr/supi-code-runtime/api";
2
6
  import type { LspClient } from "../client/client.ts";
7
+ import { getDiagnosticFileState } from "../client/client-file-state.ts";
8
+ import {
9
+ type DiagnosticEvidenceDocument,
10
+ type DiagnosticEvidenceSummary,
11
+ summarizeDiagnosticEvidence,
12
+ } from "../diagnostics/evidence.ts";
13
+ import { uriToFile } from "../utils.ts";
3
14
 
4
15
  export function closeFileAcrossClients(clients: Iterable<LspClient>, filePath: string): void {
5
16
  const resolvedPath = path.resolve(filePath);
@@ -21,14 +32,73 @@ export function pruneMissingFilesFromClients(clients: Iterable<LspClient>): stri
21
32
 
22
33
  export async function refreshOpenDiagnosticsForClients(
23
34
  clients: Iterable<LspClient>,
24
- options?: { maxWaitMs?: number; quietMs?: number },
25
- ): Promise<void> {
26
- const refreshes = Array.from(clients)
27
- .filter((client) => client.status === "running")
28
- .map((client) =>
29
- client.refreshOpenDiagnostics(options).catch(() => {
30
- // Soft-fail: LSP errors must not prevent agent startup
31
- }),
32
- );
35
+ options?: { maxWaitMs?: number; quietMs?: number } & CodeRequestControl,
36
+ ): Promise<DiagnosticEvidenceSummary> {
37
+ const documents: DiagnosticEvidenceDocument[] = [];
38
+ const refreshes = Array.from(clients).map(async (client) => {
39
+ const trackedFiles = [...client.openFiles];
40
+ if (client.status !== "running") {
41
+ documents.push(...failedCoverageForClient(client, trackedFiles));
42
+ return;
43
+ }
44
+
45
+ try {
46
+ const result = await client.refreshOpenDiagnostics(options);
47
+ const reportedFiles = new Set(result.documents.map((document) => document.file));
48
+ documents.push(...result.documents);
49
+ documents.push(
50
+ ...trackedFiles
51
+ .filter((file) => !reportedFiles.has(file))
52
+ .map((file) => ({ file, status: "failed" as const })),
53
+ );
54
+ } catch (error) {
55
+ // A cancelled refresh must not degrade into failed coverage evidence.
56
+ if (isCodeRequestInterruption(error, options)) throw error;
57
+ // Keep failed coverage for every document requested by the client.
58
+ documents.push(...failedCoverageForClient(client, trackedFiles));
59
+ }
60
+ });
33
61
  await Promise.all(refreshes);
62
+ return summarizeDiagnosticEvidence(deduplicateEvidenceDocuments(documents));
63
+ }
64
+
65
+ function failedCoverageForClient(
66
+ client: LspClient,
67
+ trackedFiles: readonly string[],
68
+ ): DiagnosticEvidenceDocument[] {
69
+ const snapshot = client.getDiagnosticSnapshot();
70
+ const files = new Set([
71
+ ...trackedFiles,
72
+ ...snapshot.documents.map((document) => uriToFile(document.uri)),
73
+ ]);
74
+ return Array.from(files).map((file) => ({
75
+ file,
76
+ status: getDiagnosticFileState(file) === "removed" ? ("removed" as const) : ("failed" as const),
77
+ }));
78
+ }
79
+
80
+ function deduplicateEvidenceDocuments(
81
+ documents: readonly DiagnosticEvidenceDocument[],
82
+ ): DiagnosticEvidenceDocument[] {
83
+ const byFile = new Map<string, DiagnosticEvidenceDocument>();
84
+ for (const document of documents) {
85
+ const existing = byFile.get(document.file);
86
+ if (!existing || evidenceStatusRank(document.status) > evidenceStatusRank(existing.status)) {
87
+ byFile.set(document.file, document);
88
+ }
89
+ }
90
+ return Array.from(byFile.values()).sort((a, b) => a.file.localeCompare(b.file));
91
+ }
92
+
93
+ function evidenceStatusRank(status: DiagnosticEvidenceDocument["status"]): number {
94
+ switch (status) {
95
+ case "confirmed":
96
+ return 1;
97
+ case "unconfirmed":
98
+ return 2;
99
+ case "failed":
100
+ return 3;
101
+ case "removed":
102
+ return 4;
103
+ }
34
104
  }
@@ -1,4 +1,9 @@
1
1
  import { readFileSync } from "node:fs";
2
+ import {
3
+ type CodeQueryResult,
4
+ type CodeRequestControl,
5
+ mapCodeQueryResult,
6
+ } from "@mrclrchtr/supi-code-runtime/api";
2
7
  import type { LspClient } from "../client/client.ts";
3
8
  import type { Diagnostic } from "../config/types.ts";
4
9
  import { relativeFilePathFromUri } from "../diagnostics/diagnostic-summary.ts";
@@ -10,10 +15,14 @@ export async function syncClientFileAndGetDiagnostics(
10
15
  client: Pick<LspClient, "syncAndWaitForDiagnostics">,
11
16
  filePath: string,
12
17
  maxSeverity: number,
13
- ): Promise<Diagnostic[]> {
14
- return filterDiagnosticsBySeverity(
15
- await client.syncAndWaitForDiagnostics(filePath, readFileSync(filePath, "utf-8")),
16
- maxSeverity,
18
+ control?: CodeRequestControl,
19
+ ): Promise<CodeQueryResult<Diagnostic[]>> {
20
+ const content = readFileSync(filePath, "utf-8");
21
+ const result = control
22
+ ? await client.syncAndWaitForDiagnostics(filePath, content, control)
23
+ : await client.syncAndWaitForDiagnostics(filePath, content);
24
+ return mapCodeQueryResult(result, (diagnostics) =>
25
+ filterDiagnosticsBySeverity(diagnostics, maxSeverity),
17
26
  );
18
27
  }
19
28