@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
@@ -3,15 +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";
10
+ import { startDebugTimer } from "@mrclrchtr/supi-core/debug";
6
11
  import {
7
12
  CancellationTokenSource,
8
13
  createMessageConnection,
14
+ ErrorCodes,
9
15
  type MessageConnection,
10
16
  NullLogger,
11
17
  ResponseError,
12
18
  StreamMessageReader,
13
19
  StreamMessageWriter,
14
20
  } from "vscode-jsonrpc/node";
21
+ import { boundCwd, LSP_REQUEST_TIMEOUT_ERROR_CODE, truncateIdentity } from "../debug-telemetry.ts";
15
22
 
16
23
  const DEFAULT_TIMEOUT_MS = 30_000;
17
24
 
@@ -20,6 +27,9 @@ const DEFAULT_TIMEOUT_MS = 30_000;
20
27
  export type NotificationHandler = (method: string, params: unknown) => void;
21
28
  export type RequestHandler = (method: string, params: unknown) => Promise<unknown> | unknown;
22
29
 
30
+ type RequestMethodClass = "diagnostic" | "lifecycle" | "other" | "refactor" | "semantic";
31
+ type RequestOutcome = "cancelled" | "completed" | "failed" | "timed-out";
32
+
23
33
  /** Re-export ResponseError so callers don't need a separate vscode-jsonrpc import. */
24
34
  const JsonRpcRequestError = ResponseError;
25
35
 
@@ -33,13 +43,19 @@ export class JsonRpcClient {
33
43
  private requestHandler: RequestHandler | null = null;
34
44
  private closed = false;
35
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;
36
50
 
37
51
  constructor(
38
52
  private readonly input: Readable,
39
53
  private readonly output: Writable,
40
- options?: { timeoutMs?: number },
54
+ options?: { timeoutMs?: number; server?: string; cwd?: string },
41
55
  ) {
42
56
  this.timeoutMs = options?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
57
+ this.server = options?.server;
58
+ this.cwd = options?.cwd;
43
59
 
44
60
  const reader = new StreamMessageReader(this.input);
45
61
  const writer = new StreamMessageWriter(this.output);
@@ -51,6 +67,13 @@ export class JsonRpcClient {
51
67
  this.notificationHandler?.(method, params);
52
68
  });
53
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
+
54
77
  // Register catch-all request handler for server-initiated requests
55
78
  this.connection.onRequest(async (method, params, _token) => {
56
79
  if (!this.requestHandler) {
@@ -81,17 +104,53 @@ export class JsonRpcClient {
81
104
  sendRequest(
82
105
  method: string,
83
106
  params?: unknown,
84
- options?: { timeoutMs?: number },
107
+ options?: { timeoutMs?: number } & CodeRequestControl,
85
108
  ): Promise<unknown> {
109
+ const timeoutMs = options?.timeoutMs ?? this.timeoutMs;
110
+ const signal = options?.signal;
111
+ const deadlineMs = options?.deadline === undefined ? undefined : options.deadline - Date.now();
112
+ const methodClass = classifyRequestMethod(method);
113
+ const boundedMethod = truncateIdentity(method);
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
+ }
86
131
  if (this.closed || !this.connection) {
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
+ );
87
142
  return Promise.reject(new Error("JSON-RPC client is closed"));
88
143
  }
89
-
90
- const timeoutMs = options?.timeoutMs ?? this.timeoutMs;
91
144
  const tokenSource = new CancellationTokenSource();
92
-
93
145
  let timeout: ReturnType<typeof setTimeout> | undefined;
94
- const timeoutError = new Error(`Request ${method} timed out after ${timeoutMs}ms`);
146
+ let timedOut = false;
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`);
95
154
 
96
155
  const request = this.connection.sendRequest(method, params, tokenSource.token);
97
156
  // Catch the raw request promise to prevent unhandled rejections when
@@ -102,17 +161,80 @@ export class JsonRpcClient {
102
161
  // the JSON-RPC token and rejects the caller. Using one timer avoids
103
162
  // a leak where the rejecting timer in a second Promise stays alive
104
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
+ });
105
176
  const promise = Promise.race([
106
177
  request,
107
178
  new Promise<never>((_resolve, reject) => {
108
179
  timeout = setTimeout(() => {
180
+ timedOut = true;
109
181
  tokenSource.cancel();
110
- reject(timeoutError);
111
- }, 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);
112
190
  }),
113
- ]).finally(() => {
114
- if (timeout !== undefined) clearTimeout(timeout);
115
- });
191
+ abort,
192
+ ])
193
+ .then(
194
+ (result) => {
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
+ );
205
+ return result;
206
+ },
207
+ (error: unknown) => {
208
+ const cancelled = timedOut || aborted || this.closed || isCancellationError(error);
209
+ const outcome: RequestOutcome = timedOut
210
+ ? "timed-out"
211
+ : cancelled
212
+ ? "cancelled"
213
+ : "failed";
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
+ );
231
+ throw error;
232
+ },
233
+ )
234
+ .finally(() => {
235
+ if (timeout !== undefined) clearTimeout(timeout);
236
+ if (abortHandler) signal?.removeEventListener("abort", abortHandler);
237
+ });
116
238
 
117
239
  // Prevent unhandled rejection when dispose() cancels requests
118
240
  promise.catch(() => {});
@@ -141,4 +263,109 @@ export class JsonRpcClient {
141
263
  this.connection = null;
142
264
  }
143
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
+ }
290
+ }
291
+
292
+ const SEMANTIC_REQUESTS = new Set([
293
+ "textDocument/definition",
294
+ "textDocument/documentSymbol",
295
+ "textDocument/hover",
296
+ "textDocument/implementation",
297
+ "textDocument/references",
298
+ "workspace/symbol",
299
+ ]);
300
+
301
+ /** Classify requests into bounded groups without retaining the raw method. */
302
+ function classifyRequestMethod(method: string): RequestMethodClass {
303
+ if (method === "initialize" || method === "shutdown") return "lifecycle";
304
+ if (method === "textDocument/diagnostic" || method === "workspace/diagnostic") {
305
+ return "diagnostic";
306
+ }
307
+ if (method === "textDocument/codeAction" || method === "textDocument/rename") {
308
+ return "refactor";
309
+ }
310
+ return SEMANTIC_REQUESTS.has(method) ? "semantic" : "other";
311
+ }
312
+
313
+ function isCancellationError(error: unknown): boolean {
314
+ if (!(error instanceof Error)) return false;
315
+ const code = (error as Error & { code?: unknown }).code;
316
+ return (
317
+ code === -32_800 ||
318
+ code === -32_802 ||
319
+ error.name === "CancellationError" ||
320
+ /\bcancell?ed\b/i.test(error.message)
321
+ );
322
+ }
323
+
324
+ /** One bounded request-timing observation with exact method identity. */
325
+ interface RequestTimingObservation {
326
+ readonly method: string;
327
+ readonly methodClass: RequestMethodClass;
328
+ readonly outcome: RequestOutcome;
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;
345
+ }
346
+
347
+ function recordRequestTiming(
348
+ timer: ReturnType<typeof startDebugTimer>,
349
+ operationId: string | undefined,
350
+ observation: RequestTimingObservation,
351
+ identity: { server?: string; cwd?: string },
352
+ ): void {
353
+ timer.finish(
354
+ () => ({
355
+ operationId,
356
+ source: "lsp",
357
+ level: "debug",
358
+ category: "request.timing",
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
+ },
368
+ }),
369
+ "request",
370
+ );
144
371
  }
@@ -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