pi-ast-sgrep 1.4.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -12
- package/dist/code-mode.d.ts +18 -10
- package/dist/code-mode.js +126 -42
- package/dist/codemode/connector.d.ts +37 -9
- package/dist/codemode/connector.js +29 -23
- package/dist/codemode/dispatch.d.ts +2 -3
- package/dist/codemode/dispatch.js +109 -131
- package/dist/codemode/index.d.ts +8 -9
- package/dist/codemode/index.js +7 -8
- package/dist/codemode/native.d.ts +6 -9
- package/dist/codemode/native.js +10 -1
- package/dist/codemode/runner.d.ts +37 -0
- package/dist/codemode/runner.js +242 -0
- package/dist/codemode/sandbox-worker.d.ts +1 -0
- package/dist/codemode/sandbox-worker.js +204 -0
- package/dist/codemode/session-pool.d.ts +1 -0
- package/dist/codemode/session-pool.js +94 -29
- package/dist/codemode/types.d.ts +2 -2
- package/dist/codemode/types.js +2 -2
- package/dist/codemode/worker.d.ts +3 -0
- package/dist/codemode/worker.js +99 -42
- package/dist/index.d.ts +6 -1
- package/dist/index.js +192 -106
- package/dist/present.d.ts +67 -0
- package/dist/present.js +166 -0
- package/dist/runtime.d.ts +16 -2
- package/dist/runtime.js +399 -131
- package/package.json +6 -14
- package/dist/codemode/sandbox.d.ts +0 -25
- package/dist/codemode/sandbox.js +0 -192
- package/skills/ast-sgrep/SKILL.md +0 -73
- package/skills/ast-sgrep/references/query-guide.md +0 -22
package/dist/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { Type } from "typebox";
|
|
2
2
|
import { createAsgrepConnector, runCodemode, runNativeBatch, runBatchViaStdin, CODEMODE_TYPES_FOR_MODEL, NativeSessionPool, argvFor, asEnvelope, } from "./codemode/index.js";
|
|
3
3
|
import { AstSgrepRuntime, FreshnessCoordinator, RuntimeError } from "./runtime.js";
|
|
4
|
+
import { ASGREP_PROMPT_GUIDELINES, ASGREP_PROMPT_SNIPPET, formatCodemodeCall, formatCodemodeResult, formatIndexCall, formatIndexResult, formatSearchCall, formatSearchResult, formatStatusCall, formatStatusResult, presentText, } from "./present.js";
|
|
4
5
|
const DEFAULT_LIMIT = 8;
|
|
5
6
|
const MAX_LIMIT = 100;
|
|
6
7
|
const MAX_EXCERPT_LINES = 100;
|
|
7
8
|
const MAX_CONTENT_CHARS = 1_200;
|
|
8
|
-
const MAX_CODEMODE_RESULT_CHARS = 8_000;
|
|
9
9
|
const searchParameters = Type.Object({
|
|
10
10
|
query: Type.String({ minLength: 1, maxLength: 4_096, description: "Natural-language query, symbol, or structural pattern" }),
|
|
11
11
|
mode: Type.Optional(Type.Union([
|
|
@@ -37,17 +37,18 @@ const codemodeParameters = Type.Object({
|
|
|
37
37
|
function bounded(text) {
|
|
38
38
|
return text.length <= MAX_CONTENT_CHARS ? text : `${text.slice(0, MAX_CONTENT_CHARS - 1)}…`;
|
|
39
39
|
}
|
|
40
|
-
function success(command, response) {
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
40
|
+
function success(command, response, extra = {}) {
|
|
41
|
+
const text = command === "status"
|
|
42
|
+
? formatStatusResult(response)
|
|
43
|
+
: command === "index" || command === "reindex"
|
|
44
|
+
? formatIndexResult(command, response)
|
|
45
|
+
: formatSearchResult(response, { command, ...extra });
|
|
45
46
|
return {
|
|
46
|
-
content: [{ type: "text", text: bounded(
|
|
47
|
+
content: [{ type: "text", text: bounded(text) }],
|
|
47
48
|
// The tool execute owns its machine command: normalize the envelope's
|
|
48
49
|
// command (native catalog names like index_status/index_repo must surface
|
|
49
50
|
// as the machine commands status/index/reindex).
|
|
50
|
-
details: { ok: true, command, response: { ...response, command } },
|
|
51
|
+
details: { ok: true, command, response: { ...response, command }, ...extra },
|
|
51
52
|
};
|
|
52
53
|
}
|
|
53
54
|
function errorDetails(cause, signal) {
|
|
@@ -101,9 +102,12 @@ export function registerAstSgrepTools(pi, runtime = new AstSgrepRuntime(pi), fre
|
|
|
101
102
|
? new FreshnessCoordinator({ refreshIntervalMs: runtime.config.refreshIntervalMs })
|
|
102
103
|
: new FreshnessCoordinator()) {
|
|
103
104
|
const pool = new NativeSessionPool();
|
|
105
|
+
let poolConfigured = false;
|
|
104
106
|
// Prefer a registration-local pool so tests / multi-agent hosts do not share
|
|
105
107
|
// sticky state. sharedNativePool remains for advanced single-session reuse.
|
|
106
108
|
const ensurePool = () => {
|
|
109
|
+
if (poolConfigured)
|
|
110
|
+
return;
|
|
107
111
|
try {
|
|
108
112
|
const env = runtime.nativeEnv?.() ?? { NO_COLOR: "1" };
|
|
109
113
|
let binary;
|
|
@@ -118,6 +122,8 @@ export function registerAstSgrepTools(pi, runtime = new AstSgrepRuntime(pi), fre
|
|
|
118
122
|
opts.binary = binary;
|
|
119
123
|
if (runtime.config?.timeoutMs !== undefined)
|
|
120
124
|
opts.timeoutMs = runtime.config.timeoutMs;
|
|
125
|
+
if (runtime.config?.maxOutputBytes !== undefined)
|
|
126
|
+
opts.maxOutputBytes = runtime.config.maxOutputBytes;
|
|
121
127
|
if (typeof env.ASGREP_NO_EMBED === "string") {
|
|
122
128
|
opts.useEmbed = env.ASGREP_NO_EMBED !== "1" && env.ASGREP_NO_EMBED !== "true";
|
|
123
129
|
}
|
|
@@ -128,8 +134,47 @@ export function registerAstSgrepTools(pi, runtime = new AstSgrepRuntime(pi), fre
|
|
|
128
134
|
catch {
|
|
129
135
|
pool.configure({});
|
|
130
136
|
}
|
|
137
|
+
poolConfigured = true;
|
|
131
138
|
};
|
|
132
139
|
const resolveRoot = async (cwd) => runtime.resolveRoot ? await runtime.resolveRoot({ cwd }) : cwd;
|
|
140
|
+
const probeCli = (options = {}) => {
|
|
141
|
+
// Test fixtures inject `run` without a resolver; production always has resolveBinaryPath.
|
|
142
|
+
if (typeof runtime.resolveBinaryPath !== "function")
|
|
143
|
+
return { kind: "cli" };
|
|
144
|
+
try {
|
|
145
|
+
const base = runtime.nativeEnv?.() ?? {};
|
|
146
|
+
if (options.env) {
|
|
147
|
+
runtime.resolveBinaryPath({ env: { ...base, ...options.env } });
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
runtime.resolveBinaryPath({ env: base });
|
|
151
|
+
}
|
|
152
|
+
return { kind: "cli" };
|
|
153
|
+
}
|
|
154
|
+
catch (cause) {
|
|
155
|
+
return {
|
|
156
|
+
kind: "unavailable",
|
|
157
|
+
cause: cause instanceof Error ? cause.message : String(cause),
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
const requireBackend = (availability, context) => {
|
|
162
|
+
if (availability.kind !== "unavailable")
|
|
163
|
+
return;
|
|
164
|
+
throw new RuntimeError("BACKEND_UNAVAILABLE", "ast-sgrep backend unavailable (no NAPI session and no CLI binary)", {
|
|
165
|
+
backend: "unavailable",
|
|
166
|
+
// Agent-facing mirrors of the closed unavailable variant (not an open product).
|
|
167
|
+
napi: false,
|
|
168
|
+
cli: false,
|
|
169
|
+
cwd: context.cwd,
|
|
170
|
+
hint: "Install @ast-sgrep/<platform> or run npm run build:native in packages/pi/extension",
|
|
171
|
+
...(availability.cause ? { cause: availability.cause } : {}),
|
|
172
|
+
});
|
|
173
|
+
};
|
|
174
|
+
const runCli = async (args, context, options = {}) => {
|
|
175
|
+
requireBackend(probeCli(options), context);
|
|
176
|
+
return runtime.run(args, context, options);
|
|
177
|
+
};
|
|
133
178
|
const nativeCall = async (tool, args, context, options = {}) => {
|
|
134
179
|
ensurePool();
|
|
135
180
|
const root = await resolveRoot(context.cwd);
|
|
@@ -137,8 +182,8 @@ export function registerAstSgrepTools(pi, runtime = new AstSgrepRuntime(pi), fre
|
|
|
137
182
|
if (worker) {
|
|
138
183
|
return asEnvelope(await worker.call(tool, args, options.signal ? { signal: options.signal } : {}));
|
|
139
184
|
}
|
|
140
|
-
//
|
|
141
|
-
return
|
|
185
|
+
// Cold CLI only when a real binary resolves -- never remap missing natives to BINARY_RESOLUTION_FAILED.
|
|
186
|
+
return runCli(argvFor(tool, args), context, options);
|
|
142
187
|
};
|
|
143
188
|
// Freshness + tools share the same warm in-process Searcher as Code Mode.
|
|
144
189
|
const warmRuntime = {
|
|
@@ -146,28 +191,60 @@ export function registerAstSgrepTools(pi, runtime = new AstSgrepRuntime(pi), fre
|
|
|
146
191
|
resolveRoot: (context) => resolveRoot(context.cwd),
|
|
147
192
|
nativeCall,
|
|
148
193
|
};
|
|
194
|
+
if (runtime.watchExternalChanges !== undefined) {
|
|
195
|
+
warmRuntime.watchExternalChanges = runtime.watchExternalChanges;
|
|
196
|
+
}
|
|
197
|
+
if (runtime.resolveIndexPath) {
|
|
198
|
+
warmRuntime.resolveIndexPath = (root) => runtime.resolveIndexPath(root);
|
|
199
|
+
}
|
|
149
200
|
if (runtime.inspectIndexCompatibility) {
|
|
150
201
|
warmRuntime.inspectIndexCompatibility = (context) => runtime.inspectIndexCompatibility(context);
|
|
151
202
|
}
|
|
152
203
|
if (runtime.rebuildIncompatibleIndex) {
|
|
153
|
-
warmRuntime.rebuildIncompatibleIndex = (context, options) =>
|
|
204
|
+
warmRuntime.rebuildIncompatibleIndex = async (context, options) => {
|
|
205
|
+
const root = await resolveRoot(context.cwd);
|
|
206
|
+
await pool.invalidate(root);
|
|
207
|
+
return runtime.rebuildIncompatibleIndex(context, options);
|
|
208
|
+
};
|
|
154
209
|
}
|
|
155
210
|
pi.on("tool_result", (event, ctx) => {
|
|
156
|
-
if (event.isError
|
|
211
|
+
if (event.isError)
|
|
212
|
+
return;
|
|
213
|
+
if (event.toolName !== "write" && event.toolName !== "edit")
|
|
157
214
|
return;
|
|
158
215
|
const path = event.input.path;
|
|
159
216
|
if (typeof path === "string")
|
|
160
217
|
freshness.markAffectedPath(path, ctx.cwd);
|
|
161
218
|
});
|
|
162
|
-
|
|
219
|
+
pi.on("session_start", (_event, ctx) => {
|
|
220
|
+
// Warm the in-process Searcher at session start so the first asgrep
|
|
221
|
+
// search does not pay NAPI/SQLite open on the user's first lookup.
|
|
222
|
+
void (async () => {
|
|
223
|
+
try {
|
|
224
|
+
ensurePool();
|
|
225
|
+
const root = await resolveRoot(ctx.cwd);
|
|
226
|
+
await pool.acquire(root);
|
|
227
|
+
}
|
|
228
|
+
catch {
|
|
229
|
+
// Doctor reports backend errors; a failed warmup must not block the session.
|
|
230
|
+
}
|
|
231
|
+
})();
|
|
232
|
+
});
|
|
233
|
+
pi.on("session_shutdown", () => {
|
|
234
|
+
freshness.shutdown?.();
|
|
235
|
+
void pool.shutdown();
|
|
236
|
+
});
|
|
237
|
+
// Primary surface: Code Mode -- in-process NAPI (MCP-class), compose in JS.
|
|
163
238
|
// Sibling to MCP: pick one surface; both link core, never each other.
|
|
164
239
|
pi.registerTool({
|
|
165
|
-
name: "
|
|
166
|
-
label: "
|
|
240
|
+
name: "asgrep",
|
|
241
|
+
label: "asgrep",
|
|
242
|
+
promptSnippet: ASGREP_PROMPT_SNIPPET,
|
|
243
|
+
promptGuidelines: [...ASGREP_PROMPT_GUIDELINES],
|
|
167
244
|
description: [
|
|
168
|
-
"Primary
|
|
169
|
-
"Compose with await / Promise.all, filter in code, return only the shaped final value.",
|
|
170
|
-
"Runs in-process (native addon)
|
|
245
|
+
"Primary code-search tool for this project. Call it whenever you need to find, trace, or understand code — do not wait for the user to mention asgrep.",
|
|
246
|
+
"Write JavaScript that calls typed asgrep.* methods. Compose with await / Promise.all, filter in code, return only the shaped final value.",
|
|
247
|
+
"Runs in-process (native addon) with a warm Searcher for the Pi session.",
|
|
171
248
|
"",
|
|
172
249
|
CODEMODE_TYPES_FOR_MODEL,
|
|
173
250
|
"",
|
|
@@ -184,13 +261,29 @@ export function registerAstSgrepTools(pi, runtime = new AstSgrepRuntime(pi), fre
|
|
|
184
261
|
"}",
|
|
185
262
|
].join("\n"),
|
|
186
263
|
parameters: codemodeParameters,
|
|
264
|
+
renderCall(args, theme, context) {
|
|
265
|
+
return presentText(formatCodemodeCall(args.code, theme), context.lastComponent);
|
|
266
|
+
},
|
|
267
|
+
renderResult(result, options, theme, context) {
|
|
268
|
+
const text = result.content[0]?.type === "text" ? result.content[0].text : "";
|
|
269
|
+
const lines = text.split("\n");
|
|
270
|
+
const max = options.expanded ? lines.length : 16;
|
|
271
|
+
const shown = lines.slice(0, max).join("\n");
|
|
272
|
+
const rest = lines.length > max ? `\n… ${lines.length - max} more` : "";
|
|
273
|
+
return presentText(shown + rest, context.lastComponent);
|
|
274
|
+
},
|
|
187
275
|
async execute(_toolCallId, params, signal, onUpdate, ctx) {
|
|
188
276
|
report(onUpdate, "codemode", "started");
|
|
189
277
|
try {
|
|
190
|
-
const
|
|
191
|
-
|
|
278
|
+
const timeoutMs = runtime.config?.timeoutMs ?? 30_000;
|
|
279
|
+
const deadline = Date.now() + timeoutMs;
|
|
280
|
+
const timeoutSignal = AbortSignal.timeout(timeoutMs);
|
|
281
|
+
const operationSignal = signal
|
|
282
|
+
? AbortSignal.any([signal, timeoutSignal])
|
|
283
|
+
: timeoutSignal;
|
|
284
|
+
const options = { signal: operationSignal };
|
|
192
285
|
ensurePool();
|
|
193
|
-
const root = await
|
|
286
|
+
const root = await freshness.ensureFresh(warmRuntime, { cwd: ctx.cwd }, options);
|
|
194
287
|
const env = runtime.nativeEnv?.() ?? { NO_COLOR: "1" };
|
|
195
288
|
let binary = null;
|
|
196
289
|
try {
|
|
@@ -217,25 +310,25 @@ export function registerAstSgrepTools(pi, runtime = new AstSgrepRuntime(pi), fre
|
|
|
217
310
|
stdinOpts.signal = o.signal;
|
|
218
311
|
if (runtime.config?.timeoutMs !== undefined)
|
|
219
312
|
stdinOpts.timeoutMs = runtime.config.timeoutMs;
|
|
313
|
+
if (runtime.config?.maxOutputBytes !== undefined)
|
|
314
|
+
stdinOpts.maxOutputBytes = runtime.config.maxOutputBytes;
|
|
220
315
|
return runBatchViaStdin(stdinOpts);
|
|
221
316
|
});
|
|
222
317
|
}
|
|
223
318
|
const bundle = createAsgrepConnector(batchHost, { cwd: ctx.cwd }, options);
|
|
224
319
|
bundle.resetStats();
|
|
225
320
|
const codemodeOptions = { stats: bundle.stats };
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
if (signal)
|
|
229
|
-
codemodeOptions.signal = signal;
|
|
321
|
+
codemodeOptions.timeoutMs = Math.max(1, deadline - Date.now());
|
|
322
|
+
codemodeOptions.signal = operationSignal;
|
|
230
323
|
const outcome = await runCodemode(params.code, bundle.asgrep, codemodeOptions);
|
|
231
324
|
report(onUpdate, "codemode", "completed");
|
|
232
325
|
if (!outcome.ok) {
|
|
233
326
|
return {
|
|
234
|
-
content: [{ type: "text", text: bounded(`codemode failed: ${outcome.error
|
|
327
|
+
content: [{ type: "text", text: bounded(`codemode failed: ${outcome.error}`) }],
|
|
235
328
|
details: {
|
|
236
329
|
ok: false,
|
|
237
330
|
command: "codemode",
|
|
238
|
-
error: { code: "CODEMODE_ERROR", message: outcome.error
|
|
331
|
+
error: { code: "CODEMODE_ERROR", message: outcome.error, details: { logs: outcome.logs, stats: outcome.stats } },
|
|
239
332
|
code: outcome.code,
|
|
240
333
|
stats: outcome.stats,
|
|
241
334
|
wallMs: outcome.wallMs,
|
|
@@ -243,9 +336,14 @@ export function registerAstSgrepTools(pi, runtime = new AstSgrepRuntime(pi), fre
|
|
|
243
336
|
},
|
|
244
337
|
};
|
|
245
338
|
}
|
|
246
|
-
const rendered =
|
|
339
|
+
const rendered = formatCodemodeResult(outcome.result, {
|
|
340
|
+
...(outcome.stats ? { stats: outcome.stats } : {}),
|
|
341
|
+
wallMs: outcome.wallMs,
|
|
342
|
+
backend: pool.backend(),
|
|
343
|
+
});
|
|
344
|
+
const activationMs = outcome.wallMs;
|
|
247
345
|
return {
|
|
248
|
-
content: [{ type: "text", text: bounded(
|
|
346
|
+
content: [{ type: "text", text: bounded(rendered) }],
|
|
249
347
|
details: {
|
|
250
348
|
ok: true,
|
|
251
349
|
command: "codemode",
|
|
@@ -254,6 +352,7 @@ export function registerAstSgrepTools(pi, runtime = new AstSgrepRuntime(pi), fre
|
|
|
254
352
|
rendered,
|
|
255
353
|
stats: outcome.stats,
|
|
256
354
|
wallMs: outcome.wallMs,
|
|
355
|
+
activationMs,
|
|
257
356
|
backend: pool.backend(),
|
|
258
357
|
},
|
|
259
358
|
};
|
|
@@ -263,26 +362,39 @@ export function registerAstSgrepTools(pi, runtime = new AstSgrepRuntime(pi), fre
|
|
|
263
362
|
}
|
|
264
363
|
},
|
|
265
364
|
});
|
|
266
|
-
// Escape hatches: one-shot tools for simple lookups. Prefer
|
|
365
|
+
// Escape hatches: one-shot tools for simple lookups. Prefer asgrep.
|
|
267
366
|
// They ride the same session sticky pool when available (no cold spawn).
|
|
268
367
|
pi.registerTool({
|
|
269
368
|
name: "asgrep_search",
|
|
270
|
-
label: "
|
|
271
|
-
|
|
369
|
+
label: "asgrep search",
|
|
370
|
+
promptSnippet: "One-shot asgrep search (natural, defs, callers, pattern, chain, semantic)",
|
|
371
|
+
description: "One-shot search. Prefer asgrep for anything multi-step, parallel, or filtered. Call this on your own whenever a single lookup is enough.",
|
|
272
372
|
parameters: searchParameters,
|
|
373
|
+
renderCall(args, theme, context) {
|
|
374
|
+
return presentText(formatSearchCall(args, theme), context.lastComponent);
|
|
375
|
+
},
|
|
376
|
+
renderResult(result, _options, _theme, context) {
|
|
377
|
+
const text = result.content[0]?.type === "text" ? result.content[0].text : "";
|
|
378
|
+
return presentText(text, context.lastComponent);
|
|
379
|
+
},
|
|
273
380
|
async execute(_toolCallId, params, signal, onUpdate, ctx) {
|
|
274
381
|
const options = signal ? { signal } : {};
|
|
382
|
+
const started = performance.now();
|
|
275
383
|
report(onUpdate, "search", "started");
|
|
276
384
|
try {
|
|
277
|
-
await freshness.ensureFresh(warmRuntime, { cwd: ctx.cwd }, options);
|
|
278
385
|
ensurePool();
|
|
279
|
-
const root = await
|
|
386
|
+
const root = await freshness.ensureFresh(warmRuntime, { cwd: ctx.cwd }, options);
|
|
280
387
|
const sticky = await pool.acquire(root);
|
|
281
388
|
const response = sticky
|
|
282
389
|
? await sticky.call(...searchToolCall(params), options)
|
|
283
|
-
: await
|
|
390
|
+
: await runCli(searchArgs(params), { cwd: ctx.cwd }, options);
|
|
284
391
|
report(onUpdate, "search", "completed");
|
|
285
|
-
return success("search", response
|
|
392
|
+
return success("search", response, {
|
|
393
|
+
query: params.query,
|
|
394
|
+
mode: params.mode ?? "natural",
|
|
395
|
+
activationMs: performance.now() - started,
|
|
396
|
+
backend: pool.backend(),
|
|
397
|
+
});
|
|
286
398
|
}
|
|
287
399
|
catch (cause) {
|
|
288
400
|
return failure("search", cause);
|
|
@@ -291,9 +403,17 @@ export function registerAstSgrepTools(pi, runtime = new AstSgrepRuntime(pi), fre
|
|
|
291
403
|
});
|
|
292
404
|
pi.registerTool({
|
|
293
405
|
name: "asgrep_index",
|
|
294
|
-
label: "
|
|
295
|
-
|
|
406
|
+
label: "asgrep index",
|
|
407
|
+
promptSnippet: "Build or rebuild the asgrep index",
|
|
408
|
+
description: "Build or rebuild the index. Prefer asgrep.indexRepo inside asgrep.",
|
|
296
409
|
parameters: indexParameters,
|
|
410
|
+
renderCall(args, theme, context) {
|
|
411
|
+
return presentText(formatIndexCall(args.force === true, theme), context.lastComponent);
|
|
412
|
+
},
|
|
413
|
+
renderResult(result, _options, _theme, context) {
|
|
414
|
+
const text = result.content[0]?.type === "text" ? result.content[0].text : "";
|
|
415
|
+
return presentText(text, context.lastComponent);
|
|
416
|
+
},
|
|
297
417
|
async execute(_toolCallId, params, signal, onUpdate, ctx) {
|
|
298
418
|
const force = params.force === true;
|
|
299
419
|
const command = force ? "reindex" : "index";
|
|
@@ -304,7 +424,7 @@ export function registerAstSgrepTools(pi, runtime = new AstSgrepRuntime(pi), fre
|
|
|
304
424
|
const sticky = await pool.acquire(root);
|
|
305
425
|
const response = sticky
|
|
306
426
|
? await sticky.call("index_repo", { force }, signal ? { signal } : {})
|
|
307
|
-
: await
|
|
427
|
+
: await runCli([command, ".", "--json"], { cwd: ctx.cwd }, signal ? { signal } : {});
|
|
308
428
|
report(onUpdate, command, "completed");
|
|
309
429
|
return success(command, response);
|
|
310
430
|
}
|
|
@@ -315,9 +435,17 @@ export function registerAstSgrepTools(pi, runtime = new AstSgrepRuntime(pi), fre
|
|
|
315
435
|
});
|
|
316
436
|
pi.registerTool({
|
|
317
437
|
name: "asgrep_status",
|
|
318
|
-
label: "
|
|
319
|
-
|
|
438
|
+
label: "asgrep status",
|
|
439
|
+
promptSnippet: "asgrep index and backend status",
|
|
440
|
+
description: "Index/runtime status. Prefer asgrep.indexStatus inside asgrep.",
|
|
320
441
|
parameters: statusParameters,
|
|
442
|
+
renderCall(_args, theme, context) {
|
|
443
|
+
return presentText(formatStatusCall(theme), context.lastComponent);
|
|
444
|
+
},
|
|
445
|
+
renderResult(result, _options, _theme, context) {
|
|
446
|
+
const text = result.content[0]?.type === "text" ? result.content[0].text : "";
|
|
447
|
+
return presentText(text, context.lastComponent);
|
|
448
|
+
},
|
|
321
449
|
async execute(_toolCallId, _params, signal, onUpdate, ctx) {
|
|
322
450
|
report(onUpdate, "status", "started");
|
|
323
451
|
try {
|
|
@@ -326,7 +454,7 @@ export function registerAstSgrepTools(pi, runtime = new AstSgrepRuntime(pi), fre
|
|
|
326
454
|
const sticky = await pool.acquire(root);
|
|
327
455
|
const response = sticky
|
|
328
456
|
? await sticky.call("index_status", {}, signal ? { signal } : {})
|
|
329
|
-
: await
|
|
457
|
+
: await runCli(["status", ".", "--json"], { cwd: ctx.cwd }, signal ? { signal } : {});
|
|
330
458
|
report(onUpdate, "status", "completed");
|
|
331
459
|
return success("status", response);
|
|
332
460
|
}
|
|
@@ -336,77 +464,35 @@ export function registerAstSgrepTools(pi, runtime = new AstSgrepRuntime(pi), fre
|
|
|
336
464
|
},
|
|
337
465
|
});
|
|
338
466
|
}
|
|
339
|
-
|
|
467
|
+
const SEARCH_CALL_SPEC = {
|
|
468
|
+
semantic: { tool: "semantic" },
|
|
469
|
+
chain: { tool: "chain" },
|
|
470
|
+
defs: { tool: "defs", key: "symbol" },
|
|
471
|
+
callers: { tool: "callers", key: "symbol" },
|
|
472
|
+
imports: { tool: "imports", key: "module" },
|
|
473
|
+
pattern: { tool: "search", prefix: "pattern" },
|
|
474
|
+
word: { tool: "search", prefix: "word" },
|
|
475
|
+
literal: { tool: "search", prefix: "literal" },
|
|
476
|
+
regex: { tool: "search", prefix: "regex" },
|
|
477
|
+
natural: { tool: "search" },
|
|
478
|
+
};
|
|
340
479
|
function searchToolCall(params) {
|
|
341
480
|
const mode = params.mode ?? "natural";
|
|
342
481
|
const limit = params.limit ?? DEFAULT_LIMIT;
|
|
343
482
|
const excerpt_lines = params.excerptLines ?? 0;
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
case "chain":
|
|
348
|
-
return ["chain", { query: params.query, limit, top_n: 20 }];
|
|
349
|
-
case "defs":
|
|
350
|
-
return ["defs", { symbol: params.query, limit, excerpt_lines }];
|
|
351
|
-
case "callers":
|
|
352
|
-
return ["callers", { symbol: params.query, limit, excerpt_lines }];
|
|
353
|
-
case "imports":
|
|
354
|
-
return ["imports", { module: params.query, limit, excerpt_lines }];
|
|
355
|
-
case "pattern":
|
|
356
|
-
case "word":
|
|
357
|
-
case "literal":
|
|
358
|
-
case "regex":
|
|
359
|
-
return ["search", { query: `${mode}: ${params.query}`, limit, excerpt_lines, format: "capsule" }];
|
|
360
|
-
case "natural":
|
|
361
|
-
return ["search", { query: params.query, limit, excerpt_lines, format: "capsule" }];
|
|
362
|
-
default: {
|
|
363
|
-
const _exhaustive = mode;
|
|
364
|
-
void _exhaustive;
|
|
365
|
-
return ["search", { query: params.query, limit, excerpt_lines, format: "capsule" }];
|
|
366
|
-
}
|
|
483
|
+
const spec = SEARCH_CALL_SPEC[mode];
|
|
484
|
+
if (spec.tool === "semantic") {
|
|
485
|
+
return ["semantic", { query: params.query, limit, excerpt_lines, format: "capsule" }];
|
|
367
486
|
}
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
try {
|
|
371
|
-
const text = JSON.stringify(value);
|
|
372
|
-
if (text === undefined)
|
|
373
|
-
return String(value);
|
|
374
|
-
return text.length <= MAX_CODEMODE_RESULT_CHARS ? text : `${text.slice(0, MAX_CODEMODE_RESULT_CHARS - 1)}…`;
|
|
375
|
-
}
|
|
376
|
-
catch {
|
|
377
|
-
return String(value);
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
function summarizeCodemode(value, stats, wallMs, backend) {
|
|
381
|
-
const parts = ["codemode completed"];
|
|
382
|
-
if (value && typeof value === "object") {
|
|
383
|
-
const record = value;
|
|
384
|
-
if (Array.isArray(record.hits))
|
|
385
|
-
parts[0] = `codemode completed: ${record.hits.length} hit${record.hits.length === 1 ? "" : "s"}`;
|
|
386
|
-
else if (typeof record.hit_count === "number")
|
|
387
|
-
parts[0] = `codemode completed: ${record.hit_count} hit${record.hit_count === 1 ? "" : "s"}`;
|
|
388
|
-
else if (typeof record.node_count === "number")
|
|
389
|
-
parts[0] = `codemode completed: ${record.node_count} node${record.node_count === 1 ? "" : "s"}`;
|
|
487
|
+
if (spec.tool === "chain") {
|
|
488
|
+
return ["chain", { query: params.query, limit, top_n: 20 }];
|
|
390
489
|
}
|
|
391
|
-
if (
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
parts.push("cli-sticky");
|
|
395
|
-
if (stats && stats.calls > 0) {
|
|
396
|
-
const via = (stats.stickyCalls ?? 0) > 0
|
|
397
|
-
? `native ${stats.stickyCalls}`
|
|
398
|
-
: stats.batchedCalls > 0
|
|
399
|
-
? `batched ${stats.batchedCalls}`
|
|
400
|
-
: stats.parallelSpawnCalls > 0
|
|
401
|
-
? `parallel-spawn ${stats.parallelSpawnCalls}`
|
|
402
|
-
: `${stats.calls} call${stats.calls === 1 ? "" : "s"}`;
|
|
403
|
-
parts.push(via);
|
|
404
|
-
if (stats.waves > 1)
|
|
405
|
-
parts.push(`${stats.waves} waves`);
|
|
490
|
+
if (spec.tool === "search") {
|
|
491
|
+
const query = spec.prefix ? `${spec.prefix}: ${params.query}` : params.query;
|
|
492
|
+
return ["search", { query, limit, excerpt_lines, format: "capsule" }];
|
|
406
493
|
}
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
return parts.join(" · ");
|
|
494
|
+
// defs / callers / imports
|
|
495
|
+
return [spec.tool, { [spec.key]: params.query, limit, excerpt_lines }];
|
|
410
496
|
}
|
|
411
497
|
const COMMANDS = [
|
|
412
498
|
["asgrep-doctor", "Check the ast-sgrep runtime, native binary, index, and project configuration", "doctor"],
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/** Neat asgrep tool chrome for the Pi TUI and the model-visible content. */
|
|
2
|
+
export type PresentTheme = {
|
|
3
|
+
bold(text: string): string;
|
|
4
|
+
fg(role: string, text: string): string;
|
|
5
|
+
};
|
|
6
|
+
export type HitLike = {
|
|
7
|
+
file?: unknown;
|
|
8
|
+
path?: unknown;
|
|
9
|
+
ref?: unknown;
|
|
10
|
+
symbol?: unknown;
|
|
11
|
+
kind?: unknown;
|
|
12
|
+
preview?: unknown;
|
|
13
|
+
start_line?: unknown;
|
|
14
|
+
line?: unknown;
|
|
15
|
+
lines?: unknown;
|
|
16
|
+
};
|
|
17
|
+
export type EnvelopeLike = {
|
|
18
|
+
ok?: unknown;
|
|
19
|
+
hits?: unknown;
|
|
20
|
+
count?: unknown;
|
|
21
|
+
total?: unknown;
|
|
22
|
+
status?: unknown;
|
|
23
|
+
index_status?: unknown;
|
|
24
|
+
counts?: unknown;
|
|
25
|
+
backend?: unknown;
|
|
26
|
+
[key: string]: unknown;
|
|
27
|
+
};
|
|
28
|
+
export declare const ASGREP_PROMPT_SNIPPET = "Search this repo by intent, symbol, callers, defs, pattern, or chain (in-process asgrep; use without being asked)";
|
|
29
|
+
export declare const ASGREP_PROMPT_GUIDELINES: readonly ["For any code lookup (find a function, callers, defs, intent, structural pattern, or imports), call asgrep or asgrep_search immediately. Do not wait for the user to mention ast-sgrep.", "Prefer the asgrep Code Mode tool when you need more than one lookup, filtering, or parallel work. Write JavaScript that calls asgrep.search / semantic / chain / defs / callers / imports / indexStatus / indexRepo and return a small shaped value.", "Use grep only for exact log strings, filenames, or config keys. Use Pi write/edit to change files; asgrep does not mutate source."];
|
|
30
|
+
export declare function formatSearchCall(params: {
|
|
31
|
+
query?: string;
|
|
32
|
+
mode?: string;
|
|
33
|
+
limit?: number;
|
|
34
|
+
excerptLines?: number;
|
|
35
|
+
}, theme?: PresentTheme): string;
|
|
36
|
+
export declare function formatIndexCall(force: boolean, theme?: PresentTheme): string;
|
|
37
|
+
export declare function formatStatusCall(theme?: PresentTheme): string;
|
|
38
|
+
export declare function formatCodemodeCall(code: string, theme?: PresentTheme): string;
|
|
39
|
+
export declare function formatSearchResult(response: EnvelopeLike, meta: {
|
|
40
|
+
command: string;
|
|
41
|
+
query?: string;
|
|
42
|
+
mode?: string;
|
|
43
|
+
activationMs?: number;
|
|
44
|
+
backend?: string;
|
|
45
|
+
}, theme?: PresentTheme): string;
|
|
46
|
+
export declare function formatStatusResult(response: EnvelopeLike, theme?: PresentTheme): string;
|
|
47
|
+
export declare function formatIndexResult(command: string, response: EnvelopeLike, theme?: PresentTheme): string;
|
|
48
|
+
export declare function formatCodemodeResult(value: unknown, meta?: {
|
|
49
|
+
stats?: {
|
|
50
|
+
calls: number;
|
|
51
|
+
batchedCalls: number;
|
|
52
|
+
parallelSpawnCalls: number;
|
|
53
|
+
stickyCalls?: number;
|
|
54
|
+
waves: number;
|
|
55
|
+
};
|
|
56
|
+
wallMs?: number;
|
|
57
|
+
backend?: string;
|
|
58
|
+
}, theme?: PresentTheme): string;
|
|
59
|
+
/** Minimal pi-tui Text stand-in so we do not take a TUI package dependency. */
|
|
60
|
+
export declare class AsgrepText {
|
|
61
|
+
#private;
|
|
62
|
+
constructor(text?: string);
|
|
63
|
+
setText(text: string): void;
|
|
64
|
+
invalidate(): void;
|
|
65
|
+
render(_width: number): string[];
|
|
66
|
+
}
|
|
67
|
+
export declare function presentText(formatted: string, last: unknown): AsgrepText;
|