pi-readseek 0.9.3 → 0.9.5

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,15 +2,800 @@
2
2
  import { readFile as fsReadFile } from "node:fs/promises";
3
3
  import {
4
4
  truncateHead as truncateHead2,
5
- DEFAULT_MAX_BYTES as DEFAULT_MAX_BYTES3,
6
- DEFAULT_MAX_LINES as DEFAULT_MAX_LINES3
5
+ DEFAULT_MAX_BYTES as DEFAULT_MAX_BYTES2,
6
+ DEFAULT_MAX_LINES as DEFAULT_MAX_LINES2
7
7
  } from "@earendil-works/pi-coding-agent";
8
8
  import { Type as Type2 } from "@sinclair/typebox";
9
9
  import { Text as Text2 } from "@earendil-works/pi-tui";
10
10
 
11
+ // ../readseek-api/src/contract.ts
12
+ var DIGEST_FACETS = [
13
+ "metadata",
14
+ "content",
15
+ "map",
16
+ "diagnostics",
17
+ "identity"
18
+ ];
19
+ var VISION_MODES = ["none", "all", "ocr", "caption", "objects"];
20
+ var VISION_ANALYSIS_MODES = ["all", "ocr", "caption", "objects"];
21
+ var VISION_LEVELS = ["low", "medium", "high"];
22
+ var DOCUMENT_NODE_KINDS = [
23
+ "artifact",
24
+ "footer",
25
+ "header",
26
+ "heading",
27
+ "marginal_label",
28
+ "page",
29
+ "page_number",
30
+ "paragraph",
31
+ "section",
32
+ "structural_section"
33
+ ];
34
+ var TOOL_NAMES = [
35
+ "read",
36
+ "edit",
37
+ "write",
38
+ "grep",
39
+ "digest",
40
+ "view",
41
+ "search",
42
+ "def",
43
+ "refs",
44
+ "rename"
45
+ ];
46
+ var PARAM_DESCRIPTIONS = {
47
+ path: "Absolute or relative file path",
48
+ searchPath: "File or directory to search; defaults to the current directory",
49
+ language: "Language override when auto-detection is ambiguous",
50
+ languageOverride: "Language override",
51
+ languageSymbol: "Language override for symbol replacement",
52
+ pattern: "AST pattern (ast-grep style), such as console.log($$$ARGS)",
53
+ grepPattern: "Regex pattern; set literal for exact text",
54
+ name: "Qualified or unqualified symbol name",
55
+ refsName: "Identifier to find references for",
56
+ scope: "Restrict results to the binding at the cursor",
57
+ line: "One-based cursor line; required with scope",
58
+ renameLine: "One-based cursor line of the symbol to rename",
59
+ column: "One-based cursor byte column for disambiguation",
60
+ cached: "In a Git repository, search tracked/indexed files",
61
+ others: "In a Git repository, search untracked files",
62
+ ignored: "With others=true, include ignored untracked files",
63
+ select: "Facets: metadata, content, map, diagnostics, identity (default content)",
64
+ selectComma: "Comma-separated facets: metadata, content, map, diagnostics, identity",
65
+ at: "Location: line:N[:COLUMN], hash:HASH[:COLUMN], or symbol:NAME",
66
+ end: "Last line to include",
67
+ limit: "Maximum number of lines to include",
68
+ offset: "One-based starting line",
69
+ readLimit: "Maximum lines to return",
70
+ symbolRead: "Symbol name to read",
71
+ bundle: "Include related symbols from the same file; requires symbol",
72
+ image: "Standalone image mode: none, all, ocr, caption, objects. Required for standalone images.",
73
+ visionModeDigest: "Standalone image mode: none, all, caption, objects, or ocr",
74
+ visionModeView: "Analyze selected assets: all, ocr, caption, or objects",
75
+ visionLevel: "Vision inference level: low (default), medium, or high",
76
+ node: "Node ID to use as the view root",
77
+ page: "One-based source page",
78
+ kind: "Node kind filter",
79
+ depth: "Maximum depth below selected roots",
80
+ outline: "Return outline nodes only",
81
+ edits: "Use set_line, replace_lines, insert_after, replace_symbol, or replace",
82
+ editVariant: "Exactly one nested edit variant",
83
+ setLine: 'Replace one line: {"set_line":{"anchor":"LINE:HASH","new_text":"..."}}',
84
+ replaceLines: 'Replace a range: {"replace_lines":{"start_anchor":"LINE:HASH","end_anchor":"LINE:HASH","new_text":"..."}}',
85
+ insertAfter: 'Insert text: {"insert_after":{"anchor":"LINE:HASH","new_text":"..."}}',
86
+ replace: 'Replace text: {"replace":{"old_text":"...","new_text":"..."}}',
87
+ replaceSymbol: 'Replace a symbol: {"replace_symbol":{"symbol":"name","new_body":"..."}}',
88
+ anchor: "Fresh LINE:HASH anchor for the line to replace",
89
+ startAnchor: "Fresh LINE:HASH anchor for the first line in the range",
90
+ endAnchor: "Fresh LINE:HASH anchor for the last line in the range",
91
+ insertAnchor: "Fresh LINE:HASH anchor for the line after which to insert",
92
+ newTextLine: "Replacement text; use an empty string to delete the line",
93
+ newTextRange: "Replacement text; use an empty string to delete the range",
94
+ newTextInsert: "Text to insert after the anchored line",
95
+ oldText: "Exact text to find",
96
+ newText: "Replacement text",
97
+ all: "Replace every exact match",
98
+ symbol: "Mapped symbol name",
99
+ newBody: "Complete replacement body for the symbol",
100
+ apply: "When supported by the host, apply the verified plan instead of previewing only",
101
+ planHash: "Require the apply plan to match this dry-run plan hash",
102
+ workspace: "Rename across the project",
103
+ to: "New symbol name",
104
+ postEditVerify: "Read back and verify persisted content",
105
+ content: "Complete text file content",
106
+ glob: "File-name glob, such as *.ts",
107
+ ignoreCase: "Ignore case",
108
+ literal: "Treat pattern literally",
109
+ context: "Surrounding lines for each match",
110
+ matchLimit: "Maximum matches to return",
111
+ summary: "Return per-file counts",
112
+ grepScope: "Group matches by enclosing symbol",
113
+ scopeContext: "Context lines within each symbol"
114
+ };
115
+ var TOOL_DESCRIPTIONS = {
116
+ read: "Read anchored text by range or symbol. Standalone images require an explicit mode. Use it before edits to obtain fresh `LINE:HASH` anchors; use `view` for documents and `digest` with the map facet for structural outlines.",
117
+ 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":"..."}}`.',
118
+ write: "Create or replace a complete text file and return `LINE:HASH` anchors. Use `edit` for small changes to an existing file.",
119
+ grep: "Search text or regex in files and return edit-ready `LINE:HASH` anchors. Use for identifiers, strings, configuration, errors, comments, and documentation.",
120
+ digest: "Digest source content and images into the native CLI envelope. Returns metadata plus requested facets unchanged from `readseek digest`; use for structural maps, diagnostics, and cursor identity.",
121
+ 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.",
122
+ 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.",
123
+ 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.",
124
+ 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.",
125
+ rename: "Rename a symbol at a source location. Resolvable same-named bindings are preserved; verified edits apply by default. Set `apply: false` to preview the plan before changing files."
126
+ };
127
+ function renderToolDescription(name, aliases = {}) {
128
+ return TOOL_NAMES.reduce((description, toolName) => description.replaceAll(`\`${toolName}\``, `\`${aliases[toolName] ?? toolName}\``), TOOL_DESCRIPTIONS[name]);
129
+ }
130
+ var gitFlagsSchema = {
131
+ cached: {
132
+ type: "boolean",
133
+ description: PARAM_DESCRIPTIONS.cached
134
+ },
135
+ others: {
136
+ type: "boolean",
137
+ description: PARAM_DESCRIPTIONS.others
138
+ },
139
+ ignored: {
140
+ type: "boolean",
141
+ description: PARAM_DESCRIPTIONS.ignored
142
+ }
143
+ };
144
+ var editItemSchema = {
145
+ type: "object",
146
+ additionalProperties: false,
147
+ minProperties: 1,
148
+ maxProperties: 1,
149
+ properties: {
150
+ set_line: {
151
+ type: "object",
152
+ additionalProperties: false,
153
+ required: ["anchor", "new_text"],
154
+ properties: {
155
+ anchor: { type: "string", description: PARAM_DESCRIPTIONS.anchor },
156
+ new_text: { type: "string", description: PARAM_DESCRIPTIONS.newTextLine }
157
+ },
158
+ description: PARAM_DESCRIPTIONS.setLine
159
+ },
160
+ replace_lines: {
161
+ type: "object",
162
+ additionalProperties: false,
163
+ required: ["start_anchor", "end_anchor", "new_text"],
164
+ properties: {
165
+ start_anchor: { type: "string", description: PARAM_DESCRIPTIONS.startAnchor },
166
+ end_anchor: { type: "string", description: PARAM_DESCRIPTIONS.endAnchor },
167
+ new_text: { type: "string", description: PARAM_DESCRIPTIONS.newTextRange }
168
+ },
169
+ description: PARAM_DESCRIPTIONS.replaceLines
170
+ },
171
+ insert_after: {
172
+ type: "object",
173
+ additionalProperties: false,
174
+ required: ["anchor", "new_text"],
175
+ properties: {
176
+ anchor: { type: "string", description: PARAM_DESCRIPTIONS.insertAnchor },
177
+ new_text: { type: "string", description: PARAM_DESCRIPTIONS.newTextInsert }
178
+ },
179
+ description: PARAM_DESCRIPTIONS.insertAfter
180
+ },
181
+ replace: {
182
+ type: "object",
183
+ additionalProperties: false,
184
+ required: ["old_text", "new_text"],
185
+ properties: {
186
+ old_text: { type: "string", description: PARAM_DESCRIPTIONS.oldText },
187
+ new_text: { type: "string", description: PARAM_DESCRIPTIONS.newText },
188
+ all: { type: "boolean", description: PARAM_DESCRIPTIONS.all }
189
+ },
190
+ description: PARAM_DESCRIPTIONS.replace
191
+ },
192
+ replace_symbol: {
193
+ type: "object",
194
+ additionalProperties: false,
195
+ required: ["symbol", "new_body"],
196
+ properties: {
197
+ symbol: { type: "string", description: PARAM_DESCRIPTIONS.symbol },
198
+ new_body: { type: "string", description: PARAM_DESCRIPTIONS.newBody }
199
+ },
200
+ description: PARAM_DESCRIPTIONS.replaceSymbol
201
+ }
202
+ },
203
+ description: PARAM_DESCRIPTIONS.editVariant
204
+ };
205
+ var TOOL_CONTRACTS = [
206
+ {
207
+ name: "read",
208
+ title: "Read anchored text",
209
+ description: TOOL_DESCRIPTIONS.read,
210
+ inputSchema: {
211
+ type: "object",
212
+ properties: {
213
+ path: { type: "string", description: PARAM_DESCRIPTIONS.path },
214
+ offset: { type: "integer", minimum: 1, description: PARAM_DESCRIPTIONS.offset },
215
+ limit: { type: "integer", minimum: 1, description: PARAM_DESCRIPTIONS.readLimit },
216
+ symbol: { type: "string", description: PARAM_DESCRIPTIONS.symbolRead },
217
+ bundle: {
218
+ type: "string",
219
+ enum: ["local"],
220
+ description: PARAM_DESCRIPTIONS.bundle
221
+ },
222
+ image: {
223
+ type: "string",
224
+ enum: [...VISION_MODES],
225
+ description: PARAM_DESCRIPTIONS.image
226
+ }
227
+ },
228
+ required: ["path"],
229
+ additionalProperties: false
230
+ }
231
+ },
232
+ {
233
+ name: "edit",
234
+ title: "Hash-verified edit",
235
+ description: TOOL_DESCRIPTIONS.edit,
236
+ inputSchema: {
237
+ type: "object",
238
+ properties: {
239
+ path: { type: "string", description: PARAM_DESCRIPTIONS.path },
240
+ edits: {
241
+ type: "array",
242
+ minItems: 1,
243
+ description: PARAM_DESCRIPTIONS.edits,
244
+ items: editItemSchema
245
+ },
246
+ language: { type: "string", description: PARAM_DESCRIPTIONS.languageSymbol },
247
+ apply: { type: "boolean", description: PARAM_DESCRIPTIONS.apply },
248
+ planHash: { type: "string", description: PARAM_DESCRIPTIONS.planHash },
249
+ postEditVerify: { type: "boolean", description: PARAM_DESCRIPTIONS.postEditVerify }
250
+ },
251
+ required: ["path", "edits"],
252
+ additionalProperties: false
253
+ }
254
+ },
255
+ {
256
+ name: "write",
257
+ title: "Write file",
258
+ description: TOOL_DESCRIPTIONS.write,
259
+ inputSchema: {
260
+ type: "object",
261
+ properties: {
262
+ path: { type: "string", description: PARAM_DESCRIPTIONS.path },
263
+ content: { type: "string", description: PARAM_DESCRIPTIONS.content }
264
+ },
265
+ required: ["path", "content"],
266
+ additionalProperties: false
267
+ }
268
+ },
269
+ {
270
+ name: "grep",
271
+ title: "Text search",
272
+ description: TOOL_DESCRIPTIONS.grep,
273
+ inputSchema: {
274
+ type: "object",
275
+ properties: {
276
+ pattern: { type: "string", description: PARAM_DESCRIPTIONS.grepPattern },
277
+ path: { type: "string", description: PARAM_DESCRIPTIONS.searchPath },
278
+ glob: { type: "string", description: PARAM_DESCRIPTIONS.glob },
279
+ ignoreCase: { type: "boolean", description: PARAM_DESCRIPTIONS.ignoreCase },
280
+ literal: { type: "boolean", description: PARAM_DESCRIPTIONS.literal },
281
+ context: { type: "integer", minimum: 0, description: PARAM_DESCRIPTIONS.context },
282
+ limit: { type: "integer", minimum: 1, description: PARAM_DESCRIPTIONS.matchLimit },
283
+ summary: { type: "boolean", description: PARAM_DESCRIPTIONS.summary },
284
+ scope: {
285
+ type: "string",
286
+ enum: ["symbol"],
287
+ description: PARAM_DESCRIPTIONS.grepScope
288
+ },
289
+ scopeContext: {
290
+ type: "integer",
291
+ minimum: 0,
292
+ description: PARAM_DESCRIPTIONS.scopeContext
293
+ }
294
+ },
295
+ required: ["pattern"],
296
+ additionalProperties: false
297
+ }
298
+ },
299
+ {
300
+ name: "digest",
301
+ title: "Digest source",
302
+ description: TOOL_DESCRIPTIONS.digest,
303
+ inputSchema: {
304
+ type: "object",
305
+ properties: {
306
+ path: { type: "string", description: PARAM_DESCRIPTIONS.path },
307
+ select: { type: "string", description: PARAM_DESCRIPTIONS.select },
308
+ at: { type: "string", description: PARAM_DESCRIPTIONS.at },
309
+ end: { type: "integer", minimum: 1, description: PARAM_DESCRIPTIONS.end },
310
+ limit: { type: "integer", minimum: 1, description: PARAM_DESCRIPTIONS.limit },
311
+ language: { type: "string", description: PARAM_DESCRIPTIONS.languageOverride },
312
+ visionMode: {
313
+ type: "string",
314
+ enum: [...VISION_MODES],
315
+ description: PARAM_DESCRIPTIONS.visionModeDigest
316
+ },
317
+ visionLevel: {
318
+ type: "string",
319
+ enum: [...VISION_LEVELS],
320
+ description: PARAM_DESCRIPTIONS.visionLevel
321
+ }
322
+ },
323
+ required: ["path"],
324
+ additionalProperties: false
325
+ }
326
+ },
327
+ {
328
+ name: "view",
329
+ title: "View document",
330
+ description: TOOL_DESCRIPTIONS.view,
331
+ inputSchema: {
332
+ type: "object",
333
+ properties: {
334
+ path: { type: "string", description: PARAM_DESCRIPTIONS.path },
335
+ node: { type: "string", description: PARAM_DESCRIPTIONS.node },
336
+ page: { type: "integer", minimum: 1, description: PARAM_DESCRIPTIONS.page },
337
+ kind: {
338
+ type: "string",
339
+ enum: [...DOCUMENT_NODE_KINDS],
340
+ description: PARAM_DESCRIPTIONS.kind
341
+ },
342
+ depth: { type: "integer", minimum: 0, description: PARAM_DESCRIPTIONS.depth },
343
+ outline: { type: "boolean", description: PARAM_DESCRIPTIONS.outline },
344
+ visionMode: {
345
+ type: "string",
346
+ enum: [...VISION_ANALYSIS_MODES],
347
+ description: PARAM_DESCRIPTIONS.visionModeView
348
+ },
349
+ visionLevel: {
350
+ type: "string",
351
+ enum: [...VISION_LEVELS],
352
+ description: PARAM_DESCRIPTIONS.visionLevel
353
+ }
354
+ },
355
+ required: ["path"],
356
+ additionalProperties: false
357
+ }
358
+ },
359
+ {
360
+ name: "search",
361
+ title: "AST search",
362
+ description: TOOL_DESCRIPTIONS.search,
363
+ inputSchema: {
364
+ type: "object",
365
+ properties: {
366
+ pattern: { type: "string", description: PARAM_DESCRIPTIONS.pattern },
367
+ path: { type: "string", description: PARAM_DESCRIPTIONS.searchPath },
368
+ language: { type: "string", description: PARAM_DESCRIPTIONS.language },
369
+ ...gitFlagsSchema
370
+ },
371
+ required: ["pattern"],
372
+ additionalProperties: false
373
+ }
374
+ },
375
+ {
376
+ name: "def",
377
+ title: "Find definition",
378
+ description: TOOL_DESCRIPTIONS.def,
379
+ inputSchema: {
380
+ type: "object",
381
+ properties: {
382
+ name: { type: "string", description: PARAM_DESCRIPTIONS.name },
383
+ path: { type: "string", description: PARAM_DESCRIPTIONS.searchPath },
384
+ language: { type: "string", description: PARAM_DESCRIPTIONS.language },
385
+ ...gitFlagsSchema
386
+ },
387
+ required: ["name"],
388
+ additionalProperties: false
389
+ }
390
+ },
391
+ {
392
+ name: "refs",
393
+ title: "Find references",
394
+ description: TOOL_DESCRIPTIONS.refs,
395
+ inputSchema: {
396
+ type: "object",
397
+ properties: {
398
+ name: { type: "string", description: PARAM_DESCRIPTIONS.refsName },
399
+ path: { type: "string", description: PARAM_DESCRIPTIONS.searchPath },
400
+ language: { type: "string", description: PARAM_DESCRIPTIONS.language },
401
+ scope: { type: "boolean", description: PARAM_DESCRIPTIONS.scope },
402
+ line: { type: "integer", minimum: 1, description: PARAM_DESCRIPTIONS.line },
403
+ column: { type: "integer", minimum: 1, description: PARAM_DESCRIPTIONS.column },
404
+ ...gitFlagsSchema
405
+ },
406
+ additionalProperties: false
407
+ }
408
+ },
409
+ {
410
+ name: "rename",
411
+ title: "Rename symbol",
412
+ description: TOOL_DESCRIPTIONS.rename,
413
+ inputSchema: {
414
+ type: "object",
415
+ properties: {
416
+ path: { type: "string", description: PARAM_DESCRIPTIONS.path },
417
+ line: { type: "integer", minimum: 1, description: PARAM_DESCRIPTIONS.renameLine },
418
+ column: { type: "integer", minimum: 1, description: PARAM_DESCRIPTIONS.column },
419
+ to: { type: "string", description: PARAM_DESCRIPTIONS.to },
420
+ workspace: { type: "boolean", description: PARAM_DESCRIPTIONS.workspace },
421
+ apply: { type: "boolean", description: PARAM_DESCRIPTIONS.apply },
422
+ planHash: { type: "string", description: PARAM_DESCRIPTIONS.planHash }
423
+ },
424
+ required: ["path", "line", "to"],
425
+ additionalProperties: false
426
+ }
427
+ }
428
+ ];
429
+ // ../readseek-api/src/validation.ts
430
+ function validateGitSelection(input) {
431
+ if (input.ignored && !input.others) {
432
+ return { ok: false, error: "ignored requires others=true" };
433
+ }
434
+ return { ok: true };
435
+ }
436
+ function validateRefsNameVsScope(input) {
437
+ if (input.scope) {
438
+ if (input.line === undefined) {
439
+ return { ok: false, error: "scope requires line" };
440
+ }
441
+ return { ok: true };
442
+ }
443
+ if (input.line !== undefined || input.column !== undefined) {
444
+ return { ok: false, error: "line/column require scope=true" };
445
+ }
446
+ if (typeof input.name !== "string" || input.name.length === 0) {
447
+ return { ok: false, error: "name is required when scope is not set" };
448
+ }
449
+ return { ok: true };
450
+ }
451
+ function validateVisionLevelVsMode(input) {
452
+ if (input.outline && (input.visionMode !== undefined || input.visionLevel !== undefined)) {
453
+ return { ok: false, error: "Cannot combine outline with visionMode or visionLevel" };
454
+ }
455
+ if (input.visionLevel !== undefined && input.visionMode === undefined) {
456
+ return { ok: false, error: "visionLevel requires visionMode" };
457
+ }
458
+ return { ok: true };
459
+ }
460
+ // ../readseek-api/src/argv.ts
461
+ function buildLineLocation(line, column) {
462
+ return column === undefined ? `line:${line}` : `line:${line}:${column}`;
463
+ }
464
+ function appendGitSelectionArgs(args, input) {
465
+ const validated = validateGitSelection(input);
466
+ if (!validated.ok) {
467
+ throw new Error(validated.error);
468
+ }
469
+ const selection = [
470
+ input.cached ? "cached" : undefined,
471
+ input.others ? "others" : undefined,
472
+ input.ignored ? "ignored" : undefined
473
+ ].filter((value) => value !== undefined);
474
+ if (selection.length > 0) {
475
+ args.push("--git", selection.join(","));
476
+ }
477
+ return args;
478
+ }
479
+ function pushOptionalString(args, flag, value) {
480
+ if (value !== undefined)
481
+ args.push(flag, value);
482
+ }
483
+ function pushOptionalNumber(args, flag, value) {
484
+ if (value !== undefined)
485
+ args.push(flag, String(value));
486
+ }
487
+ function appendApplyAndPlanHash(args, input) {
488
+ const order = input.flagOrder ?? "apply-first";
489
+ if (order === "plan-hash-first") {
490
+ pushOptionalString(args, "--plan-hash", input.planHash);
491
+ if (input.apply)
492
+ args.push("--apply");
493
+ return args;
494
+ }
495
+ if (input.apply)
496
+ args.push("--apply");
497
+ pushOptionalString(args, "--plan-hash", input.planHash);
498
+ return args;
499
+ }
500
+ function selectArg(select) {
501
+ if (select === undefined)
502
+ return;
503
+ return Array.isArray(select) ? select.join(",") : select;
504
+ }
505
+ function buildDigestArgs(input) {
506
+ const vision = validateVisionLevelVsMode({
507
+ visionMode: input.visionMode,
508
+ visionLevel: input.visionLevel
509
+ });
510
+ if (!vision.ok)
511
+ throw new Error(vision.error);
512
+ const args = [];
513
+ if (input.readseekDir !== undefined)
514
+ args.push("--readseek-dir", input.readseekDir);
515
+ args.push("digest", input.path);
516
+ const select = selectArg(input.select);
517
+ if (select !== undefined)
518
+ args.push("--select", select);
519
+ pushOptionalString(args, "--at", input.at);
520
+ pushOptionalNumber(args, "--end", input.end);
521
+ pushOptionalNumber(args, "--limit", input.limit);
522
+ pushOptionalString(args, "--language", input.language);
523
+ pushOptionalString(args, "--vision-mode", input.visionMode);
524
+ pushOptionalString(args, "--vision-level", input.visionLevel);
525
+ pushOptionalString(args, "--stdin-name", input.stdinName);
526
+ return args;
527
+ }
528
+ function buildViewArgs(input) {
529
+ const vision = validateVisionLevelVsMode({
530
+ visionMode: input.visionMode,
531
+ visionLevel: input.visionLevel,
532
+ outline: input.outline
533
+ });
534
+ if (!vision.ok)
535
+ throw new Error(vision.error);
536
+ const args = [];
537
+ if (input.readseekDir !== undefined)
538
+ args.push("--readseek-dir", input.readseekDir);
539
+ args.push("view", input.path);
540
+ if (input.format !== undefined)
541
+ args.push("--format", input.format);
542
+ pushOptionalString(args, "--node", input.node);
543
+ pushOptionalNumber(args, "--page", input.page);
544
+ pushOptionalString(args, "--kind", input.kind);
545
+ pushOptionalNumber(args, "--depth", input.depth);
546
+ if (input.outline)
547
+ args.push("--outline");
548
+ pushOptionalString(args, "--vision-mode", input.visionMode);
549
+ pushOptionalString(args, "--vision-level", input.visionLevel);
550
+ return args;
551
+ }
552
+ function buildSearchArgs(input) {
553
+ const args = ["search", input.path, input.pattern];
554
+ pushOptionalString(args, "--language", input.language);
555
+ return appendGitSelectionArgs(args, input);
556
+ }
557
+ function buildDefArgs(input) {
558
+ const args = ["def", input.path];
559
+ if (input.format !== undefined)
560
+ args.push("--format", input.format);
561
+ args.push(input.name);
562
+ pushOptionalString(args, "--language", input.language);
563
+ return appendGitSelectionArgs(args, input);
564
+ }
565
+ function buildRefsArgs(input) {
566
+ const validated = validateRefsNameVsScope(input);
567
+ if (!validated.ok)
568
+ throw new Error(validated.error);
569
+ const args = ["refs", input.path];
570
+ if (input.scope) {
571
+ args.push("--at", buildLineLocation(input.line, input.column));
572
+ } else if (input.name !== undefined) {
573
+ args.push(input.name);
574
+ }
575
+ if (input.format !== undefined)
576
+ args.push("--format", input.format);
577
+ pushOptionalString(args, "--language", input.language);
578
+ return appendGitSelectionArgs(args, input);
579
+ }
580
+ function buildEditArgs(input) {
581
+ const args = ["edit", input.path];
582
+ appendApplyAndPlanHash(args, {
583
+ apply: input.apply,
584
+ planHash: input.planHash,
585
+ flagOrder: input.flagOrder ?? "apply-first"
586
+ });
587
+ pushOptionalString(args, "--language", input.language);
588
+ if (input.requestFromStdin)
589
+ args.push("--request", "-");
590
+ return args;
591
+ }
592
+ function buildRenameArgs(input) {
593
+ const args = ["rename", input.path, "--at", buildLineLocation(input.line, input.column), "--to", input.to];
594
+ if (input.workspace !== undefined)
595
+ args.push("--workspace", input.workspace);
596
+ appendApplyAndPlanHash(args, {
597
+ apply: input.apply,
598
+ planHash: input.planHash,
599
+ flagOrder: input.flagOrder ?? "plan-hash-first"
600
+ });
601
+ pushOptionalString(args, "--language", input.language);
602
+ return appendGitSelectionArgs(args, input);
603
+ }
604
+ // ../readseek-api/src/platform.ts
605
+ import { createRequire } from "node:module";
606
+ import path from "node:path";
607
+ var READSEEK_PLATFORM_PACKAGES = {
608
+ "darwin-arm64": "@jarkkojs/readseek-darwin-arm64",
609
+ "linux-arm64": "@jarkkojs/readseek-linux-arm64",
610
+ "linux-x64": "@jarkkojs/readseek-linux-x64",
611
+ "win32-x64": "@jarkkojs/readseek-win32-x64"
612
+ };
613
+ var resolveFromPackage = createRequire(import.meta.url).resolve;
614
+ function readSeekBinaryName(platform = process.platform) {
615
+ return platform === "win32" ? "readseek.exe" : "readseek";
616
+ }
617
+ function readSeekPlatformKey(platform = process.platform, arch = process.arch) {
618
+ return `${platform}-${arch}`;
619
+ }
620
+ function resolveReadSeekBinaryFromPackages(resolver = resolveFromPackage, options = {}) {
621
+ const resolve = typeof resolver === "function" ? resolver : resolver.resolve.bind(resolver);
622
+ const platform = options.platform ?? process.platform;
623
+ const arch = options.arch ?? process.arch;
624
+ const key = readSeekPlatformKey(platform, arch);
625
+ const platformPackage = READSEEK_PLATFORM_PACKAGES[key];
626
+ if (!platformPackage) {
627
+ const supported = Object.keys(READSEEK_PLATFORM_PACKAGES).join(", ");
628
+ throw new Error(`@jarkkojs/readseek ships no binary for ${key}; it supports ${supported}`);
629
+ }
630
+ const metaPackage = options.metaPackage ?? "@jarkkojs/readseek";
631
+ const metaPackageJson = resolve(`${metaPackage}/package.json`);
632
+ const metaPackageDir = path.dirname(metaPackageJson);
633
+ const packageJsonPath = resolve(`${platformPackage}/package.json`, {
634
+ paths: [metaPackageDir]
635
+ });
636
+ return path.join(path.dirname(packageJsonPath), "bin", readSeekBinaryName(platform));
637
+ }
638
+ // ../readseek-api/src/json.ts
639
+ var READSEEK_USAGE_HINT = /\n\s*Run readseek(?: [\w-]+)* --help for more information\.?\s*$/;
640
+ function cleanReadSeekErrorText(stderr) {
641
+ return stderr.replace(READSEEK_USAGE_HINT, "").replace(/^error:\s*/i, "").replace(/\s*\n\s*/g, " ").trim();
642
+ }
643
+ function parseReadSeekJson(stdout) {
644
+ try {
645
+ return JSON.parse(stdout);
646
+ } catch {
647
+ const preview = stdout.trim().slice(0, 200);
648
+ throw new Error(`readseek returned invalid JSON: ${preview}`);
649
+ }
650
+ }
651
+ // ../readseek-api/src/process.ts
652
+ var DEFAULT_READSEEK_TIMEOUT_MS = 120000;
653
+ var DEFAULT_MAX_OUTPUT_BYTES = 32 * 1024 * 1024;
654
+ function resolveProcessLimits(options = {}) {
655
+ return {
656
+ timeoutMs: options.timeoutMs ?? DEFAULT_READSEEK_TIMEOUT_MS,
657
+ maxOutputBytes: options.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES,
658
+ cancelable: options.cancelable !== false
659
+ };
660
+ }
661
+ function readSeekCancellationError(signal) {
662
+ const reason = signal?.reason;
663
+ return reason instanceof Error && reason.name !== "AbortError" ? reason : new Error("readseek cancelled");
664
+ }
665
+ function failureFromProcessResult(result, options = {}) {
666
+ if (options.overflow) {
667
+ return new Error(`readseek ${options.overflow} exceeded the ${options.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES}-byte output limit`);
668
+ }
669
+ if (options.timedOut) {
670
+ return new Error(`readseek timed out after ${options.timeoutMs ?? DEFAULT_READSEEK_TIMEOUT_MS} ms`);
671
+ }
672
+ const cleaned = cleanReadSeekErrorText(result.stderr);
673
+ if (cleaned)
674
+ return new Error(cleaned);
675
+ if (result.signal)
676
+ return new Error(`readseek killed by signal ${result.signal}`);
677
+ return new Error(`readseek exited with status ${result.exitCode}`);
678
+ }
679
+ function ensureSuccessfulStdout(result) {
680
+ if (result.exitCode === 0)
681
+ return result.stdout;
682
+ throw failureFromProcessResult(result);
683
+ }
684
+ function parseSuccessfulJson(result) {
685
+ return parseReadSeekJson(ensureSuccessfulStdout(result));
686
+ }
687
+ // ../readseek-api/src/process-node.ts
688
+ import { spawn } from "node:child_process";
689
+ import { StringDecoder } from "node:string_decoder";
690
+ function attachBoundedStream(stream, maxOutputBytes, onOverflow) {
691
+ const decoder = new StringDecoder("utf8");
692
+ let text = "";
693
+ let bytes = 0;
694
+ let overflowed = false;
695
+ stream.on("data", (chunk) => {
696
+ if (overflowed)
697
+ return;
698
+ const buffer = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
699
+ bytes += buffer.length;
700
+ if (bytes > maxOutputBytes) {
701
+ overflowed = true;
702
+ onOverflow();
703
+ return;
704
+ }
705
+ text += decoder.write(buffer);
706
+ });
707
+ return {
708
+ text: () => text + decoder.end()
709
+ };
710
+ }
711
+ async function runReadSeekNode(binaryPath, args, options = {}) {
712
+ const limits = resolveProcessLimits(options);
713
+ if (limits.cancelable && options.signal?.aborted) {
714
+ throw readSeekCancellationError(options.signal);
715
+ }
716
+ return await new Promise((resolve, reject) => {
717
+ let settled = false;
718
+ let child;
719
+ let timer;
720
+ const finish = (result) => {
721
+ if (settled)
722
+ return;
723
+ settled = true;
724
+ if (timer !== undefined)
725
+ clearTimeout(timer);
726
+ options.signal?.removeEventListener("abort", onAbort);
727
+ resolve(result);
728
+ };
729
+ const fail = (error) => {
730
+ if (settled)
731
+ return;
732
+ settled = true;
733
+ if (timer !== undefined)
734
+ clearTimeout(timer);
735
+ options.signal?.removeEventListener("abort", onAbort);
736
+ reject(error instanceof Error ? error : new Error(String(error)));
737
+ };
738
+ const terminate = (error) => {
739
+ if (settled)
740
+ return;
741
+ fail(error);
742
+ try {
743
+ child.kill("SIGKILL");
744
+ } catch {}
745
+ };
746
+ const onAbort = () => {
747
+ terminate(readSeekCancellationError(options.signal));
748
+ };
749
+ try {
750
+ child = spawn(binaryPath, [...args], {
751
+ cwd: options.cwd,
752
+ env: process.env,
753
+ shell: false,
754
+ stdio: [options.stdin === undefined ? "ignore" : "pipe", "pipe", "pipe"],
755
+ windowsHide: true
756
+ });
757
+ } catch (error) {
758
+ fail(error);
759
+ return;
760
+ }
761
+ const stdout = attachBoundedStream(child.stdout, limits.maxOutputBytes, () => {
762
+ terminate(failureFromProcessResult({ stdout: "", stderr: "", exitCode: null, signal: null }, { overflow: "stdout", maxOutputBytes: limits.maxOutputBytes }));
763
+ });
764
+ const stderr = attachBoundedStream(child.stderr, limits.maxOutputBytes, () => {
765
+ terminate(failureFromProcessResult({ stdout: "", stderr: "", exitCode: null, signal: null }, { overflow: "stderr", maxOutputBytes: limits.maxOutputBytes }));
766
+ });
767
+ timer = setTimeout(() => {
768
+ terminate(failureFromProcessResult({ stdout: "", stderr: "", exitCode: null, signal: null }, { timedOut: true, timeoutMs: limits.timeoutMs }));
769
+ }, limits.timeoutMs);
770
+ timer.unref?.();
771
+ if (limits.cancelable && options.signal) {
772
+ options.signal.addEventListener("abort", onAbort, { once: true });
773
+ }
774
+ if (options.stdin !== undefined) {
775
+ child.stdin.on("error", (error) => {
776
+ if (error.code !== "EPIPE")
777
+ fail(error);
778
+ });
779
+ child.stdin.end(options.stdin, "utf8");
780
+ }
781
+ child.on("error", fail);
782
+ child.on("close", (code, signal) => {
783
+ finish({
784
+ stdout: stdout.text(),
785
+ stderr: stderr.text(),
786
+ exitCode: code,
787
+ signal
788
+ });
789
+ });
790
+ });
791
+ }
792
+ async function runReadSeekNodeText(binaryPath, args, options = {}) {
793
+ return ensureSuccessfulStdout(await runReadSeekNode(binaryPath, args, options));
794
+ }
795
+ async function runReadSeekNodeJson(binaryPath, args, options = {}) {
796
+ return parseSuccessfulJson(await runReadSeekNode(binaryPath, args, options));
797
+ }
11
798
  // src/tool-prompt-metadata.ts
12
- import { readFileSync } from "node:fs";
13
- import { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, formatSize } from "@earendil-works/pi-coding-agent";
14
799
  var REPLACEABLE_TOOL_GUIDELINES = {
15
800
  "read.md": {
16
801
  readSeekName: "readSeek_read",
@@ -56,11 +841,11 @@ var COMPACT_GUIDELINES = {
56
841
  "Use readSeek_refs before changing a symbol; add scope plus line/column to follow one binding."
57
842
  ]
58
843
  };
59
- function loadPrompt(promptUrl) {
60
- return readFileSync(promptUrl, "utf-8").replaceAll("{{DEFAULT_MAX_LINES}}", String(DEFAULT_MAX_LINES)).replaceAll("{{DEFAULT_MAX_BYTES}}", formatSize(DEFAULT_MAX_BYTES)).trim();
61
- }
62
- function firstPromptParagraph(prompt) {
63
- return prompt.split(/\n\s*\n/, 1)[0]?.trim() ?? prompt;
844
+ function toolNameFromPrompt(fileName) {
845
+ const name = fileName.replace(/\.md$/, "");
846
+ if (!TOOL_NAMES.includes(name))
847
+ throw new Error(`unknown ReadSeek tool prompt: ${fileName}`);
848
+ return name;
64
849
  }
65
850
  function promptFileName(promptUrl) {
66
851
  return promptUrl.pathname.split("/").pop() ?? "";
@@ -71,13 +856,18 @@ function rewriteToolAliases(value, toolAliases) {
71
856
  return Object.entries(toolAliases).reduce((rewritten, [canonicalName, registeredName]) => rewritten.replaceAll(canonicalName, registeredName), value);
72
857
  }
73
858
  function defineToolPromptMetadata(options) {
74
- const prompt = loadPrompt(options.promptUrl);
75
859
  const fileName = promptFileName(options.promptUrl);
860
+ const toolName = toolNameFromPrompt(fileName);
76
861
  const replaceable = REPLACEABLE_TOOL_GUIDELINES[fileName];
77
862
  const registeredName = options.registeredName ?? replaceable?.readSeekName;
78
863
  const preferenceGuideline = replaceable && registeredName ? registeredName === replaceable.readSeekName ? `Prefer ${registeredName} over ${replaceable.builtInName} when both are available; ${replaceable.benefit}` : `Use ${registeredName}; ${replaceable.benefit}` : undefined;
864
+ const aliases = Object.fromEntries(TOOL_NAMES.map((name) => {
865
+ const readSeekName = `readSeek_${name}`;
866
+ const alias = name === toolName && registeredName ? registeredName : options.toolAliases?.[readSeekName] ?? readSeekName;
867
+ return [name, alias];
868
+ }));
79
869
  return {
80
- description: rewriteToolAliases(firstPromptParagraph(prompt), options.toolAliases),
870
+ description: renderToolDescription(toolName, aliases),
81
871
  promptSnippet: rewriteToolAliases(options.promptSnippet, options.toolAliases),
82
872
  promptGuidelines: [
83
873
  ...preferenceGuideline ? [preferenceGuideline] : [],
@@ -409,11 +1199,10 @@ function formatFsError(err, domain) {
409
1199
  import { stat } from "node:fs/promises";
410
1200
 
411
1201
  // src/readseek-client.ts
412
- import { spawn } from "node:child_process";
413
- import { existsSync, readFileSync as readFileSync3, statSync as statSync2 } from "node:fs";
414
- import { createRequire } from "node:module";
1202
+ import { existsSync, readFileSync as readFileSync2, statSync as statSync2 } from "node:fs";
1203
+ import { createRequire as createRequire2 } from "node:module";
415
1204
  import { homedir as homedir3 } from "node:os";
416
- import path from "node:path";
1205
+ import path2 from "node:path";
417
1206
 
418
1207
  // src/readseek/enums.ts
419
1208
  var SymbolKind;
@@ -443,7 +1232,7 @@ var SymbolKind;
443
1232
  })(SymbolKind ||= {});
444
1233
 
445
1234
  // src/readseek-settings.ts
446
- import { readFileSync as readFileSync2, statSync } from "node:fs";
1235
+ import { readFileSync, statSync } from "node:fs";
447
1236
  import { homedir as homedir2 } from "node:os";
448
1237
  import { join } from "node:path";
449
1238
  var READSEEK_TOOL_REPLACEMENTS = {
@@ -470,33 +1259,33 @@ function projectSettingsPath() {
470
1259
  function isRecord(value) {
471
1260
  return typeof value === "object" && value !== null && !Array.isArray(value);
472
1261
  }
473
- function invalid(source, path) {
474
- return { source, path, message: `Invalid readseek setting at ${path}` };
1262
+ function invalid(source, path2) {
1263
+ return { source, path: path2, message: `Invalid readseek setting at ${path2}` };
475
1264
  }
476
1265
  function warnUnknownKeys(raw, known, prefix, source, warnings) {
477
1266
  for (const key of Object.keys(raw)) {
478
1267
  if (known.includes(key))
479
1268
  continue;
480
- const path = `${prefix}.${key}`;
481
- warnings.push({ source, path, message: `Unknown readseek setting at ${path}` });
1269
+ const path2 = `${prefix}.${key}`;
1270
+ warnings.push({ source, path: path2, message: `Unknown readseek setting at ${path2}` });
482
1271
  }
483
1272
  }
484
- function readPositive(raw, key, path, source, warnings) {
1273
+ function readPositive(raw, key, path2, source, warnings) {
485
1274
  if (!(key in raw))
486
1275
  return;
487
1276
  const val = raw[key];
488
1277
  if (typeof val === "number" && Number.isSafeInteger(val) && val > 0)
489
1278
  return val;
490
- warnings.push(invalid(source, path));
1279
+ warnings.push(invalid(source, path2));
491
1280
  return;
492
1281
  }
493
- function readEnum(raw, key, values, path, source, warnings) {
1282
+ function readEnum(raw, key, values, path2, source, warnings) {
494
1283
  if (!(key in raw))
495
1284
  return;
496
1285
  const val = raw[key];
497
1286
  if (typeof val === "string" && values.includes(val))
498
1287
  return val;
499
- warnings.push(invalid(source, path));
1288
+ warnings.push(invalid(source, path2));
500
1289
  return;
501
1290
  }
502
1291
  function readImageMode(raw, source, warnings) {
@@ -600,31 +1389,31 @@ function validateSettings(raw, source) {
600
1389
  return { settings, warnings };
601
1390
  }
602
1391
  var settingsFileCache = new Map;
603
- function statFileOrNull(path) {
1392
+ function statFileOrNull(path2) {
604
1393
  try {
605
- return statSync(path);
1394
+ return statSync(path2);
606
1395
  } catch {
607
1396
  return null;
608
1397
  }
609
1398
  }
610
- function readSettingsFile(path) {
611
- const stats = statFileOrNull(path);
1399
+ function readSettingsFile(path2) {
1400
+ const stats = statFileOrNull(path2);
612
1401
  if (!stats) {
613
- settingsFileCache.delete(path);
1402
+ settingsFileCache.delete(path2);
614
1403
  return { settings: {}, warnings: [] };
615
1404
  }
616
- const cached = settingsFileCache.get(path);
1405
+ const cached = settingsFileCache.get(path2);
617
1406
  if (cached && cached.mtimeMs === stats.mtimeMs && cached.size === stats.size)
618
1407
  return cached.result;
619
1408
  let result;
620
1409
  try {
621
- const text = readFileSync2(path, "utf8");
622
- result = validateSettings(JSON.parse(text), path);
1410
+ const text = readFileSync(path2, "utf8");
1411
+ result = validateSettings(JSON.parse(text), path2);
623
1412
  } catch (error) {
624
1413
  const message = error instanceof Error ? error.message : String(error);
625
- result = { settings: {}, warnings: [{ source: path, message: `Invalid JSON: ${message}` }] };
1414
+ result = { settings: {}, warnings: [{ source: path2, message: `Invalid JSON: ${message}` }] };
626
1415
  }
627
- settingsFileCache.set(path, { mtimeMs: stats.mtimeMs, size: stats.size, result });
1416
+ settingsFileCache.set(path2, { mtimeMs: stats.mtimeMs, size: stats.size, result });
628
1417
  return result;
629
1418
  }
630
1419
  function mergeSettings(base, override) {
@@ -659,18 +1448,7 @@ function resolveReadSeekToolDisplayMode(tool) {
659
1448
  }
660
1449
 
661
1450
  // src/readseek-client.ts
662
- function appendGitSelection(args, options) {
663
- const selection = [
664
- options.cached && "cached",
665
- options.others && "others",
666
- options.ignored && "ignored"
667
- ].filter((value) => Boolean(value));
668
- if (selection.length > 0)
669
- args.push("--git", selection.join(","));
670
- }
671
- function lineLocationSelector(line, column) {
672
- return column === undefined ? `line:${line}` : `line:${line}:${column}`;
673
- }
1451
+ var resolveFromHost = createRequire2(import.meta.url).resolve;
674
1452
  function normalizeLanguage(language) {
675
1453
  return language === "java" ? "Java" : language;
676
1454
  }
@@ -715,32 +1493,12 @@ function symbolsFromReadSeek(symbols) {
715
1493
  }
716
1494
  return roots;
717
1495
  }
718
- var require2 = createRequire(import.meta.url);
719
1496
  var READSEEK_REPO_PACKAGE_NAMES = new Set(["@jarkkojs/readseek", "readseek"]);
720
1497
  var defaultReadSeekDirInit;
721
- function readSeekPackageDir() {
722
- return path.dirname(require2.resolve("@jarkkojs/readseek/package.json"));
723
- }
724
- var READSEEK_PLATFORM_PACKAGES = {
725
- "darwin-arm64": "@jarkkojs/readseek-darwin-arm64",
726
- "linux-arm64": "@jarkkojs/readseek-linux-arm64",
727
- "linux-x64": "@jarkkojs/readseek-linux-x64",
728
- "win32-x64": "@jarkkojs/readseek-win32-x64"
729
- };
730
- function readSeekPlatform() {
731
- return `${process.platform}-${process.arch}`;
732
- }
733
1498
  function readSeekBinaryPath() {
734
1499
  if (process.env.READSEEK_BIN)
735
1500
  return process.env.READSEEK_BIN;
736
- const platform = readSeekPlatform();
737
- const platformPackage = READSEEK_PLATFORM_PACKAGES[platform];
738
- if (!platformPackage) {
739
- const supported = Object.keys(READSEEK_PLATFORM_PACKAGES).join(", ");
740
- throw new Error(`@jarkkojs/readseek ships no binary for ${platform}; it supports ${supported}`);
741
- }
742
- const packageJson = require2.resolve(`${platformPackage}/package.json`, { paths: [readSeekPackageDir()] });
743
- return path.join(path.dirname(packageJson), "bin", process.platform === "win32" ? "readseek.exe" : "readseek");
1501
+ return resolveReadSeekBinaryFromPackages(resolveFromHost);
744
1502
  }
745
1503
  function readSeekBinaryAvailability() {
746
1504
  try {
@@ -768,24 +1526,24 @@ function directoryExists(dirPath) {
768
1526
  }
769
1527
  var ownRepositoryByDir = new Map;
770
1528
  function isOwnReadSeekRepository(cwd = process.cwd()) {
771
- const start = path.resolve(cwd);
1529
+ const start = path2.resolve(cwd);
772
1530
  const cached = ownRepositoryByDir.get(start);
773
1531
  if (cached !== undefined)
774
1532
  return cached;
775
1533
  let dir = start;
776
1534
  let result = false;
777
1535
  while (true) {
778
- const packageJsonPath = path.join(dir, "package.json");
1536
+ const packageJsonPath = path2.join(dir, "package.json");
779
1537
  if (existsSync(packageJsonPath)) {
780
1538
  try {
781
- const packageJson = JSON.parse(readFileSync3(packageJsonPath, "utf8"));
1539
+ const packageJson = JSON.parse(readFileSync2(packageJsonPath, "utf8"));
782
1540
  if (typeof packageJson.name === "string" && READSEEK_REPO_PACKAGE_NAMES.has(packageJson.name)) {
783
1541
  result = true;
784
1542
  break;
785
1543
  }
786
1544
  } catch {}
787
1545
  }
788
- const parent = path.dirname(dir);
1546
+ const parent = path2.dirname(dir);
789
1547
  if (parent === dir)
790
1548
  break;
791
1549
  dir = parent;
@@ -795,96 +1553,18 @@ function isOwnReadSeekRepository(cwd = process.cwd()) {
795
1553
  }
796
1554
  function defaultReadSeekDir() {
797
1555
  const home = homedir3();
798
- return home ? path.join(home, ".pi", "readseek") : null;
1556
+ return home ? path2.join(home, ".pi", "readseek") : null;
799
1557
  }
800
- var DEFAULT_READSEEK_TIMEOUT_MS = 120000;
801
1558
  var DEFAULT_READSEEK_VISION_TIMEOUT_MS = 30 * 60000;
802
1559
  function readSeekTimeoutMs() {
803
1560
  return resolveReadSeekTimeoutMs() ?? DEFAULT_READSEEK_TIMEOUT_MS;
804
1561
  }
805
- var READSEEK_USAGE_HINT = /\n\s*Run readseek(?: [\w-]+)* --help for more information\.?\s*$/;
806
- function readSeekErrorMessage(stderr) {
807
- return stderr.replace(READSEEK_USAGE_HINT, "").replace(/^error:\s*/i, "").replace(/\s*\n\s*/g, " ").trim();
808
- }
809
1562
  async function spawnReadSeekRaw(args, options = {}) {
810
- return new Promise((resolve, reject) => {
811
- let settled = false;
812
- let timeout;
813
- const fail = (error) => {
814
- if (settled)
815
- return;
816
- settled = true;
817
- if (timeout !== undefined)
818
- clearTimeout(timeout);
819
- reject(error);
820
- };
821
- const succeed = (value) => {
822
- if (settled)
823
- return;
824
- settled = true;
825
- if (timeout !== undefined)
826
- clearTimeout(timeout);
827
- resolve(value);
828
- };
829
- const stdin = options.stdin;
830
- const stdio = [stdin === undefined ? "ignore" : "pipe", "pipe", "pipe"];
831
- const cancelable = options.cancelable !== false;
832
- const child = spawn(readSeekBinaryPath(), args, {
833
- stdio,
834
- signal: cancelable ? options.signal : undefined
835
- });
836
- const timeoutMs = options.timeoutMs ?? readSeekTimeoutMs();
837
- timeout = setTimeout(() => {
838
- child.kill("SIGKILL");
839
- fail(new Error(`readseek timed out after ${timeoutMs} ms`));
840
- }, timeoutMs);
841
- timeout.unref?.();
842
- const childStdout = child.stdout;
843
- const childStderr = child.stderr;
844
- const childStdin = child.stdin;
845
- if (!childStdout || !childStderr) {
846
- child.kill();
847
- fail(new Error("readseek stdio streams are unavailable"));
848
- return;
849
- }
850
- const stdoutChunks = [];
851
- const stderrChunks = [];
852
- let stdoutBytes = 0;
853
- childStdout.on("data", (chunk) => {
854
- if (settled)
855
- return;
856
- stdoutBytes += chunk.length;
857
- if (stdoutBytes > 32 * 1024 * 1024) {
858
- child.kill();
859
- fail(new Error("readseek output exceeded 32 MiB"));
860
- return;
861
- }
862
- stdoutChunks.push(chunk);
863
- });
864
- childStderr.on("data", (chunk) => stderrChunks.push(chunk));
865
- child.on("error", (error) => fail(error));
866
- if (stdin !== undefined) {
867
- if (!childStdin) {
868
- child.kill();
869
- fail(new Error("readseek stdin stream is unavailable"));
870
- return;
871
- }
872
- childStdin.on("error", (error) => {
873
- if (error?.code !== "EPIPE")
874
- fail(error);
875
- });
876
- childStdin.end(stdin, "utf-8");
877
- }
878
- child.on("close", (code, signal) => {
879
- const stdout = Buffer.concat(stdoutChunks).toString("utf-8");
880
- const stderr = Buffer.concat(stderrChunks).toString("utf-8").trim();
881
- if (code === 0)
882
- succeed(stdout);
883
- else if (signal)
884
- fail(new Error(readSeekErrorMessage(stderr) || `readseek killed by signal ${signal}`));
885
- else
886
- fail(new Error(readSeekErrorMessage(stderr) || `readseek exited with status ${code}`));
887
- });
1563
+ return runReadSeekNodeText(readSeekBinaryPath(), args, {
1564
+ signal: options.signal,
1565
+ stdin: options.stdin,
1566
+ timeoutMs: options.timeoutMs ?? readSeekTimeoutMs(),
1567
+ cancelable: options.cancelable
888
1568
  });
889
1569
  }
890
1570
  async function ensureDefaultReadSeekDir() {
@@ -908,25 +1588,24 @@ async function runReadSeekRaw(args, options = {}) {
908
1588
  return spawnReadSeekRaw(await readSeekInvocationArgs(args), options);
909
1589
  }
910
1590
  async function runReadSeek(args, options = {}) {
911
- const stdout = await runReadSeekRaw(args, options);
912
- return JSON.parse(stdout);
1591
+ return runReadSeekNodeJson(readSeekBinaryPath(), await readSeekInvocationArgs(args), {
1592
+ signal: options.signal,
1593
+ stdin: options.stdin,
1594
+ timeoutMs: options.timeoutMs ?? readSeekTimeoutMs(),
1595
+ cancelable: options.cancelable
1596
+ });
913
1597
  }
914
1598
  async function readSeekView(filePath, options = {}) {
915
- const args = ["view", filePath];
916
- if (options.node !== undefined)
917
- args.push("--node", options.node);
918
- if (options.page !== undefined)
919
- args.push("--page", String(options.page));
920
- if (options.kind !== undefined)
921
- args.push("--kind", options.kind);
922
- if (options.depth !== undefined)
923
- args.push("--depth", String(options.depth));
924
- if (options.outline)
925
- args.push("--outline");
926
- if (options.visionMode !== undefined)
927
- args.push("--vision-mode", options.visionMode);
928
- if (options.visionLevel !== undefined)
929
- args.push("--vision-level", options.visionLevel);
1599
+ const args = buildViewArgs({
1600
+ path: filePath,
1601
+ node: options.node,
1602
+ page: options.page,
1603
+ kind: options.kind,
1604
+ depth: options.depth,
1605
+ outline: options.outline,
1606
+ visionMode: options.visionMode,
1607
+ visionLevel: options.visionLevel
1608
+ });
930
1609
  const runOptions = { signal: options.signal };
931
1610
  return options.visionMode !== undefined ? runReadSeekVisionRaw(args, runOptions) : runReadSeekRaw(args, runOptions);
932
1611
  }
@@ -1316,31 +1995,18 @@ function parseDigestEnvelope(value) {
1316
1995
  result.identity = parseIdentifyOutput(output.identity);
1317
1996
  return result;
1318
1997
  }
1319
- function digestSelectArg(select) {
1320
- if (select === undefined)
1321
- return "content";
1322
- return Array.isArray(select) ? select.join(",") : select;
1323
- }
1324
1998
  function digestArgs(target, options) {
1325
- const args = ["digest", target, "--select", digestSelectArg(options.select)];
1326
- if (options.at !== undefined)
1327
- args.push("--at", options.at);
1328
- if (options.end !== undefined)
1329
- args.push("--end", String(options.end));
1330
- if (options.limit !== undefined)
1331
- args.push("--limit", String(options.limit));
1332
- if (options.language)
1333
- args.push("--language", options.language);
1334
- if (options.visionMode !== undefined)
1335
- args.push("--vision-mode", options.visionMode);
1336
- if (options.visionLevel !== undefined)
1337
- args.push("--vision-level", options.visionLevel);
1338
- if (options.stdin !== undefined) {
1339
- const stdinName = options.stdinName ?? target;
1340
- if (target === "-")
1341
- args.push("--stdin-name", stdinName);
1342
- }
1343
- return args;
1999
+ return buildDigestArgs({
2000
+ path: target,
2001
+ select: options.select ?? "content",
2002
+ at: options.at,
2003
+ end: options.end,
2004
+ limit: options.limit,
2005
+ language: options.language,
2006
+ visionMode: options.visionMode,
2007
+ visionLevel: options.visionLevel,
2008
+ stdinName: options.stdin !== undefined && target === "-" ? options.stdinName ?? target : undefined
2009
+ });
1344
2010
  }
1345
2011
  async function runDigestRaw(target, options = {}) {
1346
2012
  const args = digestArgs(target, options);
@@ -1381,23 +2047,28 @@ async function readSeekMap(filePath, totalBytes, options = {}) {
1381
2047
  return fileMapFromReadSeekOutput(envelope.map, filePath, totalBytes);
1382
2048
  }
1383
2049
  async function readSeekSearch(target, pattern, options = {}) {
1384
- const args = ["search", target, pattern];
1385
- if (options.language)
1386
- args.push("--language", options.language);
1387
- appendGitSelection(args, options);
2050
+ const args = buildSearchArgs({
2051
+ path: target,
2052
+ pattern,
2053
+ language: options.language,
2054
+ cached: options.cached,
2055
+ others: options.others,
2056
+ ignored: options.ignored
2057
+ });
1388
2058
  return parseSearchOutput(await runReadSeek(args, { signal: options.signal })).results;
1389
2059
  }
1390
- async function readSeekRefs(target, name, options = {}) {
1391
- const args = ["refs", target];
1392
- if (options.scope) {
1393
- if (options.line !== undefined)
1394
- args.push("--at", lineLocationSelector(options.line, options.column));
1395
- } else {
1396
- args.push(name);
1397
- }
1398
- if (options.language)
1399
- args.push("--language", options.language);
1400
- appendGitSelection(args, options);
2060
+ async function readSeekRefs(target, options = {}) {
2061
+ const args = buildRefsArgs({
2062
+ path: target,
2063
+ name: options.name,
2064
+ scope: options.scope,
2065
+ line: options.line,
2066
+ column: options.column,
2067
+ language: options.language,
2068
+ cached: options.cached,
2069
+ others: options.others,
2070
+ ignored: options.ignored
2071
+ });
1401
2072
  return parseRefsOutput(await runReadSeek(args, { signal: options.signal })).references;
1402
2073
  }
1403
2074
  async function readSeekCheck(filePath, content, options = {}) {
@@ -1418,7 +2089,7 @@ async function readSeekDetect(filePath, options = {}) {
1418
2089
  async function readSeekDigestContent(filePath, options = {}) {
1419
2090
  const envelope = await readSeekDigest(filePath, {
1420
2091
  select: "content",
1421
- at: options.startLine !== undefined ? lineLocationSelector(options.startLine) : undefined,
2092
+ at: options.startLine !== undefined ? buildLineLocation(options.startLine) : undefined,
1422
2093
  end: options.endLine,
1423
2094
  visionMode: options.visionMode,
1424
2095
  signal: options.signal,
@@ -1492,13 +2163,12 @@ function parseEditOutput(value) {
1492
2163
  };
1493
2164
  }
1494
2165
  async function readSeekEdit(filePath, edits, options = {}) {
1495
- const args = ["edit", filePath];
1496
- if (options.apply)
1497
- args.push("--apply");
1498
- if (options.planHash)
1499
- args.push("--plan-hash", options.planHash);
1500
- if (options.language)
1501
- args.push("--language", options.language);
2166
+ const args = buildEditArgs({
2167
+ path: filePath,
2168
+ apply: options.apply,
2169
+ planHash: options.planHash,
2170
+ language: options.language
2171
+ });
1502
2172
  return parseEditOutput(await runReadSeek(args, {
1503
2173
  signal: options.signal,
1504
2174
  stdin: JSON.stringify({ edits }),
@@ -1576,14 +2246,18 @@ function parseRenameOutput(value) {
1576
2246
  };
1577
2247
  }
1578
2248
  async function readSeekRename(filePath, options) {
1579
- const args = ["rename", filePath, "--at", lineLocationSelector(options.line, options.column), "--to", options.to];
1580
- if (options.apply)
1581
- args.push("--apply");
1582
- if (options.workspace)
1583
- args.push("--workspace", options.workspace);
1584
- if (options.language)
1585
- args.push("--language", options.language);
1586
- appendGitSelection(args, options);
2249
+ const args = buildRenameArgs({
2250
+ path: filePath,
2251
+ to: options.to,
2252
+ line: options.line,
2253
+ column: options.column,
2254
+ workspace: options.workspace,
2255
+ apply: options.apply,
2256
+ language: options.language,
2257
+ cached: options.cached,
2258
+ others: options.others,
2259
+ ignored: options.ignored
2260
+ });
1587
2261
  return parseRenameOutput(await runReadSeek(args, { signal: options.signal }));
1588
2262
  }
1589
2263
  function parseDefCompact(value) {
@@ -1610,10 +2284,15 @@ function parseDefCompact(value) {
1610
2284
  });
1611
2285
  }
1612
2286
  async function readSeekDef(target, options) {
1613
- const args = ["def", target, "--format", "plain", options.name];
1614
- if (options.language)
1615
- args.push("--language", options.language);
1616
- appendGitSelection(args, options);
2287
+ const args = buildDefArgs({
2288
+ path: target,
2289
+ name: options.name,
2290
+ format: "plain",
2291
+ language: options.language,
2292
+ cached: options.cached,
2293
+ others: options.others,
2294
+ ignored: options.ignored
2295
+ });
1617
2296
  return parseDefCompact(await runReadSeek(args, { signal: options.signal }));
1618
2297
  }
1619
2298
 
@@ -1642,7 +2321,7 @@ var THRESHOLDS = {
1642
2321
 
1643
2322
  // src/readseek/formatter.ts
1644
2323
  var BOX_LINE = "───────────────────────────────────────";
1645
- function formatSize2(bytes) {
2324
+ function formatSize(bytes) {
1646
2325
  if (bytes < 1024) {
1647
2326
  return `${bytes} B`;
1648
2327
  }
@@ -1707,22 +2386,22 @@ function formatFileMap(map, level) {
1707
2386
  "",
1708
2387
  BOX_LINE,
1709
2388
  `File Map: ${fileName}`,
1710
- `${formatNumber(map.totalLines)} lines │ ${formatSize2(map.totalBytes)} │ ${map.language}`,
2389
+ `${formatNumber(map.totalLines)} lines │ ${formatSize(map.totalBytes)} │ ${map.language}`,
1711
2390
  BOX_LINE,
1712
2391
  ""
1713
2392
  ];
1714
2393
  if (map.truncatedInfo) {
1715
2394
  const { shownSymbols, totalSymbols } = map.truncatedInfo;
1716
- lines.push(`[Map ≤${formatSize2(THRESHOLDS.MAX_TRUNCATED_BYTES)} | ${shownSymbols} of ${formatNumber(totalSymbols)} symbols]`);
2395
+ lines.push(`[Map ≤${formatSize(THRESHOLDS.MAX_TRUNCATED_BYTES)} | ${shownSymbols} of ${formatNumber(totalSymbols)} symbols]`);
1717
2396
  lines.push("");
1718
2397
  } else if (effectiveLevel === "outline" /* Outline */) {
1719
- lines.push(`[Map ≤${formatSize2(THRESHOLDS.MAX_OUTLINE_BYTES)} | outline]`);
2398
+ lines.push(`[Map ≤${formatSize(THRESHOLDS.MAX_OUTLINE_BYTES)} | outline]`);
1720
2399
  lines.push("");
1721
2400
  } else if (effectiveLevel === "minimal" /* Minimal */) {
1722
- lines.push(`[Map ≤${formatSize2(THRESHOLDS.MAX_MAP_BYTES)} | minimal]`);
2401
+ lines.push(`[Map ≤${formatSize(THRESHOLDS.MAX_MAP_BYTES)} | minimal]`);
1723
2402
  lines.push("");
1724
2403
  } else if (effectiveLevel === "compact" /* Compact */) {
1725
- lines.push(`[Map ≤${formatSize2(THRESHOLDS.COMPACT_TARGET_BYTES)} | compact]`);
2404
+ lines.push(`[Map ≤${formatSize(THRESHOLDS.COMPACT_TARGET_BYTES)} | compact]`);
1726
2405
  lines.push("");
1727
2406
  }
1728
2407
  if (map.truncatedInfo) {
@@ -2084,9 +2763,9 @@ function formatNotFound(query, map) {
2084
2763
 
2085
2764
  // src/read-output.ts
2086
2765
  import {
2087
- DEFAULT_MAX_BYTES as DEFAULT_MAX_BYTES2,
2088
- DEFAULT_MAX_LINES as DEFAULT_MAX_LINES2,
2089
- formatSize as formatSize3,
2766
+ DEFAULT_MAX_BYTES,
2767
+ DEFAULT_MAX_LINES,
2768
+ formatSize as formatSize2,
2090
2769
  truncateHead
2091
2770
  } from "@earendil-works/pi-coding-agent";
2092
2771
  function buildReadOutput(input) {
@@ -2095,14 +2774,14 @@ function buildReadOutput(input) {
2095
2774
  const warnings = input.warnings ?? [];
2096
2775
  const renderedLines = renderReadSeekLines(lines);
2097
2776
  const truncated = truncateHead(renderedLines, {
2098
- maxLines: DEFAULT_MAX_LINES2,
2099
- maxBytes: DEFAULT_MAX_BYTES2
2777
+ maxLines: DEFAULT_MAX_LINES,
2778
+ maxBytes: DEFAULT_MAX_BYTES
2100
2779
  });
2101
2780
  let text = input.truncation ? truncated.content : renderedLines;
2102
2781
  if (input.truncation) {
2103
2782
  text += `
2104
2783
 
2105
- [Output truncated: showing ${input.truncation.outputLines} of ${input.totalLines} lines (${formatSize3(input.truncation.outputBytes)} of ${formatSize3(input.truncation.totalBytes)}). Use offset=${input.startLine + input.truncation.outputLines} to continue.]`;
2784
+ [Output truncated: showing ${input.truncation.outputLines} of ${input.totalLines} lines (${formatSize2(input.truncation.outputBytes)} of ${formatSize2(input.truncation.totalBytes)}). Use offset=${input.startLine + input.truncation.outputLines} to continue.]`;
2106
2785
  } else if (input.continuation) {
2107
2786
  text += `
2108
2787
 
@@ -2426,8 +3105,9 @@ function renderReadSeekSearchCall(args, theme, rest, opts) {
2426
3105
  const context = rest[0] ?? {};
2427
3106
  let text = `${renderToolLabel(theme, opts.label)} ${theme.fg("accent", opts.accent)}`;
2428
3107
  text += theme.fg("dim", ` in ${args.path ?? "."}`);
2429
- if (args.lang)
2430
- text += theme.fg("dim", ` (${args.lang})`);
3108
+ const language = args.language ?? args.lang;
3109
+ if (language)
3110
+ text += theme.fg("dim", ` (${language})`);
2431
3111
  const flags = opts.flags.filter(Boolean);
2432
3112
  if (flags.length > 0)
2433
3113
  text += theme.fg("dim", ` [${flags.join(",")}]`);
@@ -2498,17 +3178,17 @@ function wrapDisplayLines(lines, width) {
2498
3178
  return wrapped.length > 0 ? wrapped : [""];
2499
3179
  });
2500
3180
  }
2501
- function renderSourceBlock(lines, path2) {
3181
+ function renderSourceBlock(lines, path3) {
2502
3182
  return highlightCode(lines.join(`
2503
- `), getLanguageFromPath(path2));
3183
+ `), getLanguageFromPath(path3));
2504
3184
  }
2505
- function renderReadSourceForDisplay(text, path2, anchors, width) {
3185
+ function renderReadSourceForDisplay(text, path3, anchors, width) {
2506
3186
  const output = [];
2507
3187
  let sourceLines = [];
2508
3188
  const flushSourceLines = () => {
2509
3189
  if (sourceLines.length === 0)
2510
3190
  return;
2511
- output.push(...wrapDisplayLines(renderSourceBlock(sourceLines, path2), width));
3191
+ output.push(...wrapDisplayLines(renderSourceBlock(sourceLines, path3), width));
2512
3192
  sourceLines = [];
2513
3193
  };
2514
3194
  for (const line of text.split(`
@@ -2526,19 +3206,19 @@ function renderReadSourceForDisplay(text, path2, anchors, width) {
2526
3206
  `);
2527
3207
  }
2528
3208
  var readSourceCache = new WeakMap;
2529
- function renderReadSourceForDisplayCached(cacheKey, text, path2, anchors, width, theme) {
3209
+ function renderReadSourceForDisplayCached(cacheKey, text, path3, anchors, width, theme) {
2530
3210
  const themeKey = SOURCE_THEME_STYLES.map((style) => theme.fg(style, "")).join("");
2531
3211
  const anchorKey = [...anchors].join(`
2532
3212
  `);
2533
3213
  const cached = readSourceCache.get(cacheKey);
2534
- if (cached && cached.text === text && cached.path === path2 && cached.width === width && cached.theme === themeKey && cached.anchors === anchorKey) {
3214
+ if (cached && cached.text === text && cached.path === path3 && cached.width === width && cached.theme === themeKey && cached.anchors === anchorKey) {
2535
3215
  return cached.rendered;
2536
3216
  }
2537
- const rendered = renderReadSourceForDisplay(text, path2, anchors, width);
2538
- readSourceCache.set(cacheKey, { text, path: path2, width, theme: themeKey, anchors: anchorKey, rendered });
3217
+ const rendered = renderReadSourceForDisplay(text, path3, anchors, width);
3218
+ readSourceCache.set(cacheKey, { text, path: path3, width, theme: themeKey, anchors: anchorKey, rendered });
2539
3219
  return rendered;
2540
3220
  }
2541
- function renderGrepSourceForDisplay(text, anchors, renderPath = (path2) => path2) {
3221
+ function renderGrepSourceForDisplay(text, anchors, renderPath = (path3) => path3) {
2542
3222
  return text.split(`
2543
3223
  `).map((line) => {
2544
3224
  const match = line.match(GREP_ANCHOR_RE);
@@ -2607,13 +3287,13 @@ ${lines.join(`
2607
3287
  return "No image analysis returned.";
2608
3288
  return "Image caption returned no text.";
2609
3289
  }
2610
- function documentRedirectMessage(path2, cache) {
3290
+ function documentRedirectMessage(path3, cache) {
2611
3291
  const cacheNote = cache ? ` Index ${cache.state} (hash ${cache.content_hash}).` : "";
2612
- return `Document content is not available via readSeek_read for ${path2}.${cacheNote} Use readSeek_view to digest structure or selected content.`;
3292
+ return `Document content is not available via readSeek_read for ${path3}.${cacheNote} Use readSeek_view to digest structure or selected content.`;
2613
3293
  }
2614
- function skippedImageFile(path2) {
3294
+ function skippedImageFile(path3) {
2615
3295
  return {
2616
- content: [{ type: "text", text: `[Skipped image: ${path2}; no image mode selected]` }],
3296
+ content: [{ type: "text", text: `[Skipped image: ${path3}; no image mode selected]` }],
2617
3297
  details: {}
2618
3298
  };
2619
3299
  }
@@ -2899,7 +3579,7 @@ async function executeRead(opts) {
2899
3579
  }));
2900
3580
  const selected = readSeekLines.map((line) => line.raw);
2901
3581
  const formatted = renderReadSeekLines(readSeekLines);
2902
- const truncation = truncateHead2(formatted, { maxLines: DEFAULT_MAX_LINES3, maxBytes: DEFAULT_MAX_BYTES3 });
3582
+ const truncation = truncateHead2(formatted, { maxLines: DEFAULT_MAX_LINES2, maxBytes: DEFAULT_MAX_BYTES2 });
2903
3583
  const shouldAppendMap = !!truncation.truncated && !p.offset && !p.limit && !symbolMatch;
2904
3584
  let appendedMap = false;
2905
3585
  let mapText = null;
@@ -3402,8 +4082,8 @@ var LANGUAGE_BY_EXTENSION = new Map([
3402
4082
  [".rs", "rust"],
3403
4083
  [".java", "java"]
3404
4084
  ]);
3405
- function inferLanguage(path2) {
3406
- const extensionMatch = path2.match(/\.[^.\/\\]+$/);
4085
+ function inferLanguage(path3) {
4086
+ const extensionMatch = path3.match(/\.[^.\/\\]+$/);
3407
4087
  if (!extensionMatch)
3408
4088
  return;
3409
4089
  return LANGUAGE_BY_EXTENSION.get(extensionMatch[0].toLowerCase());
@@ -3612,8 +4292,8 @@ function buildInlineDiffs(entries) {
3612
4292
  }
3613
4293
  return inlineDiffs.length ? inlineDiffs : undefined;
3614
4294
  }
3615
- function finalizeDiffData(path2, entries, blockRanges) {
3616
- const language = inferLanguage(path2);
4295
+ function finalizeDiffData(path3, entries, blockRanges) {
4296
+ const language = inferLanguage(path3);
3617
4297
  const inlineDiffs = buildInlineDiffs(entries);
3618
4298
  return {
3619
4299
  version: 1,
@@ -3844,7 +4524,7 @@ var hashlineEditSchema = Type3.Object({
3844
4524
  description: "Read back and verify persisted content"
3845
4525
  }))
3846
4526
  }, { additionalProperties: false });
3847
- function buildEditError(path2, code, message, hint, errorDetails) {
4527
+ function buildEditError(path3, code, message, hint, errorDetails) {
3848
4528
  return {
3849
4529
  content: [{ type: "text", text: message }],
3850
4530
  isError: true,
@@ -3855,7 +4535,7 @@ function buildEditError(path2, code, message, hint, errorDetails) {
3855
4535
  readSeekValue: {
3856
4536
  tool: "edit",
3857
4537
  ok: false,
3858
- path: path2,
4538
+ path: path3,
3859
4539
  error: buildReadSeekError(code, message, hint, errorDetails)
3860
4540
  }
3861
4541
  }
@@ -3869,8 +4549,8 @@ async function executeEdit(opts) {
3869
4549
  const { params, signal, cwd, wasReadInSession, onFileMutated, syntaxValidate } = opts;
3870
4550
  const parsed = params;
3871
4551
  const rawPath = parsed.path;
3872
- const path2 = rawPath.replace(/^@/, "");
3873
- const absolutePath = resolveToCwd(path2, cwd);
4552
+ const path3 = rawPath.replace(/^@/, "");
4553
+ const absolutePath = resolveToCwd(path3, cwd);
3874
4554
  throwIfAborted(signal);
3875
4555
  try {
3876
4556
  return await withFileMutationQueue(absolutePath, async () => {
@@ -3908,7 +4588,7 @@ async function executeEdit(opts) {
3908
4588
  const originalForSyntax = normalizeToLF(stripBom(original).text);
3909
4589
  const resultForSyntax = normalizeToLF(stripBom(result).text);
3910
4590
  if (!plan.changed) {
3911
- return buildEditError(absolutePath, "no-op", `No changes made to ${path2}. The edits produced identical content.`);
4591
+ return buildEditError(absolutePath, "no-op", `No changes made to ${path3}. The edits produced identical content.`);
3912
4592
  }
3913
4593
  const syntaxMode = resolveSyntaxValidateMode({ syntaxValidate });
3914
4594
  let syntaxWarning;
@@ -3949,14 +4629,14 @@ async function executeEdit(opts) {
3949
4629
  try {
3950
4630
  verifiedContent = await fsReadFile2(absolutePath, "utf-8");
3951
4631
  } catch (err) {
3952
- return buildEditError(absolutePath, "post-edit-verification-read-failed", `Edit write completed but post-edit verification failed: could not read ${path2} after writing.`, undefined, { fsCode: err?.code, fsMessage: err?.message });
4632
+ 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 });
3953
4633
  }
3954
4634
  if (verifiedContent !== plan.content) {
3955
- return buildEditError(absolutePath, "post-edit-verification-mismatch", `Edit write completed but post-edit verification did not confirm the intended content for ${path2}. Re-read the file before making follow-up edits.`, undefined, { expectedLength: plan.content.length, actualLength: verifiedContent.length });
4635
+ 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 });
3956
4636
  }
3957
4637
  }
3958
4638
  const diffResult = generateCompactOrFullDiff(original, result);
3959
- const patch = createPatch(path2, original, result);
4639
+ const patch = createPatch(path3, original, result);
3960
4640
  const diffData = buildDiffData({
3961
4641
  path: absolutePath,
3962
4642
  oldContent: original,
@@ -3968,7 +4648,7 @@ async function executeEdit(opts) {
3968
4648
  const semanticSummary = { classification: classification.classification };
3969
4649
  const builtOutput = buildEditOutput({
3970
4650
  path: absolutePath,
3971
- displayPath: path2,
4651
+ displayPath: path3,
3972
4652
  diff: diffResult.diff,
3973
4653
  patch,
3974
4654
  diffData,
@@ -3991,7 +4671,7 @@ async function executeEdit(opts) {
3991
4671
  });
3992
4672
  } catch (err) {
3993
4673
  if (typeof err?.code === "string") {
3994
- return mapEditFileError(err, absolutePath, path2, "read");
4674
+ return mapEditFileError(err, absolutePath, path3, "read");
3995
4675
  }
3996
4676
  throw err;
3997
4677
  }
@@ -4096,21 +4776,21 @@ ${theme.fg("error", info.errorText)}`;
4096
4776
 
4097
4777
  // src/grep.ts
4098
4778
  import { readFile as fsReadFile3, stat as fsStat } from "node:fs/promises";
4099
- import path2 from "node:path";
4779
+ import path3 from "node:path";
4100
4780
  import { createGrepTool } from "@earendil-works/pi-coding-agent";
4101
4781
  import { Type as Type5 } from "@sinclair/typebox";
4102
4782
  import { Text as Text5 } from "@earendil-works/pi-tui";
4103
4783
 
4104
4784
  // src/grep-output.ts
4105
4785
  import {
4106
- formatSize as formatSize4,
4786
+ formatSize as formatSize3,
4107
4787
  truncateHead as truncateHead3
4108
4788
  } from "@earendil-works/pi-coding-agent";
4109
4789
 
4110
4790
  // src/grep-budget.ts
4111
- import { DEFAULT_MAX_BYTES as DEFAULT_MAX_BYTES4, DEFAULT_MAX_LINES as DEFAULT_MAX_LINES4 } from "@earendil-works/pi-coding-agent";
4112
- var GREP_OUTPUT_DEFAULT_MAX_LINES = DEFAULT_MAX_LINES4;
4113
- var GREP_OUTPUT_DEFAULT_MAX_BYTES = Math.min(DEFAULT_MAX_BYTES4, 50 * 1024);
4791
+ import { DEFAULT_MAX_BYTES as DEFAULT_MAX_BYTES3, DEFAULT_MAX_LINES as DEFAULT_MAX_LINES3 } from "@earendil-works/pi-coding-agent";
4792
+ var GREP_OUTPUT_DEFAULT_MAX_LINES = DEFAULT_MAX_LINES3;
4793
+ var GREP_OUTPUT_DEFAULT_MAX_BYTES = Math.min(DEFAULT_MAX_BYTES3, 50 * 1024);
4114
4794
  function resolveDimension(jsonValue, ceiling) {
4115
4795
  if (jsonValue !== undefined)
4116
4796
  return Math.min(jsonValue, ceiling);
@@ -4203,7 +4883,7 @@ ${text}`;
4203
4883
  if (truncated.truncated) {
4204
4884
  text = `${truncated.content}
4205
4885
 
4206
- [Output truncated: showing ${truncated.outputLines} of ${truncated.totalLines} lines (${formatSize4(truncated.outputBytes)} of ${formatSize4(truncated.totalBytes)}). Refine pattern or increase limit.]`;
4886
+ [Output truncated: showing ${truncated.outputLines} of ${truncated.totalLines} lines (${formatSize3(truncated.outputBytes)} of ${formatSize3(truncated.totalBytes)}). Refine pattern or increase limit.]`;
4207
4887
  }
4208
4888
  const readSeekValue = {
4209
4889
  tool: "grep",
@@ -4435,20 +5115,21 @@ function formatGrepResultText(input) {
4435
5115
  // src/readseek-params.ts
4436
5116
  import { Type as Type4 } from "@sinclair/typebox";
4437
5117
  function searchPathParam() {
4438
- return Type4.Optional(Type4.String({ description: "File or directory to search; defaults to the current directory" }));
5118
+ return Type4.Optional(Type4.String({ description: PARAM_DESCRIPTIONS.searchPath }));
4439
5119
  }
4440
- function langParam() {
4441
- return Type4.Optional(Type4.String({ description: "Language override when auto-detection is ambiguous" }));
5120
+ function languageParam() {
5121
+ return Type4.Optional(Type4.String({ description: PARAM_DESCRIPTIONS.language }));
4442
5122
  }
4443
5123
  function readSeekGitSearchParams() {
4444
5124
  return {
4445
- cached: Type4.Optional(Type4.Boolean({ description: "In a Git repository, search tracked/indexed files" })),
4446
- others: Type4.Optional(Type4.Boolean({ description: "In a Git repository, search untracked files" })),
4447
- ignored: Type4.Optional(Type4.Boolean({ description: "With others=true, include ignored untracked files" }))
5125
+ cached: Type4.Optional(Type4.Boolean({ description: PARAM_DESCRIPTIONS.cached })),
5126
+ others: Type4.Optional(Type4.Boolean({ description: PARAM_DESCRIPTIONS.others })),
5127
+ ignored: Type4.Optional(Type4.Boolean({ description: PARAM_DESCRIPTIONS.ignored }))
4448
5128
  };
4449
5129
  }
4450
5130
  function validateIgnoredRequiresOthers(tool, params) {
4451
- if (params.ignored && !params.others) {
5131
+ const validated = validateGitSelection(params);
5132
+ if (!validated.ok) {
4452
5133
  return buildToolErrorResult(tool, "invalid-parameter", `${tool} parameter 'ignored' requires 'others'`);
4453
5134
  }
4454
5135
  return null;
@@ -4636,7 +5317,7 @@ async function executeGrep(opts) {
4636
5317
  };
4637
5318
  const toAbsolutePath = (displayPath) => {
4638
5319
  if (searchPathIsDirectory)
4639
- return path2.resolve(searchPath, displayPath);
5320
+ return path3.resolve(searchPath, displayPath);
4640
5321
  return searchPath;
4641
5322
  };
4642
5323
  const groupsByPath = new Map;
@@ -4913,7 +5594,7 @@ function registerGrepTool(pi, options = {}) {
4913
5594
  }
4914
5595
 
4915
5596
  // src/search.ts
4916
- import path3 from "node:path";
5597
+ import path4 from "node:path";
4917
5598
  import { Type as Type6 } from "@sinclair/typebox";
4918
5599
 
4919
5600
  // src/stat-search-path.ts
@@ -4922,9 +5603,9 @@ async function statSearchPathOrError(tool, rawPath, searchPath) {
4922
5603
  try {
4923
5604
  return { ok: true, stats: await fsStat2(searchPath) };
4924
5605
  } catch (err) {
4925
- const path3 = rawPath ?? searchPath;
5606
+ const path4 = rawPath ?? searchPath;
4926
5607
  const { code, message } = formatFsError(err, "stat-error");
4927
- return { ok: false, error: buildToolErrorResult(tool, code, message, { path: path3 }) };
5608
+ return { ok: false, error: buildToolErrorResult(tool, code, message, { path: path4 }) };
4928
5609
  }
4929
5610
  }
4930
5611
 
@@ -4998,7 +5679,7 @@ function linesFromSearchResult(result, ranges) {
4998
5679
  return lines;
4999
5680
  }
5000
5681
  function readSeekLanguageForPath(language, searchPath, isFile) {
5001
- if (language === "typescript" && isFile && path3.extname(searchPath).toLowerCase() === ".tsx")
5682
+ if (language === "typescript" && isFile && path4.extname(searchPath).toLowerCase() === ".tsx")
5002
5683
  return "tsx";
5003
5684
  return language;
5004
5685
  }
@@ -5014,7 +5695,7 @@ async function executeSearch(opts) {
5014
5695
  return statResult.error;
5015
5696
  const searchPathIsFile = statResult.stats.isFile();
5016
5697
  try {
5017
- const effectiveLang = readSeekLanguageForPath(p.lang, searchPath, searchPathIsFile);
5698
+ const effectiveLang = readSeekLanguageForPath(p.language, searchPath, searchPathIsFile);
5018
5699
  const results = await readSeekSearch(searchPath, p.pattern, {
5019
5700
  language: effectiveLang,
5020
5701
  cached: p.cached,
@@ -5033,8 +5714,8 @@ async function executeSearch(opts) {
5033
5714
  }
5034
5715
  const readSeekFiles = [];
5035
5716
  for (const result of results) {
5036
- const abs = path3.isAbsolute(result.file) ? result.file : path3.resolve(cwd, result.file);
5037
- const display = path3.relative(cwd, abs) || abs;
5717
+ const abs = path4.isAbsolute(result.file) ? result.file : path4.resolve(cwd, result.file);
5718
+ const display = path4.relative(cwd, abs) || abs;
5038
5719
  const ranges = result.matches.map((match) => ({ startLine: match.start_line, endLine: match.end_line }));
5039
5720
  const mergedRanges = mergeRanges(ranges);
5040
5721
  const lines = linesFromSearchResult(result, mergedRanges);
@@ -5082,8 +5763,8 @@ function registerSearchTool(pi, options = {}) {
5082
5763
  promptSnippet: SEARCH_PROMPT_METADATA.promptSnippet,
5083
5764
  promptGuidelines: SEARCH_PROMPT_METADATA.promptGuidelines,
5084
5765
  parameters: Type6.Object({
5085
- pattern: Type6.String({ description: "AST pattern (ast-grep style), such as console.log($$$ARGS)" }),
5086
- lang: langParam(),
5766
+ pattern: Type6.String({ description: PARAM_DESCRIPTIONS.pattern }),
5767
+ language: languageParam(),
5087
5768
  path: searchPathParam(),
5088
5769
  ...readSeekGitSearchParams()
5089
5770
  }),
@@ -5111,13 +5792,21 @@ function registerSearchTool(pi, options = {}) {
5111
5792
 
5112
5793
  // src/refs.ts
5113
5794
  import { Type as Type7 } from "@sinclair/typebox";
5114
- import path4 from "node:path";
5795
+ import path5 from "node:path";
5115
5796
 
5116
5797
  // src/refs-output.ts
5798
+ function refsQueryLabel(input) {
5799
+ if (input.name)
5800
+ return input.name;
5801
+ if (input.scope && input.line !== undefined) {
5802
+ return input.column === undefined ? `line:${input.line}` : `line:${input.line}:${input.column}`;
5803
+ }
5804
+ return "?";
5805
+ }
5117
5806
  function buildRefsOutput(input) {
5118
5807
  if (input.files.length === 0) {
5119
5808
  return {
5120
- text: `No references found for: ${input.name}`,
5809
+ text: `No references found for: ${refsQueryLabel(input)}`,
5121
5810
  readSeekValue: { tool: "refs", files: [] }
5122
5811
  };
5123
5812
  }
@@ -5148,14 +5837,14 @@ function groupReferences(references, cwd) {
5148
5837
  const files = new Map;
5149
5838
  const seen = new Set;
5150
5839
  for (const reference of references) {
5151
- const abs = path4.isAbsolute(reference.file) ? reference.file : path4.resolve(cwd, reference.file);
5840
+ const abs = path5.isAbsolute(reference.file) ? reference.file : path5.resolve(cwd, reference.file);
5152
5841
  const dedupeKey = `${abs}:${reference.line}:${reference.column}`;
5153
5842
  if (seen.has(dedupeKey))
5154
5843
  continue;
5155
5844
  seen.add(dedupeKey);
5156
5845
  let file = files.get(abs);
5157
5846
  if (!file) {
5158
- file = { displayPath: path4.relative(cwd, abs) || abs, path: abs, lines: [] };
5847
+ file = { displayPath: path5.relative(cwd, abs) || abs, path: abs, lines: [] };
5159
5848
  files.set(abs, file);
5160
5849
  }
5161
5850
  file.lines.push(refsLine(reference));
@@ -5171,26 +5860,34 @@ async function executeRefs(opts) {
5171
5860
  const ignoredError = validateIgnoredRequiresOthers("refs", p);
5172
5861
  if (ignoredError)
5173
5862
  return ignoredError;
5174
- if (p.scope && p.line === undefined) {
5175
- return buildToolErrorResult("refs", "invalid-parameter", "refs parameter 'scope' requires 'line'");
5863
+ const scopeError = validateRefsNameVsScope(p);
5864
+ if (!scopeError.ok) {
5865
+ return buildToolErrorResult("refs", "invalid-parameter", `refs parameter: ${scopeError.error}`);
5176
5866
  }
5177
5867
  const searchPath = resolveToCwd(p.path ?? ".", cwd);
5178
5868
  const statResult = await statSearchPathOrError("refs", p.path, searchPath);
5179
5869
  if (!statResult.ok)
5180
5870
  return statResult.error;
5181
5871
  try {
5182
- const references = await readSeekRefs(searchPath, p.name, {
5872
+ const references = await readSeekRefs(searchPath, {
5873
+ name: p.name,
5183
5874
  scope: p.scope,
5184
5875
  line: p.line,
5185
5876
  column: p.column,
5186
- language: p.lang,
5877
+ language: p.language,
5187
5878
  cached: p.cached,
5188
5879
  others: p.others,
5189
5880
  ignored: p.ignored,
5190
5881
  signal
5191
5882
  });
5192
5883
  const files = groupReferences(references, cwd);
5193
- const builtOutput = buildRefsOutput({ name: p.name, files });
5884
+ const builtOutput = buildRefsOutput({
5885
+ name: p.name,
5886
+ scope: p.scope,
5887
+ line: p.line,
5888
+ column: p.column,
5889
+ files
5890
+ });
5194
5891
  for (const file of files) {
5195
5892
  onFileAnchored?.(file.path);
5196
5893
  }
@@ -5214,21 +5911,22 @@ function registerRefsTool(pi, options = {}) {
5214
5911
  promptSnippet: REFS_PROMPT_METADATA.promptSnippet,
5215
5912
  promptGuidelines: REFS_PROMPT_METADATA.promptGuidelines,
5216
5913
  parameters: Type7.Object({
5217
- name: Type7.String({ description: "Identifier to find references for" }),
5914
+ name: Type7.Optional(Type7.String({ description: PARAM_DESCRIPTIONS.refsName })),
5218
5915
  path: searchPathParam(),
5219
- lang: langParam(),
5220
- scope: Type7.Optional(Type7.Boolean({ description: "Restrict results to the binding at the cursor" })),
5221
- line: Type7.Optional(Type7.Number({ description: "One-based cursor line; required with scope" })),
5222
- column: Type7.Optional(Type7.Number({ description: "One-based cursor byte column for disambiguation" })),
5916
+ language: languageParam(),
5917
+ scope: Type7.Optional(Type7.Boolean({ description: PARAM_DESCRIPTIONS.scope })),
5918
+ line: Type7.Optional(Type7.Number({ description: PARAM_DESCRIPTIONS.line })),
5919
+ column: Type7.Optional(Type7.Number({ description: PARAM_DESCRIPTIONS.column })),
5223
5920
  ...readSeekGitSearchParams()
5224
5921
  }),
5225
5922
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
5226
5923
  return executeRefs({ params, signal, cwd: ctx.cwd, onFileAnchored: options.onFileAnchored });
5227
5924
  },
5228
5925
  renderCall(args, theme, ...rest) {
5926
+ const accent = args.name ?? (args.scope ? args.column === undefined ? `line:${args.line}` : `line:${args.line}:${args.column}` : "?");
5229
5927
  return renderReadSeekSearchCall(args, theme, rest, {
5230
5928
  label: "refs",
5231
- accent: args.name,
5929
+ accent,
5232
5930
  flags: [args.scope && "scope", args.cached && "cached", args.others && "others", args.ignored && "ignored"]
5233
5931
  });
5234
5932
  },
@@ -5246,7 +5944,7 @@ function registerRefsTool(pi, options = {}) {
5246
5944
 
5247
5945
  // src/rename.ts
5248
5946
  import { Type as Type8 } from "@sinclair/typebox";
5249
- import path5 from "node:path";
5947
+ import path6 from "node:path";
5250
5948
  import { Text as Text6 } from "@earendil-works/pi-tui";
5251
5949
  var RENAME_PROMPT_METADATA = defineToolPromptMetadata({
5252
5950
  promptUrl: new URL("../prompts/rename.md", import.meta.url),
@@ -5286,13 +5984,13 @@ async function executeRename(opts) {
5286
5984
  for (const file of [output, ...output.others]) {
5287
5985
  if (file.edits.length === 0)
5288
5986
  continue;
5289
- const absoluteFile = path5.isAbsolute(file.file) ? file.file : path5.resolve(cwd, file.file);
5987
+ const absoluteFile = path6.isAbsolute(file.file) ? file.file : path6.resolve(cwd, file.file);
5290
5988
  onFileMutated?.(absoluteFile);
5291
5989
  }
5292
5990
  }
5293
5991
  const files = [output.file];
5294
5992
  for (const other of output.others) {
5295
- const abs = path5.isAbsolute(other.file) ? other.file : path5.resolve(cwd, other.file);
5993
+ const abs = path6.isAbsolute(other.file) ? other.file : path6.resolve(cwd, other.file);
5296
5994
  if (!files.includes(abs))
5297
5995
  files.push(abs);
5298
5996
  }
@@ -5374,7 +6072,7 @@ function registerRenameTool(pi, options = {}) {
5374
6072
  });
5375
6073
  if (expanded && output) {
5376
6074
  for (const file of outputs) {
5377
- const display = path5.relative(cwd, file.file) || file.file;
6075
+ const display = path6.relative(cwd, file.file) || file.file;
5378
6076
  text += `
5379
6077
  ${linkedPathLine(theme, file.file, display, cwd, ` (${file.edits.length} edits)`, width)}`;
5380
6078
  }
@@ -5395,7 +6093,7 @@ import { Text as Text7 } from "@earendil-works/pi-tui";
5395
6093
  import { Type as Type9 } from "@sinclair/typebox";
5396
6094
 
5397
6095
  // src/pending-diff-preview.ts
5398
- import { existsSync as existsSync2, readFileSync as readFileSync4, realpathSync, statSync as statSync3 } from "node:fs";
6096
+ import { existsSync as existsSync2, readFileSync as readFileSync3, realpathSync, statSync as statSync3 } from "node:fs";
5399
6097
  import { dirname, isAbsolute as isAbsolute2, relative as relative2, resolve, sep } from "node:path";
5400
6098
  var PENDING_DIFF_MAX_BYTES = 1024 * 1024;
5401
6099
  function skip(reason) {
@@ -5433,7 +6131,7 @@ function readUtf8File(filePath) {
5433
6131
  return { type: "skip", reason: "not a file" };
5434
6132
  if (stat2.size > PENDING_DIFF_MAX_BYTES)
5435
6133
  return { type: "skip", reason: "file too large" };
5436
- const content = readFileSync4(filePath, "utf-8");
6134
+ const content = readFileSync3(filePath, "utf-8");
5437
6135
  if (content.includes("\x00"))
5438
6136
  return { type: "skip", reason: "binary file" };
5439
6137
  return { type: "ok", content };
@@ -5728,13 +6426,13 @@ function registerWriteTool(pi, options = {}) {
5728
6426
  }
5729
6427
  },
5730
6428
  renderCall(args, theme, context = {}) {
5731
- const { path: path6, content } = args;
6429
+ const { path: path7, content } = args;
5732
6430
  const cwd = context.cwd ?? process.cwd();
5733
6431
  const label = renderToolLabel(theme, "write");
5734
6432
  const lineCount = typeof content === "string" ? content.split(`
5735
6433
  `).length : 0;
5736
6434
  const bytes = typeof content === "string" ? Buffer.byteLength(content, "utf8") : 0;
5737
- const renderedPath = typeof path6 === "string" ? linkToolPath(theme.fg("muted", path6), path6, cwd) : theme.fg("toolOutput", "...");
6435
+ const renderedPath = typeof path7 === "string" ? linkToolPath(theme.fg("muted", path7), path7, cwd) : theme.fg("toolOutput", "...");
5738
6436
  let text = clampLineToWidth(`${label} ${renderedPath}${typeof content === "string" ? ` (${lineCount} ${lineCount === 1 ? "line" : "lines"} • ${bytes} B)` : ""}`, context.width);
5739
6437
  if (context.executionStarted) {
5740
6438
  return upsertTextComponent(context.lastComponent, text);
@@ -5790,7 +6488,7 @@ function registerWriteTool(pi, options = {}) {
5790
6488
 
5791
6489
  // src/def.ts
5792
6490
  import { Type as Type10 } from "@sinclair/typebox";
5793
- import path6 from "node:path";
6491
+ import path7 from "node:path";
5794
6492
  var DEF_PROMPT_METADATA = defineToolPromptMetadata({
5795
6493
  promptUrl: new URL("../prompts/def.md", import.meta.url),
5796
6494
  promptSnippet: "Go to a symbol definition or declaration by name"
@@ -5811,7 +6509,7 @@ async function executeDef(opts) {
5811
6509
  try {
5812
6510
  const definitions = await readSeekDef(searchPath, {
5813
6511
  name: p.name,
5814
- language: p.lang,
6512
+ language: p.language,
5815
6513
  cached: p.cached,
5816
6514
  others: p.others,
5817
6515
  ignored: p.ignored,
@@ -5827,10 +6525,10 @@ async function executeDef(opts) {
5827
6525
  }
5828
6526
  const files = new Map;
5829
6527
  for (const def of definitions) {
5830
- const abs = path6.isAbsolute(def.file) ? def.file : path6.resolve(cwd, def.file);
6528
+ const abs = path7.isAbsolute(def.file) ? def.file : path7.resolve(cwd, def.file);
5831
6529
  let file = files.get(abs);
5832
6530
  if (!file) {
5833
- file = { displayPath: path6.relative(cwd, abs) || abs, path: abs, lines: [] };
6531
+ file = { displayPath: path7.relative(cwd, abs) || abs, path: abs, lines: [] };
5834
6532
  files.set(abs, file);
5835
6533
  }
5836
6534
  file.lines.push(buildReadSeekLineWithHash(def.line, def.line_hash, def.text));
@@ -5866,9 +6564,9 @@ function registerDefTool(pi, options = {}) {
5866
6564
  promptSnippet: DEF_PROMPT_METADATA.promptSnippet,
5867
6565
  promptGuidelines: DEF_PROMPT_METADATA.promptGuidelines,
5868
6566
  parameters: Type10.Object({
5869
- name: Type10.String({ description: "Qualified or unqualified symbol name" }),
6567
+ name: Type10.String({ description: PARAM_DESCRIPTIONS.name }),
5870
6568
  path: searchPathParam(),
5871
- lang: langParam(),
6569
+ language: languageParam(),
5872
6570
  ...readSeekGitSearchParams()
5873
6571
  }),
5874
6572
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
@@ -5895,37 +6593,31 @@ function registerDefTool(pi, options = {}) {
5895
6593
  // src/digest.ts
5896
6594
  import { Text as Text8 } from "@earendil-works/pi-tui";
5897
6595
  import { Type as Type11 } from "@sinclair/typebox";
5898
- var DIGEST_FACETS = ["metadata", "content", "map", "diagnostics", "identity"];
6596
+ var DIGEST_FACETS2 = DIGEST_FACETS;
5899
6597
  var DIGEST_PROMPT_METADATA = defineToolPromptMetadata({
5900
6598
  promptUrl: new URL("../prompts/digest.md", import.meta.url),
5901
6599
  promptSnippet: "Digest source facets as a native CLI envelope"
5902
6600
  });
5903
- var facetSchema = Type11.Union(DIGEST_FACETS.map((facet) => Type11.Literal(facet)));
6601
+ var facetSchema = Type11.Union(DIGEST_FACETS2.map((facet) => Type11.Literal(facet)));
5904
6602
  var digestSchema = Type11.Object({
5905
6603
  path: filePathParam(),
5906
6604
  select: Type11.Optional(Type11.Union([
5907
6605
  facetSchema,
5908
6606
  Type11.Array(facetSchema, { minItems: 1 }),
5909
6607
  Type11.String({
5910
- description: "Comma-separated facets: metadata, content, map, diagnostics, identity"
6608
+ description: PARAM_DESCRIPTIONS.selectComma
5911
6609
  })
5912
6610
  ], {
5913
- description: "Facets: metadata, content, map, diagnostics, identity (default content)"
6611
+ description: PARAM_DESCRIPTIONS.select
5914
6612
  })),
5915
6613
  at: Type11.Optional(Type11.String({
5916
- description: "Location: line:N[:COLUMN], hash:HASH[:COLUMN], or symbol:NAME"
6614
+ description: PARAM_DESCRIPTIONS.at
5917
6615
  })),
5918
- end: optionalIntOrString("Last line to include"),
5919
- limit: optionalIntOrString("Maximum number of lines to include"),
5920
- language: Type11.Optional(Type11.String({ description: "Language override" })),
5921
- visionMode: Type11.Optional(Type11.Union([
5922
- Type11.Literal("none"),
5923
- Type11.Literal("all"),
5924
- Type11.Literal("ocr"),
5925
- Type11.Literal("caption"),
5926
- Type11.Literal("objects")
5927
- ], { description: "Standalone image mode: none, all, caption, objects, or ocr" })),
5928
- visionLevel: Type11.Optional(Type11.Union([Type11.Literal("low"), Type11.Literal("medium"), Type11.Literal("high")], { description: "Vision inference level: low (default), medium, or high" }))
6616
+ end: optionalIntOrString(PARAM_DESCRIPTIONS.end),
6617
+ limit: optionalIntOrString(PARAM_DESCRIPTIONS.limit),
6618
+ language: Type11.Optional(Type11.String({ description: PARAM_DESCRIPTIONS.languageOverride })),
6619
+ visionMode: Type11.Optional(Type11.Union(VISION_MODES.map((mode) => Type11.Literal(mode)), { description: PARAM_DESCRIPTIONS.visionModeDigest })),
6620
+ visionLevel: Type11.Optional(Type11.Union(VISION_LEVELS.map((level) => Type11.Literal(level)), { description: PARAM_DESCRIPTIONS.visionLevel }))
5929
6621
  });
5930
6622
  function hasDigestAnchors(value) {
5931
6623
  if (!value || typeof value !== "object")
@@ -5943,8 +6635,8 @@ function parseSelect(select) {
5943
6635
  if (facets.length === 0)
5944
6636
  throw new Error("select must include at least one facet");
5945
6637
  for (const facet of facets) {
5946
- if (!DIGEST_FACETS.includes(facet)) {
5947
- throw new Error(`unknown digest facet \`${facet}\`; expected ${DIGEST_FACETS.join(", ")}`);
6638
+ if (!DIGEST_FACETS2.includes(facet)) {
6639
+ throw new Error(`unknown digest facet \`${facet}\`; expected ${DIGEST_FACETS2.join(", ")}`);
5948
6640
  }
5949
6641
  }
5950
6642
  return facets;
@@ -5952,15 +6644,15 @@ function parseSelect(select) {
5952
6644
  if (typeof select !== "string") {
5953
6645
  throw new Error("select must be a facet, facet list, or comma-separated facets");
5954
6646
  }
5955
- if (DIGEST_FACETS.includes(select)) {
6647
+ if (DIGEST_FACETS2.includes(select)) {
5956
6648
  return select;
5957
6649
  }
5958
6650
  const parts = select.split(",").map((part) => part.trim()).filter((part) => part.length > 0);
5959
6651
  if (parts.length === 0)
5960
6652
  throw new Error("select must include at least one facet");
5961
6653
  for (const part of parts) {
5962
- if (!DIGEST_FACETS.includes(part)) {
5963
- throw new Error(`unknown digest facet \`${part}\`; expected ${DIGEST_FACETS.join(", ")}`);
6654
+ if (!DIGEST_FACETS2.includes(part)) {
6655
+ throw new Error(`unknown digest facet \`${part}\`; expected ${DIGEST_FACETS2.join(", ")}`);
5964
6656
  }
5965
6657
  }
5966
6658
  return parts;
@@ -6099,24 +6791,13 @@ ${textContent}`;
6099
6791
 
6100
6792
  // src/view.ts
6101
6793
  import {
6102
- DEFAULT_MAX_BYTES as DEFAULT_MAX_BYTES5,
6103
- DEFAULT_MAX_LINES as DEFAULT_MAX_LINES5,
6794
+ DEFAULT_MAX_BYTES as DEFAULT_MAX_BYTES4,
6795
+ DEFAULT_MAX_LINES as DEFAULT_MAX_LINES4,
6104
6796
  truncateHead as truncateHead5
6105
6797
  } from "@earendil-works/pi-coding-agent";
6106
6798
  import { Text as Text9 } from "@earendil-works/pi-tui";
6107
6799
  import { Type as Type12 } from "@sinclair/typebox";
6108
- var NODE_KINDS = [
6109
- "artifact",
6110
- "footer",
6111
- "header",
6112
- "heading",
6113
- "marginal_label",
6114
- "page",
6115
- "page_number",
6116
- "paragraph",
6117
- "section",
6118
- "structural_section"
6119
- ];
6800
+ var NODE_KINDS = DOCUMENT_NODE_KINDS;
6120
6801
  var VIEW_PROMPT_METADATA = defineToolPromptMetadata({
6121
6802
  promptUrl: new URL("../prompts/view.md", import.meta.url),
6122
6803
  promptSnippet: "Digest a document's structure or selected content"
@@ -6140,11 +6821,15 @@ async function executeView(opts) {
6140
6821
  path: input.path
6141
6822
  });
6142
6823
  }
6143
- if (input.outline && (input.visionMode !== undefined || input.visionLevel !== undefined)) {
6144
- return buildToolErrorResult("view", "invalid-params-combo", "Cannot combine outline with visionMode or visionLevel.", { path: input.path });
6145
- }
6146
- if (input.visionLevel !== undefined && input.visionMode === undefined) {
6147
- return buildToolErrorResult("view", "invalid-params-combo", "visionLevel requires visionMode.", { path: input.path });
6824
+ const visionValidation = validateVisionLevelVsMode({
6825
+ outline: input.outline,
6826
+ visionMode: input.visionMode,
6827
+ visionLevel: input.visionLevel
6828
+ });
6829
+ if (!visionValidation.ok) {
6830
+ return buildToolErrorResult("view", "invalid-params-combo", `${visionValidation.error}.`, {
6831
+ path: input.path
6832
+ });
6148
6833
  }
6149
6834
  const filePath = resolveToCwd(input.path, cwd);
6150
6835
  try {
@@ -6159,8 +6844,8 @@ async function executeView(opts) {
6159
6844
  signal
6160
6845
  });
6161
6846
  const truncation = truncateHead5(output, {
6162
- maxLines: DEFAULT_MAX_LINES5,
6163
- maxBytes: DEFAULT_MAX_BYTES5
6847
+ maxLines: DEFAULT_MAX_LINES4,
6848
+ maxBytes: DEFAULT_MAX_BYTES4
6164
6849
  });
6165
6850
  const notice = truncation.truncated ? `
6166
6851
  [… document view truncated; narrow it with page, node, kind, or depth]` : "";
@@ -6188,24 +6873,15 @@ function registerViewTool(pi) {
6188
6873
  promptGuidelines: VIEW_PROMPT_METADATA.promptGuidelines,
6189
6874
  parameters: Type12.Object({
6190
6875
  path: filePathParam(),
6191
- node: Type12.Optional(Type12.String({ description: "Node ID to use as the view root" })),
6192
- page: optionalIntOrString("One-based source page"),
6876
+ node: Type12.Optional(Type12.String({ description: PARAM_DESCRIPTIONS.node })),
6877
+ page: optionalIntOrString(PARAM_DESCRIPTIONS.page),
6193
6878
  kind: Type12.Optional(Type12.Union(NODE_KINDS.map((kind) => Type12.Literal(kind)), {
6194
- description: "Node kind filter"
6879
+ description: PARAM_DESCRIPTIONS.kind
6195
6880
  })),
6196
- depth: optionalIntOrString("Maximum depth below selected roots"),
6197
- visionMode: Type12.Optional(Type12.Union([
6198
- Type12.Literal("all"),
6199
- Type12.Literal("ocr"),
6200
- Type12.Literal("caption"),
6201
- Type12.Literal("objects")
6202
- ], { description: "Analyze selected assets: all, ocr, caption, or objects" })),
6203
- visionLevel: Type12.Optional(Type12.Union([
6204
- Type12.Literal("low"),
6205
- Type12.Literal("medium"),
6206
- Type12.Literal("high")
6207
- ], { description: "Vision inference level: low (default), medium, or high" })),
6208
- outline: Type12.Optional(Type12.Boolean({ description: "Return outline nodes only" }))
6881
+ depth: optionalIntOrString(PARAM_DESCRIPTIONS.depth),
6882
+ visionMode: Type12.Optional(Type12.Union(VISION_ANALYSIS_MODES.map((mode) => Type12.Literal(mode)), { description: PARAM_DESCRIPTIONS.visionModeView })),
6883
+ visionLevel: Type12.Optional(Type12.Union(VISION_LEVELS.map((level) => Type12.Literal(level)), { description: PARAM_DESCRIPTIONS.visionLevel })),
6884
+ outline: Type12.Optional(Type12.Boolean({ description: PARAM_DESCRIPTIONS.outline }))
6209
6885
  }),
6210
6886
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
6211
6887
  return executeView({ params, signal, cwd: ctx.cwd });