@mrclrchtr/supi-lsp 1.16.0 → 2.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 (77) hide show
  1. package/README.md +44 -56
  2. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/package.json +6 -1
  3. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/abort-utils.ts +31 -0
  4. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/api.ts +8 -0
  5. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/config/config-settings.ts +10 -4
  6. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/config.ts +1 -0
  7. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/footer-registry.ts +1 -1
  8. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/index.ts +6 -0
  9. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/model-selection.ts +134 -0
  10. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings/settings-registry.ts +9 -2
  11. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings/settings-ui.ts +109 -4
  12. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings-ui.ts +5 -1
  13. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/spinner-frames.ts +11 -0
  14. package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/status-spinner.ts +68 -0
  15. package/node_modules/@mrclrchtr/supi-code-runtime/package.json +3 -2
  16. package/node_modules/@mrclrchtr/supi-code-runtime/src/api.ts +7 -0
  17. package/node_modules/@mrclrchtr/supi-code-runtime/src/capability/types.ts +56 -2
  18. package/node_modules/@mrclrchtr/supi-code-runtime/src/evidence-badge.ts +40 -0
  19. package/node_modules/@mrclrchtr/supi-code-runtime/src/index.ts +5 -0
  20. package/node_modules/@mrclrchtr/supi-code-runtime/src/types.ts +76 -3
  21. package/node_modules/@mrclrchtr/supi-code-runtime/src/workspace/runtime.ts +37 -2
  22. package/node_modules/@mrclrchtr/supi-core/package.json +6 -1
  23. package/node_modules/@mrclrchtr/supi-core/src/abort-utils.ts +31 -0
  24. package/node_modules/@mrclrchtr/supi-core/src/api.ts +8 -0
  25. package/node_modules/@mrclrchtr/supi-core/src/config/config-settings.ts +10 -4
  26. package/node_modules/@mrclrchtr/supi-core/src/config.ts +1 -0
  27. package/node_modules/@mrclrchtr/supi-core/src/footer-registry.ts +1 -1
  28. package/node_modules/@mrclrchtr/supi-core/src/index.ts +6 -0
  29. package/node_modules/@mrclrchtr/supi-core/src/model-selection.ts +134 -0
  30. package/node_modules/@mrclrchtr/supi-core/src/settings/settings-registry.ts +9 -2
  31. package/node_modules/@mrclrchtr/supi-core/src/settings/settings-ui.ts +109 -4
  32. package/node_modules/@mrclrchtr/supi-core/src/settings-ui.ts +5 -1
  33. package/node_modules/@mrclrchtr/supi-core/src/spinner-frames.ts +11 -0
  34. package/node_modules/@mrclrchtr/supi-core/src/status-spinner.ts +68 -0
  35. package/node_modules/vscode-languageserver-protocol/lib/common/protocol.diagnostic.d.ts +21 -2
  36. package/node_modules/vscode-languageserver-protocol/lib/common/protocol.diagnostic.js +1 -0
  37. package/node_modules/vscode-languageserver-protocol/lib/common/protocol.inlineValue.d.ts +2 -1
  38. package/node_modules/vscode-languageserver-protocol/package.json +1 -1
  39. package/package.json +4 -31
  40. package/src/api.ts +27 -0
  41. package/src/client/client-refresh.ts +5 -0
  42. package/src/client/client.ts +246 -1
  43. package/src/client/transport.ts +4 -0
  44. package/src/config/capabilities.ts +3 -0
  45. package/src/config/config.ts +4 -2
  46. package/src/config/lsp-settings.ts +102 -0
  47. package/src/config/server-actions.ts +59 -0
  48. package/src/config/server-config.ts +4 -0
  49. package/src/index.ts +11 -0
  50. package/src/manager/capability-index.ts +1 -1
  51. package/src/manager/manager-project-info.ts +2 -1
  52. package/src/manager/manager.ts +115 -26
  53. package/src/provider/lsp-semantic-provider.ts +177 -134
  54. package/src/provider/refactor-planning.ts +214 -0
  55. package/src/session/runtime-controller.ts +320 -0
  56. package/src/session/runtime-registration.ts +26 -5
  57. package/src/session/service-registry.ts +81 -4
  58. package/src/summary.ts +2 -0
  59. package/src/extension.ts +0 -1
  60. package/src/format.ts +0 -373
  61. package/src/handlers/diagnostic-injection.ts +0 -182
  62. package/src/handlers/session-lifecycle.ts +0 -149
  63. package/src/handlers/status-command.ts +0 -38
  64. package/src/handlers/workspace-recovery.ts +0 -92
  65. package/src/lsp.ts +0 -37
  66. package/src/session/lsp-state.ts +0 -93
  67. package/src/session/settings-registration.ts +0 -296
  68. package/src/session/tree-persist.ts +0 -75
  69. package/src/tool/guidance.ts +0 -69
  70. package/src/tool/names.ts +0 -27
  71. package/src/tool/overrides.ts +0 -204
  72. package/src/tool/register-tools.ts +0 -69
  73. package/src/tool/service-actions.ts +0 -399
  74. package/src/tool/tool-specs.ts +0 -248
  75. package/src/ui/renderer.ts +0 -120
  76. package/src/ui/ui.ts +0 -360
  77. package/src/workspace-change.ts +0 -40
@@ -1,69 +0,0 @@
1
- import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
2
- import { getSessionLspService } from "../session/service-registry.ts";
3
- import type { LspToolPromptSurfaceMap } from "./guidance.ts";
4
- import { LSP_TOOL_DEFINITION_SPECS } from "./tool-specs.ts";
5
-
6
- /** Register the expert LSP toolset. Tools are re-registered on session_start to refresh guidance. */
7
- export function registerLspTools(pi: ExtensionAPI, promptSurfaces: LspToolPromptSurfaceMap): void {
8
- for (const spec of LSP_TOOL_DEFINITION_SPECS) {
9
- const surface = promptSurfaces[spec.name];
10
- pi.registerTool({
11
- name: spec.name,
12
- label: spec.label,
13
- description: surface.description,
14
- promptSnippet: surface.promptSnippet,
15
- promptGuidelines: surface.promptGuidelines,
16
- parameters: spec.parameters,
17
- execute: createToolExecutor(spec.run),
18
- });
19
- }
20
- }
21
-
22
- function getReadyService(cwd: string) {
23
- const state = getSessionLspService(cwd);
24
- return state.kind === "ready" ? state.service : null;
25
- }
26
-
27
- function describeUnavailableService(cwd: string): string {
28
- const state = getSessionLspService(cwd);
29
- switch (state.kind) {
30
- case "pending":
31
- return "LSP is still starting for this workspace. Retry in a moment.";
32
- case "inactive":
33
- return `LSP is inactive on the current session branch for ${cwd}.`;
34
- case "disabled":
35
- return `LSP is disabled for ${cwd}.`;
36
- case "unavailable":
37
- return state.reason;
38
- default:
39
- return "LSP not initialized. Start a new session first.";
40
- }
41
- }
42
-
43
- function createToolExecutor(
44
- run: (
45
- service: NonNullable<ReturnType<typeof getReadyService>>,
46
- cwd: string,
47
- params: unknown,
48
- ) => Promise<string>,
49
- ) {
50
- return async (
51
- _toolCallId: string,
52
- params: unknown,
53
- ...rest: [AbortSignal | undefined, unknown, ExtensionContext]
54
- ) => {
55
- const ctx = rest[2];
56
- const service = getReadyService(ctx.cwd);
57
- const text = service
58
- ? await run(service, ctx.cwd, params)
59
- : describeUnavailableService(ctx.cwd);
60
- return makeTextResult(text);
61
- };
62
- }
63
-
64
- function makeTextResult(text: string) {
65
- return {
66
- content: [{ type: "text" as const, text }],
67
- details: {},
68
- };
69
- }
@@ -1,399 +0,0 @@
1
- // Service-backed LSP tool actions used by the public expert toolset.
2
-
3
- import * as fs from "node:fs";
4
- import * as path from "node:path";
5
- import type { Position } from "../config/types.ts";
6
- import { formatDiagnostics } from "../diagnostics/diagnostics.ts";
7
- import {
8
- formatCodeActions,
9
- formatDocumentSymbols,
10
- formatHover,
11
- formatLocations,
12
- formatSymbolInformation,
13
- formatWorkspaceEdit,
14
- formatWorkspaceSymbols,
15
- normalizeLocations,
16
- } from "../format.ts";
17
- import type { SessionLspService } from "../session/service-registry.ts";
18
- import { resolveSessionPath } from "../utils.ts";
19
-
20
- export type LspLookupKind = "hover" | "definition" | "references" | "implementation";
21
- export type LspRefactorKind = "rename" | "code_actions";
22
-
23
- export interface LspLookupToolParams {
24
- kind: LspLookupKind;
25
- file: string;
26
- line: number;
27
- character: number;
28
- }
29
-
30
- export interface LspDocumentSymbolsToolParams {
31
- file: string;
32
- }
33
-
34
- export interface LspWorkspaceSymbolsToolParams {
35
- query: string;
36
- }
37
-
38
- export interface LspDiagnosticsToolParams {
39
- file?: string;
40
- }
41
-
42
- export interface LspRefactorToolParams {
43
- kind: LspRefactorKind;
44
- file: string;
45
- line: number;
46
- character: number;
47
- newName?: string;
48
- }
49
-
50
- function validatePositivePosition(line: number, character: number): string | null {
51
- if (!Number.isInteger(line) || line < 1) {
52
- return "Validation error: `line` must be a positive 1-based integer.";
53
- }
54
- if (!Number.isInteger(character) || character < 1) {
55
- return "Validation error: `character` must be a positive 1-based integer.";
56
- }
57
- return null;
58
- }
59
-
60
- function toZeroBased(line: number, character: number): Position {
61
- return { line: line - 1, character: character - 1 };
62
- }
63
-
64
- function validateFile(service: SessionLspService, cwd: string, file: string): string | null {
65
- const resolvedPath = resolveSessionPath(cwd, file);
66
- if (!fs.existsSync(resolvedPath)) {
67
- return `File not found: \`${file}\``;
68
- }
69
- if (!service.isSupportedSourceFile(file)) {
70
- return noServerMessage(resolvedPath);
71
- }
72
- return null;
73
- }
74
-
75
- function noServerMessage(filePath: string): string {
76
- return `No LSP server available for this file type (${path.extname(filePath) || "unknown"})`;
77
- }
78
-
79
- function formatUnexpectedFailure(label: string, error: unknown): string {
80
- const message = error instanceof Error ? error.message : String(error);
81
- return `LSP ${label} failed: ${message}`;
82
- }
83
-
84
- // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: semantic lookup dispatch stays clearer as a single position-based switch.
85
- export async function executeLookup(
86
- service: SessionLspService,
87
- cwd: string,
88
- params: LspLookupToolParams,
89
- ): Promise<string> {
90
- try {
91
- const positionError = validatePositivePosition(params.line, params.character);
92
- if (positionError) return positionError;
93
-
94
- const fileError = validateFile(service, cwd, params.file);
95
- if (fileError) return fileError;
96
-
97
- const position = toZeroBased(params.line, params.character);
98
- switch (params.kind) {
99
- case "hover": {
100
- const hover = await service.hover(params.file, position);
101
- return hover ? formatHover(hover) : "No hover information available at this position.";
102
- }
103
- case "definition": {
104
- const result = await service.definition(params.file, position);
105
- if (!result) return "No definition found.";
106
- const locations = normalizeLocations(result);
107
- return locations.length > 0
108
- ? formatLocations("Definition", locations, cwd)
109
- : "No definition found.";
110
- }
111
- case "references": {
112
- const references = await service.references(params.file, position);
113
- return references && references.length > 0
114
- ? formatLocations("References", references, cwd)
115
- : "No references found.";
116
- }
117
- case "implementation": {
118
- const result = await service.implementation(params.file, position);
119
- if (!result) return "No implementation found.";
120
- const locations = normalizeLocations(result);
121
- return locations.length > 0
122
- ? formatLocations("Implementation", locations, cwd)
123
- : "No implementation found.";
124
- }
125
- }
126
- } catch (error) {
127
- return formatUnexpectedFailure("lookup", error);
128
- }
129
- }
130
-
131
- // ---------------------------------------------------------------------------
132
- // Focused lookup tools (replacing the old multiplexed executeLookup)
133
- // ---------------------------------------------------------------------------
134
-
135
- export interface LspPositionToolParams {
136
- file: string;
137
- line: number;
138
- character: number;
139
- }
140
-
141
- export interface LspRenameToolParams {
142
- file: string;
143
- line: number;
144
- character: number;
145
- newName: string;
146
- }
147
-
148
- export async function executeHover(
149
- service: SessionLspService,
150
- cwd: string,
151
- params: LspPositionToolParams,
152
- ): Promise<string> {
153
- try {
154
- const positionError = validatePositivePosition(params.line, params.character);
155
- if (positionError) return positionError;
156
- const fileError = validateFile(service, cwd, params.file);
157
- if (fileError) return fileError;
158
- const position = toZeroBased(params.line, params.character);
159
- const hover = await service.hover(params.file, position);
160
- return hover ? formatHover(hover) : "No hover information available at this position.";
161
- } catch (error) {
162
- return formatUnexpectedFailure("hover", error);
163
- }
164
- }
165
-
166
- export async function executeDefinition(
167
- service: SessionLspService,
168
- cwd: string,
169
- params: LspPositionToolParams,
170
- ): Promise<string> {
171
- try {
172
- const positionError = validatePositivePosition(params.line, params.character);
173
- if (positionError) return positionError;
174
- const fileError = validateFile(service, cwd, params.file);
175
- if (fileError) return fileError;
176
- const position = toZeroBased(params.line, params.character);
177
- const result = await service.definition(params.file, position);
178
- if (!result) return "No definition found.";
179
- const locations = normalizeLocations(result);
180
- return locations.length > 0
181
- ? formatLocations("Definition", locations, cwd)
182
- : "No definition found.";
183
- } catch (error) {
184
- return formatUnexpectedFailure("definition", error);
185
- }
186
- }
187
-
188
- export async function executeReferences(
189
- service: SessionLspService,
190
- cwd: string,
191
- params: LspPositionToolParams,
192
- ): Promise<string> {
193
- try {
194
- const positionError = validatePositivePosition(params.line, params.character);
195
- if (positionError) return positionError;
196
- const fileError = validateFile(service, cwd, params.file);
197
- if (fileError) return fileError;
198
- const position = toZeroBased(params.line, params.character);
199
- const references = await service.references(params.file, position);
200
- return references && references.length > 0
201
- ? formatLocations("References", references, cwd)
202
- : "No references found.";
203
- } catch (error) {
204
- return formatUnexpectedFailure("references", error);
205
- }
206
- }
207
-
208
- export async function executeImplementation(
209
- service: SessionLspService,
210
- cwd: string,
211
- params: LspPositionToolParams,
212
- ): Promise<string> {
213
- try {
214
- const positionError = validatePositivePosition(params.line, params.character);
215
- if (positionError) return positionError;
216
- const fileError = validateFile(service, cwd, params.file);
217
- if (fileError) return fileError;
218
- const position = toZeroBased(params.line, params.character);
219
- const result = await service.implementation(params.file, position);
220
- if (!result) return "No implementation found.";
221
- const locations = normalizeLocations(result);
222
- return locations.length > 0
223
- ? formatLocations("Implementation", locations, cwd)
224
- : "No implementation found.";
225
- } catch (error) {
226
- return formatUnexpectedFailure("implementation", error);
227
- }
228
- }
229
-
230
- export async function executeRename(
231
- service: SessionLspService,
232
- cwd: string,
233
- params: LspRenameToolParams,
234
- ): Promise<string> {
235
- try {
236
- const positionError = validatePositivePosition(params.line, params.character);
237
- if (positionError) return positionError;
238
- const fileError = validateFile(service, cwd, params.file);
239
- if (fileError) return fileError;
240
- const newName = params.newName?.trim();
241
- if (!newName) {
242
- return "Validation error: `newName` is required for rename.";
243
- }
244
- const position = toZeroBased(params.line, params.character);
245
- const edit = await service.rename(params.file, position, newName);
246
- return edit ? formatWorkspaceEdit(edit, cwd) : "Rename not available at this position.";
247
- } catch (error) {
248
- return formatUnexpectedFailure("rename", error);
249
- }
250
- }
251
-
252
- export async function executeCodeActions(
253
- service: SessionLspService,
254
- cwd: string,
255
- params: LspPositionToolParams,
256
- ): Promise<string> {
257
- try {
258
- const positionError = validatePositivePosition(params.line, params.character);
259
- if (positionError) return positionError;
260
- const fileError = validateFile(service, cwd, params.file);
261
- if (fileError) return fileError;
262
- const position = toZeroBased(params.line, params.character);
263
- const actions = await service.codeActions(params.file, position);
264
- return actions && actions.length > 0
265
- ? formatCodeActions(actions)
266
- : "No code actions available at this position.";
267
- } catch (error) {
268
- return formatUnexpectedFailure("code actions", error);
269
- }
270
- }
271
-
272
- export async function executeDocumentSymbols(
273
- service: SessionLspService,
274
- cwd: string,
275
- params: LspDocumentSymbolsToolParams,
276
- ): Promise<string> {
277
- try {
278
- const fileError = validateFile(service, cwd, params.file);
279
- if (fileError) return fileError;
280
-
281
- const symbols = await service.documentSymbols(params.file);
282
- if (!symbols || symbols.length === 0) return "No document symbols found.";
283
-
284
- if ("children" in symbols[0] || "selectionRange" in symbols[0]) {
285
- return formatDocumentSymbols(symbols as import("../config/types.ts").DocumentSymbol[], 0);
286
- }
287
- return formatSymbolInformation(
288
- symbols as import("../config/types.ts").SymbolInformation[],
289
- cwd,
290
- );
291
- } catch (error) {
292
- return formatUnexpectedFailure("document symbol lookup", error);
293
- }
294
- }
295
-
296
- export async function executeWorkspaceSymbols(
297
- service: SessionLspService,
298
- cwd: string,
299
- params: LspWorkspaceSymbolsToolParams,
300
- ): Promise<string> {
301
- try {
302
- const query = params.query.trim();
303
- if (query.length === 0) {
304
- return "Validation error: `query` must be a non-empty string.";
305
- }
306
-
307
- const symbols = await service.workspaceSymbol(query);
308
- if (!symbols) return "Workspace symbol search is not supported by the active language servers.";
309
- if (symbols.length === 0) return `No symbols found for query \`${query}\`.`;
310
-
311
- return formatWorkspaceSymbols(symbols, cwd);
312
- } catch (error) {
313
- return formatUnexpectedFailure("workspace symbol lookup", error);
314
- }
315
- }
316
-
317
- export async function executeDiagnostics(
318
- service: SessionLspService,
319
- cwd: string,
320
- params: LspDiagnosticsToolParams,
321
- ): Promise<string> {
322
- try {
323
- if (params.file) {
324
- const resolvedPath = resolveSessionPath(cwd, params.file);
325
- if (!fs.existsSync(resolvedPath)) {
326
- return `File not found: \`${params.file}\``;
327
- }
328
- if (!service.isSupportedSourceFile(params.file)) {
329
- return noServerMessage(resolvedPath);
330
- }
331
-
332
- const diagnostics = await service.fileDiagnostics(params.file, 4);
333
- if (!diagnostics) return noServerMessage(resolvedPath);
334
- return formatDiagnostics(resolvedPath, diagnostics, cwd);
335
- }
336
-
337
- const summary = service.getWorkspaceDiagnosticSummary();
338
- if (summary.length === 0) return "No diagnostics across any files.";
339
-
340
- const lines = ["## Diagnostics Summary", ""];
341
- for (const entry of summary) {
342
- lines.push(`- **${entry.file}**: ${entry.errors} error(s), ${entry.warnings} warning(s)`);
343
- }
344
- return lines.join("\n");
345
- } catch (error) {
346
- return formatUnexpectedFailure("diagnostics", error);
347
- }
348
- }
349
-
350
- export async function executeRefactor(
351
- service: SessionLspService,
352
- cwd: string,
353
- params: LspRefactorToolParams,
354
- ): Promise<string> {
355
- try {
356
- const positionError = validatePositivePosition(params.line, params.character);
357
- if (positionError) return positionError;
358
-
359
- const fileError = validateFile(service, cwd, params.file);
360
- if (fileError) return fileError;
361
-
362
- const position = toZeroBased(params.line, params.character);
363
-
364
- if (params.kind === "rename") {
365
- if (!params.newName || params.newName.trim().length === 0) {
366
- return "Validation error: `newName` is required for rename.";
367
- }
368
-
369
- const edit = await service.rename(params.file, position, params.newName.trim());
370
- return edit ? formatWorkspaceEdit(edit, cwd) : "Rename not available at this position.";
371
- }
372
-
373
- const actions = await service.codeActions(params.file, position);
374
- return actions && actions.length > 0
375
- ? formatCodeActions(actions)
376
- : "No code actions available at this position.";
377
- } catch (error) {
378
- return formatUnexpectedFailure("refactor", error);
379
- }
380
- }
381
-
382
- export async function executeRecover(service: SessionLspService): Promise<string> {
383
- try {
384
- const result = await service.recoverDiagnostics({ restartIfStillStale: true });
385
- const refreshed = pluralize(result.refreshedClients, "client");
386
- const restarted = pluralize(result.restartedClients, "client");
387
- const status = result.staleAssessment.suspected
388
- ? "stale diagnostics still suspected"
389
- : "stale diagnostics cleared";
390
- const warning = result.staleAssessment.warning ? ` — ${result.staleAssessment.warning}` : "";
391
- return `LSP recovery complete: refreshed ${refreshed}, restarted ${restarted}, ${status}${warning}.`;
392
- } catch (error) {
393
- return formatUnexpectedFailure("recovery", error);
394
- }
395
- }
396
-
397
- function pluralize(count: number, word: string): string {
398
- return `${count} ${word}${count === 1 ? "" : "s"}`;
399
- }