pi-ast-sgrep 1.3.2 → 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/dist/index.js CHANGED
@@ -1,5 +1,7 @@
1
1
  import { Type } from "typebox";
2
+ import { createAsgrepConnector, runCodemode, runNativeBatch, runBatchViaStdin, CODEMODE_TYPES_FOR_MODEL, NativeSessionPool, argvFor, asEnvelope, } from "./codemode/index.js";
2
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";
3
5
  const DEFAULT_LIMIT = 8;
4
6
  const MAX_LIMIT = 100;
5
7
  const MAX_EXCERPT_LINES = 100;
@@ -25,26 +27,40 @@ const indexParameters = Type.Object({
25
27
  force: Type.Optional(Type.Boolean({ default: false, description: "Rebuild the index from scratch" })),
26
28
  }, { additionalProperties: false });
27
29
  const statusParameters = Type.Object({}, { additionalProperties: false });
30
+ const codemodeParameters = Type.Object({
31
+ code: Type.String({
32
+ minLength: 1,
33
+ maxLength: 32_000,
34
+ description: "JavaScript async body that calls asgrep.* methods. Prefer Promise.all for independent lookups. Return only the shaped final value.",
35
+ }),
36
+ }, { additionalProperties: false });
28
37
  function bounded(text) {
29
38
  return text.length <= MAX_CONTENT_CHARS ? text : `${text.slice(0, MAX_CONTENT_CHARS - 1)}…`;
30
39
  }
31
- function success(command, response) {
32
- const count = Array.isArray(response.hits) ? response.hits.length :
33
- typeof response.count === "number" ? response.count :
34
- typeof response.total === "number" ? response.total : undefined;
35
- const summary = count === undefined ? `${command} completed` : `${command} completed: ${count} result${count === 1 ? "" : "s"}`;
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 });
36
46
  return {
37
- content: [{ type: "text", text: bounded(summary) }],
38
- details: { ok: true, command, response },
47
+ content: [{ type: "text", text: bounded(text) }],
48
+ // The tool execute owns its machine command: normalize the envelope's
49
+ // command (native catalog names like index_status/index_repo must surface
50
+ // as the machine commands status/index/reindex).
51
+ details: { ok: true, command, response: { ...response, command }, ...extra },
39
52
  };
40
53
  }
41
- function errorDetails(cause) {
54
+ function errorDetails(cause, signal) {
55
+ if (signal?.aborted) {
56
+ return { code: "CANCELLED", message: "cancelled", details: {} };
57
+ }
42
58
  return cause instanceof RuntimeError
43
59
  ? { code: cause.code, message: cause.message, details: cause.details }
44
60
  : { code: "UNEXPECTED_ERROR", message: cause instanceof Error ? cause.message : String(cause), details: {} };
45
61
  }
46
- function failure(command, cause) {
47
- const error = errorDetails(cause);
62
+ function failure(command, cause, signal) {
63
+ const error = errorDetails(cause, signal);
48
64
  return {
49
65
  content: [{ type: "text", text: bounded(`${command} failed [${error.code}]: ${error.message}`) }],
50
66
  details: { ok: false, command, error },
@@ -85,43 +101,399 @@ async function execute(runtime, command, args, signal, onUpdate, ctx, before) {
85
101
  export function registerAstSgrepTools(pi, runtime = new AstSgrepRuntime(pi), freshness = runtime instanceof AstSgrepRuntime
86
102
  ? new FreshnessCoordinator({ refreshIntervalMs: runtime.config.refreshIntervalMs })
87
103
  : new FreshnessCoordinator()) {
104
+ const pool = new NativeSessionPool();
105
+ let poolConfigured = false;
106
+ // Prefer a registration-local pool so tests / multi-agent hosts do not share
107
+ // sticky state. sharedNativePool remains for advanced single-session reuse.
108
+ const ensurePool = () => {
109
+ if (poolConfigured)
110
+ return;
111
+ try {
112
+ const env = runtime.nativeEnv?.() ?? { NO_COLOR: "1" };
113
+ let binary;
114
+ try {
115
+ binary = runtime.resolveBinaryPath?.({ env });
116
+ }
117
+ catch {
118
+ binary = undefined;
119
+ }
120
+ const opts = { env };
121
+ if (binary)
122
+ opts.binary = binary;
123
+ if (runtime.config?.timeoutMs !== undefined)
124
+ opts.timeoutMs = runtime.config.timeoutMs;
125
+ if (runtime.config?.maxOutputBytes !== undefined)
126
+ opts.maxOutputBytes = runtime.config.maxOutputBytes;
127
+ if (typeof env.ASGREP_NO_EMBED === "string") {
128
+ opts.useEmbed = env.ASGREP_NO_EMBED !== "1" && env.ASGREP_NO_EMBED !== "true";
129
+ }
130
+ if (typeof env.ASGREP_INDEX_PATH === "string")
131
+ opts.indexPath = env.ASGREP_INDEX_PATH;
132
+ pool.configure(opts);
133
+ }
134
+ catch {
135
+ pool.configure({});
136
+ }
137
+ poolConfigured = true;
138
+ };
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
+ };
178
+ const nativeCall = async (tool, args, context, options = {}) => {
179
+ ensurePool();
180
+ const root = await resolveRoot(context.cwd);
181
+ const worker = await pool.acquire(root);
182
+ if (worker) {
183
+ return asEnvelope(await worker.call(tool, args, options.signal ? { signal: options.signal } : {}));
184
+ }
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);
187
+ };
188
+ // Freshness + tools share the same warm in-process Searcher as Code Mode.
189
+ const warmRuntime = {
190
+ run: (args, context, options) => runtime.run(args, context, options),
191
+ resolveRoot: (context) => resolveRoot(context.cwd),
192
+ nativeCall,
193
+ };
194
+ if (runtime.watchExternalChanges !== undefined) {
195
+ warmRuntime.watchExternalChanges = runtime.watchExternalChanges;
196
+ }
197
+ if (runtime.resolveIndexPath) {
198
+ warmRuntime.resolveIndexPath = (root) => runtime.resolveIndexPath(root);
199
+ }
200
+ if (runtime.inspectIndexCompatibility) {
201
+ warmRuntime.inspectIndexCompatibility = (context) => runtime.inspectIndexCompatibility(context);
202
+ }
203
+ if (runtime.rebuildIncompatibleIndex) {
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
+ };
209
+ }
88
210
  pi.on("tool_result", (event, ctx) => {
89
- if (event.isError || (event.toolName !== "write" && event.toolName !== "edit"))
211
+ if (event.isError)
212
+ return;
213
+ if (event.toolName !== "write" && event.toolName !== "edit")
90
214
  return;
91
215
  const path = event.input.path;
92
216
  if (typeof path === "string")
93
217
  freshness.markAffectedPath(path, ctx.cwd);
94
218
  });
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.
238
+ // Sibling to MCP: pick one surface; both link core, never each other.
239
+ pi.registerTool({
240
+ name: "asgrep",
241
+ label: "asgrep",
242
+ promptSnippet: ASGREP_PROMPT_SNIPPET,
243
+ promptGuidelines: [...ASGREP_PROMPT_GUIDELINES],
244
+ description: [
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.",
248
+ "",
249
+ CODEMODE_TYPES_FOR_MODEL,
250
+ "",
251
+ "Example:",
252
+ "async () => {",
253
+ " const [seed, status] = await Promise.all([",
254
+ " asgrep.search({ query: 'auth refresh', limit: 5 }),",
255
+ " asgrep.indexStatus(),",
256
+ " ]);",
257
+ " const symbol = seed.hits?.[0]?.symbol;",
258
+ " if (!symbol) return { seed, status };",
259
+ " const graph = await asgrep.chain({ query: symbol, limit: 20 });",
260
+ " return { symbol, nodes: graph.nodes?.slice?.(0, 10) ?? graph, status };",
261
+ "}",
262
+ ].join("\n"),
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
+ },
275
+ async execute(_toolCallId, params, signal, onUpdate, ctx) {
276
+ report(onUpdate, "codemode", "started");
277
+ try {
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 };
285
+ ensurePool();
286
+ const root = await freshness.ensureFresh(warmRuntime, { cwd: ctx.cwd }, options);
287
+ const env = runtime.nativeEnv?.() ?? { NO_COLOR: "1" };
288
+ let binary = null;
289
+ try {
290
+ binary = runtime.resolveBinaryPath?.({ env }) ?? null;
291
+ }
292
+ catch {
293
+ binary = null;
294
+ }
295
+ // In-process NAPI first; CLI sticky only if addon missing.
296
+ const sticky = await pool.acquire(root);
297
+ const batchHost = {
298
+ run: (args, context, runOptions) => runtime.run(args, context, runOptions ?? {}),
299
+ sticky,
300
+ };
301
+ if (binary) {
302
+ batchHost.runBatch = (calls, context, runOptions) => runNativeBatch((a, c, o) => runtime.run(a, c, o ?? {}), calls, context, runOptions, (body, c, o) => {
303
+ const stdinOpts = {
304
+ binary: binary,
305
+ cwd: c.cwd,
306
+ body,
307
+ env,
308
+ };
309
+ if (o?.signal)
310
+ stdinOpts.signal = o.signal;
311
+ if (runtime.config?.timeoutMs !== undefined)
312
+ stdinOpts.timeoutMs = runtime.config.timeoutMs;
313
+ if (runtime.config?.maxOutputBytes !== undefined)
314
+ stdinOpts.maxOutputBytes = runtime.config.maxOutputBytes;
315
+ return runBatchViaStdin(stdinOpts);
316
+ });
317
+ }
318
+ const bundle = createAsgrepConnector(batchHost, { cwd: ctx.cwd }, options);
319
+ bundle.resetStats();
320
+ const codemodeOptions = { stats: bundle.stats };
321
+ codemodeOptions.timeoutMs = Math.max(1, deadline - Date.now());
322
+ codemodeOptions.signal = operationSignal;
323
+ const outcome = await runCodemode(params.code, bundle.asgrep, codemodeOptions);
324
+ report(onUpdate, "codemode", "completed");
325
+ if (!outcome.ok) {
326
+ return {
327
+ content: [{ type: "text", text: bounded(`codemode failed: ${outcome.error}`) }],
328
+ details: {
329
+ ok: false,
330
+ command: "codemode",
331
+ error: { code: "CODEMODE_ERROR", message: outcome.error, details: { logs: outcome.logs, stats: outcome.stats } },
332
+ code: outcome.code,
333
+ stats: outcome.stats,
334
+ wallMs: outcome.wallMs,
335
+ backend: pool.backend(),
336
+ },
337
+ };
338
+ }
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;
345
+ return {
346
+ content: [{ type: "text", text: bounded(rendered) }],
347
+ details: {
348
+ ok: true,
349
+ command: "codemode",
350
+ result: outcome.result,
351
+ logs: outcome.logs,
352
+ rendered,
353
+ stats: outcome.stats,
354
+ wallMs: outcome.wallMs,
355
+ activationMs,
356
+ backend: pool.backend(),
357
+ },
358
+ };
359
+ }
360
+ catch (cause) {
361
+ return failure("codemode", cause);
362
+ }
363
+ },
364
+ });
365
+ // Escape hatches: one-shot tools for simple lookups. Prefer asgrep.
366
+ // They ride the same session sticky pool when available (no cold spawn).
95
367
  pi.registerTool({
96
368
  name: "asgrep_search",
97
- label: "ast-sgrep search",
98
- description: "Search project code with natural language, structural patterns, symbol relationships, chains, or semantic retrieval.",
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.",
99
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
+ },
100
380
  async execute(_toolCallId, params, signal, onUpdate, ctx) {
101
381
  const options = signal ? { signal } : {};
102
- return execute(runtime, "search", searchArgs(params), signal, onUpdate, ctx, () => freshness.ensureFresh(runtime, { cwd: ctx.cwd }, options).then(() => undefined));
382
+ const started = performance.now();
383
+ report(onUpdate, "search", "started");
384
+ try {
385
+ ensurePool();
386
+ const root = await freshness.ensureFresh(warmRuntime, { cwd: ctx.cwd }, options);
387
+ const sticky = await pool.acquire(root);
388
+ const response = sticky
389
+ ? await sticky.call(...searchToolCall(params), options)
390
+ : await runCli(searchArgs(params), { cwd: ctx.cwd }, options);
391
+ report(onUpdate, "search", "completed");
392
+ return success("search", response, {
393
+ query: params.query,
394
+ mode: params.mode ?? "natural",
395
+ activationMs: performance.now() - started,
396
+ backend: pool.backend(),
397
+ });
398
+ }
399
+ catch (cause) {
400
+ return failure("search", cause);
401
+ }
103
402
  },
104
403
  });
105
404
  pi.registerTool({
106
405
  name: "asgrep_index",
107
- label: "ast-sgrep index",
108
- description: "Build or rebuild the ast-sgrep project index.",
406
+ label: "asgrep index",
407
+ promptSnippet: "Build or rebuild the asgrep index",
408
+ description: "Build or rebuild the index. Prefer asgrep.indexRepo inside asgrep.",
109
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
+ },
110
417
  async execute(_toolCallId, params, signal, onUpdate, ctx) {
111
- const command = params.force === true ? "reindex" : "index";
112
- return execute(runtime, command, [command, ".", "--json"], signal, onUpdate, ctx);
418
+ const force = params.force === true;
419
+ const command = force ? "reindex" : "index";
420
+ report(onUpdate, command, "started");
421
+ try {
422
+ ensurePool();
423
+ const root = await resolveRoot(ctx.cwd);
424
+ const sticky = await pool.acquire(root);
425
+ const response = sticky
426
+ ? await sticky.call("index_repo", { force }, signal ? { signal } : {})
427
+ : await runCli([command, ".", "--json"], { cwd: ctx.cwd }, signal ? { signal } : {});
428
+ report(onUpdate, command, "completed");
429
+ return success(command, response);
430
+ }
431
+ catch (cause) {
432
+ return failure(command, cause, signal);
433
+ }
113
434
  },
114
435
  });
115
436
  pi.registerTool({
116
437
  name: "asgrep_status",
117
- label: "ast-sgrep status",
118
- description: "Return runtime version, protocol, root, index, counts, backend, IVF, and capability status.",
438
+ label: "asgrep status",
439
+ promptSnippet: "asgrep index and backend status",
440
+ description: "Index/runtime status. Prefer asgrep.indexStatus inside asgrep.",
119
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
+ },
120
449
  async execute(_toolCallId, _params, signal, onUpdate, ctx) {
121
- return execute(runtime, "status", ["status", ".", "--json"], signal, onUpdate, ctx);
450
+ report(onUpdate, "status", "started");
451
+ try {
452
+ ensurePool();
453
+ const root = await resolveRoot(ctx.cwd);
454
+ const sticky = await pool.acquire(root);
455
+ const response = sticky
456
+ ? await sticky.call("index_status", {}, signal ? { signal } : {})
457
+ : await runCli(["status", ".", "--json"], { cwd: ctx.cwd }, signal ? { signal } : {});
458
+ report(onUpdate, "status", "completed");
459
+ return success("status", response);
460
+ }
461
+ catch (cause) {
462
+ return failure("status", cause);
463
+ }
122
464
  },
123
465
  });
124
466
  }
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
+ };
479
+ function searchToolCall(params) {
480
+ const mode = params.mode ?? "natural";
481
+ const limit = params.limit ?? DEFAULT_LIMIT;
482
+ const excerpt_lines = params.excerptLines ?? 0;
483
+ const spec = SEARCH_CALL_SPEC[mode];
484
+ if (spec.tool === "semantic") {
485
+ return ["semantic", { query: params.query, limit, excerpt_lines, format: "capsule" }];
486
+ }
487
+ if (spec.tool === "chain") {
488
+ return ["chain", { query: params.query, limit, top_n: 20 }];
489
+ }
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" }];
493
+ }
494
+ // defs / callers / imports
495
+ return [spec.tool, { [spec.key]: params.query, limit, excerpt_lines }];
496
+ }
125
497
  const COMMANDS = [
126
498
  ["asgrep-doctor", "Check the ast-sgrep runtime, native binary, index, and project configuration", "doctor"],
127
499
  ["asgrep-status", "Show ast-sgrep runtime, index, backend, and capability status", "status"],
@@ -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;