pi-readseek 0.4.29 → 0.5.0
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 +38 -44
- package/index.ts +45 -7
- package/package.json +2 -2
- package/prompts/def.md +2 -9
- package/prompts/edit.md +13 -13
- package/prompts/grep.md +3 -3
- package/prompts/hover.md +2 -6
- package/prompts/read.md +3 -3
- package/prompts/refs.md +1 -1
- package/prompts/rename.md +3 -7
- package/prompts/sg.md +1 -1
- package/prompts/write.md +5 -5
- package/src/def.ts +12 -19
- package/src/edit-classify.ts +0 -127
- package/src/edit-output.ts +5 -11
- package/src/edit-syntax-validate.ts +4 -3
- package/src/edit.ts +21 -42
- package/src/file-map.ts +2 -2
- package/src/grep-budget.ts +7 -51
- package/src/grep-output.ts +4 -4
- package/src/grep.ts +19 -26
- package/src/hover.ts +4 -8
- package/src/read-output.ts +4 -4
- package/src/read.ts +28 -37
- package/src/readseek-client.ts +171 -145
- package/src/readseek-params.ts +1 -1
- package/src/readseek-settings.ts +138 -55
- package/src/readseek-value.ts +4 -6
- package/src/refs-output.ts +3 -3
- package/src/refs.ts +6 -10
- package/src/register-tool.ts +4 -42
- package/src/rename.ts +6 -10
- package/src/replace-symbol.ts +2 -2
- package/src/sg-output.ts +3 -3
- package/src/sg.ts +21 -25
- package/src/syntax-validate-mode.ts +3 -13
- package/src/tool-prompt-metadata.ts +12 -12
- package/src/tui-render-utils.ts +2 -2
- package/src/write.ts +50 -80
|
@@ -1,25 +1,15 @@
|
|
|
1
|
+
import { resolveReadSeekSyntaxValidation } from "./readseek-settings.js";
|
|
2
|
+
|
|
1
3
|
export type SyntaxValidateMode = "warn" | "block" | "off";
|
|
2
4
|
|
|
3
5
|
export interface SyntaxValidateOptions {
|
|
4
6
|
syntaxValidate?: SyntaxValidateMode;
|
|
5
7
|
}
|
|
6
8
|
|
|
7
|
-
const VALID = new Set<SyntaxValidateMode>(["warn", "block", "off"]);
|
|
8
9
|
const DEFAULT: SyntaxValidateMode = "warn";
|
|
9
10
|
|
|
10
|
-
function coerce(value: unknown): SyntaxValidateMode | undefined {
|
|
11
|
-
if (typeof value !== "string") return undefined;
|
|
12
|
-
return VALID.has(value as SyntaxValidateMode)
|
|
13
|
-
? (value as SyntaxValidateMode)
|
|
14
|
-
: undefined;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
11
|
export function resolveSyntaxValidateMode(
|
|
18
12
|
opts: SyntaxValidateOptions,
|
|
19
13
|
): SyntaxValidateMode {
|
|
20
|
-
|
|
21
|
-
if (fromOpt) return fromOpt;
|
|
22
|
-
const fromEnv = coerce(process.env.READSEEK_SYNTAX_VALIDATE);
|
|
23
|
-
if (fromEnv) return fromEnv;
|
|
24
|
-
return DEFAULT;
|
|
14
|
+
return opts.syntaxValidate ?? resolveReadSeekSyntaxValidation() ?? DEFAULT;
|
|
25
15
|
}
|
|
@@ -4,7 +4,7 @@ import { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, formatSize } from "@earendil-work
|
|
|
4
4
|
|
|
5
5
|
const COMPACT_DESCRIPTIONS: Record<string, string> = {
|
|
6
6
|
"read.md": "Read text files/images by path; text has LINE:HASH anchors, images return the attachment plus OCR-extracted text.",
|
|
7
|
-
"edit.md": "Edit existing text files using fresh LINE:HASH anchors from
|
|
7
|
+
"edit.md": "Edit existing text files using fresh LINE:HASH anchors from readSeek_read, readSeek_grep, readSeek_search, or readSeek_write.",
|
|
8
8
|
"grep.md": "Search file contents; non-summary results include LINE:HASH anchors for edits.",
|
|
9
9
|
|
|
10
10
|
"write.md": "Create or overwrite a complete file and return anchors.",
|
|
@@ -14,30 +14,30 @@ const COMPACT_DESCRIPTIONS: Record<string, string> = {
|
|
|
14
14
|
|
|
15
15
|
const COMPACT_GUIDELINES: Record<string, string[]> = {
|
|
16
16
|
"read.md": [
|
|
17
|
-
"Use
|
|
17
|
+
"Use readSeek_read for file contents, images/screenshots, ranges, symbols, and edit anchors.",
|
|
18
18
|
"Use map or symbol mode before pulling large code files into context.",
|
|
19
|
-
"Use
|
|
19
|
+
"Use readSeek_read for images; it returns the image attachment plus OCR-extracted text, so you don't need separate OCR tools.",
|
|
20
20
|
],
|
|
21
21
|
"edit.md": [
|
|
22
|
-
"Use
|
|
22
|
+
"Use readSeek_edit with fresh LINE:HASH anchors for existing files.",
|
|
23
23
|
"Prefer set_line, replace_lines, and insert_after; use replace only when anchors are impractical.",
|
|
24
24
|
],
|
|
25
25
|
"grep.md": [
|
|
26
|
-
"Use
|
|
27
|
-
"Use
|
|
26
|
+
"Use readSeek_grep for text search and edit-ready matching anchors.",
|
|
27
|
+
"Use readSeek_grep summary mode for broad count/file discovery before narrowing.",
|
|
28
28
|
],
|
|
29
29
|
|
|
30
30
|
"write.md": [
|
|
31
|
-
"Use
|
|
32
|
-
"Use
|
|
31
|
+
"Use readSeek_write to create files or intentionally overwrite whole files.",
|
|
32
|
+
"Use readSeek_edit rather than readSeek_write for small changes or appends to existing files.",
|
|
33
33
|
],
|
|
34
34
|
"sg.md": [
|
|
35
|
-
"Use
|
|
36
|
-
"Use
|
|
35
|
+
"Use readSeek_search for AST-shaped code patterns.",
|
|
36
|
+
"Use readSeek_grep instead of readSeek_search for plain text.",
|
|
37
37
|
],
|
|
38
38
|
"refs.md": [
|
|
39
|
-
"Use
|
|
40
|
-
"Use
|
|
39
|
+
"Use readSeek_refs to find every usage of an identifier before renaming or deleting it.",
|
|
40
|
+
"Use readSeek_refs with scope plus line/column to follow a specific binding instead of every same-named identifier.",
|
|
41
41
|
],
|
|
42
42
|
};
|
|
43
43
|
|
package/src/tui-render-utils.ts
CHANGED
|
@@ -238,10 +238,10 @@ export function renderAnchoredFilesResult(
|
|
|
238
238
|
const textContent = content?.type === "text" ? content.text : "";
|
|
239
239
|
if (isError || result.isError) return renderErrorResult(textContent, { expanded, width });
|
|
240
240
|
|
|
241
|
-
const
|
|
241
|
+
const readSeekValue = (result.details as any)?.readSeekValue as
|
|
242
242
|
| { files: Array<{ path: string; lines: any[] }> }
|
|
243
243
|
| undefined;
|
|
244
|
-
const files =
|
|
244
|
+
const files = readSeekValue?.files ?? [];
|
|
245
245
|
if (files.length === 0) return new Text(summaryLine(labels.emptyLabel), 0, 0);
|
|
246
246
|
|
|
247
247
|
const fileCount = files.length;
|
package/src/write.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { access, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
2
|
import { dirname, relative } from "node:path";
|
|
3
3
|
|
|
4
|
-
import { withFileMutationQueue, type ExtensionAPI, type ToolRenderResultOptions } from "@earendil-works/pi-coding-agent";
|
|
4
|
+
import { withFileMutationQueue, truncateHead, type ExtensionAPI, type ToolRenderResultOptions } from "@earendil-works/pi-coding-agent";
|
|
5
5
|
import { Text } from "@earendil-works/pi-tui";
|
|
6
6
|
import { Type } from "@sinclair/typebox";
|
|
7
7
|
|
|
8
8
|
import { resolveToCwd } from "./path-utils.js";
|
|
9
9
|
import { ensureHashInit, formatHashlineDisplay } from "./hashline.js";
|
|
10
|
-
import {
|
|
10
|
+
import { buildReadSeekLine, buildReadSeekWarning, buildToolErrorResult, type ReadSeekLine, type ReadSeekWarning, type ToolErrorResult } from "./readseek-value.js";
|
|
11
11
|
import { looksLikeBinary } from "./binary-detect.js";
|
|
12
12
|
import { formatFsError } from "./fs-error.js";
|
|
13
13
|
import { getOrGenerateMap } from "./file-map.js";
|
|
@@ -66,8 +66,6 @@ function pendingWritePreviewParts(summary: string, preview: PendingDiffPreviewRe
|
|
|
66
66
|
return { lines: [summary, headerLine], diffData: expanded ? diffData : undefined };
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
const MAX_LINES = 2000;
|
|
70
|
-
const MAX_BYTES = 50 * 1024;
|
|
71
69
|
const WRITE_PROMPT_METADATA = defineToolPromptMetadata({
|
|
72
70
|
promptUrl: new URL("../prompts/write.md", import.meta.url),
|
|
73
71
|
promptSnippet: "Create or overwrite a complete file and return edit anchors",
|
|
@@ -82,7 +80,7 @@ export interface WriteResult extends WriteDiffFields {
|
|
|
82
80
|
text: string;
|
|
83
81
|
warnings: string[];
|
|
84
82
|
writeState?: "created" | "overwritten";
|
|
85
|
-
|
|
83
|
+
readSeekValue: {
|
|
86
84
|
tool: "write";
|
|
87
85
|
path: string;
|
|
88
86
|
lines: ReadSeekLine[];
|
|
@@ -93,6 +91,13 @@ export interface WriteResult extends WriteDiffFields {
|
|
|
93
91
|
};
|
|
94
92
|
}
|
|
95
93
|
|
|
94
|
+
|
|
95
|
+
type ExecuteWriteResult = WriteResult | ToolErrorResult;
|
|
96
|
+
|
|
97
|
+
function isToolErrorResult(result: ExecuteWriteResult): result is ToolErrorResult {
|
|
98
|
+
return "isError" in result;
|
|
99
|
+
}
|
|
100
|
+
|
|
96
101
|
async function readPreviousTextForDiff(filePath: string): Promise<string> {
|
|
97
102
|
try {
|
|
98
103
|
const previous = await readFile(filePath);
|
|
@@ -133,41 +138,30 @@ export async function executeWrite(opts: {
|
|
|
133
138
|
content: string;
|
|
134
139
|
map?: boolean;
|
|
135
140
|
cwd?: string;
|
|
136
|
-
}): Promise<
|
|
141
|
+
}): Promise<ExecuteWriteResult> {
|
|
137
142
|
await ensureHashInit();
|
|
138
143
|
|
|
139
144
|
const { path: filePath, content, map: requestMap, cwd } = opts;
|
|
140
145
|
const warnings: string[] = [];
|
|
141
|
-
const
|
|
146
|
+
const readSeekWarnings: ReadSeekWarning[] = [];
|
|
142
147
|
|
|
143
148
|
if (hasBareCarriageReturn(content)) {
|
|
144
|
-
const message = "File content contains bare CR (\\r) line endings;
|
|
149
|
+
const message = "File content contains bare CR (\\r) line endings; readSeek_write refuses to emit anchors that readSeek_read/readSeek_edit would normalize differently.";
|
|
145
150
|
warnings.push(message);
|
|
146
|
-
|
|
147
|
-
return {
|
|
148
|
-
|
|
149
|
-
warnings,
|
|
150
|
-
|
|
151
|
-
tool: "write",
|
|
152
|
-
path: filePath,
|
|
153
|
-
lines: [],
|
|
154
|
-
warnings: readseekWarnings,
|
|
155
|
-
},
|
|
156
|
-
};
|
|
151
|
+
readSeekWarnings.push(buildReadSeekWarning("bare-cr", message));
|
|
152
|
+
return buildToolErrorResult("write", "bare-cr", `Cannot write ${filePath}\n⚠️ ${message}`, {
|
|
153
|
+
path: filePath,
|
|
154
|
+
extra: { lines: [], warnings: readSeekWarnings },
|
|
155
|
+
});
|
|
157
156
|
}
|
|
158
157
|
if (looksLikeBinary(Buffer.from(content, "utf-8"))) {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
path: filePath,
|
|
167
|
-
lines: [],
|
|
168
|
-
warnings: readseekWarnings,
|
|
169
|
-
},
|
|
170
|
-
};
|
|
158
|
+
const message = "File content appears to be binary.";
|
|
159
|
+
warnings.push(message);
|
|
160
|
+
readSeekWarnings.push(buildReadSeekWarning("binary-content", message));
|
|
161
|
+
return buildToolErrorResult("write", "binary-content", `Cannot write ${filePath}\n⚠️ ${message} — refusing to write.`, {
|
|
162
|
+
path: filePath,
|
|
163
|
+
extra: { lines: [], warnings: readSeekWarnings },
|
|
164
|
+
});
|
|
171
165
|
}
|
|
172
166
|
|
|
173
167
|
const previousContent = await readPreviousTextForDiff(filePath);
|
|
@@ -179,25 +173,25 @@ export async function executeWrite(opts: {
|
|
|
179
173
|
await writeFile(filePath, content, "utf-8");
|
|
180
174
|
// Compute hashlines
|
|
181
175
|
const rawLines = content.split("\n");
|
|
182
|
-
const
|
|
176
|
+
const readSeekLines: ReadSeekLine[] = [];
|
|
183
177
|
const displayLines: string[] = [];
|
|
184
178
|
|
|
185
179
|
for (let i = 0; i < rawLines.length; i++) {
|
|
186
180
|
const lineNum = i + 1;
|
|
187
|
-
const
|
|
188
|
-
|
|
181
|
+
const readSeekLine = buildReadSeekLine(lineNum, rawLines[i]);
|
|
182
|
+
readSeekLines.push(readSeekLine);
|
|
189
183
|
displayLines.push(formatHashlineDisplay(lineNum, rawLines[i]));
|
|
190
184
|
}
|
|
191
185
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
186
|
+
const fullText = displayLines.join("\n");
|
|
187
|
+
const truncated = truncateHead(fullText);
|
|
188
|
+
let text = truncated.content;
|
|
189
|
+
if (truncated.truncated) {
|
|
190
|
+
if (truncated.truncatedBy === "lines") {
|
|
191
|
+
text += `\n[… ${truncated.totalLines - truncated.outputLines} more lines not shown — full anchors in readSeekValue]`;
|
|
192
|
+
} else {
|
|
193
|
+
text += "\n[… output truncated at 50 KB — full anchors in readSeekValue]";
|
|
194
|
+
}
|
|
201
195
|
}
|
|
202
196
|
|
|
203
197
|
// Optional structural map
|
|
@@ -234,11 +228,11 @@ export async function executeWrite(opts: {
|
|
|
234
228
|
writeState: existedBeforeWrite ? "overwritten" : "created",
|
|
235
229
|
diff: diffResult.diff,
|
|
236
230
|
diffData,
|
|
237
|
-
|
|
231
|
+
readSeekValue: {
|
|
238
232
|
tool: "write",
|
|
239
233
|
path: displayPath,
|
|
240
|
-
lines:
|
|
241
|
-
warnings:
|
|
234
|
+
lines: readSeekLines,
|
|
235
|
+
warnings: readSeekWarnings,
|
|
242
236
|
diff: diffResult.diff,
|
|
243
237
|
diffData,
|
|
244
238
|
...(requestMap ? { map: { appended: mapAppended } } : {}),
|
|
@@ -248,11 +242,7 @@ export async function executeWrite(opts: {
|
|
|
248
242
|
|
|
249
243
|
export function registerWriteTool(pi: ExtensionAPI, options: WriteToolOptions = {}) {
|
|
250
244
|
const tool = registerReadSeekTool(pi, {
|
|
251
|
-
|
|
252
|
-
pythonName: "write",
|
|
253
|
-
defaultExposure: "not-safe-by-default",
|
|
254
|
-
}, {
|
|
255
|
-
name: "write",
|
|
245
|
+
name: "readSeek_write",
|
|
256
246
|
label: "write",
|
|
257
247
|
description: WRITE_PROMPT_METADATA.description,
|
|
258
248
|
promptSnippet: WRITE_PROMPT_METADATA.promptSnippet,
|
|
@@ -267,7 +257,7 @@ export function registerWriteTool(pi: ExtensionAPI, options: WriteToolOptions =
|
|
|
267
257
|
const absolutePath = resolveToCwd(params.path, cwd);
|
|
268
258
|
try {
|
|
269
259
|
return await withFileMutationQueue(absolutePath, async () => {
|
|
270
|
-
let result:
|
|
260
|
+
let result: ExecuteWriteResult;
|
|
271
261
|
try {
|
|
272
262
|
result = await executeWrite({
|
|
273
263
|
path: absolutePath,
|
|
@@ -279,30 +269,10 @@ export function registerWriteTool(pi: ExtensionAPI, options: WriteToolOptions =
|
|
|
279
269
|
return buildWriteFsErrorResult(err, absolutePath);
|
|
280
270
|
}
|
|
281
271
|
|
|
282
|
-
if (result
|
|
283
|
-
options.onFileAnchored?.(absolutePath);
|
|
284
|
-
}
|
|
272
|
+
if (isToolErrorResult(result)) return result;
|
|
285
273
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
// The existing ReadSeekWarning entry is preserved on readseekValue.warnings for
|
|
289
|
-
// backward compatibility.
|
|
290
|
-
for (const code of ["binary-content", "bare-cr"] as const) {
|
|
291
|
-
const warning = result.readseekValue.warnings.find((w) => w.code === code);
|
|
292
|
-
if (warning) {
|
|
293
|
-
return {
|
|
294
|
-
content: [{ type: "text" as const, text: result.text }],
|
|
295
|
-
isError: true,
|
|
296
|
-
details: {
|
|
297
|
-
readseekValue: {
|
|
298
|
-
...result.readseekValue,
|
|
299
|
-
ok: false,
|
|
300
|
-
error: buildReadSeekError(code, warning.message),
|
|
301
|
-
},
|
|
302
|
-
warnings: result.warnings,
|
|
303
|
-
},
|
|
304
|
-
};
|
|
305
|
-
}
|
|
274
|
+
if (result.readSeekValue.lines.length > 0) {
|
|
275
|
+
options.onFileAnchored?.(absolutePath);
|
|
306
276
|
}
|
|
307
277
|
|
|
308
278
|
return {
|
|
@@ -311,7 +281,7 @@ export function registerWriteTool(pi: ExtensionAPI, options: WriteToolOptions =
|
|
|
311
281
|
...(result.diff !== undefined ? { diff: result.diff } : {}),
|
|
312
282
|
...(result.diffData !== undefined ? { diffData: result.diffData } : {}),
|
|
313
283
|
...(result.writeState ? { writeState: result.writeState } : {}),
|
|
314
|
-
|
|
284
|
+
readSeekValue: result.readSeekValue,
|
|
315
285
|
warnings: result.warnings,
|
|
316
286
|
},
|
|
317
287
|
};
|
|
@@ -352,7 +322,7 @@ export function registerWriteTool(pi: ExtensionAPI, options: WriteToolOptions =
|
|
|
352
322
|
if (isPartial) return renderPendingResult("pending write", width);
|
|
353
323
|
const details = result.details ?? {};
|
|
354
324
|
const output = result.content?.[0]?.type === "text" ? result.content[0].text : "";
|
|
355
|
-
if (result.isError || details.
|
|
325
|
+
if (result.isError || details.readSeekValue?.ok === false) {
|
|
356
326
|
return renderErrorResult(output, { expanded, width, fallback: "write failed" });
|
|
357
327
|
}
|
|
358
328
|
const diffData = details.diffData;
|
|
@@ -361,12 +331,12 @@ export function registerWriteTool(pi: ExtensionAPI, options: WriteToolOptions =
|
|
|
361
331
|
// instead of a diff body — every line is an add, so the gutter, line
|
|
362
332
|
// numbers, and red/green tinting are noise.
|
|
363
333
|
if (state === "created") {
|
|
364
|
-
const
|
|
365
|
-
const hasContent =
|
|
334
|
+
const readSeekLines = (details.readSeekValue?.lines ?? []) as Array<{ raw: string }>;
|
|
335
|
+
const hasContent = readSeekLines.length > 0;
|
|
366
336
|
const header = summaryLine(state, { hidden: hasContent && !expanded });
|
|
367
337
|
const lines = header.split("\n");
|
|
368
338
|
if (expanded && hasContent) {
|
|
369
|
-
const content =
|
|
339
|
+
const content = readSeekLines.map((l) => l.raw).join("\n");
|
|
370
340
|
lines.push(...formatContentPreviewLines(content, theme));
|
|
371
341
|
}
|
|
372
342
|
return new Text(clampLinesToWidth(lines, width).join("\n"), 0, 0);
|