pi-readseek 0.12.0 → 0.12.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.ts +284 -658
- package/package.json +3 -3
- package/prompts/grep.md +5 -4
package/dist/index.ts
CHANGED
|
@@ -43,7 +43,7 @@ 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.",
|
|
@@ -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 grep 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,9 +91,10 @@ 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
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.",
|
|
@@ -107,7 +107,7 @@ var TOOL_DESCRIPTIONS = {
|
|
|
107
107
|
objects: "Explicitly infer object labels and bounding boxes using an installed model.",
|
|
108
108
|
ocr: "Explicitly recognize text from pixels using an installed model; reading never silently invokes OCR.",
|
|
109
109
|
search: "Find AST nodes by pattern or KDL selection rule. Returns edit-ready anchors. Use `grep` for text and regex.",
|
|
110
|
-
grep: "Search
|
|
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
111
|
def: "Find a symbol definition or declaration by qualified or unqualified name.",
|
|
112
112
|
refs: "Find identifier-name matches with name, or binding-scoped references with at. Use before renaming or deleting a symbol.",
|
|
113
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.",
|
|
@@ -215,7 +215,7 @@ var TOOL_CONTRACTS = [
|
|
|
215
215
|
tool("objects", "Detect objects", { ...path, ...at, ...stdin, ...level }, ["path"]),
|
|
216
216
|
tool("ocr", "Recognize text", { ...path, ...at, ...stdin, ...level }, ["path"]),
|
|
217
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", {
|
|
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
219
|
tool("def", "Find definitions", { ...path, ...lang, ...gitScopeSchema, ...noAnchors, name: string(PARAM_DESCRIPTIONS.name) }, ["name"]),
|
|
220
220
|
tool("refs", "Find references", { ...path, ...at, ...lang, ...gitScopeSchema, ...noAnchors, name: string(PARAM_DESCRIPTIONS.refsName) }, []),
|
|
221
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"]),
|
|
@@ -243,7 +243,7 @@ var REPLACEABLE_TOOLS = [
|
|
|
243
243
|
{
|
|
244
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
249
|
tool: "write",
|
|
@@ -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);
|
|
@@ -684,11 +736,29 @@ function buildSearchArgs(input) {
|
|
|
684
736
|
return finish(args);
|
|
685
737
|
}
|
|
686
738
|
function buildGrepArgs(input) {
|
|
687
|
-
const
|
|
688
|
-
|
|
689
|
-
|
|
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);
|
|
690
758
|
appendGitScopeArgs(args, input);
|
|
691
|
-
|
|
759
|
+
if (input.path !== undefined && needsPathTerminator)
|
|
760
|
+
args.push("--", input.path);
|
|
761
|
+
return args;
|
|
692
762
|
}
|
|
693
763
|
function buildDefArgs(input) {
|
|
694
764
|
const args = start("def", input);
|
|
@@ -1120,25 +1190,41 @@ var DocumentOutput = Type.Object({
|
|
|
1120
1190
|
assets: Type.Array(DocumentAsset, { default: [] }),
|
|
1121
1191
|
vision: Type.Array(DocumentVision, { default: [] })
|
|
1122
1192
|
});
|
|
1123
|
-
var
|
|
1124
|
-
|
|
1125
|
-
|
|
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}$" }),
|
|
1126
1199
|
text: Str,
|
|
1127
|
-
|
|
1128
|
-
end_line: Int,
|
|
1129
|
-
start_byte: Int,
|
|
1130
|
-
end_byte: Int,
|
|
1131
|
-
hashlines: Hashlines
|
|
1200
|
+
kind: Type.Union([Type.Literal("match"), Type.Literal("context")])
|
|
1132
1201
|
});
|
|
1133
|
-
var
|
|
1134
|
-
var
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
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,
|
|
1139
1223
|
truncated: Bool,
|
|
1140
|
-
|
|
1141
|
-
|
|
1224
|
+
count_unit: Type.Literal("lines"),
|
|
1225
|
+
scope: Type.Optional(Type.Literal("symbol")),
|
|
1226
|
+
results: Type.Array(GrepSourceResultSchema),
|
|
1227
|
+
documents: Type.Array(GrepDocumentResultSchema)
|
|
1142
1228
|
});
|
|
1143
1229
|
var RuleFinding = Type.Object({
|
|
1144
1230
|
name: Str,
|
|
@@ -1183,8 +1269,35 @@ function parseStructureEnvelope(value) {
|
|
|
1183
1269
|
throw new Error("invalid readseek structure: missing structure");
|
|
1184
1270
|
return { ...output.metadata !== undefined ? { metadata: parseDetectOutput(output.metadata) } : {}, structure: parseStructureOutput(output.structure) };
|
|
1185
1271
|
}
|
|
1186
|
-
function
|
|
1187
|
-
|
|
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;
|
|
1188
1301
|
}
|
|
1189
1302
|
function parseRulesOutput(value) {
|
|
1190
1303
|
return parse(RulesOutput, value, "rules output");
|
|
@@ -1301,9 +1414,9 @@ function createReadSeekClient(runner) {
|
|
|
1301
1414
|
const value = await runner.kdl(buildSearchArgs({ ...options, path }), runOptions(options));
|
|
1302
1415
|
return parseSearchOutput(value);
|
|
1303
1416
|
},
|
|
1304
|
-
async grep(
|
|
1305
|
-
const value = await runner.kdl(buildGrepArgs(
|
|
1306
|
-
return
|
|
1417
|
+
async grep(params) {
|
|
1418
|
+
const value = await runner.kdl(buildGrepArgs(params), runOptions(params));
|
|
1419
|
+
return parseGrepOutput(value);
|
|
1307
1420
|
},
|
|
1308
1421
|
async def(path, options) {
|
|
1309
1422
|
const value = await runner.kdl(buildDefArgs({ ...options, path }), runOptions(options));
|
|
@@ -1786,6 +1899,46 @@ function search(root) {
|
|
|
1786
1899
|
}))
|
|
1787
1900
|
};
|
|
1788
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
|
+
}
|
|
1789
1942
|
function references(root) {
|
|
1790
1943
|
const parsed = children(root, "reference").map((reference) => {
|
|
1791
1944
|
const sourceLine = child(reference, "line");
|
|
@@ -1901,7 +2054,7 @@ function parseReadSeekKdl(text) {
|
|
|
1901
2054
|
case "document":
|
|
1902
2055
|
return root.properties.has("format") ? pdfDocument(root) : sourceDocument(root);
|
|
1903
2056
|
case "search":
|
|
1904
|
-
return search(root);
|
|
2057
|
+
return ["mode", "ignore_case", "summary", "count_unit"].some((key) => root.properties.has(key)) ? grep(root) : search(root);
|
|
1905
2058
|
case "check":
|
|
1906
2059
|
return diagnostics(root);
|
|
1907
2060
|
case "refs":
|
|
@@ -2975,19 +3128,13 @@ var readSeekCaption = client2.caption;
|
|
|
2975
3128
|
var readSeekObjects = client2.objects;
|
|
2976
3129
|
var readSeekOcr = client2.ocr;
|
|
2977
3130
|
var readSeekSearch = client2.search;
|
|
3131
|
+
var readSeekGrep = client2.grep;
|
|
2978
3132
|
var readSeekRefs = client2.refs;
|
|
2979
3133
|
var readSeekDef = client2.def;
|
|
2980
3134
|
var readSeekSyntax = client2.syntax;
|
|
2981
3135
|
var readSeekCheck = client2.check;
|
|
2982
3136
|
var readSeekEdit = client2.edit;
|
|
2983
3137
|
var readSeekRename = client2.rename;
|
|
2984
|
-
async function readSeekMap(filePath, options = {}) {
|
|
2985
|
-
const envelope = await client2.tree(filePath, { depth: DEFAULT_STRUCTURE_DEPTH, signal: options.signal });
|
|
2986
|
-
const map = "structure" in envelope ? envelope.structure : undefined;
|
|
2987
|
-
if (!map || map.language === "unknown" && map.symbols.length === 0)
|
|
2988
|
-
return null;
|
|
2989
|
-
return map;
|
|
2990
|
-
}
|
|
2991
3138
|
|
|
2992
3139
|
// src/edit-syntax-validate.ts
|
|
2993
3140
|
function dedupeSortLines(diagnostics) {
|
|
@@ -3907,29 +4054,12 @@ ${theme.fg("error", info.errorText)}`;
|
|
|
3907
4054
|
}
|
|
3908
4055
|
|
|
3909
4056
|
// src/grep.ts
|
|
3910
|
-
import { readFile as fsReadFile2, stat as fsStat } from "node:fs/promises";
|
|
3911
|
-
import path5 from "node:path";
|
|
3912
|
-
import { createGrepTool } from "@earendil-works/pi-coding-agent";
|
|
3913
4057
|
import { Type as Type5 } from "@sinclair/typebox";
|
|
3914
4058
|
import { Text as Text4 } from "@earendil-works/pi-tui";
|
|
3915
4059
|
|
|
3916
|
-
// src/binary-detect.ts
|
|
3917
|
-
function looksLikeBinary(buf) {
|
|
3918
|
-
if (buf.length === 0)
|
|
3919
|
-
return false;
|
|
3920
|
-
if (buf.includes(0))
|
|
3921
|
-
return true;
|
|
3922
|
-
const decoded = buf.toString("utf8");
|
|
3923
|
-
if (!decoded.includes("�"))
|
|
3924
|
-
return false;
|
|
3925
|
-
return !Buffer.from(decoded, "utf8").equals(buf);
|
|
3926
|
-
}
|
|
3927
|
-
|
|
3928
4060
|
// src/grep-output.ts
|
|
3929
|
-
import
|
|
3930
|
-
|
|
3931
|
-
truncateHead
|
|
3932
|
-
} from "@earendil-works/pi-coding-agent";
|
|
4061
|
+
import path5 from "node:path";
|
|
4062
|
+
import { formatSize, truncateHead } from "@earendil-works/pi-coding-agent";
|
|
3933
4063
|
|
|
3934
4064
|
// src/grep-budget.ts
|
|
3935
4065
|
import { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES } from "@earendil-works/pi-coding-agent";
|
|
@@ -3949,283 +4079,75 @@ function resolveGrepOutputBudget() {
|
|
|
3949
4079
|
}
|
|
3950
4080
|
|
|
3951
4081
|
// src/grep-output.ts
|
|
3952
|
-
function
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
|
|
3958
|
-
|
|
3959
|
-
|
|
3960
|
-
}
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
3967
|
-
|
|
3968
|
-
}
|
|
3969
|
-
|
|
3970
|
-
return {
|
|
3971
|
-
mode: "symbol",
|
|
3972
|
-
groups: groups.filter(hasScope).map((group) => ({
|
|
3973
|
-
path: group.absolutePath,
|
|
3974
|
-
displayPath: group.displayPath,
|
|
3975
|
-
symbol: group.scope.symbol,
|
|
3976
|
-
matchCount: group.matchCount,
|
|
3977
|
-
matchLines: [...group.scope.matchLines],
|
|
3978
|
-
lineAnchors: group.entries.flatMap((entry) => entry.kind === "separator" ? [] : [entry.line.anchor])
|
|
3979
|
-
})),
|
|
3980
|
-
warnings: [...warnings]
|
|
3981
|
-
};
|
|
3982
|
-
}
|
|
3983
|
-
function buildGrepOutput(input) {
|
|
3984
|
-
const fileCount = new Set(input.groups.map((group) => group.absolutePath)).size;
|
|
3985
|
-
const header = `[${input.totalMatches} matches in ${fileCount} files]`;
|
|
3986
|
-
let text;
|
|
3987
|
-
if (input.summary) {
|
|
3988
|
-
const fileLines = [...input.groups].sort((a, b) => b.matchCount - a.matchCount).map((group) => `${group.absolutePath}: ${group.matchCount} matches`);
|
|
3989
|
-
text = [header, ...fileLines].join(`
|
|
3990
|
-
`);
|
|
3991
|
-
} else {
|
|
3992
|
-
const blocks = [header];
|
|
3993
|
-
for (const group of input.groups) {
|
|
3994
|
-
blocks.push(renderGroupHeader(group));
|
|
3995
|
-
for (const entry of group.entries) {
|
|
3996
|
-
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 });
|
|
3997
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);
|
|
3998
4106
|
}
|
|
3999
|
-
text = blocks.join(`
|
|
4000
|
-
`);
|
|
4001
4107
|
}
|
|
4002
|
-
const
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
${
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
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
|
+
}
|
|
4013
4120
|
}
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
`)}
|
|
4019
|
-
|
|
4020
|
-
${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]`);
|
|
4021
4124
|
}
|
|
4125
|
+
let text = blocks.join(`
|
|
4126
|
+
`);
|
|
4022
4127
|
const budget = resolveGrepOutputBudget();
|
|
4023
|
-
const truncated = truncateHead(text, {
|
|
4024
|
-
|
|
4025
|
-
|
|
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] : [];
|
|
4026
4134
|
});
|
|
4027
4135
|
if (truncated.truncated) {
|
|
4028
4136
|
text = `${truncated.content}
|
|
4029
4137
|
|
|
4030
4138
|
[Output truncated: showing ${truncated.outputLines} of ${truncated.totalLines} lines (${formatSize(truncated.outputBytes)} of ${formatSize(truncated.totalBytes)}). Refine pattern or increase limit.]`;
|
|
4031
4139
|
}
|
|
4032
|
-
const readSeekValue = {
|
|
4033
|
-
tool: "grep",
|
|
4034
|
-
summary: input.summary,
|
|
4035
|
-
totalMatches: input.totalMatches,
|
|
4036
|
-
records: input.records.map((record) => ({
|
|
4037
|
-
path: record.path,
|
|
4038
|
-
line: record.line,
|
|
4039
|
-
anchor: record.anchor,
|
|
4040
|
-
kind: record.kind
|
|
4041
|
-
}))
|
|
4042
|
-
};
|
|
4043
|
-
if (!input.summary && input.scopeMode === "symbol") {
|
|
4044
|
-
readSeekValue.scopes = buildScopeMetadata(input.groups, scopeWarnings);
|
|
4045
|
-
}
|
|
4046
4140
|
return {
|
|
4047
4141
|
text,
|
|
4048
|
-
readSeekValue
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
return await readSeekMap(absPath);
|
|
4056
|
-
} catch {
|
|
4057
|
-
return null;
|
|
4058
|
-
}
|
|
4059
|
-
}
|
|
4060
|
-
|
|
4061
|
-
// src/grep-symbol-scope.ts
|
|
4062
|
-
function findEnclosingSymbol(map, lineNumber) {
|
|
4063
|
-
const byQualifiedName = new Map;
|
|
4064
|
-
for (const symbol of map.symbols) {
|
|
4065
|
-
const qname = symbol.qualified_name ?? symbol.name;
|
|
4066
|
-
if (!byQualifiedName.has(qname))
|
|
4067
|
-
byQualifiedName.set(qname, symbol);
|
|
4068
|
-
}
|
|
4069
|
-
const candidates = map.symbols.filter((symbol) => lineNumber >= symbol.start_line && lineNumber <= symbol.end_line).map((symbol) => {
|
|
4070
|
-
const qname = symbol.qualified_name ?? symbol.name;
|
|
4071
|
-
const dot = qname.lastIndexOf(".");
|
|
4072
|
-
const parent = dot === -1 ? undefined : byQualifiedName.get(qname.slice(0, dot));
|
|
4073
|
-
return {
|
|
4074
|
-
name: symbol.name,
|
|
4075
|
-
kind: symbol.kind === "constructor" ? "method" : symbol.kind,
|
|
4076
|
-
startLine: symbol.start_line,
|
|
4077
|
-
endLine: symbol.end_line,
|
|
4078
|
-
parentName: parent?.name
|
|
4079
|
-
};
|
|
4080
|
-
}).sort((a, b) => {
|
|
4081
|
-
const rangeA = a.endLine - a.startLine;
|
|
4082
|
-
const rangeB = b.endLine - b.startLine;
|
|
4083
|
-
if (rangeA !== rangeB)
|
|
4084
|
-
return rangeA - rangeB;
|
|
4085
|
-
if (a.startLine !== b.startLine)
|
|
4086
|
-
return a.startLine - b.startLine;
|
|
4087
|
-
return a.name.localeCompare(b.name);
|
|
4088
|
-
});
|
|
4089
|
-
return candidates[0] ?? null;
|
|
4090
|
-
}
|
|
4091
|
-
function firstLineNumber(group) {
|
|
4092
|
-
const first = group.entries.find((e) => e.kind !== "separator");
|
|
4093
|
-
return first ? first.line.line : Number.MAX_SAFE_INTEGER;
|
|
4094
|
-
}
|
|
4095
|
-
function buildSymbolEntries(fileLines, symbol, matchLines, scopeContext) {
|
|
4096
|
-
if (scopeContext === undefined) {
|
|
4097
|
-
const entries = [];
|
|
4098
|
-
for (let lineNumber = symbol.startLine;lineNumber <= symbol.endLine; lineNumber++) {
|
|
4099
|
-
const built = buildReadSeekLine(lineNumber, fileLines[lineNumber - 1] ?? "");
|
|
4100
|
-
entries.push({ kind: matchLines.has(lineNumber) ? "match" : "context", line: built });
|
|
4101
|
-
}
|
|
4102
|
-
return entries;
|
|
4103
|
-
}
|
|
4104
|
-
const ranges = [...matchLines].sort((a, b) => a - b).map((ln) => ({
|
|
4105
|
-
startLine: Math.max(symbol.startLine, ln - scopeContext),
|
|
4106
|
-
endLine: Math.min(symbol.endLine, ln + scopeContext)
|
|
4107
|
-
}));
|
|
4108
|
-
const merged = [];
|
|
4109
|
-
for (const r of ranges) {
|
|
4110
|
-
const last = merged[merged.length - 1];
|
|
4111
|
-
if (last && r.startLine <= last.endLine + 1) {
|
|
4112
|
-
last.endLine = Math.max(last.endLine, r.endLine);
|
|
4113
|
-
} else {
|
|
4114
|
-
merged.push({ ...r });
|
|
4115
|
-
}
|
|
4116
|
-
}
|
|
4117
|
-
const entries = [];
|
|
4118
|
-
for (let i = 0;i < merged.length; i++) {
|
|
4119
|
-
if (i > 0)
|
|
4120
|
-
entries.push({ kind: "separator", text: "--" });
|
|
4121
|
-
const range = merged[i];
|
|
4122
|
-
for (let ln = range.startLine;ln <= range.endLine; ln++) {
|
|
4123
|
-
const built = buildReadSeekLine(ln, fileLines[ln - 1] ?? "");
|
|
4124
|
-
entries.push({ kind: matchLines.has(ln) ? "match" : "context", line: built });
|
|
4125
|
-
}
|
|
4126
|
-
}
|
|
4127
|
-
return entries;
|
|
4128
|
-
}
|
|
4129
|
-
function buildFallbackEntries(fileLines, matchLines, contextLines) {
|
|
4130
|
-
const lineMap = new Map;
|
|
4131
|
-
for (const matchLine of matchLines) {
|
|
4132
|
-
const start = Math.max(1, matchLine - contextLines);
|
|
4133
|
-
const end = Math.min(fileLines.length, matchLine + contextLines);
|
|
4134
|
-
for (let lineNumber = start;lineNumber <= end; lineNumber++) {
|
|
4135
|
-
const built = buildReadSeekLine(lineNumber, fileLines[lineNumber - 1] ?? "");
|
|
4136
|
-
const candidate = { kind: lineNumber === matchLine ? "match" : "context", line: built };
|
|
4137
|
-
const existing = lineMap.get(lineNumber);
|
|
4138
|
-
if (!existing || existing.kind === "context" && candidate.kind === "match") {
|
|
4139
|
-
lineMap.set(lineNumber, candidate);
|
|
4140
|
-
}
|
|
4141
|
-
}
|
|
4142
|
-
}
|
|
4143
|
-
const ordered = [...lineMap.entries()].sort(([a], [b]) => a - b);
|
|
4144
|
-
const entries = [];
|
|
4145
|
-
for (let i = 0;i < ordered.length; i++) {
|
|
4146
|
-
if (i > 0 && ordered[i][0] > ordered[i - 1][0] + 1)
|
|
4147
|
-
entries.push({ kind: "separator", text: "--" });
|
|
4148
|
-
entries.push(ordered[i][1]);
|
|
4149
|
-
}
|
|
4150
|
-
return entries;
|
|
4151
|
-
}
|
|
4152
|
-
function scopeGrepGroupsToSymbols(input) {
|
|
4153
|
-
const warnings = [];
|
|
4154
|
-
const rendered = [];
|
|
4155
|
-
for (const group of input.groups) {
|
|
4156
|
-
const fileLines = input.fileLinesByPath.get(group.absolutePath);
|
|
4157
|
-
const fileMap = input.fileMapsByPath.get(group.absolutePath) ?? null;
|
|
4158
|
-
if (!fileLines || !fileMap) {
|
|
4159
|
-
warnings.push({
|
|
4160
|
-
code: "unmappable-file",
|
|
4161
|
-
message: `[Warning: symbol scoping unavailable for ${group.absolutePath} — showing normal grep lines for this file]`,
|
|
4162
|
-
path: group.absolutePath
|
|
4163
|
-
});
|
|
4164
|
-
rendered.push({ order: firstLineNumber(group), group });
|
|
4165
|
-
continue;
|
|
4166
|
-
}
|
|
4167
|
-
const symbolBuckets = new Map;
|
|
4168
|
-
const fallbackMatchLines = [];
|
|
4169
|
-
for (const entry of group.entries) {
|
|
4170
|
-
if (entry.kind !== "match")
|
|
4171
|
-
continue;
|
|
4172
|
-
const symbol = findEnclosingSymbol(fileMap, entry.line.line);
|
|
4173
|
-
if (!symbol) {
|
|
4174
|
-
fallbackMatchLines.push(entry.line.line);
|
|
4175
|
-
warnings.push({
|
|
4176
|
-
code: "no-enclosing-symbol",
|
|
4177
|
-
message: `[Warning: no enclosing symbol for ${group.absolutePath}:${entry.line.line} — showing normal grep lines for this match]`,
|
|
4178
|
-
path: group.absolutePath,
|
|
4179
|
-
line: entry.line.line
|
|
4180
|
-
});
|
|
4181
|
-
continue;
|
|
4182
|
-
}
|
|
4183
|
-
const key = `${symbol.startLine}:${symbol.endLine}:${symbol.parentName ?? ""}:${symbol.name}`;
|
|
4184
|
-
const bucket = symbolBuckets.get(key) ?? { symbol, matchLines: new Set };
|
|
4185
|
-
bucket.matchLines.add(entry.line.line);
|
|
4186
|
-
symbolBuckets.set(key, bucket);
|
|
4187
|
-
}
|
|
4188
|
-
const scopedGroups = [...symbolBuckets.values()].sort((a, b) => {
|
|
4189
|
-
if (a.symbol.startLine !== b.symbol.startLine)
|
|
4190
|
-
return a.symbol.startLine - b.symbol.startLine;
|
|
4191
|
-
return a.symbol.name.localeCompare(b.symbol.name);
|
|
4192
|
-
}).map(({ symbol, matchLines }) => ({
|
|
4193
|
-
displayPath: group.displayPath,
|
|
4194
|
-
absolutePath: group.absolutePath,
|
|
4195
|
-
matchCount: matchLines.size,
|
|
4196
|
-
scope: {
|
|
4197
|
-
mode: "symbol",
|
|
4198
|
-
symbol,
|
|
4199
|
-
matchLines: [...matchLines].sort((a, b) => a - b),
|
|
4200
|
-
...input.scopeContext !== undefined ? { contextLines: input.scopeContext } : {}
|
|
4201
|
-
},
|
|
4202
|
-
entries: buildSymbolEntries(fileLines, symbol, matchLines, input.scopeContext)
|
|
4203
|
-
}));
|
|
4204
|
-
for (const scopedGroup of scopedGroups)
|
|
4205
|
-
rendered.push({ order: scopedGroup.scope.symbol.startLine, group: scopedGroup });
|
|
4206
|
-
if (fallbackMatchLines.length > 0) {
|
|
4207
|
-
rendered.push({
|
|
4208
|
-
order: Math.min(...fallbackMatchLines),
|
|
4209
|
-
group: {
|
|
4210
|
-
displayPath: group.displayPath,
|
|
4211
|
-
absolutePath: group.absolutePath,
|
|
4212
|
-
matchCount: fallbackMatchLines.length,
|
|
4213
|
-
entries: buildFallbackEntries(fileLines, fallbackMatchLines, input.contextLines)
|
|
4214
|
-
}
|
|
4215
|
-
});
|
|
4216
|
-
}
|
|
4217
|
-
if (scopedGroups.length === 0 && fallbackMatchLines.length === 0) {
|
|
4218
|
-
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
|
|
4219
4149
|
}
|
|
4220
|
-
}
|
|
4221
|
-
rendered.sort((a, b) => {
|
|
4222
|
-
if (a.order !== b.order)
|
|
4223
|
-
return a.order - b.order;
|
|
4224
|
-
if (a.group.absolutePath !== b.group.absolutePath)
|
|
4225
|
-
return a.group.absolutePath.localeCompare(b.group.absolutePath);
|
|
4226
|
-
return a.group.displayPath.localeCompare(b.group.displayPath);
|
|
4227
|
-
});
|
|
4228
|
-
return { groups: rendered.map((item) => item.group), warnings };
|
|
4150
|
+
};
|
|
4229
4151
|
}
|
|
4230
4152
|
|
|
4231
4153
|
// src/grep-render-helpers.ts
|
|
@@ -4243,7 +4165,6 @@ function formatGrepCallText(args) {
|
|
|
4243
4165
|
suffix: parts.length > 0 ? parts.join(" ") : undefined
|
|
4244
4166
|
};
|
|
4245
4167
|
}
|
|
4246
|
-
var GREP_TRUNCATION_THRESHOLD = 50;
|
|
4247
4168
|
function formatGrepResultText(input) {
|
|
4248
4169
|
if (input.isError && input.errorText) {
|
|
4249
4170
|
return {
|
|
@@ -4256,17 +4177,15 @@ function formatGrepResultText(input) {
|
|
|
4256
4177
|
}
|
|
4257
4178
|
const { totalMatches, summary, fileCount } = input;
|
|
4258
4179
|
const noMatches = totalMatches === 0;
|
|
4259
|
-
const truncated =
|
|
4180
|
+
const truncated = input.truncated ?? false;
|
|
4260
4181
|
const matchWord = totalMatches === 1 ? "match" : "matches";
|
|
4261
4182
|
const fileWord = fileCount === 1 ? "file" : "files";
|
|
4262
4183
|
const summaryText = noMatches ? "" : `✓ ${totalMatches} ${matchWord} in ${fileCount} ${fileWord}`;
|
|
4263
4184
|
const badges = [];
|
|
4264
4185
|
if (truncated)
|
|
4265
|
-
badges.push("
|
|
4186
|
+
badges.push("truncated");
|
|
4266
4187
|
if (summary)
|
|
4267
4188
|
badges.push("summary");
|
|
4268
|
-
if (input.hasBinaryWarning)
|
|
4269
|
-
badges.push("⚠ binary");
|
|
4270
4189
|
return {
|
|
4271
4190
|
summary: summaryText,
|
|
4272
4191
|
badges,
|
|
@@ -4319,78 +4238,12 @@ var grepSchema = Type5.Object({
|
|
|
4319
4238
|
scope: Type5.Optional(Type5.Literal("symbol", {
|
|
4320
4239
|
description: PARAM_DESCRIPTIONS.grepScope
|
|
4321
4240
|
})),
|
|
4322
|
-
scopeContext: optionalIntOrString(PARAM_DESCRIPTIONS.scopeContext)
|
|
4241
|
+
scopeContext: optionalIntOrString(PARAM_DESCRIPTIONS.scopeContext),
|
|
4242
|
+
...readSeekGitSearchParams()
|
|
4323
4243
|
});
|
|
4324
|
-
var MATCH_LINE_RE = /^(.*?):(\d+): (.*)$/;
|
|
4325
|
-
var CONTEXT_LINE_RE = /^(.*?)-(\d+)- (.*)$/;
|
|
4326
|
-
function parseGrepOutputLine(line) {
|
|
4327
|
-
const match = line.match(MATCH_LINE_RE);
|
|
4328
|
-
if (match) {
|
|
4329
|
-
return {
|
|
4330
|
-
kind: "match",
|
|
4331
|
-
displayPath: match[1],
|
|
4332
|
-
lineNumber: Number.parseInt(match[2], 10),
|
|
4333
|
-
text: match[3]
|
|
4334
|
-
};
|
|
4335
|
-
}
|
|
4336
|
-
const context = line.match(CONTEXT_LINE_RE);
|
|
4337
|
-
if (context) {
|
|
4338
|
-
return {
|
|
4339
|
-
kind: "context",
|
|
4340
|
-
displayPath: context[1],
|
|
4341
|
-
lineNumber: Number.parseInt(context[2], 10),
|
|
4342
|
-
text: context[3]
|
|
4343
|
-
};
|
|
4344
|
-
}
|
|
4345
|
-
return null;
|
|
4346
|
-
}
|
|
4347
|
-
var GREP_MAX_MATCHES_PER_FILE = 10;
|
|
4348
|
-
function dedupeContextEntries(entries) {
|
|
4349
|
-
if (entries.length === 0)
|
|
4350
|
-
return entries;
|
|
4351
|
-
const byLine = new Map;
|
|
4352
|
-
for (const entry of entries) {
|
|
4353
|
-
if (entry.kind === "separator")
|
|
4354
|
-
continue;
|
|
4355
|
-
const existing = byLine.get(entry.line.line);
|
|
4356
|
-
if (!existing || entry.kind === "match" && existing.kind === "context") {
|
|
4357
|
-
byLine.set(entry.line.line, entry);
|
|
4358
|
-
}
|
|
4359
|
-
}
|
|
4360
|
-
const sorted = [...byLine.entries()].sort(([a], [b]) => a - b);
|
|
4361
|
-
const result = [];
|
|
4362
|
-
for (let i = 0;i < sorted.length; i++) {
|
|
4363
|
-
if (i > 0 && sorted[i][0] > sorted[i - 1][0] + 1) {
|
|
4364
|
-
result.push({ kind: "separator", text: "--" });
|
|
4365
|
-
}
|
|
4366
|
-
result.push(sorted[i][1]);
|
|
4367
|
-
}
|
|
4368
|
-
return result;
|
|
4369
|
-
}
|
|
4370
|
-
function truncateGroupEntries(group) {
|
|
4371
|
-
let matchesSeen = 0;
|
|
4372
|
-
let truncatedCount = 0;
|
|
4373
|
-
const kept = [];
|
|
4374
|
-
for (const entry of group.entries) {
|
|
4375
|
-
if (entry.kind === "match") {
|
|
4376
|
-
matchesSeen++;
|
|
4377
|
-
if (matchesSeen <= GREP_MAX_MATCHES_PER_FILE) {
|
|
4378
|
-
kept.push(entry);
|
|
4379
|
-
} else {
|
|
4380
|
-
truncatedCount++;
|
|
4381
|
-
}
|
|
4382
|
-
} else if (matchesSeen <= GREP_MAX_MATCHES_PER_FILE) {
|
|
4383
|
-
kept.push(entry);
|
|
4384
|
-
}
|
|
4385
|
-
}
|
|
4386
|
-
if (truncatedCount > 0) {
|
|
4387
|
-
kept.push({ kind: "separator", text: `... +${truncatedCount} more matches` });
|
|
4388
|
-
}
|
|
4389
|
-
return { ...group, entries: kept };
|
|
4390
|
-
}
|
|
4391
4244
|
async function executeGrep(opts) {
|
|
4392
|
-
const {
|
|
4393
|
-
|
|
4245
|
+
const { params, signal, cwd, onFileAnchored } = opts;
|
|
4246
|
+
signal?.throwIfAborted();
|
|
4394
4247
|
const rawParams = params;
|
|
4395
4248
|
const context = coerceObviousBase10Int(rawParams.context, "context");
|
|
4396
4249
|
if (!context.ok) {
|
|
@@ -4418,261 +4271,28 @@ async function executeGrep(opts) {
|
|
|
4418
4271
|
const message = `Invalid scopeContext: expected a non-negative integer, received ${scopeContext.value}.`;
|
|
4419
4272
|
return buildToolErrorResult("grep", "invalid-params-combo", message);
|
|
4420
4273
|
}
|
|
4421
|
-
const
|
|
4422
|
-
|
|
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,
|
|
4423
4280
|
context: context.value,
|
|
4424
4281
|
limit: limit.value,
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
-
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
context: context.value,
|
|
4431
|
-
limit: limit.value
|
|
4432
|
-
}, signal, onUpdate);
|
|
4433
|
-
const textBlock = result.content?.find((item) => item.type === "text" && ("text" in item) && typeof item.text === "string");
|
|
4434
|
-
if (!textBlock?.text)
|
|
4435
|
-
return result;
|
|
4436
|
-
const { path: rawSearchPath } = p;
|
|
4437
|
-
const searchPath = resolveToCwd(rawSearchPath || ".", cwd);
|
|
4438
|
-
let searchPathIsDirectory = false;
|
|
4439
|
-
try {
|
|
4440
|
-
searchPathIsDirectory = (await fsStat(searchPath)).isDirectory();
|
|
4441
|
-
} catch {
|
|
4442
|
-
searchPathIsDirectory = false;
|
|
4443
|
-
}
|
|
4444
|
-
if (!searchPathIsDirectory) {
|
|
4445
|
-
try {
|
|
4446
|
-
const buf = await fsReadFile2(searchPath);
|
|
4447
|
-
if (looksLikeBinary(buf)) {
|
|
4448
|
-
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.]`;
|
|
4449
|
-
return {
|
|
4450
|
-
...result,
|
|
4451
|
-
content: result.content.map((item) => item === textBlock ? { ...item, text: warning } : item),
|
|
4452
|
-
details: {
|
|
4453
|
-
...typeof result.details === "object" && result.details !== null ? result.details : {},
|
|
4454
|
-
readSeekValue: {
|
|
4455
|
-
tool: "grep",
|
|
4456
|
-
summary: !!p.summary,
|
|
4457
|
-
totalMatches: 0,
|
|
4458
|
-
records: []
|
|
4459
|
-
}
|
|
4460
|
-
}
|
|
4461
|
-
};
|
|
4462
|
-
}
|
|
4463
|
-
} catch {}
|
|
4464
|
-
}
|
|
4465
|
-
const fileCache = new Map;
|
|
4466
|
-
const bareCRFiles = new Set;
|
|
4467
|
-
const getFileLines = async (absolutePath) => {
|
|
4468
|
-
throwIfAborted(signal);
|
|
4469
|
-
if (fileCache.has(absolutePath))
|
|
4470
|
-
return fileCache.get(absolutePath);
|
|
4471
|
-
try {
|
|
4472
|
-
const rawBuffer = await fsReadFile2(absolutePath);
|
|
4473
|
-
if (looksLikeBinary(rawBuffer)) {
|
|
4474
|
-
fileCache.set(absolutePath, undefined);
|
|
4475
|
-
return;
|
|
4476
|
-
}
|
|
4477
|
-
const raw = rawBuffer.toString("utf-8");
|
|
4478
|
-
if (hasBareCarriageReturn(raw))
|
|
4479
|
-
bareCRFiles.add(absolutePath);
|
|
4480
|
-
const lines = normalizeToLF(stripBom(raw).text).split(`
|
|
4481
|
-
`);
|
|
4482
|
-
fileCache.set(absolutePath, lines);
|
|
4483
|
-
return lines;
|
|
4484
|
-
} catch {
|
|
4485
|
-
fileCache.set(absolutePath, undefined);
|
|
4486
|
-
return;
|
|
4487
|
-
}
|
|
4488
|
-
};
|
|
4489
|
-
const toAbsolutePath = (displayPath) => {
|
|
4490
|
-
if (searchPathIsDirectory)
|
|
4491
|
-
return path5.resolve(searchPath, displayPath);
|
|
4492
|
-
return searchPath;
|
|
4493
|
-
};
|
|
4494
|
-
const groupsByPath = new Map;
|
|
4495
|
-
const passthroughLines = [];
|
|
4496
|
-
let totalMatches = 0;
|
|
4497
|
-
let parsedCount = 0;
|
|
4498
|
-
let candidateUnparsedCount = 0;
|
|
4499
|
-
const candidateLinePattern = /^.+(?::|-)\d+(?::|-)\s/;
|
|
4500
|
-
if (!p.summary) {
|
|
4501
|
-
const pathsToRead = new Set;
|
|
4502
|
-
for (const line of textBlock.text.split(`
|
|
4503
|
-
`)) {
|
|
4504
|
-
throwIfAborted(signal);
|
|
4505
|
-
const parsed = parseGrepOutputLine(line);
|
|
4506
|
-
if (parsed && Number.isFinite(parsed.lineNumber) && parsed.lineNumber >= 1) {
|
|
4507
|
-
pathsToRead.add(toAbsolutePath(parsed.displayPath));
|
|
4508
|
-
}
|
|
4509
|
-
}
|
|
4510
|
-
const CONCURRENCY = 8;
|
|
4511
|
-
const pathList = [...pathsToRead];
|
|
4512
|
-
for (let i = 0;i < pathList.length; i += CONCURRENCY) {
|
|
4513
|
-
await Promise.all(pathList.slice(i, i + CONCURRENCY).map((p) => getFileLines(p)));
|
|
4514
|
-
}
|
|
4515
|
-
}
|
|
4516
|
-
const addSummaryMatch = (displayPath, absolutePath) => {
|
|
4517
|
-
let group = groupsByPath.get(displayPath);
|
|
4518
|
-
if (!group) {
|
|
4519
|
-
group = { displayPath, absolutePath, matchCount: 0, entries: [] };
|
|
4520
|
-
groupsByPath.set(displayPath, group);
|
|
4521
|
-
}
|
|
4522
|
-
group.matchCount++;
|
|
4523
|
-
totalMatches++;
|
|
4524
|
-
};
|
|
4525
|
-
const addEntry = (displayPath, absolutePath, kind, line) => {
|
|
4526
|
-
let group = groupsByPath.get(displayPath);
|
|
4527
|
-
if (!group) {
|
|
4528
|
-
group = { displayPath, absolutePath, matchCount: 0, entries: [] };
|
|
4529
|
-
groupsByPath.set(displayPath, group);
|
|
4530
|
-
}
|
|
4531
|
-
group.entries.push({ kind, line });
|
|
4532
|
-
if (kind === "match") {
|
|
4533
|
-
group.matchCount++;
|
|
4534
|
-
totalMatches++;
|
|
4535
|
-
}
|
|
4536
|
-
};
|
|
4537
|
-
for (const line of textBlock.text.split(`
|
|
4538
|
-
`)) {
|
|
4539
|
-
throwIfAborted(signal);
|
|
4540
|
-
const parsed = parseGrepOutputLine(line);
|
|
4541
|
-
if (!parsed || !Number.isFinite(parsed.lineNumber) || parsed.lineNumber < 1) {
|
|
4542
|
-
if (candidateLinePattern.test(line)) {
|
|
4543
|
-
candidateUnparsedCount++;
|
|
4544
|
-
}
|
|
4545
|
-
const trimmed = line.trim();
|
|
4546
|
-
if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
|
|
4547
|
-
passthroughLines.push(trimmed);
|
|
4548
|
-
}
|
|
4549
|
-
continue;
|
|
4550
|
-
}
|
|
4551
|
-
parsedCount++;
|
|
4552
|
-
const absolute = toAbsolutePath(parsed.displayPath);
|
|
4553
|
-
if (p.summary) {
|
|
4554
|
-
if (parsed.kind === "match") {
|
|
4555
|
-
addSummaryMatch(parsed.displayPath, absolute);
|
|
4556
|
-
}
|
|
4557
|
-
continue;
|
|
4558
|
-
}
|
|
4559
|
-
const fileLines = await getFileLines(absolute);
|
|
4560
|
-
if (fileLines === undefined)
|
|
4561
|
-
continue;
|
|
4562
|
-
if (parsed.kind === "match" && bareCRFiles.has(absolute)) {
|
|
4563
|
-
const flags = p.ignoreCase ? "i" : "";
|
|
4564
|
-
let patternRe = null;
|
|
4565
|
-
try {
|
|
4566
|
-
patternRe = p.literal ? new RegExp(p.pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), flags) : new RegExp(p.pattern, flags);
|
|
4567
|
-
} catch {}
|
|
4568
|
-
if (patternRe !== null) {
|
|
4569
|
-
let emitted = false;
|
|
4570
|
-
for (let i = 0;i < fileLines.length; i++) {
|
|
4571
|
-
if (!patternRe.test(fileLines[i]))
|
|
4572
|
-
continue;
|
|
4573
|
-
addEntry(parsed.displayPath, absolute, "match", buildReadSeekLine(i + 1, fileLines[i]));
|
|
4574
|
-
emitted = true;
|
|
4575
|
-
}
|
|
4576
|
-
if (emitted)
|
|
4577
|
-
continue;
|
|
4578
|
-
}
|
|
4579
|
-
}
|
|
4580
|
-
const sourceLine = fileLines[parsed.lineNumber - 1] ?? parsed.text;
|
|
4581
|
-
const built = buildReadSeekLine(parsed.lineNumber, sourceLine);
|
|
4582
|
-
addEntry(parsed.displayPath, absolute, parsed.kind, {
|
|
4583
|
-
...built,
|
|
4584
|
-
display: escapeControlCharsForDisplay(parsed.text)
|
|
4585
|
-
});
|
|
4586
|
-
}
|
|
4587
|
-
if (p.summary && parsedCount === 0 && candidateUnparsedCount > 0) {
|
|
4588
|
-
const passthroughDetails = typeof result.details === "object" && result.details !== null ? result.details : {};
|
|
4589
|
-
return {
|
|
4590
|
-
...result,
|
|
4591
|
-
details: {
|
|
4592
|
-
...passthroughDetails,
|
|
4593
|
-
readSeekValue: {
|
|
4594
|
-
tool: "grep",
|
|
4595
|
-
summary: true,
|
|
4596
|
-
totalMatches: 0,
|
|
4597
|
-
records: []
|
|
4598
|
-
}
|
|
4599
|
-
}
|
|
4600
|
-
};
|
|
4601
|
-
}
|
|
4602
|
-
if (parsedCount === 0 && candidateUnparsedCount > 0) {
|
|
4603
|
-
const warning = "[hashline grep passthrough] Unparsed grep format; returned original output.";
|
|
4604
|
-
const passthroughDetails = typeof result.details === "object" && result.details !== null ? result.details : {};
|
|
4605
|
-
return {
|
|
4606
|
-
...result,
|
|
4607
|
-
content: result.content.map((item) => item === textBlock ? { ...item, text: `${textBlock.text}
|
|
4608
|
-
|
|
4609
|
-
${warning}` } : item),
|
|
4610
|
-
details: {
|
|
4611
|
-
...passthroughDetails,
|
|
4612
|
-
hashlinePassthrough: true,
|
|
4613
|
-
hashlineWarning: warning,
|
|
4614
|
-
readSeekValue: {
|
|
4615
|
-
tool: "grep",
|
|
4616
|
-
summary: !!p.summary,
|
|
4617
|
-
totalMatches: 0,
|
|
4618
|
-
records: []
|
|
4619
|
-
}
|
|
4620
|
-
}
|
|
4621
|
-
};
|
|
4622
|
-
}
|
|
4623
|
-
const summary = !!p.summary;
|
|
4624
|
-
const effectiveLimit = typeof p.limit === "number" ? p.limit : 100;
|
|
4625
|
-
const groups = [...groupsByPath.values()];
|
|
4626
|
-
for (const group of groups) {
|
|
4627
|
-
group.entries = dedupeContextEntries(group.entries);
|
|
4628
|
-
}
|
|
4629
|
-
let renderedGroups = totalMatches > GREP_TRUNCATION_THRESHOLD ? groups.map(truncateGroupEntries) : groups;
|
|
4630
|
-
let scopeWarnings = [];
|
|
4631
|
-
if (p.scope === "symbol" && !summary) {
|
|
4632
|
-
const fileLinesByPath = new Map;
|
|
4633
|
-
const fileMapsByPath = new Map;
|
|
4634
|
-
for (const group of renderedGroups) {
|
|
4635
|
-
const lines = await getFileLines(group.absolutePath);
|
|
4636
|
-
if (lines)
|
|
4637
|
-
fileLinesByPath.set(group.absolutePath, lines);
|
|
4638
|
-
fileMapsByPath.set(group.absolutePath, await getOrGenerateMap(group.absolutePath));
|
|
4639
|
-
}
|
|
4640
|
-
const scoped = scopeGrepGroupsToSymbols({
|
|
4641
|
-
groups: renderedGroups,
|
|
4642
|
-
fileLinesByPath,
|
|
4643
|
-
fileMapsByPath,
|
|
4644
|
-
contextLines: typeof p.context === "number" ? p.context : 0,
|
|
4645
|
-
scopeContext: typeof p.scopeContext === "number" ? p.scopeContext : undefined
|
|
4646
|
-
});
|
|
4647
|
-
renderedGroups = scoped.groups;
|
|
4648
|
-
scopeWarnings = scoped.warnings;
|
|
4649
|
-
}
|
|
4650
|
-
const readSeekRecords = renderedGroups.flatMap((group) => group.entries.flatMap((entry) => entry.kind === "separator" ? [] : [{ path: group.absolutePath, ...entry.line, kind: entry.kind }]));
|
|
4651
|
-
const builtOutput = buildGrepOutput({
|
|
4652
|
-
summary: !!summary,
|
|
4653
|
-
totalMatches,
|
|
4654
|
-
groups: renderedGroups,
|
|
4655
|
-
limit: effectiveLimit,
|
|
4656
|
-
records: readSeekRecords,
|
|
4657
|
-
scopeMode: p.scope === "symbol" && !summary ? "symbol" : undefined,
|
|
4658
|
-
scopeWarnings,
|
|
4659
|
-
passthroughLines
|
|
4282
|
+
summary: rawParams.summary,
|
|
4283
|
+
scope: rawParams.scope,
|
|
4284
|
+
scopeContext: scopeContext.value,
|
|
4285
|
+
gitScope: rawParams.gitScope,
|
|
4286
|
+
signal
|
|
4660
4287
|
});
|
|
4661
|
-
|
|
4662
|
-
|
|
4663
|
-
|
|
4664
|
-
|
|
4665
|
-
}
|
|
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);
|
|
4666
4292
|
}
|
|
4667
|
-
const existingDetails = typeof result.details === "object" && result.details !== null ? result.details : {};
|
|
4668
|
-
const { linesTruncated: _ignoredLinesTruncated, truncation: _ignoredTruncation, ...compactDetails } = existingDetails;
|
|
4669
4293
|
return {
|
|
4670
|
-
|
|
4671
|
-
|
|
4672
|
-
details: {
|
|
4673
|
-
...compactDetails,
|
|
4674
|
-
readSeekValue: builtOutput.readSeekValue
|
|
4675
|
-
}
|
|
4294
|
+
content: [{ type: "text", text: builtOutput.text }],
|
|
4295
|
+
details: { readSeekValue: builtOutput.readSeekValue }
|
|
4676
4296
|
};
|
|
4677
4297
|
}
|
|
4678
4298
|
function registerGrepTool(pi, options = {}) {
|
|
@@ -4727,22 +4347,16 @@ function registerGrepTool(pi, options = {}) {
|
|
|
4727
4347
|
if (isError || result.isError)
|
|
4728
4348
|
return renderErrorResult(textContent, { expanded, width, theme });
|
|
4729
4349
|
const readSeekValue = result.details?.readSeekValue;
|
|
4730
|
-
const hasBinaryWarning = textContent.includes("appears to be a binary file");
|
|
4731
|
-
const fileSet = new Set;
|
|
4732
|
-
for (const r of readSeekValue?.records ?? []) {
|
|
4733
|
-
if (r.path)
|
|
4734
|
-
fileSet.add(r.path);
|
|
4735
|
-
}
|
|
4736
4350
|
const info = formatGrepResultText({
|
|
4737
4351
|
totalMatches: readSeekValue?.totalMatches ?? 0,
|
|
4738
4352
|
summary: readSeekValue?.summary ?? false,
|
|
4739
4353
|
records: readSeekValue?.records ?? [],
|
|
4740
|
-
fileCount:
|
|
4741
|
-
|
|
4354
|
+
fileCount: readSeekValue?.fileCount ?? 0,
|
|
4355
|
+
truncated: readSeekValue?.truncated
|
|
4742
4356
|
});
|
|
4743
|
-
if (info.noMatches
|
|
4357
|
+
if (info.noMatches)
|
|
4744
4358
|
return new Text4(summaryLine("no matches", { theme, style: "dim" }), 0, 0);
|
|
4745
|
-
const matchCount = readSeekValue?.
|
|
4359
|
+
const matchCount = readSeekValue?.returnedMatches ?? 0;
|
|
4746
4360
|
const matchWord = matchCount === 1 ? "match" : "matches";
|
|
4747
4361
|
let text = summaryLine(`${matchCount} ${matchWord} returned`, {
|
|
4748
4362
|
hidden: !!textContent && !expanded,
|
|
@@ -4769,10 +4383,10 @@ import path6 from "node:path";
|
|
|
4769
4383
|
import { Type as Type6 } from "@sinclair/typebox";
|
|
4770
4384
|
|
|
4771
4385
|
// src/stat-search-path.ts
|
|
4772
|
-
import { stat as
|
|
4386
|
+
import { stat as fsStat } from "node:fs/promises";
|
|
4773
4387
|
async function statSearchPathOrError(tool, rawPath, searchPath) {
|
|
4774
4388
|
try {
|
|
4775
|
-
return { ok: true, stats: await
|
|
4389
|
+
return { ok: true, stats: await fsStat(searchPath) };
|
|
4776
4390
|
} catch (err) {
|
|
4777
4391
|
const path = rawPath ?? searchPath;
|
|
4778
4392
|
const { code, message } = formatFsError(err, "stat-error");
|
|
@@ -5251,6 +4865,18 @@ import { withFileMutationQueue as withFileMutationQueue2, truncateHead as trunca
|
|
|
5251
4865
|
import { Text as Text6 } from "@earendil-works/pi-tui";
|
|
5252
4866
|
import { Type as Type9 } from "@sinclair/typebox";
|
|
5253
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
|
+
|
|
5254
4880
|
// src/pending-diff-preview.ts
|
|
5255
4881
|
import { existsSync as existsSync3, readFileSync as readFileSync2, realpathSync, statSync as statSync4 } from "node:fs";
|
|
5256
4882
|
import { dirname, isAbsolute, relative as relative2, resolve, sep } from "node:path";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-readseek",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.2",
|
|
4
4
|
"description": "Pi extension for LINE:HASH-anchored file operations and structural code navigation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -39,8 +39,8 @@
|
|
|
39
39
|
"node": ">=20.0.0"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@jarkkojs/readseek": "^0.12.
|
|
43
|
-
"@jarkkojs/readseek-api": "^0.12.
|
|
42
|
+
"@jarkkojs/readseek": "^0.12.2",
|
|
43
|
+
"@jarkkojs/readseek-api": "^0.12.2",
|
|
44
44
|
"diff": "^9.0.0",
|
|
45
45
|
"xxhash-wasm": "^1.1.0"
|
|
46
46
|
},
|
package/prompts/grep.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
Search text or regex
|
|
1
|
+
Search text or regex with native ReadSeek. Source results return edit-ready `LINE:HASH` anchors; document results return `node:`/`page:` selectors, not editable anchors.
|
|
2
2
|
|
|
3
3
|
## Modes
|
|
4
4
|
|
|
@@ -13,15 +13,16 @@ Search text or regex in files and return edit-ready `LINE:HASH` anchors. Use for
|
|
|
13
13
|
|
|
14
14
|
## Parameters
|
|
15
15
|
|
|
16
|
-
- `pattern` — regular expression by default; set `literal: true` for exact text.
|
|
16
|
+
- `pattern` — UTF-8 RE2 regular expression by default; set `literal: true` for exact text. Lookaround and backreferences are not supported.
|
|
17
17
|
- `path` — file or directory; defaults to the current directory.
|
|
18
18
|
- `glob` — file-name filter such as `*.ts` or `**/*.test.ts`.
|
|
19
19
|
- `ignoreCase` — case-insensitive search.
|
|
20
20
|
- `context` — surrounding lines in normal mode.
|
|
21
|
-
- `limit` — maximum
|
|
21
|
+
- `limit` — maximum matching lines (not occurrences); positive, defaults to 100.
|
|
22
22
|
- `summary` — return counts without anchors.
|
|
23
23
|
- `scope` — only `"symbol"` is supported.
|
|
24
|
-
- `scopeContext` — non-negative context inside symbol scope
|
|
24
|
+
- `scopeContext` — non-negative context inside symbol scope; requires `scope: "symbol"`.
|
|
25
|
+
- `gitScope` — `tracked`, `untracked`, `ignored`, or `all`; defaults to tracked plus untracked files.
|
|
25
26
|
|
|
26
27
|
If `limit` or the display budget truncates output, start with `summary`, narrow
|
|
27
28
|
the file set and pattern, then request context or symbol blocks.
|