pi-readseek 0.11.4 → 0.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -6
- package/dist/index.ts +618 -942
- package/package.json +3 -3
- package/prompts/assets.md +6 -0
- package/prompts/{find_definitions.md → def.md} +3 -3
- package/prompts/{edit_text.md → edit.md} +2 -2
- package/prompts/{find_text.md → grep.md} +5 -4
- package/prompts/refs.md +10 -0
- package/prompts/{rename_symbol.md → rename.md} +1 -2
- package/prompts/{find_nodes.md → search.md} +3 -1
- package/prompts/show.md +16 -0
- package/prompts/find_references.md +0 -14
- package/prompts/show_assets.md +0 -11
- package/prompts/show_content.md +0 -34
- /package/prompts/{write_file.md → write.md} +0 -0
package/dist/index.ts
CHANGED
|
@@ -16,22 +16,22 @@ var EDIT_VARIANTS = [
|
|
|
16
16
|
];
|
|
17
17
|
var GIT_SCOPES = ["tracked", "untracked", "ignored", "all"];
|
|
18
18
|
var TOOL_NAMES = [
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"
|
|
19
|
+
"show",
|
|
20
|
+
"tree",
|
|
21
|
+
"meta",
|
|
22
|
+
"assets",
|
|
23
|
+
"syntax",
|
|
24
|
+
"check",
|
|
25
|
+
"caption",
|
|
26
|
+
"objects",
|
|
27
|
+
"ocr",
|
|
28
|
+
"search",
|
|
29
|
+
"grep",
|
|
30
|
+
"def",
|
|
31
|
+
"refs",
|
|
32
|
+
"edit",
|
|
33
|
+
"rename",
|
|
34
|
+
"write"
|
|
35
35
|
];
|
|
36
36
|
var PARAM_DESCRIPTIONS = {
|
|
37
37
|
path: "Absolute or relative file path",
|
|
@@ -43,12 +43,12 @@ var PARAM_DESCRIPTIONS = {
|
|
|
43
43
|
pattern: "AST pattern (ast-grep style), such as console.log($$$ARGS)",
|
|
44
44
|
patternContext: "Surrounding code context for sub-statement patterns",
|
|
45
45
|
selector: "Target AST node kind to extract from pattern context",
|
|
46
|
-
grepPattern: "
|
|
46
|
+
grepPattern: "Native regex pattern; set literal for exact text. Empty regex matches every line; empty literal is invalid.",
|
|
47
47
|
name: "Qualified or unqualified symbol name",
|
|
48
48
|
refsName: "Identifier to find references for",
|
|
49
49
|
gitScope: "In a Git repository, which files to search: tracked, untracked, ignored (untracked plus ignored), or all. Defaults to tracked plus untracked.",
|
|
50
|
-
at: "Location selector: line:N[:COLUMN], hash:H[:COLUMN], symbol:NAME, page:N, node:ID, or asset:ID",
|
|
51
|
-
assetAt: "Document selector: page:N, node:ID, or asset:ID",
|
|
50
|
+
at: "Location selector: N[:COLUMN], START-END, H[:COLUMN], NAME, line:N[:COLUMN], hash:H[:COLUMN], symbol:NAME, page:N, node:ID, or asset:ID",
|
|
51
|
+
assetAt: "Document selector: N, page:N, node:ID, or asset:ID",
|
|
52
52
|
rule: "KDL node selection rule, @path, or '-' for standard input",
|
|
53
53
|
rules: "KDL structural checks, @path, or '-' for standard input",
|
|
54
54
|
stdinName: "File name hint when the target is '-'",
|
|
@@ -60,7 +60,6 @@ var PARAM_DESCRIPTIONS = {
|
|
|
60
60
|
kind: "Restrict structure to this node kind",
|
|
61
61
|
outline: "Show outline of document hierarchy or source symbols",
|
|
62
62
|
noAnchors: "Omit source line anchors",
|
|
63
|
-
text: "Literal native text query; adapter find_text also supports regex via pattern",
|
|
64
63
|
edits: "Use set_line, replace_lines, insert_after, replace_symbol, or replace",
|
|
65
64
|
editVariant: "Exactly one nested edit variant",
|
|
66
65
|
setLine: 'Replace one line: {"set_line":{"anchor":"LINE:HASH","new_text":"..."}}',
|
|
@@ -92,27 +91,28 @@ var PARAM_DESCRIPTIONS = {
|
|
|
92
91
|
literal: "Treat pattern literally",
|
|
93
92
|
context: "Surrounding lines for each match",
|
|
94
93
|
matchLimit: "Maximum matches to return",
|
|
94
|
+
grepLimit: "Maximum matching lines to return; positive integer, defaults to 100",
|
|
95
95
|
summary: "Return per-file counts",
|
|
96
96
|
grepScope: "Group matches by enclosing symbol",
|
|
97
|
-
scopeContext: "
|
|
97
|
+
scopeContext: "Non-negative context lines within each symbol; requires scope: symbol"
|
|
98
98
|
};
|
|
99
99
|
var TOOL_DESCRIPTIONS = {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
100
|
+
show: "Read source, document content, or image descriptors without inference. Obtain fresh `LINE:HASH` anchors before edits; use at with a column for cursor context. Document results include typed nodes and cached asset paths.",
|
|
101
|
+
tree: "Show parsed source or document hierarchy. Use shallow depth for file overviews.",
|
|
102
|
+
meta: "Show format, source identity, language, dimensions, or page count without decoding image content or inference.",
|
|
103
|
+
assets: "Return selected document asset descriptors with usable cached paths. Adapters attach images from those paths.",
|
|
104
|
+
syntax: "Check parser syntax diagnostics. Exit 0 is clean, 1 has findings, 2 is failure; this is not type checking.",
|
|
105
|
+
check: "Evaluate KDL structural assertions. Exit 0 is clean, 1 has findings, 2 is failure.",
|
|
106
|
+
caption: "Explicitly infer a caption using an installed model. Include text and objects to request OCR and detection too; never downloads models automatically.",
|
|
107
|
+
objects: "Explicitly infer object labels and bounding boxes using an installed model.",
|
|
108
|
+
ocr: "Explicitly recognize text from pixels using an installed model; reading never silently invokes OCR.",
|
|
109
|
+
search: "Find AST nodes by pattern or KDL selection rule. Returns edit-ready anchors. Use `grep` for text and regex.",
|
|
110
|
+
grep: "Search with native regex or literal matching. Returns matching-line counts, source LINE:HASH anchors, and document node-local locations. Native glob, context, summary, limits, Git scope, and symbol grouping apply; default limit is 100 matching lines.",
|
|
111
|
+
def: "Find a symbol definition or declaration by qualified or unqualified name.",
|
|
112
|
+
refs: "Find identifier-name matches with name, or binding-scoped references with at. Use before renaming or deleting a symbol.",
|
|
113
|
+
edit: "Edit existing files with fresh `LINE:HASH` anchors. Each edits item contains exactly one set_line, replace_lines, insert_after, replace_symbol, or replace object. Preview before applying a verified plan.",
|
|
114
|
+
rename: "Rename a symbol at a line or hash cursor with binding-aware edits and stale-plan protection.",
|
|
115
|
+
write: "Create or replace a complete text file and return `LINE:HASH` anchors. Use `edit` for small changes."
|
|
116
116
|
};
|
|
117
117
|
function rewriteBacktickedToolNames(text, aliases = {}) {
|
|
118
118
|
return TOOL_NAMES.reduce((rewritten, toolName) => rewritten.replaceAll(`\`${toolName}\``, `\`${aliases[toolName] ?? toolName}\``), text);
|
|
@@ -205,22 +205,22 @@ function tool(name, title, properties, required) {
|
|
|
205
205
|
return { name, title, description: TOOL_DESCRIPTIONS[name], inputSchema: { type: "object", properties, required, additionalProperties: false } };
|
|
206
206
|
}
|
|
207
207
|
var TOOL_CONTRACTS = [
|
|
208
|
-
tool("
|
|
209
|
-
tool("
|
|
210
|
-
tool("
|
|
211
|
-
tool("
|
|
212
|
-
tool("
|
|
213
|
-
tool("
|
|
214
|
-
tool("
|
|
215
|
-
tool("
|
|
216
|
-
tool("
|
|
217
|
-
tool("
|
|
218
|
-
tool("
|
|
219
|
-
tool("
|
|
220
|
-
tool("
|
|
221
|
-
tool("
|
|
222
|
-
tool("
|
|
223
|
-
tool("
|
|
208
|
+
tool("show", "Show content", { ...path, ...at, ...lang, ...stdin, ...noAnchors, end: integer(PARAM_DESCRIPTIONS.end), limit: integer(PARAM_DESCRIPTIONS.limit) }, ["path"]),
|
|
209
|
+
tool("tree", "Show structure", { ...path, ...at, ...lang, ...stdin, depth: integer(PARAM_DESCRIPTIONS.depth, 0), kind: string(PARAM_DESCRIPTIONS.kind), outline: { type: "boolean", description: PARAM_DESCRIPTIONS.outline } }, ["path"]),
|
|
210
|
+
tool("meta", "Show metadata", { ...path, ...lang, ...stdin }, ["path"]),
|
|
211
|
+
tool("assets", "Show assets", { ...path, at: string(PARAM_DESCRIPTIONS.assetAt), ...stdin }, ["path"]),
|
|
212
|
+
tool("syntax", "Check syntax", { ...path, ...lang, ...stdin }, ["path"]),
|
|
213
|
+
tool("check", "Check rules", { path: string(PARAM_DESCRIPTIONS.searchPath), ...gitScopeSchema, rules: string(PARAM_DESCRIPTIONS.rules) }, ["rules"]),
|
|
214
|
+
tool("caption", "Generate caption", { ...path, ...at, ...stdin, ...level, include: { type: "array", items: { type: "string", enum: ["text", "objects"] }, minItems: 1, uniqueItems: true, description: PARAM_DESCRIPTIONS.include } }, ["path"]),
|
|
215
|
+
tool("objects", "Detect objects", { ...path, ...at, ...stdin, ...level }, ["path"]),
|
|
216
|
+
tool("ocr", "Recognize text", { ...path, ...at, ...stdin, ...level }, ["path"]),
|
|
217
|
+
tool("search", "Search AST nodes", { ...path, ...lang, ...stdin, ...limit, ...gitScopeSchema, ...noAnchors, pattern: string(PARAM_DESCRIPTIONS.pattern), context: string(PARAM_DESCRIPTIONS.patternContext), selector: string(PARAM_DESCRIPTIONS.selector), rule: string(PARAM_DESCRIPTIONS.rule) }, []),
|
|
218
|
+
tool("grep", "Search text", { path: string(PARAM_DESCRIPTIONS.searchPath), limit: { ...integer(PARAM_DESCRIPTIONS.grepLimit), default: 100 }, ...gitScopeSchema, pattern: string(PARAM_DESCRIPTIONS.grepPattern), glob: string(PARAM_DESCRIPTIONS.glob), ignoreCase: { type: "boolean", description: PARAM_DESCRIPTIONS.ignoreCase }, literal: { type: "boolean", description: PARAM_DESCRIPTIONS.literal }, context: integer(PARAM_DESCRIPTIONS.context, 0), summary: { type: "boolean", description: PARAM_DESCRIPTIONS.summary }, scope: { type: "string", enum: ["symbol"], description: PARAM_DESCRIPTIONS.grepScope }, scopeContext: integer(PARAM_DESCRIPTIONS.scopeContext, 0) }, ["pattern"]),
|
|
219
|
+
tool("def", "Find definitions", { ...path, ...lang, ...gitScopeSchema, ...noAnchors, name: string(PARAM_DESCRIPTIONS.name) }, ["name"]),
|
|
220
|
+
tool("refs", "Find references", { ...path, ...at, ...lang, ...gitScopeSchema, ...noAnchors, name: string(PARAM_DESCRIPTIONS.refsName) }, []),
|
|
221
|
+
tool("edit", "Hash-verified edit", { ...path, ...lang, ...gitScopeSchema, ...apply, edits: { type: "array", minItems: 1, items: editItemSchema, description: PARAM_DESCRIPTIONS.edits }, postEditVerify: { type: "boolean", description: PARAM_DESCRIPTIONS.postEditVerify } }, ["path", "edits"]),
|
|
222
|
+
tool("rename", "Rename symbol", { ...path, ...at, ...lang, ...gitScopeSchema, ...apply, to: string(PARAM_DESCRIPTIONS.to), workspace: { type: "boolean", description: PARAM_DESCRIPTIONS.workspace } }, ["path", "at", "to"]),
|
|
223
|
+
tool("write", "Write file", { ...path, content: string(PARAM_DESCRIPTIONS.content) }, ["path", "content"])
|
|
224
224
|
];
|
|
225
225
|
function getToolContract(name) {
|
|
226
226
|
const contract = TOOL_CONTRACTS.find((entry) => entry.name === name);
|
|
@@ -231,44 +231,44 @@ function getToolContract(name) {
|
|
|
231
231
|
// ../readseek-api/src/policy.ts
|
|
232
232
|
var REPLACEABLE_TOOLS = [
|
|
233
233
|
{
|
|
234
|
-
tool: "
|
|
234
|
+
tool: "show",
|
|
235
235
|
builtIn: "read",
|
|
236
236
|
benefit: "it returns LINE:HASH anchors for safe edits."
|
|
237
237
|
},
|
|
238
238
|
{
|
|
239
|
-
tool: "
|
|
239
|
+
tool: "edit",
|
|
240
240
|
builtIn: "edit",
|
|
241
241
|
benefit: "it verifies fresh LINE:HASH anchors."
|
|
242
242
|
},
|
|
243
243
|
{
|
|
244
|
-
tool: "
|
|
244
|
+
tool: "grep",
|
|
245
245
|
builtIn: "grep",
|
|
246
|
-
benefit: "it returns LINE:HASH anchors."
|
|
246
|
+
benefit: "it runs native regex/literal matching and returns source LINE:HASH anchors or document locations."
|
|
247
247
|
},
|
|
248
248
|
{
|
|
249
|
-
tool: "
|
|
249
|
+
tool: "write",
|
|
250
250
|
builtIn: "write",
|
|
251
251
|
benefit: "it returns LINE:HASH anchors."
|
|
252
252
|
}
|
|
253
253
|
];
|
|
254
254
|
var TOOL_COMPACT_GUIDELINES = {
|
|
255
|
-
|
|
256
|
-
"Use `
|
|
255
|
+
show: [
|
|
256
|
+
"Use `tree` for shallow overviews and `syntax` for diagnostics; use `def`, `refs`, or `search` for targeted symbol questions."
|
|
257
257
|
],
|
|
258
|
-
|
|
258
|
+
edit: [
|
|
259
259
|
"Prefer set_line, replace_lines, and insert_after; use replace only when anchors are impractical."
|
|
260
260
|
],
|
|
261
|
-
|
|
262
|
-
"Use `
|
|
261
|
+
grep: [
|
|
262
|
+
"Use `grep` with summary first for broad searches, then narrow by path, glob, or pattern."
|
|
263
263
|
],
|
|
264
|
-
|
|
265
|
-
"Use anchored edits rather than `
|
|
264
|
+
write: [
|
|
265
|
+
"Use anchored edits rather than `write` for small changes or appends."
|
|
266
266
|
],
|
|
267
|
-
|
|
268
|
-
"Use `
|
|
267
|
+
search: [
|
|
268
|
+
"Use `search` for AST patterns; use `grep` for plain text."
|
|
269
269
|
],
|
|
270
|
-
|
|
271
|
-
"Use `
|
|
270
|
+
refs: [
|
|
271
|
+
"Use `refs` before changing a symbol; use at with line:N:C to follow one binding."
|
|
272
272
|
]
|
|
273
273
|
};
|
|
274
274
|
function resolveToolName(name, aliases = {}) {
|
|
@@ -279,21 +279,21 @@ function renderCompactGuidelines(name, aliases = {}) {
|
|
|
279
279
|
}
|
|
280
280
|
function renderToolRoutingPolicy(options = {}) {
|
|
281
281
|
const aliases = options.aliases ?? {};
|
|
282
|
-
const
|
|
283
|
-
const
|
|
284
|
-
const
|
|
285
|
-
const
|
|
286
|
-
const
|
|
287
|
-
const
|
|
288
|
-
const
|
|
289
|
-
const
|
|
290
|
-
const
|
|
282
|
+
const show = resolveToolName("show", aliases);
|
|
283
|
+
const edit = resolveToolName("edit", aliases);
|
|
284
|
+
const write = resolveToolName("write", aliases);
|
|
285
|
+
const grep = resolveToolName("grep", aliases);
|
|
286
|
+
const search = resolveToolName("search", aliases);
|
|
287
|
+
const def = resolveToolName("def", aliases);
|
|
288
|
+
const refs = resolveToolName("refs", aliases);
|
|
289
|
+
const rename = resolveToolName("rename", aliases);
|
|
290
|
+
const assets = resolveToolName("assets", aliases);
|
|
291
291
|
const title = options.title ?? "ReadSeek tool policy:";
|
|
292
292
|
const bullets = [];
|
|
293
293
|
if (options.preferOverBuiltIns) {
|
|
294
294
|
bullets.push("- Prefer ReadSeek tools over built-ins when they can do the job.");
|
|
295
295
|
}
|
|
296
|
-
bullets.push(`- Read with ${
|
|
296
|
+
bullets.push(`- Read with ${show} first; ${edit} needs fresh LINE:HASH anchors.`, `- Use ${grep} for text/regex, ${search} for AST patterns, ${def}/${refs} for targeted symbols, and shallow ${resolveToolName("tree", aliases)} structures only for file overviews.`, `- Use ${edit} for existing files, ${write} for whole-file creation or replacement, and ${rename} for symbol renames.`, `- Use ${resolveToolName("tree", aliases)} for document structure; run ${resolveToolName("syntax", aliases)} after source edits for a quick syntax check.`, `- Use ${assets} to materialize selected document assets and read their returned paths.`);
|
|
297
297
|
if (options.demoteBuiltIns && options.demoteBuiltIns.length > 0) {
|
|
298
298
|
bullets.push(`- Do not use built-in ${options.demoteBuiltIns.join(", ")} when a ReadSeek tool can do the job.`);
|
|
299
299
|
}
|
|
@@ -325,6 +325,59 @@ function validateReferencesSelection(input) {
|
|
|
325
325
|
return { ok: false, error: "exactly one of name or at is required" };
|
|
326
326
|
return { ok: true };
|
|
327
327
|
}
|
|
328
|
+
function validateGrepParams(input) {
|
|
329
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) {
|
|
330
|
+
return { ok: false, error: "grep params must be an object" };
|
|
331
|
+
}
|
|
332
|
+
const params = input;
|
|
333
|
+
const allowed = [
|
|
334
|
+
"pattern",
|
|
335
|
+
"path",
|
|
336
|
+
"glob",
|
|
337
|
+
"ignoreCase",
|
|
338
|
+
"literal",
|
|
339
|
+
"context",
|
|
340
|
+
"limit",
|
|
341
|
+
"summary",
|
|
342
|
+
"scope",
|
|
343
|
+
"scopeContext",
|
|
344
|
+
"gitScope",
|
|
345
|
+
"signal",
|
|
346
|
+
"stdin",
|
|
347
|
+
"timeoutMs",
|
|
348
|
+
"cancelable"
|
|
349
|
+
];
|
|
350
|
+
if (Object.keys(params).some((key) => !allowed.includes(key)))
|
|
351
|
+
return { ok: false, error: "unknown grep parameter" };
|
|
352
|
+
if (typeof params.pattern !== "string")
|
|
353
|
+
return { ok: false, error: "grep requires a pattern string" };
|
|
354
|
+
if (params.literal === true && params.pattern.length === 0)
|
|
355
|
+
return { ok: false, error: "literal pattern must not be empty" };
|
|
356
|
+
for (const key of ["path", "glob"]) {
|
|
357
|
+
if (params[key] !== undefined && (typeof params[key] !== "string" || params[key].length === 0)) {
|
|
358
|
+
return { ok: false, error: `${key} must be a non-empty string` };
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
for (const key of ["ignoreCase", "literal", "summary"]) {
|
|
362
|
+
if (params[key] !== undefined && typeof params[key] !== "boolean")
|
|
363
|
+
return { ok: false, error: `${key} must be a boolean` };
|
|
364
|
+
}
|
|
365
|
+
for (const key of ["context", "limit", "scopeContext"]) {
|
|
366
|
+
const value = params[key];
|
|
367
|
+
const minimum = key === "limit" ? 1 : 0;
|
|
368
|
+
if (value !== undefined && (typeof value !== "number" || !Number.isSafeInteger(value) || value < minimum)) {
|
|
369
|
+
return { ok: false, error: `${key} must be a ${minimum === 1 ? "positive" : "non-negative"} integer` };
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
if (params.scope !== undefined && params.scope !== "symbol")
|
|
373
|
+
return { ok: false, error: "scope must be symbol" };
|
|
374
|
+
if (params.scopeContext !== undefined && params.scope !== "symbol")
|
|
375
|
+
return { ok: false, error: "scopeContext requires scope" };
|
|
376
|
+
if (params.gitScope !== undefined && !GIT_SCOPES.includes(params.gitScope)) {
|
|
377
|
+
return { ok: false, error: "invalid gitScope" };
|
|
378
|
+
}
|
|
379
|
+
return { ok: true };
|
|
380
|
+
}
|
|
328
381
|
var VARIANT_FIELDS = {
|
|
329
382
|
set_line: ["anchor", "new_text"],
|
|
330
383
|
replace_lines: ["start_anchor", "end_anchor", "new_text"],
|
|
@@ -423,7 +476,6 @@ function coerceObviousBase10Int(value, name) {
|
|
|
423
476
|
return invalidInteger(value, name);
|
|
424
477
|
}
|
|
425
478
|
// ../readseek-api/src/argv.ts
|
|
426
|
-
var DEFAULT_STRUCTURE_DEPTH = 1;
|
|
427
479
|
function appendGitScopeArgs(args, input) {
|
|
428
480
|
if (input.gitScope !== undefined)
|
|
429
481
|
args.push("--git-scope", input.gitScope);
|
|
@@ -459,50 +511,111 @@ var SELECTOR_KINDS = ["line", "hash", "symbol", "page", "node", "asset"];
|
|
|
459
511
|
function isSelectorKind(value) {
|
|
460
512
|
return SELECTOR_KINDS.includes(value);
|
|
461
513
|
}
|
|
514
|
+
function parsePositiveSelectorInt(raw, name) {
|
|
515
|
+
if (!/^-?\d+$/.test(raw))
|
|
516
|
+
throw new Error(`invalid ${name} \`${raw}\``);
|
|
517
|
+
const parsed = Number(raw);
|
|
518
|
+
if (!Number.isSafeInteger(parsed))
|
|
519
|
+
throw new Error(`invalid ${name} \`${raw}\``);
|
|
520
|
+
if (parsed < 1)
|
|
521
|
+
throw new Error(`${name} must be greater than zero`);
|
|
522
|
+
return parsed;
|
|
523
|
+
}
|
|
524
|
+
function parseLineSelector(value, explicitKind) {
|
|
525
|
+
const parts = value.split(":");
|
|
526
|
+
if (parts.length > 2)
|
|
527
|
+
throw new Error("selector has too many components");
|
|
528
|
+
const head = parts[0];
|
|
529
|
+
if (parts.length === 2) {
|
|
530
|
+
if (head.slice(1).includes("-"))
|
|
531
|
+
throw new Error("line range cannot include a column");
|
|
532
|
+
const line = parsePositiveSelectorInt(head, "line");
|
|
533
|
+
const column = parsePositiveSelectorInt(parts[1], "column");
|
|
534
|
+
return { kind: "line", value: String(line), column, explicitKind };
|
|
535
|
+
}
|
|
536
|
+
const dash = head.indexOf("-", 1);
|
|
537
|
+
if (dash > 0) {
|
|
538
|
+
const start = parsePositiveSelectorInt(head.slice(0, dash), "line");
|
|
539
|
+
const endLine = parsePositiveSelectorInt(head.slice(dash + 1), "end line");
|
|
540
|
+
if (endLine < start)
|
|
541
|
+
throw new Error(`end line ${endLine} precedes start line ${start}`);
|
|
542
|
+
return { kind: "line", value: String(start), endLine, explicitKind };
|
|
543
|
+
}
|
|
544
|
+
const line = parsePositiveSelectorInt(head, "line");
|
|
545
|
+
return { kind: "line", value: String(line), explicitKind };
|
|
546
|
+
}
|
|
547
|
+
function parseHashSelector(value, explicitKind) {
|
|
548
|
+
const parts = value.split(":");
|
|
549
|
+
if (parts.length > 2)
|
|
550
|
+
throw new Error("selector has too many components");
|
|
551
|
+
const head = parts[0];
|
|
552
|
+
if (!/^[0-9a-fA-F]{6}$/.test(head))
|
|
553
|
+
throw new Error(`invalid line hash \`${head}\``);
|
|
554
|
+
if (parts.length === 2) {
|
|
555
|
+
const column = parsePositiveSelectorInt(parts[1], "column");
|
|
556
|
+
return { kind: "hash", value: head, column, explicitKind };
|
|
557
|
+
}
|
|
558
|
+
return { kind: "hash", value: head, explicitKind };
|
|
559
|
+
}
|
|
560
|
+
function isDocumentSelector(parsed) {
|
|
561
|
+
return ["page", "node", "asset"].includes(parsed.kind) || parsed.kind === "line" && !parsed.explicitKind && parsed.column === undefined && parsed.endLine === undefined;
|
|
562
|
+
}
|
|
563
|
+
function isBindingSelector(parsed) {
|
|
564
|
+
if (parsed.endLine !== undefined)
|
|
565
|
+
return false;
|
|
566
|
+
if (parsed.kind === "line" || parsed.kind === "hash")
|
|
567
|
+
return true;
|
|
568
|
+
return parsed.kind === "symbol" && !parsed.explicitKind && /^[0-9a-fA-F]{6}$/.test(parsed.value);
|
|
569
|
+
}
|
|
462
570
|
function parseSelector(text) {
|
|
571
|
+
if (text.length === 0)
|
|
572
|
+
throw new Error("selector must not be empty");
|
|
463
573
|
const separator = text.indexOf(":");
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
574
|
+
const hasSingleColon = separator >= 0 && text[separator + 1] !== ":";
|
|
575
|
+
if (hasSingleColon) {
|
|
576
|
+
const kind = text.slice(0, separator);
|
|
577
|
+
const value = text.slice(separator + 1);
|
|
578
|
+
if (isSelectorKind(kind)) {
|
|
579
|
+
if (value.length === 0)
|
|
580
|
+
throw new Error(`${kind} selector must not be empty`);
|
|
581
|
+
if (kind === "line")
|
|
582
|
+
return parseLineSelector(value, true);
|
|
583
|
+
if (kind === "hash")
|
|
584
|
+
return parseHashSelector(value, true);
|
|
585
|
+
if (kind === "page") {
|
|
586
|
+
if (value.includes(":"))
|
|
587
|
+
throw new Error("page selector has too many components");
|
|
588
|
+
parsePositiveSelectorInt(value, "page");
|
|
589
|
+
}
|
|
590
|
+
return { kind, value, explicitKind: true };
|
|
591
|
+
}
|
|
592
|
+
if (/^\d+$/.test(kind))
|
|
593
|
+
return parseLineSelector(text, false);
|
|
594
|
+
if (/^[0-9a-fA-F]{6}$/.test(kind) && /^\d+$/.test(value))
|
|
595
|
+
return parseHashSelector(text, false);
|
|
472
596
|
throw new Error(`unknown selector kind \`${kind}\`; expected line, hash, symbol, page, node, or asset`);
|
|
473
597
|
}
|
|
474
|
-
if (
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
throw new Error("selector has too many components");
|
|
478
|
-
if (parts.length === 2) {
|
|
479
|
-
const column = Number(parts[1]);
|
|
480
|
-
if (!Number.isSafeInteger(column))
|
|
481
|
-
throw new Error(`invalid column \`${parts[1]}\``);
|
|
482
|
-
if (column < 1)
|
|
483
|
-
throw new Error("column must be greater than zero");
|
|
484
|
-
return { kind, value: parts[0], column };
|
|
598
|
+
if (/^[0-9-]/.test(text)) {
|
|
599
|
+
if (/^[0-9a-fA-F]{6}$/.test(text) && /[a-fA-F]/.test(text)) {
|
|
600
|
+
return parseHashSelector(text, false);
|
|
485
601
|
}
|
|
602
|
+
return parseLineSelector(text, false);
|
|
486
603
|
}
|
|
487
|
-
if (
|
|
488
|
-
|
|
489
|
-
throw new Error("page selector has too many components");
|
|
490
|
-
const page = Number(value);
|
|
491
|
-
if (!Number.isSafeInteger(page))
|
|
492
|
-
throw new Error(`invalid page \`${value}\``);
|
|
493
|
-
if (page < 1)
|
|
494
|
-
throw new Error("page must be greater than zero");
|
|
604
|
+
if (/^[0-9a-fA-F]{6}$/.test(text) && /\d/.test(text)) {
|
|
605
|
+
return parseHashSelector(text, false);
|
|
495
606
|
}
|
|
496
|
-
|
|
607
|
+
if (/\s/.test(text))
|
|
608
|
+
throw new Error(`invalid selector \`${text}\``);
|
|
609
|
+
return { kind: "symbol", value: text, explicitKind: false };
|
|
497
610
|
}
|
|
498
|
-
function start(
|
|
611
|
+
function start(command, input) {
|
|
499
612
|
if (!input.path)
|
|
500
|
-
throw new Error(`${
|
|
613
|
+
throw new Error(`${command} requires a path`);
|
|
501
614
|
if (input.stdinName !== undefined && input.path !== "-")
|
|
502
615
|
throw new Error("stdinName requires '-' as the target");
|
|
503
616
|
const args = [];
|
|
504
617
|
pushOptionalString(args, "--readseek-dir", input.readseekDir);
|
|
505
|
-
args.push(
|
|
618
|
+
args.push(command, input.path);
|
|
506
619
|
pushOptionalString(args, "--stdin-name", input.stdinName);
|
|
507
620
|
return args;
|
|
508
621
|
}
|
|
@@ -528,12 +641,12 @@ function anchors(args, input) {
|
|
|
528
641
|
if (input.noAnchors)
|
|
529
642
|
args.push("--no-anchors");
|
|
530
643
|
}
|
|
531
|
-
function
|
|
644
|
+
function buildShowArgs(input) {
|
|
532
645
|
if (input.end !== undefined)
|
|
533
646
|
validatePositiveInteger(input.end, "end");
|
|
534
647
|
if (input.end !== undefined && input.limit !== undefined)
|
|
535
648
|
throw new Error("cannot combine end with limit");
|
|
536
|
-
const args = start("show",
|
|
649
|
+
const args = start("show", input);
|
|
537
650
|
at2(args, input.at);
|
|
538
651
|
pushOptionalNumber(args, "--end", input.end);
|
|
539
652
|
limit2(args, input.limit);
|
|
@@ -541,10 +654,10 @@ function buildShowContentArgs(input) {
|
|
|
541
654
|
anchors(args, input);
|
|
542
655
|
return finish(args);
|
|
543
656
|
}
|
|
544
|
-
function
|
|
657
|
+
function buildTreeArgs(input) {
|
|
545
658
|
if (input.depth !== undefined && (!Number.isSafeInteger(input.depth) || input.depth < 0))
|
|
546
659
|
throw new Error("depth must be a non-negative integer");
|
|
547
|
-
const args = start("
|
|
660
|
+
const args = start("tree", input);
|
|
548
661
|
at2(args, input.at);
|
|
549
662
|
pushOptionalNumber(args, "--depth", input.depth);
|
|
550
663
|
pushOptionalString(args, "--kind", input.kind);
|
|
@@ -553,35 +666,35 @@ function buildShowStructureArgs(input) {
|
|
|
553
666
|
args.push("--outline");
|
|
554
667
|
return finish(args);
|
|
555
668
|
}
|
|
556
|
-
function
|
|
557
|
-
const args = start("
|
|
669
|
+
function buildMetaArgs(input) {
|
|
670
|
+
const args = start("meta", input);
|
|
558
671
|
language(args, input);
|
|
559
672
|
return finish(args);
|
|
560
673
|
}
|
|
561
|
-
function
|
|
562
|
-
if (input.at !== undefined && !
|
|
674
|
+
function buildAssetsArgs(input) {
|
|
675
|
+
if (input.at !== undefined && !isDocumentSelector(parseSelector(input.at)))
|
|
563
676
|
throw new Error("assets requires a page, node, or asset selector");
|
|
564
|
-
const args = start("
|
|
677
|
+
const args = start("assets", input);
|
|
565
678
|
at2(args, input.at);
|
|
566
679
|
return finish(args);
|
|
567
680
|
}
|
|
568
|
-
function
|
|
569
|
-
const args = start("
|
|
681
|
+
function buildSyntaxArgs(input) {
|
|
682
|
+
const args = start("syntax", input);
|
|
570
683
|
language(args, input);
|
|
571
684
|
return finish(args);
|
|
572
685
|
}
|
|
573
|
-
function visionArgs(
|
|
686
|
+
function visionArgs(command, input) {
|
|
574
687
|
if (input.level !== undefined && !VISION_LEVELS.includes(input.level))
|
|
575
688
|
throw new Error("invalid vision level");
|
|
576
|
-
if (input.at !== undefined && !
|
|
689
|
+
if (input.at !== undefined && !isDocumentSelector(parseSelector(input.at)))
|
|
577
690
|
throw new Error("vision requires a document selector");
|
|
578
|
-
const args = start(
|
|
691
|
+
const args = start(command, input);
|
|
579
692
|
at2(args, input.at);
|
|
580
693
|
pushOptionalString(args, "--level", input.level);
|
|
581
694
|
return args;
|
|
582
695
|
}
|
|
583
|
-
function
|
|
584
|
-
const args = visionArgs("
|
|
696
|
+
function buildCaptionArgs(input) {
|
|
697
|
+
const args = visionArgs("caption", input);
|
|
585
698
|
if (input.include !== undefined) {
|
|
586
699
|
if (input.include.length === 0 || input.include.some((task) => task !== "text" && task !== "objects"))
|
|
587
700
|
throw new Error("include requires text or objects");
|
|
@@ -589,13 +702,13 @@ function buildGenerateCaptionArgs(input) {
|
|
|
589
702
|
}
|
|
590
703
|
return finish(args);
|
|
591
704
|
}
|
|
592
|
-
function
|
|
593
|
-
return finish(visionArgs("
|
|
705
|
+
function buildObjectsArgs(input) {
|
|
706
|
+
return finish(visionArgs("objects", input));
|
|
594
707
|
}
|
|
595
|
-
function
|
|
596
|
-
return finish(visionArgs("
|
|
708
|
+
function buildOcrArgs(input) {
|
|
709
|
+
return finish(visionArgs("ocr", input));
|
|
597
710
|
}
|
|
598
|
-
function
|
|
711
|
+
function buildSearchArgs(input) {
|
|
599
712
|
const hasPattern = Boolean(input.pattern);
|
|
600
713
|
const hasRule = Boolean(input.rule);
|
|
601
714
|
const hasContext = Boolean(input.context);
|
|
@@ -603,7 +716,7 @@ function buildFindNodesArgs(input) {
|
|
|
603
716
|
if (hasPattern || hasContext || input.selector)
|
|
604
717
|
throw new Error("rule cannot be combined with pattern, context, or selector");
|
|
605
718
|
} else if (!hasPattern && !hasContext) {
|
|
606
|
-
throw new Error("
|
|
719
|
+
throw new Error("search requires pattern, context, or rule");
|
|
607
720
|
}
|
|
608
721
|
if (hasContext && !input.selector) {
|
|
609
722
|
throw new Error("context requires selector");
|
|
@@ -611,7 +724,7 @@ function buildFindNodesArgs(input) {
|
|
|
611
724
|
if (hasPattern && hasContext) {
|
|
612
725
|
throw new Error("pattern and context cannot be combined");
|
|
613
726
|
}
|
|
614
|
-
const args = start("
|
|
727
|
+
const args = start("search", input);
|
|
615
728
|
pushOptionalString(args, "--pattern", input.pattern);
|
|
616
729
|
pushOptionalString(args, "--context", input.context);
|
|
617
730
|
pushOptionalString(args, "--selector", input.selector);
|
|
@@ -622,28 +735,46 @@ function buildFindNodesArgs(input) {
|
|
|
622
735
|
anchors(args, input);
|
|
623
736
|
return finish(args);
|
|
624
737
|
}
|
|
625
|
-
function
|
|
626
|
-
const
|
|
627
|
-
|
|
628
|
-
|
|
738
|
+
function buildGrepArgs(input) {
|
|
739
|
+
const validated = validateGrepParams(input);
|
|
740
|
+
if (!validated.ok)
|
|
741
|
+
throw new Error(validated.error);
|
|
742
|
+
const args = ["grep", "--kdl", `--pattern=${input.pattern}`];
|
|
743
|
+
const needsPathTerminator = input.path !== undefined && input.path.startsWith("-") && input.path !== "-";
|
|
744
|
+
if (input.path !== undefined && !needsPathTerminator)
|
|
745
|
+
args.push(input.path);
|
|
746
|
+
if (input.literal)
|
|
747
|
+
args.push("--literal");
|
|
748
|
+
if (input.ignoreCase)
|
|
749
|
+
args.push("--ignore-case");
|
|
750
|
+
if (input.glob !== undefined)
|
|
751
|
+
args.push(`--glob=${input.glob}`);
|
|
752
|
+
pushOptionalNumber(args, "--context", input.context);
|
|
753
|
+
limit2(args, input.limit ?? 100);
|
|
754
|
+
if (input.summary)
|
|
755
|
+
args.push("--summary");
|
|
756
|
+
pushOptionalString(args, "--scope", input.scope);
|
|
757
|
+
pushOptionalNumber(args, "--scope-context", input.scopeContext);
|
|
629
758
|
appendGitScopeArgs(args, input);
|
|
630
|
-
|
|
759
|
+
if (input.path !== undefined && needsPathTerminator)
|
|
760
|
+
args.push("--", input.path);
|
|
761
|
+
return args;
|
|
631
762
|
}
|
|
632
|
-
function
|
|
633
|
-
const args = start("
|
|
763
|
+
function buildDefArgs(input) {
|
|
764
|
+
const args = start("def", input);
|
|
634
765
|
args.push("--name", input.name);
|
|
635
766
|
language(args, input);
|
|
636
767
|
appendGitScopeArgs(args, input);
|
|
637
768
|
anchors(args, input);
|
|
638
769
|
return finish(args);
|
|
639
770
|
}
|
|
640
|
-
function
|
|
771
|
+
function buildRefsArgs(input) {
|
|
641
772
|
const validated = validateReferencesSelection(input);
|
|
642
773
|
if (!validated.ok)
|
|
643
774
|
throw new Error(validated.error);
|
|
644
|
-
if (input.at !== undefined && !
|
|
775
|
+
if (input.at !== undefined && !isBindingSelector(parseSelector(input.at)))
|
|
645
776
|
throw new Error("references requires a line or hash selector");
|
|
646
|
-
const args = start("
|
|
777
|
+
const args = start("refs", input);
|
|
647
778
|
pushOptionalString(args, "--name", input.name);
|
|
648
779
|
at2(args, input.at);
|
|
649
780
|
language(args, input);
|
|
@@ -651,14 +782,14 @@ function buildFindReferencesArgs(input) {
|
|
|
651
782
|
anchors(args, input);
|
|
652
783
|
return finish(args);
|
|
653
784
|
}
|
|
654
|
-
function
|
|
655
|
-
const args = start("check",
|
|
785
|
+
function buildCheckArgs(input) {
|
|
786
|
+
const args = start("check", input);
|
|
656
787
|
args.push("--rules", input.rules);
|
|
657
788
|
appendGitScopeArgs(args, input);
|
|
658
789
|
return finish(args);
|
|
659
790
|
}
|
|
660
|
-
function
|
|
661
|
-
const args = start("edit",
|
|
791
|
+
function buildEditArgs(input) {
|
|
792
|
+
const args = start("edit", input);
|
|
662
793
|
appendApplyAndPlanHash(args, input);
|
|
663
794
|
language(args, input);
|
|
664
795
|
appendGitScopeArgs(args, input);
|
|
@@ -666,10 +797,10 @@ function buildEditTextArgs(input) {
|
|
|
666
797
|
args.push("--edits", "-");
|
|
667
798
|
return finish(args);
|
|
668
799
|
}
|
|
669
|
-
function
|
|
670
|
-
if (!
|
|
800
|
+
function buildRenameArgs(input) {
|
|
801
|
+
if (!isBindingSelector(parseSelector(input.at)))
|
|
671
802
|
throw new Error("rename requires a line or hash selector");
|
|
672
|
-
const args = start("rename",
|
|
803
|
+
const args = start("rename", input);
|
|
673
804
|
at2(args, input.at);
|
|
674
805
|
args.push("--to", input.to);
|
|
675
806
|
if (input.workspace !== undefined)
|
|
@@ -1059,25 +1190,41 @@ var DocumentOutput = Type.Object({
|
|
|
1059
1190
|
assets: Type.Array(DocumentAsset, { default: [] }),
|
|
1060
1191
|
vision: Type.Array(DocumentVision, { default: [] })
|
|
1061
1192
|
});
|
|
1062
|
-
var
|
|
1063
|
-
|
|
1064
|
-
|
|
1193
|
+
var GrepCount = Type.Integer({ minimum: 0, maximum: Number.MAX_SAFE_INTEGER });
|
|
1194
|
+
var GrepPosition = Type.Integer({ minimum: 1, maximum: Number.MAX_SAFE_INTEGER });
|
|
1195
|
+
var GrepAnchor = Type.String({ pattern: "^[1-9][0-9]*:[0-9a-f]{6}$" });
|
|
1196
|
+
var GrepLineSchema = Type.Object({
|
|
1197
|
+
line: GrepPosition,
|
|
1198
|
+
hash: Type.String({ pattern: "^[0-9a-f]{6}$" }),
|
|
1065
1199
|
text: Str,
|
|
1066
|
-
|
|
1067
|
-
end_line: Int,
|
|
1068
|
-
start_byte: Int,
|
|
1069
|
-
end_byte: Int,
|
|
1070
|
-
hashlines: Hashlines
|
|
1200
|
+
kind: Type.Union([Type.Literal("match"), Type.Literal("context")])
|
|
1071
1201
|
});
|
|
1072
|
-
var
|
|
1073
|
-
var
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1202
|
+
var GrepHitSchema = Type.Object({ line: GrepPosition, column: GrepPosition, start_byte: GrepCount, end_byte: GrepCount, text: Str, anchor: GrepAnchor });
|
|
1203
|
+
var GrepSymbolSchema = Type.Object({ name: Str, kind: Str, start_line: GrepPosition, end_line: GrepPosition, lines: Type.Array(GrepLineSchema) });
|
|
1204
|
+
var GrepSourceResultSchema = Type.Object({
|
|
1205
|
+
file: Str,
|
|
1206
|
+
file_hash: Str,
|
|
1207
|
+
language: Str,
|
|
1208
|
+
total_matches: GrepCount,
|
|
1209
|
+
returned_matches: GrepCount,
|
|
1210
|
+
hits: Type.Array(GrepHitSchema),
|
|
1211
|
+
lines: Type.Array(GrepLineSchema),
|
|
1212
|
+
symbols: Type.Array(GrepSymbolSchema)
|
|
1213
|
+
});
|
|
1214
|
+
var GrepDocumentHitSchema = Type.Object({ node_id: Str, kind: Str, snippet: Str, line: GrepPosition, page: Type.Optional(GrepPosition) });
|
|
1215
|
+
var GrepDocumentResultSchema = Type.Object({ file: Str, content_hash: Str, total_matches: GrepCount, returned_matches: GrepCount, hits: Type.Array(GrepDocumentHitSchema) });
|
|
1216
|
+
var GrepOutputSchema = Type.Object({
|
|
1217
|
+
pattern: Str,
|
|
1218
|
+
mode: Type.Union([Type.Literal("regex"), Type.Literal("literal")]),
|
|
1219
|
+
ignore_case: Bool,
|
|
1220
|
+
summary: Bool,
|
|
1221
|
+
total_matches: GrepCount,
|
|
1222
|
+
returned_matches: GrepCount,
|
|
1078
1223
|
truncated: Bool,
|
|
1079
|
-
|
|
1080
|
-
|
|
1224
|
+
count_unit: Type.Literal("lines"),
|
|
1225
|
+
scope: Type.Optional(Type.Literal("symbol")),
|
|
1226
|
+
results: Type.Array(GrepSourceResultSchema),
|
|
1227
|
+
documents: Type.Array(GrepDocumentResultSchema)
|
|
1081
1228
|
});
|
|
1082
1229
|
var RuleFinding = Type.Object({
|
|
1083
1230
|
name: Str,
|
|
@@ -1122,8 +1269,35 @@ function parseStructureEnvelope(value) {
|
|
|
1122
1269
|
throw new Error("invalid readseek structure: missing structure");
|
|
1123
1270
|
return { ...output.metadata !== undefined ? { metadata: parseDetectOutput(output.metadata) } : {}, structure: parseStructureOutput(output.structure) };
|
|
1124
1271
|
}
|
|
1125
|
-
function
|
|
1126
|
-
|
|
1272
|
+
function parseGrepOutput(value) {
|
|
1273
|
+
const output = parse(GrepOutputSchema, value, "grep output");
|
|
1274
|
+
const invalid = (message) => {
|
|
1275
|
+
throw new Error(`invalid readseek grep output: ${message}`);
|
|
1276
|
+
};
|
|
1277
|
+
const groups = [...output.results, ...output.documents];
|
|
1278
|
+
if (output.returned_matches > output.total_matches || groups.reduce((sum, group) => sum + group.total_matches, 0) !== output.total_matches || groups.reduce((sum, group) => sum + group.returned_matches, 0) !== output.returned_matches)
|
|
1279
|
+
invalid("inconsistent counts");
|
|
1280
|
+
for (const group of groups) {
|
|
1281
|
+
if (group.total_matches === 0 || group.returned_matches > group.total_matches)
|
|
1282
|
+
invalid("inconsistent file counts");
|
|
1283
|
+
if (output.summary ? group.hits.length !== 0 : group.hits.length !== group.returned_matches)
|
|
1284
|
+
invalid("inconsistent hit count");
|
|
1285
|
+
}
|
|
1286
|
+
for (const result of output.results) {
|
|
1287
|
+
if (output.summary && (result.lines.length > 0 || result.symbols.length > 0))
|
|
1288
|
+
invalid("summary contains display entries");
|
|
1289
|
+
if (output.scope !== "symbol" && result.symbols.length > 0)
|
|
1290
|
+
invalid("symbols require symbol scope");
|
|
1291
|
+
for (const hit of result.hits) {
|
|
1292
|
+
if (hit.end_byte < hit.start_byte || Number(hit.anchor.split(":")[0]) !== hit.line)
|
|
1293
|
+
invalid("invalid hit span or anchor");
|
|
1294
|
+
}
|
|
1295
|
+
for (const symbol of result.symbols) {
|
|
1296
|
+
if (symbol.end_line < symbol.start_line || symbol.lines.some((line) => line.line < symbol.start_line || line.line > symbol.end_line))
|
|
1297
|
+
invalid("invalid symbol range");
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
return output;
|
|
1127
1301
|
}
|
|
1128
1302
|
function parseRulesOutput(value) {
|
|
1129
1303
|
return parse(RulesOutput, value, "rules output");
|
|
@@ -1200,71 +1374,71 @@ function runOptions(options, vision = false) {
|
|
|
1200
1374
|
}
|
|
1201
1375
|
function createReadSeekClient(runner) {
|
|
1202
1376
|
return {
|
|
1203
|
-
async
|
|
1204
|
-
const value = await runner.kdl(
|
|
1377
|
+
async show(path, options = {}) {
|
|
1378
|
+
const value = await runner.kdl(buildShowArgs({ ...options, path }), runOptions(options));
|
|
1205
1379
|
return parseContentOutput(value);
|
|
1206
1380
|
},
|
|
1207
|
-
async
|
|
1208
|
-
const value = await runner.kdl(
|
|
1381
|
+
async tree(path, options = {}) {
|
|
1382
|
+
const value = await runner.kdl(buildTreeArgs({ ...options, path }), runOptions(options));
|
|
1209
1383
|
return parseStructureEnvelope(value);
|
|
1210
1384
|
},
|
|
1211
|
-
async
|
|
1212
|
-
const value = await runner.kdl(
|
|
1385
|
+
async meta(path, options = {}) {
|
|
1386
|
+
const value = await runner.kdl(buildMetaArgs({ ...options, path }), runOptions(options));
|
|
1213
1387
|
return parsePdfMetadata(value) ?? parseDetectOutput(value?.metadata);
|
|
1214
1388
|
},
|
|
1215
|
-
async
|
|
1216
|
-
const value = await runner.kdl(
|
|
1389
|
+
async assets(path, options = {}) {
|
|
1390
|
+
const value = await runner.kdl(buildAssetsArgs({ ...options, path }), runOptions(options));
|
|
1217
1391
|
return parseAssetsOutput(value);
|
|
1218
1392
|
},
|
|
1219
|
-
async
|
|
1220
|
-
const value = await runner.kdl(
|
|
1393
|
+
async syntax(path, options = {}) {
|
|
1394
|
+
const value = await runner.kdl(buildSyntaxArgs({ ...options, path }), runOptions(options));
|
|
1221
1395
|
return parseCheckOutput(value?.diagnostics ?? value);
|
|
1222
1396
|
},
|
|
1223
|
-
async
|
|
1224
|
-
const value = await runner.kdl(
|
|
1397
|
+
async check(path, options) {
|
|
1398
|
+
const value = await runner.kdl(buildCheckArgs({ ...options, path }), runOptions(options));
|
|
1225
1399
|
return parseRulesOutput(value);
|
|
1226
1400
|
},
|
|
1227
|
-
async
|
|
1228
|
-
const value = await runner.kdl(
|
|
1401
|
+
async caption(path, options = {}) {
|
|
1402
|
+
const value = await runner.kdl(buildCaptionArgs({ ...options, path }), runOptions(options, true));
|
|
1229
1403
|
return parseContentOutput(value);
|
|
1230
1404
|
},
|
|
1231
|
-
async
|
|
1232
|
-
const value = await runner.kdl(
|
|
1405
|
+
async objects(path, options = {}) {
|
|
1406
|
+
const value = await runner.kdl(buildObjectsArgs({ ...options, path }), runOptions(options, true));
|
|
1233
1407
|
return parseContentOutput(value);
|
|
1234
1408
|
},
|
|
1235
|
-
async
|
|
1236
|
-
const value = await runner.kdl(
|
|
1409
|
+
async ocr(path, options = {}) {
|
|
1410
|
+
const value = await runner.kdl(buildOcrArgs({ ...options, path }), runOptions(options, true));
|
|
1237
1411
|
return parseContentOutput(value);
|
|
1238
1412
|
},
|
|
1239
|
-
async
|
|
1240
|
-
const value = await runner.kdl(
|
|
1413
|
+
async search(path, options) {
|
|
1414
|
+
const value = await runner.kdl(buildSearchArgs({ ...options, path }), runOptions(options));
|
|
1241
1415
|
return parseSearchOutput(value);
|
|
1242
1416
|
},
|
|
1243
|
-
async
|
|
1244
|
-
const value = await runner.kdl(
|
|
1245
|
-
return
|
|
1417
|
+
async grep(params) {
|
|
1418
|
+
const value = await runner.kdl(buildGrepArgs(params), runOptions(params));
|
|
1419
|
+
return parseGrepOutput(value);
|
|
1246
1420
|
},
|
|
1247
|
-
async
|
|
1248
|
-
const value = await runner.kdl(
|
|
1421
|
+
async def(path, options) {
|
|
1422
|
+
const value = await runner.kdl(buildDefArgs({ ...options, path }), runOptions(options));
|
|
1249
1423
|
return parseDefOutput(value);
|
|
1250
1424
|
},
|
|
1251
|
-
async
|
|
1252
|
-
const value = await runner.kdl(
|
|
1425
|
+
async refs(path, options) {
|
|
1426
|
+
const value = await runner.kdl(buildRefsArgs({ ...options, path }), runOptions(options));
|
|
1253
1427
|
return parseRefsOutput(value);
|
|
1254
1428
|
},
|
|
1255
|
-
async
|
|
1429
|
+
async edit(path, edits, options = {}) {
|
|
1256
1430
|
const stdin = serializeEditPlan(edits);
|
|
1257
|
-
return parseEditOutput(await runner.kdl(
|
|
1431
|
+
return parseEditOutput(await runner.kdl(buildEditArgs({ ...options, path, requestFromStdin: true }), runOptions({ ...options, stdin })));
|
|
1258
1432
|
},
|
|
1259
|
-
async
|
|
1260
|
-
const value = await runner.kdl(
|
|
1433
|
+
async rename(path, options) {
|
|
1434
|
+
const value = await runner.kdl(buildRenameArgs({ ...options, path }), runOptions(options));
|
|
1261
1435
|
return parseRenameOutput(value);
|
|
1262
1436
|
}
|
|
1263
1437
|
};
|
|
1264
1438
|
}
|
|
1265
1439
|
// ../readseek-api/src/show-presentation.ts
|
|
1266
1440
|
function isShowTool(name) {
|
|
1267
|
-
return name === "
|
|
1441
|
+
return name === "show" || name === "tree" || name === "meta" || name === "assets";
|
|
1268
1442
|
}
|
|
1269
1443
|
function record(value) {
|
|
1270
1444
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
@@ -1379,7 +1553,7 @@ function presentShowResult(tool, value, options = {}) {
|
|
|
1379
1553
|
const path = inline(options.path || output.source || output.file || content.file || structure.file || metadata.file || cursor.file) || "(unknown path)";
|
|
1380
1554
|
const summary = [];
|
|
1381
1555
|
const result = { summary: "", body: "", notices: [], metadata: {} };
|
|
1382
|
-
if (tool === "
|
|
1556
|
+
if (tool === "meta") {
|
|
1383
1557
|
const data = Object.keys(metadata).length > 0 ? metadata : output;
|
|
1384
1558
|
if (text(data.format || data.language || data.type))
|
|
1385
1559
|
summary.push(inline(data.format || data.language || data.type));
|
|
@@ -1388,7 +1562,7 @@ function presentShowResult(tool, value, options = {}) {
|
|
|
1388
1562
|
summary.push(plural(result.metadata.pages, "page"));
|
|
1389
1563
|
}
|
|
1390
1564
|
result.body = fields(data) || "No metadata available.";
|
|
1391
|
-
} else if (tool === "
|
|
1565
|
+
} else if (tool === "assets") {
|
|
1392
1566
|
const assets = records(output.assets);
|
|
1393
1567
|
result.metadata.assets = assets.length;
|
|
1394
1568
|
summary.push(plural(assets.length, "asset"));
|
|
@@ -1402,7 +1576,7 @@ function presentShowResult(tool, value, options = {}) {
|
|
|
1402
1576
|
summary.push(plural(nodes.length, "node"));
|
|
1403
1577
|
if (result.metadata.pages !== undefined)
|
|
1404
1578
|
summary.push(plural(result.metadata.pages, "page"));
|
|
1405
|
-
const sections = [text(output.title) ? safe(text(output.title)) : "", documentNodes(nodes, tool === "
|
|
1579
|
+
const sections = [text(output.title) ? safe(text(output.title)) : "", documentNodes(nodes, tool === "show")];
|
|
1406
1580
|
if (assets.length > 0) {
|
|
1407
1581
|
result.metadata.assets = assets.length;
|
|
1408
1582
|
sections.push(`Assets
|
|
@@ -1411,7 +1585,7 @@ ${assetList(assets)}`);
|
|
|
1411
1585
|
result.body = sections.filter(Boolean).join(`
|
|
1412
1586
|
|
|
1413
1587
|
`) || "No content in this selection.";
|
|
1414
|
-
} else if (tool === "
|
|
1588
|
+
} else if (tool === "tree") {
|
|
1415
1589
|
const symbols = records(structure.symbols);
|
|
1416
1590
|
result.metadata.symbols = symbols.length;
|
|
1417
1591
|
summary.push(plural(symbols.length, "symbol"));
|
|
@@ -1465,7 +1639,7 @@ ${assetList(assets)}`);
|
|
|
1465
1639
|
const description = Object.keys(content).length > 0 ? content : output;
|
|
1466
1640
|
result.body = fields(description) || "No content in this selection.";
|
|
1467
1641
|
}
|
|
1468
|
-
if (tool === "
|
|
1642
|
+
if (tool === "tree")
|
|
1469
1643
|
result.notices.push("Structure is limited to the requested depth, kind, and outline. Use a deeper depth or focus with at to inspect further.");
|
|
1470
1644
|
if (text(output.warning))
|
|
1471
1645
|
result.notices.push(safe(text(output.warning)));
|
|
@@ -1725,6 +1899,46 @@ function search(root) {
|
|
|
1725
1899
|
}))
|
|
1726
1900
|
};
|
|
1727
1901
|
}
|
|
1902
|
+
function grepChildren(node, allowed) {
|
|
1903
|
+
if (node.children.some((child) => !allowed.includes(child.name))) {
|
|
1904
|
+
throw new Error(`invalid readseek KDL grep: unexpected child of ${node.name}`);
|
|
1905
|
+
}
|
|
1906
|
+
}
|
|
1907
|
+
function grepLine(node) {
|
|
1908
|
+
grepChildren(node, []);
|
|
1909
|
+
const parsed = line(node);
|
|
1910
|
+
if (requireProperty(node, "number") !== parsed.line)
|
|
1911
|
+
throw new Error("invalid readseek KDL grep line number");
|
|
1912
|
+
return { ...parsed, kind: requireProperty(node, "kind") };
|
|
1913
|
+
}
|
|
1914
|
+
function grep(root) {
|
|
1915
|
+
grepChildren(root, ["match", "document"]);
|
|
1916
|
+
return parseGrepOutput({
|
|
1917
|
+
...properties(root),
|
|
1918
|
+
results: children(root, "match").map((match) => {
|
|
1919
|
+
grepChildren(match, ["hit", "line", "symbol"]);
|
|
1920
|
+
return {
|
|
1921
|
+
...properties(match),
|
|
1922
|
+
hits: children(match, "hit").map((hit) => {
|
|
1923
|
+
grepChildren(hit, []);
|
|
1924
|
+
return properties(hit);
|
|
1925
|
+
}),
|
|
1926
|
+
lines: children(match, "line").map(grepLine),
|
|
1927
|
+
symbols: children(match, "symbol").map((symbol) => {
|
|
1928
|
+
grepChildren(symbol, ["line"]);
|
|
1929
|
+
return { ...properties(symbol), lines: children(symbol, "line").map(grepLine) };
|
|
1930
|
+
})
|
|
1931
|
+
};
|
|
1932
|
+
}),
|
|
1933
|
+
documents: children(root, "document").map((document) => {
|
|
1934
|
+
grepChildren(document, ["hit"]);
|
|
1935
|
+
return { ...properties(document), hits: children(document, "hit").map((hit) => {
|
|
1936
|
+
grepChildren(hit, []);
|
|
1937
|
+
return properties(hit);
|
|
1938
|
+
}) };
|
|
1939
|
+
})
|
|
1940
|
+
});
|
|
1941
|
+
}
|
|
1728
1942
|
function references(root) {
|
|
1729
1943
|
const parsed = children(root, "reference").map((reference) => {
|
|
1730
1944
|
const sourceLine = child(reference, "line");
|
|
@@ -1840,7 +2054,7 @@ function parseReadSeekKdl(text) {
|
|
|
1840
2054
|
case "document":
|
|
1841
2055
|
return root.properties.has("format") ? pdfDocument(root) : sourceDocument(root);
|
|
1842
2056
|
case "search":
|
|
1843
|
-
return search(root);
|
|
2057
|
+
return ["mode", "ignore_case", "summary", "count_unit"].some((key) => root.properties.has(key)) ? grep(root) : search(root);
|
|
1844
2058
|
case "check":
|
|
1845
2059
|
return diagnostics(root);
|
|
1846
2060
|
case "refs":
|
|
@@ -1905,7 +2119,7 @@ function acceptsReadSeekExitCode(args, exitCode) {
|
|
|
1905
2119
|
index++;
|
|
1906
2120
|
continue;
|
|
1907
2121
|
}
|
|
1908
|
-
return arg === "
|
|
2122
|
+
return arg === "grep" || arg === "search" || arg === "def" || arg === "refs" || arg === "syntax" || arg === "check";
|
|
1909
2123
|
}
|
|
1910
2124
|
return false;
|
|
1911
2125
|
}
|
|
@@ -2491,7 +2705,7 @@ function formatReplaceHint(edits, noopEdits) {
|
|
|
2491
2705
|
return;
|
|
2492
2706
|
if (counts.replace !== counts.total)
|
|
2493
2707
|
return;
|
|
2494
|
-
return "[info: this edit used replace (unverified). For safer future edits, prefer set_line/replace_lines with an anchor from
|
|
2708
|
+
return "[info: this edit used replace (unverified). For safer future edits, prefer set_line/replace_lines with an anchor from readSeek_show/readSeek_grep/readSeek_search.]";
|
|
2495
2709
|
}
|
|
2496
2710
|
function buildEditOutput(input) {
|
|
2497
2711
|
const summary = `Updated ${input.displayPath}`;
|
|
@@ -2591,10 +2805,10 @@ import { readFileSync, statSync as statSync2 } from "node:fs";
|
|
|
2591
2805
|
import { homedir as homedir2 } from "node:os";
|
|
2592
2806
|
import { join } from "node:path";
|
|
2593
2807
|
var READSEEK_TOOL_REPLACEMENTS = {
|
|
2594
|
-
read: "
|
|
2595
|
-
edit: "
|
|
2596
|
-
grep: "
|
|
2597
|
-
write: "
|
|
2808
|
+
read: "readSeek_show",
|
|
2809
|
+
edit: "readSeek_edit",
|
|
2810
|
+
grep: "readSeek_grep",
|
|
2811
|
+
write: "readSeek_write"
|
|
2598
2812
|
};
|
|
2599
2813
|
var READSEEK_KEYS = ["overrideTools", "imageMode", "syntaxValidation", "timeoutMs", "grep", "display"];
|
|
2600
2814
|
var READSEEK_GREP_KEYS = ["maxLines", "maxBytes"];
|
|
@@ -2830,7 +3044,7 @@ async function ensureDefaultReadSeekDir() {
|
|
|
2830
3044
|
return null;
|
|
2831
3045
|
if (directoryExists(dir))
|
|
2832
3046
|
return dir;
|
|
2833
|
-
defaultReadSeekDirInit ??= runReadSeekNodeText(readSeekBinaryPath(), ["--readseek-dir", dir, "
|
|
3047
|
+
defaultReadSeekDirInit ??= runReadSeekNodeText(readSeekBinaryPath(), ["--readseek-dir", dir, "init"], { timeoutMs: readSeekTimeoutMs() }).then(() => directoryExists(dir) ? dir : null).catch(() => null).finally(() => {
|
|
2834
3048
|
defaultReadSeekDirInit = undefined;
|
|
2835
3049
|
});
|
|
2836
3050
|
return defaultReadSeekDirInit;
|
|
@@ -2848,18 +3062,7 @@ function hasProjectReadSeekDir(cwd = process.cwd()) {
|
|
|
2848
3062
|
}
|
|
2849
3063
|
function supportsReadSeekDir(args) {
|
|
2850
3064
|
const first = args[0];
|
|
2851
|
-
|
|
2852
|
-
if (first === "initialize" && second === "index")
|
|
2853
|
-
return true;
|
|
2854
|
-
if (first === "show" && (second === "content" || second === "assets"))
|
|
2855
|
-
return true;
|
|
2856
|
-
if (first === "generate" && second === "caption")
|
|
2857
|
-
return true;
|
|
2858
|
-
if (first === "detect" && second === "objects")
|
|
2859
|
-
return true;
|
|
2860
|
-
if (first === "recognize" && second === "text")
|
|
2861
|
-
return true;
|
|
2862
|
-
return false;
|
|
3065
|
+
return first === "init" || first === "show" || first === "assets" || first === "caption" || first === "objects" || first === "ocr";
|
|
2863
3066
|
}
|
|
2864
3067
|
async function readSeekInvocationArgs(args) {
|
|
2865
3068
|
if (!supportsReadSeekDir(args) || hasProjectReadSeekDir())
|
|
@@ -2917,27 +3120,21 @@ var runner = {
|
|
|
2917
3120
|
kdl: async (args, options = {}) => parseReadSeekKdl(await runner.text(args, options))
|
|
2918
3121
|
};
|
|
2919
3122
|
var client2 = createReadSeekClient(runner);
|
|
2920
|
-
var
|
|
2921
|
-
var
|
|
2922
|
-
var
|
|
2923
|
-
var
|
|
2924
|
-
var
|
|
2925
|
-
var
|
|
2926
|
-
var
|
|
2927
|
-
var
|
|
2928
|
-
var
|
|
2929
|
-
var
|
|
2930
|
-
var
|
|
2931
|
-
var
|
|
2932
|
-
var
|
|
2933
|
-
var
|
|
2934
|
-
|
|
2935
|
-
const envelope = await client2.showStructure(filePath, { depth: DEFAULT_STRUCTURE_DEPTH, signal: options.signal });
|
|
2936
|
-
const map = "structure" in envelope ? envelope.structure : undefined;
|
|
2937
|
-
if (!map || map.language === "unknown" && map.symbols.length === 0)
|
|
2938
|
-
return null;
|
|
2939
|
-
return map;
|
|
2940
|
-
}
|
|
3123
|
+
var readSeekShow = client2.show;
|
|
3124
|
+
var readSeekTree = client2.tree;
|
|
3125
|
+
var readSeekMeta = client2.meta;
|
|
3126
|
+
var readSeekAssets = client2.assets;
|
|
3127
|
+
var readSeekCaption = client2.caption;
|
|
3128
|
+
var readSeekObjects = client2.objects;
|
|
3129
|
+
var readSeekOcr = client2.ocr;
|
|
3130
|
+
var readSeekSearch = client2.search;
|
|
3131
|
+
var readSeekGrep = client2.grep;
|
|
3132
|
+
var readSeekRefs = client2.refs;
|
|
3133
|
+
var readSeekDef = client2.def;
|
|
3134
|
+
var readSeekSyntax = client2.syntax;
|
|
3135
|
+
var readSeekCheck = client2.check;
|
|
3136
|
+
var readSeekEdit = client2.edit;
|
|
3137
|
+
var readSeekRename = client2.rename;
|
|
2941
3138
|
|
|
2942
3139
|
// src/edit-syntax-validate.ts
|
|
2943
3140
|
function dedupeSortLines(diagnostics) {
|
|
@@ -2958,8 +3155,8 @@ async function validateSyntaxRegression(input, options = {}) {
|
|
|
2958
3155
|
let before;
|
|
2959
3156
|
let after;
|
|
2960
3157
|
try {
|
|
2961
|
-
before = input.before === undefined ? EMPTY : await
|
|
2962
|
-
after = await
|
|
3158
|
+
before = input.before === undefined ? EMPTY : await readSeekSyntax("-", { stdinName: input.filePath, stdin: input.before, signal: options.signal });
|
|
3159
|
+
after = await readSeekSyntax("-", { stdinName: input.filePath, stdin: input.after, signal: options.signal });
|
|
2963
3160
|
} catch {
|
|
2964
3161
|
return null;
|
|
2965
3162
|
}
|
|
@@ -3645,7 +3842,7 @@ async function executeEdit(opts) {
|
|
|
3645
3842
|
return await withFileMutationQueue(absolutePath, async () => {
|
|
3646
3843
|
throwIfAborted(signal);
|
|
3647
3844
|
if (wasReadInSession && !wasReadInSession(absolutePath)) {
|
|
3648
|
-
const hint = `Call
|
|
3845
|
+
const hint = `Call readSeek_show(${JSON.stringify(rawPath)}) first, or use readSeek_grep, readSeek_search, or readSeek_write to produce fresh anchors for this file.`;
|
|
3649
3846
|
return buildEditError(absolutePath, "file-not-read", `You must get fresh anchors for ${absolutePath} before editing it. ${hint}`, hint);
|
|
3650
3847
|
}
|
|
3651
3848
|
const parsedEdits = validateExactlyOneEditVariant(parsed.edits ?? []);
|
|
@@ -3655,7 +3852,7 @@ async function executeEdit(opts) {
|
|
|
3655
3852
|
const edits = parsedEdits.edits;
|
|
3656
3853
|
let plan;
|
|
3657
3854
|
try {
|
|
3658
|
-
plan = await
|
|
3855
|
+
plan = await readSeekEdit(absolutePath, edits, { signal, language: parsed.language });
|
|
3659
3856
|
} catch (err) {
|
|
3660
3857
|
throwIfAborted(signal);
|
|
3661
3858
|
const failure = classifyReadSeekFailure(err);
|
|
@@ -3691,7 +3888,7 @@ async function executeEdit(opts) {
|
|
|
3691
3888
|
throwIfAborted(signal);
|
|
3692
3889
|
let applied;
|
|
3693
3890
|
try {
|
|
3694
|
-
applied = await
|
|
3891
|
+
applied = await readSeekEdit(absolutePath, edits, {
|
|
3695
3892
|
apply: true,
|
|
3696
3893
|
planHash: plan.plan_hash,
|
|
3697
3894
|
language: parsed.language,
|
|
@@ -3759,9 +3956,9 @@ async function executeEdit(opts) {
|
|
|
3759
3956
|
}
|
|
3760
3957
|
}
|
|
3761
3958
|
function registerEditTool(pi, options = {}) {
|
|
3762
|
-
const name = options.name ?? "
|
|
3959
|
+
const name = options.name ?? "readSeek_edit";
|
|
3763
3960
|
const promptMetadata = defineToolPromptMetadata({
|
|
3764
|
-
toolName: "
|
|
3961
|
+
toolName: "edit",
|
|
3765
3962
|
promptSnippet: "Edit existing files with fresh LINE:HASH anchors",
|
|
3766
3963
|
registeredName: name,
|
|
3767
3964
|
toolAliases: options.toolAliases
|
|
@@ -3857,29 +4054,12 @@ ${theme.fg("error", info.errorText)}`;
|
|
|
3857
4054
|
}
|
|
3858
4055
|
|
|
3859
4056
|
// src/grep.ts
|
|
3860
|
-
import { readFile as fsReadFile2, stat as fsStat } from "node:fs/promises";
|
|
3861
|
-
import path5 from "node:path";
|
|
3862
|
-
import { createGrepTool } from "@earendil-works/pi-coding-agent";
|
|
3863
4057
|
import { Type as Type5 } from "@sinclair/typebox";
|
|
3864
4058
|
import { Text as Text4 } from "@earendil-works/pi-tui";
|
|
3865
4059
|
|
|
3866
|
-
// src/binary-detect.ts
|
|
3867
|
-
function looksLikeBinary(buf) {
|
|
3868
|
-
if (buf.length === 0)
|
|
3869
|
-
return false;
|
|
3870
|
-
if (buf.includes(0))
|
|
3871
|
-
return true;
|
|
3872
|
-
const decoded = buf.toString("utf8");
|
|
3873
|
-
if (!decoded.includes("�"))
|
|
3874
|
-
return false;
|
|
3875
|
-
return !Buffer.from(decoded, "utf8").equals(buf);
|
|
3876
|
-
}
|
|
3877
|
-
|
|
3878
4060
|
// src/grep-output.ts
|
|
3879
|
-
import
|
|
3880
|
-
|
|
3881
|
-
truncateHead
|
|
3882
|
-
} from "@earendil-works/pi-coding-agent";
|
|
4061
|
+
import path5 from "node:path";
|
|
4062
|
+
import { formatSize, truncateHead } from "@earendil-works/pi-coding-agent";
|
|
3883
4063
|
|
|
3884
4064
|
// src/grep-budget.ts
|
|
3885
4065
|
import { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES } from "@earendil-works/pi-coding-agent";
|
|
@@ -3899,283 +4079,75 @@ function resolveGrepOutputBudget() {
|
|
|
3899
4079
|
}
|
|
3900
4080
|
|
|
3901
4081
|
// src/grep-output.ts
|
|
3902
|
-
function
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
}
|
|
3911
|
-
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
|
|
3918
|
-
}
|
|
3919
|
-
|
|
3920
|
-
return {
|
|
3921
|
-
mode: "symbol",
|
|
3922
|
-
groups: groups.filter(hasScope).map((group) => ({
|
|
3923
|
-
path: group.absolutePath,
|
|
3924
|
-
displayPath: group.displayPath,
|
|
3925
|
-
symbol: group.scope.symbol,
|
|
3926
|
-
matchCount: group.matchCount,
|
|
3927
|
-
matchLines: [...group.scope.matchLines],
|
|
3928
|
-
lineAnchors: group.entries.flatMap((entry) => entry.kind === "separator" ? [] : [entry.line.anchor])
|
|
3929
|
-
})),
|
|
3930
|
-
warnings: [...warnings]
|
|
3931
|
-
};
|
|
3932
|
-
}
|
|
3933
|
-
function buildGrepOutput(input) {
|
|
3934
|
-
const fileCount = new Set(input.groups.map((group) => group.absolutePath)).size;
|
|
3935
|
-
const header = `[${input.totalMatches} matches in ${fileCount} files]`;
|
|
3936
|
-
let text;
|
|
3937
|
-
if (input.summary) {
|
|
3938
|
-
const fileLines = [...input.groups].sort((a, b) => b.matchCount - a.matchCount).map((group) => `${group.absolutePath}: ${group.matchCount} matches`);
|
|
3939
|
-
text = [header, ...fileLines].join(`
|
|
3940
|
-
`);
|
|
3941
|
-
} else {
|
|
3942
|
-
const blocks = [header];
|
|
3943
|
-
for (const group of input.groups) {
|
|
3944
|
-
blocks.push(renderGroupHeader(group));
|
|
3945
|
-
for (const entry of group.entries) {
|
|
3946
|
-
blocks.push(renderEntry(group.displayPath, entry));
|
|
4082
|
+
function buildGrepOutput(output, cwd) {
|
|
4083
|
+
const recordBlocks = new Map;
|
|
4084
|
+
const fileCount = new Set([...output.results, ...output.documents].map((group) => path5.resolve(cwd, group.file))).size;
|
|
4085
|
+
const blocks = [`[${output.total_matches} matches in ${fileCount} files]`];
|
|
4086
|
+
for (const group of output.results) {
|
|
4087
|
+
const absolutePath = path5.resolve(cwd, group.file);
|
|
4088
|
+
const displayPath = path5.relative(cwd, absolutePath) || absolutePath;
|
|
4089
|
+
if (output.summary) {
|
|
4090
|
+
blocks.push(`${absolutePath}: ${group.total_matches} matches`);
|
|
4091
|
+
continue;
|
|
4092
|
+
}
|
|
4093
|
+
blocks.push(`--- ${displayPath} (${group.total_matches} matches) ---`);
|
|
4094
|
+
const renderLines = (lines) => {
|
|
4095
|
+
for (const line of lines) {
|
|
4096
|
+
const built = buildReadSeekLineWithHash(line.line, line.hash, line.text);
|
|
4097
|
+
const marker = line.kind === "match" ? ">>" : " ";
|
|
4098
|
+
blocks.push(`${displayPath}:${marker}${built.anchor}|${built.display}`);
|
|
4099
|
+
recordBlocks.set(blocks.length - 1, { path: absolutePath, line: line.line, anchor: built.anchor, kind: line.kind });
|
|
3947
4100
|
}
|
|
4101
|
+
};
|
|
4102
|
+
renderLines(group.lines);
|
|
4103
|
+
for (const symbol of group.symbols) {
|
|
4104
|
+
blocks.push(`--- ${displayPath} :: ${symbol.kind} ${symbol.name} (${symbol.start_line}-${symbol.end_line}) ---`);
|
|
4105
|
+
renderLines(symbol.lines);
|
|
3948
4106
|
}
|
|
3949
|
-
text = blocks.join(`
|
|
3950
|
-
`);
|
|
3951
4107
|
}
|
|
3952
|
-
const
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
${
|
|
3957
|
-
|
|
3958
|
-
|
|
3959
|
-
|
|
3960
|
-
|
|
3961
|
-
|
|
3962
|
-
|
|
4108
|
+
for (const group of output.documents) {
|
|
4109
|
+
const absolutePath = path5.resolve(cwd, group.file);
|
|
4110
|
+
const displayPath = path5.relative(cwd, absolutePath) || absolutePath;
|
|
4111
|
+
if (output.summary) {
|
|
4112
|
+
blocks.push(`${absolutePath}: ${group.total_matches} matches`);
|
|
4113
|
+
continue;
|
|
4114
|
+
}
|
|
4115
|
+
blocks.push(`--- ${displayPath} (${group.total_matches} matches, document) ---`);
|
|
4116
|
+
for (const hit of group.hits) {
|
|
4117
|
+
const page = hit.page === undefined ? "" : ` page:${hit.page}`;
|
|
4118
|
+
blocks.push(`${displayPath} node:${hit.node_id}${page} (${hit.kind}): ${escapeControlCharsForDisplay(hit.snippet)}`);
|
|
4119
|
+
}
|
|
3963
4120
|
}
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
3967
|
-
|
|
3968
|
-
`)}
|
|
3969
|
-
|
|
3970
|
-
${text}`;
|
|
4121
|
+
if (output.truncated) {
|
|
4122
|
+
blocks.push(`
|
|
4123
|
+
[Results truncated: ${output.returned_matches} of ${output.total_matches} matching lines returned — refine pattern or increase limit]`);
|
|
3971
4124
|
}
|
|
4125
|
+
let text = blocks.join(`
|
|
4126
|
+
`);
|
|
3972
4127
|
const budget = resolveGrepOutputBudget();
|
|
3973
|
-
const truncated = truncateHead(text, {
|
|
3974
|
-
|
|
3975
|
-
|
|
4128
|
+
const truncated = truncateHead(text, { maxLines: budget.maxLines, maxBytes: budget.maxBytes });
|
|
4129
|
+
let endOffset = 0;
|
|
4130
|
+
const records = blocks.flatMap((block, index) => {
|
|
4131
|
+
endOffset += block.length + (index > 0 ? 1 : 0);
|
|
4132
|
+
const record = recordBlocks.get(index);
|
|
4133
|
+
return record && endOffset <= truncated.content.length ? [record] : [];
|
|
3976
4134
|
});
|
|
3977
4135
|
if (truncated.truncated) {
|
|
3978
4136
|
text = `${truncated.content}
|
|
3979
4137
|
|
|
3980
4138
|
[Output truncated: showing ${truncated.outputLines} of ${truncated.totalLines} lines (${formatSize(truncated.outputBytes)} of ${formatSize(truncated.totalBytes)}). Refine pattern or increase limit.]`;
|
|
3981
4139
|
}
|
|
3982
|
-
const readSeekValue = {
|
|
3983
|
-
tool: "grep",
|
|
3984
|
-
summary: input.summary,
|
|
3985
|
-
totalMatches: input.totalMatches,
|
|
3986
|
-
records: input.records.map((record) => ({
|
|
3987
|
-
path: record.path,
|
|
3988
|
-
line: record.line,
|
|
3989
|
-
anchor: record.anchor,
|
|
3990
|
-
kind: record.kind
|
|
3991
|
-
}))
|
|
3992
|
-
};
|
|
3993
|
-
if (!input.summary && input.scopeMode === "symbol") {
|
|
3994
|
-
readSeekValue.scopes = buildScopeMetadata(input.groups, scopeWarnings);
|
|
3995
|
-
}
|
|
3996
4140
|
return {
|
|
3997
4141
|
text,
|
|
3998
|
-
readSeekValue
|
|
3999
|
-
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
return await readSeekMap(absPath);
|
|
4006
|
-
} catch {
|
|
4007
|
-
return null;
|
|
4008
|
-
}
|
|
4009
|
-
}
|
|
4010
|
-
|
|
4011
|
-
// src/grep-symbol-scope.ts
|
|
4012
|
-
function findEnclosingSymbol(map, lineNumber) {
|
|
4013
|
-
const byQualifiedName = new Map;
|
|
4014
|
-
for (const symbol of map.symbols) {
|
|
4015
|
-
const qname = symbol.qualified_name ?? symbol.name;
|
|
4016
|
-
if (!byQualifiedName.has(qname))
|
|
4017
|
-
byQualifiedName.set(qname, symbol);
|
|
4018
|
-
}
|
|
4019
|
-
const candidates = map.symbols.filter((symbol) => lineNumber >= symbol.start_line && lineNumber <= symbol.end_line).map((symbol) => {
|
|
4020
|
-
const qname = symbol.qualified_name ?? symbol.name;
|
|
4021
|
-
const dot = qname.lastIndexOf(".");
|
|
4022
|
-
const parent = dot === -1 ? undefined : byQualifiedName.get(qname.slice(0, dot));
|
|
4023
|
-
return {
|
|
4024
|
-
name: symbol.name,
|
|
4025
|
-
kind: symbol.kind === "constructor" ? "method" : symbol.kind,
|
|
4026
|
-
startLine: symbol.start_line,
|
|
4027
|
-
endLine: symbol.end_line,
|
|
4028
|
-
parentName: parent?.name
|
|
4029
|
-
};
|
|
4030
|
-
}).sort((a, b) => {
|
|
4031
|
-
const rangeA = a.endLine - a.startLine;
|
|
4032
|
-
const rangeB = b.endLine - b.startLine;
|
|
4033
|
-
if (rangeA !== rangeB)
|
|
4034
|
-
return rangeA - rangeB;
|
|
4035
|
-
if (a.startLine !== b.startLine)
|
|
4036
|
-
return a.startLine - b.startLine;
|
|
4037
|
-
return a.name.localeCompare(b.name);
|
|
4038
|
-
});
|
|
4039
|
-
return candidates[0] ?? null;
|
|
4040
|
-
}
|
|
4041
|
-
function firstLineNumber(group) {
|
|
4042
|
-
const first = group.entries.find((e) => e.kind !== "separator");
|
|
4043
|
-
return first ? first.line.line : Number.MAX_SAFE_INTEGER;
|
|
4044
|
-
}
|
|
4045
|
-
function buildSymbolEntries(fileLines, symbol, matchLines, scopeContext) {
|
|
4046
|
-
if (scopeContext === undefined) {
|
|
4047
|
-
const entries = [];
|
|
4048
|
-
for (let lineNumber = symbol.startLine;lineNumber <= symbol.endLine; lineNumber++) {
|
|
4049
|
-
const built = buildReadSeekLine(lineNumber, fileLines[lineNumber - 1] ?? "");
|
|
4050
|
-
entries.push({ kind: matchLines.has(lineNumber) ? "match" : "context", line: built });
|
|
4051
|
-
}
|
|
4052
|
-
return entries;
|
|
4053
|
-
}
|
|
4054
|
-
const ranges = [...matchLines].sort((a, b) => a - b).map((ln) => ({
|
|
4055
|
-
startLine: Math.max(symbol.startLine, ln - scopeContext),
|
|
4056
|
-
endLine: Math.min(symbol.endLine, ln + scopeContext)
|
|
4057
|
-
}));
|
|
4058
|
-
const merged = [];
|
|
4059
|
-
for (const r of ranges) {
|
|
4060
|
-
const last = merged[merged.length - 1];
|
|
4061
|
-
if (last && r.startLine <= last.endLine + 1) {
|
|
4062
|
-
last.endLine = Math.max(last.endLine, r.endLine);
|
|
4063
|
-
} else {
|
|
4064
|
-
merged.push({ ...r });
|
|
4065
|
-
}
|
|
4066
|
-
}
|
|
4067
|
-
const entries = [];
|
|
4068
|
-
for (let i = 0;i < merged.length; i++) {
|
|
4069
|
-
if (i > 0)
|
|
4070
|
-
entries.push({ kind: "separator", text: "--" });
|
|
4071
|
-
const range = merged[i];
|
|
4072
|
-
for (let ln = range.startLine;ln <= range.endLine; ln++) {
|
|
4073
|
-
const built = buildReadSeekLine(ln, fileLines[ln - 1] ?? "");
|
|
4074
|
-
entries.push({ kind: matchLines.has(ln) ? "match" : "context", line: built });
|
|
4075
|
-
}
|
|
4076
|
-
}
|
|
4077
|
-
return entries;
|
|
4078
|
-
}
|
|
4079
|
-
function buildFallbackEntries(fileLines, matchLines, contextLines) {
|
|
4080
|
-
const lineMap = new Map;
|
|
4081
|
-
for (const matchLine of matchLines) {
|
|
4082
|
-
const start = Math.max(1, matchLine - contextLines);
|
|
4083
|
-
const end = Math.min(fileLines.length, matchLine + contextLines);
|
|
4084
|
-
for (let lineNumber = start;lineNumber <= end; lineNumber++) {
|
|
4085
|
-
const built = buildReadSeekLine(lineNumber, fileLines[lineNumber - 1] ?? "");
|
|
4086
|
-
const candidate = { kind: lineNumber === matchLine ? "match" : "context", line: built };
|
|
4087
|
-
const existing = lineMap.get(lineNumber);
|
|
4088
|
-
if (!existing || existing.kind === "context" && candidate.kind === "match") {
|
|
4089
|
-
lineMap.set(lineNumber, candidate);
|
|
4090
|
-
}
|
|
4091
|
-
}
|
|
4092
|
-
}
|
|
4093
|
-
const ordered = [...lineMap.entries()].sort(([a], [b]) => a - b);
|
|
4094
|
-
const entries = [];
|
|
4095
|
-
for (let i = 0;i < ordered.length; i++) {
|
|
4096
|
-
if (i > 0 && ordered[i][0] > ordered[i - 1][0] + 1)
|
|
4097
|
-
entries.push({ kind: "separator", text: "--" });
|
|
4098
|
-
entries.push(ordered[i][1]);
|
|
4099
|
-
}
|
|
4100
|
-
return entries;
|
|
4101
|
-
}
|
|
4102
|
-
function scopeGrepGroupsToSymbols(input) {
|
|
4103
|
-
const warnings = [];
|
|
4104
|
-
const rendered = [];
|
|
4105
|
-
for (const group of input.groups) {
|
|
4106
|
-
const fileLines = input.fileLinesByPath.get(group.absolutePath);
|
|
4107
|
-
const fileMap = input.fileMapsByPath.get(group.absolutePath) ?? null;
|
|
4108
|
-
if (!fileLines || !fileMap) {
|
|
4109
|
-
warnings.push({
|
|
4110
|
-
code: "unmappable-file",
|
|
4111
|
-
message: `[Warning: symbol scoping unavailable for ${group.absolutePath} — showing normal grep lines for this file]`,
|
|
4112
|
-
path: group.absolutePath
|
|
4113
|
-
});
|
|
4114
|
-
rendered.push({ order: firstLineNumber(group), group });
|
|
4115
|
-
continue;
|
|
4116
|
-
}
|
|
4117
|
-
const symbolBuckets = new Map;
|
|
4118
|
-
const fallbackMatchLines = [];
|
|
4119
|
-
for (const entry of group.entries) {
|
|
4120
|
-
if (entry.kind !== "match")
|
|
4121
|
-
continue;
|
|
4122
|
-
const symbol = findEnclosingSymbol(fileMap, entry.line.line);
|
|
4123
|
-
if (!symbol) {
|
|
4124
|
-
fallbackMatchLines.push(entry.line.line);
|
|
4125
|
-
warnings.push({
|
|
4126
|
-
code: "no-enclosing-symbol",
|
|
4127
|
-
message: `[Warning: no enclosing symbol for ${group.absolutePath}:${entry.line.line} — showing normal grep lines for this match]`,
|
|
4128
|
-
path: group.absolutePath,
|
|
4129
|
-
line: entry.line.line
|
|
4130
|
-
});
|
|
4131
|
-
continue;
|
|
4132
|
-
}
|
|
4133
|
-
const key = `${symbol.startLine}:${symbol.endLine}:${symbol.parentName ?? ""}:${symbol.name}`;
|
|
4134
|
-
const bucket = symbolBuckets.get(key) ?? { symbol, matchLines: new Set };
|
|
4135
|
-
bucket.matchLines.add(entry.line.line);
|
|
4136
|
-
symbolBuckets.set(key, bucket);
|
|
4137
|
-
}
|
|
4138
|
-
const scopedGroups = [...symbolBuckets.values()].sort((a, b) => {
|
|
4139
|
-
if (a.symbol.startLine !== b.symbol.startLine)
|
|
4140
|
-
return a.symbol.startLine - b.symbol.startLine;
|
|
4141
|
-
return a.symbol.name.localeCompare(b.symbol.name);
|
|
4142
|
-
}).map(({ symbol, matchLines }) => ({
|
|
4143
|
-
displayPath: group.displayPath,
|
|
4144
|
-
absolutePath: group.absolutePath,
|
|
4145
|
-
matchCount: matchLines.size,
|
|
4146
|
-
scope: {
|
|
4147
|
-
mode: "symbol",
|
|
4148
|
-
symbol,
|
|
4149
|
-
matchLines: [...matchLines].sort((a, b) => a - b),
|
|
4150
|
-
...input.scopeContext !== undefined ? { contextLines: input.scopeContext } : {}
|
|
4151
|
-
},
|
|
4152
|
-
entries: buildSymbolEntries(fileLines, symbol, matchLines, input.scopeContext)
|
|
4153
|
-
}));
|
|
4154
|
-
for (const scopedGroup of scopedGroups)
|
|
4155
|
-
rendered.push({ order: scopedGroup.scope.symbol.startLine, group: scopedGroup });
|
|
4156
|
-
if (fallbackMatchLines.length > 0) {
|
|
4157
|
-
rendered.push({
|
|
4158
|
-
order: Math.min(...fallbackMatchLines),
|
|
4159
|
-
group: {
|
|
4160
|
-
displayPath: group.displayPath,
|
|
4161
|
-
absolutePath: group.absolutePath,
|
|
4162
|
-
matchCount: fallbackMatchLines.length,
|
|
4163
|
-
entries: buildFallbackEntries(fileLines, fallbackMatchLines, input.contextLines)
|
|
4164
|
-
}
|
|
4165
|
-
});
|
|
4166
|
-
}
|
|
4167
|
-
if (scopedGroups.length === 0 && fallbackMatchLines.length === 0) {
|
|
4168
|
-
rendered.push({ order: firstLineNumber(group), group });
|
|
4142
|
+
readSeekValue: {
|
|
4143
|
+
...output,
|
|
4144
|
+
tool: "grep",
|
|
4145
|
+
totalMatches: output.total_matches,
|
|
4146
|
+
returnedMatches: output.returned_matches,
|
|
4147
|
+
fileCount,
|
|
4148
|
+
records
|
|
4169
4149
|
}
|
|
4170
|
-
}
|
|
4171
|
-
rendered.sort((a, b) => {
|
|
4172
|
-
if (a.order !== b.order)
|
|
4173
|
-
return a.order - b.order;
|
|
4174
|
-
if (a.group.absolutePath !== b.group.absolutePath)
|
|
4175
|
-
return a.group.absolutePath.localeCompare(b.group.absolutePath);
|
|
4176
|
-
return a.group.displayPath.localeCompare(b.group.displayPath);
|
|
4177
|
-
});
|
|
4178
|
-
return { groups: rendered.map((item) => item.group), warnings };
|
|
4150
|
+
};
|
|
4179
4151
|
}
|
|
4180
4152
|
|
|
4181
4153
|
// src/grep-render-helpers.ts
|
|
@@ -4193,7 +4165,6 @@ function formatGrepCallText(args) {
|
|
|
4193
4165
|
suffix: parts.length > 0 ? parts.join(" ") : undefined
|
|
4194
4166
|
};
|
|
4195
4167
|
}
|
|
4196
|
-
var GREP_TRUNCATION_THRESHOLD = 50;
|
|
4197
4168
|
function formatGrepResultText(input) {
|
|
4198
4169
|
if (input.isError && input.errorText) {
|
|
4199
4170
|
return {
|
|
@@ -4206,17 +4177,15 @@ function formatGrepResultText(input) {
|
|
|
4206
4177
|
}
|
|
4207
4178
|
const { totalMatches, summary, fileCount } = input;
|
|
4208
4179
|
const noMatches = totalMatches === 0;
|
|
4209
|
-
const truncated =
|
|
4180
|
+
const truncated = input.truncated ?? false;
|
|
4210
4181
|
const matchWord = totalMatches === 1 ? "match" : "matches";
|
|
4211
4182
|
const fileWord = fileCount === 1 ? "file" : "files";
|
|
4212
4183
|
const summaryText = noMatches ? "" : `✓ ${totalMatches} ${matchWord} in ${fileCount} ${fileWord}`;
|
|
4213
4184
|
const badges = [];
|
|
4214
4185
|
if (truncated)
|
|
4215
|
-
badges.push("
|
|
4186
|
+
badges.push("truncated");
|
|
4216
4187
|
if (summary)
|
|
4217
4188
|
badges.push("summary");
|
|
4218
|
-
if (input.hasBinaryWarning)
|
|
4219
|
-
badges.push("⚠ binary");
|
|
4220
4189
|
return {
|
|
4221
4190
|
summary: summaryText,
|
|
4222
4191
|
badges,
|
|
@@ -4269,78 +4238,12 @@ var grepSchema = Type5.Object({
|
|
|
4269
4238
|
scope: Type5.Optional(Type5.Literal("symbol", {
|
|
4270
4239
|
description: PARAM_DESCRIPTIONS.grepScope
|
|
4271
4240
|
})),
|
|
4272
|
-
scopeContext: optionalIntOrString(PARAM_DESCRIPTIONS.scopeContext)
|
|
4241
|
+
scopeContext: optionalIntOrString(PARAM_DESCRIPTIONS.scopeContext),
|
|
4242
|
+
...readSeekGitSearchParams()
|
|
4273
4243
|
});
|
|
4274
|
-
var MATCH_LINE_RE = /^(.*?):(\d+): (.*)$/;
|
|
4275
|
-
var CONTEXT_LINE_RE = /^(.*?)-(\d+)- (.*)$/;
|
|
4276
|
-
function parseGrepOutputLine(line) {
|
|
4277
|
-
const match = line.match(MATCH_LINE_RE);
|
|
4278
|
-
if (match) {
|
|
4279
|
-
return {
|
|
4280
|
-
kind: "match",
|
|
4281
|
-
displayPath: match[1],
|
|
4282
|
-
lineNumber: Number.parseInt(match[2], 10),
|
|
4283
|
-
text: match[3]
|
|
4284
|
-
};
|
|
4285
|
-
}
|
|
4286
|
-
const context = line.match(CONTEXT_LINE_RE);
|
|
4287
|
-
if (context) {
|
|
4288
|
-
return {
|
|
4289
|
-
kind: "context",
|
|
4290
|
-
displayPath: context[1],
|
|
4291
|
-
lineNumber: Number.parseInt(context[2], 10),
|
|
4292
|
-
text: context[3]
|
|
4293
|
-
};
|
|
4294
|
-
}
|
|
4295
|
-
return null;
|
|
4296
|
-
}
|
|
4297
|
-
var GREP_MAX_MATCHES_PER_FILE = 10;
|
|
4298
|
-
function dedupeContextEntries(entries) {
|
|
4299
|
-
if (entries.length === 0)
|
|
4300
|
-
return entries;
|
|
4301
|
-
const byLine = new Map;
|
|
4302
|
-
for (const entry of entries) {
|
|
4303
|
-
if (entry.kind === "separator")
|
|
4304
|
-
continue;
|
|
4305
|
-
const existing = byLine.get(entry.line.line);
|
|
4306
|
-
if (!existing || entry.kind === "match" && existing.kind === "context") {
|
|
4307
|
-
byLine.set(entry.line.line, entry);
|
|
4308
|
-
}
|
|
4309
|
-
}
|
|
4310
|
-
const sorted = [...byLine.entries()].sort(([a], [b]) => a - b);
|
|
4311
|
-
const result = [];
|
|
4312
|
-
for (let i = 0;i < sorted.length; i++) {
|
|
4313
|
-
if (i > 0 && sorted[i][0] > sorted[i - 1][0] + 1) {
|
|
4314
|
-
result.push({ kind: "separator", text: "--" });
|
|
4315
|
-
}
|
|
4316
|
-
result.push(sorted[i][1]);
|
|
4317
|
-
}
|
|
4318
|
-
return result;
|
|
4319
|
-
}
|
|
4320
|
-
function truncateGroupEntries(group) {
|
|
4321
|
-
let matchesSeen = 0;
|
|
4322
|
-
let truncatedCount = 0;
|
|
4323
|
-
const kept = [];
|
|
4324
|
-
for (const entry of group.entries) {
|
|
4325
|
-
if (entry.kind === "match") {
|
|
4326
|
-
matchesSeen++;
|
|
4327
|
-
if (matchesSeen <= GREP_MAX_MATCHES_PER_FILE) {
|
|
4328
|
-
kept.push(entry);
|
|
4329
|
-
} else {
|
|
4330
|
-
truncatedCount++;
|
|
4331
|
-
}
|
|
4332
|
-
} else if (matchesSeen <= GREP_MAX_MATCHES_PER_FILE) {
|
|
4333
|
-
kept.push(entry);
|
|
4334
|
-
}
|
|
4335
|
-
}
|
|
4336
|
-
if (truncatedCount > 0) {
|
|
4337
|
-
kept.push({ kind: "separator", text: `... +${truncatedCount} more matches` });
|
|
4338
|
-
}
|
|
4339
|
-
return { ...group, entries: kept };
|
|
4340
|
-
}
|
|
4341
4244
|
async function executeGrep(opts) {
|
|
4342
|
-
const {
|
|
4343
|
-
|
|
4245
|
+
const { params, signal, cwd, onFileAnchored } = opts;
|
|
4246
|
+
signal?.throwIfAborted();
|
|
4344
4247
|
const rawParams = params;
|
|
4345
4248
|
const context = coerceObviousBase10Int(rawParams.context, "context");
|
|
4346
4249
|
if (!context.ok) {
|
|
@@ -4368,267 +4271,34 @@ async function executeGrep(opts) {
|
|
|
4368
4271
|
const message = `Invalid scopeContext: expected a non-negative integer, received ${scopeContext.value}.`;
|
|
4369
4272
|
return buildToolErrorResult("grep", "invalid-params-combo", message);
|
|
4370
4273
|
}
|
|
4371
|
-
const
|
|
4372
|
-
|
|
4274
|
+
const output = await readSeekGrep({
|
|
4275
|
+
pattern: rawParams.pattern,
|
|
4276
|
+
path: resolveToCwd(rawParams.path ?? ".", cwd),
|
|
4277
|
+
glob: rawParams.glob,
|
|
4278
|
+
ignoreCase: rawParams.ignoreCase,
|
|
4279
|
+
literal: rawParams.literal,
|
|
4373
4280
|
context: context.value,
|
|
4374
4281
|
limit: limit.value,
|
|
4375
|
-
|
|
4376
|
-
|
|
4377
|
-
|
|
4378
|
-
|
|
4379
|
-
|
|
4380
|
-
context: context.value,
|
|
4381
|
-
limit: limit.value
|
|
4382
|
-
}, signal, onUpdate);
|
|
4383
|
-
const textBlock = result.content?.find((item) => item.type === "text" && ("text" in item) && typeof item.text === "string");
|
|
4384
|
-
if (!textBlock?.text)
|
|
4385
|
-
return result;
|
|
4386
|
-
const { path: rawSearchPath } = p;
|
|
4387
|
-
const searchPath = resolveToCwd(rawSearchPath || ".", cwd);
|
|
4388
|
-
let searchPathIsDirectory = false;
|
|
4389
|
-
try {
|
|
4390
|
-
searchPathIsDirectory = (await fsStat(searchPath)).isDirectory();
|
|
4391
|
-
} catch {
|
|
4392
|
-
searchPathIsDirectory = false;
|
|
4393
|
-
}
|
|
4394
|
-
if (!searchPathIsDirectory) {
|
|
4395
|
-
try {
|
|
4396
|
-
const buf = await fsReadFile2(searchPath);
|
|
4397
|
-
if (looksLikeBinary(buf)) {
|
|
4398
|
-
const warning = `[Warning: '${p.path ?? searchPath}' appears to be a binary file — grep skips binary files by default. Use a hex tool or the read tool to inspect it.]`;
|
|
4399
|
-
return {
|
|
4400
|
-
...result,
|
|
4401
|
-
content: result.content.map((item) => item === textBlock ? { ...item, text: warning } : item),
|
|
4402
|
-
details: {
|
|
4403
|
-
...typeof result.details === "object" && result.details !== null ? result.details : {},
|
|
4404
|
-
readSeekValue: {
|
|
4405
|
-
tool: "grep",
|
|
4406
|
-
summary: !!p.summary,
|
|
4407
|
-
totalMatches: 0,
|
|
4408
|
-
records: []
|
|
4409
|
-
}
|
|
4410
|
-
}
|
|
4411
|
-
};
|
|
4412
|
-
}
|
|
4413
|
-
} catch {}
|
|
4414
|
-
}
|
|
4415
|
-
const fileCache = new Map;
|
|
4416
|
-
const bareCRFiles = new Set;
|
|
4417
|
-
const getFileLines = async (absolutePath) => {
|
|
4418
|
-
throwIfAborted(signal);
|
|
4419
|
-
if (fileCache.has(absolutePath))
|
|
4420
|
-
return fileCache.get(absolutePath);
|
|
4421
|
-
try {
|
|
4422
|
-
const rawBuffer = await fsReadFile2(absolutePath);
|
|
4423
|
-
if (looksLikeBinary(rawBuffer)) {
|
|
4424
|
-
fileCache.set(absolutePath, undefined);
|
|
4425
|
-
return;
|
|
4426
|
-
}
|
|
4427
|
-
const raw = rawBuffer.toString("utf-8");
|
|
4428
|
-
if (hasBareCarriageReturn(raw))
|
|
4429
|
-
bareCRFiles.add(absolutePath);
|
|
4430
|
-
const lines = normalizeToLF(stripBom(raw).text).split(`
|
|
4431
|
-
`);
|
|
4432
|
-
fileCache.set(absolutePath, lines);
|
|
4433
|
-
return lines;
|
|
4434
|
-
} catch {
|
|
4435
|
-
fileCache.set(absolutePath, undefined);
|
|
4436
|
-
return;
|
|
4437
|
-
}
|
|
4438
|
-
};
|
|
4439
|
-
const toAbsolutePath = (displayPath) => {
|
|
4440
|
-
if (searchPathIsDirectory)
|
|
4441
|
-
return path5.resolve(searchPath, displayPath);
|
|
4442
|
-
return searchPath;
|
|
4443
|
-
};
|
|
4444
|
-
const groupsByPath = new Map;
|
|
4445
|
-
const passthroughLines = [];
|
|
4446
|
-
let totalMatches = 0;
|
|
4447
|
-
let parsedCount = 0;
|
|
4448
|
-
let candidateUnparsedCount = 0;
|
|
4449
|
-
const candidateLinePattern = /^.+(?::|-)\d+(?::|-)\s/;
|
|
4450
|
-
if (!p.summary) {
|
|
4451
|
-
const pathsToRead = new Set;
|
|
4452
|
-
for (const line of textBlock.text.split(`
|
|
4453
|
-
`)) {
|
|
4454
|
-
throwIfAborted(signal);
|
|
4455
|
-
const parsed = parseGrepOutputLine(line);
|
|
4456
|
-
if (parsed && Number.isFinite(parsed.lineNumber) && parsed.lineNumber >= 1) {
|
|
4457
|
-
pathsToRead.add(toAbsolutePath(parsed.displayPath));
|
|
4458
|
-
}
|
|
4459
|
-
}
|
|
4460
|
-
const CONCURRENCY = 8;
|
|
4461
|
-
const pathList = [...pathsToRead];
|
|
4462
|
-
for (let i = 0;i < pathList.length; i += CONCURRENCY) {
|
|
4463
|
-
await Promise.all(pathList.slice(i, i + CONCURRENCY).map((p) => getFileLines(p)));
|
|
4464
|
-
}
|
|
4465
|
-
}
|
|
4466
|
-
const addSummaryMatch = (displayPath, absolutePath) => {
|
|
4467
|
-
let group = groupsByPath.get(displayPath);
|
|
4468
|
-
if (!group) {
|
|
4469
|
-
group = { displayPath, absolutePath, matchCount: 0, entries: [] };
|
|
4470
|
-
groupsByPath.set(displayPath, group);
|
|
4471
|
-
}
|
|
4472
|
-
group.matchCount++;
|
|
4473
|
-
totalMatches++;
|
|
4474
|
-
};
|
|
4475
|
-
const addEntry = (displayPath, absolutePath, kind, line) => {
|
|
4476
|
-
let group = groupsByPath.get(displayPath);
|
|
4477
|
-
if (!group) {
|
|
4478
|
-
group = { displayPath, absolutePath, matchCount: 0, entries: [] };
|
|
4479
|
-
groupsByPath.set(displayPath, group);
|
|
4480
|
-
}
|
|
4481
|
-
group.entries.push({ kind, line });
|
|
4482
|
-
if (kind === "match") {
|
|
4483
|
-
group.matchCount++;
|
|
4484
|
-
totalMatches++;
|
|
4485
|
-
}
|
|
4486
|
-
};
|
|
4487
|
-
for (const line of textBlock.text.split(`
|
|
4488
|
-
`)) {
|
|
4489
|
-
throwIfAborted(signal);
|
|
4490
|
-
const parsed = parseGrepOutputLine(line);
|
|
4491
|
-
if (!parsed || !Number.isFinite(parsed.lineNumber) || parsed.lineNumber < 1) {
|
|
4492
|
-
if (candidateLinePattern.test(line)) {
|
|
4493
|
-
candidateUnparsedCount++;
|
|
4494
|
-
}
|
|
4495
|
-
const trimmed = line.trim();
|
|
4496
|
-
if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
|
|
4497
|
-
passthroughLines.push(trimmed);
|
|
4498
|
-
}
|
|
4499
|
-
continue;
|
|
4500
|
-
}
|
|
4501
|
-
parsedCount++;
|
|
4502
|
-
const absolute = toAbsolutePath(parsed.displayPath);
|
|
4503
|
-
if (p.summary) {
|
|
4504
|
-
if (parsed.kind === "match") {
|
|
4505
|
-
addSummaryMatch(parsed.displayPath, absolute);
|
|
4506
|
-
}
|
|
4507
|
-
continue;
|
|
4508
|
-
}
|
|
4509
|
-
const fileLines = await getFileLines(absolute);
|
|
4510
|
-
if (fileLines === undefined)
|
|
4511
|
-
continue;
|
|
4512
|
-
if (parsed.kind === "match" && bareCRFiles.has(absolute)) {
|
|
4513
|
-
const flags = p.ignoreCase ? "i" : "";
|
|
4514
|
-
let patternRe = null;
|
|
4515
|
-
try {
|
|
4516
|
-
patternRe = p.literal ? new RegExp(p.pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), flags) : new RegExp(p.pattern, flags);
|
|
4517
|
-
} catch {}
|
|
4518
|
-
if (patternRe !== null) {
|
|
4519
|
-
let emitted = false;
|
|
4520
|
-
for (let i = 0;i < fileLines.length; i++) {
|
|
4521
|
-
if (!patternRe.test(fileLines[i]))
|
|
4522
|
-
continue;
|
|
4523
|
-
addEntry(parsed.displayPath, absolute, "match", buildReadSeekLine(i + 1, fileLines[i]));
|
|
4524
|
-
emitted = true;
|
|
4525
|
-
}
|
|
4526
|
-
if (emitted)
|
|
4527
|
-
continue;
|
|
4528
|
-
}
|
|
4529
|
-
}
|
|
4530
|
-
const sourceLine = fileLines[parsed.lineNumber - 1] ?? parsed.text;
|
|
4531
|
-
const built = buildReadSeekLine(parsed.lineNumber, sourceLine);
|
|
4532
|
-
addEntry(parsed.displayPath, absolute, parsed.kind, {
|
|
4533
|
-
...built,
|
|
4534
|
-
display: escapeControlCharsForDisplay(parsed.text)
|
|
4535
|
-
});
|
|
4536
|
-
}
|
|
4537
|
-
if (p.summary && parsedCount === 0 && candidateUnparsedCount > 0) {
|
|
4538
|
-
const passthroughDetails = typeof result.details === "object" && result.details !== null ? result.details : {};
|
|
4539
|
-
return {
|
|
4540
|
-
...result,
|
|
4541
|
-
details: {
|
|
4542
|
-
...passthroughDetails,
|
|
4543
|
-
readSeekValue: {
|
|
4544
|
-
tool: "grep",
|
|
4545
|
-
summary: true,
|
|
4546
|
-
totalMatches: 0,
|
|
4547
|
-
records: []
|
|
4548
|
-
}
|
|
4549
|
-
}
|
|
4550
|
-
};
|
|
4551
|
-
}
|
|
4552
|
-
if (parsedCount === 0 && candidateUnparsedCount > 0) {
|
|
4553
|
-
const warning = "[hashline grep passthrough] Unparsed grep format; returned original output.";
|
|
4554
|
-
const passthroughDetails = typeof result.details === "object" && result.details !== null ? result.details : {};
|
|
4555
|
-
return {
|
|
4556
|
-
...result,
|
|
4557
|
-
content: result.content.map((item) => item === textBlock ? { ...item, text: `${textBlock.text}
|
|
4558
|
-
|
|
4559
|
-
${warning}` } : item),
|
|
4560
|
-
details: {
|
|
4561
|
-
...passthroughDetails,
|
|
4562
|
-
hashlinePassthrough: true,
|
|
4563
|
-
hashlineWarning: warning,
|
|
4564
|
-
readSeekValue: {
|
|
4565
|
-
tool: "grep",
|
|
4566
|
-
summary: !!p.summary,
|
|
4567
|
-
totalMatches: 0,
|
|
4568
|
-
records: []
|
|
4569
|
-
}
|
|
4570
|
-
}
|
|
4571
|
-
};
|
|
4572
|
-
}
|
|
4573
|
-
const summary = !!p.summary;
|
|
4574
|
-
const effectiveLimit = typeof p.limit === "number" ? p.limit : 100;
|
|
4575
|
-
const groups = [...groupsByPath.values()];
|
|
4576
|
-
for (const group of groups) {
|
|
4577
|
-
group.entries = dedupeContextEntries(group.entries);
|
|
4578
|
-
}
|
|
4579
|
-
let renderedGroups = totalMatches > GREP_TRUNCATION_THRESHOLD ? groups.map(truncateGroupEntries) : groups;
|
|
4580
|
-
let scopeWarnings = [];
|
|
4581
|
-
if (p.scope === "symbol" && !summary) {
|
|
4582
|
-
const fileLinesByPath = new Map;
|
|
4583
|
-
const fileMapsByPath = new Map;
|
|
4584
|
-
for (const group of renderedGroups) {
|
|
4585
|
-
const lines = await getFileLines(group.absolutePath);
|
|
4586
|
-
if (lines)
|
|
4587
|
-
fileLinesByPath.set(group.absolutePath, lines);
|
|
4588
|
-
fileMapsByPath.set(group.absolutePath, await getOrGenerateMap(group.absolutePath));
|
|
4589
|
-
}
|
|
4590
|
-
const scoped = scopeGrepGroupsToSymbols({
|
|
4591
|
-
groups: renderedGroups,
|
|
4592
|
-
fileLinesByPath,
|
|
4593
|
-
fileMapsByPath,
|
|
4594
|
-
contextLines: typeof p.context === "number" ? p.context : 0,
|
|
4595
|
-
scopeContext: typeof p.scopeContext === "number" ? p.scopeContext : undefined
|
|
4596
|
-
});
|
|
4597
|
-
renderedGroups = scoped.groups;
|
|
4598
|
-
scopeWarnings = scoped.warnings;
|
|
4599
|
-
}
|
|
4600
|
-
const readSeekRecords = renderedGroups.flatMap((group) => group.entries.flatMap((entry) => entry.kind === "separator" ? [] : [{ path: group.absolutePath, ...entry.line, kind: entry.kind }]));
|
|
4601
|
-
const builtOutput = buildGrepOutput({
|
|
4602
|
-
summary: !!summary,
|
|
4603
|
-
totalMatches,
|
|
4604
|
-
groups: renderedGroups,
|
|
4605
|
-
limit: effectiveLimit,
|
|
4606
|
-
records: readSeekRecords,
|
|
4607
|
-
scopeMode: p.scope === "symbol" && !summary ? "symbol" : undefined,
|
|
4608
|
-
scopeWarnings,
|
|
4609
|
-
passthroughLines
|
|
4282
|
+
summary: rawParams.summary,
|
|
4283
|
+
scope: rawParams.scope,
|
|
4284
|
+
scopeContext: scopeContext.value,
|
|
4285
|
+
gitScope: rawParams.gitScope,
|
|
4286
|
+
signal
|
|
4610
4287
|
});
|
|
4611
|
-
|
|
4612
|
-
|
|
4613
|
-
|
|
4614
|
-
|
|
4615
|
-
}
|
|
4288
|
+
signal?.throwIfAborted();
|
|
4289
|
+
const builtOutput = buildGrepOutput(output, cwd);
|
|
4290
|
+
for (const absolutePath of new Set(builtOutput.readSeekValue.records.map((record) => record.path))) {
|
|
4291
|
+
onFileAnchored?.(absolutePath);
|
|
4616
4292
|
}
|
|
4617
|
-
const existingDetails = typeof result.details === "object" && result.details !== null ? result.details : {};
|
|
4618
|
-
const { linesTruncated: _ignoredLinesTruncated, truncation: _ignoredTruncation, ...compactDetails } = existingDetails;
|
|
4619
4293
|
return {
|
|
4620
|
-
|
|
4621
|
-
|
|
4622
|
-
details: {
|
|
4623
|
-
...compactDetails,
|
|
4624
|
-
readSeekValue: builtOutput.readSeekValue
|
|
4625
|
-
}
|
|
4294
|
+
content: [{ type: "text", text: builtOutput.text }],
|
|
4295
|
+
details: { readSeekValue: builtOutput.readSeekValue }
|
|
4626
4296
|
};
|
|
4627
4297
|
}
|
|
4628
4298
|
function registerGrepTool(pi, options = {}) {
|
|
4629
|
-
const name = options.name ?? "
|
|
4299
|
+
const name = options.name ?? "readSeek_grep";
|
|
4630
4300
|
const promptMetadata = defineToolPromptMetadata({
|
|
4631
|
-
toolName: "
|
|
4301
|
+
toolName: "grep",
|
|
4632
4302
|
promptSnippet: "Search text or regex with edit-ready LINE:HASH anchors",
|
|
4633
4303
|
registeredName: name
|
|
4634
4304
|
});
|
|
@@ -4677,22 +4347,16 @@ function registerGrepTool(pi, options = {}) {
|
|
|
4677
4347
|
if (isError || result.isError)
|
|
4678
4348
|
return renderErrorResult(textContent, { expanded, width, theme });
|
|
4679
4349
|
const readSeekValue = result.details?.readSeekValue;
|
|
4680
|
-
const hasBinaryWarning = textContent.includes("appears to be a binary file");
|
|
4681
|
-
const fileSet = new Set;
|
|
4682
|
-
for (const r of readSeekValue?.records ?? []) {
|
|
4683
|
-
if (r.path)
|
|
4684
|
-
fileSet.add(r.path);
|
|
4685
|
-
}
|
|
4686
4350
|
const info = formatGrepResultText({
|
|
4687
4351
|
totalMatches: readSeekValue?.totalMatches ?? 0,
|
|
4688
4352
|
summary: readSeekValue?.summary ?? false,
|
|
4689
4353
|
records: readSeekValue?.records ?? [],
|
|
4690
|
-
fileCount:
|
|
4691
|
-
|
|
4354
|
+
fileCount: readSeekValue?.fileCount ?? 0,
|
|
4355
|
+
truncated: readSeekValue?.truncated
|
|
4692
4356
|
});
|
|
4693
|
-
if (info.noMatches
|
|
4357
|
+
if (info.noMatches)
|
|
4694
4358
|
return new Text4(summaryLine("no matches", { theme, style: "dim" }), 0, 0);
|
|
4695
|
-
const matchCount = readSeekValue?.
|
|
4359
|
+
const matchCount = readSeekValue?.returnedMatches ?? 0;
|
|
4696
4360
|
const matchWord = matchCount === 1 ? "match" : "matches";
|
|
4697
4361
|
let text = summaryLine(`${matchCount} ${matchWord} returned`, {
|
|
4698
4362
|
hidden: !!textContent && !expanded,
|
|
@@ -4719,10 +4383,10 @@ import path6 from "node:path";
|
|
|
4719
4383
|
import { Type as Type6 } from "@sinclair/typebox";
|
|
4720
4384
|
|
|
4721
4385
|
// src/stat-search-path.ts
|
|
4722
|
-
import { stat as
|
|
4386
|
+
import { stat as fsStat } from "node:fs/promises";
|
|
4723
4387
|
async function statSearchPathOrError(tool, rawPath, searchPath) {
|
|
4724
4388
|
try {
|
|
4725
|
-
return { ok: true, stats: await
|
|
4389
|
+
return { ok: true, stats: await fsStat(searchPath) };
|
|
4726
4390
|
} catch (err) {
|
|
4727
4391
|
const path = rawPath ?? searchPath;
|
|
4728
4392
|
const { code, message } = formatFsError(err, "stat-error");
|
|
@@ -4774,7 +4438,7 @@ function mergeRanges(ranges) {
|
|
|
4774
4438
|
return merged;
|
|
4775
4439
|
}
|
|
4776
4440
|
var SEARCH_PROMPT_METADATA = defineToolPromptMetadata({
|
|
4777
|
-
toolName: "
|
|
4441
|
+
toolName: "search",
|
|
4778
4442
|
promptSnippet: "Search code by AST structure using ast-grep-style patterns"
|
|
4779
4443
|
});
|
|
4780
4444
|
function linesFromSearchResult(result, ranges) {
|
|
@@ -4814,7 +4478,7 @@ async function executeSearch(opts) {
|
|
|
4814
4478
|
const searchPathIsFile = statResult.stats.isFile();
|
|
4815
4479
|
try {
|
|
4816
4480
|
const effectiveLang = readSeekLanguageForPath(p.language, searchPath, searchPathIsFile);
|
|
4817
|
-
const results = await
|
|
4481
|
+
const results = await readSeekSearch(searchPath, {
|
|
4818
4482
|
pattern: p.pattern,
|
|
4819
4483
|
context: p.context,
|
|
4820
4484
|
selector: p.selector,
|
|
@@ -4878,7 +4542,7 @@ async function executeSearch(opts) {
|
|
|
4878
4542
|
}
|
|
4879
4543
|
function registerSearchTool(pi, options = {}) {
|
|
4880
4544
|
const tool = registerReadSeekTool(pi, {
|
|
4881
|
-
name: "
|
|
4545
|
+
name: "readSeek_search",
|
|
4882
4546
|
label: "Structural Search",
|
|
4883
4547
|
description: SEARCH_PROMPT_METADATA.description,
|
|
4884
4548
|
promptSnippet: SEARCH_PROMPT_METADATA.promptSnippet,
|
|
@@ -4898,14 +4562,14 @@ function registerSearchTool(pi, options = {}) {
|
|
|
4898
4562
|
},
|
|
4899
4563
|
renderCall(args, theme, ...rest) {
|
|
4900
4564
|
return renderReadSeekSearchCall(args, theme, rest, {
|
|
4901
|
-
label: "
|
|
4565
|
+
label: "readSeek_search",
|
|
4902
4566
|
accent: `/${args.pattern ?? args.context ?? args.rule}/`,
|
|
4903
4567
|
flags: [args.gitScope && `git-scope=${args.gitScope}`]
|
|
4904
4568
|
});
|
|
4905
4569
|
},
|
|
4906
4570
|
renderResult(result, options, theme, ...rest) {
|
|
4907
4571
|
return renderAnchoredFilesResult(result, options, theme, rest, {
|
|
4908
|
-
pendingLabel: "pending
|
|
4572
|
+
pendingLabel: "pending readSeek_search",
|
|
4909
4573
|
emptyLabel: "no matches",
|
|
4910
4574
|
unitSingular: "match",
|
|
4911
4575
|
unitPlural: "matches"
|
|
@@ -4948,7 +4612,7 @@ function buildRefsOutput(input) {
|
|
|
4948
4612
|
|
|
4949
4613
|
// src/refs.ts
|
|
4950
4614
|
var REFS_PROMPT_METADATA = defineToolPromptMetadata({
|
|
4951
|
-
toolName: "
|
|
4615
|
+
toolName: "refs",
|
|
4952
4616
|
promptSnippet: "Find references by identifier name or cursor binding"
|
|
4953
4617
|
});
|
|
4954
4618
|
function refsLine(reference) {
|
|
@@ -4990,7 +4654,7 @@ async function executeRefs(opts) {
|
|
|
4990
4654
|
if (!statResult.ok)
|
|
4991
4655
|
return statResult.error;
|
|
4992
4656
|
try {
|
|
4993
|
-
const references = await
|
|
4657
|
+
const references = await readSeekRefs(searchPath, {
|
|
4994
4658
|
name: p.name,
|
|
4995
4659
|
at: p.at,
|
|
4996
4660
|
language: p.language,
|
|
@@ -5020,7 +4684,7 @@ async function executeRefs(opts) {
|
|
|
5020
4684
|
}
|
|
5021
4685
|
function registerRefsTool(pi, options = {}) {
|
|
5022
4686
|
const tool = registerReadSeekTool(pi, {
|
|
5023
|
-
name: "
|
|
4687
|
+
name: "readSeek_refs",
|
|
5024
4688
|
label: "References",
|
|
5025
4689
|
description: REFS_PROMPT_METADATA.description,
|
|
5026
4690
|
promptSnippet: REFS_PROMPT_METADATA.promptSnippet,
|
|
@@ -5038,14 +4702,14 @@ function registerRefsTool(pi, options = {}) {
|
|
|
5038
4702
|
renderCall(args, theme, ...rest) {
|
|
5039
4703
|
const accent = args.name ?? args.at ?? "?";
|
|
5040
4704
|
return renderReadSeekSearchCall(args, theme, rest, {
|
|
5041
|
-
label: "
|
|
4705
|
+
label: "readSeek_refs",
|
|
5042
4706
|
accent,
|
|
5043
4707
|
flags: [args.at && "binding", args.gitScope && `git-scope=${args.gitScope}`]
|
|
5044
4708
|
});
|
|
5045
4709
|
},
|
|
5046
4710
|
renderResult(result, options, theme, ...rest) {
|
|
5047
4711
|
return renderAnchoredFilesResult(result, options, theme, rest, {
|
|
5048
|
-
pendingLabel: "pending
|
|
4712
|
+
pendingLabel: "pending readSeek_refs",
|
|
5049
4713
|
emptyLabel: "no references",
|
|
5050
4714
|
unitSingular: "reference",
|
|
5051
4715
|
unitPlural: "references"
|
|
@@ -5060,7 +4724,7 @@ import { Type as Type8 } from "@sinclair/typebox";
|
|
|
5060
4724
|
import path8 from "node:path";
|
|
5061
4725
|
import { Text as Text5 } from "@earendil-works/pi-tui";
|
|
5062
4726
|
var RENAME_PROMPT_METADATA = defineToolPromptMetadata({
|
|
5063
|
-
toolName: "
|
|
4727
|
+
toolName: "rename",
|
|
5064
4728
|
promptSnippet: "Rename a symbol at a source location with verified edits"
|
|
5065
4729
|
});
|
|
5066
4730
|
var renameSchema = Type8.Object({
|
|
@@ -5076,12 +4740,12 @@ async function executeRename(opts) {
|
|
|
5076
4740
|
if (!p.to.trim()) {
|
|
5077
4741
|
return buildToolErrorResult("rename", "invalid-parameter", "rename parameter 'to' must not be empty");
|
|
5078
4742
|
}
|
|
5079
|
-
if (typeof p.at !== "string" || !/^(line:[1-9][0-9]*|hash:[0-9a-fA-F]
|
|
5080
|
-
return buildToolErrorResult("
|
|
4743
|
+
if (typeof p.at !== "string" || !/^(line:[1-9][0-9]*|hash:[0-9a-fA-F]+|[1-9][0-9]*|[0-9a-fA-F]{6})(:[1-9][0-9]*)?$/.test(p.at)) {
|
|
4744
|
+
return buildToolErrorResult("rename", "invalid-parameter", "at must be a line or hash location with an optional positive column");
|
|
5081
4745
|
}
|
|
5082
4746
|
const filePath = resolveToCwd(p.path, cwd);
|
|
5083
4747
|
try {
|
|
5084
|
-
const output = await
|
|
4748
|
+
const output = await readSeekRename(filePath, {
|
|
5085
4749
|
to: p.to,
|
|
5086
4750
|
at: p.at,
|
|
5087
4751
|
workspace: p.workspace ? cwd : undefined,
|
|
@@ -5138,7 +4802,7 @@ First conflict at ${firstConflict.line}:${firstConflict.column}: ${firstConflict
|
|
|
5138
4802
|
}
|
|
5139
4803
|
function registerRenameTool(pi, options = {}) {
|
|
5140
4804
|
registerReadSeekTool(pi, {
|
|
5141
|
-
name: "
|
|
4805
|
+
name: "readSeek_rename",
|
|
5142
4806
|
label: "Rename",
|
|
5143
4807
|
description: RENAME_PROMPT_METADATA.description,
|
|
5144
4808
|
promptSnippet: RENAME_PROMPT_METADATA.promptSnippet,
|
|
@@ -5151,7 +4815,7 @@ function registerRenameTool(pi, options = {}) {
|
|
|
5151
4815
|
const context = rest[0] ?? {};
|
|
5152
4816
|
const cwd = context.cwd ?? process.cwd();
|
|
5153
4817
|
const displayPath = typeof args?.path === "string" ? args.path : "?";
|
|
5154
|
-
let text = theme.fg("toolTitle", theme.bold("
|
|
4818
|
+
let text = theme.fg("toolTitle", theme.bold("readSeek_rename"));
|
|
5155
4819
|
text += ` ${linkToolPath(theme.fg("accent", displayPath), displayPath, cwd)}`;
|
|
5156
4820
|
if (args?.to)
|
|
5157
4821
|
text += theme.fg("dim", ` → ${args.to}`);
|
|
@@ -5160,13 +4824,13 @@ function registerRenameTool(pi, options = {}) {
|
|
|
5160
4824
|
renderResult(result, options, theme, ...rest) {
|
|
5161
4825
|
const { isPartial, isError, expanded, cwd, width } = resolveRenderResultContext(options, rest);
|
|
5162
4826
|
if (isPartial)
|
|
5163
|
-
return renderPendingResult("pending
|
|
4827
|
+
return renderPendingResult("pending readSeek_rename", width, theme);
|
|
5164
4828
|
const content = result.content?.[0];
|
|
5165
4829
|
const textContent = content?.type === "text" ? content.text : "";
|
|
5166
4830
|
const readSeekValue = result.details?.readSeekValue;
|
|
5167
4831
|
const output = readSeekValue?.output;
|
|
5168
4832
|
if (isError || result.isError) {
|
|
5169
|
-
return renderErrorResult(textContent, { expanded, width, fallback: "
|
|
4833
|
+
return renderErrorResult(textContent, { expanded, width, fallback: "readSeek_rename failed", theme });
|
|
5170
4834
|
}
|
|
5171
4835
|
const outputs = output ? [output, ...output.others] : [];
|
|
5172
4836
|
const editCount = outputs.reduce((total, file) => total + file.edits.length, 0);
|
|
@@ -5201,6 +4865,18 @@ import { withFileMutationQueue as withFileMutationQueue2, truncateHead as trunca
|
|
|
5201
4865
|
import { Text as Text6 } from "@earendil-works/pi-tui";
|
|
5202
4866
|
import { Type as Type9 } from "@sinclair/typebox";
|
|
5203
4867
|
|
|
4868
|
+
// src/binary-detect.ts
|
|
4869
|
+
function looksLikeBinary(buf) {
|
|
4870
|
+
if (buf.length === 0)
|
|
4871
|
+
return false;
|
|
4872
|
+
if (buf.includes(0))
|
|
4873
|
+
return true;
|
|
4874
|
+
const decoded = buf.toString("utf8");
|
|
4875
|
+
if (!decoded.includes("�"))
|
|
4876
|
+
return false;
|
|
4877
|
+
return !Buffer.from(decoded, "utf8").equals(buf);
|
|
4878
|
+
}
|
|
4879
|
+
|
|
5204
4880
|
// src/pending-diff-preview.ts
|
|
5205
4881
|
import { existsSync as existsSync3, readFileSync as readFileSync2, realpathSync, statSync as statSync4 } from "node:fs";
|
|
5206
4882
|
import { dirname, isAbsolute, relative as relative2, resolve, sep } from "node:path";
|
|
@@ -5404,7 +5080,7 @@ async function executeWrite(opts) {
|
|
|
5404
5080
|
const warnings = [];
|
|
5405
5081
|
const readSeekWarnings = [];
|
|
5406
5082
|
if (hasBareCarriageReturn(content)) {
|
|
5407
|
-
const message = "File content contains bare CR (\\r) line endings;
|
|
5083
|
+
const message = "File content contains bare CR (\\r) line endings; readSeek_write refuses to emit anchors that readSeek_show/readSeek_edit would normalize differently.";
|
|
5408
5084
|
warnings.push(message);
|
|
5409
5085
|
readSeekWarnings.push(buildReadSeekWarning("bare-cr", message));
|
|
5410
5086
|
return buildToolErrorResult("write", "bare-cr", `Cannot write ${filePath}
|
|
@@ -5483,9 +5159,9 @@ async function executeWrite(opts) {
|
|
|
5483
5159
|
};
|
|
5484
5160
|
}
|
|
5485
5161
|
function registerWriteTool(pi, options = {}) {
|
|
5486
|
-
const name = options.name ?? "
|
|
5162
|
+
const name = options.name ?? "readSeek_write";
|
|
5487
5163
|
const promptMetadata = defineToolPromptMetadata({
|
|
5488
|
-
toolName: "
|
|
5164
|
+
toolName: "write",
|
|
5489
5165
|
promptSnippet: "Create or replace a complete file with LINE:HASH anchors",
|
|
5490
5166
|
registeredName: name
|
|
5491
5167
|
});
|
|
@@ -5599,21 +5275,21 @@ function registerWriteTool(pi, options = {}) {
|
|
|
5599
5275
|
import { Type as Type10 } from "@sinclair/typebox";
|
|
5600
5276
|
import path9 from "node:path";
|
|
5601
5277
|
var DEF_PROMPT_METADATA = defineToolPromptMetadata({
|
|
5602
|
-
toolName: "
|
|
5278
|
+
toolName: "def",
|
|
5603
5279
|
promptSnippet: "Go to a symbol definition or declaration by name"
|
|
5604
5280
|
});
|
|
5605
5281
|
async function executeDef(opts) {
|
|
5606
5282
|
const { params, signal, cwd, onFileAnchored } = opts;
|
|
5607
5283
|
const p = params;
|
|
5608
5284
|
if (!p.name || !p.name.trim()) {
|
|
5609
|
-
return buildToolErrorResult("def", "invalid-parameter", "
|
|
5285
|
+
return buildToolErrorResult("def", "invalid-parameter", "readSeek_def requires 'name'");
|
|
5610
5286
|
}
|
|
5611
5287
|
const searchPath = resolveToCwd(p.path ?? ".", cwd);
|
|
5612
5288
|
const statResult = await statSearchPathOrError("def", p.path, searchPath);
|
|
5613
5289
|
if (!statResult.ok)
|
|
5614
5290
|
return statResult.error;
|
|
5615
5291
|
try {
|
|
5616
|
-
const definitions = await
|
|
5292
|
+
const definitions = await readSeekDef(searchPath, {
|
|
5617
5293
|
name: p.name,
|
|
5618
5294
|
language: p.language,
|
|
5619
5295
|
gitScope: p.gitScope,
|
|
@@ -5662,7 +5338,7 @@ async function executeDef(opts) {
|
|
|
5662
5338
|
}
|
|
5663
5339
|
function registerDefTool(pi, options = {}) {
|
|
5664
5340
|
registerReadSeekTool(pi, {
|
|
5665
|
-
name: "
|
|
5341
|
+
name: "readSeek_def",
|
|
5666
5342
|
label: "Definition",
|
|
5667
5343
|
description: DEF_PROMPT_METADATA.description,
|
|
5668
5344
|
promptSnippet: DEF_PROMPT_METADATA.promptSnippet,
|
|
@@ -5678,14 +5354,14 @@ function registerDefTool(pi, options = {}) {
|
|
|
5678
5354
|
},
|
|
5679
5355
|
renderCall(args, theme, ...rest) {
|
|
5680
5356
|
return renderReadSeekSearchCall(args, theme, rest, {
|
|
5681
|
-
label: "
|
|
5357
|
+
label: "readSeek_def",
|
|
5682
5358
|
accent: args.name,
|
|
5683
5359
|
flags: [args.gitScope && `git-scope=${args.gitScope}`]
|
|
5684
5360
|
});
|
|
5685
5361
|
},
|
|
5686
5362
|
renderResult(result, options, theme, ...rest) {
|
|
5687
5363
|
return renderAnchoredFilesResult(result, options, theme, rest, {
|
|
5688
|
-
pendingLabel: "pending
|
|
5364
|
+
pendingLabel: "pending readSeek_def",
|
|
5689
5365
|
emptyLabel: "no definitions",
|
|
5690
5366
|
unitSingular: "definition",
|
|
5691
5367
|
unitPlural: "definitions"
|
|
@@ -5699,32 +5375,32 @@ import { Text as Text7, wrapTextWithAnsi as wrapTextWithAnsi2 } from "@earendil-
|
|
|
5699
5375
|
import { Type as Type11 } from "@sinclair/typebox";
|
|
5700
5376
|
import { readFile as readFile2 } from "node:fs/promises";
|
|
5701
5377
|
var operations = {
|
|
5702
|
-
|
|
5703
|
-
|
|
5704
|
-
|
|
5705
|
-
|
|
5706
|
-
|
|
5707
|
-
|
|
5708
|
-
|
|
5709
|
-
|
|
5710
|
-
|
|
5378
|
+
show: readSeekShow,
|
|
5379
|
+
tree: readSeekTree,
|
|
5380
|
+
meta: readSeekMeta,
|
|
5381
|
+
assets: readSeekAssets,
|
|
5382
|
+
syntax: readSeekSyntax,
|
|
5383
|
+
check: readSeekCheck,
|
|
5384
|
+
caption: readSeekCaption,
|
|
5385
|
+
objects: readSeekObjects,
|
|
5386
|
+
ocr: readSeekOcr
|
|
5711
5387
|
};
|
|
5712
5388
|
function resolveNonShowSummary(tool, envelope) {
|
|
5713
|
-
if (tool === "
|
|
5389
|
+
if (tool === "syntax") {
|
|
5714
5390
|
const errorCount = envelope?.diagnostics?.error_count ?? 0;
|
|
5715
5391
|
return errorCount === 0 ? "clean" : `${errorCount} syntax error${errorCount === 1 ? "" : "s"}`;
|
|
5716
5392
|
}
|
|
5717
|
-
if (tool === "
|
|
5393
|
+
if (tool === "check") {
|
|
5718
5394
|
const violations = Array.isArray(envelope?.violations) ? envelope.violations.length : 0;
|
|
5719
5395
|
return violations === 0 ? "clean" : `${violations} rule violation${violations === 1 ? "" : "s"}`;
|
|
5720
5396
|
}
|
|
5721
|
-
if (tool === "
|
|
5397
|
+
if (tool === "objects") {
|
|
5722
5398
|
const objects = Array.isArray(envelope?.objects) ? envelope.objects.length : 0;
|
|
5723
5399
|
return `${objects} object${objects === 1 ? "" : "s"}`;
|
|
5724
5400
|
}
|
|
5725
|
-
if (tool === "
|
|
5401
|
+
if (tool === "caption")
|
|
5726
5402
|
return "caption generated";
|
|
5727
|
-
if (tool === "
|
|
5403
|
+
if (tool === "ocr")
|
|
5728
5404
|
return "text recognized";
|
|
5729
5405
|
return "complete";
|
|
5730
5406
|
}
|
|
@@ -5799,7 +5475,7 @@ async function executeAccess(opts) {
|
|
|
5799
5475
|
if (key === "stdinName" || !Object.hasOwn(contract.inputSchema.properties, key))
|
|
5800
5476
|
return buildToolErrorResult(opts.tool, "invalid-parameter", `Unknown parameter: ${key}`);
|
|
5801
5477
|
}
|
|
5802
|
-
const path = p.path === undefined && opts.tool === "
|
|
5478
|
+
const path = p.path === undefined && opts.tool === "check" ? "." : p.path;
|
|
5803
5479
|
if (typeof path !== "string" || !path.trim())
|
|
5804
5480
|
return buildToolErrorResult(opts.tool, "invalid-parameter", "path is required");
|
|
5805
5481
|
if (path === "-")
|
|
@@ -5814,7 +5490,7 @@ async function executeAccess(opts) {
|
|
|
5814
5490
|
const content = [
|
|
5815
5491
|
{ type: "text", text: presentation ? formatShowResult(presentation) : JSON.stringify(envelope) }
|
|
5816
5492
|
];
|
|
5817
|
-
if (opts.tool === "
|
|
5493
|
+
if (opts.tool === "show" || opts.tool === "assets") {
|
|
5818
5494
|
for (const [imagePath, mimeType] of imagePaths(envelope)) {
|
|
5819
5495
|
if (opts.imageMode === "off")
|
|
5820
5496
|
content.push({ type: "text", text: `Image available at ${imagePath}; image attachments are disabled by readseek.imageMode.` });
|
|
@@ -5832,7 +5508,7 @@ async function executeAccess(opts) {
|
|
|
5832
5508
|
}
|
|
5833
5509
|
function registerAccessTools(pi, options = {}) {
|
|
5834
5510
|
for (const name of Object.keys(operations)) {
|
|
5835
|
-
const registeredName = name === "
|
|
5511
|
+
const registeredName = name === "show" ? options.name ?? `readSeek_${name}` : `readSeek_${name}`;
|
|
5836
5512
|
const contract = getToolContract(name);
|
|
5837
5513
|
const metadata = defineToolPromptMetadata({
|
|
5838
5514
|
toolName: name,
|
|
@@ -5919,37 +5595,37 @@ class SessionAnchors {
|
|
|
5919
5595
|
|
|
5920
5596
|
// index.ts
|
|
5921
5597
|
var READSEEK_TOOL_ENTRIES = [
|
|
5922
|
-
{ builtIn: "edit", readSeekName: "
|
|
5923
|
-
{ builtIn: "grep", readSeekName: "
|
|
5924
|
-
{ builtIn: null, readSeekName: "
|
|
5925
|
-
{ builtIn: null, readSeekName: "
|
|
5926
|
-
{ builtIn: null, readSeekName: "
|
|
5927
|
-
{ builtIn: "write", readSeekName: "
|
|
5928
|
-
{ builtIn: null, readSeekName: "
|
|
5929
|
-
{ builtIn: "read", readSeekName: "
|
|
5930
|
-
{ builtIn: null, readSeekName: "
|
|
5931
|
-
{ builtIn: null, readSeekName: "
|
|
5932
|
-
{ builtIn: null, readSeekName: "
|
|
5933
|
-
{ builtIn: null, readSeekName: "
|
|
5934
|
-
{ builtIn: null, readSeekName: "
|
|
5935
|
-
{ builtIn: null, readSeekName: "
|
|
5936
|
-
{ builtIn: null, readSeekName: "
|
|
5937
|
-
{ builtIn: null, readSeekName: "
|
|
5598
|
+
{ builtIn: "edit", readSeekName: "readSeek_edit" },
|
|
5599
|
+
{ builtIn: "grep", readSeekName: "readSeek_grep" },
|
|
5600
|
+
{ builtIn: null, readSeekName: "readSeek_search" },
|
|
5601
|
+
{ builtIn: null, readSeekName: "readSeek_refs" },
|
|
5602
|
+
{ builtIn: null, readSeekName: "readSeek_rename" },
|
|
5603
|
+
{ builtIn: "write", readSeekName: "readSeek_write" },
|
|
5604
|
+
{ builtIn: null, readSeekName: "readSeek_def" },
|
|
5605
|
+
{ builtIn: "read", readSeekName: "readSeek_show" },
|
|
5606
|
+
{ builtIn: null, readSeekName: "readSeek_tree" },
|
|
5607
|
+
{ builtIn: null, readSeekName: "readSeek_meta" },
|
|
5608
|
+
{ builtIn: null, readSeekName: "readSeek_assets" },
|
|
5609
|
+
{ builtIn: null, readSeekName: "readSeek_syntax" },
|
|
5610
|
+
{ builtIn: null, readSeekName: "readSeek_check" },
|
|
5611
|
+
{ builtIn: null, readSeekName: "readSeek_caption" },
|
|
5612
|
+
{ builtIn: null, readSeekName: "readSeek_objects" },
|
|
5613
|
+
{ builtIn: null, readSeekName: "readSeek_ocr" }
|
|
5938
5614
|
];
|
|
5939
5615
|
function piReadSeekExtension(pi) {
|
|
5940
5616
|
const sessionAnchors = new SessionAnchors;
|
|
5941
5617
|
const { settings } = resolveReadSeekJsonSettings();
|
|
5942
5618
|
const replacedBuiltIns = new Set(settings.overrideTools ?? []);
|
|
5943
5619
|
const swap = (builtIn, readSeekName) => replacedBuiltIns.has(builtIn) ? builtIn : readSeekName;
|
|
5944
|
-
const readName = swap("read", "
|
|
5945
|
-
const editName = swap("edit", "
|
|
5946
|
-
const writeName = swap("write", "
|
|
5947
|
-
const grepName = swap("grep", "
|
|
5620
|
+
const readName = swap("read", "readSeek_show");
|
|
5621
|
+
const editName = swap("edit", "readSeek_edit");
|
|
5622
|
+
const writeName = swap("write", "readSeek_write");
|
|
5623
|
+
const grepName = swap("grep", "readSeek_grep");
|
|
5948
5624
|
const toolAliases = {
|
|
5949
|
-
|
|
5950
|
-
|
|
5951
|
-
|
|
5952
|
-
|
|
5625
|
+
readSeek_show: readName,
|
|
5626
|
+
readSeek_edit: editName,
|
|
5627
|
+
readSeek_grep: grepName,
|
|
5628
|
+
readSeek_write: writeName
|
|
5953
5629
|
};
|
|
5954
5630
|
const policyAliases = Object.fromEntries(TOOL_NAMES.map((name) => {
|
|
5955
5631
|
const readSeekName = `readSeek_${name}`;
|