pi-readseek 0.11.1 → 0.11.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.ts +166 -93
- package/package.json +3 -3
package/dist/index.ts
CHANGED
|
@@ -39,7 +39,6 @@ var PARAM_DESCRIPTIONS = {
|
|
|
39
39
|
projectSearchPath: "File or directory to search; defaults to the project directory",
|
|
40
40
|
searchPath: "File or directory to search; defaults to the current directory",
|
|
41
41
|
language: "Language override when auto-detection is ambiguous",
|
|
42
|
-
languageOverride: "Language override",
|
|
43
42
|
languageSymbol: "Language override for symbol replacement",
|
|
44
43
|
pattern: "AST pattern (ast-grep style), such as console.log($$$ARGS)",
|
|
45
44
|
patternContext: "Surrounding code context for sub-statement patterns",
|
|
@@ -56,14 +55,10 @@ var PARAM_DESCRIPTIONS = {
|
|
|
56
55
|
end: "Last line to include",
|
|
57
56
|
limit: "Maximum number of lines to include",
|
|
58
57
|
depth: "Maximum structural depth for structure output; defaults to 1 in agent tools",
|
|
59
|
-
offset: "One-based starting line",
|
|
60
|
-
readLimit: "Maximum lines to return",
|
|
61
|
-
symbolRead: "Symbol name to read",
|
|
62
|
-
bundle: "Include related symbols from the same file; requires symbol",
|
|
63
58
|
level: "Explicit inference level: low (default), medium, or high",
|
|
64
59
|
include: "Additional caption tasks: text and/or objects",
|
|
65
60
|
kind: "Restrict structure to this node kind",
|
|
66
|
-
outline: "
|
|
61
|
+
outline: "Show outline of document hierarchy or source symbols",
|
|
67
62
|
noAnchors: "Omit source line anchors",
|
|
68
63
|
text: "Literal native text query; adapter find_text also supports regex via pattern",
|
|
69
64
|
edits: "Use set_line, replace_lines, insert_after, replace_symbol, or replace",
|
|
@@ -119,11 +114,14 @@ var TOOL_DESCRIPTIONS = {
|
|
|
119
114
|
rename_symbol: "Rename a symbol at a line or hash cursor with binding-aware edits and stale-plan protection.",
|
|
120
115
|
write_file: "Create or replace a complete text file and return `LINE:HASH` anchors. Use `edit_text` for small changes."
|
|
121
116
|
};
|
|
117
|
+
function rewriteBacktickedToolNames(text, aliases = {}) {
|
|
118
|
+
return TOOL_NAMES.reduce((rewritten, toolName) => rewritten.replaceAll(`\`${toolName}\``, `\`${aliases[toolName] ?? toolName}\``), text);
|
|
119
|
+
}
|
|
122
120
|
function renderToolDescription(name, aliases = {}) {
|
|
123
121
|
const desc = TOOL_DESCRIPTIONS[name];
|
|
124
122
|
if (!desc)
|
|
125
123
|
throw new Error(`Missing TOOL_DESCRIPTIONS for ${name}`);
|
|
126
|
-
return
|
|
124
|
+
return rewriteBacktickedToolNames(desc, aliases);
|
|
127
125
|
}
|
|
128
126
|
var gitScopeSchema = {
|
|
129
127
|
gitScope: {
|
|
@@ -212,7 +210,7 @@ var TOOL_CONTRACTS = [
|
|
|
212
210
|
tool("show_metadata", "Show metadata", { ...path, ...lang, ...stdin }, ["path"]),
|
|
213
211
|
tool("show_assets", "Show assets", { ...path, at: string(PARAM_DESCRIPTIONS.assetAt), ...stdin }, ["path"]),
|
|
214
212
|
tool("check_syntax", "Check syntax", { ...path, ...lang, ...stdin }, ["path"]),
|
|
215
|
-
tool("check_rules", "Check rules", {
|
|
213
|
+
tool("check_rules", "Check rules", { path: string(PARAM_DESCRIPTIONS.searchPath), ...gitScopeSchema, rules: string(PARAM_DESCRIPTIONS.rules) }, ["rules"]),
|
|
216
214
|
tool("generate_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"]),
|
|
217
215
|
tool("detect_objects", "Detect objects", { ...path, ...at, ...stdin, ...level }, ["path"]),
|
|
218
216
|
tool("recognize_text", "Recognize text", { ...path, ...at, ...stdin, ...level }, ["path"]),
|
|
@@ -276,9 +274,6 @@ var TOOL_COMPACT_GUIDELINES = {
|
|
|
276
274
|
function resolveToolName(name, aliases = {}) {
|
|
277
275
|
return aliases[name] ?? name;
|
|
278
276
|
}
|
|
279
|
-
function rewriteBacktickedToolNames(text, aliases = {}) {
|
|
280
|
-
return TOOL_NAMES.reduce((rewritten, toolName) => rewritten.replaceAll(`\`${toolName}\``, `\`${resolveToolName(toolName, aliases)}\``), text);
|
|
281
|
-
}
|
|
282
277
|
function renderCompactGuidelines(name, aliases = {}) {
|
|
283
278
|
return (TOOL_COMPACT_GUIDELINES[name] ?? []).map((guideline) => rewriteBacktickedToolNames(guideline, aliases));
|
|
284
279
|
}
|
|
@@ -293,15 +288,6 @@ function renderToolRoutingPolicy(options = {}) {
|
|
|
293
288
|
const find_references = resolveToolName("find_references", aliases);
|
|
294
289
|
const rename_symbol = resolveToolName("rename_symbol", aliases);
|
|
295
290
|
const show_assets = resolveToolName("show_assets", aliases);
|
|
296
|
-
if (options.style === "server") {
|
|
297
|
-
return [
|
|
298
|
-
"Structural code navigation, document inspection, and hash-verified edits with readseek.",
|
|
299
|
-
"For structural questions, prefer these tools to text Grep/Bash: targeted definitions, references, and AST patterns; shallow show_structure views; check_syntax diagnostics and show_content cursor context; and document inspection.",
|
|
300
|
-
`Use ${edit_text} for LINE:HASH-anchored mutations (preview with apply=false; apply defaults to true and re-checks plan_hash).`,
|
|
301
|
-
"Paths resolve against CLAUDE_PROJECT_DIR. Native Read/Grep/Edit hooks provide passive context;",
|
|
302
|
-
"call MCP tools for active navigation, document questions, or anchored edits."
|
|
303
|
-
].join(" ");
|
|
304
|
-
}
|
|
305
291
|
const title = options.title ?? "ReadSeek tool policy:";
|
|
306
292
|
const bullets = [];
|
|
307
293
|
if (options.preferOverBuiltIns) {
|
|
@@ -404,8 +390,38 @@ function validateExactlyOneEditVariant(edits) {
|
|
|
404
390
|
}
|
|
405
391
|
return { ok: true, edits: normalized };
|
|
406
392
|
}
|
|
393
|
+
var BASE10_INT_RE = /^-?\d+$/;
|
|
407
394
|
var MIN_SAFE_INTEGER = BigInt(Number.MIN_SAFE_INTEGER);
|
|
408
395
|
var MAX_SAFE_INTEGER = BigInt(Number.MAX_SAFE_INTEGER);
|
|
396
|
+
function formatReceived(value) {
|
|
397
|
+
if (typeof value === "number" || typeof value === "bigint")
|
|
398
|
+
return String(value);
|
|
399
|
+
return JSON.stringify(value) ?? String(value);
|
|
400
|
+
}
|
|
401
|
+
function invalidInteger(value, name) {
|
|
402
|
+
return {
|
|
403
|
+
ok: false,
|
|
404
|
+
message: `Invalid ${name}: expected a safe base-10 integer, received ${formatReceived(value)}.`
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
function coerceObviousBase10Int(value, name) {
|
|
408
|
+
if (value === undefined || typeof value === "string" && value.trim() === "") {
|
|
409
|
+
return { ok: true, value: undefined };
|
|
410
|
+
}
|
|
411
|
+
if (typeof value === "number") {
|
|
412
|
+
if (Number.isSafeInteger(value)) {
|
|
413
|
+
return { ok: true, value };
|
|
414
|
+
}
|
|
415
|
+
return invalidInteger(value, name);
|
|
416
|
+
}
|
|
417
|
+
if (typeof value === "string" && BASE10_INT_RE.test(value)) {
|
|
418
|
+
const parsed = BigInt(value);
|
|
419
|
+
if (parsed >= MIN_SAFE_INTEGER && parsed <= MAX_SAFE_INTEGER) {
|
|
420
|
+
return { ok: true, value: Number(parsed) };
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
return invalidInteger(value, name);
|
|
424
|
+
}
|
|
409
425
|
// ../readseek-api/src/argv.ts
|
|
410
426
|
var DEFAULT_STRUCTURE_DEPTH = 1;
|
|
411
427
|
function appendGitScopeArgs(args, input) {
|
|
@@ -1262,10 +1278,10 @@ function text(value) {
|
|
|
1262
1278
|
function count(value) {
|
|
1263
1279
|
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : undefined;
|
|
1264
1280
|
}
|
|
1265
|
-
function safe(value) {
|
|
1281
|
+
function safe(value, preserveTabs = false) {
|
|
1266
1282
|
return value.replace(/[\x00-\x09\x0b-\x1f\x7f-\x9f]/g, (char) => {
|
|
1267
1283
|
if (char === "\t")
|
|
1268
|
-
return "\\t";
|
|
1284
|
+
return preserveTabs ? char : "\\t";
|
|
1269
1285
|
if (char === "\r")
|
|
1270
1286
|
return "\\r";
|
|
1271
1287
|
return `\\x${char.charCodeAt(0).toString(16).padStart(2, "0")}`;
|
|
@@ -1425,7 +1441,7 @@ ${assetList(assets)}`);
|
|
|
1425
1441
|
const lang = text(metadata.language || content.language);
|
|
1426
1442
|
if (lang)
|
|
1427
1443
|
summary.push(inline(lang));
|
|
1428
|
-
const rows = lines.map((line) => `${line.line}${options.noAnchors ? "" : `:${text(line.hash)}`} | ${safe(text(line.text)
|
|
1444
|
+
const rows = lines.map((line) => `${line.line}${options.noAnchors ? "" : `:${text(line.hash)}`} | ${safe(text(line.text), true)}`);
|
|
1429
1445
|
const identifier = record(cursor.identifier);
|
|
1430
1446
|
if (count(cursor.line) !== undefined)
|
|
1431
1447
|
result.metadata.line = cursor.line;
|
|
@@ -1524,6 +1540,23 @@ function resolveReadSeekBinaryFromPackages(resolver = resolveFromPackage, option
|
|
|
1524
1540
|
accessSync(binaryPath, fsConstants.X_OK);
|
|
1525
1541
|
return binaryPath;
|
|
1526
1542
|
}
|
|
1543
|
+
// ../readseek-api/src/path.ts
|
|
1544
|
+
import { homedir } from "node:os";
|
|
1545
|
+
import path3 from "node:path";
|
|
1546
|
+
var UNICODE_SPACES = /[\u00A0\u2000-\u200A\u202F\u205F\u3000]/g;
|
|
1547
|
+
function expandUserPath(filePath, homedirFn = homedir) {
|
|
1548
|
+
const withoutAt = filePath.startsWith("@") ? filePath.slice(1) : filePath;
|
|
1549
|
+
const normalized = withoutAt.replace(UNICODE_SPACES, " ");
|
|
1550
|
+
if (normalized === "~")
|
|
1551
|
+
return homedirFn();
|
|
1552
|
+
if (normalized.startsWith("~/"))
|
|
1553
|
+
return homedirFn() + normalized.slice(1);
|
|
1554
|
+
return normalized;
|
|
1555
|
+
}
|
|
1556
|
+
function resolveToCwd(filePath, cwd, homedirFn = homedir) {
|
|
1557
|
+
const expanded = expandUserPath(filePath, homedirFn);
|
|
1558
|
+
return path3.isAbsolute(expanded) ? expanded : path3.resolve(cwd, expanded);
|
|
1559
|
+
}
|
|
1527
1560
|
// ../readseek-api/src/error.ts
|
|
1528
1561
|
var READSEEK_USAGE_HINT = /(?:^|\n)\s*Run readseek(?: [\w-]+)* --help for more information\.?\s*$/;
|
|
1529
1562
|
function cleanReadSeekErrorText(stderr) {
|
|
@@ -2013,8 +2046,7 @@ function descriptionAliases(toolName, registeredName, toolAliases) {
|
|
|
2013
2046
|
}));
|
|
2014
2047
|
}
|
|
2015
2048
|
function defineToolPromptMetadata(options) {
|
|
2016
|
-
const
|
|
2017
|
-
const toolName = toolNameFromPrompt(fileName);
|
|
2049
|
+
const toolName = options.toolName ?? toolNameFromPrompt(promptFileName(options.promptUrl));
|
|
2018
2050
|
const replaceable = REPLACEABLE_BY_TOOL.get(toolName);
|
|
2019
2051
|
const defaultReadSeekName = `readSeek_${toolName}`;
|
|
2020
2052
|
const registeredName = options.registeredName ?? (replaceable ? defaultReadSeekName : undefined);
|
|
@@ -2221,13 +2253,6 @@ function generateCompactOrFullDiff(oldContent, newContent, contextLines = 4) {
|
|
|
2221
2253
|
}
|
|
2222
2254
|
return generateDiffString(oldContent, newContent, contextLines);
|
|
2223
2255
|
}
|
|
2224
|
-
|
|
2225
|
-
// src/path-utils.ts
|
|
2226
|
-
import {
|
|
2227
|
-
expandUserPath as expandUserPath2,
|
|
2228
|
-
resolveToCwd as resolveToCwd2
|
|
2229
|
-
} from "@jarkkojs/readseek-api";
|
|
2230
|
-
|
|
2231
2256
|
// src/runtime.ts
|
|
2232
2257
|
function throwIfAborted(signal) {
|
|
2233
2258
|
if (signal?.aborted)
|
|
@@ -2558,12 +2583,12 @@ function classifyEdit(oldContent, newContent) {
|
|
|
2558
2583
|
// src/readseek-client.ts
|
|
2559
2584
|
import { existsSync as existsSync2, statSync as statSync3 } from "node:fs";
|
|
2560
2585
|
import { createRequire as createRequire2 } from "node:module";
|
|
2561
|
-
import { homedir as
|
|
2562
|
-
import
|
|
2586
|
+
import { homedir as homedir3 } from "node:os";
|
|
2587
|
+
import path4 from "node:path";
|
|
2563
2588
|
|
|
2564
2589
|
// src/readseek-settings.ts
|
|
2565
2590
|
import { readFileSync, statSync as statSync2 } from "node:fs";
|
|
2566
|
-
import { homedir } from "node:os";
|
|
2591
|
+
import { homedir as homedir2 } from "node:os";
|
|
2567
2592
|
import { join } from "node:path";
|
|
2568
2593
|
var READSEEK_TOOL_REPLACEMENTS = {
|
|
2569
2594
|
read: "readSeek_show_content",
|
|
@@ -2750,7 +2775,7 @@ function mergeSettings(base, override) {
|
|
|
2750
2775
|
return settings;
|
|
2751
2776
|
}
|
|
2752
2777
|
function resolveReadSeekJsonSettings() {
|
|
2753
|
-
const globalResult = readSettingsFile(join(
|
|
2778
|
+
const globalResult = readSettingsFile(join(homedir2(), ".pi/agent/settings.json"));
|
|
2754
2779
|
const projectResult = readSettingsFile(join(process.cwd(), ".pi/settings.json"));
|
|
2755
2780
|
return {
|
|
2756
2781
|
settings: mergeSettings(globalResult.settings, projectResult.settings),
|
|
@@ -2785,8 +2810,8 @@ function readSeekBinaryAvailability() {
|
|
|
2785
2810
|
}
|
|
2786
2811
|
var defaultReadSeekDirInit;
|
|
2787
2812
|
function defaultReadSeekDir() {
|
|
2788
|
-
const home =
|
|
2789
|
-
return home ?
|
|
2813
|
+
const home = homedir3();
|
|
2814
|
+
return home ? path4.join(home, ".pi", "readseek") : null;
|
|
2790
2815
|
}
|
|
2791
2816
|
function directoryExists(dir) {
|
|
2792
2817
|
try {
|
|
@@ -2811,11 +2836,11 @@ async function ensureDefaultReadSeekDir() {
|
|
|
2811
2836
|
return defaultReadSeekDirInit;
|
|
2812
2837
|
}
|
|
2813
2838
|
function hasProjectReadSeekDir(cwd = process.cwd()) {
|
|
2814
|
-
let dir =
|
|
2839
|
+
let dir = path4.resolve(cwd);
|
|
2815
2840
|
while (true) {
|
|
2816
|
-
if (existsSync2(
|
|
2841
|
+
if (existsSync2(path4.join(dir, ".readseek")))
|
|
2817
2842
|
return true;
|
|
2818
|
-
const parent =
|
|
2843
|
+
const parent = path4.dirname(dir);
|
|
2819
2844
|
if (parent === dir)
|
|
2820
2845
|
return false;
|
|
2821
2846
|
dir = parent;
|
|
@@ -3212,7 +3237,7 @@ function linkToolPath(styledText, rawPath, cwd) {
|
|
|
3212
3237
|
try {
|
|
3213
3238
|
if (!getCapabilities().hyperlinks)
|
|
3214
3239
|
return styledText;
|
|
3215
|
-
const absolutePath =
|
|
3240
|
+
const absolutePath = resolveToCwd(rawPath, cwd);
|
|
3216
3241
|
return hyperlink(styledText, pathToFileURL(absolutePath).href);
|
|
3217
3242
|
} catch {
|
|
3218
3243
|
return styledText;
|
|
@@ -3614,7 +3639,7 @@ async function executeEdit(opts) {
|
|
|
3614
3639
|
const parsed = params;
|
|
3615
3640
|
const rawPath = parsed.path;
|
|
3616
3641
|
const path = rawPath.replace(/^@/, "");
|
|
3617
|
-
const absolutePath =
|
|
3642
|
+
const absolutePath = resolveToCwd(path, cwd);
|
|
3618
3643
|
throwIfAborted(signal);
|
|
3619
3644
|
try {
|
|
3620
3645
|
return await withFileMutationQueue(absolutePath, async () => {
|
|
@@ -3736,7 +3761,7 @@ async function executeEdit(opts) {
|
|
|
3736
3761
|
function registerEditTool(pi, options = {}) {
|
|
3737
3762
|
const name = options.name ?? "readSeek_edit_text";
|
|
3738
3763
|
const promptMetadata = defineToolPromptMetadata({
|
|
3739
|
-
|
|
3764
|
+
toolName: "edit_text",
|
|
3740
3765
|
promptSnippet: "Edit existing files with fresh LINE:HASH anchors",
|
|
3741
3766
|
registeredName: name,
|
|
3742
3767
|
toolAliases: options.toolAliases
|
|
@@ -3833,7 +3858,7 @@ ${theme.fg("error", info.errorText)}`;
|
|
|
3833
3858
|
|
|
3834
3859
|
// src/grep.ts
|
|
3835
3860
|
import { readFile as fsReadFile2, stat as fsStat } from "node:fs/promises";
|
|
3836
|
-
import
|
|
3861
|
+
import path5 from "node:path";
|
|
3837
3862
|
import { createGrepTool } from "@earendil-works/pi-coding-agent";
|
|
3838
3863
|
import { Type as Type5 } from "@sinclair/typebox";
|
|
3839
3864
|
import { Text as Text4 } from "@earendil-works/pi-tui";
|
|
@@ -4200,12 +4225,6 @@ function formatGrepResultText(input) {
|
|
|
4200
4225
|
errorText: undefined
|
|
4201
4226
|
};
|
|
4202
4227
|
}
|
|
4203
|
-
|
|
4204
|
-
// src/coerce-obvious-int.ts
|
|
4205
|
-
import {
|
|
4206
|
-
coerceObviousBase10Int
|
|
4207
|
-
} from "@jarkkojs/readseek-api";
|
|
4208
|
-
|
|
4209
4228
|
// src/tui-source-render.ts
|
|
4210
4229
|
var GREP_ANCHOR_RE = /^(.*?):(>>| )(\d+):([0-9a-zA-Z]{1,16})\|(.*)$/;
|
|
4211
4230
|
function renderGrepSourceForDisplay(text, anchors, renderPath = (path) => path) {
|
|
@@ -4365,7 +4384,7 @@ async function executeGrep(opts) {
|
|
|
4365
4384
|
if (!textBlock?.text)
|
|
4366
4385
|
return result;
|
|
4367
4386
|
const { path: rawSearchPath } = p;
|
|
4368
|
-
const searchPath =
|
|
4387
|
+
const searchPath = resolveToCwd(rawSearchPath || ".", cwd);
|
|
4369
4388
|
let searchPathIsDirectory = false;
|
|
4370
4389
|
try {
|
|
4371
4390
|
searchPathIsDirectory = (await fsStat(searchPath)).isDirectory();
|
|
@@ -4419,7 +4438,7 @@ async function executeGrep(opts) {
|
|
|
4419
4438
|
};
|
|
4420
4439
|
const toAbsolutePath = (displayPath) => {
|
|
4421
4440
|
if (searchPathIsDirectory)
|
|
4422
|
-
return
|
|
4441
|
+
return path5.resolve(searchPath, displayPath);
|
|
4423
4442
|
return searchPath;
|
|
4424
4443
|
};
|
|
4425
4444
|
const groupsByPath = new Map;
|
|
@@ -4609,7 +4628,7 @@ ${warning}` } : item),
|
|
|
4609
4628
|
function registerGrepTool(pi, options = {}) {
|
|
4610
4629
|
const name = options.name ?? "readSeek_find_text";
|
|
4611
4630
|
const promptMetadata = defineToolPromptMetadata({
|
|
4612
|
-
|
|
4631
|
+
toolName: "find_text",
|
|
4613
4632
|
promptSnippet: "Search text or regex with edit-ready LINE:HASH anchors",
|
|
4614
4633
|
registeredName: name
|
|
4615
4634
|
});
|
|
@@ -4696,7 +4715,7 @@ function registerGrepTool(pi, options = {}) {
|
|
|
4696
4715
|
}
|
|
4697
4716
|
|
|
4698
4717
|
// src/search.ts
|
|
4699
|
-
import
|
|
4718
|
+
import path6 from "node:path";
|
|
4700
4719
|
import { Type as Type6 } from "@sinclair/typebox";
|
|
4701
4720
|
|
|
4702
4721
|
// src/stat-search-path.ts
|
|
@@ -4755,7 +4774,7 @@ function mergeRanges(ranges) {
|
|
|
4755
4774
|
return merged;
|
|
4756
4775
|
}
|
|
4757
4776
|
var SEARCH_PROMPT_METADATA = defineToolPromptMetadata({
|
|
4758
|
-
|
|
4777
|
+
toolName: "find_nodes",
|
|
4759
4778
|
promptSnippet: "Search code by AST structure using ast-grep-style patterns"
|
|
4760
4779
|
});
|
|
4761
4780
|
function linesFromSearchResult(result, ranges) {
|
|
@@ -4781,14 +4800,14 @@ function linesFromSearchResult(result, ranges) {
|
|
|
4781
4800
|
return lines;
|
|
4782
4801
|
}
|
|
4783
4802
|
function readSeekLanguageForPath(language, searchPath, isFile) {
|
|
4784
|
-
if (language === "typescript" && isFile &&
|
|
4803
|
+
if (language === "typescript" && isFile && path6.extname(searchPath).toLowerCase() === ".tsx")
|
|
4785
4804
|
return "tsx";
|
|
4786
4805
|
return language;
|
|
4787
4806
|
}
|
|
4788
4807
|
async function executeSearch(opts) {
|
|
4789
4808
|
const { params, signal, cwd, onFileAnchored } = opts;
|
|
4790
4809
|
const p = params;
|
|
4791
|
-
const searchPath =
|
|
4810
|
+
const searchPath = resolveToCwd(p.path ?? ".", cwd);
|
|
4792
4811
|
const statResult = await statSearchPathOrError("search", p.path, searchPath);
|
|
4793
4812
|
if (!statResult.ok)
|
|
4794
4813
|
return statResult.error;
|
|
@@ -4816,8 +4835,8 @@ async function executeSearch(opts) {
|
|
|
4816
4835
|
}
|
|
4817
4836
|
const readSeekFiles = [];
|
|
4818
4837
|
for (const result of results) {
|
|
4819
|
-
const abs =
|
|
4820
|
-
const display =
|
|
4838
|
+
const abs = path6.isAbsolute(result.file) ? result.file : path6.resolve(cwd, result.file);
|
|
4839
|
+
const display = path6.relative(cwd, abs) || abs;
|
|
4821
4840
|
const ranges = result.matches.map((match) => ({ startLine: match.start_line, endLine: match.end_line }));
|
|
4822
4841
|
const mergedRanges = mergeRanges(ranges);
|
|
4823
4842
|
const lines = linesFromSearchResult(result, mergedRanges);
|
|
@@ -4898,7 +4917,7 @@ function registerSearchTool(pi, options = {}) {
|
|
|
4898
4917
|
|
|
4899
4918
|
// src/refs.ts
|
|
4900
4919
|
import { Type as Type7 } from "@sinclair/typebox";
|
|
4901
|
-
import
|
|
4920
|
+
import path7 from "node:path";
|
|
4902
4921
|
|
|
4903
4922
|
// src/refs-output.ts
|
|
4904
4923
|
function refsQueryLabel(input) {
|
|
@@ -4929,7 +4948,7 @@ function buildRefsOutput(input) {
|
|
|
4929
4948
|
|
|
4930
4949
|
// src/refs.ts
|
|
4931
4950
|
var REFS_PROMPT_METADATA = defineToolPromptMetadata({
|
|
4932
|
-
|
|
4951
|
+
toolName: "find_references",
|
|
4933
4952
|
promptSnippet: "Find references by identifier name or cursor binding"
|
|
4934
4953
|
});
|
|
4935
4954
|
function refsLine(reference) {
|
|
@@ -4942,14 +4961,14 @@ function groupReferences(references, cwd) {
|
|
|
4942
4961
|
const files = new Map;
|
|
4943
4962
|
const seen = new Set;
|
|
4944
4963
|
for (const reference of references) {
|
|
4945
|
-
const abs =
|
|
4964
|
+
const abs = path7.isAbsolute(reference.file) ? reference.file : path7.resolve(cwd, reference.file);
|
|
4946
4965
|
const dedupeKey = `${abs}:${reference.line}:${reference.column}`;
|
|
4947
4966
|
if (seen.has(dedupeKey))
|
|
4948
4967
|
continue;
|
|
4949
4968
|
seen.add(dedupeKey);
|
|
4950
4969
|
let file = files.get(abs);
|
|
4951
4970
|
if (!file) {
|
|
4952
|
-
file = { displayPath:
|
|
4971
|
+
file = { displayPath: path7.relative(cwd, abs) || abs, path: abs, lines: [] };
|
|
4953
4972
|
files.set(abs, file);
|
|
4954
4973
|
}
|
|
4955
4974
|
file.lines.push(refsLine(reference));
|
|
@@ -4966,7 +4985,7 @@ async function executeRefs(opts) {
|
|
|
4966
4985
|
if (!scopeError.ok) {
|
|
4967
4986
|
return buildToolErrorResult("refs", "invalid-parameter", `refs parameter: ${scopeError.error}`);
|
|
4968
4987
|
}
|
|
4969
|
-
const searchPath =
|
|
4988
|
+
const searchPath = resolveToCwd(p.path ?? ".", cwd);
|
|
4970
4989
|
const statResult = await statSearchPathOrError("refs", p.path, searchPath);
|
|
4971
4990
|
if (!statResult.ok)
|
|
4972
4991
|
return statResult.error;
|
|
@@ -5038,10 +5057,10 @@ function registerRefsTool(pi, options = {}) {
|
|
|
5038
5057
|
|
|
5039
5058
|
// src/rename.ts
|
|
5040
5059
|
import { Type as Type8 } from "@sinclair/typebox";
|
|
5041
|
-
import
|
|
5060
|
+
import path8 from "node:path";
|
|
5042
5061
|
import { Text as Text5 } from "@earendil-works/pi-tui";
|
|
5043
5062
|
var RENAME_PROMPT_METADATA = defineToolPromptMetadata({
|
|
5044
|
-
|
|
5063
|
+
toolName: "rename_symbol",
|
|
5045
5064
|
promptSnippet: "Rename a symbol at a source location with verified edits"
|
|
5046
5065
|
});
|
|
5047
5066
|
var renameSchema = Type8.Object({
|
|
@@ -5060,7 +5079,7 @@ async function executeRename(opts) {
|
|
|
5060
5079
|
if (typeof p.at !== "string" || !/^(line:[1-9][0-9]*|hash:[0-9a-fA-F]+)(:[1-9][0-9]*)?$/.test(p.at)) {
|
|
5061
5080
|
return buildToolErrorResult("rename_symbol", "invalid-parameter", "at must be a line or hash location with an optional positive column");
|
|
5062
5081
|
}
|
|
5063
|
-
const filePath =
|
|
5082
|
+
const filePath = resolveToCwd(p.path, cwd);
|
|
5064
5083
|
try {
|
|
5065
5084
|
const output = await readSeekRenameSymbol(filePath, {
|
|
5066
5085
|
to: p.to,
|
|
@@ -5073,14 +5092,14 @@ async function executeRename(opts) {
|
|
|
5073
5092
|
for (const file of [output, ...output.others]) {
|
|
5074
5093
|
if (file.edits.length === 0)
|
|
5075
5094
|
continue;
|
|
5076
|
-
const absoluteFile =
|
|
5095
|
+
const absoluteFile = path8.isAbsolute(file.file) ? file.file : path8.resolve(cwd, file.file);
|
|
5077
5096
|
onFileMutated?.(absoluteFile);
|
|
5078
5097
|
}
|
|
5079
5098
|
}
|
|
5080
|
-
const primaryAbs =
|
|
5099
|
+
const primaryAbs = path8.isAbsolute(output.file) ? output.file : path8.resolve(cwd, output.file);
|
|
5081
5100
|
const files = [primaryAbs];
|
|
5082
5101
|
for (const other of output.others) {
|
|
5083
|
-
const abs =
|
|
5102
|
+
const abs = path8.isAbsolute(other.file) ? other.file : path8.resolve(cwd, other.file);
|
|
5084
5103
|
if (!files.includes(abs))
|
|
5085
5104
|
files.push(abs);
|
|
5086
5105
|
}
|
|
@@ -5162,7 +5181,7 @@ function registerRenameTool(pi, options = {}) {
|
|
|
5162
5181
|
});
|
|
5163
5182
|
if (expanded && output) {
|
|
5164
5183
|
for (const file of outputs) {
|
|
5165
|
-
const display =
|
|
5184
|
+
const display = path8.relative(cwd, file.file) || file.file;
|
|
5166
5185
|
text += `
|
|
5167
5186
|
${linkedPathLine(theme, file.file, display, cwd, ` (${file.edits.length} edits)`, width)}`;
|
|
5168
5187
|
}
|
|
@@ -5466,7 +5485,7 @@ async function executeWrite(opts) {
|
|
|
5466
5485
|
function registerWriteTool(pi, options = {}) {
|
|
5467
5486
|
const name = options.name ?? "readSeek_write_file";
|
|
5468
5487
|
const promptMetadata = defineToolPromptMetadata({
|
|
5469
|
-
|
|
5488
|
+
toolName: "write_file",
|
|
5470
5489
|
promptSnippet: "Create or replace a complete file with LINE:HASH anchors",
|
|
5471
5490
|
registeredName: name
|
|
5472
5491
|
});
|
|
@@ -5482,7 +5501,7 @@ function registerWriteTool(pi, options = {}) {
|
|
|
5482
5501
|
}),
|
|
5483
5502
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
5484
5503
|
const cwd = ctx?.cwd ?? process.cwd();
|
|
5485
|
-
const absolutePath =
|
|
5504
|
+
const absolutePath = resolveToCwd(params.path, cwd);
|
|
5486
5505
|
try {
|
|
5487
5506
|
return await withFileMutationQueue2(absolutePath, async () => {
|
|
5488
5507
|
let result;
|
|
@@ -5578,9 +5597,9 @@ function registerWriteTool(pi, options = {}) {
|
|
|
5578
5597
|
|
|
5579
5598
|
// src/def.ts
|
|
5580
5599
|
import { Type as Type10 } from "@sinclair/typebox";
|
|
5581
|
-
import
|
|
5600
|
+
import path9 from "node:path";
|
|
5582
5601
|
var DEF_PROMPT_METADATA = defineToolPromptMetadata({
|
|
5583
|
-
|
|
5602
|
+
toolName: "find_definitions",
|
|
5584
5603
|
promptSnippet: "Go to a symbol definition or declaration by name"
|
|
5585
5604
|
});
|
|
5586
5605
|
async function executeDef(opts) {
|
|
@@ -5589,7 +5608,7 @@ async function executeDef(opts) {
|
|
|
5589
5608
|
if (!p.name || !p.name.trim()) {
|
|
5590
5609
|
return buildToolErrorResult("def", "invalid-parameter", "readSeek_find_definitions requires 'name'");
|
|
5591
5610
|
}
|
|
5592
|
-
const searchPath =
|
|
5611
|
+
const searchPath = resolveToCwd(p.path ?? ".", cwd);
|
|
5593
5612
|
const statResult = await statSearchPathOrError("def", p.path, searchPath);
|
|
5594
5613
|
if (!statResult.ok)
|
|
5595
5614
|
return statResult.error;
|
|
@@ -5604,16 +5623,16 @@ async function executeDef(opts) {
|
|
|
5604
5623
|
return {
|
|
5605
5624
|
content: [{ type: "text", text: "no definitions found" }],
|
|
5606
5625
|
details: {
|
|
5607
|
-
readSeekValue: { tool: "def", ok: true, path: searchPath, definitions: [] }
|
|
5626
|
+
readSeekValue: { tool: "def", ok: true, path: searchPath, definitions: [], files: [] }
|
|
5608
5627
|
}
|
|
5609
5628
|
};
|
|
5610
5629
|
}
|
|
5611
5630
|
const files = new Map;
|
|
5612
5631
|
for (const def of definitions) {
|
|
5613
|
-
const abs =
|
|
5632
|
+
const abs = path9.isAbsolute(def.file) ? def.file : path9.resolve(cwd, def.file);
|
|
5614
5633
|
let file = files.get(abs);
|
|
5615
5634
|
if (!file) {
|
|
5616
|
-
file = { displayPath:
|
|
5635
|
+
file = { displayPath: path9.relative(cwd, abs) || abs, path: abs, lines: [] };
|
|
5617
5636
|
files.set(abs, file);
|
|
5618
5637
|
}
|
|
5619
5638
|
file.lines.push(buildReadSeekLineWithHash(def.line, def.line_hash, def.text));
|
|
@@ -5633,7 +5652,7 @@ async function executeDef(opts) {
|
|
|
5633
5652
|
content: [{ type: "text", text: textParts.join(`
|
|
5634
5653
|
`) }],
|
|
5635
5654
|
details: {
|
|
5636
|
-
readSeekValue: { tool: "def", ok: true, path: searchPath, definitions }
|
|
5655
|
+
readSeekValue: { tool: "def", ok: true, path: searchPath, definitions, files: fileList }
|
|
5637
5656
|
}
|
|
5638
5657
|
};
|
|
5639
5658
|
} catch (err) {
|
|
@@ -5676,7 +5695,7 @@ function registerDefTool(pi, options = {}) {
|
|
|
5676
5695
|
}
|
|
5677
5696
|
|
|
5678
5697
|
// src/access.ts
|
|
5679
|
-
import { Text as Text7 } from "@earendil-works/pi-tui";
|
|
5698
|
+
import { Text as Text7, wrapTextWithAnsi as wrapTextWithAnsi2 } from "@earendil-works/pi-tui";
|
|
5680
5699
|
import { Type as Type11 } from "@sinclair/typebox";
|
|
5681
5700
|
import { readFile as readFile2 } from "node:fs/promises";
|
|
5682
5701
|
var operations = {
|
|
@@ -5709,6 +5728,48 @@ function resolveNonShowSummary(tool, envelope) {
|
|
|
5709
5728
|
return "text recognized";
|
|
5710
5729
|
return "complete";
|
|
5711
5730
|
}
|
|
5731
|
+
var SOURCE_ROW_RE = /^(\d+)(:[0-9a-fA-F]+)? \| (.*)$/;
|
|
5732
|
+
function renderShowPresentation(summary, body, extras, expanded, theme, fallbackWidth) {
|
|
5733
|
+
const fg = typeof theme?.fg === "function" ? (style, value) => theme.fg(style, value) : (_style, value) => value;
|
|
5734
|
+
const rawBodyLines = expanded && body ? body.split(`
|
|
5735
|
+
`) : [];
|
|
5736
|
+
let maxDigits = 0;
|
|
5737
|
+
for (const line of rawBodyLines) {
|
|
5738
|
+
const match = line.match(SOURCE_ROW_RE);
|
|
5739
|
+
if (match && match[1].length > maxDigits)
|
|
5740
|
+
maxDigits = match[1].length;
|
|
5741
|
+
}
|
|
5742
|
+
return {
|
|
5743
|
+
invalidate() {},
|
|
5744
|
+
render(renderWidth) {
|
|
5745
|
+
const normalized = normalizeWidth(renderWidth, fallbackWidth ?? 80);
|
|
5746
|
+
const out = [...wrapTextWithAnsi2(summary, normalized)];
|
|
5747
|
+
if (!expanded)
|
|
5748
|
+
return out;
|
|
5749
|
+
for (const line of rawBodyLines) {
|
|
5750
|
+
if (!line && maxDigits > 0)
|
|
5751
|
+
continue;
|
|
5752
|
+
const match = line.match(SOURCE_ROW_RE);
|
|
5753
|
+
if (match) {
|
|
5754
|
+
const anchor = `${match[1].padStart(maxDigits, " ")}${match[2] ?? ""}`;
|
|
5755
|
+
const prefix = ` ${fg("dim", `${anchor} | `)}`;
|
|
5756
|
+
out.push(...wrapWithHangingIndent(prefix, match[3] ?? "", normalized));
|
|
5757
|
+
} else if (line.startsWith("Cursor: ")) {
|
|
5758
|
+
out.push(...wrapWithHangingIndent(" ", fg("dim", line), normalized));
|
|
5759
|
+
} else {
|
|
5760
|
+
out.push(...wrapTextWithAnsi2(line, normalized));
|
|
5761
|
+
}
|
|
5762
|
+
}
|
|
5763
|
+
for (const extra of extras) {
|
|
5764
|
+
for (const line of extra.split(`
|
|
5765
|
+
`)) {
|
|
5766
|
+
out.push(...wrapTextWithAnsi2(fg("warning", line), normalized));
|
|
5767
|
+
}
|
|
5768
|
+
}
|
|
5769
|
+
return out;
|
|
5770
|
+
}
|
|
5771
|
+
};
|
|
5772
|
+
}
|
|
5712
5773
|
function hasAnchors(value) {
|
|
5713
5774
|
if (!value || typeof value !== "object")
|
|
5714
5775
|
return false;
|
|
@@ -5743,7 +5804,7 @@ async function executeAccess(opts) {
|
|
|
5743
5804
|
return buildToolErrorResult(opts.tool, "invalid-parameter", "path is required");
|
|
5744
5805
|
if (path === "-")
|
|
5745
5806
|
return buildToolErrorResult(opts.tool, "invalid-parameter", "Agent tools require a file path; stdin is available through the CLI or API");
|
|
5746
|
-
const filePath =
|
|
5807
|
+
const filePath = resolveToCwd(path, opts.cwd);
|
|
5747
5808
|
try {
|
|
5748
5809
|
const { path: _path, ...options } = p;
|
|
5749
5810
|
const envelope = await operations[opts.tool](filePath, { ...options, signal: opts.signal });
|
|
@@ -5774,7 +5835,7 @@ function registerAccessTools(pi, options = {}) {
|
|
|
5774
5835
|
const registeredName = name === "show_content" ? options.name ?? `readSeek_${name}` : `readSeek_${name}`;
|
|
5775
5836
|
const contract = getToolContract(name);
|
|
5776
5837
|
const metadata = defineToolPromptMetadata({
|
|
5777
|
-
|
|
5838
|
+
toolName: name,
|
|
5778
5839
|
promptSnippet: contract.title,
|
|
5779
5840
|
registeredName,
|
|
5780
5841
|
toolAliases: options.toolAliases
|
|
@@ -5796,8 +5857,18 @@ function registerAccessTools(pi, options = {}) {
|
|
|
5796
5857
|
const label = renderToolLabel(theme, registeredName);
|
|
5797
5858
|
const filePath = typeof args?.path === "string" ? args.path : args?.path ? String(args.path) : "";
|
|
5798
5859
|
const renderedPath = filePath ? linkToolPath(theme?.fg ? theme.fg("accent", filePath) : filePath, filePath, cwd) : "";
|
|
5799
|
-
const atBadge = args?.at ? theme?.fg ? theme.fg("dim",
|
|
5800
|
-
const
|
|
5860
|
+
const atBadge = args?.at ? theme?.fg ? theme.fg("dim", `@ ${args.at}`) : `@ ${args.at}` : "";
|
|
5861
|
+
const flags = [
|
|
5862
|
+
args?.limit !== undefined && `limit=${args.limit}`,
|
|
5863
|
+
args?.end !== undefined && `end=${args.end}`,
|
|
5864
|
+
args?.depth !== undefined && `depth=${args.depth}`,
|
|
5865
|
+
args?.kind && `kind=${args.kind}`,
|
|
5866
|
+
args?.outline && "outline",
|
|
5867
|
+
args?.noAnchors && "no-anchors",
|
|
5868
|
+
args?.level && `level=${args.level}`
|
|
5869
|
+
].filter(Boolean);
|
|
5870
|
+
const flagsBadge = flags.length > 0 ? theme?.fg ? theme.fg("dim", `[${flags.join(",")}]`) : `[${flags.join(",")}]` : "";
|
|
5871
|
+
const text = clampLineToWidth([label, renderedPath, atBadge, flagsBadge].filter(Boolean).join(" "), context.width);
|
|
5801
5872
|
return upsertTextComponent(context.lastComponent, text);
|
|
5802
5873
|
},
|
|
5803
5874
|
renderResult(result, renderOptions, theme, ...rest) {
|
|
@@ -5808,18 +5879,20 @@ function registerAccessTools(pi, options = {}) {
|
|
|
5808
5879
|
if (isError || result.isError)
|
|
5809
5880
|
return renderErrorResult(text, { expanded, width, fallback: `${registeredName} failed`, theme });
|
|
5810
5881
|
const presentation = result.details?.readSeekValue?.presentation;
|
|
5882
|
+
const envelope = result.details?.readSeekValue?.envelope;
|
|
5811
5883
|
if (presentation) {
|
|
5812
|
-
const
|
|
5813
|
-
const notes = presentation.notices.map((notice) => `Note: ${notice}`);
|
|
5884
|
+
const warning = typeof envelope?.warning === "string" && envelope.warning ? presentation.notices.at(-1) : undefined;
|
|
5814
5885
|
const attachments = result.content?.filter((item) => item.type === "text").slice(1).map((item) => item.text) ?? [];
|
|
5815
|
-
|
|
5816
|
-
|
|
5817
|
-
|
|
5886
|
+
const extras = [...warning ? [`Note: ${warning}`] : [], ...attachments];
|
|
5887
|
+
const hasWarning = extras.length > 0;
|
|
5888
|
+
const hasExpandable = Boolean(presentation.body) || hasWarning;
|
|
5889
|
+
const summary = summaryLine(presentation.summary, { hidden: hasExpandable && !expanded, theme, style: hasWarning ? "warning" : "success" });
|
|
5890
|
+
return renderShowPresentation(summary, presentation.body, extras, expanded, theme, width);
|
|
5818
5891
|
}
|
|
5819
|
-
const envelope = result.details?.readSeekValue?.envelope;
|
|
5820
5892
|
const nonShowSummary = resolveNonShowSummary(name, envelope);
|
|
5821
5893
|
const summary = summaryLine(nonShowSummary, { hidden: !!text && !expanded, theme, style: "success" });
|
|
5822
|
-
return new Text7(clampLinesToWidth((expanded ? `${summary}
|
|
5894
|
+
return new Text7(clampLinesToWidth((expanded ? `${summary}
|
|
5895
|
+
${text}` : summary).split(`
|
|
5823
5896
|
`), width).join(`
|
|
5824
5897
|
`), 0, 0);
|
|
5825
5898
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-readseek",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.3",
|
|
4
4
|
"description": "Pi extension for LINE:HASH-anchored file operations and structural code navigation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -39,8 +39,8 @@
|
|
|
39
39
|
"node": ">=20.0.0"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@jarkkojs/readseek": "^0.11.
|
|
43
|
-
"@jarkkojs/readseek-api": "^0.11.
|
|
42
|
+
"@jarkkojs/readseek": "^0.11.3",
|
|
43
|
+
"@jarkkojs/readseek-api": "^0.11.3",
|
|
44
44
|
"diff": "^9.0.0",
|
|
45
45
|
"xxhash-wasm": "^1.1.0"
|
|
46
46
|
},
|