@mrclrchtr/supi-code-intelligence 1.5.0 → 1.7.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 (80) hide show
  1. package/README.md +42 -24
  2. package/node_modules/@mrclrchtr/supi-core/package.json +6 -2
  3. package/node_modules/@mrclrchtr/supi-core/src/api.ts +12 -1
  4. package/node_modules/@mrclrchtr/supi-core/src/config/config.ts +1 -1
  5. package/node_modules/@mrclrchtr/supi-core/src/index.ts +12 -1
  6. package/node_modules/@mrclrchtr/supi-core/src/tool-framework.ts +116 -0
  7. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/package.json +6 -2
  8. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/api.ts +12 -1
  9. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/config/config.ts +1 -1
  10. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/index.ts +12 -1
  11. package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/tool-framework.ts +116 -0
  12. package/node_modules/@mrclrchtr/supi-lsp/package.json +10 -3
  13. package/node_modules/@mrclrchtr/supi-lsp/src/client/client.ts +8 -5
  14. package/node_modules/@mrclrchtr/supi-lsp/src/client/transport.ts +79 -190
  15. package/node_modules/@mrclrchtr/supi-lsp/src/config/server-config.ts +38 -0
  16. package/node_modules/@mrclrchtr/supi-lsp/src/config/types.ts +61 -387
  17. package/node_modules/@mrclrchtr/supi-lsp/src/format.ts +16 -8
  18. package/node_modules/@mrclrchtr/supi-lsp/src/lsp.ts +2 -2
  19. package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-project-info.ts +1 -1
  20. package/node_modules/@mrclrchtr/supi-lsp/src/pattern-matcher.ts +11 -184
  21. package/node_modules/@mrclrchtr/supi-lsp/src/session/lsp-state.ts +1 -1
  22. package/node_modules/@mrclrchtr/supi-lsp/src/tool/guidance.ts +1 -1
  23. package/node_modules/@mrclrchtr/supi-lsp/src/tool/tool-specs.ts +1 -1
  24. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/package.json +6 -2
  25. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/api.ts +12 -1
  26. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/config/config.ts +1 -1
  27. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/index.ts +12 -1
  28. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/tool-framework.ts +116 -0
  29. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/LICENSE +21 -0
  30. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/README.md +265 -0
  31. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/debug/web-tree-sitter.cjs +4661 -0
  32. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/debug/web-tree-sitter.cjs.map +7 -0
  33. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/debug/web-tree-sitter.js +4605 -0
  34. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/debug/web-tree-sitter.js.map +7 -0
  35. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/debug/web-tree-sitter.wasm +0 -0
  36. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/debug/web-tree-sitter.wasm.map +57 -0
  37. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/package.json +100 -0
  38. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/web-tree-sitter.cjs +4063 -0
  39. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/web-tree-sitter.cjs.map +7 -0
  40. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/web-tree-sitter.d.cts +1025 -0
  41. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/web-tree-sitter.d.cts.map +58 -0
  42. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/web-tree-sitter.d.ts +1025 -0
  43. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/web-tree-sitter.d.ts.map +58 -0
  44. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/web-tree-sitter.js +4007 -0
  45. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/web-tree-sitter.js.map +7 -0
  46. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/web-tree-sitter.wasm +0 -0
  47. package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/web-tree-sitter.wasm.map +55 -0
  48. package/node_modules/@mrclrchtr/supi-tree-sitter/package.json +2 -2
  49. package/package.json +4 -4
  50. package/src/actions/affected-action.ts +67 -54
  51. package/src/actions/brief-action.ts +142 -5
  52. package/src/actions/callees-action.ts +1 -1
  53. package/src/actions/callers-action.ts +38 -67
  54. package/src/actions/implementations-action.ts +27 -63
  55. package/src/actions/map-action.ts +206 -0
  56. package/src/actions/pattern-action.ts +1 -1
  57. package/src/api.ts +1 -0
  58. package/src/brief-focused.ts +5 -5
  59. package/src/brief.ts +3 -3
  60. package/src/code-intelligence.ts +6 -75
  61. package/src/index.ts +1 -0
  62. package/src/pattern-structured.ts +1 -1
  63. package/src/prioritization-signals.ts +13 -26
  64. package/src/query-params.ts +15 -0
  65. package/src/resolve-target.ts +2 -2
  66. package/src/search-helpers.ts +2 -2
  67. package/src/target-resolution.ts +27 -102
  68. package/src/tool/execute-affected.ts +25 -0
  69. package/src/tool/execute-brief.ts +25 -0
  70. package/src/tool/execute-map.ts +32 -0
  71. package/src/tool/execute-pattern.ts +26 -0
  72. package/src/tool/execute-relations.ts +48 -0
  73. package/src/tool/guidance.ts +24 -13
  74. package/src/tool/register-tools.ts +32 -0
  75. package/src/tool/tool-specs.ts +184 -0
  76. package/src/tool/validation.ts +43 -0
  77. package/src/types.ts +10 -0
  78. package/src/actions/index-action.ts +0 -187
  79. package/src/tool/action-specs.ts +0 -66
  80. package/src/tool-actions.ts +0 -100
@@ -1,16 +1,18 @@
1
- // JSON-RPC 2.0 transport over stdio with Content-Length header framing.
1
+ // JSON-RPC 2.0 transport thin wrapper around vscode-jsonrpc.
2
+ // Handles Content-Length framing, request/response correlation, timeouts,
3
+ // and notification/request dispatching through vscode-jsonrpc's MessageConnection.
2
4
 
3
5
  import type { Readable, Writable } from "node:stream";
4
- import type {
5
- JsonRpcId,
6
- JsonRpcMessage,
7
- JsonRpcNotification,
8
- JsonRpcRequest,
9
- JsonRpcResponse,
10
- } from "../config/types.ts";
11
-
12
- const CONTENT_LENGTH = "Content-Length: ";
13
- const HEADER_DELIMITER = "\r\n\r\n";
6
+ import {
7
+ CancellationTokenSource,
8
+ createMessageConnection,
9
+ type MessageConnection,
10
+ NullLogger,
11
+ ResponseError,
12
+ StreamMessageReader,
13
+ StreamMessageWriter,
14
+ } from "vscode-jsonrpc/node";
15
+
14
16
  const DEFAULT_TIMEOUT_MS = 30_000;
15
17
 
16
18
  // ── Types ─────────────────────────────────────────────────────────────
@@ -18,29 +20,15 @@ const DEFAULT_TIMEOUT_MS = 30_000;
18
20
  export type NotificationHandler = (method: string, params: unknown) => void;
19
21
  export type RequestHandler = (method: string, params: unknown) => Promise<unknown> | unknown;
20
22
 
21
- interface PendingRequest {
22
- resolve: (result: unknown) => void;
23
- reject: (error: Error) => void;
24
- timer: ReturnType<typeof setTimeout>;
25
- }
23
+ /** Re-export ResponseError so callers don't need a separate vscode-jsonrpc import. */
24
+ const JsonRpcRequestError = ResponseError;
26
25
 
27
- export class JsonRpcRequestError extends Error {
28
- constructor(
29
- readonly code: number,
30
- message: string,
31
- readonly data?: unknown,
32
- ) {
33
- super(message);
34
- this.name = "JsonRpcRequestError";
35
- }
36
- }
26
+ export { JsonRpcRequestError };
37
27
 
38
28
  // ── JsonRpcClient ─────────────────────────────────────────────────────
39
29
 
40
30
  export class JsonRpcClient {
41
- private nextId = 1;
42
- private buffer = Buffer.alloc(0);
43
- private pending = new Map<JsonRpcId, PendingRequest>();
31
+ private connection: MessageConnection | null = null;
44
32
  private notificationHandler: NotificationHandler | null = null;
45
33
  private requestHandler: RequestHandler | null = null;
46
34
  private closed = false;
@@ -52,9 +40,31 @@ export class JsonRpcClient {
52
40
  options?: { timeoutMs?: number },
53
41
  ) {
54
42
  this.timeoutMs = options?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
55
- this.input.on("data", (chunk: Buffer) => this.onData(chunk));
56
- this.input.on("end", () => this.onClose());
57
- this.input.on("error", () => this.onClose());
43
+
44
+ const reader = new StreamMessageReader(this.input);
45
+ const writer = new StreamMessageWriter(this.output);
46
+
47
+ this.connection = createMessageConnection(reader, writer, NullLogger);
48
+
49
+ // Register catch-all notification handler
50
+ this.connection.onNotification((method, params) => {
51
+ this.notificationHandler?.(method, params);
52
+ });
53
+
54
+ // Register catch-all request handler for server-initiated requests
55
+ this.connection.onRequest(async (method, params, _token) => {
56
+ if (!this.requestHandler) {
57
+ throw new JsonRpcRequestError(-32601, `Method not found: ${method}`);
58
+ }
59
+ return this.requestHandler(method, params);
60
+ });
61
+
62
+ // Handle connection close
63
+ this.connection.onClose(() => {
64
+ this.closed = true;
65
+ });
66
+
67
+ this.connection.listen();
58
68
  }
59
69
 
60
70
  /** Register a handler for server notifications (no id). */
@@ -73,176 +83,55 @@ export class JsonRpcClient {
73
83
  params?: unknown,
74
84
  options?: { timeoutMs?: number },
75
85
  ): Promise<unknown> {
76
- if (this.closed) {
86
+ if (this.closed || !this.connection) {
77
87
  return Promise.reject(new Error("JSON-RPC client is closed"));
78
88
  }
79
89
 
80
- const id = this.nextId++;
81
90
  const timeoutMs = options?.timeoutMs ?? this.timeoutMs;
82
- const promise = new Promise<unknown>((resolve, reject) => {
83
- const timer = setTimeout(() => {
84
- this.pending.delete(id);
85
- reject(new Error(`Request ${method} (id=${id}) timed out after ${timeoutMs}ms`));
86
- }, timeoutMs);
87
-
88
- this.pending.set(id, { resolve, reject, timer });
89
- });
90
-
91
- const msg: JsonRpcRequest = { jsonrpc: "2.0", id, method, params };
92
- this.writeMessage(msg);
93
-
94
- // Prevent unhandled rejection when dispose() rejects orphaned promises
91
+ const tokenSource = new CancellationTokenSource();
92
+
93
+ const timer = setTimeout(() => tokenSource.cancel(), timeoutMs);
94
+
95
+ const request = this.connection.sendRequest(method, params, tokenSource.token);
96
+
97
+ // Race the request against a timeout so callers don't hang forever.
98
+ // The CancellationToken is also passed to sendRequest so the connection
99
+ // can short-circuit writes and cleanup when the token fires.
100
+ const promise = Promise.race([
101
+ request,
102
+ new Promise<never>((_, reject) =>
103
+ setTimeout(
104
+ () => reject(new Error(`Request ${method} timed out after ${timeoutMs}ms`)),
105
+ timeoutMs,
106
+ ),
107
+ ),
108
+ ]).finally(() => clearTimeout(timer));
109
+
110
+ // Prevent unhandled rejection when dispose() cancels requests
95
111
  promise.catch(() => {});
96
112
  return promise;
97
113
  }
98
114
 
99
- /** Send a notification (no response expected). */
100
- sendNotification(method: string, params?: unknown): void {
101
- if (this.closed) return;
102
- const msg: JsonRpcNotification = { jsonrpc: "2.0", method, params };
103
- this.writeMessage(msg);
115
+ /**
116
+ * Send a notification (no response expected).
117
+ *
118
+ * Returns the underlying write promise so ordering-sensitive cleanup paths
119
+ * can await the final flush. A no-op catch is still attached to prevent
120
+ * unhandled rejections when callers intentionally fire-and-forget.
121
+ */
122
+ sendNotification(method: string, params?: unknown): Promise<void> {
123
+ if (this.closed || !this.connection) return Promise.resolve();
124
+ const promise = this.connection.sendNotification(method, params);
125
+ promise.catch(() => {});
126
+ return promise;
104
127
  }
105
128
 
106
- /** Clean up all pending requests. */
129
+ /** Clean up the connection. */
107
130
  dispose(): void {
108
131
  this.closed = true;
109
- for (const [id, p] of this.pending) {
110
- clearTimeout(p.timer);
111
- p.reject(new Error("JSON-RPC client disposed"));
112
- this.pending.delete(id);
113
- }
114
- }
115
-
116
- // ── Private ───────────────────────────────────────────────────────
117
-
118
- private writeMessage(msg: JsonRpcMessage): void {
119
- const body = JSON.stringify(msg);
120
- const contentLength = Buffer.byteLength(body, "utf-8");
121
- const header = `${CONTENT_LENGTH}${contentLength}${HEADER_DELIMITER}`;
122
- this.output.write(header + body);
123
- }
124
-
125
- private onData(chunk: Buffer): void {
126
- this.buffer = Buffer.concat([this.buffer, chunk]);
127
- this.processBuffer();
128
- }
129
-
130
- private processBuffer(): void {
131
- while (true) {
132
- // Look for header delimiter
133
- const headerEnd = this.buffer.indexOf(HEADER_DELIMITER);
134
- if (headerEnd === -1) return;
135
-
136
- // Parse Content-Length from headers
137
- const headerText = this.buffer.subarray(0, headerEnd).toString("utf-8");
138
- const contentLength = parseContentLength(headerText);
139
- if (contentLength === null) {
140
- // Malformed header — skip past delimiter and try again
141
- this.buffer = this.buffer.subarray(headerEnd + HEADER_DELIMITER.length);
142
- continue;
143
- }
144
-
145
- // Check if we have the full body
146
- const bodyStart = headerEnd + HEADER_DELIMITER.length;
147
- const messageEnd = bodyStart + contentLength;
148
- if (this.buffer.length < messageEnd) {
149
- return; // Need more data — partial message
150
- }
151
-
152
- // Extract and parse the body
153
- const body = this.buffer.subarray(bodyStart, messageEnd).toString("utf-8");
154
- this.buffer = this.buffer.subarray(messageEnd);
155
-
156
- try {
157
- const msg = JSON.parse(body) as JsonRpcMessage;
158
- this.handleMessage(msg);
159
- } catch {
160
- // Malformed JSON — skip
161
- }
162
- }
163
- }
164
-
165
- private handleMessage(msg: JsonRpcMessage): void {
166
- // Response (has id, has result or error)
167
- if ("id" in msg && msg.id != null && ("result" in msg || "error" in msg)) {
168
- const response = msg as JsonRpcResponse;
169
- const id = response.id;
170
- if (id === null) return;
171
- const pending = this.pending.get(id);
172
- if (pending) {
173
- this.pending.delete(id);
174
- clearTimeout(pending.timer);
175
- if (response.error) {
176
- pending.reject(new Error(`LSP error ${response.error.code}: ${response.error.message}`));
177
- } else {
178
- pending.resolve(response.result);
179
- }
180
- }
181
- return;
182
- }
183
-
184
- // Notification (no id)
185
- if ("method" in msg && !("id" in msg)) {
186
- const notification = msg as JsonRpcNotification;
187
- this.notificationHandler?.(notification.method, notification.params);
188
- return;
189
- }
190
-
191
- // Request from server (has id + method)
192
- if ("method" in msg && "id" in msg && msg.id != null) {
193
- void this.handleInboundRequest(msg as JsonRpcRequest);
194
- }
195
- }
196
-
197
- private async handleInboundRequest(request: JsonRpcRequest): Promise<void> {
198
- if (this.closed) return;
199
-
200
- try {
201
- if (!this.requestHandler) {
202
- throw new JsonRpcRequestError(-32601, `Method not found: ${request.method}`);
203
- }
204
-
205
- const result = await this.requestHandler(request.method, request.params);
206
- this.writeMessage({
207
- jsonrpc: "2.0",
208
- id: request.id,
209
- result: result ?? null,
210
- } satisfies JsonRpcResponse);
211
- } catch (error) {
212
- const failure =
213
- error instanceof JsonRpcRequestError
214
- ? error
215
- : new JsonRpcRequestError(-32603, error instanceof Error ? error.message : String(error));
216
- this.writeMessage({
217
- jsonrpc: "2.0",
218
- id: request.id,
219
- error: {
220
- code: failure.code,
221
- message: failure.message,
222
- ...(failure.data !== undefined ? { data: failure.data } : {}),
223
- },
224
- } satisfies JsonRpcResponse);
225
- }
226
- }
227
-
228
- private onClose(): void {
229
- this.closed = true;
230
- for (const [id, p] of this.pending) {
231
- clearTimeout(p.timer);
232
- p.reject(new Error("JSON-RPC connection closed"));
233
- this.pending.delete(id);
234
- }
235
- }
236
- }
237
-
238
- // ── Helpers ───────────────────────────────────────────────────────────
239
-
240
- function parseContentLength(header: string): number | null {
241
- for (const line of header.split("\r\n")) {
242
- if (line.startsWith(CONTENT_LENGTH)) {
243
- const value = parseInt(line.slice(CONTENT_LENGTH.length), 10);
244
- if (Number.isFinite(value) && value >= 0) return value;
132
+ if (this.connection) {
133
+ this.connection.dispose();
134
+ this.connection = null;
245
135
  }
246
136
  }
247
- return null;
248
137
  }
@@ -0,0 +1,38 @@
1
+ // SuPi-specific server configuration types — not part of the LSP specification.
2
+ // These are our own types for server discovery, configuration, and status tracking.
3
+
4
+ export interface ServerConfig {
5
+ command: string;
6
+ args?: string[];
7
+ fileTypes: string[];
8
+ rootMarkers: string[];
9
+ enabled?: boolean;
10
+ initializationOptions?: unknown;
11
+ }
12
+
13
+ /** LSP configuration keyed by language name (e.g. `typescript`, `python`). */
14
+ export interface LspConfig {
15
+ servers: Record<string, ServerConfig>;
16
+ }
17
+
18
+ export interface DetectedProjectServer {
19
+ name: string;
20
+ root: string;
21
+ fileTypes: string[];
22
+ }
23
+
24
+ export interface ProjectServerInfo extends DetectedProjectServer {
25
+ status: "running" | "error" | "unavailable";
26
+ supportedActions: string[];
27
+ openFiles: string[];
28
+ }
29
+
30
+ /** A language whose source files are present but the server binary is missing. */
31
+ export interface MissingServer {
32
+ /** Language name (e.g. "python", "rust"). */
33
+ name: string;
34
+ /** Server command that was not found on PATH. */
35
+ command: string;
36
+ /** File extensions found in the project (subset of server.fileTypes). */
37
+ foundExtensions: string[];
38
+ }