@jarkkojs/readseek-api 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.js CHANGED
@@ -9,22 +9,22 @@ var EDIT_VARIANTS = [
9
9
  ];
10
10
  var GIT_SCOPES = ["tracked", "untracked", "ignored", "all"];
11
11
  var TOOL_NAMES = [
12
- "show_content",
13
- "show_structure",
14
- "show_metadata",
15
- "show_assets",
16
- "check_syntax",
17
- "check_rules",
18
- "generate_caption",
19
- "detect_objects",
20
- "recognize_text",
21
- "find_nodes",
22
- "find_text",
23
- "find_definitions",
24
- "find_references",
25
- "edit_text",
26
- "rename_symbol",
27
- "write_file"
12
+ "show",
13
+ "tree",
14
+ "meta",
15
+ "assets",
16
+ "syntax",
17
+ "check",
18
+ "caption",
19
+ "objects",
20
+ "ocr",
21
+ "search",
22
+ "grep",
23
+ "def",
24
+ "refs",
25
+ "edit",
26
+ "rename",
27
+ "write"
28
28
  ];
29
29
  var PARAM_DESCRIPTIONS = {
30
30
  path: "Absolute or relative file path",
@@ -36,12 +36,12 @@ var PARAM_DESCRIPTIONS = {
36
36
  pattern: "AST pattern (ast-grep style), such as console.log($$$ARGS)",
37
37
  patternContext: "Surrounding code context for sub-statement patterns",
38
38
  selector: "Target AST node kind to extract from pattern context",
39
- grepPattern: "Regex pattern; set literal for exact text",
39
+ grepPattern: "Native regex pattern; set literal for exact text. Empty regex matches every line; empty literal is invalid.",
40
40
  name: "Qualified or unqualified symbol name",
41
41
  refsName: "Identifier to find references for",
42
42
  gitScope: "In a Git repository, which files to search: tracked, untracked, ignored (untracked plus ignored), or all. Defaults to tracked plus untracked.",
43
- at: "Location selector: line:N[:COLUMN], hash:H[:COLUMN], symbol:NAME, page:N, node:ID, or asset:ID",
44
- assetAt: "Document selector: page:N, node:ID, or asset:ID",
43
+ 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",
44
+ assetAt: "Document selector: N, page:N, node:ID, or asset:ID",
45
45
  rule: "KDL node selection rule, @path, or '-' for standard input",
46
46
  rules: "KDL structural checks, @path, or '-' for standard input",
47
47
  stdinName: "File name hint when the target is '-'",
@@ -53,7 +53,6 @@ var PARAM_DESCRIPTIONS = {
53
53
  kind: "Restrict structure to this node kind",
54
54
  outline: "Show outline of document hierarchy or source symbols",
55
55
  noAnchors: "Omit source line anchors",
56
- text: "Literal native text query; adapter find_text also supports regex via pattern",
57
56
  edits: "Use set_line, replace_lines, insert_after, replace_symbol, or replace",
58
57
  editVariant: "Exactly one nested edit variant",
59
58
  setLine: 'Replace one line: {"set_line":{"anchor":"LINE:HASH","new_text":"..."}}',
@@ -85,27 +84,28 @@ var PARAM_DESCRIPTIONS = {
85
84
  literal: "Treat pattern literally",
86
85
  context: "Surrounding lines for each match",
87
86
  matchLimit: "Maximum matches to return",
87
+ grepLimit: "Maximum matching lines to return; positive integer, defaults to 100",
88
88
  summary: "Return per-file counts",
89
89
  grepScope: "Group matches by enclosing symbol",
90
- scopeContext: "Context lines within each symbol"
90
+ scopeContext: "Non-negative context lines within each symbol; requires scope: symbol"
91
91
  };
92
92
  var TOOL_DESCRIPTIONS = {
93
- 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.",
94
- show_structure: "Show parsed source or document hierarchy. Use shallow depth for file overviews.",
95
- show_metadata: "Show format, source identity, language, dimensions, or page count without decoding image content or inference.",
96
- show_assets: "Return selected document asset descriptors with usable cached paths. Adapters attach images from those paths.",
97
- check_syntax: "Check parser syntax diagnostics. Exit 0 is clean, 1 has findings, 2 is failure; this is not type checking.",
98
- check_rules: "Evaluate KDL structural assertions. Exit 0 is clean, 1 has findings, 2 is failure.",
99
- generate_caption: "Explicitly infer a caption using an installed model. Include text and objects to request OCR and detection too; never downloads models automatically.",
100
- detect_objects: "Explicitly infer object labels and bounding boxes using an installed model.",
101
- recognize_text: "Explicitly recognize text from pixels using an installed model; reading never silently invokes OCR.",
102
- find_nodes: "Find AST nodes by pattern or KDL selection rule. Returns edit-ready anchors. Use `find_text` for text and regex.",
103
- 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.",
104
- find_definitions: "Find a symbol definition or declaration by qualified or unqualified name.",
105
- find_references: "Find identifier-name matches with name, or binding-scoped references with at. Use before renaming or deleting a symbol.",
106
- 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.",
107
- rename_symbol: "Rename a symbol at a line or hash cursor with binding-aware edits and stale-plan protection.",
108
- write_file: "Create or replace a complete text file and return `LINE:HASH` anchors. Use `edit_text` for small changes."
93
+ 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.",
94
+ tree: "Show parsed source or document hierarchy. Use shallow depth for file overviews.",
95
+ meta: "Show format, source identity, language, dimensions, or page count without decoding image content or inference.",
96
+ assets: "Return selected document asset descriptors with usable cached paths. Adapters attach images from those paths.",
97
+ syntax: "Check parser syntax diagnostics. Exit 0 is clean, 1 has findings, 2 is failure; this is not type checking.",
98
+ check: "Evaluate KDL structural assertions. Exit 0 is clean, 1 has findings, 2 is failure.",
99
+ caption: "Explicitly infer a caption using an installed model. Include text and objects to request OCR and detection too; never downloads models automatically.",
100
+ objects: "Explicitly infer object labels and bounding boxes using an installed model.",
101
+ ocr: "Explicitly recognize text from pixels using an installed model; reading never silently invokes OCR.",
102
+ search: "Find AST nodes by pattern or KDL selection rule. Returns edit-ready anchors. Use `grep` for text and regex.",
103
+ 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.",
104
+ def: "Find a symbol definition or declaration by qualified or unqualified name.",
105
+ refs: "Find identifier-name matches with name, or binding-scoped references with at. Use before renaming or deleting a symbol.",
106
+ 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.",
107
+ rename: "Rename a symbol at a line or hash cursor with binding-aware edits and stale-plan protection.",
108
+ write: "Create or replace a complete text file and return `LINE:HASH` anchors. Use `edit` for small changes."
109
109
  };
110
110
  function rewriteBacktickedToolNames(text, aliases = {}) {
111
111
  return TOOL_NAMES.reduce((rewritten, toolName) => rewritten.replaceAll(`\`${toolName}\``, `\`${aliases[toolName] ?? toolName}\``), text);
@@ -198,22 +198,22 @@ function tool(name, title, properties, required) {
198
198
  return { name, title, description: TOOL_DESCRIPTIONS[name], inputSchema: { type: "object", properties, required, additionalProperties: false } };
199
199
  }
200
200
  var TOOL_CONTRACTS = [
201
- tool("show_content", "Show content", { ...path, ...at, ...lang, ...stdin, ...noAnchors, end: integer(PARAM_DESCRIPTIONS.end), limit: integer(PARAM_DESCRIPTIONS.limit) }, ["path"]),
202
- 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"]),
203
- tool("show_metadata", "Show metadata", { ...path, ...lang, ...stdin }, ["path"]),
204
- tool("show_assets", "Show assets", { ...path, at: string(PARAM_DESCRIPTIONS.assetAt), ...stdin }, ["path"]),
205
- tool("check_syntax", "Check syntax", { ...path, ...lang, ...stdin }, ["path"]),
206
- tool("check_rules", "Check rules", { path: string(PARAM_DESCRIPTIONS.searchPath), ...gitScopeSchema, rules: string(PARAM_DESCRIPTIONS.rules) }, ["rules"]),
207
- 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"]),
208
- tool("detect_objects", "Detect objects", { ...path, ...at, ...stdin, ...level }, ["path"]),
209
- tool("recognize_text", "Recognize text", { ...path, ...at, ...stdin, ...level }, ["path"]),
210
- 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) }, []),
211
- 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) }, []),
212
- tool("find_definitions", "Find definitions", { ...path, ...lang, ...gitScopeSchema, ...noAnchors, name: string(PARAM_DESCRIPTIONS.name) }, ["name"]),
213
- tool("find_references", "Find references", { ...path, ...at, ...lang, ...gitScopeSchema, ...noAnchors, name: string(PARAM_DESCRIPTIONS.refsName) }, []),
214
- 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"]),
215
- 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"]),
216
- tool("write_file", "Write file", { ...path, content: string(PARAM_DESCRIPTIONS.content) }, ["path", "content"])
201
+ tool("show", "Show content", { ...path, ...at, ...lang, ...stdin, ...noAnchors, end: integer(PARAM_DESCRIPTIONS.end), limit: integer(PARAM_DESCRIPTIONS.limit) }, ["path"]),
202
+ 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"]),
203
+ tool("meta", "Show metadata", { ...path, ...lang, ...stdin }, ["path"]),
204
+ tool("assets", "Show assets", { ...path, at: string(PARAM_DESCRIPTIONS.assetAt), ...stdin }, ["path"]),
205
+ tool("syntax", "Check syntax", { ...path, ...lang, ...stdin }, ["path"]),
206
+ tool("check", "Check rules", { path: string(PARAM_DESCRIPTIONS.searchPath), ...gitScopeSchema, rules: string(PARAM_DESCRIPTIONS.rules) }, ["rules"]),
207
+ 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"]),
208
+ tool("objects", "Detect objects", { ...path, ...at, ...stdin, ...level }, ["path"]),
209
+ tool("ocr", "Recognize text", { ...path, ...at, ...stdin, ...level }, ["path"]),
210
+ 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) }, []),
211
+ 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"]),
212
+ tool("def", "Find definitions", { ...path, ...lang, ...gitScopeSchema, ...noAnchors, name: string(PARAM_DESCRIPTIONS.name) }, ["name"]),
213
+ tool("refs", "Find references", { ...path, ...at, ...lang, ...gitScopeSchema, ...noAnchors, name: string(PARAM_DESCRIPTIONS.refsName) }, []),
214
+ 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"]),
215
+ tool("rename", "Rename symbol", { ...path, ...at, ...lang, ...gitScopeSchema, ...apply, to: string(PARAM_DESCRIPTIONS.to), workspace: { type: "boolean", description: PARAM_DESCRIPTIONS.workspace } }, ["path", "at", "to"]),
216
+ tool("write", "Write file", { ...path, content: string(PARAM_DESCRIPTIONS.content) }, ["path", "content"])
217
217
  ];
218
218
  function getToolContract(name) {
219
219
  const contract = TOOL_CONTRACTS.find((entry) => entry.name === name);
@@ -224,70 +224,70 @@ function getToolContract(name) {
224
224
  // src/policy.ts
225
225
  var REPLACEABLE_TOOLS = [
226
226
  {
227
- tool: "show_content",
227
+ tool: "show",
228
228
  builtIn: "read",
229
229
  benefit: "it returns LINE:HASH anchors for safe edits."
230
230
  },
231
231
  {
232
- tool: "edit_text",
232
+ tool: "edit",
233
233
  builtIn: "edit",
234
234
  benefit: "it verifies fresh LINE:HASH anchors."
235
235
  },
236
236
  {
237
- tool: "find_text",
237
+ tool: "grep",
238
238
  builtIn: "grep",
239
- benefit: "it returns LINE:HASH anchors."
239
+ benefit: "it runs native regex/literal matching and returns source LINE:HASH anchors or document locations."
240
240
  },
241
241
  {
242
- tool: "write_file",
242
+ tool: "write",
243
243
  builtIn: "write",
244
244
  benefit: "it returns LINE:HASH anchors."
245
245
  }
246
246
  ];
247
247
  var TOOL_COMPACT_GUIDELINES = {
248
- show_content: [
249
- "Use `show_structure` for shallow overviews and `check_syntax` for diagnostics; use `find_definitions`, `find_references`, or `find_nodes` for targeted symbol questions."
248
+ show: [
249
+ "Use `tree` for shallow overviews and `syntax` for diagnostics; use `def`, `refs`, or `search` for targeted symbol questions."
250
250
  ],
251
- edit_text: [
251
+ edit: [
252
252
  "Prefer set_line, replace_lines, and insert_after; use replace only when anchors are impractical."
253
253
  ],
254
- find_text: [
255
- "Use `find_text` with summary first for broad searches, then narrow by path, glob, or pattern."
254
+ grep: [
255
+ "Use `grep` with summary first for broad searches, then narrow by path, glob, or pattern."
256
256
  ],
257
- write_file: [
258
- "Use anchored edits rather than `write_file` for small changes or appends."
257
+ write: [
258
+ "Use anchored edits rather than `write` for small changes or appends."
259
259
  ],
260
- find_nodes: [
261
- "Use `find_nodes` for AST patterns; use `find_text` for plain text."
260
+ search: [
261
+ "Use `search` for AST patterns; use `grep` for plain text."
262
262
  ],
263
- find_references: [
264
- "Use `find_references` before changing a symbol; use at with line:N:C to follow one binding."
263
+ refs: [
264
+ "Use `refs` before changing a symbol; use at with line:N:C to follow one binding."
265
265
  ]
266
266
  };
267
267
  var BUILTIN_FALLBACKS = {
268
- read: "show_content",
269
- edit: "edit_text",
270
- write: "write_file",
271
- grep: "find_text",
272
- apply_patch: "edit_text"
268
+ read: "show",
269
+ edit: "edit",
270
+ write: "write",
271
+ grep: "grep",
272
+ apply_patch: "edit"
273
273
  };
274
274
  var PREFERENCE_LEADS = {
275
- show_content: "Preferred for reading source with anchors.",
276
- edit_text: "Preferred for editing existing text files with fresh LINE:HASH anchors.",
277
- write_file: "Preferred for creating or replacing a complete text file; returns LINE:HASH anchors.",
278
- find_text: "Preferred text/regex search; returns edit-ready LINE:HASH anchors.",
279
- find_nodes: "Preferred for AST or structural code search with ast-grep-style patterns.",
280
- find_definitions: "Preferred for go-to-definition or declaration lookup by name.",
281
- find_references: "Preferred for finding identifier-name matches or binding-specific references before rename_symbol or delete.",
282
- rename_symbol: "Preferred for renaming a symbol with binding-aware, verified edits.",
283
- show_assets: "Preferred for materializing selected document assets with usable cached paths."
275
+ show: "Preferred for reading source with anchors.",
276
+ edit: "Preferred for editing existing text files with fresh LINE:HASH anchors.",
277
+ write: "Preferred for creating or replacing a complete text file; returns LINE:HASH anchors.",
278
+ grep: "Preferred native text/regex search; returns edit-ready source LINE:HASH anchors and document node-local locations.",
279
+ search: "Preferred for AST or structural code search with ast-grep-style patterns.",
280
+ def: "Preferred for go-to-definition or declaration lookup by name.",
281
+ refs: "Preferred for finding identifier-name matches or binding-specific references before `rename` or delete.",
282
+ rename: "Preferred for renaming a symbol with binding-aware, verified edits.",
283
+ assets: "Preferred for materializing selected document assets with usable cached paths."
284
284
  };
285
285
  var FALLBACK_TEMPLATES = {
286
- read: "Prefer `show_content` with fresh LINE:HASH anchors.",
287
- edit: "Prefer `edit_text` with fresh LINE:HASH anchors.",
288
- write: "Prefer `write_file` so the result returns LINE:HASH anchors.",
289
- grep: "Prefer `find_text` for text/regex search with edit-ready anchors.",
290
- apply_patch: "Prefer `edit_text` for existing text files."
286
+ read: "Prefer `show` with fresh LINE:HASH anchors.",
287
+ edit: "Prefer `edit` with fresh LINE:HASH anchors.",
288
+ write: "Prefer `write` so the result returns LINE:HASH anchors.",
289
+ grep: "Prefer `grep` for native text/regex search with source anchors and document locations.",
290
+ apply_patch: "Prefer `edit` for existing text files."
291
291
  };
292
292
  function resolveToolName(name, aliases = {}) {
293
293
  return aliases[name] ?? name;
@@ -306,21 +306,21 @@ function renderFallbackToolDescription(builtIn, aliases = {}) {
306
306
  }
307
307
  function renderToolRoutingPolicy(options = {}) {
308
308
  const aliases = options.aliases ?? {};
309
- const show_content = resolveToolName("show_content", aliases);
310
- const edit_text = resolveToolName("edit_text", aliases);
311
- const write_file = resolveToolName("write_file", aliases);
312
- const find_text = resolveToolName("find_text", aliases);
313
- const find_nodes = resolveToolName("find_nodes", aliases);
314
- const find_definitions = resolveToolName("find_definitions", aliases);
315
- const find_references = resolveToolName("find_references", aliases);
316
- const rename_symbol = resolveToolName("rename_symbol", aliases);
317
- const show_assets = resolveToolName("show_assets", aliases);
309
+ const show = resolveToolName("show", aliases);
310
+ const edit = resolveToolName("edit", aliases);
311
+ const write = resolveToolName("write", aliases);
312
+ const grep = resolveToolName("grep", aliases);
313
+ const search = resolveToolName("search", aliases);
314
+ const def = resolveToolName("def", aliases);
315
+ const refs = resolveToolName("refs", aliases);
316
+ const rename = resolveToolName("rename", aliases);
317
+ const assets = resolveToolName("assets", aliases);
318
318
  const title = options.title ?? "ReadSeek tool policy:";
319
319
  const bullets = [];
320
320
  if (options.preferOverBuiltIns) {
321
321
  bullets.push("- Prefer ReadSeek tools over built-ins when they can do the job.");
322
322
  }
323
- 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.`);
323
+ 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.`);
324
324
  if (options.demoteBuiltIns && options.demoteBuiltIns.length > 0) {
325
325
  bullets.push(`- Do not use built-in ${options.demoteBuiltIns.join(", ")} when a ReadSeek tool can do the job.`);
326
326
  }
@@ -367,6 +367,59 @@ function validateReferencesSelection(input) {
367
367
  return { ok: false, error: "exactly one of name or at is required" };
368
368
  return { ok: true };
369
369
  }
370
+ function validateGrepParams(input) {
371
+ if (!input || typeof input !== "object" || Array.isArray(input)) {
372
+ return { ok: false, error: "grep params must be an object" };
373
+ }
374
+ const params = input;
375
+ const allowed = [
376
+ "pattern",
377
+ "path",
378
+ "glob",
379
+ "ignoreCase",
380
+ "literal",
381
+ "context",
382
+ "limit",
383
+ "summary",
384
+ "scope",
385
+ "scopeContext",
386
+ "gitScope",
387
+ "signal",
388
+ "stdin",
389
+ "timeoutMs",
390
+ "cancelable"
391
+ ];
392
+ if (Object.keys(params).some((key) => !allowed.includes(key)))
393
+ return { ok: false, error: "unknown grep parameter" };
394
+ if (typeof params.pattern !== "string")
395
+ return { ok: false, error: "grep requires a pattern string" };
396
+ if (params.literal === true && params.pattern.length === 0)
397
+ return { ok: false, error: "literal pattern must not be empty" };
398
+ for (const key of ["path", "glob"]) {
399
+ if (params[key] !== undefined && (typeof params[key] !== "string" || params[key].length === 0)) {
400
+ return { ok: false, error: `${key} must be a non-empty string` };
401
+ }
402
+ }
403
+ for (const key of ["ignoreCase", "literal", "summary"]) {
404
+ if (params[key] !== undefined && typeof params[key] !== "boolean")
405
+ return { ok: false, error: `${key} must be a boolean` };
406
+ }
407
+ for (const key of ["context", "limit", "scopeContext"]) {
408
+ const value = params[key];
409
+ const minimum = key === "limit" ? 1 : 0;
410
+ if (value !== undefined && (typeof value !== "number" || !Number.isSafeInteger(value) || value < minimum)) {
411
+ return { ok: false, error: `${key} must be a ${minimum === 1 ? "positive" : "non-negative"} integer` };
412
+ }
413
+ }
414
+ if (params.scope !== undefined && params.scope !== "symbol")
415
+ return { ok: false, error: "scope must be symbol" };
416
+ if (params.scopeContext !== undefined && params.scope !== "symbol")
417
+ return { ok: false, error: "scopeContext requires scope" };
418
+ if (params.gitScope !== undefined && !GIT_SCOPES.includes(params.gitScope)) {
419
+ return { ok: false, error: "invalid gitScope" };
420
+ }
421
+ return { ok: true };
422
+ }
370
423
  var VARIANT_FIELDS = {
371
424
  set_line: ["anchor", "new_text"],
372
425
  replace_lines: ["start_anchor", "end_anchor", "new_text"],
@@ -530,50 +583,111 @@ var SELECTOR_KINDS = ["line", "hash", "symbol", "page", "node", "asset"];
530
583
  function isSelectorKind(value) {
531
584
  return SELECTOR_KINDS.includes(value);
532
585
  }
586
+ function parsePositiveSelectorInt(raw, name) {
587
+ if (!/^-?\d+$/.test(raw))
588
+ throw new Error(`invalid ${name} \`${raw}\``);
589
+ const parsed = Number(raw);
590
+ if (!Number.isSafeInteger(parsed))
591
+ throw new Error(`invalid ${name} \`${raw}\``);
592
+ if (parsed < 1)
593
+ throw new Error(`${name} must be greater than zero`);
594
+ return parsed;
595
+ }
596
+ function parseLineSelector(value, explicitKind) {
597
+ const parts = value.split(":");
598
+ if (parts.length > 2)
599
+ throw new Error("selector has too many components");
600
+ const head = parts[0];
601
+ if (parts.length === 2) {
602
+ if (head.slice(1).includes("-"))
603
+ throw new Error("line range cannot include a column");
604
+ const line = parsePositiveSelectorInt(head, "line");
605
+ const column = parsePositiveSelectorInt(parts[1], "column");
606
+ return { kind: "line", value: String(line), column, explicitKind };
607
+ }
608
+ const dash = head.indexOf("-", 1);
609
+ if (dash > 0) {
610
+ const start = parsePositiveSelectorInt(head.slice(0, dash), "line");
611
+ const endLine = parsePositiveSelectorInt(head.slice(dash + 1), "end line");
612
+ if (endLine < start)
613
+ throw new Error(`end line ${endLine} precedes start line ${start}`);
614
+ return { kind: "line", value: String(start), endLine, explicitKind };
615
+ }
616
+ const line = parsePositiveSelectorInt(head, "line");
617
+ return { kind: "line", value: String(line), explicitKind };
618
+ }
619
+ function parseHashSelector(value, explicitKind) {
620
+ const parts = value.split(":");
621
+ if (parts.length > 2)
622
+ throw new Error("selector has too many components");
623
+ const head = parts[0];
624
+ if (!/^[0-9a-fA-F]{6}$/.test(head))
625
+ throw new Error(`invalid line hash \`${head}\``);
626
+ if (parts.length === 2) {
627
+ const column = parsePositiveSelectorInt(parts[1], "column");
628
+ return { kind: "hash", value: head, column, explicitKind };
629
+ }
630
+ return { kind: "hash", value: head, explicitKind };
631
+ }
632
+ function isDocumentSelector(parsed) {
633
+ return ["page", "node", "asset"].includes(parsed.kind) || parsed.kind === "line" && !parsed.explicitKind && parsed.column === undefined && parsed.endLine === undefined;
634
+ }
635
+ function isBindingSelector(parsed) {
636
+ if (parsed.endLine !== undefined)
637
+ return false;
638
+ if (parsed.kind === "line" || parsed.kind === "hash")
639
+ return true;
640
+ return parsed.kind === "symbol" && !parsed.explicitKind && /^[0-9a-fA-F]{6}$/.test(parsed.value);
641
+ }
533
642
  function parseSelector(text) {
643
+ if (text.length === 0)
644
+ throw new Error("selector must not be empty");
534
645
  const separator = text.indexOf(":");
535
- if (separator < 0) {
536
- throw new Error("selector must start with line:, hash:, symbol:, page:, node:, or asset:");
537
- }
538
- const kind = text.slice(0, separator);
539
- const value = text.slice(separator + 1);
540
- if (value.length === 0)
541
- throw new Error(`${kind} selector must not be empty`);
542
- if (!isSelectorKind(kind)) {
646
+ const hasSingleColon = separator >= 0 && text[separator + 1] !== ":";
647
+ if (hasSingleColon) {
648
+ const kind = text.slice(0, separator);
649
+ const value = text.slice(separator + 1);
650
+ if (isSelectorKind(kind)) {
651
+ if (value.length === 0)
652
+ throw new Error(`${kind} selector must not be empty`);
653
+ if (kind === "line")
654
+ return parseLineSelector(value, true);
655
+ if (kind === "hash")
656
+ return parseHashSelector(value, true);
657
+ if (kind === "page") {
658
+ if (value.includes(":"))
659
+ throw new Error("page selector has too many components");
660
+ parsePositiveSelectorInt(value, "page");
661
+ }
662
+ return { kind, value, explicitKind: true };
663
+ }
664
+ if (/^\d+$/.test(kind))
665
+ return parseLineSelector(text, false);
666
+ if (/^[0-9a-fA-F]{6}$/.test(kind) && /^\d+$/.test(value))
667
+ return parseHashSelector(text, false);
543
668
  throw new Error(`unknown selector kind \`${kind}\`; expected line, hash, symbol, page, node, or asset`);
544
669
  }
545
- if (kind === "line" || kind === "hash") {
546
- const parts = value.split(":");
547
- if (parts.length > 2)
548
- throw new Error("selector has too many components");
549
- if (parts.length === 2) {
550
- const column = Number(parts[1]);
551
- if (!Number.isSafeInteger(column))
552
- throw new Error(`invalid column \`${parts[1]}\``);
553
- if (column < 1)
554
- throw new Error("column must be greater than zero");
555
- return { kind, value: parts[0], column };
670
+ if (/^[0-9-]/.test(text)) {
671
+ if (/^[0-9a-fA-F]{6}$/.test(text) && /[a-fA-F]/.test(text)) {
672
+ return parseHashSelector(text, false);
556
673
  }
674
+ return parseLineSelector(text, false);
557
675
  }
558
- if (kind === "page") {
559
- if (value.includes(":"))
560
- throw new Error("page selector has too many components");
561
- const page = Number(value);
562
- if (!Number.isSafeInteger(page))
563
- throw new Error(`invalid page \`${value}\``);
564
- if (page < 1)
565
- throw new Error("page must be greater than zero");
676
+ if (/^[0-9a-fA-F]{6}$/.test(text) && /\d/.test(text)) {
677
+ return parseHashSelector(text, false);
566
678
  }
567
- return { kind, value };
679
+ if (/\s/.test(text))
680
+ throw new Error(`invalid selector \`${text}\``);
681
+ return { kind: "symbol", value: text, explicitKind: false };
568
682
  }
569
- function start(verb, object, input) {
683
+ function start(command, input) {
570
684
  if (!input.path)
571
- throw new Error(`${verb} ${object} requires a path`);
685
+ throw new Error(`${command} requires a path`);
572
686
  if (input.stdinName !== undefined && input.path !== "-")
573
687
  throw new Error("stdinName requires '-' as the target");
574
688
  const args = [];
575
689
  pushOptionalString(args, "--readseek-dir", input.readseekDir);
576
- args.push(verb, object, input.path);
690
+ args.push(command, input.path);
577
691
  pushOptionalString(args, "--stdin-name", input.stdinName);
578
692
  return args;
579
693
  }
@@ -599,12 +713,12 @@ function anchors(args, input) {
599
713
  if (input.noAnchors)
600
714
  args.push("--no-anchors");
601
715
  }
602
- function buildShowContentArgs(input) {
716
+ function buildShowArgs(input) {
603
717
  if (input.end !== undefined)
604
718
  validatePositiveInteger(input.end, "end");
605
719
  if (input.end !== undefined && input.limit !== undefined)
606
720
  throw new Error("cannot combine end with limit");
607
- const args = start("show", "content", input);
721
+ const args = start("show", input);
608
722
  at2(args, input.at);
609
723
  pushOptionalNumber(args, "--end", input.end);
610
724
  limit2(args, input.limit);
@@ -612,10 +726,10 @@ function buildShowContentArgs(input) {
612
726
  anchors(args, input);
613
727
  return finish(args);
614
728
  }
615
- function buildShowStructureArgs(input) {
729
+ function buildTreeArgs(input) {
616
730
  if (input.depth !== undefined && (!Number.isSafeInteger(input.depth) || input.depth < 0))
617
731
  throw new Error("depth must be a non-negative integer");
618
- const args = start("show", "structure", input);
732
+ const args = start("tree", input);
619
733
  at2(args, input.at);
620
734
  pushOptionalNumber(args, "--depth", input.depth);
621
735
  pushOptionalString(args, "--kind", input.kind);
@@ -624,35 +738,35 @@ function buildShowStructureArgs(input) {
624
738
  args.push("--outline");
625
739
  return finish(args);
626
740
  }
627
- function buildShowMetadataArgs(input) {
628
- const args = start("show", "metadata", input);
741
+ function buildMetaArgs(input) {
742
+ const args = start("meta", input);
629
743
  language(args, input);
630
744
  return finish(args);
631
745
  }
632
- function buildShowAssetsArgs(input) {
633
- if (input.at !== undefined && !["page", "node", "asset"].includes(parseSelector(input.at).kind))
746
+ function buildAssetsArgs(input) {
747
+ if (input.at !== undefined && !isDocumentSelector(parseSelector(input.at)))
634
748
  throw new Error("assets requires a page, node, or asset selector");
635
- const args = start("show", "assets", input);
749
+ const args = start("assets", input);
636
750
  at2(args, input.at);
637
751
  return finish(args);
638
752
  }
639
- function buildCheckSyntaxArgs(input) {
640
- const args = start("check", "syntax", input);
753
+ function buildSyntaxArgs(input) {
754
+ const args = start("syntax", input);
641
755
  language(args, input);
642
756
  return finish(args);
643
757
  }
644
- function visionArgs(verb, object, input) {
758
+ function visionArgs(command, input) {
645
759
  if (input.level !== undefined && !VISION_LEVELS.includes(input.level))
646
760
  throw new Error("invalid vision level");
647
- if (input.at !== undefined && !["page", "node", "asset"].includes(parseSelector(input.at).kind))
761
+ if (input.at !== undefined && !isDocumentSelector(parseSelector(input.at)))
648
762
  throw new Error("vision requires a document selector");
649
- const args = start(verb, object, input);
763
+ const args = start(command, input);
650
764
  at2(args, input.at);
651
765
  pushOptionalString(args, "--level", input.level);
652
766
  return args;
653
767
  }
654
- function buildGenerateCaptionArgs(input) {
655
- const args = visionArgs("generate", "caption", input);
768
+ function buildCaptionArgs(input) {
769
+ const args = visionArgs("caption", input);
656
770
  if (input.include !== undefined) {
657
771
  if (input.include.length === 0 || input.include.some((task) => task !== "text" && task !== "objects"))
658
772
  throw new Error("include requires text or objects");
@@ -660,13 +774,13 @@ function buildGenerateCaptionArgs(input) {
660
774
  }
661
775
  return finish(args);
662
776
  }
663
- function buildDetectObjectsArgs(input) {
664
- return finish(visionArgs("detect", "objects", input));
777
+ function buildObjectsArgs(input) {
778
+ return finish(visionArgs("objects", input));
665
779
  }
666
- function buildRecognizeTextArgs(input) {
667
- return finish(visionArgs("recognize", "text", input));
780
+ function buildOcrArgs(input) {
781
+ return finish(visionArgs("ocr", input));
668
782
  }
669
- function buildFindNodesArgs(input) {
783
+ function buildSearchArgs(input) {
670
784
  const hasPattern = Boolean(input.pattern);
671
785
  const hasRule = Boolean(input.rule);
672
786
  const hasContext = Boolean(input.context);
@@ -674,7 +788,7 @@ function buildFindNodesArgs(input) {
674
788
  if (hasPattern || hasContext || input.selector)
675
789
  throw new Error("rule cannot be combined with pattern, context, or selector");
676
790
  } else if (!hasPattern && !hasContext) {
677
- throw new Error("find nodes requires pattern, context, or rule");
791
+ throw new Error("search requires pattern, context, or rule");
678
792
  }
679
793
  if (hasContext && !input.selector) {
680
794
  throw new Error("context requires selector");
@@ -682,7 +796,7 @@ function buildFindNodesArgs(input) {
682
796
  if (hasPattern && hasContext) {
683
797
  throw new Error("pattern and context cannot be combined");
684
798
  }
685
- const args = start("find", "nodes", input);
799
+ const args = start("search", input);
686
800
  pushOptionalString(args, "--pattern", input.pattern);
687
801
  pushOptionalString(args, "--context", input.context);
688
802
  pushOptionalString(args, "--selector", input.selector);
@@ -693,28 +807,46 @@ function buildFindNodesArgs(input) {
693
807
  anchors(args, input);
694
808
  return finish(args);
695
809
  }
696
- function buildFindTextArgs(input) {
697
- const args = start("find", "text", input);
698
- args.push("--text", input.text);
699
- limit2(args, input.limit);
810
+ function buildGrepArgs(input) {
811
+ const validated = validateGrepParams(input);
812
+ if (!validated.ok)
813
+ throw new Error(validated.error);
814
+ const args = ["grep", "--kdl", `--pattern=${input.pattern}`];
815
+ const needsPathTerminator = input.path !== undefined && input.path.startsWith("-") && input.path !== "-";
816
+ if (input.path !== undefined && !needsPathTerminator)
817
+ args.push(input.path);
818
+ if (input.literal)
819
+ args.push("--literal");
820
+ if (input.ignoreCase)
821
+ args.push("--ignore-case");
822
+ if (input.glob !== undefined)
823
+ args.push(`--glob=${input.glob}`);
824
+ pushOptionalNumber(args, "--context", input.context);
825
+ limit2(args, input.limit ?? 100);
826
+ if (input.summary)
827
+ args.push("--summary");
828
+ pushOptionalString(args, "--scope", input.scope);
829
+ pushOptionalNumber(args, "--scope-context", input.scopeContext);
700
830
  appendGitScopeArgs(args, input);
701
- return finish(args);
831
+ if (input.path !== undefined && needsPathTerminator)
832
+ args.push("--", input.path);
833
+ return args;
702
834
  }
703
- function buildFindDefinitionsArgs(input) {
704
- const args = start("find", "definitions", input);
835
+ function buildDefArgs(input) {
836
+ const args = start("def", input);
705
837
  args.push("--name", input.name);
706
838
  language(args, input);
707
839
  appendGitScopeArgs(args, input);
708
840
  anchors(args, input);
709
841
  return finish(args);
710
842
  }
711
- function buildFindReferencesArgs(input) {
843
+ function buildRefsArgs(input) {
712
844
  const validated = validateReferencesSelection(input);
713
845
  if (!validated.ok)
714
846
  throw new Error(validated.error);
715
- if (input.at !== undefined && !["line", "hash"].includes(parseSelector(input.at).kind))
847
+ if (input.at !== undefined && !isBindingSelector(parseSelector(input.at)))
716
848
  throw new Error("references requires a line or hash selector");
717
- const args = start("find", "references", input);
849
+ const args = start("refs", input);
718
850
  pushOptionalString(args, "--name", input.name);
719
851
  at2(args, input.at);
720
852
  language(args, input);
@@ -722,14 +854,14 @@ function buildFindReferencesArgs(input) {
722
854
  anchors(args, input);
723
855
  return finish(args);
724
856
  }
725
- function buildCheckRulesArgs(input) {
726
- const args = start("check", "rules", input);
857
+ function buildCheckArgs(input) {
858
+ const args = start("check", input);
727
859
  args.push("--rules", input.rules);
728
860
  appendGitScopeArgs(args, input);
729
861
  return finish(args);
730
862
  }
731
- function buildEditTextArgs(input) {
732
- const args = start("edit", "text", input);
863
+ function buildEditArgs(input) {
864
+ const args = start("edit", input);
733
865
  appendApplyAndPlanHash(args, input);
734
866
  language(args, input);
735
867
  appendGitScopeArgs(args, input);
@@ -737,10 +869,10 @@ function buildEditTextArgs(input) {
737
869
  args.push("--edits", "-");
738
870
  return finish(args);
739
871
  }
740
- function buildRenameSymbolArgs(input) {
741
- if (!["line", "hash"].includes(parseSelector(input.at).kind))
872
+ function buildRenameArgs(input) {
873
+ if (!isBindingSelector(parseSelector(input.at)))
742
874
  throw new Error("rename requires a line or hash selector");
743
- const args = start("rename", "symbol", input);
875
+ const args = start("rename", input);
744
876
  at2(args, input.at);
745
877
  args.push("--to", input.to);
746
878
  if (input.workspace !== undefined)
@@ -1130,25 +1262,41 @@ var DocumentOutput = Type.Object({
1130
1262
  assets: Type.Array(DocumentAsset, { default: [] }),
1131
1263
  vision: Type.Array(DocumentVision, { default: [] })
1132
1264
  });
1133
- var TextHit = Type.Object({
1134
- line: Int,
1135
- column: Int,
1265
+ var GrepCount = Type.Integer({ minimum: 0, maximum: Number.MAX_SAFE_INTEGER });
1266
+ var GrepPosition = Type.Integer({ minimum: 1, maximum: Number.MAX_SAFE_INTEGER });
1267
+ var GrepAnchor = Type.String({ pattern: "^[1-9][0-9]*:[0-9a-f]{6}$" });
1268
+ var GrepLineSchema = Type.Object({
1269
+ line: GrepPosition,
1270
+ hash: Type.String({ pattern: "^[0-9a-f]{6}$" }),
1136
1271
  text: Str,
1137
- start_line: Int,
1138
- end_line: Int,
1139
- start_byte: Int,
1140
- end_byte: Int,
1141
- hashlines: Hashlines
1272
+ kind: Type.Union([Type.Literal("match"), Type.Literal("context")])
1142
1273
  });
1143
- var DocumentHit = Type.Object({ node_id: Str, kind: Str, snippet: Str, page: OptInt });
1144
- var TextSearchOutput = Type.Object({
1145
- text: Str,
1146
- mode: Type.Literal("literal"),
1147
- total_matches: Int,
1148
- returned_matches: Int,
1274
+ var GrepHitSchema = Type.Object({ line: GrepPosition, column: GrepPosition, start_byte: GrepCount, end_byte: GrepCount, text: Str, anchor: GrepAnchor });
1275
+ var GrepSymbolSchema = Type.Object({ name: Str, kind: Str, start_line: GrepPosition, end_line: GrepPosition, lines: Type.Array(GrepLineSchema) });
1276
+ var GrepSourceResultSchema = Type.Object({
1277
+ file: Str,
1278
+ file_hash: Str,
1279
+ language: Str,
1280
+ total_matches: GrepCount,
1281
+ returned_matches: GrepCount,
1282
+ hits: Type.Array(GrepHitSchema),
1283
+ lines: Type.Array(GrepLineSchema),
1284
+ symbols: Type.Array(GrepSymbolSchema)
1285
+ });
1286
+ var GrepDocumentHitSchema = Type.Object({ node_id: Str, kind: Str, snippet: Str, line: GrepPosition, page: Type.Optional(GrepPosition) });
1287
+ var GrepDocumentResultSchema = Type.Object({ file: Str, content_hash: Str, total_matches: GrepCount, returned_matches: GrepCount, hits: Type.Array(GrepDocumentHitSchema) });
1288
+ var GrepOutputSchema = Type.Object({
1289
+ pattern: Str,
1290
+ mode: Type.Union([Type.Literal("regex"), Type.Literal("literal")]),
1291
+ ignore_case: Bool,
1292
+ summary: Bool,
1293
+ total_matches: GrepCount,
1294
+ returned_matches: GrepCount,
1149
1295
  truncated: Bool,
1150
- results: Type.Array(Type.Object({ file: Str, file_hash: Str, matches: Type.Array(TextHit) })),
1151
- documents: Type.Array(Type.Object({ file: Str, content_hash: Str, hits: Type.Array(DocumentHit) }))
1296
+ count_unit: Type.Literal("lines"),
1297
+ scope: Type.Optional(Type.Literal("symbol")),
1298
+ results: Type.Array(GrepSourceResultSchema),
1299
+ documents: Type.Array(GrepDocumentResultSchema)
1152
1300
  });
1153
1301
  var RuleFinding = Type.Object({
1154
1302
  name: Str,
@@ -1193,8 +1341,35 @@ function parseStructureEnvelope(value) {
1193
1341
  throw new Error("invalid readseek structure: missing structure");
1194
1342
  return { ...output.metadata !== undefined ? { metadata: parseDetectOutput(output.metadata) } : {}, structure: parseStructureOutput(output.structure) };
1195
1343
  }
1196
- function parseTextSearchOutput(value) {
1197
- return parse(TextSearchOutput, value, "text search output");
1344
+ function parseGrepOutput(value) {
1345
+ const output = parse(GrepOutputSchema, value, "grep output");
1346
+ const invalid = (message) => {
1347
+ throw new Error(`invalid readseek grep output: ${message}`);
1348
+ };
1349
+ const groups = [...output.results, ...output.documents];
1350
+ 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)
1351
+ invalid("inconsistent counts");
1352
+ for (const group of groups) {
1353
+ if (group.total_matches === 0 || group.returned_matches > group.total_matches)
1354
+ invalid("inconsistent file counts");
1355
+ if (output.summary ? group.hits.length !== 0 : group.hits.length !== group.returned_matches)
1356
+ invalid("inconsistent hit count");
1357
+ }
1358
+ for (const result of output.results) {
1359
+ if (output.summary && (result.lines.length > 0 || result.symbols.length > 0))
1360
+ invalid("summary contains display entries");
1361
+ if (output.scope !== "symbol" && result.symbols.length > 0)
1362
+ invalid("symbols require symbol scope");
1363
+ for (const hit of result.hits) {
1364
+ if (hit.end_byte < hit.start_byte || Number(hit.anchor.split(":")[0]) !== hit.line)
1365
+ invalid("invalid hit span or anchor");
1366
+ }
1367
+ for (const symbol of result.symbols) {
1368
+ if (symbol.end_line < symbol.start_line || symbol.lines.some((line) => line.line < symbol.start_line || line.line > symbol.end_line))
1369
+ invalid("invalid symbol range");
1370
+ }
1371
+ }
1372
+ return output;
1198
1373
  }
1199
1374
  function parseRulesOutput(value) {
1200
1375
  return parse(RulesOutput, value, "rules output");
@@ -1271,71 +1446,71 @@ function runOptions(options, vision = false) {
1271
1446
  }
1272
1447
  function createReadSeekClient(runner) {
1273
1448
  return {
1274
- async showContent(path, options = {}) {
1275
- const value = await runner.kdl(buildShowContentArgs({ ...options, path }), runOptions(options));
1449
+ async show(path, options = {}) {
1450
+ const value = await runner.kdl(buildShowArgs({ ...options, path }), runOptions(options));
1276
1451
  return parseContentOutput(value);
1277
1452
  },
1278
- async showStructure(path, options = {}) {
1279
- const value = await runner.kdl(buildShowStructureArgs({ ...options, path }), runOptions(options));
1453
+ async tree(path, options = {}) {
1454
+ const value = await runner.kdl(buildTreeArgs({ ...options, path }), runOptions(options));
1280
1455
  return parseStructureEnvelope(value);
1281
1456
  },
1282
- async showMetadata(path, options = {}) {
1283
- const value = await runner.kdl(buildShowMetadataArgs({ ...options, path }), runOptions(options));
1457
+ async meta(path, options = {}) {
1458
+ const value = await runner.kdl(buildMetaArgs({ ...options, path }), runOptions(options));
1284
1459
  return parsePdfMetadata(value) ?? parseDetectOutput(value?.metadata);
1285
1460
  },
1286
- async showAssets(path, options = {}) {
1287
- const value = await runner.kdl(buildShowAssetsArgs({ ...options, path }), runOptions(options));
1461
+ async assets(path, options = {}) {
1462
+ const value = await runner.kdl(buildAssetsArgs({ ...options, path }), runOptions(options));
1288
1463
  return parseAssetsOutput(value);
1289
1464
  },
1290
- async checkSyntax(path, options = {}) {
1291
- const value = await runner.kdl(buildCheckSyntaxArgs({ ...options, path }), runOptions(options));
1465
+ async syntax(path, options = {}) {
1466
+ const value = await runner.kdl(buildSyntaxArgs({ ...options, path }), runOptions(options));
1292
1467
  return parseCheckOutput(value?.diagnostics ?? value);
1293
1468
  },
1294
- async checkRules(path, options) {
1295
- const value = await runner.kdl(buildCheckRulesArgs({ ...options, path }), runOptions(options));
1469
+ async check(path, options) {
1470
+ const value = await runner.kdl(buildCheckArgs({ ...options, path }), runOptions(options));
1296
1471
  return parseRulesOutput(value);
1297
1472
  },
1298
- async generateCaption(path, options = {}) {
1299
- const value = await runner.kdl(buildGenerateCaptionArgs({ ...options, path }), runOptions(options, true));
1473
+ async caption(path, options = {}) {
1474
+ const value = await runner.kdl(buildCaptionArgs({ ...options, path }), runOptions(options, true));
1300
1475
  return parseContentOutput(value);
1301
1476
  },
1302
- async detectObjects(path, options = {}) {
1303
- const value = await runner.kdl(buildDetectObjectsArgs({ ...options, path }), runOptions(options, true));
1477
+ async objects(path, options = {}) {
1478
+ const value = await runner.kdl(buildObjectsArgs({ ...options, path }), runOptions(options, true));
1304
1479
  return parseContentOutput(value);
1305
1480
  },
1306
- async recognizeText(path, options = {}) {
1307
- const value = await runner.kdl(buildRecognizeTextArgs({ ...options, path }), runOptions(options, true));
1481
+ async ocr(path, options = {}) {
1482
+ const value = await runner.kdl(buildOcrArgs({ ...options, path }), runOptions(options, true));
1308
1483
  return parseContentOutput(value);
1309
1484
  },
1310
- async findNodes(path, options) {
1311
- const value = await runner.kdl(buildFindNodesArgs({ ...options, path }), runOptions(options));
1485
+ async search(path, options) {
1486
+ const value = await runner.kdl(buildSearchArgs({ ...options, path }), runOptions(options));
1312
1487
  return parseSearchOutput(value);
1313
1488
  },
1314
- async findText(path, options) {
1315
- const value = await runner.kdl(buildFindTextArgs({ ...options, path }), runOptions(options));
1316
- return parseTextSearchOutput(value);
1489
+ async grep(params) {
1490
+ const value = await runner.kdl(buildGrepArgs(params), runOptions(params));
1491
+ return parseGrepOutput(value);
1317
1492
  },
1318
- async findDefinitions(path, options) {
1319
- const value = await runner.kdl(buildFindDefinitionsArgs({ ...options, path }), runOptions(options));
1493
+ async def(path, options) {
1494
+ const value = await runner.kdl(buildDefArgs({ ...options, path }), runOptions(options));
1320
1495
  return parseDefOutput(value);
1321
1496
  },
1322
- async findReferences(path, options) {
1323
- const value = await runner.kdl(buildFindReferencesArgs({ ...options, path }), runOptions(options));
1497
+ async refs(path, options) {
1498
+ const value = await runner.kdl(buildRefsArgs({ ...options, path }), runOptions(options));
1324
1499
  return parseRefsOutput(value);
1325
1500
  },
1326
- async editText(path, edits, options = {}) {
1501
+ async edit(path, edits, options = {}) {
1327
1502
  const stdin = serializeEditPlan(edits);
1328
- return parseEditOutput(await runner.kdl(buildEditTextArgs({ ...options, path, requestFromStdin: true }), runOptions({ ...options, stdin })));
1503
+ return parseEditOutput(await runner.kdl(buildEditArgs({ ...options, path, requestFromStdin: true }), runOptions({ ...options, stdin })));
1329
1504
  },
1330
- async renameSymbol(path, options) {
1331
- const value = await runner.kdl(buildRenameSymbolArgs({ ...options, path }), runOptions(options));
1505
+ async rename(path, options) {
1506
+ const value = await runner.kdl(buildRenameArgs({ ...options, path }), runOptions(options));
1332
1507
  return parseRenameOutput(value);
1333
1508
  }
1334
1509
  };
1335
1510
  }
1336
1511
  // src/show-presentation.ts
1337
1512
  function isShowTool(name) {
1338
- return name === "show_content" || name === "show_structure" || name === "show_metadata" || name === "show_assets";
1513
+ return name === "show" || name === "tree" || name === "meta" || name === "assets";
1339
1514
  }
1340
1515
  function record(value) {
1341
1516
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
@@ -1450,7 +1625,7 @@ function presentShowResult(tool, value, options = {}) {
1450
1625
  const path = inline(options.path || output.source || output.file || content.file || structure.file || metadata.file || cursor.file) || "(unknown path)";
1451
1626
  const summary = [];
1452
1627
  const result = { summary: "", body: "", notices: [], metadata: {} };
1453
- if (tool === "show_metadata") {
1628
+ if (tool === "meta") {
1454
1629
  const data = Object.keys(metadata).length > 0 ? metadata : output;
1455
1630
  if (text(data.format || data.language || data.type))
1456
1631
  summary.push(inline(data.format || data.language || data.type));
@@ -1459,7 +1634,7 @@ function presentShowResult(tool, value, options = {}) {
1459
1634
  summary.push(plural(result.metadata.pages, "page"));
1460
1635
  }
1461
1636
  result.body = fields(data) || "No metadata available.";
1462
- } else if (tool === "show_assets") {
1637
+ } else if (tool === "assets") {
1463
1638
  const assets = records(output.assets);
1464
1639
  result.metadata.assets = assets.length;
1465
1640
  summary.push(plural(assets.length, "asset"));
@@ -1473,7 +1648,7 @@ function presentShowResult(tool, value, options = {}) {
1473
1648
  summary.push(plural(nodes.length, "node"));
1474
1649
  if (result.metadata.pages !== undefined)
1475
1650
  summary.push(plural(result.metadata.pages, "page"));
1476
- const sections = [text(output.title) ? safe(text(output.title)) : "", documentNodes(nodes, tool === "show_content")];
1651
+ const sections = [text(output.title) ? safe(text(output.title)) : "", documentNodes(nodes, tool === "show")];
1477
1652
  if (assets.length > 0) {
1478
1653
  result.metadata.assets = assets.length;
1479
1654
  sections.push(`Assets
@@ -1482,7 +1657,7 @@ ${assetList(assets)}`);
1482
1657
  result.body = sections.filter(Boolean).join(`
1483
1658
 
1484
1659
  `) || "No content in this selection.";
1485
- } else if (tool === "show_structure") {
1660
+ } else if (tool === "tree") {
1486
1661
  const symbols = records(structure.symbols);
1487
1662
  result.metadata.symbols = symbols.length;
1488
1663
  summary.push(plural(symbols.length, "symbol"));
@@ -1536,7 +1711,7 @@ ${assetList(assets)}`);
1536
1711
  const description = Object.keys(content).length > 0 ? content : output;
1537
1712
  result.body = fields(description) || "No content in this selection.";
1538
1713
  }
1539
- if (tool === "show_structure")
1714
+ if (tool === "tree")
1540
1715
  result.notices.push("Structure is limited to the requested depth, kind, and outline. Use a deeper depth or focus with at to inspect further.");
1541
1716
  if (text(output.warning))
1542
1717
  result.notices.push(safe(text(output.warning)));
@@ -1548,6 +1723,31 @@ function formatShowResult(presentation) {
1548
1723
 
1549
1724
  `);
1550
1725
  }
1726
+ // src/grep-presentation.ts
1727
+ function renderGrepOutput(output) {
1728
+ const rendered = [`${output.returned_matches} of ${output.total_matches} matching lines${output.truncated ? " (truncated)" : ""}`];
1729
+ const displayLine = (line) => `${line.line}:${line.hash}|${line.kind === "match" ? ">>" : " "}${line.text}`;
1730
+ for (const result of output.results) {
1731
+ rendered.push(`${result.file}: ${result.total_matches} matching lines (${result.returned_matches} returned)`);
1732
+ if (output.summary)
1733
+ continue;
1734
+ rendered.push(...result.lines.map(displayLine));
1735
+ for (const symbol of result.symbols) {
1736
+ rendered.push(` ${symbol.kind} ${symbol.name} (${symbol.start_line}-${symbol.end_line})`);
1737
+ rendered.push(...symbol.lines.map(displayLine));
1738
+ }
1739
+ }
1740
+ for (const document of output.documents) {
1741
+ rendered.push(`${document.file}: ${document.total_matches} matching lines (${document.returned_matches} returned)`);
1742
+ if (output.summary)
1743
+ continue;
1744
+ for (const hit of document.hits) {
1745
+ rendered.push(` node:${hit.node_id}${hit.page === undefined ? "" : ` page:${hit.page}`} line:${hit.line} (${hit.kind}): ${hit.snippet}`);
1746
+ }
1747
+ }
1748
+ return rendered.join(`
1749
+ `);
1750
+ }
1551
1751
  // src/platform.ts
1552
1752
  import { accessSync, constants as fsConstants, existsSync, statSync } from "node:fs";
1553
1753
  import { createRequire } from "node:module";
@@ -1869,6 +2069,46 @@ function search(root) {
1869
2069
  }))
1870
2070
  };
1871
2071
  }
2072
+ function grepChildren(node, allowed) {
2073
+ if (node.children.some((child) => !allowed.includes(child.name))) {
2074
+ throw new Error(`invalid readseek KDL grep: unexpected child of ${node.name}`);
2075
+ }
2076
+ }
2077
+ function grepLine(node) {
2078
+ grepChildren(node, []);
2079
+ const parsed = line(node);
2080
+ if (requireProperty(node, "number") !== parsed.line)
2081
+ throw new Error("invalid readseek KDL grep line number");
2082
+ return { ...parsed, kind: requireProperty(node, "kind") };
2083
+ }
2084
+ function grep(root) {
2085
+ grepChildren(root, ["match", "document"]);
2086
+ return parseGrepOutput({
2087
+ ...properties(root),
2088
+ results: children(root, "match").map((match) => {
2089
+ grepChildren(match, ["hit", "line", "symbol"]);
2090
+ return {
2091
+ ...properties(match),
2092
+ hits: children(match, "hit").map((hit) => {
2093
+ grepChildren(hit, []);
2094
+ return properties(hit);
2095
+ }),
2096
+ lines: children(match, "line").map(grepLine),
2097
+ symbols: children(match, "symbol").map((symbol) => {
2098
+ grepChildren(symbol, ["line"]);
2099
+ return { ...properties(symbol), lines: children(symbol, "line").map(grepLine) };
2100
+ })
2101
+ };
2102
+ }),
2103
+ documents: children(root, "document").map((document) => {
2104
+ grepChildren(document, ["hit"]);
2105
+ return { ...properties(document), hits: children(document, "hit").map((hit) => {
2106
+ grepChildren(hit, []);
2107
+ return properties(hit);
2108
+ }) };
2109
+ })
2110
+ });
2111
+ }
1872
2112
  function references(root) {
1873
2113
  const parsed = children(root, "reference").map((reference) => {
1874
2114
  const sourceLine = child(reference, "line");
@@ -1984,7 +2224,7 @@ function parseReadSeekKdl(text) {
1984
2224
  case "document":
1985
2225
  return root.properties.has("format") ? pdfDocument(root) : sourceDocument(root);
1986
2226
  case "search":
1987
- return search(root);
2227
+ return ["mode", "ignore_case", "summary", "count_unit"].some((key) => root.properties.has(key)) ? grep(root) : search(root);
1988
2228
  case "check":
1989
2229
  return diagnostics(root);
1990
2230
  case "refs":
@@ -2049,7 +2289,7 @@ function acceptsReadSeekExitCode(args, exitCode) {
2049
2289
  index++;
2050
2290
  continue;
2051
2291
  }
2052
- return arg === "find" || arg === "check";
2292
+ return arg === "grep" || arg === "search" || arg === "def" || arg === "refs" || arg === "syntax" || arg === "check";
2053
2293
  }
2054
2294
  return false;
2055
2295
  }
@@ -2285,22 +2525,22 @@ export {
2285
2525
  acceptsReadSeekExitCode,
2286
2526
  appendApplyAndPlanHash,
2287
2527
  appendGitScopeArgs,
2288
- buildCheckRulesArgs,
2289
- buildCheckSyntaxArgs,
2290
- buildDetectObjectsArgs,
2291
- buildEditTextArgs,
2292
- buildFindDefinitionsArgs,
2293
- buildFindNodesArgs,
2294
- buildFindReferencesArgs,
2295
- buildFindTextArgs,
2296
- buildGenerateCaptionArgs,
2528
+ buildAssetsArgs,
2529
+ buildCaptionArgs,
2530
+ buildCheckArgs,
2531
+ buildDefArgs,
2532
+ buildEditArgs,
2533
+ buildGrepArgs,
2297
2534
  buildLineLocation,
2298
- buildRecognizeTextArgs,
2299
- buildRenameSymbolArgs,
2300
- buildShowAssetsArgs,
2301
- buildShowContentArgs,
2302
- buildShowMetadataArgs,
2303
- buildShowStructureArgs,
2535
+ buildMetaArgs,
2536
+ buildObjectsArgs,
2537
+ buildOcrArgs,
2538
+ buildRefsArgs,
2539
+ buildRenameArgs,
2540
+ buildSearchArgs,
2541
+ buildShowArgs,
2542
+ buildSyntaxArgs,
2543
+ buildTreeArgs,
2304
2544
  canonicalPathKey,
2305
2545
  classifyReadSeekFailure,
2306
2546
  cleanReadSeekErrorText,
@@ -2318,6 +2558,7 @@ export {
2318
2558
  getToolContract,
2319
2559
  isShowTool,
2320
2560
  isSubPath,
2561
+ parseGrepOutput,
2321
2562
  parseLineAnchor,
2322
2563
  parseReadSeekKdl,
2323
2564
  parseSelector,
@@ -2329,6 +2570,7 @@ export {
2329
2570
  readSeekProcessEnvironment,
2330
2571
  renderCompactGuidelines,
2331
2572
  renderFallbackToolDescription,
2573
+ renderGrepOutput,
2332
2574
  renderPreferredToolDescription,
2333
2575
  renderToolDescription,
2334
2576
  renderToolRoutingPolicy,
@@ -2349,5 +2591,6 @@ export {
2349
2591
  serializeEditPlan,
2350
2592
  tryParseLineAnchor,
2351
2593
  validateExactlyOneEditVariant,
2594
+ validateGrepParams,
2352
2595
  validateReferencesSelection
2353
2596
  };