pi-readseek 0.11.4 → 0.12.1

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.ts CHANGED
@@ -16,22 +16,22 @@ var EDIT_VARIANTS = [
16
16
  ];
17
17
  var GIT_SCOPES = ["tracked", "untracked", "ignored", "all"];
18
18
  var TOOL_NAMES = [
19
- "show_content",
20
- "show_structure",
21
- "show_metadata",
22
- "show_assets",
23
- "check_syntax",
24
- "check_rules",
25
- "generate_caption",
26
- "detect_objects",
27
- "recognize_text",
28
- "find_nodes",
29
- "find_text",
30
- "find_definitions",
31
- "find_references",
32
- "edit_text",
33
- "rename_symbol",
34
- "write_file"
19
+ "show",
20
+ "tree",
21
+ "meta",
22
+ "assets",
23
+ "syntax",
24
+ "check",
25
+ "caption",
26
+ "objects",
27
+ "ocr",
28
+ "search",
29
+ "grep",
30
+ "def",
31
+ "refs",
32
+ "edit",
33
+ "rename",
34
+ "write"
35
35
  ];
36
36
  var PARAM_DESCRIPTIONS = {
37
37
  path: "Absolute or relative file path",
@@ -43,12 +43,12 @@ var PARAM_DESCRIPTIONS = {
43
43
  pattern: "AST pattern (ast-grep style), such as console.log($$$ARGS)",
44
44
  patternContext: "Surrounding code context for sub-statement patterns",
45
45
  selector: "Target AST node kind to extract from pattern context",
46
- grepPattern: "Regex pattern; set literal for exact text",
46
+ grepPattern: "Native regex pattern; set literal for exact text. Empty regex matches every line; empty literal is invalid.",
47
47
  name: "Qualified or unqualified symbol name",
48
48
  refsName: "Identifier to find references for",
49
49
  gitScope: "In a Git repository, which files to search: tracked, untracked, ignored (untracked plus ignored), or all. Defaults to tracked plus untracked.",
50
- at: "Location selector: line:N[:COLUMN], hash:H[:COLUMN], symbol:NAME, page:N, node:ID, or asset:ID",
51
- assetAt: "Document selector: page:N, node:ID, or asset:ID",
50
+ at: "Location selector: N[:COLUMN], START-END, H[:COLUMN], NAME, line:N[:COLUMN], hash:H[:COLUMN], symbol:NAME, page:N, node:ID, or asset:ID",
51
+ assetAt: "Document selector: N, page:N, node:ID, or asset:ID",
52
52
  rule: "KDL node selection rule, @path, or '-' for standard input",
53
53
  rules: "KDL structural checks, @path, or '-' for standard input",
54
54
  stdinName: "File name hint when the target is '-'",
@@ -60,7 +60,6 @@ var PARAM_DESCRIPTIONS = {
60
60
  kind: "Restrict structure to this node kind",
61
61
  outline: "Show outline of document hierarchy or source symbols",
62
62
  noAnchors: "Omit source line anchors",
63
- text: "Literal native text query; adapter find_text also supports regex via pattern",
64
63
  edits: "Use set_line, replace_lines, insert_after, replace_symbol, or replace",
65
64
  editVariant: "Exactly one nested edit variant",
66
65
  setLine: 'Replace one line: {"set_line":{"anchor":"LINE:HASH","new_text":"..."}}',
@@ -92,27 +91,28 @@ var PARAM_DESCRIPTIONS = {
92
91
  literal: "Treat pattern literally",
93
92
  context: "Surrounding lines for each match",
94
93
  matchLimit: "Maximum matches to return",
94
+ grepLimit: "Maximum matching lines to return; positive integer, defaults to 100",
95
95
  summary: "Return per-file counts",
96
96
  grepScope: "Group matches by enclosing symbol",
97
- scopeContext: "Context lines within each symbol"
97
+ scopeContext: "Non-negative context lines within each symbol; requires scope: symbol"
98
98
  };
99
99
  var TOOL_DESCRIPTIONS = {
100
- show_content: "Read source, document content, or image descriptors without inference. Obtain fresh `LINE:HASH` anchors before edits; use at with a column for cursor context. Document results include typed nodes and cached asset paths.",
101
- show_structure: "Show parsed source or document hierarchy. Use shallow depth for file overviews.",
102
- show_metadata: "Show format, source identity, language, dimensions, or page count without decoding image content or inference.",
103
- show_assets: "Return selected document asset descriptors with usable cached paths. Adapters attach images from those paths.",
104
- check_syntax: "Check parser syntax diagnostics. Exit 0 is clean, 1 has findings, 2 is failure; this is not type checking.",
105
- check_rules: "Evaluate KDL structural assertions. Exit 0 is clean, 1 has findings, 2 is failure.",
106
- generate_caption: "Explicitly infer a caption using an installed model. Include text and objects to request OCR and detection too; never downloads models automatically.",
107
- detect_objects: "Explicitly infer object labels and bounding boxes using an installed model.",
108
- recognize_text: "Explicitly recognize text from pixels using an installed model; reading never silently invokes OCR.",
109
- find_nodes: "Find AST nodes by pattern or KDL selection rule. Returns edit-ready anchors. Use `find_text` for text and regex.",
110
- find_text: "Search text or regex and return edit-ready `LINE:HASH` anchors. Native CLI text is literal; adapters preserve regex, glob, context, summary, and symbol grouping.",
111
- find_definitions: "Find a symbol definition or declaration by qualified or unqualified name.",
112
- find_references: "Find identifier-name matches with name, or binding-scoped references with at. Use before renaming or deleting a symbol.",
113
- edit_text: "Edit existing files with fresh `LINE:HASH` anchors. Each edits item contains exactly one set_line, replace_lines, insert_after, replace_symbol, or replace object. Preview before applying a verified plan.",
114
- rename_symbol: "Rename a symbol at a line or hash cursor with binding-aware edits and stale-plan protection.",
115
- write_file: "Create or replace a complete text file and return `LINE:HASH` anchors. Use `edit_text` for small changes."
100
+ show: "Read source, document content, or image descriptors without inference. Obtain fresh `LINE:HASH` anchors before edits; use at with a column for cursor context. Document results include typed nodes and cached asset paths.",
101
+ tree: "Show parsed source or document hierarchy. Use shallow depth for file overviews.",
102
+ meta: "Show format, source identity, language, dimensions, or page count without decoding image content or inference.",
103
+ assets: "Return selected document asset descriptors with usable cached paths. Adapters attach images from those paths.",
104
+ syntax: "Check parser syntax diagnostics. Exit 0 is clean, 1 has findings, 2 is failure; this is not type checking.",
105
+ check: "Evaluate KDL structural assertions. Exit 0 is clean, 1 has findings, 2 is failure.",
106
+ caption: "Explicitly infer a caption using an installed model. Include text and objects to request OCR and detection too; never downloads models automatically.",
107
+ objects: "Explicitly infer object labels and bounding boxes using an installed model.",
108
+ ocr: "Explicitly recognize text from pixels using an installed model; reading never silently invokes OCR.",
109
+ search: "Find AST nodes by pattern or KDL selection rule. Returns edit-ready anchors. Use `grep` for text and regex.",
110
+ grep: "Search with native regex or literal matching. Returns matching-line counts, source LINE:HASH anchors, and document node-local locations. Native glob, context, summary, limits, Git scope, and symbol grouping apply; default limit is 100 matching lines.",
111
+ def: "Find a symbol definition or declaration by qualified or unqualified name.",
112
+ refs: "Find identifier-name matches with name, or binding-scoped references with at. Use before renaming or deleting a symbol.",
113
+ edit: "Edit existing files with fresh `LINE:HASH` anchors. Each edits item contains exactly one set_line, replace_lines, insert_after, replace_symbol, or replace object. Preview before applying a verified plan.",
114
+ rename: "Rename a symbol at a line or hash cursor with binding-aware edits and stale-plan protection.",
115
+ write: "Create or replace a complete text file and return `LINE:HASH` anchors. Use `edit` for small changes."
116
116
  };
117
117
  function rewriteBacktickedToolNames(text, aliases = {}) {
118
118
  return TOOL_NAMES.reduce((rewritten, toolName) => rewritten.replaceAll(`\`${toolName}\``, `\`${aliases[toolName] ?? toolName}\``), text);
@@ -205,22 +205,22 @@ function tool(name, title, properties, required) {
205
205
  return { name, title, description: TOOL_DESCRIPTIONS[name], inputSchema: { type: "object", properties, required, additionalProperties: false } };
206
206
  }
207
207
  var TOOL_CONTRACTS = [
208
- tool("show_content", "Show content", { ...path, ...at, ...lang, ...stdin, ...noAnchors, end: integer(PARAM_DESCRIPTIONS.end), limit: integer(PARAM_DESCRIPTIONS.limit) }, ["path"]),
209
- tool("show_structure", "Show structure", { ...path, ...at, ...lang, ...stdin, depth: integer(PARAM_DESCRIPTIONS.depth, 0), kind: string(PARAM_DESCRIPTIONS.kind), outline: { type: "boolean", description: PARAM_DESCRIPTIONS.outline } }, ["path"]),
210
- tool("show_metadata", "Show metadata", { ...path, ...lang, ...stdin }, ["path"]),
211
- tool("show_assets", "Show assets", { ...path, at: string(PARAM_DESCRIPTIONS.assetAt), ...stdin }, ["path"]),
212
- tool("check_syntax", "Check syntax", { ...path, ...lang, ...stdin }, ["path"]),
213
- tool("check_rules", "Check rules", { path: string(PARAM_DESCRIPTIONS.searchPath), ...gitScopeSchema, rules: string(PARAM_DESCRIPTIONS.rules) }, ["rules"]),
214
- tool("generate_caption", "Generate caption", { ...path, ...at, ...stdin, ...level, include: { type: "array", items: { type: "string", enum: ["text", "objects"] }, minItems: 1, uniqueItems: true, description: PARAM_DESCRIPTIONS.include } }, ["path"]),
215
- tool("detect_objects", "Detect objects", { ...path, ...at, ...stdin, ...level }, ["path"]),
216
- tool("recognize_text", "Recognize text", { ...path, ...at, ...stdin, ...level }, ["path"]),
217
- tool("find_nodes", "Find nodes", { ...path, ...lang, ...stdin, ...limit, ...gitScopeSchema, ...noAnchors, pattern: string(PARAM_DESCRIPTIONS.pattern), context: string(PARAM_DESCRIPTIONS.patternContext), selector: string(PARAM_DESCRIPTIONS.selector), rule: string(PARAM_DESCRIPTIONS.rule) }, []),
218
- tool("find_text", "Find text", { ...path, ...limit, ...gitScopeSchema, text: string(PARAM_DESCRIPTIONS.text), pattern: string(PARAM_DESCRIPTIONS.grepPattern), glob: string(PARAM_DESCRIPTIONS.glob), ignoreCase: { type: "boolean", description: PARAM_DESCRIPTIONS.ignoreCase }, literal: { type: "boolean", description: PARAM_DESCRIPTIONS.literal }, context: integer(PARAM_DESCRIPTIONS.context, 0), summary: { type: "boolean", description: PARAM_DESCRIPTIONS.summary }, scope: { type: "string", enum: ["symbol"], description: PARAM_DESCRIPTIONS.grepScope }, scopeContext: integer(PARAM_DESCRIPTIONS.scopeContext, 0) }, []),
219
- tool("find_definitions", "Find definitions", { ...path, ...lang, ...gitScopeSchema, ...noAnchors, name: string(PARAM_DESCRIPTIONS.name) }, ["name"]),
220
- tool("find_references", "Find references", { ...path, ...at, ...lang, ...gitScopeSchema, ...noAnchors, name: string(PARAM_DESCRIPTIONS.refsName) }, []),
221
- tool("edit_text", "Hash-verified edit", { ...path, ...lang, ...gitScopeSchema, ...apply, edits: { type: "array", minItems: 1, items: editItemSchema, description: PARAM_DESCRIPTIONS.edits }, postEditVerify: { type: "boolean", description: PARAM_DESCRIPTIONS.postEditVerify } }, ["path", "edits"]),
222
- tool("rename_symbol", "Rename symbol", { ...path, ...at, ...lang, ...gitScopeSchema, ...apply, to: string(PARAM_DESCRIPTIONS.to), workspace: { type: "boolean", description: PARAM_DESCRIPTIONS.workspace } }, ["path", "at", "to"]),
223
- tool("write_file", "Write file", { ...path, content: string(PARAM_DESCRIPTIONS.content) }, ["path", "content"])
208
+ tool("show", "Show content", { ...path, ...at, ...lang, ...stdin, ...noAnchors, end: integer(PARAM_DESCRIPTIONS.end), limit: integer(PARAM_DESCRIPTIONS.limit) }, ["path"]),
209
+ tool("tree", "Show structure", { ...path, ...at, ...lang, ...stdin, depth: integer(PARAM_DESCRIPTIONS.depth, 0), kind: string(PARAM_DESCRIPTIONS.kind), outline: { type: "boolean", description: PARAM_DESCRIPTIONS.outline } }, ["path"]),
210
+ tool("meta", "Show metadata", { ...path, ...lang, ...stdin }, ["path"]),
211
+ tool("assets", "Show assets", { ...path, at: string(PARAM_DESCRIPTIONS.assetAt), ...stdin }, ["path"]),
212
+ tool("syntax", "Check syntax", { ...path, ...lang, ...stdin }, ["path"]),
213
+ tool("check", "Check rules", { path: string(PARAM_DESCRIPTIONS.searchPath), ...gitScopeSchema, rules: string(PARAM_DESCRIPTIONS.rules) }, ["rules"]),
214
+ tool("caption", "Generate caption", { ...path, ...at, ...stdin, ...level, include: { type: "array", items: { type: "string", enum: ["text", "objects"] }, minItems: 1, uniqueItems: true, description: PARAM_DESCRIPTIONS.include } }, ["path"]),
215
+ tool("objects", "Detect objects", { ...path, ...at, ...stdin, ...level }, ["path"]),
216
+ tool("ocr", "Recognize text", { ...path, ...at, ...stdin, ...level }, ["path"]),
217
+ tool("search", "Search AST nodes", { ...path, ...lang, ...stdin, ...limit, ...gitScopeSchema, ...noAnchors, pattern: string(PARAM_DESCRIPTIONS.pattern), context: string(PARAM_DESCRIPTIONS.patternContext), selector: string(PARAM_DESCRIPTIONS.selector), rule: string(PARAM_DESCRIPTIONS.rule) }, []),
218
+ tool("grep", "Search text", { path: string(PARAM_DESCRIPTIONS.searchPath), limit: { ...integer(PARAM_DESCRIPTIONS.grepLimit), default: 100 }, ...gitScopeSchema, pattern: string(PARAM_DESCRIPTIONS.grepPattern), glob: string(PARAM_DESCRIPTIONS.glob), ignoreCase: { type: "boolean", description: PARAM_DESCRIPTIONS.ignoreCase }, literal: { type: "boolean", description: PARAM_DESCRIPTIONS.literal }, context: integer(PARAM_DESCRIPTIONS.context, 0), summary: { type: "boolean", description: PARAM_DESCRIPTIONS.summary }, scope: { type: "string", enum: ["symbol"], description: PARAM_DESCRIPTIONS.grepScope }, scopeContext: integer(PARAM_DESCRIPTIONS.scopeContext, 0) }, ["pattern"]),
219
+ tool("def", "Find definitions", { ...path, ...lang, ...gitScopeSchema, ...noAnchors, name: string(PARAM_DESCRIPTIONS.name) }, ["name"]),
220
+ tool("refs", "Find references", { ...path, ...at, ...lang, ...gitScopeSchema, ...noAnchors, name: string(PARAM_DESCRIPTIONS.refsName) }, []),
221
+ tool("edit", "Hash-verified edit", { ...path, ...lang, ...gitScopeSchema, ...apply, edits: { type: "array", minItems: 1, items: editItemSchema, description: PARAM_DESCRIPTIONS.edits }, postEditVerify: { type: "boolean", description: PARAM_DESCRIPTIONS.postEditVerify } }, ["path", "edits"]),
222
+ tool("rename", "Rename symbol", { ...path, ...at, ...lang, ...gitScopeSchema, ...apply, to: string(PARAM_DESCRIPTIONS.to), workspace: { type: "boolean", description: PARAM_DESCRIPTIONS.workspace } }, ["path", "at", "to"]),
223
+ tool("write", "Write file", { ...path, content: string(PARAM_DESCRIPTIONS.content) }, ["path", "content"])
224
224
  ];
225
225
  function getToolContract(name) {
226
226
  const contract = TOOL_CONTRACTS.find((entry) => entry.name === name);
@@ -231,44 +231,44 @@ function getToolContract(name) {
231
231
  // ../readseek-api/src/policy.ts
232
232
  var REPLACEABLE_TOOLS = [
233
233
  {
234
- tool: "show_content",
234
+ tool: "show",
235
235
  builtIn: "read",
236
236
  benefit: "it returns LINE:HASH anchors for safe edits."
237
237
  },
238
238
  {
239
- tool: "edit_text",
239
+ tool: "edit",
240
240
  builtIn: "edit",
241
241
  benefit: "it verifies fresh LINE:HASH anchors."
242
242
  },
243
243
  {
244
- tool: "find_text",
244
+ tool: "grep",
245
245
  builtIn: "grep",
246
- benefit: "it returns LINE:HASH anchors."
246
+ benefit: "it runs native regex/literal matching and returns source LINE:HASH anchors or document locations."
247
247
  },
248
248
  {
249
- tool: "write_file",
249
+ tool: "write",
250
250
  builtIn: "write",
251
251
  benefit: "it returns LINE:HASH anchors."
252
252
  }
253
253
  ];
254
254
  var TOOL_COMPACT_GUIDELINES = {
255
- show_content: [
256
- "Use `show_structure` for shallow overviews and `check_syntax` for diagnostics; use `find_definitions`, `find_references`, or `find_nodes` for targeted symbol questions."
255
+ show: [
256
+ "Use `tree` for shallow overviews and `syntax` for diagnostics; use `def`, `refs`, or `search` for targeted symbol questions."
257
257
  ],
258
- edit_text: [
258
+ edit: [
259
259
  "Prefer set_line, replace_lines, and insert_after; use replace only when anchors are impractical."
260
260
  ],
261
- find_text: [
262
- "Use `find_text` with summary first for broad searches, then narrow by path, glob, or pattern."
261
+ grep: [
262
+ "Use `grep` with summary first for broad searches, then narrow by path, glob, or pattern."
263
263
  ],
264
- write_file: [
265
- "Use anchored edits rather than `write_file` for small changes or appends."
264
+ write: [
265
+ "Use anchored edits rather than `write` for small changes or appends."
266
266
  ],
267
- find_nodes: [
268
- "Use `find_nodes` for AST patterns; use `find_text` for plain text."
267
+ search: [
268
+ "Use `search` for AST patterns; use `grep` for plain text."
269
269
  ],
270
- find_references: [
271
- "Use `find_references` before changing a symbol; use at with line:N:C to follow one binding."
270
+ refs: [
271
+ "Use `refs` before changing a symbol; use at with line:N:C to follow one binding."
272
272
  ]
273
273
  };
274
274
  function resolveToolName(name, aliases = {}) {
@@ -279,21 +279,21 @@ function renderCompactGuidelines(name, aliases = {}) {
279
279
  }
280
280
  function renderToolRoutingPolicy(options = {}) {
281
281
  const aliases = options.aliases ?? {};
282
- const show_content = resolveToolName("show_content", aliases);
283
- const edit_text = resolveToolName("edit_text", aliases);
284
- const write_file = resolveToolName("write_file", aliases);
285
- const find_text = resolveToolName("find_text", aliases);
286
- const find_nodes = resolveToolName("find_nodes", aliases);
287
- const find_definitions = resolveToolName("find_definitions", aliases);
288
- const find_references = resolveToolName("find_references", aliases);
289
- const rename_symbol = resolveToolName("rename_symbol", aliases);
290
- const show_assets = resolveToolName("show_assets", aliases);
282
+ const show = resolveToolName("show", aliases);
283
+ const edit = resolveToolName("edit", aliases);
284
+ const write = resolveToolName("write", aliases);
285
+ const grep = resolveToolName("grep", aliases);
286
+ const search = resolveToolName("search", aliases);
287
+ const def = resolveToolName("def", aliases);
288
+ const refs = resolveToolName("refs", aliases);
289
+ const rename = resolveToolName("rename", aliases);
290
+ const assets = resolveToolName("assets", aliases);
291
291
  const title = options.title ?? "ReadSeek tool policy:";
292
292
  const bullets = [];
293
293
  if (options.preferOverBuiltIns) {
294
294
  bullets.push("- Prefer ReadSeek tools over built-ins when they can do the job.");
295
295
  }
296
- bullets.push(`- Read with ${show_content} first; ${edit_text} needs fresh LINE:HASH anchors.`, `- Use ${find_text} for text/regex, ${find_nodes} for AST patterns, ${find_definitions}/${find_references} for targeted symbols, and shallow ${resolveToolName("show_structure", aliases)} structures only for file overviews.`, `- Use ${edit_text} for existing files, ${write_file} for whole-file creation or replacement, and ${rename_symbol} for symbol renames.`, `- Use ${resolveToolName("show_structure", aliases)} for document structure; run ${resolveToolName("check_syntax", aliases)} after source edits for a quick syntax check.`, `- Use ${show_assets} to materialize selected document assets and read their returned paths.`);
296
+ bullets.push(`- Read with ${show} first; ${edit} needs fresh LINE:HASH anchors.`, `- Use ${grep} for text/regex, ${search} for AST patterns, ${def}/${refs} for targeted symbols, and shallow ${resolveToolName("tree", aliases)} structures only for file overviews.`, `- Use ${edit} for existing files, ${write} for whole-file creation or replacement, and ${rename} for symbol renames.`, `- Use ${resolveToolName("tree", aliases)} for document structure; run ${resolveToolName("syntax", aliases)} after source edits for a quick syntax check.`, `- Use ${assets} to materialize selected document assets and read their returned paths.`);
297
297
  if (options.demoteBuiltIns && options.demoteBuiltIns.length > 0) {
298
298
  bullets.push(`- Do not use built-in ${options.demoteBuiltIns.join(", ")} when a ReadSeek tool can do the job.`);
299
299
  }
@@ -325,6 +325,59 @@ function validateReferencesSelection(input) {
325
325
  return { ok: false, error: "exactly one of name or at is required" };
326
326
  return { ok: true };
327
327
  }
328
+ function validateGrepParams(input) {
329
+ if (!input || typeof input !== "object" || Array.isArray(input)) {
330
+ return { ok: false, error: "grep params must be an object" };
331
+ }
332
+ const params = input;
333
+ const allowed = [
334
+ "pattern",
335
+ "path",
336
+ "glob",
337
+ "ignoreCase",
338
+ "literal",
339
+ "context",
340
+ "limit",
341
+ "summary",
342
+ "scope",
343
+ "scopeContext",
344
+ "gitScope",
345
+ "signal",
346
+ "stdin",
347
+ "timeoutMs",
348
+ "cancelable"
349
+ ];
350
+ if (Object.keys(params).some((key) => !allowed.includes(key)))
351
+ return { ok: false, error: "unknown grep parameter" };
352
+ if (typeof params.pattern !== "string")
353
+ return { ok: false, error: "grep requires a pattern string" };
354
+ if (params.literal === true && params.pattern.length === 0)
355
+ return { ok: false, error: "literal pattern must not be empty" };
356
+ for (const key of ["path", "glob"]) {
357
+ if (params[key] !== undefined && (typeof params[key] !== "string" || params[key].length === 0)) {
358
+ return { ok: false, error: `${key} must be a non-empty string` };
359
+ }
360
+ }
361
+ for (const key of ["ignoreCase", "literal", "summary"]) {
362
+ if (params[key] !== undefined && typeof params[key] !== "boolean")
363
+ return { ok: false, error: `${key} must be a boolean` };
364
+ }
365
+ for (const key of ["context", "limit", "scopeContext"]) {
366
+ const value = params[key];
367
+ const minimum = key === "limit" ? 1 : 0;
368
+ if (value !== undefined && (typeof value !== "number" || !Number.isSafeInteger(value) || value < minimum)) {
369
+ return { ok: false, error: `${key} must be a ${minimum === 1 ? "positive" : "non-negative"} integer` };
370
+ }
371
+ }
372
+ if (params.scope !== undefined && params.scope !== "symbol")
373
+ return { ok: false, error: "scope must be symbol" };
374
+ if (params.scopeContext !== undefined && params.scope !== "symbol")
375
+ return { ok: false, error: "scopeContext requires scope" };
376
+ if (params.gitScope !== undefined && !GIT_SCOPES.includes(params.gitScope)) {
377
+ return { ok: false, error: "invalid gitScope" };
378
+ }
379
+ return { ok: true };
380
+ }
328
381
  var VARIANT_FIELDS = {
329
382
  set_line: ["anchor", "new_text"],
330
383
  replace_lines: ["start_anchor", "end_anchor", "new_text"],
@@ -423,7 +476,6 @@ function coerceObviousBase10Int(value, name) {
423
476
  return invalidInteger(value, name);
424
477
  }
425
478
  // ../readseek-api/src/argv.ts
426
- var DEFAULT_STRUCTURE_DEPTH = 1;
427
479
  function appendGitScopeArgs(args, input) {
428
480
  if (input.gitScope !== undefined)
429
481
  args.push("--git-scope", input.gitScope);
@@ -459,50 +511,111 @@ var SELECTOR_KINDS = ["line", "hash", "symbol", "page", "node", "asset"];
459
511
  function isSelectorKind(value) {
460
512
  return SELECTOR_KINDS.includes(value);
461
513
  }
514
+ function parsePositiveSelectorInt(raw, name) {
515
+ if (!/^-?\d+$/.test(raw))
516
+ throw new Error(`invalid ${name} \`${raw}\``);
517
+ const parsed = Number(raw);
518
+ if (!Number.isSafeInteger(parsed))
519
+ throw new Error(`invalid ${name} \`${raw}\``);
520
+ if (parsed < 1)
521
+ throw new Error(`${name} must be greater than zero`);
522
+ return parsed;
523
+ }
524
+ function parseLineSelector(value, explicitKind) {
525
+ const parts = value.split(":");
526
+ if (parts.length > 2)
527
+ throw new Error("selector has too many components");
528
+ const head = parts[0];
529
+ if (parts.length === 2) {
530
+ if (head.slice(1).includes("-"))
531
+ throw new Error("line range cannot include a column");
532
+ const line = parsePositiveSelectorInt(head, "line");
533
+ const column = parsePositiveSelectorInt(parts[1], "column");
534
+ return { kind: "line", value: String(line), column, explicitKind };
535
+ }
536
+ const dash = head.indexOf("-", 1);
537
+ if (dash > 0) {
538
+ const start = parsePositiveSelectorInt(head.slice(0, dash), "line");
539
+ const endLine = parsePositiveSelectorInt(head.slice(dash + 1), "end line");
540
+ if (endLine < start)
541
+ throw new Error(`end line ${endLine} precedes start line ${start}`);
542
+ return { kind: "line", value: String(start), endLine, explicitKind };
543
+ }
544
+ const line = parsePositiveSelectorInt(head, "line");
545
+ return { kind: "line", value: String(line), explicitKind };
546
+ }
547
+ function parseHashSelector(value, explicitKind) {
548
+ const parts = value.split(":");
549
+ if (parts.length > 2)
550
+ throw new Error("selector has too many components");
551
+ const head = parts[0];
552
+ if (!/^[0-9a-fA-F]{6}$/.test(head))
553
+ throw new Error(`invalid line hash \`${head}\``);
554
+ if (parts.length === 2) {
555
+ const column = parsePositiveSelectorInt(parts[1], "column");
556
+ return { kind: "hash", value: head, column, explicitKind };
557
+ }
558
+ return { kind: "hash", value: head, explicitKind };
559
+ }
560
+ function isDocumentSelector(parsed) {
561
+ return ["page", "node", "asset"].includes(parsed.kind) || parsed.kind === "line" && !parsed.explicitKind && parsed.column === undefined && parsed.endLine === undefined;
562
+ }
563
+ function isBindingSelector(parsed) {
564
+ if (parsed.endLine !== undefined)
565
+ return false;
566
+ if (parsed.kind === "line" || parsed.kind === "hash")
567
+ return true;
568
+ return parsed.kind === "symbol" && !parsed.explicitKind && /^[0-9a-fA-F]{6}$/.test(parsed.value);
569
+ }
462
570
  function parseSelector(text) {
571
+ if (text.length === 0)
572
+ throw new Error("selector must not be empty");
463
573
  const separator = text.indexOf(":");
464
- if (separator < 0) {
465
- throw new Error("selector must start with line:, hash:, symbol:, page:, node:, or asset:");
466
- }
467
- const kind = text.slice(0, separator);
468
- const value = text.slice(separator + 1);
469
- if (value.length === 0)
470
- throw new Error(`${kind} selector must not be empty`);
471
- if (!isSelectorKind(kind)) {
574
+ const hasSingleColon = separator >= 0 && text[separator + 1] !== ":";
575
+ if (hasSingleColon) {
576
+ const kind = text.slice(0, separator);
577
+ const value = text.slice(separator + 1);
578
+ if (isSelectorKind(kind)) {
579
+ if (value.length === 0)
580
+ throw new Error(`${kind} selector must not be empty`);
581
+ if (kind === "line")
582
+ return parseLineSelector(value, true);
583
+ if (kind === "hash")
584
+ return parseHashSelector(value, true);
585
+ if (kind === "page") {
586
+ if (value.includes(":"))
587
+ throw new Error("page selector has too many components");
588
+ parsePositiveSelectorInt(value, "page");
589
+ }
590
+ return { kind, value, explicitKind: true };
591
+ }
592
+ if (/^\d+$/.test(kind))
593
+ return parseLineSelector(text, false);
594
+ if (/^[0-9a-fA-F]{6}$/.test(kind) && /^\d+$/.test(value))
595
+ return parseHashSelector(text, false);
472
596
  throw new Error(`unknown selector kind \`${kind}\`; expected line, hash, symbol, page, node, or asset`);
473
597
  }
474
- if (kind === "line" || kind === "hash") {
475
- const parts = value.split(":");
476
- if (parts.length > 2)
477
- throw new Error("selector has too many components");
478
- if (parts.length === 2) {
479
- const column = Number(parts[1]);
480
- if (!Number.isSafeInteger(column))
481
- throw new Error(`invalid column \`${parts[1]}\``);
482
- if (column < 1)
483
- throw new Error("column must be greater than zero");
484
- return { kind, value: parts[0], column };
598
+ if (/^[0-9-]/.test(text)) {
599
+ if (/^[0-9a-fA-F]{6}$/.test(text) && /[a-fA-F]/.test(text)) {
600
+ return parseHashSelector(text, false);
485
601
  }
602
+ return parseLineSelector(text, false);
486
603
  }
487
- if (kind === "page") {
488
- if (value.includes(":"))
489
- throw new Error("page selector has too many components");
490
- const page = Number(value);
491
- if (!Number.isSafeInteger(page))
492
- throw new Error(`invalid page \`${value}\``);
493
- if (page < 1)
494
- throw new Error("page must be greater than zero");
604
+ if (/^[0-9a-fA-F]{6}$/.test(text) && /\d/.test(text)) {
605
+ return parseHashSelector(text, false);
495
606
  }
496
- return { kind, value };
607
+ if (/\s/.test(text))
608
+ throw new Error(`invalid selector \`${text}\``);
609
+ return { kind: "symbol", value: text, explicitKind: false };
497
610
  }
498
- function start(verb, object, input) {
611
+ function start(command, input) {
499
612
  if (!input.path)
500
- throw new Error(`${verb} ${object} requires a path`);
613
+ throw new Error(`${command} requires a path`);
501
614
  if (input.stdinName !== undefined && input.path !== "-")
502
615
  throw new Error("stdinName requires '-' as the target");
503
616
  const args = [];
504
617
  pushOptionalString(args, "--readseek-dir", input.readseekDir);
505
- args.push(verb, object, input.path);
618
+ args.push(command, input.path);
506
619
  pushOptionalString(args, "--stdin-name", input.stdinName);
507
620
  return args;
508
621
  }
@@ -528,12 +641,12 @@ function anchors(args, input) {
528
641
  if (input.noAnchors)
529
642
  args.push("--no-anchors");
530
643
  }
531
- function buildShowContentArgs(input) {
644
+ function buildShowArgs(input) {
532
645
  if (input.end !== undefined)
533
646
  validatePositiveInteger(input.end, "end");
534
647
  if (input.end !== undefined && input.limit !== undefined)
535
648
  throw new Error("cannot combine end with limit");
536
- const args = start("show", "content", input);
649
+ const args = start("show", input);
537
650
  at2(args, input.at);
538
651
  pushOptionalNumber(args, "--end", input.end);
539
652
  limit2(args, input.limit);
@@ -541,10 +654,10 @@ function buildShowContentArgs(input) {
541
654
  anchors(args, input);
542
655
  return finish(args);
543
656
  }
544
- function buildShowStructureArgs(input) {
657
+ function buildTreeArgs(input) {
545
658
  if (input.depth !== undefined && (!Number.isSafeInteger(input.depth) || input.depth < 0))
546
659
  throw new Error("depth must be a non-negative integer");
547
- const args = start("show", "structure", input);
660
+ const args = start("tree", input);
548
661
  at2(args, input.at);
549
662
  pushOptionalNumber(args, "--depth", input.depth);
550
663
  pushOptionalString(args, "--kind", input.kind);
@@ -553,35 +666,35 @@ function buildShowStructureArgs(input) {
553
666
  args.push("--outline");
554
667
  return finish(args);
555
668
  }
556
- function buildShowMetadataArgs(input) {
557
- const args = start("show", "metadata", input);
669
+ function buildMetaArgs(input) {
670
+ const args = start("meta", input);
558
671
  language(args, input);
559
672
  return finish(args);
560
673
  }
561
- function buildShowAssetsArgs(input) {
562
- if (input.at !== undefined && !["page", "node", "asset"].includes(parseSelector(input.at).kind))
674
+ function buildAssetsArgs(input) {
675
+ if (input.at !== undefined && !isDocumentSelector(parseSelector(input.at)))
563
676
  throw new Error("assets requires a page, node, or asset selector");
564
- const args = start("show", "assets", input);
677
+ const args = start("assets", input);
565
678
  at2(args, input.at);
566
679
  return finish(args);
567
680
  }
568
- function buildCheckSyntaxArgs(input) {
569
- const args = start("check", "syntax", input);
681
+ function buildSyntaxArgs(input) {
682
+ const args = start("syntax", input);
570
683
  language(args, input);
571
684
  return finish(args);
572
685
  }
573
- function visionArgs(verb, object, input) {
686
+ function visionArgs(command, input) {
574
687
  if (input.level !== undefined && !VISION_LEVELS.includes(input.level))
575
688
  throw new Error("invalid vision level");
576
- if (input.at !== undefined && !["page", "node", "asset"].includes(parseSelector(input.at).kind))
689
+ if (input.at !== undefined && !isDocumentSelector(parseSelector(input.at)))
577
690
  throw new Error("vision requires a document selector");
578
- const args = start(verb, object, input);
691
+ const args = start(command, input);
579
692
  at2(args, input.at);
580
693
  pushOptionalString(args, "--level", input.level);
581
694
  return args;
582
695
  }
583
- function buildGenerateCaptionArgs(input) {
584
- const args = visionArgs("generate", "caption", input);
696
+ function buildCaptionArgs(input) {
697
+ const args = visionArgs("caption", input);
585
698
  if (input.include !== undefined) {
586
699
  if (input.include.length === 0 || input.include.some((task) => task !== "text" && task !== "objects"))
587
700
  throw new Error("include requires text or objects");
@@ -589,13 +702,13 @@ function buildGenerateCaptionArgs(input) {
589
702
  }
590
703
  return finish(args);
591
704
  }
592
- function buildDetectObjectsArgs(input) {
593
- return finish(visionArgs("detect", "objects", input));
705
+ function buildObjectsArgs(input) {
706
+ return finish(visionArgs("objects", input));
594
707
  }
595
- function buildRecognizeTextArgs(input) {
596
- return finish(visionArgs("recognize", "text", input));
708
+ function buildOcrArgs(input) {
709
+ return finish(visionArgs("ocr", input));
597
710
  }
598
- function buildFindNodesArgs(input) {
711
+ function buildSearchArgs(input) {
599
712
  const hasPattern = Boolean(input.pattern);
600
713
  const hasRule = Boolean(input.rule);
601
714
  const hasContext = Boolean(input.context);
@@ -603,7 +716,7 @@ function buildFindNodesArgs(input) {
603
716
  if (hasPattern || hasContext || input.selector)
604
717
  throw new Error("rule cannot be combined with pattern, context, or selector");
605
718
  } else if (!hasPattern && !hasContext) {
606
- throw new Error("find nodes requires pattern, context, or rule");
719
+ throw new Error("search requires pattern, context, or rule");
607
720
  }
608
721
  if (hasContext && !input.selector) {
609
722
  throw new Error("context requires selector");
@@ -611,7 +724,7 @@ function buildFindNodesArgs(input) {
611
724
  if (hasPattern && hasContext) {
612
725
  throw new Error("pattern and context cannot be combined");
613
726
  }
614
- const args = start("find", "nodes", input);
727
+ const args = start("search", input);
615
728
  pushOptionalString(args, "--pattern", input.pattern);
616
729
  pushOptionalString(args, "--context", input.context);
617
730
  pushOptionalString(args, "--selector", input.selector);
@@ -622,28 +735,46 @@ function buildFindNodesArgs(input) {
622
735
  anchors(args, input);
623
736
  return finish(args);
624
737
  }
625
- function buildFindTextArgs(input) {
626
- const args = start("find", "text", input);
627
- args.push("--text", input.text);
628
- limit2(args, input.limit);
738
+ function buildGrepArgs(input) {
739
+ const validated = validateGrepParams(input);
740
+ if (!validated.ok)
741
+ throw new Error(validated.error);
742
+ const args = ["grep", "--kdl", `--pattern=${input.pattern}`];
743
+ const needsPathTerminator = input.path !== undefined && input.path.startsWith("-") && input.path !== "-";
744
+ if (input.path !== undefined && !needsPathTerminator)
745
+ args.push(input.path);
746
+ if (input.literal)
747
+ args.push("--literal");
748
+ if (input.ignoreCase)
749
+ args.push("--ignore-case");
750
+ if (input.glob !== undefined)
751
+ args.push(`--glob=${input.glob}`);
752
+ pushOptionalNumber(args, "--context", input.context);
753
+ limit2(args, input.limit ?? 100);
754
+ if (input.summary)
755
+ args.push("--summary");
756
+ pushOptionalString(args, "--scope", input.scope);
757
+ pushOptionalNumber(args, "--scope-context", input.scopeContext);
629
758
  appendGitScopeArgs(args, input);
630
- return finish(args);
759
+ if (input.path !== undefined && needsPathTerminator)
760
+ args.push("--", input.path);
761
+ return args;
631
762
  }
632
- function buildFindDefinitionsArgs(input) {
633
- const args = start("find", "definitions", input);
763
+ function buildDefArgs(input) {
764
+ const args = start("def", input);
634
765
  args.push("--name", input.name);
635
766
  language(args, input);
636
767
  appendGitScopeArgs(args, input);
637
768
  anchors(args, input);
638
769
  return finish(args);
639
770
  }
640
- function buildFindReferencesArgs(input) {
771
+ function buildRefsArgs(input) {
641
772
  const validated = validateReferencesSelection(input);
642
773
  if (!validated.ok)
643
774
  throw new Error(validated.error);
644
- if (input.at !== undefined && !["line", "hash"].includes(parseSelector(input.at).kind))
775
+ if (input.at !== undefined && !isBindingSelector(parseSelector(input.at)))
645
776
  throw new Error("references requires a line or hash selector");
646
- const args = start("find", "references", input);
777
+ const args = start("refs", input);
647
778
  pushOptionalString(args, "--name", input.name);
648
779
  at2(args, input.at);
649
780
  language(args, input);
@@ -651,14 +782,14 @@ function buildFindReferencesArgs(input) {
651
782
  anchors(args, input);
652
783
  return finish(args);
653
784
  }
654
- function buildCheckRulesArgs(input) {
655
- const args = start("check", "rules", input);
785
+ function buildCheckArgs(input) {
786
+ const args = start("check", input);
656
787
  args.push("--rules", input.rules);
657
788
  appendGitScopeArgs(args, input);
658
789
  return finish(args);
659
790
  }
660
- function buildEditTextArgs(input) {
661
- const args = start("edit", "text", input);
791
+ function buildEditArgs(input) {
792
+ const args = start("edit", input);
662
793
  appendApplyAndPlanHash(args, input);
663
794
  language(args, input);
664
795
  appendGitScopeArgs(args, input);
@@ -666,10 +797,10 @@ function buildEditTextArgs(input) {
666
797
  args.push("--edits", "-");
667
798
  return finish(args);
668
799
  }
669
- function buildRenameSymbolArgs(input) {
670
- if (!["line", "hash"].includes(parseSelector(input.at).kind))
800
+ function buildRenameArgs(input) {
801
+ if (!isBindingSelector(parseSelector(input.at)))
671
802
  throw new Error("rename requires a line or hash selector");
672
- const args = start("rename", "symbol", input);
803
+ const args = start("rename", input);
673
804
  at2(args, input.at);
674
805
  args.push("--to", input.to);
675
806
  if (input.workspace !== undefined)
@@ -1059,25 +1190,41 @@ var DocumentOutput = Type.Object({
1059
1190
  assets: Type.Array(DocumentAsset, { default: [] }),
1060
1191
  vision: Type.Array(DocumentVision, { default: [] })
1061
1192
  });
1062
- var TextHit = Type.Object({
1063
- line: Int,
1064
- column: Int,
1193
+ var GrepCount = Type.Integer({ minimum: 0, maximum: Number.MAX_SAFE_INTEGER });
1194
+ var GrepPosition = Type.Integer({ minimum: 1, maximum: Number.MAX_SAFE_INTEGER });
1195
+ var GrepAnchor = Type.String({ pattern: "^[1-9][0-9]*:[0-9a-f]{6}$" });
1196
+ var GrepLineSchema = Type.Object({
1197
+ line: GrepPosition,
1198
+ hash: Type.String({ pattern: "^[0-9a-f]{6}$" }),
1065
1199
  text: Str,
1066
- start_line: Int,
1067
- end_line: Int,
1068
- start_byte: Int,
1069
- end_byte: Int,
1070
- hashlines: Hashlines
1200
+ kind: Type.Union([Type.Literal("match"), Type.Literal("context")])
1071
1201
  });
1072
- var DocumentHit = Type.Object({ node_id: Str, kind: Str, snippet: Str, page: OptInt });
1073
- var TextSearchOutput = Type.Object({
1074
- text: Str,
1075
- mode: Type.Literal("literal"),
1076
- total_matches: Int,
1077
- returned_matches: Int,
1202
+ var GrepHitSchema = Type.Object({ line: GrepPosition, column: GrepPosition, start_byte: GrepCount, end_byte: GrepCount, text: Str, anchor: GrepAnchor });
1203
+ var GrepSymbolSchema = Type.Object({ name: Str, kind: Str, start_line: GrepPosition, end_line: GrepPosition, lines: Type.Array(GrepLineSchema) });
1204
+ var GrepSourceResultSchema = Type.Object({
1205
+ file: Str,
1206
+ file_hash: Str,
1207
+ language: Str,
1208
+ total_matches: GrepCount,
1209
+ returned_matches: GrepCount,
1210
+ hits: Type.Array(GrepHitSchema),
1211
+ lines: Type.Array(GrepLineSchema),
1212
+ symbols: Type.Array(GrepSymbolSchema)
1213
+ });
1214
+ var GrepDocumentHitSchema = Type.Object({ node_id: Str, kind: Str, snippet: Str, line: GrepPosition, page: Type.Optional(GrepPosition) });
1215
+ var GrepDocumentResultSchema = Type.Object({ file: Str, content_hash: Str, total_matches: GrepCount, returned_matches: GrepCount, hits: Type.Array(GrepDocumentHitSchema) });
1216
+ var GrepOutputSchema = Type.Object({
1217
+ pattern: Str,
1218
+ mode: Type.Union([Type.Literal("regex"), Type.Literal("literal")]),
1219
+ ignore_case: Bool,
1220
+ summary: Bool,
1221
+ total_matches: GrepCount,
1222
+ returned_matches: GrepCount,
1078
1223
  truncated: Bool,
1079
- results: Type.Array(Type.Object({ file: Str, file_hash: Str, matches: Type.Array(TextHit) })),
1080
- documents: Type.Array(Type.Object({ file: Str, content_hash: Str, hits: Type.Array(DocumentHit) }))
1224
+ count_unit: Type.Literal("lines"),
1225
+ scope: Type.Optional(Type.Literal("symbol")),
1226
+ results: Type.Array(GrepSourceResultSchema),
1227
+ documents: Type.Array(GrepDocumentResultSchema)
1081
1228
  });
1082
1229
  var RuleFinding = Type.Object({
1083
1230
  name: Str,
@@ -1122,8 +1269,35 @@ function parseStructureEnvelope(value) {
1122
1269
  throw new Error("invalid readseek structure: missing structure");
1123
1270
  return { ...output.metadata !== undefined ? { metadata: parseDetectOutput(output.metadata) } : {}, structure: parseStructureOutput(output.structure) };
1124
1271
  }
1125
- function parseTextSearchOutput(value) {
1126
- return parse(TextSearchOutput, value, "text search output");
1272
+ function parseGrepOutput(value) {
1273
+ const output = parse(GrepOutputSchema, value, "grep output");
1274
+ const invalid = (message) => {
1275
+ throw new Error(`invalid readseek grep output: ${message}`);
1276
+ };
1277
+ const groups = [...output.results, ...output.documents];
1278
+ if (output.returned_matches > output.total_matches || groups.reduce((sum, group) => sum + group.total_matches, 0) !== output.total_matches || groups.reduce((sum, group) => sum + group.returned_matches, 0) !== output.returned_matches)
1279
+ invalid("inconsistent counts");
1280
+ for (const group of groups) {
1281
+ if (group.total_matches === 0 || group.returned_matches > group.total_matches)
1282
+ invalid("inconsistent file counts");
1283
+ if (output.summary ? group.hits.length !== 0 : group.hits.length !== group.returned_matches)
1284
+ invalid("inconsistent hit count");
1285
+ }
1286
+ for (const result of output.results) {
1287
+ if (output.summary && (result.lines.length > 0 || result.symbols.length > 0))
1288
+ invalid("summary contains display entries");
1289
+ if (output.scope !== "symbol" && result.symbols.length > 0)
1290
+ invalid("symbols require symbol scope");
1291
+ for (const hit of result.hits) {
1292
+ if (hit.end_byte < hit.start_byte || Number(hit.anchor.split(":")[0]) !== hit.line)
1293
+ invalid("invalid hit span or anchor");
1294
+ }
1295
+ for (const symbol of result.symbols) {
1296
+ if (symbol.end_line < symbol.start_line || symbol.lines.some((line) => line.line < symbol.start_line || line.line > symbol.end_line))
1297
+ invalid("invalid symbol range");
1298
+ }
1299
+ }
1300
+ return output;
1127
1301
  }
1128
1302
  function parseRulesOutput(value) {
1129
1303
  return parse(RulesOutput, value, "rules output");
@@ -1200,71 +1374,71 @@ function runOptions(options, vision = false) {
1200
1374
  }
1201
1375
  function createReadSeekClient(runner) {
1202
1376
  return {
1203
- async showContent(path, options = {}) {
1204
- const value = await runner.kdl(buildShowContentArgs({ ...options, path }), runOptions(options));
1377
+ async show(path, options = {}) {
1378
+ const value = await runner.kdl(buildShowArgs({ ...options, path }), runOptions(options));
1205
1379
  return parseContentOutput(value);
1206
1380
  },
1207
- async showStructure(path, options = {}) {
1208
- const value = await runner.kdl(buildShowStructureArgs({ ...options, path }), runOptions(options));
1381
+ async tree(path, options = {}) {
1382
+ const value = await runner.kdl(buildTreeArgs({ ...options, path }), runOptions(options));
1209
1383
  return parseStructureEnvelope(value);
1210
1384
  },
1211
- async showMetadata(path, options = {}) {
1212
- const value = await runner.kdl(buildShowMetadataArgs({ ...options, path }), runOptions(options));
1385
+ async meta(path, options = {}) {
1386
+ const value = await runner.kdl(buildMetaArgs({ ...options, path }), runOptions(options));
1213
1387
  return parsePdfMetadata(value) ?? parseDetectOutput(value?.metadata);
1214
1388
  },
1215
- async showAssets(path, options = {}) {
1216
- const value = await runner.kdl(buildShowAssetsArgs({ ...options, path }), runOptions(options));
1389
+ async assets(path, options = {}) {
1390
+ const value = await runner.kdl(buildAssetsArgs({ ...options, path }), runOptions(options));
1217
1391
  return parseAssetsOutput(value);
1218
1392
  },
1219
- async checkSyntax(path, options = {}) {
1220
- const value = await runner.kdl(buildCheckSyntaxArgs({ ...options, path }), runOptions(options));
1393
+ async syntax(path, options = {}) {
1394
+ const value = await runner.kdl(buildSyntaxArgs({ ...options, path }), runOptions(options));
1221
1395
  return parseCheckOutput(value?.diagnostics ?? value);
1222
1396
  },
1223
- async checkRules(path, options) {
1224
- const value = await runner.kdl(buildCheckRulesArgs({ ...options, path }), runOptions(options));
1397
+ async check(path, options) {
1398
+ const value = await runner.kdl(buildCheckArgs({ ...options, path }), runOptions(options));
1225
1399
  return parseRulesOutput(value);
1226
1400
  },
1227
- async generateCaption(path, options = {}) {
1228
- const value = await runner.kdl(buildGenerateCaptionArgs({ ...options, path }), runOptions(options, true));
1401
+ async caption(path, options = {}) {
1402
+ const value = await runner.kdl(buildCaptionArgs({ ...options, path }), runOptions(options, true));
1229
1403
  return parseContentOutput(value);
1230
1404
  },
1231
- async detectObjects(path, options = {}) {
1232
- const value = await runner.kdl(buildDetectObjectsArgs({ ...options, path }), runOptions(options, true));
1405
+ async objects(path, options = {}) {
1406
+ const value = await runner.kdl(buildObjectsArgs({ ...options, path }), runOptions(options, true));
1233
1407
  return parseContentOutput(value);
1234
1408
  },
1235
- async recognizeText(path, options = {}) {
1236
- const value = await runner.kdl(buildRecognizeTextArgs({ ...options, path }), runOptions(options, true));
1409
+ async ocr(path, options = {}) {
1410
+ const value = await runner.kdl(buildOcrArgs({ ...options, path }), runOptions(options, true));
1237
1411
  return parseContentOutput(value);
1238
1412
  },
1239
- async findNodes(path, options) {
1240
- const value = await runner.kdl(buildFindNodesArgs({ ...options, path }), runOptions(options));
1413
+ async search(path, options) {
1414
+ const value = await runner.kdl(buildSearchArgs({ ...options, path }), runOptions(options));
1241
1415
  return parseSearchOutput(value);
1242
1416
  },
1243
- async findText(path, options) {
1244
- const value = await runner.kdl(buildFindTextArgs({ ...options, path }), runOptions(options));
1245
- return parseTextSearchOutput(value);
1417
+ async grep(params) {
1418
+ const value = await runner.kdl(buildGrepArgs(params), runOptions(params));
1419
+ return parseGrepOutput(value);
1246
1420
  },
1247
- async findDefinitions(path, options) {
1248
- const value = await runner.kdl(buildFindDefinitionsArgs({ ...options, path }), runOptions(options));
1421
+ async def(path, options) {
1422
+ const value = await runner.kdl(buildDefArgs({ ...options, path }), runOptions(options));
1249
1423
  return parseDefOutput(value);
1250
1424
  },
1251
- async findReferences(path, options) {
1252
- const value = await runner.kdl(buildFindReferencesArgs({ ...options, path }), runOptions(options));
1425
+ async refs(path, options) {
1426
+ const value = await runner.kdl(buildRefsArgs({ ...options, path }), runOptions(options));
1253
1427
  return parseRefsOutput(value);
1254
1428
  },
1255
- async editText(path, edits, options = {}) {
1429
+ async edit(path, edits, options = {}) {
1256
1430
  const stdin = serializeEditPlan(edits);
1257
- return parseEditOutput(await runner.kdl(buildEditTextArgs({ ...options, path, requestFromStdin: true }), runOptions({ ...options, stdin })));
1431
+ return parseEditOutput(await runner.kdl(buildEditArgs({ ...options, path, requestFromStdin: true }), runOptions({ ...options, stdin })));
1258
1432
  },
1259
- async renameSymbol(path, options) {
1260
- const value = await runner.kdl(buildRenameSymbolArgs({ ...options, path }), runOptions(options));
1433
+ async rename(path, options) {
1434
+ const value = await runner.kdl(buildRenameArgs({ ...options, path }), runOptions(options));
1261
1435
  return parseRenameOutput(value);
1262
1436
  }
1263
1437
  };
1264
1438
  }
1265
1439
  // ../readseek-api/src/show-presentation.ts
1266
1440
  function isShowTool(name) {
1267
- return name === "show_content" || name === "show_structure" || name === "show_metadata" || name === "show_assets";
1441
+ return name === "show" || name === "tree" || name === "meta" || name === "assets";
1268
1442
  }
1269
1443
  function record(value) {
1270
1444
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
@@ -1379,7 +1553,7 @@ function presentShowResult(tool, value, options = {}) {
1379
1553
  const path = inline(options.path || output.source || output.file || content.file || structure.file || metadata.file || cursor.file) || "(unknown path)";
1380
1554
  const summary = [];
1381
1555
  const result = { summary: "", body: "", notices: [], metadata: {} };
1382
- if (tool === "show_metadata") {
1556
+ if (tool === "meta") {
1383
1557
  const data = Object.keys(metadata).length > 0 ? metadata : output;
1384
1558
  if (text(data.format || data.language || data.type))
1385
1559
  summary.push(inline(data.format || data.language || data.type));
@@ -1388,7 +1562,7 @@ function presentShowResult(tool, value, options = {}) {
1388
1562
  summary.push(plural(result.metadata.pages, "page"));
1389
1563
  }
1390
1564
  result.body = fields(data) || "No metadata available.";
1391
- } else if (tool === "show_assets") {
1565
+ } else if (tool === "assets") {
1392
1566
  const assets = records(output.assets);
1393
1567
  result.metadata.assets = assets.length;
1394
1568
  summary.push(plural(assets.length, "asset"));
@@ -1402,7 +1576,7 @@ function presentShowResult(tool, value, options = {}) {
1402
1576
  summary.push(plural(nodes.length, "node"));
1403
1577
  if (result.metadata.pages !== undefined)
1404
1578
  summary.push(plural(result.metadata.pages, "page"));
1405
- const sections = [text(output.title) ? safe(text(output.title)) : "", documentNodes(nodes, tool === "show_content")];
1579
+ const sections = [text(output.title) ? safe(text(output.title)) : "", documentNodes(nodes, tool === "show")];
1406
1580
  if (assets.length > 0) {
1407
1581
  result.metadata.assets = assets.length;
1408
1582
  sections.push(`Assets
@@ -1411,7 +1585,7 @@ ${assetList(assets)}`);
1411
1585
  result.body = sections.filter(Boolean).join(`
1412
1586
 
1413
1587
  `) || "No content in this selection.";
1414
- } else if (tool === "show_structure") {
1588
+ } else if (tool === "tree") {
1415
1589
  const symbols = records(structure.symbols);
1416
1590
  result.metadata.symbols = symbols.length;
1417
1591
  summary.push(plural(symbols.length, "symbol"));
@@ -1465,7 +1639,7 @@ ${assetList(assets)}`);
1465
1639
  const description = Object.keys(content).length > 0 ? content : output;
1466
1640
  result.body = fields(description) || "No content in this selection.";
1467
1641
  }
1468
- if (tool === "show_structure")
1642
+ if (tool === "tree")
1469
1643
  result.notices.push("Structure is limited to the requested depth, kind, and outline. Use a deeper depth or focus with at to inspect further.");
1470
1644
  if (text(output.warning))
1471
1645
  result.notices.push(safe(text(output.warning)));
@@ -1725,6 +1899,46 @@ function search(root) {
1725
1899
  }))
1726
1900
  };
1727
1901
  }
1902
+ function grepChildren(node, allowed) {
1903
+ if (node.children.some((child) => !allowed.includes(child.name))) {
1904
+ throw new Error(`invalid readseek KDL grep: unexpected child of ${node.name}`);
1905
+ }
1906
+ }
1907
+ function grepLine(node) {
1908
+ grepChildren(node, []);
1909
+ const parsed = line(node);
1910
+ if (requireProperty(node, "number") !== parsed.line)
1911
+ throw new Error("invalid readseek KDL grep line number");
1912
+ return { ...parsed, kind: requireProperty(node, "kind") };
1913
+ }
1914
+ function grep(root) {
1915
+ grepChildren(root, ["match", "document"]);
1916
+ return parseGrepOutput({
1917
+ ...properties(root),
1918
+ results: children(root, "match").map((match) => {
1919
+ grepChildren(match, ["hit", "line", "symbol"]);
1920
+ return {
1921
+ ...properties(match),
1922
+ hits: children(match, "hit").map((hit) => {
1923
+ grepChildren(hit, []);
1924
+ return properties(hit);
1925
+ }),
1926
+ lines: children(match, "line").map(grepLine),
1927
+ symbols: children(match, "symbol").map((symbol) => {
1928
+ grepChildren(symbol, ["line"]);
1929
+ return { ...properties(symbol), lines: children(symbol, "line").map(grepLine) };
1930
+ })
1931
+ };
1932
+ }),
1933
+ documents: children(root, "document").map((document) => {
1934
+ grepChildren(document, ["hit"]);
1935
+ return { ...properties(document), hits: children(document, "hit").map((hit) => {
1936
+ grepChildren(hit, []);
1937
+ return properties(hit);
1938
+ }) };
1939
+ })
1940
+ });
1941
+ }
1728
1942
  function references(root) {
1729
1943
  const parsed = children(root, "reference").map((reference) => {
1730
1944
  const sourceLine = child(reference, "line");
@@ -1840,7 +2054,7 @@ function parseReadSeekKdl(text) {
1840
2054
  case "document":
1841
2055
  return root.properties.has("format") ? pdfDocument(root) : sourceDocument(root);
1842
2056
  case "search":
1843
- return search(root);
2057
+ return ["mode", "ignore_case", "summary", "count_unit"].some((key) => root.properties.has(key)) ? grep(root) : search(root);
1844
2058
  case "check":
1845
2059
  return diagnostics(root);
1846
2060
  case "refs":
@@ -1905,7 +2119,7 @@ function acceptsReadSeekExitCode(args, exitCode) {
1905
2119
  index++;
1906
2120
  continue;
1907
2121
  }
1908
- return arg === "find" || arg === "check";
2122
+ return arg === "grep" || arg === "search" || arg === "def" || arg === "refs" || arg === "syntax" || arg === "check";
1909
2123
  }
1910
2124
  return false;
1911
2125
  }
@@ -2491,7 +2705,7 @@ function formatReplaceHint(edits, noopEdits) {
2491
2705
  return;
2492
2706
  if (counts.replace !== counts.total)
2493
2707
  return;
2494
- return "[info: this edit used replace (unverified). For safer future edits, prefer set_line/replace_lines with an anchor from readSeek_show_content/readSeek_find_text/readSeek_find_nodes.]";
2708
+ return "[info: this edit used replace (unverified). For safer future edits, prefer set_line/replace_lines with an anchor from readSeek_show/readSeek_grep/readSeek_search.]";
2495
2709
  }
2496
2710
  function buildEditOutput(input) {
2497
2711
  const summary = `Updated ${input.displayPath}`;
@@ -2591,10 +2805,10 @@ import { readFileSync, statSync as statSync2 } from "node:fs";
2591
2805
  import { homedir as homedir2 } from "node:os";
2592
2806
  import { join } from "node:path";
2593
2807
  var READSEEK_TOOL_REPLACEMENTS = {
2594
- read: "readSeek_show_content",
2595
- edit: "readSeek_edit_text",
2596
- grep: "readSeek_find_text",
2597
- write: "readSeek_write_file"
2808
+ read: "readSeek_show",
2809
+ edit: "readSeek_edit",
2810
+ grep: "readSeek_grep",
2811
+ write: "readSeek_write"
2598
2812
  };
2599
2813
  var READSEEK_KEYS = ["overrideTools", "imageMode", "syntaxValidation", "timeoutMs", "grep", "display"];
2600
2814
  var READSEEK_GREP_KEYS = ["maxLines", "maxBytes"];
@@ -2830,7 +3044,7 @@ async function ensureDefaultReadSeekDir() {
2830
3044
  return null;
2831
3045
  if (directoryExists(dir))
2832
3046
  return dir;
2833
- defaultReadSeekDirInit ??= runReadSeekNodeText(readSeekBinaryPath(), ["--readseek-dir", dir, "initialize", "index"], { timeoutMs: readSeekTimeoutMs() }).then(() => directoryExists(dir) ? dir : null).catch(() => null).finally(() => {
3047
+ defaultReadSeekDirInit ??= runReadSeekNodeText(readSeekBinaryPath(), ["--readseek-dir", dir, "init"], { timeoutMs: readSeekTimeoutMs() }).then(() => directoryExists(dir) ? dir : null).catch(() => null).finally(() => {
2834
3048
  defaultReadSeekDirInit = undefined;
2835
3049
  });
2836
3050
  return defaultReadSeekDirInit;
@@ -2848,18 +3062,7 @@ function hasProjectReadSeekDir(cwd = process.cwd()) {
2848
3062
  }
2849
3063
  function supportsReadSeekDir(args) {
2850
3064
  const first = args[0];
2851
- const second = args[1];
2852
- if (first === "initialize" && second === "index")
2853
- return true;
2854
- if (first === "show" && (second === "content" || second === "assets"))
2855
- return true;
2856
- if (first === "generate" && second === "caption")
2857
- return true;
2858
- if (first === "detect" && second === "objects")
2859
- return true;
2860
- if (first === "recognize" && second === "text")
2861
- return true;
2862
- return false;
3065
+ return first === "init" || first === "show" || first === "assets" || first === "caption" || first === "objects" || first === "ocr";
2863
3066
  }
2864
3067
  async function readSeekInvocationArgs(args) {
2865
3068
  if (!supportsReadSeekDir(args) || hasProjectReadSeekDir())
@@ -2917,27 +3120,21 @@ var runner = {
2917
3120
  kdl: async (args, options = {}) => parseReadSeekKdl(await runner.text(args, options))
2918
3121
  };
2919
3122
  var client2 = createReadSeekClient(runner);
2920
- var readSeekShowContent = client2.showContent;
2921
- var readSeekShowStructure = client2.showStructure;
2922
- var readSeekShowMetadata = client2.showMetadata;
2923
- var readSeekShowAssets = client2.showAssets;
2924
- var readSeekGenerateCaption = client2.generateCaption;
2925
- var readSeekDetectObjects = client2.detectObjects;
2926
- var readSeekRecognizeText = client2.recognizeText;
2927
- var readSeekFindNodes = client2.findNodes;
2928
- var readSeekFindReferences = client2.findReferences;
2929
- var readSeekFindDefinitions = client2.findDefinitions;
2930
- var readSeekCheckSyntax = client2.checkSyntax;
2931
- var readSeekCheckRules = client2.checkRules;
2932
- var readSeekEditText = client2.editText;
2933
- var readSeekRenameSymbol = client2.renameSymbol;
2934
- async function readSeekMap(filePath, options = {}) {
2935
- const envelope = await client2.showStructure(filePath, { depth: DEFAULT_STRUCTURE_DEPTH, signal: options.signal });
2936
- const map = "structure" in envelope ? envelope.structure : undefined;
2937
- if (!map || map.language === "unknown" && map.symbols.length === 0)
2938
- return null;
2939
- return map;
2940
- }
3123
+ var readSeekShow = client2.show;
3124
+ var readSeekTree = client2.tree;
3125
+ var readSeekMeta = client2.meta;
3126
+ var readSeekAssets = client2.assets;
3127
+ var readSeekCaption = client2.caption;
3128
+ var readSeekObjects = client2.objects;
3129
+ var readSeekOcr = client2.ocr;
3130
+ var readSeekSearch = client2.search;
3131
+ var readSeekGrep = client2.grep;
3132
+ var readSeekRefs = client2.refs;
3133
+ var readSeekDef = client2.def;
3134
+ var readSeekSyntax = client2.syntax;
3135
+ var readSeekCheck = client2.check;
3136
+ var readSeekEdit = client2.edit;
3137
+ var readSeekRename = client2.rename;
2941
3138
 
2942
3139
  // src/edit-syntax-validate.ts
2943
3140
  function dedupeSortLines(diagnostics) {
@@ -2958,8 +3155,8 @@ async function validateSyntaxRegression(input, options = {}) {
2958
3155
  let before;
2959
3156
  let after;
2960
3157
  try {
2961
- before = input.before === undefined ? EMPTY : await readSeekCheckSyntax("-", { stdinName: input.filePath, stdin: input.before, signal: options.signal });
2962
- after = await readSeekCheckSyntax("-", { stdinName: input.filePath, stdin: input.after, signal: options.signal });
3158
+ before = input.before === undefined ? EMPTY : await readSeekSyntax("-", { stdinName: input.filePath, stdin: input.before, signal: options.signal });
3159
+ after = await readSeekSyntax("-", { stdinName: input.filePath, stdin: input.after, signal: options.signal });
2963
3160
  } catch {
2964
3161
  return null;
2965
3162
  }
@@ -3645,7 +3842,7 @@ async function executeEdit(opts) {
3645
3842
  return await withFileMutationQueue(absolutePath, async () => {
3646
3843
  throwIfAborted(signal);
3647
3844
  if (wasReadInSession && !wasReadInSession(absolutePath)) {
3648
- const hint = `Call readSeek_show_content(${JSON.stringify(rawPath)}) first, or use readSeek_find_text, readSeek_find_nodes, or readSeek_write_file to produce fresh anchors for this file.`;
3845
+ const hint = `Call readSeek_show(${JSON.stringify(rawPath)}) first, or use readSeek_grep, readSeek_search, or readSeek_write to produce fresh anchors for this file.`;
3649
3846
  return buildEditError(absolutePath, "file-not-read", `You must get fresh anchors for ${absolutePath} before editing it. ${hint}`, hint);
3650
3847
  }
3651
3848
  const parsedEdits = validateExactlyOneEditVariant(parsed.edits ?? []);
@@ -3655,7 +3852,7 @@ async function executeEdit(opts) {
3655
3852
  const edits = parsedEdits.edits;
3656
3853
  let plan;
3657
3854
  try {
3658
- plan = await readSeekEditText(absolutePath, edits, { signal, language: parsed.language });
3855
+ plan = await readSeekEdit(absolutePath, edits, { signal, language: parsed.language });
3659
3856
  } catch (err) {
3660
3857
  throwIfAborted(signal);
3661
3858
  const failure = classifyReadSeekFailure(err);
@@ -3691,7 +3888,7 @@ async function executeEdit(opts) {
3691
3888
  throwIfAborted(signal);
3692
3889
  let applied;
3693
3890
  try {
3694
- applied = await readSeekEditText(absolutePath, edits, {
3891
+ applied = await readSeekEdit(absolutePath, edits, {
3695
3892
  apply: true,
3696
3893
  planHash: plan.plan_hash,
3697
3894
  language: parsed.language,
@@ -3759,9 +3956,9 @@ async function executeEdit(opts) {
3759
3956
  }
3760
3957
  }
3761
3958
  function registerEditTool(pi, options = {}) {
3762
- const name = options.name ?? "readSeek_edit_text";
3959
+ const name = options.name ?? "readSeek_edit";
3763
3960
  const promptMetadata = defineToolPromptMetadata({
3764
- toolName: "edit_text",
3961
+ toolName: "edit",
3765
3962
  promptSnippet: "Edit existing files with fresh LINE:HASH anchors",
3766
3963
  registeredName: name,
3767
3964
  toolAliases: options.toolAliases
@@ -3857,29 +4054,12 @@ ${theme.fg("error", info.errorText)}`;
3857
4054
  }
3858
4055
 
3859
4056
  // src/grep.ts
3860
- import { readFile as fsReadFile2, stat as fsStat } from "node:fs/promises";
3861
- import path5 from "node:path";
3862
- import { createGrepTool } from "@earendil-works/pi-coding-agent";
3863
4057
  import { Type as Type5 } from "@sinclair/typebox";
3864
4058
  import { Text as Text4 } from "@earendil-works/pi-tui";
3865
4059
 
3866
- // src/binary-detect.ts
3867
- function looksLikeBinary(buf) {
3868
- if (buf.length === 0)
3869
- return false;
3870
- if (buf.includes(0))
3871
- return true;
3872
- const decoded = buf.toString("utf8");
3873
- if (!decoded.includes("�"))
3874
- return false;
3875
- return !Buffer.from(decoded, "utf8").equals(buf);
3876
- }
3877
-
3878
4060
  // src/grep-output.ts
3879
- import {
3880
- formatSize,
3881
- truncateHead
3882
- } from "@earendil-works/pi-coding-agent";
4061
+ import path5 from "node:path";
4062
+ import { formatSize, truncateHead } from "@earendil-works/pi-coding-agent";
3883
4063
 
3884
4064
  // src/grep-budget.ts
3885
4065
  import { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES } from "@earendil-works/pi-coding-agent";
@@ -3899,283 +4079,75 @@ function resolveGrepOutputBudget() {
3899
4079
  }
3900
4080
 
3901
4081
  // src/grep-output.ts
3902
- function hasScope(group) {
3903
- return group.scope !== undefined;
3904
- }
3905
- function renderEntry(displayPath, entry) {
3906
- if (entry.kind === "separator")
3907
- return entry.text;
3908
- const marker = entry.kind === "match" ? ">>" : " ";
3909
- return `${displayPath}:${marker}${entry.line.anchor}|${entry.line.display}`;
3910
- }
3911
- function renderGroupHeader(group) {
3912
- if (!group.scope) {
3913
- return `--- ${group.displayPath} (${group.matchCount} matches) ---`;
3914
- }
3915
- const parent = group.scope.symbol.parentName ? ` in ${group.scope.symbol.parentName}` : "";
3916
- const suffix = group.scope.contextLines !== undefined ? `, scoped to ±${group.scope.contextLines} lines` : "";
3917
- return `--- ${group.displayPath} :: ${group.scope.symbol.kind} ${group.scope.symbol.name}${parent} (${group.scope.symbol.startLine}-${group.scope.symbol.endLine}, ${group.matchCount} matches${suffix}) ---`;
3918
- }
3919
- function buildScopeMetadata(groups, warnings) {
3920
- return {
3921
- mode: "symbol",
3922
- groups: groups.filter(hasScope).map((group) => ({
3923
- path: group.absolutePath,
3924
- displayPath: group.displayPath,
3925
- symbol: group.scope.symbol,
3926
- matchCount: group.matchCount,
3927
- matchLines: [...group.scope.matchLines],
3928
- lineAnchors: group.entries.flatMap((entry) => entry.kind === "separator" ? [] : [entry.line.anchor])
3929
- })),
3930
- warnings: [...warnings]
3931
- };
3932
- }
3933
- function buildGrepOutput(input) {
3934
- const fileCount = new Set(input.groups.map((group) => group.absolutePath)).size;
3935
- const header = `[${input.totalMatches} matches in ${fileCount} files]`;
3936
- let text;
3937
- if (input.summary) {
3938
- const fileLines = [...input.groups].sort((a, b) => b.matchCount - a.matchCount).map((group) => `${group.absolutePath}: ${group.matchCount} matches`);
3939
- text = [header, ...fileLines].join(`
3940
- `);
3941
- } else {
3942
- const blocks = [header];
3943
- for (const group of input.groups) {
3944
- blocks.push(renderGroupHeader(group));
3945
- for (const entry of group.entries) {
3946
- blocks.push(renderEntry(group.displayPath, entry));
4082
+ function buildGrepOutput(output, cwd) {
4083
+ const recordBlocks = new Map;
4084
+ const fileCount = new Set([...output.results, ...output.documents].map((group) => path5.resolve(cwd, group.file))).size;
4085
+ const blocks = [`[${output.total_matches} matches in ${fileCount} files]`];
4086
+ for (const group of output.results) {
4087
+ const absolutePath = path5.resolve(cwd, group.file);
4088
+ const displayPath = path5.relative(cwd, absolutePath) || absolutePath;
4089
+ if (output.summary) {
4090
+ blocks.push(`${absolutePath}: ${group.total_matches} matches`);
4091
+ continue;
4092
+ }
4093
+ blocks.push(`--- ${displayPath} (${group.total_matches} matches) ---`);
4094
+ const renderLines = (lines) => {
4095
+ for (const line of lines) {
4096
+ const built = buildReadSeekLineWithHash(line.line, line.hash, line.text);
4097
+ const marker = line.kind === "match" ? ">>" : " ";
4098
+ blocks.push(`${displayPath}:${marker}${built.anchor}|${built.display}`);
4099
+ recordBlocks.set(blocks.length - 1, { path: absolutePath, line: line.line, anchor: built.anchor, kind: line.kind });
3947
4100
  }
4101
+ };
4102
+ renderLines(group.lines);
4103
+ for (const symbol of group.symbols) {
4104
+ blocks.push(`--- ${displayPath} :: ${symbol.kind} ${symbol.name} (${symbol.start_line}-${symbol.end_line}) ---`);
4105
+ renderLines(symbol.lines);
3948
4106
  }
3949
- text = blocks.join(`
3950
- `);
3951
4107
  }
3952
- const passthroughLines = input.passthroughLines ?? [];
3953
- if (passthroughLines.length > 0) {
3954
- text += `
3955
-
3956
- ${passthroughLines.join(`
3957
- `)}`;
3958
- }
3959
- if (input.limit !== undefined && input.totalMatches === input.limit) {
3960
- text += `
3961
-
3962
- [Results truncated at ${input.limit} matches — refine pattern or increase limit]`;
4108
+ for (const group of output.documents) {
4109
+ const absolutePath = path5.resolve(cwd, group.file);
4110
+ const displayPath = path5.relative(cwd, absolutePath) || absolutePath;
4111
+ if (output.summary) {
4112
+ blocks.push(`${absolutePath}: ${group.total_matches} matches`);
4113
+ continue;
4114
+ }
4115
+ blocks.push(`--- ${displayPath} (${group.total_matches} matches, document) ---`);
4116
+ for (const hit of group.hits) {
4117
+ const page = hit.page === undefined ? "" : ` page:${hit.page}`;
4118
+ blocks.push(`${displayPath} node:${hit.node_id}${page} (${hit.kind}): ${escapeControlCharsForDisplay(hit.snippet)}`);
4119
+ }
3963
4120
  }
3964
- const scopeWarnings = input.scopeWarnings ?? [];
3965
- if (!input.summary && input.scopeMode === "symbol" && scopeWarnings.length > 0) {
3966
- text = `${scopeWarnings.map((warning) => warning.message).join(`
3967
-
3968
- `)}
3969
-
3970
- ${text}`;
4121
+ if (output.truncated) {
4122
+ blocks.push(`
4123
+ [Results truncated: ${output.returned_matches} of ${output.total_matches} matching lines returned — refine pattern or increase limit]`);
3971
4124
  }
4125
+ let text = blocks.join(`
4126
+ `);
3972
4127
  const budget = resolveGrepOutputBudget();
3973
- const truncated = truncateHead(text, {
3974
- maxLines: budget.maxLines,
3975
- maxBytes: budget.maxBytes
4128
+ const truncated = truncateHead(text, { maxLines: budget.maxLines, maxBytes: budget.maxBytes });
4129
+ let endOffset = 0;
4130
+ const records = blocks.flatMap((block, index) => {
4131
+ endOffset += block.length + (index > 0 ? 1 : 0);
4132
+ const record = recordBlocks.get(index);
4133
+ return record && endOffset <= truncated.content.length ? [record] : [];
3976
4134
  });
3977
4135
  if (truncated.truncated) {
3978
4136
  text = `${truncated.content}
3979
4137
 
3980
4138
  [Output truncated: showing ${truncated.outputLines} of ${truncated.totalLines} lines (${formatSize(truncated.outputBytes)} of ${formatSize(truncated.totalBytes)}). Refine pattern or increase limit.]`;
3981
4139
  }
3982
- const readSeekValue = {
3983
- tool: "grep",
3984
- summary: input.summary,
3985
- totalMatches: input.totalMatches,
3986
- records: input.records.map((record) => ({
3987
- path: record.path,
3988
- line: record.line,
3989
- anchor: record.anchor,
3990
- kind: record.kind
3991
- }))
3992
- };
3993
- if (!input.summary && input.scopeMode === "symbol") {
3994
- readSeekValue.scopes = buildScopeMetadata(input.groups, scopeWarnings);
3995
- }
3996
4140
  return {
3997
4141
  text,
3998
- readSeekValue
3999
- };
4000
- }
4001
-
4002
- // src/file-map.ts
4003
- async function getOrGenerateMap(absPath) {
4004
- try {
4005
- return await readSeekMap(absPath);
4006
- } catch {
4007
- return null;
4008
- }
4009
- }
4010
-
4011
- // src/grep-symbol-scope.ts
4012
- function findEnclosingSymbol(map, lineNumber) {
4013
- const byQualifiedName = new Map;
4014
- for (const symbol of map.symbols) {
4015
- const qname = symbol.qualified_name ?? symbol.name;
4016
- if (!byQualifiedName.has(qname))
4017
- byQualifiedName.set(qname, symbol);
4018
- }
4019
- const candidates = map.symbols.filter((symbol) => lineNumber >= symbol.start_line && lineNumber <= symbol.end_line).map((symbol) => {
4020
- const qname = symbol.qualified_name ?? symbol.name;
4021
- const dot = qname.lastIndexOf(".");
4022
- const parent = dot === -1 ? undefined : byQualifiedName.get(qname.slice(0, dot));
4023
- return {
4024
- name: symbol.name,
4025
- kind: symbol.kind === "constructor" ? "method" : symbol.kind,
4026
- startLine: symbol.start_line,
4027
- endLine: symbol.end_line,
4028
- parentName: parent?.name
4029
- };
4030
- }).sort((a, b) => {
4031
- const rangeA = a.endLine - a.startLine;
4032
- const rangeB = b.endLine - b.startLine;
4033
- if (rangeA !== rangeB)
4034
- return rangeA - rangeB;
4035
- if (a.startLine !== b.startLine)
4036
- return a.startLine - b.startLine;
4037
- return a.name.localeCompare(b.name);
4038
- });
4039
- return candidates[0] ?? null;
4040
- }
4041
- function firstLineNumber(group) {
4042
- const first = group.entries.find((e) => e.kind !== "separator");
4043
- return first ? first.line.line : Number.MAX_SAFE_INTEGER;
4044
- }
4045
- function buildSymbolEntries(fileLines, symbol, matchLines, scopeContext) {
4046
- if (scopeContext === undefined) {
4047
- const entries = [];
4048
- for (let lineNumber = symbol.startLine;lineNumber <= symbol.endLine; lineNumber++) {
4049
- const built = buildReadSeekLine(lineNumber, fileLines[lineNumber - 1] ?? "");
4050
- entries.push({ kind: matchLines.has(lineNumber) ? "match" : "context", line: built });
4051
- }
4052
- return entries;
4053
- }
4054
- const ranges = [...matchLines].sort((a, b) => a - b).map((ln) => ({
4055
- startLine: Math.max(symbol.startLine, ln - scopeContext),
4056
- endLine: Math.min(symbol.endLine, ln + scopeContext)
4057
- }));
4058
- const merged = [];
4059
- for (const r of ranges) {
4060
- const last = merged[merged.length - 1];
4061
- if (last && r.startLine <= last.endLine + 1) {
4062
- last.endLine = Math.max(last.endLine, r.endLine);
4063
- } else {
4064
- merged.push({ ...r });
4065
- }
4066
- }
4067
- const entries = [];
4068
- for (let i = 0;i < merged.length; i++) {
4069
- if (i > 0)
4070
- entries.push({ kind: "separator", text: "--" });
4071
- const range = merged[i];
4072
- for (let ln = range.startLine;ln <= range.endLine; ln++) {
4073
- const built = buildReadSeekLine(ln, fileLines[ln - 1] ?? "");
4074
- entries.push({ kind: matchLines.has(ln) ? "match" : "context", line: built });
4075
- }
4076
- }
4077
- return entries;
4078
- }
4079
- function buildFallbackEntries(fileLines, matchLines, contextLines) {
4080
- const lineMap = new Map;
4081
- for (const matchLine of matchLines) {
4082
- const start = Math.max(1, matchLine - contextLines);
4083
- const end = Math.min(fileLines.length, matchLine + contextLines);
4084
- for (let lineNumber = start;lineNumber <= end; lineNumber++) {
4085
- const built = buildReadSeekLine(lineNumber, fileLines[lineNumber - 1] ?? "");
4086
- const candidate = { kind: lineNumber === matchLine ? "match" : "context", line: built };
4087
- const existing = lineMap.get(lineNumber);
4088
- if (!existing || existing.kind === "context" && candidate.kind === "match") {
4089
- lineMap.set(lineNumber, candidate);
4090
- }
4091
- }
4092
- }
4093
- const ordered = [...lineMap.entries()].sort(([a], [b]) => a - b);
4094
- const entries = [];
4095
- for (let i = 0;i < ordered.length; i++) {
4096
- if (i > 0 && ordered[i][0] > ordered[i - 1][0] + 1)
4097
- entries.push({ kind: "separator", text: "--" });
4098
- entries.push(ordered[i][1]);
4099
- }
4100
- return entries;
4101
- }
4102
- function scopeGrepGroupsToSymbols(input) {
4103
- const warnings = [];
4104
- const rendered = [];
4105
- for (const group of input.groups) {
4106
- const fileLines = input.fileLinesByPath.get(group.absolutePath);
4107
- const fileMap = input.fileMapsByPath.get(group.absolutePath) ?? null;
4108
- if (!fileLines || !fileMap) {
4109
- warnings.push({
4110
- code: "unmappable-file",
4111
- message: `[Warning: symbol scoping unavailable for ${group.absolutePath} — showing normal grep lines for this file]`,
4112
- path: group.absolutePath
4113
- });
4114
- rendered.push({ order: firstLineNumber(group), group });
4115
- continue;
4116
- }
4117
- const symbolBuckets = new Map;
4118
- const fallbackMatchLines = [];
4119
- for (const entry of group.entries) {
4120
- if (entry.kind !== "match")
4121
- continue;
4122
- const symbol = findEnclosingSymbol(fileMap, entry.line.line);
4123
- if (!symbol) {
4124
- fallbackMatchLines.push(entry.line.line);
4125
- warnings.push({
4126
- code: "no-enclosing-symbol",
4127
- message: `[Warning: no enclosing symbol for ${group.absolutePath}:${entry.line.line} — showing normal grep lines for this match]`,
4128
- path: group.absolutePath,
4129
- line: entry.line.line
4130
- });
4131
- continue;
4132
- }
4133
- const key = `${symbol.startLine}:${symbol.endLine}:${symbol.parentName ?? ""}:${symbol.name}`;
4134
- const bucket = symbolBuckets.get(key) ?? { symbol, matchLines: new Set };
4135
- bucket.matchLines.add(entry.line.line);
4136
- symbolBuckets.set(key, bucket);
4137
- }
4138
- const scopedGroups = [...symbolBuckets.values()].sort((a, b) => {
4139
- if (a.symbol.startLine !== b.symbol.startLine)
4140
- return a.symbol.startLine - b.symbol.startLine;
4141
- return a.symbol.name.localeCompare(b.symbol.name);
4142
- }).map(({ symbol, matchLines }) => ({
4143
- displayPath: group.displayPath,
4144
- absolutePath: group.absolutePath,
4145
- matchCount: matchLines.size,
4146
- scope: {
4147
- mode: "symbol",
4148
- symbol,
4149
- matchLines: [...matchLines].sort((a, b) => a - b),
4150
- ...input.scopeContext !== undefined ? { contextLines: input.scopeContext } : {}
4151
- },
4152
- entries: buildSymbolEntries(fileLines, symbol, matchLines, input.scopeContext)
4153
- }));
4154
- for (const scopedGroup of scopedGroups)
4155
- rendered.push({ order: scopedGroup.scope.symbol.startLine, group: scopedGroup });
4156
- if (fallbackMatchLines.length > 0) {
4157
- rendered.push({
4158
- order: Math.min(...fallbackMatchLines),
4159
- group: {
4160
- displayPath: group.displayPath,
4161
- absolutePath: group.absolutePath,
4162
- matchCount: fallbackMatchLines.length,
4163
- entries: buildFallbackEntries(fileLines, fallbackMatchLines, input.contextLines)
4164
- }
4165
- });
4166
- }
4167
- if (scopedGroups.length === 0 && fallbackMatchLines.length === 0) {
4168
- rendered.push({ order: firstLineNumber(group), group });
4142
+ readSeekValue: {
4143
+ ...output,
4144
+ tool: "grep",
4145
+ totalMatches: output.total_matches,
4146
+ returnedMatches: output.returned_matches,
4147
+ fileCount,
4148
+ records
4169
4149
  }
4170
- }
4171
- rendered.sort((a, b) => {
4172
- if (a.order !== b.order)
4173
- return a.order - b.order;
4174
- if (a.group.absolutePath !== b.group.absolutePath)
4175
- return a.group.absolutePath.localeCompare(b.group.absolutePath);
4176
- return a.group.displayPath.localeCompare(b.group.displayPath);
4177
- });
4178
- return { groups: rendered.map((item) => item.group), warnings };
4150
+ };
4179
4151
  }
4180
4152
 
4181
4153
  // src/grep-render-helpers.ts
@@ -4193,7 +4165,6 @@ function formatGrepCallText(args) {
4193
4165
  suffix: parts.length > 0 ? parts.join(" ") : undefined
4194
4166
  };
4195
4167
  }
4196
- var GREP_TRUNCATION_THRESHOLD = 50;
4197
4168
  function formatGrepResultText(input) {
4198
4169
  if (input.isError && input.errorText) {
4199
4170
  return {
@@ -4206,17 +4177,15 @@ function formatGrepResultText(input) {
4206
4177
  }
4207
4178
  const { totalMatches, summary, fileCount } = input;
4208
4179
  const noMatches = totalMatches === 0;
4209
- const truncated = totalMatches > GREP_TRUNCATION_THRESHOLD;
4180
+ const truncated = input.truncated ?? false;
4210
4181
  const matchWord = totalMatches === 1 ? "match" : "matches";
4211
4182
  const fileWord = fileCount === 1 ? "file" : "files";
4212
4183
  const summaryText = noMatches ? "" : `✓ ${totalMatches} ${matchWord} in ${fileCount} ${fileWord}`;
4213
4184
  const badges = [];
4214
4185
  if (truncated)
4215
- badges.push("10/file cap");
4186
+ badges.push("truncated");
4216
4187
  if (summary)
4217
4188
  badges.push("summary");
4218
- if (input.hasBinaryWarning)
4219
- badges.push("⚠ binary");
4220
4189
  return {
4221
4190
  summary: summaryText,
4222
4191
  badges,
@@ -4269,78 +4238,12 @@ var grepSchema = Type5.Object({
4269
4238
  scope: Type5.Optional(Type5.Literal("symbol", {
4270
4239
  description: PARAM_DESCRIPTIONS.grepScope
4271
4240
  })),
4272
- scopeContext: optionalIntOrString(PARAM_DESCRIPTIONS.scopeContext)
4241
+ scopeContext: optionalIntOrString(PARAM_DESCRIPTIONS.scopeContext),
4242
+ ...readSeekGitSearchParams()
4273
4243
  });
4274
- var MATCH_LINE_RE = /^(.*?):(\d+): (.*)$/;
4275
- var CONTEXT_LINE_RE = /^(.*?)-(\d+)- (.*)$/;
4276
- function parseGrepOutputLine(line) {
4277
- const match = line.match(MATCH_LINE_RE);
4278
- if (match) {
4279
- return {
4280
- kind: "match",
4281
- displayPath: match[1],
4282
- lineNumber: Number.parseInt(match[2], 10),
4283
- text: match[3]
4284
- };
4285
- }
4286
- const context = line.match(CONTEXT_LINE_RE);
4287
- if (context) {
4288
- return {
4289
- kind: "context",
4290
- displayPath: context[1],
4291
- lineNumber: Number.parseInt(context[2], 10),
4292
- text: context[3]
4293
- };
4294
- }
4295
- return null;
4296
- }
4297
- var GREP_MAX_MATCHES_PER_FILE = 10;
4298
- function dedupeContextEntries(entries) {
4299
- if (entries.length === 0)
4300
- return entries;
4301
- const byLine = new Map;
4302
- for (const entry of entries) {
4303
- if (entry.kind === "separator")
4304
- continue;
4305
- const existing = byLine.get(entry.line.line);
4306
- if (!existing || entry.kind === "match" && existing.kind === "context") {
4307
- byLine.set(entry.line.line, entry);
4308
- }
4309
- }
4310
- const sorted = [...byLine.entries()].sort(([a], [b]) => a - b);
4311
- const result = [];
4312
- for (let i = 0;i < sorted.length; i++) {
4313
- if (i > 0 && sorted[i][0] > sorted[i - 1][0] + 1) {
4314
- result.push({ kind: "separator", text: "--" });
4315
- }
4316
- result.push(sorted[i][1]);
4317
- }
4318
- return result;
4319
- }
4320
- function truncateGroupEntries(group) {
4321
- let matchesSeen = 0;
4322
- let truncatedCount = 0;
4323
- const kept = [];
4324
- for (const entry of group.entries) {
4325
- if (entry.kind === "match") {
4326
- matchesSeen++;
4327
- if (matchesSeen <= GREP_MAX_MATCHES_PER_FILE) {
4328
- kept.push(entry);
4329
- } else {
4330
- truncatedCount++;
4331
- }
4332
- } else if (matchesSeen <= GREP_MAX_MATCHES_PER_FILE) {
4333
- kept.push(entry);
4334
- }
4335
- }
4336
- if (truncatedCount > 0) {
4337
- kept.push({ kind: "separator", text: `... +${truncatedCount} more matches` });
4338
- }
4339
- return { ...group, entries: kept };
4340
- }
4341
4244
  async function executeGrep(opts) {
4342
- const { toolCallId, params, signal, onUpdate, cwd, onFileAnchored } = opts;
4343
- await ensureHashInit();
4245
+ const { params, signal, cwd, onFileAnchored } = opts;
4246
+ signal?.throwIfAborted();
4344
4247
  const rawParams = params;
4345
4248
  const context = coerceObviousBase10Int(rawParams.context, "context");
4346
4249
  if (!context.ok) {
@@ -4368,267 +4271,34 @@ async function executeGrep(opts) {
4368
4271
  const message = `Invalid scopeContext: expected a non-negative integer, received ${scopeContext.value}.`;
4369
4272
  return buildToolErrorResult("grep", "invalid-params-combo", message);
4370
4273
  }
4371
- const p = {
4372
- ...rawParams,
4274
+ const output = await readSeekGrep({
4275
+ pattern: rawParams.pattern,
4276
+ path: resolveToCwd(rawParams.path ?? ".", cwd),
4277
+ glob: rawParams.glob,
4278
+ ignoreCase: rawParams.ignoreCase,
4279
+ literal: rawParams.literal,
4373
4280
  context: context.value,
4374
4281
  limit: limit.value,
4375
- scopeContext: scopeContext.value
4376
- };
4377
- const builtin = createGrepTool(cwd);
4378
- const result = await builtin.execute(toolCallId, {
4379
- ...p,
4380
- context: context.value,
4381
- limit: limit.value
4382
- }, signal, onUpdate);
4383
- const textBlock = result.content?.find((item) => item.type === "text" && ("text" in item) && typeof item.text === "string");
4384
- if (!textBlock?.text)
4385
- return result;
4386
- const { path: rawSearchPath } = p;
4387
- const searchPath = resolveToCwd(rawSearchPath || ".", cwd);
4388
- let searchPathIsDirectory = false;
4389
- try {
4390
- searchPathIsDirectory = (await fsStat(searchPath)).isDirectory();
4391
- } catch {
4392
- searchPathIsDirectory = false;
4393
- }
4394
- if (!searchPathIsDirectory) {
4395
- try {
4396
- const buf = await fsReadFile2(searchPath);
4397
- if (looksLikeBinary(buf)) {
4398
- const warning = `[Warning: '${p.path ?? searchPath}' appears to be a binary file — grep skips binary files by default. Use a hex tool or the read tool to inspect it.]`;
4399
- return {
4400
- ...result,
4401
- content: result.content.map((item) => item === textBlock ? { ...item, text: warning } : item),
4402
- details: {
4403
- ...typeof result.details === "object" && result.details !== null ? result.details : {},
4404
- readSeekValue: {
4405
- tool: "grep",
4406
- summary: !!p.summary,
4407
- totalMatches: 0,
4408
- records: []
4409
- }
4410
- }
4411
- };
4412
- }
4413
- } catch {}
4414
- }
4415
- const fileCache = new Map;
4416
- const bareCRFiles = new Set;
4417
- const getFileLines = async (absolutePath) => {
4418
- throwIfAborted(signal);
4419
- if (fileCache.has(absolutePath))
4420
- return fileCache.get(absolutePath);
4421
- try {
4422
- const rawBuffer = await fsReadFile2(absolutePath);
4423
- if (looksLikeBinary(rawBuffer)) {
4424
- fileCache.set(absolutePath, undefined);
4425
- return;
4426
- }
4427
- const raw = rawBuffer.toString("utf-8");
4428
- if (hasBareCarriageReturn(raw))
4429
- bareCRFiles.add(absolutePath);
4430
- const lines = normalizeToLF(stripBom(raw).text).split(`
4431
- `);
4432
- fileCache.set(absolutePath, lines);
4433
- return lines;
4434
- } catch {
4435
- fileCache.set(absolutePath, undefined);
4436
- return;
4437
- }
4438
- };
4439
- const toAbsolutePath = (displayPath) => {
4440
- if (searchPathIsDirectory)
4441
- return path5.resolve(searchPath, displayPath);
4442
- return searchPath;
4443
- };
4444
- const groupsByPath = new Map;
4445
- const passthroughLines = [];
4446
- let totalMatches = 0;
4447
- let parsedCount = 0;
4448
- let candidateUnparsedCount = 0;
4449
- const candidateLinePattern = /^.+(?::|-)\d+(?::|-)\s/;
4450
- if (!p.summary) {
4451
- const pathsToRead = new Set;
4452
- for (const line of textBlock.text.split(`
4453
- `)) {
4454
- throwIfAborted(signal);
4455
- const parsed = parseGrepOutputLine(line);
4456
- if (parsed && Number.isFinite(parsed.lineNumber) && parsed.lineNumber >= 1) {
4457
- pathsToRead.add(toAbsolutePath(parsed.displayPath));
4458
- }
4459
- }
4460
- const CONCURRENCY = 8;
4461
- const pathList = [...pathsToRead];
4462
- for (let i = 0;i < pathList.length; i += CONCURRENCY) {
4463
- await Promise.all(pathList.slice(i, i + CONCURRENCY).map((p) => getFileLines(p)));
4464
- }
4465
- }
4466
- const addSummaryMatch = (displayPath, absolutePath) => {
4467
- let group = groupsByPath.get(displayPath);
4468
- if (!group) {
4469
- group = { displayPath, absolutePath, matchCount: 0, entries: [] };
4470
- groupsByPath.set(displayPath, group);
4471
- }
4472
- group.matchCount++;
4473
- totalMatches++;
4474
- };
4475
- const addEntry = (displayPath, absolutePath, kind, line) => {
4476
- let group = groupsByPath.get(displayPath);
4477
- if (!group) {
4478
- group = { displayPath, absolutePath, matchCount: 0, entries: [] };
4479
- groupsByPath.set(displayPath, group);
4480
- }
4481
- group.entries.push({ kind, line });
4482
- if (kind === "match") {
4483
- group.matchCount++;
4484
- totalMatches++;
4485
- }
4486
- };
4487
- for (const line of textBlock.text.split(`
4488
- `)) {
4489
- throwIfAborted(signal);
4490
- const parsed = parseGrepOutputLine(line);
4491
- if (!parsed || !Number.isFinite(parsed.lineNumber) || parsed.lineNumber < 1) {
4492
- if (candidateLinePattern.test(line)) {
4493
- candidateUnparsedCount++;
4494
- }
4495
- const trimmed = line.trim();
4496
- if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
4497
- passthroughLines.push(trimmed);
4498
- }
4499
- continue;
4500
- }
4501
- parsedCount++;
4502
- const absolute = toAbsolutePath(parsed.displayPath);
4503
- if (p.summary) {
4504
- if (parsed.kind === "match") {
4505
- addSummaryMatch(parsed.displayPath, absolute);
4506
- }
4507
- continue;
4508
- }
4509
- const fileLines = await getFileLines(absolute);
4510
- if (fileLines === undefined)
4511
- continue;
4512
- if (parsed.kind === "match" && bareCRFiles.has(absolute)) {
4513
- const flags = p.ignoreCase ? "i" : "";
4514
- let patternRe = null;
4515
- try {
4516
- patternRe = p.literal ? new RegExp(p.pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), flags) : new RegExp(p.pattern, flags);
4517
- } catch {}
4518
- if (patternRe !== null) {
4519
- let emitted = false;
4520
- for (let i = 0;i < fileLines.length; i++) {
4521
- if (!patternRe.test(fileLines[i]))
4522
- continue;
4523
- addEntry(parsed.displayPath, absolute, "match", buildReadSeekLine(i + 1, fileLines[i]));
4524
- emitted = true;
4525
- }
4526
- if (emitted)
4527
- continue;
4528
- }
4529
- }
4530
- const sourceLine = fileLines[parsed.lineNumber - 1] ?? parsed.text;
4531
- const built = buildReadSeekLine(parsed.lineNumber, sourceLine);
4532
- addEntry(parsed.displayPath, absolute, parsed.kind, {
4533
- ...built,
4534
- display: escapeControlCharsForDisplay(parsed.text)
4535
- });
4536
- }
4537
- if (p.summary && parsedCount === 0 && candidateUnparsedCount > 0) {
4538
- const passthroughDetails = typeof result.details === "object" && result.details !== null ? result.details : {};
4539
- return {
4540
- ...result,
4541
- details: {
4542
- ...passthroughDetails,
4543
- readSeekValue: {
4544
- tool: "grep",
4545
- summary: true,
4546
- totalMatches: 0,
4547
- records: []
4548
- }
4549
- }
4550
- };
4551
- }
4552
- if (parsedCount === 0 && candidateUnparsedCount > 0) {
4553
- const warning = "[hashline grep passthrough] Unparsed grep format; returned original output.";
4554
- const passthroughDetails = typeof result.details === "object" && result.details !== null ? result.details : {};
4555
- return {
4556
- ...result,
4557
- content: result.content.map((item) => item === textBlock ? { ...item, text: `${textBlock.text}
4558
-
4559
- ${warning}` } : item),
4560
- details: {
4561
- ...passthroughDetails,
4562
- hashlinePassthrough: true,
4563
- hashlineWarning: warning,
4564
- readSeekValue: {
4565
- tool: "grep",
4566
- summary: !!p.summary,
4567
- totalMatches: 0,
4568
- records: []
4569
- }
4570
- }
4571
- };
4572
- }
4573
- const summary = !!p.summary;
4574
- const effectiveLimit = typeof p.limit === "number" ? p.limit : 100;
4575
- const groups = [...groupsByPath.values()];
4576
- for (const group of groups) {
4577
- group.entries = dedupeContextEntries(group.entries);
4578
- }
4579
- let renderedGroups = totalMatches > GREP_TRUNCATION_THRESHOLD ? groups.map(truncateGroupEntries) : groups;
4580
- let scopeWarnings = [];
4581
- if (p.scope === "symbol" && !summary) {
4582
- const fileLinesByPath = new Map;
4583
- const fileMapsByPath = new Map;
4584
- for (const group of renderedGroups) {
4585
- const lines = await getFileLines(group.absolutePath);
4586
- if (lines)
4587
- fileLinesByPath.set(group.absolutePath, lines);
4588
- fileMapsByPath.set(group.absolutePath, await getOrGenerateMap(group.absolutePath));
4589
- }
4590
- const scoped = scopeGrepGroupsToSymbols({
4591
- groups: renderedGroups,
4592
- fileLinesByPath,
4593
- fileMapsByPath,
4594
- contextLines: typeof p.context === "number" ? p.context : 0,
4595
- scopeContext: typeof p.scopeContext === "number" ? p.scopeContext : undefined
4596
- });
4597
- renderedGroups = scoped.groups;
4598
- scopeWarnings = scoped.warnings;
4599
- }
4600
- const readSeekRecords = renderedGroups.flatMap((group) => group.entries.flatMap((entry) => entry.kind === "separator" ? [] : [{ path: group.absolutePath, ...entry.line, kind: entry.kind }]));
4601
- const builtOutput = buildGrepOutput({
4602
- summary: !!summary,
4603
- totalMatches,
4604
- groups: renderedGroups,
4605
- limit: effectiveLimit,
4606
- records: readSeekRecords,
4607
- scopeMode: p.scope === "symbol" && !summary ? "symbol" : undefined,
4608
- scopeWarnings,
4609
- passthroughLines
4282
+ summary: rawParams.summary,
4283
+ scope: rawParams.scope,
4284
+ scopeContext: scopeContext.value,
4285
+ gitScope: rawParams.gitScope,
4286
+ signal
4610
4287
  });
4611
- if (!summary && readSeekRecords.length > 0) {
4612
- const anchoredPaths = new Set(readSeekRecords.map((record) => record.path));
4613
- for (const absolutePath of anchoredPaths) {
4614
- onFileAnchored?.(absolutePath);
4615
- }
4288
+ signal?.throwIfAborted();
4289
+ const builtOutput = buildGrepOutput(output, cwd);
4290
+ for (const absolutePath of new Set(builtOutput.readSeekValue.records.map((record) => record.path))) {
4291
+ onFileAnchored?.(absolutePath);
4616
4292
  }
4617
- const existingDetails = typeof result.details === "object" && result.details !== null ? result.details : {};
4618
- const { linesTruncated: _ignoredLinesTruncated, truncation: _ignoredTruncation, ...compactDetails } = existingDetails;
4619
4293
  return {
4620
- ...result,
4621
- content: result.content.map((item) => item === textBlock ? { ...item, text: builtOutput.text } : item),
4622
- details: {
4623
- ...compactDetails,
4624
- readSeekValue: builtOutput.readSeekValue
4625
- }
4294
+ content: [{ type: "text", text: builtOutput.text }],
4295
+ details: { readSeekValue: builtOutput.readSeekValue }
4626
4296
  };
4627
4297
  }
4628
4298
  function registerGrepTool(pi, options = {}) {
4629
- const name = options.name ?? "readSeek_find_text";
4299
+ const name = options.name ?? "readSeek_grep";
4630
4300
  const promptMetadata = defineToolPromptMetadata({
4631
- toolName: "find_text",
4301
+ toolName: "grep",
4632
4302
  promptSnippet: "Search text or regex with edit-ready LINE:HASH anchors",
4633
4303
  registeredName: name
4634
4304
  });
@@ -4677,22 +4347,16 @@ function registerGrepTool(pi, options = {}) {
4677
4347
  if (isError || result.isError)
4678
4348
  return renderErrorResult(textContent, { expanded, width, theme });
4679
4349
  const readSeekValue = result.details?.readSeekValue;
4680
- const hasBinaryWarning = textContent.includes("appears to be a binary file");
4681
- const fileSet = new Set;
4682
- for (const r of readSeekValue?.records ?? []) {
4683
- if (r.path)
4684
- fileSet.add(r.path);
4685
- }
4686
4350
  const info = formatGrepResultText({
4687
4351
  totalMatches: readSeekValue?.totalMatches ?? 0,
4688
4352
  summary: readSeekValue?.summary ?? false,
4689
4353
  records: readSeekValue?.records ?? [],
4690
- fileCount: fileSet.size,
4691
- hasBinaryWarning
4354
+ fileCount: readSeekValue?.fileCount ?? 0,
4355
+ truncated: readSeekValue?.truncated
4692
4356
  });
4693
- if (info.noMatches && !hasBinaryWarning)
4357
+ if (info.noMatches)
4694
4358
  return new Text4(summaryLine("no matches", { theme, style: "dim" }), 0, 0);
4695
- const matchCount = readSeekValue?.totalMatches ?? 0;
4359
+ const matchCount = readSeekValue?.returnedMatches ?? 0;
4696
4360
  const matchWord = matchCount === 1 ? "match" : "matches";
4697
4361
  let text = summaryLine(`${matchCount} ${matchWord} returned`, {
4698
4362
  hidden: !!textContent && !expanded,
@@ -4719,10 +4383,10 @@ import path6 from "node:path";
4719
4383
  import { Type as Type6 } from "@sinclair/typebox";
4720
4384
 
4721
4385
  // src/stat-search-path.ts
4722
- import { stat as fsStat2 } from "node:fs/promises";
4386
+ import { stat as fsStat } from "node:fs/promises";
4723
4387
  async function statSearchPathOrError(tool, rawPath, searchPath) {
4724
4388
  try {
4725
- return { ok: true, stats: await fsStat2(searchPath) };
4389
+ return { ok: true, stats: await fsStat(searchPath) };
4726
4390
  } catch (err) {
4727
4391
  const path = rawPath ?? searchPath;
4728
4392
  const { code, message } = formatFsError(err, "stat-error");
@@ -4774,7 +4438,7 @@ function mergeRanges(ranges) {
4774
4438
  return merged;
4775
4439
  }
4776
4440
  var SEARCH_PROMPT_METADATA = defineToolPromptMetadata({
4777
- toolName: "find_nodes",
4441
+ toolName: "search",
4778
4442
  promptSnippet: "Search code by AST structure using ast-grep-style patterns"
4779
4443
  });
4780
4444
  function linesFromSearchResult(result, ranges) {
@@ -4814,7 +4478,7 @@ async function executeSearch(opts) {
4814
4478
  const searchPathIsFile = statResult.stats.isFile();
4815
4479
  try {
4816
4480
  const effectiveLang = readSeekLanguageForPath(p.language, searchPath, searchPathIsFile);
4817
- const results = await readSeekFindNodes(searchPath, {
4481
+ const results = await readSeekSearch(searchPath, {
4818
4482
  pattern: p.pattern,
4819
4483
  context: p.context,
4820
4484
  selector: p.selector,
@@ -4878,7 +4542,7 @@ async function executeSearch(opts) {
4878
4542
  }
4879
4543
  function registerSearchTool(pi, options = {}) {
4880
4544
  const tool = registerReadSeekTool(pi, {
4881
- name: "readSeek_find_nodes",
4545
+ name: "readSeek_search",
4882
4546
  label: "Structural Search",
4883
4547
  description: SEARCH_PROMPT_METADATA.description,
4884
4548
  promptSnippet: SEARCH_PROMPT_METADATA.promptSnippet,
@@ -4898,14 +4562,14 @@ function registerSearchTool(pi, options = {}) {
4898
4562
  },
4899
4563
  renderCall(args, theme, ...rest) {
4900
4564
  return renderReadSeekSearchCall(args, theme, rest, {
4901
- label: "readSeek_find_nodes",
4565
+ label: "readSeek_search",
4902
4566
  accent: `/${args.pattern ?? args.context ?? args.rule}/`,
4903
4567
  flags: [args.gitScope && `git-scope=${args.gitScope}`]
4904
4568
  });
4905
4569
  },
4906
4570
  renderResult(result, options, theme, ...rest) {
4907
4571
  return renderAnchoredFilesResult(result, options, theme, rest, {
4908
- pendingLabel: "pending readSeek_find_nodes",
4572
+ pendingLabel: "pending readSeek_search",
4909
4573
  emptyLabel: "no matches",
4910
4574
  unitSingular: "match",
4911
4575
  unitPlural: "matches"
@@ -4948,7 +4612,7 @@ function buildRefsOutput(input) {
4948
4612
 
4949
4613
  // src/refs.ts
4950
4614
  var REFS_PROMPT_METADATA = defineToolPromptMetadata({
4951
- toolName: "find_references",
4615
+ toolName: "refs",
4952
4616
  promptSnippet: "Find references by identifier name or cursor binding"
4953
4617
  });
4954
4618
  function refsLine(reference) {
@@ -4990,7 +4654,7 @@ async function executeRefs(opts) {
4990
4654
  if (!statResult.ok)
4991
4655
  return statResult.error;
4992
4656
  try {
4993
- const references = await readSeekFindReferences(searchPath, {
4657
+ const references = await readSeekRefs(searchPath, {
4994
4658
  name: p.name,
4995
4659
  at: p.at,
4996
4660
  language: p.language,
@@ -5020,7 +4684,7 @@ async function executeRefs(opts) {
5020
4684
  }
5021
4685
  function registerRefsTool(pi, options = {}) {
5022
4686
  const tool = registerReadSeekTool(pi, {
5023
- name: "readSeek_find_references",
4687
+ name: "readSeek_refs",
5024
4688
  label: "References",
5025
4689
  description: REFS_PROMPT_METADATA.description,
5026
4690
  promptSnippet: REFS_PROMPT_METADATA.promptSnippet,
@@ -5038,14 +4702,14 @@ function registerRefsTool(pi, options = {}) {
5038
4702
  renderCall(args, theme, ...rest) {
5039
4703
  const accent = args.name ?? args.at ?? "?";
5040
4704
  return renderReadSeekSearchCall(args, theme, rest, {
5041
- label: "readSeek_find_references",
4705
+ label: "readSeek_refs",
5042
4706
  accent,
5043
4707
  flags: [args.at && "binding", args.gitScope && `git-scope=${args.gitScope}`]
5044
4708
  });
5045
4709
  },
5046
4710
  renderResult(result, options, theme, ...rest) {
5047
4711
  return renderAnchoredFilesResult(result, options, theme, rest, {
5048
- pendingLabel: "pending readSeek_find_references",
4712
+ pendingLabel: "pending readSeek_refs",
5049
4713
  emptyLabel: "no references",
5050
4714
  unitSingular: "reference",
5051
4715
  unitPlural: "references"
@@ -5060,7 +4724,7 @@ import { Type as Type8 } from "@sinclair/typebox";
5060
4724
  import path8 from "node:path";
5061
4725
  import { Text as Text5 } from "@earendil-works/pi-tui";
5062
4726
  var RENAME_PROMPT_METADATA = defineToolPromptMetadata({
5063
- toolName: "rename_symbol",
4727
+ toolName: "rename",
5064
4728
  promptSnippet: "Rename a symbol at a source location with verified edits"
5065
4729
  });
5066
4730
  var renameSchema = Type8.Object({
@@ -5076,12 +4740,12 @@ async function executeRename(opts) {
5076
4740
  if (!p.to.trim()) {
5077
4741
  return buildToolErrorResult("rename", "invalid-parameter", "rename parameter 'to' must not be empty");
5078
4742
  }
5079
- if (typeof p.at !== "string" || !/^(line:[1-9][0-9]*|hash:[0-9a-fA-F]+)(:[1-9][0-9]*)?$/.test(p.at)) {
5080
- return buildToolErrorResult("rename_symbol", "invalid-parameter", "at must be a line or hash location with an optional positive column");
4743
+ if (typeof p.at !== "string" || !/^(line:[1-9][0-9]*|hash:[0-9a-fA-F]+|[1-9][0-9]*|[0-9a-fA-F]{6})(:[1-9][0-9]*)?$/.test(p.at)) {
4744
+ return buildToolErrorResult("rename", "invalid-parameter", "at must be a line or hash location with an optional positive column");
5081
4745
  }
5082
4746
  const filePath = resolveToCwd(p.path, cwd);
5083
4747
  try {
5084
- const output = await readSeekRenameSymbol(filePath, {
4748
+ const output = await readSeekRename(filePath, {
5085
4749
  to: p.to,
5086
4750
  at: p.at,
5087
4751
  workspace: p.workspace ? cwd : undefined,
@@ -5138,7 +4802,7 @@ First conflict at ${firstConflict.line}:${firstConflict.column}: ${firstConflict
5138
4802
  }
5139
4803
  function registerRenameTool(pi, options = {}) {
5140
4804
  registerReadSeekTool(pi, {
5141
- name: "readSeek_rename_symbol",
4805
+ name: "readSeek_rename",
5142
4806
  label: "Rename",
5143
4807
  description: RENAME_PROMPT_METADATA.description,
5144
4808
  promptSnippet: RENAME_PROMPT_METADATA.promptSnippet,
@@ -5151,7 +4815,7 @@ function registerRenameTool(pi, options = {}) {
5151
4815
  const context = rest[0] ?? {};
5152
4816
  const cwd = context.cwd ?? process.cwd();
5153
4817
  const displayPath = typeof args?.path === "string" ? args.path : "?";
5154
- let text = theme.fg("toolTitle", theme.bold("readSeek_rename_symbol"));
4818
+ let text = theme.fg("toolTitle", theme.bold("readSeek_rename"));
5155
4819
  text += ` ${linkToolPath(theme.fg("accent", displayPath), displayPath, cwd)}`;
5156
4820
  if (args?.to)
5157
4821
  text += theme.fg("dim", ` → ${args.to}`);
@@ -5160,13 +4824,13 @@ function registerRenameTool(pi, options = {}) {
5160
4824
  renderResult(result, options, theme, ...rest) {
5161
4825
  const { isPartial, isError, expanded, cwd, width } = resolveRenderResultContext(options, rest);
5162
4826
  if (isPartial)
5163
- return renderPendingResult("pending readSeek_rename_symbol", width, theme);
4827
+ return renderPendingResult("pending readSeek_rename", width, theme);
5164
4828
  const content = result.content?.[0];
5165
4829
  const textContent = content?.type === "text" ? content.text : "";
5166
4830
  const readSeekValue = result.details?.readSeekValue;
5167
4831
  const output = readSeekValue?.output;
5168
4832
  if (isError || result.isError) {
5169
- return renderErrorResult(textContent, { expanded, width, fallback: "readSeek_rename_symbol failed", theme });
4833
+ return renderErrorResult(textContent, { expanded, width, fallback: "readSeek_rename failed", theme });
5170
4834
  }
5171
4835
  const outputs = output ? [output, ...output.others] : [];
5172
4836
  const editCount = outputs.reduce((total, file) => total + file.edits.length, 0);
@@ -5201,6 +4865,18 @@ import { withFileMutationQueue as withFileMutationQueue2, truncateHead as trunca
5201
4865
  import { Text as Text6 } from "@earendil-works/pi-tui";
5202
4866
  import { Type as Type9 } from "@sinclair/typebox";
5203
4867
 
4868
+ // src/binary-detect.ts
4869
+ function looksLikeBinary(buf) {
4870
+ if (buf.length === 0)
4871
+ return false;
4872
+ if (buf.includes(0))
4873
+ return true;
4874
+ const decoded = buf.toString("utf8");
4875
+ if (!decoded.includes("�"))
4876
+ return false;
4877
+ return !Buffer.from(decoded, "utf8").equals(buf);
4878
+ }
4879
+
5204
4880
  // src/pending-diff-preview.ts
5205
4881
  import { existsSync as existsSync3, readFileSync as readFileSync2, realpathSync, statSync as statSync4 } from "node:fs";
5206
4882
  import { dirname, isAbsolute, relative as relative2, resolve, sep } from "node:path";
@@ -5404,7 +5080,7 @@ async function executeWrite(opts) {
5404
5080
  const warnings = [];
5405
5081
  const readSeekWarnings = [];
5406
5082
  if (hasBareCarriageReturn(content)) {
5407
- const message = "File content contains bare CR (\\r) line endings; readSeek_write_file refuses to emit anchors that readSeek_show_content/readSeek_edit_text would normalize differently.";
5083
+ const message = "File content contains bare CR (\\r) line endings; readSeek_write refuses to emit anchors that readSeek_show/readSeek_edit would normalize differently.";
5408
5084
  warnings.push(message);
5409
5085
  readSeekWarnings.push(buildReadSeekWarning("bare-cr", message));
5410
5086
  return buildToolErrorResult("write", "bare-cr", `Cannot write ${filePath}
@@ -5483,9 +5159,9 @@ async function executeWrite(opts) {
5483
5159
  };
5484
5160
  }
5485
5161
  function registerWriteTool(pi, options = {}) {
5486
- const name = options.name ?? "readSeek_write_file";
5162
+ const name = options.name ?? "readSeek_write";
5487
5163
  const promptMetadata = defineToolPromptMetadata({
5488
- toolName: "write_file",
5164
+ toolName: "write",
5489
5165
  promptSnippet: "Create or replace a complete file with LINE:HASH anchors",
5490
5166
  registeredName: name
5491
5167
  });
@@ -5599,21 +5275,21 @@ function registerWriteTool(pi, options = {}) {
5599
5275
  import { Type as Type10 } from "@sinclair/typebox";
5600
5276
  import path9 from "node:path";
5601
5277
  var DEF_PROMPT_METADATA = defineToolPromptMetadata({
5602
- toolName: "find_definitions",
5278
+ toolName: "def",
5603
5279
  promptSnippet: "Go to a symbol definition or declaration by name"
5604
5280
  });
5605
5281
  async function executeDef(opts) {
5606
5282
  const { params, signal, cwd, onFileAnchored } = opts;
5607
5283
  const p = params;
5608
5284
  if (!p.name || !p.name.trim()) {
5609
- return buildToolErrorResult("def", "invalid-parameter", "readSeek_find_definitions requires 'name'");
5285
+ return buildToolErrorResult("def", "invalid-parameter", "readSeek_def requires 'name'");
5610
5286
  }
5611
5287
  const searchPath = resolveToCwd(p.path ?? ".", cwd);
5612
5288
  const statResult = await statSearchPathOrError("def", p.path, searchPath);
5613
5289
  if (!statResult.ok)
5614
5290
  return statResult.error;
5615
5291
  try {
5616
- const definitions = await readSeekFindDefinitions(searchPath, {
5292
+ const definitions = await readSeekDef(searchPath, {
5617
5293
  name: p.name,
5618
5294
  language: p.language,
5619
5295
  gitScope: p.gitScope,
@@ -5662,7 +5338,7 @@ async function executeDef(opts) {
5662
5338
  }
5663
5339
  function registerDefTool(pi, options = {}) {
5664
5340
  registerReadSeekTool(pi, {
5665
- name: "readSeek_find_definitions",
5341
+ name: "readSeek_def",
5666
5342
  label: "Definition",
5667
5343
  description: DEF_PROMPT_METADATA.description,
5668
5344
  promptSnippet: DEF_PROMPT_METADATA.promptSnippet,
@@ -5678,14 +5354,14 @@ function registerDefTool(pi, options = {}) {
5678
5354
  },
5679
5355
  renderCall(args, theme, ...rest) {
5680
5356
  return renderReadSeekSearchCall(args, theme, rest, {
5681
- label: "readSeek_find_definitions",
5357
+ label: "readSeek_def",
5682
5358
  accent: args.name,
5683
5359
  flags: [args.gitScope && `git-scope=${args.gitScope}`]
5684
5360
  });
5685
5361
  },
5686
5362
  renderResult(result, options, theme, ...rest) {
5687
5363
  return renderAnchoredFilesResult(result, options, theme, rest, {
5688
- pendingLabel: "pending readSeek_find_definitions",
5364
+ pendingLabel: "pending readSeek_def",
5689
5365
  emptyLabel: "no definitions",
5690
5366
  unitSingular: "definition",
5691
5367
  unitPlural: "definitions"
@@ -5699,32 +5375,32 @@ import { Text as Text7, wrapTextWithAnsi as wrapTextWithAnsi2 } from "@earendil-
5699
5375
  import { Type as Type11 } from "@sinclair/typebox";
5700
5376
  import { readFile as readFile2 } from "node:fs/promises";
5701
5377
  var operations = {
5702
- show_content: readSeekShowContent,
5703
- show_structure: readSeekShowStructure,
5704
- show_metadata: readSeekShowMetadata,
5705
- show_assets: readSeekShowAssets,
5706
- check_syntax: readSeekCheckSyntax,
5707
- check_rules: readSeekCheckRules,
5708
- generate_caption: readSeekGenerateCaption,
5709
- detect_objects: readSeekDetectObjects,
5710
- recognize_text: readSeekRecognizeText
5378
+ show: readSeekShow,
5379
+ tree: readSeekTree,
5380
+ meta: readSeekMeta,
5381
+ assets: readSeekAssets,
5382
+ syntax: readSeekSyntax,
5383
+ check: readSeekCheck,
5384
+ caption: readSeekCaption,
5385
+ objects: readSeekObjects,
5386
+ ocr: readSeekOcr
5711
5387
  };
5712
5388
  function resolveNonShowSummary(tool, envelope) {
5713
- if (tool === "check_syntax") {
5389
+ if (tool === "syntax") {
5714
5390
  const errorCount = envelope?.diagnostics?.error_count ?? 0;
5715
5391
  return errorCount === 0 ? "clean" : `${errorCount} syntax error${errorCount === 1 ? "" : "s"}`;
5716
5392
  }
5717
- if (tool === "check_rules") {
5393
+ if (tool === "check") {
5718
5394
  const violations = Array.isArray(envelope?.violations) ? envelope.violations.length : 0;
5719
5395
  return violations === 0 ? "clean" : `${violations} rule violation${violations === 1 ? "" : "s"}`;
5720
5396
  }
5721
- if (tool === "detect_objects") {
5397
+ if (tool === "objects") {
5722
5398
  const objects = Array.isArray(envelope?.objects) ? envelope.objects.length : 0;
5723
5399
  return `${objects} object${objects === 1 ? "" : "s"}`;
5724
5400
  }
5725
- if (tool === "generate_caption")
5401
+ if (tool === "caption")
5726
5402
  return "caption generated";
5727
- if (tool === "recognize_text")
5403
+ if (tool === "ocr")
5728
5404
  return "text recognized";
5729
5405
  return "complete";
5730
5406
  }
@@ -5799,7 +5475,7 @@ async function executeAccess(opts) {
5799
5475
  if (key === "stdinName" || !Object.hasOwn(contract.inputSchema.properties, key))
5800
5476
  return buildToolErrorResult(opts.tool, "invalid-parameter", `Unknown parameter: ${key}`);
5801
5477
  }
5802
- const path = p.path === undefined && opts.tool === "check_rules" ? "." : p.path;
5478
+ const path = p.path === undefined && opts.tool === "check" ? "." : p.path;
5803
5479
  if (typeof path !== "string" || !path.trim())
5804
5480
  return buildToolErrorResult(opts.tool, "invalid-parameter", "path is required");
5805
5481
  if (path === "-")
@@ -5814,7 +5490,7 @@ async function executeAccess(opts) {
5814
5490
  const content = [
5815
5491
  { type: "text", text: presentation ? formatShowResult(presentation) : JSON.stringify(envelope) }
5816
5492
  ];
5817
- if (opts.tool === "show_content" || opts.tool === "show_assets") {
5493
+ if (opts.tool === "show" || opts.tool === "assets") {
5818
5494
  for (const [imagePath, mimeType] of imagePaths(envelope)) {
5819
5495
  if (opts.imageMode === "off")
5820
5496
  content.push({ type: "text", text: `Image available at ${imagePath}; image attachments are disabled by readseek.imageMode.` });
@@ -5832,7 +5508,7 @@ async function executeAccess(opts) {
5832
5508
  }
5833
5509
  function registerAccessTools(pi, options = {}) {
5834
5510
  for (const name of Object.keys(operations)) {
5835
- const registeredName = name === "show_content" ? options.name ?? `readSeek_${name}` : `readSeek_${name}`;
5511
+ const registeredName = name === "show" ? options.name ?? `readSeek_${name}` : `readSeek_${name}`;
5836
5512
  const contract = getToolContract(name);
5837
5513
  const metadata = defineToolPromptMetadata({
5838
5514
  toolName: name,
@@ -5919,37 +5595,37 @@ class SessionAnchors {
5919
5595
 
5920
5596
  // index.ts
5921
5597
  var READSEEK_TOOL_ENTRIES = [
5922
- { builtIn: "edit", readSeekName: "readSeek_edit_text" },
5923
- { builtIn: "grep", readSeekName: "readSeek_find_text" },
5924
- { builtIn: null, readSeekName: "readSeek_find_nodes" },
5925
- { builtIn: null, readSeekName: "readSeek_find_references" },
5926
- { builtIn: null, readSeekName: "readSeek_rename_symbol" },
5927
- { builtIn: "write", readSeekName: "readSeek_write_file" },
5928
- { builtIn: null, readSeekName: "readSeek_find_definitions" },
5929
- { builtIn: "read", readSeekName: "readSeek_show_content" },
5930
- { builtIn: null, readSeekName: "readSeek_show_structure" },
5931
- { builtIn: null, readSeekName: "readSeek_show_metadata" },
5932
- { builtIn: null, readSeekName: "readSeek_show_assets" },
5933
- { builtIn: null, readSeekName: "readSeek_check_syntax" },
5934
- { builtIn: null, readSeekName: "readSeek_check_rules" },
5935
- { builtIn: null, readSeekName: "readSeek_generate_caption" },
5936
- { builtIn: null, readSeekName: "readSeek_detect_objects" },
5937
- { builtIn: null, readSeekName: "readSeek_recognize_text" }
5598
+ { builtIn: "edit", readSeekName: "readSeek_edit" },
5599
+ { builtIn: "grep", readSeekName: "readSeek_grep" },
5600
+ { builtIn: null, readSeekName: "readSeek_search" },
5601
+ { builtIn: null, readSeekName: "readSeek_refs" },
5602
+ { builtIn: null, readSeekName: "readSeek_rename" },
5603
+ { builtIn: "write", readSeekName: "readSeek_write" },
5604
+ { builtIn: null, readSeekName: "readSeek_def" },
5605
+ { builtIn: "read", readSeekName: "readSeek_show" },
5606
+ { builtIn: null, readSeekName: "readSeek_tree" },
5607
+ { builtIn: null, readSeekName: "readSeek_meta" },
5608
+ { builtIn: null, readSeekName: "readSeek_assets" },
5609
+ { builtIn: null, readSeekName: "readSeek_syntax" },
5610
+ { builtIn: null, readSeekName: "readSeek_check" },
5611
+ { builtIn: null, readSeekName: "readSeek_caption" },
5612
+ { builtIn: null, readSeekName: "readSeek_objects" },
5613
+ { builtIn: null, readSeekName: "readSeek_ocr" }
5938
5614
  ];
5939
5615
  function piReadSeekExtension(pi) {
5940
5616
  const sessionAnchors = new SessionAnchors;
5941
5617
  const { settings } = resolveReadSeekJsonSettings();
5942
5618
  const replacedBuiltIns = new Set(settings.overrideTools ?? []);
5943
5619
  const swap = (builtIn, readSeekName) => replacedBuiltIns.has(builtIn) ? builtIn : readSeekName;
5944
- const readName = swap("read", "readSeek_show_content");
5945
- const editName = swap("edit", "readSeek_edit_text");
5946
- const writeName = swap("write", "readSeek_write_file");
5947
- const grepName = swap("grep", "readSeek_find_text");
5620
+ const readName = swap("read", "readSeek_show");
5621
+ const editName = swap("edit", "readSeek_edit");
5622
+ const writeName = swap("write", "readSeek_write");
5623
+ const grepName = swap("grep", "readSeek_grep");
5948
5624
  const toolAliases = {
5949
- readSeek_show_content: readName,
5950
- readSeek_edit_text: editName,
5951
- readSeek_find_text: grepName,
5952
- readSeek_write_file: writeName
5625
+ readSeek_show: readName,
5626
+ readSeek_edit: editName,
5627
+ readSeek_grep: grepName,
5628
+ readSeek_write: writeName
5953
5629
  };
5954
5630
  const policyAliases = Object.fromEntries(TOOL_NAMES.map((name) => {
5955
5631
  const readSeekName = `readSeek_${name}`;