@mrclrchtr/supi-code-intelligence 1.4.0 → 1.5.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/node_modules/@mrclrchtr/supi-core/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-core/src/api.ts +2 -0
- package/node_modules/@mrclrchtr/supi-core/src/index.ts +2 -0
- package/node_modules/@mrclrchtr/supi-core/src/path-utils.ts +40 -0
- package/node_modules/@mrclrchtr/supi-core/src/registry-utils.ts +42 -10
- 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 +2 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/index.ts +2 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/path-utils.ts +40 -0
- package/node_modules/@mrclrchtr/supi-lsp/node_modules/@mrclrchtr/supi-core/src/registry-utils.ts +42 -10
- package/node_modules/@mrclrchtr/supi-lsp/package.json +2 -2
- package/node_modules/@mrclrchtr/supi-lsp/src/manager/manager-project-info.ts +2 -16
- package/node_modules/@mrclrchtr/supi-lsp/src/session/service-registry.ts +5 -21
- package/node_modules/@mrclrchtr/supi-lsp/src/tool/guidance.ts +15 -75
- package/node_modules/@mrclrchtr/supi-lsp/src/tool/register-tools.ts +13 -166
- package/node_modules/@mrclrchtr/supi-lsp/src/tool/tool-specs.ts +248 -0
- package/node_modules/@mrclrchtr/supi-lsp/src/utils.ts +5 -34
- package/node_modules/@mrclrchtr/supi-tree-sitter/README.md +18 -6
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/README.md +107 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/package.json +44 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/api.ts +85 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/config/config-settings.ts +76 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/config/config.ts +186 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/context/context-messages.ts +119 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/context/context-provider-registry.ts +36 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/context/context-tag.ts +31 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/debug-registry.ts +255 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/extension.ts +1 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/index.ts +85 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/path-utils.ts +40 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/project-roots.ts +170 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/registry-utils.ts +86 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/session-utils.ts +29 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/settings/settings-command.ts +15 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/settings/settings-registry.ts +41 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/settings/settings-ui.ts +226 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/node_modules/@mrclrchtr/supi-core/src/terminal.ts +60 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/package.json +8 -3
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/api.ts +5 -1
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/index.ts +5 -1
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/runtime.ts +3 -2
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/service-registry.ts +30 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/session/session.ts +16 -8
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/action-specs.ts +92 -0
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/tool/guidance.ts +12 -3
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/tree-sitter.ts +111 -61
- package/node_modules/@mrclrchtr/supi-tree-sitter/src/types.ts +13 -2
- package/package.json +4 -4
- package/src/actions/brief-action.ts +5 -5
- package/src/code-intelligence.ts +3 -10
- package/src/pattern-structured.ts +1 -1
- package/src/providers/structural-provider.ts +15 -3
- package/src/search-helpers.ts +4 -15
- package/src/tool/action-specs.ts +66 -0
- package/src/tool/guidance.ts +4 -7
- package/src/tool-actions.ts +23 -40
|
@@ -5,6 +5,18 @@ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
|
5
5
|
import { Type } from "typebox";
|
|
6
6
|
import { detectGrammar, isJsTsGrammar } from "./language.ts";
|
|
7
7
|
import { TreeSitterRuntime } from "./session/runtime.ts";
|
|
8
|
+
import {
|
|
9
|
+
clearSessionTreeSitterService,
|
|
10
|
+
setSessionTreeSitterService,
|
|
11
|
+
} from "./session/service-registry.ts";
|
|
12
|
+
import { createTreeSitterService } from "./session/session.ts";
|
|
13
|
+
import {
|
|
14
|
+
formatTreeSitterActionList,
|
|
15
|
+
getTreeSitterActionSpec,
|
|
16
|
+
isTreeSitterAction,
|
|
17
|
+
TREE_SITTER_ACTION_NAMES,
|
|
18
|
+
type TreeSitterAction,
|
|
19
|
+
} from "./tool/action-specs.ts";
|
|
8
20
|
import {
|
|
9
21
|
formatNonSuccess,
|
|
10
22
|
formatOutlineItemsCapped,
|
|
@@ -18,26 +30,30 @@ import { promptGuidelines, promptSnippet, toolDescription } from "./tool/guidanc
|
|
|
18
30
|
import { collectOutline } from "./tool/outline.ts";
|
|
19
31
|
import { extractExports, extractImports, lookupCalleesAt, lookupNodeAt } from "./tool/structure.ts";
|
|
20
32
|
|
|
21
|
-
const TreeSitterActionEnum = StringEnum(
|
|
22
|
-
"outline",
|
|
23
|
-
"imports",
|
|
24
|
-
"exports",
|
|
25
|
-
"node_at",
|
|
26
|
-
"query",
|
|
27
|
-
"callees",
|
|
28
|
-
] as const);
|
|
33
|
+
const TreeSitterActionEnum = StringEnum(TREE_SITTER_ACTION_NAMES);
|
|
29
34
|
|
|
30
35
|
export default function treeSitterExtension(pi: ExtensionAPI) {
|
|
31
36
|
let runtime: TreeSitterRuntime | undefined;
|
|
37
|
+
let activeCwd: string | null = null;
|
|
32
38
|
|
|
33
39
|
pi.on("session_start", (_event, ctx) => {
|
|
34
|
-
runtime
|
|
40
|
+
if (runtime && activeCwd) {
|
|
41
|
+
clearSessionTreeSitterService(activeCwd);
|
|
42
|
+
runtime.dispose();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
activeCwd = ctx.cwd;
|
|
35
46
|
runtime = new TreeSitterRuntime(ctx.cwd);
|
|
47
|
+
setSessionTreeSitterService(ctx.cwd, createTreeSitterService(runtime));
|
|
36
48
|
});
|
|
37
49
|
|
|
38
50
|
pi.on("session_shutdown", () => {
|
|
51
|
+
if (activeCwd) {
|
|
52
|
+
clearSessionTreeSitterService(activeCwd);
|
|
53
|
+
}
|
|
39
54
|
runtime?.dispose();
|
|
40
55
|
runtime = undefined;
|
|
56
|
+
activeCwd = null;
|
|
41
57
|
});
|
|
42
58
|
|
|
43
59
|
pi.registerTool({
|
|
@@ -73,8 +89,6 @@ export default function treeSitterExtension(pi: ExtensionAPI) {
|
|
|
73
89
|
});
|
|
74
90
|
}
|
|
75
91
|
|
|
76
|
-
type TreeSitterAction = "outline" | "imports" | "exports" | "node_at" | "query" | "callees";
|
|
77
|
-
|
|
78
92
|
type ToolParams = {
|
|
79
93
|
action?: string;
|
|
80
94
|
file?: string;
|
|
@@ -83,17 +97,47 @@ type ToolParams = {
|
|
|
83
97
|
query?: string;
|
|
84
98
|
};
|
|
85
99
|
|
|
100
|
+
interface ValidatedToolParams {
|
|
101
|
+
action: TreeSitterAction;
|
|
102
|
+
file: string;
|
|
103
|
+
line?: number;
|
|
104
|
+
character?: number;
|
|
105
|
+
query?: string;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const SUPPORTED_ACTIONS_TEXT = formatTreeSitterActionList();
|
|
109
|
+
|
|
110
|
+
const ACTION_HANDLERS: Record<
|
|
111
|
+
TreeSitterAction,
|
|
112
|
+
(runtime: TreeSitterRuntime, params: ValidatedToolParams) => Promise<string>
|
|
113
|
+
> = {
|
|
114
|
+
outline: (runtime, params) => handleOutline(runtime, params.file),
|
|
115
|
+
imports: (runtime, params) => handleImports(runtime, params.file),
|
|
116
|
+
exports: (runtime, params) => handleExports(runtime, params.file),
|
|
117
|
+
node_at: (runtime, params) =>
|
|
118
|
+
handleNodeAt(runtime, params.file, params.line as number, params.character as number),
|
|
119
|
+
query: (runtime, params) => handleQuery(runtime, params.file, params.query as string),
|
|
120
|
+
callees: (runtime, params) =>
|
|
121
|
+
handleCallees(runtime, params.file, params.line as number, params.character as number),
|
|
122
|
+
};
|
|
123
|
+
|
|
86
124
|
async function executeToolAction(runtime: TreeSitterRuntime, params: ToolParams): Promise<string> {
|
|
125
|
+
const validated = validateToolParams(params);
|
|
126
|
+
if (typeof validated === "string") {
|
|
127
|
+
return validated;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return ACTION_HANDLERS[validated.action](runtime, validated);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function validateToolParams(params: ToolParams): ValidatedToolParams | string {
|
|
87
134
|
if (!params.action) {
|
|
88
|
-
return validationError(
|
|
89
|
-
"`action` is required. Supported: outline, imports, exports, node_at, query, callees.",
|
|
90
|
-
);
|
|
135
|
+
return validationError(`\`action\` is required. Supported: ${SUPPORTED_ACTIONS_TEXT}.`);
|
|
91
136
|
}
|
|
92
137
|
|
|
93
|
-
|
|
94
|
-
if (!action) {
|
|
138
|
+
if (!isTreeSitterAction(params.action)) {
|
|
95
139
|
return validationError(
|
|
96
|
-
`Unknown action: ${params.action}. Supported:
|
|
140
|
+
`Unknown action: ${params.action}. Supported: ${SUPPORTED_ACTIONS_TEXT}`,
|
|
97
141
|
);
|
|
98
142
|
}
|
|
99
143
|
|
|
@@ -101,12 +145,37 @@ async function executeToolAction(runtime: TreeSitterRuntime, params: ToolParams)
|
|
|
101
145
|
return validationError("`file` is required for all actions.");
|
|
102
146
|
}
|
|
103
147
|
|
|
104
|
-
|
|
105
|
-
if (
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
148
|
+
const spec = getTreeSitterActionSpec(params.action);
|
|
149
|
+
if (spec.requiresPosition) {
|
|
150
|
+
const lineError = validatePositiveInteger("line", params.line, params.action);
|
|
151
|
+
if (lineError) return lineError;
|
|
152
|
+
|
|
153
|
+
const characterError = validatePositiveInteger("character", params.character, params.action);
|
|
154
|
+
if (characterError) return characterError;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (spec.requiresQuery && (!params.query || params.query.trim().length === 0)) {
|
|
158
|
+
return validationError("`query` is required and must be non-empty.");
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return {
|
|
162
|
+
action: params.action,
|
|
163
|
+
file: params.file,
|
|
164
|
+
line: params.line,
|
|
165
|
+
character: params.character,
|
|
166
|
+
query: params.query,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function validatePositiveInteger(
|
|
171
|
+
field: "line" | "character",
|
|
172
|
+
value: number | undefined,
|
|
173
|
+
action: TreeSitterAction,
|
|
174
|
+
): string | null {
|
|
175
|
+
if (value === undefined || !Number.isInteger(value) || value < 1) {
|
|
176
|
+
return validationError(`\`${field}\` must be a positive 1-based integer for ${action} action.`);
|
|
177
|
+
}
|
|
178
|
+
return null;
|
|
110
179
|
}
|
|
111
180
|
|
|
112
181
|
async function handleOutline(runtime: TreeSitterRuntime, file: string): Promise<string> {
|
|
@@ -175,18 +244,13 @@ async function handleExports(runtime: TreeSitterRuntime, file: string): Promise<
|
|
|
175
244
|
return lines.join("\n");
|
|
176
245
|
}
|
|
177
246
|
|
|
178
|
-
async function handleNodeAt(
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
const file = params.file;
|
|
187
|
-
const line = params.line as number;
|
|
188
|
-
const character = params.character as number;
|
|
189
|
-
const result = await lookupNodeAt(runtime, file as string, line, character);
|
|
247
|
+
async function handleNodeAt(
|
|
248
|
+
runtime: TreeSitterRuntime,
|
|
249
|
+
file: string,
|
|
250
|
+
line: number,
|
|
251
|
+
character: number,
|
|
252
|
+
): Promise<string> {
|
|
253
|
+
const result = await lookupNodeAt(runtime, file, line, character);
|
|
190
254
|
if (result.kind !== "success") return formatNonSuccess(result);
|
|
191
255
|
|
|
192
256
|
const { data } = result;
|
|
@@ -212,13 +276,12 @@ async function handleNodeAt(runtime: TreeSitterRuntime, params: ToolParams): Pro
|
|
|
212
276
|
return lines.join("\n");
|
|
213
277
|
}
|
|
214
278
|
|
|
215
|
-
async function handleQuery(
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
const
|
|
221
|
-
const result = await runtime.queryFile(file, params.query);
|
|
279
|
+
async function handleQuery(
|
|
280
|
+
runtime: TreeSitterRuntime,
|
|
281
|
+
file: string,
|
|
282
|
+
query: string,
|
|
283
|
+
): Promise<string> {
|
|
284
|
+
const result = await runtime.queryFile(file, query);
|
|
222
285
|
if (result.kind !== "success") return formatNonSuccess(result);
|
|
223
286
|
|
|
224
287
|
const { data: captures } = result;
|
|
@@ -237,25 +300,12 @@ async function handleQuery(runtime: TreeSitterRuntime, params: ToolParams): Prom
|
|
|
237
300
|
return lines.join("\n");
|
|
238
301
|
}
|
|
239
302
|
|
|
240
|
-
function
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
async function handleCallees(runtime: TreeSitterRuntime, params: ToolParams): Promise<string> {
|
|
248
|
-
if (!Number.isInteger(params.line) || (params.line as number) < 1) {
|
|
249
|
-
return validationError("`line` must be a positive 1-based integer for callees action.");
|
|
250
|
-
}
|
|
251
|
-
if (!Number.isInteger(params.character) || (params.character as number) < 1) {
|
|
252
|
-
return validationError("`character` must be a positive 1-based integer for callees action.");
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
const file = params.file as string;
|
|
256
|
-
const line = params.line as number;
|
|
257
|
-
const character = params.character as number;
|
|
258
|
-
|
|
303
|
+
async function handleCallees(
|
|
304
|
+
runtime: TreeSitterRuntime,
|
|
305
|
+
file: string,
|
|
306
|
+
line: number,
|
|
307
|
+
character: number,
|
|
308
|
+
): Promise<string> {
|
|
259
309
|
const result = await lookupCalleesAt(runtime, file, line, character);
|
|
260
310
|
if (result.kind !== "success") return formatNonSuccess(result);
|
|
261
311
|
|
|
@@ -66,8 +66,8 @@ export interface QueryCapture {
|
|
|
66
66
|
text: string;
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
/**
|
|
70
|
-
export interface
|
|
69
|
+
/** Shared Tree-sitter service surface, independent of lifecycle ownership. */
|
|
70
|
+
export interface TreeSitterService {
|
|
71
71
|
/** Validate that a supported file can be read and parsed; does not expose the raw tree. */
|
|
72
72
|
canParse(file: string): Promise<TreeSitterResult<{ file: string; language: string }>>;
|
|
73
73
|
/** Run a Tree-sitter query and return all captures. */
|
|
@@ -86,10 +86,21 @@ export interface TreeSitterSession {
|
|
|
86
86
|
line: number,
|
|
87
87
|
character: number,
|
|
88
88
|
): Promise<TreeSitterResult<CalleesAtResult>>;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Owned Tree-sitter session that must release its runtime resources. */
|
|
92
|
+
export interface TreeSitterSession extends TreeSitterService {
|
|
89
93
|
/** Release parser and grammar resources owned by this session. */
|
|
90
94
|
dispose(): void;
|
|
91
95
|
}
|
|
92
96
|
|
|
97
|
+
/** Session-scoped shared structural service published by the extension runtime. */
|
|
98
|
+
export type SessionTreeSitterService = TreeSitterService;
|
|
99
|
+
|
|
100
|
+
export type SessionTreeSitterServiceState =
|
|
101
|
+
| { kind: "ready"; service: SessionTreeSitterService }
|
|
102
|
+
| { kind: "unavailable"; reason: string };
|
|
103
|
+
|
|
93
104
|
/** Supported grammar identifiers. */
|
|
94
105
|
export type GrammarId =
|
|
95
106
|
| "javascript"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrclrchtr/supi-code-intelligence",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "SuPi Code Intelligence extension — architecture briefs, caller/callee analysis, impact assessment, and pattern search for pi",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -19,9 +19,9 @@
|
|
|
19
19
|
"src/**/*.ts"
|
|
20
20
|
],
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@mrclrchtr/supi-core": "1.
|
|
23
|
-
"@mrclrchtr/supi-
|
|
24
|
-
"@mrclrchtr/supi-
|
|
22
|
+
"@mrclrchtr/supi-core": "1.5.0",
|
|
23
|
+
"@mrclrchtr/supi-tree-sitter": "1.5.0",
|
|
24
|
+
"@mrclrchtr/supi-lsp": "1.5.0"
|
|
25
25
|
},
|
|
26
26
|
"bundledDependencies": [
|
|
27
27
|
"@mrclrchtr/supi-core",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import * as fs from "node:fs";
|
|
4
4
|
import * as path from "node:path";
|
|
5
|
-
import type {
|
|
5
|
+
import type { TreeSitterService } from "@mrclrchtr/supi-tree-sitter/api";
|
|
6
6
|
import { buildArchitectureModel, findModuleForPath } from "../architecture.ts";
|
|
7
7
|
import { generateFocusedBrief, generateProjectBrief } from "../brief.ts";
|
|
8
8
|
import { withStructuralSession } from "../providers/structural-provider.ts";
|
|
@@ -123,7 +123,7 @@ async function addTreeSitterContext(input: TreeSitterContextInput): Promise<void
|
|
|
123
123
|
|
|
124
124
|
async function addNodeContext(
|
|
125
125
|
lines: string[],
|
|
126
|
-
ts:
|
|
126
|
+
ts: TreeSitterService,
|
|
127
127
|
relPath: string,
|
|
128
128
|
pos: { line: number; char: number },
|
|
129
129
|
): Promise<void> {
|
|
@@ -143,7 +143,7 @@ async function addNodeContext(
|
|
|
143
143
|
|
|
144
144
|
async function addOutlineContext(
|
|
145
145
|
lines: string[],
|
|
146
|
-
ts:
|
|
146
|
+
ts: TreeSitterService,
|
|
147
147
|
relPath: string,
|
|
148
148
|
line1: number,
|
|
149
149
|
): Promise<void> {
|
|
@@ -182,7 +182,7 @@ function getOutlinePrefix(kind: string): string {
|
|
|
182
182
|
|
|
183
183
|
async function addImportsContext(
|
|
184
184
|
lines: string[],
|
|
185
|
-
ts:
|
|
185
|
+
ts: TreeSitterService,
|
|
186
186
|
relPath: string,
|
|
187
187
|
): Promise<void> {
|
|
188
188
|
const result = await ts.imports(relPath);
|
|
@@ -200,7 +200,7 @@ async function addImportsContext(
|
|
|
200
200
|
|
|
201
201
|
async function addExportsContext(
|
|
202
202
|
lines: string[],
|
|
203
|
-
ts:
|
|
203
|
+
ts: TreeSitterService,
|
|
204
204
|
relPath: string,
|
|
205
205
|
): Promise<void> {
|
|
206
206
|
const result = await ts.exports(relPath);
|
package/src/code-intelligence.ts
CHANGED
|
@@ -6,20 +6,13 @@ 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 { CODE_INTEL_ACTION_NAMES, type CodeIntelAction } from "./tool/action-specs.ts";
|
|
9
10
|
import { promptGuidelines, promptSnippet, toolDescription } from "./tool/guidance.ts";
|
|
10
|
-
import {
|
|
11
|
+
import { executeAction } from "./tool-actions.ts";
|
|
11
12
|
|
|
12
13
|
const OVERVIEW_CUSTOM_TYPE = "code-intelligence-overview";
|
|
13
14
|
|
|
14
|
-
const CodeIntelActionEnum = StringEnum(
|
|
15
|
-
"brief",
|
|
16
|
-
"callers",
|
|
17
|
-
"callees",
|
|
18
|
-
"implementations",
|
|
19
|
-
"affected",
|
|
20
|
-
"pattern",
|
|
21
|
-
"index",
|
|
22
|
-
] as const);
|
|
15
|
+
const CodeIntelActionEnum = StringEnum(CODE_INTEL_ACTION_NAMES);
|
|
23
16
|
|
|
24
17
|
/**
|
|
25
18
|
* Register the `code_intel` tool and inject a lightweight architecture overview
|
|
@@ -72,7 +72,7 @@ export async function getStructuredPatternMatches(
|
|
|
72
72
|
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: kind-specific tree-sitter matching is clearest as one helper
|
|
73
73
|
async function collectMatchesForFile(
|
|
74
74
|
matches: StructuredMatch[],
|
|
75
|
-
tsSession: import("@mrclrchtr/supi-tree-sitter/api").
|
|
75
|
+
tsSession: import("@mrclrchtr/supi-tree-sitter/api").TreeSitterService,
|
|
76
76
|
relFile: string,
|
|
77
77
|
kind: StructuredPatternKind,
|
|
78
78
|
matcher: (value: string) => boolean,
|
|
@@ -1,10 +1,22 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
createTreeSitterSession,
|
|
3
|
+
getSessionTreeSitterService,
|
|
4
|
+
type TreeSitterService,
|
|
5
|
+
} from "@mrclrchtr/supi-tree-sitter/api";
|
|
2
6
|
|
|
3
|
-
/**
|
|
7
|
+
/**
|
|
8
|
+
* Run work against the shared session-scoped Tree-sitter service when available,
|
|
9
|
+
* falling back to a short-lived owned session otherwise.
|
|
10
|
+
*/
|
|
4
11
|
export async function withStructuralSession<T>(
|
|
5
12
|
cwd: string,
|
|
6
|
-
fn: (session:
|
|
13
|
+
fn: (session: TreeSitterService) => Promise<T>,
|
|
7
14
|
): Promise<T> {
|
|
15
|
+
const current = getSessionTreeSitterService(cwd);
|
|
16
|
+
if (current.kind === "ready") {
|
|
17
|
+
return fn(current.service);
|
|
18
|
+
}
|
|
19
|
+
|
|
8
20
|
const session = createTreeSitterSession(cwd);
|
|
9
21
|
try {
|
|
10
22
|
return await fn(session);
|
package/src/search-helpers.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { execFileSync } from "node:child_process";
|
|
4
4
|
import * as path from "node:path";
|
|
5
|
+
import { resolveToolPath, uriToFile as uriToFileShared } from "@mrclrchtr/supi-core/api";
|
|
5
6
|
|
|
6
7
|
const LOW_SIGNAL_DIRS = new Set([
|
|
7
8
|
"node_modules",
|
|
@@ -25,19 +26,8 @@ export function isLowSignalPath(filePath: string): boolean {
|
|
|
25
26
|
return segments.some((s) => LOW_SIGNAL_DIRS.has(s));
|
|
26
27
|
}
|
|
27
28
|
|
|
28
|
-
/** Convert a file:// URI to a file path, matching the
|
|
29
|
-
export
|
|
30
|
-
if (!uri.startsWith("file://")) return uri;
|
|
31
|
-
let filePath = decodeURIComponent(uri.slice(7));
|
|
32
|
-
if (
|
|
33
|
-
process.platform === "win32" &&
|
|
34
|
-
filePath.startsWith("/") &&
|
|
35
|
-
/^[A-Za-z]:/.test(filePath.slice(1))
|
|
36
|
-
) {
|
|
37
|
-
filePath = filePath.slice(1);
|
|
38
|
-
}
|
|
39
|
-
return filePath;
|
|
40
|
-
}
|
|
29
|
+
/** Convert a file:// URI to a file path, matching the shared SuPi normalization. */
|
|
30
|
+
export const uriToFile = uriToFileShared;
|
|
41
31
|
|
|
42
32
|
/** Check whether a resolved file path is inside the current project (within cwd, not under node_modules or .pnpm). */
|
|
43
33
|
export function isInProjectPath(filePath: string, cwd: string): boolean {
|
|
@@ -59,8 +49,7 @@ export function escapeRegex(s: string): string {
|
|
|
59
49
|
|
|
60
50
|
/** Normalize a file/path value: strip leading @, resolve relative to cwd. */
|
|
61
51
|
export function normalizePath(input: string, cwd: string): string {
|
|
62
|
-
|
|
63
|
-
return path.resolve(cwd, stripped);
|
|
52
|
+
return resolveToolPath(cwd, input);
|
|
64
53
|
}
|
|
65
54
|
|
|
66
55
|
export interface RgMatch {
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single source of truth for the public `code_intel` action surface.
|
|
3
|
+
*
|
|
4
|
+
* The action list, prompt guidance, and router validation should derive from
|
|
5
|
+
* these specs so the high-level orchestration tool stays internally coherent.
|
|
6
|
+
*/
|
|
7
|
+
export const CODE_INTEL_ACTION_SPECS = [
|
|
8
|
+
{
|
|
9
|
+
name: "brief",
|
|
10
|
+
promptGuideline:
|
|
11
|
+
'Use code_intel with `action: "brief"` for a project, package, directory, file, or anchored-position brief before opening more files.',
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
name: "callers",
|
|
15
|
+
promptGuideline:
|
|
16
|
+
'Use code_intel with `action: "callers"` to find who invokes a symbol or file-level surface before falling back to text search.',
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
name: "callees",
|
|
20
|
+
promptGuideline:
|
|
21
|
+
'Use code_intel with `action: "callees"` for outgoing calls from a function or method at a known `file`, `line`, and `character`.',
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
name: "implementations",
|
|
25
|
+
promptGuideline:
|
|
26
|
+
'Use code_intel with `action: "implementations"` to find which concrete types implement a declaration.',
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
name: "affected",
|
|
30
|
+
promptGuideline:
|
|
31
|
+
'Use code_intel with `action: "affected"` before edits for blast radius, downstream modules, risk, and likely follow-up checks or tests.',
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
name: "pattern",
|
|
35
|
+
promptGuideline:
|
|
36
|
+
'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.',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: "index",
|
|
40
|
+
promptGuideline:
|
|
41
|
+
'Use code_intel with `action: "index"` for a project map, top-level directories, language mix, or landmark files.',
|
|
42
|
+
},
|
|
43
|
+
] as const;
|
|
44
|
+
|
|
45
|
+
export type CodeIntelAction = (typeof CODE_INTEL_ACTION_SPECS)[number]["name"];
|
|
46
|
+
export type CodeIntelActionSpec = (typeof CODE_INTEL_ACTION_SPECS)[number];
|
|
47
|
+
|
|
48
|
+
/** Ordered action names for schemas, validation messages, and docs. */
|
|
49
|
+
export const CODE_INTEL_ACTION_NAMES = CODE_INTEL_ACTION_SPECS.map(
|
|
50
|
+
(spec) => spec.name,
|
|
51
|
+
) as readonly CodeIntelAction[];
|
|
52
|
+
|
|
53
|
+
const CODE_INTEL_ACTION_NAME_SET = new Set<string>(CODE_INTEL_ACTION_NAMES);
|
|
54
|
+
|
|
55
|
+
/** Check whether a runtime string is a supported `code_intel` action. */
|
|
56
|
+
export function isCodeIntelAction(action: string): action is CodeIntelAction {
|
|
57
|
+
return CODE_INTEL_ACTION_NAME_SET.has(action);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Format the public action list for validation messages and docs. */
|
|
61
|
+
export function formatCodeIntelActionList(options?: { fenced?: boolean }): string {
|
|
62
|
+
if (options?.fenced) {
|
|
63
|
+
return CODE_INTEL_ACTION_NAMES.map((name) => `\`${name}\``).join(", ");
|
|
64
|
+
}
|
|
65
|
+
return CODE_INTEL_ACTION_NAMES.join(", ");
|
|
66
|
+
}
|
package/src/tool/guidance.ts
CHANGED
|
@@ -1,18 +1,15 @@
|
|
|
1
1
|
// Prompt guidance and tool description for the code_intel tool.
|
|
2
2
|
|
|
3
|
+
import { CODE_INTEL_ACTION_SPECS, formatCodeIntelActionList } from "./action-specs.ts";
|
|
4
|
+
|
|
3
5
|
export const toolDescription = `Code intelligence tool — codebase orientation, semantic relationships, impact analysis, and scoped search.
|
|
4
6
|
|
|
5
|
-
Actions:
|
|
7
|
+
Actions: ${formatCodeIntelActionList()}.
|
|
6
8
|
|
|
7
9
|
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
10
|
|
|
9
11
|
export const promptGuidelines = [
|
|
10
|
-
|
|
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.',
|
|
12
|
+
...CODE_INTEL_ACTION_SPECS.map((spec) => spec.promptGuideline),
|
|
16
13
|
"Use code_intel with `file`, `line`, and `character` for anchored positions; do not pair `line` or `character` with `path`.",
|
|
17
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.",
|
|
18
15
|
];
|
package/src/tool-actions.ts
CHANGED
|
@@ -9,16 +9,14 @@ import { executeImplementationsAction } from "./actions/implementations-action.t
|
|
|
9
9
|
import { executeIndexAction } from "./actions/index-action.ts";
|
|
10
10
|
import { executePatternAction } from "./actions/pattern-action.ts";
|
|
11
11
|
import { normalizePath } from "./search-helpers.ts";
|
|
12
|
+
import {
|
|
13
|
+
type CodeIntelAction,
|
|
14
|
+
formatCodeIntelActionList,
|
|
15
|
+
isCodeIntelAction,
|
|
16
|
+
} from "./tool/action-specs.ts";
|
|
12
17
|
import type { CodeIntelResult } from "./types.ts";
|
|
13
18
|
|
|
14
|
-
export type CodeIntelAction
|
|
15
|
-
| "brief"
|
|
16
|
-
| "callers"
|
|
17
|
-
| "callees"
|
|
18
|
-
| "implementations"
|
|
19
|
-
| "affected"
|
|
20
|
-
| "pattern"
|
|
21
|
-
| "index";
|
|
19
|
+
export type { CodeIntelAction } from "./tool/action-specs.ts";
|
|
22
20
|
|
|
23
21
|
/** Flat parameter bag shared by `code_intel` action handlers. */
|
|
24
22
|
export interface ActionParams {
|
|
@@ -40,15 +38,20 @@ export interface ActionParams {
|
|
|
40
38
|
summary?: boolean;
|
|
41
39
|
}
|
|
42
40
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
41
|
+
type ActionHandler = (
|
|
42
|
+
params: ActionParams,
|
|
43
|
+
cwd: string,
|
|
44
|
+
) => CodeIntelResult | Promise<CodeIntelResult>;
|
|
45
|
+
|
|
46
|
+
const ACTION_HANDLERS: Record<CodeIntelAction, ActionHandler> = {
|
|
47
|
+
brief: executeBriefAction,
|
|
48
|
+
callers: executeCallersAction,
|
|
49
|
+
callees: executeCalleesAction,
|
|
50
|
+
implementations: executeImplementationsAction,
|
|
51
|
+
affected: executeAffectedAction,
|
|
52
|
+
pattern: executePatternAction,
|
|
53
|
+
index: (_params, cwd) => executeIndexAction(cwd),
|
|
54
|
+
};
|
|
52
55
|
|
|
53
56
|
/**
|
|
54
57
|
* Main action dispatcher — validates params and routes to specific action handlers.
|
|
@@ -62,32 +65,12 @@ export async function executeAction(
|
|
|
62
65
|
const error = validateParams(params, cwd);
|
|
63
66
|
if (error) return { content: error, details: undefined };
|
|
64
67
|
|
|
65
|
-
|
|
66
|
-
case "brief":
|
|
67
|
-
return executeBriefAction(params, cwd);
|
|
68
|
-
case "callers":
|
|
69
|
-
return executeCallersAction(params, cwd);
|
|
70
|
-
case "callees":
|
|
71
|
-
return executeCalleesAction(params, cwd);
|
|
72
|
-
case "implementations":
|
|
73
|
-
return executeImplementationsAction(params, cwd);
|
|
74
|
-
case "affected":
|
|
75
|
-
return executeAffectedAction(params, cwd);
|
|
76
|
-
case "pattern":
|
|
77
|
-
return executePatternAction(params, cwd);
|
|
78
|
-
case "index":
|
|
79
|
-
return executeIndexAction(cwd);
|
|
80
|
-
default:
|
|
81
|
-
return {
|
|
82
|
-
content: `**Error:** Unknown action \`${params.action}\`.`,
|
|
83
|
-
details: undefined,
|
|
84
|
-
};
|
|
85
|
-
}
|
|
68
|
+
return ACTION_HANDLERS[params.action](params, cwd);
|
|
86
69
|
}
|
|
87
70
|
|
|
88
71
|
function validateParams(params: ActionParams, cwd: string): string | null {
|
|
89
|
-
if (!params.action || !
|
|
90
|
-
return `**Error:** Unknown action \`${params.action ?? "(none)"}\`. Supported:
|
|
72
|
+
if (!params.action || !isCodeIntelAction(params.action)) {
|
|
73
|
+
return `**Error:** Unknown action \`${params.action ?? "(none)"}\`. Supported: ${formatCodeIntelActionList({ fenced: true })}.`;
|
|
91
74
|
}
|
|
92
75
|
|
|
93
76
|
if (params.path && (params.line != null || params.character != null)) {
|