pi-readseek 0.9.16 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.ts CHANGED
@@ -2,11 +2,11 @@
2
2
  import { readFile as fsReadFile } from "node:fs/promises";
3
3
  import { createPatch } from "diff";
4
4
  import { withFileMutationQueue } from "@earendil-works/pi-coding-agent";
5
- import { Type as Type2 } from "@sinclair/typebox";
5
+ import { Type as Type3 } from "@sinclair/typebox";
6
6
  import { Text as Text3 } from "@earendil-works/pi-tui";
7
7
 
8
8
  // ../readseek-api/src/contract.ts
9
- var DIGEST_FACETS = [
9
+ var INSPECT_FACETS = [
10
10
  "metadata",
11
11
  "content",
12
12
  "map",
@@ -14,20 +14,7 @@ var DIGEST_FACETS = [
14
14
  "identity"
15
15
  ];
16
16
  var VISION_MODES = ["none", "all", "ocr", "caption", "objects"];
17
- var VISION_ANALYSIS_MODES = ["all", "ocr", "caption", "objects"];
18
17
  var VISION_LEVELS = ["low", "medium", "high"];
19
- var DOCUMENT_NODE_KINDS = [
20
- "artifact",
21
- "footer",
22
- "header",
23
- "heading",
24
- "marginal_label",
25
- "page",
26
- "page_number",
27
- "paragraph",
28
- "section",
29
- "structural_section"
30
- ];
31
18
  var EDIT_VARIANTS = [
32
19
  "set_line",
33
20
  "replace_lines",
@@ -35,12 +22,13 @@ var EDIT_VARIANTS = [
35
22
  "replace",
36
23
  "replace_symbol"
37
24
  ];
25
+ var GIT_SCOPES = ["tracked", "untracked", "ignored", "all"];
38
26
  var TOOL_NAMES = [
39
- "digest",
27
+ "inspect",
40
28
  "edit",
41
29
  "write",
42
30
  "grep",
43
- "view",
31
+ "extract",
44
32
  "search",
45
33
  "def",
46
34
  "refs",
@@ -62,28 +50,24 @@ var PARAM_DESCRIPTIONS = {
62
50
  line: "One-based cursor line; required with scope",
63
51
  renameLine: "One-based cursor line of the symbol to rename",
64
52
  column: "One-based cursor byte column for disambiguation",
65
- cached: "In a Git repository, search tracked/indexed files",
66
- others: "In a Git repository, search untracked files",
67
- ignored: "With others=true, include ignored untracked files",
53
+ gitScope: "In a Git repository, which files to search: tracked, untracked, ignored (untracked plus ignored), or all. Defaults to tracked plus untracked.",
68
54
  select: "Facets: metadata, content, map, diagnostics, identity (default content)",
69
55
  selectComma: "Comma-separated facets: metadata, content, map, diagnostics, identity",
70
- at: "Location: line:N[:COLUMN], hash:HASH[:COLUMN], or symbol:NAME",
56
+ selector: "Positional selector: line:N[:COLUMN], hash:H[:COLUMN], symbol:NAME, page:N, node:ID, or asset:ID. Columns require the identity facet, which in turn rejects symbol selectors",
57
+ assetSelector: "Document asset selector in the form asset:ID",
58
+ query: "KDL selection query, @path, or '-' for standard input",
59
+ stdinName: "File name hint when the target is '-'",
71
60
  end: "Last line to include",
72
61
  limit: "Maximum number of lines to include",
73
- digestDepth: "Maximum structural depth for map output; defaults to 1 in agent tools",
62
+ inspectDepth: "Maximum structural depth for map output; defaults to 1 in agent tools",
74
63
  offset: "One-based starting line",
75
64
  readLimit: "Maximum lines to return",
76
65
  symbolRead: "Symbol name to read",
77
66
  bundle: "Include related symbols from the same file; requires symbol",
78
67
  image: "Standalone image mode: none, all, ocr, caption, objects. Required for standalone images.",
79
- visionModeDigest: "Standalone image mode: none, all, caption, objects, or ocr",
80
- visionModeView: "Analyze selected assets: all, ocr, caption, or objects",
68
+ visionModeInspect: "Standalone image mode: none, all, caption, objects, or ocr",
81
69
  visionLevel: "Vision inference level: low (default), medium, or high",
82
- node: "Node ID to use as the view root",
83
- page: "One-based source page",
84
- kind: "Node kind filter",
85
- depth: "Maximum depth below selected roots",
86
- outline: "Return outline nodes only",
70
+ output: "Destination file for extracted binary data; stdout is not supported",
87
71
  edits: "Use set_line, replace_lines, insert_after, replace_symbol, or replace",
88
72
  editVariant: "Exactly one nested edit variant",
89
73
  setLine: 'Replace one line: {"set_line":{"anchor":"LINE:HASH","new_text":"..."}}',
@@ -120,11 +104,11 @@ var PARAM_DESCRIPTIONS = {
120
104
  scopeContext: "Context lines within each symbol"
121
105
  };
122
106
  var TOOL_DESCRIPTIONS = {
123
- digest: "Digest source content and images into the native CLI envelope. Returns metadata plus requested facets unchanged from `readseek digest`. Use it before edits to obtain fresh `LINE:HASH` anchors; use shallow maps for structural overviews, diagnostics for syntax checks, and identity for cursor context.",
107
+ inspect: "Inspect source content, images, and documents through the native CLI envelope. Use it before edits to obtain fresh `LINE:HASH` anchors; use shallow maps for structural overviews, diagnostics for syntax checks, and identity for cursor context.",
124
108
  edit: 'Edit existing text files with fresh `LINE:HASH` anchors. Read or search first when no fresh anchors are available. Each `edits[]` item must use exactly one nested object: `{"set_line":{"anchor":"LINE:HASH","new_text":"..."}}`, `{"replace_lines":{"start_anchor":"LINE:HASH","end_anchor":"LINE:HASH","new_text":"..."}}`, `{"insert_after":{"anchor":"LINE:HASH","new_text":"..."}}`, `{"replace_symbol":{"symbol":"name","new_body":"..."}}`, or `{"replace":{"old_text":"...","new_text":"..."}}`.',
125
109
  write: "Create or replace a complete text file and return `LINE:HASH` anchors. Use `edit` for small changes to an existing file.",
126
110
  grep: "Search text or regex in files and return edit-ready `LINE:HASH` anchors. Use for identifiers, strings, configuration, errors, comments, and documentation.",
127
- view: "Digest a document's structure or selected content. Start with the overview, then narrow by page, node, kind, or depth instead of reading the whole document. PDF is the first supported document format.",
111
+ extract: "Extract a selected document asset into a destination file. Use an `asset:ID` selector discovered through `inspect`; extracted binary data is never returned on stdout.",
128
112
  search: "Search code by AST structure using ast-grep-style patterns. Results include line hashes usable as `LINE:HASH` anchors. Use `grep` for identifiers, strings, comments, configuration, documentation, or other plain text and regex.",
129
113
  def: "Go to a symbol definition or declaration by name. Accepts qualified or unqualified names; use this instead of text search for functions, methods, classes, types, and other declarations.",
130
114
  refs: "Find references or usages of an identifier by name or cursor binding. Results include enclosing symbols and line hashes usable as `LINE:HASH` anchors. Without cursor scope, results are identifier-name matches; with scope, they follow one binding and exclude shadows. Use text search for literals, and use this before renaming or deleting a symbol.",
@@ -136,18 +120,11 @@ function renderToolDescription(name, aliases = {}) {
136
120
  throw new Error(`Missing TOOL_DESCRIPTIONS for ${name}`);
137
121
  return TOOL_NAMES.reduce((description, toolName) => description.replaceAll(`\`${toolName}\``, `\`${aliases[toolName] ?? toolName}\``), desc);
138
122
  }
139
- var gitFlagsSchema = {
140
- cached: {
141
- type: "boolean",
142
- description: PARAM_DESCRIPTIONS.cached
143
- },
144
- others: {
145
- type: "boolean",
146
- description: PARAM_DESCRIPTIONS.others
147
- },
148
- ignored: {
149
- type: "boolean",
150
- description: PARAM_DESCRIPTIONS.ignored
123
+ var gitScopeSchema = {
124
+ gitScope: {
125
+ type: "string",
126
+ enum: GIT_SCOPES,
127
+ description: PARAM_DESCRIPTIONS.gitScope
151
128
  }
152
129
  };
153
130
  var editItemSchema = {
@@ -213,29 +190,31 @@ var editItemSchema = {
213
190
  };
214
191
  var TOOL_CONTRACTS = [
215
192
  {
216
- name: "digest",
217
- title: "Digest source",
218
- description: TOOL_DESCRIPTIONS.digest,
193
+ name: "inspect",
194
+ title: "Inspect input",
195
+ description: TOOL_DESCRIPTIONS.inspect,
219
196
  inputSchema: {
220
197
  type: "object",
221
198
  properties: {
222
199
  path: { type: "string", description: PARAM_DESCRIPTIONS.path },
200
+ selector: { type: "string", description: PARAM_DESCRIPTIONS.selector },
223
201
  select: { type: "string", description: PARAM_DESCRIPTIONS.select },
224
- at: { type: "string", description: PARAM_DESCRIPTIONS.at },
202
+ query: { type: "string", description: PARAM_DESCRIPTIONS.query },
225
203
  end: { type: "integer", minimum: 1, description: PARAM_DESCRIPTIONS.end },
226
204
  limit: { type: "integer", minimum: 1, description: PARAM_DESCRIPTIONS.limit },
227
- depth: { type: "integer", minimum: 0, description: PARAM_DESCRIPTIONS.digestDepth },
205
+ depth: { type: "integer", minimum: 0, description: PARAM_DESCRIPTIONS.inspectDepth },
228
206
  language: { type: "string", description: PARAM_DESCRIPTIONS.languageOverride },
229
207
  visionMode: {
230
208
  type: "string",
231
209
  enum: [...VISION_MODES],
232
- description: PARAM_DESCRIPTIONS.visionModeDigest
210
+ description: PARAM_DESCRIPTIONS.visionModeInspect
233
211
  },
234
212
  visionLevel: {
235
213
  type: "string",
236
214
  enum: [...VISION_LEVELS],
237
215
  description: PARAM_DESCRIPTIONS.visionLevel
238
- }
216
+ },
217
+ stdinName: { type: "string", description: PARAM_DESCRIPTIONS.stdinName }
239
218
  },
240
219
  required: ["path"],
241
220
  additionalProperties: false
@@ -309,34 +288,27 @@ var TOOL_CONTRACTS = [
309
288
  }
310
289
  },
311
290
  {
312
- name: "view",
313
- title: "View document",
314
- description: TOOL_DESCRIPTIONS.view,
291
+ name: "extract",
292
+ title: "Extract asset",
293
+ description: TOOL_DESCRIPTIONS.extract,
315
294
  inputSchema: {
316
295
  type: "object",
317
296
  properties: {
318
297
  path: { type: "string", description: PARAM_DESCRIPTIONS.path },
319
- node: { type: "string", description: PARAM_DESCRIPTIONS.node },
320
- page: { type: "integer", minimum: 1, description: PARAM_DESCRIPTIONS.page },
321
- kind: {
298
+ selector: {
322
299
  type: "string",
323
- enum: [...DOCUMENT_NODE_KINDS],
324
- description: PARAM_DESCRIPTIONS.kind
300
+ pattern: "^asset:.+",
301
+ description: PARAM_DESCRIPTIONS.assetSelector
325
302
  },
326
- depth: { type: "integer", minimum: 0, description: PARAM_DESCRIPTIONS.depth },
327
- outline: { type: "boolean", description: PARAM_DESCRIPTIONS.outline },
328
- visionMode: {
303
+ output: {
329
304
  type: "string",
330
- enum: [...VISION_ANALYSIS_MODES],
331
- description: PARAM_DESCRIPTIONS.visionModeView
305
+ minLength: 1,
306
+ pattern: "^(?!-$).+",
307
+ description: PARAM_DESCRIPTIONS.output
332
308
  },
333
- visionLevel: {
334
- type: "string",
335
- enum: [...VISION_LEVELS],
336
- description: PARAM_DESCRIPTIONS.visionLevel
337
- }
309
+ stdinName: { type: "string", description: PARAM_DESCRIPTIONS.stdinName }
338
310
  },
339
- required: ["path"],
311
+ required: ["path", "selector", "output"],
340
312
  additionalProperties: false
341
313
  }
342
314
  },
@@ -350,7 +322,8 @@ var TOOL_CONTRACTS = [
350
322
  pattern: { type: "string", description: PARAM_DESCRIPTIONS.pattern },
351
323
  path: { type: "string", description: PARAM_DESCRIPTIONS.searchPath },
352
324
  language: { type: "string", description: PARAM_DESCRIPTIONS.language },
353
- ...gitFlagsSchema
325
+ limit: { type: "integer", minimum: 1, description: PARAM_DESCRIPTIONS.matchLimit },
326
+ ...gitScopeSchema
354
327
  },
355
328
  required: ["pattern"],
356
329
  additionalProperties: false
@@ -366,7 +339,7 @@ var TOOL_CONTRACTS = [
366
339
  name: { type: "string", description: PARAM_DESCRIPTIONS.name },
367
340
  path: { type: "string", description: PARAM_DESCRIPTIONS.searchPath },
368
341
  language: { type: "string", description: PARAM_DESCRIPTIONS.language },
369
- ...gitFlagsSchema
342
+ ...gitScopeSchema
370
343
  },
371
344
  required: ["name"],
372
345
  additionalProperties: false
@@ -385,7 +358,7 @@ var TOOL_CONTRACTS = [
385
358
  scope: { type: "boolean", description: PARAM_DESCRIPTIONS.scope },
386
359
  line: { type: "integer", minimum: 1, description: PARAM_DESCRIPTIONS.line },
387
360
  column: { type: "integer", minimum: 1, description: PARAM_DESCRIPTIONS.column },
388
- ...gitFlagsSchema
361
+ ...gitScopeSchema
389
362
  },
390
363
  additionalProperties: false
391
364
  }
@@ -403,7 +376,9 @@ var TOOL_CONTRACTS = [
403
376
  to: { type: "string", description: PARAM_DESCRIPTIONS.to },
404
377
  workspace: { type: "boolean", description: PARAM_DESCRIPTIONS.workspace },
405
378
  apply: { type: "boolean", description: PARAM_DESCRIPTIONS.apply },
406
- planHash: { type: "string", description: PARAM_DESCRIPTIONS.planHash }
379
+ planHash: { type: "string", description: PARAM_DESCRIPTIONS.planHash },
380
+ language: { type: "string", description: PARAM_DESCRIPTIONS.language },
381
+ ...gitScopeSchema
407
382
  },
408
383
  required: ["path", "line", "to"],
409
384
  additionalProperties: false
@@ -413,7 +388,7 @@ var TOOL_CONTRACTS = [
413
388
  // ../readseek-api/src/policy.ts
414
389
  var REPLACEABLE_TOOLS = [
415
390
  {
416
- tool: "digest",
391
+ tool: "inspect",
417
392
  builtIn: "read",
418
393
  benefit: "it returns LINE:HASH anchors for safe edits."
419
394
  },
@@ -434,8 +409,8 @@ var REPLACEABLE_TOOLS = [
434
409
  }
435
410
  ];
436
411
  var TOOL_COMPACT_GUIDELINES = {
437
- digest: [
438
- "Use `digest` for shallow maps, diagnostics, and identity; use `def`, `refs`, or `search` for targeted symbol questions."
412
+ inspect: [
413
+ "Use `inspect` for shallow maps, diagnostics, and identity; use `def`, `refs`, or `search` for targeted symbol questions."
439
414
  ],
440
415
  edit: [
441
416
  "Prefer set_line, replace_lines, and insert_after; use replace only when anchors are impractical."
@@ -464,7 +439,7 @@ function renderCompactGuidelines(name, aliases = {}) {
464
439
  }
465
440
  function renderToolRoutingPolicy(options = {}) {
466
441
  const aliases = options.aliases ?? {};
467
- const digest = resolveToolName("digest", aliases);
442
+ const inspect = resolveToolName("inspect", aliases);
468
443
  const edit = resolveToolName("edit", aliases);
469
444
  const write = resolveToolName("write", aliases);
470
445
  const grep = resolveToolName("grep", aliases);
@@ -472,11 +447,11 @@ function renderToolRoutingPolicy(options = {}) {
472
447
  const def = resolveToolName("def", aliases);
473
448
  const refs = resolveToolName("refs", aliases);
474
449
  const rename = resolveToolName("rename", aliases);
475
- const view = resolveToolName("view", aliases);
450
+ const extract = resolveToolName("extract", aliases);
476
451
  if (options.style === "server") {
477
452
  return [
478
- "Structural code navigation, document viewing, and hash-verified edits with readseek.",
479
- "For structural questions, prefer these tools to text Grep/Bash: targeted definitions, references, and AST search; shallow digest maps; diagnostics and identity; and document views.",
453
+ "Structural code navigation, document inspection, and hash-verified edits with readseek.",
454
+ "For structural questions, prefer these tools to text Grep/Bash: targeted definitions, references, and AST search; shallow inspect maps; diagnostics and identity; and document inspection.",
480
455
  `Use ${edit} for LINE:HASH-anchored mutations (preview with apply=false; apply defaults to true and re-checks plan_hash).`,
481
456
  "Paths resolve against CLAUDE_PROJECT_DIR. Native Read/Grep/Edit hooks provide passive context;",
482
457
  "call MCP tools for active navigation, document questions, or anchored edits."
@@ -487,7 +462,7 @@ function renderToolRoutingPolicy(options = {}) {
487
462
  if (options.preferOverBuiltIns) {
488
463
  bullets.push("- Prefer ReadSeek tools over built-ins when they can do the job.");
489
464
  }
490
- bullets.push(`- Read with ${digest} first; ${edit} needs fresh LINE:HASH anchors.`, `- Use ${grep} for text/regex, ${search} for AST patterns, ${def}/${refs} for targeted symbols, and shallow ${digest} maps only for file overviews.`, `- Use ${edit} for existing files, ${write} for whole-file creation or replacement, and ${rename} for symbol renames.`, `- Use ${view} for document structure; run ${digest} with select diagnostics after source edits for a quick syntax check.`);
465
+ bullets.push(`- Read with ${inspect} first; ${edit} needs fresh LINE:HASH anchors.`, `- Use ${grep} for text/regex, ${search} for AST patterns, ${def}/${refs} for targeted symbols, and shallow ${inspect} maps only for file overviews.`, `- Use ${edit} for existing files, ${write} for whole-file creation or replacement, and ${rename} for symbol renames.`, `- Use ${inspect} for document structure; run ${inspect} with select diagnostics after source edits for a quick syntax check.`, `- Use ${extract} to save a selected document asset.`);
491
466
  if (options.demoteBuiltIns && options.demoteBuiltIns.length > 0) {
492
467
  bullets.push(`- Do not use built-in ${options.demoteBuiltIns.join(", ")} when a ReadSeek tool can do the job.`);
493
468
  }
@@ -514,12 +489,6 @@ function tryParseLineAnchor(anchor) {
514
489
  }
515
490
 
516
491
  // ../readseek-api/src/validation.ts
517
- function validateGitSelection(input) {
518
- if (input.ignored && !input.others) {
519
- return { ok: false, error: "ignored requires others=true" };
520
- }
521
- return { ok: true };
522
- }
523
492
  function validateRefsNameVsScope(input) {
524
493
  if (input.scope) {
525
494
  if (input.line === undefined) {
@@ -536,11 +505,8 @@ function validateRefsNameVsScope(input) {
536
505
  return { ok: true };
537
506
  }
538
507
  function validateVisionLevelVsMode(input) {
539
- if (input.outline && (input.visionMode !== undefined || input.visionLevel !== undefined)) {
540
- return { ok: false, error: "Cannot combine outline with visionMode or visionLevel" };
541
- }
542
- if (input.visionLevel !== undefined && input.visionMode === undefined) {
543
- return { ok: false, error: "visionLevel requires visionMode" };
508
+ if (input.visionLevel !== undefined && !["all", "ocr", "caption", "objects"].includes(input.visionMode ?? "")) {
509
+ return { ok: false, error: "visionLevel requires an analysis visionMode" };
544
510
  }
545
511
  return { ok: true };
546
512
  }
@@ -611,22 +577,54 @@ function validateExactlyOneEditVariant(edits) {
611
577
  }
612
578
  var MIN_SAFE_INTEGER = BigInt(Number.MIN_SAFE_INTEGER);
613
579
  var MAX_SAFE_INTEGER = BigInt(Number.MAX_SAFE_INTEGER);
580
+ // ../readseek-api/src/edit-plan.ts
581
+ import { Document, Node, format } from "@bgotink/kdl";
582
+ function node(name, properties, children = []) {
583
+ const result = Node.create(name);
584
+ for (const [key, value] of Object.entries(properties))
585
+ result.setProperty(key, value);
586
+ if (children.length > 0)
587
+ result.children = new Document(children);
588
+ return result;
589
+ }
590
+ function editNode(edit) {
591
+ if ("set_line" in edit) {
592
+ const value = edit.set_line;
593
+ return node("set-line", { anchor: value.anchor, text: value.new_text });
594
+ }
595
+ if ("replace_lines" in edit) {
596
+ const value = edit.replace_lines;
597
+ return node("replace-lines", { start: value.start_anchor, end: value.end_anchor, text: value.new_text });
598
+ }
599
+ if ("insert_after" in edit) {
600
+ const value = edit.insert_after;
601
+ return node("insert-after", { anchor: value.anchor, text: value.new_text });
602
+ }
603
+ if ("replace_symbol" in edit) {
604
+ const value = edit.replace_symbol;
605
+ return node("replace-symbol", { symbol: value.symbol, body: value.new_body });
606
+ }
607
+ const value = edit.replace;
608
+ return node("replace", {
609
+ old: value.old_text,
610
+ new: value.new_text,
611
+ ...value.all === undefined ? {} : { all: value.all }
612
+ });
613
+ }
614
+ function serializeEditPlan(edits) {
615
+ const validation = validateExactlyOneEditVariant(edits);
616
+ if (!validation.ok)
617
+ throw new Error(validation.error);
618
+ return format(new Document([node("plan", {}, validation.edits.map(editNode))]));
619
+ }
614
620
  // ../readseek-api/src/argv.ts
615
- var DEFAULT_DIGEST_MAP_DEPTH = 1;
621
+ var DEFAULT_INSPECT_MAP_DEPTH = 1;
616
622
  function buildLineLocation(line, column) {
617
623
  return column === undefined ? `line:${line}` : `line:${line}:${column}`;
618
624
  }
619
- function appendGitSelectionArgs(args, input) {
620
- const validated = validateGitSelection(input);
621
- if (!validated.ok) {
622
- throw new Error(validated.error);
623
- }
624
- if (input.cached)
625
- args.push("--cached");
626
- if (input.others)
627
- args.push("--others");
628
- if (input.ignored)
629
- args.push("--ignored");
625
+ function appendGitScopeArgs(args, input) {
626
+ if (input.gitScope !== undefined)
627
+ args.push("--git-scope", input.gitScope);
630
628
  return args;
631
629
  }
632
630
  function pushOptionalString(args, flag, value) {
@@ -655,33 +653,138 @@ function selectArg(select) {
655
653
  return;
656
654
  return Array.isArray(select) ? select.join(",") : select;
657
655
  }
658
- function selectsDigestMap(select) {
659
- const value = selectArg(select);
660
- return value?.split(",").some((facet) => facet.trim() === "map") ?? false;
656
+ function selectedInspectFacets(select) {
657
+ const facets = (selectArg(select) ?? "content").split(",").map((facet) => facet.trim());
658
+ for (const facet of facets) {
659
+ if (!INSPECT_FACETS.includes(facet)) {
660
+ throw new Error(`unknown inspect facet: ${facet}`);
661
+ }
662
+ }
663
+ return facets;
664
+ }
665
+ function hasInspectFacet(facets, facet) {
666
+ return facets.includes(facet);
667
+ }
668
+ function validateTarget(command, target, stdinName) {
669
+ if (target.length === 0)
670
+ throw new Error(`${command} requires a target file path or '-'`);
671
+ if (stdinName !== undefined && stdinName.length > 0 && target !== "-") {
672
+ throw new Error("stdinName requires '-' as the target");
673
+ }
674
+ }
675
+ function validatePositiveInteger(value, name) {
676
+ if (!Number.isSafeInteger(value) || value < 1) {
677
+ throw new Error(`${name} must be a positive integer`);
678
+ }
679
+ }
680
+ var SELECTOR_KINDS = ["line", "hash", "symbol", "page", "node", "asset"];
681
+ function isSelectorKind(value) {
682
+ return SELECTOR_KINDS.includes(value);
683
+ }
684
+ function parseSelector(text) {
685
+ const separator = text.indexOf(":");
686
+ if (separator < 0) {
687
+ throw new Error("selector must start with line:, hash:, symbol:, page:, node:, or asset:");
688
+ }
689
+ const kind = text.slice(0, separator);
690
+ const value = text.slice(separator + 1);
691
+ if (value.length === 0)
692
+ throw new Error(`${kind} selector must not be empty`);
693
+ if (!isSelectorKind(kind)) {
694
+ throw new Error(`unknown selector kind \`${kind}\`; expected line, hash, symbol, page, node, or asset`);
695
+ }
696
+ if (kind === "line" || kind === "hash") {
697
+ const parts = value.split(":");
698
+ if (parts.length > 2)
699
+ throw new Error("selector has too many components");
700
+ if (parts.length === 2) {
701
+ const column = Number(parts[1]);
702
+ if (!Number.isSafeInteger(column))
703
+ throw new Error(`invalid column \`${parts[1]}\``);
704
+ if (column < 1)
705
+ throw new Error("column must be greater than zero");
706
+ return { kind, value: parts[0], column };
707
+ }
708
+ }
709
+ if (kind === "page") {
710
+ if (value.includes(":"))
711
+ throw new Error("page selector has too many components");
712
+ const page = Number(value);
713
+ if (!Number.isSafeInteger(page))
714
+ throw new Error(`invalid page \`${value}\``);
715
+ if (page < 1)
716
+ throw new Error("page must be greater than zero");
717
+ }
718
+ return { kind, value };
661
719
  }
662
- function buildDigestArgs(input) {
720
+ function buildInspectArgs(input) {
721
+ validateTarget("inspect", input.path, input.stdinName);
722
+ if (input.selector !== undefined && input.selector.length === 0) {
723
+ throw new Error("selector must not be empty");
724
+ }
663
725
  const vision = validateVisionLevelVsMode({
664
726
  visionMode: input.visionMode,
665
727
  visionLevel: input.visionLevel
666
728
  });
667
729
  if (!vision.ok)
668
730
  throw new Error(vision.error);
669
- const selectsMap = selectsDigestMap(input.select);
731
+ const facets = selectedInspectFacets(input.select);
732
+ const hasContent = hasInspectFacet(facets, "content");
733
+ const hasIdentity = hasInspectFacet(facets, "identity");
734
+ const hasMap = hasInspectFacet(facets, "map");
735
+ const hasSourceFacet = hasMap || hasInspectFacet(facets, "diagnostics") || hasIdentity;
736
+ if (input.end !== undefined)
737
+ validatePositiveInteger(input.end, "end");
738
+ if (input.limit !== undefined)
739
+ validatePositiveInteger(input.limit, "limit");
740
+ if (input.end !== undefined && input.limit !== undefined) {
741
+ throw new Error("cannot combine end with limit");
742
+ }
670
743
  if (input.depth !== undefined) {
671
- if (!Number.isInteger(input.depth) || input.depth < 0) {
744
+ if (!Number.isSafeInteger(input.depth) || input.depth < 0) {
672
745
  throw new Error("depth must be a non-negative integer");
673
746
  }
674
- if (!selectsMap)
747
+ if (!hasMap)
675
748
  throw new Error("depth requires the map facet");
676
749
  }
750
+ if (input.selector !== undefined && input.query !== undefined) {
751
+ throw new Error("cannot combine selector with query");
752
+ }
753
+ if (!hasContent && (input.end !== undefined || input.limit !== undefined || input.query !== undefined)) {
754
+ throw new Error("ranges and queries require the content facet");
755
+ }
756
+ if (hasIdentity && input.selector === undefined) {
757
+ throw new Error("the identity facet requires a selector");
758
+ }
759
+ if (input.selector !== undefined && !hasContent && !hasIdentity) {
760
+ throw new Error("selectors require the content or identity facet");
761
+ }
762
+ if (input.selector !== undefined) {
763
+ const selector = parseSelector(input.selector);
764
+ if (selector.kind === "symbol" && hasIdentity) {
765
+ throw new Error("the identity facet does not accept symbol selectors");
766
+ }
767
+ if (selector.column !== undefined && !hasIdentity) {
768
+ throw new Error("selector columns require the identity facet");
769
+ }
770
+ }
771
+ const hasVision = input.visionMode !== undefined || input.visionLevel !== undefined;
772
+ if (hasVision && !hasContent) {
773
+ throw new Error("vision options require the content facet");
774
+ }
775
+ if (hasVision && hasSourceFacet) {
776
+ throw new Error("vision options cannot be combined with source facets");
777
+ }
677
778
  const args = [];
678
779
  if (input.readseekDir !== undefined)
679
780
  args.push("--readseek-dir", input.readseekDir);
680
- args.push("digest", input.path);
781
+ args.push("inspect", input.path);
782
+ if (input.selector !== undefined)
783
+ args.push(input.selector);
681
784
  const select = selectArg(input.select);
682
785
  if (select !== undefined)
683
786
  args.push("--select", select);
684
- pushOptionalString(args, "--at", input.at);
787
+ pushOptionalString(args, "--query", input.query);
685
788
  pushOptionalNumber(args, "--end", input.end);
686
789
  pushOptionalNumber(args, "--limit", input.limit);
687
790
  pushOptionalNumber(args, "--depth", input.depth);
@@ -689,30 +792,24 @@ function buildDigestArgs(input) {
689
792
  pushOptionalString(args, "--vision-mode", input.visionMode);
690
793
  pushOptionalString(args, "--vision-level", input.visionLevel);
691
794
  pushOptionalString(args, "--stdin-name", input.stdinName);
795
+ args.push("--kdl");
692
796
  return args;
693
797
  }
694
- function buildViewArgs(input) {
695
- const vision = validateVisionLevelVsMode({
696
- visionMode: input.visionMode,
697
- visionLevel: input.visionLevel,
698
- outline: input.outline
699
- });
700
- if (!vision.ok)
701
- throw new Error(vision.error);
798
+ function buildExtractArgs(input) {
799
+ validateTarget("extract", input.path, input.stdinName);
800
+ if (input.selector.length === 0)
801
+ throw new Error("extract requires an asset selector");
802
+ if (!input.selector.startsWith("asset:") || input.selector === "asset:") {
803
+ throw new Error("extract requires an asset: selector");
804
+ }
805
+ if (input.output.length === 0 || input.output === "-") {
806
+ throw new Error("extract output must be a file path");
807
+ }
702
808
  const args = [];
703
809
  if (input.readseekDir !== undefined)
704
810
  args.push("--readseek-dir", input.readseekDir);
705
- args.push("view", input.path);
706
- if (input.format !== undefined)
707
- args.push("--format", input.format);
708
- pushOptionalString(args, "--node", input.node);
709
- pushOptionalNumber(args, "--page", input.page);
710
- pushOptionalString(args, "--kind", input.kind);
711
- pushOptionalNumber(args, "--depth", input.depth);
712
- if (input.outline)
713
- args.push("--outline");
714
- pushOptionalString(args, "--vision-mode", input.visionMode);
715
- pushOptionalString(args, "--vision-level", input.visionLevel);
811
+ args.push("--output", input.output, "extract", input.path, input.selector);
812
+ pushOptionalString(args, "--stdin-name", input.stdinName);
716
813
  return args;
717
814
  }
718
815
  function buildSearchArgs(input) {
@@ -720,15 +817,17 @@ function buildSearchArgs(input) {
720
817
  pushOptionalString(args, "--path", input.path);
721
818
  pushOptionalString(args, "--language", input.language);
722
819
  pushOptionalNumber(args, "--limit", input.limit);
723
- return appendGitSelectionArgs(args, input);
820
+ appendGitScopeArgs(args, input);
821
+ args.push("--kdl");
822
+ return args;
724
823
  }
725
824
  function buildDefArgs(input) {
726
825
  const args = ["def", input.name];
727
826
  pushOptionalString(args, "--path", input.path);
728
- if (input.format !== undefined)
729
- args.push("--format", input.format);
730
827
  pushOptionalString(args, "--language", input.language);
731
- return appendGitSelectionArgs(args, input);
828
+ appendGitScopeArgs(args, input);
829
+ args.push("--kdl");
830
+ return args;
732
831
  }
733
832
  function buildRefsArgs(input) {
734
833
  const validated = validateRefsNameVsScope(input);
@@ -743,10 +842,10 @@ function buildRefsArgs(input) {
743
842
  pushOptionalNumber(args, "--line", input.line);
744
843
  pushOptionalNumber(args, "--column", input.column);
745
844
  }
746
- if (input.format !== undefined)
747
- args.push("--format", input.format);
748
845
  pushOptionalString(args, "--language", input.language);
749
- return appendGitSelectionArgs(args, input);
846
+ appendGitScopeArgs(args, input);
847
+ args.push("--kdl");
848
+ return args;
750
849
  }
751
850
  function buildEditArgs(input) {
752
851
  const args = ["edit", input.path];
@@ -758,6 +857,7 @@ function buildEditArgs(input) {
758
857
  pushOptionalString(args, "--language", input.language);
759
858
  if (input.requestFromStdin)
760
859
  args.push("--edits", "-");
860
+ args.push("--kdl");
761
861
  return args;
762
862
  }
763
863
  function buildRenameArgs(input) {
@@ -770,7 +870,9 @@ function buildRenameArgs(input) {
770
870
  flagOrder: input.flagOrder ?? "plan-hash-first"
771
871
  });
772
872
  pushOptionalString(args, "--language", input.language);
773
- return appendGitSelectionArgs(args, input);
873
+ appendGitScopeArgs(args, input);
874
+ args.push("--kdl");
875
+ return args;
774
876
  }
775
877
  // ../readseek-api/src/platform.ts
776
878
  import { accessSync, constants as fsConstants, existsSync, statSync } from "node:fs";
@@ -785,6 +887,8 @@ var READSEEK_PLATFORM_PACKAGES = {
785
887
  };
786
888
  var resolveFromPackage = createRequire(import.meta.url).resolve;
787
889
  function readSeekBinaryName(platform = process.platform) {
890
+ if (platform === "linux")
891
+ return "readseek.AppImage";
788
892
  return platform === "win32" ? "readseek.exe" : "readseek";
789
893
  }
790
894
  function readSeekPlatformKey(platform = process.platform, arch = process.arch) {
@@ -833,22 +937,304 @@ function resolveReadSeekBinaryFromPackages(resolver = resolveFromPackage, option
833
937
  accessSync(binaryPath, fsConstants.X_OK);
834
938
  return binaryPath;
835
939
  }
836
- // ../readseek-api/src/json.ts
837
- var READSEEK_USAGE_HINT = /\n\s*Run readseek(?: [\w-]+)* --help for more information\.?\s*$/;
940
+ // ../readseek-api/src/error.ts
941
+ var READSEEK_USAGE_HINT = /(?:^|\n)\s*Run readseek(?: [\w-]+)* --help for more information\.?\s*$/;
838
942
  function cleanReadSeekErrorText(stderr) {
839
943
  return stderr.replace(READSEEK_USAGE_HINT, "").replace(/^error:\s*/i, "").replace(/\s*\n\s*/g, " ").trim();
840
944
  }
841
- function parseReadSeekJson(stdout) {
842
- try {
843
- return JSON.parse(stdout);
844
- } catch {
845
- const preview = stdout.trim().slice(0, 200);
846
- throw new Error(`readseek returned invalid JSON: ${preview}`);
945
+ // ../readseek-api/src/kdl.ts
946
+ import { parse } from "@bgotink/kdl";
947
+ function convertNode(node) {
948
+ if (node.getTag() !== null)
949
+ throw new Error("KDL node annotations are not supported");
950
+ const properties = new Map;
951
+ for (const entry of node.entries) {
952
+ const name = entry.getName();
953
+ if (name === null)
954
+ throw new Error("KDL positional arguments are not supported");
955
+ if (properties.has(name))
956
+ throw new Error(`KDL duplicate property '${name}'`);
957
+ if (entry.getTag() !== null)
958
+ throw new Error("KDL value annotations are not supported");
959
+ const value = entry.getValue();
960
+ if (value === null)
961
+ throw new Error("KDL null values are not supported");
962
+ if (typeof value === "number" && !Number.isFinite(value)) {
963
+ throw new Error("KDL number out of range");
964
+ }
965
+ properties.set(name, value);
966
+ }
967
+ return {
968
+ name: node.getName(),
969
+ properties,
970
+ children: node.children?.nodes.map(convertNode) ?? []
971
+ };
972
+ }
973
+ function parseKdlDocument(text) {
974
+ const document = parse(text);
975
+ if (document.nodes.length !== 1)
976
+ throw new Error("KDL document must contain exactly one root node");
977
+ return convertNode(document.nodes[0]);
978
+ }
979
+
980
+ // ../readseek-api/src/output-kdl.ts
981
+ function properties(node) {
982
+ return Object.fromEntries(node.properties);
983
+ }
984
+ function children(node, name) {
985
+ return node.children.filter((child) => child.name === name);
986
+ }
987
+ function child(node, name) {
988
+ return node.children.find((candidate) => candidate.name === name);
989
+ }
990
+ function requireProperty(node, name) {
991
+ const value = node.properties.get(name);
992
+ if (value === undefined)
993
+ throw new Error(`invalid readseek KDL: ${node.name} is missing '${name}'`);
994
+ return value;
995
+ }
996
+ function line(node) {
997
+ const anchor = requireProperty(node, "anchor");
998
+ const content = requireProperty(node, "content");
999
+ if (typeof anchor !== "string" || typeof content !== "string") {
1000
+ throw new Error("invalid readseek KDL line");
1001
+ }
1002
+ const match = /^(\d+):([0-9a-f]+)$/.exec(anchor);
1003
+ if (!match)
1004
+ throw new Error(`invalid readseek KDL line anchor: ${anchor}`);
1005
+ return {
1006
+ line: Number(match[1]),
1007
+ hash: match[2],
1008
+ text: content
1009
+ };
1010
+ }
1011
+ function mapNode(node) {
1012
+ return {
1013
+ ...properties(node),
1014
+ children: children(node, "node").map(mapNode)
1015
+ };
1016
+ }
1017
+ function sourceContent(node) {
1018
+ const result = properties(node);
1019
+ result.hashlines = children(node, "line").map(line);
1020
+ const objects = children(node, "object");
1021
+ if (objects.length > 0) {
1022
+ result.objects = objects.map((object) => ({
1023
+ label: requireProperty(object, "label"),
1024
+ bbox: [
1025
+ requireProperty(object, "xmin"),
1026
+ requireProperty(object, "ymin"),
1027
+ requireProperty(object, "xmax"),
1028
+ requireProperty(object, "ymax")
1029
+ ]
1030
+ }));
1031
+ }
1032
+ return result;
1033
+ }
1034
+ function sourceMap(node) {
1035
+ const root = child(node, "node");
1036
+ return {
1037
+ ...properties(node),
1038
+ ...root ? { root: mapNode(root) } : {},
1039
+ symbols: children(node, "symbol").map(properties)
1040
+ };
1041
+ }
1042
+ function diagnostics(node) {
1043
+ return {
1044
+ ...properties(node),
1045
+ diagnostics: children(node, "diagnostic").map(properties)
1046
+ };
1047
+ }
1048
+ function identity(node) {
1049
+ const result = properties(node);
1050
+ const identifier = child(node, "identifier");
1051
+ const symbol = child(node, "symbol");
1052
+ if (identifier)
1053
+ result.identifier = properties(identifier);
1054
+ if (symbol)
1055
+ result.symbol = properties(symbol);
1056
+ return result;
1057
+ }
1058
+ function sourceDocument(root) {
1059
+ const result = {};
1060
+ const metadata = child(root, "metadata");
1061
+ const cache = child(root, "cache");
1062
+ const content = child(root, "content");
1063
+ const map = child(root, "map");
1064
+ const diagnosticOutput = child(root, "diagnostics");
1065
+ const identityOutput = child(root, "identity");
1066
+ if (metadata)
1067
+ result.metadata = properties(metadata);
1068
+ if (cache)
1069
+ result.cache = properties(cache);
1070
+ if (content)
1071
+ result.content = sourceContent(content);
1072
+ if (map)
1073
+ result.map = sourceMap(map);
1074
+ if (diagnosticOutput)
1075
+ result.diagnostics = diagnostics(diagnosticOutput);
1076
+ if (identityOutput)
1077
+ result.identity = identity(identityOutput);
1078
+ return result;
1079
+ }
1080
+ function hit(node) {
1081
+ return {
1082
+ ...properties(node),
1083
+ hashlines: children(node, "line").map(line),
1084
+ captures: children(node, "capture").map((capture) => ({
1085
+ ...properties(capture),
1086
+ hashlines: children(capture, "line").map(line)
1087
+ }))
1088
+ };
1089
+ }
1090
+ function search(root) {
1091
+ return {
1092
+ ...properties(root),
1093
+ results: children(root, "match").map((match) => ({
1094
+ ...properties(match),
1095
+ matches: children(match, "hit").map(hit)
1096
+ })),
1097
+ documents: children(root, "document").map((documentNode) => ({
1098
+ ...properties(documentNode),
1099
+ hits: children(documentNode, "hit").map(properties)
1100
+ }))
1101
+ };
1102
+ }
1103
+ function references(root) {
1104
+ const parsed = children(root, "reference").map((reference) => {
1105
+ const sourceLine = child(reference, "line");
1106
+ if (!sourceLine)
1107
+ throw new Error("invalid readseek KDL reference: missing line");
1108
+ const parsedLine = line(sourceLine);
1109
+ const symbol = child(reference, "symbol");
1110
+ return {
1111
+ ...properties(reference),
1112
+ line_hash: parsedLine.hash,
1113
+ text: parsedLine.text,
1114
+ ...symbol ? { symbol: properties(symbol) } : {}
1115
+ };
1116
+ });
1117
+ return {
1118
+ references: parsed,
1119
+ locations: parsed.map((reference) => {
1120
+ const { symbol, ...location } = reference;
1121
+ return symbol && typeof symbol === "object" ? { ...location, ...symbol } : location;
1122
+ })
1123
+ };
1124
+ }
1125
+ function definitions(root) {
1126
+ return {
1127
+ locations: children(root, "definition").map((definition) => {
1128
+ const symbol = child(definition, "symbol");
1129
+ const sourceLine = child(definition, "line");
1130
+ if (!symbol || !sourceLine) {
1131
+ throw new Error("invalid readseek KDL definition: missing symbol or line");
1132
+ }
1133
+ const symbolProperties = properties(symbol);
1134
+ const parsedLine = line(sourceLine);
1135
+ const definitionColumn = definition.properties.get("column");
1136
+ const nameByte = symbol.properties.get("name_byte");
1137
+ const startByte = symbol.properties.get("start_byte");
1138
+ let column = 1;
1139
+ if (typeof definitionColumn === "number") {
1140
+ column = definitionColumn;
1141
+ } else if (typeof nameByte === "number" && typeof startByte === "number") {
1142
+ column = nameByte - startByte + 1;
1143
+ }
1144
+ return {
1145
+ file: requireProperty(definition, "file"),
1146
+ line: parsedLine.line,
1147
+ column,
1148
+ line_hash: parsedLine.hash,
1149
+ text: parsedLine.text,
1150
+ kind: symbolProperties.kind,
1151
+ name: symbolProperties.name,
1152
+ qualified_name: symbolProperties.qualified_name
1153
+ };
1154
+ })
1155
+ };
1156
+ }
1157
+ function edit(root) {
1158
+ const result = properties(root);
1159
+ for (const name of ["before_content", "content"]) {
1160
+ const contentNode = child(root, name);
1161
+ if (contentNode)
1162
+ result[name] = requireProperty(contentNode, "content");
1163
+ }
1164
+ return result;
1165
+ }
1166
+ function renameEdit(node) {
1167
+ const sourceLine = child(node, "line");
1168
+ if (!sourceLine)
1169
+ throw new Error("invalid readseek KDL rename edit: missing line");
1170
+ return {
1171
+ ...properties(node),
1172
+ text: requireProperty(sourceLine, "content")
1173
+ };
1174
+ }
1175
+ function renameFile(node) {
1176
+ return {
1177
+ ...properties(node),
1178
+ edits: children(node, "edit").map(renameEdit),
1179
+ conflicts: children(node, "conflict").map(properties)
1180
+ };
1181
+ }
1182
+ function rename(root) {
1183
+ return {
1184
+ ...renameFile(root),
1185
+ others: children(root, "other").map(renameFile)
1186
+ };
1187
+ }
1188
+ function genericNode(node) {
1189
+ const result = properties(node);
1190
+ const textNode = child(node, "text");
1191
+ const bbox = child(node, "bbox");
1192
+ if (textNode)
1193
+ result.text = requireProperty(textNode, "content");
1194
+ if (bbox)
1195
+ result.bbox = properties(bbox);
1196
+ const objects = children(node, "object");
1197
+ if (objects.length > 0)
1198
+ result.objects = objects.map(genericNode);
1199
+ return result;
1200
+ }
1201
+ function pdfDocument(root) {
1202
+ return {
1203
+ ...properties(root),
1204
+ nodes: children(root, "node").map(genericNode),
1205
+ assets: children(root, "asset").map(genericNode),
1206
+ vision: children(root, "vision").map(genericNode)
1207
+ };
1208
+ }
1209
+ function parseReadSeekKdl(text) {
1210
+ const root = parseKdlDocument(text);
1211
+ switch (root.name) {
1212
+ case "document":
1213
+ return root.properties.has("format") ? pdfDocument(root) : sourceDocument(root);
1214
+ case "search":
1215
+ return search(root);
1216
+ case "check":
1217
+ return diagnostics(root);
1218
+ case "refs":
1219
+ return references(root);
1220
+ case "def":
1221
+ return definitions(root);
1222
+ case "edit":
1223
+ return edit(root);
1224
+ case "edit-batch":
1225
+ return { ...properties(root), files: children(root, "file").map(edit) };
1226
+ case "rename":
1227
+ return rename(root);
1228
+ default:
1229
+ throw new Error(`unsupported readseek KDL root: ${root.name}`);
847
1230
  }
848
1231
  }
849
1232
  // ../readseek-api/src/process.ts
850
1233
  var DEFAULT_READSEEK_TIMEOUT_MS = 120000;
851
1234
  var DEFAULT_MAX_OUTPUT_BYTES = 32 * 1024 * 1024;
1235
+ function readSeekProcessEnvironment() {
1236
+ return process.platform === "linux" ? { ...process.env, APPIMAGE_EXTRACT_AND_RUN: "1" } : process.env;
1237
+ }
852
1238
  function resolveProcessLimits(options = {}) {
853
1239
  return {
854
1240
  timeoutMs: options.timeoutMs ?? DEFAULT_READSEEK_TIMEOUT_MS,
@@ -879,9 +1265,6 @@ function ensureSuccessfulStdout(result) {
879
1265
  return result.stdout;
880
1266
  throw failureFromProcessResult(result);
881
1267
  }
882
- function parseSuccessfulJson(result) {
883
- return parseReadSeekJson(ensureSuccessfulStdout(result));
884
- }
885
1268
  // ../readseek-api/src/process-node.ts
886
1269
  import { spawn } from "node:child_process";
887
1270
  import { StringDecoder } from "node:string_decoder";
@@ -947,7 +1330,7 @@ async function runReadSeekNode(binaryPath, args, options = {}) {
947
1330
  try {
948
1331
  child = spawn(binaryPath, [...args], {
949
1332
  cwd: options.cwd,
950
- env: process.env,
1333
+ env: readSeekProcessEnvironment(),
951
1334
  shell: false,
952
1335
  stdio: [options.stdin === undefined ? "ignore" : "pipe", "pipe", "pipe"],
953
1336
  windowsHide: true
@@ -990,363 +1373,357 @@ async function runReadSeekNode(binaryPath, args, options = {}) {
990
1373
  async function runReadSeekNodeText(binaryPath, args, options = {}) {
991
1374
  return ensureSuccessfulStdout(await runReadSeekNode(binaryPath, args, options));
992
1375
  }
993
- async function runReadSeekNodeJson(binaryPath, args, options = {}) {
994
- return parseSuccessfulJson(await runReadSeekNode(binaryPath, args, options));
995
- }
996
- // ../readseek-api/src/client.ts
997
- function classifyReadSeekFailure(err) {
998
- const failure = err;
999
- const message = String(failure?.message ?? err);
1000
- const missing = failure?.code === "ENOENT" || /Cannot find package|Cannot find module|no such file/i.test(message);
1001
- if (missing) {
1002
- return { code: "readseek-not-installed", message, hint: "Run npm install to install @jarkkojs/readseek." };
1003
- }
1004
- return { code: "readseek-execution-error", message };
1005
- }
1006
- function requireNumber(value, field) {
1007
- if (typeof value !== "number" || !Number.isSafeInteger(value))
1008
- throw new Error(`invalid readseek ${field}: expected safe integer`);
1009
- return value;
1010
- }
1011
- function requireString(value, field) {
1012
- if (typeof value !== "string")
1013
- throw new Error(`invalid readseek ${field}`);
1014
- return value;
1015
- }
1016
- function requireBoolean(value, field) {
1017
- if (typeof value !== "boolean")
1018
- throw new Error(`invalid readseek ${field}: expected boolean`);
1019
- return value;
1020
- }
1021
- function optionalString(value, field) {
1022
- if (value === undefined || value === null)
1023
- return;
1024
- return requireString(value, field);
1025
- }
1026
- function parseHashline(value, field) {
1027
- if (!value || typeof value !== "object")
1028
- throw new Error(`invalid readseek ${field}`);
1029
- const item = value;
1030
- return {
1031
- line: requireNumber(item.line, `${field}.line`),
1032
- hash: requireString(item.hash, `${field}.hash`),
1033
- text: requireString(item.text, `${field}.text`)
1034
- };
1035
- }
1036
- function parseSearchHashlines(value, field) {
1037
- if (!Array.isArray(value))
1038
- throw new Error(`invalid readseek ${field}`);
1039
- return value.map((line) => parseHashline(line, field));
1040
- }
1376
+ // ../readseek-api/src/client-output.ts
1377
+ import { Type } from "@sinclair/typebox";
1378
+ import { AssertError, Value } from "@sinclair/typebox/value";
1379
+ var OPERATIONS = ["Clone", "Clean", "Default", "Assert"];
1380
+ function parse2(schema, value, what) {
1381
+ try {
1382
+ return Value.Parse(OPERATIONS, schema, value);
1383
+ } catch (error) {
1384
+ if (!(error instanceof AssertError))
1385
+ throw error;
1386
+ const detail = error.error;
1387
+ throw new Error(`invalid readseek ${what}${detail?.path ?? ""}: ${detail?.message ?? "invalid value"}`);
1388
+ }
1389
+ }
1390
+ var Int = Type.Integer({ minimum: Number.MIN_SAFE_INTEGER, maximum: Number.MAX_SAFE_INTEGER });
1391
+ var Str = Type.String();
1392
+ var Bool = Type.Boolean();
1393
+ var OptStr = Type.Optional(Str);
1394
+ var OptInt = Type.Optional(Int);
1395
+ var Hashline = Type.Object({ line: Int, hash: Str, text: Str });
1396
+ var Hashlines = Type.Array(Hashline);
1397
+ var ReadOutput = Type.Object({
1398
+ file: Str,
1399
+ language: Str,
1400
+ line_count: Int,
1401
+ file_hash: Str,
1402
+ start_line: Int,
1403
+ end_line: Int,
1404
+ hashlines: Hashlines
1405
+ });
1406
+ var SymbolOutput = Type.Object({
1407
+ kind: Str,
1408
+ name: Str,
1409
+ qualified_name: OptStr,
1410
+ start_line: Int,
1411
+ end_line: Int,
1412
+ start_hash: OptStr,
1413
+ end_hash: OptStr,
1414
+ start_anchor: OptStr,
1415
+ end_anchor: OptStr
1416
+ });
1417
+ var MapNode = Type.Recursive((Self) => Type.Object({
1418
+ kind: Str,
1419
+ name: Str,
1420
+ start_line: Int,
1421
+ end_line: Int,
1422
+ start_anchor: Str,
1423
+ end_anchor: Str,
1424
+ children: Type.Array(Self)
1425
+ }));
1426
+ var MapOutput = Type.Object({
1427
+ file: Str,
1428
+ language: Str,
1429
+ line_count: Int,
1430
+ file_hash: Str,
1431
+ root: Type.Optional(MapNode),
1432
+ symbols: Type.Array(SymbolOutput)
1433
+ });
1434
+ var SearchSpan = {
1435
+ start_byte: OptInt,
1436
+ end_byte: OptInt,
1437
+ text: OptStr,
1438
+ start_line: Int,
1439
+ end_line: Int,
1440
+ start_hash: Str,
1441
+ end_hash: Str,
1442
+ hashlines: Hashlines
1443
+ };
1444
+ var SearchCapture = Type.Object({ name: Str, ...SearchSpan });
1445
+ var SearchMatch = Type.Object({
1446
+ pattern_index: Type.Integer({ default: 0 }),
1447
+ ...SearchSpan,
1448
+ captures: Type.Array(SearchCapture)
1449
+ });
1450
+ var SearchFileOutput = Type.Object({
1451
+ file: Str,
1452
+ language: Str,
1453
+ file_hash: Str,
1454
+ matches: Type.Array(SearchMatch)
1455
+ });
1456
+ var SearchEnvelope = Type.Object({
1457
+ results: Type.Array(SearchFileOutput),
1458
+ returned_matches: OptInt,
1459
+ truncated: Type.Optional(Bool),
1460
+ byte_basis: OptStr
1461
+ });
1462
+ var RefsEnvelope = Type.Object({
1463
+ references: Type.Array(Type.Object({
1464
+ file: Str,
1465
+ line: Int,
1466
+ column: Int,
1467
+ line_hash: Str,
1468
+ text: Str,
1469
+ symbol: Type.Optional(Type.Object({ qualified_name: OptStr }))
1470
+ }))
1471
+ });
1472
+ var Diagnostic = Type.Object({
1473
+ kind: Type.Union([Type.Literal("error"), Type.Literal("missing")]),
1474
+ start_line: Int,
1475
+ end_line: Int
1476
+ });
1477
+ var CheckEnvelope = Type.Object({
1478
+ file_hash: OptStr,
1479
+ error_count: Int,
1480
+ missing_count: Int,
1481
+ diagnostics: Type.Array(Diagnostic)
1482
+ });
1483
+ var DetectedObject = Type.Object({
1484
+ label: Str,
1485
+ bbox: Type.Tuple([Int, Int, Int, Int])
1486
+ });
1487
+ var PreparedImage = Type.Object({
1488
+ mime: Str,
1489
+ encoding: Type.Literal("base64"),
1490
+ data: Str
1491
+ });
1492
+ var SourceDetection = Type.Object({
1493
+ kind: Type.Literal("source"),
1494
+ type: Str,
1495
+ file: Str,
1496
+ language: Str,
1497
+ engine: OptStr,
1498
+ supported: Bool,
1499
+ mime: OptStr,
1500
+ syntax: OptStr
1501
+ });
1502
+ var ImageDetection = Type.Object({
1503
+ kind: Type.Literal("image"),
1504
+ type: Str,
1505
+ file: Str,
1506
+ mime: OptStr,
1507
+ format: Str,
1508
+ width: Int,
1509
+ height: Int,
1510
+ animated: Bool,
1511
+ encoding: Type.Optional(Type.Literal("base64")),
1512
+ data: OptStr,
1513
+ ocr: OptStr,
1514
+ caption: OptStr,
1515
+ objects: Type.Optional(Type.Array(DetectedObject))
1516
+ });
1517
+ var TextDetection = Type.Object({
1518
+ kind: Type.Literal("text"),
1519
+ type: Str,
1520
+ file: Str,
1521
+ mime: OptStr
1522
+ });
1523
+ var PdfDetection = Type.Object({
1524
+ kind: Type.Literal("pdf"),
1525
+ type: Type.Literal("application/pdf"),
1526
+ file: Str,
1527
+ mime: OptStr,
1528
+ format: Type.Literal("pdf"),
1529
+ pages: Int
1530
+ });
1531
+ var Detection = Type.Union([SourceDetection, ImageDetection, TextDetection, PdfDetection]);
1532
+ var SourceDetectionInput = Type.Omit(SourceDetection, ["kind"]);
1533
+ var ImageDetectionInput = Type.Omit(ImageDetection, ["kind"]);
1534
+ var TextDetectionInput = Type.Omit(TextDetection, ["kind"]);
1535
+ var PdfDetectionInput = Type.Omit(PdfDetection, ["kind"]);
1536
+ var UnspannedContent = Type.Omit(ReadOutput, ["start_line", "end_line"]);
1537
+ var PdfInspectMetadata = Type.Object({ source: Str, format: Type.Literal("pdf"), pages: Int });
1538
+ var DocumentCache = Type.Object({
1539
+ content_hash: Str,
1540
+ state: Type.Union([Type.Literal("built"), Type.Literal("reused")])
1541
+ });
1542
+ var Identifier = Type.Object({
1543
+ text: Str,
1544
+ start_column: Int,
1545
+ end_column: Int,
1546
+ start_byte: Int,
1547
+ end_byte: Int
1548
+ });
1549
+ var IdentifyEnvelope = Type.Object({
1550
+ file: Str,
1551
+ language: Str,
1552
+ engine: OptStr,
1553
+ line_count: Int,
1554
+ file_hash: Str,
1555
+ line: Int,
1556
+ column: Int,
1557
+ line_hash: Str,
1558
+ identifier: Type.Optional(Identifier),
1559
+ symbol: Type.Optional(Type.Object({
1560
+ name: Str,
1561
+ kind: Str,
1562
+ qualified_name: Str,
1563
+ start_line: Int,
1564
+ end_line: Int
1565
+ }))
1566
+ });
1567
+ var EditOutputSchema = Type.Object({
1568
+ file: Str,
1569
+ before_hash: Str,
1570
+ file_hash: Str,
1571
+ plan_hash: Str,
1572
+ applied: Bool,
1573
+ changed: Bool,
1574
+ edit_count: Int,
1575
+ before_content: OptStr,
1576
+ content: OptStr
1577
+ });
1578
+ var RenameConflictSchema = Type.Object({ line: Int, column: Int, reason: Str });
1579
+ var RenameEditSchema = Type.Object({
1580
+ line: Int,
1581
+ start_column: Int,
1582
+ end_column: Int,
1583
+ start_byte: Int,
1584
+ end_byte: Int,
1585
+ occurrence: Str,
1586
+ line_hash: Str,
1587
+ text: Str
1588
+ });
1589
+ var Conflicts = Type.Array(RenameConflictSchema, { default: [] });
1590
+ var Edits = Type.Array(RenameEditSchema, { default: [] });
1591
+ var RenameFileOutputSchema = Type.Object({
1592
+ file: Str,
1593
+ language: Str,
1594
+ engine: OptStr,
1595
+ file_hash: Str,
1596
+ conflicts: Conflicts,
1597
+ edits: Edits
1598
+ });
1599
+ var RenameOutputSchema = Type.Object({
1600
+ file: Str,
1601
+ language: Str,
1602
+ engine: OptStr,
1603
+ file_hash: Str,
1604
+ old_name: Str,
1605
+ new_name: Str,
1606
+ plan_hash: Str,
1607
+ applied: Bool,
1608
+ conflicts: Conflicts,
1609
+ edits: Edits,
1610
+ others: Type.Array(RenameFileOutputSchema, { default: [] })
1611
+ });
1612
+ var DefEnvelope = Type.Object({
1613
+ locations: Type.Array(Type.Object({
1614
+ file: Str,
1615
+ line: Int,
1616
+ column: Int,
1617
+ line_hash: Str,
1618
+ text: Str,
1619
+ kind: OptStr,
1620
+ name: OptStr,
1621
+ qualified_name: OptStr
1622
+ }))
1623
+ });
1041
1624
  function parseReadOutput(value) {
1042
- if (!value || typeof value !== "object")
1043
- throw new Error("invalid readseek read output");
1044
- const output = value;
1045
- const hashlines = output.hashlines;
1046
- if (!Array.isArray(hashlines))
1047
- throw new Error("invalid readseek hashlines");
1048
- return {
1049
- file: requireString(output.file, "file"),
1050
- language: requireString(output.language, "language"),
1051
- line_count: requireNumber(output.line_count, "line_count"),
1052
- file_hash: requireString(output.file_hash, "file_hash"),
1053
- start_line: requireNumber(output.start_line, "start_line"),
1054
- end_line: requireNumber(output.end_line, "end_line"),
1055
- hashlines: hashlines.map((line) => parseHashline(line, "hashline"))
1056
- };
1057
- }
1058
- function parseMapNode(value) {
1059
- if (!value || typeof value !== "object")
1060
- throw new Error("invalid readseek map node");
1061
- const node = value;
1062
- if (!Array.isArray(node.children))
1063
- throw new Error("invalid readseek map node children");
1064
- return {
1065
- kind: requireString(node.kind, "map node.kind"),
1066
- name: requireString(node.name, "map node.name"),
1067
- start_line: requireNumber(node.start_line, "map node.start_line"),
1068
- end_line: requireNumber(node.end_line, "map node.end_line"),
1069
- start_anchor: requireString(node.start_anchor, "map node.start_anchor"),
1070
- end_anchor: requireString(node.end_anchor, "map node.end_anchor"),
1071
- children: node.children.map(parseMapNode)
1072
- };
1625
+ return parse2(ReadOutput, value, "read output");
1073
1626
  }
1074
1627
  function parseMapOutput(value) {
1075
- if (!value || typeof value !== "object")
1076
- throw new Error("invalid readseek map output");
1077
- const output = value;
1078
- const symbols = output.symbols;
1079
- if (!Array.isArray(symbols))
1080
- throw new Error("invalid readseek symbols");
1081
- return {
1082
- file: requireString(output.file, "file"),
1083
- language: requireString(output.language, "language"),
1084
- line_count: requireNumber(output.line_count, "line_count"),
1085
- file_hash: requireString(output.file_hash, "file_hash"),
1086
- root: parseMapNode(output.root),
1087
- symbols: symbols.map((symbol) => {
1088
- if (!symbol || typeof symbol !== "object")
1089
- throw new Error("invalid readseek symbol");
1090
- const item = symbol;
1091
- return {
1092
- kind: requireString(item.kind, "symbol.kind"),
1093
- name: requireString(item.name, "symbol.name"),
1094
- qualified_name: requireString(item.qualified_name, "symbol.qualified_name"),
1095
- start_line: requireNumber(item.start_line, "symbol.start_line"),
1096
- end_line: requireNumber(item.end_line, "symbol.end_line"),
1097
- start_hash: requireString(item.start_hash, "symbol.start_hash"),
1098
- end_hash: requireString(item.end_hash, "symbol.end_hash")
1099
- };
1100
- })
1101
- };
1628
+ return parse2(MapOutput, value, "map output");
1102
1629
  }
1103
1630
  function parseSearchOutput(value) {
1104
- if (!value || typeof value !== "object")
1105
- throw new Error("invalid readseek search output");
1106
- const output = value;
1107
- if (!Array.isArray(output.results))
1108
- throw new Error("invalid readseek search results");
1109
- return output.results.map((result) => {
1110
- if (!result || typeof result !== "object")
1111
- throw new Error("invalid readseek search result");
1112
- const file = result;
1113
- if (!Array.isArray(file.matches))
1114
- throw new Error("invalid readseek search matches");
1115
- return {
1116
- file: requireString(file.file, "search.file"),
1117
- language: requireString(file.language, "search.language"),
1118
- file_hash: requireString(file.file_hash, "search.file_hash"),
1119
- matches: file.matches.map((match) => {
1120
- if (!match || typeof match !== "object")
1121
- throw new Error("invalid readseek search match");
1122
- const item = match;
1123
- if (!Array.isArray(item.captures))
1124
- throw new Error("invalid readseek search captures");
1125
- return {
1126
- pattern_index: item.pattern_index === undefined ? 0 : requireNumber(item.pattern_index, "search.match.pattern_index"),
1127
- start_line: requireNumber(item.start_line, "search.match.start_line"),
1128
- end_line: requireNumber(item.end_line, "search.match.end_line"),
1129
- start_hash: requireString(item.start_hash, "search.match.start_hash"),
1130
- end_hash: requireString(item.end_hash, "search.match.end_hash"),
1131
- hashlines: parseSearchHashlines(item.hashlines, "search.match.hashlines"),
1132
- captures: item.captures.map((capture) => {
1133
- if (!capture || typeof capture !== "object")
1134
- throw new Error("invalid readseek search capture");
1135
- const captureItem = capture;
1136
- return {
1137
- name: requireString(captureItem.name, "search.capture.name"),
1138
- start_line: requireNumber(captureItem.start_line, "search.capture.start_line"),
1139
- end_line: requireNumber(captureItem.end_line, "search.capture.end_line"),
1140
- start_hash: requireString(captureItem.start_hash, "search.capture.start_hash"),
1141
- end_hash: requireString(captureItem.end_hash, "search.capture.end_hash"),
1142
- hashlines: parseSearchHashlines(captureItem.hashlines, "search.capture.hashlines")
1143
- };
1144
- })
1145
- };
1146
- })
1147
- };
1148
- });
1631
+ const envelope = parse2(SearchEnvelope, value, "search output");
1632
+ const results = envelope.results;
1633
+ if (envelope.returned_matches !== undefined)
1634
+ results.returned_matches = envelope.returned_matches;
1635
+ if (envelope.truncated !== undefined)
1636
+ results.truncated = envelope.truncated;
1637
+ if (envelope.byte_basis !== undefined)
1638
+ results.byte_basis = envelope.byte_basis;
1639
+ return results;
1149
1640
  }
1150
1641
  function parseRefsOutput(value) {
1151
- if (!value || typeof value !== "object")
1152
- throw new Error("invalid readseek refs output");
1153
- const output = value;
1154
- if (!Array.isArray(output.references))
1155
- throw new Error("invalid readseek references");
1156
- return output.references.map((reference) => {
1157
- if (!reference || typeof reference !== "object")
1158
- throw new Error("invalid readseek reference");
1159
- const item = reference;
1160
- const symbol = item.symbol;
1161
- const enclosing = symbol && typeof symbol === "object" ? optionalString(symbol.qualified_name, "reference.symbol.qualified_name") : undefined;
1162
- return {
1163
- file: requireString(item.file, "reference.file"),
1164
- line: requireNumber(item.line, "reference.line"),
1165
- column: requireNumber(item.column, "reference.column"),
1166
- line_hash: requireString(item.line_hash, "reference.line_hash"),
1167
- text: requireString(item.text, "reference.text"),
1168
- enclosingSymbol: enclosing
1169
- };
1170
- });
1171
- }
1172
- function parseDiagnosticKind(value) {
1173
- if (value === "error" || value === "missing")
1174
- return value;
1175
- throw new Error("invalid readseek diagnostic.kind");
1642
+ return parse2(RefsEnvelope, value, "refs output").references.map(({ symbol, ...reference }) => ({
1643
+ ...reference,
1644
+ enclosingSymbol: symbol?.qualified_name
1645
+ }));
1176
1646
  }
1177
1647
  function parseCheckOutput(value) {
1178
- if (!value || typeof value !== "object")
1179
- throw new Error("invalid readseek check output");
1180
- const output = value;
1181
- if (!Array.isArray(output.diagnostics))
1182
- throw new Error("invalid readseek diagnostics");
1648
+ const output = parse2(CheckEnvelope, value, "check output");
1183
1649
  return {
1184
- file_hash: optionalString(output.file_hash, "file_hash"),
1185
- errorCount: requireNumber(output.error_count, "error_count"),
1186
- missingCount: requireNumber(output.missing_count, "missing_count"),
1187
- diagnostics: output.diagnostics.map((diagnostic) => {
1188
- if (!diagnostic || typeof diagnostic !== "object")
1189
- throw new Error("invalid readseek diagnostic");
1190
- const item = diagnostic;
1191
- return {
1192
- kind: parseDiagnosticKind(item.kind),
1193
- start_line: requireNumber(item.start_line, "diagnostic.start_line"),
1194
- end_line: requireNumber(item.end_line, "diagnostic.end_line")
1195
- };
1196
- })
1650
+ file_hash: output.file_hash,
1651
+ errorCount: output.error_count,
1652
+ missingCount: output.missing_count,
1653
+ diagnostics: output.diagnostics
1197
1654
  };
1198
1655
  }
1199
- function parseDetectedObjects(value) {
1200
- if (value === undefined || value === null)
1201
- return;
1202
- if (!Array.isArray(value))
1203
- throw new Error("invalid readseek detect objects");
1204
- return value.map((object) => {
1205
- if (!object || typeof object !== "object")
1206
- throw new Error("invalid readseek detect object");
1207
- const item = object;
1208
- const bbox = item.bbox;
1209
- if (!Array.isArray(bbox) || bbox.length !== 4)
1210
- throw new Error("invalid readseek detect object.bbox");
1211
- return {
1212
- label: requireString(item.label, "object.label"),
1213
- bbox: bbox.map((n, i) => requireNumber(n, `object.bbox[${i}]`))
1214
- };
1215
- });
1216
- }
1217
- function parseImageEncoding(value) {
1218
- if (value === "base64")
1219
- return value;
1220
- throw new Error("invalid image encoding");
1221
- }
1222
1656
  function parseDetectOutput(value) {
1223
- if (!value || typeof value !== "object")
1224
- throw new Error("invalid readseek detect output");
1225
- const output = value;
1226
- const type = requireString(output.type, "type");
1227
- const file = requireString(output.file, "file");
1228
- const mime = optionalString(output.mime, "mime");
1229
- if (type === "application/pdf") {
1230
- if (output.format !== "pdf")
1231
- throw new Error("invalid readseek PDF format");
1232
- return {
1233
- kind: "pdf",
1234
- type,
1235
- file,
1236
- mime,
1237
- format: output.format,
1238
- pages: requireNumber(output.pages, "pages")
1239
- };
1657
+ const output = value ?? {};
1658
+ if (output.type === "application/pdf") {
1659
+ return { kind: "pdf", ...parse2(PdfDetectionInput, value, "detect output") };
1240
1660
  }
1241
1661
  if (output.width !== undefined || output.height !== undefined) {
1242
- return {
1243
- kind: "image",
1244
- type,
1245
- file,
1246
- mime,
1247
- format: requireString(output.format, "format"),
1248
- width: requireNumber(output.width, "width"),
1249
- height: requireNumber(output.height, "height"),
1250
- animated: requireBoolean(output.animated, "animated"),
1251
- encoding: output.encoding === undefined ? undefined : parseImageEncoding(output.encoding),
1252
- data: output.data === undefined ? undefined : requireString(output.data, "data"),
1253
- ocr: optionalString(output.ocr, "ocr"),
1254
- caption: optionalString(output.caption, "caption"),
1255
- objects: parseDetectedObjects(output.objects)
1256
- };
1662
+ return { kind: "image", ...parse2(ImageDetectionInput, value, "detect output") };
1257
1663
  }
1258
1664
  if (output.language !== undefined) {
1259
- return {
1260
- kind: "source",
1261
- type,
1262
- file,
1263
- language: requireString(output.language, "language"),
1264
- engine: optionalString(output.engine, "engine"),
1265
- supported: requireBoolean(output.supported, "supported"),
1266
- mime,
1267
- syntax: optionalString(output.syntax, "syntax")
1268
- };
1665
+ return { kind: "source", ...parse2(SourceDetectionInput, value, "detect output") };
1269
1666
  }
1270
- return { kind: "text", type, file, mime };
1271
- }
1272
- function parseDocumentCache(value) {
1273
- if (!value || typeof value !== "object")
1274
- throw new Error("invalid readseek document cache");
1275
- const cache = value;
1276
- const state = requireString(cache.state, "cache.state");
1277
- if (state !== "built" && state !== "reused")
1278
- throw new Error("invalid readseek cache.state");
1279
- return {
1280
- content_hash: requireString(cache.content_hash, "cache.content_hash"),
1281
- state
1282
- };
1283
- }
1284
- function parseIdentifyOutput(value) {
1285
- if (!value || typeof value !== "object")
1286
- throw new Error("invalid readseek identify output");
1287
- const output = value;
1288
- const identifier = output.identifier;
1289
- const symbol = output.symbol;
1290
- return {
1291
- file: requireString(output.file, "file"),
1292
- language: requireString(output.language, "language"),
1293
- engine: optionalString(output.engine, "engine"),
1294
- line_count: requireNumber(output.line_count, "line_count"),
1295
- file_hash: requireString(output.file_hash, "file_hash"),
1296
- line: requireNumber(output.line, "line"),
1297
- column: requireNumber(output.column, "column"),
1298
- line_hash: requireString(output.line_hash, "line_hash"),
1299
- identifier: identifier && typeof identifier === "object" ? {
1300
- text: requireString(identifier.text, "identifier.text"),
1301
- start_column: requireNumber(identifier.start_column, "identifier.start_column"),
1302
- end_column: requireNumber(identifier.end_column, "identifier.end_column"),
1303
- start_byte: requireNumber(identifier.start_byte, "identifier.start_byte"),
1304
- end_byte: requireNumber(identifier.end_byte, "identifier.end_byte")
1305
- } : undefined,
1306
- symbol: symbol && typeof symbol === "object" ? {
1307
- name: requireString(symbol.name, "symbol.name"),
1308
- kind: requireString(symbol.kind, "symbol.kind"),
1309
- qualified_name: requireString(symbol.qualified_name, "symbol.qualified_name"),
1310
- start_line: requireNumber(symbol.start_line, "symbol.start_line"),
1311
- end_line: requireNumber(symbol.end_line, "symbol.end_line")
1312
- } : undefined
1313
- };
1667
+ return { kind: "text", ...parse2(TextDetectionInput, value, "detect output") };
1314
1668
  }
1315
1669
  function parseImageContent(value) {
1316
1670
  const detection = parseDetectOutput(value);
1317
1671
  if (detection.kind !== "image")
1318
- throw new Error("invalid readseek image content");
1672
+ throw new Error("invalid readseek image content: expected an image");
1319
1673
  return detection;
1320
1674
  }
1321
1675
  function parseTextContent(value) {
1676
+ const output = value ?? {};
1677
+ if (output.hashlines === undefined || output.start_line !== undefined)
1678
+ return parseReadOutput(value);
1679
+ const spanned = parse2(UnspannedContent, value, "content");
1680
+ return {
1681
+ ...spanned,
1682
+ start_line: spanned.hashlines[0]?.line ?? 1,
1683
+ end_line: spanned.hashlines[spanned.hashlines.length - 1]?.line ?? 0
1684
+ };
1685
+ }
1686
+ function parseDocumentCache(value) {
1687
+ return parse2(DocumentCache, value, "document cache");
1688
+ }
1689
+ function parseIdentifyOutput(value) {
1690
+ return parse2(IdentifyEnvelope, value, "identify output");
1691
+ }
1692
+ function parsePdfInspectMetadata(value) {
1322
1693
  if (!value || typeof value !== "object")
1323
- throw new Error("invalid readseek content");
1324
- const output = value;
1325
- if (output.hashlines !== undefined && output.start_line === undefined) {
1326
- const hashlines = parseSearchHashlines(output.hashlines, "content.hashlines");
1327
- const first = hashlines[0];
1328
- const last = hashlines[hashlines.length - 1];
1329
- return {
1330
- file: requireString(output.file, "file"),
1331
- language: requireString(output.language, "language"),
1332
- line_count: requireNumber(output.line_count, "line_count"),
1333
- file_hash: requireString(output.file_hash, "file_hash"),
1334
- start_line: first?.line ?? 1,
1335
- end_line: last?.line ?? 0,
1336
- hashlines
1337
- };
1694
+ return;
1695
+ if (value.format !== "pdf")
1696
+ return;
1697
+ const document = parse2(PdfInspectMetadata, value, "inspect metadata");
1698
+ return { kind: "pdf", type: "application/pdf", file: document.source, format: "pdf", pages: document.pages };
1699
+ }
1700
+ function parseEditOutput(value) {
1701
+ return parse2(EditOutputSchema, value, "edit output");
1702
+ }
1703
+ function parseRenameOutput(value) {
1704
+ return parse2(RenameOutputSchema, value, "rename output");
1705
+ }
1706
+ function parseDefOutput(value) {
1707
+ return parse2(DefEnvelope, value, "def output").locations;
1708
+ }
1709
+
1710
+ // ../readseek-api/src/client.ts
1711
+ function classifyReadSeekFailure(err) {
1712
+ const failure = err;
1713
+ const message = String(failure?.message ?? err);
1714
+ const missing = failure?.code === "ENOENT" || /Cannot find package|Cannot find module|no such file/i.test(message);
1715
+ if (missing) {
1716
+ return { code: "readseek-not-installed", message, hint: "Run npm install to install @jarkkojs/readseek." };
1338
1717
  }
1339
- return parseReadOutput(value);
1718
+ return { code: "readseek-execution-error", message };
1340
1719
  }
1341
- function parseDigestEnvelope(value) {
1720
+ function parseSourceInspectEnvelope(value) {
1342
1721
  if (!value || typeof value !== "object")
1343
- throw new Error("invalid readseek digest output");
1722
+ throw new Error("invalid readseek inspect output");
1344
1723
  const output = value;
1345
- if (output.metadata === undefined)
1346
- throw new Error("invalid readseek digest metadata");
1347
- const result = {
1348
- metadata: parseDetectOutput(output.metadata)
1349
- };
1724
+ const result = {};
1725
+ if (output.metadata !== undefined)
1726
+ result.metadata = parseDetectOutput(output.metadata);
1350
1727
  if (output.cache !== undefined)
1351
1728
  result.cache = parseDocumentCache(output.cache);
1352
1729
  if (output.content !== undefined)
@@ -1359,224 +1736,104 @@ function parseDigestEnvelope(value) {
1359
1736
  result.identity = parseIdentifyOutput(output.identity);
1360
1737
  return result;
1361
1738
  }
1362
- function parseEditOutput(value) {
1363
- if (!value || typeof value !== "object")
1364
- throw new Error("invalid readseek edit output");
1365
- const output = value;
1366
- return {
1367
- file: requireString(output.file, "file"),
1368
- before_hash: requireString(output.before_hash, "before_hash"),
1369
- file_hash: requireString(output.file_hash, "file_hash"),
1370
- plan_hash: requireString(output.plan_hash, "plan_hash"),
1371
- applied: requireBoolean(output.applied, "applied"),
1372
- changed: requireBoolean(output.changed, "changed"),
1373
- edit_count: requireNumber(output.edit_count, "edit_count"),
1374
- before_content: optionalString(output.before_content, "before_content"),
1375
- content: optionalString(output.content, "content")
1376
- };
1377
- }
1378
- function parseRenameConflicts(value, field) {
1379
- if (value === undefined)
1380
- return [];
1381
- if (!Array.isArray(value))
1382
- throw new Error(`invalid readseek ${field}`);
1383
- return value.map((item) => {
1384
- if (!item || typeof item !== "object")
1385
- throw new Error(`invalid readseek ${field} entry`);
1386
- const c = item;
1387
- return {
1388
- line: requireNumber(c.line, `${field}.line`),
1389
- column: requireNumber(c.column, `${field}.column`),
1390
- reason: requireString(c.reason, `${field}.reason`)
1391
- };
1392
- });
1393
- }
1394
- function parseRenameEdits(value, field) {
1395
- if (value === undefined)
1396
- return [];
1397
- if (!Array.isArray(value))
1398
- throw new Error(`invalid readseek ${field} entry`);
1399
- return value.map((item) => {
1400
- if (!item || typeof item !== "object")
1401
- throw new Error(`invalid readseek ${field} entry`);
1402
- const e = item;
1403
- return {
1404
- line: requireNumber(e.line, `${field}.line`),
1405
- start_column: requireNumber(e.start_column, `${field}.start_column`),
1406
- end_column: requireNumber(e.end_column, `${field}.end_column`),
1407
- start_byte: requireNumber(e.start_byte, `${field}.start_byte`),
1408
- end_byte: requireNumber(e.end_byte, `${field}.end_byte`),
1409
- occurrence: requireString(e.occurrence, `${field}.occurrence`),
1410
- line_hash: requireString(e.line_hash, `${field}.line_hash`),
1411
- text: requireString(e.text, `${field}.text`)
1412
- };
1413
- });
1414
- }
1415
- function parseRenameOutput(value) {
1416
- if (!value || typeof value !== "object")
1417
- throw new Error("invalid readseek rename output");
1418
- const output = value;
1419
- const others = output.others;
1420
- if (others !== undefined && !Array.isArray(others))
1421
- throw new Error("invalid readseek others");
1422
- return {
1423
- file: requireString(output.file, "file"),
1424
- language: requireString(output.language, "language"),
1425
- engine: optionalString(output.engine, "engine"),
1426
- file_hash: requireString(output.file_hash, "file_hash"),
1427
- old_name: requireString(output.old_name, "old_name"),
1428
- new_name: requireString(output.new_name, "new_name"),
1429
- applied: requireBoolean(output.applied, "applied"),
1430
- conflicts: parseRenameConflicts(output.conflicts, "conflicts"),
1431
- edits: parseRenameEdits(output.edits, "edits"),
1432
- others: others?.map((entry) => {
1433
- if (!entry || typeof entry !== "object")
1434
- throw new Error("invalid readseek other");
1435
- const o = entry;
1436
- return {
1437
- file: requireString(o.file, "other.file"),
1438
- language: requireString(o.language, "other.language"),
1439
- engine: optionalString(o.engine, "other.engine"),
1440
- file_hash: requireString(o.file_hash, "other.file_hash"),
1441
- conflicts: parseRenameConflicts(o.conflicts, "other.conflicts"),
1442
- edits: parseRenameEdits(o.edits, "other.edits")
1443
- };
1444
- }) ?? []
1445
- };
1446
- }
1447
- function parseDefCompact(value) {
1448
- if (!value || typeof value !== "object")
1449
- throw new Error("invalid readseek def output");
1450
- const output = value;
1451
- const locations = output.locations;
1452
- if (!Array.isArray(locations))
1453
- throw new Error("invalid readseek def locations");
1454
- return locations.map((loc) => {
1455
- if (!loc || typeof loc !== "object")
1456
- throw new Error("invalid readseek def location");
1457
- const item = loc;
1458
- return {
1459
- file: requireString(item.file, "location.file"),
1460
- line: requireNumber(item.line, "location.line"),
1461
- column: requireNumber(item.column, "location.column"),
1462
- line_hash: requireString(item.line_hash, "location.line_hash"),
1463
- text: requireString(item.text, "location.text"),
1464
- kind: optionalString(item.kind, "location.kind"),
1465
- name: optionalString(item.name, "location.name"),
1466
- qualified_name: optionalString(item.qualified_name, "location.qualified_name")
1467
- };
1468
- });
1469
- }
1470
1739
  function createReadSeekClient(runner) {
1471
- function digestArgs(target, options) {
1472
- return buildDigestArgs({
1740
+ function inspectArgs(target, options) {
1741
+ return buildInspectArgs({
1473
1742
  path: target,
1743
+ selector: options.selector,
1474
1744
  select: options.select ?? "content",
1475
- at: options.at,
1745
+ query: options.query,
1476
1746
  end: options.end,
1477
1747
  limit: options.limit,
1478
1748
  depth: options.depth,
1479
1749
  language: options.language,
1480
1750
  visionMode: options.visionMode,
1481
1751
  visionLevel: options.visionLevel,
1482
- stdinName: options.stdin !== undefined && target === "-" ? options.stdinName ?? target : undefined
1752
+ stdinName: options.stdinName
1483
1753
  });
1484
1754
  }
1485
- async function runDigestRaw(target, options = {}) {
1486
- const args = digestArgs(target, options);
1487
- const runOptions = {
1755
+ function inspectRunOptions(options) {
1756
+ return {
1488
1757
  signal: options.signal,
1489
1758
  stdin: options.stdin,
1490
1759
  timeoutMs: options.timeoutMs,
1491
1760
  vision: options.vision
1492
1761
  };
1493
- return runner.json(args, runOptions);
1494
1762
  }
1495
- async function digest(target, options = {}) {
1496
- return parseDigestEnvelope(await runDigestRaw(target, options));
1763
+ async function inspect(target, options = {}) {
1764
+ return runner.kdl(inspectArgs(target, options), inspectRunOptions(options));
1765
+ }
1766
+ async function inspectText(target, options = {}) {
1767
+ return runner.text(inspectArgs(target, options), inspectRunOptions(options));
1768
+ }
1769
+ async function inspectSource(target, options = {}) {
1770
+ return parseSourceInspectEnvelope(await inspect(target, options));
1497
1771
  }
1498
- function requireDigestContent(envelope, field) {
1772
+ function requireInspectMetadata(envelope, field) {
1773
+ if (envelope.metadata === undefined)
1774
+ throw new Error(`invalid readseek ${field}: missing metadata`);
1775
+ return envelope.metadata;
1776
+ }
1777
+ function requireInspectContent(envelope, field) {
1499
1778
  if (envelope.content === undefined)
1500
1779
  throw new Error(`invalid readseek ${field}: missing content`);
1501
1780
  return envelope.content;
1502
1781
  }
1503
- async function digestContent(filePath, options = {}) {
1504
- const envelope = await digest(filePath, {
1505
- select: "content",
1506
- at: options.startLine !== undefined ? buildLineLocation(options.startLine) : undefined,
1782
+ async function inspectContent(filePath, options = {}) {
1783
+ const inspected = await inspect(filePath, {
1784
+ select: ["metadata", "content"],
1785
+ selector: options.startLine !== undefined ? buildLineLocation(options.startLine) : undefined,
1507
1786
  end: options.endLine,
1508
1787
  visionMode: options.visionMode,
1509
1788
  signal: options.signal,
1510
1789
  vision: options.vision
1511
1790
  });
1512
- if (envelope.metadata.kind === "pdf") {
1513
- return { metadata: envelope.metadata, cache: envelope.cache };
1514
- }
1515
- const content = requireDigestContent(envelope, "content");
1516
- if (envelope.metadata.kind === "image") {
1517
- return { metadata: envelope.metadata, image: parseImageContent(content) };
1791
+ const pdf = parsePdfInspectMetadata(inspected);
1792
+ if (pdf)
1793
+ return { metadata: pdf };
1794
+ const envelope = parseSourceInspectEnvelope(inspected);
1795
+ const metadata = requireInspectMetadata(envelope, "content");
1796
+ const content = requireInspectContent(envelope, "content");
1797
+ if (metadata.kind === "image") {
1798
+ return { metadata, image: parseImageContent(content) };
1518
1799
  }
1519
- return { metadata: envelope.metadata, text: parseTextContent(content) };
1800
+ return { metadata, text: parseTextContent(content) };
1520
1801
  }
1521
1802
  return {
1522
- async view(filePath, options = {}) {
1523
- const args = buildViewArgs({
1524
- path: filePath,
1525
- node: options.node,
1526
- page: options.page,
1527
- kind: options.kind,
1528
- depth: options.depth,
1529
- outline: options.outline,
1530
- visionMode: options.visionMode,
1531
- visionLevel: options.visionLevel
1532
- });
1533
- return runner.text(args, {
1803
+ inspect,
1804
+ inspectText,
1805
+ inspectContent,
1806
+ async extract(target, selector, output, options = {}) {
1807
+ await runner.text(buildExtractArgs({
1808
+ path: target,
1809
+ selector,
1810
+ output,
1811
+ stdinName: options.stdinName
1812
+ }), {
1534
1813
  signal: options.signal,
1535
- vision: options.visionMode !== undefined
1814
+ stdin: options.stdin,
1815
+ timeoutMs: options.timeoutMs,
1816
+ cancelable: options.cancelable
1536
1817
  });
1537
1818
  },
1538
- digestRaw(target, options = {}) {
1539
- return runDigestRaw(target, options);
1540
- },
1541
- digest,
1542
- digestContent,
1543
1819
  async image(filePath, modes, options = {}) {
1544
- const requestedModes = modes.length > 1 ? ["all"] : modes;
1545
- const results = await Promise.allSettled(requestedModes.map(async (mode) => {
1546
- const digested = await digestContent(filePath, {
1547
- visionMode: mode,
1548
- signal: options.signal,
1549
- vision: true
1550
- });
1551
- if (!digested.image)
1552
- throw new Error(`readseek returned no image analysis for ${filePath}`);
1553
- return digested.image;
1554
- }));
1555
- let merged;
1556
- for (const result of results) {
1557
- if (result.status !== "fulfilled")
1558
- continue;
1559
- const detection = result.value;
1560
- merged = merged === undefined ? detection : {
1561
- ...merged,
1562
- ocr: detection.ocr ?? merged.ocr,
1563
- caption: detection.caption ?? merged.caption,
1564
- objects: detection.objects ?? merged.objects
1565
- };
1566
- }
1567
- if (merged !== undefined)
1568
- return merged;
1569
- const failure = results.find((result) => result.status === "rejected");
1570
- if (failure?.status === "rejected")
1571
- throw failure.reason;
1572
- throw new Error(`readseek returned no image analysis for ${filePath}`);
1820
+ if (modes.length === 0)
1821
+ throw new Error(`readseek returned no image analysis for ${filePath}`);
1822
+ const inspected = await inspectContent(filePath, {
1823
+ visionMode: modes.length > 1 ? "all" : modes[0],
1824
+ signal: options.signal,
1825
+ vision: true
1826
+ });
1827
+ if (!inspected.image)
1828
+ throw new Error(`readseek returned no image analysis for ${filePath}`);
1829
+ return inspected.image;
1573
1830
  },
1574
1831
  async preparedImage(filePath, options = {}) {
1575
- const digested = await digestContent(filePath, {
1832
+ const inspected = await inspectContent(filePath, {
1576
1833
  visionMode: "none",
1577
1834
  signal: options.signal
1578
1835
  });
1579
- const output = digested.image;
1836
+ const output = inspected.image;
1580
1837
  if (!output || output.encoding !== "base64" || output.data === undefined || output.mime === undefined) {
1581
1838
  throw new Error(`readseek returned no prepared image for ${filePath}`);
1582
1839
  }
@@ -1588,11 +1845,9 @@ function createReadSeekClient(runner) {
1588
1845
  pattern,
1589
1846
  language: options.language,
1590
1847
  limit: options.limit,
1591
- cached: options.cached,
1592
- others: options.others,
1593
- ignored: options.ignored
1848
+ gitScope: options.gitScope
1594
1849
  });
1595
- return parseSearchOutput(await runner.json(args, { signal: options.signal }));
1850
+ return parseSearchOutput(await runner.kdl(args, { signal: options.signal }));
1596
1851
  },
1597
1852
  async refs(target, options = {}) {
1598
1853
  const args = buildRefsArgs({
@@ -1602,26 +1857,21 @@ function createReadSeekClient(runner) {
1602
1857
  line: options.line,
1603
1858
  column: options.column,
1604
1859
  language: options.language,
1605
- cached: options.cached,
1606
- others: options.others,
1607
- ignored: options.ignored
1860
+ gitScope: options.gitScope
1608
1861
  });
1609
- return parseRefsOutput(await runner.json(args, { signal: options.signal }));
1862
+ return parseRefsOutput(await runner.kdl(args, { signal: options.signal }));
1610
1863
  },
1611
1864
  async def(target, options) {
1612
1865
  const args = buildDefArgs({
1613
1866
  path: target,
1614
1867
  name: options.name,
1615
- format: "plain",
1616
1868
  language: options.language,
1617
- cached: options.cached,
1618
- others: options.others,
1619
- ignored: options.ignored
1869
+ gitScope: options.gitScope
1620
1870
  });
1621
- return parseDefCompact(await runner.json(args, { signal: options.signal }));
1871
+ return parseDefOutput(await runner.kdl(args, { signal: options.signal }));
1622
1872
  },
1623
1873
  async check(filePath, content, options = {}) {
1624
- const envelope = await digest("-", {
1874
+ const envelope = await inspectSource("-", {
1625
1875
  select: "diagnostics",
1626
1876
  stdin: content,
1627
1877
  stdinName: filePath,
@@ -1631,12 +1881,12 @@ function createReadSeekClient(runner) {
1631
1881
  throw new Error("invalid readseek diagnostics: missing diagnostics facet");
1632
1882
  return envelope.diagnostics;
1633
1883
  },
1634
- async identify(filePath, content, options = {}) {
1635
- const envelope = await digest("-", {
1884
+ async identify(filePath, content, options) {
1885
+ const envelope = await inspectSource("-", {
1636
1886
  select: "identity",
1637
1887
  stdin: content,
1638
1888
  stdinName: filePath,
1639
- at: options.line !== undefined ? buildLineLocation(options.line, options.column) : undefined,
1889
+ selector: buildLineLocation(options.line, options.column),
1640
1890
  language: options.language,
1641
1891
  signal: options.signal
1642
1892
  });
@@ -1645,8 +1895,11 @@ function createReadSeekClient(runner) {
1645
1895
  return envelope.identity;
1646
1896
  },
1647
1897
  async detect(filePath, options = {}) {
1648
- const envelope = await digest(filePath, { select: "metadata", signal: options.signal });
1649
- return envelope.metadata;
1898
+ const inspected = await inspect(filePath, { select: "metadata", signal: options.signal });
1899
+ const pdf = parsePdfInspectMetadata(inspected);
1900
+ if (pdf)
1901
+ return pdf;
1902
+ return requireInspectMetadata(parseSourceInspectEnvelope(inspected), "metadata");
1650
1903
  },
1651
1904
  async edit(filePath, edits, options = {}) {
1652
1905
  const args = buildEditArgs({
@@ -1656,9 +1909,9 @@ function createReadSeekClient(runner) {
1656
1909
  language: options.language,
1657
1910
  requestFromStdin: true
1658
1911
  });
1659
- return parseEditOutput(await runner.json(args, {
1912
+ return parseEditOutput(await runner.kdl(args, {
1660
1913
  signal: options.signal,
1661
- stdin: JSON.stringify({ edits }),
1914
+ stdin: serializeEditPlan(edits),
1662
1915
  timeoutMs: options.timeoutMs,
1663
1916
  cancelable: options.cancelable
1664
1917
  }));
@@ -1671,12 +1924,11 @@ function createReadSeekClient(runner) {
1671
1924
  column: options.column,
1672
1925
  workspace: options.workspace,
1673
1926
  apply: options.apply,
1927
+ planHash: options.planHash,
1674
1928
  language: options.language,
1675
- cached: options.cached,
1676
- others: options.others,
1677
- ignored: options.ignored
1929
+ gitScope: options.gitScope
1678
1930
  });
1679
- return parseRenameOutput(await runner.json(args, { signal: options.signal }));
1931
+ return parseRenameOutput(await runner.kdl(args, { signal: options.signal }));
1680
1932
  }
1681
1933
  };
1682
1934
  }
@@ -2157,7 +2409,7 @@ function formatReplaceHint(edits, noopEdits) {
2157
2409
  return;
2158
2410
  if (counts.replace !== counts.total)
2159
2411
  return;
2160
- return "[info: this edit used replace (unverified). For safer future edits, prefer set_line/replace_lines with an anchor from readSeek_digest/readSeek_grep/readSeek_search.]";
2412
+ return "[info: this edit used replace (unverified). For safer future edits, prefer set_line/replace_lines with an anchor from readSeek_inspect/readSeek_grep/readSeek_search.]";
2161
2413
  }
2162
2414
  function buildEditOutput(input) {
2163
2415
  const summary = `Updated ${input.displayPath}`;
@@ -2204,20 +2456,20 @@ function classifyEdit(oldContent, newContent) {
2204
2456
  const newLines = newContent.split(`
2205
2457
  `);
2206
2458
  if (oldLines.length === newLines.length) {
2207
- let hasWhitespaceChange2 = false;
2208
- let hasSemanticChange2 = false;
2459
+ let hasWhitespaceChange = false;
2460
+ let hasSemanticChange = false;
2209
2461
  for (let i = 0;i < oldLines.length; i++) {
2210
2462
  if (oldLines[i] === newLines[i])
2211
2463
  continue;
2212
2464
  if (oldLines[i].trim() === newLines[i].trim()) {
2213
- hasWhitespaceChange2 = true;
2465
+ hasWhitespaceChange = true;
2214
2466
  } else {
2215
- hasSemanticChange2 = true;
2467
+ hasSemanticChange = true;
2216
2468
  }
2217
2469
  }
2218
- if (hasSemanticChange2 && hasWhitespaceChange2)
2470
+ if (hasSemanticChange && hasWhitespaceChange)
2219
2471
  return { classification: "mixed" };
2220
- if (hasWhitespaceChange2)
2472
+ if (hasWhitespaceChange)
2221
2473
  return { classification: "whitespace-only" };
2222
2474
  return { classification: "semantic" };
2223
2475
  }
@@ -2257,7 +2509,7 @@ import { readFileSync, statSync as statSync2 } from "node:fs";
2257
2509
  import { homedir } from "node:os";
2258
2510
  import { join } from "node:path";
2259
2511
  var READSEEK_TOOL_REPLACEMENTS = {
2260
- read: "readSeek_digest",
2512
+ read: "readSeek_inspect",
2261
2513
  edit: "readSeek_edit",
2262
2514
  grep: "readSeek_grep",
2263
2515
  write: "readSeek_write"
@@ -2270,42 +2522,36 @@ var DEFAULT_DISPLAY_MODES = {
2270
2522
  edit: "expanded",
2271
2523
  write: "expanded"
2272
2524
  };
2273
- function globalSettingsPath() {
2274
- return join(homedir(), ".pi/agent/settings.json");
2275
- }
2276
- function projectSettingsPath() {
2277
- return join(process.cwd(), ".pi/settings.json");
2278
- }
2279
2525
  function isRecord(value) {
2280
2526
  return typeof value === "object" && value !== null && !Array.isArray(value);
2281
2527
  }
2282
- function invalid(source, path2) {
2283
- return { source, path: path2, message: `Invalid readseek setting at ${path2}` };
2528
+ function invalid(source, path) {
2529
+ return { source, path, message: `Invalid readseek setting at ${path}` };
2284
2530
  }
2285
2531
  function warnUnknownKeys(raw, known, prefix, source, warnings) {
2286
2532
  for (const key of Object.keys(raw)) {
2287
2533
  if (known.includes(key))
2288
2534
  continue;
2289
- const path2 = `${prefix}.${key}`;
2290
- warnings.push({ source, path: path2, message: `Unknown readseek setting at ${path2}` });
2535
+ const path = `${prefix}.${key}`;
2536
+ warnings.push({ source, path, message: `Unknown readseek setting at ${path}` });
2291
2537
  }
2292
2538
  }
2293
- function readPositive(raw, key, path2, source, warnings) {
2539
+ function readPositive(raw, key, path, source, warnings) {
2294
2540
  if (!(key in raw))
2295
2541
  return;
2296
2542
  const val = raw[key];
2297
2543
  if (typeof val === "number" && Number.isSafeInteger(val) && val > 0)
2298
2544
  return val;
2299
- warnings.push(invalid(source, path2));
2545
+ warnings.push(invalid(source, path));
2300
2546
  return;
2301
2547
  }
2302
- function readEnum(raw, key, values, path2, source, warnings) {
2548
+ function readEnum(raw, key, values, path, source, warnings) {
2303
2549
  if (!(key in raw))
2304
2550
  return;
2305
2551
  const val = raw[key];
2306
2552
  if (typeof val === "string" && values.includes(val))
2307
2553
  return val;
2308
- warnings.push(invalid(source, path2));
2554
+ warnings.push(invalid(source, path));
2309
2555
  return;
2310
2556
  }
2311
2557
  function readImageMode(raw, source, warnings) {
@@ -2409,31 +2655,31 @@ function validateSettings(raw, source) {
2409
2655
  return { settings, warnings };
2410
2656
  }
2411
2657
  var settingsFileCache = new Map;
2412
- function statFileOrNull(path2) {
2658
+ function statFileOrNull(path) {
2413
2659
  try {
2414
- return statSync2(path2);
2660
+ return statSync2(path);
2415
2661
  } catch {
2416
2662
  return null;
2417
2663
  }
2418
2664
  }
2419
- function readSettingsFile(path2) {
2420
- const stats = statFileOrNull(path2);
2665
+ function readSettingsFile(path) {
2666
+ const stats = statFileOrNull(path);
2421
2667
  if (!stats) {
2422
- settingsFileCache.delete(path2);
2668
+ settingsFileCache.delete(path);
2423
2669
  return { settings: {}, warnings: [] };
2424
2670
  }
2425
- const cached = settingsFileCache.get(path2);
2671
+ const cached = settingsFileCache.get(path);
2426
2672
  if (cached && cached.mtimeMs === stats.mtimeMs && cached.size === stats.size)
2427
2673
  return cached.result;
2428
2674
  let result;
2429
2675
  try {
2430
- const text = readFileSync(path2, "utf8");
2431
- result = validateSettings(JSON.parse(text), path2);
2676
+ const text = readFileSync(path, "utf8");
2677
+ result = validateSettings(JSON.parse(text), path);
2432
2678
  } catch (error) {
2433
2679
  const message = error instanceof Error ? error.message : String(error);
2434
- result = { settings: {}, warnings: [{ source: path2, message: `Invalid JSON: ${message}` }] };
2680
+ result = { settings: {}, warnings: [{ source: path, message: `Invalid JSON: ${message}` }] };
2435
2681
  }
2436
- settingsFileCache.set(path2, { mtimeMs: stats.mtimeMs, size: stats.size, result });
2682
+ settingsFileCache.set(path, { mtimeMs: stats.mtimeMs, size: stats.size, result });
2437
2683
  return result;
2438
2684
  }
2439
2685
  function mergeSettings(base, override) {
@@ -2447,8 +2693,8 @@ function mergeSettings(base, override) {
2447
2693
  return settings;
2448
2694
  }
2449
2695
  function resolveReadSeekJsonSettings() {
2450
- const globalResult = readSettingsFile(globalSettingsPath());
2451
- const projectResult = readSettingsFile(projectSettingsPath());
2696
+ const globalResult = readSettingsFile(join(homedir(), ".pi/agent/settings.json"));
2697
+ const projectResult = readSettingsFile(join(process.cwd(), ".pi/settings.json"));
2452
2698
  return {
2453
2699
  settings: mergeSettings(globalResult.settings, projectResult.settings),
2454
2700
  warnings: [...globalResult.warnings, ...projectResult.warnings]
@@ -2496,21 +2742,13 @@ var DEFAULT_READSEEK_VISION_TIMEOUT_MS = 30 * 60000;
2496
2742
  function readSeekTimeoutMs() {
2497
2743
  return resolveReadSeekTimeoutMs() ?? DEFAULT_READSEEK_TIMEOUT_MS;
2498
2744
  }
2499
- async function spawnReadSeekRaw(args, options = {}) {
2500
- return runReadSeekNodeText(readSeekBinaryPath(), args, {
2501
- signal: options.signal,
2502
- stdin: options.stdin,
2503
- timeoutMs: options.timeoutMs ?? readSeekTimeoutMs(),
2504
- cancelable: options.cancelable
2505
- });
2506
- }
2507
2745
  async function ensureDefaultReadSeekDir() {
2508
2746
  const dir = defaultReadSeekDir();
2509
2747
  if (!dir)
2510
2748
  return null;
2511
2749
  if (directoryExists(dir))
2512
2750
  return dir;
2513
- defaultReadSeekDirInit ??= spawnReadSeekRaw(["--readseek-dir", dir, "init"]).then(() => directoryExists(dir) ? dir : null).catch(() => null).finally(() => {
2751
+ defaultReadSeekDirInit ??= runReadSeekNodeText(readSeekBinaryPath(), ["--readseek-dir", dir, "init"], { timeoutMs: readSeekTimeoutMs() }).then(() => directoryExists(dir) ? dir : null).catch(() => null).finally(() => {
2514
2752
  defaultReadSeekDirInit = undefined;
2515
2753
  });
2516
2754
  return defaultReadSeekDirInit;
@@ -2532,17 +2770,6 @@ async function readSeekInvocationArgs(args) {
2532
2770
  const readSeekDir = await ensureDefaultReadSeekDir();
2533
2771
  return readSeekDir ? ["--readseek-dir", readSeekDir, ...args] : [...args];
2534
2772
  }
2535
- async function runReadSeekRaw(args, options = {}) {
2536
- return spawnReadSeekRaw(await readSeekInvocationArgs(args), options);
2537
- }
2538
- async function runReadSeek(args, options = {}) {
2539
- return runReadSeekNodeJson(readSeekBinaryPath(), await readSeekInvocationArgs(args), {
2540
- signal: options.signal,
2541
- stdin: options.stdin,
2542
- timeoutMs: options.timeoutMs ?? readSeekTimeoutMs(),
2543
- cancelable: options.cancelable
2544
- });
2545
- }
2546
2773
  var visionInvocationTail = Promise.resolve();
2547
2774
  async function runReadSeekVisionRaw(args, options = {}) {
2548
2775
  let release;
@@ -2573,40 +2800,38 @@ async function runReadSeekVisionRaw(args, options = {}) {
2573
2800
  await predecessor;
2574
2801
  }
2575
2802
  signal?.throwIfAborted();
2576
- return await runReadSeekRaw(args, {
2577
- ...options,
2578
- timeoutMs: options.timeoutMs ?? resolveReadSeekTimeoutMs() ?? DEFAULT_READSEEK_VISION_TIMEOUT_MS
2803
+ return await runReadSeekNodeText(readSeekBinaryPath(), await readSeekInvocationArgs(args), {
2804
+ signal: options.signal,
2805
+ stdin: options.stdin,
2806
+ timeoutMs: options.timeoutMs ?? resolveReadSeekTimeoutMs() ?? DEFAULT_READSEEK_VISION_TIMEOUT_MS,
2807
+ cancelable: options.cancelable
2579
2808
  });
2580
2809
  } finally {
2581
2810
  release();
2582
2811
  }
2583
2812
  }
2584
- async function runReadSeekVision(args, options = {}) {
2585
- return parseReadSeekJson(await runReadSeekVisionRaw(args, options));
2586
- }
2587
2813
  var runner = {
2588
- text: (args, options = {}) => options.vision ? runReadSeekVisionRaw(args, options) : runReadSeekRaw(args, options),
2589
- json: (args, options = {}) => options.vision ? runReadSeekVision(args, options) : runReadSeek(args, options)
2814
+ text: async (args, options = {}) => options.vision ? runReadSeekVisionRaw(args, options) : runReadSeekNodeText(readSeekBinaryPath(), await readSeekInvocationArgs(args), {
2815
+ signal: options.signal,
2816
+ stdin: options.stdin,
2817
+ timeoutMs: options.timeoutMs ?? readSeekTimeoutMs(),
2818
+ cancelable: options.cancelable
2819
+ }),
2820
+ kdl: async (args, options = {}) => parseReadSeekKdl(await runner.text(args, options))
2590
2821
  };
2591
2822
  var client = createReadSeekClient(runner);
2592
- var readSeekView = client.view;
2593
- var readSeekDigestNative = client.digestRaw;
2594
- var readSeekDigest = client.digest;
2595
- var readSeekDigestContent = client.digestContent;
2596
- var readSeekImage = client.image;
2597
- var readSeekPreparedImage = client.preparedImage;
2823
+ var readSeekInspect = client.inspect;
2824
+ var readSeekExtract = client.extract;
2598
2825
  var readSeekSearch = client.search;
2599
2826
  var readSeekRefs = client.refs;
2600
2827
  var readSeekDef = client.def;
2601
2828
  var readSeekCheck = client.check;
2602
- var readSeekIdentify = client.identify;
2603
- var readSeekDetect = client.detect;
2604
2829
  var readSeekEdit = client.edit;
2605
2830
  var readSeekRename = client.rename;
2606
2831
  async function readSeekMap(filePath, options = {}) {
2607
- const envelope = await client.digest(filePath, {
2832
+ const envelope = await client.inspect(filePath, {
2608
2833
  select: "map",
2609
- depth: DEFAULT_DIGEST_MAP_DEPTH,
2834
+ depth: DEFAULT_INSPECT_MAP_DEPTH,
2610
2835
  signal: options.signal
2611
2836
  });
2612
2837
  const map = envelope.map;
@@ -2672,8 +2897,8 @@ var LANGUAGE_BY_EXTENSION = new Map([
2672
2897
  [".rs", "rust"],
2673
2898
  [".java", "java"]
2674
2899
  ]);
2675
- function inferLanguage(path3) {
2676
- const extensionMatch = path3.match(/\.[^.\/\\]+$/);
2900
+ function inferLanguage(path) {
2901
+ const extensionMatch = path.match(/\.[^.\/\\]+$/);
2677
2902
  if (!extensionMatch)
2678
2903
  return;
2679
2904
  return LANGUAGE_BY_EXTENSION.get(extensionMatch[0].toLowerCase());
@@ -2719,10 +2944,10 @@ function parseCompactDiffEntries(diff, oldContent, newContent) {
2719
2944
  `);
2720
2945
  const compactDelete = diff.match(/^(\d+):[0-9a-f]{6}\|(.*) → \[deleted\]$/);
2721
2946
  if (compactDelete) {
2722
- const oldLine2 = Number(compactDelete[1]);
2947
+ const oldLine = Number(compactDelete[1]);
2723
2948
  const text = compactDelete[2] ?? "";
2724
- if (oldLines[oldLine2 - 1] === text)
2725
- return [{ kind: "remove", oldLine: oldLine2, text }];
2949
+ if (oldLines[oldLine - 1] === text)
2950
+ return [{ kind: "remove", oldLine, text }];
2726
2951
  }
2727
2952
  const compactPrefix = diff.match(/^(\d+):[0-9a-f]{6}\|/);
2728
2953
  if (!compactPrefix)
@@ -2762,9 +2987,9 @@ function longestCommonSubsequence(a, b) {
2762
2987
  const rows = a.length + 1;
2763
2988
  const cols = b.length + 1;
2764
2989
  const table = Array.from({ length: rows }, () => Array(cols).fill(0));
2765
- for (let i2 = a.length - 1;i2 >= 0; i2--) {
2766
- for (let j2 = b.length - 1;j2 >= 0; j2--) {
2767
- table[i2][j2] = a[i2] === b[j2] ? table[i2 + 1][j2 + 1] + 1 : Math.max(table[i2 + 1][j2], table[i2][j2 + 1]);
2990
+ for (let i = a.length - 1;i >= 0; i--) {
2991
+ for (let j = b.length - 1;j >= 0; j--) {
2992
+ table[i][j] = a[i] === b[j] ? table[i + 1][j + 1] + 1 : Math.max(table[i + 1][j], table[i][j + 1]);
2768
2993
  }
2769
2994
  }
2770
2995
  const pairs = [];
@@ -2882,8 +3107,8 @@ function buildInlineDiffs(entries) {
2882
3107
  }
2883
3108
  return inlineDiffs.length ? inlineDiffs : undefined;
2884
3109
  }
2885
- function finalizeDiffData(path3, entries, blockRanges) {
2886
- const language = inferLanguage(path3);
3110
+ function finalizeDiffData(path, entries, blockRanges) {
3111
+ const language = inferLanguage(path);
2887
3112
  const inlineDiffs = buildInlineDiffs(entries);
2888
3113
  return {
2889
3114
  version: 1,
@@ -2976,7 +3201,6 @@ function wrapWithHangingIndent(prefix, content, width, options = {}) {
2976
3201
  const indent = " ".repeat(prefixWidth);
2977
3202
  return wrapped.map((line, index) => tint(clampLineToWidth(index === 0 ? prefix + line : indent + line, normalized)));
2978
3203
  }
2979
- var wrappedHashlinesCache = new WeakMap;
2980
3204
  function renderPendingResult(pendingLabel, width, theme) {
2981
3205
  return new Text(clampLinesToWidth([summaryLine(pendingLabel, { theme, style: "muted" })], width).join(`
2982
3206
  `), 0, 0);
@@ -3107,8 +3331,8 @@ function padRightVisual(line, width) {
3107
3331
  function tint(theme, entry, text) {
3108
3332
  return entry.kind === "add" ? theme.fg("success", text) : entry.kind === "remove" ? theme.fg("error", text) : theme.fg("toolOutput", text);
3109
3333
  }
3110
- function spans(theme, spans2, fallback) {
3111
- return spans2?.map((s) => s.kind === "add" ? theme.fg("success", s.text) : s.kind === "remove" ? theme.fg("error", s.text) : s.text).join("") ?? fallback;
3334
+ function spans(theme, spans, fallback) {
3335
+ return spans?.map((s) => s.kind === "add" ? theme.fg("success", s.text) : s.kind === "remove" ? theme.fg("error", s.text) : s.text).join("") ?? fallback;
3112
3336
  }
3113
3337
  function inlineText(input, index, entry) {
3114
3338
  const pair = Array.isArray(input.diffData.inlineDiffs) ? input.diffData.inlineDiffs.find((d) => entry.kind === "remove" ? d.removeLineIndex === index : entry.kind === "add" ? d.addLineIndex === index : false) : undefined;
@@ -3237,12 +3461,12 @@ function upsertTextComponent(lastComponent, text) {
3237
3461
  }
3238
3462
 
3239
3463
  // src/register-tool.ts
3240
- import { Type } from "@sinclair/typebox";
3464
+ import { Type as Type2 } from "@sinclair/typebox";
3241
3465
  function optionalIntOrString(description) {
3242
- return Type.Optional(Type.Union([Type.Number({ description }), Type.String({ description })]));
3466
+ return Type2.Optional(Type2.Union([Type2.Number({ description }), Type2.String({ description })]));
3243
3467
  }
3244
3468
  function filePathParam() {
3245
- return Type.String({ description: PARAM_DESCRIPTIONS.path });
3469
+ return Type2.String({ description: PARAM_DESCRIPTIONS.path });
3246
3470
  }
3247
3471
  function registerReadSeekTool(pi, tool) {
3248
3472
  pi.registerTool(tool);
@@ -3250,28 +3474,28 @@ function registerReadSeekTool(pi, tool) {
3250
3474
  }
3251
3475
 
3252
3476
  // src/edit.ts
3253
- var hashlineEditItemSchema = Type2.Object({
3254
- set_line: Type2.Optional(Type2.Object({
3255
- anchor: Type2.String({ description: PARAM_DESCRIPTIONS.anchor }),
3256
- new_text: Type2.String({ description: PARAM_DESCRIPTIONS.newTextLine })
3477
+ var hashlineEditItemSchema = Type3.Object({
3478
+ set_line: Type3.Optional(Type3.Object({
3479
+ anchor: Type3.String({ description: PARAM_DESCRIPTIONS.anchor }),
3480
+ new_text: Type3.String({ description: PARAM_DESCRIPTIONS.newTextLine })
3257
3481
  }, { additionalProperties: false, description: PARAM_DESCRIPTIONS.setLine })),
3258
- replace_lines: Type2.Optional(Type2.Object({
3259
- start_anchor: Type2.String({ description: PARAM_DESCRIPTIONS.startAnchor }),
3260
- end_anchor: Type2.String({ description: PARAM_DESCRIPTIONS.endAnchor }),
3261
- new_text: Type2.String({ description: PARAM_DESCRIPTIONS.newTextRange })
3482
+ replace_lines: Type3.Optional(Type3.Object({
3483
+ start_anchor: Type3.String({ description: PARAM_DESCRIPTIONS.startAnchor }),
3484
+ end_anchor: Type3.String({ description: PARAM_DESCRIPTIONS.endAnchor }),
3485
+ new_text: Type3.String({ description: PARAM_DESCRIPTIONS.newTextRange })
3262
3486
  }, { additionalProperties: false, description: PARAM_DESCRIPTIONS.replaceLines })),
3263
- insert_after: Type2.Optional(Type2.Object({
3264
- anchor: Type2.String({ description: PARAM_DESCRIPTIONS.insertAnchor }),
3265
- new_text: Type2.String({ description: PARAM_DESCRIPTIONS.newTextInsert })
3487
+ insert_after: Type3.Optional(Type3.Object({
3488
+ anchor: Type3.String({ description: PARAM_DESCRIPTIONS.insertAnchor }),
3489
+ new_text: Type3.String({ description: PARAM_DESCRIPTIONS.newTextInsert })
3266
3490
  }, { additionalProperties: false, description: PARAM_DESCRIPTIONS.insertAfter })),
3267
- replace: Type2.Optional(Type2.Object({
3268
- old_text: Type2.String({ description: PARAM_DESCRIPTIONS.oldText }),
3269
- new_text: Type2.String({ description: PARAM_DESCRIPTIONS.newText }),
3270
- all: Type2.Optional(Type2.Boolean({ description: PARAM_DESCRIPTIONS.all }))
3491
+ replace: Type3.Optional(Type3.Object({
3492
+ old_text: Type3.String({ description: PARAM_DESCRIPTIONS.oldText }),
3493
+ new_text: Type3.String({ description: PARAM_DESCRIPTIONS.newText }),
3494
+ all: Type3.Optional(Type3.Boolean({ description: PARAM_DESCRIPTIONS.all }))
3271
3495
  }, { additionalProperties: false, description: PARAM_DESCRIPTIONS.replace })),
3272
- replace_symbol: Type2.Optional(Type2.Object({
3273
- symbol: Type2.String({ description: PARAM_DESCRIPTIONS.symbol }),
3274
- new_body: Type2.String({ description: PARAM_DESCRIPTIONS.newBody })
3496
+ replace_symbol: Type3.Optional(Type3.Object({
3497
+ symbol: Type3.String({ description: PARAM_DESCRIPTIONS.symbol }),
3498
+ new_body: Type3.String({ description: PARAM_DESCRIPTIONS.newBody })
3275
3499
  }, { additionalProperties: false, description: PARAM_DESCRIPTIONS.replaceSymbol }))
3276
3500
  }, {
3277
3501
  additionalProperties: false,
@@ -3279,18 +3503,18 @@ var hashlineEditItemSchema = Type2.Object({
3279
3503
  maxProperties: 1,
3280
3504
  description: PARAM_DESCRIPTIONS.editVariant
3281
3505
  });
3282
- var hashlineEditSchema = Type2.Object({
3506
+ var hashlineEditSchema = Type3.Object({
3283
3507
  path: filePathParam(),
3284
- language: Type2.Optional(Type2.String({ description: PARAM_DESCRIPTIONS.languageSymbol })),
3285
- edits: Type2.Array(hashlineEditItemSchema, {
3508
+ language: Type3.Optional(Type3.String({ description: PARAM_DESCRIPTIONS.languageSymbol })),
3509
+ edits: Type3.Array(hashlineEditItemSchema, {
3286
3510
  minItems: 1,
3287
3511
  description: PARAM_DESCRIPTIONS.edits
3288
3512
  }),
3289
- postEditVerify: Type2.Optional(Type2.Boolean({
3513
+ postEditVerify: Type3.Optional(Type3.Boolean({
3290
3514
  description: PARAM_DESCRIPTIONS.postEditVerify
3291
3515
  }))
3292
3516
  }, { additionalProperties: false });
3293
- function buildEditError(path3, code, message, hint, errorDetails) {
3517
+ function buildEditError(path, code, message, hint, errorDetails) {
3294
3518
  return {
3295
3519
  content: [{ type: "text", text: message }],
3296
3520
  isError: true,
@@ -3301,7 +3525,7 @@ function buildEditError(path3, code, message, hint, errorDetails) {
3301
3525
  readSeekValue: {
3302
3526
  tool: "edit",
3303
3527
  ok: false,
3304
- path: path3,
3528
+ path,
3305
3529
  error: buildReadSeekError(code, message, hint, errorDetails)
3306
3530
  }
3307
3531
  }
@@ -3315,14 +3539,14 @@ async function executeEdit(opts) {
3315
3539
  const { params, signal, cwd, wasReadInSession, onFileMutated, syntaxValidate } = opts;
3316
3540
  const parsed = params;
3317
3541
  const rawPath = parsed.path;
3318
- const path3 = rawPath.replace(/^@/, "");
3319
- const absolutePath = resolveToCwd2(path3, cwd);
3542
+ const path = rawPath.replace(/^@/, "");
3543
+ const absolutePath = resolveToCwd2(path, cwd);
3320
3544
  throwIfAborted(signal);
3321
3545
  try {
3322
3546
  return await withFileMutationQueue(absolutePath, async () => {
3323
3547
  throwIfAborted(signal);
3324
3548
  if (wasReadInSession && !wasReadInSession(absolutePath)) {
3325
- const hint = `Call readSeek_digest(${JSON.stringify(rawPath)}) first, or use readSeek_grep, readSeek_search, or readSeek_write to produce fresh anchors for this file.`;
3549
+ const hint = `Call readSeek_inspect(${JSON.stringify(rawPath)}) first, or use readSeek_grep, readSeek_search, or readSeek_write to produce fresh anchors for this file.`;
3326
3550
  return buildEditError(absolutePath, "file-not-read", `You must get fresh anchors for ${absolutePath} before editing it. ${hint}`, hint);
3327
3551
  }
3328
3552
  const parsedEdits = validateExactlyOneEditVariant(parsed.edits ?? []);
@@ -3347,7 +3571,7 @@ async function executeEdit(opts) {
3347
3571
  const originalForSyntax = normalizeToLF(stripBom(original).text);
3348
3572
  const resultForSyntax = normalizeToLF(stripBom(result).text);
3349
3573
  if (!plan.changed) {
3350
- return buildEditError(absolutePath, "no-op", `No changes made to ${path3}. The edits produced identical content.`);
3574
+ return buildEditError(absolutePath, "no-op", `No changes made to ${path}. The edits produced identical content.`);
3351
3575
  }
3352
3576
  const syntaxMode = resolveSyntaxValidateMode({ syntaxValidate });
3353
3577
  let syntaxWarning;
@@ -3388,14 +3612,14 @@ async function executeEdit(opts) {
3388
3612
  try {
3389
3613
  verifiedContent = await fsReadFile(absolutePath, "utf-8");
3390
3614
  } catch (err) {
3391
- return buildEditError(absolutePath, "post-edit-verification-read-failed", `Edit write completed but post-edit verification failed: could not read ${path3} after writing.`, undefined, { fsCode: err?.code, fsMessage: err?.message });
3615
+ return buildEditError(absolutePath, "post-edit-verification-read-failed", `Edit write completed but post-edit verification failed: could not read ${path} after writing.`, undefined, { fsCode: err?.code, fsMessage: err?.message });
3392
3616
  }
3393
3617
  if (verifiedContent !== plan.content) {
3394
- return buildEditError(absolutePath, "post-edit-verification-mismatch", `Edit write completed but post-edit verification did not confirm the intended content for ${path3}. Re-read the file before making follow-up edits.`, undefined, { expectedLength: plan.content.length, actualLength: verifiedContent.length });
3618
+ return buildEditError(absolutePath, "post-edit-verification-mismatch", `Edit write completed but post-edit verification did not confirm the intended content for ${path}. Re-read the file before making follow-up edits.`, undefined, { expectedLength: plan.content.length, actualLength: verifiedContent.length });
3395
3619
  }
3396
3620
  }
3397
3621
  const diffResult = generateCompactOrFullDiff(original, result);
3398
- const patch = createPatch(path3, original, result);
3622
+ const patch = createPatch(path, original, result);
3399
3623
  const diffData = buildDiffData({
3400
3624
  path: absolutePath,
3401
3625
  oldContent: original,
@@ -3407,7 +3631,7 @@ async function executeEdit(opts) {
3407
3631
  const semanticSummary = { classification: classification.classification };
3408
3632
  const builtOutput = buildEditOutput({
3409
3633
  path: absolutePath,
3410
- displayPath: path3,
3634
+ displayPath: path,
3411
3635
  diff: diffResult.diff,
3412
3636
  patch,
3413
3637
  diffData,
@@ -3430,7 +3654,7 @@ async function executeEdit(opts) {
3430
3654
  });
3431
3655
  } catch (err) {
3432
3656
  if (typeof err?.code === "string") {
3433
- return mapEditFileError(err, absolutePath, path3, "read");
3657
+ return mapEditFileError(err, absolutePath, path, "read");
3434
3658
  }
3435
3659
  throw err;
3436
3660
  }
@@ -3480,8 +3704,8 @@ function registerEditTool(pi, options = {}) {
3480
3704
  text = clampLineToWidth(text, context.width);
3481
3705
  return upsertTextComponent(context.lastComponent, text);
3482
3706
  },
3483
- renderResult(result, options2, theme, ...rest) {
3484
- const { isPartial, isError, expanded, width, context } = resolveRenderResultContext(options2, rest, resolveReadSeekToolDisplayMode("edit") === "expanded");
3707
+ renderResult(result, options, theme, ...rest) {
3708
+ const { isPartial, isError, expanded, width, context } = resolveRenderResultContext(options, rest, resolveReadSeekToolDisplayMode("edit") === "expanded");
3485
3709
  if (isPartial) {
3486
3710
  return renderPendingResult("pending edit", width, theme);
3487
3711
  }
@@ -3537,7 +3761,7 @@ ${theme.fg("error", info.errorText)}`;
3537
3761
  import { readFile as fsReadFile2, stat as fsStat } from "node:fs/promises";
3538
3762
  import path3 from "node:path";
3539
3763
  import { createGrepTool } from "@earendil-works/pi-coding-agent";
3540
- import { Type as Type4 } from "@sinclair/typebox";
3764
+ import { Type as Type5 } from "@sinclair/typebox";
3541
3765
  import { Text as Text4 } from "@earendil-works/pi-tui";
3542
3766
 
3543
3767
  // src/binary-detect.ts
@@ -3609,14 +3833,14 @@ function buildScopeMetadata(groups, warnings) {
3609
3833
  }
3610
3834
  function buildGrepOutput(input) {
3611
3835
  const fileCount = new Set(input.groups.map((group) => group.absolutePath)).size;
3612
- const header2 = `[${input.totalMatches} matches in ${fileCount} files]`;
3836
+ const header = `[${input.totalMatches} matches in ${fileCount} files]`;
3613
3837
  let text;
3614
3838
  if (input.summary) {
3615
3839
  const fileLines = [...input.groups].sort((a, b) => b.matchCount - a.matchCount).map((group) => `${group.absolutePath}: ${group.matchCount} matches`);
3616
- text = [header2, ...fileLines].join(`
3840
+ text = [header, ...fileLines].join(`
3617
3841
  `);
3618
3842
  } else {
3619
- const blocks = [header2];
3843
+ const blocks = [header];
3620
3844
  for (const group of input.groups) {
3621
3845
  blocks.push(renderGroupHeader(group));
3622
3846
  for (const entry of group.entries) {
@@ -3721,12 +3945,12 @@ function firstLineNumber(group) {
3721
3945
  }
3722
3946
  function buildSymbolEntries(fileLines, symbol, matchLines, scopeContext) {
3723
3947
  if (scopeContext === undefined) {
3724
- const entries2 = [];
3948
+ const entries = [];
3725
3949
  for (let lineNumber = symbol.startLine;lineNumber <= symbol.endLine; lineNumber++) {
3726
3950
  const built = buildReadSeekLine(lineNumber, fileLines[lineNumber - 1] ?? "");
3727
- entries2.push({ kind: matchLines.has(lineNumber) ? "match" : "context", line: built });
3951
+ entries.push({ kind: matchLines.has(lineNumber) ? "match" : "context", line: built });
3728
3952
  }
3729
- return entries2;
3953
+ return entries;
3730
3954
  }
3731
3955
  const ranges = [...matchLines].sort((a, b) => a - b).map((ln) => ({
3732
3956
  startLine: Math.max(symbol.startLine, ln - scopeContext),
@@ -3909,11 +4133,8 @@ import {
3909
4133
  } from "@jarkkojs/readseek-api";
3910
4134
 
3911
4135
  // src/tui-source-render.ts
3912
- import { getLanguageFromPath, highlightCode } from "@earendil-works/pi-coding-agent";
3913
- import { wrapTextWithAnsi as wrapTextWithAnsi2 } from "@earendil-works/pi-tui";
3914
4136
  var GREP_ANCHOR_RE = /^(.*?):(>>| )(\d+):([0-9a-zA-Z]{1,16})\|(.*)$/;
3915
- var readSourceCache = new WeakMap;
3916
- function renderGrepSourceForDisplay(text, anchors, renderPath = (path3) => path3) {
4137
+ function renderGrepSourceForDisplay(text, anchors, renderPath = (path) => path) {
3917
4138
  return text.split(`
3918
4139
  `).map((line) => {
3919
4140
  const match = line.match(GREP_ANCHOR_RE);
@@ -3929,39 +4150,30 @@ function renderGrepSourceForDisplay(text, anchors, renderPath = (path3) => path3
3929
4150
  }
3930
4151
 
3931
4152
  // src/readseek-params.ts
3932
- import { Type as Type3 } from "@sinclair/typebox";
4153
+ import { Type as Type4 } from "@sinclair/typebox";
3933
4154
  function searchPathParam() {
3934
- return Type3.Optional(Type3.String({ description: PARAM_DESCRIPTIONS.searchPath }));
4155
+ return Type4.Optional(Type4.String({ description: PARAM_DESCRIPTIONS.searchPath }));
3935
4156
  }
3936
4157
  function languageParam() {
3937
- return Type3.Optional(Type3.String({ description: PARAM_DESCRIPTIONS.language }));
4158
+ return Type4.Optional(Type4.String({ description: PARAM_DESCRIPTIONS.language }));
3938
4159
  }
3939
4160
  function readSeekGitSearchParams() {
3940
4161
  return {
3941
- cached: Type3.Optional(Type3.Boolean({ description: PARAM_DESCRIPTIONS.cached })),
3942
- others: Type3.Optional(Type3.Boolean({ description: PARAM_DESCRIPTIONS.others })),
3943
- ignored: Type3.Optional(Type3.Boolean({ description: PARAM_DESCRIPTIONS.ignored }))
4162
+ gitScope: Type4.Optional(Type4.Union(GIT_SCOPES.map((scope) => Type4.Literal(scope)), { description: PARAM_DESCRIPTIONS.gitScope }))
3944
4163
  };
3945
4164
  }
3946
- function validateIgnoredRequiresOthers(tool, params) {
3947
- const validated = validateGitSelection(params);
3948
- if (!validated.ok) {
3949
- return buildToolErrorResult(tool, "invalid-parameter", `${tool} parameter 'ignored' requires 'others'`);
3950
- }
3951
- return null;
3952
- }
3953
4165
 
3954
4166
  // src/grep.ts
3955
- var grepSchema = Type4.Object({
3956
- pattern: Type4.String({ description: PARAM_DESCRIPTIONS.grepPattern }),
4167
+ var grepSchema = Type5.Object({
4168
+ pattern: Type5.String({ description: PARAM_DESCRIPTIONS.grepPattern }),
3957
4169
  path: searchPathParam(),
3958
- glob: Type4.Optional(Type4.String({ description: PARAM_DESCRIPTIONS.glob })),
3959
- ignoreCase: Type4.Optional(Type4.Boolean({ description: PARAM_DESCRIPTIONS.ignoreCase })),
3960
- literal: Type4.Optional(Type4.Boolean({ description: PARAM_DESCRIPTIONS.literal })),
4170
+ glob: Type5.Optional(Type5.String({ description: PARAM_DESCRIPTIONS.glob })),
4171
+ ignoreCase: Type5.Optional(Type5.Boolean({ description: PARAM_DESCRIPTIONS.ignoreCase })),
4172
+ literal: Type5.Optional(Type5.Boolean({ description: PARAM_DESCRIPTIONS.literal })),
3961
4173
  context: optionalIntOrString(PARAM_DESCRIPTIONS.context),
3962
4174
  limit: optionalIntOrString(PARAM_DESCRIPTIONS.matchLimit),
3963
- summary: Type4.Optional(Type4.Boolean({ description: PARAM_DESCRIPTIONS.summary })),
3964
- scope: Type4.Optional(Type4.Literal("symbol", {
4175
+ summary: Type5.Optional(Type5.Boolean({ description: PARAM_DESCRIPTIONS.summary })),
4176
+ scope: Type5.Optional(Type5.Literal("symbol", {
3965
4177
  description: PARAM_DESCRIPTIONS.grepScope
3966
4178
  })),
3967
4179
  scopeContext: optionalIntOrString(PARAM_DESCRIPTIONS.scopeContext)
@@ -4033,12 +4245,6 @@ function truncateGroupEntries(group) {
4033
4245
  }
4034
4246
  return { ...group, entries: kept };
4035
4247
  }
4036
- function recordsFromGroups(groups) {
4037
- return groups.flatMap((group) => group.entries.flatMap((entry) => entry.kind === "separator" ? [] : [{ path: group.absolutePath, ...entry.line, kind: entry.kind }]));
4038
- }
4039
- function escapeForRegex(s) {
4040
- return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4041
- }
4042
4248
  async function executeGrep(opts) {
4043
4249
  const { toolCallId, params, signal, onUpdate, cwd, onFileAnchored } = opts;
4044
4250
  await ensureHashInit();
@@ -4047,10 +4253,16 @@ async function executeGrep(opts) {
4047
4253
  if (!context.ok) {
4048
4254
  return buildToolErrorResult("grep", "invalid-params-combo", context.message);
4049
4255
  }
4256
+ if (context.value !== undefined && context.value < 0) {
4257
+ return buildToolErrorResult("grep", "invalid-params-combo", `Invalid context: expected a non-negative integer, received ${context.value}.`);
4258
+ }
4050
4259
  const limit = coerceObviousBase10Int2(rawParams.limit, "limit");
4051
4260
  if (!limit.ok) {
4052
4261
  return buildToolErrorResult("grep", "invalid-limit", limit.message);
4053
4262
  }
4263
+ if (limit.value !== undefined && limit.value < 1) {
4264
+ return buildToolErrorResult("grep", "invalid-limit", `Invalid limit: expected a positive integer, received ${limit.value}.`);
4265
+ }
4054
4266
  const scopeContext = coerceObviousBase10Int2(rawParams.scopeContext, "scopeContext");
4055
4267
  if (!scopeContext.ok) {
4056
4268
  return buildToolErrorResult("grep", "invalid-params-combo", scopeContext.message);
@@ -4155,7 +4367,7 @@ async function executeGrep(opts) {
4155
4367
  const CONCURRENCY = 8;
4156
4368
  const pathList = [...pathsToRead];
4157
4369
  for (let i = 0;i < pathList.length; i += CONCURRENCY) {
4158
- await Promise.all(pathList.slice(i, i + CONCURRENCY).map((p2) => getFileLines(p2)));
4370
+ await Promise.all(pathList.slice(i, i + CONCURRENCY).map((p) => getFileLines(p)));
4159
4371
  }
4160
4372
  }
4161
4373
  const addSummaryMatch = (displayPath, absolutePath) => {
@@ -4208,7 +4420,7 @@ async function executeGrep(opts) {
4208
4420
  const flags = p.ignoreCase ? "i" : "";
4209
4421
  let patternRe = null;
4210
4422
  try {
4211
- patternRe = p.literal ? new RegExp(escapeForRegex(p.pattern), flags) : new RegExp(p.pattern, flags);
4423
+ patternRe = p.literal ? new RegExp(p.pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), flags) : new RegExp(p.pattern, flags);
4212
4424
  } catch {}
4213
4425
  if (patternRe !== null) {
4214
4426
  let emitted = false;
@@ -4292,7 +4504,7 @@ ${warning}` } : item),
4292
4504
  renderedGroups = scoped.groups;
4293
4505
  scopeWarnings = scoped.warnings;
4294
4506
  }
4295
- const readSeekRecords = recordsFromGroups(renderedGroups);
4507
+ const readSeekRecords = renderedGroups.flatMap((group) => group.entries.flatMap((entry) => entry.kind === "separator" ? [] : [{ path: group.absolutePath, ...entry.line, kind: entry.kind }]));
4296
4508
  const builtOutput = buildGrepOutput({
4297
4509
  summary: !!summary,
4298
4510
  totalMatches,
@@ -4363,8 +4575,8 @@ function registerGrepTool(pi, options = {}) {
4363
4575
  }
4364
4576
  return new Text4(clampLineToWidth(text, context.width), 0, 0);
4365
4577
  },
4366
- renderResult(result, options2, theme, ...rest) {
4367
- const { isPartial, isError, expanded, cwd, width } = resolveRenderResultContext(options2, rest, resolveReadSeekToolDisplayMode("grep") === "expanded");
4578
+ renderResult(result, options, theme, ...rest) {
4579
+ const { isPartial, isError, expanded, cwd, width } = resolveRenderResultContext(options, rest, resolveReadSeekToolDisplayMode("grep") === "expanded");
4368
4580
  if (isPartial)
4369
4581
  return renderPendingResult("pending grep", width, theme);
4370
4582
  const content = result.content?.[0];
@@ -4411,7 +4623,7 @@ function registerGrepTool(pi, options = {}) {
4411
4623
 
4412
4624
  // src/search.ts
4413
4625
  import path4 from "node:path";
4414
- import { Type as Type5 } from "@sinclair/typebox";
4626
+ import { Type as Type6 } from "@sinclair/typebox";
4415
4627
 
4416
4628
  // src/stat-search-path.ts
4417
4629
  import { stat as fsStat2 } from "node:fs/promises";
@@ -4419,9 +4631,9 @@ async function statSearchPathOrError(tool, rawPath, searchPath) {
4419
4631
  try {
4420
4632
  return { ok: true, stats: await fsStat2(searchPath) };
4421
4633
  } catch (err) {
4422
- const path4 = rawPath ?? searchPath;
4634
+ const path = rawPath ?? searchPath;
4423
4635
  const { code, message } = formatFsError(err, "stat-error");
4424
- return { ok: false, error: buildToolErrorResult(tool, code, message, { path: path4 }) };
4636
+ return { ok: false, error: buildToolErrorResult(tool, code, message, { path }) };
4425
4637
  }
4426
4638
  }
4427
4639
 
@@ -4502,9 +4714,6 @@ function readSeekLanguageForPath(language, searchPath, isFile) {
4502
4714
  async function executeSearch(opts) {
4503
4715
  const { params, signal, cwd, onFileAnchored } = opts;
4504
4716
  const p = params;
4505
- const ignoredError = validateIgnoredRequiresOthers("search", p);
4506
- if (ignoredError)
4507
- return ignoredError;
4508
4717
  const searchPath = resolveToCwd2(p.path ?? ".", cwd);
4509
4718
  const statResult = await statSearchPathOrError("search", p.path, searchPath);
4510
4719
  if (!statResult.ok)
@@ -4514,9 +4723,7 @@ async function executeSearch(opts) {
4514
4723
  const effectiveLang = readSeekLanguageForPath(p.language, searchPath, searchPathIsFile);
4515
4724
  const results = await readSeekSearch(searchPath, p.pattern, {
4516
4725
  language: effectiveLang,
4517
- cached: p.cached,
4518
- others: p.others,
4519
- ignored: p.ignored,
4726
+ gitScope: p.gitScope,
4520
4727
  signal
4521
4728
  });
4522
4729
  if (results.length === 0) {
@@ -4578,8 +4785,8 @@ function registerSearchTool(pi, options = {}) {
4578
4785
  description: SEARCH_PROMPT_METADATA.description,
4579
4786
  promptSnippet: SEARCH_PROMPT_METADATA.promptSnippet,
4580
4787
  promptGuidelines: SEARCH_PROMPT_METADATA.promptGuidelines,
4581
- parameters: Type5.Object({
4582
- pattern: Type5.String({ description: PARAM_DESCRIPTIONS.pattern }),
4788
+ parameters: Type6.Object({
4789
+ pattern: Type6.String({ description: PARAM_DESCRIPTIONS.pattern }),
4583
4790
  language: languageParam(),
4584
4791
  path: searchPathParam(),
4585
4792
  ...readSeekGitSearchParams()
@@ -4591,11 +4798,11 @@ function registerSearchTool(pi, options = {}) {
4591
4798
  return renderReadSeekSearchCall(args, theme, rest, {
4592
4799
  label: "search",
4593
4800
  accent: `/${args.pattern}/`,
4594
- flags: [args.cached && "cached", args.others && "others", args.ignored && "ignored"]
4801
+ flags: [args.gitScope && `git-scope=${args.gitScope}`]
4595
4802
  });
4596
4803
  },
4597
- renderResult(result, options2, theme, ...rest) {
4598
- return renderAnchoredFilesResult(result, options2, theme, rest, {
4804
+ renderResult(result, options, theme, ...rest) {
4805
+ return renderAnchoredFilesResult(result, options, theme, rest, {
4599
4806
  pendingLabel: "pending search",
4600
4807
  emptyLabel: "no matches",
4601
4808
  unitSingular: "match",
@@ -4607,7 +4814,7 @@ function registerSearchTool(pi, options = {}) {
4607
4814
  }
4608
4815
 
4609
4816
  // src/refs.ts
4610
- import { Type as Type6 } from "@sinclair/typebox";
4817
+ import { Type as Type7 } from "@sinclair/typebox";
4611
4818
  import path5 from "node:path";
4612
4819
 
4613
4820
  // src/refs-output.ts
@@ -4673,9 +4880,6 @@ function isReadSeekCursorValidationFailure(message) {
4673
4880
  async function executeRefs(opts) {
4674
4881
  const { params, signal, cwd, onFileAnchored } = opts;
4675
4882
  const p = params;
4676
- const ignoredError = validateIgnoredRequiresOthers("refs", p);
4677
- if (ignoredError)
4678
- return ignoredError;
4679
4883
  const scopeError = validateRefsNameVsScope(p);
4680
4884
  if (!scopeError.ok) {
4681
4885
  return buildToolErrorResult("refs", "invalid-parameter", `refs parameter: ${scopeError.error}`);
@@ -4691,9 +4895,7 @@ async function executeRefs(opts) {
4691
4895
  line: p.line,
4692
4896
  column: p.column,
4693
4897
  language: p.language,
4694
- cached: p.cached,
4695
- others: p.others,
4696
- ignored: p.ignored,
4898
+ gitScope: p.gitScope,
4697
4899
  signal
4698
4900
  });
4699
4901
  const files = groupReferences(references, cwd);
@@ -4726,13 +4928,13 @@ function registerRefsTool(pi, options = {}) {
4726
4928
  description: REFS_PROMPT_METADATA.description,
4727
4929
  promptSnippet: REFS_PROMPT_METADATA.promptSnippet,
4728
4930
  promptGuidelines: REFS_PROMPT_METADATA.promptGuidelines,
4729
- parameters: Type6.Object({
4730
- name: Type6.Optional(Type6.String({ description: PARAM_DESCRIPTIONS.refsName })),
4931
+ parameters: Type7.Object({
4932
+ name: Type7.Optional(Type7.String({ description: PARAM_DESCRIPTIONS.refsName })),
4731
4933
  path: searchPathParam(),
4732
4934
  language: languageParam(),
4733
- scope: Type6.Optional(Type6.Boolean({ description: PARAM_DESCRIPTIONS.scope })),
4734
- line: Type6.Optional(Type6.Number({ description: PARAM_DESCRIPTIONS.line })),
4735
- column: Type6.Optional(Type6.Number({ description: PARAM_DESCRIPTIONS.column })),
4935
+ scope: Type7.Optional(Type7.Boolean({ description: PARAM_DESCRIPTIONS.scope })),
4936
+ line: Type7.Optional(Type7.Number({ description: PARAM_DESCRIPTIONS.line })),
4937
+ column: Type7.Optional(Type7.Number({ description: PARAM_DESCRIPTIONS.column })),
4736
4938
  ...readSeekGitSearchParams()
4737
4939
  }),
4738
4940
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
@@ -4743,11 +4945,11 @@ function registerRefsTool(pi, options = {}) {
4743
4945
  return renderReadSeekSearchCall(args, theme, rest, {
4744
4946
  label: "refs",
4745
4947
  accent,
4746
- flags: [args.scope && "scope", args.cached && "cached", args.others && "others", args.ignored && "ignored"]
4948
+ flags: [args.scope && "scope", args.gitScope && `git-scope=${args.gitScope}`]
4747
4949
  });
4748
4950
  },
4749
- renderResult(result, options2, theme, ...rest) {
4750
- return renderAnchoredFilesResult(result, options2, theme, rest, {
4951
+ renderResult(result, options, theme, ...rest) {
4952
+ return renderAnchoredFilesResult(result, options, theme, rest, {
4751
4953
  pendingLabel: "pending refs",
4752
4954
  emptyLabel: "no references",
4753
4955
  unitSingular: "reference",
@@ -4759,20 +4961,20 @@ function registerRefsTool(pi, options = {}) {
4759
4961
  }
4760
4962
 
4761
4963
  // src/rename.ts
4762
- import { Type as Type7 } from "@sinclair/typebox";
4964
+ import { Type as Type8 } from "@sinclair/typebox";
4763
4965
  import path6 from "node:path";
4764
4966
  import { Text as Text5 } from "@earendil-works/pi-tui";
4765
4967
  var RENAME_PROMPT_METADATA = defineToolPromptMetadata({
4766
4968
  promptUrl: new URL("../prompts/rename.md", import.meta.url),
4767
4969
  promptSnippet: "Rename a symbol at a source location with verified edits"
4768
4970
  });
4769
- var renameSchema = Type7.Object({
4971
+ var renameSchema = Type8.Object({
4770
4972
  path: filePathParam(),
4771
- line: Type7.Integer({ minimum: 1, description: PARAM_DESCRIPTIONS.renameLine }),
4772
- column: Type7.Optional(Type7.Integer({ minimum: 1, description: PARAM_DESCRIPTIONS.column })),
4773
- to: Type7.String({ description: PARAM_DESCRIPTIONS.to }),
4774
- workspace: Type7.Optional(Type7.Boolean({ description: PARAM_DESCRIPTIONS.workspace })),
4775
- apply: Type7.Optional(Type7.Boolean({ description: PARAM_DESCRIPTIONS.applyDefaultTrue }))
4973
+ line: Type8.Integer({ minimum: 1, description: PARAM_DESCRIPTIONS.renameLine }),
4974
+ column: Type8.Optional(Type8.Integer({ minimum: 1, description: PARAM_DESCRIPTIONS.column })),
4975
+ to: Type8.String({ description: PARAM_DESCRIPTIONS.to }),
4976
+ workspace: Type8.Optional(Type8.Boolean({ description: PARAM_DESCRIPTIONS.workspace })),
4977
+ apply: Type8.Optional(Type8.Boolean({ description: PARAM_DESCRIPTIONS.applyDefaultTrue }))
4776
4978
  });
4777
4979
  async function executeRename(opts) {
4778
4980
  const { params, signal, cwd, onFileMutated } = opts;
@@ -4804,7 +5006,8 @@ async function executeRename(opts) {
4804
5006
  onFileMutated?.(absoluteFile);
4805
5007
  }
4806
5008
  }
4807
- const files = [output.file];
5009
+ const primaryAbs = path6.isAbsolute(output.file) ? output.file : path6.resolve(cwd, output.file);
5010
+ const files = [primaryAbs];
4808
5011
  for (const other of output.others) {
4809
5012
  const abs = path6.isAbsolute(other.file) ? other.file : path6.resolve(cwd, other.file);
4810
5013
  if (!files.includes(abs))
@@ -4864,8 +5067,8 @@ function registerRenameTool(pi, options = {}) {
4864
5067
  text += theme.fg("dim", ` → ${args.to}`);
4865
5068
  return new Text5(clampLineToWidth(text, context.width), 0, 0);
4866
5069
  },
4867
- renderResult(result, options2, theme, ...rest) {
4868
- const { isPartial, isError, expanded, cwd, width } = resolveRenderResultContext(options2, rest);
5070
+ renderResult(result, options, theme, ...rest) {
5071
+ const { isPartial, isError, expanded, cwd, width } = resolveRenderResultContext(options, rest);
4869
5072
  if (isPartial)
4870
5073
  return renderPendingResult("pending rename", width, theme);
4871
5074
  const content = result.content?.[0];
@@ -4906,7 +5109,7 @@ import { mkdir, readFile, writeFile } from "node:fs/promises";
4906
5109
  import { dirname as dirname2, relative as relative3 } from "node:path";
4907
5110
  import { withFileMutationQueue as withFileMutationQueue2, truncateHead as truncateHead2 } from "@earendil-works/pi-coding-agent";
4908
5111
  import { Text as Text6 } from "@earendil-works/pi-tui";
4909
- import { Type as Type8 } from "@sinclair/typebox";
5112
+ import { Type as Type9 } from "@sinclair/typebox";
4910
5113
 
4911
5114
  // src/pending-diff-preview.ts
4912
5115
  import { existsSync as existsSync3, readFileSync as readFileSync2, realpathSync, statSync as statSync4 } from "node:fs";
@@ -5053,9 +5256,9 @@ function pendingWritePreviewParts(summary, preview, expanded, theme) {
5053
5256
  if (!preview || preview.type !== "ok")
5054
5257
  return { lines: summary.split(`
5055
5258
  `) };
5056
- const hasOldSide2 = preview.data.fileExistedBeforeWrite;
5259
+ const hasOldSide = preview.data.fileExistedBeforeWrite;
5057
5260
  const headerLine = summaryLine(preview.data.headerLabel, { hidden: !expanded });
5058
- if (!hasOldSide2) {
5261
+ if (!hasOldSide) {
5059
5262
  const lines = [summary, headerLine];
5060
5263
  if (expanded)
5061
5264
  lines.push(...formatContentPreviewLines(preview.data.nextContent, theme));
@@ -5111,7 +5314,7 @@ async function executeWrite(opts) {
5111
5314
  const warnings = [];
5112
5315
  const readSeekWarnings = [];
5113
5316
  if (hasBareCarriageReturn(content)) {
5114
- const message = "File content contains bare CR (\\r) line endings; readSeek_write refuses to emit anchors that readSeek_digest/readSeek_edit would normalize differently.";
5317
+ const message = "File content contains bare CR (\\r) line endings; readSeek_write refuses to emit anchors that readSeek_inspect/readSeek_edit would normalize differently.";
5115
5318
  warnings.push(message);
5116
5319
  readSeekWarnings.push(buildReadSeekWarning("bare-cr", message));
5117
5320
  return buildToolErrorResult("write", "bare-cr", `Cannot write ${filePath}
@@ -5202,9 +5405,9 @@ function registerWriteTool(pi, options = {}) {
5202
5405
  description: promptMetadata.description,
5203
5406
  promptSnippet: promptMetadata.promptSnippet,
5204
5407
  promptGuidelines: promptMetadata.promptGuidelines,
5205
- parameters: Type8.Object({
5408
+ parameters: Type9.Object({
5206
5409
  path: filePathParam(),
5207
- content: Type8.String({ description: PARAM_DESCRIPTIONS.content })
5410
+ content: Type9.String({ description: PARAM_DESCRIPTIONS.content })
5208
5411
  }),
5209
5412
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
5210
5413
  const cwd = ctx?.cwd ?? process.cwd();
@@ -5242,13 +5445,13 @@ function registerWriteTool(pi, options = {}) {
5242
5445
  }
5243
5446
  },
5244
5447
  renderCall(args, theme, context = {}) {
5245
- const { path: path7, content } = args;
5448
+ const { path, content } = args;
5246
5449
  const cwd = context.cwd ?? process.cwd();
5247
5450
  const label = renderToolLabel(theme, "write");
5248
5451
  const lineCount = typeof content === "string" ? content.split(`
5249
5452
  `).length : 0;
5250
5453
  const bytes = typeof content === "string" ? Buffer.byteLength(content, "utf8") : 0;
5251
- const renderedPath = typeof path7 === "string" ? linkToolPath(theme.fg("muted", path7), path7, cwd) : theme.fg("toolOutput", "...");
5454
+ const renderedPath = typeof path === "string" ? linkToolPath(theme.fg("muted", path), path, cwd) : theme.fg("toolOutput", "...");
5252
5455
  let text = clampLineToWidth(`${label} ${renderedPath}${typeof content === "string" ? ` (${lineCount} ${lineCount === 1 ? "line" : "lines"} • ${bytes} B)` : ""}`, context.width);
5253
5456
  if (context.executionStarted) {
5254
5457
  return upsertTextComponent(context.lastComponent, text);
@@ -5263,8 +5466,8 @@ function registerWriteTool(pi, options = {}) {
5263
5466
  return upsertTextComponent(context.lastComponent, clampLinesToWidth(parts.lines, context.width).join(`
5264
5467
  `));
5265
5468
  },
5266
- renderResult(result, options2, theme, ...rest) {
5267
- const { isPartial, expanded, width, context } = resolveRenderResultContext(options2, rest, resolveReadSeekToolDisplayMode("write") === "expanded");
5469
+ renderResult(result, options, theme, ...rest) {
5470
+ const { isPartial, expanded, width, context } = resolveRenderResultContext(options, rest, resolveReadSeekToolDisplayMode("write") === "expanded");
5268
5471
  if (isPartial)
5269
5472
  return renderPendingResult("pending write", width, theme);
5270
5473
  const details = result.details ?? {};
@@ -5277,8 +5480,8 @@ function registerWriteTool(pi, options = {}) {
5277
5480
  if (state === "created") {
5278
5481
  const readSeekLines = details.readSeekValue?.lines ?? [];
5279
5482
  const hasContent = readSeekLines.length > 0;
5280
- const header2 = summaryLine(state, { hidden: hasContent && !expanded, theme, style: "success" });
5281
- const lines = header2.split(`
5483
+ const header = summaryLine(state, { hidden: hasContent && !expanded, theme, style: "success" });
5484
+ const lines = header.split(`
5282
5485
  `);
5283
5486
  if (expanded && hasContent) {
5284
5487
  const content = readSeekLines.map((l) => l.raw).join(`
@@ -5303,7 +5506,7 @@ function registerWriteTool(pi, options = {}) {
5303
5506
  }
5304
5507
 
5305
5508
  // src/def.ts
5306
- import { Type as Type9 } from "@sinclair/typebox";
5509
+ import { Type as Type10 } from "@sinclair/typebox";
5307
5510
  import path7 from "node:path";
5308
5511
  var DEF_PROMPT_METADATA = defineToolPromptMetadata({
5309
5512
  promptUrl: new URL("../prompts/def.md", import.meta.url),
@@ -5312,9 +5515,6 @@ var DEF_PROMPT_METADATA = defineToolPromptMetadata({
5312
5515
  async function executeDef(opts) {
5313
5516
  const { params, signal, cwd, onFileAnchored } = opts;
5314
5517
  const p = params;
5315
- const ignoredError = validateIgnoredRequiresOthers("def", p);
5316
- if (ignoredError)
5317
- return ignoredError;
5318
5518
  if (!p.name || !p.name.trim()) {
5319
5519
  return buildToolErrorResult("def", "invalid-parameter", "readSeek_def requires 'name'");
5320
5520
  }
@@ -5326,9 +5526,7 @@ async function executeDef(opts) {
5326
5526
  const definitions = await readSeekDef(searchPath, {
5327
5527
  name: p.name,
5328
5528
  language: p.language,
5329
- cached: p.cached,
5330
- others: p.others,
5331
- ignored: p.ignored,
5529
+ gitScope: p.gitScope,
5332
5530
  signal
5333
5531
  });
5334
5532
  if (definitions.length === 0) {
@@ -5379,8 +5577,8 @@ function registerDefTool(pi, options = {}) {
5379
5577
  description: DEF_PROMPT_METADATA.description,
5380
5578
  promptSnippet: DEF_PROMPT_METADATA.promptSnippet,
5381
5579
  promptGuidelines: DEF_PROMPT_METADATA.promptGuidelines,
5382
- parameters: Type9.Object({
5383
- name: Type9.String({ description: PARAM_DESCRIPTIONS.name }),
5580
+ parameters: Type10.Object({
5581
+ name: Type10.String({ description: PARAM_DESCRIPTIONS.name }),
5384
5582
  path: searchPathParam(),
5385
5583
  language: languageParam(),
5386
5584
  ...readSeekGitSearchParams()
@@ -5392,11 +5590,11 @@ function registerDefTool(pi, options = {}) {
5392
5590
  return renderReadSeekSearchCall(args, theme, rest, {
5393
5591
  label: "def",
5394
5592
  accent: args.name,
5395
- flags: [args.cached && "cached", args.others && "others", args.ignored && "ignored"]
5593
+ flags: [args.gitScope && `git-scope=${args.gitScope}`]
5396
5594
  });
5397
5595
  },
5398
- renderResult(result, options2, theme, ...rest) {
5399
- return renderAnchoredFilesResult(result, options2, theme, rest, {
5596
+ renderResult(result, options, theme, ...rest) {
5597
+ return renderAnchoredFilesResult(result, options, theme, rest, {
5400
5598
  pendingLabel: "pending def",
5401
5599
  emptyLabel: "no definitions",
5402
5600
  unitSingular: "definition",
@@ -5406,39 +5604,33 @@ function registerDefTool(pi, options = {}) {
5406
5604
  });
5407
5605
  }
5408
5606
 
5409
- // src/digest.ts
5607
+ // src/inspect.ts
5410
5608
  import { Text as Text7 } from "@earendil-works/pi-tui";
5411
- import { Type as Type10 } from "@sinclair/typebox";
5412
- var DIGEST_FACETS2 = DIGEST_FACETS;
5413
- var facetSchema = Type10.Union(DIGEST_FACETS2.map((facet) => Type10.Literal(facet)));
5414
- var digestSchema = Type10.Object({
5609
+ import { Type as Type11 } from "@sinclair/typebox";
5610
+ var facetSchema = Type11.Union(INSPECT_FACETS.map((facet) => Type11.Literal(facet)));
5611
+ var inspectSchema = Type11.Object({
5415
5612
  path: filePathParam(),
5416
- select: Type10.Optional(Type10.Union([
5613
+ selector: Type11.Optional(Type11.String({ description: PARAM_DESCRIPTIONS.selector })),
5614
+ select: Type11.Optional(Type11.Union([
5417
5615
  facetSchema,
5418
- Type10.Array(facetSchema, { minItems: 1 }),
5419
- Type10.String({
5420
- description: PARAM_DESCRIPTIONS.selectComma
5421
- })
5422
- ], {
5423
- description: PARAM_DESCRIPTIONS.select
5424
- })),
5425
- at: Type10.Optional(Type10.String({
5426
- description: PARAM_DESCRIPTIONS.at
5427
- })),
5616
+ Type11.Array(facetSchema, { minItems: 1 }),
5617
+ Type11.String({ description: PARAM_DESCRIPTIONS.selectComma })
5618
+ ], { description: PARAM_DESCRIPTIONS.select })),
5619
+ query: Type11.Optional(Type11.String({ description: PARAM_DESCRIPTIONS.query })),
5428
5620
  end: optionalIntOrString(PARAM_DESCRIPTIONS.end),
5429
5621
  limit: optionalIntOrString(PARAM_DESCRIPTIONS.limit),
5430
- depth: optionalIntOrString(PARAM_DESCRIPTIONS.digestDepth),
5431
- language: Type10.Optional(Type10.String({ description: PARAM_DESCRIPTIONS.languageOverride })),
5432
- visionMode: Type10.Optional(Type10.Union(VISION_MODES.map((mode) => Type10.Literal(mode)), { description: PARAM_DESCRIPTIONS.visionModeDigest })),
5433
- visionLevel: Type10.Optional(Type10.Union(VISION_LEVELS.map((level) => Type10.Literal(level)), { description: PARAM_DESCRIPTIONS.visionLevel }))
5622
+ depth: optionalIntOrString(PARAM_DESCRIPTIONS.inspectDepth),
5623
+ language: Type11.Optional(Type11.String({ description: PARAM_DESCRIPTIONS.languageOverride })),
5624
+ visionMode: Type11.Optional(Type11.Union(VISION_MODES.map((mode) => Type11.Literal(mode)), { description: PARAM_DESCRIPTIONS.visionModeInspect })),
5625
+ visionLevel: Type11.Optional(Type11.Union(VISION_LEVELS.map((level) => Type11.Literal(level)), { description: PARAM_DESCRIPTIONS.visionLevel }))
5434
5626
  });
5435
- function hasDigestAnchors(value) {
5627
+ function hasInspectAnchors(value) {
5436
5628
  if (!value || typeof value !== "object")
5437
5629
  return false;
5438
5630
  const record = value;
5439
5631
  if (Array.isArray(record.hashlines) && record.hashlines.length > 0)
5440
5632
  return true;
5441
- return Object.values(record).some(hasDigestAnchors);
5633
+ return Object.values(record).some(hasInspectAnchors);
5442
5634
  }
5443
5635
  function parseSelect(select) {
5444
5636
  if (select === undefined)
@@ -5448,8 +5640,8 @@ function parseSelect(select) {
5448
5640
  if (facets.length === 0)
5449
5641
  throw new Error("select must include at least one facet");
5450
5642
  for (const facet of facets) {
5451
- if (!DIGEST_FACETS2.includes(facet)) {
5452
- throw new Error(`unknown digest facet \`${facet}\`; expected ${DIGEST_FACETS2.join(", ")}`);
5643
+ if (!INSPECT_FACETS.includes(facet)) {
5644
+ throw new Error(`unknown inspect facet \`${facet}\`; expected ${INSPECT_FACETS.join(", ")}`);
5453
5645
  }
5454
5646
  }
5455
5647
  return facets;
@@ -5457,15 +5649,15 @@ function parseSelect(select) {
5457
5649
  if (typeof select !== "string") {
5458
5650
  throw new Error("select must be a facet, facet list, or comma-separated facets");
5459
5651
  }
5460
- if (DIGEST_FACETS2.includes(select)) {
5652
+ if (INSPECT_FACETS.includes(select)) {
5461
5653
  return select;
5462
5654
  }
5463
5655
  const parts = select.split(",").map((part) => part.trim()).filter((part) => part.length > 0);
5464
5656
  if (parts.length === 0)
5465
5657
  throw new Error("select must include at least one facet");
5466
5658
  for (const part of parts) {
5467
- if (!DIGEST_FACETS2.includes(part)) {
5468
- throw new Error(`unknown digest facet \`${part}\`; expected ${DIGEST_FACETS2.join(", ")}`);
5659
+ if (!INSPECT_FACETS.includes(part)) {
5660
+ throw new Error(`unknown inspect facet \`${part}\`; expected ${INSPECT_FACETS.join(", ")}`);
5469
5661
  }
5470
5662
  }
5471
5663
  return parts;
@@ -5477,7 +5669,7 @@ function selectedFacets(select) {
5477
5669
  }
5478
5670
  function summarizeEnvelope(envelope, facets) {
5479
5671
  if (!envelope || typeof envelope !== "object")
5480
- return `digested ${facets.join(",")}`;
5672
+ return `inspected ${facets.join(",")}`;
5481
5673
  const record = envelope;
5482
5674
  const parts = [];
5483
5675
  for (const facet of facets) {
@@ -5486,7 +5678,7 @@ function summarizeEnvelope(envelope, facets) {
5486
5678
  }
5487
5679
  if (record.metadata !== undefined && !parts.includes("metadata"))
5488
5680
  parts.unshift("metadata");
5489
- return `digested ${parts.join(",") || "metadata"}`;
5681
+ return `inspected ${parts.join(",") || "metadata"}`;
5490
5682
  }
5491
5683
  function parseImageEnvelope(envelope) {
5492
5684
  if (!envelope || typeof envelope !== "object")
@@ -5504,12 +5696,12 @@ function parseImageEnvelope(envelope) {
5504
5696
  const prepared = content.encoding === "base64" && typeof content.data === "string" && typeof mime === "string" ? { data: content.data, mimeType: mime } : undefined;
5505
5697
  return { metadata, prepared };
5506
5698
  }
5507
- function buildDigestSuccess(filePath, facets, envelope, content = [{ type: "text", text: JSON.stringify(envelope) }]) {
5699
+ function buildInspectSuccess(filePath, facets, envelope, content = [{ type: "text", text: JSON.stringify(envelope) }]) {
5508
5700
  return {
5509
5701
  content,
5510
5702
  details: {
5511
5703
  readSeekValue: {
5512
- tool: "digest",
5704
+ tool: "inspect",
5513
5705
  ok: true,
5514
5706
  path: filePath,
5515
5707
  select: facets,
@@ -5518,14 +5710,14 @@ function buildDigestSuccess(filePath, facets, envelope, content = [{ type: "text
5518
5710
  }
5519
5711
  };
5520
5712
  }
5521
- async function executeDigest(opts) {
5713
+ async function executeInspect(opts) {
5522
5714
  const { params, signal, cwd, onFileAnchored } = opts;
5523
5715
  const p = params;
5524
5716
  let select;
5525
5717
  try {
5526
5718
  select = parseSelect(p.select);
5527
5719
  } catch (err) {
5528
- return buildToolErrorResult("digest", "invalid-parameter", err?.message ?? String(err), {
5720
+ return buildToolErrorResult("inspect", "invalid-parameter", err?.message ?? String(err), {
5529
5721
  path: p.path
5530
5722
  });
5531
5723
  }
@@ -5534,52 +5726,66 @@ async function executeDigest(opts) {
5534
5726
  const end = coerceObviousBase10Int2(p.end, "end");
5535
5727
  if (!end.ok || end.value !== undefined && end.value < 1) {
5536
5728
  const message = end.ok ? `Invalid end: expected a positive integer, received ${end.value}.` : end.message;
5537
- return buildToolErrorResult("digest", "invalid-parameter", message, { path: p.path });
5729
+ return buildToolErrorResult("inspect", "invalid-parameter", message, { path: p.path });
5538
5730
  }
5539
5731
  const limit = coerceObviousBase10Int2(p.limit, "limit");
5540
5732
  if (!limit.ok || limit.value !== undefined && limit.value < 1) {
5541
5733
  const message = limit.ok ? `Invalid limit: expected a positive integer, received ${limit.value}.` : limit.message;
5542
- return buildToolErrorResult("digest", "invalid-parameter", message, { path: p.path });
5734
+ return buildToolErrorResult("inspect", "invalid-parameter", message, { path: p.path });
5735
+ }
5736
+ if (end.value !== undefined && limit.value !== undefined) {
5737
+ return buildToolErrorResult("inspect", "invalid-params-combo", "end and limit cannot be combined.", { path: p.path });
5543
5738
  }
5544
5739
  const depth = coerceObviousBase10Int2(p.depth, "depth");
5545
5740
  if (!depth.ok || depth.value !== undefined && depth.value < 0) {
5546
5741
  const message = depth.ok ? `Invalid depth: expected a non-negative integer, received ${depth.value}.` : depth.message;
5547
- return buildToolErrorResult("digest", "invalid-parameter", message, { path: p.path });
5742
+ return buildToolErrorResult("inspect", "invalid-parameter", message, { path: p.path });
5548
5743
  }
5549
5744
  if (depth.value !== undefined && !facets.includes("map")) {
5550
- return buildToolErrorResult("digest", "invalid-parameter", "depth requires the map facet.", { path: p.path });
5745
+ return buildToolErrorResult("inspect", "invalid-parameter", "depth requires the map facet.", { path: p.path });
5551
5746
  }
5552
- const at = typeof p.at === "string" ? p.at.trim() : undefined;
5553
- if (p.at !== undefined && !at) {
5554
- return buildToolErrorResult("digest", "invalid-parameter", "Invalid at: expected a non-empty location.", {
5555
- path: p.path
5556
- });
5747
+ const selector = typeof p.selector === "string" ? p.selector.trim() : undefined;
5748
+ if (p.selector !== undefined && !selector) {
5749
+ return buildToolErrorResult("inspect", "invalid-parameter", "Invalid selector: expected a non-empty selector.", { path: p.path });
5557
5750
  }
5558
- if (p.visionLevel !== undefined && p.visionMode === undefined && !appliesImagePolicy) {
5559
- return buildToolErrorResult("digest", "invalid-params-combo", "visionLevel requires visionMode.", {
5560
- path: p.path
5561
- });
5751
+ const query = typeof p.query === "string" ? p.query.trim() : undefined;
5752
+ if (p.query !== undefined && !query) {
5753
+ return buildToolErrorResult("inspect", "invalid-parameter", "Invalid query: expected a non-empty query.", { path: p.path });
5754
+ }
5755
+ if (selector !== undefined && query !== undefined) {
5756
+ return buildToolErrorResult("inspect", "invalid-params-combo", "selector and query cannot be combined.", { path: p.path });
5757
+ }
5758
+ if (facets.includes("identity") && selector === undefined) {
5759
+ return buildToolErrorResult("inspect", "invalid-params-combo", "the identity facet requires a selector.", { path: p.path });
5760
+ }
5761
+ if (p.visionLevel !== undefined && (p.visionMode === undefined || p.visionMode === "none") && !appliesImagePolicy) {
5762
+ return buildToolErrorResult("inspect", "invalid-params-combo", "visionLevel requires an analysis visionMode.", { path: p.path });
5562
5763
  }
5563
5764
  const filePath = resolveToCwd2(p.path, cwd);
5564
5765
  const commonOptions = {
5565
5766
  select,
5566
5767
  signal,
5567
- depth: depth.value ?? (facets.includes("map") ? DEFAULT_DIGEST_MAP_DEPTH : undefined)
5768
+ depth: depth.value ?? (facets.includes("map") ? DEFAULT_INSPECT_MAP_DEPTH : undefined)
5568
5769
  };
5569
- const textOptions = {
5770
+ const selectionOptions = {
5570
5771
  ...commonOptions,
5571
- at,
5772
+ selector,
5773
+ query,
5572
5774
  end: end.value,
5573
5775
  limit: limit.value,
5574
5776
  language: p.language
5575
5777
  };
5576
- const hasTextOptions = at !== undefined || end.value !== undefined || limit.value !== undefined || p.language !== undefined;
5778
+ const probeOptions = {
5779
+ ...commonOptions,
5780
+ select: [...new Set(["metadata", ...facets])].filter((facet) => facet !== "identity")
5781
+ };
5782
+ const hasSelectionOptions = selector !== undefined || query !== undefined || end.value !== undefined || limit.value !== undefined || p.language !== undefined;
5577
5783
  const hasVisionOptions = p.visionMode !== undefined || p.visionLevel !== undefined;
5578
5784
  const initialVisionMode = appliesImagePolicy ? undefined : p.visionMode;
5579
5785
  const initialVisionLevel = appliesImagePolicy ? undefined : p.visionLevel;
5580
5786
  try {
5581
- const envelope = await readSeekDigestNative(filePath, {
5582
- ...appliesImagePolicy ? commonOptions : textOptions,
5787
+ const envelope = await readSeekInspect(filePath, {
5788
+ ...appliesImagePolicy ? probeOptions : selectionOptions,
5583
5789
  visionMode: initialVisionMode,
5584
5790
  visionLevel: initialVisionLevel,
5585
5791
  vision: initialVisionMode !== undefined && initialVisionMode !== "none"
@@ -5588,60 +5794,60 @@ async function executeDigest(opts) {
5588
5794
  if (image) {
5589
5795
  const imageDetails = { metadata: image.metadata };
5590
5796
  if (opts.imageMode === "off") {
5591
- return buildDigestSuccess(filePath, facets, imageDetails, [
5797
+ return buildInspectSuccess(filePath, facets, imageDetails, [
5592
5798
  { type: "text", text: `[Skipped standalone image: ${p.path}; readseek.imageMode is off]` }
5593
5799
  ]);
5594
5800
  }
5595
5801
  if (opts.imageMode === "auto" && opts.modelSupportsImages && image.prepared) {
5596
- return buildDigestSuccess(filePath, facets, imageDetails, [
5802
+ return buildInspectSuccess(filePath, facets, imageDetails, [
5597
5803
  { type: "image", data: image.prepared.data, mimeType: image.prepared.mimeType }
5598
5804
  ]);
5599
5805
  }
5600
5806
  const visionMode = p.visionMode && p.visionMode !== "none" ? p.visionMode : "all";
5601
- const analyzedEnvelope = await readSeekDigestNative(filePath, {
5807
+ const analyzedEnvelope = await readSeekInspect(filePath, {
5602
5808
  ...commonOptions,
5603
5809
  visionMode,
5604
5810
  visionLevel: p.visionLevel,
5605
5811
  vision: true
5606
5812
  });
5607
- return buildDigestSuccess(filePath, facets, analyzedEnvelope);
5813
+ return buildInspectSuccess(filePath, facets, analyzedEnvelope);
5608
5814
  }
5609
- if (p.visionLevel !== undefined && p.visionMode === undefined) {
5610
- return buildToolErrorResult("digest", "invalid-params-combo", "visionLevel requires visionMode.", {
5815
+ if (p.visionLevel !== undefined && (p.visionMode === undefined || p.visionMode === "none")) {
5816
+ return buildToolErrorResult("inspect", "invalid-params-combo", "visionLevel requires an analysis visionMode.", {
5611
5817
  path: p.path
5612
5818
  });
5613
5819
  }
5614
- const textEnvelope = appliesImagePolicy && (hasTextOptions || hasVisionOptions) ? await readSeekDigestNative(filePath, {
5615
- ...textOptions,
5820
+ const selectedEnvelope = appliesImagePolicy && (hasSelectionOptions || hasVisionOptions) ? await readSeekInspect(filePath, {
5821
+ ...selectionOptions,
5616
5822
  visionMode: p.visionMode,
5617
5823
  visionLevel: p.visionLevel,
5618
5824
  vision: p.visionMode !== undefined && p.visionMode !== "none"
5619
5825
  }) : envelope;
5620
- if (hasDigestAnchors(textEnvelope))
5826
+ if (hasInspectAnchors(selectedEnvelope))
5621
5827
  onFileAnchored?.(filePath);
5622
- return buildDigestSuccess(filePath, facets, textEnvelope);
5828
+ return buildInspectSuccess(filePath, facets, selectedEnvelope);
5623
5829
  } catch (err) {
5624
5830
  const failure = classifyReadSeekFailure(err);
5625
- return buildToolErrorResult("digest", failure.code, failure.message, failure.hint ? { hint: failure.hint } : {});
5831
+ return buildToolErrorResult("inspect", failure.code, failure.message, failure.hint ? { hint: failure.hint } : {});
5626
5832
  }
5627
5833
  }
5628
- function registerDigestTool(pi, options = {}) {
5834
+ function registerInspectTool(pi, options = {}) {
5629
5835
  const promptMetadata = defineToolPromptMetadata({
5630
- promptUrl: new URL("../prompts/digest.md", import.meta.url),
5631
- promptSnippet: "Digest source facets as a native CLI envelope",
5632
- registeredName: options.name ?? "readSeek_digest",
5836
+ promptUrl: new URL("../prompts/inspect.md", import.meta.url),
5837
+ promptSnippet: "Inspect source, images, and documents",
5838
+ registeredName: options.name ?? "readSeek_inspect",
5633
5839
  toolAliases: options.toolAliases
5634
5840
  });
5635
5841
  return registerReadSeekTool(pi, {
5636
- name: options.name ?? "readSeek_digest",
5637
- label: "Digest",
5842
+ name: options.name ?? "readSeek_inspect",
5843
+ label: "Inspect",
5638
5844
  description: promptMetadata.description,
5639
5845
  promptSnippet: promptMetadata.promptSnippet,
5640
5846
  toolAliases: options.toolAliases,
5641
5847
  promptGuidelines: promptMetadata.promptGuidelines,
5642
- parameters: digestSchema,
5848
+ parameters: inspectSchema,
5643
5849
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
5644
- return executeDigest({
5850
+ return executeInspect({
5645
5851
  params,
5646
5852
  signal,
5647
5853
  cwd: ctx.cwd,
@@ -5654,23 +5860,23 @@ function registerDigestTool(pi, options = {}) {
5654
5860
  const context = rest[0] ?? {};
5655
5861
  const cwd = context.cwd ?? process.cwd();
5656
5862
  const displayPath = typeof args?.path === "string" ? args.path : "?";
5657
- let text = theme.fg("toolTitle", theme.bold("digest"));
5863
+ let text = theme.fg("toolTitle", theme.bold("inspect"));
5658
5864
  text += ` ${linkToolPath(theme.fg("accent", displayPath), displayPath, cwd)}`;
5659
5865
  if (args?.select !== undefined) {
5660
5866
  const select = Array.isArray(args.select) ? args.select.join(",") : String(args.select);
5661
5867
  text += theme.fg("dim", ` [${select}]`);
5662
5868
  }
5663
- if (typeof args?.at === "string" && args.at)
5664
- text += theme.fg("dim", ` @ ${args.at}`);
5869
+ if (typeof args?.selector === "string" && args.selector)
5870
+ text += theme.fg("dim", ` @ ${args.selector}`);
5665
5871
  return new Text7(clampLineToWidth(text, context.width), 0, 0);
5666
5872
  },
5667
- renderResult(result, options2, theme, ...rest) {
5668
- const { isPartial, isError, expanded, width } = resolveRenderResultContext(options2, rest);
5873
+ renderResult(result, options, theme, ...rest) {
5874
+ const { isPartial, isError, expanded, width } = resolveRenderResultContext(options, rest);
5669
5875
  if (isPartial)
5670
- return renderPendingResult("pending digest", width, theme);
5876
+ return renderPendingResult("pending inspect", width, theme);
5671
5877
  const textContent = result.content?.[0]?.type === "text" ? result.content[0].text : "";
5672
5878
  if (isError || result.isError) {
5673
- return renderErrorResult(textContent, { expanded, width, fallback: "digest failed", theme });
5879
+ return renderErrorResult(textContent, { expanded, width, fallback: "inspect failed", theme });
5674
5880
  }
5675
5881
  const value = result.details?.readSeekValue;
5676
5882
  const facets = Array.isArray(value?.select) ? value.select : ["content"];
@@ -5686,131 +5892,88 @@ ${textContent}`;
5686
5892
  });
5687
5893
  }
5688
5894
 
5689
- // src/view.ts
5690
- import {
5691
- DEFAULT_MAX_BYTES as DEFAULT_MAX_BYTES2,
5692
- DEFAULT_MAX_LINES as DEFAULT_MAX_LINES2,
5693
- truncateHead as truncateHead3
5694
- } from "@earendil-works/pi-coding-agent";
5895
+ // src/extract.ts
5695
5896
  import { Text as Text8 } from "@earendil-works/pi-tui";
5696
- import { Type as Type11 } from "@sinclair/typebox";
5697
- var NODE_KINDS = DOCUMENT_NODE_KINDS;
5698
- var VIEW_PROMPT_METADATA = defineToolPromptMetadata({
5699
- promptUrl: new URL("../prompts/view.md", import.meta.url),
5700
- promptSnippet: "Digest a document's structure or selected content"
5897
+ import { Type as Type12 } from "@sinclair/typebox";
5898
+ var EXTRACT_PROMPT_METADATA = defineToolPromptMetadata({
5899
+ promptUrl: new URL("../prompts/extract.md", import.meta.url),
5900
+ promptSnippet: "Extract a selected document asset to a file"
5701
5901
  });
5702
- async function executeView(opts) {
5703
- const { params, signal, cwd } = opts;
5704
- const input = params;
5705
- const page = coerceObviousBase10Int2(input.page, "page");
5706
- if (!page.ok || page.value !== undefined && page.value < 1) {
5707
- const message = page.ok ? `Invalid page: expected a positive integer, received ${page.value}.` : page.message;
5708
- return buildToolErrorResult("view", "invalid-page", message, { path: input.path });
5709
- }
5710
- const depth = coerceObviousBase10Int2(input.depth, "depth");
5711
- if (!depth.ok || depth.value !== undefined && depth.value < 0) {
5712
- const message = depth.ok ? `Invalid depth: expected a non-negative integer, received ${depth.value}.` : depth.message;
5713
- return buildToolErrorResult("view", "invalid-depth", message, { path: input.path });
5714
- }
5715
- const node = input.node?.trim();
5716
- if (input.node !== undefined && !node) {
5717
- return buildToolErrorResult("view", "invalid-node", "Invalid node: expected a non-empty ID.", {
5902
+ async function executeExtract(opts) {
5903
+ const { signal, cwd, onFileMutated } = opts;
5904
+ const input = opts.params;
5905
+ const selector = typeof input.selector === "string" ? input.selector.trim() : "";
5906
+ if (!selector.startsWith("asset:") || selector === "asset:") {
5907
+ return buildToolErrorResult("extract", "invalid-selector", "selector must identify an asset as asset:ID.", {
5718
5908
  path: input.path
5719
5909
  });
5720
5910
  }
5721
- const visionValidation = validateVisionLevelVsMode({
5722
- outline: input.outline,
5723
- visionMode: input.visionMode,
5724
- visionLevel: input.visionLevel
5725
- });
5726
- if (!visionValidation.ok) {
5727
- return buildToolErrorResult("view", "invalid-params-combo", `${visionValidation.error}.`, {
5911
+ const output = typeof input.output === "string" ? input.output.trim() : "";
5912
+ if (!output || output === "-") {
5913
+ return buildToolErrorResult("extract", "invalid-output", "output must be a file path, not stdout.", {
5728
5914
  path: input.path
5729
5915
  });
5730
5916
  }
5731
5917
  const filePath = resolveToCwd2(input.path, cwd);
5918
+ const outputPath = resolveToCwd2(output, cwd);
5732
5919
  try {
5733
- const output = await readSeekView(filePath, {
5734
- node,
5735
- page: page.value,
5736
- kind: input.kind,
5737
- depth: depth.value,
5738
- outline: input.outline,
5739
- visionMode: input.visionMode,
5740
- visionLevel: input.visionLevel,
5741
- signal
5742
- });
5743
- const truncation = truncateHead3(output, {
5744
- maxLines: DEFAULT_MAX_LINES2,
5745
- maxBytes: DEFAULT_MAX_BYTES2
5746
- });
5747
- const notice = truncation.truncated ? `
5748
- [… document view truncated; narrow it with page, node, kind, or depth]` : "";
5920
+ await readSeekExtract(filePath, selector, outputPath, { signal, cancelable: false });
5921
+ onFileMutated?.(outputPath);
5749
5922
  return {
5750
- content: [{ type: "text", text: `${truncation.content}${notice}` }],
5923
+ content: [{ type: "text", text: `Extracted ${selector} to ${outputPath}` }],
5751
5924
  details: {
5752
5925
  readSeekValue: {
5753
- tool: "view",
5926
+ tool: "extract",
5927
+ ok: true,
5754
5928
  path: filePath,
5755
- truncated: truncation.truncated
5929
+ selector,
5930
+ output: outputPath
5756
5931
  }
5757
5932
  }
5758
5933
  };
5759
5934
  } catch (error) {
5760
5935
  const failure = classifyReadSeekFailure(error);
5761
- return buildToolErrorResult("view", failure.code, failure.message, failure.hint ? { hint: failure.hint } : {});
5936
+ return buildToolErrorResult("extract", failure.code, failure.message, failure.hint ? { hint: failure.hint } : {});
5762
5937
  }
5763
5938
  }
5764
- function registerViewTool(pi) {
5939
+ function registerExtractTool(pi, options = {}) {
5765
5940
  return registerReadSeekTool(pi, {
5766
- name: "readSeek_view",
5767
- label: "View",
5768
- description: VIEW_PROMPT_METADATA.description,
5769
- promptSnippet: VIEW_PROMPT_METADATA.promptSnippet,
5770
- promptGuidelines: VIEW_PROMPT_METADATA.promptGuidelines,
5771
- parameters: Type11.Object({
5941
+ name: "readSeek_extract",
5942
+ label: "Extract",
5943
+ ...EXTRACT_PROMPT_METADATA,
5944
+ parameters: Type12.Object({
5772
5945
  path: filePathParam(),
5773
- node: Type11.Optional(Type11.String({ description: PARAM_DESCRIPTIONS.node })),
5774
- page: optionalIntOrString(PARAM_DESCRIPTIONS.page),
5775
- kind: Type11.Optional(Type11.Union(NODE_KINDS.map((kind) => Type11.Literal(kind)), {
5776
- description: PARAM_DESCRIPTIONS.kind
5777
- })),
5778
- depth: optionalIntOrString(PARAM_DESCRIPTIONS.depth),
5779
- visionMode: Type11.Optional(Type11.Union(VISION_ANALYSIS_MODES.map((mode) => Type11.Literal(mode)), { description: PARAM_DESCRIPTIONS.visionModeView })),
5780
- visionLevel: Type11.Optional(Type11.Union(VISION_LEVELS.map((level) => Type11.Literal(level)), { description: PARAM_DESCRIPTIONS.visionLevel })),
5781
- outline: Type11.Optional(Type11.Boolean({ description: PARAM_DESCRIPTIONS.outline }))
5946
+ selector: Type12.String({ pattern: "^asset:.+", description: PARAM_DESCRIPTIONS.assetSelector }),
5947
+ output: Type12.String({ minLength: 1, pattern: "^(?!-$).+", description: PARAM_DESCRIPTIONS.output })
5782
5948
  }),
5783
5949
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
5784
- return executeView({ params, signal, cwd: ctx.cwd });
5950
+ return executeExtract({
5951
+ params,
5952
+ signal,
5953
+ cwd: ctx.cwd,
5954
+ onFileMutated: options.onFileMutated
5955
+ });
5785
5956
  },
5786
5957
  renderCall(args, theme, ...rest) {
5787
5958
  const context = rest[0] ?? {};
5788
5959
  const cwd = context.cwd ?? process.cwd();
5789
5960
  const displayPath = typeof args?.path === "string" ? args.path : "?";
5790
- const text = `${theme.fg("toolTitle", theme.bold("view"))} ${linkToolPath(theme.fg("accent", displayPath), displayPath, cwd)}`;
5961
+ const output = typeof args?.output === "string" ? args.output : "?";
5962
+ let text = `${theme.fg("toolTitle", theme.bold("extract"))} ${linkToolPath(theme.fg("accent", displayPath), displayPath, cwd)}`;
5963
+ if (typeof args?.selector === "string")
5964
+ text += theme.fg("dim", ` ${args.selector}`);
5965
+ text += theme.fg("dim", ` → ${output}`);
5791
5966
  return new Text8(clampLineToWidth(text, context.width), 0, 0);
5792
5967
  },
5793
5968
  renderResult(result, options, theme, ...rest) {
5794
- const { isPartial, isError, expanded, width } = resolveRenderResultContext(options, rest);
5969
+ const { isPartial, isError, width } = resolveRenderResultContext(options, rest);
5795
5970
  if (isPartial)
5796
- return renderPendingResult("pending view", width, theme);
5971
+ return renderPendingResult("pending extract", width, theme);
5797
5972
  const textContent = result.content?.[0]?.type === "text" ? result.content[0].text : "";
5798
5973
  if (isError || result.isError) {
5799
- return renderErrorResult(textContent, { expanded, width, fallback: "view failed", theme });
5974
+ return renderErrorResult(textContent, { expanded: false, width, fallback: "extract failed", theme });
5800
5975
  }
5801
- const lines = textContent.split(`
5802
- `).filter(Boolean).length;
5803
- let text = summaryLine(`loaded ${lines} document ${lines === 1 ? "line" : "lines"}`, {
5804
- hidden: !!textContent && !expanded,
5805
- theme,
5806
- style: "success"
5807
- });
5808
- if (expanded && textContent)
5809
- text += `
5810
- ${textContent}`;
5811
- return new Text8(clampLinesToWidth(text.split(`
5812
- `), width).join(`
5813
- `), 0, 0);
5976
+ return new Text8(clampLineToWidth(summaryLine(textContent || "asset extracted", { theme, style: "success" }), width), 0, 0);
5814
5977
  }
5815
5978
  });
5816
5979
  }
@@ -5841,32 +6004,20 @@ var READSEEK_TOOL_ENTRIES = [
5841
6004
  { builtIn: null, readSeekName: "readSeek_rename" },
5842
6005
  { builtIn: "write", readSeekName: "readSeek_write" },
5843
6006
  { builtIn: null, readSeekName: "readSeek_def" },
5844
- { builtIn: "read", readSeekName: "readSeek_digest" },
5845
- { builtIn: null, readSeekName: "readSeek_view" }
6007
+ { builtIn: "read", readSeekName: "readSeek_inspect" },
6008
+ { builtIn: null, readSeekName: "readSeek_extract" }
5846
6009
  ];
5847
- function formatSettingsWarning(warning) {
5848
- return `${warning.message} (${warning.source})`;
5849
- }
5850
- function toolRoutingPolicy(aliases) {
5851
- return renderToolRoutingPolicy({
5852
- aliases,
5853
- title: "ReadSeek tool policy:"
5854
- });
5855
- }
5856
6010
  function piReadSeekExtension(pi) {
5857
6011
  const sessionAnchors = new SessionAnchors;
5858
- const markAnchored = (absolutePath) => sessionAnchors.markAnchored(absolutePath);
5859
- const hasFreshAnchors = (absolutePath) => sessionAnchors.hasFreshAnchors(absolutePath);
5860
- const forgetAnchors = (absolutePath) => sessionAnchors.forget(absolutePath);
5861
6012
  const { settings } = resolveReadSeekJsonSettings();
5862
6013
  const replacedBuiltIns = new Set(settings.overrideTools ?? []);
5863
6014
  const swap = (builtIn, readSeekName) => replacedBuiltIns.has(builtIn) ? builtIn : readSeekName;
5864
- const readName = swap("read", "readSeek_digest");
6015
+ const readName = swap("read", "readSeek_inspect");
5865
6016
  const editName = swap("edit", "readSeek_edit");
5866
6017
  const writeName = swap("write", "readSeek_write");
5867
6018
  const grepName = swap("grep", "readSeek_grep");
5868
6019
  const toolAliases = {
5869
- readSeek_digest: readName,
6020
+ readSeek_inspect: readName,
5870
6021
  readSeek_edit: editName,
5871
6022
  readSeek_grep: grepName,
5872
6023
  readSeek_write: writeName
@@ -5875,20 +6026,33 @@ function piReadSeekExtension(pi) {
5875
6026
  const readSeekName = `readSeek_${name}`;
5876
6027
  return [name, toolAliases[readSeekName] ?? readSeekName];
5877
6028
  }));
5878
- registerEditTool(pi, { wasReadInSession: hasFreshAnchors, onFileMutated: forgetAnchors, name: editName, toolAliases });
5879
- registerGrepTool(pi, { onFileAnchored: markAnchored, name: grepName });
5880
- registerSearchTool(pi, { onFileAnchored: markAnchored });
5881
- registerRefsTool(pi, { onFileAnchored: markAnchored });
5882
- registerRenameTool(pi, { onFileMutated: forgetAnchors });
5883
- registerDefTool(pi, { onFileAnchored: markAnchored });
5884
- registerDigestTool(pi, {
5885
- onFileAnchored: markAnchored,
6029
+ registerEditTool(pi, {
6030
+ wasReadInSession: (absolutePath) => sessionAnchors.hasFreshAnchors(absolutePath),
6031
+ onFileMutated: (absolutePath) => sessionAnchors.forget(absolutePath),
6032
+ name: editName,
6033
+ toolAliases
6034
+ });
6035
+ registerGrepTool(pi, {
6036
+ onFileAnchored: (absolutePath) => sessionAnchors.markAnchored(absolutePath),
6037
+ name: grepName
6038
+ });
6039
+ registerSearchTool(pi, { onFileAnchored: (absolutePath) => sessionAnchors.markAnchored(absolutePath) });
6040
+ registerRefsTool(pi, { onFileAnchored: (absolutePath) => sessionAnchors.markAnchored(absolutePath) });
6041
+ registerRenameTool(pi, { onFileMutated: (absolutePath) => sessionAnchors.forget(absolutePath) });
6042
+ registerDefTool(pi, { onFileAnchored: (absolutePath) => sessionAnchors.markAnchored(absolutePath) });
6043
+ registerInspectTool(pi, {
6044
+ onFileAnchored: (absolutePath) => sessionAnchors.markAnchored(absolutePath),
5886
6045
  name: readName,
5887
6046
  toolAliases,
5888
6047
  imageMode: replacedBuiltIns.has("read") ? resolveReadSeekImageMode() : undefined
5889
6048
  });
5890
- registerViewTool(pi);
5891
- registerWriteTool(pi, { onFileAnchored: markAnchored, name: writeName });
6049
+ registerExtractTool(pi, {
6050
+ onFileMutated: (absolutePath) => sessionAnchors.forget(absolutePath)
6051
+ });
6052
+ registerWriteTool(pi, {
6053
+ onFileAnchored: (absolutePath) => sessionAnchors.markAnchored(absolutePath),
6054
+ name: writeName
6055
+ });
5892
6056
  const activeReadSeekNames = READSEEK_TOOL_ENTRIES.map((entry) => entry.builtIn && replacedBuiltIns.has(entry.builtIn) ? entry.builtIn : entry.readSeekName);
5893
6057
  const inactiveReadSeekNames = new Set(READSEEK_TOOL_ENTRIES.filter((entry) => entry.builtIn !== null && replacedBuiltIns.has(entry.builtIn)).map((entry) => entry.readSeekName));
5894
6058
  const activateReadSeekTools = () => {
@@ -5908,14 +6072,16 @@ function piReadSeekExtension(pi) {
5908
6072
  pi.on("before_agent_start", (event) => {
5909
6073
  if (!activateReadSeekTools())
5910
6074
  return;
5911
- return { systemPrompt: `${event.systemPrompt}
6075
+ return {
6076
+ systemPrompt: `${event.systemPrompt}
5912
6077
 
5913
- ${toolRoutingPolicy(policyAliases)}` };
6078
+ ${renderToolRoutingPolicy({ aliases: policyAliases, title: "ReadSeek tool policy:" })}`
6079
+ };
5914
6080
  });
5915
6081
  pi.on("session_start", (_event, ctx) => {
5916
6082
  sessionAnchors.clear();
5917
6083
  const { warnings } = resolveReadSeekJsonSettings();
5918
- const problems = warnings.map(formatSettingsWarning);
6084
+ const problems = warnings.map((warning) => `${warning.message} (${warning.source})`);
5919
6085
  const availability = readSeekBinaryAvailability();
5920
6086
  if (!availability.available) {
5921
6087
  problems.push(`readseek installation is broken: ${availability.reason}`);