@mrclrchtr/supi-code-intelligence 1.6.0 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -24
- package/node_modules/@mrclrchtr/supi-core/package.json +6 -2
- package/node_modules/@mrclrchtr/supi-core/src/api.ts +12 -1
- package/node_modules/@mrclrchtr/supi-core/src/config/config.ts +1 -1
- package/node_modules/@mrclrchtr/supi-core/src/index.ts +12 -1
- package/node_modules/@mrclrchtr/supi-core/src/tool-framework.ts +116 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/package.json +6 -2
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/api.ts +12 -1
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/config/config.ts +1 -1
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/index.ts +12 -1
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/tool-framework.ts +116 -0
- package/node_modules/@mrclrchtr/supi-lsp/package.json +9 -3
- package/node_modules/@mrclrchtr/supi-lsp/src/client/client.ts +8 -5
- package/node_modules/@mrclrchtr/supi-lsp/src/client/transport.ts +79 -190
- package/node_modules/@mrclrchtr/supi-lsp/src/config/server-config.ts +38 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/config/types.ts +61 -387
- package/node_modules/@mrclrchtr/supi-lsp/src/format.ts +16 -8
- package/node_modules/@mrclrchtr/supi-lsp/src/lsp.ts +2 -2
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-project-info.ts +1 -1
- package/node_modules/@mrclrchtr/supi-lsp/src/session/lsp-state.ts +1 -1
- package/node_modules/@mrclrchtr/supi-lsp/src/tool/guidance.ts +1 -1
- package/node_modules/@mrclrchtr/supi-lsp/src/tool/tool-specs.ts +1 -1
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/package.json +6 -2
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/api.ts +12 -1
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/config/config.ts +1 -1
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/index.ts +12 -1
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/tool-framework.ts +116 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/LICENSE +21 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/README.md +265 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/debug/web-tree-sitter.cjs +4661 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/debug/web-tree-sitter.cjs.map +7 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/debug/web-tree-sitter.js +4605 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/debug/web-tree-sitter.js.map +7 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/debug/web-tree-sitter.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/debug/web-tree-sitter.wasm.map +57 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/package.json +100 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/web-tree-sitter.cjs +4063 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/web-tree-sitter.cjs.map +7 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/web-tree-sitter.d.cts +1025 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/web-tree-sitter.d.cts.map +58 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/web-tree-sitter.d.ts +1025 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/web-tree-sitter.d.ts.map +58 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/web-tree-sitter.js +4007 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/web-tree-sitter.js.map +7 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/web-tree-sitter.wasm +0 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/web-tree-sitter/web-tree-sitter.wasm.map +55 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/package.json +2 -2
- package/package.json +4 -4
- package/src/actions/affected-action.ts +67 -54
- package/src/actions/brief-action.ts +142 -5
- package/src/actions/callees-action.ts +1 -1
- package/src/actions/callers-action.ts +38 -67
- package/src/actions/implementations-action.ts +27 -63
- package/src/actions/map-action.ts +206 -0
- package/src/actions/pattern-action.ts +1 -1
- package/src/api.ts +1 -0
- package/src/brief-focused.ts +5 -5
- package/src/brief.ts +3 -3
- package/src/code-intelligence.ts +6 -75
- package/src/index.ts +1 -0
- package/src/pattern-structured.ts +1 -1
- package/src/prioritization-signals.ts +13 -26
- package/src/query-params.ts +15 -0
- package/src/resolve-target.ts +2 -2
- package/src/search-helpers.ts +2 -2
- package/src/target-resolution.ts +27 -102
- package/src/tool/execute-affected.ts +25 -0
- package/src/tool/execute-brief.ts +25 -0
- package/src/tool/execute-map.ts +32 -0
- package/src/tool/execute-pattern.ts +26 -0
- package/src/tool/execute-relations.ts +48 -0
- package/src/tool/guidance.ts +24 -13
- package/src/tool/register-tools.ts +32 -0
- package/src/tool/tool-specs.ts +184 -0
- package/src/tool/validation.ts +43 -0
- package/src/types.ts +10 -0
- package/src/actions/index-action.ts +0 -187
- package/src/tool/action-specs.ts +0 -66
- package/src/tool-actions.ts +0 -100
package/src/target-resolution.ts
CHANGED
|
@@ -8,7 +8,7 @@ import { isWithinOrEqual } from "@mrclrchtr/supi-core/api";
|
|
|
8
8
|
import { type Position, type SessionLspService, toLspPosition } from "@mrclrchtr/supi-lsp/api";
|
|
9
9
|
import { getSemanticService, getSemanticServiceState } from "./providers/semantic-provider.ts";
|
|
10
10
|
import { withStructuralSession } from "./providers/structural-provider.ts";
|
|
11
|
-
import {
|
|
11
|
+
import { normalizePath } from "./search-helpers.ts";
|
|
12
12
|
import { highestConfidence } from "./semantic-action-helpers.ts";
|
|
13
13
|
import type { ConfidenceMode, DisambiguationCandidate } from "./types.ts";
|
|
14
14
|
|
|
@@ -64,7 +64,7 @@ export function resolveAnchoredTarget(
|
|
|
64
64
|
if (isBinaryFile(resolvedFile)) {
|
|
65
65
|
return {
|
|
66
66
|
kind: "error",
|
|
67
|
-
message: `File type not supported for semantic analysis: \`${file}\`.
|
|
67
|
+
message: `File type not supported for semantic analysis: \`${file}\`. Use \`code_pattern\` for explicit text search.`,
|
|
68
68
|
};
|
|
69
69
|
}
|
|
70
70
|
|
|
@@ -101,7 +101,7 @@ export async function resolveFileTargetGroup(
|
|
|
101
101
|
if (isBinaryFile(resolvedFile)) {
|
|
102
102
|
return {
|
|
103
103
|
kind: "error",
|
|
104
|
-
message: `File type not supported for semantic analysis: \`${file}\`.
|
|
104
|
+
message: `File type not supported for semantic analysis: \`${file}\`. Use \`code_pattern\` for explicit text search.`,
|
|
105
105
|
};
|
|
106
106
|
}
|
|
107
107
|
|
|
@@ -132,7 +132,7 @@ export async function resolveFileTargetGroup(
|
|
|
132
132
|
|
|
133
133
|
/**
|
|
134
134
|
* Resolve a target from symbol discovery — finds matching declarations.
|
|
135
|
-
*
|
|
135
|
+
* Symbol discovery is semantic-only; it does not fall back to text search.
|
|
136
136
|
*/
|
|
137
137
|
export async function resolveSymbolTarget(
|
|
138
138
|
symbol: string,
|
|
@@ -145,14 +145,19 @@ export async function resolveSymbolTarget(
|
|
|
145
145
|
): Promise<TargetResolutionResult> {
|
|
146
146
|
const lspState = await getSemanticServiceState(cwd, { waitForReady: true });
|
|
147
147
|
|
|
148
|
-
if (lspState.kind
|
|
149
|
-
return
|
|
148
|
+
if (lspState.kind !== "ready") {
|
|
149
|
+
return {
|
|
150
|
+
kind: "error",
|
|
151
|
+
message:
|
|
152
|
+
`Symbol discovery for \`${symbol}\` requires active LSP. ` +
|
|
153
|
+
"Use `file` + coordinates, or enable LSP and retry.",
|
|
154
|
+
};
|
|
150
155
|
}
|
|
151
156
|
|
|
152
|
-
|
|
153
|
-
return resolveSymbolViaSearch(symbol, cwd, options);
|
|
157
|
+
return resolveSymbolViaLsp(symbol, cwd, lspState.service, options);
|
|
154
158
|
}
|
|
155
159
|
|
|
160
|
+
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: pre-existing — not introduced by this change
|
|
156
161
|
async function resolveSymbolViaLsp(
|
|
157
162
|
symbol: string,
|
|
158
163
|
cwd: string,
|
|
@@ -202,7 +207,12 @@ async function resolveSymbolViaLsp(
|
|
|
202
207
|
|
|
203
208
|
if (candidates.length === 1) {
|
|
204
209
|
const c = candidates[0];
|
|
205
|
-
const
|
|
210
|
+
const rawLoc = "location" in c ? c.location : null;
|
|
211
|
+
// WorkspaceSymbol.location may be Location | { uri: string }; we need range.
|
|
212
|
+
const loc =
|
|
213
|
+
rawLoc && "range" in rawLoc
|
|
214
|
+
? (rawLoc as { uri: string; range: { start: { line: number; character: number } } })
|
|
215
|
+
: null;
|
|
206
216
|
if (!loc) {
|
|
207
217
|
return { kind: "error", message: `Symbol not found: \`${symbol}\`` };
|
|
208
218
|
}
|
|
@@ -235,95 +245,6 @@ async function resolveSymbolViaLsp(
|
|
|
235
245
|
};
|
|
236
246
|
}
|
|
237
247
|
|
|
238
|
-
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: ripgrep-based symbol discovery with pattern parsing
|
|
239
|
-
async function resolveSymbolViaSearch(
|
|
240
|
-
symbol: string,
|
|
241
|
-
cwd: string,
|
|
242
|
-
options?: { path?: string; kind?: string; exportedOnly?: boolean },
|
|
243
|
-
): Promise<TargetResolutionResult> {
|
|
244
|
-
const { execFileSync } = await import("node:child_process");
|
|
245
|
-
const scopePath = options?.path ? normalizePath(options.path, cwd) : cwd;
|
|
246
|
-
|
|
247
|
-
try {
|
|
248
|
-
const exportOnly = options?.exportedOnly;
|
|
249
|
-
const pattern = exportOnly
|
|
250
|
-
? `export\\s+(function|class|interface|type|const|let|var)\\s+${escapeRegex(symbol)}\\b`
|
|
251
|
-
: `(function|class|interface|type|const|let|var|export)\\s+${escapeRegex(symbol)}\\b`;
|
|
252
|
-
let output: string;
|
|
253
|
-
try {
|
|
254
|
-
output = execFileSync("rg", ["--json", "-m", "10", "-e", pattern, scopePath], {
|
|
255
|
-
encoding: "utf-8",
|
|
256
|
-
cwd,
|
|
257
|
-
timeout: 5000,
|
|
258
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
259
|
-
});
|
|
260
|
-
} catch (err: unknown) {
|
|
261
|
-
// rg exits 1 for no-match; capture stdout if available
|
|
262
|
-
const e = err as { status?: number; stdout?: string };
|
|
263
|
-
output = e.stdout ?? "";
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
const matches: Array<{ file: string; line: number; text: string }> = [];
|
|
267
|
-
for (const line of output.split("\n")) {
|
|
268
|
-
if (!line.trim()) continue;
|
|
269
|
-
try {
|
|
270
|
-
const parsed = JSON.parse(line);
|
|
271
|
-
if (parsed.type === "match" && parsed.data) {
|
|
272
|
-
const filePath = parsed.data.path?.text;
|
|
273
|
-
const lineNum = parsed.data.line_number;
|
|
274
|
-
const text = parsed.data.lines?.text?.trim();
|
|
275
|
-
if (filePath && lineNum) {
|
|
276
|
-
matches.push({ file: filePath, line: lineNum, text: text ?? "" });
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
} catch {
|
|
280
|
-
// Skip malformed JSON lines
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
if (matches.length === 0) {
|
|
285
|
-
return { kind: "error", message: `Symbol not found: \`${symbol}\`` };
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
if (matches.length === 1) {
|
|
289
|
-
const m = matches[0];
|
|
290
|
-
const resolvedFile = path.resolve(cwd, m.file);
|
|
291
|
-
return {
|
|
292
|
-
kind: "resolved",
|
|
293
|
-
target: {
|
|
294
|
-
file: resolvedFile,
|
|
295
|
-
position: { line: m.line - 1, character: 0 },
|
|
296
|
-
displayLine: m.line,
|
|
297
|
-
displayCharacter: 1,
|
|
298
|
-
name: symbol,
|
|
299
|
-
kind: null,
|
|
300
|
-
confidence: "heuristic",
|
|
301
|
-
},
|
|
302
|
-
};
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
// Multiple matches — disambiguation
|
|
306
|
-
const disambiguated: DisambiguationCandidate[] = matches.slice(0, 8).map((m, idx) => ({
|
|
307
|
-
name: symbol,
|
|
308
|
-
kind: null,
|
|
309
|
-
container: null,
|
|
310
|
-
file: m.file,
|
|
311
|
-
line: m.line,
|
|
312
|
-
character: 1,
|
|
313
|
-
reason: m.text.slice(0, 80),
|
|
314
|
-
rank: idx + 1,
|
|
315
|
-
}));
|
|
316
|
-
|
|
317
|
-
return {
|
|
318
|
-
kind: "disambiguation",
|
|
319
|
-
candidates: disambiguated,
|
|
320
|
-
omittedCount: Math.max(0, matches.length - 8),
|
|
321
|
-
};
|
|
322
|
-
} catch {
|
|
323
|
-
return { kind: "error", message: `Symbol not found: \`${symbol}\`` };
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
|
|
327
248
|
async function resolveFileTargetsViaLsp(
|
|
328
249
|
resolvedFile: string,
|
|
329
250
|
cwd: string,
|
|
@@ -488,26 +409,30 @@ function mapCandidateToDisambiguation(
|
|
|
488
409
|
name: string;
|
|
489
410
|
kind: number;
|
|
490
411
|
containerName?: string | null;
|
|
491
|
-
location?: { uri: string
|
|
412
|
+
location?: { uri: string } | null;
|
|
492
413
|
},
|
|
493
414
|
idx: number,
|
|
494
415
|
cwd: string,
|
|
495
416
|
): DisambiguationCandidate {
|
|
496
|
-
const loc =
|
|
417
|
+
const loc = c.location;
|
|
497
418
|
const filePath = loc
|
|
498
419
|
? loc.uri.startsWith("file://")
|
|
499
420
|
? decodeURIComponent(loc.uri.slice(7))
|
|
500
421
|
: loc.uri
|
|
501
422
|
: "";
|
|
502
423
|
const relPath = filePath ? path.relative(cwd, filePath) : "";
|
|
424
|
+
const rangeObj =
|
|
425
|
+
loc && "range" in loc
|
|
426
|
+
? (loc as unknown as { range: { start: { line: number; character: number } } }).range
|
|
427
|
+
: null;
|
|
503
428
|
|
|
504
429
|
return {
|
|
505
430
|
name: c.name,
|
|
506
431
|
kind: symbolKindName(c.kind),
|
|
507
432
|
container: "containerName" in c ? (c.containerName ?? null) : null,
|
|
508
433
|
file: relPath,
|
|
509
|
-
line:
|
|
510
|
-
character:
|
|
434
|
+
line: rangeObj ? rangeObj.start.line + 1 : 0,
|
|
435
|
+
character: rangeObj ? rangeObj.start.character + 1 : 0,
|
|
511
436
|
reason: relPath,
|
|
512
437
|
rank: idx + 1,
|
|
513
438
|
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { executeAffectedAction } from "../actions/affected-action.ts";
|
|
2
|
+
import type { CodeIntelResult } from "../types.ts";
|
|
3
|
+
import { validateFocusedToolParams } from "./validation.ts";
|
|
4
|
+
|
|
5
|
+
export interface CodeAffectedToolParams {
|
|
6
|
+
file?: string;
|
|
7
|
+
line?: number;
|
|
8
|
+
character?: number;
|
|
9
|
+
symbol?: string;
|
|
10
|
+
exportedOnly?: boolean;
|
|
11
|
+
maxResults?: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Execute the public code_affected tool through the substrate-only affected action. */
|
|
15
|
+
export async function executeAffectedTool(
|
|
16
|
+
params: CodeAffectedToolParams,
|
|
17
|
+
ctx: { cwd: string },
|
|
18
|
+
): Promise<CodeIntelResult> {
|
|
19
|
+
const error = validateFocusedToolParams(params, ctx.cwd);
|
|
20
|
+
if (error) {
|
|
21
|
+
return { content: error, details: undefined };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return executeAffectedAction(params, ctx.cwd);
|
|
25
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { executeBriefAction } from "../actions/brief-action.ts";
|
|
2
|
+
import type { CodeIntelResult } from "../types.ts";
|
|
3
|
+
import { validateFocusedToolParams } from "./validation.ts";
|
|
4
|
+
|
|
5
|
+
export interface CodeBriefToolParams {
|
|
6
|
+
path?: string;
|
|
7
|
+
file?: string;
|
|
8
|
+
line?: number;
|
|
9
|
+
character?: number;
|
|
10
|
+
symbol?: string;
|
|
11
|
+
maxResults?: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Execute the public code_brief tool through the existing brief action. */
|
|
15
|
+
export async function executeBriefTool(
|
|
16
|
+
params: CodeBriefToolParams,
|
|
17
|
+
ctx: { cwd: string },
|
|
18
|
+
): Promise<CodeIntelResult> {
|
|
19
|
+
const error = validateFocusedToolParams(params, ctx.cwd);
|
|
20
|
+
if (error) {
|
|
21
|
+
return { content: error, details: undefined };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return executeBriefAction(params, ctx.cwd);
|
|
25
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import { executeMapAction } from "../actions/map-action.ts";
|
|
3
|
+
import { normalizePath } from "../search-helpers.ts";
|
|
4
|
+
import type { CodeIntelResult } from "../types.ts";
|
|
5
|
+
|
|
6
|
+
export interface CodeMapToolParams {
|
|
7
|
+
path?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** Execute the public code_map tool with focused directory validation. */
|
|
11
|
+
export async function executeMapTool(
|
|
12
|
+
params: CodeMapToolParams,
|
|
13
|
+
ctx: { cwd: string },
|
|
14
|
+
): Promise<CodeIntelResult> {
|
|
15
|
+
const scopePath = params.path ? normalizePath(params.path, ctx.cwd) : ctx.cwd;
|
|
16
|
+
|
|
17
|
+
if (!fs.existsSync(scopePath)) {
|
|
18
|
+
return {
|
|
19
|
+
content: `**Error:** code_map path not found: \`${params.path ?? scopePath}\``,
|
|
20
|
+
details: undefined,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (!fs.statSync(scopePath).isDirectory()) {
|
|
25
|
+
return {
|
|
26
|
+
content: "**Error:** code_map requires a directory path, not a file.",
|
|
27
|
+
details: undefined,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return executeMapAction(scopePath, ctx.cwd);
|
|
32
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { executePatternAction } from "../actions/pattern-action.ts";
|
|
2
|
+
import type { CodeIntelResult } from "../types.ts";
|
|
3
|
+
import { validatePatternToolParams } from "./validation.ts";
|
|
4
|
+
|
|
5
|
+
export interface CodePatternToolParams {
|
|
6
|
+
path?: string;
|
|
7
|
+
pattern: string;
|
|
8
|
+
regex?: boolean;
|
|
9
|
+
kind?: string;
|
|
10
|
+
maxResults?: number;
|
|
11
|
+
contextLines?: number;
|
|
12
|
+
summary?: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Execute the public code_pattern tool as the sole heuristic/structured search surface. */
|
|
16
|
+
export async function executePatternTool(
|
|
17
|
+
params: CodePatternToolParams,
|
|
18
|
+
ctx: { cwd: string },
|
|
19
|
+
): Promise<CodeIntelResult> {
|
|
20
|
+
const error = validatePatternToolParams(params, ctx.cwd);
|
|
21
|
+
if (error) {
|
|
22
|
+
return { content: error, details: undefined };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return executePatternAction(params, ctx.cwd);
|
|
26
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { executeCalleesAction } from "../actions/callees-action.ts";
|
|
2
|
+
import { executeCallersAction } from "../actions/callers-action.ts";
|
|
3
|
+
import { executeImplementationsAction } from "../actions/implementations-action.ts";
|
|
4
|
+
import type { CodeIntelResult } from "../types.ts";
|
|
5
|
+
import type { CodeRelationsKind } from "./tool-specs.ts";
|
|
6
|
+
import { validateFocusedToolParams } from "./validation.ts";
|
|
7
|
+
|
|
8
|
+
export interface CodeRelationsToolParams {
|
|
9
|
+
kind: CodeRelationsKind;
|
|
10
|
+
path?: string;
|
|
11
|
+
file?: string;
|
|
12
|
+
line?: number;
|
|
13
|
+
character?: number;
|
|
14
|
+
symbol?: string;
|
|
15
|
+
exportedOnly?: boolean;
|
|
16
|
+
maxResults?: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const RELATION_HANDLERS = {
|
|
20
|
+
callers: executeCallersAction,
|
|
21
|
+
callees: executeCalleesAction,
|
|
22
|
+
implementations: executeImplementationsAction,
|
|
23
|
+
} as const;
|
|
24
|
+
|
|
25
|
+
/** Execute the public code_relations tool by dispatching to the selected substrate-backed relation action. */
|
|
26
|
+
export async function executeRelationsTool(
|
|
27
|
+
params: CodeRelationsToolParams,
|
|
28
|
+
ctx: { cwd: string },
|
|
29
|
+
): Promise<CodeIntelResult> {
|
|
30
|
+
const error = validateFocusedToolParams(params, ctx.cwd);
|
|
31
|
+
if (error) {
|
|
32
|
+
return { content: error, details: undefined };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const handler = RELATION_HANDLERS[params.kind];
|
|
36
|
+
return handler(
|
|
37
|
+
{
|
|
38
|
+
path: params.path,
|
|
39
|
+
file: params.file,
|
|
40
|
+
line: params.line,
|
|
41
|
+
character: params.character,
|
|
42
|
+
symbol: params.symbol,
|
|
43
|
+
exportedOnly: params.exportedOnly,
|
|
44
|
+
maxResults: params.maxResults,
|
|
45
|
+
},
|
|
46
|
+
ctx.cwd,
|
|
47
|
+
);
|
|
48
|
+
}
|
package/src/tool/guidance.ts
CHANGED
|
@@ -1,18 +1,29 @@
|
|
|
1
|
-
// Prompt guidance and tool
|
|
1
|
+
// Prompt guidance and tool descriptions for the focused code-intelligence tool surface.
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { CODE_INTELLIGENCE_TOOL_SPECS, type CodeIntelligenceToolName } from "./tool-specs.ts";
|
|
4
4
|
|
|
5
|
-
export
|
|
5
|
+
export interface CodeIntelligenceToolPromptSurface {
|
|
6
|
+
description: string;
|
|
7
|
+
promptSnippet: string;
|
|
8
|
+
promptGuidelines: string[];
|
|
9
|
+
}
|
|
6
10
|
|
|
7
|
-
|
|
11
|
+
export type CodeIntelligenceToolPromptSurfaceMap = Record<
|
|
12
|
+
CodeIntelligenceToolName,
|
|
13
|
+
CodeIntelligenceToolPromptSurface
|
|
14
|
+
>;
|
|
8
15
|
|
|
9
|
-
|
|
16
|
+
export function buildCodeIntelligenceToolPromptSurfaces(): CodeIntelligenceToolPromptSurfaceMap {
|
|
17
|
+
return Object.fromEntries(
|
|
18
|
+
CODE_INTELLIGENCE_TOOL_SPECS.map((spec) => [
|
|
19
|
+
spec.name,
|
|
20
|
+
{
|
|
21
|
+
description: spec.description,
|
|
22
|
+
promptSnippet: spec.promptSnippet,
|
|
23
|
+
promptGuidelines: [...spec.basePromptGuidelines],
|
|
24
|
+
} satisfies CodeIntelligenceToolPromptSurface,
|
|
25
|
+
]),
|
|
26
|
+
) as CodeIntelligenceToolPromptSurfaceMap;
|
|
27
|
+
}
|
|
10
28
|
|
|
11
|
-
export const
|
|
12
|
-
...CODE_INTEL_ACTION_SPECS.map((spec) => spec.promptGuideline),
|
|
13
|
-
"Use code_intel with `file`, `line`, and `character` for anchored positions; do not pair `line` or `character` with `path`.",
|
|
14
|
-
"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.",
|
|
15
|
-
];
|
|
16
|
-
|
|
17
|
-
export const promptSnippet =
|
|
18
|
-
"code_intel — codebase orientation, callers/callees, blast radius, and scoped search before file drill-down";
|
|
29
|
+
export const CODE_INTELLIGENCE_TOOL_PROMPT_SURFACES = buildCodeIntelligenceToolPromptSurfaces();
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import {
|
|
3
|
+
CODE_INTELLIGENCE_TOOL_PROMPT_SURFACES,
|
|
4
|
+
type CodeIntelligenceToolPromptSurfaceMap,
|
|
5
|
+
} from "./guidance.ts";
|
|
6
|
+
import { CODE_INTELLIGENCE_TOOL_SPECS } from "./tool-specs.ts";
|
|
7
|
+
|
|
8
|
+
/** Register the focused code-intelligence tool surface from shared specs. */
|
|
9
|
+
export function registerCodeIntelligenceTools(
|
|
10
|
+
pi: ExtensionAPI,
|
|
11
|
+
promptSurfaces: CodeIntelligenceToolPromptSurfaceMap = CODE_INTELLIGENCE_TOOL_PROMPT_SURFACES,
|
|
12
|
+
): void {
|
|
13
|
+
for (const spec of CODE_INTELLIGENCE_TOOL_SPECS) {
|
|
14
|
+
const surface = promptSurfaces[spec.name];
|
|
15
|
+
pi.registerTool({
|
|
16
|
+
name: spec.name,
|
|
17
|
+
label: spec.label,
|
|
18
|
+
description: surface.description,
|
|
19
|
+
promptSnippet: surface.promptSnippet,
|
|
20
|
+
promptGuidelines: surface.promptGuidelines,
|
|
21
|
+
parameters: spec.parameters,
|
|
22
|
+
// biome-ignore lint/complexity/useMaxParams: pi ToolDefinition.execute signature
|
|
23
|
+
execute: async (_toolCallId, params, _signal, _onUpdate, ctx: ExtensionContext) => {
|
|
24
|
+
const { content, details } = await spec.run(params, { cwd: ctx.cwd });
|
|
25
|
+
return {
|
|
26
|
+
content: [{ type: "text" as const, text: content }],
|
|
27
|
+
details,
|
|
28
|
+
};
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { StringEnum } from "@earendil-works/pi-ai";
|
|
2
|
+
import { type TSchema, Type } from "typebox";
|
|
3
|
+
import type { CodeIntelResult } from "../types.ts";
|
|
4
|
+
import { executeAffectedTool } from "./execute-affected.ts";
|
|
5
|
+
import { executeBriefTool } from "./execute-brief.ts";
|
|
6
|
+
import { executeMapTool } from "./execute-map.ts";
|
|
7
|
+
import { executePatternTool } from "./execute-pattern.ts";
|
|
8
|
+
import { executeRelationsTool } from "./execute-relations.ts";
|
|
9
|
+
|
|
10
|
+
const PathParam = Type.String({ description: "Scope or focus path (package, directory, or file)" });
|
|
11
|
+
const FileParam = Type.String({
|
|
12
|
+
description:
|
|
13
|
+
"Anchored target file (use with line/character) or a file-level semantic target where supported",
|
|
14
|
+
});
|
|
15
|
+
const LineParam = Type.Number({ description: "1-based line number for anchored target" });
|
|
16
|
+
const CharacterParam = Type.Number({
|
|
17
|
+
description: "1-based character column (UTF-16) for anchored target",
|
|
18
|
+
});
|
|
19
|
+
const SymbolParam = Type.String({ description: "Symbol name for discovery-based resolution" });
|
|
20
|
+
const PatternParam = Type.String({ description: "Text search pattern" });
|
|
21
|
+
const RegexParam = Type.Boolean({ description: "Use regex semantics instead of literal matching" });
|
|
22
|
+
const ExportedOnlyParam = Type.Boolean({ description: "Limit discovery to exported symbols" });
|
|
23
|
+
const MaxResultsParam = Type.Number({ description: "Maximum results to return" });
|
|
24
|
+
const ContextLinesParam = Type.Number({ description: "Context lines around matches" });
|
|
25
|
+
const SummaryParam = Type.Boolean({
|
|
26
|
+
description: "Aggregate counts by directory instead of line-level matches",
|
|
27
|
+
});
|
|
28
|
+
const StructuredPatternKindParam = Type.String({
|
|
29
|
+
description: "Structured pattern kind (`definition` | `export` | `import`)",
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
export const CODE_INTELLIGENCE_TOOL_NAMES = [
|
|
33
|
+
"code_brief",
|
|
34
|
+
"code_map",
|
|
35
|
+
"code_relations",
|
|
36
|
+
"code_affected",
|
|
37
|
+
"code_pattern",
|
|
38
|
+
] as const;
|
|
39
|
+
export type CodeIntelligenceToolName = (typeof CODE_INTELLIGENCE_TOOL_NAMES)[number];
|
|
40
|
+
|
|
41
|
+
export const CODE_RELATION_KIND_NAMES = ["callers", "callees", "implementations"] as const;
|
|
42
|
+
export type CodeRelationsKind = (typeof CODE_RELATION_KIND_NAMES)[number];
|
|
43
|
+
|
|
44
|
+
const CodeRelationsKindEnum = StringEnum(CODE_RELATION_KIND_NAMES);
|
|
45
|
+
|
|
46
|
+
const CodeBriefParameters = Type.Object(
|
|
47
|
+
{
|
|
48
|
+
path: Type.Optional(PathParam),
|
|
49
|
+
file: Type.Optional(FileParam),
|
|
50
|
+
line: Type.Optional(LineParam),
|
|
51
|
+
character: Type.Optional(CharacterParam),
|
|
52
|
+
symbol: Type.Optional(SymbolParam),
|
|
53
|
+
maxResults: Type.Optional(MaxResultsParam),
|
|
54
|
+
},
|
|
55
|
+
{ additionalProperties: false },
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
const CodeMapParameters = Type.Object(
|
|
59
|
+
{
|
|
60
|
+
path: Type.Optional(Type.String({ description: "Project, package, or directory path to map" })),
|
|
61
|
+
},
|
|
62
|
+
{ additionalProperties: false },
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
const CodeRelationsParameters = Type.Object(
|
|
66
|
+
{
|
|
67
|
+
kind: CodeRelationsKindEnum,
|
|
68
|
+
path: Type.Optional(PathParam),
|
|
69
|
+
file: Type.Optional(FileParam),
|
|
70
|
+
line: Type.Optional(LineParam),
|
|
71
|
+
character: Type.Optional(CharacterParam),
|
|
72
|
+
symbol: Type.Optional(SymbolParam),
|
|
73
|
+
exportedOnly: Type.Optional(ExportedOnlyParam),
|
|
74
|
+
maxResults: Type.Optional(MaxResultsParam),
|
|
75
|
+
},
|
|
76
|
+
{ additionalProperties: false },
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
const CodeAffectedParameters = Type.Object(
|
|
80
|
+
{
|
|
81
|
+
file: Type.Optional(FileParam),
|
|
82
|
+
line: Type.Optional(LineParam),
|
|
83
|
+
character: Type.Optional(CharacterParam),
|
|
84
|
+
symbol: Type.Optional(SymbolParam),
|
|
85
|
+
exportedOnly: Type.Optional(ExportedOnlyParam),
|
|
86
|
+
maxResults: Type.Optional(MaxResultsParam),
|
|
87
|
+
},
|
|
88
|
+
{ additionalProperties: false },
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
const CodePatternParameters = Type.Object(
|
|
92
|
+
{
|
|
93
|
+
path: Type.Optional(PathParam),
|
|
94
|
+
pattern: PatternParam,
|
|
95
|
+
regex: Type.Optional(RegexParam),
|
|
96
|
+
kind: Type.Optional(StructuredPatternKindParam),
|
|
97
|
+
maxResults: Type.Optional(MaxResultsParam),
|
|
98
|
+
contextLines: Type.Optional(ContextLinesParam),
|
|
99
|
+
summary: Type.Optional(SummaryParam),
|
|
100
|
+
},
|
|
101
|
+
{ additionalProperties: false },
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
export interface CodeIntelligenceToolDefinitionSpec {
|
|
105
|
+
name: CodeIntelligenceToolName;
|
|
106
|
+
label: string;
|
|
107
|
+
description: string;
|
|
108
|
+
promptSnippet: string;
|
|
109
|
+
basePromptGuidelines: string[];
|
|
110
|
+
parameters: TSchema;
|
|
111
|
+
run: (params: unknown, ctx: { cwd: string }) => Promise<CodeIntelResult> | CodeIntelResult;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export const CODE_INTELLIGENCE_TOOL_SPECS = [
|
|
115
|
+
{
|
|
116
|
+
name: "code_brief",
|
|
117
|
+
label: "Code Brief",
|
|
118
|
+
description:
|
|
119
|
+
"Code brief tool — interpretive orientation for a project, package, directory, file, or symbol. Use code_brief when you need prioritized context and start-here guidance rather than raw search output.",
|
|
120
|
+
promptSnippet: "code_brief — interpretive orientation and start-here guidance",
|
|
121
|
+
basePromptGuidelines: [
|
|
122
|
+
"Use code_brief for a project, package, directory, file, or symbol overview when you need prioritized context.",
|
|
123
|
+
"Use code_brief before deeper drill-down when you want a start-here recommendation instead of raw inventory output.",
|
|
124
|
+
],
|
|
125
|
+
parameters: CodeBriefParameters,
|
|
126
|
+
run: (params, ctx) => executeBriefTool(params as Parameters<typeof executeBriefTool>[0], ctx),
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
name: "code_map",
|
|
130
|
+
label: "Code Map",
|
|
131
|
+
description:
|
|
132
|
+
"Code map tool — factual inventory for a repo, package, or directory. Use code_map when you want counts, child directories, language mix, and landmark files without interpretive guidance.",
|
|
133
|
+
promptSnippet: "code_map — factual project, package, or directory inventory",
|
|
134
|
+
basePromptGuidelines: [
|
|
135
|
+
"Use code_map for a factual repo, package, or directory map when you need counts, landmark files, and local structure without interpretation.",
|
|
136
|
+
"Use code_map with `path` for any directory path you want to inventory; code_map should stay factual rather than prioritized.",
|
|
137
|
+
],
|
|
138
|
+
parameters: CodeMapParameters,
|
|
139
|
+
run: (params, ctx) => executeMapTool(params as Parameters<typeof executeMapTool>[0], ctx),
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
name: "code_relations",
|
|
143
|
+
label: "Code Relations",
|
|
144
|
+
description:
|
|
145
|
+
'Code relations tool — trace callers, callees, or implementations for a resolved target. Use code_relations with `kind: "callers" | "callees" | "implementations"` when you need semantic or structural relationships rather than broad search.',
|
|
146
|
+
promptSnippet: "code_relations — callers, callees, or implementations for a resolved target",
|
|
147
|
+
basePromptGuidelines: [
|
|
148
|
+
'Use code_relations with `kind: "callers"` to find who invokes a symbol or file-level surface.',
|
|
149
|
+
'Use code_relations with `kind: "callees"` to inspect outgoing calls from a function or method at a known position.',
|
|
150
|
+
'Use code_relations with `kind: "implementations"` to find which concrete types implement a declaration.',
|
|
151
|
+
],
|
|
152
|
+
parameters: CodeRelationsParameters,
|
|
153
|
+
run: (params, ctx) =>
|
|
154
|
+
executeRelationsTool(params as Parameters<typeof executeRelationsTool>[0], ctx),
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
name: "code_affected",
|
|
158
|
+
label: "Code Affected",
|
|
159
|
+
description:
|
|
160
|
+
"Code affected tool — estimate blast radius, downstream impact, and risk for a target. Use code_affected before edits when you need an impact-oriented view rather than a plain relationship list.",
|
|
161
|
+
promptSnippet: "code_affected — blast radius, downstream impact, and risk",
|
|
162
|
+
basePromptGuidelines: [
|
|
163
|
+
"Use code_affected before edits when you need blast radius, downstream impact, and likely follow-up checks.",
|
|
164
|
+
"Use code_affected after you have a concrete file/position or symbol target to estimate change risk.",
|
|
165
|
+
],
|
|
166
|
+
parameters: CodeAffectedParameters,
|
|
167
|
+
run: (params, ctx) =>
|
|
168
|
+
executeAffectedTool(params as Parameters<typeof executeAffectedTool>[0], ctx),
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
name: "code_pattern",
|
|
172
|
+
label: "Code Pattern",
|
|
173
|
+
description:
|
|
174
|
+
"Code pattern tool — explicit literal, regex, or structured search within a bounded scope. Use code_pattern when you intentionally want search behavior rather than semantic or structural relationships.",
|
|
175
|
+
promptSnippet: "code_pattern — explicit literal, regex, or structured search",
|
|
176
|
+
basePromptGuidelines: [
|
|
177
|
+
"Use code_pattern for explicit literal or regex search within a bounded path.",
|
|
178
|
+
'Use code_pattern with `kind: "definition" | "export" | "import"` for structured search instead of plain text matching.',
|
|
179
|
+
],
|
|
180
|
+
parameters: CodePatternParameters,
|
|
181
|
+
run: (params, ctx) =>
|
|
182
|
+
executePatternTool(params as Parameters<typeof executePatternTool>[0], ctx),
|
|
183
|
+
},
|
|
184
|
+
] as const satisfies readonly CodeIntelligenceToolDefinitionSpec[];
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import type { CodeQueryParams } from "../query-params.ts";
|
|
3
|
+
import { normalizePath } from "../search-helpers.ts";
|
|
4
|
+
|
|
5
|
+
const STRUCTURED_PATTERN_KINDS = new Set(["definition", "export", "import"]);
|
|
6
|
+
|
|
7
|
+
/** Shared validation for focused tool inputs that can anchor into files. */
|
|
8
|
+
export function validateFocusedToolParams(
|
|
9
|
+
params: Pick<CodeQueryParams, "path" | "file" | "line" | "character">,
|
|
10
|
+
cwd: string,
|
|
11
|
+
): string | null {
|
|
12
|
+
if (params.path && (params.line != null || params.character != null)) {
|
|
13
|
+
return "**Error:** `line` and `character` require `file`, not `path`. Use `path` to scope/focus; use `file` to anchor a position.";
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (params.file) {
|
|
17
|
+
const resolvedFile = normalizePath(params.file, cwd);
|
|
18
|
+
if (fs.existsSync(resolvedFile) && fs.statSync(resolvedFile).isDirectory()) {
|
|
19
|
+
return "**Error:** `file` points to a directory. Use `path` to scope a directory; use `file` to anchor a position in a file.";
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if ((params.line != null || params.character != null) && !params.file) {
|
|
24
|
+
return "**Error:** `line` and `character` require `file`.";
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Shared validation for explicit pattern-tool input. */
|
|
31
|
+
export function validatePatternToolParams(
|
|
32
|
+
params: Pick<CodeQueryParams, "path" | "file" | "line" | "character" | "kind">,
|
|
33
|
+
cwd: string,
|
|
34
|
+
): string | null {
|
|
35
|
+
const commonError = validateFocusedToolParams(params, cwd);
|
|
36
|
+
if (commonError) return commonError;
|
|
37
|
+
|
|
38
|
+
if (params.kind && !STRUCTURED_PATTERN_KINDS.has(params.kind)) {
|
|
39
|
+
return "**Error:** `code_pattern` `kind` must be one of `definition`, `export`, or `import`.";
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return null;
|
|
43
|
+
}
|