pi-readseek 0.4.10 → 0.4.12
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/package.json +2 -2
- package/src/edit.ts +20 -13
- package/src/refs.ts +3 -20
- package/src/sg.ts +4 -17
- package/src/stat-search-path.ts +34 -0
- package/src/write.ts +15 -16
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-readseek",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.12",
|
|
4
4
|
"description": "Pi extension for readseek-backed hash-anchored read/edit/grep, structural code maps, structural search, and file exploration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"node": ">=20.0.0"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@jarkkojs/readseek": "^0.4.
|
|
42
|
+
"@jarkkojs/readseek": "^0.4.16",
|
|
43
43
|
"diff": "^8.0.3",
|
|
44
44
|
"ignore": "^7.0.5",
|
|
45
45
|
"picomatch": "^4.0.4",
|
package/src/edit.ts
CHANGED
|
@@ -16,6 +16,7 @@ import { buildEditOutput } from "./edit-output.js";
|
|
|
16
16
|
import { classifyEdit, isDifftAvailable, runDifftastic } from "./edit-classify.js";
|
|
17
17
|
import type { SemanticSummary } from "./readseek-value.js";
|
|
18
18
|
import { buildReadseekError } from "./readseek-value.js";
|
|
19
|
+
import { classifyReadseekFailure } from "./readseek-client.js";
|
|
19
20
|
import { countEditTypes, formatEditCallText, formatEditResultText } from "./edit-render-helpers.js";
|
|
20
21
|
import { validateSyntaxRegression } from "./edit-syntax-validate.js";
|
|
21
22
|
import { resolveSyntaxValidateMode, type SyntaxValidateOptions } from "./syntax-validate-mode.js";
|
|
@@ -276,20 +277,26 @@ export async function executeEdit(opts: ExecuteEditOptions): Promise<any> {
|
|
|
276
277
|
// readseekMapContent is invoked at most once per replace_symbol edit.
|
|
277
278
|
const replaceSymbolRanges: { start: number; end: number }[] = [];
|
|
278
279
|
const rsProbeResults: { type: "ok"; content: string; replacement: string; warnings: string[]; range: { start: number; end: number } }[] = [];
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
280
|
+
try {
|
|
281
|
+
for (const rs of replaceSymbolEdits) {
|
|
282
|
+
throwIfAborted(signal);
|
|
283
|
+
const probe = await replaceSymbol({
|
|
284
|
+
filePath: absolutePath,
|
|
285
|
+
content: originalNormalized,
|
|
286
|
+
symbol: rs.replace_symbol.symbol,
|
|
287
|
+
newBody: rs.replace_symbol.new_body,
|
|
288
|
+
});
|
|
289
|
+
if (probe.type !== "ok") {
|
|
290
|
+
// symbol-resolution errors surface before the overlap check.
|
|
291
|
+
return buildEditError(absolutePath, "invalid-edit-variant", probe.message);
|
|
292
|
+
}
|
|
293
|
+
rsProbeResults.push(probe);
|
|
294
|
+
replaceSymbolRanges.push(probe.range);
|
|
290
295
|
}
|
|
291
|
-
|
|
292
|
-
|
|
296
|
+
} catch (err) {
|
|
297
|
+
throwIfAborted(signal);
|
|
298
|
+
const failure = classifyReadseekFailure(err);
|
|
299
|
+
return buildEditError(absolutePath, failure.code, failure.message, failure.hint);
|
|
293
300
|
}
|
|
294
301
|
|
|
295
302
|
const sortedReplaceSymbolRanges = [...replaceSymbolRanges].sort((a, b) => a.start - b.start || a.end - b.end);
|
package/src/refs.ts
CHANGED
|
@@ -2,8 +2,8 @@ import type { ExtensionAPI, ToolRenderResultOptions } from "@earendil-works/pi-c
|
|
|
2
2
|
import { Text } from "@earendil-works/pi-tui";
|
|
3
3
|
import { Type } from "@sinclair/typebox";
|
|
4
4
|
import path from "node:path";
|
|
5
|
-
import { stat as fsStat } from "node:fs/promises";
|
|
6
5
|
import { defineToolPromptMetadata } from "./tool-prompt-metadata.js";
|
|
6
|
+
import { statSearchPathOrError } from "./stat-search-path.js";
|
|
7
7
|
import { buildReadseekLineWithHash, buildToolErrorResult } from "./readseek-value.js";
|
|
8
8
|
import { resolveToCwd } from "./path-utils.js";
|
|
9
9
|
import { classifyReadseekFailure, readseekRefs, type ReadseekReference } from "./readseek-client.js";
|
|
@@ -91,25 +91,8 @@ export async function executeRefs(opts: ExecuteRefsOptions): Promise<any> {
|
|
|
91
91
|
}
|
|
92
92
|
const searchPath = resolveToCwd(p.path ?? ".", cwd);
|
|
93
93
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
} catch (err: any) {
|
|
97
|
-
if (err?.code === "ENOENT") {
|
|
98
|
-
return buildToolErrorResult("refs", "path-not-found", `Error: path '${p.path ?? "."}' does not exist`, {
|
|
99
|
-
path: p.path ?? searchPath,
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
if (err?.code === "EACCES" || err?.code === "EPERM") {
|
|
103
|
-
return buildToolErrorResult("refs", "permission-denied", `Error: permission denied for path '${p.path ?? "."}'`, {
|
|
104
|
-
path: p.path ?? searchPath,
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
const message = `Error: could not access path '${p.path ?? "."}': ${err?.message ?? String(err)}`;
|
|
108
|
-
return buildToolErrorResult("refs", "fs-error", message, {
|
|
109
|
-
path: p.path ?? searchPath,
|
|
110
|
-
details: { fsCode: err?.code, fsMessage: err?.message },
|
|
111
|
-
});
|
|
112
|
-
}
|
|
94
|
+
const statResult = await statSearchPathOrError("refs", p.path, searchPath);
|
|
95
|
+
if (!statResult.ok) return statResult.error;
|
|
113
96
|
|
|
114
97
|
try {
|
|
115
98
|
const references = await readseekRefs(searchPath, p.name, {
|
package/src/sg.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import { stat as fsStat } from "node:fs/promises";
|
|
3
2
|
|
|
4
3
|
import type { ExtensionAPI, ToolRenderResultOptions } from "@earendil-works/pi-coding-agent";
|
|
5
4
|
import { Text } from "@earendil-works/pi-tui";
|
|
@@ -8,6 +7,7 @@ import { Type } from "@sinclair/typebox";
|
|
|
8
7
|
import { defineToolPromptMetadata } from "./tool-prompt-metadata.js";
|
|
9
8
|
import { buildReadseekLineWithHash, buildToolErrorResult, type ReadseekLine } from "./readseek-value.js";
|
|
10
9
|
import { resolveToCwd } from "./path-utils.js";
|
|
10
|
+
import { statSearchPathOrError } from "./stat-search-path.js";
|
|
11
11
|
import { classifyReadseekFailure, isReadseekAvailable, readseekSearch, type ReadseekHashline, type ReadseekSearchFileOutput } from "./readseek-client.js";
|
|
12
12
|
import { buildSgOutput } from "./sg-output.js";
|
|
13
13
|
import type { FileAnchoredCallback } from "./tool-types.js";
|
|
@@ -112,23 +112,10 @@ export async function executeSg(opts: ExecuteSgOptions): Promise<any> {
|
|
|
112
112
|
return buildToolErrorResult("search", "invalid-parameter", message);
|
|
113
113
|
}
|
|
114
114
|
const searchPath = resolveToCwd(p.path ?? ".", cwd);
|
|
115
|
-
let searchPathIsFile = false;
|
|
116
115
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
} catch (err: any) {
|
|
121
|
-
if (err?.code === "ENOENT") {
|
|
122
|
-
const message = `Error: path '${p.path ?? "."}' does not exist`;
|
|
123
|
-
return buildToolErrorResult("search", "path-not-found", message, { path: p.path ?? searchPath });
|
|
124
|
-
}
|
|
125
|
-
if (err?.code === "EACCES" || err?.code === "EPERM") {
|
|
126
|
-
const message = `Error: permission denied for path '${p.path ?? "."}'`;
|
|
127
|
-
return buildToolErrorResult("search", "permission-denied", message, { path: p.path ?? searchPath });
|
|
128
|
-
}
|
|
129
|
-
const message = `Error: could not access path '${p.path ?? "."}': ${err?.message ?? String(err)}`;
|
|
130
|
-
return buildToolErrorResult("search", "fs-error", message, { path: p.path ?? searchPath, details: { fsCode: err?.code, fsMessage: err?.message } });
|
|
131
|
-
}
|
|
116
|
+
const statResult = await statSearchPathOrError("search", p.path, searchPath);
|
|
117
|
+
if (!statResult.ok) return statResult.error;
|
|
118
|
+
const searchPathIsFile = statResult.stats.isFile();
|
|
132
119
|
|
|
133
120
|
try {
|
|
134
121
|
const effectiveLang = readseekLanguageForPath(p.lang, searchPath, searchPathIsFile);
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { stat as fsStat } from "node:fs/promises";
|
|
2
|
+
import type { Stats } from "node:fs";
|
|
3
|
+
|
|
4
|
+
import { buildToolErrorResult, type ToolErrorResult } from "./readseek-value.js";
|
|
5
|
+
|
|
6
|
+
export type StatSearchPathResult =
|
|
7
|
+
| { ok: true; stats: Stats }
|
|
8
|
+
| { ok: false; error: ToolErrorResult };
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Stat a tool's search path, mapping access failures into the shared readseek
|
|
12
|
+
* error taxonomy. Used by refs and search, which reject a missing or
|
|
13
|
+
* unreadable path with identical codes and messages.
|
|
14
|
+
*/
|
|
15
|
+
export async function statSearchPathOrError(
|
|
16
|
+
tool: string,
|
|
17
|
+
rawPath: string | undefined,
|
|
18
|
+
searchPath: string,
|
|
19
|
+
): Promise<StatSearchPathResult> {
|
|
20
|
+
try {
|
|
21
|
+
return { ok: true, stats: await fsStat(searchPath) };
|
|
22
|
+
} catch (err: any) {
|
|
23
|
+
const display = rawPath ?? ".";
|
|
24
|
+
const path = rawPath ?? searchPath;
|
|
25
|
+
if (err?.code === "ENOENT") {
|
|
26
|
+
return { ok: false, error: buildToolErrorResult(tool, "path-not-found", `Error: path '${display}' does not exist`, { path }) };
|
|
27
|
+
}
|
|
28
|
+
if (err?.code === "EACCES" || err?.code === "EPERM") {
|
|
29
|
+
return { ok: false, error: buildToolErrorResult(tool, "permission-denied", `Error: permission denied for path '${display}'`, { path }) };
|
|
30
|
+
}
|
|
31
|
+
const message = `Error: could not access path '${display}': ${err?.message ?? String(err)}`;
|
|
32
|
+
return { ok: false, error: buildToolErrorResult(tool, "fs-error", message, { path, details: { fsCode: err?.code, fsMessage: err?.message } }) };
|
|
33
|
+
}
|
|
34
|
+
}
|
package/src/write.ts
CHANGED
|
@@ -222,6 +222,21 @@ export async function executeWrite(opts: {
|
|
|
222
222
|
},
|
|
223
223
|
};
|
|
224
224
|
}
|
|
225
|
+
if (looksLikeBinary(Buffer.from(content, "utf-8"))) {
|
|
226
|
+
warnings.push("File content appears to be binary.");
|
|
227
|
+
readseekWarnings.push(buildReadseekWarning("binary-content", "File content appears to be binary."));
|
|
228
|
+
return {
|
|
229
|
+
text: `Cannot write ${filePath}\n⚠️ File content appears to be binary — refusing to write.`,
|
|
230
|
+
warnings,
|
|
231
|
+
readseekValue: {
|
|
232
|
+
tool: "write",
|
|
233
|
+
path: filePath,
|
|
234
|
+
lines: [],
|
|
235
|
+
warnings: readseekWarnings,
|
|
236
|
+
},
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
225
240
|
const previousContent = await readPreviousTextForDiff(filePath);
|
|
226
241
|
const existedBeforeWrite = await access(filePath).then(() => true, () => false);
|
|
227
242
|
|
|
@@ -240,22 +255,6 @@ export async function executeWrite(opts: {
|
|
|
240
255
|
throw err;
|
|
241
256
|
}
|
|
242
257
|
|
|
243
|
-
// Binary detection
|
|
244
|
-
if (looksLikeBinary(Buffer.from(content, "utf-8"))) {
|
|
245
|
-
warnings.push("File content appears to be binary.");
|
|
246
|
-
readseekWarnings.push(buildReadseekWarning("binary-content", "File content appears to be binary."));
|
|
247
|
-
return {
|
|
248
|
-
text: `Wrote ${filePath}\n⚠️ File content appears to be binary — hashlines not generated.`,
|
|
249
|
-
warnings,
|
|
250
|
-
readseekValue: {
|
|
251
|
-
tool: "write",
|
|
252
|
-
path: filePath,
|
|
253
|
-
lines: [],
|
|
254
|
-
warnings: readseekWarnings,
|
|
255
|
-
},
|
|
256
|
-
};
|
|
257
|
-
}
|
|
258
|
-
|
|
259
258
|
// Compute hashlines
|
|
260
259
|
const rawLines = content.split("\n");
|
|
261
260
|
const readseekLines: ReadseekLine[] = [];
|