@mrclrchtr/supi-code-intelligence 1.3.1 → 1.4.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.
- package/README.md +70 -32
- package/node_modules/@mrclrchtr/supi-core/README.md +52 -41
- package/node_modules/@mrclrchtr/supi-core/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-core/src/api.ts +13 -13
- package/node_modules/@mrclrchtr/supi-core/src/{config-settings.ts → config/config-settings.ts} +2 -2
- package/node_modules/@mrclrchtr/{supi-lsp/node_modules/@mrclrchtr/supi-core/src → supi-core/src/context}/context-provider-registry.ts +1 -1
- package/node_modules/@mrclrchtr/supi-core/src/extension.ts +1 -1
- package/node_modules/@mrclrchtr/supi-core/src/index.ts +13 -13
- package/node_modules/@mrclrchtr/{supi-lsp/node_modules/@mrclrchtr/supi-core/src → supi-core/src/settings}/settings-registry.ts +1 -1
- package/node_modules/@mrclrchtr/supi-lsp/README.md +58 -39
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/README.md +52 -41
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/api.ts +13 -13
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/{config-settings.ts → config/config-settings.ts} +2 -2
- package/node_modules/@mrclrchtr/{supi-core/src → supi-lsp/node_modules/@mrclrchtr/supi-core/src/context}/context-provider-registry.ts +1 -1
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/extension.ts +1 -1
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/index.ts +13 -13
- package/node_modules/@mrclrchtr/{supi-core/src → supi-lsp/node_modules/@mrclrchtr/supi-core/src/settings}/settings-registry.ts +1 -1
- package/node_modules/@mrclrchtr/supi-lsp/package.json +3 -2
- package/node_modules/@mrclrchtr/supi-lsp/src/api.ts +16 -3
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client-refresh.ts +1 -1
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client.ts +27 -3
- package/node_modules/@mrclrchtr/supi-lsp/src/client/transport.ts +61 -5
- package/node_modules/@mrclrchtr/supi-lsp/src/config/tsconfig-scope.ts +244 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/{types.ts → config/types.ts} +4 -2
- package/node_modules/@mrclrchtr/supi-lsp/src/coordinates.ts +11 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/diagnostic-augmentation.ts +5 -5
- package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/diagnostic-context.ts +115 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/diagnostic-display.ts +1 -1
- package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/diagnostic-summary.ts +3 -2
- package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/diagnostics.ts +1 -1
- package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/stale-diagnostics.ts +1 -1
- package/node_modules/@mrclrchtr/supi-lsp/src/diagnostics/suppression-diagnostics.ts +1 -1
- package/node_modules/@mrclrchtr/supi-lsp/src/{workspace-sentinels.ts → diagnostics/workspace-sentinels.ts} +2 -2
- package/node_modules/@mrclrchtr/supi-lsp/src/format.ts +2 -23
- package/node_modules/@mrclrchtr/supi-lsp/src/index.ts +18 -5
- package/node_modules/@mrclrchtr/supi-lsp/src/lsp.ts +72 -120
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-diagnostics.ts +1 -1
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-helpers.ts +4 -2
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-project-info.ts +10 -7
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-workspace-recovery.ts +1 -1
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-workspace-symbol.ts +158 -6
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager.ts +202 -43
- package/node_modules/@mrclrchtr/supi-lsp/src/{lsp-state.ts → session/lsp-state.ts} +22 -11
- package/node_modules/@mrclrchtr/supi-lsp/src/{scanner.ts → session/scanner.ts} +3 -3
- package/node_modules/@mrclrchtr/supi-lsp/src/{service-registry.ts → session/service-registry.ts} +104 -12
- package/node_modules/@mrclrchtr/supi-lsp/src/{settings-registration.ts → session/settings-registration.ts} +1 -1
- package/node_modules/@mrclrchtr/supi-lsp/src/session/tree-persist.ts +75 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/summary.ts +1 -1
- package/node_modules/@mrclrchtr/supi-lsp/src/tool/guidance.ts +138 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/tool/names.ts +19 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/{overrides.ts → tool/overrides.ts} +55 -24
- package/node_modules/@mrclrchtr/supi-lsp/src/tool/register-tools.ts +224 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/tool/service-actions.ts +258 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/{ui.ts → ui/ui.ts} +4 -4
- package/node_modules/@mrclrchtr/supi-lsp/src/utils.ts +11 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/README.md +46 -39
- package/node_modules/@mrclrchtr/supi-tree-sitter/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/api.ts +1 -1
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/index.ts +1 -1
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/{runtime.ts → session/runtime.ts} +3 -3
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/{session.ts → session/session.ts} +4 -4
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/{callees.ts → tool/callees.ts} +3 -3
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/{exports.ts → tool/exports.ts} +4 -4
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/{formatting.ts → tool/formatting.ts} +1 -1
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/guidance.ts +22 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/{imports.ts → tool/imports.ts} +4 -4
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/{node-at.ts → tool/node-at.ts} +3 -3
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/{outline.ts → tool/outline.ts} +3 -3
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/tree-sitter.ts +6 -29
- package/package.json +4 -4
- package/src/actions/affected-action.ts +4 -4
- package/src/actions/brief-action.ts +12 -13
- package/src/actions/callees-action.ts +14 -10
- package/src/actions/callers-action.ts +4 -4
- package/src/actions/implementations-action.ts +4 -4
- package/src/code-intelligence.ts +1 -1
- package/src/pattern-structured.ts +20 -22
- package/src/providers/semantic-provider.ts +34 -0
- package/src/providers/structural-provider.ts +14 -0
- package/src/target-resolution.ts +26 -35
- package/src/tool/guidance.ts +21 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/guidance.ts +0 -163
- package/node_modules/@mrclrchtr/supi-lsp/src/search-fallback.ts +0 -98
- package/node_modules/@mrclrchtr/supi-lsp/src/tool-actions.ts +0 -430
- package/node_modules/@mrclrchtr/supi-lsp/src/tree-persist.ts +0 -48
- package/node_modules/@mrclrchtr/supi-lsp/src/tsconfig-scope.ts +0 -156
- package/src/guidance.ts +0 -42
- /package/node_modules/@mrclrchtr/supi-core/src/{config.ts → config/config.ts} +0 -0
- /package/node_modules/@mrclrchtr/supi-core/src/{context-messages.ts → context/context-messages.ts} +0 -0
- /package/node_modules/@mrclrchtr/supi-core/src/{context-tag.ts → context/context-tag.ts} +0 -0
- /package/node_modules/@mrclrchtr/supi-core/src/{settings-command.ts → settings/settings-command.ts} +0 -0
- /package/node_modules/@mrclrchtr/supi-core/src/{settings-ui.ts → settings/settings-ui.ts} +0 -0
- /package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/{config.ts → config/config.ts} +0 -0
- /package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/{context-messages.ts → context/context-messages.ts} +0 -0
- /package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/{context-tag.ts → context/context-tag.ts} +0 -0
- /package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/{settings-command.ts → settings/settings-command.ts} +0 -0
- /package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/{settings-ui.ts → settings/settings-ui.ts} +0 -0
- /package/node_modules/@mrclrchtr/supi-lsp/src/{capabilities.ts → config/capabilities.ts} +0 -0
- /package/node_modules/@mrclrchtr/supi-lsp/src/{config.ts → config/config.ts} +0 -0
- /package/node_modules/@mrclrchtr/supi-lsp/src/{defaults.json → config/defaults.json} +0 -0
- /package/node_modules/@mrclrchtr/supi-lsp/src/{renderer.ts → ui/renderer.ts} +0 -0
- /package/node_modules/@mrclrchtr/supi-tree-sitter/src/{structure.ts → tool/structure.ts} +0 -0
|
@@ -1,76 +1,71 @@
|
|
|
1
|
-
// LSP Extension for pi —
|
|
2
|
-
//
|
|
3
|
-
// and injects diagnostic context only when outstanding issues exist.
|
|
1
|
+
// LSP Extension for pi — registers an expert semantic toolset, keeps language servers warm,
|
|
2
|
+
// surfaces inline diagnostics, and injects diagnostic context only when outstanding issues exist.
|
|
4
3
|
// biome-ignore-all lint/nursery/noExcessiveLinesPerFile: lsp.ts stays cohesive wiring; recovery and sentinel helpers live in focused modules.
|
|
5
4
|
|
|
6
5
|
import * as path from "node:path";
|
|
7
|
-
import {
|
|
8
|
-
|
|
6
|
+
import type {
|
|
7
|
+
AgentEndEvent,
|
|
8
|
+
BeforeAgentStartEvent,
|
|
9
|
+
BeforeAgentStartEventResult,
|
|
10
|
+
ContextEvent,
|
|
11
|
+
ExtensionAPI,
|
|
12
|
+
ExtensionContext,
|
|
13
|
+
SessionStartEvent,
|
|
14
|
+
ToolResultEvent,
|
|
15
|
+
} from "@earendil-works/pi-coding-agent";
|
|
9
16
|
import { pruneAndReorderContextMessages, restorePromptContent } from "@mrclrchtr/supi-core/api";
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import { assessStaleDiagnostics } from "./diagnostics/stale-diagnostics.ts";
|
|
17
|
+
import { loadConfig, resolveLanguageAlias } from "./config/config.ts";
|
|
18
|
+
import { clearTsconfigCache } from "./config/tsconfig-scope.ts";
|
|
19
|
+
import { FileChangeType } from "./config/types.ts";
|
|
14
20
|
import {
|
|
15
|
-
buildProjectGuidelines,
|
|
16
21
|
diagnosticsContextFingerprint,
|
|
17
22
|
formatDiagnosticsContext,
|
|
18
|
-
lspPromptGuidelines,
|
|
19
|
-
lspPromptSnippet,
|
|
20
23
|
MAX_DETAILED_DIAGNOSTICS,
|
|
21
|
-
} from "./
|
|
24
|
+
} from "./diagnostics/diagnostic-context.ts";
|
|
25
|
+
import { formatDiagnosticsDisplayContent } from "./diagnostics/diagnostic-display.ts";
|
|
26
|
+
import { assessStaleDiagnostics } from "./diagnostics/stale-diagnostics.ts";
|
|
27
|
+
import {
|
|
28
|
+
isWorkspaceRecoveryTrigger,
|
|
29
|
+
scanWorkspaceSentinels,
|
|
30
|
+
syncWorkspaceSentinelSnapshot,
|
|
31
|
+
} from "./diagnostics/workspace-sentinels.ts";
|
|
32
|
+
import { LspManager } from "./manager/manager.ts";
|
|
33
|
+
import { forceResyncStaleModuleFiles } from "./manager/manager-stale-resync.ts";
|
|
22
34
|
import {
|
|
23
35
|
createRuntimeState,
|
|
24
36
|
disableLspState,
|
|
25
|
-
|
|
37
|
+
ensureLspToolsActive,
|
|
26
38
|
isLspAwareTool,
|
|
27
39
|
type LspRuntimeState,
|
|
28
40
|
refreshProjectServers,
|
|
29
|
-
|
|
30
|
-
} from "./lsp-state.ts";
|
|
31
|
-
import {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
41
|
+
removeLspTools,
|
|
42
|
+
} from "./session/lsp-state.ts";
|
|
43
|
+
import {
|
|
44
|
+
scanMissingServers,
|
|
45
|
+
scanProjectCapabilities,
|
|
46
|
+
startDetectedServers,
|
|
47
|
+
} from "./session/scanner.ts";
|
|
36
48
|
import {
|
|
37
49
|
clearSessionLspService,
|
|
38
50
|
SessionLspService,
|
|
39
51
|
setSessionLspServiceState,
|
|
40
|
-
} from "./service-registry.ts";
|
|
52
|
+
} from "./session/service-registry.ts";
|
|
41
53
|
import {
|
|
42
54
|
getLspDisabledMessage,
|
|
43
55
|
loadLspSettings,
|
|
44
56
|
registerLspSettings,
|
|
45
|
-
} from "./settings-registration.ts";
|
|
46
|
-
import { type LspAction, lspToolDescription, safeExecuteAction } from "./tool-actions.ts";
|
|
57
|
+
} from "./session/settings-registration.ts";
|
|
47
58
|
import {
|
|
48
59
|
persistLspActiveState,
|
|
49
60
|
persistLspInactiveState,
|
|
50
61
|
registerTreePersistHandlers,
|
|
51
|
-
} from "./tree-persist.ts";
|
|
52
|
-
import {
|
|
53
|
-
import {
|
|
54
|
-
import {
|
|
55
|
-
import {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
syncWorkspaceSentinelSnapshot,
|
|
59
|
-
} from "./workspace-sentinels.ts";
|
|
60
|
-
|
|
61
|
-
const LspActionEnum = StringEnum([
|
|
62
|
-
"hover",
|
|
63
|
-
"definition",
|
|
64
|
-
"references",
|
|
65
|
-
"diagnostics",
|
|
66
|
-
"symbols",
|
|
67
|
-
"rename",
|
|
68
|
-
"code_actions",
|
|
69
|
-
"workspace_symbol",
|
|
70
|
-
"search",
|
|
71
|
-
"symbol_hover",
|
|
72
|
-
"recover",
|
|
73
|
-
] as const);
|
|
62
|
+
} from "./session/tree-persist.ts";
|
|
63
|
+
import { buildLspToolPromptSurfaces, defaultLspToolPromptSurfaces } from "./tool/guidance.ts";
|
|
64
|
+
import { registerLspAwareToolOverrides } from "./tool/overrides.ts";
|
|
65
|
+
import { registerLspTools } from "./tool/register-tools.ts";
|
|
66
|
+
import { registerLspMessageRenderer } from "./ui/renderer.ts";
|
|
67
|
+
import { toggleLspStatusOverlay, updateLspUi } from "./ui/ui.ts";
|
|
68
|
+
import { fileToUri, resolveSessionPath } from "./utils.ts";
|
|
74
69
|
|
|
75
70
|
export default function lspExtension(pi: ExtensionAPI) {
|
|
76
71
|
registerLspSettings();
|
|
@@ -79,10 +74,10 @@ export default function lspExtension(pi: ExtensionAPI) {
|
|
|
79
74
|
registerLspAwareToolOverrides(pi, {
|
|
80
75
|
getInlineSeverity: () => state.inlineSeverity,
|
|
81
76
|
getManager: () => state.manager,
|
|
82
|
-
|
|
77
|
+
isActive: () => state.lspActive,
|
|
83
78
|
});
|
|
84
79
|
|
|
85
|
-
|
|
80
|
+
registerLspTools(pi, defaultLspToolPromptSurfaces);
|
|
86
81
|
registerSessionLifecycleHandlers(pi, state);
|
|
87
82
|
registerBehaviorHandlers(pi, state);
|
|
88
83
|
registerTreePersistHandlers(pi, state);
|
|
@@ -92,13 +87,14 @@ export default function lspExtension(pi: ExtensionAPI) {
|
|
|
92
87
|
|
|
93
88
|
function registerSessionLifecycleHandlers(pi: ExtensionAPI, state: LspRuntimeState): void {
|
|
94
89
|
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: session_start orchestrates setup, server detection, settings, and persistence.
|
|
95
|
-
pi.on("session_start", async (_event, ctx) => {
|
|
90
|
+
pi.on("session_start", async (_event: SessionStartEvent, ctx: ExtensionContext) => {
|
|
96
91
|
if (state.manager) {
|
|
97
92
|
clearSessionLspService(state.manager.getCwd());
|
|
98
93
|
await state.manager.shutdownAll();
|
|
99
94
|
}
|
|
100
95
|
|
|
101
96
|
const cwd = ctx.cwd;
|
|
97
|
+
clearTsconfigCache();
|
|
102
98
|
const lspSettings = loadLspSettings(cwd);
|
|
103
99
|
|
|
104
100
|
if (!lspSettings.enabled) {
|
|
@@ -151,13 +147,14 @@ function registerSessionLifecycleHandlers(pi: ExtensionAPI, state: LspRuntimeSta
|
|
|
151
147
|
kind: "ready",
|
|
152
148
|
service: new SessionLspService(state.manager),
|
|
153
149
|
});
|
|
154
|
-
|
|
155
|
-
|
|
150
|
+
registerLspTools(pi, buildLspToolPromptSurfaces(state.projectServers, cwd));
|
|
151
|
+
ensureLspToolsActive(pi);
|
|
156
152
|
persistLspActiveState(pi, state);
|
|
157
153
|
updateLspUi(ctx, state.manager, state.inlineSeverity, state.projectServers);
|
|
158
154
|
});
|
|
159
155
|
|
|
160
156
|
pi.on("session_shutdown", async () => {
|
|
157
|
+
clearTsconfigCache();
|
|
161
158
|
if (state.manager) {
|
|
162
159
|
clearSessionLspService(state.manager.getCwd());
|
|
163
160
|
await state.manager.shutdownAll();
|
|
@@ -174,7 +171,7 @@ function registerSessionLifecycleHandlers(pi: ExtensionAPI, state: LspRuntimeSta
|
|
|
174
171
|
state.sentinelSnapshot = new Map();
|
|
175
172
|
});
|
|
176
173
|
|
|
177
|
-
pi.on("agent_end", async (_event, ctx) => {
|
|
174
|
+
pi.on("agent_end", async (_event: AgentEndEvent, ctx: ExtensionContext) => {
|
|
178
175
|
state.currentContextToken = null;
|
|
179
176
|
refreshProjectServers(state);
|
|
180
177
|
|
|
@@ -193,10 +190,11 @@ function markWorkspaceChange(state: LspRuntimeState): void {
|
|
|
193
190
|
|
|
194
191
|
function softRecoverWorkspaceChanges(
|
|
195
192
|
state: LspRuntimeState,
|
|
196
|
-
changes: import("./types.ts").FileEvent[],
|
|
193
|
+
changes: import("./config/types.ts").FileEvent[],
|
|
197
194
|
): boolean {
|
|
198
195
|
if (!state.manager || changes.length === 0) return false;
|
|
199
196
|
|
|
197
|
+
clearTsconfigCache();
|
|
200
198
|
state.manager.clearAllPullResultIds();
|
|
201
199
|
state.manager.notifyWorkspaceFileChanges(changes);
|
|
202
200
|
markWorkspaceChange(state);
|
|
@@ -209,6 +207,11 @@ function refreshWorkspaceSentinels(state: LspRuntimeState, cwd: string): boolean
|
|
|
209
207
|
return softRecoverWorkspaceChanges(state, changes);
|
|
210
208
|
}
|
|
211
209
|
|
|
210
|
+
function shouldInvalidateTsconfigScopeCache(filePath: string): boolean {
|
|
211
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
212
|
+
return ext === ".json" || ext === ".jsonc";
|
|
213
|
+
}
|
|
214
|
+
|
|
212
215
|
function recoverWorkspaceChangesFromToolResult(
|
|
213
216
|
state: LspRuntimeState,
|
|
214
217
|
cwd: string,
|
|
@@ -221,11 +224,14 @@ function recoverWorkspaceChangesFromToolResult(
|
|
|
221
224
|
const pathValue = (event.input as { path?: unknown }).path;
|
|
222
225
|
if (typeof pathValue !== "string") return false;
|
|
223
226
|
|
|
224
|
-
const resolvedPath =
|
|
227
|
+
const resolvedPath = resolveSessionPath(cwd, pathValue);
|
|
228
|
+
if (shouldInvalidateTsconfigScopeCache(resolvedPath)) {
|
|
229
|
+
clearTsconfigCache();
|
|
230
|
+
}
|
|
225
231
|
const fileEvent = { uri: fileToUri(resolvedPath), type: FileChangeType.Changed };
|
|
226
232
|
|
|
227
233
|
// Sentinel files (package.json, tsconfig.json, lockfiles, .d.ts)
|
|
228
|
-
if (isWorkspaceRecoveryTrigger(
|
|
234
|
+
if (isWorkspaceRecoveryTrigger(resolvedPath, cwd)) {
|
|
229
235
|
if (resolvedPath.endsWith(".d.ts")) {
|
|
230
236
|
return softRecoverWorkspaceChanges(state, [fileEvent]);
|
|
231
237
|
}
|
|
@@ -236,7 +242,7 @@ function recoverWorkspaceChangesFromToolResult(
|
|
|
236
242
|
}
|
|
237
243
|
|
|
238
244
|
// Source files matching an active language server's file types
|
|
239
|
-
if (state.manager.hasServerForExtension(
|
|
245
|
+
if (state.manager.hasServerForExtension(resolvedPath)) {
|
|
240
246
|
return softRecoverWorkspaceChanges(state, [fileEvent]);
|
|
241
247
|
}
|
|
242
248
|
|
|
@@ -247,7 +253,7 @@ function recoverWorkspaceChangesFromToolResult(
|
|
|
247
253
|
// biome-ignore lint/complexity/useMaxParams: wrapper groups the prompt payload fields in one place.
|
|
248
254
|
function buildDiagnosticResult(
|
|
249
255
|
diagnostics: import("./manager/manager-types.ts").OutstandingDiagnosticSummaryEntry[],
|
|
250
|
-
detailed: { file: string; diagnostics: import("./types.ts").Diagnostic[] }[] | undefined,
|
|
256
|
+
detailed: { file: string; diagnostics: import("./config/types.ts").Diagnostic[] }[] | undefined,
|
|
251
257
|
severity: number,
|
|
252
258
|
token: string,
|
|
253
259
|
staleWarning?: string | null,
|
|
@@ -276,16 +282,16 @@ function buildDiagnosticResult(
|
|
|
276
282
|
|
|
277
283
|
function registerBehaviorHandlers(pi: ExtensionAPI, state: LspRuntimeState): void {
|
|
278
284
|
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: before_agent_start coordinates sentinel recovery, pruning, refresh, and diagnostic injection.
|
|
279
|
-
pi.on("before_agent_start", async (_event, ctx) => {
|
|
285
|
+
pi.on("before_agent_start", async (_event: BeforeAgentStartEvent, ctx: ExtensionContext) => {
|
|
280
286
|
if (!state.manager || !state.lspActive) {
|
|
281
|
-
|
|
287
|
+
removeLspTools(pi);
|
|
282
288
|
if (!state.manager && state.lspActive) {
|
|
283
289
|
persistLspInactiveState(pi, state);
|
|
284
290
|
}
|
|
285
291
|
return;
|
|
286
292
|
}
|
|
287
293
|
|
|
288
|
-
|
|
294
|
+
ensureLspToolsActive(pi);
|
|
289
295
|
|
|
290
296
|
refreshWorkspaceSentinels(state, ctx.cwd);
|
|
291
297
|
|
|
@@ -348,18 +354,16 @@ function registerBehaviorHandlers(pi: ExtensionAPI, state: LspRuntimeState): voi
|
|
|
348
354
|
state.lastDiagnosticsFingerprint = fingerprint;
|
|
349
355
|
state.currentContextToken = `lsp-context-${++state.contextCounter}`;
|
|
350
356
|
|
|
351
|
-
|
|
357
|
+
return buildDiagnosticResult(
|
|
352
358
|
diagnostics,
|
|
353
359
|
detailed,
|
|
354
360
|
state.inlineSeverity,
|
|
355
361
|
state.currentContextToken,
|
|
356
362
|
staleWarning,
|
|
357
363
|
);
|
|
358
|
-
|
|
359
|
-
return result;
|
|
360
364
|
});
|
|
361
365
|
|
|
362
|
-
pi.on("context", (event) => {
|
|
366
|
+
pi.on("context", (event: ContextEvent) => {
|
|
363
367
|
const messages = pruneAndReorderContextMessages(
|
|
364
368
|
event.messages as Array<{
|
|
365
369
|
role?: string;
|
|
@@ -378,14 +382,14 @@ function registerBehaviorHandlers(pi: ExtensionAPI, state: LspRuntimeState): voi
|
|
|
378
382
|
|
|
379
383
|
if (
|
|
380
384
|
contextMessages.length === event.messages.length &&
|
|
381
|
-
contextMessages.every((
|
|
385
|
+
contextMessages.every((message, index) => message === event.messages[index])
|
|
382
386
|
) {
|
|
383
387
|
return;
|
|
384
388
|
}
|
|
385
389
|
return { messages: contextMessages };
|
|
386
390
|
});
|
|
387
391
|
|
|
388
|
-
pi.on("tool_result", async (event, ctx) => {
|
|
392
|
+
pi.on("tool_result", async (event: ToolResultEvent, ctx: ExtensionContext) => {
|
|
389
393
|
if (!state.manager) return;
|
|
390
394
|
|
|
391
395
|
const recoveryTriggered = recoverWorkspaceChangesFromToolResult(state, ctx.cwd, {
|
|
@@ -401,62 +405,10 @@ function registerBehaviorHandlers(pi: ExtensionAPI, state: LspRuntimeState): voi
|
|
|
401
405
|
});
|
|
402
406
|
}
|
|
403
407
|
|
|
404
|
-
function registerLspTool(
|
|
405
|
-
pi: ExtensionAPI,
|
|
406
|
-
state: LspRuntimeState,
|
|
407
|
-
promptGuidelines: string[],
|
|
408
|
-
): void {
|
|
409
|
-
pi.registerTool({
|
|
410
|
-
name: "lsp",
|
|
411
|
-
label: "LSP",
|
|
412
|
-
description: lspToolDescription,
|
|
413
|
-
promptSnippet: lspPromptSnippet,
|
|
414
|
-
promptGuidelines,
|
|
415
|
-
parameters: Type.Object({
|
|
416
|
-
action: LspActionEnum,
|
|
417
|
-
file: Type.Optional(Type.String({ description: "File path (relative or absolute)" })),
|
|
418
|
-
line: Type.Optional(Type.Number({ description: "1-based line number" })),
|
|
419
|
-
character: Type.Optional(Type.Number({ description: "1-based column number" })),
|
|
420
|
-
newName: Type.Optional(Type.String({ description: "New name (for rename action)" })),
|
|
421
|
-
query: Type.Optional(
|
|
422
|
-
Type.String({ description: "Search query (for workspace_symbol and search actions)" }),
|
|
423
|
-
),
|
|
424
|
-
symbol: Type.Optional(Type.String({ description: "Symbol name (for symbol_hover action)" })),
|
|
425
|
-
}),
|
|
426
|
-
// biome-ignore lint/complexity/useMaxParams: pi ToolDefinition.execute signature
|
|
427
|
-
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
428
|
-
if (!state.manager) {
|
|
429
|
-
return {
|
|
430
|
-
content: [{ type: "text", text: "LSP not initialized. Start a new session first." }],
|
|
431
|
-
details: {},
|
|
432
|
-
};
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
const text = await safeExecuteAction(
|
|
436
|
-
state.manager,
|
|
437
|
-
params as {
|
|
438
|
-
action: LspAction;
|
|
439
|
-
file?: string;
|
|
440
|
-
line?: number;
|
|
441
|
-
character?: number;
|
|
442
|
-
newName?: string;
|
|
443
|
-
query?: string;
|
|
444
|
-
symbol?: string;
|
|
445
|
-
},
|
|
446
|
-
);
|
|
447
|
-
|
|
448
|
-
return {
|
|
449
|
-
content: [{ type: "text", text }],
|
|
450
|
-
details: {},
|
|
451
|
-
};
|
|
452
|
-
},
|
|
453
|
-
});
|
|
454
|
-
}
|
|
455
|
-
|
|
456
408
|
function registerLspStatusCommand(pi: ExtensionAPI, state: LspRuntimeState): void {
|
|
457
409
|
pi.registerCommand("lsp-status", {
|
|
458
410
|
description: "Show detected LSP servers, roots, open files, and diagnostics",
|
|
459
|
-
handler: async (_args, ctx) => {
|
|
411
|
+
handler: async (_args: string, ctx: ExtensionContext) => {
|
|
460
412
|
const lspSettings = loadLspSettings(ctx.cwd);
|
|
461
413
|
if (!lspSettings.enabled) {
|
|
462
414
|
ctx.ui.notify(getLspDisabledMessage(ctx.cwd), "warning");
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import type { LspClient } from "../client/client.ts";
|
|
3
|
+
import type { Diagnostic } from "../config/types.ts";
|
|
3
4
|
import { relativeFilePathFromUri } from "../diagnostics/diagnostic-summary.ts";
|
|
4
5
|
import { shouldIgnoreLspPath } from "../summary.ts";
|
|
5
|
-
import type { Diagnostic } from "../types.ts";
|
|
6
6
|
import { fileToUri, uriToFile } from "../utils.ts";
|
|
7
7
|
import { isExcludedByPattern } from "./manager-helpers.ts";
|
|
8
8
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as path from "node:path";
|
|
2
2
|
import * as projectRoots from "@mrclrchtr/supi-core/api";
|
|
3
3
|
import { isGlobMatch } from "../pattern-matcher.ts";
|
|
4
|
+
import { resolveSessionPath } from "../utils.ts";
|
|
4
5
|
|
|
5
6
|
/** Unique key for a client identified by server name and root. */
|
|
6
7
|
export function clientKey(serverName: string, root: string): string {
|
|
@@ -14,10 +15,11 @@ export function resolveRootForFile(
|
|
|
14
15
|
rootMarkers: string[],
|
|
15
16
|
opts: { knownRoots: Map<string, string[]>; cwd: string },
|
|
16
17
|
): string {
|
|
18
|
+
const resolvedFilePath = resolveSessionPath(opts.cwd, filePath);
|
|
17
19
|
const preferredRoots = opts.knownRoots.get(serverName) ?? [];
|
|
18
|
-
const knownRoot = projectRoots.resolveKnownRoot(
|
|
20
|
+
const knownRoot = projectRoots.resolveKnownRoot(resolvedFilePath, preferredRoots);
|
|
19
21
|
if (knownRoot) return knownRoot;
|
|
20
|
-
const fileDir = path.dirname(
|
|
22
|
+
const fileDir = path.dirname(resolvedFilePath);
|
|
21
23
|
return projectRoots.findProjectRoot(fileDir, rootMarkers, opts.cwd);
|
|
22
24
|
}
|
|
23
25
|
|
|
@@ -1,26 +1,27 @@
|
|
|
1
1
|
import type { LspClient } from "../client/client.ts";
|
|
2
|
+
import type { ProjectServerInfo } from "../config/types.ts";
|
|
2
3
|
import { displayRelativeFilePath } from "../summary.ts";
|
|
3
|
-
import type { ProjectServerInfo } from "../types.ts";
|
|
4
4
|
|
|
5
5
|
interface ProjectServerInfoInput {
|
|
6
6
|
serverName: string;
|
|
7
7
|
root: string;
|
|
8
8
|
fileTypes: string[];
|
|
9
9
|
client: LspClient | undefined;
|
|
10
|
-
|
|
10
|
+
unavailableReason?: "missing-command" | "start-failed" | "runtime-error";
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
export function buildProjectServerInfo(
|
|
14
14
|
input: ProjectServerInfoInput,
|
|
15
15
|
cwd: string,
|
|
16
16
|
): ProjectServerInfo {
|
|
17
|
-
const status =
|
|
18
|
-
|
|
19
|
-
: input.client?.status === "running"
|
|
17
|
+
const status =
|
|
18
|
+
input.client?.status === "running"
|
|
20
19
|
? "running"
|
|
21
|
-
: input.client?.status === "error"
|
|
20
|
+
: input.client?.status === "error" || input.unavailableReason === "start-failed"
|
|
22
21
|
? "error"
|
|
23
|
-
: "
|
|
22
|
+
: input.unavailableReason === "runtime-error"
|
|
23
|
+
? "error"
|
|
24
|
+
: "unavailable";
|
|
24
25
|
|
|
25
26
|
return {
|
|
26
27
|
name: input.serverName,
|
|
@@ -39,7 +40,9 @@ function getSupportedActions(capabilities: LspClient["serverCapabilities"] | und
|
|
|
39
40
|
if (capabilities.hoverProvider) actions.push("hover(file,line,char)");
|
|
40
41
|
if (capabilities.definitionProvider) actions.push("definition(file,line,char)");
|
|
41
42
|
if (capabilities.referencesProvider) actions.push("references(file,line,char)");
|
|
43
|
+
if (capabilities.implementationProvider) actions.push("implementation(file,line,char)");
|
|
42
44
|
if (capabilities.documentSymbolProvider) actions.push("symbols(file)");
|
|
45
|
+
if (capabilities.workspaceSymbolProvider) actions.push("workspace_symbols(query)");
|
|
43
46
|
if (capabilities.renameProvider) actions.push("rename(file,line,char,newName)");
|
|
44
47
|
if (capabilities.codeActionProvider) actions.push("code_actions(file,line,char)");
|
|
45
48
|
return actions;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
+
import type { Diagnostic, FileEvent } from "../config/types.ts";
|
|
1
2
|
import {
|
|
2
3
|
assessStaleDiagnostics,
|
|
3
4
|
type StaleDiagnosticAssessment,
|
|
4
5
|
} from "../diagnostics/stale-diagnostics.ts";
|
|
5
|
-
import type { Diagnostic, FileEvent } from "../types.ts";
|
|
6
6
|
|
|
7
7
|
export interface WorkspaceRecoveryResult {
|
|
8
8
|
refreshedClients: number;
|
|
@@ -1,18 +1,170 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { walkProject } from "@mrclrchtr/supi-core/api";
|
|
1
4
|
import type { LspClient } from "../client/client.ts";
|
|
2
|
-
import type {
|
|
5
|
+
import type {
|
|
6
|
+
DocumentSymbol,
|
|
7
|
+
Position,
|
|
8
|
+
SymbolInformation,
|
|
9
|
+
WorkspaceSymbol,
|
|
10
|
+
} from "../config/types.ts";
|
|
3
11
|
|
|
4
|
-
|
|
12
|
+
type WorkspaceSymbolLike = SymbolInformation | WorkspaceSymbol;
|
|
13
|
+
|
|
14
|
+
const SKIP_DIRS = new Set(["node_modules", ".git", ".pnpm", "dist", "build", "coverage"]);
|
|
15
|
+
const DEFAULT_WARM_FILE_DEPTH = 4;
|
|
16
|
+
const DEFAULT_MARKER_SCAN_DEPTH = 6;
|
|
17
|
+
|
|
18
|
+
export interface WorkspaceSymbolWarmTarget {
|
|
19
|
+
projectRoot: string;
|
|
20
|
+
file: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface WorkspaceSymbolWarmOptions {
|
|
24
|
+
maxFileDepth?: number;
|
|
25
|
+
maxMarkerDepth?: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function getWorkspaceSymbolWarmPosition(
|
|
29
|
+
symbols: DocumentSymbol[] | SymbolInformation[] | null,
|
|
30
|
+
): Position | null {
|
|
31
|
+
if (!symbols || symbols.length === 0) return null;
|
|
32
|
+
const first = symbols[0];
|
|
33
|
+
if ("selectionRange" in first) {
|
|
34
|
+
return first.selectionRange.start;
|
|
35
|
+
}
|
|
36
|
+
if ("location" in first) {
|
|
37
|
+
return first.location.range.start;
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export async function collectWorkspaceSymbols(
|
|
5
43
|
clients: Iterable<LspClient>,
|
|
6
44
|
query: string,
|
|
7
|
-
): Promise<
|
|
8
|
-
const
|
|
45
|
+
): Promise<{ results: WorkspaceSymbolLike[]; hasSupport: boolean }> {
|
|
46
|
+
const results: WorkspaceSymbolLike[] = [];
|
|
9
47
|
let hasSupport = false;
|
|
48
|
+
|
|
10
49
|
for (const client of clients) {
|
|
11
50
|
if (client.status !== "running") continue;
|
|
12
51
|
if (!client.serverCapabilities?.workspaceSymbolProvider) continue;
|
|
13
52
|
hasSupport = true;
|
|
14
53
|
const result = await client.workspaceSymbol(query);
|
|
15
|
-
if (result)
|
|
54
|
+
if (result) results.push(...result);
|
|
16
55
|
}
|
|
17
|
-
|
|
56
|
+
|
|
57
|
+
return { results, hasSupport };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export async function managerWorkspaceSymbol(
|
|
61
|
+
clients: Iterable<LspClient>,
|
|
62
|
+
query: string,
|
|
63
|
+
): Promise<WorkspaceSymbolLike[] | null> {
|
|
64
|
+
const { results, hasSupport } = await collectWorkspaceSymbols(clients, query);
|
|
65
|
+
return hasSupport ? results : null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function findWorkspaceSymbolWarmTargets(
|
|
69
|
+
root: string,
|
|
70
|
+
rootMarkers: string[],
|
|
71
|
+
fileTypes: string[],
|
|
72
|
+
options: WorkspaceSymbolWarmOptions = {},
|
|
73
|
+
): WorkspaceSymbolWarmTarget[] {
|
|
74
|
+
const resolvedRoot = path.resolve(root);
|
|
75
|
+
const allowed = new Set(fileTypes.map((fileType) => fileType.toLowerCase()));
|
|
76
|
+
const maxFileDepth = options.maxFileDepth ?? DEFAULT_WARM_FILE_DEPTH;
|
|
77
|
+
const maxMarkerDepth = options.maxMarkerDepth ?? DEFAULT_MARKER_SCAN_DEPTH;
|
|
78
|
+
if (allowed.size === 0) return [];
|
|
79
|
+
|
|
80
|
+
const markerRoots = collectMarkerRoots(resolvedRoot, rootMarkers, maxMarkerDepth);
|
|
81
|
+
const targets = markerRoots
|
|
82
|
+
.map((entry) => {
|
|
83
|
+
const file = findWarmFileRecursive(entry.root, allowed, maxFileDepth);
|
|
84
|
+
return file ? { projectRoot: entry.root, file, priority: entry.priority } : null;
|
|
85
|
+
})
|
|
86
|
+
.filter((entry): entry is { projectRoot: string; file: string; priority: number } =>
|
|
87
|
+
Boolean(entry),
|
|
88
|
+
)
|
|
89
|
+
.sort(
|
|
90
|
+
(a, b) =>
|
|
91
|
+
a.priority - b.priority ||
|
|
92
|
+
a.projectRoot.length - b.projectRoot.length ||
|
|
93
|
+
a.projectRoot.localeCompare(b.projectRoot),
|
|
94
|
+
)
|
|
95
|
+
.map(({ projectRoot, file }) => ({ projectRoot, file }));
|
|
96
|
+
|
|
97
|
+
if (targets.length > 0) return targets;
|
|
98
|
+
|
|
99
|
+
const fallback = findWarmFileRecursive(resolvedRoot, allowed, maxFileDepth);
|
|
100
|
+
return fallback ? [{ projectRoot: resolvedRoot, file: fallback }] : [];
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
interface MarkerRootEntry {
|
|
104
|
+
root: string;
|
|
105
|
+
priority: number;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function collectMarkerRoots(
|
|
109
|
+
root: string,
|
|
110
|
+
rootMarkers: string[],
|
|
111
|
+
maxDepth: number,
|
|
112
|
+
): MarkerRootEntry[] {
|
|
113
|
+
if (rootMarkers.length === 0) return [];
|
|
114
|
+
|
|
115
|
+
const markerIndex = new Map(rootMarkers.map((marker, index) => [marker, index]));
|
|
116
|
+
const matches = new Map<string, number>();
|
|
117
|
+
|
|
118
|
+
walkProject(root, maxDepth, (directory, entryNames) => {
|
|
119
|
+
const matchedPriority = rootMarkers.reduce<number | null>((best, marker) => {
|
|
120
|
+
if (!entryNames.has(marker)) return best;
|
|
121
|
+
const next = markerIndex.get(marker) ?? Number.MAX_SAFE_INTEGER;
|
|
122
|
+
return best === null ? next : Math.min(best, next);
|
|
123
|
+
}, null);
|
|
124
|
+
|
|
125
|
+
if (matchedPriority === null) return;
|
|
126
|
+
const resolvedDirectory = path.resolve(directory);
|
|
127
|
+
const existing = matches.get(resolvedDirectory);
|
|
128
|
+
if (existing === undefined || matchedPriority < existing) {
|
|
129
|
+
matches.set(resolvedDirectory, matchedPriority);
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
return Array.from(matches.entries()).map(([matchedRoot, priority]) => ({
|
|
134
|
+
root: matchedRoot,
|
|
135
|
+
priority,
|
|
136
|
+
}));
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function findWarmFileRecursive(
|
|
140
|
+
directory: string,
|
|
141
|
+
allowed: Set<string>,
|
|
142
|
+
depth: number,
|
|
143
|
+
): string | null {
|
|
144
|
+
let entries: fs.Dirent[];
|
|
145
|
+
try {
|
|
146
|
+
entries = fs.readdirSync(directory, { withFileTypes: true });
|
|
147
|
+
} catch {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const sortedEntries = [...entries].sort((a, b) => a.name.localeCompare(b.name));
|
|
152
|
+
|
|
153
|
+
for (const entry of sortedEntries) {
|
|
154
|
+
if (!entry.isFile()) continue;
|
|
155
|
+
const ext = path.extname(entry.name).slice(1).toLowerCase();
|
|
156
|
+
if (!allowed.has(ext)) continue;
|
|
157
|
+
return path.join(directory, entry.name);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (depth <= 0) return null;
|
|
161
|
+
|
|
162
|
+
for (const entry of sortedEntries) {
|
|
163
|
+
if (!entry.isDirectory()) continue;
|
|
164
|
+
if (SKIP_DIRS.has(entry.name)) continue;
|
|
165
|
+
const nested = findWarmFileRecursive(path.join(directory, entry.name), allowed, depth - 1);
|
|
166
|
+
if (nested) return nested;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return null;
|
|
18
170
|
}
|