pi-readseek 0.9.15 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.ts CHANGED
@@ -2,11 +2,11 @@
2
2
  import { readFile as fsReadFile } from "node:fs/promises";
3
3
  import { createPatch } from "diff";
4
4
  import { withFileMutationQueue } from "@earendil-works/pi-coding-agent";
5
- import { Type as Type2 } from "@sinclair/typebox";
5
+ import { Type as Type3 } from "@sinclair/typebox";
6
6
  import { Text as Text3 } from "@earendil-works/pi-tui";
7
7
 
8
8
  // ../readseek-api/src/contract.ts
9
- var DIGEST_FACETS = [
9
+ var INSPECT_FACETS = [
10
10
  "metadata",
11
11
  "content",
12
12
  "map",
@@ -14,20 +14,7 @@ var DIGEST_FACETS = [
14
14
  "identity"
15
15
  ];
16
16
  var VISION_MODES = ["none", "all", "ocr", "caption", "objects"];
17
- var VISION_ANALYSIS_MODES = ["all", "ocr", "caption", "objects"];
18
17
  var VISION_LEVELS = ["low", "medium", "high"];
19
- var DOCUMENT_NODE_KINDS = [
20
- "artifact",
21
- "footer",
22
- "header",
23
- "heading",
24
- "marginal_label",
25
- "page",
26
- "page_number",
27
- "paragraph",
28
- "section",
29
- "structural_section"
30
- ];
31
18
  var EDIT_VARIANTS = [
32
19
  "set_line",
33
20
  "replace_lines",
@@ -35,12 +22,13 @@ var EDIT_VARIANTS = [
35
22
  "replace",
36
23
  "replace_symbol"
37
24
  ];
25
+ var GIT_SCOPES = ["tracked", "untracked", "ignored", "all"];
38
26
  var TOOL_NAMES = [
39
- "digest",
27
+ "inspect",
40
28
  "edit",
41
29
  "write",
42
30
  "grep",
43
- "view",
31
+ "extract",
44
32
  "search",
45
33
  "def",
46
34
  "refs",
@@ -62,27 +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",
62
+ inspectDepth: "Maximum structural depth for map output; defaults to 1 in agent tools",
73
63
  offset: "One-based starting line",
74
64
  readLimit: "Maximum lines to return",
75
65
  symbolRead: "Symbol name to read",
76
66
  bundle: "Include related symbols from the same file; requires symbol",
77
67
  image: "Standalone image mode: none, all, ocr, caption, objects. Required for standalone images.",
78
- visionModeDigest: "Standalone image mode: none, all, caption, objects, or ocr",
79
- visionModeView: "Analyze selected assets: all, ocr, caption, or objects",
68
+ visionModeInspect: "Standalone image mode: none, all, caption, objects, or ocr",
80
69
  visionLevel: "Vision inference level: low (default), medium, or high",
81
- node: "Node ID to use as the view root",
82
- page: "One-based source page",
83
- kind: "Node kind filter",
84
- depth: "Maximum depth below selected roots",
85
- outline: "Return outline nodes only",
70
+ output: "Destination file for extracted binary data; stdout is not supported",
86
71
  edits: "Use set_line, replace_lines, insert_after, replace_symbol, or replace",
87
72
  editVariant: "Exactly one nested edit variant",
88
73
  setLine: 'Replace one line: {"set_line":{"anchor":"LINE:HASH","new_text":"..."}}',
@@ -119,11 +104,11 @@ var PARAM_DESCRIPTIONS = {
119
104
  scopeContext: "Context lines within each symbol"
120
105
  };
121
106
  var TOOL_DESCRIPTIONS = {
122
- 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 for structural maps, diagnostics, and cursor identity.",
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.",
123
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":"..."}}`.',
124
109
  write: "Create or replace a complete text file and return `LINE:HASH` anchors. Use `edit` for small changes to an existing file.",
125
110
  grep: "Search text or regex in files and return edit-ready `LINE:HASH` anchors. Use for identifiers, strings, configuration, errors, comments, and documentation.",
126
- 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.",
127
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.",
128
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.",
129
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.",
@@ -135,18 +120,11 @@ function renderToolDescription(name, aliases = {}) {
135
120
  throw new Error(`Missing TOOL_DESCRIPTIONS for ${name}`);
136
121
  return TOOL_NAMES.reduce((description, toolName) => description.replaceAll(`\`${toolName}\``, `\`${aliases[toolName] ?? toolName}\``), desc);
137
122
  }
138
- var gitFlagsSchema = {
139
- cached: {
140
- type: "boolean",
141
- description: PARAM_DESCRIPTIONS.cached
142
- },
143
- others: {
144
- type: "boolean",
145
- description: PARAM_DESCRIPTIONS.others
146
- },
147
- ignored: {
148
- type: "boolean",
149
- description: PARAM_DESCRIPTIONS.ignored
123
+ var gitScopeSchema = {
124
+ gitScope: {
125
+ type: "string",
126
+ enum: GIT_SCOPES,
127
+ description: PARAM_DESCRIPTIONS.gitScope
150
128
  }
151
129
  };
152
130
  var editItemSchema = {
@@ -212,28 +190,31 @@ var editItemSchema = {
212
190
  };
213
191
  var TOOL_CONTRACTS = [
214
192
  {
215
- name: "digest",
216
- title: "Digest source",
217
- description: TOOL_DESCRIPTIONS.digest,
193
+ name: "inspect",
194
+ title: "Inspect input",
195
+ description: TOOL_DESCRIPTIONS.inspect,
218
196
  inputSchema: {
219
197
  type: "object",
220
198
  properties: {
221
199
  path: { type: "string", description: PARAM_DESCRIPTIONS.path },
200
+ selector: { type: "string", description: PARAM_DESCRIPTIONS.selector },
222
201
  select: { type: "string", description: PARAM_DESCRIPTIONS.select },
223
- at: { type: "string", description: PARAM_DESCRIPTIONS.at },
202
+ query: { type: "string", description: PARAM_DESCRIPTIONS.query },
224
203
  end: { type: "integer", minimum: 1, description: PARAM_DESCRIPTIONS.end },
225
204
  limit: { type: "integer", minimum: 1, description: PARAM_DESCRIPTIONS.limit },
205
+ depth: { type: "integer", minimum: 0, description: PARAM_DESCRIPTIONS.inspectDepth },
226
206
  language: { type: "string", description: PARAM_DESCRIPTIONS.languageOverride },
227
207
  visionMode: {
228
208
  type: "string",
229
209
  enum: [...VISION_MODES],
230
- description: PARAM_DESCRIPTIONS.visionModeDigest
210
+ description: PARAM_DESCRIPTIONS.visionModeInspect
231
211
  },
232
212
  visionLevel: {
233
213
  type: "string",
234
214
  enum: [...VISION_LEVELS],
235
215
  description: PARAM_DESCRIPTIONS.visionLevel
236
- }
216
+ },
217
+ stdinName: { type: "string", description: PARAM_DESCRIPTIONS.stdinName }
237
218
  },
238
219
  required: ["path"],
239
220
  additionalProperties: false
@@ -307,34 +288,27 @@ var TOOL_CONTRACTS = [
307
288
  }
308
289
  },
309
290
  {
310
- name: "view",
311
- title: "View document",
312
- description: TOOL_DESCRIPTIONS.view,
291
+ name: "extract",
292
+ title: "Extract asset",
293
+ description: TOOL_DESCRIPTIONS.extract,
313
294
  inputSchema: {
314
295
  type: "object",
315
296
  properties: {
316
297
  path: { type: "string", description: PARAM_DESCRIPTIONS.path },
317
- node: { type: "string", description: PARAM_DESCRIPTIONS.node },
318
- page: { type: "integer", minimum: 1, description: PARAM_DESCRIPTIONS.page },
319
- kind: {
298
+ selector: {
320
299
  type: "string",
321
- enum: [...DOCUMENT_NODE_KINDS],
322
- description: PARAM_DESCRIPTIONS.kind
300
+ pattern: "^asset:.+",
301
+ description: PARAM_DESCRIPTIONS.assetSelector
323
302
  },
324
- depth: { type: "integer", minimum: 0, description: PARAM_DESCRIPTIONS.depth },
325
- outline: { type: "boolean", description: PARAM_DESCRIPTIONS.outline },
326
- visionMode: {
303
+ output: {
327
304
  type: "string",
328
- enum: [...VISION_ANALYSIS_MODES],
329
- description: PARAM_DESCRIPTIONS.visionModeView
305
+ minLength: 1,
306
+ pattern: "^(?!-$).+",
307
+ description: PARAM_DESCRIPTIONS.output
330
308
  },
331
- visionLevel: {
332
- type: "string",
333
- enum: [...VISION_LEVELS],
334
- description: PARAM_DESCRIPTIONS.visionLevel
335
- }
309
+ stdinName: { type: "string", description: PARAM_DESCRIPTIONS.stdinName }
336
310
  },
337
- required: ["path"],
311
+ required: ["path", "selector", "output"],
338
312
  additionalProperties: false
339
313
  }
340
314
  },
@@ -348,7 +322,8 @@ var TOOL_CONTRACTS = [
348
322
  pattern: { type: "string", description: PARAM_DESCRIPTIONS.pattern },
349
323
  path: { type: "string", description: PARAM_DESCRIPTIONS.searchPath },
350
324
  language: { type: "string", description: PARAM_DESCRIPTIONS.language },
351
- ...gitFlagsSchema
325
+ limit: { type: "integer", minimum: 1, description: PARAM_DESCRIPTIONS.matchLimit },
326
+ ...gitScopeSchema
352
327
  },
353
328
  required: ["pattern"],
354
329
  additionalProperties: false
@@ -364,7 +339,7 @@ var TOOL_CONTRACTS = [
364
339
  name: { type: "string", description: PARAM_DESCRIPTIONS.name },
365
340
  path: { type: "string", description: PARAM_DESCRIPTIONS.searchPath },
366
341
  language: { type: "string", description: PARAM_DESCRIPTIONS.language },
367
- ...gitFlagsSchema
342
+ ...gitScopeSchema
368
343
  },
369
344
  required: ["name"],
370
345
  additionalProperties: false
@@ -383,7 +358,7 @@ var TOOL_CONTRACTS = [
383
358
  scope: { type: "boolean", description: PARAM_DESCRIPTIONS.scope },
384
359
  line: { type: "integer", minimum: 1, description: PARAM_DESCRIPTIONS.line },
385
360
  column: { type: "integer", minimum: 1, description: PARAM_DESCRIPTIONS.column },
386
- ...gitFlagsSchema
361
+ ...gitScopeSchema
387
362
  },
388
363
  additionalProperties: false
389
364
  }
@@ -401,7 +376,9 @@ var TOOL_CONTRACTS = [
401
376
  to: { type: "string", description: PARAM_DESCRIPTIONS.to },
402
377
  workspace: { type: "boolean", description: PARAM_DESCRIPTIONS.workspace },
403
378
  apply: { type: "boolean", description: PARAM_DESCRIPTIONS.apply },
404
- 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
405
382
  },
406
383
  required: ["path", "line", "to"],
407
384
  additionalProperties: false
@@ -411,7 +388,7 @@ var TOOL_CONTRACTS = [
411
388
  // ../readseek-api/src/policy.ts
412
389
  var REPLACEABLE_TOOLS = [
413
390
  {
414
- tool: "digest",
391
+ tool: "inspect",
415
392
  builtIn: "read",
416
393
  benefit: "it returns LINE:HASH anchors for safe edits."
417
394
  },
@@ -432,8 +409,8 @@ var REPLACEABLE_TOOLS = [
432
409
  }
433
410
  ];
434
411
  var TOOL_COMPACT_GUIDELINES = {
435
- digest: [
436
- "Use `digest` for map, diagnostics, and identity facets instead of separate tools."
412
+ inspect: [
413
+ "Use `inspect` for shallow maps, diagnostics, and identity; use `def`, `refs`, or `search` for targeted symbol questions."
437
414
  ],
438
415
  edit: [
439
416
  "Prefer set_line, replace_lines, and insert_after; use replace only when anchors are impractical."
@@ -462,7 +439,7 @@ function renderCompactGuidelines(name, aliases = {}) {
462
439
  }
463
440
  function renderToolRoutingPolicy(options = {}) {
464
441
  const aliases = options.aliases ?? {};
465
- const digest = resolveToolName("digest", aliases);
442
+ const inspect = resolveToolName("inspect", aliases);
466
443
  const edit = resolveToolName("edit", aliases);
467
444
  const write = resolveToolName("write", aliases);
468
445
  const grep = resolveToolName("grep", aliases);
@@ -470,11 +447,11 @@ function renderToolRoutingPolicy(options = {}) {
470
447
  const def = resolveToolName("def", aliases);
471
448
  const refs = resolveToolName("refs", aliases);
472
449
  const rename = resolveToolName("rename", aliases);
473
- const view = resolveToolName("view", aliases);
450
+ const extract = resolveToolName("extract", aliases);
474
451
  if (options.style === "server") {
475
452
  return [
476
- "Structural code navigation, document viewing, and hash-verified edits with readseek.",
477
- "For structural questions, prefer these tools to text Grep/Bash: definitions, references, AST search, multi-facet digest (content/map/diagnostics/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.",
478
455
  `Use ${edit} for LINE:HASH-anchored mutations (preview with apply=false; apply defaults to true and re-checks plan_hash).`,
479
456
  "Paths resolve against CLAUDE_PROJECT_DIR. Native Read/Grep/Edit hooks provide passive context;",
480
457
  "call MCP tools for active navigation, document questions, or anchored edits."
@@ -485,7 +462,7 @@ function renderToolRoutingPolicy(options = {}) {
485
462
  if (options.preferOverBuiltIns) {
486
463
  bullets.push("- Prefer ReadSeek tools over built-ins when they can do the job.");
487
464
  }
488
- bullets.push(`- Read with ${digest} first; ${edit} needs fresh LINE:HASH anchors.`, `- Use ${grep} for text/regex, ${search} for AST patterns, and ${digest}/${def}/${refs} for symbol navigation.`, `- 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.`);
489
466
  if (options.demoteBuiltIns && options.demoteBuiltIns.length > 0) {
490
467
  bullets.push(`- Do not use built-in ${options.demoteBuiltIns.join(", ")} when a ReadSeek tool can do the job.`);
491
468
  }
@@ -512,12 +489,6 @@ function tryParseLineAnchor(anchor) {
512
489
  }
513
490
 
514
491
  // ../readseek-api/src/validation.ts
515
- function validateGitSelection(input) {
516
- if (input.ignored && !input.others) {
517
- return { ok: false, error: "ignored requires others=true" };
518
- }
519
- return { ok: true };
520
- }
521
492
  function validateRefsNameVsScope(input) {
522
493
  if (input.scope) {
523
494
  if (input.line === undefined) {
@@ -534,11 +505,8 @@ function validateRefsNameVsScope(input) {
534
505
  return { ok: true };
535
506
  }
536
507
  function validateVisionLevelVsMode(input) {
537
- if (input.outline && (input.visionMode !== undefined || input.visionLevel !== undefined)) {
538
- return { ok: false, error: "Cannot combine outline with visionMode or visionLevel" };
539
- }
540
- if (input.visionLevel !== undefined && input.visionMode === undefined) {
541
- 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" };
542
510
  }
543
511
  return { ok: true };
544
512
  }
@@ -609,21 +577,54 @@ function validateExactlyOneEditVariant(edits) {
609
577
  }
610
578
  var MIN_SAFE_INTEGER = BigInt(Number.MIN_SAFE_INTEGER);
611
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
+ }
612
620
  // ../readseek-api/src/argv.ts
621
+ var DEFAULT_INSPECT_MAP_DEPTH = 1;
613
622
  function buildLineLocation(line, column) {
614
623
  return column === undefined ? `line:${line}` : `line:${line}:${column}`;
615
624
  }
616
- function appendGitSelectionArgs(args, input) {
617
- const validated = validateGitSelection(input);
618
- if (!validated.ok) {
619
- throw new Error(validated.error);
620
- }
621
- if (input.cached)
622
- args.push("--cached");
623
- if (input.others)
624
- args.push("--others");
625
- if (input.ignored)
626
- args.push("--ignored");
625
+ function appendGitScopeArgs(args, input) {
626
+ if (input.gitScope !== undefined)
627
+ args.push("--git-scope", input.gitScope);
627
628
  return args;
628
629
  }
629
630
  function pushOptionalString(args, flag, value) {
@@ -652,51 +653,163 @@ function selectArg(select) {
652
653
  return;
653
654
  return Array.isArray(select) ? select.join(",") : select;
654
655
  }
655
- function buildDigestArgs(input) {
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 };
719
+ }
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
+ }
656
725
  const vision = validateVisionLevelVsMode({
657
726
  visionMode: input.visionMode,
658
727
  visionLevel: input.visionLevel
659
728
  });
660
729
  if (!vision.ok)
661
730
  throw new Error(vision.error);
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
+ }
743
+ if (input.depth !== undefined) {
744
+ if (!Number.isSafeInteger(input.depth) || input.depth < 0) {
745
+ throw new Error("depth must be a non-negative integer");
746
+ }
747
+ if (!hasMap)
748
+ throw new Error("depth requires the map facet");
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
+ }
662
778
  const args = [];
663
779
  if (input.readseekDir !== undefined)
664
780
  args.push("--readseek-dir", input.readseekDir);
665
- args.push("digest", input.path);
781
+ args.push("inspect", input.path);
782
+ if (input.selector !== undefined)
783
+ args.push(input.selector);
666
784
  const select = selectArg(input.select);
667
785
  if (select !== undefined)
668
786
  args.push("--select", select);
669
- pushOptionalString(args, "--at", input.at);
787
+ pushOptionalString(args, "--query", input.query);
670
788
  pushOptionalNumber(args, "--end", input.end);
671
789
  pushOptionalNumber(args, "--limit", input.limit);
790
+ pushOptionalNumber(args, "--depth", input.depth);
672
791
  pushOptionalString(args, "--language", input.language);
673
792
  pushOptionalString(args, "--vision-mode", input.visionMode);
674
793
  pushOptionalString(args, "--vision-level", input.visionLevel);
675
794
  pushOptionalString(args, "--stdin-name", input.stdinName);
795
+ args.push("--kdl");
676
796
  return args;
677
797
  }
678
- function buildViewArgs(input) {
679
- const vision = validateVisionLevelVsMode({
680
- visionMode: input.visionMode,
681
- visionLevel: input.visionLevel,
682
- outline: input.outline
683
- });
684
- if (!vision.ok)
685
- 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
+ }
686
808
  const args = [];
687
809
  if (input.readseekDir !== undefined)
688
810
  args.push("--readseek-dir", input.readseekDir);
689
- args.push("view", input.path);
690
- if (input.format !== undefined)
691
- args.push("--format", input.format);
692
- pushOptionalString(args, "--node", input.node);
693
- pushOptionalNumber(args, "--page", input.page);
694
- pushOptionalString(args, "--kind", input.kind);
695
- pushOptionalNumber(args, "--depth", input.depth);
696
- if (input.outline)
697
- args.push("--outline");
698
- pushOptionalString(args, "--vision-mode", input.visionMode);
699
- pushOptionalString(args, "--vision-level", input.visionLevel);
811
+ args.push("--output", input.output, "extract", input.path, input.selector);
812
+ pushOptionalString(args, "--stdin-name", input.stdinName);
700
813
  return args;
701
814
  }
702
815
  function buildSearchArgs(input) {
@@ -704,15 +817,17 @@ function buildSearchArgs(input) {
704
817
  pushOptionalString(args, "--path", input.path);
705
818
  pushOptionalString(args, "--language", input.language);
706
819
  pushOptionalNumber(args, "--limit", input.limit);
707
- return appendGitSelectionArgs(args, input);
820
+ appendGitScopeArgs(args, input);
821
+ args.push("--kdl");
822
+ return args;
708
823
  }
709
824
  function buildDefArgs(input) {
710
825
  const args = ["def", input.name];
711
826
  pushOptionalString(args, "--path", input.path);
712
- if (input.format !== undefined)
713
- args.push("--format", input.format);
714
827
  pushOptionalString(args, "--language", input.language);
715
- return appendGitSelectionArgs(args, input);
828
+ appendGitScopeArgs(args, input);
829
+ args.push("--kdl");
830
+ return args;
716
831
  }
717
832
  function buildRefsArgs(input) {
718
833
  const validated = validateRefsNameVsScope(input);
@@ -727,10 +842,10 @@ function buildRefsArgs(input) {
727
842
  pushOptionalNumber(args, "--line", input.line);
728
843
  pushOptionalNumber(args, "--column", input.column);
729
844
  }
730
- if (input.format !== undefined)
731
- args.push("--format", input.format);
732
845
  pushOptionalString(args, "--language", input.language);
733
- return appendGitSelectionArgs(args, input);
846
+ appendGitScopeArgs(args, input);
847
+ args.push("--kdl");
848
+ return args;
734
849
  }
735
850
  function buildEditArgs(input) {
736
851
  const args = ["edit", input.path];
@@ -742,6 +857,7 @@ function buildEditArgs(input) {
742
857
  pushOptionalString(args, "--language", input.language);
743
858
  if (input.requestFromStdin)
744
859
  args.push("--edits", "-");
860
+ args.push("--kdl");
745
861
  return args;
746
862
  }
747
863
  function buildRenameArgs(input) {
@@ -754,7 +870,9 @@ function buildRenameArgs(input) {
754
870
  flagOrder: input.flagOrder ?? "plan-hash-first"
755
871
  });
756
872
  pushOptionalString(args, "--language", input.language);
757
- return appendGitSelectionArgs(args, input);
873
+ appendGitScopeArgs(args, input);
874
+ args.push("--kdl");
875
+ return args;
758
876
  }
759
877
  // ../readseek-api/src/platform.ts
760
878
  import { accessSync, constants as fsConstants, existsSync, statSync } from "node:fs";
@@ -769,6 +887,8 @@ var READSEEK_PLATFORM_PACKAGES = {
769
887
  };
770
888
  var resolveFromPackage = createRequire(import.meta.url).resolve;
771
889
  function readSeekBinaryName(platform = process.platform) {
890
+ if (platform === "linux")
891
+ return "readseek.AppImage";
772
892
  return platform === "win32" ? "readseek.exe" : "readseek";
773
893
  }
774
894
  function readSeekPlatformKey(platform = process.platform, arch = process.arch) {
@@ -800,9 +920,9 @@ function resolveReadSeekBinaryFromPackages(resolver = resolveFromPackage, option
800
920
  const platformPackage = READSEEK_PLATFORM_PACKAGES[key];
801
921
  if (!platformPackage) {
802
922
  const supported = Object.keys(READSEEK_PLATFORM_PACKAGES).join(", ");
803
- throw new Error(`@jarkkojs/readseek ships no binary for ${key}; it supports ${supported}`);
923
+ throw new Error(`@jarkkojs/readseek-api ships no binary for ${key}; it supports ${supported}`);
804
924
  }
805
- const metaPackage = options.metaPackage ?? "@jarkkojs/readseek";
925
+ const metaPackage = options.metaPackage ?? "@jarkkojs/readseek-api";
806
926
  const metaPackageJson = resolve(`${metaPackage}/package.json`);
807
927
  const metaPackageDir = path.dirname(metaPackageJson);
808
928
  let packageJsonPath;
@@ -817,22 +937,304 @@ function resolveReadSeekBinaryFromPackages(resolver = resolveFromPackage, option
817
937
  accessSync(binaryPath, fsConstants.X_OK);
818
938
  return binaryPath;
819
939
  }
820
- // ../readseek-api/src/json.ts
821
- 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*$/;
822
942
  function cleanReadSeekErrorText(stderr) {
823
943
  return stderr.replace(READSEEK_USAGE_HINT, "").replace(/^error:\s*/i, "").replace(/\s*\n\s*/g, " ").trim();
824
944
  }
825
- function parseReadSeekJson(stdout) {
826
- try {
827
- return JSON.parse(stdout);
828
- } catch {
829
- const preview = stdout.trim().slice(0, 200);
830
- 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}`);
831
1230
  }
832
1231
  }
833
1232
  // ../readseek-api/src/process.ts
834
1233
  var DEFAULT_READSEEK_TIMEOUT_MS = 120000;
835
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
+ }
836
1238
  function resolveProcessLimits(options = {}) {
837
1239
  return {
838
1240
  timeoutMs: options.timeoutMs ?? DEFAULT_READSEEK_TIMEOUT_MS,
@@ -863,9 +1265,6 @@ function ensureSuccessfulStdout(result) {
863
1265
  return result.stdout;
864
1266
  throw failureFromProcessResult(result);
865
1267
  }
866
- function parseSuccessfulJson(result) {
867
- return parseReadSeekJson(ensureSuccessfulStdout(result));
868
- }
869
1268
  // ../readseek-api/src/process-node.ts
870
1269
  import { spawn } from "node:child_process";
871
1270
  import { StringDecoder } from "node:string_decoder";
@@ -931,7 +1330,7 @@ async function runReadSeekNode(binaryPath, args, options = {}) {
931
1330
  try {
932
1331
  child = spawn(binaryPath, [...args], {
933
1332
  cwd: options.cwd,
934
- env: process.env,
1333
+ env: readSeekProcessEnvironment(),
935
1334
  shell: false,
936
1335
  stdio: [options.stdin === undefined ? "ignore" : "pipe", "pipe", "pipe"],
937
1336
  windowsHide: true
@@ -974,346 +1373,357 @@ async function runReadSeekNode(binaryPath, args, options = {}) {
974
1373
  async function runReadSeekNodeText(binaryPath, args, options = {}) {
975
1374
  return ensureSuccessfulStdout(await runReadSeekNode(binaryPath, args, options));
976
1375
  }
977
- async function runReadSeekNodeJson(binaryPath, args, options = {}) {
978
- return parseSuccessfulJson(await runReadSeekNode(binaryPath, args, options));
979
- }
980
- // ../readseek-api/src/client.ts
981
- function classifyReadSeekFailure(err) {
982
- const failure = err;
983
- const message = String(failure?.message ?? err);
984
- const missing = failure?.code === "ENOENT" || /Cannot find package|Cannot find module|no such file/i.test(message);
985
- if (missing) {
986
- return { code: "readseek-not-installed", message, hint: "Run npm install to install @jarkkojs/readseek." };
987
- }
988
- return { code: "readseek-execution-error", message };
989
- }
990
- function requireNumber(value, field) {
991
- if (typeof value !== "number" || !Number.isSafeInteger(value))
992
- throw new Error(`invalid readseek ${field}: expected safe integer`);
993
- return value;
994
- }
995
- function requireString(value, field) {
996
- if (typeof value !== "string")
997
- throw new Error(`invalid readseek ${field}`);
998
- return value;
999
- }
1000
- function requireBoolean(value, field) {
1001
- if (typeof value !== "boolean")
1002
- throw new Error(`invalid readseek ${field}: expected boolean`);
1003
- return value;
1004
- }
1005
- function optionalString(value, field) {
1006
- if (value === undefined || value === null)
1007
- return;
1008
- return requireString(value, field);
1009
- }
1010
- function parseHashline(value, field) {
1011
- if (!value || typeof value !== "object")
1012
- throw new Error(`invalid readseek ${field}`);
1013
- const item = value;
1014
- return {
1015
- line: requireNumber(item.line, `${field}.line`),
1016
- hash: requireString(item.hash, `${field}.hash`),
1017
- text: requireString(item.text, `${field}.text`)
1018
- };
1019
- }
1020
- function parseSearchHashlines(value, field) {
1021
- if (!Array.isArray(value))
1022
- throw new Error(`invalid readseek ${field}`);
1023
- return value.map((line) => parseHashline(line, field));
1024
- }
1376
+ // ../readseek-api/src/client-output.ts
1377
+ import { Type } from "@sinclair/typebox";
1378
+ import { AssertError, Value } from "@sinclair/typebox/value";
1379
+ var OPERATIONS = ["Clone", "Clean", "Default", "Assert"];
1380
+ function parse2(schema, value, what) {
1381
+ try {
1382
+ return Value.Parse(OPERATIONS, schema, value);
1383
+ } catch (error) {
1384
+ if (!(error instanceof AssertError))
1385
+ throw error;
1386
+ const detail = error.error;
1387
+ throw new Error(`invalid readseek ${what}${detail?.path ?? ""}: ${detail?.message ?? "invalid value"}`);
1388
+ }
1389
+ }
1390
+ var Int = Type.Integer({ minimum: Number.MIN_SAFE_INTEGER, maximum: Number.MAX_SAFE_INTEGER });
1391
+ var Str = Type.String();
1392
+ var Bool = Type.Boolean();
1393
+ var OptStr = Type.Optional(Str);
1394
+ var OptInt = Type.Optional(Int);
1395
+ var Hashline = Type.Object({ line: Int, hash: Str, text: Str });
1396
+ var Hashlines = Type.Array(Hashline);
1397
+ var ReadOutput = Type.Object({
1398
+ file: Str,
1399
+ language: Str,
1400
+ line_count: Int,
1401
+ file_hash: Str,
1402
+ start_line: Int,
1403
+ end_line: Int,
1404
+ hashlines: Hashlines
1405
+ });
1406
+ var SymbolOutput = Type.Object({
1407
+ kind: Str,
1408
+ name: Str,
1409
+ qualified_name: OptStr,
1410
+ start_line: Int,
1411
+ end_line: Int,
1412
+ start_hash: OptStr,
1413
+ end_hash: OptStr,
1414
+ start_anchor: OptStr,
1415
+ end_anchor: OptStr
1416
+ });
1417
+ var MapNode = Type.Recursive((Self) => Type.Object({
1418
+ kind: Str,
1419
+ name: Str,
1420
+ start_line: Int,
1421
+ end_line: Int,
1422
+ start_anchor: Str,
1423
+ end_anchor: Str,
1424
+ children: Type.Array(Self)
1425
+ }));
1426
+ var MapOutput = Type.Object({
1427
+ file: Str,
1428
+ language: Str,
1429
+ line_count: Int,
1430
+ file_hash: Str,
1431
+ root: Type.Optional(MapNode),
1432
+ symbols: Type.Array(SymbolOutput)
1433
+ });
1434
+ var SearchSpan = {
1435
+ start_byte: OptInt,
1436
+ end_byte: OptInt,
1437
+ text: OptStr,
1438
+ start_line: Int,
1439
+ end_line: Int,
1440
+ start_hash: Str,
1441
+ end_hash: Str,
1442
+ hashlines: Hashlines
1443
+ };
1444
+ var SearchCapture = Type.Object({ name: Str, ...SearchSpan });
1445
+ var SearchMatch = Type.Object({
1446
+ pattern_index: Type.Integer({ default: 0 }),
1447
+ ...SearchSpan,
1448
+ captures: Type.Array(SearchCapture)
1449
+ });
1450
+ var SearchFileOutput = Type.Object({
1451
+ file: Str,
1452
+ language: Str,
1453
+ file_hash: Str,
1454
+ matches: Type.Array(SearchMatch)
1455
+ });
1456
+ var SearchEnvelope = Type.Object({
1457
+ results: Type.Array(SearchFileOutput),
1458
+ returned_matches: OptInt,
1459
+ truncated: Type.Optional(Bool),
1460
+ byte_basis: OptStr
1461
+ });
1462
+ var RefsEnvelope = Type.Object({
1463
+ references: Type.Array(Type.Object({
1464
+ file: Str,
1465
+ line: Int,
1466
+ column: Int,
1467
+ line_hash: Str,
1468
+ text: Str,
1469
+ symbol: Type.Optional(Type.Object({ qualified_name: OptStr }))
1470
+ }))
1471
+ });
1472
+ var Diagnostic = Type.Object({
1473
+ kind: Type.Union([Type.Literal("error"), Type.Literal("missing")]),
1474
+ start_line: Int,
1475
+ end_line: Int
1476
+ });
1477
+ var CheckEnvelope = Type.Object({
1478
+ file_hash: OptStr,
1479
+ error_count: Int,
1480
+ missing_count: Int,
1481
+ diagnostics: Type.Array(Diagnostic)
1482
+ });
1483
+ var DetectedObject = Type.Object({
1484
+ label: Str,
1485
+ bbox: Type.Tuple([Int, Int, Int, Int])
1486
+ });
1487
+ var PreparedImage = Type.Object({
1488
+ mime: Str,
1489
+ encoding: Type.Literal("base64"),
1490
+ data: Str
1491
+ });
1492
+ var SourceDetection = Type.Object({
1493
+ kind: Type.Literal("source"),
1494
+ type: Str,
1495
+ file: Str,
1496
+ language: Str,
1497
+ engine: OptStr,
1498
+ supported: Bool,
1499
+ mime: OptStr,
1500
+ syntax: OptStr
1501
+ });
1502
+ var ImageDetection = Type.Object({
1503
+ kind: Type.Literal("image"),
1504
+ type: Str,
1505
+ file: Str,
1506
+ mime: OptStr,
1507
+ format: Str,
1508
+ width: Int,
1509
+ height: Int,
1510
+ animated: Bool,
1511
+ encoding: Type.Optional(Type.Literal("base64")),
1512
+ data: OptStr,
1513
+ ocr: OptStr,
1514
+ caption: OptStr,
1515
+ objects: Type.Optional(Type.Array(DetectedObject))
1516
+ });
1517
+ var TextDetection = Type.Object({
1518
+ kind: Type.Literal("text"),
1519
+ type: Str,
1520
+ file: Str,
1521
+ mime: OptStr
1522
+ });
1523
+ var PdfDetection = Type.Object({
1524
+ kind: Type.Literal("pdf"),
1525
+ type: Type.Literal("application/pdf"),
1526
+ file: Str,
1527
+ mime: OptStr,
1528
+ format: Type.Literal("pdf"),
1529
+ pages: Int
1530
+ });
1531
+ var Detection = Type.Union([SourceDetection, ImageDetection, TextDetection, PdfDetection]);
1532
+ var SourceDetectionInput = Type.Omit(SourceDetection, ["kind"]);
1533
+ var ImageDetectionInput = Type.Omit(ImageDetection, ["kind"]);
1534
+ var TextDetectionInput = Type.Omit(TextDetection, ["kind"]);
1535
+ var PdfDetectionInput = Type.Omit(PdfDetection, ["kind"]);
1536
+ var UnspannedContent = Type.Omit(ReadOutput, ["start_line", "end_line"]);
1537
+ var PdfInspectMetadata = Type.Object({ source: Str, format: Type.Literal("pdf"), pages: Int });
1538
+ var DocumentCache = Type.Object({
1539
+ content_hash: Str,
1540
+ state: Type.Union([Type.Literal("built"), Type.Literal("reused")])
1541
+ });
1542
+ var Identifier = Type.Object({
1543
+ text: Str,
1544
+ start_column: Int,
1545
+ end_column: Int,
1546
+ start_byte: Int,
1547
+ end_byte: Int
1548
+ });
1549
+ var IdentifyEnvelope = Type.Object({
1550
+ file: Str,
1551
+ language: Str,
1552
+ engine: OptStr,
1553
+ line_count: Int,
1554
+ file_hash: Str,
1555
+ line: Int,
1556
+ column: Int,
1557
+ line_hash: Str,
1558
+ identifier: Type.Optional(Identifier),
1559
+ symbol: Type.Optional(Type.Object({
1560
+ name: Str,
1561
+ kind: Str,
1562
+ qualified_name: Str,
1563
+ start_line: Int,
1564
+ end_line: Int
1565
+ }))
1566
+ });
1567
+ var EditOutputSchema = Type.Object({
1568
+ file: Str,
1569
+ before_hash: Str,
1570
+ file_hash: Str,
1571
+ plan_hash: Str,
1572
+ applied: Bool,
1573
+ changed: Bool,
1574
+ edit_count: Int,
1575
+ before_content: OptStr,
1576
+ content: OptStr
1577
+ });
1578
+ var RenameConflictSchema = Type.Object({ line: Int, column: Int, reason: Str });
1579
+ var RenameEditSchema = Type.Object({
1580
+ line: Int,
1581
+ start_column: Int,
1582
+ end_column: Int,
1583
+ start_byte: Int,
1584
+ end_byte: Int,
1585
+ occurrence: Str,
1586
+ line_hash: Str,
1587
+ text: Str
1588
+ });
1589
+ var Conflicts = Type.Array(RenameConflictSchema, { default: [] });
1590
+ var Edits = Type.Array(RenameEditSchema, { default: [] });
1591
+ var RenameFileOutputSchema = Type.Object({
1592
+ file: Str,
1593
+ language: Str,
1594
+ engine: OptStr,
1595
+ file_hash: Str,
1596
+ conflicts: Conflicts,
1597
+ edits: Edits
1598
+ });
1599
+ var RenameOutputSchema = Type.Object({
1600
+ file: Str,
1601
+ language: Str,
1602
+ engine: OptStr,
1603
+ file_hash: Str,
1604
+ old_name: Str,
1605
+ new_name: Str,
1606
+ plan_hash: Str,
1607
+ applied: Bool,
1608
+ conflicts: Conflicts,
1609
+ edits: Edits,
1610
+ others: Type.Array(RenameFileOutputSchema, { default: [] })
1611
+ });
1612
+ var DefEnvelope = Type.Object({
1613
+ locations: Type.Array(Type.Object({
1614
+ file: Str,
1615
+ line: Int,
1616
+ column: Int,
1617
+ line_hash: Str,
1618
+ text: Str,
1619
+ kind: OptStr,
1620
+ name: OptStr,
1621
+ qualified_name: OptStr
1622
+ }))
1623
+ });
1025
1624
  function parseReadOutput(value) {
1026
- if (!value || typeof value !== "object")
1027
- throw new Error("invalid readseek read output");
1028
- const output = value;
1029
- const hashlines = output.hashlines;
1030
- if (!Array.isArray(hashlines))
1031
- throw new Error("invalid readseek hashlines");
1032
- return {
1033
- file: requireString(output.file, "file"),
1034
- language: requireString(output.language, "language"),
1035
- line_count: requireNumber(output.line_count, "line_count"),
1036
- file_hash: requireString(output.file_hash, "file_hash"),
1037
- start_line: requireNumber(output.start_line, "start_line"),
1038
- end_line: requireNumber(output.end_line, "end_line"),
1039
- hashlines: hashlines.map((line) => parseHashline(line, "hashline"))
1040
- };
1625
+ return parse2(ReadOutput, value, "read output");
1041
1626
  }
1042
1627
  function parseMapOutput(value) {
1043
- if (!value || typeof value !== "object")
1044
- throw new Error("invalid readseek map output");
1045
- const output = value;
1046
- const symbols = output.symbols;
1047
- if (!Array.isArray(symbols))
1048
- throw new Error("invalid readseek symbols");
1049
- return {
1050
- file: requireString(output.file, "file"),
1051
- language: requireString(output.language, "language"),
1052
- line_count: requireNumber(output.line_count, "line_count"),
1053
- file_hash: requireString(output.file_hash, "file_hash"),
1054
- symbols: symbols.map((symbol) => {
1055
- if (!symbol || typeof symbol !== "object")
1056
- throw new Error("invalid readseek symbol");
1057
- const item = symbol;
1058
- return {
1059
- kind: requireString(item.kind, "symbol.kind"),
1060
- name: requireString(item.name, "symbol.name"),
1061
- qualified_name: requireString(item.qualified_name, "symbol.qualified_name"),
1062
- start_line: requireNumber(item.start_line, "symbol.start_line"),
1063
- end_line: requireNumber(item.end_line, "symbol.end_line"),
1064
- start_hash: requireString(item.start_hash, "symbol.start_hash"),
1065
- end_hash: requireString(item.end_hash, "symbol.end_hash")
1066
- };
1067
- })
1068
- };
1628
+ return parse2(MapOutput, value, "map output");
1069
1629
  }
1070
1630
  function parseSearchOutput(value) {
1071
- if (!value || typeof value !== "object")
1072
- throw new Error("invalid readseek search output");
1073
- const output = value;
1074
- if (!Array.isArray(output.results))
1075
- throw new Error("invalid readseek search results");
1076
- return output.results.map((result) => {
1077
- if (!result || typeof result !== "object")
1078
- throw new Error("invalid readseek search result");
1079
- const file = result;
1080
- if (!Array.isArray(file.matches))
1081
- throw new Error("invalid readseek search matches");
1082
- return {
1083
- file: requireString(file.file, "search.file"),
1084
- language: requireString(file.language, "search.language"),
1085
- file_hash: requireString(file.file_hash, "search.file_hash"),
1086
- matches: file.matches.map((match) => {
1087
- if (!match || typeof match !== "object")
1088
- throw new Error("invalid readseek search match");
1089
- const item = match;
1090
- if (!Array.isArray(item.captures))
1091
- throw new Error("invalid readseek search captures");
1092
- return {
1093
- pattern_index: item.pattern_index === undefined ? 0 : requireNumber(item.pattern_index, "search.match.pattern_index"),
1094
- start_line: requireNumber(item.start_line, "search.match.start_line"),
1095
- end_line: requireNumber(item.end_line, "search.match.end_line"),
1096
- start_hash: requireString(item.start_hash, "search.match.start_hash"),
1097
- end_hash: requireString(item.end_hash, "search.match.end_hash"),
1098
- hashlines: parseSearchHashlines(item.hashlines, "search.match.hashlines"),
1099
- captures: item.captures.map((capture) => {
1100
- if (!capture || typeof capture !== "object")
1101
- throw new Error("invalid readseek search capture");
1102
- const captureItem = capture;
1103
- return {
1104
- name: requireString(captureItem.name, "search.capture.name"),
1105
- start_line: requireNumber(captureItem.start_line, "search.capture.start_line"),
1106
- end_line: requireNumber(captureItem.end_line, "search.capture.end_line"),
1107
- start_hash: requireString(captureItem.start_hash, "search.capture.start_hash"),
1108
- end_hash: requireString(captureItem.end_hash, "search.capture.end_hash"),
1109
- hashlines: parseSearchHashlines(captureItem.hashlines, "search.capture.hashlines")
1110
- };
1111
- })
1112
- };
1113
- })
1114
- };
1115
- });
1631
+ const envelope = parse2(SearchEnvelope, value, "search output");
1632
+ const results = envelope.results;
1633
+ if (envelope.returned_matches !== undefined)
1634
+ results.returned_matches = envelope.returned_matches;
1635
+ if (envelope.truncated !== undefined)
1636
+ results.truncated = envelope.truncated;
1637
+ if (envelope.byte_basis !== undefined)
1638
+ results.byte_basis = envelope.byte_basis;
1639
+ return results;
1116
1640
  }
1117
1641
  function parseRefsOutput(value) {
1118
- if (!value || typeof value !== "object")
1119
- throw new Error("invalid readseek refs output");
1120
- const output = value;
1121
- if (!Array.isArray(output.references))
1122
- throw new Error("invalid readseek references");
1123
- return output.references.map((reference) => {
1124
- if (!reference || typeof reference !== "object")
1125
- throw new Error("invalid readseek reference");
1126
- const item = reference;
1127
- const symbol = item.symbol;
1128
- const enclosing = symbol && typeof symbol === "object" ? optionalString(symbol.qualified_name, "reference.symbol.qualified_name") : undefined;
1129
- return {
1130
- file: requireString(item.file, "reference.file"),
1131
- line: requireNumber(item.line, "reference.line"),
1132
- column: requireNumber(item.column, "reference.column"),
1133
- line_hash: requireString(item.line_hash, "reference.line_hash"),
1134
- text: requireString(item.text, "reference.text"),
1135
- enclosingSymbol: enclosing
1136
- };
1137
- });
1138
- }
1139
- function parseDiagnosticKind(value) {
1140
- if (value === "error" || value === "missing")
1141
- return value;
1142
- throw new Error("invalid readseek diagnostic.kind");
1642
+ return parse2(RefsEnvelope, value, "refs output").references.map(({ symbol, ...reference }) => ({
1643
+ ...reference,
1644
+ enclosingSymbol: symbol?.qualified_name
1645
+ }));
1143
1646
  }
1144
1647
  function parseCheckOutput(value) {
1145
- if (!value || typeof value !== "object")
1146
- throw new Error("invalid readseek check output");
1147
- const output = value;
1148
- if (!Array.isArray(output.diagnostics))
1149
- throw new Error("invalid readseek diagnostics");
1648
+ const output = parse2(CheckEnvelope, value, "check output");
1150
1649
  return {
1151
- file_hash: optionalString(output.file_hash, "file_hash"),
1152
- errorCount: requireNumber(output.error_count, "error_count"),
1153
- missingCount: requireNumber(output.missing_count, "missing_count"),
1154
- diagnostics: output.diagnostics.map((diagnostic) => {
1155
- if (!diagnostic || typeof diagnostic !== "object")
1156
- throw new Error("invalid readseek diagnostic");
1157
- const item = diagnostic;
1158
- return {
1159
- kind: parseDiagnosticKind(item.kind),
1160
- start_line: requireNumber(item.start_line, "diagnostic.start_line"),
1161
- end_line: requireNumber(item.end_line, "diagnostic.end_line")
1162
- };
1163
- })
1650
+ file_hash: output.file_hash,
1651
+ errorCount: output.error_count,
1652
+ missingCount: output.missing_count,
1653
+ diagnostics: output.diagnostics
1164
1654
  };
1165
1655
  }
1166
- function parseDetectedObjects(value) {
1167
- if (value === undefined || value === null)
1168
- return;
1169
- if (!Array.isArray(value))
1170
- throw new Error("invalid readseek detect objects");
1171
- return value.map((object) => {
1172
- if (!object || typeof object !== "object")
1173
- throw new Error("invalid readseek detect object");
1174
- const item = object;
1175
- const bbox = item.bbox;
1176
- if (!Array.isArray(bbox) || bbox.length !== 4)
1177
- throw new Error("invalid readseek detect object.bbox");
1178
- return {
1179
- label: requireString(item.label, "object.label"),
1180
- bbox: bbox.map((n, i) => requireNumber(n, `object.bbox[${i}]`))
1181
- };
1182
- });
1183
- }
1184
- function parseImageEncoding(value) {
1185
- if (value === "base64")
1186
- return value;
1187
- throw new Error("invalid image encoding");
1188
- }
1189
1656
  function parseDetectOutput(value) {
1190
- if (!value || typeof value !== "object")
1191
- throw new Error("invalid readseek detect output");
1192
- const output = value;
1193
- const type = requireString(output.type, "type");
1194
- const file = requireString(output.file, "file");
1195
- const mime = optionalString(output.mime, "mime");
1196
- if (type === "application/pdf") {
1197
- if (output.format !== "pdf")
1198
- throw new Error("invalid readseek PDF format");
1199
- return {
1200
- kind: "pdf",
1201
- type,
1202
- file,
1203
- mime,
1204
- format: output.format,
1205
- pages: requireNumber(output.pages, "pages")
1206
- };
1657
+ const output = value ?? {};
1658
+ if (output.type === "application/pdf") {
1659
+ return { kind: "pdf", ...parse2(PdfDetectionInput, value, "detect output") };
1207
1660
  }
1208
1661
  if (output.width !== undefined || output.height !== undefined) {
1209
- return {
1210
- kind: "image",
1211
- type,
1212
- file,
1213
- mime,
1214
- format: requireString(output.format, "format"),
1215
- width: requireNumber(output.width, "width"),
1216
- height: requireNumber(output.height, "height"),
1217
- animated: requireBoolean(output.animated, "animated"),
1218
- encoding: output.encoding === undefined ? undefined : parseImageEncoding(output.encoding),
1219
- data: output.data === undefined ? undefined : requireString(output.data, "data"),
1220
- ocr: optionalString(output.ocr, "ocr"),
1221
- caption: optionalString(output.caption, "caption"),
1222
- objects: parseDetectedObjects(output.objects)
1223
- };
1662
+ return { kind: "image", ...parse2(ImageDetectionInput, value, "detect output") };
1224
1663
  }
1225
1664
  if (output.language !== undefined) {
1226
- return {
1227
- kind: "source",
1228
- type,
1229
- file,
1230
- language: requireString(output.language, "language"),
1231
- engine: optionalString(output.engine, "engine"),
1232
- supported: requireBoolean(output.supported, "supported"),
1233
- mime,
1234
- syntax: optionalString(output.syntax, "syntax")
1235
- };
1665
+ return { kind: "source", ...parse2(SourceDetectionInput, value, "detect output") };
1236
1666
  }
1237
- return { kind: "text", type, file, mime };
1238
- }
1239
- function parseDocumentCache(value) {
1240
- if (!value || typeof value !== "object")
1241
- throw new Error("invalid readseek document cache");
1242
- const cache = value;
1243
- const state = requireString(cache.state, "cache.state");
1244
- if (state !== "built" && state !== "reused")
1245
- throw new Error("invalid readseek cache.state");
1246
- return {
1247
- content_hash: requireString(cache.content_hash, "cache.content_hash"),
1248
- state
1249
- };
1250
- }
1251
- function parseIdentifyOutput(value) {
1252
- if (!value || typeof value !== "object")
1253
- throw new Error("invalid readseek identify output");
1254
- const output = value;
1255
- const identifier = output.identifier;
1256
- const symbol = output.symbol;
1257
- return {
1258
- file: requireString(output.file, "file"),
1259
- language: requireString(output.language, "language"),
1260
- engine: optionalString(output.engine, "engine"),
1261
- line_count: requireNumber(output.line_count, "line_count"),
1262
- file_hash: requireString(output.file_hash, "file_hash"),
1263
- line: requireNumber(output.line, "line"),
1264
- column: requireNumber(output.column, "column"),
1265
- line_hash: requireString(output.line_hash, "line_hash"),
1266
- identifier: identifier && typeof identifier === "object" ? {
1267
- text: requireString(identifier.text, "identifier.text"),
1268
- start_column: requireNumber(identifier.start_column, "identifier.start_column"),
1269
- end_column: requireNumber(identifier.end_column, "identifier.end_column"),
1270
- start_byte: requireNumber(identifier.start_byte, "identifier.start_byte"),
1271
- end_byte: requireNumber(identifier.end_byte, "identifier.end_byte")
1272
- } : undefined,
1273
- symbol: symbol && typeof symbol === "object" ? {
1274
- name: requireString(symbol.name, "symbol.name"),
1275
- kind: requireString(symbol.kind, "symbol.kind"),
1276
- qualified_name: requireString(symbol.qualified_name, "symbol.qualified_name"),
1277
- start_line: requireNumber(symbol.start_line, "symbol.start_line"),
1278
- end_line: requireNumber(symbol.end_line, "symbol.end_line")
1279
- } : undefined
1280
- };
1667
+ return { kind: "text", ...parse2(TextDetectionInput, value, "detect output") };
1281
1668
  }
1282
1669
  function parseImageContent(value) {
1283
1670
  const detection = parseDetectOutput(value);
1284
1671
  if (detection.kind !== "image")
1285
- throw new Error("invalid readseek image content");
1672
+ throw new Error("invalid readseek image content: expected an image");
1286
1673
  return detection;
1287
1674
  }
1288
1675
  function parseTextContent(value) {
1676
+ const output = value ?? {};
1677
+ if (output.hashlines === undefined || output.start_line !== undefined)
1678
+ return parseReadOutput(value);
1679
+ const spanned = parse2(UnspannedContent, value, "content");
1680
+ return {
1681
+ ...spanned,
1682
+ start_line: spanned.hashlines[0]?.line ?? 1,
1683
+ end_line: spanned.hashlines[spanned.hashlines.length - 1]?.line ?? 0
1684
+ };
1685
+ }
1686
+ function parseDocumentCache(value) {
1687
+ return parse2(DocumentCache, value, "document cache");
1688
+ }
1689
+ function parseIdentifyOutput(value) {
1690
+ return parse2(IdentifyEnvelope, value, "identify output");
1691
+ }
1692
+ function parsePdfInspectMetadata(value) {
1289
1693
  if (!value || typeof value !== "object")
1290
- throw new Error("invalid readseek content");
1291
- const output = value;
1292
- if (output.hashlines !== undefined && output.start_line === undefined) {
1293
- const hashlines = parseSearchHashlines(output.hashlines, "content.hashlines");
1294
- const first = hashlines[0];
1295
- const last = hashlines[hashlines.length - 1];
1296
- return {
1297
- file: requireString(output.file, "file"),
1298
- language: requireString(output.language, "language"),
1299
- line_count: requireNumber(output.line_count, "line_count"),
1300
- file_hash: requireString(output.file_hash, "file_hash"),
1301
- start_line: first?.line ?? 1,
1302
- end_line: last?.line ?? 0,
1303
- hashlines
1304
- };
1694
+ return;
1695
+ if (value.format !== "pdf")
1696
+ return;
1697
+ const document = parse2(PdfInspectMetadata, value, "inspect metadata");
1698
+ return { kind: "pdf", type: "application/pdf", file: document.source, format: "pdf", pages: document.pages };
1699
+ }
1700
+ function parseEditOutput(value) {
1701
+ return parse2(EditOutputSchema, value, "edit output");
1702
+ }
1703
+ function parseRenameOutput(value) {
1704
+ return parse2(RenameOutputSchema, value, "rename output");
1705
+ }
1706
+ function parseDefOutput(value) {
1707
+ return parse2(DefEnvelope, value, "def output").locations;
1708
+ }
1709
+
1710
+ // ../readseek-api/src/client.ts
1711
+ function classifyReadSeekFailure(err) {
1712
+ const failure = err;
1713
+ const message = String(failure?.message ?? err);
1714
+ const missing = failure?.code === "ENOENT" || /Cannot find package|Cannot find module|no such file/i.test(message);
1715
+ if (missing) {
1716
+ return { code: "readseek-not-installed", message, hint: "Run npm install to install @jarkkojs/readseek." };
1305
1717
  }
1306
- return parseReadOutput(value);
1718
+ return { code: "readseek-execution-error", message };
1307
1719
  }
1308
- function parseDigestEnvelope(value) {
1720
+ function parseSourceInspectEnvelope(value) {
1309
1721
  if (!value || typeof value !== "object")
1310
- throw new Error("invalid readseek digest output");
1722
+ throw new Error("invalid readseek inspect output");
1311
1723
  const output = value;
1312
- if (output.metadata === undefined)
1313
- throw new Error("invalid readseek digest metadata");
1314
- const result = {
1315
- metadata: parseDetectOutput(output.metadata)
1316
- };
1724
+ const result = {};
1725
+ if (output.metadata !== undefined)
1726
+ result.metadata = parseDetectOutput(output.metadata);
1317
1727
  if (output.cache !== undefined)
1318
1728
  result.cache = parseDocumentCache(output.cache);
1319
1729
  if (output.content !== undefined)
@@ -1326,223 +1736,104 @@ function parseDigestEnvelope(value) {
1326
1736
  result.identity = parseIdentifyOutput(output.identity);
1327
1737
  return result;
1328
1738
  }
1329
- function parseEditOutput(value) {
1330
- if (!value || typeof value !== "object")
1331
- throw new Error("invalid readseek edit output");
1332
- const output = value;
1333
- return {
1334
- file: requireString(output.file, "file"),
1335
- before_hash: requireString(output.before_hash, "before_hash"),
1336
- file_hash: requireString(output.file_hash, "file_hash"),
1337
- plan_hash: requireString(output.plan_hash, "plan_hash"),
1338
- applied: requireBoolean(output.applied, "applied"),
1339
- changed: requireBoolean(output.changed, "changed"),
1340
- edit_count: requireNumber(output.edit_count, "edit_count"),
1341
- before_content: optionalString(output.before_content, "before_content"),
1342
- content: optionalString(output.content, "content")
1343
- };
1344
- }
1345
- function parseRenameConflicts(value, field) {
1346
- if (value === undefined)
1347
- return [];
1348
- if (!Array.isArray(value))
1349
- throw new Error(`invalid readseek ${field}`);
1350
- return value.map((item) => {
1351
- if (!item || typeof item !== "object")
1352
- throw new Error(`invalid readseek ${field} entry`);
1353
- const c = item;
1354
- return {
1355
- line: requireNumber(c.line, `${field}.line`),
1356
- column: requireNumber(c.column, `${field}.column`),
1357
- reason: requireString(c.reason, `${field}.reason`)
1358
- };
1359
- });
1360
- }
1361
- function parseRenameEdits(value, field) {
1362
- if (value === undefined)
1363
- return [];
1364
- if (!Array.isArray(value))
1365
- throw new Error(`invalid readseek ${field} entry`);
1366
- return value.map((item) => {
1367
- if (!item || typeof item !== "object")
1368
- throw new Error(`invalid readseek ${field} entry`);
1369
- const e = item;
1370
- return {
1371
- line: requireNumber(e.line, `${field}.line`),
1372
- start_column: requireNumber(e.start_column, `${field}.start_column`),
1373
- end_column: requireNumber(e.end_column, `${field}.end_column`),
1374
- start_byte: requireNumber(e.start_byte, `${field}.start_byte`),
1375
- end_byte: requireNumber(e.end_byte, `${field}.end_byte`),
1376
- occurrence: requireString(e.occurrence, `${field}.occurrence`),
1377
- line_hash: requireString(e.line_hash, `${field}.line_hash`),
1378
- text: requireString(e.text, `${field}.text`)
1379
- };
1380
- });
1381
- }
1382
- function parseRenameOutput(value) {
1383
- if (!value || typeof value !== "object")
1384
- throw new Error("invalid readseek rename output");
1385
- const output = value;
1386
- const others = output.others;
1387
- if (others !== undefined && !Array.isArray(others))
1388
- throw new Error("invalid readseek others");
1389
- return {
1390
- file: requireString(output.file, "file"),
1391
- language: requireString(output.language, "language"),
1392
- engine: optionalString(output.engine, "engine"),
1393
- file_hash: requireString(output.file_hash, "file_hash"),
1394
- old_name: requireString(output.old_name, "old_name"),
1395
- new_name: requireString(output.new_name, "new_name"),
1396
- applied: requireBoolean(output.applied, "applied"),
1397
- conflicts: parseRenameConflicts(output.conflicts, "conflicts"),
1398
- edits: parseRenameEdits(output.edits, "edits"),
1399
- others: others?.map((entry) => {
1400
- if (!entry || typeof entry !== "object")
1401
- throw new Error("invalid readseek other");
1402
- const o = entry;
1403
- return {
1404
- file: requireString(o.file, "other.file"),
1405
- language: requireString(o.language, "other.language"),
1406
- engine: optionalString(o.engine, "other.engine"),
1407
- file_hash: requireString(o.file_hash, "other.file_hash"),
1408
- conflicts: parseRenameConflicts(o.conflicts, "other.conflicts"),
1409
- edits: parseRenameEdits(o.edits, "other.edits")
1410
- };
1411
- }) ?? []
1412
- };
1413
- }
1414
- function parseDefCompact(value) {
1415
- if (!value || typeof value !== "object")
1416
- throw new Error("invalid readseek def output");
1417
- const output = value;
1418
- const locations = output.locations;
1419
- if (!Array.isArray(locations))
1420
- throw new Error("invalid readseek def locations");
1421
- return locations.map((loc) => {
1422
- if (!loc || typeof loc !== "object")
1423
- throw new Error("invalid readseek def location");
1424
- const item = loc;
1425
- return {
1426
- file: requireString(item.file, "location.file"),
1427
- line: requireNumber(item.line, "location.line"),
1428
- column: requireNumber(item.column, "location.column"),
1429
- line_hash: requireString(item.line_hash, "location.line_hash"),
1430
- text: requireString(item.text, "location.text"),
1431
- kind: optionalString(item.kind, "location.kind"),
1432
- name: optionalString(item.name, "location.name"),
1433
- qualified_name: optionalString(item.qualified_name, "location.qualified_name")
1434
- };
1435
- });
1436
- }
1437
1739
  function createReadSeekClient(runner) {
1438
- function digestArgs(target, options) {
1439
- return buildDigestArgs({
1740
+ function inspectArgs(target, options) {
1741
+ return buildInspectArgs({
1440
1742
  path: target,
1743
+ selector: options.selector,
1441
1744
  select: options.select ?? "content",
1442
- at: options.at,
1745
+ query: options.query,
1443
1746
  end: options.end,
1444
1747
  limit: options.limit,
1748
+ depth: options.depth,
1445
1749
  language: options.language,
1446
1750
  visionMode: options.visionMode,
1447
1751
  visionLevel: options.visionLevel,
1448
- stdinName: options.stdin !== undefined && target === "-" ? options.stdinName ?? target : undefined
1752
+ stdinName: options.stdinName
1449
1753
  });
1450
1754
  }
1451
- async function runDigestRaw(target, options = {}) {
1452
- const args = digestArgs(target, options);
1453
- const runOptions = {
1755
+ function inspectRunOptions(options) {
1756
+ return {
1454
1757
  signal: options.signal,
1455
1758
  stdin: options.stdin,
1456
1759
  timeoutMs: options.timeoutMs,
1457
1760
  vision: options.vision
1458
1761
  };
1459
- return runner.json(args, runOptions);
1460
1762
  }
1461
- async function digest(target, options = {}) {
1462
- return parseDigestEnvelope(await runDigestRaw(target, options));
1763
+ async function inspect(target, options = {}) {
1764
+ return runner.kdl(inspectArgs(target, options), inspectRunOptions(options));
1765
+ }
1766
+ async function inspectText(target, options = {}) {
1767
+ return runner.text(inspectArgs(target, options), inspectRunOptions(options));
1768
+ }
1769
+ async function inspectSource(target, options = {}) {
1770
+ return parseSourceInspectEnvelope(await inspect(target, options));
1463
1771
  }
1464
- function requireDigestContent(envelope, field) {
1772
+ function requireInspectMetadata(envelope, field) {
1773
+ if (envelope.metadata === undefined)
1774
+ throw new Error(`invalid readseek ${field}: missing metadata`);
1775
+ return envelope.metadata;
1776
+ }
1777
+ function requireInspectContent(envelope, field) {
1465
1778
  if (envelope.content === undefined)
1466
1779
  throw new Error(`invalid readseek ${field}: missing content`);
1467
1780
  return envelope.content;
1468
1781
  }
1469
- async function digestContent(filePath, options = {}) {
1470
- const envelope = await digest(filePath, {
1471
- select: "content",
1472
- at: options.startLine !== undefined ? buildLineLocation(options.startLine) : undefined,
1782
+ async function inspectContent(filePath, options = {}) {
1783
+ const inspected = await inspect(filePath, {
1784
+ select: ["metadata", "content"],
1785
+ selector: options.startLine !== undefined ? buildLineLocation(options.startLine) : undefined,
1473
1786
  end: options.endLine,
1474
1787
  visionMode: options.visionMode,
1475
1788
  signal: options.signal,
1476
1789
  vision: options.vision
1477
1790
  });
1478
- if (envelope.metadata.kind === "pdf") {
1479
- return { metadata: envelope.metadata, cache: envelope.cache };
1480
- }
1481
- const content = requireDigestContent(envelope, "content");
1482
- if (envelope.metadata.kind === "image") {
1483
- return { metadata: envelope.metadata, image: parseImageContent(content) };
1791
+ const pdf = parsePdfInspectMetadata(inspected);
1792
+ if (pdf)
1793
+ return { metadata: pdf };
1794
+ const envelope = parseSourceInspectEnvelope(inspected);
1795
+ const metadata = requireInspectMetadata(envelope, "content");
1796
+ const content = requireInspectContent(envelope, "content");
1797
+ if (metadata.kind === "image") {
1798
+ return { metadata, image: parseImageContent(content) };
1484
1799
  }
1485
- return { metadata: envelope.metadata, text: parseTextContent(content) };
1800
+ return { metadata, text: parseTextContent(content) };
1486
1801
  }
1487
1802
  return {
1488
- async view(filePath, options = {}) {
1489
- const args = buildViewArgs({
1490
- path: filePath,
1491
- node: options.node,
1492
- page: options.page,
1493
- kind: options.kind,
1494
- depth: options.depth,
1495
- outline: options.outline,
1496
- visionMode: options.visionMode,
1497
- visionLevel: options.visionLevel
1498
- });
1499
- return runner.text(args, {
1803
+ inspect,
1804
+ inspectText,
1805
+ inspectContent,
1806
+ async extract(target, selector, output, options = {}) {
1807
+ await runner.text(buildExtractArgs({
1808
+ path: target,
1809
+ selector,
1810
+ output,
1811
+ stdinName: options.stdinName
1812
+ }), {
1500
1813
  signal: options.signal,
1501
- vision: options.visionMode !== undefined
1814
+ stdin: options.stdin,
1815
+ timeoutMs: options.timeoutMs,
1816
+ cancelable: options.cancelable
1502
1817
  });
1503
1818
  },
1504
- digestRaw(target, options = {}) {
1505
- return runDigestRaw(target, options);
1506
- },
1507
- digest,
1508
- digestContent,
1509
1819
  async image(filePath, modes, options = {}) {
1510
- const requestedModes = modes.length > 1 ? ["all"] : modes;
1511
- const results = await Promise.allSettled(requestedModes.map(async (mode) => {
1512
- const digested = await digestContent(filePath, {
1513
- visionMode: mode,
1514
- signal: options.signal,
1515
- vision: true
1516
- });
1517
- if (!digested.image)
1518
- throw new Error(`readseek returned no image analysis for ${filePath}`);
1519
- return digested.image;
1520
- }));
1521
- let merged;
1522
- for (const result of results) {
1523
- if (result.status !== "fulfilled")
1524
- continue;
1525
- const detection = result.value;
1526
- merged = merged === undefined ? detection : {
1527
- ...merged,
1528
- ocr: detection.ocr ?? merged.ocr,
1529
- caption: detection.caption ?? merged.caption,
1530
- objects: detection.objects ?? merged.objects
1531
- };
1532
- }
1533
- if (merged !== undefined)
1534
- return merged;
1535
- const failure = results.find((result) => result.status === "rejected");
1536
- if (failure?.status === "rejected")
1537
- throw failure.reason;
1538
- throw new Error(`readseek returned no image analysis for ${filePath}`);
1820
+ if (modes.length === 0)
1821
+ throw new Error(`readseek returned no image analysis for ${filePath}`);
1822
+ const inspected = await inspectContent(filePath, {
1823
+ visionMode: modes.length > 1 ? "all" : modes[0],
1824
+ signal: options.signal,
1825
+ vision: true
1826
+ });
1827
+ if (!inspected.image)
1828
+ throw new Error(`readseek returned no image analysis for ${filePath}`);
1829
+ return inspected.image;
1539
1830
  },
1540
1831
  async preparedImage(filePath, options = {}) {
1541
- const digested = await digestContent(filePath, {
1832
+ const inspected = await inspectContent(filePath, {
1542
1833
  visionMode: "none",
1543
1834
  signal: options.signal
1544
1835
  });
1545
- const output = digested.image;
1836
+ const output = inspected.image;
1546
1837
  if (!output || output.encoding !== "base64" || output.data === undefined || output.mime === undefined) {
1547
1838
  throw new Error(`readseek returned no prepared image for ${filePath}`);
1548
1839
  }
@@ -1554,11 +1845,9 @@ function createReadSeekClient(runner) {
1554
1845
  pattern,
1555
1846
  language: options.language,
1556
1847
  limit: options.limit,
1557
- cached: options.cached,
1558
- others: options.others,
1559
- ignored: options.ignored
1848
+ gitScope: options.gitScope
1560
1849
  });
1561
- return parseSearchOutput(await runner.json(args, { signal: options.signal }));
1850
+ return parseSearchOutput(await runner.kdl(args, { signal: options.signal }));
1562
1851
  },
1563
1852
  async refs(target, options = {}) {
1564
1853
  const args = buildRefsArgs({
@@ -1568,26 +1857,21 @@ function createReadSeekClient(runner) {
1568
1857
  line: options.line,
1569
1858
  column: options.column,
1570
1859
  language: options.language,
1571
- cached: options.cached,
1572
- others: options.others,
1573
- ignored: options.ignored
1860
+ gitScope: options.gitScope
1574
1861
  });
1575
- return parseRefsOutput(await runner.json(args, { signal: options.signal }));
1862
+ return parseRefsOutput(await runner.kdl(args, { signal: options.signal }));
1576
1863
  },
1577
1864
  async def(target, options) {
1578
1865
  const args = buildDefArgs({
1579
1866
  path: target,
1580
1867
  name: options.name,
1581
- format: "plain",
1582
1868
  language: options.language,
1583
- cached: options.cached,
1584
- others: options.others,
1585
- ignored: options.ignored
1869
+ gitScope: options.gitScope
1586
1870
  });
1587
- return parseDefCompact(await runner.json(args, { signal: options.signal }));
1871
+ return parseDefOutput(await runner.kdl(args, { signal: options.signal }));
1588
1872
  },
1589
1873
  async check(filePath, content, options = {}) {
1590
- const envelope = await digest("-", {
1874
+ const envelope = await inspectSource("-", {
1591
1875
  select: "diagnostics",
1592
1876
  stdin: content,
1593
1877
  stdinName: filePath,
@@ -1597,12 +1881,12 @@ function createReadSeekClient(runner) {
1597
1881
  throw new Error("invalid readseek diagnostics: missing diagnostics facet");
1598
1882
  return envelope.diagnostics;
1599
1883
  },
1600
- async identify(filePath, content, options = {}) {
1601
- const envelope = await digest("-", {
1884
+ async identify(filePath, content, options) {
1885
+ const envelope = await inspectSource("-", {
1602
1886
  select: "identity",
1603
1887
  stdin: content,
1604
1888
  stdinName: filePath,
1605
- at: options.line !== undefined ? buildLineLocation(options.line, options.column) : undefined,
1889
+ selector: buildLineLocation(options.line, options.column),
1606
1890
  language: options.language,
1607
1891
  signal: options.signal
1608
1892
  });
@@ -1611,8 +1895,11 @@ function createReadSeekClient(runner) {
1611
1895
  return envelope.identity;
1612
1896
  },
1613
1897
  async detect(filePath, options = {}) {
1614
- const envelope = await digest(filePath, { select: "metadata", signal: options.signal });
1615
- return envelope.metadata;
1898
+ const inspected = await inspect(filePath, { select: "metadata", signal: options.signal });
1899
+ const pdf = parsePdfInspectMetadata(inspected);
1900
+ if (pdf)
1901
+ return pdf;
1902
+ return requireInspectMetadata(parseSourceInspectEnvelope(inspected), "metadata");
1616
1903
  },
1617
1904
  async edit(filePath, edits, options = {}) {
1618
1905
  const args = buildEditArgs({
@@ -1622,9 +1909,9 @@ function createReadSeekClient(runner) {
1622
1909
  language: options.language,
1623
1910
  requestFromStdin: true
1624
1911
  });
1625
- return parseEditOutput(await runner.json(args, {
1912
+ return parseEditOutput(await runner.kdl(args, {
1626
1913
  signal: options.signal,
1627
- stdin: JSON.stringify({ edits }),
1914
+ stdin: serializeEditPlan(edits),
1628
1915
  timeoutMs: options.timeoutMs,
1629
1916
  cancelable: options.cancelable
1630
1917
  }));
@@ -1637,12 +1924,11 @@ function createReadSeekClient(runner) {
1637
1924
  column: options.column,
1638
1925
  workspace: options.workspace,
1639
1926
  apply: options.apply,
1927
+ planHash: options.planHash,
1640
1928
  language: options.language,
1641
- cached: options.cached,
1642
- others: options.others,
1643
- ignored: options.ignored
1929
+ gitScope: options.gitScope
1644
1930
  });
1645
- return parseRenameOutput(await runner.json(args, { signal: options.signal }));
1931
+ return parseRenameOutput(await runner.kdl(args, { signal: options.signal }));
1646
1932
  }
1647
1933
  };
1648
1934
  }
@@ -2123,7 +2409,7 @@ function formatReplaceHint(edits, noopEdits) {
2123
2409
  return;
2124
2410
  if (counts.replace !== counts.total)
2125
2411
  return;
2126
- return "[info: this edit used replace (unverified). For safer future edits, prefer set_line/replace_lines with an anchor from readSeek_digest/readSeek_grep/readSeek_search.]";
2412
+ return "[info: this edit used replace (unverified). For safer future edits, prefer set_line/replace_lines with an anchor from readSeek_inspect/readSeek_grep/readSeek_search.]";
2127
2413
  }
2128
2414
  function buildEditOutput(input) {
2129
2415
  const summary = `Updated ${input.displayPath}`;
@@ -2170,20 +2456,20 @@ function classifyEdit(oldContent, newContent) {
2170
2456
  const newLines = newContent.split(`
2171
2457
  `);
2172
2458
  if (oldLines.length === newLines.length) {
2173
- let hasWhitespaceChange2 = false;
2174
- let hasSemanticChange2 = false;
2459
+ let hasWhitespaceChange = false;
2460
+ let hasSemanticChange = false;
2175
2461
  for (let i = 0;i < oldLines.length; i++) {
2176
2462
  if (oldLines[i] === newLines[i])
2177
2463
  continue;
2178
2464
  if (oldLines[i].trim() === newLines[i].trim()) {
2179
- hasWhitespaceChange2 = true;
2465
+ hasWhitespaceChange = true;
2180
2466
  } else {
2181
- hasSemanticChange2 = true;
2467
+ hasSemanticChange = true;
2182
2468
  }
2183
2469
  }
2184
- if (hasSemanticChange2 && hasWhitespaceChange2)
2470
+ if (hasSemanticChange && hasWhitespaceChange)
2185
2471
  return { classification: "mixed" };
2186
- if (hasWhitespaceChange2)
2472
+ if (hasWhitespaceChange)
2187
2473
  return { classification: "whitespace-only" };
2188
2474
  return { classification: "semantic" };
2189
2475
  }
@@ -2223,7 +2509,7 @@ import { readFileSync, statSync as statSync2 } from "node:fs";
2223
2509
  import { homedir } from "node:os";
2224
2510
  import { join } from "node:path";
2225
2511
  var READSEEK_TOOL_REPLACEMENTS = {
2226
- read: "readSeek_digest",
2512
+ read: "readSeek_inspect",
2227
2513
  edit: "readSeek_edit",
2228
2514
  grep: "readSeek_grep",
2229
2515
  write: "readSeek_write"
@@ -2236,42 +2522,36 @@ var DEFAULT_DISPLAY_MODES = {
2236
2522
  edit: "expanded",
2237
2523
  write: "expanded"
2238
2524
  };
2239
- function globalSettingsPath() {
2240
- return join(homedir(), ".pi/agent/settings.json");
2241
- }
2242
- function projectSettingsPath() {
2243
- return join(process.cwd(), ".pi/settings.json");
2244
- }
2245
2525
  function isRecord(value) {
2246
2526
  return typeof value === "object" && value !== null && !Array.isArray(value);
2247
2527
  }
2248
- function invalid(source, path2) {
2249
- return { source, path: path2, message: `Invalid readseek setting at ${path2}` };
2528
+ function invalid(source, path) {
2529
+ return { source, path, message: `Invalid readseek setting at ${path}` };
2250
2530
  }
2251
2531
  function warnUnknownKeys(raw, known, prefix, source, warnings) {
2252
2532
  for (const key of Object.keys(raw)) {
2253
2533
  if (known.includes(key))
2254
2534
  continue;
2255
- const path2 = `${prefix}.${key}`;
2256
- warnings.push({ source, path: path2, message: `Unknown readseek setting at ${path2}` });
2535
+ const path = `${prefix}.${key}`;
2536
+ warnings.push({ source, path, message: `Unknown readseek setting at ${path}` });
2257
2537
  }
2258
2538
  }
2259
- function readPositive(raw, key, path2, source, warnings) {
2539
+ function readPositive(raw, key, path, source, warnings) {
2260
2540
  if (!(key in raw))
2261
2541
  return;
2262
2542
  const val = raw[key];
2263
2543
  if (typeof val === "number" && Number.isSafeInteger(val) && val > 0)
2264
2544
  return val;
2265
- warnings.push(invalid(source, path2));
2545
+ warnings.push(invalid(source, path));
2266
2546
  return;
2267
2547
  }
2268
- function readEnum(raw, key, values, path2, source, warnings) {
2548
+ function readEnum(raw, key, values, path, source, warnings) {
2269
2549
  if (!(key in raw))
2270
2550
  return;
2271
2551
  const val = raw[key];
2272
2552
  if (typeof val === "string" && values.includes(val))
2273
2553
  return val;
2274
- warnings.push(invalid(source, path2));
2554
+ warnings.push(invalid(source, path));
2275
2555
  return;
2276
2556
  }
2277
2557
  function readImageMode(raw, source, warnings) {
@@ -2375,31 +2655,31 @@ function validateSettings(raw, source) {
2375
2655
  return { settings, warnings };
2376
2656
  }
2377
2657
  var settingsFileCache = new Map;
2378
- function statFileOrNull(path2) {
2658
+ function statFileOrNull(path) {
2379
2659
  try {
2380
- return statSync2(path2);
2660
+ return statSync2(path);
2381
2661
  } catch {
2382
2662
  return null;
2383
2663
  }
2384
2664
  }
2385
- function readSettingsFile(path2) {
2386
- const stats = statFileOrNull(path2);
2665
+ function readSettingsFile(path) {
2666
+ const stats = statFileOrNull(path);
2387
2667
  if (!stats) {
2388
- settingsFileCache.delete(path2);
2668
+ settingsFileCache.delete(path);
2389
2669
  return { settings: {}, warnings: [] };
2390
2670
  }
2391
- const cached = settingsFileCache.get(path2);
2671
+ const cached = settingsFileCache.get(path);
2392
2672
  if (cached && cached.mtimeMs === stats.mtimeMs && cached.size === stats.size)
2393
2673
  return cached.result;
2394
2674
  let result;
2395
2675
  try {
2396
- const text = readFileSync(path2, "utf8");
2397
- result = validateSettings(JSON.parse(text), path2);
2676
+ const text = readFileSync(path, "utf8");
2677
+ result = validateSettings(JSON.parse(text), path);
2398
2678
  } catch (error) {
2399
2679
  const message = error instanceof Error ? error.message : String(error);
2400
- result = { settings: {}, warnings: [{ source: path2, message: `Invalid JSON: ${message}` }] };
2680
+ result = { settings: {}, warnings: [{ source: path, message: `Invalid JSON: ${message}` }] };
2401
2681
  }
2402
- settingsFileCache.set(path2, { mtimeMs: stats.mtimeMs, size: stats.size, result });
2682
+ settingsFileCache.set(path, { mtimeMs: stats.mtimeMs, size: stats.size, result });
2403
2683
  return result;
2404
2684
  }
2405
2685
  function mergeSettings(base, override) {
@@ -2413,8 +2693,8 @@ function mergeSettings(base, override) {
2413
2693
  return settings;
2414
2694
  }
2415
2695
  function resolveReadSeekJsonSettings() {
2416
- const globalResult = readSettingsFile(globalSettingsPath());
2417
- const projectResult = readSettingsFile(projectSettingsPath());
2696
+ const globalResult = readSettingsFile(join(homedir(), ".pi/agent/settings.json"));
2697
+ const projectResult = readSettingsFile(join(process.cwd(), ".pi/settings.json"));
2418
2698
  return {
2419
2699
  settings: mergeSettings(globalResult.settings, projectResult.settings),
2420
2700
  warnings: [...globalResult.warnings, ...projectResult.warnings]
@@ -2462,21 +2742,13 @@ var DEFAULT_READSEEK_VISION_TIMEOUT_MS = 30 * 60000;
2462
2742
  function readSeekTimeoutMs() {
2463
2743
  return resolveReadSeekTimeoutMs() ?? DEFAULT_READSEEK_TIMEOUT_MS;
2464
2744
  }
2465
- async function spawnReadSeekRaw(args, options = {}) {
2466
- return runReadSeekNodeText(readSeekBinaryPath(), args, {
2467
- signal: options.signal,
2468
- stdin: options.stdin,
2469
- timeoutMs: options.timeoutMs ?? readSeekTimeoutMs(),
2470
- cancelable: options.cancelable
2471
- });
2472
- }
2473
2745
  async function ensureDefaultReadSeekDir() {
2474
2746
  const dir = defaultReadSeekDir();
2475
2747
  if (!dir)
2476
2748
  return null;
2477
2749
  if (directoryExists(dir))
2478
2750
  return dir;
2479
- defaultReadSeekDirInit ??= spawnReadSeekRaw(["--readseek-dir", dir, "init"]).then(() => directoryExists(dir) ? dir : null).catch(() => null).finally(() => {
2751
+ defaultReadSeekDirInit ??= runReadSeekNodeText(readSeekBinaryPath(), ["--readseek-dir", dir, "init"], { timeoutMs: readSeekTimeoutMs() }).then(() => directoryExists(dir) ? dir : null).catch(() => null).finally(() => {
2480
2752
  defaultReadSeekDirInit = undefined;
2481
2753
  });
2482
2754
  return defaultReadSeekDirInit;
@@ -2498,17 +2770,6 @@ async function readSeekInvocationArgs(args) {
2498
2770
  const readSeekDir = await ensureDefaultReadSeekDir();
2499
2771
  return readSeekDir ? ["--readseek-dir", readSeekDir, ...args] : [...args];
2500
2772
  }
2501
- async function runReadSeekRaw(args, options = {}) {
2502
- return spawnReadSeekRaw(await readSeekInvocationArgs(args), options);
2503
- }
2504
- async function runReadSeek(args, options = {}) {
2505
- return runReadSeekNodeJson(readSeekBinaryPath(), await readSeekInvocationArgs(args), {
2506
- signal: options.signal,
2507
- stdin: options.stdin,
2508
- timeoutMs: options.timeoutMs ?? readSeekTimeoutMs(),
2509
- cancelable: options.cancelable
2510
- });
2511
- }
2512
2773
  var visionInvocationTail = Promise.resolve();
2513
2774
  async function runReadSeekVisionRaw(args, options = {}) {
2514
2775
  let release;
@@ -2539,38 +2800,40 @@ async function runReadSeekVisionRaw(args, options = {}) {
2539
2800
  await predecessor;
2540
2801
  }
2541
2802
  signal?.throwIfAborted();
2542
- return await runReadSeekRaw(args, {
2543
- ...options,
2544
- timeoutMs: options.timeoutMs ?? resolveReadSeekTimeoutMs() ?? DEFAULT_READSEEK_VISION_TIMEOUT_MS
2803
+ return await runReadSeekNodeText(readSeekBinaryPath(), await readSeekInvocationArgs(args), {
2804
+ signal: options.signal,
2805
+ stdin: options.stdin,
2806
+ timeoutMs: options.timeoutMs ?? resolveReadSeekTimeoutMs() ?? DEFAULT_READSEEK_VISION_TIMEOUT_MS,
2807
+ cancelable: options.cancelable
2545
2808
  });
2546
2809
  } finally {
2547
2810
  release();
2548
2811
  }
2549
2812
  }
2550
- async function runReadSeekVision(args, options = {}) {
2551
- return parseReadSeekJson(await runReadSeekVisionRaw(args, options));
2552
- }
2553
2813
  var runner = {
2554
- text: (args, options = {}) => options.vision ? runReadSeekVisionRaw(args, options) : runReadSeekRaw(args, options),
2555
- json: (args, options = {}) => options.vision ? runReadSeekVision(args, options) : runReadSeek(args, options)
2814
+ text: async (args, options = {}) => options.vision ? runReadSeekVisionRaw(args, options) : runReadSeekNodeText(readSeekBinaryPath(), await readSeekInvocationArgs(args), {
2815
+ signal: options.signal,
2816
+ stdin: options.stdin,
2817
+ timeoutMs: options.timeoutMs ?? readSeekTimeoutMs(),
2818
+ cancelable: options.cancelable
2819
+ }),
2820
+ kdl: async (args, options = {}) => parseReadSeekKdl(await runner.text(args, options))
2556
2821
  };
2557
2822
  var client = createReadSeekClient(runner);
2558
- var readSeekView = client.view;
2559
- var readSeekDigestNative = client.digestRaw;
2560
- var readSeekDigest = client.digest;
2561
- var readSeekDigestContent = client.digestContent;
2562
- var readSeekImage = client.image;
2563
- var readSeekPreparedImage = client.preparedImage;
2823
+ var readSeekInspect = client.inspect;
2824
+ var readSeekExtract = client.extract;
2564
2825
  var readSeekSearch = client.search;
2565
2826
  var readSeekRefs = client.refs;
2566
2827
  var readSeekDef = client.def;
2567
2828
  var readSeekCheck = client.check;
2568
- var readSeekIdentify = client.identify;
2569
- var readSeekDetect = client.detect;
2570
2829
  var readSeekEdit = client.edit;
2571
2830
  var readSeekRename = client.rename;
2572
2831
  async function readSeekMap(filePath, options = {}) {
2573
- const envelope = await client.digest(filePath, { select: "map", signal: options.signal });
2832
+ const envelope = await client.inspect(filePath, {
2833
+ select: "map",
2834
+ depth: DEFAULT_INSPECT_MAP_DEPTH,
2835
+ signal: options.signal
2836
+ });
2574
2837
  const map = envelope.map;
2575
2838
  if (!map || map.language === "unknown" && map.symbols.length === 0)
2576
2839
  return null;
@@ -2634,8 +2897,8 @@ var LANGUAGE_BY_EXTENSION = new Map([
2634
2897
  [".rs", "rust"],
2635
2898
  [".java", "java"]
2636
2899
  ]);
2637
- function inferLanguage(path3) {
2638
- const extensionMatch = path3.match(/\.[^.\/\\]+$/);
2900
+ function inferLanguage(path) {
2901
+ const extensionMatch = path.match(/\.[^.\/\\]+$/);
2639
2902
  if (!extensionMatch)
2640
2903
  return;
2641
2904
  return LANGUAGE_BY_EXTENSION.get(extensionMatch[0].toLowerCase());
@@ -2681,10 +2944,10 @@ function parseCompactDiffEntries(diff, oldContent, newContent) {
2681
2944
  `);
2682
2945
  const compactDelete = diff.match(/^(\d+):[0-9a-f]{6}\|(.*) → \[deleted\]$/);
2683
2946
  if (compactDelete) {
2684
- const oldLine2 = Number(compactDelete[1]);
2947
+ const oldLine = Number(compactDelete[1]);
2685
2948
  const text = compactDelete[2] ?? "";
2686
- if (oldLines[oldLine2 - 1] === text)
2687
- return [{ kind: "remove", oldLine: oldLine2, text }];
2949
+ if (oldLines[oldLine - 1] === text)
2950
+ return [{ kind: "remove", oldLine, text }];
2688
2951
  }
2689
2952
  const compactPrefix = diff.match(/^(\d+):[0-9a-f]{6}\|/);
2690
2953
  if (!compactPrefix)
@@ -2724,9 +2987,9 @@ function longestCommonSubsequence(a, b) {
2724
2987
  const rows = a.length + 1;
2725
2988
  const cols = b.length + 1;
2726
2989
  const table = Array.from({ length: rows }, () => Array(cols).fill(0));
2727
- for (let i2 = a.length - 1;i2 >= 0; i2--) {
2728
- for (let j2 = b.length - 1;j2 >= 0; j2--) {
2729
- table[i2][j2] = a[i2] === b[j2] ? table[i2 + 1][j2 + 1] + 1 : Math.max(table[i2 + 1][j2], table[i2][j2 + 1]);
2990
+ for (let i = a.length - 1;i >= 0; i--) {
2991
+ for (let j = b.length - 1;j >= 0; j--) {
2992
+ table[i][j] = a[i] === b[j] ? table[i + 1][j + 1] + 1 : Math.max(table[i + 1][j], table[i][j + 1]);
2730
2993
  }
2731
2994
  }
2732
2995
  const pairs = [];
@@ -2844,8 +3107,8 @@ function buildInlineDiffs(entries) {
2844
3107
  }
2845
3108
  return inlineDiffs.length ? inlineDiffs : undefined;
2846
3109
  }
2847
- function finalizeDiffData(path3, entries, blockRanges) {
2848
- const language = inferLanguage(path3);
3110
+ function finalizeDiffData(path, entries, blockRanges) {
3111
+ const language = inferLanguage(path);
2849
3112
  const inlineDiffs = buildInlineDiffs(entries);
2850
3113
  return {
2851
3114
  version: 1,
@@ -2938,7 +3201,6 @@ function wrapWithHangingIndent(prefix, content, width, options = {}) {
2938
3201
  const indent = " ".repeat(prefixWidth);
2939
3202
  return wrapped.map((line, index) => tint(clampLineToWidth(index === 0 ? prefix + line : indent + line, normalized)));
2940
3203
  }
2941
- var wrappedHashlinesCache = new WeakMap;
2942
3204
  function renderPendingResult(pendingLabel, width, theme) {
2943
3205
  return new Text(clampLinesToWidth([summaryLine(pendingLabel, { theme, style: "muted" })], width).join(`
2944
3206
  `), 0, 0);
@@ -3004,28 +3266,53 @@ import { Text as Text2, visibleWidth as visibleWidth3 } from "@earendil-works/pi
3004
3266
 
3005
3267
  // src/tui-diff-renderer.ts
3006
3268
  import { visibleWidth as visibleWidth2 } from "@earendil-works/pi-tui";
3007
- function hasOldSide(data) {
3008
- return data.entries.some((e) => e.kind === "remove" || e.kind === "context");
3269
+ function isDiffEntry(value) {
3270
+ if (typeof value !== "object" || value === null)
3271
+ return false;
3272
+ const entry = value;
3273
+ if (typeof entry.text !== "string")
3274
+ return false;
3275
+ if (entry.kind === "meta")
3276
+ return true;
3277
+ if (entry.kind === "add")
3278
+ return Number.isInteger(entry.newLine) && Number(entry.newLine) > 0;
3279
+ if (entry.kind === "remove")
3280
+ return Number.isInteger(entry.oldLine) && Number(entry.oldLine) > 0;
3281
+ return entry.kind === "context" && Number.isInteger(entry.oldLine) && Number(entry.oldLine) > 0 && Number.isInteger(entry.newLine) && Number(entry.newLine) > 0;
3282
+ }
3283
+ function indexedDiffEntries(data) {
3284
+ if (!Array.isArray(data.entries))
3285
+ return [];
3286
+ const entries = [];
3287
+ for (const [index, entry] of data.entries.entries()) {
3288
+ if (isDiffEntry(entry))
3289
+ entries.push([index, entry]);
3290
+ }
3291
+ return entries;
3292
+ }
3293
+ function hasOldSide(entries) {
3294
+ return entries.some(([, entry]) => entry.kind === "remove" || entry.kind === "context");
3009
3295
  }
3010
- function chooseMode(width, data) {
3296
+ function chooseMode(width, entries) {
3011
3297
  if (width < 24)
3012
3298
  return "summary";
3013
3299
  if (width < 50)
3014
3300
  return "compact";
3015
- if (width >= 100 && hasOldSide(data))
3301
+ if (width >= 100 && hasOldSide(entries))
3016
3302
  return "split";
3017
3303
  return "unified";
3018
3304
  }
3019
- function hunkCount(data) {
3020
- return Math.max(1, data.blockRanges?.length ?? (data.entries.some((e) => e.kind === "add" || e.kind === "remove") ? 1 : 0));
3305
+ function hunkCount(data, entries) {
3306
+ const blockRangeCount = Array.isArray(data.blockRanges) ? data.blockRanges.length : undefined;
3307
+ return Math.max(1, blockRangeCount ?? (entries.some(([, entry]) => entry.kind === "add" || entry.kind === "remove") ? 1 : 0));
3021
3308
  }
3022
3309
  function compactHeader(data) {
3023
3310
  return `↳ diff +${data.stats.added} -${data.stats.removed}`;
3024
3311
  }
3025
- function header(data, mode, width) {
3312
+ function header(data, mode, width, entries) {
3026
3313
  if (mode === "summary" && width <= 10)
3027
3314
  return `↳ diff +${data.stats.added}`;
3028
- const full = `${compactHeader(data)} • ${hunkCount(data)} hunk • 1 file • ${mode === "split" ? "split" : "unified"}`;
3315
+ const full = `${compactHeader(data)} • ${hunkCount(data, entries)} hunk • 1 file • ${mode === "split" ? "split" : "unified"}`;
3029
3316
  return visibleWidth2(full) <= width ? full : clampLineToWidth(compactHeader(data), width);
3030
3317
  }
3031
3318
  function lineNo(entry) {
@@ -3044,18 +3331,18 @@ function padRightVisual(line, width) {
3044
3331
  function tint(theme, entry, text) {
3045
3332
  return entry.kind === "add" ? theme.fg("success", text) : entry.kind === "remove" ? theme.fg("error", text) : theme.fg("toolOutput", text);
3046
3333
  }
3047
- function spans(theme, spans2, fallback) {
3048
- return spans2?.map((s) => s.kind === "add" ? theme.fg("success", s.text) : s.kind === "remove" ? theme.fg("error", s.text) : s.text).join("") ?? fallback;
3334
+ function spans(theme, spans, fallback) {
3335
+ return spans?.map((s) => s.kind === "add" ? theme.fg("success", s.text) : s.kind === "remove" ? theme.fg("error", s.text) : s.text).join("") ?? fallback;
3049
3336
  }
3050
3337
  function inlineText(input, index, entry) {
3051
- const pair = input.diffData.inlineDiffs?.find((d) => entry.kind === "remove" ? d.removeLineIndex === index : entry.kind === "add" ? d.addLineIndex === index : false);
3338
+ const pair = Array.isArray(input.diffData.inlineDiffs) ? input.diffData.inlineDiffs.find((d) => entry.kind === "remove" ? d.removeLineIndex === index : entry.kind === "add" ? d.addLineIndex === index : false) : undefined;
3052
3339
  if (!pair)
3053
3340
  return textOf(entry);
3054
3341
  return entry.kind === "remove" ? spans(input.theme, pair.removeSpans, textOf(entry)) : entry.kind === "add" ? spans(input.theme, pair.addSpans, textOf(entry)) : textOf(entry);
3055
3342
  }
3056
- function unifiedRows(input, width) {
3343
+ function unifiedRows(input, width, entries) {
3057
3344
  const rows = [];
3058
- for (const [i, e] of input.diffData.entries.entries()) {
3345
+ for (const [i, e] of entries) {
3059
3346
  if (e.kind === "meta")
3060
3347
  continue;
3061
3348
  const prefix = `▌${gutterMarker(e)} ${lineNo(e)} │ `;
@@ -3064,9 +3351,9 @@ function unifiedRows(input, width) {
3064
3351
  }
3065
3352
  return rows;
3066
3353
  }
3067
- function compactRows(input, width) {
3354
+ function compactRows(input, width, entries) {
3068
3355
  const rows = [];
3069
- for (const [i, e] of input.diffData.entries.entries()) {
3356
+ for (const [i, e] of entries) {
3070
3357
  if (e.kind !== "add" && e.kind !== "remove")
3071
3358
  continue;
3072
3359
  const prefix = `▌${gutterMarker(e)} ${lineNo(e)} `;
@@ -3075,11 +3362,11 @@ function compactRows(input, width) {
3075
3362
  }
3076
3363
  return rows;
3077
3364
  }
3078
- function splitRows(input, width) {
3365
+ function splitRows(input, width, entries) {
3079
3366
  const pane = Math.max(10, Math.floor((width - 3) / 2));
3080
3367
  const rows = [`${padRightVisual("old", pane)} │ new`];
3081
3368
  const blankPane = " ".repeat(pane);
3082
- for (const [i, e] of input.diffData.entries.entries()) {
3369
+ for (const [i, e] of entries) {
3083
3370
  if (e.kind === "remove") {
3084
3371
  const left = wrapWithHangingIndent(`▌- ${e.oldLine} │ `, inlineText(input, i, e), pane, { tint: (text) => tint(input.theme, e, text) });
3085
3372
  for (const line of left)
@@ -3107,13 +3394,18 @@ function hiddenHint(hiddenLines, hiddenHunks, width) {
3107
3394
  }
3108
3395
  function renderTuiDiff(input) {
3109
3396
  const width = normalizeWidth(input.width);
3110
- const mode = chooseMode(width, input.diffData);
3111
- const lines = [header(input.diffData, mode, width)];
3397
+ const entries = indexedDiffEntries(input.diffData);
3398
+ const rawEntryCount = Array.isArray(input.diffData.entries) ? input.diffData.entries.length : input.diffData.stats.added + input.diffData.stats.removed + input.diffData.stats.context;
3399
+ const truncated = !Array.isArray(input.diffData.entries) || entries.length !== rawEntryCount;
3400
+ const mode = chooseMode(width, entries);
3401
+ const lines = [header(input.diffData, mode, width, entries)];
3112
3402
  if (!input.expanded)
3113
- return { mode, width, lines: clampLinesToWidth([...lines, hiddenHint(input.diffData.entries.length, hunkCount(input.diffData), width)], width) };
3403
+ return { mode, width, lines: clampLinesToWidth([...lines, hiddenHint(rawEntryCount, hunkCount(input.diffData, entries), width)], width) };
3114
3404
  if (mode === "summary")
3115
3405
  return { mode, width, lines: clampLinesToWidth(lines, width) };
3116
- const rows = mode === "split" ? splitRows(input, width) : mode === "compact" ? compactRows(input, width) : unifiedRows(input, width);
3406
+ const rows = mode === "split" ? splitRows(input, width, entries) : mode === "compact" ? compactRows(input, width, entries) : unifiedRows(input, width, entries);
3407
+ if (truncated)
3408
+ rows.push(clampLineToWidth("… diff preview truncated", width));
3117
3409
  return { mode, width, lines: [...lines, ...rows] };
3118
3410
  }
3119
3411
 
@@ -3169,12 +3461,12 @@ function upsertTextComponent(lastComponent, text) {
3169
3461
  }
3170
3462
 
3171
3463
  // src/register-tool.ts
3172
- import { Type } from "@sinclair/typebox";
3464
+ import { Type as Type2 } from "@sinclair/typebox";
3173
3465
  function optionalIntOrString(description) {
3174
- return Type.Optional(Type.Union([Type.Number({ description }), Type.String({ description })]));
3466
+ return Type2.Optional(Type2.Union([Type2.Number({ description }), Type2.String({ description })]));
3175
3467
  }
3176
3468
  function filePathParam() {
3177
- return Type.String({ description: PARAM_DESCRIPTIONS.path });
3469
+ return Type2.String({ description: PARAM_DESCRIPTIONS.path });
3178
3470
  }
3179
3471
  function registerReadSeekTool(pi, tool) {
3180
3472
  pi.registerTool(tool);
@@ -3182,28 +3474,28 @@ function registerReadSeekTool(pi, tool) {
3182
3474
  }
3183
3475
 
3184
3476
  // src/edit.ts
3185
- var hashlineEditItemSchema = Type2.Object({
3186
- set_line: Type2.Optional(Type2.Object({
3187
- anchor: Type2.String({ description: PARAM_DESCRIPTIONS.anchor }),
3188
- new_text: Type2.String({ description: PARAM_DESCRIPTIONS.newTextLine })
3477
+ var hashlineEditItemSchema = Type3.Object({
3478
+ set_line: Type3.Optional(Type3.Object({
3479
+ anchor: Type3.String({ description: PARAM_DESCRIPTIONS.anchor }),
3480
+ new_text: Type3.String({ description: PARAM_DESCRIPTIONS.newTextLine })
3189
3481
  }, { additionalProperties: false, description: PARAM_DESCRIPTIONS.setLine })),
3190
- replace_lines: Type2.Optional(Type2.Object({
3191
- start_anchor: Type2.String({ description: PARAM_DESCRIPTIONS.startAnchor }),
3192
- end_anchor: Type2.String({ description: PARAM_DESCRIPTIONS.endAnchor }),
3193
- new_text: Type2.String({ description: PARAM_DESCRIPTIONS.newTextRange })
3482
+ replace_lines: Type3.Optional(Type3.Object({
3483
+ start_anchor: Type3.String({ description: PARAM_DESCRIPTIONS.startAnchor }),
3484
+ end_anchor: Type3.String({ description: PARAM_DESCRIPTIONS.endAnchor }),
3485
+ new_text: Type3.String({ description: PARAM_DESCRIPTIONS.newTextRange })
3194
3486
  }, { additionalProperties: false, description: PARAM_DESCRIPTIONS.replaceLines })),
3195
- insert_after: Type2.Optional(Type2.Object({
3196
- anchor: Type2.String({ description: PARAM_DESCRIPTIONS.insertAnchor }),
3197
- new_text: Type2.String({ description: PARAM_DESCRIPTIONS.newTextInsert })
3487
+ insert_after: Type3.Optional(Type3.Object({
3488
+ anchor: Type3.String({ description: PARAM_DESCRIPTIONS.insertAnchor }),
3489
+ new_text: Type3.String({ description: PARAM_DESCRIPTIONS.newTextInsert })
3198
3490
  }, { additionalProperties: false, description: PARAM_DESCRIPTIONS.insertAfter })),
3199
- replace: Type2.Optional(Type2.Object({
3200
- old_text: Type2.String({ description: PARAM_DESCRIPTIONS.oldText }),
3201
- new_text: Type2.String({ description: PARAM_DESCRIPTIONS.newText }),
3202
- all: Type2.Optional(Type2.Boolean({ description: PARAM_DESCRIPTIONS.all }))
3491
+ replace: Type3.Optional(Type3.Object({
3492
+ old_text: Type3.String({ description: PARAM_DESCRIPTIONS.oldText }),
3493
+ new_text: Type3.String({ description: PARAM_DESCRIPTIONS.newText }),
3494
+ all: Type3.Optional(Type3.Boolean({ description: PARAM_DESCRIPTIONS.all }))
3203
3495
  }, { additionalProperties: false, description: PARAM_DESCRIPTIONS.replace })),
3204
- replace_symbol: Type2.Optional(Type2.Object({
3205
- symbol: Type2.String({ description: PARAM_DESCRIPTIONS.symbol }),
3206
- new_body: Type2.String({ description: PARAM_DESCRIPTIONS.newBody })
3496
+ replace_symbol: Type3.Optional(Type3.Object({
3497
+ symbol: Type3.String({ description: PARAM_DESCRIPTIONS.symbol }),
3498
+ new_body: Type3.String({ description: PARAM_DESCRIPTIONS.newBody })
3207
3499
  }, { additionalProperties: false, description: PARAM_DESCRIPTIONS.replaceSymbol }))
3208
3500
  }, {
3209
3501
  additionalProperties: false,
@@ -3211,18 +3503,18 @@ var hashlineEditItemSchema = Type2.Object({
3211
3503
  maxProperties: 1,
3212
3504
  description: PARAM_DESCRIPTIONS.editVariant
3213
3505
  });
3214
- var hashlineEditSchema = Type2.Object({
3506
+ var hashlineEditSchema = Type3.Object({
3215
3507
  path: filePathParam(),
3216
- language: Type2.Optional(Type2.String({ description: PARAM_DESCRIPTIONS.languageSymbol })),
3217
- edits: Type2.Array(hashlineEditItemSchema, {
3508
+ language: Type3.Optional(Type3.String({ description: PARAM_DESCRIPTIONS.languageSymbol })),
3509
+ edits: Type3.Array(hashlineEditItemSchema, {
3218
3510
  minItems: 1,
3219
3511
  description: PARAM_DESCRIPTIONS.edits
3220
3512
  }),
3221
- postEditVerify: Type2.Optional(Type2.Boolean({
3513
+ postEditVerify: Type3.Optional(Type3.Boolean({
3222
3514
  description: PARAM_DESCRIPTIONS.postEditVerify
3223
3515
  }))
3224
3516
  }, { additionalProperties: false });
3225
- function buildEditError(path3, code, message, hint, errorDetails) {
3517
+ function buildEditError(path, code, message, hint, errorDetails) {
3226
3518
  return {
3227
3519
  content: [{ type: "text", text: message }],
3228
3520
  isError: true,
@@ -3233,7 +3525,7 @@ function buildEditError(path3, code, message, hint, errorDetails) {
3233
3525
  readSeekValue: {
3234
3526
  tool: "edit",
3235
3527
  ok: false,
3236
- path: path3,
3528
+ path,
3237
3529
  error: buildReadSeekError(code, message, hint, errorDetails)
3238
3530
  }
3239
3531
  }
@@ -3247,14 +3539,14 @@ async function executeEdit(opts) {
3247
3539
  const { params, signal, cwd, wasReadInSession, onFileMutated, syntaxValidate } = opts;
3248
3540
  const parsed = params;
3249
3541
  const rawPath = parsed.path;
3250
- const path3 = rawPath.replace(/^@/, "");
3251
- const absolutePath = resolveToCwd2(path3, cwd);
3542
+ const path = rawPath.replace(/^@/, "");
3543
+ const absolutePath = resolveToCwd2(path, cwd);
3252
3544
  throwIfAborted(signal);
3253
3545
  try {
3254
3546
  return await withFileMutationQueue(absolutePath, async () => {
3255
3547
  throwIfAborted(signal);
3256
3548
  if (wasReadInSession && !wasReadInSession(absolutePath)) {
3257
- const hint = `Call readSeek_digest(${JSON.stringify(rawPath)}) first, or use readSeek_grep, readSeek_search, or readSeek_write to produce fresh anchors for this file.`;
3549
+ const hint = `Call readSeek_inspect(${JSON.stringify(rawPath)}) first, or use readSeek_grep, readSeek_search, or readSeek_write to produce fresh anchors for this file.`;
3258
3550
  return buildEditError(absolutePath, "file-not-read", `You must get fresh anchors for ${absolutePath} before editing it. ${hint}`, hint);
3259
3551
  }
3260
3552
  const parsedEdits = validateExactlyOneEditVariant(parsed.edits ?? []);
@@ -3279,7 +3571,7 @@ async function executeEdit(opts) {
3279
3571
  const originalForSyntax = normalizeToLF(stripBom(original).text);
3280
3572
  const resultForSyntax = normalizeToLF(stripBom(result).text);
3281
3573
  if (!plan.changed) {
3282
- return buildEditError(absolutePath, "no-op", `No changes made to ${path3}. The edits produced identical content.`);
3574
+ return buildEditError(absolutePath, "no-op", `No changes made to ${path}. The edits produced identical content.`);
3283
3575
  }
3284
3576
  const syntaxMode = resolveSyntaxValidateMode({ syntaxValidate });
3285
3577
  let syntaxWarning;
@@ -3320,14 +3612,14 @@ async function executeEdit(opts) {
3320
3612
  try {
3321
3613
  verifiedContent = await fsReadFile(absolutePath, "utf-8");
3322
3614
  } catch (err) {
3323
- return buildEditError(absolutePath, "post-edit-verification-read-failed", `Edit write completed but post-edit verification failed: could not read ${path3} after writing.`, undefined, { fsCode: err?.code, fsMessage: err?.message });
3615
+ return buildEditError(absolutePath, "post-edit-verification-read-failed", `Edit write completed but post-edit verification failed: could not read ${path} after writing.`, undefined, { fsCode: err?.code, fsMessage: err?.message });
3324
3616
  }
3325
3617
  if (verifiedContent !== plan.content) {
3326
- return buildEditError(absolutePath, "post-edit-verification-mismatch", `Edit write completed but post-edit verification did not confirm the intended content for ${path3}. Re-read the file before making follow-up edits.`, undefined, { expectedLength: plan.content.length, actualLength: verifiedContent.length });
3618
+ return buildEditError(absolutePath, "post-edit-verification-mismatch", `Edit write completed but post-edit verification did not confirm the intended content for ${path}. Re-read the file before making follow-up edits.`, undefined, { expectedLength: plan.content.length, actualLength: verifiedContent.length });
3327
3619
  }
3328
3620
  }
3329
3621
  const diffResult = generateCompactOrFullDiff(original, result);
3330
- const patch = createPatch(path3, original, result);
3622
+ const patch = createPatch(path, original, result);
3331
3623
  const diffData = buildDiffData({
3332
3624
  path: absolutePath,
3333
3625
  oldContent: original,
@@ -3339,7 +3631,7 @@ async function executeEdit(opts) {
3339
3631
  const semanticSummary = { classification: classification.classification };
3340
3632
  const builtOutput = buildEditOutput({
3341
3633
  path: absolutePath,
3342
- displayPath: path3,
3634
+ displayPath: path,
3343
3635
  diff: diffResult.diff,
3344
3636
  patch,
3345
3637
  diffData,
@@ -3362,7 +3654,7 @@ async function executeEdit(opts) {
3362
3654
  });
3363
3655
  } catch (err) {
3364
3656
  if (typeof err?.code === "string") {
3365
- return mapEditFileError(err, absolutePath, path3, "read");
3657
+ return mapEditFileError(err, absolutePath, path, "read");
3366
3658
  }
3367
3659
  throw err;
3368
3660
  }
@@ -3412,8 +3704,8 @@ function registerEditTool(pi, options = {}) {
3412
3704
  text = clampLineToWidth(text, context.width);
3413
3705
  return upsertTextComponent(context.lastComponent, text);
3414
3706
  },
3415
- renderResult(result, options2, theme, ...rest) {
3416
- const { isPartial, isError, expanded, width, context } = resolveRenderResultContext(options2, rest, resolveReadSeekToolDisplayMode("edit") === "expanded");
3707
+ renderResult(result, options, theme, ...rest) {
3708
+ const { isPartial, isError, expanded, width, context } = resolveRenderResultContext(options, rest, resolveReadSeekToolDisplayMode("edit") === "expanded");
3417
3709
  if (isPartial) {
3418
3710
  return renderPendingResult("pending edit", width, theme);
3419
3711
  }
@@ -3469,7 +3761,7 @@ ${theme.fg("error", info.errorText)}`;
3469
3761
  import { readFile as fsReadFile2, stat as fsStat } from "node:fs/promises";
3470
3762
  import path3 from "node:path";
3471
3763
  import { createGrepTool } from "@earendil-works/pi-coding-agent";
3472
- import { Type as Type4 } from "@sinclair/typebox";
3764
+ import { Type as Type5 } from "@sinclair/typebox";
3473
3765
  import { Text as Text4 } from "@earendil-works/pi-tui";
3474
3766
 
3475
3767
  // src/binary-detect.ts
@@ -3541,14 +3833,14 @@ function buildScopeMetadata(groups, warnings) {
3541
3833
  }
3542
3834
  function buildGrepOutput(input) {
3543
3835
  const fileCount = new Set(input.groups.map((group) => group.absolutePath)).size;
3544
- const header2 = `[${input.totalMatches} matches in ${fileCount} files]`;
3836
+ const header = `[${input.totalMatches} matches in ${fileCount} files]`;
3545
3837
  let text;
3546
3838
  if (input.summary) {
3547
3839
  const fileLines = [...input.groups].sort((a, b) => b.matchCount - a.matchCount).map((group) => `${group.absolutePath}: ${group.matchCount} matches`);
3548
- text = [header2, ...fileLines].join(`
3840
+ text = [header, ...fileLines].join(`
3549
3841
  `);
3550
3842
  } else {
3551
- const blocks = [header2];
3843
+ const blocks = [header];
3552
3844
  for (const group of input.groups) {
3553
3845
  blocks.push(renderGroupHeader(group));
3554
3846
  for (const entry of group.entries) {
@@ -3653,12 +3945,12 @@ function firstLineNumber(group) {
3653
3945
  }
3654
3946
  function buildSymbolEntries(fileLines, symbol, matchLines, scopeContext) {
3655
3947
  if (scopeContext === undefined) {
3656
- const entries2 = [];
3948
+ const entries = [];
3657
3949
  for (let lineNumber = symbol.startLine;lineNumber <= symbol.endLine; lineNumber++) {
3658
3950
  const built = buildReadSeekLine(lineNumber, fileLines[lineNumber - 1] ?? "");
3659
- entries2.push({ kind: matchLines.has(lineNumber) ? "match" : "context", line: built });
3951
+ entries.push({ kind: matchLines.has(lineNumber) ? "match" : "context", line: built });
3660
3952
  }
3661
- return entries2;
3953
+ return entries;
3662
3954
  }
3663
3955
  const ranges = [...matchLines].sort((a, b) => a - b).map((ln) => ({
3664
3956
  startLine: Math.max(symbol.startLine, ln - scopeContext),
@@ -3841,11 +4133,8 @@ import {
3841
4133
  } from "@jarkkojs/readseek-api";
3842
4134
 
3843
4135
  // src/tui-source-render.ts
3844
- import { getLanguageFromPath, highlightCode } from "@earendil-works/pi-coding-agent";
3845
- import { wrapTextWithAnsi as wrapTextWithAnsi2 } from "@earendil-works/pi-tui";
3846
4136
  var GREP_ANCHOR_RE = /^(.*?):(>>| )(\d+):([0-9a-zA-Z]{1,16})\|(.*)$/;
3847
- var readSourceCache = new WeakMap;
3848
- function renderGrepSourceForDisplay(text, anchors, renderPath = (path3) => path3) {
4137
+ function renderGrepSourceForDisplay(text, anchors, renderPath = (path) => path) {
3849
4138
  return text.split(`
3850
4139
  `).map((line) => {
3851
4140
  const match = line.match(GREP_ANCHOR_RE);
@@ -3861,39 +4150,30 @@ function renderGrepSourceForDisplay(text, anchors, renderPath = (path3) => path3
3861
4150
  }
3862
4151
 
3863
4152
  // src/readseek-params.ts
3864
- import { Type as Type3 } from "@sinclair/typebox";
4153
+ import { Type as Type4 } from "@sinclair/typebox";
3865
4154
  function searchPathParam() {
3866
- return Type3.Optional(Type3.String({ description: PARAM_DESCRIPTIONS.searchPath }));
4155
+ return Type4.Optional(Type4.String({ description: PARAM_DESCRIPTIONS.searchPath }));
3867
4156
  }
3868
4157
  function languageParam() {
3869
- return Type3.Optional(Type3.String({ description: PARAM_DESCRIPTIONS.language }));
4158
+ return Type4.Optional(Type4.String({ description: PARAM_DESCRIPTIONS.language }));
3870
4159
  }
3871
4160
  function readSeekGitSearchParams() {
3872
4161
  return {
3873
- cached: Type3.Optional(Type3.Boolean({ description: PARAM_DESCRIPTIONS.cached })),
3874
- others: Type3.Optional(Type3.Boolean({ description: PARAM_DESCRIPTIONS.others })),
3875
- ignored: Type3.Optional(Type3.Boolean({ description: PARAM_DESCRIPTIONS.ignored }))
4162
+ gitScope: Type4.Optional(Type4.Union(GIT_SCOPES.map((scope) => Type4.Literal(scope)), { description: PARAM_DESCRIPTIONS.gitScope }))
3876
4163
  };
3877
4164
  }
3878
- function validateIgnoredRequiresOthers(tool, params) {
3879
- const validated = validateGitSelection(params);
3880
- if (!validated.ok) {
3881
- return buildToolErrorResult(tool, "invalid-parameter", `${tool} parameter 'ignored' requires 'others'`);
3882
- }
3883
- return null;
3884
- }
3885
4165
 
3886
4166
  // src/grep.ts
3887
- var grepSchema = Type4.Object({
3888
- pattern: Type4.String({ description: PARAM_DESCRIPTIONS.grepPattern }),
4167
+ var grepSchema = Type5.Object({
4168
+ pattern: Type5.String({ description: PARAM_DESCRIPTIONS.grepPattern }),
3889
4169
  path: searchPathParam(),
3890
- glob: Type4.Optional(Type4.String({ description: PARAM_DESCRIPTIONS.glob })),
3891
- ignoreCase: Type4.Optional(Type4.Boolean({ description: PARAM_DESCRIPTIONS.ignoreCase })),
3892
- literal: Type4.Optional(Type4.Boolean({ description: PARAM_DESCRIPTIONS.literal })),
4170
+ glob: Type5.Optional(Type5.String({ description: PARAM_DESCRIPTIONS.glob })),
4171
+ ignoreCase: Type5.Optional(Type5.Boolean({ description: PARAM_DESCRIPTIONS.ignoreCase })),
4172
+ literal: Type5.Optional(Type5.Boolean({ description: PARAM_DESCRIPTIONS.literal })),
3893
4173
  context: optionalIntOrString(PARAM_DESCRIPTIONS.context),
3894
4174
  limit: optionalIntOrString(PARAM_DESCRIPTIONS.matchLimit),
3895
- summary: Type4.Optional(Type4.Boolean({ description: PARAM_DESCRIPTIONS.summary })),
3896
- scope: Type4.Optional(Type4.Literal("symbol", {
4175
+ summary: Type5.Optional(Type5.Boolean({ description: PARAM_DESCRIPTIONS.summary })),
4176
+ scope: Type5.Optional(Type5.Literal("symbol", {
3897
4177
  description: PARAM_DESCRIPTIONS.grepScope
3898
4178
  })),
3899
4179
  scopeContext: optionalIntOrString(PARAM_DESCRIPTIONS.scopeContext)
@@ -3965,12 +4245,6 @@ function truncateGroupEntries(group) {
3965
4245
  }
3966
4246
  return { ...group, entries: kept };
3967
4247
  }
3968
- function recordsFromGroups(groups) {
3969
- return groups.flatMap((group) => group.entries.flatMap((entry) => entry.kind === "separator" ? [] : [{ path: group.absolutePath, ...entry.line, kind: entry.kind }]));
3970
- }
3971
- function escapeForRegex(s) {
3972
- return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3973
- }
3974
4248
  async function executeGrep(opts) {
3975
4249
  const { toolCallId, params, signal, onUpdate, cwd, onFileAnchored } = opts;
3976
4250
  await ensureHashInit();
@@ -3979,10 +4253,16 @@ async function executeGrep(opts) {
3979
4253
  if (!context.ok) {
3980
4254
  return buildToolErrorResult("grep", "invalid-params-combo", context.message);
3981
4255
  }
4256
+ if (context.value !== undefined && context.value < 0) {
4257
+ return buildToolErrorResult("grep", "invalid-params-combo", `Invalid context: expected a non-negative integer, received ${context.value}.`);
4258
+ }
3982
4259
  const limit = coerceObviousBase10Int2(rawParams.limit, "limit");
3983
4260
  if (!limit.ok) {
3984
4261
  return buildToolErrorResult("grep", "invalid-limit", limit.message);
3985
4262
  }
4263
+ if (limit.value !== undefined && limit.value < 1) {
4264
+ return buildToolErrorResult("grep", "invalid-limit", `Invalid limit: expected a positive integer, received ${limit.value}.`);
4265
+ }
3986
4266
  const scopeContext = coerceObviousBase10Int2(rawParams.scopeContext, "scopeContext");
3987
4267
  if (!scopeContext.ok) {
3988
4268
  return buildToolErrorResult("grep", "invalid-params-combo", scopeContext.message);
@@ -4087,7 +4367,7 @@ async function executeGrep(opts) {
4087
4367
  const CONCURRENCY = 8;
4088
4368
  const pathList = [...pathsToRead];
4089
4369
  for (let i = 0;i < pathList.length; i += CONCURRENCY) {
4090
- await Promise.all(pathList.slice(i, i + CONCURRENCY).map((p2) => getFileLines(p2)));
4370
+ await Promise.all(pathList.slice(i, i + CONCURRENCY).map((p) => getFileLines(p)));
4091
4371
  }
4092
4372
  }
4093
4373
  const addSummaryMatch = (displayPath, absolutePath) => {
@@ -4140,7 +4420,7 @@ async function executeGrep(opts) {
4140
4420
  const flags = p.ignoreCase ? "i" : "";
4141
4421
  let patternRe = null;
4142
4422
  try {
4143
- patternRe = p.literal ? new RegExp(escapeForRegex(p.pattern), flags) : new RegExp(p.pattern, flags);
4423
+ patternRe = p.literal ? new RegExp(p.pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), flags) : new RegExp(p.pattern, flags);
4144
4424
  } catch {}
4145
4425
  if (patternRe !== null) {
4146
4426
  let emitted = false;
@@ -4224,7 +4504,7 @@ ${warning}` } : item),
4224
4504
  renderedGroups = scoped.groups;
4225
4505
  scopeWarnings = scoped.warnings;
4226
4506
  }
4227
- const readSeekRecords = recordsFromGroups(renderedGroups);
4507
+ const readSeekRecords = renderedGroups.flatMap((group) => group.entries.flatMap((entry) => entry.kind === "separator" ? [] : [{ path: group.absolutePath, ...entry.line, kind: entry.kind }]));
4228
4508
  const builtOutput = buildGrepOutput({
4229
4509
  summary: !!summary,
4230
4510
  totalMatches,
@@ -4295,8 +4575,8 @@ function registerGrepTool(pi, options = {}) {
4295
4575
  }
4296
4576
  return new Text4(clampLineToWidth(text, context.width), 0, 0);
4297
4577
  },
4298
- renderResult(result, options2, theme, ...rest) {
4299
- const { isPartial, isError, expanded, cwd, width } = resolveRenderResultContext(options2, rest, resolveReadSeekToolDisplayMode("grep") === "expanded");
4578
+ renderResult(result, options, theme, ...rest) {
4579
+ const { isPartial, isError, expanded, cwd, width } = resolveRenderResultContext(options, rest, resolveReadSeekToolDisplayMode("grep") === "expanded");
4300
4580
  if (isPartial)
4301
4581
  return renderPendingResult("pending grep", width, theme);
4302
4582
  const content = result.content?.[0];
@@ -4343,7 +4623,7 @@ function registerGrepTool(pi, options = {}) {
4343
4623
 
4344
4624
  // src/search.ts
4345
4625
  import path4 from "node:path";
4346
- import { Type as Type5 } from "@sinclair/typebox";
4626
+ import { Type as Type6 } from "@sinclair/typebox";
4347
4627
 
4348
4628
  // src/stat-search-path.ts
4349
4629
  import { stat as fsStat2 } from "node:fs/promises";
@@ -4351,9 +4631,9 @@ async function statSearchPathOrError(tool, rawPath, searchPath) {
4351
4631
  try {
4352
4632
  return { ok: true, stats: await fsStat2(searchPath) };
4353
4633
  } catch (err) {
4354
- const path4 = rawPath ?? searchPath;
4634
+ const path = rawPath ?? searchPath;
4355
4635
  const { code, message } = formatFsError(err, "stat-error");
4356
- return { ok: false, error: buildToolErrorResult(tool, code, message, { path: path4 }) };
4636
+ return { ok: false, error: buildToolErrorResult(tool, code, message, { path }) };
4357
4637
  }
4358
4638
  }
4359
4639
 
@@ -4434,9 +4714,6 @@ function readSeekLanguageForPath(language, searchPath, isFile) {
4434
4714
  async function executeSearch(opts) {
4435
4715
  const { params, signal, cwd, onFileAnchored } = opts;
4436
4716
  const p = params;
4437
- const ignoredError = validateIgnoredRequiresOthers("search", p);
4438
- if (ignoredError)
4439
- return ignoredError;
4440
4717
  const searchPath = resolveToCwd2(p.path ?? ".", cwd);
4441
4718
  const statResult = await statSearchPathOrError("search", p.path, searchPath);
4442
4719
  if (!statResult.ok)
@@ -4446,9 +4723,7 @@ async function executeSearch(opts) {
4446
4723
  const effectiveLang = readSeekLanguageForPath(p.language, searchPath, searchPathIsFile);
4447
4724
  const results = await readSeekSearch(searchPath, p.pattern, {
4448
4725
  language: effectiveLang,
4449
- cached: p.cached,
4450
- others: p.others,
4451
- ignored: p.ignored,
4726
+ gitScope: p.gitScope,
4452
4727
  signal
4453
4728
  });
4454
4729
  if (results.length === 0) {
@@ -4510,8 +4785,8 @@ function registerSearchTool(pi, options = {}) {
4510
4785
  description: SEARCH_PROMPT_METADATA.description,
4511
4786
  promptSnippet: SEARCH_PROMPT_METADATA.promptSnippet,
4512
4787
  promptGuidelines: SEARCH_PROMPT_METADATA.promptGuidelines,
4513
- parameters: Type5.Object({
4514
- pattern: Type5.String({ description: PARAM_DESCRIPTIONS.pattern }),
4788
+ parameters: Type6.Object({
4789
+ pattern: Type6.String({ description: PARAM_DESCRIPTIONS.pattern }),
4515
4790
  language: languageParam(),
4516
4791
  path: searchPathParam(),
4517
4792
  ...readSeekGitSearchParams()
@@ -4523,11 +4798,11 @@ function registerSearchTool(pi, options = {}) {
4523
4798
  return renderReadSeekSearchCall(args, theme, rest, {
4524
4799
  label: "search",
4525
4800
  accent: `/${args.pattern}/`,
4526
- flags: [args.cached && "cached", args.others && "others", args.ignored && "ignored"]
4801
+ flags: [args.gitScope && `git-scope=${args.gitScope}`]
4527
4802
  });
4528
4803
  },
4529
- renderResult(result, options2, theme, ...rest) {
4530
- return renderAnchoredFilesResult(result, options2, theme, rest, {
4804
+ renderResult(result, options, theme, ...rest) {
4805
+ return renderAnchoredFilesResult(result, options, theme, rest, {
4531
4806
  pendingLabel: "pending search",
4532
4807
  emptyLabel: "no matches",
4533
4808
  unitSingular: "match",
@@ -4539,7 +4814,7 @@ function registerSearchTool(pi, options = {}) {
4539
4814
  }
4540
4815
 
4541
4816
  // src/refs.ts
4542
- import { Type as Type6 } from "@sinclair/typebox";
4817
+ import { Type as Type7 } from "@sinclair/typebox";
4543
4818
  import path5 from "node:path";
4544
4819
 
4545
4820
  // src/refs-output.ts
@@ -4605,9 +4880,6 @@ function isReadSeekCursorValidationFailure(message) {
4605
4880
  async function executeRefs(opts) {
4606
4881
  const { params, signal, cwd, onFileAnchored } = opts;
4607
4882
  const p = params;
4608
- const ignoredError = validateIgnoredRequiresOthers("refs", p);
4609
- if (ignoredError)
4610
- return ignoredError;
4611
4883
  const scopeError = validateRefsNameVsScope(p);
4612
4884
  if (!scopeError.ok) {
4613
4885
  return buildToolErrorResult("refs", "invalid-parameter", `refs parameter: ${scopeError.error}`);
@@ -4623,9 +4895,7 @@ async function executeRefs(opts) {
4623
4895
  line: p.line,
4624
4896
  column: p.column,
4625
4897
  language: p.language,
4626
- cached: p.cached,
4627
- others: p.others,
4628
- ignored: p.ignored,
4898
+ gitScope: p.gitScope,
4629
4899
  signal
4630
4900
  });
4631
4901
  const files = groupReferences(references, cwd);
@@ -4658,13 +4928,13 @@ function registerRefsTool(pi, options = {}) {
4658
4928
  description: REFS_PROMPT_METADATA.description,
4659
4929
  promptSnippet: REFS_PROMPT_METADATA.promptSnippet,
4660
4930
  promptGuidelines: REFS_PROMPT_METADATA.promptGuidelines,
4661
- parameters: Type6.Object({
4662
- name: Type6.Optional(Type6.String({ description: PARAM_DESCRIPTIONS.refsName })),
4931
+ parameters: Type7.Object({
4932
+ name: Type7.Optional(Type7.String({ description: PARAM_DESCRIPTIONS.refsName })),
4663
4933
  path: searchPathParam(),
4664
4934
  language: languageParam(),
4665
- scope: Type6.Optional(Type6.Boolean({ description: PARAM_DESCRIPTIONS.scope })),
4666
- line: Type6.Optional(Type6.Number({ description: PARAM_DESCRIPTIONS.line })),
4667
- column: Type6.Optional(Type6.Number({ description: PARAM_DESCRIPTIONS.column })),
4935
+ scope: Type7.Optional(Type7.Boolean({ description: PARAM_DESCRIPTIONS.scope })),
4936
+ line: Type7.Optional(Type7.Number({ description: PARAM_DESCRIPTIONS.line })),
4937
+ column: Type7.Optional(Type7.Number({ description: PARAM_DESCRIPTIONS.column })),
4668
4938
  ...readSeekGitSearchParams()
4669
4939
  }),
4670
4940
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
@@ -4675,11 +4945,11 @@ function registerRefsTool(pi, options = {}) {
4675
4945
  return renderReadSeekSearchCall(args, theme, rest, {
4676
4946
  label: "refs",
4677
4947
  accent,
4678
- flags: [args.scope && "scope", args.cached && "cached", args.others && "others", args.ignored && "ignored"]
4948
+ flags: [args.scope && "scope", args.gitScope && `git-scope=${args.gitScope}`]
4679
4949
  });
4680
4950
  },
4681
- renderResult(result, options2, theme, ...rest) {
4682
- return renderAnchoredFilesResult(result, options2, theme, rest, {
4951
+ renderResult(result, options, theme, ...rest) {
4952
+ return renderAnchoredFilesResult(result, options, theme, rest, {
4683
4953
  pendingLabel: "pending refs",
4684
4954
  emptyLabel: "no references",
4685
4955
  unitSingular: "reference",
@@ -4691,20 +4961,20 @@ function registerRefsTool(pi, options = {}) {
4691
4961
  }
4692
4962
 
4693
4963
  // src/rename.ts
4694
- import { Type as Type7 } from "@sinclair/typebox";
4964
+ import { Type as Type8 } from "@sinclair/typebox";
4695
4965
  import path6 from "node:path";
4696
4966
  import { Text as Text5 } from "@earendil-works/pi-tui";
4697
4967
  var RENAME_PROMPT_METADATA = defineToolPromptMetadata({
4698
4968
  promptUrl: new URL("../prompts/rename.md", import.meta.url),
4699
4969
  promptSnippet: "Rename a symbol at a source location with verified edits"
4700
4970
  });
4701
- var renameSchema = Type7.Object({
4971
+ var renameSchema = Type8.Object({
4702
4972
  path: filePathParam(),
4703
- line: Type7.Integer({ minimum: 1, description: PARAM_DESCRIPTIONS.renameLine }),
4704
- column: Type7.Optional(Type7.Integer({ minimum: 1, description: PARAM_DESCRIPTIONS.column })),
4705
- to: Type7.String({ description: PARAM_DESCRIPTIONS.to }),
4706
- workspace: Type7.Optional(Type7.Boolean({ description: PARAM_DESCRIPTIONS.workspace })),
4707
- apply: Type7.Optional(Type7.Boolean({ description: PARAM_DESCRIPTIONS.applyDefaultTrue }))
4973
+ line: Type8.Integer({ minimum: 1, description: PARAM_DESCRIPTIONS.renameLine }),
4974
+ column: Type8.Optional(Type8.Integer({ minimum: 1, description: PARAM_DESCRIPTIONS.column })),
4975
+ to: Type8.String({ description: PARAM_DESCRIPTIONS.to }),
4976
+ workspace: Type8.Optional(Type8.Boolean({ description: PARAM_DESCRIPTIONS.workspace })),
4977
+ apply: Type8.Optional(Type8.Boolean({ description: PARAM_DESCRIPTIONS.applyDefaultTrue }))
4708
4978
  });
4709
4979
  async function executeRename(opts) {
4710
4980
  const { params, signal, cwd, onFileMutated } = opts;
@@ -4736,7 +5006,8 @@ async function executeRename(opts) {
4736
5006
  onFileMutated?.(absoluteFile);
4737
5007
  }
4738
5008
  }
4739
- const files = [output.file];
5009
+ const primaryAbs = path6.isAbsolute(output.file) ? output.file : path6.resolve(cwd, output.file);
5010
+ const files = [primaryAbs];
4740
5011
  for (const other of output.others) {
4741
5012
  const abs = path6.isAbsolute(other.file) ? other.file : path6.resolve(cwd, other.file);
4742
5013
  if (!files.includes(abs))
@@ -4796,8 +5067,8 @@ function registerRenameTool(pi, options = {}) {
4796
5067
  text += theme.fg("dim", ` → ${args.to}`);
4797
5068
  return new Text5(clampLineToWidth(text, context.width), 0, 0);
4798
5069
  },
4799
- renderResult(result, options2, theme, ...rest) {
4800
- const { isPartial, isError, expanded, cwd, width } = resolveRenderResultContext(options2, rest);
5070
+ renderResult(result, options, theme, ...rest) {
5071
+ const { isPartial, isError, expanded, cwd, width } = resolveRenderResultContext(options, rest);
4801
5072
  if (isPartial)
4802
5073
  return renderPendingResult("pending rename", width, theme);
4803
5074
  const content = result.content?.[0];
@@ -4838,7 +5109,7 @@ import { mkdir, readFile, writeFile } from "node:fs/promises";
4838
5109
  import { dirname as dirname2, relative as relative3 } from "node:path";
4839
5110
  import { withFileMutationQueue as withFileMutationQueue2, truncateHead as truncateHead2 } from "@earendil-works/pi-coding-agent";
4840
5111
  import { Text as Text6 } from "@earendil-works/pi-tui";
4841
- import { Type as Type8 } from "@sinclair/typebox";
5112
+ import { Type as Type9 } from "@sinclair/typebox";
4842
5113
 
4843
5114
  // src/pending-diff-preview.ts
4844
5115
  import { existsSync as existsSync3, readFileSync as readFileSync2, realpathSync, statSync as statSync4 } from "node:fs";
@@ -4985,9 +5256,9 @@ function pendingWritePreviewParts(summary, preview, expanded, theme) {
4985
5256
  if (!preview || preview.type !== "ok")
4986
5257
  return { lines: summary.split(`
4987
5258
  `) };
4988
- const hasOldSide2 = preview.data.fileExistedBeforeWrite;
5259
+ const hasOldSide = preview.data.fileExistedBeforeWrite;
4989
5260
  const headerLine = summaryLine(preview.data.headerLabel, { hidden: !expanded });
4990
- if (!hasOldSide2) {
5261
+ if (!hasOldSide) {
4991
5262
  const lines = [summary, headerLine];
4992
5263
  if (expanded)
4993
5264
  lines.push(...formatContentPreviewLines(preview.data.nextContent, theme));
@@ -5043,7 +5314,7 @@ async function executeWrite(opts) {
5043
5314
  const warnings = [];
5044
5315
  const readSeekWarnings = [];
5045
5316
  if (hasBareCarriageReturn(content)) {
5046
- const message = "File content contains bare CR (\\r) line endings; readSeek_write refuses to emit anchors that readSeek_digest/readSeek_edit would normalize differently.";
5317
+ const message = "File content contains bare CR (\\r) line endings; readSeek_write refuses to emit anchors that readSeek_inspect/readSeek_edit would normalize differently.";
5047
5318
  warnings.push(message);
5048
5319
  readSeekWarnings.push(buildReadSeekWarning("bare-cr", message));
5049
5320
  return buildToolErrorResult("write", "bare-cr", `Cannot write ${filePath}
@@ -5134,9 +5405,9 @@ function registerWriteTool(pi, options = {}) {
5134
5405
  description: promptMetadata.description,
5135
5406
  promptSnippet: promptMetadata.promptSnippet,
5136
5407
  promptGuidelines: promptMetadata.promptGuidelines,
5137
- parameters: Type8.Object({
5408
+ parameters: Type9.Object({
5138
5409
  path: filePathParam(),
5139
- content: Type8.String({ description: PARAM_DESCRIPTIONS.content })
5410
+ content: Type9.String({ description: PARAM_DESCRIPTIONS.content })
5140
5411
  }),
5141
5412
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
5142
5413
  const cwd = ctx?.cwd ?? process.cwd();
@@ -5174,13 +5445,13 @@ function registerWriteTool(pi, options = {}) {
5174
5445
  }
5175
5446
  },
5176
5447
  renderCall(args, theme, context = {}) {
5177
- const { path: path7, content } = args;
5448
+ const { path, content } = args;
5178
5449
  const cwd = context.cwd ?? process.cwd();
5179
5450
  const label = renderToolLabel(theme, "write");
5180
5451
  const lineCount = typeof content === "string" ? content.split(`
5181
5452
  `).length : 0;
5182
5453
  const bytes = typeof content === "string" ? Buffer.byteLength(content, "utf8") : 0;
5183
- const renderedPath = typeof path7 === "string" ? linkToolPath(theme.fg("muted", path7), path7, cwd) : theme.fg("toolOutput", "...");
5454
+ const renderedPath = typeof path === "string" ? linkToolPath(theme.fg("muted", path), path, cwd) : theme.fg("toolOutput", "...");
5184
5455
  let text = clampLineToWidth(`${label} ${renderedPath}${typeof content === "string" ? ` (${lineCount} ${lineCount === 1 ? "line" : "lines"} • ${bytes} B)` : ""}`, context.width);
5185
5456
  if (context.executionStarted) {
5186
5457
  return upsertTextComponent(context.lastComponent, text);
@@ -5195,8 +5466,8 @@ function registerWriteTool(pi, options = {}) {
5195
5466
  return upsertTextComponent(context.lastComponent, clampLinesToWidth(parts.lines, context.width).join(`
5196
5467
  `));
5197
5468
  },
5198
- renderResult(result, options2, theme, ...rest) {
5199
- const { isPartial, expanded, width, context } = resolveRenderResultContext(options2, rest, resolveReadSeekToolDisplayMode("write") === "expanded");
5469
+ renderResult(result, options, theme, ...rest) {
5470
+ const { isPartial, expanded, width, context } = resolveRenderResultContext(options, rest, resolveReadSeekToolDisplayMode("write") === "expanded");
5200
5471
  if (isPartial)
5201
5472
  return renderPendingResult("pending write", width, theme);
5202
5473
  const details = result.details ?? {};
@@ -5209,8 +5480,8 @@ function registerWriteTool(pi, options = {}) {
5209
5480
  if (state === "created") {
5210
5481
  const readSeekLines = details.readSeekValue?.lines ?? [];
5211
5482
  const hasContent = readSeekLines.length > 0;
5212
- const header2 = summaryLine(state, { hidden: hasContent && !expanded, theme, style: "success" });
5213
- const lines = header2.split(`
5483
+ const header = summaryLine(state, { hidden: hasContent && !expanded, theme, style: "success" });
5484
+ const lines = header.split(`
5214
5485
  `);
5215
5486
  if (expanded && hasContent) {
5216
5487
  const content = readSeekLines.map((l) => l.raw).join(`
@@ -5235,7 +5506,7 @@ function registerWriteTool(pi, options = {}) {
5235
5506
  }
5236
5507
 
5237
5508
  // src/def.ts
5238
- import { Type as Type9 } from "@sinclair/typebox";
5509
+ import { Type as Type10 } from "@sinclair/typebox";
5239
5510
  import path7 from "node:path";
5240
5511
  var DEF_PROMPT_METADATA = defineToolPromptMetadata({
5241
5512
  promptUrl: new URL("../prompts/def.md", import.meta.url),
@@ -5244,9 +5515,6 @@ var DEF_PROMPT_METADATA = defineToolPromptMetadata({
5244
5515
  async function executeDef(opts) {
5245
5516
  const { params, signal, cwd, onFileAnchored } = opts;
5246
5517
  const p = params;
5247
- const ignoredError = validateIgnoredRequiresOthers("def", p);
5248
- if (ignoredError)
5249
- return ignoredError;
5250
5518
  if (!p.name || !p.name.trim()) {
5251
5519
  return buildToolErrorResult("def", "invalid-parameter", "readSeek_def requires 'name'");
5252
5520
  }
@@ -5258,9 +5526,7 @@ async function executeDef(opts) {
5258
5526
  const definitions = await readSeekDef(searchPath, {
5259
5527
  name: p.name,
5260
5528
  language: p.language,
5261
- cached: p.cached,
5262
- others: p.others,
5263
- ignored: p.ignored,
5529
+ gitScope: p.gitScope,
5264
5530
  signal
5265
5531
  });
5266
5532
  if (definitions.length === 0) {
@@ -5311,8 +5577,8 @@ function registerDefTool(pi, options = {}) {
5311
5577
  description: DEF_PROMPT_METADATA.description,
5312
5578
  promptSnippet: DEF_PROMPT_METADATA.promptSnippet,
5313
5579
  promptGuidelines: DEF_PROMPT_METADATA.promptGuidelines,
5314
- parameters: Type9.Object({
5315
- name: Type9.String({ description: PARAM_DESCRIPTIONS.name }),
5580
+ parameters: Type10.Object({
5581
+ name: Type10.String({ description: PARAM_DESCRIPTIONS.name }),
5316
5582
  path: searchPathParam(),
5317
5583
  language: languageParam(),
5318
5584
  ...readSeekGitSearchParams()
@@ -5324,11 +5590,11 @@ function registerDefTool(pi, options = {}) {
5324
5590
  return renderReadSeekSearchCall(args, theme, rest, {
5325
5591
  label: "def",
5326
5592
  accent: args.name,
5327
- flags: [args.cached && "cached", args.others && "others", args.ignored && "ignored"]
5593
+ flags: [args.gitScope && `git-scope=${args.gitScope}`]
5328
5594
  });
5329
5595
  },
5330
- renderResult(result, options2, theme, ...rest) {
5331
- return renderAnchoredFilesResult(result, options2, theme, rest, {
5596
+ renderResult(result, options, theme, ...rest) {
5597
+ return renderAnchoredFilesResult(result, options, theme, rest, {
5332
5598
  pendingLabel: "pending def",
5333
5599
  emptyLabel: "no definitions",
5334
5600
  unitSingular: "definition",
@@ -5338,38 +5604,33 @@ function registerDefTool(pi, options = {}) {
5338
5604
  });
5339
5605
  }
5340
5606
 
5341
- // src/digest.ts
5607
+ // src/inspect.ts
5342
5608
  import { Text as Text7 } from "@earendil-works/pi-tui";
5343
- import { Type as Type10 } from "@sinclair/typebox";
5344
- var DIGEST_FACETS2 = DIGEST_FACETS;
5345
- var facetSchema = Type10.Union(DIGEST_FACETS2.map((facet) => Type10.Literal(facet)));
5346
- var digestSchema = Type10.Object({
5609
+ import { Type as Type11 } from "@sinclair/typebox";
5610
+ var facetSchema = Type11.Union(INSPECT_FACETS.map((facet) => Type11.Literal(facet)));
5611
+ var inspectSchema = Type11.Object({
5347
5612
  path: filePathParam(),
5348
- select: Type10.Optional(Type10.Union([
5613
+ selector: Type11.Optional(Type11.String({ description: PARAM_DESCRIPTIONS.selector })),
5614
+ select: Type11.Optional(Type11.Union([
5349
5615
  facetSchema,
5350
- Type10.Array(facetSchema, { minItems: 1 }),
5351
- Type10.String({
5352
- description: PARAM_DESCRIPTIONS.selectComma
5353
- })
5354
- ], {
5355
- description: PARAM_DESCRIPTIONS.select
5356
- })),
5357
- at: Type10.Optional(Type10.String({
5358
- description: PARAM_DESCRIPTIONS.at
5359
- })),
5616
+ Type11.Array(facetSchema, { minItems: 1 }),
5617
+ Type11.String({ description: PARAM_DESCRIPTIONS.selectComma })
5618
+ ], { description: PARAM_DESCRIPTIONS.select })),
5619
+ query: Type11.Optional(Type11.String({ description: PARAM_DESCRIPTIONS.query })),
5360
5620
  end: optionalIntOrString(PARAM_DESCRIPTIONS.end),
5361
5621
  limit: optionalIntOrString(PARAM_DESCRIPTIONS.limit),
5362
- language: Type10.Optional(Type10.String({ description: PARAM_DESCRIPTIONS.languageOverride })),
5363
- visionMode: Type10.Optional(Type10.Union(VISION_MODES.map((mode) => Type10.Literal(mode)), { description: PARAM_DESCRIPTIONS.visionModeDigest })),
5364
- visionLevel: Type10.Optional(Type10.Union(VISION_LEVELS.map((level) => Type10.Literal(level)), { description: PARAM_DESCRIPTIONS.visionLevel }))
5622
+ depth: optionalIntOrString(PARAM_DESCRIPTIONS.inspectDepth),
5623
+ language: Type11.Optional(Type11.String({ description: PARAM_DESCRIPTIONS.languageOverride })),
5624
+ visionMode: Type11.Optional(Type11.Union(VISION_MODES.map((mode) => Type11.Literal(mode)), { description: PARAM_DESCRIPTIONS.visionModeInspect })),
5625
+ visionLevel: Type11.Optional(Type11.Union(VISION_LEVELS.map((level) => Type11.Literal(level)), { description: PARAM_DESCRIPTIONS.visionLevel }))
5365
5626
  });
5366
- function hasDigestAnchors(value) {
5627
+ function hasInspectAnchors(value) {
5367
5628
  if (!value || typeof value !== "object")
5368
5629
  return false;
5369
5630
  const record = value;
5370
5631
  if (Array.isArray(record.hashlines) && record.hashlines.length > 0)
5371
5632
  return true;
5372
- return Object.values(record).some(hasDigestAnchors);
5633
+ return Object.values(record).some(hasInspectAnchors);
5373
5634
  }
5374
5635
  function parseSelect(select) {
5375
5636
  if (select === undefined)
@@ -5379,8 +5640,8 @@ function parseSelect(select) {
5379
5640
  if (facets.length === 0)
5380
5641
  throw new Error("select must include at least one facet");
5381
5642
  for (const facet of facets) {
5382
- if (!DIGEST_FACETS2.includes(facet)) {
5383
- throw new Error(`unknown digest facet \`${facet}\`; expected ${DIGEST_FACETS2.join(", ")}`);
5643
+ if (!INSPECT_FACETS.includes(facet)) {
5644
+ throw new Error(`unknown inspect facet \`${facet}\`; expected ${INSPECT_FACETS.join(", ")}`);
5384
5645
  }
5385
5646
  }
5386
5647
  return facets;
@@ -5388,15 +5649,15 @@ function parseSelect(select) {
5388
5649
  if (typeof select !== "string") {
5389
5650
  throw new Error("select must be a facet, facet list, or comma-separated facets");
5390
5651
  }
5391
- if (DIGEST_FACETS2.includes(select)) {
5652
+ if (INSPECT_FACETS.includes(select)) {
5392
5653
  return select;
5393
5654
  }
5394
5655
  const parts = select.split(",").map((part) => part.trim()).filter((part) => part.length > 0);
5395
5656
  if (parts.length === 0)
5396
5657
  throw new Error("select must include at least one facet");
5397
5658
  for (const part of parts) {
5398
- if (!DIGEST_FACETS2.includes(part)) {
5399
- throw new Error(`unknown digest facet \`${part}\`; expected ${DIGEST_FACETS2.join(", ")}`);
5659
+ if (!INSPECT_FACETS.includes(part)) {
5660
+ throw new Error(`unknown inspect facet \`${part}\`; expected ${INSPECT_FACETS.join(", ")}`);
5400
5661
  }
5401
5662
  }
5402
5663
  return parts;
@@ -5408,7 +5669,7 @@ function selectedFacets(select) {
5408
5669
  }
5409
5670
  function summarizeEnvelope(envelope, facets) {
5410
5671
  if (!envelope || typeof envelope !== "object")
5411
- return `digested ${facets.join(",")}`;
5672
+ return `inspected ${facets.join(",")}`;
5412
5673
  const record = envelope;
5413
5674
  const parts = [];
5414
5675
  for (const facet of facets) {
@@ -5417,7 +5678,7 @@ function summarizeEnvelope(envelope, facets) {
5417
5678
  }
5418
5679
  if (record.metadata !== undefined && !parts.includes("metadata"))
5419
5680
  parts.unshift("metadata");
5420
- return `digested ${parts.join(",") || "metadata"}`;
5681
+ return `inspected ${parts.join(",") || "metadata"}`;
5421
5682
  }
5422
5683
  function parseImageEnvelope(envelope) {
5423
5684
  if (!envelope || typeof envelope !== "object")
@@ -5435,12 +5696,12 @@ function parseImageEnvelope(envelope) {
5435
5696
  const prepared = content.encoding === "base64" && typeof content.data === "string" && typeof mime === "string" ? { data: content.data, mimeType: mime } : undefined;
5436
5697
  return { metadata, prepared };
5437
5698
  }
5438
- function buildDigestSuccess(filePath, facets, envelope, content = [{ type: "text", text: JSON.stringify(envelope) }]) {
5699
+ function buildInspectSuccess(filePath, facets, envelope, content = [{ type: "text", text: JSON.stringify(envelope) }]) {
5439
5700
  return {
5440
5701
  content,
5441
5702
  details: {
5442
5703
  readSeekValue: {
5443
- tool: "digest",
5704
+ tool: "inspect",
5444
5705
  ok: true,
5445
5706
  path: filePath,
5446
5707
  select: facets,
@@ -5449,14 +5710,14 @@ function buildDigestSuccess(filePath, facets, envelope, content = [{ type: "text
5449
5710
  }
5450
5711
  };
5451
5712
  }
5452
- async function executeDigest(opts) {
5713
+ async function executeInspect(opts) {
5453
5714
  const { params, signal, cwd, onFileAnchored } = opts;
5454
5715
  const p = params;
5455
5716
  let select;
5456
5717
  try {
5457
5718
  select = parseSelect(p.select);
5458
5719
  } catch (err) {
5459
- return buildToolErrorResult("digest", "invalid-parameter", err?.message ?? String(err), {
5720
+ return buildToolErrorResult("inspect", "invalid-parameter", err?.message ?? String(err), {
5460
5721
  path: p.path
5461
5722
  });
5462
5723
  }
@@ -5465,40 +5726,66 @@ async function executeDigest(opts) {
5465
5726
  const end = coerceObviousBase10Int2(p.end, "end");
5466
5727
  if (!end.ok || end.value !== undefined && end.value < 1) {
5467
5728
  const message = end.ok ? `Invalid end: expected a positive integer, received ${end.value}.` : end.message;
5468
- return buildToolErrorResult("digest", "invalid-parameter", message, { path: p.path });
5729
+ return buildToolErrorResult("inspect", "invalid-parameter", message, { path: p.path });
5469
5730
  }
5470
5731
  const limit = coerceObviousBase10Int2(p.limit, "limit");
5471
5732
  if (!limit.ok || limit.value !== undefined && limit.value < 1) {
5472
5733
  const message = limit.ok ? `Invalid limit: expected a positive integer, received ${limit.value}.` : limit.message;
5473
- return buildToolErrorResult("digest", "invalid-parameter", message, { path: p.path });
5734
+ return buildToolErrorResult("inspect", "invalid-parameter", message, { path: p.path });
5474
5735
  }
5475
- const at = typeof p.at === "string" ? p.at.trim() : undefined;
5476
- if (p.at !== undefined && !at) {
5477
- return buildToolErrorResult("digest", "invalid-parameter", "Invalid at: expected a non-empty location.", {
5478
- path: p.path
5479
- });
5736
+ if (end.value !== undefined && limit.value !== undefined) {
5737
+ return buildToolErrorResult("inspect", "invalid-params-combo", "end and limit cannot be combined.", { path: p.path });
5480
5738
  }
5481
- if (p.visionLevel !== undefined && p.visionMode === undefined && !appliesImagePolicy) {
5482
- return buildToolErrorResult("digest", "invalid-params-combo", "visionLevel requires visionMode.", {
5483
- path: p.path
5484
- });
5739
+ const depth = coerceObviousBase10Int2(p.depth, "depth");
5740
+ if (!depth.ok || depth.value !== undefined && depth.value < 0) {
5741
+ const message = depth.ok ? `Invalid depth: expected a non-negative integer, received ${depth.value}.` : depth.message;
5742
+ return buildToolErrorResult("inspect", "invalid-parameter", message, { path: p.path });
5743
+ }
5744
+ if (depth.value !== undefined && !facets.includes("map")) {
5745
+ return buildToolErrorResult("inspect", "invalid-parameter", "depth requires the map facet.", { path: p.path });
5746
+ }
5747
+ const selector = typeof p.selector === "string" ? p.selector.trim() : undefined;
5748
+ if (p.selector !== undefined && !selector) {
5749
+ return buildToolErrorResult("inspect", "invalid-parameter", "Invalid selector: expected a non-empty selector.", { path: p.path });
5750
+ }
5751
+ const query = typeof p.query === "string" ? p.query.trim() : undefined;
5752
+ if (p.query !== undefined && !query) {
5753
+ return buildToolErrorResult("inspect", "invalid-parameter", "Invalid query: expected a non-empty query.", { path: p.path });
5754
+ }
5755
+ if (selector !== undefined && query !== undefined) {
5756
+ return buildToolErrorResult("inspect", "invalid-params-combo", "selector and query cannot be combined.", { path: p.path });
5757
+ }
5758
+ if (facets.includes("identity") && selector === undefined) {
5759
+ return buildToolErrorResult("inspect", "invalid-params-combo", "the identity facet requires a selector.", { path: p.path });
5760
+ }
5761
+ if (p.visionLevel !== undefined && (p.visionMode === undefined || p.visionMode === "none") && !appliesImagePolicy) {
5762
+ return buildToolErrorResult("inspect", "invalid-params-combo", "visionLevel requires an analysis visionMode.", { path: p.path });
5485
5763
  }
5486
5764
  const filePath = resolveToCwd2(p.path, cwd);
5487
- const commonOptions = { select, signal };
5488
- const textOptions = {
5765
+ const commonOptions = {
5766
+ select,
5767
+ signal,
5768
+ depth: depth.value ?? (facets.includes("map") ? DEFAULT_INSPECT_MAP_DEPTH : undefined)
5769
+ };
5770
+ const selectionOptions = {
5489
5771
  ...commonOptions,
5490
- at,
5772
+ selector,
5773
+ query,
5491
5774
  end: end.value,
5492
5775
  limit: limit.value,
5493
5776
  language: p.language
5494
5777
  };
5495
- const hasTextOptions = at !== undefined || end.value !== undefined || limit.value !== undefined || p.language !== undefined;
5778
+ const probeOptions = {
5779
+ ...commonOptions,
5780
+ select: [...new Set(["metadata", ...facets])].filter((facet) => facet !== "identity")
5781
+ };
5782
+ const hasSelectionOptions = selector !== undefined || query !== undefined || end.value !== undefined || limit.value !== undefined || p.language !== undefined;
5496
5783
  const hasVisionOptions = p.visionMode !== undefined || p.visionLevel !== undefined;
5497
5784
  const initialVisionMode = appliesImagePolicy ? undefined : p.visionMode;
5498
5785
  const initialVisionLevel = appliesImagePolicy ? undefined : p.visionLevel;
5499
5786
  try {
5500
- const envelope = await readSeekDigestNative(filePath, {
5501
- ...appliesImagePolicy ? commonOptions : textOptions,
5787
+ const envelope = await readSeekInspect(filePath, {
5788
+ ...appliesImagePolicy ? probeOptions : selectionOptions,
5502
5789
  visionMode: initialVisionMode,
5503
5790
  visionLevel: initialVisionLevel,
5504
5791
  vision: initialVisionMode !== undefined && initialVisionMode !== "none"
@@ -5507,60 +5794,60 @@ async function executeDigest(opts) {
5507
5794
  if (image) {
5508
5795
  const imageDetails = { metadata: image.metadata };
5509
5796
  if (opts.imageMode === "off") {
5510
- return buildDigestSuccess(filePath, facets, imageDetails, [
5797
+ return buildInspectSuccess(filePath, facets, imageDetails, [
5511
5798
  { type: "text", text: `[Skipped standalone image: ${p.path}; readseek.imageMode is off]` }
5512
5799
  ]);
5513
5800
  }
5514
5801
  if (opts.imageMode === "auto" && opts.modelSupportsImages && image.prepared) {
5515
- return buildDigestSuccess(filePath, facets, imageDetails, [
5802
+ return buildInspectSuccess(filePath, facets, imageDetails, [
5516
5803
  { type: "image", data: image.prepared.data, mimeType: image.prepared.mimeType }
5517
5804
  ]);
5518
5805
  }
5519
5806
  const visionMode = p.visionMode && p.visionMode !== "none" ? p.visionMode : "all";
5520
- const analyzedEnvelope = await readSeekDigestNative(filePath, {
5807
+ const analyzedEnvelope = await readSeekInspect(filePath, {
5521
5808
  ...commonOptions,
5522
5809
  visionMode,
5523
5810
  visionLevel: p.visionLevel,
5524
5811
  vision: true
5525
5812
  });
5526
- return buildDigestSuccess(filePath, facets, analyzedEnvelope);
5813
+ return buildInspectSuccess(filePath, facets, analyzedEnvelope);
5527
5814
  }
5528
- if (p.visionLevel !== undefined && p.visionMode === undefined) {
5529
- return buildToolErrorResult("digest", "invalid-params-combo", "visionLevel requires visionMode.", {
5815
+ if (p.visionLevel !== undefined && (p.visionMode === undefined || p.visionMode === "none")) {
5816
+ return buildToolErrorResult("inspect", "invalid-params-combo", "visionLevel requires an analysis visionMode.", {
5530
5817
  path: p.path
5531
5818
  });
5532
5819
  }
5533
- const textEnvelope = appliesImagePolicy && (hasTextOptions || hasVisionOptions) ? await readSeekDigestNative(filePath, {
5534
- ...textOptions,
5820
+ const selectedEnvelope = appliesImagePolicy && (hasSelectionOptions || hasVisionOptions) ? await readSeekInspect(filePath, {
5821
+ ...selectionOptions,
5535
5822
  visionMode: p.visionMode,
5536
5823
  visionLevel: p.visionLevel,
5537
5824
  vision: p.visionMode !== undefined && p.visionMode !== "none"
5538
5825
  }) : envelope;
5539
- if (hasDigestAnchors(textEnvelope))
5826
+ if (hasInspectAnchors(selectedEnvelope))
5540
5827
  onFileAnchored?.(filePath);
5541
- return buildDigestSuccess(filePath, facets, textEnvelope);
5828
+ return buildInspectSuccess(filePath, facets, selectedEnvelope);
5542
5829
  } catch (err) {
5543
5830
  const failure = classifyReadSeekFailure(err);
5544
- return buildToolErrorResult("digest", failure.code, failure.message, failure.hint ? { hint: failure.hint } : {});
5831
+ return buildToolErrorResult("inspect", failure.code, failure.message, failure.hint ? { hint: failure.hint } : {});
5545
5832
  }
5546
5833
  }
5547
- function registerDigestTool(pi, options = {}) {
5834
+ function registerInspectTool(pi, options = {}) {
5548
5835
  const promptMetadata = defineToolPromptMetadata({
5549
- promptUrl: new URL("../prompts/digest.md", import.meta.url),
5550
- promptSnippet: "Digest source facets as a native CLI envelope",
5551
- registeredName: options.name ?? "readSeek_digest",
5836
+ promptUrl: new URL("../prompts/inspect.md", import.meta.url),
5837
+ promptSnippet: "Inspect source, images, and documents",
5838
+ registeredName: options.name ?? "readSeek_inspect",
5552
5839
  toolAliases: options.toolAliases
5553
5840
  });
5554
5841
  return registerReadSeekTool(pi, {
5555
- name: options.name ?? "readSeek_digest",
5556
- label: "Digest",
5842
+ name: options.name ?? "readSeek_inspect",
5843
+ label: "Inspect",
5557
5844
  description: promptMetadata.description,
5558
5845
  promptSnippet: promptMetadata.promptSnippet,
5559
5846
  toolAliases: options.toolAliases,
5560
5847
  promptGuidelines: promptMetadata.promptGuidelines,
5561
- parameters: digestSchema,
5848
+ parameters: inspectSchema,
5562
5849
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
5563
- return executeDigest({
5850
+ return executeInspect({
5564
5851
  params,
5565
5852
  signal,
5566
5853
  cwd: ctx.cwd,
@@ -5573,23 +5860,23 @@ function registerDigestTool(pi, options = {}) {
5573
5860
  const context = rest[0] ?? {};
5574
5861
  const cwd = context.cwd ?? process.cwd();
5575
5862
  const displayPath = typeof args?.path === "string" ? args.path : "?";
5576
- let text = theme.fg("toolTitle", theme.bold("digest"));
5863
+ let text = theme.fg("toolTitle", theme.bold("inspect"));
5577
5864
  text += ` ${linkToolPath(theme.fg("accent", displayPath), displayPath, cwd)}`;
5578
5865
  if (args?.select !== undefined) {
5579
5866
  const select = Array.isArray(args.select) ? args.select.join(",") : String(args.select);
5580
5867
  text += theme.fg("dim", ` [${select}]`);
5581
5868
  }
5582
- if (typeof args?.at === "string" && args.at)
5583
- text += theme.fg("dim", ` @ ${args.at}`);
5869
+ if (typeof args?.selector === "string" && args.selector)
5870
+ text += theme.fg("dim", ` @ ${args.selector}`);
5584
5871
  return new Text7(clampLineToWidth(text, context.width), 0, 0);
5585
5872
  },
5586
- renderResult(result, options2, theme, ...rest) {
5587
- const { isPartial, isError, expanded, width } = resolveRenderResultContext(options2, rest);
5873
+ renderResult(result, options, theme, ...rest) {
5874
+ const { isPartial, isError, expanded, width } = resolveRenderResultContext(options, rest);
5588
5875
  if (isPartial)
5589
- return renderPendingResult("pending digest", width, theme);
5876
+ return renderPendingResult("pending inspect", width, theme);
5590
5877
  const textContent = result.content?.[0]?.type === "text" ? result.content[0].text : "";
5591
5878
  if (isError || result.isError) {
5592
- return renderErrorResult(textContent, { expanded, width, fallback: "digest failed", theme });
5879
+ return renderErrorResult(textContent, { expanded, width, fallback: "inspect failed", theme });
5593
5880
  }
5594
5881
  const value = result.details?.readSeekValue;
5595
5882
  const facets = Array.isArray(value?.select) ? value.select : ["content"];
@@ -5605,131 +5892,88 @@ ${textContent}`;
5605
5892
  });
5606
5893
  }
5607
5894
 
5608
- // src/view.ts
5609
- import {
5610
- DEFAULT_MAX_BYTES as DEFAULT_MAX_BYTES2,
5611
- DEFAULT_MAX_LINES as DEFAULT_MAX_LINES2,
5612
- truncateHead as truncateHead3
5613
- } from "@earendil-works/pi-coding-agent";
5895
+ // src/extract.ts
5614
5896
  import { Text as Text8 } from "@earendil-works/pi-tui";
5615
- import { Type as Type11 } from "@sinclair/typebox";
5616
- var NODE_KINDS = DOCUMENT_NODE_KINDS;
5617
- var VIEW_PROMPT_METADATA = defineToolPromptMetadata({
5618
- promptUrl: new URL("../prompts/view.md", import.meta.url),
5619
- promptSnippet: "Digest a document's structure or selected content"
5897
+ import { Type as Type12 } from "@sinclair/typebox";
5898
+ var EXTRACT_PROMPT_METADATA = defineToolPromptMetadata({
5899
+ promptUrl: new URL("../prompts/extract.md", import.meta.url),
5900
+ promptSnippet: "Extract a selected document asset to a file"
5620
5901
  });
5621
- async function executeView(opts) {
5622
- const { params, signal, cwd } = opts;
5623
- const input = params;
5624
- const page = coerceObviousBase10Int2(input.page, "page");
5625
- if (!page.ok || page.value !== undefined && page.value < 1) {
5626
- const message = page.ok ? `Invalid page: expected a positive integer, received ${page.value}.` : page.message;
5627
- return buildToolErrorResult("view", "invalid-page", message, { path: input.path });
5628
- }
5629
- const depth = coerceObviousBase10Int2(input.depth, "depth");
5630
- if (!depth.ok || depth.value !== undefined && depth.value < 0) {
5631
- const message = depth.ok ? `Invalid depth: expected a non-negative integer, received ${depth.value}.` : depth.message;
5632
- return buildToolErrorResult("view", "invalid-depth", message, { path: input.path });
5633
- }
5634
- const node = input.node?.trim();
5635
- if (input.node !== undefined && !node) {
5636
- return buildToolErrorResult("view", "invalid-node", "Invalid node: expected a non-empty ID.", {
5902
+ async function executeExtract(opts) {
5903
+ const { signal, cwd, onFileMutated } = opts;
5904
+ const input = opts.params;
5905
+ const selector = typeof input.selector === "string" ? input.selector.trim() : "";
5906
+ if (!selector.startsWith("asset:") || selector === "asset:") {
5907
+ return buildToolErrorResult("extract", "invalid-selector", "selector must identify an asset as asset:ID.", {
5637
5908
  path: input.path
5638
5909
  });
5639
5910
  }
5640
- const visionValidation = validateVisionLevelVsMode({
5641
- outline: input.outline,
5642
- visionMode: input.visionMode,
5643
- visionLevel: input.visionLevel
5644
- });
5645
- if (!visionValidation.ok) {
5646
- return buildToolErrorResult("view", "invalid-params-combo", `${visionValidation.error}.`, {
5911
+ const output = typeof input.output === "string" ? input.output.trim() : "";
5912
+ if (!output || output === "-") {
5913
+ return buildToolErrorResult("extract", "invalid-output", "output must be a file path, not stdout.", {
5647
5914
  path: input.path
5648
5915
  });
5649
5916
  }
5650
5917
  const filePath = resolveToCwd2(input.path, cwd);
5918
+ const outputPath = resolveToCwd2(output, cwd);
5651
5919
  try {
5652
- const output = await readSeekView(filePath, {
5653
- node,
5654
- page: page.value,
5655
- kind: input.kind,
5656
- depth: depth.value,
5657
- outline: input.outline,
5658
- visionMode: input.visionMode,
5659
- visionLevel: input.visionLevel,
5660
- signal
5661
- });
5662
- const truncation = truncateHead3(output, {
5663
- maxLines: DEFAULT_MAX_LINES2,
5664
- maxBytes: DEFAULT_MAX_BYTES2
5665
- });
5666
- const notice = truncation.truncated ? `
5667
- [… document view truncated; narrow it with page, node, kind, or depth]` : "";
5920
+ await readSeekExtract(filePath, selector, outputPath, { signal, cancelable: false });
5921
+ onFileMutated?.(outputPath);
5668
5922
  return {
5669
- content: [{ type: "text", text: `${truncation.content}${notice}` }],
5923
+ content: [{ type: "text", text: `Extracted ${selector} to ${outputPath}` }],
5670
5924
  details: {
5671
5925
  readSeekValue: {
5672
- tool: "view",
5926
+ tool: "extract",
5927
+ ok: true,
5673
5928
  path: filePath,
5674
- truncated: truncation.truncated
5929
+ selector,
5930
+ output: outputPath
5675
5931
  }
5676
5932
  }
5677
5933
  };
5678
5934
  } catch (error) {
5679
5935
  const failure = classifyReadSeekFailure(error);
5680
- return buildToolErrorResult("view", failure.code, failure.message, failure.hint ? { hint: failure.hint } : {});
5936
+ return buildToolErrorResult("extract", failure.code, failure.message, failure.hint ? { hint: failure.hint } : {});
5681
5937
  }
5682
5938
  }
5683
- function registerViewTool(pi) {
5939
+ function registerExtractTool(pi, options = {}) {
5684
5940
  return registerReadSeekTool(pi, {
5685
- name: "readSeek_view",
5686
- label: "View",
5687
- description: VIEW_PROMPT_METADATA.description,
5688
- promptSnippet: VIEW_PROMPT_METADATA.promptSnippet,
5689
- promptGuidelines: VIEW_PROMPT_METADATA.promptGuidelines,
5690
- parameters: Type11.Object({
5941
+ name: "readSeek_extract",
5942
+ label: "Extract",
5943
+ ...EXTRACT_PROMPT_METADATA,
5944
+ parameters: Type12.Object({
5691
5945
  path: filePathParam(),
5692
- node: Type11.Optional(Type11.String({ description: PARAM_DESCRIPTIONS.node })),
5693
- page: optionalIntOrString(PARAM_DESCRIPTIONS.page),
5694
- kind: Type11.Optional(Type11.Union(NODE_KINDS.map((kind) => Type11.Literal(kind)), {
5695
- description: PARAM_DESCRIPTIONS.kind
5696
- })),
5697
- depth: optionalIntOrString(PARAM_DESCRIPTIONS.depth),
5698
- visionMode: Type11.Optional(Type11.Union(VISION_ANALYSIS_MODES.map((mode) => Type11.Literal(mode)), { description: PARAM_DESCRIPTIONS.visionModeView })),
5699
- visionLevel: Type11.Optional(Type11.Union(VISION_LEVELS.map((level) => Type11.Literal(level)), { description: PARAM_DESCRIPTIONS.visionLevel })),
5700
- outline: Type11.Optional(Type11.Boolean({ description: PARAM_DESCRIPTIONS.outline }))
5946
+ selector: Type12.String({ pattern: "^asset:.+", description: PARAM_DESCRIPTIONS.assetSelector }),
5947
+ output: Type12.String({ minLength: 1, pattern: "^(?!-$).+", description: PARAM_DESCRIPTIONS.output })
5701
5948
  }),
5702
5949
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
5703
- return executeView({ params, signal, cwd: ctx.cwd });
5950
+ return executeExtract({
5951
+ params,
5952
+ signal,
5953
+ cwd: ctx.cwd,
5954
+ onFileMutated: options.onFileMutated
5955
+ });
5704
5956
  },
5705
5957
  renderCall(args, theme, ...rest) {
5706
5958
  const context = rest[0] ?? {};
5707
5959
  const cwd = context.cwd ?? process.cwd();
5708
5960
  const displayPath = typeof args?.path === "string" ? args.path : "?";
5709
- const text = `${theme.fg("toolTitle", theme.bold("view"))} ${linkToolPath(theme.fg("accent", displayPath), displayPath, cwd)}`;
5961
+ const output = typeof args?.output === "string" ? args.output : "?";
5962
+ let text = `${theme.fg("toolTitle", theme.bold("extract"))} ${linkToolPath(theme.fg("accent", displayPath), displayPath, cwd)}`;
5963
+ if (typeof args?.selector === "string")
5964
+ text += theme.fg("dim", ` ${args.selector}`);
5965
+ text += theme.fg("dim", ` → ${output}`);
5710
5966
  return new Text8(clampLineToWidth(text, context.width), 0, 0);
5711
5967
  },
5712
5968
  renderResult(result, options, theme, ...rest) {
5713
- const { isPartial, isError, expanded, width } = resolveRenderResultContext(options, rest);
5969
+ const { isPartial, isError, width } = resolveRenderResultContext(options, rest);
5714
5970
  if (isPartial)
5715
- return renderPendingResult("pending view", width, theme);
5971
+ return renderPendingResult("pending extract", width, theme);
5716
5972
  const textContent = result.content?.[0]?.type === "text" ? result.content[0].text : "";
5717
5973
  if (isError || result.isError) {
5718
- return renderErrorResult(textContent, { expanded, width, fallback: "view failed", theme });
5974
+ return renderErrorResult(textContent, { expanded: false, width, fallback: "extract failed", theme });
5719
5975
  }
5720
- const lines = textContent.split(`
5721
- `).filter(Boolean).length;
5722
- let text = summaryLine(`loaded ${lines} document ${lines === 1 ? "line" : "lines"}`, {
5723
- hidden: !!textContent && !expanded,
5724
- theme,
5725
- style: "success"
5726
- });
5727
- if (expanded && textContent)
5728
- text += `
5729
- ${textContent}`;
5730
- return new Text8(clampLinesToWidth(text.split(`
5731
- `), width).join(`
5732
- `), 0, 0);
5976
+ return new Text8(clampLineToWidth(summaryLine(textContent || "asset extracted", { theme, style: "success" }), width), 0, 0);
5733
5977
  }
5734
5978
  });
5735
5979
  }
@@ -5760,32 +6004,20 @@ var READSEEK_TOOL_ENTRIES = [
5760
6004
  { builtIn: null, readSeekName: "readSeek_rename" },
5761
6005
  { builtIn: "write", readSeekName: "readSeek_write" },
5762
6006
  { builtIn: null, readSeekName: "readSeek_def" },
5763
- { builtIn: "read", readSeekName: "readSeek_digest" },
5764
- { builtIn: null, readSeekName: "readSeek_view" }
6007
+ { builtIn: "read", readSeekName: "readSeek_inspect" },
6008
+ { builtIn: null, readSeekName: "readSeek_extract" }
5765
6009
  ];
5766
- function formatSettingsWarning(warning) {
5767
- return `${warning.message} (${warning.source})`;
5768
- }
5769
- function toolRoutingPolicy(aliases) {
5770
- return renderToolRoutingPolicy({
5771
- aliases,
5772
- title: "ReadSeek tool policy:"
5773
- });
5774
- }
5775
6010
  function piReadSeekExtension(pi) {
5776
6011
  const sessionAnchors = new SessionAnchors;
5777
- const markAnchored = (absolutePath) => sessionAnchors.markAnchored(absolutePath);
5778
- const hasFreshAnchors = (absolutePath) => sessionAnchors.hasFreshAnchors(absolutePath);
5779
- const forgetAnchors = (absolutePath) => sessionAnchors.forget(absolutePath);
5780
6012
  const { settings } = resolveReadSeekJsonSettings();
5781
6013
  const replacedBuiltIns = new Set(settings.overrideTools ?? []);
5782
6014
  const swap = (builtIn, readSeekName) => replacedBuiltIns.has(builtIn) ? builtIn : readSeekName;
5783
- const readName = swap("read", "readSeek_digest");
6015
+ const readName = swap("read", "readSeek_inspect");
5784
6016
  const editName = swap("edit", "readSeek_edit");
5785
6017
  const writeName = swap("write", "readSeek_write");
5786
6018
  const grepName = swap("grep", "readSeek_grep");
5787
6019
  const toolAliases = {
5788
- readSeek_digest: readName,
6020
+ readSeek_inspect: readName,
5789
6021
  readSeek_edit: editName,
5790
6022
  readSeek_grep: grepName,
5791
6023
  readSeek_write: writeName
@@ -5794,20 +6026,33 @@ function piReadSeekExtension(pi) {
5794
6026
  const readSeekName = `readSeek_${name}`;
5795
6027
  return [name, toolAliases[readSeekName] ?? readSeekName];
5796
6028
  }));
5797
- registerEditTool(pi, { wasReadInSession: hasFreshAnchors, onFileMutated: forgetAnchors, name: editName, toolAliases });
5798
- registerGrepTool(pi, { onFileAnchored: markAnchored, name: grepName });
5799
- registerSearchTool(pi, { onFileAnchored: markAnchored });
5800
- registerRefsTool(pi, { onFileAnchored: markAnchored });
5801
- registerRenameTool(pi, { onFileMutated: forgetAnchors });
5802
- registerDefTool(pi, { onFileAnchored: markAnchored });
5803
- registerDigestTool(pi, {
5804
- onFileAnchored: markAnchored,
6029
+ registerEditTool(pi, {
6030
+ wasReadInSession: (absolutePath) => sessionAnchors.hasFreshAnchors(absolutePath),
6031
+ onFileMutated: (absolutePath) => sessionAnchors.forget(absolutePath),
6032
+ name: editName,
6033
+ toolAliases
6034
+ });
6035
+ registerGrepTool(pi, {
6036
+ onFileAnchored: (absolutePath) => sessionAnchors.markAnchored(absolutePath),
6037
+ name: grepName
6038
+ });
6039
+ registerSearchTool(pi, { onFileAnchored: (absolutePath) => sessionAnchors.markAnchored(absolutePath) });
6040
+ registerRefsTool(pi, { onFileAnchored: (absolutePath) => sessionAnchors.markAnchored(absolutePath) });
6041
+ registerRenameTool(pi, { onFileMutated: (absolutePath) => sessionAnchors.forget(absolutePath) });
6042
+ registerDefTool(pi, { onFileAnchored: (absolutePath) => sessionAnchors.markAnchored(absolutePath) });
6043
+ registerInspectTool(pi, {
6044
+ onFileAnchored: (absolutePath) => sessionAnchors.markAnchored(absolutePath),
5805
6045
  name: readName,
5806
6046
  toolAliases,
5807
6047
  imageMode: replacedBuiltIns.has("read") ? resolveReadSeekImageMode() : undefined
5808
6048
  });
5809
- registerViewTool(pi);
5810
- registerWriteTool(pi, { onFileAnchored: markAnchored, name: writeName });
6049
+ registerExtractTool(pi, {
6050
+ onFileMutated: (absolutePath) => sessionAnchors.forget(absolutePath)
6051
+ });
6052
+ registerWriteTool(pi, {
6053
+ onFileAnchored: (absolutePath) => sessionAnchors.markAnchored(absolutePath),
6054
+ name: writeName
6055
+ });
5811
6056
  const activeReadSeekNames = READSEEK_TOOL_ENTRIES.map((entry) => entry.builtIn && replacedBuiltIns.has(entry.builtIn) ? entry.builtIn : entry.readSeekName);
5812
6057
  const inactiveReadSeekNames = new Set(READSEEK_TOOL_ENTRIES.filter((entry) => entry.builtIn !== null && replacedBuiltIns.has(entry.builtIn)).map((entry) => entry.readSeekName));
5813
6058
  const activateReadSeekTools = () => {
@@ -5827,14 +6072,16 @@ function piReadSeekExtension(pi) {
5827
6072
  pi.on("before_agent_start", (event) => {
5828
6073
  if (!activateReadSeekTools())
5829
6074
  return;
5830
- return { systemPrompt: `${event.systemPrompt}
6075
+ return {
6076
+ systemPrompt: `${event.systemPrompt}
5831
6077
 
5832
- ${toolRoutingPolicy(policyAliases)}` };
6078
+ ${renderToolRoutingPolicy({ aliases: policyAliases, title: "ReadSeek tool policy:" })}`
6079
+ };
5833
6080
  });
5834
6081
  pi.on("session_start", (_event, ctx) => {
5835
6082
  sessionAnchors.clear();
5836
6083
  const { warnings } = resolveReadSeekJsonSettings();
5837
- const problems = warnings.map(formatSettingsWarning);
6084
+ const problems = warnings.map((warning) => `${warning.message} (${warning.source})`);
5838
6085
  const availability = readSeekBinaryAvailability();
5839
6086
  if (!availability.available) {
5840
6087
  problems.push(`readseek installation is broken: ${availability.reason}`);