opencode-readseek 0.11.4 → 0.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -17
- package/dist/index.js +733 -360
- package/dist/v2.js +15 -15
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -16,22 +16,22 @@ var EDIT_VARIANTS = [
|
|
|
16
16
|
];
|
|
17
17
|
var GIT_SCOPES = ["tracked", "untracked", "ignored", "all"];
|
|
18
18
|
var TOOL_NAMES = [
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"
|
|
19
|
+
"show",
|
|
20
|
+
"tree",
|
|
21
|
+
"meta",
|
|
22
|
+
"assets",
|
|
23
|
+
"syntax",
|
|
24
|
+
"check",
|
|
25
|
+
"caption",
|
|
26
|
+
"objects",
|
|
27
|
+
"ocr",
|
|
28
|
+
"search",
|
|
29
|
+
"grep",
|
|
30
|
+
"def",
|
|
31
|
+
"refs",
|
|
32
|
+
"edit",
|
|
33
|
+
"rename",
|
|
34
|
+
"write"
|
|
35
35
|
];
|
|
36
36
|
var PARAM_DESCRIPTIONS = {
|
|
37
37
|
path: "Absolute or relative file path",
|
|
@@ -43,12 +43,12 @@ var PARAM_DESCRIPTIONS = {
|
|
|
43
43
|
pattern: "AST pattern (ast-grep style), such as console.log($$$ARGS)",
|
|
44
44
|
patternContext: "Surrounding code context for sub-statement patterns",
|
|
45
45
|
selector: "Target AST node kind to extract from pattern context",
|
|
46
|
-
grepPattern: "
|
|
46
|
+
grepPattern: "Native regex pattern; set literal for exact text. Empty regex matches every line; empty literal is invalid.",
|
|
47
47
|
name: "Qualified or unqualified symbol name",
|
|
48
48
|
refsName: "Identifier to find references for",
|
|
49
49
|
gitScope: "In a Git repository, which files to search: tracked, untracked, ignored (untracked plus ignored), or all. Defaults to tracked plus untracked.",
|
|
50
|
-
at: "Location selector: line:N[:COLUMN], hash:H[:COLUMN], symbol:NAME, page:N, node:ID, or asset:ID",
|
|
51
|
-
assetAt: "Document selector: page:N, node:ID, or asset:ID",
|
|
50
|
+
at: "Location selector: N[:COLUMN], START-END, H[:COLUMN], NAME, line:N[:COLUMN], hash:H[:COLUMN], symbol:NAME, page:N, node:ID, or asset:ID",
|
|
51
|
+
assetAt: "Document selector: N, page:N, node:ID, or asset:ID",
|
|
52
52
|
rule: "KDL node selection rule, @path, or '-' for standard input",
|
|
53
53
|
rules: "KDL structural checks, @path, or '-' for standard input",
|
|
54
54
|
stdinName: "File name hint when the target is '-'",
|
|
@@ -60,7 +60,6 @@ var PARAM_DESCRIPTIONS = {
|
|
|
60
60
|
kind: "Restrict structure to this node kind",
|
|
61
61
|
outline: "Show outline of document hierarchy or source symbols",
|
|
62
62
|
noAnchors: "Omit source line anchors",
|
|
63
|
-
text: "Literal native text query; adapter find_text also supports regex via pattern",
|
|
64
63
|
edits: "Use set_line, replace_lines, insert_after, replace_symbol, or replace",
|
|
65
64
|
editVariant: "Exactly one nested edit variant",
|
|
66
65
|
setLine: 'Replace one line: {"set_line":{"anchor":"LINE:HASH","new_text":"..."}}',
|
|
@@ -92,27 +91,28 @@ var PARAM_DESCRIPTIONS = {
|
|
|
92
91
|
literal: "Treat pattern literally",
|
|
93
92
|
context: "Surrounding lines for each match",
|
|
94
93
|
matchLimit: "Maximum matches to return",
|
|
94
|
+
grepLimit: "Maximum matching lines to return; positive integer, defaults to 100",
|
|
95
95
|
summary: "Return per-file counts",
|
|
96
96
|
grepScope: "Group matches by enclosing symbol",
|
|
97
|
-
scopeContext: "
|
|
97
|
+
scopeContext: "Non-negative context lines within each symbol; requires scope: symbol"
|
|
98
98
|
};
|
|
99
99
|
var TOOL_DESCRIPTIONS = {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
100
|
+
show: "Read source, document content, or image descriptors without inference. Obtain fresh `LINE:HASH` anchors before edits; use at with a column for cursor context. Document results include typed nodes and cached asset paths.",
|
|
101
|
+
tree: "Show parsed source or document hierarchy. Use shallow depth for file overviews.",
|
|
102
|
+
meta: "Show format, source identity, language, dimensions, or page count without decoding image content or inference.",
|
|
103
|
+
assets: "Return selected document asset descriptors with usable cached paths. Adapters attach images from those paths.",
|
|
104
|
+
syntax: "Check parser syntax diagnostics. Exit 0 is clean, 1 has findings, 2 is failure; this is not type checking.",
|
|
105
|
+
check: "Evaluate KDL structural assertions. Exit 0 is clean, 1 has findings, 2 is failure.",
|
|
106
|
+
caption: "Explicitly infer a caption using an installed model. Include text and objects to request OCR and detection too; never downloads models automatically.",
|
|
107
|
+
objects: "Explicitly infer object labels and bounding boxes using an installed model.",
|
|
108
|
+
ocr: "Explicitly recognize text from pixels using an installed model; reading never silently invokes OCR.",
|
|
109
|
+
search: "Find AST nodes by pattern or KDL selection rule. Returns edit-ready anchors. Use `grep` for text and regex.",
|
|
110
|
+
grep: "Search with native regex or literal matching. Returns matching-line counts, source LINE:HASH anchors, and document node-local locations. Native glob, context, summary, limits, Git scope, and symbol grouping apply; default limit is 100 matching lines.",
|
|
111
|
+
def: "Find a symbol definition or declaration by qualified or unqualified name.",
|
|
112
|
+
refs: "Find identifier-name matches with name, or binding-scoped references with at. Use before renaming or deleting a symbol.",
|
|
113
|
+
edit: "Edit existing files with fresh `LINE:HASH` anchors. Each edits item contains exactly one set_line, replace_lines, insert_after, replace_symbol, or replace object. Preview before applying a verified plan.",
|
|
114
|
+
rename: "Rename a symbol at a line or hash cursor with binding-aware edits and stale-plan protection.",
|
|
115
|
+
write: "Create or replace a complete text file and return `LINE:HASH` anchors. Use `edit` for small changes."
|
|
116
116
|
};
|
|
117
117
|
function rewriteBacktickedToolNames(text, aliases = {}) {
|
|
118
118
|
return TOOL_NAMES.reduce((rewritten, toolName) => rewritten.replaceAll(`\`${toolName}\``, `\`${aliases[toolName] ?? toolName}\``), text);
|
|
@@ -205,48 +205,48 @@ function tool(name, title, properties, required) {
|
|
|
205
205
|
return { name, title, description: TOOL_DESCRIPTIONS[name], inputSchema: { type: "object", properties, required, additionalProperties: false } };
|
|
206
206
|
}
|
|
207
207
|
var TOOL_CONTRACTS = [
|
|
208
|
-
tool("
|
|
209
|
-
tool("
|
|
210
|
-
tool("
|
|
211
|
-
tool("
|
|
212
|
-
tool("
|
|
213
|
-
tool("
|
|
214
|
-
tool("
|
|
215
|
-
tool("
|
|
216
|
-
tool("
|
|
217
|
-
tool("
|
|
218
|
-
tool("
|
|
219
|
-
tool("
|
|
220
|
-
tool("
|
|
221
|
-
tool("
|
|
222
|
-
tool("
|
|
223
|
-
tool("
|
|
208
|
+
tool("show", "Show content", { ...path, ...at, ...lang, ...stdin, ...noAnchors, end: integer(PARAM_DESCRIPTIONS.end), limit: integer(PARAM_DESCRIPTIONS.limit) }, ["path"]),
|
|
209
|
+
tool("tree", "Show structure", { ...path, ...at, ...lang, ...stdin, depth: integer(PARAM_DESCRIPTIONS.depth, 0), kind: string(PARAM_DESCRIPTIONS.kind), outline: { type: "boolean", description: PARAM_DESCRIPTIONS.outline } }, ["path"]),
|
|
210
|
+
tool("meta", "Show metadata", { ...path, ...lang, ...stdin }, ["path"]),
|
|
211
|
+
tool("assets", "Show assets", { ...path, at: string(PARAM_DESCRIPTIONS.assetAt), ...stdin }, ["path"]),
|
|
212
|
+
tool("syntax", "Check syntax", { ...path, ...lang, ...stdin }, ["path"]),
|
|
213
|
+
tool("check", "Check rules", { path: string(PARAM_DESCRIPTIONS.searchPath), ...gitScopeSchema, rules: string(PARAM_DESCRIPTIONS.rules) }, ["rules"]),
|
|
214
|
+
tool("caption", "Generate caption", { ...path, ...at, ...stdin, ...level, include: { type: "array", items: { type: "string", enum: ["text", "objects"] }, minItems: 1, uniqueItems: true, description: PARAM_DESCRIPTIONS.include } }, ["path"]),
|
|
215
|
+
tool("objects", "Detect objects", { ...path, ...at, ...stdin, ...level }, ["path"]),
|
|
216
|
+
tool("ocr", "Recognize text", { ...path, ...at, ...stdin, ...level }, ["path"]),
|
|
217
|
+
tool("search", "Search AST nodes", { ...path, ...lang, ...stdin, ...limit, ...gitScopeSchema, ...noAnchors, pattern: string(PARAM_DESCRIPTIONS.pattern), context: string(PARAM_DESCRIPTIONS.patternContext), selector: string(PARAM_DESCRIPTIONS.selector), rule: string(PARAM_DESCRIPTIONS.rule) }, []),
|
|
218
|
+
tool("grep", "Search text", { path: string(PARAM_DESCRIPTIONS.searchPath), limit: { ...integer(PARAM_DESCRIPTIONS.grepLimit), default: 100 }, ...gitScopeSchema, pattern: string(PARAM_DESCRIPTIONS.grepPattern), glob: string(PARAM_DESCRIPTIONS.glob), ignoreCase: { type: "boolean", description: PARAM_DESCRIPTIONS.ignoreCase }, literal: { type: "boolean", description: PARAM_DESCRIPTIONS.literal }, context: integer(PARAM_DESCRIPTIONS.context, 0), summary: { type: "boolean", description: PARAM_DESCRIPTIONS.summary }, scope: { type: "string", enum: ["symbol"], description: PARAM_DESCRIPTIONS.grepScope }, scopeContext: integer(PARAM_DESCRIPTIONS.scopeContext, 0) }, ["pattern"]),
|
|
219
|
+
tool("def", "Find definitions", { ...path, ...lang, ...gitScopeSchema, ...noAnchors, name: string(PARAM_DESCRIPTIONS.name) }, ["name"]),
|
|
220
|
+
tool("refs", "Find references", { ...path, ...at, ...lang, ...gitScopeSchema, ...noAnchors, name: string(PARAM_DESCRIPTIONS.refsName) }, []),
|
|
221
|
+
tool("edit", "Hash-verified edit", { ...path, ...lang, ...gitScopeSchema, ...apply, edits: { type: "array", minItems: 1, items: editItemSchema, description: PARAM_DESCRIPTIONS.edits }, postEditVerify: { type: "boolean", description: PARAM_DESCRIPTIONS.postEditVerify } }, ["path", "edits"]),
|
|
222
|
+
tool("rename", "Rename symbol", { ...path, ...at, ...lang, ...gitScopeSchema, ...apply, to: string(PARAM_DESCRIPTIONS.to), workspace: { type: "boolean", description: PARAM_DESCRIPTIONS.workspace } }, ["path", "at", "to"]),
|
|
223
|
+
tool("write", "Write file", { ...path, content: string(PARAM_DESCRIPTIONS.content) }, ["path", "content"])
|
|
224
224
|
];
|
|
225
225
|
// ../readseek-api/src/policy.ts
|
|
226
226
|
var BUILTIN_FALLBACKS = {
|
|
227
|
-
read: "
|
|
228
|
-
edit: "
|
|
229
|
-
write: "
|
|
230
|
-
grep: "
|
|
231
|
-
apply_patch: "
|
|
227
|
+
read: "show",
|
|
228
|
+
edit: "edit",
|
|
229
|
+
write: "write",
|
|
230
|
+
grep: "grep",
|
|
231
|
+
apply_patch: "edit"
|
|
232
232
|
};
|
|
233
233
|
var PREFERENCE_LEADS = {
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
234
|
+
show: "Preferred for reading source with anchors.",
|
|
235
|
+
edit: "Preferred for editing existing text files with fresh LINE:HASH anchors.",
|
|
236
|
+
write: "Preferred for creating or replacing a complete text file; returns LINE:HASH anchors.",
|
|
237
|
+
grep: "Preferred native text/regex search; returns edit-ready source LINE:HASH anchors and document node-local locations.",
|
|
238
|
+
search: "Preferred for AST or structural code search with ast-grep-style patterns.",
|
|
239
|
+
def: "Preferred for go-to-definition or declaration lookup by name.",
|
|
240
|
+
refs: "Preferred for finding identifier-name matches or binding-specific references before `rename` or delete.",
|
|
241
|
+
rename: "Preferred for renaming a symbol with binding-aware, verified edits.",
|
|
242
|
+
assets: "Preferred for materializing selected document assets with usable cached paths."
|
|
243
243
|
};
|
|
244
244
|
var FALLBACK_TEMPLATES = {
|
|
245
|
-
read: "Prefer `
|
|
246
|
-
edit: "Prefer `
|
|
247
|
-
write: "Prefer `
|
|
248
|
-
grep: "Prefer `
|
|
249
|
-
apply_patch: "Prefer `
|
|
245
|
+
read: "Prefer `show` with fresh LINE:HASH anchors.",
|
|
246
|
+
edit: "Prefer `edit` with fresh LINE:HASH anchors.",
|
|
247
|
+
write: "Prefer `write` so the result returns LINE:HASH anchors.",
|
|
248
|
+
grep: "Prefer `grep` for native text/regex search with source anchors and document locations.",
|
|
249
|
+
apply_patch: "Prefer `edit` for existing text files."
|
|
250
250
|
};
|
|
251
251
|
function resolveToolName(name, aliases = {}) {
|
|
252
252
|
return aliases[name] ?? name;
|
|
@@ -262,21 +262,21 @@ function renderFallbackToolDescription(builtIn, aliases = {}) {
|
|
|
262
262
|
}
|
|
263
263
|
function renderToolRoutingPolicy(options = {}) {
|
|
264
264
|
const aliases = options.aliases ?? {};
|
|
265
|
-
const
|
|
266
|
-
const
|
|
267
|
-
const
|
|
268
|
-
const
|
|
269
|
-
const
|
|
270
|
-
const
|
|
271
|
-
const
|
|
272
|
-
const
|
|
273
|
-
const
|
|
265
|
+
const show = resolveToolName("show", aliases);
|
|
266
|
+
const edit = resolveToolName("edit", aliases);
|
|
267
|
+
const write = resolveToolName("write", aliases);
|
|
268
|
+
const grep = resolveToolName("grep", aliases);
|
|
269
|
+
const search = resolveToolName("search", aliases);
|
|
270
|
+
const def = resolveToolName("def", aliases);
|
|
271
|
+
const refs = resolveToolName("refs", aliases);
|
|
272
|
+
const rename = resolveToolName("rename", aliases);
|
|
273
|
+
const assets = resolveToolName("assets", aliases);
|
|
274
274
|
const title = options.title ?? "ReadSeek tool policy:";
|
|
275
275
|
const bullets = [];
|
|
276
276
|
if (options.preferOverBuiltIns) {
|
|
277
277
|
bullets.push("- Prefer ReadSeek tools over built-ins when they can do the job.");
|
|
278
278
|
}
|
|
279
|
-
bullets.push(`- Read with ${
|
|
279
|
+
bullets.push(`- Read with ${show} first; ${edit} needs fresh LINE:HASH anchors.`, `- Use ${grep} for text/regex, ${search} for AST patterns, ${def}/${refs} for targeted symbols, and shallow ${resolveToolName("tree", aliases)} structures only for file overviews.`, `- Use ${edit} for existing files, ${write} for whole-file creation or replacement, and ${rename} for symbol renames.`, `- Use ${resolveToolName("tree", aliases)} for document structure; run ${resolveToolName("syntax", aliases)} after source edits for a quick syntax check.`, `- Use ${assets} to materialize selected document assets and read their returned paths.`);
|
|
280
280
|
if (options.demoteBuiltIns && options.demoteBuiltIns.length > 0) {
|
|
281
281
|
bullets.push(`- Do not use built-in ${options.demoteBuiltIns.join(", ")} when a ReadSeek tool can do the job.`);
|
|
282
282
|
}
|
|
@@ -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"],
|
|
@@ -420,50 +473,111 @@ var SELECTOR_KINDS = ["line", "hash", "symbol", "page", "node", "asset"];
|
|
|
420
473
|
function isSelectorKind(value) {
|
|
421
474
|
return SELECTOR_KINDS.includes(value);
|
|
422
475
|
}
|
|
476
|
+
function parsePositiveSelectorInt(raw, name) {
|
|
477
|
+
if (!/^-?\d+$/.test(raw))
|
|
478
|
+
throw new Error(`invalid ${name} \`${raw}\``);
|
|
479
|
+
const parsed = Number(raw);
|
|
480
|
+
if (!Number.isSafeInteger(parsed))
|
|
481
|
+
throw new Error(`invalid ${name} \`${raw}\``);
|
|
482
|
+
if (parsed < 1)
|
|
483
|
+
throw new Error(`${name} must be greater than zero`);
|
|
484
|
+
return parsed;
|
|
485
|
+
}
|
|
486
|
+
function parseLineSelector(value, explicitKind) {
|
|
487
|
+
const parts = value.split(":");
|
|
488
|
+
if (parts.length > 2)
|
|
489
|
+
throw new Error("selector has too many components");
|
|
490
|
+
const head = parts[0];
|
|
491
|
+
if (parts.length === 2) {
|
|
492
|
+
if (head.slice(1).includes("-"))
|
|
493
|
+
throw new Error("line range cannot include a column");
|
|
494
|
+
const line = parsePositiveSelectorInt(head, "line");
|
|
495
|
+
const column = parsePositiveSelectorInt(parts[1], "column");
|
|
496
|
+
return { kind: "line", value: String(line), column, explicitKind };
|
|
497
|
+
}
|
|
498
|
+
const dash = head.indexOf("-", 1);
|
|
499
|
+
if (dash > 0) {
|
|
500
|
+
const start = parsePositiveSelectorInt(head.slice(0, dash), "line");
|
|
501
|
+
const endLine = parsePositiveSelectorInt(head.slice(dash + 1), "end line");
|
|
502
|
+
if (endLine < start)
|
|
503
|
+
throw new Error(`end line ${endLine} precedes start line ${start}`);
|
|
504
|
+
return { kind: "line", value: String(start), endLine, explicitKind };
|
|
505
|
+
}
|
|
506
|
+
const line = parsePositiveSelectorInt(head, "line");
|
|
507
|
+
return { kind: "line", value: String(line), explicitKind };
|
|
508
|
+
}
|
|
509
|
+
function parseHashSelector(value, explicitKind) {
|
|
510
|
+
const parts = value.split(":");
|
|
511
|
+
if (parts.length > 2)
|
|
512
|
+
throw new Error("selector has too many components");
|
|
513
|
+
const head = parts[0];
|
|
514
|
+
if (!/^[0-9a-fA-F]{6}$/.test(head))
|
|
515
|
+
throw new Error(`invalid line hash \`${head}\``);
|
|
516
|
+
if (parts.length === 2) {
|
|
517
|
+
const column = parsePositiveSelectorInt(parts[1], "column");
|
|
518
|
+
return { kind: "hash", value: head, column, explicitKind };
|
|
519
|
+
}
|
|
520
|
+
return { kind: "hash", value: head, explicitKind };
|
|
521
|
+
}
|
|
522
|
+
function isDocumentSelector(parsed) {
|
|
523
|
+
return ["page", "node", "asset"].includes(parsed.kind) || parsed.kind === "line" && !parsed.explicitKind && parsed.column === undefined && parsed.endLine === undefined;
|
|
524
|
+
}
|
|
525
|
+
function isBindingSelector(parsed) {
|
|
526
|
+
if (parsed.endLine !== undefined)
|
|
527
|
+
return false;
|
|
528
|
+
if (parsed.kind === "line" || parsed.kind === "hash")
|
|
529
|
+
return true;
|
|
530
|
+
return parsed.kind === "symbol" && !parsed.explicitKind && /^[0-9a-fA-F]{6}$/.test(parsed.value);
|
|
531
|
+
}
|
|
423
532
|
function parseSelector(text) {
|
|
533
|
+
if (text.length === 0)
|
|
534
|
+
throw new Error("selector must not be empty");
|
|
424
535
|
const separator = text.indexOf(":");
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
536
|
+
const hasSingleColon = separator >= 0 && text[separator + 1] !== ":";
|
|
537
|
+
if (hasSingleColon) {
|
|
538
|
+
const kind = text.slice(0, separator);
|
|
539
|
+
const value = text.slice(separator + 1);
|
|
540
|
+
if (isSelectorKind(kind)) {
|
|
541
|
+
if (value.length === 0)
|
|
542
|
+
throw new Error(`${kind} selector must not be empty`);
|
|
543
|
+
if (kind === "line")
|
|
544
|
+
return parseLineSelector(value, true);
|
|
545
|
+
if (kind === "hash")
|
|
546
|
+
return parseHashSelector(value, true);
|
|
547
|
+
if (kind === "page") {
|
|
548
|
+
if (value.includes(":"))
|
|
549
|
+
throw new Error("page selector has too many components");
|
|
550
|
+
parsePositiveSelectorInt(value, "page");
|
|
551
|
+
}
|
|
552
|
+
return { kind, value, explicitKind: true };
|
|
553
|
+
}
|
|
554
|
+
if (/^\d+$/.test(kind))
|
|
555
|
+
return parseLineSelector(text, false);
|
|
556
|
+
if (/^[0-9a-fA-F]{6}$/.test(kind) && /^\d+$/.test(value))
|
|
557
|
+
return parseHashSelector(text, false);
|
|
433
558
|
throw new Error(`unknown selector kind \`${kind}\`; expected line, hash, symbol, page, node, or asset`);
|
|
434
559
|
}
|
|
435
|
-
if (
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
throw new Error("selector has too many components");
|
|
439
|
-
if (parts.length === 2) {
|
|
440
|
-
const column = Number(parts[1]);
|
|
441
|
-
if (!Number.isSafeInteger(column))
|
|
442
|
-
throw new Error(`invalid column \`${parts[1]}\``);
|
|
443
|
-
if (column < 1)
|
|
444
|
-
throw new Error("column must be greater than zero");
|
|
445
|
-
return { kind, value: parts[0], column };
|
|
560
|
+
if (/^[0-9-]/.test(text)) {
|
|
561
|
+
if (/^[0-9a-fA-F]{6}$/.test(text) && /[a-fA-F]/.test(text)) {
|
|
562
|
+
return parseHashSelector(text, false);
|
|
446
563
|
}
|
|
564
|
+
return parseLineSelector(text, false);
|
|
447
565
|
}
|
|
448
|
-
if (
|
|
449
|
-
|
|
450
|
-
throw new Error("page selector has too many components");
|
|
451
|
-
const page = Number(value);
|
|
452
|
-
if (!Number.isSafeInteger(page))
|
|
453
|
-
throw new Error(`invalid page \`${value}\``);
|
|
454
|
-
if (page < 1)
|
|
455
|
-
throw new Error("page must be greater than zero");
|
|
566
|
+
if (/^[0-9a-fA-F]{6}$/.test(text) && /\d/.test(text)) {
|
|
567
|
+
return parseHashSelector(text, false);
|
|
456
568
|
}
|
|
457
|
-
|
|
569
|
+
if (/\s/.test(text))
|
|
570
|
+
throw new Error(`invalid selector \`${text}\``);
|
|
571
|
+
return { kind: "symbol", value: text, explicitKind: false };
|
|
458
572
|
}
|
|
459
|
-
function start(
|
|
573
|
+
function start(command, input) {
|
|
460
574
|
if (!input.path)
|
|
461
|
-
throw new Error(`${
|
|
575
|
+
throw new Error(`${command} requires a path`);
|
|
462
576
|
if (input.stdinName !== undefined && input.path !== "-")
|
|
463
577
|
throw new Error("stdinName requires '-' as the target");
|
|
464
578
|
const args = [];
|
|
465
579
|
pushOptionalString(args, "--readseek-dir", input.readseekDir);
|
|
466
|
-
args.push(
|
|
580
|
+
args.push(command, input.path);
|
|
467
581
|
pushOptionalString(args, "--stdin-name", input.stdinName);
|
|
468
582
|
return args;
|
|
469
583
|
}
|
|
@@ -489,12 +603,12 @@ function anchors(args, input) {
|
|
|
489
603
|
if (input.noAnchors)
|
|
490
604
|
args.push("--no-anchors");
|
|
491
605
|
}
|
|
492
|
-
function
|
|
606
|
+
function buildShowArgs(input) {
|
|
493
607
|
if (input.end !== undefined)
|
|
494
608
|
validatePositiveInteger(input.end, "end");
|
|
495
609
|
if (input.end !== undefined && input.limit !== undefined)
|
|
496
610
|
throw new Error("cannot combine end with limit");
|
|
497
|
-
const args = start("show",
|
|
611
|
+
const args = start("show", input);
|
|
498
612
|
at2(args, input.at);
|
|
499
613
|
pushOptionalNumber(args, "--end", input.end);
|
|
500
614
|
limit2(args, input.limit);
|
|
@@ -502,10 +616,10 @@ function buildShowContentArgs(input) {
|
|
|
502
616
|
anchors(args, input);
|
|
503
617
|
return finish(args);
|
|
504
618
|
}
|
|
505
|
-
function
|
|
619
|
+
function buildTreeArgs(input) {
|
|
506
620
|
if (input.depth !== undefined && (!Number.isSafeInteger(input.depth) || input.depth < 0))
|
|
507
621
|
throw new Error("depth must be a non-negative integer");
|
|
508
|
-
const args = start("
|
|
622
|
+
const args = start("tree", input);
|
|
509
623
|
at2(args, input.at);
|
|
510
624
|
pushOptionalNumber(args, "--depth", input.depth);
|
|
511
625
|
pushOptionalString(args, "--kind", input.kind);
|
|
@@ -514,35 +628,35 @@ function buildShowStructureArgs(input) {
|
|
|
514
628
|
args.push("--outline");
|
|
515
629
|
return finish(args);
|
|
516
630
|
}
|
|
517
|
-
function
|
|
518
|
-
const args = start("
|
|
631
|
+
function buildMetaArgs(input) {
|
|
632
|
+
const args = start("meta", input);
|
|
519
633
|
language(args, input);
|
|
520
634
|
return finish(args);
|
|
521
635
|
}
|
|
522
|
-
function
|
|
523
|
-
if (input.at !== undefined && !
|
|
636
|
+
function buildAssetsArgs(input) {
|
|
637
|
+
if (input.at !== undefined && !isDocumentSelector(parseSelector(input.at)))
|
|
524
638
|
throw new Error("assets requires a page, node, or asset selector");
|
|
525
|
-
const args = start("
|
|
639
|
+
const args = start("assets", input);
|
|
526
640
|
at2(args, input.at);
|
|
527
641
|
return finish(args);
|
|
528
642
|
}
|
|
529
|
-
function
|
|
530
|
-
const args = start("
|
|
643
|
+
function buildSyntaxArgs(input) {
|
|
644
|
+
const args = start("syntax", input);
|
|
531
645
|
language(args, input);
|
|
532
646
|
return finish(args);
|
|
533
647
|
}
|
|
534
|
-
function visionArgs(
|
|
648
|
+
function visionArgs(command, input) {
|
|
535
649
|
if (input.level !== undefined && !VISION_LEVELS.includes(input.level))
|
|
536
650
|
throw new Error("invalid vision level");
|
|
537
|
-
if (input.at !== undefined && !
|
|
651
|
+
if (input.at !== undefined && !isDocumentSelector(parseSelector(input.at)))
|
|
538
652
|
throw new Error("vision requires a document selector");
|
|
539
|
-
const args = start(
|
|
653
|
+
const args = start(command, input);
|
|
540
654
|
at2(args, input.at);
|
|
541
655
|
pushOptionalString(args, "--level", input.level);
|
|
542
656
|
return args;
|
|
543
657
|
}
|
|
544
|
-
function
|
|
545
|
-
const args = visionArgs("
|
|
658
|
+
function buildCaptionArgs(input) {
|
|
659
|
+
const args = visionArgs("caption", input);
|
|
546
660
|
if (input.include !== undefined) {
|
|
547
661
|
if (input.include.length === 0 || input.include.some((task) => task !== "text" && task !== "objects"))
|
|
548
662
|
throw new Error("include requires text or objects");
|
|
@@ -550,13 +664,13 @@ function buildGenerateCaptionArgs(input) {
|
|
|
550
664
|
}
|
|
551
665
|
return finish(args);
|
|
552
666
|
}
|
|
553
|
-
function
|
|
554
|
-
return finish(visionArgs("
|
|
667
|
+
function buildObjectsArgs(input) {
|
|
668
|
+
return finish(visionArgs("objects", input));
|
|
555
669
|
}
|
|
556
|
-
function
|
|
557
|
-
return finish(visionArgs("
|
|
670
|
+
function buildOcrArgs(input) {
|
|
671
|
+
return finish(visionArgs("ocr", input));
|
|
558
672
|
}
|
|
559
|
-
function
|
|
673
|
+
function buildSearchArgs(input) {
|
|
560
674
|
const hasPattern = Boolean(input.pattern);
|
|
561
675
|
const hasRule = Boolean(input.rule);
|
|
562
676
|
const hasContext = Boolean(input.context);
|
|
@@ -564,7 +678,7 @@ function buildFindNodesArgs(input) {
|
|
|
564
678
|
if (hasPattern || hasContext || input.selector)
|
|
565
679
|
throw new Error("rule cannot be combined with pattern, context, or selector");
|
|
566
680
|
} else if (!hasPattern && !hasContext) {
|
|
567
|
-
throw new Error("
|
|
681
|
+
throw new Error("search requires pattern, context, or rule");
|
|
568
682
|
}
|
|
569
683
|
if (hasContext && !input.selector) {
|
|
570
684
|
throw new Error("context requires selector");
|
|
@@ -572,7 +686,7 @@ function buildFindNodesArgs(input) {
|
|
|
572
686
|
if (hasPattern && hasContext) {
|
|
573
687
|
throw new Error("pattern and context cannot be combined");
|
|
574
688
|
}
|
|
575
|
-
const args = start("
|
|
689
|
+
const args = start("search", input);
|
|
576
690
|
pushOptionalString(args, "--pattern", input.pattern);
|
|
577
691
|
pushOptionalString(args, "--context", input.context);
|
|
578
692
|
pushOptionalString(args, "--selector", input.selector);
|
|
@@ -583,28 +697,46 @@ function buildFindNodesArgs(input) {
|
|
|
583
697
|
anchors(args, input);
|
|
584
698
|
return finish(args);
|
|
585
699
|
}
|
|
586
|
-
function
|
|
587
|
-
const
|
|
588
|
-
|
|
589
|
-
|
|
700
|
+
function buildGrepArgs(input) {
|
|
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);
|
|
590
720
|
appendGitScopeArgs(args, input);
|
|
591
|
-
|
|
721
|
+
if (input.path !== undefined && needsPathTerminator)
|
|
722
|
+
args.push("--", input.path);
|
|
723
|
+
return args;
|
|
592
724
|
}
|
|
593
|
-
function
|
|
594
|
-
const args = start("
|
|
725
|
+
function buildDefArgs(input) {
|
|
726
|
+
const args = start("def", input);
|
|
595
727
|
args.push("--name", input.name);
|
|
596
728
|
language(args, input);
|
|
597
729
|
appendGitScopeArgs(args, input);
|
|
598
730
|
anchors(args, input);
|
|
599
731
|
return finish(args);
|
|
600
732
|
}
|
|
601
|
-
function
|
|
733
|
+
function buildRefsArgs(input) {
|
|
602
734
|
const validated = validateReferencesSelection(input);
|
|
603
735
|
if (!validated.ok)
|
|
604
736
|
throw new Error(validated.error);
|
|
605
|
-
if (input.at !== undefined && !
|
|
737
|
+
if (input.at !== undefined && !isBindingSelector(parseSelector(input.at)))
|
|
606
738
|
throw new Error("references requires a line or hash selector");
|
|
607
|
-
const args = start("
|
|
739
|
+
const args = start("refs", input);
|
|
608
740
|
pushOptionalString(args, "--name", input.name);
|
|
609
741
|
at2(args, input.at);
|
|
610
742
|
language(args, input);
|
|
@@ -612,14 +744,14 @@ function buildFindReferencesArgs(input) {
|
|
|
612
744
|
anchors(args, input);
|
|
613
745
|
return finish(args);
|
|
614
746
|
}
|
|
615
|
-
function
|
|
616
|
-
const args = start("check",
|
|
747
|
+
function buildCheckArgs(input) {
|
|
748
|
+
const args = start("check", input);
|
|
617
749
|
args.push("--rules", input.rules);
|
|
618
750
|
appendGitScopeArgs(args, input);
|
|
619
751
|
return finish(args);
|
|
620
752
|
}
|
|
621
|
-
function
|
|
622
|
-
const args = start("edit",
|
|
753
|
+
function buildEditArgs(input) {
|
|
754
|
+
const args = start("edit", input);
|
|
623
755
|
appendApplyAndPlanHash(args, input);
|
|
624
756
|
language(args, input);
|
|
625
757
|
appendGitScopeArgs(args, input);
|
|
@@ -627,10 +759,10 @@ function buildEditTextArgs(input) {
|
|
|
627
759
|
args.push("--edits", "-");
|
|
628
760
|
return finish(args);
|
|
629
761
|
}
|
|
630
|
-
function
|
|
631
|
-
if (!
|
|
762
|
+
function buildRenameArgs(input) {
|
|
763
|
+
if (!isBindingSelector(parseSelector(input.at)))
|
|
632
764
|
throw new Error("rename requires a line or hash selector");
|
|
633
|
-
const args = start("rename",
|
|
765
|
+
const args = start("rename", input);
|
|
634
766
|
at2(args, input.at);
|
|
635
767
|
args.push("--to", input.to);
|
|
636
768
|
if (input.workspace !== undefined)
|
|
@@ -643,6 +775,17 @@ function buildRenameSymbolArgs(input) {
|
|
|
643
775
|
// ../readseek-api/src/client-output.ts
|
|
644
776
|
import { Type } from "@sinclair/typebox";
|
|
645
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
|
+
}
|
|
646
789
|
var Int = Type.Integer({ minimum: Number.MIN_SAFE_INTEGER, maximum: Number.MAX_SAFE_INTEGER });
|
|
647
790
|
var Str = Type.String();
|
|
648
791
|
var Bool = Type.Boolean();
|
|
@@ -877,6 +1020,82 @@ var DefEnvelope = Type.Object({
|
|
|
877
1020
|
qualified_name: OptStr
|
|
878
1021
|
}))
|
|
879
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
|
+
}
|
|
880
1099
|
var Bbox = Type.Object({ x: Type.Number(), y: Type.Number(), width: Type.Number(), height: Type.Number() });
|
|
881
1100
|
var Position = { page: OptInt, destination: OptStr, bbox: Type.Optional(Bbox) };
|
|
882
1101
|
var Provenance = { inferred: Type.Optional(Bool), model: OptStr, level: OptStr, task: OptStr };
|
|
@@ -933,25 +1152,41 @@ var DocumentOutput = Type.Object({
|
|
|
933
1152
|
assets: Type.Array(DocumentAsset, { default: [] }),
|
|
934
1153
|
vision: Type.Array(DocumentVision, { default: [] })
|
|
935
1154
|
});
|
|
936
|
-
var
|
|
937
|
-
|
|
938
|
-
|
|
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}$" }),
|
|
939
1161
|
text: Str,
|
|
940
|
-
|
|
941
|
-
end_line: Int,
|
|
942
|
-
start_byte: Int,
|
|
943
|
-
end_byte: Int,
|
|
944
|
-
hashlines: Hashlines
|
|
1162
|
+
kind: Type.Union([Type.Literal("match"), Type.Literal("context")])
|
|
945
1163
|
});
|
|
946
|
-
var
|
|
947
|
-
var
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
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,
|
|
952
1185
|
truncated: Bool,
|
|
953
|
-
|
|
954
|
-
|
|
1186
|
+
count_unit: Type.Literal("lines"),
|
|
1187
|
+
scope: Type.Optional(Type.Literal("symbol")),
|
|
1188
|
+
results: Type.Array(GrepSourceResultSchema),
|
|
1189
|
+
documents: Type.Array(GrepDocumentResultSchema)
|
|
955
1190
|
});
|
|
956
1191
|
var RuleFinding = Type.Object({
|
|
957
1192
|
name: Str,
|
|
@@ -972,7 +1207,72 @@ var RulesOutput = Type.Object({
|
|
|
972
1207
|
violation_count: Int,
|
|
973
1208
|
diagnostics: Type.Array(RuleFinding)
|
|
974
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
|
+
}
|
|
975
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
|
+
}
|
|
976
1276
|
|
|
977
1277
|
// ../readseek-api/src/edit-plan.ts
|
|
978
1278
|
import { Document, Node, format } from "@bgotink/kdl";
|
|
@@ -1014,9 +1314,84 @@ function serializeEditPlan(edits) {
|
|
|
1014
1314
|
throw new Error(validation.error);
|
|
1015
1315
|
return format(new Document([node("plan", {}, validation.edits.map(editNode))]));
|
|
1016
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
|
+
}
|
|
1017
1392
|
// ../readseek-api/src/show-presentation.ts
|
|
1018
1393
|
function isShowTool(name) {
|
|
1019
|
-
return name === "
|
|
1394
|
+
return name === "show" || name === "tree" || name === "meta" || name === "assets";
|
|
1020
1395
|
}
|
|
1021
1396
|
function record(value) {
|
|
1022
1397
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
@@ -1131,7 +1506,7 @@ function presentShowResult(tool, value, options = {}) {
|
|
|
1131
1506
|
const path = inline(options.path || output.source || output.file || content.file || structure.file || metadata.file || cursor.file) || "(unknown path)";
|
|
1132
1507
|
const summary = [];
|
|
1133
1508
|
const result = { summary: "", body: "", notices: [], metadata: {} };
|
|
1134
|
-
if (tool === "
|
|
1509
|
+
if (tool === "meta") {
|
|
1135
1510
|
const data = Object.keys(metadata).length > 0 ? metadata : output;
|
|
1136
1511
|
if (text(data.format || data.language || data.type))
|
|
1137
1512
|
summary.push(inline(data.format || data.language || data.type));
|
|
@@ -1140,7 +1515,7 @@ function presentShowResult(tool, value, options = {}) {
|
|
|
1140
1515
|
summary.push(plural(result.metadata.pages, "page"));
|
|
1141
1516
|
}
|
|
1142
1517
|
result.body = fields(data) || "No metadata available.";
|
|
1143
|
-
} else if (tool === "
|
|
1518
|
+
} else if (tool === "assets") {
|
|
1144
1519
|
const assets = records(output.assets);
|
|
1145
1520
|
result.metadata.assets = assets.length;
|
|
1146
1521
|
summary.push(plural(assets.length, "asset"));
|
|
@@ -1154,7 +1529,7 @@ function presentShowResult(tool, value, options = {}) {
|
|
|
1154
1529
|
summary.push(plural(nodes.length, "node"));
|
|
1155
1530
|
if (result.metadata.pages !== undefined)
|
|
1156
1531
|
summary.push(plural(result.metadata.pages, "page"));
|
|
1157
|
-
const sections = [text(output.title) ? safe(text(output.title)) : "", documentNodes(nodes, tool === "
|
|
1532
|
+
const sections = [text(output.title) ? safe(text(output.title)) : "", documentNodes(nodes, tool === "show")];
|
|
1158
1533
|
if (assets.length > 0) {
|
|
1159
1534
|
result.metadata.assets = assets.length;
|
|
1160
1535
|
sections.push(`Assets
|
|
@@ -1163,7 +1538,7 @@ ${assetList(assets)}`);
|
|
|
1163
1538
|
result.body = sections.filter(Boolean).join(`
|
|
1164
1539
|
|
|
1165
1540
|
`) || "No content in this selection.";
|
|
1166
|
-
} else if (tool === "
|
|
1541
|
+
} else if (tool === "tree") {
|
|
1167
1542
|
const symbols = records(structure.symbols);
|
|
1168
1543
|
result.metadata.symbols = symbols.length;
|
|
1169
1544
|
summary.push(plural(symbols.length, "symbol"));
|
|
@@ -1217,7 +1592,7 @@ ${assetList(assets)}`);
|
|
|
1217
1592
|
const description = Object.keys(content).length > 0 ? content : output;
|
|
1218
1593
|
result.body = fields(description) || "No content in this selection.";
|
|
1219
1594
|
}
|
|
1220
|
-
if (tool === "
|
|
1595
|
+
if (tool === "tree")
|
|
1221
1596
|
result.notices.push("Structure is limited to the requested depth, kind, and outline. Use a deeper depth or focus with at to inspect further.");
|
|
1222
1597
|
if (text(output.warning))
|
|
1223
1598
|
result.notices.push(safe(text(output.warning)));
|
|
@@ -1229,6 +1604,31 @@ function formatShowResult(presentation) {
|
|
|
1229
1604
|
|
|
1230
1605
|
`);
|
|
1231
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
|
+
}
|
|
1232
1632
|
// ../readseek-api/src/platform.ts
|
|
1233
1633
|
import { accessSync, constants as fsConstants, existsSync, statSync } from "node:fs";
|
|
1234
1634
|
import { createRequire } from "node:module";
|
|
@@ -1330,7 +1730,7 @@ function cleanReadSeekErrorText(stderr) {
|
|
|
1330
1730
|
return stderr.replace(READSEEK_USAGE_HINT, "").replace(/^error:\s*/i, "").replace(/\s*\n\s*/g, " ").trim();
|
|
1331
1731
|
}
|
|
1332
1732
|
// ../readseek-api/src/kdl.ts
|
|
1333
|
-
import { parse } from "@bgotink/kdl";
|
|
1733
|
+
import { parse as parse2 } from "@bgotink/kdl";
|
|
1334
1734
|
function convertNode(node) {
|
|
1335
1735
|
if (node.getTag() !== null)
|
|
1336
1736
|
throw new Error("KDL node annotations are not supported");
|
|
@@ -1358,7 +1758,7 @@ function convertNode(node) {
|
|
|
1358
1758
|
};
|
|
1359
1759
|
}
|
|
1360
1760
|
function parseKdlDocument(text) {
|
|
1361
|
-
const document =
|
|
1761
|
+
const document = parse2(text);
|
|
1362
1762
|
if (document.nodes.length !== 1)
|
|
1363
1763
|
throw new Error("KDL document must contain exactly one root node");
|
|
1364
1764
|
return convertNode(document.nodes[0]);
|
|
@@ -1492,6 +1892,46 @@ function search(root) {
|
|
|
1492
1892
|
}))
|
|
1493
1893
|
};
|
|
1494
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
|
+
}
|
|
1495
1935
|
function references(root) {
|
|
1496
1936
|
const parsed = children(root, "reference").map((reference) => {
|
|
1497
1937
|
const sourceLine = child(reference, "line");
|
|
@@ -1607,7 +2047,7 @@ function parseReadSeekKdl(text) {
|
|
|
1607
2047
|
case "document":
|
|
1608
2048
|
return root.properties.has("format") ? pdfDocument(root) : sourceDocument(root);
|
|
1609
2049
|
case "search":
|
|
1610
|
-
return search(root);
|
|
2050
|
+
return ["mode", "ignore_case", "summary", "count_unit"].some((key) => root.properties.has(key)) ? grep(root) : search(root);
|
|
1611
2051
|
case "check":
|
|
1612
2052
|
return diagnostics(root);
|
|
1613
2053
|
case "refs":
|
|
@@ -1672,7 +2112,7 @@ function acceptsReadSeekExitCode(args, exitCode) {
|
|
|
1672
2112
|
index++;
|
|
1673
2113
|
continue;
|
|
1674
2114
|
}
|
|
1675
|
-
return arg === "
|
|
2115
|
+
return arg === "grep" || arg === "search" || arg === "def" || arg === "refs" || arg === "syntax" || arg === "check";
|
|
1676
2116
|
}
|
|
1677
2117
|
return false;
|
|
1678
2118
|
}
|
|
@@ -1806,8 +2246,8 @@ function collectAnchoredFiles(value, files) {
|
|
|
1806
2246
|
if (!value || typeof value !== "object")
|
|
1807
2247
|
return;
|
|
1808
2248
|
const output = value;
|
|
1809
|
-
const
|
|
1810
|
-
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")))
|
|
1811
2251
|
files.add(output.file);
|
|
1812
2252
|
for (const item of Object.values(output))
|
|
1813
2253
|
collectAnchoredFiles(item, files);
|
|
@@ -1873,12 +2313,12 @@ function resolveImagePolicy(options) {
|
|
|
1873
2313
|
throw new Error('opencode-readseek imageMode must be "on", "auto", or "off"');
|
|
1874
2314
|
}
|
|
1875
2315
|
function contentArgs(filePath, options = {}) {
|
|
1876
|
-
return
|
|
2316
|
+
return buildShowArgs({ path: filePath, ...options, at: options.at ?? (options.end === undefined ? undefined : "line:1") });
|
|
1877
2317
|
}
|
|
1878
2318
|
function requireContent(envelope) {
|
|
1879
2319
|
const value = record2(envelope).content;
|
|
1880
2320
|
if (value === undefined || value === null || typeof value !== "object") {
|
|
1881
|
-
throw new Error("show
|
|
2321
|
+
throw new Error("show returned no source content");
|
|
1882
2322
|
}
|
|
1883
2323
|
return value;
|
|
1884
2324
|
}
|
|
@@ -1920,6 +2360,12 @@ async function runReadSeek(context, args, options = {}) {
|
|
|
1920
2360
|
timeoutMs: options.timeoutMs
|
|
1921
2361
|
}));
|
|
1922
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
|
+
}
|
|
1923
2369
|
async function runContent(context, args, options = {}) {
|
|
1924
2370
|
return requireContent(await runReadSeek(context, args, options));
|
|
1925
2371
|
}
|
|
@@ -1954,7 +2400,7 @@ async function ensureReadSeekCache(context) {
|
|
|
1954
2400
|
if (error.code !== "ENOENT")
|
|
1955
2401
|
throw error;
|
|
1956
2402
|
}
|
|
1957
|
-
readSeekCacheInit ??= runReadSeekRaw(context, ["--readseek-dir", cacheDir, "
|
|
2403
|
+
readSeekCacheInit ??= runReadSeekRaw(context, ["--readseek-dir", cacheDir, "init"], { cancelable: false }).then(() => cacheDir).finally(() => {
|
|
1958
2404
|
readSeekCacheInit = undefined;
|
|
1959
2405
|
});
|
|
1960
2406
|
return readSeekCacheInit;
|
|
@@ -2043,7 +2489,6 @@ ${[...paths].sort().map((filePath) => `- ${filePath}`).join(`
|
|
|
2043
2489
|
}
|
|
2044
2490
|
|
|
2045
2491
|
// src/tools.ts
|
|
2046
|
-
import { readFile as readFile2, stat as stat3 } from "node:fs/promises";
|
|
2047
2492
|
import { tool as tool3 } from "@opencode-ai/plugin";
|
|
2048
2493
|
|
|
2049
2494
|
// src/constants.ts
|
|
@@ -2372,31 +2817,31 @@ import { pathToFileURL } from "node:url";
|
|
|
2372
2817
|
import { tool as tool2 } from "@opencode-ai/plugin";
|
|
2373
2818
|
function initialTitle(kind, args) {
|
|
2374
2819
|
switch (kind) {
|
|
2375
|
-
case "
|
|
2376
|
-
case "
|
|
2377
|
-
case "
|
|
2378
|
-
case "
|
|
2379
|
-
case "
|
|
2380
|
-
case "
|
|
2381
|
-
case "
|
|
2382
|
-
return `${kind
|
|
2383
|
-
case "
|
|
2820
|
+
case "tree":
|
|
2821
|
+
case "meta":
|
|
2822
|
+
case "syntax":
|
|
2823
|
+
case "check":
|
|
2824
|
+
case "caption":
|
|
2825
|
+
case "objects":
|
|
2826
|
+
case "ocr":
|
|
2827
|
+
return `${kind} ${args.path}`;
|
|
2828
|
+
case "show":
|
|
2384
2829
|
return `Show content ${args.path}`;
|
|
2385
|
-
case "
|
|
2830
|
+
case "assets":
|
|
2386
2831
|
return `Show assets ${args.path}`;
|
|
2387
|
-
case "
|
|
2832
|
+
case "search":
|
|
2388
2833
|
return `Find nodes ${args.pattern ?? args.context ?? args.rule}`;
|
|
2389
|
-
case "
|
|
2390
|
-
return `Find text ${args.pattern
|
|
2391
|
-
case "
|
|
2834
|
+
case "grep":
|
|
2835
|
+
return `Find text ${args.pattern}`;
|
|
2836
|
+
case "def":
|
|
2392
2837
|
return `Find definition ${args.name}`;
|
|
2393
|
-
case "
|
|
2838
|
+
case "refs":
|
|
2394
2839
|
return `Find references to ${args.name ?? args.at}`;
|
|
2395
|
-
case "
|
|
2840
|
+
case "rename":
|
|
2396
2841
|
return `Rename ${args.path}:${args.at} to ${args.to}`;
|
|
2397
|
-
case "
|
|
2842
|
+
case "edit":
|
|
2398
2843
|
return `Edit ${args.path}`;
|
|
2399
|
-
case "
|
|
2844
|
+
case "write":
|
|
2400
2845
|
return `Write ${args.path}`;
|
|
2401
2846
|
}
|
|
2402
2847
|
}
|
|
@@ -2407,34 +2852,31 @@ function resultPresentation(kind, args, value) {
|
|
|
2407
2852
|
return { title: presentation.summary, metadata: presentation.metadata };
|
|
2408
2853
|
}
|
|
2409
2854
|
switch (kind) {
|
|
2410
|
-
case "
|
|
2855
|
+
case "syntax": {
|
|
2411
2856
|
const diagnostics = record2(output.diagnostics ?? output);
|
|
2412
2857
|
const errors = typeof diagnostics.error_count === "number" ? diagnostics.error_count : 0;
|
|
2413
2858
|
const missing = typeof diagnostics.missing_count === "number" ? diagnostics.missing_count : 0;
|
|
2414
2859
|
return { title: `Syntax ${args.path} (${errors} errors, ${missing} missing)`, metadata: { error_count: errors, missing_count: missing } };
|
|
2415
2860
|
}
|
|
2416
|
-
case "
|
|
2861
|
+
case "search": {
|
|
2417
2862
|
const results = items(output.results);
|
|
2418
2863
|
const matches = results.reduce((total, result) => total + items(record2(result).matches).length, 0);
|
|
2419
2864
|
return { title: `Found ${matches} matches`, metadata: { results: results.length, matches } };
|
|
2420
2865
|
}
|
|
2421
|
-
case "
|
|
2422
|
-
const results = items(output.results);
|
|
2423
|
-
const matches =
|
|
2424
|
-
|
|
2425
|
-
return total + (typeof entry.count === "number" ? entry.count : items(entry.matches).length);
|
|
2426
|
-
}, 0);
|
|
2427
|
-
return { title: `Found ${matches} matches`, metadata: { results: results.length, matches } };
|
|
2866
|
+
case "grep": {
|
|
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 } };
|
|
2428
2870
|
}
|
|
2429
|
-
case "
|
|
2871
|
+
case "def": {
|
|
2430
2872
|
const locations = items(output.locations).length;
|
|
2431
2873
|
return { title: `Found ${locations} definitions`, metadata: { locations } };
|
|
2432
2874
|
}
|
|
2433
|
-
case "
|
|
2875
|
+
case "refs": {
|
|
2434
2876
|
const references = items(output.references).length;
|
|
2435
2877
|
return { title: `Found ${references} references`, metadata: { references } };
|
|
2436
2878
|
}
|
|
2437
|
-
case "
|
|
2879
|
+
case "rename": {
|
|
2438
2880
|
const oldName = output.old_name;
|
|
2439
2881
|
const newName = output.new_name;
|
|
2440
2882
|
const otherOutputs = items(output.others).map(record2);
|
|
@@ -2444,10 +2886,10 @@ function resultPresentation(kind, args, value) {
|
|
|
2444
2886
|
const title = typeof oldName === "string" && typeof newName === "string" ? `${output.applied === true ? "Renamed" : "Plan"} ${oldName} -> ${newName}` : initialTitle(kind, args);
|
|
2445
2887
|
return { title, metadata: { edits, conflicts, others } };
|
|
2446
2888
|
}
|
|
2447
|
-
case "
|
|
2448
|
-
case "
|
|
2889
|
+
case "edit":
|
|
2890
|
+
case "write": {
|
|
2449
2891
|
const preview = typeof output.preview_error === "string" ? " (preview unavailable)" : "";
|
|
2450
|
-
return { title: `${kind === "
|
|
2892
|
+
return { title: `${kind === "edit" ? "Edited" : "Wrote"} ${args.path}${preview}`, metadata: {} };
|
|
2451
2893
|
}
|
|
2452
2894
|
}
|
|
2453
2895
|
return { title: initialTitle(kind, args), metadata: {} };
|
|
@@ -2482,8 +2924,8 @@ function readseekTool(description, args, kind, execute, imagePolicy) {
|
|
|
2482
2924
|
return {
|
|
2483
2925
|
...attachments.length > 0 ? { attachments } : {},
|
|
2484
2926
|
title: presentation.title,
|
|
2485
|
-
output: isShowTool(kind) ? formatShowResult(presentShowResult(kind, displayResult, args)) : render(displayResult),
|
|
2486
|
-
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 } : {} }
|
|
2487
2929
|
};
|
|
2488
2930
|
}
|
|
2489
2931
|
});
|
|
@@ -2493,107 +2935,38 @@ function readseekTool(description, args, kind, execute, imagePolicy) {
|
|
|
2493
2935
|
function createReadSeekTools(imagePolicy) {
|
|
2494
2936
|
return {
|
|
2495
2937
|
...createAccessTools(imagePolicy),
|
|
2496
|
-
|
|
2497
|
-
pattern: tool3.schema.string().
|
|
2498
|
-
text: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.text),
|
|
2938
|
+
readseek_grep: readseekTool(openCodeToolDescription("grep"), {
|
|
2939
|
+
pattern: tool3.schema.string().describe(PARAM_DESCRIPTIONS.grepPattern),
|
|
2499
2940
|
summary: tool3.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.summary),
|
|
2500
2941
|
path: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.projectSearchPath),
|
|
2501
2942
|
glob: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.glob),
|
|
2502
2943
|
literal: tool3.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.literal),
|
|
2503
2944
|
ignoreCase: tool3.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.ignoreCase),
|
|
2504
2945
|
context: tool3.schema.number().int().nonnegative().optional().describe(PARAM_DESCRIPTIONS.context),
|
|
2505
|
-
limit: tool3.schema.number().int().positive().optional().describe(PARAM_DESCRIPTIONS.matchLimit)
|
|
2506
|
-
|
|
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)
|
|
2950
|
+
}, "grep", async (input, context) => {
|
|
2507
2951
|
const target = resolvePath(context.directory, input.path ?? ".");
|
|
2508
|
-
|
|
2509
|
-
throw new Error("exactly one of pattern or text is required");
|
|
2510
|
-
}
|
|
2511
|
-
const pattern = input.pattern ?? input.text;
|
|
2952
|
+
const pattern = input.pattern;
|
|
2512
2953
|
await authorizeSearch(context, target, pattern);
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
else if (info.isDirectory()) {
|
|
2528
|
-
const glob = new Bun.Glob(input.glob ?? "**/*");
|
|
2529
|
-
for await (const filePath of glob.scan({ cwd: target, absolute: true, onlyFiles: true, followSymlinks: false })) {
|
|
2530
|
-
files.push(filePath);
|
|
2531
|
-
}
|
|
2532
|
-
} else
|
|
2533
|
-
throw new Error(`grep target is not a file or directory: ${target}`);
|
|
2534
|
-
const maxMatches = input.limit ?? 100;
|
|
2535
|
-
const contextLines = input.context ?? 0;
|
|
2536
|
-
const results = [];
|
|
2537
|
-
let totalMatches = 0;
|
|
2538
|
-
let truncated = false;
|
|
2539
|
-
for (const filePath of files.sort()) {
|
|
2540
|
-
context.abort.throwIfAborted();
|
|
2541
|
-
if (truncated)
|
|
2542
|
-
break;
|
|
2543
|
-
const buffer = await readFile2(filePath);
|
|
2544
|
-
if (buffer.includes(0))
|
|
2545
|
-
continue;
|
|
2546
|
-
const lines = buffer.toString("utf8").replace(/\r\n/g, `
|
|
2547
|
-
`).split(`
|
|
2548
|
-
`);
|
|
2549
|
-
const matchLines = new Set;
|
|
2550
|
-
const ranges = [];
|
|
2551
|
-
for (let index = 0;index < lines.length; index++) {
|
|
2552
|
-
expression.lastIndex = 0;
|
|
2553
|
-
if (!expression.test(lines[index] ?? ""))
|
|
2554
|
-
continue;
|
|
2555
|
-
if (totalMatches === maxMatches) {
|
|
2556
|
-
truncated = true;
|
|
2557
|
-
break;
|
|
2558
|
-
}
|
|
2559
|
-
const line = index + 1;
|
|
2560
|
-
matchLines.add(line);
|
|
2561
|
-
ranges.push([Math.max(1, line - contextLines), Math.min(lines.length, line + contextLines)]);
|
|
2562
|
-
totalMatches++;
|
|
2563
|
-
}
|
|
2564
|
-
if (ranges.length === 0)
|
|
2565
|
-
continue;
|
|
2566
|
-
if (input.summary) {
|
|
2567
|
-
results.push({ file: filePath, count: matchLines.size });
|
|
2568
|
-
continue;
|
|
2569
|
-
}
|
|
2570
|
-
const mergedRanges = [];
|
|
2571
|
-
for (const [start, end] of ranges) {
|
|
2572
|
-
const previous = mergedRanges.at(-1);
|
|
2573
|
-
if (previous && start <= previous[1] + 1)
|
|
2574
|
-
previous[1] = Math.max(previous[1], end);
|
|
2575
|
-
else
|
|
2576
|
-
mergedRanges.push([start, end]);
|
|
2577
|
-
}
|
|
2578
|
-
const linesByNumber = new Map;
|
|
2579
|
-
for (const [start, end] of mergedRanges) {
|
|
2580
|
-
const output = record2(await runContent(context, readRangeArgs(filePath, start, end)));
|
|
2581
|
-
for (const hashline of items(output.hashlines)) {
|
|
2582
|
-
const line = record2(hashline).line;
|
|
2583
|
-
if (typeof line === "number" && !linesByNumber.has(line))
|
|
2584
|
-
linesByNumber.set(line, hashline);
|
|
2585
|
-
}
|
|
2586
|
-
}
|
|
2587
|
-
const hashlines = [...linesByNumber.values()];
|
|
2588
|
-
const matches = hashlines.filter((hashline) => {
|
|
2589
|
-
const line = record2(hashline).line;
|
|
2590
|
-
return typeof line === "number" && matchLines.has(line);
|
|
2591
|
-
});
|
|
2592
|
-
results.push({ file: filePath, matches, hashlines });
|
|
2593
|
-
}
|
|
2594
|
-
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
|
+
});
|
|
2595
2968
|
}),
|
|
2596
|
-
|
|
2969
|
+
readseek_search: readseekTool(openCodeToolDescription("search"), {
|
|
2597
2970
|
pattern: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.pattern),
|
|
2598
2971
|
context: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.patternContext),
|
|
2599
2972
|
selector: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.selector),
|
|
@@ -2602,9 +2975,9 @@ function createReadSeekTools(imagePolicy) {
|
|
|
2602
2975
|
path: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.projectSearchPath),
|
|
2603
2976
|
language: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.language),
|
|
2604
2977
|
gitScope: tool3.schema.enum(GIT_SCOPES).optional().describe(PARAM_DESCRIPTIONS.gitScope)
|
|
2605
|
-
}, "
|
|
2978
|
+
}, "search", async (input, context) => {
|
|
2606
2979
|
const target = resolvePath(context.directory, input.path ?? ".");
|
|
2607
|
-
const args =
|
|
2980
|
+
const args = buildSearchArgs({
|
|
2608
2981
|
path: target,
|
|
2609
2982
|
pattern: input.pattern,
|
|
2610
2983
|
context: input.context,
|
|
@@ -2622,14 +2995,14 @@ function createReadSeekTools(imagePolicy) {
|
|
|
2622
2995
|
throw new Error("rule stdin is unavailable in OpenCode tools; use inline KDL or @path");
|
|
2623
2996
|
return runReadSeek(context, args);
|
|
2624
2997
|
}),
|
|
2625
|
-
|
|
2998
|
+
readseek_def: readseekTool(openCodeToolDescription("def"), {
|
|
2626
2999
|
name: tool3.schema.string().describe(PARAM_DESCRIPTIONS.name),
|
|
2627
3000
|
path: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.projectSearchPath),
|
|
2628
3001
|
language: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.language),
|
|
2629
3002
|
gitScope: tool3.schema.enum(GIT_SCOPES).optional().describe(PARAM_DESCRIPTIONS.gitScope)
|
|
2630
|
-
}, "
|
|
3003
|
+
}, "def", async (input, context) => {
|
|
2631
3004
|
const target = resolvePath(context.directory, input.path ?? ".");
|
|
2632
|
-
const args =
|
|
3005
|
+
const args = buildDefArgs({
|
|
2633
3006
|
path: target,
|
|
2634
3007
|
name: input.name,
|
|
2635
3008
|
language: input.language,
|
|
@@ -2638,16 +3011,16 @@ function createReadSeekTools(imagePolicy) {
|
|
|
2638
3011
|
await authorizeSearch(context, target, input.name);
|
|
2639
3012
|
return runReadSeek(context, args);
|
|
2640
3013
|
}),
|
|
2641
|
-
|
|
3014
|
+
readseek_refs: readseekTool(openCodeToolDescription("refs"), {
|
|
2642
3015
|
name: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.refsName),
|
|
2643
3016
|
path: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.projectSearchPath),
|
|
2644
3017
|
language: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.language),
|
|
2645
3018
|
at: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.at),
|
|
2646
3019
|
gitScope: tool3.schema.enum(GIT_SCOPES).optional().describe(PARAM_DESCRIPTIONS.gitScope)
|
|
2647
|
-
}, "
|
|
3020
|
+
}, "refs", async (input, context) => {
|
|
2648
3021
|
const target = resolvePath(context.directory, input.path ?? ".");
|
|
2649
3022
|
const name = input.name;
|
|
2650
|
-
const args =
|
|
3023
|
+
const args = buildRefsArgs({
|
|
2651
3024
|
path: target,
|
|
2652
3025
|
name,
|
|
2653
3026
|
at: input.at,
|
|
@@ -2657,7 +3030,7 @@ function createReadSeekTools(imagePolicy) {
|
|
|
2657
3030
|
await authorizeSearch(context, target, name ?? "*");
|
|
2658
3031
|
return runReadSeek(context, args);
|
|
2659
3032
|
}),
|
|
2660
|
-
|
|
3033
|
+
readseek_rename: readseekTool(openCodeToolDescription("rename"), {
|
|
2661
3034
|
path: tool3.schema.string().describe(PARAM_DESCRIPTIONS.projectPath),
|
|
2662
3035
|
at: tool3.schema.string().describe(PARAM_DESCRIPTIONS.at),
|
|
2663
3036
|
to: tool3.schema.string().min(1).describe(PARAM_DESCRIPTIONS.to),
|
|
@@ -2666,7 +3039,7 @@ function createReadSeekTools(imagePolicy) {
|
|
|
2666
3039
|
planHash: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.planHash),
|
|
2667
3040
|
language: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.language),
|
|
2668
3041
|
gitScope: tool3.schema.enum(GIT_SCOPES).optional().describe(PARAM_DESCRIPTIONS.gitScope)
|
|
2669
|
-
}, "
|
|
3042
|
+
}, "rename", async (input, context) => {
|
|
2670
3043
|
const filePath = resolvePath(context.directory, input.path);
|
|
2671
3044
|
await authorizeRead(context, filePath);
|
|
2672
3045
|
if (input.workspace) {
|
|
@@ -2676,7 +3049,7 @@ function createReadSeekTools(imagePolicy) {
|
|
|
2676
3049
|
throw new Error("readseek could not identify a binding at the rename cursor");
|
|
2677
3050
|
await authorizeSearch(context, context.directory, name);
|
|
2678
3051
|
}
|
|
2679
|
-
const args =
|
|
3052
|
+
const args = buildRenameArgs({
|
|
2680
3053
|
path: filePath,
|
|
2681
3054
|
to: input.to,
|
|
2682
3055
|
at: input.at,
|
|
@@ -2696,7 +3069,7 @@ function createReadSeekTools(imagePolicy) {
|
|
|
2696
3069
|
throw new Error("readseek rename plan did not include a plan hash");
|
|
2697
3070
|
}
|
|
2698
3071
|
await authorizeEdit(context, files);
|
|
2699
|
-
return runReadSeek(context,
|
|
3072
|
+
return runReadSeek(context, buildRenameArgs({
|
|
2700
3073
|
path: filePath,
|
|
2701
3074
|
to: input.to,
|
|
2702
3075
|
at: input.at,
|
|
@@ -2710,7 +3083,7 @@ function createReadSeekTools(imagePolicy) {
|
|
|
2710
3083
|
timeoutMs: DEFAULT_READSEEK_TIMEOUT_MS
|
|
2711
3084
|
});
|
|
2712
3085
|
}),
|
|
2713
|
-
|
|
3086
|
+
readseek_edit: readseekTool(openCodeToolDescription("edit"), {
|
|
2714
3087
|
path: tool3.schema.string().describe(PARAM_DESCRIPTIONS.projectPath),
|
|
2715
3088
|
edits: tool3.schema.array(tool3.schema.union([
|
|
2716
3089
|
tool3.schema.object({
|
|
@@ -2750,7 +3123,7 @@ function createReadSeekTools(imagePolicy) {
|
|
|
2750
3123
|
apply: tool3.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.applyDefaultTrue),
|
|
2751
3124
|
planHash: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.planHash),
|
|
2752
3125
|
gitScope: tool3.schema.enum(GIT_SCOPES).optional().describe(PARAM_DESCRIPTIONS.gitScope)
|
|
2753
|
-
}, "
|
|
3126
|
+
}, "edit", async (input, context) => {
|
|
2754
3127
|
const filePath = resolvePath(context.directory, input.path);
|
|
2755
3128
|
validateEdits(input.edits);
|
|
2756
3129
|
return withFileMutationQueue(filePath, async () => {
|
|
@@ -2759,7 +3132,7 @@ function createReadSeekTools(imagePolicy) {
|
|
|
2759
3132
|
await rejectSymlinkMutation(filePath);
|
|
2760
3133
|
const edits = input.edits;
|
|
2761
3134
|
const request = serializeEditPlan(edits);
|
|
2762
|
-
const baseArgs =
|
|
3135
|
+
const baseArgs = buildEditArgs({
|
|
2763
3136
|
path: filePath,
|
|
2764
3137
|
language: input.language,
|
|
2765
3138
|
gitScope: input.gitScope,
|
|
@@ -2783,7 +3156,7 @@ function createReadSeekTools(imagePolicy) {
|
|
|
2783
3156
|
}
|
|
2784
3157
|
await authorizeEdit(context, [filePath], simpleDiff(filePath, before, after), false);
|
|
2785
3158
|
context.abort.throwIfAborted();
|
|
2786
|
-
const applied = record2(await runReadSeek(context,
|
|
3159
|
+
const applied = record2(await runReadSeek(context, buildEditArgs({
|
|
2787
3160
|
path: filePath,
|
|
2788
3161
|
language: input.language,
|
|
2789
3162
|
gitScope: input.gitScope,
|
|
@@ -2801,10 +3174,10 @@ function createReadSeekTools(imagePolicy) {
|
|
|
2801
3174
|
return postMutationPreview(context, previewArgs, filePath, true);
|
|
2802
3175
|
});
|
|
2803
3176
|
}),
|
|
2804
|
-
|
|
3177
|
+
readseek_write: readseekTool(openCodeToolDescription("write"), {
|
|
2805
3178
|
path: tool3.schema.string().describe(PARAM_DESCRIPTIONS.projectPath),
|
|
2806
3179
|
content: tool3.schema.string().describe(PARAM_DESCRIPTIONS.content)
|
|
2807
|
-
}, "
|
|
3180
|
+
}, "write", async (input, context) => {
|
|
2808
3181
|
const filePath = resolvePath(context.directory, input.path);
|
|
2809
3182
|
await authorizeExternal(context, filePath);
|
|
2810
3183
|
return withFileMutationQueue(filePath, async () => {
|
|
@@ -2828,37 +3201,37 @@ function createAccessTools(imagePolicy) {
|
|
|
2828
3201
|
const languageSchema = { language: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.language) };
|
|
2829
3202
|
const levelSchema = { level: tool3.schema.enum(VISION_LEVEL_VALUES).optional().describe(PARAM_DESCRIPTIONS.level) };
|
|
2830
3203
|
const definitions = {
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
3204
|
+
show: { build: buildShowArgs, args: { ...pathSchema, ...atSchema, ...languageSchema, end: tool3.schema.number().int().positive().optional(), limit: tool3.schema.number().int().positive().optional(), noAnchors: tool3.schema.boolean().optional() } },
|
|
3205
|
+
tree: { build: buildTreeArgs, args: { ...pathSchema, ...atSchema, ...languageSchema, depth: tool3.schema.number().int().nonnegative().optional(), kind: tool3.schema.string().optional(), outline: tool3.schema.boolean().optional() } },
|
|
3206
|
+
meta: { build: buildMetaArgs, args: { ...pathSchema, ...languageSchema } },
|
|
3207
|
+
assets: { build: buildAssetsArgs, args: { ...pathSchema, ...atSchema } },
|
|
3208
|
+
syntax: { build: buildSyntaxArgs, args: { ...pathSchema, ...languageSchema } },
|
|
3209
|
+
check: { build: buildCheckArgs, args: { path: tool3.schema.string().optional().describe(PARAM_DESCRIPTIONS.projectSearchPath), rules: tool3.schema.string().min(1), gitScope: tool3.schema.enum(GIT_SCOPES).optional() } },
|
|
3210
|
+
caption: { build: buildCaptionArgs, args: { ...pathSchema, ...atSchema, ...levelSchema, include: tool3.schema.array(tool3.schema.enum(["text", "objects"])).min(1).optional() } },
|
|
3211
|
+
objects: { build: buildObjectsArgs, args: { ...pathSchema, ...atSchema, ...levelSchema } },
|
|
3212
|
+
ocr: { build: buildOcrArgs, args: { ...pathSchema, ...atSchema, ...levelSchema } }
|
|
2840
3213
|
};
|
|
2841
3214
|
return Object.fromEntries(Object.entries(definitions).map(([name, definition]) => [
|
|
2842
3215
|
`readseek_${name}`,
|
|
2843
3216
|
readseekTool(openCodeToolDescription(name), definition.args, name, async (input, context) => {
|
|
2844
3217
|
const filePath = resolvePath(context.directory, input.path ?? ".");
|
|
2845
|
-
const buildInput = { ...input, path: filePath, ...name === "
|
|
3218
|
+
const buildInput = { ...input, path: filePath, ...name === "tree" ? { depth: input.depth ?? DEFAULT_STRUCTURE_DEPTH } : {} };
|
|
2846
3219
|
let args = definition.build(buildInput);
|
|
2847
3220
|
await authorizeRead(context, filePath);
|
|
2848
|
-
if (name === "
|
|
3221
|
+
if (name === "check") {
|
|
2849
3222
|
if (input.rules === "-")
|
|
2850
3223
|
throw new Error("rules stdin is unavailable in OpenCode tools; use inline KDL or @path");
|
|
2851
3224
|
await authorizeSearch(context, filePath, input.rules);
|
|
2852
3225
|
if (input.rules.startsWith("@"))
|
|
2853
3226
|
await authorizeRead(context, resolvePath(context.directory, input.rules.slice(1)));
|
|
2854
3227
|
}
|
|
2855
|
-
const inference = ["
|
|
2856
|
-
if (name === "
|
|
3228
|
+
const inference = ["caption", "objects", "ocr"].includes(name);
|
|
3229
|
+
if (name === "show" || name === "assets" || inference) {
|
|
2857
3230
|
const readseekDir = await ensureReadSeekCache(context);
|
|
2858
3231
|
args = definition.build({ ...buildInput, readseekDir });
|
|
2859
3232
|
}
|
|
2860
3233
|
return runReadSeek(context, args, { timeoutMs: inference ? DEFAULT_VISION_TIMEOUT_MS : DEFAULT_READSEEK_TIMEOUT_MS });
|
|
2861
|
-
}, name === "
|
|
3234
|
+
}, name === "show" || name === "assets" ? imagePolicy : undefined)
|
|
2862
3235
|
]));
|
|
2863
3236
|
}
|
|
2864
3237
|
|
|
@@ -2873,28 +3246,28 @@ var SKILL = {
|
|
|
2873
3246
|
|
|
2874
3247
|
When ReadSeek tools are unavailable, use the readseek CLI for structural navigation and anchored edits if it is installed and available on PATH. Run \`readseek --help\` to inspect supported commands before invoking them. Get fresh anchors before editing; re-read after external file changes.
|
|
2875
3248
|
|
|
2876
|
-
Commands use
|
|
3249
|
+
Commands use flat single-word verbs, with target and primary query arguments positional:
|
|
2877
3250
|
|
|
2878
3251
|
\`\`\`sh
|
|
2879
|
-
readseek show
|
|
2880
|
-
readseek
|
|
2881
|
-
readseek
|
|
2882
|
-
readseek
|
|
2883
|
-
readseek
|
|
2884
|
-
readseek check
|
|
2885
|
-
readseek
|
|
2886
|
-
readseek
|
|
2887
|
-
readseek
|
|
2888
|
-
readseek
|
|
2889
|
-
readseek edit
|
|
2890
|
-
readseek rename
|
|
3252
|
+
readseek show file.ts --at line:10 --limit 40 --kdl
|
|
3253
|
+
readseek tree file.ts --depth 1 --kdl
|
|
3254
|
+
readseek meta paper.pdf --kdl
|
|
3255
|
+
readseek assets paper.pdf --at page:2 --kdl
|
|
3256
|
+
readseek syntax file.ts --kdl
|
|
3257
|
+
readseek check . @checks.kdl --kdl
|
|
3258
|
+
readseek search 'console.log($$$ARGS)' src --kdl
|
|
3259
|
+
readseek grep 'TODO' src --kdl
|
|
3260
|
+
readseek def main src --kdl
|
|
3261
|
+
readseek refs file.ts --at line:10:3 --kdl
|
|
3262
|
+
readseek edit file.ts @edits.kdl --kdl
|
|
3263
|
+
readseek rename file.ts --at line:10:3 nextName --kdl
|
|
2891
3264
|
\`\`\`
|
|
2892
3265
|
|
|
2893
|
-
Native \`
|
|
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"\`.
|
|
2894
3267
|
|
|
2895
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.
|
|
2896
3269
|
|
|
2897
|
-
Content and assets return cached paths; assets auto-materialize. Read those paths directly. There is no extract command or asset destination option. Reading does not invoke inference. Request inference explicitly with \`
|
|
3270
|
+
Content and assets return cached paths; assets auto-materialize. Read those paths directly. There is no extract command or asset destination option. Reading does not invoke inference. Request inference explicitly with \`caption\` (optionally \`--include text,objects\`), \`objects\`, or \`ocr\`, using \`--level low|medium|high\` and an installed model; models are never downloaded automatically.
|
|
2898
3271
|
|
|
2899
3272
|
This V2 plugin supplies guidance only; it does not register readseek_* tools. If the CLI is unavailable, use OpenCode's built-in tools instead.`
|
|
2900
3273
|
};
|
|
@@ -2951,7 +3324,7 @@ var ReadSeekPlugin = async (_input, options) => {
|
|
|
2951
3324
|
return;
|
|
2952
3325
|
try {
|
|
2953
3326
|
const result = output.metadata.readseek ?? JSON.parse(output.output);
|
|
2954
|
-
if (input.tool === "
|
|
3327
|
+
if (input.tool === "readseek_rename") {
|
|
2955
3328
|
anchors.planRename(input.sessionID, result);
|
|
2956
3329
|
if (record2(result).applied === true) {
|
|
2957
3330
|
for (const filePath of renameFiles(result))
|