@mrclrchtr/supi-code-intelligence 4.9.0 → 4.10.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 (28) hide show
  1. package/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
  2. package/node_modules/@mrclrchtr/supi-core/package.json +1 -1
  3. package/node_modules/@mrclrchtr/supi-core/src/debug-timing.ts +34 -21
  4. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
  5. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/package.json +1 -1
  6. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/debug-timing.ts +34 -21
  7. package/node_modules/@mrclrchtr/supi-lsp/package.json +3 -3
  8. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostic-timing.ts +155 -0
  9. package/node_modules/@mrclrchtr/supi-lsp/src/client/client-diagnostics.ts +70 -34
  10. package/node_modules/@mrclrchtr/supi-lsp/src/client/transport.ts +104 -5
  11. package/node_modules/@mrclrchtr/supi-tree-sitter/README.md +10 -0
  12. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
  13. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/package.json +1 -1
  14. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/debug-timing.ts +34 -21
  15. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/debug/web-tree-sitter.wasm +0 -0
  16. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/debug/web-tree-sitter.wasm.map +4 -4
  17. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/package.json +1 -1
  18. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/web-tree-sitter.wasm +0 -0
  19. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/web-tree-sitter.wasm.map +4 -4
  20. package/node_modules/@mrclrchtr/supi-tree-sitter/package.json +4 -3
  21. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/kotlin/tree-sitter-kotlin.wasm.json +1 -1
  22. package/node_modules/@mrclrchtr/supi-tree-sitter/resources/grammars/sql/tree-sitter-sql.wasm.json +1 -1
  23. package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/runtime.ts +106 -2
  24. package/package.json +5 -5
  25. package/src/analysis/refactor/apply.ts +14 -19
  26. package/src/analysis/refactor/position.ts +47 -0
  27. package/src/analysis/refactor/safety.ts +18 -10
  28. package/src/analysis/search/ast-scan-timing.ts +0 -2
@@ -3,6 +3,7 @@
3
3
  // and notification/request dispatching through vscode-jsonrpc's MessageConnection.
4
4
 
5
5
  import type { Readable, Writable } from "node:stream";
6
+ import { startDebugTimer } from "@mrclrchtr/supi-core/debug";
6
7
  import {
7
8
  CancellationTokenSource,
8
9
  createMessageConnection,
@@ -20,6 +21,9 @@ const DEFAULT_TIMEOUT_MS = 30_000;
20
21
  export type NotificationHandler = (method: string, params: unknown) => void;
21
22
  export type RequestHandler = (method: string, params: unknown) => Promise<unknown> | unknown;
22
23
 
24
+ type RequestMethodClass = "diagnostic" | "lifecycle" | "other" | "refactor" | "semantic";
25
+ type RequestOutcome = "cancelled" | "completed" | "failed" | "timed-out";
26
+
23
27
  /** Re-export ResponseError so callers don't need a separate vscode-jsonrpc import. */
24
28
  const JsonRpcRequestError = ResponseError;
25
29
 
@@ -83,14 +87,23 @@ export class JsonRpcClient {
83
87
  params?: unknown,
84
88
  options?: { timeoutMs?: number },
85
89
  ): Promise<unknown> {
90
+ const timeoutMs = options?.timeoutMs ?? this.timeoutMs;
91
+ const methodClass = classifyRequestMethod(method);
92
+ const timer = startDebugTimer();
86
93
  if (this.closed || !this.connection) {
94
+ recordRequestTiming(timer, {
95
+ methodClass,
96
+ outcome: "cancelled",
97
+ timeoutMs,
98
+ timedOut: false,
99
+ cancelled: true,
100
+ });
87
101
  return Promise.reject(new Error("JSON-RPC client is closed"));
88
102
  }
89
103
 
90
- const timeoutMs = options?.timeoutMs ?? this.timeoutMs;
91
104
  const tokenSource = new CancellationTokenSource();
92
-
93
105
  let timeout: ReturnType<typeof setTimeout> | undefined;
106
+ let timedOut = false;
94
107
  const timeoutError = new Error(`Request ${method} timed out after ${timeoutMs}ms`);
95
108
 
96
109
  const request = this.connection.sendRequest(method, params, tokenSource.token);
@@ -106,13 +119,43 @@ export class JsonRpcClient {
106
119
  request,
107
120
  new Promise<never>((_resolve, reject) => {
108
121
  timeout = setTimeout(() => {
122
+ timedOut = true;
109
123
  tokenSource.cancel();
110
124
  reject(timeoutError);
111
125
  }, timeoutMs);
112
126
  }),
113
- ]).finally(() => {
114
- if (timeout !== undefined) clearTimeout(timeout);
115
- });
127
+ ])
128
+ .then(
129
+ (result) => {
130
+ recordRequestTiming(timer, {
131
+ methodClass,
132
+ outcome: "completed",
133
+ timeoutMs,
134
+ timedOut: false,
135
+ cancelled: false,
136
+ });
137
+ return result;
138
+ },
139
+ (error: unknown) => {
140
+ const cancelled = timedOut || this.closed || isCancellationError(error);
141
+ const outcome: RequestOutcome = timedOut
142
+ ? "timed-out"
143
+ : cancelled
144
+ ? "cancelled"
145
+ : "failed";
146
+ recordRequestTiming(timer, {
147
+ methodClass,
148
+ outcome,
149
+ timeoutMs,
150
+ timedOut,
151
+ cancelled,
152
+ });
153
+ throw error;
154
+ },
155
+ )
156
+ .finally(() => {
157
+ if (timeout !== undefined) clearTimeout(timeout);
158
+ });
116
159
 
117
160
  // Prevent unhandled rejection when dispose() cancels requests
118
161
  promise.catch(() => {});
@@ -142,3 +185,59 @@ export class JsonRpcClient {
142
185
  }
143
186
  }
144
187
  }
188
+
189
+ const SEMANTIC_REQUESTS = new Set([
190
+ "textDocument/definition",
191
+ "textDocument/documentSymbol",
192
+ "textDocument/hover",
193
+ "textDocument/implementation",
194
+ "textDocument/references",
195
+ "workspace/symbol",
196
+ ]);
197
+
198
+ /** Classify requests into bounded groups without retaining the raw method. */
199
+ function classifyRequestMethod(method: string): RequestMethodClass {
200
+ if (method === "initialize" || method === "shutdown") return "lifecycle";
201
+ if (method === "textDocument/diagnostic" || method === "workspace/diagnostic") {
202
+ return "diagnostic";
203
+ }
204
+ if (method === "textDocument/codeAction" || method === "textDocument/rename") {
205
+ return "refactor";
206
+ }
207
+ return SEMANTIC_REQUESTS.has(method) ? "semantic" : "other";
208
+ }
209
+
210
+ function isCancellationError(error: unknown): boolean {
211
+ if (!(error instanceof Error)) return false;
212
+ const code = (error as Error & { code?: unknown }).code;
213
+ return (
214
+ code === -32_800 ||
215
+ code === -32_802 ||
216
+ error.name === "CancellationError" ||
217
+ /\bcancell?ed\b/i.test(error.message)
218
+ );
219
+ }
220
+
221
+ interface RequestTimingObservation {
222
+ readonly methodClass: RequestMethodClass;
223
+ readonly outcome: RequestOutcome;
224
+ readonly timeoutMs: number;
225
+ readonly timedOut: boolean;
226
+ readonly cancelled: boolean;
227
+ }
228
+
229
+ function recordRequestTiming(
230
+ timer: ReturnType<typeof startDebugTimer>,
231
+ observation: RequestTimingObservation,
232
+ ): void {
233
+ timer.finish(
234
+ () => ({
235
+ source: "lsp",
236
+ level: "debug",
237
+ category: "request.timing",
238
+ message: `LSP ${observation.methodClass} request ${observation.outcome}`,
239
+ data: { ...observation },
240
+ }),
241
+ "request",
242
+ );
243
+ }
@@ -96,6 +96,16 @@ if (state.kind === "ready") {
96
96
  }
97
97
  ```
98
98
 
99
+ ## Structural performance baseline
100
+
101
+ Run the stable outline fixture on the same machine and dependency versions:
102
+
103
+ ```bash
104
+ pnpm --filter @mrclrchtr/supi-tree-sitter bench:structural
105
+ ```
106
+
107
+ The benchmark reports a cold session parser baseline and a repeated parser baseline. Debug capture is active, so the baseline includes the internal timing observation cost. The benchmark records measurements but does not set a pass or fail threshold.
108
+
99
109
  ## Source
100
110
 
101
111
  - `src/api.ts` — public library entrypoint
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrclrchtr/supi-code-runtime",
3
- "version": "4.9.0",
3
+ "version": "4.10.0",
4
4
  "description": "Shared workspace context and capability contracts for code intelligence",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrclrchtr/supi-core",
3
- "version": "4.9.0",
3
+ "version": "4.10.0",
4
4
  "description": "Shared settings, configuration, reporting, and session infrastructure",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -46,13 +46,21 @@ export interface DebugTimer {
46
46
  * names are accumulated. Event data reserves the `timing` field. When Debug is
47
47
  * disabled at start, this returns a no-op timer and does not read the clock.
48
48
  * Pass a factory to `finish()` to avoid event-data construction when disabled.
49
+ * Clock, event-construction, and registry failures are isolated from the
50
+ * measured operation and make the timer a no-op.
49
51
  */
50
52
  export function startDebugTimer(options: DebugTimerOptions = {}): DebugTimer {
51
53
  if (!isDebugRegistryEnabled()) return DISABLED_DEBUG_TIMER;
52
54
  const now = options.now ?? performance.now.bind(performance);
53
- const startedAt = now();
55
+ let startedAt: number;
56
+ try {
57
+ startedAt = now();
58
+ } catch {
59
+ return DISABLED_DEBUG_TIMER;
60
+ }
54
61
  let previousAt = startedAt;
55
62
  let finished = false;
63
+ let failed = false;
56
64
  const phases = new Map<string, number>();
57
65
 
58
66
  const markAt = (phase: string, current: number): void => {
@@ -65,30 +73,35 @@ export function startDebugTimer(options: DebugTimerOptions = {}): DebugTimer {
65
73
  return {
66
74
  enabled: true,
67
75
  mark(phase) {
68
- if (finished) return;
69
- markAt(phase, now());
76
+ if (finished || failed) return;
77
+ try {
78
+ markAt(phase, now());
79
+ } catch {
80
+ failed = true;
81
+ }
70
82
  },
71
83
  finish(input, finalPhase) {
72
- if (finished) return null;
73
- if (!isDebugRegistryEnabled()) {
74
- finished = true;
84
+ if (finished || failed) return null;
85
+ finished = true;
86
+ try {
87
+ if (!isDebugRegistryEnabled()) return null;
88
+ const completedAt = now();
89
+ if (finalPhase) markAt(finalPhase, completedAt);
90
+ const phasesMs = Object.fromEntries(
91
+ [...phases.entries()].map(([name, value]) => [name, duration(value)]),
92
+ );
93
+ const timing: DebugTiming = {
94
+ durationMs: duration(completedAt - startedAt),
95
+ phasesMs,
96
+ };
97
+ const eventInput = typeof input === "function" ? input() : input;
98
+ return recordDebugEvent({
99
+ ...eventInput,
100
+ data: { ...eventInput.data, timing },
101
+ });
102
+ } catch {
75
103
  return null;
76
104
  }
77
- const completedAt = now();
78
- if (finalPhase) markAt(finalPhase, completedAt);
79
- finished = true;
80
- const phasesMs = Object.fromEntries(
81
- [...phases.entries()].map(([name, value]) => [name, duration(value)]),
82
- );
83
- const timing: DebugTiming = {
84
- durationMs: duration(completedAt - startedAt),
85
- phasesMs,
86
- };
87
- const eventInput = typeof input === "function" ? input() : input;
88
- return recordDebugEvent({
89
- ...eventInput,
90
- data: { ...eventInput.data, timing },
91
- });
92
105
  },
93
106
  };
94
107
  }