pi-readseek 0.9.16 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.ts CHANGED
@@ -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,363 @@ 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));
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
+ function mapNodeShape(child) {
1418
+ return Type.Object({
1419
+ kind: Str,
1420
+ name: Str,
1421
+ start_line: Int,
1422
+ end_line: Int,
1423
+ start_anchor: Str,
1424
+ end_anchor: Str,
1425
+ children: Type.Array(child)
1426
+ });
1040
1427
  }
1428
+ function recursiveMapNode() {
1429
+ return Type.Recursive((self) => mapNodeShape(self));
1430
+ }
1431
+ var MapNode = typeof Type.Recursive === "function" ? recursiveMapNode() : Type.Cyclic({ MapNode: mapNodeShape(Type.Ref("MapNode")) }, "MapNode");
1432
+ var MapOutput = Type.Object({
1433
+ file: Str,
1434
+ language: Str,
1435
+ line_count: Int,
1436
+ file_hash: Str,
1437
+ root: Type.Optional(MapNode),
1438
+ symbols: Type.Array(SymbolOutput)
1439
+ });
1440
+ var SearchSpan = {
1441
+ start_byte: OptInt,
1442
+ end_byte: OptInt,
1443
+ text: OptStr,
1444
+ start_line: Int,
1445
+ end_line: Int,
1446
+ start_hash: Str,
1447
+ end_hash: Str,
1448
+ hashlines: Hashlines
1449
+ };
1450
+ var SearchCapture = Type.Object({ name: Str, ...SearchSpan });
1451
+ var SearchMatch = Type.Object({
1452
+ pattern_index: Type.Integer({ default: 0 }),
1453
+ ...SearchSpan,
1454
+ captures: Type.Array(SearchCapture)
1455
+ });
1456
+ var SearchFileOutput = Type.Object({
1457
+ file: Str,
1458
+ language: Str,
1459
+ file_hash: Str,
1460
+ matches: Type.Array(SearchMatch)
1461
+ });
1462
+ var SearchEnvelope = Type.Object({
1463
+ results: Type.Array(SearchFileOutput),
1464
+ returned_matches: OptInt,
1465
+ truncated: Type.Optional(Bool),
1466
+ byte_basis: OptStr
1467
+ });
1468
+ var RefsEnvelope = Type.Object({
1469
+ references: Type.Array(Type.Object({
1470
+ file: Str,
1471
+ line: Int,
1472
+ column: Int,
1473
+ line_hash: Str,
1474
+ text: Str,
1475
+ symbol: Type.Optional(Type.Object({ qualified_name: OptStr }))
1476
+ }))
1477
+ });
1478
+ var Diagnostic = Type.Object({
1479
+ kind: Type.Union([Type.Literal("error"), Type.Literal("missing")]),
1480
+ start_line: Int,
1481
+ end_line: Int
1482
+ });
1483
+ var CheckEnvelope = Type.Object({
1484
+ file_hash: OptStr,
1485
+ error_count: Int,
1486
+ missing_count: Int,
1487
+ diagnostics: Type.Array(Diagnostic)
1488
+ });
1489
+ var DetectedObject = Type.Object({
1490
+ label: Str,
1491
+ bbox: Type.Tuple([Int, Int, Int, Int])
1492
+ });
1493
+ var PreparedImage = Type.Object({
1494
+ mime: Str,
1495
+ encoding: Type.Literal("base64"),
1496
+ data: Str
1497
+ });
1498
+ var SourceDetection = Type.Object({
1499
+ kind: Type.Literal("source"),
1500
+ type: Str,
1501
+ file: Str,
1502
+ language: Str,
1503
+ engine: OptStr,
1504
+ supported: Bool,
1505
+ mime: OptStr,
1506
+ syntax: OptStr
1507
+ });
1508
+ var ImageDetection = Type.Object({
1509
+ kind: Type.Literal("image"),
1510
+ type: Str,
1511
+ file: Str,
1512
+ mime: OptStr,
1513
+ format: Str,
1514
+ width: Int,
1515
+ height: Int,
1516
+ animated: Bool,
1517
+ encoding: Type.Optional(Type.Literal("base64")),
1518
+ data: OptStr,
1519
+ ocr: OptStr,
1520
+ caption: OptStr,
1521
+ objects: Type.Optional(Type.Array(DetectedObject))
1522
+ });
1523
+ var TextDetection = Type.Object({
1524
+ kind: Type.Literal("text"),
1525
+ type: Str,
1526
+ file: Str,
1527
+ mime: OptStr
1528
+ });
1529
+ var PdfDetection = Type.Object({
1530
+ kind: Type.Literal("pdf"),
1531
+ type: Type.Literal("application/pdf"),
1532
+ file: Str,
1533
+ mime: OptStr,
1534
+ format: Type.Literal("pdf"),
1535
+ pages: Int
1536
+ });
1537
+ var Detection = Type.Union([SourceDetection, ImageDetection, TextDetection, PdfDetection]);
1538
+ var SourceDetectionInput = Type.Omit(SourceDetection, ["kind"]);
1539
+ var ImageDetectionInput = Type.Omit(ImageDetection, ["kind"]);
1540
+ var TextDetectionInput = Type.Omit(TextDetection, ["kind"]);
1541
+ var PdfDetectionInput = Type.Omit(PdfDetection, ["kind"]);
1542
+ var UnspannedContent = Type.Omit(ReadOutput, ["start_line", "end_line"]);
1543
+ var PdfInspectMetadata = Type.Object({ source: Str, format: Type.Literal("pdf"), pages: Int });
1544
+ var DocumentCache = Type.Object({
1545
+ content_hash: Str,
1546
+ state: Type.Union([Type.Literal("built"), Type.Literal("reused")])
1547
+ });
1548
+ var Identifier = Type.Object({
1549
+ text: Str,
1550
+ start_column: Int,
1551
+ end_column: Int,
1552
+ start_byte: Int,
1553
+ end_byte: Int
1554
+ });
1555
+ var IdentifyEnvelope = Type.Object({
1556
+ file: Str,
1557
+ language: Str,
1558
+ engine: OptStr,
1559
+ line_count: Int,
1560
+ file_hash: Str,
1561
+ line: Int,
1562
+ column: Int,
1563
+ line_hash: Str,
1564
+ identifier: Type.Optional(Identifier),
1565
+ symbol: Type.Optional(Type.Object({
1566
+ name: Str,
1567
+ kind: Str,
1568
+ qualified_name: Str,
1569
+ start_line: Int,
1570
+ end_line: Int
1571
+ }))
1572
+ });
1573
+ var EditOutputSchema = Type.Object({
1574
+ file: Str,
1575
+ before_hash: Str,
1576
+ file_hash: Str,
1577
+ plan_hash: Str,
1578
+ applied: Bool,
1579
+ changed: Bool,
1580
+ edit_count: Int,
1581
+ before_content: OptStr,
1582
+ content: OptStr
1583
+ });
1584
+ var RenameConflictSchema = Type.Object({ line: Int, column: Int, reason: Str });
1585
+ var RenameEditSchema = Type.Object({
1586
+ line: Int,
1587
+ start_column: Int,
1588
+ end_column: Int,
1589
+ start_byte: Int,
1590
+ end_byte: Int,
1591
+ occurrence: Str,
1592
+ line_hash: Str,
1593
+ text: Str
1594
+ });
1595
+ var Conflicts = Type.Array(RenameConflictSchema, { default: [] });
1596
+ var Edits = Type.Array(RenameEditSchema, { default: [] });
1597
+ var RenameFileOutputSchema = Type.Object({
1598
+ file: Str,
1599
+ language: Str,
1600
+ engine: OptStr,
1601
+ file_hash: Str,
1602
+ conflicts: Conflicts,
1603
+ edits: Edits
1604
+ });
1605
+ var RenameOutputSchema = Type.Object({
1606
+ file: Str,
1607
+ language: Str,
1608
+ engine: OptStr,
1609
+ file_hash: Str,
1610
+ old_name: Str,
1611
+ new_name: Str,
1612
+ plan_hash: Str,
1613
+ applied: Bool,
1614
+ conflicts: Conflicts,
1615
+ edits: Edits,
1616
+ others: Type.Array(RenameFileOutputSchema, { default: [] })
1617
+ });
1618
+ var DefEnvelope = Type.Object({
1619
+ locations: Type.Array(Type.Object({
1620
+ file: Str,
1621
+ line: Int,
1622
+ column: Int,
1623
+ line_hash: Str,
1624
+ text: Str,
1625
+ kind: OptStr,
1626
+ name: OptStr,
1627
+ qualified_name: OptStr
1628
+ }))
1629
+ });
1041
1630
  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
- };
1631
+ return parse2(ReadOutput, value, "read output");
1073
1632
  }
1074
1633
  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
- };
1634
+ return parse2(MapOutput, value, "map output");
1102
1635
  }
1103
1636
  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
- });
1637
+ const envelope = parse2(SearchEnvelope, value, "search output");
1638
+ const results = envelope.results;
1639
+ if (envelope.returned_matches !== undefined)
1640
+ results.returned_matches = envelope.returned_matches;
1641
+ if (envelope.truncated !== undefined)
1642
+ results.truncated = envelope.truncated;
1643
+ if (envelope.byte_basis !== undefined)
1644
+ results.byte_basis = envelope.byte_basis;
1645
+ return results;
1149
1646
  }
1150
1647
  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");
1648
+ return parse2(RefsEnvelope, value, "refs output").references.map(({ symbol, ...reference }) => ({
1649
+ ...reference,
1650
+ enclosingSymbol: symbol?.qualified_name
1651
+ }));
1176
1652
  }
1177
1653
  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");
1654
+ const output = parse2(CheckEnvelope, value, "check output");
1183
1655
  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
- })
1656
+ file_hash: output.file_hash,
1657
+ errorCount: output.error_count,
1658
+ missingCount: output.missing_count,
1659
+ diagnostics: output.diagnostics
1197
1660
  };
1198
1661
  }
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
1662
  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
- };
1663
+ const output = value ?? {};
1664
+ if (output.type === "application/pdf") {
1665
+ return { kind: "pdf", ...parse2(PdfDetectionInput, value, "detect output") };
1240
1666
  }
1241
1667
  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
- };
1668
+ return { kind: "image", ...parse2(ImageDetectionInput, value, "detect output") };
1257
1669
  }
1258
1670
  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
- };
1671
+ return { kind: "source", ...parse2(SourceDetectionInput, value, "detect output") };
1269
1672
  }
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
- };
1673
+ return { kind: "text", ...parse2(TextDetectionInput, value, "detect output") };
1314
1674
  }
1315
1675
  function parseImageContent(value) {
1316
1676
  const detection = parseDetectOutput(value);
1317
1677
  if (detection.kind !== "image")
1318
- throw new Error("invalid readseek image content");
1678
+ throw new Error("invalid readseek image content: expected an image");
1319
1679
  return detection;
1320
1680
  }
1321
1681
  function parseTextContent(value) {
1682
+ const output = value ?? {};
1683
+ if (output.hashlines === undefined || output.start_line !== undefined)
1684
+ return parseReadOutput(value);
1685
+ const spanned = parse2(UnspannedContent, value, "content");
1686
+ return {
1687
+ ...spanned,
1688
+ start_line: spanned.hashlines[0]?.line ?? 1,
1689
+ end_line: spanned.hashlines[spanned.hashlines.length - 1]?.line ?? 0
1690
+ };
1691
+ }
1692
+ function parseDocumentCache(value) {
1693
+ return parse2(DocumentCache, value, "document cache");
1694
+ }
1695
+ function parseIdentifyOutput(value) {
1696
+ return parse2(IdentifyEnvelope, value, "identify output");
1697
+ }
1698
+ function parsePdfInspectMetadata(value) {
1322
1699
  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
- };
1700
+ return;
1701
+ if (value.format !== "pdf")
1702
+ return;
1703
+ const document = parse2(PdfInspectMetadata, value, "inspect metadata");
1704
+ return { kind: "pdf", type: "application/pdf", file: document.source, format: "pdf", pages: document.pages };
1705
+ }
1706
+ function parseEditOutput(value) {
1707
+ return parse2(EditOutputSchema, value, "edit output");
1708
+ }
1709
+ function parseRenameOutput(value) {
1710
+ return parse2(RenameOutputSchema, value, "rename output");
1711
+ }
1712
+ function parseDefOutput(value) {
1713
+ return parse2(DefEnvelope, value, "def output").locations;
1714
+ }
1715
+
1716
+ // ../readseek-api/src/client.ts
1717
+ function classifyReadSeekFailure(err) {
1718
+ const failure = err;
1719
+ const message = String(failure?.message ?? err);
1720
+ const missing = failure?.code === "ENOENT" || /Cannot find package|Cannot find module|no such file/i.test(message);
1721
+ if (missing) {
1722
+ return { code: "readseek-not-installed", message, hint: "Run npm install to install @jarkkojs/readseek." };
1338
1723
  }
1339
- return parseReadOutput(value);
1724
+ return { code: "readseek-execution-error", message };
1340
1725
  }
1341
- function parseDigestEnvelope(value) {
1726
+ function parseSourceInspectEnvelope(value) {
1342
1727
  if (!value || typeof value !== "object")
1343
- throw new Error("invalid readseek digest output");
1728
+ throw new Error("invalid readseek inspect output");
1344
1729
  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
- };
1730
+ const result = {};
1731
+ if (output.metadata !== undefined)
1732
+ result.metadata = parseDetectOutput(output.metadata);
1350
1733
  if (output.cache !== undefined)
1351
1734
  result.cache = parseDocumentCache(output.cache);
1352
1735
  if (output.content !== undefined)
@@ -1359,224 +1742,104 @@ function parseDigestEnvelope(value) {
1359
1742
  result.identity = parseIdentifyOutput(output.identity);
1360
1743
  return result;
1361
1744
  }
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
1745
  function createReadSeekClient(runner) {
1471
- function digestArgs(target, options) {
1472
- return buildDigestArgs({
1746
+ function inspectArgs(target, options) {
1747
+ return buildInspectArgs({
1473
1748
  path: target,
1749
+ selector: options.selector,
1474
1750
  select: options.select ?? "content",
1475
- at: options.at,
1751
+ query: options.query,
1476
1752
  end: options.end,
1477
1753
  limit: options.limit,
1478
1754
  depth: options.depth,
1479
1755
  language: options.language,
1480
1756
  visionMode: options.visionMode,
1481
1757
  visionLevel: options.visionLevel,
1482
- stdinName: options.stdin !== undefined && target === "-" ? options.stdinName ?? target : undefined
1758
+ stdinName: options.stdinName
1483
1759
  });
1484
1760
  }
1485
- async function runDigestRaw(target, options = {}) {
1486
- const args = digestArgs(target, options);
1487
- const runOptions = {
1761
+ function inspectRunOptions(options) {
1762
+ return {
1488
1763
  signal: options.signal,
1489
1764
  stdin: options.stdin,
1490
1765
  timeoutMs: options.timeoutMs,
1491
1766
  vision: options.vision
1492
1767
  };
1493
- return runner.json(args, runOptions);
1494
1768
  }
1495
- async function digest(target, options = {}) {
1496
- return parseDigestEnvelope(await runDigestRaw(target, options));
1769
+ async function inspect(target, options = {}) {
1770
+ return runner.kdl(inspectArgs(target, options), inspectRunOptions(options));
1771
+ }
1772
+ async function inspectText(target, options = {}) {
1773
+ return runner.text(inspectArgs(target, options), inspectRunOptions(options));
1774
+ }
1775
+ async function inspectSource(target, options = {}) {
1776
+ return parseSourceInspectEnvelope(await inspect(target, options));
1497
1777
  }
1498
- function requireDigestContent(envelope, field) {
1778
+ function requireInspectMetadata(envelope, field) {
1779
+ if (envelope.metadata === undefined)
1780
+ throw new Error(`invalid readseek ${field}: missing metadata`);
1781
+ return envelope.metadata;
1782
+ }
1783
+ function requireInspectContent(envelope, field) {
1499
1784
  if (envelope.content === undefined)
1500
1785
  throw new Error(`invalid readseek ${field}: missing content`);
1501
1786
  return envelope.content;
1502
1787
  }
1503
- async function digestContent(filePath, options = {}) {
1504
- const envelope = await digest(filePath, {
1505
- select: "content",
1506
- at: options.startLine !== undefined ? buildLineLocation(options.startLine) : undefined,
1788
+ async function inspectContent(filePath, options = {}) {
1789
+ const inspected = await inspect(filePath, {
1790
+ select: ["metadata", "content"],
1791
+ selector: options.startLine !== undefined ? buildLineLocation(options.startLine) : undefined,
1507
1792
  end: options.endLine,
1508
1793
  visionMode: options.visionMode,
1509
1794
  signal: options.signal,
1510
1795
  vision: options.vision
1511
1796
  });
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) };
1797
+ const pdf = parsePdfInspectMetadata(inspected);
1798
+ if (pdf)
1799
+ return { metadata: pdf };
1800
+ const envelope = parseSourceInspectEnvelope(inspected);
1801
+ const metadata = requireInspectMetadata(envelope, "content");
1802
+ const content = requireInspectContent(envelope, "content");
1803
+ if (metadata.kind === "image") {
1804
+ return { metadata, image: parseImageContent(content) };
1518
1805
  }
1519
- return { metadata: envelope.metadata, text: parseTextContent(content) };
1806
+ return { metadata, text: parseTextContent(content) };
1520
1807
  }
1521
1808
  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, {
1809
+ inspect,
1810
+ inspectText,
1811
+ inspectContent,
1812
+ async extract(target, selector, output, options = {}) {
1813
+ await runner.text(buildExtractArgs({
1814
+ path: target,
1815
+ selector,
1816
+ output,
1817
+ stdinName: options.stdinName
1818
+ }), {
1534
1819
  signal: options.signal,
1535
- vision: options.visionMode !== undefined
1820
+ stdin: options.stdin,
1821
+ timeoutMs: options.timeoutMs,
1822
+ cancelable: options.cancelable
1536
1823
  });
1537
1824
  },
1538
- digestRaw(target, options = {}) {
1539
- return runDigestRaw(target, options);
1540
- },
1541
- digest,
1542
- digestContent,
1543
1825
  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}`);
1826
+ if (modes.length === 0)
1827
+ throw new Error(`readseek returned no image analysis for ${filePath}`);
1828
+ const inspected = await inspectContent(filePath, {
1829
+ visionMode: modes.length > 1 ? "all" : modes[0],
1830
+ signal: options.signal,
1831
+ vision: true
1832
+ });
1833
+ if (!inspected.image)
1834
+ throw new Error(`readseek returned no image analysis for ${filePath}`);
1835
+ return inspected.image;
1573
1836
  },
1574
1837
  async preparedImage(filePath, options = {}) {
1575
- const digested = await digestContent(filePath, {
1838
+ const inspected = await inspectContent(filePath, {
1576
1839
  visionMode: "none",
1577
1840
  signal: options.signal
1578
1841
  });
1579
- const output = digested.image;
1842
+ const output = inspected.image;
1580
1843
  if (!output || output.encoding !== "base64" || output.data === undefined || output.mime === undefined) {
1581
1844
  throw new Error(`readseek returned no prepared image for ${filePath}`);
1582
1845
  }
@@ -1588,11 +1851,9 @@ function createReadSeekClient(runner) {
1588
1851
  pattern,
1589
1852
  language: options.language,
1590
1853
  limit: options.limit,
1591
- cached: options.cached,
1592
- others: options.others,
1593
- ignored: options.ignored
1854
+ gitScope: options.gitScope
1594
1855
  });
1595
- return parseSearchOutput(await runner.json(args, { signal: options.signal }));
1856
+ return parseSearchOutput(await runner.kdl(args, { signal: options.signal }));
1596
1857
  },
1597
1858
  async refs(target, options = {}) {
1598
1859
  const args = buildRefsArgs({
@@ -1602,26 +1863,21 @@ function createReadSeekClient(runner) {
1602
1863
  line: options.line,
1603
1864
  column: options.column,
1604
1865
  language: options.language,
1605
- cached: options.cached,
1606
- others: options.others,
1607
- ignored: options.ignored
1866
+ gitScope: options.gitScope
1608
1867
  });
1609
- return parseRefsOutput(await runner.json(args, { signal: options.signal }));
1868
+ return parseRefsOutput(await runner.kdl(args, { signal: options.signal }));
1610
1869
  },
1611
1870
  async def(target, options) {
1612
1871
  const args = buildDefArgs({
1613
1872
  path: target,
1614
1873
  name: options.name,
1615
- format: "plain",
1616
1874
  language: options.language,
1617
- cached: options.cached,
1618
- others: options.others,
1619
- ignored: options.ignored
1875
+ gitScope: options.gitScope
1620
1876
  });
1621
- return parseDefCompact(await runner.json(args, { signal: options.signal }));
1877
+ return parseDefOutput(await runner.kdl(args, { signal: options.signal }));
1622
1878
  },
1623
1879
  async check(filePath, content, options = {}) {
1624
- const envelope = await digest("-", {
1880
+ const envelope = await inspectSource("-", {
1625
1881
  select: "diagnostics",
1626
1882
  stdin: content,
1627
1883
  stdinName: filePath,
@@ -1631,12 +1887,12 @@ function createReadSeekClient(runner) {
1631
1887
  throw new Error("invalid readseek diagnostics: missing diagnostics facet");
1632
1888
  return envelope.diagnostics;
1633
1889
  },
1634
- async identify(filePath, content, options = {}) {
1635
- const envelope = await digest("-", {
1890
+ async identify(filePath, content, options) {
1891
+ const envelope = await inspectSource("-", {
1636
1892
  select: "identity",
1637
1893
  stdin: content,
1638
1894
  stdinName: filePath,
1639
- at: options.line !== undefined ? buildLineLocation(options.line, options.column) : undefined,
1895
+ selector: buildLineLocation(options.line, options.column),
1640
1896
  language: options.language,
1641
1897
  signal: options.signal
1642
1898
  });
@@ -1645,8 +1901,11 @@ function createReadSeekClient(runner) {
1645
1901
  return envelope.identity;
1646
1902
  },
1647
1903
  async detect(filePath, options = {}) {
1648
- const envelope = await digest(filePath, { select: "metadata", signal: options.signal });
1649
- return envelope.metadata;
1904
+ const inspected = await inspect(filePath, { select: "metadata", signal: options.signal });
1905
+ const pdf = parsePdfInspectMetadata(inspected);
1906
+ if (pdf)
1907
+ return pdf;
1908
+ return requireInspectMetadata(parseSourceInspectEnvelope(inspected), "metadata");
1650
1909
  },
1651
1910
  async edit(filePath, edits, options = {}) {
1652
1911
  const args = buildEditArgs({
@@ -1656,9 +1915,9 @@ function createReadSeekClient(runner) {
1656
1915
  language: options.language,
1657
1916
  requestFromStdin: true
1658
1917
  });
1659
- return parseEditOutput(await runner.json(args, {
1918
+ return parseEditOutput(await runner.kdl(args, {
1660
1919
  signal: options.signal,
1661
- stdin: JSON.stringify({ edits }),
1920
+ stdin: serializeEditPlan(edits),
1662
1921
  timeoutMs: options.timeoutMs,
1663
1922
  cancelable: options.cancelable
1664
1923
  }));
@@ -1671,12 +1930,11 @@ function createReadSeekClient(runner) {
1671
1930
  column: options.column,
1672
1931
  workspace: options.workspace,
1673
1932
  apply: options.apply,
1933
+ planHash: options.planHash,
1674
1934
  language: options.language,
1675
- cached: options.cached,
1676
- others: options.others,
1677
- ignored: options.ignored
1935
+ gitScope: options.gitScope
1678
1936
  });
1679
- return parseRenameOutput(await runner.json(args, { signal: options.signal }));
1937
+ return parseRenameOutput(await runner.kdl(args, { signal: options.signal }));
1680
1938
  }
1681
1939
  };
1682
1940
  }
@@ -2157,7 +2415,7 @@ function formatReplaceHint(edits, noopEdits) {
2157
2415
  return;
2158
2416
  if (counts.replace !== counts.total)
2159
2417
  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.]";
2418
+ 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
2419
  }
2162
2420
  function buildEditOutput(input) {
2163
2421
  const summary = `Updated ${input.displayPath}`;
@@ -2204,20 +2462,20 @@ function classifyEdit(oldContent, newContent) {
2204
2462
  const newLines = newContent.split(`
2205
2463
  `);
2206
2464
  if (oldLines.length === newLines.length) {
2207
- let hasWhitespaceChange2 = false;
2208
- let hasSemanticChange2 = false;
2465
+ let hasWhitespaceChange = false;
2466
+ let hasSemanticChange = false;
2209
2467
  for (let i = 0;i < oldLines.length; i++) {
2210
2468
  if (oldLines[i] === newLines[i])
2211
2469
  continue;
2212
2470
  if (oldLines[i].trim() === newLines[i].trim()) {
2213
- hasWhitespaceChange2 = true;
2471
+ hasWhitespaceChange = true;
2214
2472
  } else {
2215
- hasSemanticChange2 = true;
2473
+ hasSemanticChange = true;
2216
2474
  }
2217
2475
  }
2218
- if (hasSemanticChange2 && hasWhitespaceChange2)
2476
+ if (hasSemanticChange && hasWhitespaceChange)
2219
2477
  return { classification: "mixed" };
2220
- if (hasWhitespaceChange2)
2478
+ if (hasWhitespaceChange)
2221
2479
  return { classification: "whitespace-only" };
2222
2480
  return { classification: "semantic" };
2223
2481
  }
@@ -2257,7 +2515,7 @@ import { readFileSync, statSync as statSync2 } from "node:fs";
2257
2515
  import { homedir } from "node:os";
2258
2516
  import { join } from "node:path";
2259
2517
  var READSEEK_TOOL_REPLACEMENTS = {
2260
- read: "readSeek_digest",
2518
+ read: "readSeek_inspect",
2261
2519
  edit: "readSeek_edit",
2262
2520
  grep: "readSeek_grep",
2263
2521
  write: "readSeek_write"
@@ -2270,42 +2528,36 @@ var DEFAULT_DISPLAY_MODES = {
2270
2528
  edit: "expanded",
2271
2529
  write: "expanded"
2272
2530
  };
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
2531
  function isRecord(value) {
2280
2532
  return typeof value === "object" && value !== null && !Array.isArray(value);
2281
2533
  }
2282
- function invalid(source, path2) {
2283
- return { source, path: path2, message: `Invalid readseek setting at ${path2}` };
2534
+ function invalid(source, path) {
2535
+ return { source, path, message: `Invalid readseek setting at ${path}` };
2284
2536
  }
2285
2537
  function warnUnknownKeys(raw, known, prefix, source, warnings) {
2286
2538
  for (const key of Object.keys(raw)) {
2287
2539
  if (known.includes(key))
2288
2540
  continue;
2289
- const path2 = `${prefix}.${key}`;
2290
- warnings.push({ source, path: path2, message: `Unknown readseek setting at ${path2}` });
2541
+ const path = `${prefix}.${key}`;
2542
+ warnings.push({ source, path, message: `Unknown readseek setting at ${path}` });
2291
2543
  }
2292
2544
  }
2293
- function readPositive(raw, key, path2, source, warnings) {
2545
+ function readPositive(raw, key, path, source, warnings) {
2294
2546
  if (!(key in raw))
2295
2547
  return;
2296
2548
  const val = raw[key];
2297
2549
  if (typeof val === "number" && Number.isSafeInteger(val) && val > 0)
2298
2550
  return val;
2299
- warnings.push(invalid(source, path2));
2551
+ warnings.push(invalid(source, path));
2300
2552
  return;
2301
2553
  }
2302
- function readEnum(raw, key, values, path2, source, warnings) {
2554
+ function readEnum(raw, key, values, path, source, warnings) {
2303
2555
  if (!(key in raw))
2304
2556
  return;
2305
2557
  const val = raw[key];
2306
2558
  if (typeof val === "string" && values.includes(val))
2307
2559
  return val;
2308
- warnings.push(invalid(source, path2));
2560
+ warnings.push(invalid(source, path));
2309
2561
  return;
2310
2562
  }
2311
2563
  function readImageMode(raw, source, warnings) {
@@ -2409,31 +2661,31 @@ function validateSettings(raw, source) {
2409
2661
  return { settings, warnings };
2410
2662
  }
2411
2663
  var settingsFileCache = new Map;
2412
- function statFileOrNull(path2) {
2664
+ function statFileOrNull(path) {
2413
2665
  try {
2414
- return statSync2(path2);
2666
+ return statSync2(path);
2415
2667
  } catch {
2416
2668
  return null;
2417
2669
  }
2418
2670
  }
2419
- function readSettingsFile(path2) {
2420
- const stats = statFileOrNull(path2);
2671
+ function readSettingsFile(path) {
2672
+ const stats = statFileOrNull(path);
2421
2673
  if (!stats) {
2422
- settingsFileCache.delete(path2);
2674
+ settingsFileCache.delete(path);
2423
2675
  return { settings: {}, warnings: [] };
2424
2676
  }
2425
- const cached = settingsFileCache.get(path2);
2677
+ const cached = settingsFileCache.get(path);
2426
2678
  if (cached && cached.mtimeMs === stats.mtimeMs && cached.size === stats.size)
2427
2679
  return cached.result;
2428
2680
  let result;
2429
2681
  try {
2430
- const text = readFileSync(path2, "utf8");
2431
- result = validateSettings(JSON.parse(text), path2);
2682
+ const text = readFileSync(path, "utf8");
2683
+ result = validateSettings(JSON.parse(text), path);
2432
2684
  } catch (error) {
2433
2685
  const message = error instanceof Error ? error.message : String(error);
2434
- result = { settings: {}, warnings: [{ source: path2, message: `Invalid JSON: ${message}` }] };
2686
+ result = { settings: {}, warnings: [{ source: path, message: `Invalid JSON: ${message}` }] };
2435
2687
  }
2436
- settingsFileCache.set(path2, { mtimeMs: stats.mtimeMs, size: stats.size, result });
2688
+ settingsFileCache.set(path, { mtimeMs: stats.mtimeMs, size: stats.size, result });
2437
2689
  return result;
2438
2690
  }
2439
2691
  function mergeSettings(base, override) {
@@ -2447,8 +2699,8 @@ function mergeSettings(base, override) {
2447
2699
  return settings;
2448
2700
  }
2449
2701
  function resolveReadSeekJsonSettings() {
2450
- const globalResult = readSettingsFile(globalSettingsPath());
2451
- const projectResult = readSettingsFile(projectSettingsPath());
2702
+ const globalResult = readSettingsFile(join(homedir(), ".pi/agent/settings.json"));
2703
+ const projectResult = readSettingsFile(join(process.cwd(), ".pi/settings.json"));
2452
2704
  return {
2453
2705
  settings: mergeSettings(globalResult.settings, projectResult.settings),
2454
2706
  warnings: [...globalResult.warnings, ...projectResult.warnings]
@@ -2496,21 +2748,13 @@ var DEFAULT_READSEEK_VISION_TIMEOUT_MS = 30 * 60000;
2496
2748
  function readSeekTimeoutMs() {
2497
2749
  return resolveReadSeekTimeoutMs() ?? DEFAULT_READSEEK_TIMEOUT_MS;
2498
2750
  }
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
2751
  async function ensureDefaultReadSeekDir() {
2508
2752
  const dir = defaultReadSeekDir();
2509
2753
  if (!dir)
2510
2754
  return null;
2511
2755
  if (directoryExists(dir))
2512
2756
  return dir;
2513
- defaultReadSeekDirInit ??= spawnReadSeekRaw(["--readseek-dir", dir, "init"]).then(() => directoryExists(dir) ? dir : null).catch(() => null).finally(() => {
2757
+ defaultReadSeekDirInit ??= runReadSeekNodeText(readSeekBinaryPath(), ["--readseek-dir", dir, "init"], { timeoutMs: readSeekTimeoutMs() }).then(() => directoryExists(dir) ? dir : null).catch(() => null).finally(() => {
2514
2758
  defaultReadSeekDirInit = undefined;
2515
2759
  });
2516
2760
  return defaultReadSeekDirInit;
@@ -2532,17 +2776,6 @@ async function readSeekInvocationArgs(args) {
2532
2776
  const readSeekDir = await ensureDefaultReadSeekDir();
2533
2777
  return readSeekDir ? ["--readseek-dir", readSeekDir, ...args] : [...args];
2534
2778
  }
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
2779
  var visionInvocationTail = Promise.resolve();
2547
2780
  async function runReadSeekVisionRaw(args, options = {}) {
2548
2781
  let release;
@@ -2573,40 +2806,38 @@ async function runReadSeekVisionRaw(args, options = {}) {
2573
2806
  await predecessor;
2574
2807
  }
2575
2808
  signal?.throwIfAborted();
2576
- return await runReadSeekRaw(args, {
2577
- ...options,
2578
- timeoutMs: options.timeoutMs ?? resolveReadSeekTimeoutMs() ?? DEFAULT_READSEEK_VISION_TIMEOUT_MS
2809
+ return await runReadSeekNodeText(readSeekBinaryPath(), await readSeekInvocationArgs(args), {
2810
+ signal: options.signal,
2811
+ stdin: options.stdin,
2812
+ timeoutMs: options.timeoutMs ?? resolveReadSeekTimeoutMs() ?? DEFAULT_READSEEK_VISION_TIMEOUT_MS,
2813
+ cancelable: options.cancelable
2579
2814
  });
2580
2815
  } finally {
2581
2816
  release();
2582
2817
  }
2583
2818
  }
2584
- async function runReadSeekVision(args, options = {}) {
2585
- return parseReadSeekJson(await runReadSeekVisionRaw(args, options));
2586
- }
2587
2819
  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)
2820
+ text: async (args, options = {}) => options.vision ? runReadSeekVisionRaw(args, options) : runReadSeekNodeText(readSeekBinaryPath(), await readSeekInvocationArgs(args), {
2821
+ signal: options.signal,
2822
+ stdin: options.stdin,
2823
+ timeoutMs: options.timeoutMs ?? readSeekTimeoutMs(),
2824
+ cancelable: options.cancelable
2825
+ }),
2826
+ kdl: async (args, options = {}) => parseReadSeekKdl(await runner.text(args, options))
2590
2827
  };
2591
2828
  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;
2829
+ var readSeekInspect = client.inspect;
2830
+ var readSeekExtract = client.extract;
2598
2831
  var readSeekSearch = client.search;
2599
2832
  var readSeekRefs = client.refs;
2600
2833
  var readSeekDef = client.def;
2601
2834
  var readSeekCheck = client.check;
2602
- var readSeekIdentify = client.identify;
2603
- var readSeekDetect = client.detect;
2604
2835
  var readSeekEdit = client.edit;
2605
2836
  var readSeekRename = client.rename;
2606
2837
  async function readSeekMap(filePath, options = {}) {
2607
- const envelope = await client.digest(filePath, {
2838
+ const envelope = await client.inspect(filePath, {
2608
2839
  select: "map",
2609
- depth: DEFAULT_DIGEST_MAP_DEPTH,
2840
+ depth: DEFAULT_INSPECT_MAP_DEPTH,
2610
2841
  signal: options.signal
2611
2842
  });
2612
2843
  const map = envelope.map;
@@ -2672,8 +2903,8 @@ var LANGUAGE_BY_EXTENSION = new Map([
2672
2903
  [".rs", "rust"],
2673
2904
  [".java", "java"]
2674
2905
  ]);
2675
- function inferLanguage(path3) {
2676
- const extensionMatch = path3.match(/\.[^.\/\\]+$/);
2906
+ function inferLanguage(path) {
2907
+ const extensionMatch = path.match(/\.[^.\/\\]+$/);
2677
2908
  if (!extensionMatch)
2678
2909
  return;
2679
2910
  return LANGUAGE_BY_EXTENSION.get(extensionMatch[0].toLowerCase());
@@ -2719,10 +2950,10 @@ function parseCompactDiffEntries(diff, oldContent, newContent) {
2719
2950
  `);
2720
2951
  const compactDelete = diff.match(/^(\d+):[0-9a-f]{6}\|(.*) → \[deleted\]$/);
2721
2952
  if (compactDelete) {
2722
- const oldLine2 = Number(compactDelete[1]);
2953
+ const oldLine = Number(compactDelete[1]);
2723
2954
  const text = compactDelete[2] ?? "";
2724
- if (oldLines[oldLine2 - 1] === text)
2725
- return [{ kind: "remove", oldLine: oldLine2, text }];
2955
+ if (oldLines[oldLine - 1] === text)
2956
+ return [{ kind: "remove", oldLine, text }];
2726
2957
  }
2727
2958
  const compactPrefix = diff.match(/^(\d+):[0-9a-f]{6}\|/);
2728
2959
  if (!compactPrefix)
@@ -2762,9 +2993,9 @@ function longestCommonSubsequence(a, b) {
2762
2993
  const rows = a.length + 1;
2763
2994
  const cols = b.length + 1;
2764
2995
  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]);
2996
+ for (let i = a.length - 1;i >= 0; i--) {
2997
+ for (let j = b.length - 1;j >= 0; j--) {
2998
+ table[i][j] = a[i] === b[j] ? table[i + 1][j + 1] + 1 : Math.max(table[i + 1][j], table[i][j + 1]);
2768
2999
  }
2769
3000
  }
2770
3001
  const pairs = [];
@@ -2882,8 +3113,8 @@ function buildInlineDiffs(entries) {
2882
3113
  }
2883
3114
  return inlineDiffs.length ? inlineDiffs : undefined;
2884
3115
  }
2885
- function finalizeDiffData(path3, entries, blockRanges) {
2886
- const language = inferLanguage(path3);
3116
+ function finalizeDiffData(path, entries, blockRanges) {
3117
+ const language = inferLanguage(path);
2887
3118
  const inlineDiffs = buildInlineDiffs(entries);
2888
3119
  return {
2889
3120
  version: 1,
@@ -2976,7 +3207,6 @@ function wrapWithHangingIndent(prefix, content, width, options = {}) {
2976
3207
  const indent = " ".repeat(prefixWidth);
2977
3208
  return wrapped.map((line, index) => tint(clampLineToWidth(index === 0 ? prefix + line : indent + line, normalized)));
2978
3209
  }
2979
- var wrappedHashlinesCache = new WeakMap;
2980
3210
  function renderPendingResult(pendingLabel, width, theme) {
2981
3211
  return new Text(clampLinesToWidth([summaryLine(pendingLabel, { theme, style: "muted" })], width).join(`
2982
3212
  `), 0, 0);
@@ -3107,8 +3337,8 @@ function padRightVisual(line, width) {
3107
3337
  function tint(theme, entry, text) {
3108
3338
  return entry.kind === "add" ? theme.fg("success", text) : entry.kind === "remove" ? theme.fg("error", text) : theme.fg("toolOutput", text);
3109
3339
  }
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;
3340
+ function spans(theme, spans, fallback) {
3341
+ 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
3342
  }
3113
3343
  function inlineText(input, index, entry) {
3114
3344
  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 +3467,12 @@ function upsertTextComponent(lastComponent, text) {
3237
3467
  }
3238
3468
 
3239
3469
  // src/register-tool.ts
3240
- import { Type } from "@sinclair/typebox";
3470
+ import { Type as Type2 } from "@sinclair/typebox";
3241
3471
  function optionalIntOrString(description) {
3242
- return Type.Optional(Type.Union([Type.Number({ description }), Type.String({ description })]));
3472
+ return Type2.Optional(Type2.Union([Type2.Number({ description }), Type2.String({ description })]));
3243
3473
  }
3244
3474
  function filePathParam() {
3245
- return Type.String({ description: PARAM_DESCRIPTIONS.path });
3475
+ return Type2.String({ description: PARAM_DESCRIPTIONS.path });
3246
3476
  }
3247
3477
  function registerReadSeekTool(pi, tool) {
3248
3478
  pi.registerTool(tool);
@@ -3250,28 +3480,28 @@ function registerReadSeekTool(pi, tool) {
3250
3480
  }
3251
3481
 
3252
3482
  // 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 })
3483
+ var hashlineEditItemSchema = Type3.Object({
3484
+ set_line: Type3.Optional(Type3.Object({
3485
+ anchor: Type3.String({ description: PARAM_DESCRIPTIONS.anchor }),
3486
+ new_text: Type3.String({ description: PARAM_DESCRIPTIONS.newTextLine })
3257
3487
  }, { 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 })
3488
+ replace_lines: Type3.Optional(Type3.Object({
3489
+ start_anchor: Type3.String({ description: PARAM_DESCRIPTIONS.startAnchor }),
3490
+ end_anchor: Type3.String({ description: PARAM_DESCRIPTIONS.endAnchor }),
3491
+ new_text: Type3.String({ description: PARAM_DESCRIPTIONS.newTextRange })
3262
3492
  }, { 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 })
3493
+ insert_after: Type3.Optional(Type3.Object({
3494
+ anchor: Type3.String({ description: PARAM_DESCRIPTIONS.insertAnchor }),
3495
+ new_text: Type3.String({ description: PARAM_DESCRIPTIONS.newTextInsert })
3266
3496
  }, { 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 }))
3497
+ replace: Type3.Optional(Type3.Object({
3498
+ old_text: Type3.String({ description: PARAM_DESCRIPTIONS.oldText }),
3499
+ new_text: Type3.String({ description: PARAM_DESCRIPTIONS.newText }),
3500
+ all: Type3.Optional(Type3.Boolean({ description: PARAM_DESCRIPTIONS.all }))
3271
3501
  }, { 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 })
3502
+ replace_symbol: Type3.Optional(Type3.Object({
3503
+ symbol: Type3.String({ description: PARAM_DESCRIPTIONS.symbol }),
3504
+ new_body: Type3.String({ description: PARAM_DESCRIPTIONS.newBody })
3275
3505
  }, { additionalProperties: false, description: PARAM_DESCRIPTIONS.replaceSymbol }))
3276
3506
  }, {
3277
3507
  additionalProperties: false,
@@ -3279,18 +3509,18 @@ var hashlineEditItemSchema = Type2.Object({
3279
3509
  maxProperties: 1,
3280
3510
  description: PARAM_DESCRIPTIONS.editVariant
3281
3511
  });
3282
- var hashlineEditSchema = Type2.Object({
3512
+ var hashlineEditSchema = Type3.Object({
3283
3513
  path: filePathParam(),
3284
- language: Type2.Optional(Type2.String({ description: PARAM_DESCRIPTIONS.languageSymbol })),
3285
- edits: Type2.Array(hashlineEditItemSchema, {
3514
+ language: Type3.Optional(Type3.String({ description: PARAM_DESCRIPTIONS.languageSymbol })),
3515
+ edits: Type3.Array(hashlineEditItemSchema, {
3286
3516
  minItems: 1,
3287
3517
  description: PARAM_DESCRIPTIONS.edits
3288
3518
  }),
3289
- postEditVerify: Type2.Optional(Type2.Boolean({
3519
+ postEditVerify: Type3.Optional(Type3.Boolean({
3290
3520
  description: PARAM_DESCRIPTIONS.postEditVerify
3291
3521
  }))
3292
3522
  }, { additionalProperties: false });
3293
- function buildEditError(path3, code, message, hint, errorDetails) {
3523
+ function buildEditError(path, code, message, hint, errorDetails) {
3294
3524
  return {
3295
3525
  content: [{ type: "text", text: message }],
3296
3526
  isError: true,
@@ -3301,7 +3531,7 @@ function buildEditError(path3, code, message, hint, errorDetails) {
3301
3531
  readSeekValue: {
3302
3532
  tool: "edit",
3303
3533
  ok: false,
3304
- path: path3,
3534
+ path,
3305
3535
  error: buildReadSeekError(code, message, hint, errorDetails)
3306
3536
  }
3307
3537
  }
@@ -3315,14 +3545,14 @@ async function executeEdit(opts) {
3315
3545
  const { params, signal, cwd, wasReadInSession, onFileMutated, syntaxValidate } = opts;
3316
3546
  const parsed = params;
3317
3547
  const rawPath = parsed.path;
3318
- const path3 = rawPath.replace(/^@/, "");
3319
- const absolutePath = resolveToCwd2(path3, cwd);
3548
+ const path = rawPath.replace(/^@/, "");
3549
+ const absolutePath = resolveToCwd2(path, cwd);
3320
3550
  throwIfAborted(signal);
3321
3551
  try {
3322
3552
  return await withFileMutationQueue(absolutePath, async () => {
3323
3553
  throwIfAborted(signal);
3324
3554
  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.`;
3555
+ 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
3556
  return buildEditError(absolutePath, "file-not-read", `You must get fresh anchors for ${absolutePath} before editing it. ${hint}`, hint);
3327
3557
  }
3328
3558
  const parsedEdits = validateExactlyOneEditVariant(parsed.edits ?? []);
@@ -3347,7 +3577,7 @@ async function executeEdit(opts) {
3347
3577
  const originalForSyntax = normalizeToLF(stripBom(original).text);
3348
3578
  const resultForSyntax = normalizeToLF(stripBom(result).text);
3349
3579
  if (!plan.changed) {
3350
- return buildEditError(absolutePath, "no-op", `No changes made to ${path3}. The edits produced identical content.`);
3580
+ return buildEditError(absolutePath, "no-op", `No changes made to ${path}. The edits produced identical content.`);
3351
3581
  }
3352
3582
  const syntaxMode = resolveSyntaxValidateMode({ syntaxValidate });
3353
3583
  let syntaxWarning;
@@ -3388,14 +3618,14 @@ async function executeEdit(opts) {
3388
3618
  try {
3389
3619
  verifiedContent = await fsReadFile(absolutePath, "utf-8");
3390
3620
  } 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 });
3621
+ 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
3622
  }
3393
3623
  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 });
3624
+ 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
3625
  }
3396
3626
  }
3397
3627
  const diffResult = generateCompactOrFullDiff(original, result);
3398
- const patch = createPatch(path3, original, result);
3628
+ const patch = createPatch(path, original, result);
3399
3629
  const diffData = buildDiffData({
3400
3630
  path: absolutePath,
3401
3631
  oldContent: original,
@@ -3407,7 +3637,7 @@ async function executeEdit(opts) {
3407
3637
  const semanticSummary = { classification: classification.classification };
3408
3638
  const builtOutput = buildEditOutput({
3409
3639
  path: absolutePath,
3410
- displayPath: path3,
3640
+ displayPath: path,
3411
3641
  diff: diffResult.diff,
3412
3642
  patch,
3413
3643
  diffData,
@@ -3430,7 +3660,7 @@ async function executeEdit(opts) {
3430
3660
  });
3431
3661
  } catch (err) {
3432
3662
  if (typeof err?.code === "string") {
3433
- return mapEditFileError(err, absolutePath, path3, "read");
3663
+ return mapEditFileError(err, absolutePath, path, "read");
3434
3664
  }
3435
3665
  throw err;
3436
3666
  }
@@ -3480,8 +3710,8 @@ function registerEditTool(pi, options = {}) {
3480
3710
  text = clampLineToWidth(text, context.width);
3481
3711
  return upsertTextComponent(context.lastComponent, text);
3482
3712
  },
3483
- renderResult(result, options2, theme, ...rest) {
3484
- const { isPartial, isError, expanded, width, context } = resolveRenderResultContext(options2, rest, resolveReadSeekToolDisplayMode("edit") === "expanded");
3713
+ renderResult(result, options, theme, ...rest) {
3714
+ const { isPartial, isError, expanded, width, context } = resolveRenderResultContext(options, rest, resolveReadSeekToolDisplayMode("edit") === "expanded");
3485
3715
  if (isPartial) {
3486
3716
  return renderPendingResult("pending edit", width, theme);
3487
3717
  }
@@ -3537,7 +3767,7 @@ ${theme.fg("error", info.errorText)}`;
3537
3767
  import { readFile as fsReadFile2, stat as fsStat } from "node:fs/promises";
3538
3768
  import path3 from "node:path";
3539
3769
  import { createGrepTool } from "@earendil-works/pi-coding-agent";
3540
- import { Type as Type4 } from "@sinclair/typebox";
3770
+ import { Type as Type5 } from "@sinclair/typebox";
3541
3771
  import { Text as Text4 } from "@earendil-works/pi-tui";
3542
3772
 
3543
3773
  // src/binary-detect.ts
@@ -3609,14 +3839,14 @@ function buildScopeMetadata(groups, warnings) {
3609
3839
  }
3610
3840
  function buildGrepOutput(input) {
3611
3841
  const fileCount = new Set(input.groups.map((group) => group.absolutePath)).size;
3612
- const header2 = `[${input.totalMatches} matches in ${fileCount} files]`;
3842
+ const header = `[${input.totalMatches} matches in ${fileCount} files]`;
3613
3843
  let text;
3614
3844
  if (input.summary) {
3615
3845
  const fileLines = [...input.groups].sort((a, b) => b.matchCount - a.matchCount).map((group) => `${group.absolutePath}: ${group.matchCount} matches`);
3616
- text = [header2, ...fileLines].join(`
3846
+ text = [header, ...fileLines].join(`
3617
3847
  `);
3618
3848
  } else {
3619
- const blocks = [header2];
3849
+ const blocks = [header];
3620
3850
  for (const group of input.groups) {
3621
3851
  blocks.push(renderGroupHeader(group));
3622
3852
  for (const entry of group.entries) {
@@ -3721,12 +3951,12 @@ function firstLineNumber(group) {
3721
3951
  }
3722
3952
  function buildSymbolEntries(fileLines, symbol, matchLines, scopeContext) {
3723
3953
  if (scopeContext === undefined) {
3724
- const entries2 = [];
3954
+ const entries = [];
3725
3955
  for (let lineNumber = symbol.startLine;lineNumber <= symbol.endLine; lineNumber++) {
3726
3956
  const built = buildReadSeekLine(lineNumber, fileLines[lineNumber - 1] ?? "");
3727
- entries2.push({ kind: matchLines.has(lineNumber) ? "match" : "context", line: built });
3957
+ entries.push({ kind: matchLines.has(lineNumber) ? "match" : "context", line: built });
3728
3958
  }
3729
- return entries2;
3959
+ return entries;
3730
3960
  }
3731
3961
  const ranges = [...matchLines].sort((a, b) => a - b).map((ln) => ({
3732
3962
  startLine: Math.max(symbol.startLine, ln - scopeContext),
@@ -3909,11 +4139,8 @@ import {
3909
4139
  } from "@jarkkojs/readseek-api";
3910
4140
 
3911
4141
  // 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
4142
  var GREP_ANCHOR_RE = /^(.*?):(>>| )(\d+):([0-9a-zA-Z]{1,16})\|(.*)$/;
3915
- var readSourceCache = new WeakMap;
3916
- function renderGrepSourceForDisplay(text, anchors, renderPath = (path3) => path3) {
4143
+ function renderGrepSourceForDisplay(text, anchors, renderPath = (path) => path) {
3917
4144
  return text.split(`
3918
4145
  `).map((line) => {
3919
4146
  const match = line.match(GREP_ANCHOR_RE);
@@ -3929,39 +4156,30 @@ function renderGrepSourceForDisplay(text, anchors, renderPath = (path3) => path3
3929
4156
  }
3930
4157
 
3931
4158
  // src/readseek-params.ts
3932
- import { Type as Type3 } from "@sinclair/typebox";
4159
+ import { Type as Type4 } from "@sinclair/typebox";
3933
4160
  function searchPathParam() {
3934
- return Type3.Optional(Type3.String({ description: PARAM_DESCRIPTIONS.searchPath }));
4161
+ return Type4.Optional(Type4.String({ description: PARAM_DESCRIPTIONS.searchPath }));
3935
4162
  }
3936
4163
  function languageParam() {
3937
- return Type3.Optional(Type3.String({ description: PARAM_DESCRIPTIONS.language }));
4164
+ return Type4.Optional(Type4.String({ description: PARAM_DESCRIPTIONS.language }));
3938
4165
  }
3939
4166
  function readSeekGitSearchParams() {
3940
4167
  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 }))
4168
+ gitScope: Type4.Optional(Type4.Union(GIT_SCOPES.map((scope) => Type4.Literal(scope)), { description: PARAM_DESCRIPTIONS.gitScope }))
3944
4169
  };
3945
4170
  }
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
4171
 
3954
4172
  // src/grep.ts
3955
- var grepSchema = Type4.Object({
3956
- pattern: Type4.String({ description: PARAM_DESCRIPTIONS.grepPattern }),
4173
+ var grepSchema = Type5.Object({
4174
+ pattern: Type5.String({ description: PARAM_DESCRIPTIONS.grepPattern }),
3957
4175
  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 })),
4176
+ glob: Type5.Optional(Type5.String({ description: PARAM_DESCRIPTIONS.glob })),
4177
+ ignoreCase: Type5.Optional(Type5.Boolean({ description: PARAM_DESCRIPTIONS.ignoreCase })),
4178
+ literal: Type5.Optional(Type5.Boolean({ description: PARAM_DESCRIPTIONS.literal })),
3961
4179
  context: optionalIntOrString(PARAM_DESCRIPTIONS.context),
3962
4180
  limit: optionalIntOrString(PARAM_DESCRIPTIONS.matchLimit),
3963
- summary: Type4.Optional(Type4.Boolean({ description: PARAM_DESCRIPTIONS.summary })),
3964
- scope: Type4.Optional(Type4.Literal("symbol", {
4181
+ summary: Type5.Optional(Type5.Boolean({ description: PARAM_DESCRIPTIONS.summary })),
4182
+ scope: Type5.Optional(Type5.Literal("symbol", {
3965
4183
  description: PARAM_DESCRIPTIONS.grepScope
3966
4184
  })),
3967
4185
  scopeContext: optionalIntOrString(PARAM_DESCRIPTIONS.scopeContext)
@@ -4033,12 +4251,6 @@ function truncateGroupEntries(group) {
4033
4251
  }
4034
4252
  return { ...group, entries: kept };
4035
4253
  }
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
4254
  async function executeGrep(opts) {
4043
4255
  const { toolCallId, params, signal, onUpdate, cwd, onFileAnchored } = opts;
4044
4256
  await ensureHashInit();
@@ -4047,10 +4259,16 @@ async function executeGrep(opts) {
4047
4259
  if (!context.ok) {
4048
4260
  return buildToolErrorResult("grep", "invalid-params-combo", context.message);
4049
4261
  }
4262
+ if (context.value !== undefined && context.value < 0) {
4263
+ return buildToolErrorResult("grep", "invalid-params-combo", `Invalid context: expected a non-negative integer, received ${context.value}.`);
4264
+ }
4050
4265
  const limit = coerceObviousBase10Int2(rawParams.limit, "limit");
4051
4266
  if (!limit.ok) {
4052
4267
  return buildToolErrorResult("grep", "invalid-limit", limit.message);
4053
4268
  }
4269
+ if (limit.value !== undefined && limit.value < 1) {
4270
+ return buildToolErrorResult("grep", "invalid-limit", `Invalid limit: expected a positive integer, received ${limit.value}.`);
4271
+ }
4054
4272
  const scopeContext = coerceObviousBase10Int2(rawParams.scopeContext, "scopeContext");
4055
4273
  if (!scopeContext.ok) {
4056
4274
  return buildToolErrorResult("grep", "invalid-params-combo", scopeContext.message);
@@ -4155,7 +4373,7 @@ async function executeGrep(opts) {
4155
4373
  const CONCURRENCY = 8;
4156
4374
  const pathList = [...pathsToRead];
4157
4375
  for (let i = 0;i < pathList.length; i += CONCURRENCY) {
4158
- await Promise.all(pathList.slice(i, i + CONCURRENCY).map((p2) => getFileLines(p2)));
4376
+ await Promise.all(pathList.slice(i, i + CONCURRENCY).map((p) => getFileLines(p)));
4159
4377
  }
4160
4378
  }
4161
4379
  const addSummaryMatch = (displayPath, absolutePath) => {
@@ -4208,7 +4426,7 @@ async function executeGrep(opts) {
4208
4426
  const flags = p.ignoreCase ? "i" : "";
4209
4427
  let patternRe = null;
4210
4428
  try {
4211
- patternRe = p.literal ? new RegExp(escapeForRegex(p.pattern), flags) : new RegExp(p.pattern, flags);
4429
+ patternRe = p.literal ? new RegExp(p.pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), flags) : new RegExp(p.pattern, flags);
4212
4430
  } catch {}
4213
4431
  if (patternRe !== null) {
4214
4432
  let emitted = false;
@@ -4292,7 +4510,7 @@ ${warning}` } : item),
4292
4510
  renderedGroups = scoped.groups;
4293
4511
  scopeWarnings = scoped.warnings;
4294
4512
  }
4295
- const readSeekRecords = recordsFromGroups(renderedGroups);
4513
+ const readSeekRecords = renderedGroups.flatMap((group) => group.entries.flatMap((entry) => entry.kind === "separator" ? [] : [{ path: group.absolutePath, ...entry.line, kind: entry.kind }]));
4296
4514
  const builtOutput = buildGrepOutput({
4297
4515
  summary: !!summary,
4298
4516
  totalMatches,
@@ -4363,8 +4581,8 @@ function registerGrepTool(pi, options = {}) {
4363
4581
  }
4364
4582
  return new Text4(clampLineToWidth(text, context.width), 0, 0);
4365
4583
  },
4366
- renderResult(result, options2, theme, ...rest) {
4367
- const { isPartial, isError, expanded, cwd, width } = resolveRenderResultContext(options2, rest, resolveReadSeekToolDisplayMode("grep") === "expanded");
4584
+ renderResult(result, options, theme, ...rest) {
4585
+ const { isPartial, isError, expanded, cwd, width } = resolveRenderResultContext(options, rest, resolveReadSeekToolDisplayMode("grep") === "expanded");
4368
4586
  if (isPartial)
4369
4587
  return renderPendingResult("pending grep", width, theme);
4370
4588
  const content = result.content?.[0];
@@ -4411,7 +4629,7 @@ function registerGrepTool(pi, options = {}) {
4411
4629
 
4412
4630
  // src/search.ts
4413
4631
  import path4 from "node:path";
4414
- import { Type as Type5 } from "@sinclair/typebox";
4632
+ import { Type as Type6 } from "@sinclair/typebox";
4415
4633
 
4416
4634
  // src/stat-search-path.ts
4417
4635
  import { stat as fsStat2 } from "node:fs/promises";
@@ -4419,9 +4637,9 @@ async function statSearchPathOrError(tool, rawPath, searchPath) {
4419
4637
  try {
4420
4638
  return { ok: true, stats: await fsStat2(searchPath) };
4421
4639
  } catch (err) {
4422
- const path4 = rawPath ?? searchPath;
4640
+ const path = rawPath ?? searchPath;
4423
4641
  const { code, message } = formatFsError(err, "stat-error");
4424
- return { ok: false, error: buildToolErrorResult(tool, code, message, { path: path4 }) };
4642
+ return { ok: false, error: buildToolErrorResult(tool, code, message, { path }) };
4425
4643
  }
4426
4644
  }
4427
4645
 
@@ -4502,9 +4720,6 @@ function readSeekLanguageForPath(language, searchPath, isFile) {
4502
4720
  async function executeSearch(opts) {
4503
4721
  const { params, signal, cwd, onFileAnchored } = opts;
4504
4722
  const p = params;
4505
- const ignoredError = validateIgnoredRequiresOthers("search", p);
4506
- if (ignoredError)
4507
- return ignoredError;
4508
4723
  const searchPath = resolveToCwd2(p.path ?? ".", cwd);
4509
4724
  const statResult = await statSearchPathOrError("search", p.path, searchPath);
4510
4725
  if (!statResult.ok)
@@ -4514,9 +4729,7 @@ async function executeSearch(opts) {
4514
4729
  const effectiveLang = readSeekLanguageForPath(p.language, searchPath, searchPathIsFile);
4515
4730
  const results = await readSeekSearch(searchPath, p.pattern, {
4516
4731
  language: effectiveLang,
4517
- cached: p.cached,
4518
- others: p.others,
4519
- ignored: p.ignored,
4732
+ gitScope: p.gitScope,
4520
4733
  signal
4521
4734
  });
4522
4735
  if (results.length === 0) {
@@ -4578,8 +4791,8 @@ function registerSearchTool(pi, options = {}) {
4578
4791
  description: SEARCH_PROMPT_METADATA.description,
4579
4792
  promptSnippet: SEARCH_PROMPT_METADATA.promptSnippet,
4580
4793
  promptGuidelines: SEARCH_PROMPT_METADATA.promptGuidelines,
4581
- parameters: Type5.Object({
4582
- pattern: Type5.String({ description: PARAM_DESCRIPTIONS.pattern }),
4794
+ parameters: Type6.Object({
4795
+ pattern: Type6.String({ description: PARAM_DESCRIPTIONS.pattern }),
4583
4796
  language: languageParam(),
4584
4797
  path: searchPathParam(),
4585
4798
  ...readSeekGitSearchParams()
@@ -4591,11 +4804,11 @@ function registerSearchTool(pi, options = {}) {
4591
4804
  return renderReadSeekSearchCall(args, theme, rest, {
4592
4805
  label: "search",
4593
4806
  accent: `/${args.pattern}/`,
4594
- flags: [args.cached && "cached", args.others && "others", args.ignored && "ignored"]
4807
+ flags: [args.gitScope && `git-scope=${args.gitScope}`]
4595
4808
  });
4596
4809
  },
4597
- renderResult(result, options2, theme, ...rest) {
4598
- return renderAnchoredFilesResult(result, options2, theme, rest, {
4810
+ renderResult(result, options, theme, ...rest) {
4811
+ return renderAnchoredFilesResult(result, options, theme, rest, {
4599
4812
  pendingLabel: "pending search",
4600
4813
  emptyLabel: "no matches",
4601
4814
  unitSingular: "match",
@@ -4607,7 +4820,7 @@ function registerSearchTool(pi, options = {}) {
4607
4820
  }
4608
4821
 
4609
4822
  // src/refs.ts
4610
- import { Type as Type6 } from "@sinclair/typebox";
4823
+ import { Type as Type7 } from "@sinclair/typebox";
4611
4824
  import path5 from "node:path";
4612
4825
 
4613
4826
  // src/refs-output.ts
@@ -4673,9 +4886,6 @@ function isReadSeekCursorValidationFailure(message) {
4673
4886
  async function executeRefs(opts) {
4674
4887
  const { params, signal, cwd, onFileAnchored } = opts;
4675
4888
  const p = params;
4676
- const ignoredError = validateIgnoredRequiresOthers("refs", p);
4677
- if (ignoredError)
4678
- return ignoredError;
4679
4889
  const scopeError = validateRefsNameVsScope(p);
4680
4890
  if (!scopeError.ok) {
4681
4891
  return buildToolErrorResult("refs", "invalid-parameter", `refs parameter: ${scopeError.error}`);
@@ -4691,9 +4901,7 @@ async function executeRefs(opts) {
4691
4901
  line: p.line,
4692
4902
  column: p.column,
4693
4903
  language: p.language,
4694
- cached: p.cached,
4695
- others: p.others,
4696
- ignored: p.ignored,
4904
+ gitScope: p.gitScope,
4697
4905
  signal
4698
4906
  });
4699
4907
  const files = groupReferences(references, cwd);
@@ -4726,13 +4934,13 @@ function registerRefsTool(pi, options = {}) {
4726
4934
  description: REFS_PROMPT_METADATA.description,
4727
4935
  promptSnippet: REFS_PROMPT_METADATA.promptSnippet,
4728
4936
  promptGuidelines: REFS_PROMPT_METADATA.promptGuidelines,
4729
- parameters: Type6.Object({
4730
- name: Type6.Optional(Type6.String({ description: PARAM_DESCRIPTIONS.refsName })),
4937
+ parameters: Type7.Object({
4938
+ name: Type7.Optional(Type7.String({ description: PARAM_DESCRIPTIONS.refsName })),
4731
4939
  path: searchPathParam(),
4732
4940
  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 })),
4941
+ scope: Type7.Optional(Type7.Boolean({ description: PARAM_DESCRIPTIONS.scope })),
4942
+ line: Type7.Optional(Type7.Number({ description: PARAM_DESCRIPTIONS.line })),
4943
+ column: Type7.Optional(Type7.Number({ description: PARAM_DESCRIPTIONS.column })),
4736
4944
  ...readSeekGitSearchParams()
4737
4945
  }),
4738
4946
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
@@ -4743,11 +4951,11 @@ function registerRefsTool(pi, options = {}) {
4743
4951
  return renderReadSeekSearchCall(args, theme, rest, {
4744
4952
  label: "refs",
4745
4953
  accent,
4746
- flags: [args.scope && "scope", args.cached && "cached", args.others && "others", args.ignored && "ignored"]
4954
+ flags: [args.scope && "scope", args.gitScope && `git-scope=${args.gitScope}`]
4747
4955
  });
4748
4956
  },
4749
- renderResult(result, options2, theme, ...rest) {
4750
- return renderAnchoredFilesResult(result, options2, theme, rest, {
4957
+ renderResult(result, options, theme, ...rest) {
4958
+ return renderAnchoredFilesResult(result, options, theme, rest, {
4751
4959
  pendingLabel: "pending refs",
4752
4960
  emptyLabel: "no references",
4753
4961
  unitSingular: "reference",
@@ -4759,20 +4967,20 @@ function registerRefsTool(pi, options = {}) {
4759
4967
  }
4760
4968
 
4761
4969
  // src/rename.ts
4762
- import { Type as Type7 } from "@sinclair/typebox";
4970
+ import { Type as Type8 } from "@sinclair/typebox";
4763
4971
  import path6 from "node:path";
4764
4972
  import { Text as Text5 } from "@earendil-works/pi-tui";
4765
4973
  var RENAME_PROMPT_METADATA = defineToolPromptMetadata({
4766
4974
  promptUrl: new URL("../prompts/rename.md", import.meta.url),
4767
4975
  promptSnippet: "Rename a symbol at a source location with verified edits"
4768
4976
  });
4769
- var renameSchema = Type7.Object({
4977
+ var renameSchema = Type8.Object({
4770
4978
  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 }))
4979
+ line: Type8.Integer({ minimum: 1, description: PARAM_DESCRIPTIONS.renameLine }),
4980
+ column: Type8.Optional(Type8.Integer({ minimum: 1, description: PARAM_DESCRIPTIONS.column })),
4981
+ to: Type8.String({ description: PARAM_DESCRIPTIONS.to }),
4982
+ workspace: Type8.Optional(Type8.Boolean({ description: PARAM_DESCRIPTIONS.workspace })),
4983
+ apply: Type8.Optional(Type8.Boolean({ description: PARAM_DESCRIPTIONS.applyDefaultTrue }))
4776
4984
  });
4777
4985
  async function executeRename(opts) {
4778
4986
  const { params, signal, cwd, onFileMutated } = opts;
@@ -4804,7 +5012,8 @@ async function executeRename(opts) {
4804
5012
  onFileMutated?.(absoluteFile);
4805
5013
  }
4806
5014
  }
4807
- const files = [output.file];
5015
+ const primaryAbs = path6.isAbsolute(output.file) ? output.file : path6.resolve(cwd, output.file);
5016
+ const files = [primaryAbs];
4808
5017
  for (const other of output.others) {
4809
5018
  const abs = path6.isAbsolute(other.file) ? other.file : path6.resolve(cwd, other.file);
4810
5019
  if (!files.includes(abs))
@@ -4864,8 +5073,8 @@ function registerRenameTool(pi, options = {}) {
4864
5073
  text += theme.fg("dim", ` → ${args.to}`);
4865
5074
  return new Text5(clampLineToWidth(text, context.width), 0, 0);
4866
5075
  },
4867
- renderResult(result, options2, theme, ...rest) {
4868
- const { isPartial, isError, expanded, cwd, width } = resolveRenderResultContext(options2, rest);
5076
+ renderResult(result, options, theme, ...rest) {
5077
+ const { isPartial, isError, expanded, cwd, width } = resolveRenderResultContext(options, rest);
4869
5078
  if (isPartial)
4870
5079
  return renderPendingResult("pending rename", width, theme);
4871
5080
  const content = result.content?.[0];
@@ -4906,7 +5115,7 @@ import { mkdir, readFile, writeFile } from "node:fs/promises";
4906
5115
  import { dirname as dirname2, relative as relative3 } from "node:path";
4907
5116
  import { withFileMutationQueue as withFileMutationQueue2, truncateHead as truncateHead2 } from "@earendil-works/pi-coding-agent";
4908
5117
  import { Text as Text6 } from "@earendil-works/pi-tui";
4909
- import { Type as Type8 } from "@sinclair/typebox";
5118
+ import { Type as Type9 } from "@sinclair/typebox";
4910
5119
 
4911
5120
  // src/pending-diff-preview.ts
4912
5121
  import { existsSync as existsSync3, readFileSync as readFileSync2, realpathSync, statSync as statSync4 } from "node:fs";
@@ -5053,9 +5262,9 @@ function pendingWritePreviewParts(summary, preview, expanded, theme) {
5053
5262
  if (!preview || preview.type !== "ok")
5054
5263
  return { lines: summary.split(`
5055
5264
  `) };
5056
- const hasOldSide2 = preview.data.fileExistedBeforeWrite;
5265
+ const hasOldSide = preview.data.fileExistedBeforeWrite;
5057
5266
  const headerLine = summaryLine(preview.data.headerLabel, { hidden: !expanded });
5058
- if (!hasOldSide2) {
5267
+ if (!hasOldSide) {
5059
5268
  const lines = [summary, headerLine];
5060
5269
  if (expanded)
5061
5270
  lines.push(...formatContentPreviewLines(preview.data.nextContent, theme));
@@ -5111,7 +5320,7 @@ async function executeWrite(opts) {
5111
5320
  const warnings = [];
5112
5321
  const readSeekWarnings = [];
5113
5322
  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.";
5323
+ 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
5324
  warnings.push(message);
5116
5325
  readSeekWarnings.push(buildReadSeekWarning("bare-cr", message));
5117
5326
  return buildToolErrorResult("write", "bare-cr", `Cannot write ${filePath}
@@ -5202,9 +5411,9 @@ function registerWriteTool(pi, options = {}) {
5202
5411
  description: promptMetadata.description,
5203
5412
  promptSnippet: promptMetadata.promptSnippet,
5204
5413
  promptGuidelines: promptMetadata.promptGuidelines,
5205
- parameters: Type8.Object({
5414
+ parameters: Type9.Object({
5206
5415
  path: filePathParam(),
5207
- content: Type8.String({ description: PARAM_DESCRIPTIONS.content })
5416
+ content: Type9.String({ description: PARAM_DESCRIPTIONS.content })
5208
5417
  }),
5209
5418
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
5210
5419
  const cwd = ctx?.cwd ?? process.cwd();
@@ -5242,13 +5451,13 @@ function registerWriteTool(pi, options = {}) {
5242
5451
  }
5243
5452
  },
5244
5453
  renderCall(args, theme, context = {}) {
5245
- const { path: path7, content } = args;
5454
+ const { path, content } = args;
5246
5455
  const cwd = context.cwd ?? process.cwd();
5247
5456
  const label = renderToolLabel(theme, "write");
5248
5457
  const lineCount = typeof content === "string" ? content.split(`
5249
5458
  `).length : 0;
5250
5459
  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", "...");
5460
+ const renderedPath = typeof path === "string" ? linkToolPath(theme.fg("muted", path), path, cwd) : theme.fg("toolOutput", "...");
5252
5461
  let text = clampLineToWidth(`${label} ${renderedPath}${typeof content === "string" ? ` (${lineCount} ${lineCount === 1 ? "line" : "lines"} • ${bytes} B)` : ""}`, context.width);
5253
5462
  if (context.executionStarted) {
5254
5463
  return upsertTextComponent(context.lastComponent, text);
@@ -5263,8 +5472,8 @@ function registerWriteTool(pi, options = {}) {
5263
5472
  return upsertTextComponent(context.lastComponent, clampLinesToWidth(parts.lines, context.width).join(`
5264
5473
  `));
5265
5474
  },
5266
- renderResult(result, options2, theme, ...rest) {
5267
- const { isPartial, expanded, width, context } = resolveRenderResultContext(options2, rest, resolveReadSeekToolDisplayMode("write") === "expanded");
5475
+ renderResult(result, options, theme, ...rest) {
5476
+ const { isPartial, expanded, width, context } = resolveRenderResultContext(options, rest, resolveReadSeekToolDisplayMode("write") === "expanded");
5268
5477
  if (isPartial)
5269
5478
  return renderPendingResult("pending write", width, theme);
5270
5479
  const details = result.details ?? {};
@@ -5277,8 +5486,8 @@ function registerWriteTool(pi, options = {}) {
5277
5486
  if (state === "created") {
5278
5487
  const readSeekLines = details.readSeekValue?.lines ?? [];
5279
5488
  const hasContent = readSeekLines.length > 0;
5280
- const header2 = summaryLine(state, { hidden: hasContent && !expanded, theme, style: "success" });
5281
- const lines = header2.split(`
5489
+ const header = summaryLine(state, { hidden: hasContent && !expanded, theme, style: "success" });
5490
+ const lines = header.split(`
5282
5491
  `);
5283
5492
  if (expanded && hasContent) {
5284
5493
  const content = readSeekLines.map((l) => l.raw).join(`
@@ -5303,7 +5512,7 @@ function registerWriteTool(pi, options = {}) {
5303
5512
  }
5304
5513
 
5305
5514
  // src/def.ts
5306
- import { Type as Type9 } from "@sinclair/typebox";
5515
+ import { Type as Type10 } from "@sinclair/typebox";
5307
5516
  import path7 from "node:path";
5308
5517
  var DEF_PROMPT_METADATA = defineToolPromptMetadata({
5309
5518
  promptUrl: new URL("../prompts/def.md", import.meta.url),
@@ -5312,9 +5521,6 @@ var DEF_PROMPT_METADATA = defineToolPromptMetadata({
5312
5521
  async function executeDef(opts) {
5313
5522
  const { params, signal, cwd, onFileAnchored } = opts;
5314
5523
  const p = params;
5315
- const ignoredError = validateIgnoredRequiresOthers("def", p);
5316
- if (ignoredError)
5317
- return ignoredError;
5318
5524
  if (!p.name || !p.name.trim()) {
5319
5525
  return buildToolErrorResult("def", "invalid-parameter", "readSeek_def requires 'name'");
5320
5526
  }
@@ -5326,9 +5532,7 @@ async function executeDef(opts) {
5326
5532
  const definitions = await readSeekDef(searchPath, {
5327
5533
  name: p.name,
5328
5534
  language: p.language,
5329
- cached: p.cached,
5330
- others: p.others,
5331
- ignored: p.ignored,
5535
+ gitScope: p.gitScope,
5332
5536
  signal
5333
5537
  });
5334
5538
  if (definitions.length === 0) {
@@ -5379,8 +5583,8 @@ function registerDefTool(pi, options = {}) {
5379
5583
  description: DEF_PROMPT_METADATA.description,
5380
5584
  promptSnippet: DEF_PROMPT_METADATA.promptSnippet,
5381
5585
  promptGuidelines: DEF_PROMPT_METADATA.promptGuidelines,
5382
- parameters: Type9.Object({
5383
- name: Type9.String({ description: PARAM_DESCRIPTIONS.name }),
5586
+ parameters: Type10.Object({
5587
+ name: Type10.String({ description: PARAM_DESCRIPTIONS.name }),
5384
5588
  path: searchPathParam(),
5385
5589
  language: languageParam(),
5386
5590
  ...readSeekGitSearchParams()
@@ -5392,11 +5596,11 @@ function registerDefTool(pi, options = {}) {
5392
5596
  return renderReadSeekSearchCall(args, theme, rest, {
5393
5597
  label: "def",
5394
5598
  accent: args.name,
5395
- flags: [args.cached && "cached", args.others && "others", args.ignored && "ignored"]
5599
+ flags: [args.gitScope && `git-scope=${args.gitScope}`]
5396
5600
  });
5397
5601
  },
5398
- renderResult(result, options2, theme, ...rest) {
5399
- return renderAnchoredFilesResult(result, options2, theme, rest, {
5602
+ renderResult(result, options, theme, ...rest) {
5603
+ return renderAnchoredFilesResult(result, options, theme, rest, {
5400
5604
  pendingLabel: "pending def",
5401
5605
  emptyLabel: "no definitions",
5402
5606
  unitSingular: "definition",
@@ -5406,39 +5610,33 @@ function registerDefTool(pi, options = {}) {
5406
5610
  });
5407
5611
  }
5408
5612
 
5409
- // src/digest.ts
5613
+ // src/inspect.ts
5410
5614
  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({
5615
+ import { Type as Type11 } from "@sinclair/typebox";
5616
+ var facetSchema = Type11.Union(INSPECT_FACETS.map((facet) => Type11.Literal(facet)));
5617
+ var inspectSchema = Type11.Object({
5415
5618
  path: filePathParam(),
5416
- select: Type10.Optional(Type10.Union([
5619
+ selector: Type11.Optional(Type11.String({ description: PARAM_DESCRIPTIONS.selector })),
5620
+ select: Type11.Optional(Type11.Union([
5417
5621
  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
- })),
5622
+ Type11.Array(facetSchema, { minItems: 1 }),
5623
+ Type11.String({ description: PARAM_DESCRIPTIONS.selectComma })
5624
+ ], { description: PARAM_DESCRIPTIONS.select })),
5625
+ query: Type11.Optional(Type11.String({ description: PARAM_DESCRIPTIONS.query })),
5428
5626
  end: optionalIntOrString(PARAM_DESCRIPTIONS.end),
5429
5627
  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 }))
5628
+ depth: optionalIntOrString(PARAM_DESCRIPTIONS.inspectDepth),
5629
+ language: Type11.Optional(Type11.String({ description: PARAM_DESCRIPTIONS.languageOverride })),
5630
+ visionMode: Type11.Optional(Type11.Union(VISION_MODES.map((mode) => Type11.Literal(mode)), { description: PARAM_DESCRIPTIONS.visionModeInspect })),
5631
+ visionLevel: Type11.Optional(Type11.Union(VISION_LEVELS.map((level) => Type11.Literal(level)), { description: PARAM_DESCRIPTIONS.visionLevel }))
5434
5632
  });
5435
- function hasDigestAnchors(value) {
5633
+ function hasInspectAnchors(value) {
5436
5634
  if (!value || typeof value !== "object")
5437
5635
  return false;
5438
5636
  const record = value;
5439
5637
  if (Array.isArray(record.hashlines) && record.hashlines.length > 0)
5440
5638
  return true;
5441
- return Object.values(record).some(hasDigestAnchors);
5639
+ return Object.values(record).some(hasInspectAnchors);
5442
5640
  }
5443
5641
  function parseSelect(select) {
5444
5642
  if (select === undefined)
@@ -5448,8 +5646,8 @@ function parseSelect(select) {
5448
5646
  if (facets.length === 0)
5449
5647
  throw new Error("select must include at least one facet");
5450
5648
  for (const facet of facets) {
5451
- if (!DIGEST_FACETS2.includes(facet)) {
5452
- throw new Error(`unknown digest facet \`${facet}\`; expected ${DIGEST_FACETS2.join(", ")}`);
5649
+ if (!INSPECT_FACETS.includes(facet)) {
5650
+ throw new Error(`unknown inspect facet \`${facet}\`; expected ${INSPECT_FACETS.join(", ")}`);
5453
5651
  }
5454
5652
  }
5455
5653
  return facets;
@@ -5457,15 +5655,15 @@ function parseSelect(select) {
5457
5655
  if (typeof select !== "string") {
5458
5656
  throw new Error("select must be a facet, facet list, or comma-separated facets");
5459
5657
  }
5460
- if (DIGEST_FACETS2.includes(select)) {
5658
+ if (INSPECT_FACETS.includes(select)) {
5461
5659
  return select;
5462
5660
  }
5463
5661
  const parts = select.split(",").map((part) => part.trim()).filter((part) => part.length > 0);
5464
5662
  if (parts.length === 0)
5465
5663
  throw new Error("select must include at least one facet");
5466
5664
  for (const part of parts) {
5467
- if (!DIGEST_FACETS2.includes(part)) {
5468
- throw new Error(`unknown digest facet \`${part}\`; expected ${DIGEST_FACETS2.join(", ")}`);
5665
+ if (!INSPECT_FACETS.includes(part)) {
5666
+ throw new Error(`unknown inspect facet \`${part}\`; expected ${INSPECT_FACETS.join(", ")}`);
5469
5667
  }
5470
5668
  }
5471
5669
  return parts;
@@ -5477,7 +5675,7 @@ function selectedFacets(select) {
5477
5675
  }
5478
5676
  function summarizeEnvelope(envelope, facets) {
5479
5677
  if (!envelope || typeof envelope !== "object")
5480
- return `digested ${facets.join(",")}`;
5678
+ return `inspected ${facets.join(",")}`;
5481
5679
  const record = envelope;
5482
5680
  const parts = [];
5483
5681
  for (const facet of facets) {
@@ -5486,7 +5684,7 @@ function summarizeEnvelope(envelope, facets) {
5486
5684
  }
5487
5685
  if (record.metadata !== undefined && !parts.includes("metadata"))
5488
5686
  parts.unshift("metadata");
5489
- return `digested ${parts.join(",") || "metadata"}`;
5687
+ return `inspected ${parts.join(",") || "metadata"}`;
5490
5688
  }
5491
5689
  function parseImageEnvelope(envelope) {
5492
5690
  if (!envelope || typeof envelope !== "object")
@@ -5504,12 +5702,12 @@ function parseImageEnvelope(envelope) {
5504
5702
  const prepared = content.encoding === "base64" && typeof content.data === "string" && typeof mime === "string" ? { data: content.data, mimeType: mime } : undefined;
5505
5703
  return { metadata, prepared };
5506
5704
  }
5507
- function buildDigestSuccess(filePath, facets, envelope, content = [{ type: "text", text: JSON.stringify(envelope) }]) {
5705
+ function buildInspectSuccess(filePath, facets, envelope, content = [{ type: "text", text: JSON.stringify(envelope) }]) {
5508
5706
  return {
5509
5707
  content,
5510
5708
  details: {
5511
5709
  readSeekValue: {
5512
- tool: "digest",
5710
+ tool: "inspect",
5513
5711
  ok: true,
5514
5712
  path: filePath,
5515
5713
  select: facets,
@@ -5518,14 +5716,14 @@ function buildDigestSuccess(filePath, facets, envelope, content = [{ type: "text
5518
5716
  }
5519
5717
  };
5520
5718
  }
5521
- async function executeDigest(opts) {
5719
+ async function executeInspect(opts) {
5522
5720
  const { params, signal, cwd, onFileAnchored } = opts;
5523
5721
  const p = params;
5524
5722
  let select;
5525
5723
  try {
5526
5724
  select = parseSelect(p.select);
5527
5725
  } catch (err) {
5528
- return buildToolErrorResult("digest", "invalid-parameter", err?.message ?? String(err), {
5726
+ return buildToolErrorResult("inspect", "invalid-parameter", err?.message ?? String(err), {
5529
5727
  path: p.path
5530
5728
  });
5531
5729
  }
@@ -5534,52 +5732,66 @@ async function executeDigest(opts) {
5534
5732
  const end = coerceObviousBase10Int2(p.end, "end");
5535
5733
  if (!end.ok || end.value !== undefined && end.value < 1) {
5536
5734
  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 });
5735
+ return buildToolErrorResult("inspect", "invalid-parameter", message, { path: p.path });
5538
5736
  }
5539
5737
  const limit = coerceObviousBase10Int2(p.limit, "limit");
5540
5738
  if (!limit.ok || limit.value !== undefined && limit.value < 1) {
5541
5739
  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 });
5740
+ return buildToolErrorResult("inspect", "invalid-parameter", message, { path: p.path });
5741
+ }
5742
+ if (end.value !== undefined && limit.value !== undefined) {
5743
+ return buildToolErrorResult("inspect", "invalid-params-combo", "end and limit cannot be combined.", { path: p.path });
5543
5744
  }
5544
5745
  const depth = coerceObviousBase10Int2(p.depth, "depth");
5545
5746
  if (!depth.ok || depth.value !== undefined && depth.value < 0) {
5546
5747
  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 });
5748
+ return buildToolErrorResult("inspect", "invalid-parameter", message, { path: p.path });
5548
5749
  }
5549
5750
  if (depth.value !== undefined && !facets.includes("map")) {
5550
- return buildToolErrorResult("digest", "invalid-parameter", "depth requires the map facet.", { path: p.path });
5751
+ return buildToolErrorResult("inspect", "invalid-parameter", "depth requires the map facet.", { path: p.path });
5551
5752
  }
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
- });
5753
+ const selector = typeof p.selector === "string" ? p.selector.trim() : undefined;
5754
+ if (p.selector !== undefined && !selector) {
5755
+ return buildToolErrorResult("inspect", "invalid-parameter", "Invalid selector: expected a non-empty selector.", { path: p.path });
5557
5756
  }
5558
- if (p.visionLevel !== undefined && p.visionMode === undefined && !appliesImagePolicy) {
5559
- return buildToolErrorResult("digest", "invalid-params-combo", "visionLevel requires visionMode.", {
5560
- path: p.path
5561
- });
5757
+ const query = typeof p.query === "string" ? p.query.trim() : undefined;
5758
+ if (p.query !== undefined && !query) {
5759
+ return buildToolErrorResult("inspect", "invalid-parameter", "Invalid query: expected a non-empty query.", { path: p.path });
5760
+ }
5761
+ if (selector !== undefined && query !== undefined) {
5762
+ return buildToolErrorResult("inspect", "invalid-params-combo", "selector and query cannot be combined.", { path: p.path });
5763
+ }
5764
+ if (facets.includes("identity") && selector === undefined) {
5765
+ return buildToolErrorResult("inspect", "invalid-params-combo", "the identity facet requires a selector.", { path: p.path });
5766
+ }
5767
+ if (p.visionLevel !== undefined && (p.visionMode === undefined || p.visionMode === "none") && !appliesImagePolicy) {
5768
+ return buildToolErrorResult("inspect", "invalid-params-combo", "visionLevel requires an analysis visionMode.", { path: p.path });
5562
5769
  }
5563
5770
  const filePath = resolveToCwd2(p.path, cwd);
5564
5771
  const commonOptions = {
5565
5772
  select,
5566
5773
  signal,
5567
- depth: depth.value ?? (facets.includes("map") ? DEFAULT_DIGEST_MAP_DEPTH : undefined)
5774
+ depth: depth.value ?? (facets.includes("map") ? DEFAULT_INSPECT_MAP_DEPTH : undefined)
5568
5775
  };
5569
- const textOptions = {
5776
+ const selectionOptions = {
5570
5777
  ...commonOptions,
5571
- at,
5778
+ selector,
5779
+ query,
5572
5780
  end: end.value,
5573
5781
  limit: limit.value,
5574
5782
  language: p.language
5575
5783
  };
5576
- const hasTextOptions = at !== undefined || end.value !== undefined || limit.value !== undefined || p.language !== undefined;
5784
+ const probeOptions = {
5785
+ ...commonOptions,
5786
+ select: [...new Set(["metadata", ...facets])].filter((facet) => facet !== "identity")
5787
+ };
5788
+ const hasSelectionOptions = selector !== undefined || query !== undefined || end.value !== undefined || limit.value !== undefined || p.language !== undefined;
5577
5789
  const hasVisionOptions = p.visionMode !== undefined || p.visionLevel !== undefined;
5578
5790
  const initialVisionMode = appliesImagePolicy ? undefined : p.visionMode;
5579
5791
  const initialVisionLevel = appliesImagePolicy ? undefined : p.visionLevel;
5580
5792
  try {
5581
- const envelope = await readSeekDigestNative(filePath, {
5582
- ...appliesImagePolicy ? commonOptions : textOptions,
5793
+ const envelope = await readSeekInspect(filePath, {
5794
+ ...appliesImagePolicy ? probeOptions : selectionOptions,
5583
5795
  visionMode: initialVisionMode,
5584
5796
  visionLevel: initialVisionLevel,
5585
5797
  vision: initialVisionMode !== undefined && initialVisionMode !== "none"
@@ -5588,60 +5800,60 @@ async function executeDigest(opts) {
5588
5800
  if (image) {
5589
5801
  const imageDetails = { metadata: image.metadata };
5590
5802
  if (opts.imageMode === "off") {
5591
- return buildDigestSuccess(filePath, facets, imageDetails, [
5803
+ return buildInspectSuccess(filePath, facets, imageDetails, [
5592
5804
  { type: "text", text: `[Skipped standalone image: ${p.path}; readseek.imageMode is off]` }
5593
5805
  ]);
5594
5806
  }
5595
5807
  if (opts.imageMode === "auto" && opts.modelSupportsImages && image.prepared) {
5596
- return buildDigestSuccess(filePath, facets, imageDetails, [
5808
+ return buildInspectSuccess(filePath, facets, imageDetails, [
5597
5809
  { type: "image", data: image.prepared.data, mimeType: image.prepared.mimeType }
5598
5810
  ]);
5599
5811
  }
5600
5812
  const visionMode = p.visionMode && p.visionMode !== "none" ? p.visionMode : "all";
5601
- const analyzedEnvelope = await readSeekDigestNative(filePath, {
5813
+ const analyzedEnvelope = await readSeekInspect(filePath, {
5602
5814
  ...commonOptions,
5603
5815
  visionMode,
5604
5816
  visionLevel: p.visionLevel,
5605
5817
  vision: true
5606
5818
  });
5607
- return buildDigestSuccess(filePath, facets, analyzedEnvelope);
5819
+ return buildInspectSuccess(filePath, facets, analyzedEnvelope);
5608
5820
  }
5609
- if (p.visionLevel !== undefined && p.visionMode === undefined) {
5610
- return buildToolErrorResult("digest", "invalid-params-combo", "visionLevel requires visionMode.", {
5821
+ if (p.visionLevel !== undefined && (p.visionMode === undefined || p.visionMode === "none")) {
5822
+ return buildToolErrorResult("inspect", "invalid-params-combo", "visionLevel requires an analysis visionMode.", {
5611
5823
  path: p.path
5612
5824
  });
5613
5825
  }
5614
- const textEnvelope = appliesImagePolicy && (hasTextOptions || hasVisionOptions) ? await readSeekDigestNative(filePath, {
5615
- ...textOptions,
5826
+ const selectedEnvelope = appliesImagePolicy && (hasSelectionOptions || hasVisionOptions) ? await readSeekInspect(filePath, {
5827
+ ...selectionOptions,
5616
5828
  visionMode: p.visionMode,
5617
5829
  visionLevel: p.visionLevel,
5618
5830
  vision: p.visionMode !== undefined && p.visionMode !== "none"
5619
5831
  }) : envelope;
5620
- if (hasDigestAnchors(textEnvelope))
5832
+ if (hasInspectAnchors(selectedEnvelope))
5621
5833
  onFileAnchored?.(filePath);
5622
- return buildDigestSuccess(filePath, facets, textEnvelope);
5834
+ return buildInspectSuccess(filePath, facets, selectedEnvelope);
5623
5835
  } catch (err) {
5624
5836
  const failure = classifyReadSeekFailure(err);
5625
- return buildToolErrorResult("digest", failure.code, failure.message, failure.hint ? { hint: failure.hint } : {});
5837
+ return buildToolErrorResult("inspect", failure.code, failure.message, failure.hint ? { hint: failure.hint } : {});
5626
5838
  }
5627
5839
  }
5628
- function registerDigestTool(pi, options = {}) {
5840
+ function registerInspectTool(pi, options = {}) {
5629
5841
  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",
5842
+ promptUrl: new URL("../prompts/inspect.md", import.meta.url),
5843
+ promptSnippet: "Inspect source, images, and documents",
5844
+ registeredName: options.name ?? "readSeek_inspect",
5633
5845
  toolAliases: options.toolAliases
5634
5846
  });
5635
5847
  return registerReadSeekTool(pi, {
5636
- name: options.name ?? "readSeek_digest",
5637
- label: "Digest",
5848
+ name: options.name ?? "readSeek_inspect",
5849
+ label: "Inspect",
5638
5850
  description: promptMetadata.description,
5639
5851
  promptSnippet: promptMetadata.promptSnippet,
5640
5852
  toolAliases: options.toolAliases,
5641
5853
  promptGuidelines: promptMetadata.promptGuidelines,
5642
- parameters: digestSchema,
5854
+ parameters: inspectSchema,
5643
5855
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
5644
- return executeDigest({
5856
+ return executeInspect({
5645
5857
  params,
5646
5858
  signal,
5647
5859
  cwd: ctx.cwd,
@@ -5654,23 +5866,23 @@ function registerDigestTool(pi, options = {}) {
5654
5866
  const context = rest[0] ?? {};
5655
5867
  const cwd = context.cwd ?? process.cwd();
5656
5868
  const displayPath = typeof args?.path === "string" ? args.path : "?";
5657
- let text = theme.fg("toolTitle", theme.bold("digest"));
5869
+ let text = theme.fg("toolTitle", theme.bold("inspect"));
5658
5870
  text += ` ${linkToolPath(theme.fg("accent", displayPath), displayPath, cwd)}`;
5659
5871
  if (args?.select !== undefined) {
5660
5872
  const select = Array.isArray(args.select) ? args.select.join(",") : String(args.select);
5661
5873
  text += theme.fg("dim", ` [${select}]`);
5662
5874
  }
5663
- if (typeof args?.at === "string" && args.at)
5664
- text += theme.fg("dim", ` @ ${args.at}`);
5875
+ if (typeof args?.selector === "string" && args.selector)
5876
+ text += theme.fg("dim", ` @ ${args.selector}`);
5665
5877
  return new Text7(clampLineToWidth(text, context.width), 0, 0);
5666
5878
  },
5667
- renderResult(result, options2, theme, ...rest) {
5668
- const { isPartial, isError, expanded, width } = resolveRenderResultContext(options2, rest);
5879
+ renderResult(result, options, theme, ...rest) {
5880
+ const { isPartial, isError, expanded, width } = resolveRenderResultContext(options, rest);
5669
5881
  if (isPartial)
5670
- return renderPendingResult("pending digest", width, theme);
5882
+ return renderPendingResult("pending inspect", width, theme);
5671
5883
  const textContent = result.content?.[0]?.type === "text" ? result.content[0].text : "";
5672
5884
  if (isError || result.isError) {
5673
- return renderErrorResult(textContent, { expanded, width, fallback: "digest failed", theme });
5885
+ return renderErrorResult(textContent, { expanded, width, fallback: "inspect failed", theme });
5674
5886
  }
5675
5887
  const value = result.details?.readSeekValue;
5676
5888
  const facets = Array.isArray(value?.select) ? value.select : ["content"];
@@ -5686,131 +5898,88 @@ ${textContent}`;
5686
5898
  });
5687
5899
  }
5688
5900
 
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";
5901
+ // src/extract.ts
5695
5902
  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"
5903
+ import { Type as Type12 } from "@sinclair/typebox";
5904
+ var EXTRACT_PROMPT_METADATA = defineToolPromptMetadata({
5905
+ promptUrl: new URL("../prompts/extract.md", import.meta.url),
5906
+ promptSnippet: "Extract a selected document asset to a file"
5701
5907
  });
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.", {
5908
+ async function executeExtract(opts) {
5909
+ const { signal, cwd, onFileMutated } = opts;
5910
+ const input = opts.params;
5911
+ const selector = typeof input.selector === "string" ? input.selector.trim() : "";
5912
+ if (!selector.startsWith("asset:") || selector === "asset:") {
5913
+ return buildToolErrorResult("extract", "invalid-selector", "selector must identify an asset as asset:ID.", {
5718
5914
  path: input.path
5719
5915
  });
5720
5916
  }
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}.`, {
5917
+ const output = typeof input.output === "string" ? input.output.trim() : "";
5918
+ if (!output || output === "-") {
5919
+ return buildToolErrorResult("extract", "invalid-output", "output must be a file path, not stdout.", {
5728
5920
  path: input.path
5729
5921
  });
5730
5922
  }
5731
5923
  const filePath = resolveToCwd2(input.path, cwd);
5924
+ const outputPath = resolveToCwd2(output, cwd);
5732
5925
  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]` : "";
5926
+ await readSeekExtract(filePath, selector, outputPath, { signal, cancelable: false });
5927
+ onFileMutated?.(outputPath);
5749
5928
  return {
5750
- content: [{ type: "text", text: `${truncation.content}${notice}` }],
5929
+ content: [{ type: "text", text: `Extracted ${selector} to ${outputPath}` }],
5751
5930
  details: {
5752
5931
  readSeekValue: {
5753
- tool: "view",
5932
+ tool: "extract",
5933
+ ok: true,
5754
5934
  path: filePath,
5755
- truncated: truncation.truncated
5935
+ selector,
5936
+ output: outputPath
5756
5937
  }
5757
5938
  }
5758
5939
  };
5759
5940
  } catch (error) {
5760
5941
  const failure = classifyReadSeekFailure(error);
5761
- return buildToolErrorResult("view", failure.code, failure.message, failure.hint ? { hint: failure.hint } : {});
5942
+ return buildToolErrorResult("extract", failure.code, failure.message, failure.hint ? { hint: failure.hint } : {});
5762
5943
  }
5763
5944
  }
5764
- function registerViewTool(pi) {
5945
+ function registerExtractTool(pi, options = {}) {
5765
5946
  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({
5947
+ name: "readSeek_extract",
5948
+ label: "Extract",
5949
+ ...EXTRACT_PROMPT_METADATA,
5950
+ parameters: Type12.Object({
5772
5951
  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 }))
5952
+ selector: Type12.String({ pattern: "^asset:.+", description: PARAM_DESCRIPTIONS.assetSelector }),
5953
+ output: Type12.String({ minLength: 1, pattern: "^(?!-$).+", description: PARAM_DESCRIPTIONS.output })
5782
5954
  }),
5783
5955
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
5784
- return executeView({ params, signal, cwd: ctx.cwd });
5956
+ return executeExtract({
5957
+ params,
5958
+ signal,
5959
+ cwd: ctx.cwd,
5960
+ onFileMutated: options.onFileMutated
5961
+ });
5785
5962
  },
5786
5963
  renderCall(args, theme, ...rest) {
5787
5964
  const context = rest[0] ?? {};
5788
5965
  const cwd = context.cwd ?? process.cwd();
5789
5966
  const displayPath = typeof args?.path === "string" ? args.path : "?";
5790
- const text = `${theme.fg("toolTitle", theme.bold("view"))} ${linkToolPath(theme.fg("accent", displayPath), displayPath, cwd)}`;
5967
+ const output = typeof args?.output === "string" ? args.output : "?";
5968
+ let text = `${theme.fg("toolTitle", theme.bold("extract"))} ${linkToolPath(theme.fg("accent", displayPath), displayPath, cwd)}`;
5969
+ if (typeof args?.selector === "string")
5970
+ text += theme.fg("dim", ` ${args.selector}`);
5971
+ text += theme.fg("dim", ` → ${output}`);
5791
5972
  return new Text8(clampLineToWidth(text, context.width), 0, 0);
5792
5973
  },
5793
5974
  renderResult(result, options, theme, ...rest) {
5794
- const { isPartial, isError, expanded, width } = resolveRenderResultContext(options, rest);
5975
+ const { isPartial, isError, width } = resolveRenderResultContext(options, rest);
5795
5976
  if (isPartial)
5796
- return renderPendingResult("pending view", width, theme);
5977
+ return renderPendingResult("pending extract", width, theme);
5797
5978
  const textContent = result.content?.[0]?.type === "text" ? result.content[0].text : "";
5798
5979
  if (isError || result.isError) {
5799
- return renderErrorResult(textContent, { expanded, width, fallback: "view failed", theme });
5980
+ return renderErrorResult(textContent, { expanded: false, width, fallback: "extract failed", theme });
5800
5981
  }
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);
5982
+ return new Text8(clampLineToWidth(summaryLine(textContent || "asset extracted", { theme, style: "success" }), width), 0, 0);
5814
5983
  }
5815
5984
  });
5816
5985
  }
@@ -5841,32 +6010,20 @@ var READSEEK_TOOL_ENTRIES = [
5841
6010
  { builtIn: null, readSeekName: "readSeek_rename" },
5842
6011
  { builtIn: "write", readSeekName: "readSeek_write" },
5843
6012
  { builtIn: null, readSeekName: "readSeek_def" },
5844
- { builtIn: "read", readSeekName: "readSeek_digest" },
5845
- { builtIn: null, readSeekName: "readSeek_view" }
6013
+ { builtIn: "read", readSeekName: "readSeek_inspect" },
6014
+ { builtIn: null, readSeekName: "readSeek_extract" }
5846
6015
  ];
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
6016
  function piReadSeekExtension(pi) {
5857
6017
  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
6018
  const { settings } = resolveReadSeekJsonSettings();
5862
6019
  const replacedBuiltIns = new Set(settings.overrideTools ?? []);
5863
6020
  const swap = (builtIn, readSeekName) => replacedBuiltIns.has(builtIn) ? builtIn : readSeekName;
5864
- const readName = swap("read", "readSeek_digest");
6021
+ const readName = swap("read", "readSeek_inspect");
5865
6022
  const editName = swap("edit", "readSeek_edit");
5866
6023
  const writeName = swap("write", "readSeek_write");
5867
6024
  const grepName = swap("grep", "readSeek_grep");
5868
6025
  const toolAliases = {
5869
- readSeek_digest: readName,
6026
+ readSeek_inspect: readName,
5870
6027
  readSeek_edit: editName,
5871
6028
  readSeek_grep: grepName,
5872
6029
  readSeek_write: writeName
@@ -5875,20 +6032,33 @@ function piReadSeekExtension(pi) {
5875
6032
  const readSeekName = `readSeek_${name}`;
5876
6033
  return [name, toolAliases[readSeekName] ?? readSeekName];
5877
6034
  }));
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,
6035
+ registerEditTool(pi, {
6036
+ wasReadInSession: (absolutePath) => sessionAnchors.hasFreshAnchors(absolutePath),
6037
+ onFileMutated: (absolutePath) => sessionAnchors.forget(absolutePath),
6038
+ name: editName,
6039
+ toolAliases
6040
+ });
6041
+ registerGrepTool(pi, {
6042
+ onFileAnchored: (absolutePath) => sessionAnchors.markAnchored(absolutePath),
6043
+ name: grepName
6044
+ });
6045
+ registerSearchTool(pi, { onFileAnchored: (absolutePath) => sessionAnchors.markAnchored(absolutePath) });
6046
+ registerRefsTool(pi, { onFileAnchored: (absolutePath) => sessionAnchors.markAnchored(absolutePath) });
6047
+ registerRenameTool(pi, { onFileMutated: (absolutePath) => sessionAnchors.forget(absolutePath) });
6048
+ registerDefTool(pi, { onFileAnchored: (absolutePath) => sessionAnchors.markAnchored(absolutePath) });
6049
+ registerInspectTool(pi, {
6050
+ onFileAnchored: (absolutePath) => sessionAnchors.markAnchored(absolutePath),
5886
6051
  name: readName,
5887
6052
  toolAliases,
5888
6053
  imageMode: replacedBuiltIns.has("read") ? resolveReadSeekImageMode() : undefined
5889
6054
  });
5890
- registerViewTool(pi);
5891
- registerWriteTool(pi, { onFileAnchored: markAnchored, name: writeName });
6055
+ registerExtractTool(pi, {
6056
+ onFileMutated: (absolutePath) => sessionAnchors.forget(absolutePath)
6057
+ });
6058
+ registerWriteTool(pi, {
6059
+ onFileAnchored: (absolutePath) => sessionAnchors.markAnchored(absolutePath),
6060
+ name: writeName
6061
+ });
5892
6062
  const activeReadSeekNames = READSEEK_TOOL_ENTRIES.map((entry) => entry.builtIn && replacedBuiltIns.has(entry.builtIn) ? entry.builtIn : entry.readSeekName);
5893
6063
  const inactiveReadSeekNames = new Set(READSEEK_TOOL_ENTRIES.filter((entry) => entry.builtIn !== null && replacedBuiltIns.has(entry.builtIn)).map((entry) => entry.readSeekName));
5894
6064
  const activateReadSeekTools = () => {
@@ -5908,14 +6078,16 @@ function piReadSeekExtension(pi) {
5908
6078
  pi.on("before_agent_start", (event) => {
5909
6079
  if (!activateReadSeekTools())
5910
6080
  return;
5911
- return { systemPrompt: `${event.systemPrompt}
6081
+ return {
6082
+ systemPrompt: `${event.systemPrompt}
5912
6083
 
5913
- ${toolRoutingPolicy(policyAliases)}` };
6084
+ ${renderToolRoutingPolicy({ aliases: policyAliases, title: "ReadSeek tool policy:" })}`
6085
+ };
5914
6086
  });
5915
6087
  pi.on("session_start", (_event, ctx) => {
5916
6088
  sessionAnchors.clear();
5917
6089
  const { warnings } = resolveReadSeekJsonSettings();
5918
- const problems = warnings.map(formatSettingsWarning);
6090
+ const problems = warnings.map((warning) => `${warning.message} (${warning.source})`);
5919
6091
  const availability = readSeekBinaryAvailability();
5920
6092
  if (!availability.available) {
5921
6093
  problems.push(`readseek installation is broken: ${availability.reason}`);