@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
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
// biome-ignore-all lint/nursery/noExcessiveLinesPerFile: file-level and single-target affected flows share helpers to keep the blast-radius logic in one place
|
|
3
3
|
|
|
4
4
|
import * as path from "node:path";
|
|
5
|
-
import { getSessionLspService } from "@mrclrchtr/supi-lsp/api";
|
|
6
5
|
import { buildArchitectureModel, findModuleForPath, getDependents } from "../architecture.ts";
|
|
7
6
|
import {
|
|
8
7
|
appendPrioritySignalsSection,
|
|
9
8
|
summarizePrioritySignalsForFiles,
|
|
10
9
|
} from "../prioritization-signals.ts";
|
|
10
|
+
import { getSemanticService } from "../providers/semantic-provider.ts";
|
|
11
11
|
import { resolveTarget } from "../resolve-target.ts";
|
|
12
12
|
import {
|
|
13
13
|
escapeRegex,
|
|
@@ -188,12 +188,12 @@ async function gatherReferences(
|
|
|
188
188
|
params: ActionParams,
|
|
189
189
|
cwd: string,
|
|
190
190
|
): Promise<{ refs: GatheredRef[]; confidence: ConfidenceMode; externalCount: number }> {
|
|
191
|
-
const
|
|
191
|
+
const lsp = await getSemanticService(cwd, { waitForReady: true });
|
|
192
192
|
const refs: GatheredRef[] = [];
|
|
193
193
|
let externalCount = 0;
|
|
194
194
|
|
|
195
|
-
if (
|
|
196
|
-
const lspRefs = await
|
|
195
|
+
if (lsp) {
|
|
196
|
+
const lspRefs = await lsp.references(target.file, target.position);
|
|
197
197
|
if (lspRefs && lspRefs.length > 0) {
|
|
198
198
|
const filtered = filterOutDeclaration(lspRefs, target.file, target.position);
|
|
199
199
|
for (const ref of filtered) {
|
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
import * as fs from "node:fs";
|
|
4
4
|
import * as path from "node:path";
|
|
5
|
-
import {
|
|
5
|
+
import type { TreeSitterSession } from "@mrclrchtr/supi-tree-sitter/api";
|
|
6
6
|
import { buildArchitectureModel, findModuleForPath } from "../architecture.ts";
|
|
7
7
|
import { generateFocusedBrief, generateProjectBrief } from "../brief.ts";
|
|
8
|
+
import { withStructuralSession } from "../providers/structural-provider.ts";
|
|
8
9
|
import { normalizePath } from "../search-helpers.ts";
|
|
9
10
|
import type { ActionParams } from "../tool-actions.ts";
|
|
10
11
|
import type { CodeIntelResult } from "../types.ts";
|
|
@@ -108,23 +109,21 @@ interface TreeSitterContextInput {
|
|
|
108
109
|
|
|
109
110
|
async function addTreeSitterContext(input: TreeSitterContextInput): Promise<void> {
|
|
110
111
|
const { lines, relPath, line1, char1, cwd } = input;
|
|
111
|
-
let tsSession: ReturnType<typeof createTreeSitterSession> | null = null;
|
|
112
112
|
try {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
113
|
+
await withStructuralSession(cwd, async (tsSession) => {
|
|
114
|
+
await addNodeContext(lines, tsSession, relPath, { line: line1, char: char1 });
|
|
115
|
+
await addOutlineContext(lines, tsSession, relPath, line1);
|
|
116
|
+
await addImportsContext(lines, tsSession, relPath);
|
|
117
|
+
await addExportsContext(lines, tsSession, relPath);
|
|
118
|
+
});
|
|
118
119
|
} catch {
|
|
119
120
|
// Tree-sitter not available
|
|
120
|
-
} finally {
|
|
121
|
-
tsSession?.dispose();
|
|
122
121
|
}
|
|
123
122
|
}
|
|
124
123
|
|
|
125
124
|
async function addNodeContext(
|
|
126
125
|
lines: string[],
|
|
127
|
-
ts:
|
|
126
|
+
ts: TreeSitterSession,
|
|
128
127
|
relPath: string,
|
|
129
128
|
pos: { line: number; char: number },
|
|
130
129
|
): Promise<void> {
|
|
@@ -144,7 +143,7 @@ async function addNodeContext(
|
|
|
144
143
|
|
|
145
144
|
async function addOutlineContext(
|
|
146
145
|
lines: string[],
|
|
147
|
-
ts:
|
|
146
|
+
ts: TreeSitterSession,
|
|
148
147
|
relPath: string,
|
|
149
148
|
line1: number,
|
|
150
149
|
): Promise<void> {
|
|
@@ -183,7 +182,7 @@ function getOutlinePrefix(kind: string): string {
|
|
|
183
182
|
|
|
184
183
|
async function addImportsContext(
|
|
185
184
|
lines: string[],
|
|
186
|
-
ts:
|
|
185
|
+
ts: TreeSitterSession,
|
|
187
186
|
relPath: string,
|
|
188
187
|
): Promise<void> {
|
|
189
188
|
const result = await ts.imports(relPath);
|
|
@@ -201,7 +200,7 @@ async function addImportsContext(
|
|
|
201
200
|
|
|
202
201
|
async function addExportsContext(
|
|
203
202
|
lines: string[],
|
|
204
|
-
ts:
|
|
203
|
+
ts: TreeSitterSession,
|
|
205
204
|
relPath: string,
|
|
206
205
|
): Promise<void> {
|
|
207
206
|
const result = await ts.exports(relPath);
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// extraction to @mrclrchtr/supi-tree-sitter/api.
|
|
4
4
|
|
|
5
5
|
import * as path from "node:path";
|
|
6
|
-
import {
|
|
6
|
+
import { withStructuralSession } from "../providers/structural-provider.ts";
|
|
7
7
|
import { resolveTarget } from "../resolve-target.ts";
|
|
8
8
|
import { isResolvedTargetGroup } from "../semantic-action-helpers.ts";
|
|
9
9
|
import type { ActionParams } from "../tool-actions.ts";
|
|
@@ -47,11 +47,11 @@ export async function executeCalleesAction(
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
const relPath = path.relative(cwd, target.file);
|
|
50
|
-
let tsSession: ReturnType<typeof createTreeSitterSession> | null = null;
|
|
51
50
|
|
|
52
51
|
try {
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
const result = await withStructuralSession(cwd, (tsSession) =>
|
|
53
|
+
tsSession.calleesAt(relPath, target.displayLine, target.displayCharacter),
|
|
54
|
+
);
|
|
55
55
|
|
|
56
56
|
if (result.kind !== "success") {
|
|
57
57
|
return {
|
|
@@ -63,7 +63,9 @@ export async function executeCalleesAction(
|
|
|
63
63
|
scope: null,
|
|
64
64
|
candidateCount: 0,
|
|
65
65
|
omittedCount: 0,
|
|
66
|
-
nextQueries: [
|
|
66
|
+
nextQueries: [
|
|
67
|
+
'Use `lsp_lookup` with `kind: "hover"` for type-aware analysis on this file',
|
|
68
|
+
],
|
|
67
69
|
},
|
|
68
70
|
},
|
|
69
71
|
};
|
|
@@ -95,7 +97,9 @@ export async function executeCalleesAction(
|
|
|
95
97
|
scope: null,
|
|
96
98
|
candidateCount: callees.length,
|
|
97
99
|
omittedCount: Math.max(0, callees.length - (params.maxResults ?? 8)),
|
|
98
|
-
nextQueries: [
|
|
100
|
+
nextQueries: [
|
|
101
|
+
'Use `lsp_lookup` with `kind: "hover"` for precise type information on callees',
|
|
102
|
+
],
|
|
99
103
|
};
|
|
100
104
|
return { content, details: { type: "search" as const, data: details } };
|
|
101
105
|
} catch {
|
|
@@ -108,12 +112,12 @@ export async function executeCalleesAction(
|
|
|
108
112
|
scope: null,
|
|
109
113
|
candidateCount: 0,
|
|
110
114
|
omittedCount: 0,
|
|
111
|
-
nextQueries: [
|
|
115
|
+
nextQueries: [
|
|
116
|
+
'Use `lsp_lookup` with `kind: "hover"` for type-aware analysis on this file',
|
|
117
|
+
],
|
|
112
118
|
},
|
|
113
119
|
},
|
|
114
120
|
};
|
|
115
|
-
} finally {
|
|
116
|
-
tsSession?.dispose();
|
|
117
121
|
}
|
|
118
122
|
}
|
|
119
123
|
|
|
@@ -140,7 +144,7 @@ function formatCallees(
|
|
|
140
144
|
}
|
|
141
145
|
lines.push("");
|
|
142
146
|
lines.push(
|
|
143
|
-
|
|
147
|
+
'_Structural analysis — may include unresolved or qualified names. Use `lsp_lookup` with `kind: "hover"` for precise type information._',
|
|
144
148
|
);
|
|
145
149
|
lines.push("");
|
|
146
150
|
return lines.join("\n");
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Callers action — find call sites for a symbol.
|
|
2
2
|
|
|
3
3
|
import * as path from "node:path";
|
|
4
|
-
import {
|
|
4
|
+
import { getSemanticService } from "../providers/semantic-provider.ts";
|
|
5
5
|
import { resolveTarget } from "../resolve-target.ts";
|
|
6
6
|
import {
|
|
7
7
|
escapeRegex,
|
|
@@ -169,10 +169,10 @@ async function collectCallerRefs(
|
|
|
169
169
|
params: ActionParams,
|
|
170
170
|
cwd: string,
|
|
171
171
|
): Promise<CallerCollection> {
|
|
172
|
-
const
|
|
172
|
+
const lsp = await getSemanticService(cwd, { waitForReady: true });
|
|
173
173
|
|
|
174
|
-
if (
|
|
175
|
-
const refs = await
|
|
174
|
+
if (lsp) {
|
|
175
|
+
const refs = await lsp.references(target.file, target.position);
|
|
176
176
|
if (refs && refs.length > 0) {
|
|
177
177
|
const filtered = filterOutDeclaration(refs, target.file, target.position);
|
|
178
178
|
const projectRefs: CallerRef[] = [];
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Implementations action — find concrete implementations via LSP or heuristic.
|
|
2
2
|
|
|
3
3
|
import * as path from "node:path";
|
|
4
|
-
import {
|
|
4
|
+
import { getSemanticService } from "../providers/semantic-provider.ts";
|
|
5
5
|
import { resolveTarget } from "../resolve-target.ts";
|
|
6
6
|
import {
|
|
7
7
|
escapeRegex,
|
|
@@ -52,11 +52,11 @@ export async function executeImplementationsAction(
|
|
|
52
52
|
};
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
const
|
|
55
|
+
const lsp = await getSemanticService(cwd, { waitForReady: true });
|
|
56
56
|
const relPath = path.relative(cwd, target.file);
|
|
57
57
|
|
|
58
|
-
if (
|
|
59
|
-
const impls = await
|
|
58
|
+
if (lsp) {
|
|
59
|
+
const impls = await lsp.implementation(target.file, target.position);
|
|
60
60
|
if (impls) {
|
|
61
61
|
const locations = Array.isArray(impls) ? impls : [impls];
|
|
62
62
|
if (locations.length > 0) {
|
package/src/code-intelligence.ts
CHANGED
|
@@ -6,7 +6,7 @@ import type { BeforeAgentStartEventResult, ExtensionAPI } from "@earendil-works/
|
|
|
6
6
|
import { Type } from "typebox";
|
|
7
7
|
import { buildArchitectureModel } from "./architecture.ts";
|
|
8
8
|
import { generateOverview } from "./brief.ts";
|
|
9
|
-
import { promptGuidelines, promptSnippet, toolDescription } from "./guidance.ts";
|
|
9
|
+
import { promptGuidelines, promptSnippet, toolDescription } from "./tool/guidance.ts";
|
|
10
10
|
import { type CodeIntelAction, executeAction } from "./tool-actions.ts";
|
|
11
11
|
|
|
12
12
|
const OVERVIEW_CUSTOM_TYPE = "code-intelligence-overview";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
-
import {
|
|
3
|
+
import { withStructuralSession } from "./providers/structural-provider.ts";
|
|
4
4
|
import type { ActionParams } from "./tool-actions.ts";
|
|
5
5
|
|
|
6
6
|
export const STRUCTURED_PATTERN_FILE_CAP = 200;
|
|
@@ -42,31 +42,29 @@ export async function getStructuredPatternMatches(
|
|
|
42
42
|
return matcher;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
let tsSession: ReturnType<typeof createTreeSitterSession> | null = null;
|
|
46
45
|
try {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
46
|
+
return await withStructuralSession(cwd, async (tsSession) => {
|
|
47
|
+
const matches: StructuredMatch[] = [];
|
|
48
|
+
let timedOut = collected.timedOut;
|
|
49
|
+
|
|
50
|
+
for (const [index, file] of collected.files.entries()) {
|
|
51
|
+
if (Date.now() > deadline) {
|
|
52
|
+
collected.omittedCount += collected.files.length - index;
|
|
53
|
+
timedOut = true;
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
const relFile = path.relative(cwd, file);
|
|
57
|
+
await collectMatchesForFile(matches, tsSession, relFile, params.kind, matcher);
|
|
56
58
|
}
|
|
57
|
-
const relFile = path.relative(cwd, file);
|
|
58
|
-
await collectMatchesForFile(matches, tsSession, relFile, params.kind, matcher);
|
|
59
|
-
}
|
|
60
59
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
60
|
+
return {
|
|
61
|
+
matches,
|
|
62
|
+
omittedCount: timedOut ? Math.max(1, collected.omittedCount) : collected.omittedCount,
|
|
63
|
+
partialReason: timedOut ? "timeout" : collected.omittedCount > 0 ? "file-cap" : null,
|
|
64
|
+
};
|
|
65
|
+
});
|
|
66
66
|
} catch {
|
|
67
67
|
return `No structured ${params.kind} search data available in \`${relScope}\`. Try omitting \`kind\` for plain text search.`;
|
|
68
|
-
} finally {
|
|
69
|
-
tsSession?.dispose();
|
|
70
68
|
}
|
|
71
69
|
}
|
|
72
70
|
|
|
@@ -74,7 +72,7 @@ export async function getStructuredPatternMatches(
|
|
|
74
72
|
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: kind-specific tree-sitter matching is clearest as one helper
|
|
75
73
|
async function collectMatchesForFile(
|
|
76
74
|
matches: StructuredMatch[],
|
|
77
|
-
tsSession:
|
|
75
|
+
tsSession: import("@mrclrchtr/supi-tree-sitter/api").TreeSitterSession,
|
|
78
76
|
relFile: string,
|
|
79
77
|
kind: StructuredPatternKind,
|
|
80
78
|
matcher: (value: string) => boolean,
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getSessionLspService,
|
|
3
|
+
type SessionLspService,
|
|
4
|
+
type SessionLspServiceState,
|
|
5
|
+
waitForSessionLspService,
|
|
6
|
+
} from "@mrclrchtr/supi-lsp/api";
|
|
7
|
+
|
|
8
|
+
export interface SemanticProviderOptions {
|
|
9
|
+
waitForReady?: boolean;
|
|
10
|
+
timeoutMs?: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const DEFAULT_SEMANTIC_WAIT_MS = 250;
|
|
14
|
+
|
|
15
|
+
/** Acquire the current session-scoped LSP service, optionally waiting for startup. */
|
|
16
|
+
export async function getSemanticService(
|
|
17
|
+
cwd: string,
|
|
18
|
+
options: SemanticProviderOptions = {},
|
|
19
|
+
): Promise<SessionLspService | null> {
|
|
20
|
+
const state = await getSemanticServiceState(cwd, options);
|
|
21
|
+
return state.kind === "ready" ? state.service : null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Read the current LSP service state with an optional short wait for readiness. */
|
|
25
|
+
export async function getSemanticServiceState(
|
|
26
|
+
cwd: string,
|
|
27
|
+
options: SemanticProviderOptions = {},
|
|
28
|
+
): Promise<SessionLspServiceState> {
|
|
29
|
+
const current = getSessionLspService(cwd);
|
|
30
|
+
if (!options.waitForReady || current.kind !== "pending") {
|
|
31
|
+
return current;
|
|
32
|
+
}
|
|
33
|
+
return waitForSessionLspService(cwd, options.timeoutMs ?? DEFAULT_SEMANTIC_WAIT_MS);
|
|
34
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { createTreeSitterSession, type TreeSitterSession } from "@mrclrchtr/supi-tree-sitter/api";
|
|
2
|
+
|
|
3
|
+
/** Run work against a short-lived Tree-sitter session and dispose it afterward. */
|
|
4
|
+
export async function withStructuralSession<T>(
|
|
5
|
+
cwd: string,
|
|
6
|
+
fn: (session: TreeSitterSession) => Promise<T>,
|
|
7
|
+
): Promise<T> {
|
|
8
|
+
const session = createTreeSitterSession(cwd);
|
|
9
|
+
try {
|
|
10
|
+
return await fn(session);
|
|
11
|
+
} finally {
|
|
12
|
+
session.dispose();
|
|
13
|
+
}
|
|
14
|
+
}
|
package/src/target-resolution.ts
CHANGED
|
@@ -5,12 +5,9 @@
|
|
|
5
5
|
import * as fs from "node:fs";
|
|
6
6
|
import * as path from "node:path";
|
|
7
7
|
import { isWithinOrEqual } from "@mrclrchtr/supi-core/api";
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
type SessionLspService,
|
|
12
|
-
} from "@mrclrchtr/supi-lsp/api";
|
|
13
|
-
import { createTreeSitterSession } from "@mrclrchtr/supi-tree-sitter/api";
|
|
8
|
+
import { type Position, type SessionLspService, toLspPosition } from "@mrclrchtr/supi-lsp/api";
|
|
9
|
+
import { getSemanticService, getSemanticServiceState } from "./providers/semantic-provider.ts";
|
|
10
|
+
import { withStructuralSession } from "./providers/structural-provider.ts";
|
|
14
11
|
import { escapeRegex, normalizePath } from "./search-helpers.ts";
|
|
15
12
|
import { highestConfidence } from "./semantic-action-helpers.ts";
|
|
16
13
|
import type { ConfidenceMode, DisambiguationCandidate } from "./types.ts";
|
|
@@ -46,7 +43,7 @@ export { normalizePath } from "./search-helpers.ts";
|
|
|
46
43
|
* Convert 1-based public coordinates to 0-based LSP Position.
|
|
47
44
|
*/
|
|
48
45
|
export function toZeroBased(line: number, character: number): Position {
|
|
49
|
-
return
|
|
46
|
+
return toLspPosition(line, character);
|
|
50
47
|
}
|
|
51
48
|
|
|
52
49
|
/**
|
|
@@ -146,16 +143,12 @@ export async function resolveSymbolTarget(
|
|
|
146
143
|
exportedOnly?: boolean;
|
|
147
144
|
},
|
|
148
145
|
): Promise<TargetResolutionResult> {
|
|
149
|
-
const lspState =
|
|
146
|
+
const lspState = await getSemanticServiceState(cwd, { waitForReady: true });
|
|
150
147
|
|
|
151
148
|
if (lspState.kind === "ready") {
|
|
152
149
|
return resolveSymbolViaLsp(symbol, cwd, lspState.service, options);
|
|
153
150
|
}
|
|
154
151
|
|
|
155
|
-
if (lspState.kind === "pending") {
|
|
156
|
-
// In v1, we may wait for LSP. For now, try structural fallback.
|
|
157
|
-
}
|
|
158
|
-
|
|
159
152
|
// Structural fallback via text search
|
|
160
153
|
return resolveSymbolViaSearch(symbol, cwd, options);
|
|
161
154
|
}
|
|
@@ -336,10 +329,10 @@ async function resolveFileTargetsViaLsp(
|
|
|
336
329
|
cwd: string,
|
|
337
330
|
structuralTargets: ResolvedTarget[] | null,
|
|
338
331
|
): Promise<ResolvedTarget[] | null> {
|
|
339
|
-
const
|
|
340
|
-
if (
|
|
332
|
+
const lsp = await getSemanticService(cwd, { waitForReady: true });
|
|
333
|
+
if (!lsp) return null;
|
|
341
334
|
|
|
342
|
-
const symbols = await
|
|
335
|
+
const symbols = await lsp.documentSymbols(resolvedFile);
|
|
343
336
|
if (!symbols || symbols.length === 0) {
|
|
344
337
|
return structuralTargets;
|
|
345
338
|
}
|
|
@@ -378,30 +371,28 @@ async function resolveFileTargetsViaTreeSitter(
|
|
|
378
371
|
resolvedFile: string,
|
|
379
372
|
cwd: string,
|
|
380
373
|
): Promise<ResolvedTarget[] | null> {
|
|
381
|
-
let tsSession: ReturnType<typeof createTreeSitterSession> | null = null;
|
|
382
374
|
try {
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
375
|
+
return await withStructuralSession(cwd, async (tsSession) => {
|
|
376
|
+
const exportsResult = await tsSession.exports(relPath);
|
|
377
|
+
if (exportsResult.kind !== "success" || exportsResult.data.length === 0) {
|
|
378
|
+
return null;
|
|
379
|
+
}
|
|
388
380
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
381
|
+
return dedupeTargets(
|
|
382
|
+
exportsResult.data.map((record) =>
|
|
383
|
+
createResolvedTarget({
|
|
384
|
+
file: resolvedFile,
|
|
385
|
+
line: record.range.startLine,
|
|
386
|
+
character: record.range.startCharacter,
|
|
387
|
+
name: record.name,
|
|
388
|
+
kind: record.kind,
|
|
389
|
+
confidence: "structural",
|
|
390
|
+
}),
|
|
391
|
+
),
|
|
392
|
+
);
|
|
393
|
+
});
|
|
401
394
|
} catch {
|
|
402
395
|
return null;
|
|
403
|
-
} finally {
|
|
404
|
-
tsSession?.dispose();
|
|
405
396
|
}
|
|
406
397
|
}
|
|
407
398
|
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Prompt guidance and tool description for the code_intel tool.
|
|
2
|
+
|
|
3
|
+
export const toolDescription = `Code intelligence tool — codebase orientation, semantic relationships, impact analysis, and scoped search.
|
|
4
|
+
|
|
5
|
+
Actions: brief, callers, callees, implementations, affected, pattern, index.
|
|
6
|
+
|
|
7
|
+
Use code_intel to localize relevant files before precise drill-down: summarize a project/package/file, find callers/callees/implementations, estimate blast radius, or search within a scope. Prefer lsp_lookup, lsp_document_symbols, lsp_workspace_symbols, lsp_diagnostics, lsp_refactor, and lsp_recover for semantic drill-down once the target is known; use tree_sitter for exact syntax and read/rg once you know the file. line and character are 1-based and require file. pattern is literal unless regex is true; kind supports definition, export, or import. Relative paths resolve from cwd, and leading @ on path/file is stripped.`;
|
|
8
|
+
|
|
9
|
+
export const promptGuidelines = [
|
|
10
|
+
'Use code_intel with `action: "brief"` for a project, package, directory, file, or anchored-position brief before opening more files.',
|
|
11
|
+
'Use code_intel with `action: "index"` for a project map, top-level directories, language mix, or landmark files.',
|
|
12
|
+
'Use code_intel with `action: "callers"` or `action: "implementations"` to find who invokes a symbol or which concrete types implement a declaration.',
|
|
13
|
+
'Use code_intel with `action: "callees"` for outgoing calls from a function or method at a known `file`, `line`, and `character`.',
|
|
14
|
+
'Use code_intel with `action: "affected"` before edits for blast radius, downstream modules, risk, and likely follow-up checks or tests.',
|
|
15
|
+
'Use code_intel with `action: "pattern"` for bounded search within a path; `pattern` is literal by default, set `regex: true` for regex, and use `kind: "definition" | "export" | "import"` for structured search.',
|
|
16
|
+
"Use code_intel with `file`, `line`, and `character` for anchored positions; do not pair `line` or `character` with `path`.",
|
|
17
|
+
"Use code_intel first when the area is not yet localized; switch to lsp_lookup, lsp_document_symbols, lsp_workspace_symbols, lsp_diagnostics, lsp_refactor, or lsp_recover for semantic drill-down once code_intel narrows the target.",
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
export const promptSnippet =
|
|
21
|
+
"code_intel — codebase orientation, callers/callees, blast radius, and scoped search before file drill-down";
|
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
import * as path from "node:path";
|
|
2
|
-
import { splitSuppressionDiagnostics } from "./diagnostics/suppression-diagnostics.ts";
|
|
3
|
-
import type { OutstandingDiagnosticSummaryEntry } from "./manager/manager-types.ts";
|
|
4
|
-
import type { Diagnostic, ProjectServerInfo } from "./types.ts";
|
|
5
|
-
|
|
6
|
-
export const lspPromptSnippet =
|
|
7
|
-
"Use semantic code intelligence for hover, definitions, references, symbols, rename planning, code actions, and diagnostics in supported languages.";
|
|
8
|
-
|
|
9
|
-
export const lspPromptGuidelines = [
|
|
10
|
-
"Prefer the lsp tool over bash text search for supported source files when the task is semantic code navigation or diagnostics.",
|
|
11
|
-
"Use lsp for hover, definitions, references, document symbols, rename planning, code actions, and diagnostics before falling back to grep-style shell search.",
|
|
12
|
-
"Fall back to bash/read when LSP is unavailable, the file type is unsupported, or the task is plain-text search across docs, config files, or string literals.",
|
|
13
|
-
"Diagnostics are automatically delivered: inline after every write/edit tool result, and as context before each agent turn. You do not need to call the lsp tool to check them — they are already in your context.",
|
|
14
|
-
"When delivered diagnostics show errors, decide: (a) expected temporary state from a planned multi-step change — continue your sequence, then verify at the end; (b) unexpected 'Cannot find module', unresolved imports, or type mismatches — stop and fix the root cause before editing more files.",
|
|
15
|
-
"When the SAME error pattern appears across MULTIPLE files after you changed imports, dependencies, or shared types, it is a systemic root-cause issue (missing install, broken import path, wrong dependency version). Do not patch each file individually — find and fix the root cause first.",
|
|
16
|
-
"When diagnostics look stale after package.json, lockfile, tsconfig, or generated-type changes, use lsp recover before editing more files.",
|
|
17
|
-
"After changing package.json dependencies, imports, or peer dependencies, run the package manager install command (e.g., pnpm install) before concluding that module resolution errors are real code bugs.",
|
|
18
|
-
];
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Build per-project `promptGuidelines` for the `lsp` tool registration.
|
|
22
|
-
* These guidelines are part of pi's stable system prompt after session-start
|
|
23
|
-
* tool registration, avoiding per-turn `before_agent_start` prompt overrides.
|
|
24
|
-
*/
|
|
25
|
-
export function buildProjectGuidelines(servers: ProjectServerInfo[], cwd: string): string[] {
|
|
26
|
-
const dynamic = servers.map((server) => {
|
|
27
|
-
const root = displayRoot(server.root, cwd);
|
|
28
|
-
const fileTypes = server.fileTypes.map((entry) => `.${entry}`).join(", ");
|
|
29
|
-
const actions = server.supportedActions.join(", ");
|
|
30
|
-
const status = server.status === "running" ? "active" : "unavailable";
|
|
31
|
-
const actionText = actions.length > 0 ? ` | actions: ${actions}` : "";
|
|
32
|
-
return `LSP ${status}: ${server.name} | root: ${root} | files: ${fileTypes}${actionText}`;
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
return [
|
|
36
|
-
...lspPromptGuidelines.slice(0, 2),
|
|
37
|
-
"Use lsp before grep/rg/find for understanding code, finding usages, diagnostics, symbol lookup, and refactors in supported languages.",
|
|
38
|
-
...dynamic,
|
|
39
|
-
"Use lsp actions by task: hover/definition/references/symbols for understanding code, references/workspace_symbol/search for usages, diagnostics/hover/code_actions for issues, and rename/code_actions for refactors.",
|
|
40
|
-
...lspPromptGuidelines.slice(2),
|
|
41
|
-
].filter(Boolean);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export const MAX_DETAILED_DIAGNOSTICS = 5;
|
|
45
|
-
const MAX_DETAIL_LINES_PER_FILE = 3;
|
|
46
|
-
|
|
47
|
-
interface DetailedDiagnostics {
|
|
48
|
-
file: string;
|
|
49
|
-
diagnostics: Diagnostic[];
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export function formatDiagnosticsContext(
|
|
53
|
-
diagnostics: OutstandingDiagnosticSummaryEntry[],
|
|
54
|
-
maxFiles: number = 3,
|
|
55
|
-
detailed?: DetailedDiagnostics[],
|
|
56
|
-
staleWarning?: string | null,
|
|
57
|
-
): string | null {
|
|
58
|
-
if (diagnostics.length === 0) return null;
|
|
59
|
-
|
|
60
|
-
const totalDiags = diagnostics.reduce((sum, d) => sum + d.total, 0);
|
|
61
|
-
const detailMap = buildDetailMap(diagnostics, totalDiags, detailed);
|
|
62
|
-
|
|
63
|
-
const lines: string[] = [];
|
|
64
|
-
if (staleWarning) lines.push(staleWarning);
|
|
65
|
-
const visible = diagnostics.slice(0, maxFiles);
|
|
66
|
-
|
|
67
|
-
for (const entry of visible) {
|
|
68
|
-
lines.push(`- ${entry.file}: ${formatCounts(entry)}`);
|
|
69
|
-
appendDetailLines(lines, detailMap?.get(entry.file));
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const remaining = diagnostics.length - visible.length;
|
|
73
|
-
if (remaining > 0) {
|
|
74
|
-
lines.push(`- +${remaining} more file${remaining === 1 ? "" : "s"}`);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
appendSuppressionCleanup(
|
|
78
|
-
lines,
|
|
79
|
-
visible.map((entry) => entry.file),
|
|
80
|
-
detailMap,
|
|
81
|
-
);
|
|
82
|
-
|
|
83
|
-
return [
|
|
84
|
-
'<extension-context source="supi-lsp">',
|
|
85
|
-
"Outstanding diagnostics — fix these before proceeding:",
|
|
86
|
-
...lines,
|
|
87
|
-
"</extension-context>",
|
|
88
|
-
].join("\n");
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function buildDetailMap(
|
|
92
|
-
_diagnostics: OutstandingDiagnosticSummaryEntry[],
|
|
93
|
-
totalDiags: number,
|
|
94
|
-
detailed?: DetailedDiagnostics[],
|
|
95
|
-
): Map<string, Diagnostic[]> | null {
|
|
96
|
-
if (totalDiags > MAX_DETAILED_DIAGNOSTICS || !detailed || detailed.length === 0) return null;
|
|
97
|
-
return new Map(detailed.map((d) => [d.file, d.diagnostics]));
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function appendDetailLines(lines: string[], details?: Diagnostic[]): void {
|
|
101
|
-
if (!details) return;
|
|
102
|
-
for (const d of details.slice(0, MAX_DETAIL_LINES_PER_FILE)) {
|
|
103
|
-
const line = d.range.start.line + 1;
|
|
104
|
-
const char = d.range.start.character + 1;
|
|
105
|
-
const source = d.source ? ` ${d.source}` : "";
|
|
106
|
-
lines.push(` L${line} C${char}${source}: ${d.message}`);
|
|
107
|
-
}
|
|
108
|
-
if (details.length > MAX_DETAIL_LINES_PER_FILE) {
|
|
109
|
-
const extra = details.length - MAX_DETAIL_LINES_PER_FILE;
|
|
110
|
-
lines.push(` +${extra} more`);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
function appendSuppressionCleanup(
|
|
115
|
-
lines: string[],
|
|
116
|
-
visibleFiles: string[],
|
|
117
|
-
detailMap: Map<string, Diagnostic[]> | null,
|
|
118
|
-
): void {
|
|
119
|
-
if (!detailMap) return;
|
|
120
|
-
|
|
121
|
-
const suppressionLines: string[] = [];
|
|
122
|
-
for (const file of visibleFiles) {
|
|
123
|
-
const diagnostics = detailMap.get(file);
|
|
124
|
-
if (!diagnostics) continue;
|
|
125
|
-
|
|
126
|
-
const { suppressions } = splitSuppressionDiagnostics(diagnostics, 1);
|
|
127
|
-
if (suppressions.length === 0) continue;
|
|
128
|
-
|
|
129
|
-
suppressionLines.push(`- ${file}`);
|
|
130
|
-
appendDetailLines(suppressionLines, suppressions);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
if (suppressionLines.length === 0) return;
|
|
134
|
-
lines.push("", "Stale suppression comments — clean these up:", ...suppressionLines);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
export function diagnosticsContextFingerprint(content: string | null): string | null {
|
|
138
|
-
return content;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// reorderDiagnosticContextMessages, getContextToken, and findLastUserMessageIndex
|
|
142
|
-
// have been extracted to supi-core/context-messages.ts.
|
|
143
|
-
// Use pruneAndReorderContextMessages(messages, "lsp-context", activeToken) instead.
|
|
144
|
-
|
|
145
|
-
function formatCounts(entry: OutstandingDiagnosticSummaryEntry): string {
|
|
146
|
-
const counts: string[] = [];
|
|
147
|
-
if (entry.errors > 0) counts.push(pluralize(entry.errors, "error"));
|
|
148
|
-
if (entry.warnings > 0) counts.push(pluralize(entry.warnings, "warning"));
|
|
149
|
-
if (entry.information > 0) counts.push(pluralize(entry.information, "info"));
|
|
150
|
-
if (entry.hints > 0) counts.push(pluralize(entry.hints, "hint"));
|
|
151
|
-
return counts.join(", ");
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
function displayRoot(root: string, cwd: string): string {
|
|
155
|
-
const relative = path.relative(cwd, root);
|
|
156
|
-
if (relative === "") return ".";
|
|
157
|
-
if (relative.startsWith(`..${path.sep}`) || relative === "..") return root;
|
|
158
|
-
return relative.replaceAll(path.sep, "/");
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
function pluralize(count: number, word: string): string {
|
|
162
|
-
return `${count} ${word}${count === 1 ? "" : "s"}`;
|
|
163
|
-
}
|