opencode-readseek 0.12.0 → 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 +8 -3
- package/dist/index.js +444 -132
- package/dist/v2.js +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -68,9 +68,14 @@ is retained separately in `metadata.readseek`; image attachments are unchanged.
|
|
|
68
68
|
references and renames use line or hash cursors. KDL selection/check rules
|
|
69
69
|
may be inline or `@path`; tool calls cannot supply standard input.
|
|
70
70
|
|
|
71
|
-
`readseek_grep`
|
|
72
|
-
|
|
73
|
-
|
|
71
|
+
`readseek_grep` requires `pattern`: native UTF-8 RE2 regex by default, or exact
|
|
72
|
+
text with `literal: true`. Lookaround and backreferences are unsupported.
|
|
73
|
+
Native ReadSeek handles `ignoreCase`, `glob`, `context`, `summary`, and
|
|
74
|
+
`scope: "symbol"` grouping. `scopeContext` requires symbol scope. The positive
|
|
75
|
+
`limit` defaults to 100 matching lines, not occurrences. `gitScope` accepts
|
|
76
|
+
`tracked`, `untracked`, `ignored`, or `all` (default: tracked plus untracked).
|
|
77
|
+
Source hits carry editable `LINE:HASH` anchors; document hits carry node/page
|
|
78
|
+
selectors and do not mark files as editable.
|
|
74
79
|
|
|
75
80
|
Read fresh `LINE:HASH` anchors before editing. Each edit item contains exactly
|
|
76
81
|
one nested `set_line`, `replace_lines`, `insert_after`, `replace_symbol`, or
|
package/dist/index.js
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"]),
|
|
@@ -234,7 +234,7 @@ var PREFERENCE_LEADS = {
|
|
|
234
234
|
show: "Preferred for reading source with anchors.",
|
|
235
235
|
edit: "Preferred for editing existing text files with fresh LINE:HASH anchors.",
|
|
236
236
|
write: "Preferred for creating or replacing a complete text file; returns LINE:HASH anchors.",
|
|
237
|
-
grep: "Preferred text/regex search; returns edit-ready LINE:HASH anchors.",
|
|
237
|
+
grep: "Preferred native text/regex search; returns edit-ready source LINE:HASH anchors and document node-local locations.",
|
|
238
238
|
search: "Preferred for AST or structural code search with ast-grep-style patterns.",
|
|
239
239
|
def: "Preferred for go-to-definition or declaration lookup by name.",
|
|
240
240
|
refs: "Preferred for finding identifier-name matches or binding-specific references before `rename` or delete.",
|
|
@@ -245,7 +245,7 @@ var FALLBACK_TEMPLATES = {
|
|
|
245
245
|
read: "Prefer `show` with fresh LINE:HASH anchors.",
|
|
246
246
|
edit: "Prefer `edit` with fresh LINE:HASH anchors.",
|
|
247
247
|
write: "Prefer `write` so the result returns LINE:HASH anchors.",
|
|
248
|
-
grep: "Prefer `grep` for text/regex search with
|
|
248
|
+
grep: "Prefer `grep` for native text/regex search with source anchors and document locations.",
|
|
249
249
|
apply_patch: "Prefer `edit` for existing text files."
|
|
250
250
|
};
|
|
251
251
|
function resolveToolName(name, aliases = {}) {
|
|
@@ -316,6 +316,59 @@ function validateReferencesSelection(input) {
|
|
|
316
316
|
return { ok: false, error: "exactly one of name or at is required" };
|
|
317
317
|
return { ok: true };
|
|
318
318
|
}
|
|
319
|
+
function validateGrepParams(input) {
|
|
320
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) {
|
|
321
|
+
return { ok: false, error: "grep params must be an object" };
|
|
322
|
+
}
|
|
323
|
+
const params = input;
|
|
324
|
+
const allowed = [
|
|
325
|
+
"pattern",
|
|
326
|
+
"path",
|
|
327
|
+
"glob",
|
|
328
|
+
"ignoreCase",
|
|
329
|
+
"literal",
|
|
330
|
+
"context",
|
|
331
|
+
"limit",
|
|
332
|
+
"summary",
|
|
333
|
+
"scope",
|
|
334
|
+
"scopeContext",
|
|
335
|
+
"gitScope",
|
|
336
|
+
"signal",
|
|
337
|
+
"stdin",
|
|
338
|
+
"timeoutMs",
|
|
339
|
+
"cancelable"
|
|
340
|
+
];
|
|
341
|
+
if (Object.keys(params).some((key) => !allowed.includes(key)))
|
|
342
|
+
return { ok: false, error: "unknown grep parameter" };
|
|
343
|
+
if (typeof params.pattern !== "string")
|
|
344
|
+
return { ok: false, error: "grep requires a pattern string" };
|
|
345
|
+
if (params.literal === true && params.pattern.length === 0)
|
|
346
|
+
return { ok: false, error: "literal pattern must not be empty" };
|
|
347
|
+
for (const key of ["path", "glob"]) {
|
|
348
|
+
if (params[key] !== undefined && (typeof params[key] !== "string" || params[key].length === 0)) {
|
|
349
|
+
return { ok: false, error: `${key} must be a non-empty string` };
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
for (const key of ["ignoreCase", "literal", "summary"]) {
|
|
353
|
+
if (params[key] !== undefined && typeof params[key] !== "boolean")
|
|
354
|
+
return { ok: false, error: `${key} must be a boolean` };
|
|
355
|
+
}
|
|
356
|
+
for (const key of ["context", "limit", "scopeContext"]) {
|
|
357
|
+
const value = params[key];
|
|
358
|
+
const minimum = key === "limit" ? 1 : 0;
|
|
359
|
+
if (value !== undefined && (typeof value !== "number" || !Number.isSafeInteger(value) || value < minimum)) {
|
|
360
|
+
return { ok: false, error: `${key} must be a ${minimum === 1 ? "positive" : "non-negative"} integer` };
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
if (params.scope !== undefined && params.scope !== "symbol")
|
|
364
|
+
return { ok: false, error: "scope must be symbol" };
|
|
365
|
+
if (params.scopeContext !== undefined && params.scope !== "symbol")
|
|
366
|
+
return { ok: false, error: "scopeContext requires scope" };
|
|
367
|
+
if (params.gitScope !== undefined && !GIT_SCOPES.includes(params.gitScope)) {
|
|
368
|
+
return { ok: false, error: "invalid gitScope" };
|
|
369
|
+
}
|
|
370
|
+
return { ok: true };
|
|
371
|
+
}
|
|
319
372
|
var VARIANT_FIELDS = {
|
|
320
373
|
set_line: ["anchor", "new_text"],
|
|
321
374
|
replace_lines: ["start_anchor", "end_anchor", "new_text"],
|
|
@@ -645,11 +698,29 @@ function buildSearchArgs(input) {
|
|
|
645
698
|
return finish(args);
|
|
646
699
|
}
|
|
647
700
|
function buildGrepArgs(input) {
|
|
648
|
-
const
|
|
649
|
-
|
|
650
|
-
|
|
701
|
+
const validated = validateGrepParams(input);
|
|
702
|
+
if (!validated.ok)
|
|
703
|
+
throw new Error(validated.error);
|
|
704
|
+
const args = ["grep", "--kdl", `--pattern=${input.pattern}`];
|
|
705
|
+
const needsPathTerminator = input.path !== undefined && input.path.startsWith("-") && input.path !== "-";
|
|
706
|
+
if (input.path !== undefined && !needsPathTerminator)
|
|
707
|
+
args.push(input.path);
|
|
708
|
+
if (input.literal)
|
|
709
|
+
args.push("--literal");
|
|
710
|
+
if (input.ignoreCase)
|
|
711
|
+
args.push("--ignore-case");
|
|
712
|
+
if (input.glob !== undefined)
|
|
713
|
+
args.push(`--glob=${input.glob}`);
|
|
714
|
+
pushOptionalNumber(args, "--context", input.context);
|
|
715
|
+
limit2(args, input.limit ?? 100);
|
|
716
|
+
if (input.summary)
|
|
717
|
+
args.push("--summary");
|
|
718
|
+
pushOptionalString(args, "--scope", input.scope);
|
|
719
|
+
pushOptionalNumber(args, "--scope-context", input.scopeContext);
|
|
651
720
|
appendGitScopeArgs(args, input);
|
|
652
|
-
|
|
721
|
+
if (input.path !== undefined && needsPathTerminator)
|
|
722
|
+
args.push("--", input.path);
|
|
723
|
+
return args;
|
|
653
724
|
}
|
|
654
725
|
function buildDefArgs(input) {
|
|
655
726
|
const args = start("def", input);
|
|
@@ -704,6 +775,17 @@ function buildRenameArgs(input) {
|
|
|
704
775
|
// ../readseek-api/src/client-output.ts
|
|
705
776
|
import { Type } from "@sinclair/typebox";
|
|
706
777
|
import { AssertError, Value } from "@sinclair/typebox/value";
|
|
778
|
+
var OPERATIONS = ["Clone", "Clean", "Default", "Assert"];
|
|
779
|
+
function parse(schema, value, what) {
|
|
780
|
+
try {
|
|
781
|
+
return Value.Parse(OPERATIONS, schema, value);
|
|
782
|
+
} catch (error) {
|
|
783
|
+
if (!(error instanceof AssertError))
|
|
784
|
+
throw error;
|
|
785
|
+
const detail = error.error;
|
|
786
|
+
throw new Error(`invalid readseek ${what}${detail?.path ?? ""}: ${detail?.message ?? "invalid value"}`);
|
|
787
|
+
}
|
|
788
|
+
}
|
|
707
789
|
var Int = Type.Integer({ minimum: Number.MIN_SAFE_INTEGER, maximum: Number.MAX_SAFE_INTEGER });
|
|
708
790
|
var Str = Type.String();
|
|
709
791
|
var Bool = Type.Boolean();
|
|
@@ -938,6 +1020,82 @@ var DefEnvelope = Type.Object({
|
|
|
938
1020
|
qualified_name: OptStr
|
|
939
1021
|
}))
|
|
940
1022
|
});
|
|
1023
|
+
function parseReadOutput(value) {
|
|
1024
|
+
return parse(ReadOutput, value, "read output");
|
|
1025
|
+
}
|
|
1026
|
+
function parseStructureOutput(value) {
|
|
1027
|
+
return parse(StructureOutput, value, "structure output");
|
|
1028
|
+
}
|
|
1029
|
+
function parseSearchOutput(value) {
|
|
1030
|
+
const envelope = parse(SearchEnvelope, value, "search output");
|
|
1031
|
+
const results = envelope.results;
|
|
1032
|
+
if (envelope.returned_matches !== undefined)
|
|
1033
|
+
results.returned_matches = envelope.returned_matches;
|
|
1034
|
+
if (envelope.truncated !== undefined)
|
|
1035
|
+
results.truncated = envelope.truncated;
|
|
1036
|
+
if (envelope.byte_basis !== undefined)
|
|
1037
|
+
results.byte_basis = envelope.byte_basis;
|
|
1038
|
+
return results;
|
|
1039
|
+
}
|
|
1040
|
+
function parseRefsOutput(value) {
|
|
1041
|
+
return parse(RefsEnvelope, value, "refs output").references.map(({ symbol, ...reference }) => ({
|
|
1042
|
+
...reference,
|
|
1043
|
+
enclosingSymbol: symbol?.qualified_name
|
|
1044
|
+
}));
|
|
1045
|
+
}
|
|
1046
|
+
function parseCheckOutput(value) {
|
|
1047
|
+
const output = parse(CheckEnvelope, value, "check output");
|
|
1048
|
+
return {
|
|
1049
|
+
file_hash: output.file_hash,
|
|
1050
|
+
errorCount: output.error_count,
|
|
1051
|
+
missingCount: output.missing_count,
|
|
1052
|
+
diagnostics: output.diagnostics
|
|
1053
|
+
};
|
|
1054
|
+
}
|
|
1055
|
+
function parseDetectOutput(value) {
|
|
1056
|
+
const output = value ?? {};
|
|
1057
|
+
if (output.type === "application/pdf") {
|
|
1058
|
+
return { kind: "pdf", ...parse(PdfDetectionInput, value, "detect output") };
|
|
1059
|
+
}
|
|
1060
|
+
if (output.width !== undefined || output.height !== undefined) {
|
|
1061
|
+
return { kind: "image", ...parse(ImageDetectionInput, value, "detect output") };
|
|
1062
|
+
}
|
|
1063
|
+
if (output.language !== undefined) {
|
|
1064
|
+
return { kind: "source", ...parse(SourceDetectionInput, value, "detect output") };
|
|
1065
|
+
}
|
|
1066
|
+
return { kind: "text", ...parse(TextDetectionInput, value, "detect output") };
|
|
1067
|
+
}
|
|
1068
|
+
function parseTextContent(value) {
|
|
1069
|
+
const output = value ?? {};
|
|
1070
|
+
if (output.hashlines === undefined || output.start_line !== undefined)
|
|
1071
|
+
return parseReadOutput(value);
|
|
1072
|
+
const spanned = parse(UnspannedContent, value, "content");
|
|
1073
|
+
return {
|
|
1074
|
+
...spanned,
|
|
1075
|
+
start_line: spanned.hashlines[0]?.line ?? 1,
|
|
1076
|
+
end_line: spanned.hashlines[spanned.hashlines.length - 1]?.line ?? 0
|
|
1077
|
+
};
|
|
1078
|
+
}
|
|
1079
|
+
function parseCursorOutput(value) {
|
|
1080
|
+
return parse(CursorEnvelope, value, "cursor output");
|
|
1081
|
+
}
|
|
1082
|
+
function parsePdfMetadata(value) {
|
|
1083
|
+
if (!value || typeof value !== "object")
|
|
1084
|
+
return;
|
|
1085
|
+
if (value.format !== "pdf")
|
|
1086
|
+
return;
|
|
1087
|
+
const document = parse(PdfMetadata, value, "inspect metadata");
|
|
1088
|
+
return { kind: "pdf", type: "application/pdf", file: document.source, format: "pdf", pages: document.pages };
|
|
1089
|
+
}
|
|
1090
|
+
function parseEditOutput(value) {
|
|
1091
|
+
return parse(EditOutputSchema, value, "edit output");
|
|
1092
|
+
}
|
|
1093
|
+
function parseRenameOutput(value) {
|
|
1094
|
+
return parse(RenameOutputSchema, value, "rename output");
|
|
1095
|
+
}
|
|
1096
|
+
function parseDefOutput(value) {
|
|
1097
|
+
return parse(DefEnvelope, value, "def output").locations;
|
|
1098
|
+
}
|
|
941
1099
|
var Bbox = Type.Object({ x: Type.Number(), y: Type.Number(), width: Type.Number(), height: Type.Number() });
|
|
942
1100
|
var Position = { page: OptInt, destination: OptStr, bbox: Type.Optional(Bbox) };
|
|
943
1101
|
var Provenance = { inferred: Type.Optional(Bool), model: OptStr, level: OptStr, task: OptStr };
|
|
@@ -994,25 +1152,41 @@ var DocumentOutput = Type.Object({
|
|
|
994
1152
|
assets: Type.Array(DocumentAsset, { default: [] }),
|
|
995
1153
|
vision: Type.Array(DocumentVision, { default: [] })
|
|
996
1154
|
});
|
|
997
|
-
var
|
|
998
|
-
|
|
999
|
-
|
|
1155
|
+
var GrepCount = Type.Integer({ minimum: 0, maximum: Number.MAX_SAFE_INTEGER });
|
|
1156
|
+
var GrepPosition = Type.Integer({ minimum: 1, maximum: Number.MAX_SAFE_INTEGER });
|
|
1157
|
+
var GrepAnchor = Type.String({ pattern: "^[1-9][0-9]*:[0-9a-f]{6}$" });
|
|
1158
|
+
var GrepLineSchema = Type.Object({
|
|
1159
|
+
line: GrepPosition,
|
|
1160
|
+
hash: Type.String({ pattern: "^[0-9a-f]{6}$" }),
|
|
1000
1161
|
text: Str,
|
|
1001
|
-
|
|
1002
|
-
end_line: Int,
|
|
1003
|
-
start_byte: Int,
|
|
1004
|
-
end_byte: Int,
|
|
1005
|
-
hashlines: Hashlines
|
|
1162
|
+
kind: Type.Union([Type.Literal("match"), Type.Literal("context")])
|
|
1006
1163
|
});
|
|
1007
|
-
var
|
|
1008
|
-
var
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1164
|
+
var GrepHitSchema = Type.Object({ line: GrepPosition, column: GrepPosition, start_byte: GrepCount, end_byte: GrepCount, text: Str, anchor: GrepAnchor });
|
|
1165
|
+
var GrepSymbolSchema = Type.Object({ name: Str, kind: Str, start_line: GrepPosition, end_line: GrepPosition, lines: Type.Array(GrepLineSchema) });
|
|
1166
|
+
var GrepSourceResultSchema = Type.Object({
|
|
1167
|
+
file: Str,
|
|
1168
|
+
file_hash: Str,
|
|
1169
|
+
language: Str,
|
|
1170
|
+
total_matches: GrepCount,
|
|
1171
|
+
returned_matches: GrepCount,
|
|
1172
|
+
hits: Type.Array(GrepHitSchema),
|
|
1173
|
+
lines: Type.Array(GrepLineSchema),
|
|
1174
|
+
symbols: Type.Array(GrepSymbolSchema)
|
|
1175
|
+
});
|
|
1176
|
+
var GrepDocumentHitSchema = Type.Object({ node_id: Str, kind: Str, snippet: Str, line: GrepPosition, page: Type.Optional(GrepPosition) });
|
|
1177
|
+
var GrepDocumentResultSchema = Type.Object({ file: Str, content_hash: Str, total_matches: GrepCount, returned_matches: GrepCount, hits: Type.Array(GrepDocumentHitSchema) });
|
|
1178
|
+
var GrepOutputSchema = Type.Object({
|
|
1179
|
+
pattern: Str,
|
|
1180
|
+
mode: Type.Union([Type.Literal("regex"), Type.Literal("literal")]),
|
|
1181
|
+
ignore_case: Bool,
|
|
1182
|
+
summary: Bool,
|
|
1183
|
+
total_matches: GrepCount,
|
|
1184
|
+
returned_matches: GrepCount,
|
|
1013
1185
|
truncated: Bool,
|
|
1014
|
-
|
|
1015
|
-
|
|
1186
|
+
count_unit: Type.Literal("lines"),
|
|
1187
|
+
scope: Type.Optional(Type.Literal("symbol")),
|
|
1188
|
+
results: Type.Array(GrepSourceResultSchema),
|
|
1189
|
+
documents: Type.Array(GrepDocumentResultSchema)
|
|
1016
1190
|
});
|
|
1017
1191
|
var RuleFinding = Type.Object({
|
|
1018
1192
|
name: Str,
|
|
@@ -1033,7 +1207,72 @@ var RulesOutput = Type.Object({
|
|
|
1033
1207
|
violation_count: Int,
|
|
1034
1208
|
diagnostics: Type.Array(RuleFinding)
|
|
1035
1209
|
});
|
|
1210
|
+
function parseDocumentOutput(value) {
|
|
1211
|
+
return parse(DocumentOutput, value, "document output");
|
|
1212
|
+
}
|
|
1213
|
+
function parseContentOutput(value) {
|
|
1214
|
+
const output = value;
|
|
1215
|
+
if (output?.format !== undefined)
|
|
1216
|
+
return parseDocumentOutput(value);
|
|
1217
|
+
if (!output || output.metadata === undefined || output.content === undefined)
|
|
1218
|
+
throw new Error("invalid readseek content: missing metadata or content");
|
|
1219
|
+
const metadata = parseDetectOutput(output.metadata);
|
|
1220
|
+
return {
|
|
1221
|
+
metadata,
|
|
1222
|
+
content: metadata.kind === "image" ? parse(ImageDescriptor, output.content, "image descriptor") : parseTextContent(output.content),
|
|
1223
|
+
...output.cursor !== undefined ? { cursor: parseCursorOutput(output.cursor) } : {}
|
|
1224
|
+
};
|
|
1225
|
+
}
|
|
1226
|
+
function parseStructureEnvelope(value) {
|
|
1227
|
+
const output = value;
|
|
1228
|
+
if (output?.format !== undefined)
|
|
1229
|
+
return parseDocumentOutput(value);
|
|
1230
|
+
if (!output || output.structure === undefined)
|
|
1231
|
+
throw new Error("invalid readseek structure: missing structure");
|
|
1232
|
+
return { ...output.metadata !== undefined ? { metadata: parseDetectOutput(output.metadata) } : {}, structure: parseStructureOutput(output.structure) };
|
|
1233
|
+
}
|
|
1234
|
+
function parseGrepOutput(value) {
|
|
1235
|
+
const output = parse(GrepOutputSchema, value, "grep output");
|
|
1236
|
+
const invalid = (message) => {
|
|
1237
|
+
throw new Error(`invalid readseek grep output: ${message}`);
|
|
1238
|
+
};
|
|
1239
|
+
const groups = [...output.results, ...output.documents];
|
|
1240
|
+
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)
|
|
1241
|
+
invalid("inconsistent counts");
|
|
1242
|
+
for (const group of groups) {
|
|
1243
|
+
if (group.total_matches === 0 || group.returned_matches > group.total_matches)
|
|
1244
|
+
invalid("inconsistent file counts");
|
|
1245
|
+
if (output.summary ? group.hits.length !== 0 : group.hits.length !== group.returned_matches)
|
|
1246
|
+
invalid("inconsistent hit count");
|
|
1247
|
+
}
|
|
1248
|
+
for (const result of output.results) {
|
|
1249
|
+
if (output.summary && (result.lines.length > 0 || result.symbols.length > 0))
|
|
1250
|
+
invalid("summary contains display entries");
|
|
1251
|
+
if (output.scope !== "symbol" && result.symbols.length > 0)
|
|
1252
|
+
invalid("symbols require symbol scope");
|
|
1253
|
+
for (const hit of result.hits) {
|
|
1254
|
+
if (hit.end_byte < hit.start_byte || Number(hit.anchor.split(":")[0]) !== hit.line)
|
|
1255
|
+
invalid("invalid hit span or anchor");
|
|
1256
|
+
}
|
|
1257
|
+
for (const symbol of result.symbols) {
|
|
1258
|
+
if (symbol.end_line < symbol.start_line || symbol.lines.some((line) => line.line < symbol.start_line || line.line > symbol.end_line))
|
|
1259
|
+
invalid("invalid symbol range");
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
return output;
|
|
1263
|
+
}
|
|
1264
|
+
function parseRulesOutput(value) {
|
|
1265
|
+
return parse(RulesOutput, value, "rules output");
|
|
1266
|
+
}
|
|
1036
1267
|
var SourceAssetsOutput = Type.Object({ file: OptStr, metadata: Type.Optional(Detection), assets: Type.Array(DocumentAsset, { default: [] }) });
|
|
1268
|
+
function parseAssetsOutput(value) {
|
|
1269
|
+
const raw = value;
|
|
1270
|
+
if (raw?.format !== undefined)
|
|
1271
|
+
return parseDocumentOutput(value);
|
|
1272
|
+
if (!raw || raw.file === undefined && raw.metadata === undefined)
|
|
1273
|
+
throw new Error("invalid readseek assets output: missing file or metadata");
|
|
1274
|
+
return parse(SourceAssetsOutput, { ...raw, ...raw.metadata !== undefined ? { metadata: parseDetectOutput(raw.metadata) } : {} }, "assets output");
|
|
1275
|
+
}
|
|
1037
1276
|
|
|
1038
1277
|
// ../readseek-api/src/edit-plan.ts
|
|
1039
1278
|
import { Document, Node, format } from "@bgotink/kdl";
|
|
@@ -1075,6 +1314,81 @@ function serializeEditPlan(edits) {
|
|
|
1075
1314
|
throw new Error(validation.error);
|
|
1076
1315
|
return format(new Document([node("plan", {}, validation.edits.map(editNode))]));
|
|
1077
1316
|
}
|
|
1317
|
+
|
|
1318
|
+
// ../readseek-api/src/client.ts
|
|
1319
|
+
function runOptions(options, vision = false) {
|
|
1320
|
+
return {
|
|
1321
|
+
signal: options.signal,
|
|
1322
|
+
stdin: options.stdin,
|
|
1323
|
+
timeoutMs: options.timeoutMs,
|
|
1324
|
+
cancelable: options.cancelable,
|
|
1325
|
+
...vision ? { vision: true } : {}
|
|
1326
|
+
};
|
|
1327
|
+
}
|
|
1328
|
+
function createReadSeekClient(runner) {
|
|
1329
|
+
return {
|
|
1330
|
+
async show(path, options = {}) {
|
|
1331
|
+
const value = await runner.kdl(buildShowArgs({ ...options, path }), runOptions(options));
|
|
1332
|
+
return parseContentOutput(value);
|
|
1333
|
+
},
|
|
1334
|
+
async tree(path, options = {}) {
|
|
1335
|
+
const value = await runner.kdl(buildTreeArgs({ ...options, path }), runOptions(options));
|
|
1336
|
+
return parseStructureEnvelope(value);
|
|
1337
|
+
},
|
|
1338
|
+
async meta(path, options = {}) {
|
|
1339
|
+
const value = await runner.kdl(buildMetaArgs({ ...options, path }), runOptions(options));
|
|
1340
|
+
return parsePdfMetadata(value) ?? parseDetectOutput(value?.metadata);
|
|
1341
|
+
},
|
|
1342
|
+
async assets(path, options = {}) {
|
|
1343
|
+
const value = await runner.kdl(buildAssetsArgs({ ...options, path }), runOptions(options));
|
|
1344
|
+
return parseAssetsOutput(value);
|
|
1345
|
+
},
|
|
1346
|
+
async syntax(path, options = {}) {
|
|
1347
|
+
const value = await runner.kdl(buildSyntaxArgs({ ...options, path }), runOptions(options));
|
|
1348
|
+
return parseCheckOutput(value?.diagnostics ?? value);
|
|
1349
|
+
},
|
|
1350
|
+
async check(path, options) {
|
|
1351
|
+
const value = await runner.kdl(buildCheckArgs({ ...options, path }), runOptions(options));
|
|
1352
|
+
return parseRulesOutput(value);
|
|
1353
|
+
},
|
|
1354
|
+
async caption(path, options = {}) {
|
|
1355
|
+
const value = await runner.kdl(buildCaptionArgs({ ...options, path }), runOptions(options, true));
|
|
1356
|
+
return parseContentOutput(value);
|
|
1357
|
+
},
|
|
1358
|
+
async objects(path, options = {}) {
|
|
1359
|
+
const value = await runner.kdl(buildObjectsArgs({ ...options, path }), runOptions(options, true));
|
|
1360
|
+
return parseContentOutput(value);
|
|
1361
|
+
},
|
|
1362
|
+
async ocr(path, options = {}) {
|
|
1363
|
+
const value = await runner.kdl(buildOcrArgs({ ...options, path }), runOptions(options, true));
|
|
1364
|
+
return parseContentOutput(value);
|
|
1365
|
+
},
|
|
1366
|
+
async search(path, options) {
|
|
1367
|
+
const value = await runner.kdl(buildSearchArgs({ ...options, path }), runOptions(options));
|
|
1368
|
+
return parseSearchOutput(value);
|
|
1369
|
+
},
|
|
1370
|
+
async grep(params) {
|
|
1371
|
+
const value = await runner.kdl(buildGrepArgs(params), runOptions(params));
|
|
1372
|
+
return parseGrepOutput(value);
|
|
1373
|
+
},
|
|
1374
|
+
async def(path, options) {
|
|
1375
|
+
const value = await runner.kdl(buildDefArgs({ ...options, path }), runOptions(options));
|
|
1376
|
+
return parseDefOutput(value);
|
|
1377
|
+
},
|
|
1378
|
+
async refs(path, options) {
|
|
1379
|
+
const value = await runner.kdl(buildRefsArgs({ ...options, path }), runOptions(options));
|
|
1380
|
+
return parseRefsOutput(value);
|
|
1381
|
+
},
|
|
1382
|
+
async edit(path, edits, options = {}) {
|
|
1383
|
+
const stdin = serializeEditPlan(edits);
|
|
1384
|
+
return parseEditOutput(await runner.kdl(buildEditArgs({ ...options, path, requestFromStdin: true }), runOptions({ ...options, stdin })));
|
|
1385
|
+
},
|
|
1386
|
+
async rename(path, options) {
|
|
1387
|
+
const value = await runner.kdl(buildRenameArgs({ ...options, path }), runOptions(options));
|
|
1388
|
+
return parseRenameOutput(value);
|
|
1389
|
+
}
|
|
1390
|
+
};
|
|
1391
|
+
}
|
|
1078
1392
|
// ../readseek-api/src/show-presentation.ts
|
|
1079
1393
|
function isShowTool(name) {
|
|
1080
1394
|
return name === "show" || name === "tree" || name === "meta" || name === "assets";
|
|
@@ -1290,6 +1604,31 @@ function formatShowResult(presentation) {
|
|
|
1290
1604
|
|
|
1291
1605
|
`);
|
|
1292
1606
|
}
|
|
1607
|
+
// ../readseek-api/src/grep-presentation.ts
|
|
1608
|
+
function renderGrepOutput(output) {
|
|
1609
|
+
const rendered = [`${output.returned_matches} of ${output.total_matches} matching lines${output.truncated ? " (truncated)" : ""}`];
|
|
1610
|
+
const displayLine = (line) => `${line.line}:${line.hash}|${line.kind === "match" ? ">>" : " "}${line.text}`;
|
|
1611
|
+
for (const result of output.results) {
|
|
1612
|
+
rendered.push(`${result.file}: ${result.total_matches} matching lines (${result.returned_matches} returned)`);
|
|
1613
|
+
if (output.summary)
|
|
1614
|
+
continue;
|
|
1615
|
+
rendered.push(...result.lines.map(displayLine));
|
|
1616
|
+
for (const symbol of result.symbols) {
|
|
1617
|
+
rendered.push(` ${symbol.kind} ${symbol.name} (${symbol.start_line}-${symbol.end_line})`);
|
|
1618
|
+
rendered.push(...symbol.lines.map(displayLine));
|
|
1619
|
+
}
|
|
1620
|
+
}
|
|
1621
|
+
for (const document of output.documents) {
|
|
1622
|
+
rendered.push(`${document.file}: ${document.total_matches} matching lines (${document.returned_matches} returned)`);
|
|
1623
|
+
if (output.summary)
|
|
1624
|
+
continue;
|
|
1625
|
+
for (const hit of document.hits) {
|
|
1626
|
+
rendered.push(` node:${hit.node_id}${hit.page === undefined ? "" : ` page:${hit.page}`} line:${hit.line} (${hit.kind}): ${hit.snippet}`);
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1629
|
+
return rendered.join(`
|
|
1630
|
+
`);
|
|
1631
|
+
}
|
|
1293
1632
|
// ../readseek-api/src/platform.ts
|
|
1294
1633
|
import { accessSync, constants as fsConstants, existsSync, statSync } from "node:fs";
|
|
1295
1634
|
import { createRequire } from "node:module";
|
|
@@ -1391,7 +1730,7 @@ function cleanReadSeekErrorText(stderr) {
|
|
|
1391
1730
|
return stderr.replace(READSEEK_USAGE_HINT, "").replace(/^error:\s*/i, "").replace(/\s*\n\s*/g, " ").trim();
|
|
1392
1731
|
}
|
|
1393
1732
|
// ../readseek-api/src/kdl.ts
|
|
1394
|
-
import { parse } from "@bgotink/kdl";
|
|
1733
|
+
import { parse as parse2 } from "@bgotink/kdl";
|
|
1395
1734
|
function convertNode(node) {
|
|
1396
1735
|
if (node.getTag() !== null)
|
|
1397
1736
|
throw new Error("KDL node annotations are not supported");
|
|
@@ -1419,7 +1758,7 @@ function convertNode(node) {
|
|
|
1419
1758
|
};
|
|
1420
1759
|
}
|
|
1421
1760
|
function parseKdlDocument(text) {
|
|
1422
|
-
const document =
|
|
1761
|
+
const document = parse2(text);
|
|
1423
1762
|
if (document.nodes.length !== 1)
|
|
1424
1763
|
throw new Error("KDL document must contain exactly one root node");
|
|
1425
1764
|
return convertNode(document.nodes[0]);
|
|
@@ -1553,6 +1892,46 @@ function search(root) {
|
|
|
1553
1892
|
}))
|
|
1554
1893
|
};
|
|
1555
1894
|
}
|
|
1895
|
+
function grepChildren(node, allowed) {
|
|
1896
|
+
if (node.children.some((child) => !allowed.includes(child.name))) {
|
|
1897
|
+
throw new Error(`invalid readseek KDL grep: unexpected child of ${node.name}`);
|
|
1898
|
+
}
|
|
1899
|
+
}
|
|
1900
|
+
function grepLine(node) {
|
|
1901
|
+
grepChildren(node, []);
|
|
1902
|
+
const parsed = line(node);
|
|
1903
|
+
if (requireProperty(node, "number") !== parsed.line)
|
|
1904
|
+
throw new Error("invalid readseek KDL grep line number");
|
|
1905
|
+
return { ...parsed, kind: requireProperty(node, "kind") };
|
|
1906
|
+
}
|
|
1907
|
+
function grep(root) {
|
|
1908
|
+
grepChildren(root, ["match", "document"]);
|
|
1909
|
+
return parseGrepOutput({
|
|
1910
|
+
...properties(root),
|
|
1911
|
+
results: children(root, "match").map((match) => {
|
|
1912
|
+
grepChildren(match, ["hit", "line", "symbol"]);
|
|
1913
|
+
return {
|
|
1914
|
+
...properties(match),
|
|
1915
|
+
hits: children(match, "hit").map((hit) => {
|
|
1916
|
+
grepChildren(hit, []);
|
|
1917
|
+
return properties(hit);
|
|
1918
|
+
}),
|
|
1919
|
+
lines: children(match, "line").map(grepLine),
|
|
1920
|
+
symbols: children(match, "symbol").map((symbol) => {
|
|
1921
|
+
grepChildren(symbol, ["line"]);
|
|
1922
|
+
return { ...properties(symbol), lines: children(symbol, "line").map(grepLine) };
|
|
1923
|
+
})
|
|
1924
|
+
};
|
|
1925
|
+
}),
|
|
1926
|
+
documents: children(root, "document").map((document) => {
|
|
1927
|
+
grepChildren(document, ["hit"]);
|
|
1928
|
+
return { ...properties(document), hits: children(document, "hit").map((hit) => {
|
|
1929
|
+
grepChildren(hit, []);
|
|
1930
|
+
return properties(hit);
|
|
1931
|
+
}) };
|
|
1932
|
+
})
|
|
1933
|
+
});
|
|
1934
|
+
}
|
|
1556
1935
|
function references(root) {
|
|
1557
1936
|
const parsed = children(root, "reference").map((reference) => {
|
|
1558
1937
|
const sourceLine = child(reference, "line");
|
|
@@ -1668,7 +2047,7 @@ function parseReadSeekKdl(text) {
|
|
|
1668
2047
|
case "document":
|
|
1669
2048
|
return root.properties.has("format") ? pdfDocument(root) : sourceDocument(root);
|
|
1670
2049
|
case "search":
|
|
1671
|
-
return search(root);
|
|
2050
|
+
return ["mode", "ignore_case", "summary", "count_unit"].some((key) => root.properties.has(key)) ? grep(root) : search(root);
|
|
1672
2051
|
case "check":
|
|
1673
2052
|
return diagnostics(root);
|
|
1674
2053
|
case "refs":
|
|
@@ -1867,8 +2246,8 @@ function collectAnchoredFiles(value, files) {
|
|
|
1867
2246
|
if (!value || typeof value !== "object")
|
|
1868
2247
|
return;
|
|
1869
2248
|
const output = value;
|
|
1870
|
-
const
|
|
1871
|
-
if (typeof output.file === "string" &&
|
|
2249
|
+
const lineGroups = [output.hashlines, output.lines, ...items(output.symbols).map((symbol) => record2(symbol).lines)];
|
|
2250
|
+
if (typeof output.file === "string" && lineGroups.some((lines) => items(lines).some((line) => typeof record2(line).line === "number" && typeof record2(line).hash === "string")))
|
|
1872
2251
|
files.add(output.file);
|
|
1873
2252
|
for (const item of Object.values(output))
|
|
1874
2253
|
collectAnchoredFiles(item, files);
|
|
@@ -1981,6 +2360,12 @@ async function runReadSeek(context, args, options = {}) {
|
|
|
1981
2360
|
timeoutMs: options.timeoutMs
|
|
1982
2361
|
}));
|
|
1983
2362
|
}
|
|
2363
|
+
function readSeekClient(context) {
|
|
2364
|
+
return createReadSeekClient({
|
|
2365
|
+
text: (args, options) => runReadSeekRaw(context, [...args], options),
|
|
2366
|
+
kdl: (args, options) => runReadSeek(context, [...args], options)
|
|
2367
|
+
});
|
|
2368
|
+
}
|
|
1984
2369
|
async function runContent(context, args, options = {}) {
|
|
1985
2370
|
return requireContent(await runReadSeek(context, args, options));
|
|
1986
2371
|
}
|
|
@@ -2104,7 +2489,6 @@ ${[...paths].sort().map((filePath) => `- ${filePath}`).join(`
|
|
|
2104
2489
|
}
|
|
2105
2490
|
|
|
2106
2491
|
// src/tools.ts
|
|
2107
|
-
import { readFile as readFile2, stat as stat3 } from "node:fs/promises";
|
|
2108
2492
|
import { tool as tool3 } from "@opencode-ai/plugin";
|
|
2109
2493
|
|
|
2110
2494
|
// src/constants.ts
|
|
@@ -2448,7 +2832,7 @@ function initialTitle(kind, args) {
|
|
|
2448
2832
|
case "search":
|
|
2449
2833
|
return `Find nodes ${args.pattern ?? args.context ?? args.rule}`;
|
|
2450
2834
|
case "grep":
|
|
2451
|
-
return `Find text ${args.pattern
|
|
2835
|
+
return `Find text ${args.pattern}`;
|
|
2452
2836
|
case "def":
|
|
2453
2837
|
return `Find definition ${args.name}`;
|
|
2454
2838
|
case "refs":
|
|
@@ -2480,12 +2864,9 @@ function resultPresentation(kind, args, value) {
|
|
|
2480
2864
|
return { title: `Found ${matches} matches`, metadata: { results: results.length, matches } };
|
|
2481
2865
|
}
|
|
2482
2866
|
case "grep": {
|
|
2483
|
-
const results = items(output.results);
|
|
2484
|
-
const matches =
|
|
2485
|
-
|
|
2486
|
-
return total + (typeof entry.count === "number" ? entry.count : items(entry.matches).length);
|
|
2487
|
-
}, 0);
|
|
2488
|
-
return { title: `Found ${matches} matches`, metadata: { results: results.length, matches } };
|
|
2867
|
+
const results = items(output.results).length + items(output.documents).length;
|
|
2868
|
+
const matches = output.total_matches;
|
|
2869
|
+
return { title: `Found ${matches} matches`, metadata: { results, matches } };
|
|
2489
2870
|
}
|
|
2490
2871
|
case "def": {
|
|
2491
2872
|
const locations = items(output.locations).length;
|
|
@@ -2543,8 +2924,8 @@ function readseekTool(description, args, kind, execute, imagePolicy) {
|
|
|
2543
2924
|
return {
|
|
2544
2925
|
...attachments.length > 0 ? { attachments } : {},
|
|
2545
2926
|
title: presentation.title,
|
|
2546
|
-
output: isShowTool(kind) ? formatShowResult(presentShowResult(kind, displayResult, args)) : render(displayResult),
|
|
2547
|
-
metadata: { ...presentation.metadata, ...isShowTool(kind) ? { readseek: result } : {} }
|
|
2927
|
+
output: isShowTool(kind) ? formatShowResult(presentShowResult(kind, displayResult, args)) : kind === "grep" ? renderGrepOutput(result) : render(displayResult),
|
|
2928
|
+
metadata: { ...presentation.metadata, ...isShowTool(kind) || kind === "grep" ? { readseek: result } : {} }
|
|
2548
2929
|
};
|
|
2549
2930
|
}
|
|
2550
2931
|
});
|
|
@@ -2555,104 +2936,35 @@ function createReadSeekTools(imagePolicy) {
|
|
|
2555
2936
|
return {
|
|
2556
2937
|
...createAccessTools(imagePolicy),
|
|
2557
2938
|
readseek_grep: readseekTool(openCodeToolDescription("grep"), {
|
|
2558
|
-
pattern: tool3.schema.string().
|
|
2559
|
-
text: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.text),
|
|
2939
|
+
pattern: tool3.schema.string().describe(PARAM_DESCRIPTIONS.grepPattern),
|
|
2560
2940
|
summary: tool3.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.summary),
|
|
2561
2941
|
path: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.projectSearchPath),
|
|
2562
2942
|
glob: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.glob),
|
|
2563
2943
|
literal: tool3.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.literal),
|
|
2564
2944
|
ignoreCase: tool3.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.ignoreCase),
|
|
2565
2945
|
context: tool3.schema.number().int().nonnegative().optional().describe(PARAM_DESCRIPTIONS.context),
|
|
2566
|
-
limit: tool3.schema.number().int().positive().optional().describe(PARAM_DESCRIPTIONS.matchLimit)
|
|
2946
|
+
limit: tool3.schema.number().int().positive().optional().describe(PARAM_DESCRIPTIONS.matchLimit),
|
|
2947
|
+
scope: tool3.schema.literal("symbol").optional().describe(PARAM_DESCRIPTIONS.grepScope),
|
|
2948
|
+
scopeContext: tool3.schema.number().int().nonnegative().optional().describe(PARAM_DESCRIPTIONS.scopeContext),
|
|
2949
|
+
gitScope: tool3.schema.enum(GIT_SCOPES).optional().describe(PARAM_DESCRIPTIONS.gitScope)
|
|
2567
2950
|
}, "grep", async (input, context) => {
|
|
2568
2951
|
const target = resolvePath(context.directory, input.path ?? ".");
|
|
2569
|
-
|
|
2570
|
-
throw new Error("exactly one of pattern or text is required");
|
|
2571
|
-
}
|
|
2572
|
-
const pattern = input.pattern ?? input.text;
|
|
2952
|
+
const pattern = input.pattern;
|
|
2573
2953
|
await authorizeSearch(context, target, pattern);
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
else if (info.isDirectory()) {
|
|
2589
|
-
const glob = new Bun.Glob(input.glob ?? "**/*");
|
|
2590
|
-
for await (const filePath of glob.scan({ cwd: target, absolute: true, onlyFiles: true, followSymlinks: false })) {
|
|
2591
|
-
files.push(filePath);
|
|
2592
|
-
}
|
|
2593
|
-
} else
|
|
2594
|
-
throw new Error(`grep target is not a file or directory: ${target}`);
|
|
2595
|
-
const maxMatches = input.limit ?? 100;
|
|
2596
|
-
const contextLines = input.context ?? 0;
|
|
2597
|
-
const results = [];
|
|
2598
|
-
let totalMatches = 0;
|
|
2599
|
-
let truncated = false;
|
|
2600
|
-
for (const filePath of files.sort()) {
|
|
2601
|
-
context.abort.throwIfAborted();
|
|
2602
|
-
if (truncated)
|
|
2603
|
-
break;
|
|
2604
|
-
const buffer = await readFile2(filePath);
|
|
2605
|
-
if (buffer.includes(0))
|
|
2606
|
-
continue;
|
|
2607
|
-
const lines = buffer.toString("utf8").replace(/\r\n/g, `
|
|
2608
|
-
`).split(`
|
|
2609
|
-
`);
|
|
2610
|
-
const matchLines = new Set;
|
|
2611
|
-
const ranges = [];
|
|
2612
|
-
for (let index = 0;index < lines.length; index++) {
|
|
2613
|
-
expression.lastIndex = 0;
|
|
2614
|
-
if (!expression.test(lines[index] ?? ""))
|
|
2615
|
-
continue;
|
|
2616
|
-
if (totalMatches === maxMatches) {
|
|
2617
|
-
truncated = true;
|
|
2618
|
-
break;
|
|
2619
|
-
}
|
|
2620
|
-
const line = index + 1;
|
|
2621
|
-
matchLines.add(line);
|
|
2622
|
-
ranges.push([Math.max(1, line - contextLines), Math.min(lines.length, line + contextLines)]);
|
|
2623
|
-
totalMatches++;
|
|
2624
|
-
}
|
|
2625
|
-
if (ranges.length === 0)
|
|
2626
|
-
continue;
|
|
2627
|
-
if (input.summary) {
|
|
2628
|
-
results.push({ file: filePath, count: matchLines.size });
|
|
2629
|
-
continue;
|
|
2630
|
-
}
|
|
2631
|
-
const mergedRanges = [];
|
|
2632
|
-
for (const [start, end] of ranges) {
|
|
2633
|
-
const previous = mergedRanges.at(-1);
|
|
2634
|
-
if (previous && start <= previous[1] + 1)
|
|
2635
|
-
previous[1] = Math.max(previous[1], end);
|
|
2636
|
-
else
|
|
2637
|
-
mergedRanges.push([start, end]);
|
|
2638
|
-
}
|
|
2639
|
-
const linesByNumber = new Map;
|
|
2640
|
-
for (const [start, end] of mergedRanges) {
|
|
2641
|
-
const output = record2(await runContent(context, readRangeArgs(filePath, start, end)));
|
|
2642
|
-
for (const hashline of items(output.hashlines)) {
|
|
2643
|
-
const line = record2(hashline).line;
|
|
2644
|
-
if (typeof line === "number" && !linesByNumber.has(line))
|
|
2645
|
-
linesByNumber.set(line, hashline);
|
|
2646
|
-
}
|
|
2647
|
-
}
|
|
2648
|
-
const hashlines = [...linesByNumber.values()];
|
|
2649
|
-
const matches = hashlines.filter((hashline) => {
|
|
2650
|
-
const line = record2(hashline).line;
|
|
2651
|
-
return typeof line === "number" && matchLines.has(line);
|
|
2652
|
-
});
|
|
2653
|
-
results.push({ file: filePath, matches, hashlines });
|
|
2654
|
-
}
|
|
2655
|
-
return { results, truncated };
|
|
2954
|
+
return readSeekClient(context).grep({
|
|
2955
|
+
pattern,
|
|
2956
|
+
path: target,
|
|
2957
|
+
glob: input.glob,
|
|
2958
|
+
ignoreCase: input.ignoreCase,
|
|
2959
|
+
literal: input.literal,
|
|
2960
|
+
context: input.context,
|
|
2961
|
+
limit: input.limit,
|
|
2962
|
+
summary: input.summary,
|
|
2963
|
+
scope: input.scope,
|
|
2964
|
+
scopeContext: input.scopeContext,
|
|
2965
|
+
gitScope: input.gitScope,
|
|
2966
|
+
signal: context.abort
|
|
2967
|
+
});
|
|
2656
2968
|
}),
|
|
2657
2969
|
readseek_search: readseekTool(openCodeToolDescription("search"), {
|
|
2658
2970
|
pattern: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.pattern),
|
|
@@ -2951,7 +3263,7 @@ readseek edit file.ts @edits.kdl --kdl
|
|
|
2951
3263
|
readseek rename file.ts --at line:10:3 nextName --kdl
|
|
2952
3264
|
\`\`\`
|
|
2953
3265
|
|
|
2954
|
-
Native \`grep\`
|
|
3266
|
+
Native \`grep\` uses UTF-8 RE2 regex by default; add \`--literal\` for exact text. Its positive \`--limit\` defaults to 100 matching lines, not occurrences. Reference selection requires either a symbol \`name\` or \`--at\`; rename requires a line or hash cursor. Preview mutations first, then use \`--apply --plan-hash HASH\` with the returned plan hash. Edit requests are KDL: for example, \`set_line anchor="10:HASH" new_text="replacement"\`.
|
|
2955
3267
|
|
|
2956
3268
|
Syntax and rule checks return exit 0 when clean, 1 for findings, and 2 on failure. Exit 1 is not a transport failure. Syntax checks are parser checks, not type checking.
|
|
2957
3269
|
|
package/dist/v2.js
CHANGED
|
@@ -26,7 +26,7 @@ readseek edit file.ts @edits.kdl --kdl
|
|
|
26
26
|
readseek rename file.ts --at line:10:3 nextName --kdl
|
|
27
27
|
\`\`\`
|
|
28
28
|
|
|
29
|
-
Native \`grep\`
|
|
29
|
+
Native \`grep\` uses UTF-8 RE2 regex by default; add \`--literal\` for exact text. Its positive \`--limit\` defaults to 100 matching lines, not occurrences. Reference selection requires either a symbol \`name\` or \`--at\`; rename requires a line or hash cursor. Preview mutations first, then use \`--apply --plan-hash HASH\` with the returned plan hash. Edit requests are KDL: for example, \`set_line anchor="10:HASH" new_text="replacement"\`.
|
|
30
30
|
|
|
31
31
|
Syntax and rule checks return exit 0 when clean, 1 for findings, and 2 on failure. Exit 1 is not a transport failure. Syntax checks are parser checks, not type checking.
|
|
32
32
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-readseek",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.1",
|
|
4
4
|
"description": "OpenCode plugin for anchored file operations and structural code navigation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -33,8 +33,8 @@
|
|
|
33
33
|
"node": ">=20.0.0"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@jarkkojs/readseek": "^0.12.
|
|
37
|
-
"@jarkkojs/readseek-api": "^0.12.
|
|
36
|
+
"@jarkkojs/readseek": "^0.12.1",
|
|
37
|
+
"@jarkkojs/readseek-api": "^0.12.1",
|
|
38
38
|
"@opencode-ai/plugin": "^1.18.18"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|