pi-readseek 0.4.30 → 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-output.ts +3 -3
- package/src/edit-syntax-validate.ts +3 -3
- package/src/edit.ts +18 -25
- 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 +25 -34
- package/src/readseek-client.ts +111 -79
- package/src/readseek-params.ts +1 -1
- package/src/readseek-settings.ts +138 -55
- package/src/readseek-value.ts +4 -4
- 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 +22 -26
package/src/refs.ts
CHANGED
|
@@ -5,10 +5,10 @@ import { defineToolPromptMetadata } from "./tool-prompt-metadata.js";
|
|
|
5
5
|
import { statSearchPathOrError } from "./stat-search-path.js";
|
|
6
6
|
import { buildReadSeekLineWithHash, buildToolErrorResult } from "./readseek-value.js";
|
|
7
7
|
import { resolveToCwd } from "./path-utils.js";
|
|
8
|
-
import { classifyReadSeekFailure,
|
|
8
|
+
import { classifyReadSeekFailure, readSeekRefs, type ReadSeekReference } from "./readseek-client.js";
|
|
9
9
|
import { buildRefsOutput, type RefsOutputFile, type RefsOutputLine } from "./refs-output.js";
|
|
10
10
|
import type { FileAnchoredCallback } from "./tool-types.js";
|
|
11
|
-
import { langParam,
|
|
11
|
+
import { langParam, readSeekGitSearchParams, searchPathParam, validateIgnoredRequiresOthers } from "./readseek-params.js";
|
|
12
12
|
import { registerReadSeekTool } from "./register-tool.js";
|
|
13
13
|
|
|
14
14
|
import { renderAnchoredFilesResult, renderReadSeekSearchCall } from "./tui-render-utils.js";
|
|
@@ -94,7 +94,7 @@ export async function executeRefs(opts: ExecuteRefsOptions): Promise<any> {
|
|
|
94
94
|
if (!statResult.ok) return statResult.error;
|
|
95
95
|
|
|
96
96
|
try {
|
|
97
|
-
const references = await
|
|
97
|
+
const references = await readSeekRefs(searchPath, p.name, {
|
|
98
98
|
scope: p.scope,
|
|
99
99
|
line: p.line,
|
|
100
100
|
column: p.column,
|
|
@@ -111,7 +111,7 @@ export async function executeRefs(opts: ExecuteRefsOptions): Promise<any> {
|
|
|
111
111
|
}
|
|
112
112
|
return {
|
|
113
113
|
content: [{ type: "text", text: builtOutput.text }],
|
|
114
|
-
details: {
|
|
114
|
+
details: { readSeekValue: builtOutput.readSeekValue },
|
|
115
115
|
};
|
|
116
116
|
} catch (err: any) {
|
|
117
117
|
const failure = classifyReadSeekFailure(err);
|
|
@@ -124,11 +124,7 @@ export async function executeRefs(opts: ExecuteRefsOptions): Promise<any> {
|
|
|
124
124
|
|
|
125
125
|
export function registerRefsTool(pi: ExtensionAPI, options: RefsToolOptions = {}) {
|
|
126
126
|
const tool = registerReadSeekTool(pi, {
|
|
127
|
-
|
|
128
|
-
pythonName: "refs",
|
|
129
|
-
defaultExposure: "opt-in",
|
|
130
|
-
}, {
|
|
131
|
-
name: "refs",
|
|
127
|
+
name: "readSeek_refs",
|
|
132
128
|
label: "References",
|
|
133
129
|
description: REFS_PROMPT_METADATA.description,
|
|
134
130
|
promptSnippet: REFS_PROMPT_METADATA.promptSnippet,
|
|
@@ -140,7 +136,7 @@ export function registerRefsTool(pi: ExtensionAPI, options: RefsToolOptions = {}
|
|
|
140
136
|
scope: Type.Optional(Type.Boolean({ description: "Restrict to the binding under line/column (single file)" })),
|
|
141
137
|
line: Type.Optional(Type.Number({ description: "One-based cursor line, used with scope" })),
|
|
142
138
|
column: Type.Optional(Type.Number({ description: "One-based cursor byte column, used with scope" })),
|
|
143
|
-
...
|
|
139
|
+
...readSeekGitSearchParams(),
|
|
144
140
|
}),
|
|
145
141
|
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
146
142
|
return executeRefs({ params, signal, cwd: ctx.cwd, onFileAnchored: options.onFileAnchored });
|
package/src/register-tool.ts
CHANGED
|
@@ -20,48 +20,10 @@ export function mapParam() {
|
|
|
20
20
|
return Type.Optional(Type.Boolean({ description: "Append structural map" }));
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
export type ReadSeekToolPolicy = "read-only" | "mutating";
|
|
24
|
-
|
|
25
|
-
export type ReadSeekToolExposure = "safe-by-default" | "opt-in" | "not-safe-by-default";
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Per-tool fields of the pi tool config (`ptc`) that genuinely differ between
|
|
29
|
-
* readseek tools. The remaining fields are constant or derived.
|
|
30
|
-
*/
|
|
31
|
-
export interface ReadSeekToolConfig {
|
|
32
|
-
policy: ReadSeekToolPolicy;
|
|
33
|
-
pythonName: string;
|
|
34
|
-
defaultExposure: ReadSeekToolExposure;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export interface ReadSeekToolPtc extends ReadSeekToolConfig {
|
|
38
|
-
callable: true;
|
|
39
|
-
enabled: true;
|
|
40
|
-
readOnly: boolean;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
23
|
type ToolSpec = Parameters<ExtensionAPI["registerTool"]>[0];
|
|
44
24
|
|
|
45
|
-
/**
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
* `policy`, so callers supply only the fields that vary between tools.
|
|
50
|
-
*/
|
|
51
|
-
export function registerReadSeekTool<T extends ToolSpec>(
|
|
52
|
-
pi: ExtensionAPI,
|
|
53
|
-
config: ReadSeekToolConfig,
|
|
54
|
-
tool: T,
|
|
55
|
-
): T & { ptc: ReadSeekToolPtc } {
|
|
56
|
-
const ptc: ReadSeekToolPtc = {
|
|
57
|
-
callable: true,
|
|
58
|
-
enabled: true,
|
|
59
|
-
policy: config.policy,
|
|
60
|
-
readOnly: config.policy === "read-only",
|
|
61
|
-
pythonName: config.pythonName,
|
|
62
|
-
defaultExposure: config.defaultExposure,
|
|
63
|
-
};
|
|
64
|
-
const registered = { ...tool, ptc };
|
|
65
|
-
pi.registerTool(registered);
|
|
66
|
-
return registered;
|
|
25
|
+
/** Register a readseek-backed tool definition. */
|
|
26
|
+
export function registerReadSeekTool<T extends ToolSpec>(pi: ExtensionAPI, tool: T): T {
|
|
27
|
+
pi.registerTool(tool);
|
|
28
|
+
return tool;
|
|
67
29
|
}
|
package/src/rename.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { Text } from "@earendil-works/pi-tui";
|
|
|
6
6
|
import { defineToolPromptMetadata } from "./tool-prompt-metadata.js";
|
|
7
7
|
import { buildToolErrorResult } from "./readseek-value.js";
|
|
8
8
|
import { resolveToCwd } from "./path-utils.js";
|
|
9
|
-
import { classifyReadSeekFailure,
|
|
9
|
+
import { classifyReadSeekFailure, readSeekRename, type RenameOutput } from "./readseek-client.js";
|
|
10
10
|
import { filePathParam, registerReadSeekTool } from "./register-tool.js";
|
|
11
11
|
|
|
12
12
|
import { clampLinesToWidth, linkToolPath, renderPendingResult, resolveRenderResultContext, summaryLine } from "./tui-render-utils.js";
|
|
@@ -54,7 +54,7 @@ export async function executeRename(opts: ExecuteRenameOptions): Promise<any> {
|
|
|
54
54
|
const filePath = resolveToCwd(p.path, cwd);
|
|
55
55
|
|
|
56
56
|
try {
|
|
57
|
-
const output = await
|
|
57
|
+
const output = await readSeekRename(filePath, {
|
|
58
58
|
to: p.to,
|
|
59
59
|
line: p.line,
|
|
60
60
|
column: p.column,
|
|
@@ -92,7 +92,7 @@ export async function executeRename(opts: ExecuteRenameOptions): Promise<any> {
|
|
|
92
92
|
return {
|
|
93
93
|
content: [{ type: "text", text }],
|
|
94
94
|
details: {
|
|
95
|
-
|
|
95
|
+
readSeekValue: {
|
|
96
96
|
tool: "rename",
|
|
97
97
|
ok: true,
|
|
98
98
|
path: filePath,
|
|
@@ -108,11 +108,7 @@ export async function executeRename(opts: ExecuteRenameOptions): Promise<any> {
|
|
|
108
108
|
|
|
109
109
|
export function registerRenameTool(pi: ExtensionAPI) {
|
|
110
110
|
registerReadSeekTool(pi, {
|
|
111
|
-
|
|
112
|
-
pythonName: "rename",
|
|
113
|
-
defaultExposure: "not-safe-by-default",
|
|
114
|
-
}, {
|
|
115
|
-
name: "rename",
|
|
111
|
+
name: "readSeek_rename",
|
|
116
112
|
label: "Rename",
|
|
117
113
|
description: RENAME_PROMPT_METADATA.description,
|
|
118
114
|
promptSnippet: RENAME_PROMPT_METADATA.promptSnippet,
|
|
@@ -138,8 +134,8 @@ export function registerRenameTool(pi: ExtensionAPI) {
|
|
|
138
134
|
|
|
139
135
|
const content = result.content?.[0];
|
|
140
136
|
const textContent = content?.type === "text" ? content.text : "";
|
|
141
|
-
const
|
|
142
|
-
const output =
|
|
137
|
+
const readSeekValue = (result.details as any)?.readSeekValue;
|
|
138
|
+
const output = readSeekValue?.output as RenameOutput | undefined;
|
|
143
139
|
|
|
144
140
|
if (isError || result.isError) {
|
|
145
141
|
return new Text(textContent || "rename failed", 0, 0);
|
package/src/replace-symbol.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readSeekMapContent } from "./readseek-client.js";
|
|
2
2
|
import { findSymbol } from "./readseek/symbol-lookup.js";
|
|
3
3
|
import { formatAmbiguous, formatNotFound } from "./readseek/symbol-error-format.js";
|
|
4
4
|
import { normalizeToLF } from "./edit-diff.js";
|
|
@@ -33,7 +33,7 @@ function reindent(text: string, indent: string): string {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
export async function replaceSymbol(input: ReplaceSymbolInput): Promise<ReplaceSymbolResult> {
|
|
36
|
-
const map = await
|
|
36
|
+
const map = await readSeekMapContent(input.filePath, input.content);
|
|
37
37
|
if (!map) {
|
|
38
38
|
return {
|
|
39
39
|
type: "unsupported",
|
package/src/sg-output.ts
CHANGED
|
@@ -15,7 +15,7 @@ export interface BuildSgOutputInput {
|
|
|
15
15
|
|
|
16
16
|
export interface SgOutputResult {
|
|
17
17
|
text: string;
|
|
18
|
-
|
|
18
|
+
readSeekValue: {
|
|
19
19
|
tool: "search";
|
|
20
20
|
files: Array<{
|
|
21
21
|
path: string;
|
|
@@ -29,7 +29,7 @@ export function buildSgOutput(input: BuildSgOutputInput): SgOutputResult {
|
|
|
29
29
|
if (input.files.length === 0) {
|
|
30
30
|
return {
|
|
31
31
|
text: `No matches found for pattern: ${input.pattern}`,
|
|
32
|
-
|
|
32
|
+
readSeekValue: {
|
|
33
33
|
tool: "search",
|
|
34
34
|
files: [],
|
|
35
35
|
},
|
|
@@ -38,7 +38,7 @@ export function buildSgOutput(input: BuildSgOutputInput): SgOutputResult {
|
|
|
38
38
|
|
|
39
39
|
return {
|
|
40
40
|
text: formatAnchoredFileBlocks(input.files),
|
|
41
|
-
|
|
41
|
+
readSeekValue: {
|
|
42
42
|
tool: "search",
|
|
43
43
|
files: input.files.map((file) => ({
|
|
44
44
|
path: file.path,
|
package/src/sg.ts
CHANGED
|
@@ -7,10 +7,10 @@ import { defineToolPromptMetadata } from "./tool-prompt-metadata.js";
|
|
|
7
7
|
import { buildReadSeekLineWithHash, buildToolErrorResult, type ReadSeekLine } from "./readseek-value.js";
|
|
8
8
|
import { resolveToCwd } from "./path-utils.js";
|
|
9
9
|
import { statSearchPathOrError } from "./stat-search-path.js";
|
|
10
|
-
import { classifyReadSeekFailure,
|
|
10
|
+
import { classifyReadSeekFailure, readSeekSearch, type ReadSeekHashline, type ReadSeekSearchFileOutput } from "./readseek-client.js";
|
|
11
11
|
import { buildSgOutput } from "./sg-output.js";
|
|
12
12
|
import type { FileAnchoredCallback } from "./tool-types.js";
|
|
13
|
-
import { langParam,
|
|
13
|
+
import { langParam, readSeekGitSearchParams, searchPathParam, validateIgnoredRequiresOthers } from "./readseek-params.js";
|
|
14
14
|
import { registerReadSeekTool } from "./register-tool.js";
|
|
15
15
|
|
|
16
16
|
import { renderAnchoredFilesResult, renderReadSeekSearchCall } from "./tui-render-utils.js";
|
|
@@ -66,7 +66,7 @@ export interface ExecuteSgOptions {
|
|
|
66
66
|
onFileAnchored?: FileAnchoredCallback;
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
function
|
|
69
|
+
function readSeekLineFromSearch(line: ReadSeekHashline): ReadSeekLine {
|
|
70
70
|
return buildReadSeekLineWithHash(line.line, line.hash, line.text);
|
|
71
71
|
}
|
|
72
72
|
|
|
@@ -74,7 +74,7 @@ function linesFromSearchResult(result: ReadSeekSearchFileOutput, ranges: SgRange
|
|
|
74
74
|
const lineMap = new Map<number, ReadSeekLine>();
|
|
75
75
|
for (const match of result.matches) {
|
|
76
76
|
for (const line of match.hashlines) {
|
|
77
|
-
lineMap.set(line.line,
|
|
77
|
+
lineMap.set(line.line, readSeekLineFromSearch(line));
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
80
|
|
|
@@ -83,16 +83,16 @@ function linesFromSearchResult(result: ReadSeekSearchFileOutput, ranges: SgRange
|
|
|
83
83
|
for (const range of ranges) {
|
|
84
84
|
for (let line = range.startLine; line <= range.endLine; line++) {
|
|
85
85
|
if (seen.has(line)) continue;
|
|
86
|
-
const
|
|
87
|
-
if (!
|
|
86
|
+
const readSeekLine = lineMap.get(line);
|
|
87
|
+
if (!readSeekLine) continue;
|
|
88
88
|
seen.add(line);
|
|
89
|
-
lines.push(
|
|
89
|
+
lines.push(readSeekLine);
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
92
|
return lines;
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
-
function
|
|
95
|
+
function readSeekLanguageForPath(language: string | undefined, searchPath: string, isFile: boolean): string | undefined {
|
|
96
96
|
if (language === "typescript" && isFile && path.extname(searchPath).toLowerCase() === ".tsx") return "tsx";
|
|
97
97
|
return language;
|
|
98
98
|
}
|
|
@@ -112,8 +112,8 @@ export async function executeSg(opts: ExecuteSgOptions): Promise<any> {
|
|
|
112
112
|
const searchPathIsFile = statResult.stats.isFile();
|
|
113
113
|
|
|
114
114
|
try {
|
|
115
|
-
const effectiveLang =
|
|
116
|
-
const results = await
|
|
115
|
+
const effectiveLang = readSeekLanguageForPath(p.lang, searchPath, searchPathIsFile);
|
|
116
|
+
const results = await readSeekSearch(searchPath, p.pattern, {
|
|
117
117
|
language: effectiveLang,
|
|
118
118
|
cached: p.cached,
|
|
119
119
|
others: p.others,
|
|
@@ -125,12 +125,12 @@ export async function executeSg(opts: ExecuteSgOptions): Promise<any> {
|
|
|
125
125
|
return {
|
|
126
126
|
content: [{ type: "text", text: emptyOutput.text }],
|
|
127
127
|
details: {
|
|
128
|
-
|
|
128
|
+
readSeekValue: emptyOutput.readSeekValue,
|
|
129
129
|
},
|
|
130
130
|
};
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
-
const
|
|
133
|
+
const readSeekFiles: Array<{
|
|
134
134
|
displayPath: string;
|
|
135
135
|
path: string;
|
|
136
136
|
ranges: SgRange[];
|
|
@@ -145,7 +145,7 @@ export async function executeSg(opts: ExecuteSgOptions): Promise<any> {
|
|
|
145
145
|
const mergedRanges = mergeRanges(ranges);
|
|
146
146
|
const lines = linesFromSearchResult(result, mergedRanges);
|
|
147
147
|
if (lines.length === 0) continue;
|
|
148
|
-
|
|
148
|
+
readSeekFiles.push({
|
|
149
149
|
displayPath: display,
|
|
150
150
|
path: abs,
|
|
151
151
|
ranges: mergedRanges.map((range) => ({ ...range })),
|
|
@@ -153,27 +153,27 @@ export async function executeSg(opts: ExecuteSgOptions): Promise<any> {
|
|
|
153
153
|
});
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
-
if (
|
|
156
|
+
if (readSeekFiles.length === 0) {
|
|
157
157
|
const emptyOutput = buildSgOutput({ pattern: p.pattern, files: [] });
|
|
158
158
|
return {
|
|
159
159
|
content: [{ type: "text", text: emptyOutput.text }],
|
|
160
160
|
details: {
|
|
161
|
-
|
|
161
|
+
readSeekValue: emptyOutput.readSeekValue,
|
|
162
162
|
},
|
|
163
163
|
};
|
|
164
164
|
}
|
|
165
165
|
|
|
166
166
|
const builtOutput = buildSgOutput({
|
|
167
167
|
pattern: p.pattern,
|
|
168
|
-
files:
|
|
168
|
+
files: readSeekFiles,
|
|
169
169
|
});
|
|
170
|
-
for (const
|
|
171
|
-
onFileAnchored?.(
|
|
170
|
+
for (const readSeekFile of readSeekFiles) {
|
|
171
|
+
onFileAnchored?.(readSeekFile.path);
|
|
172
172
|
}
|
|
173
173
|
return {
|
|
174
174
|
content: [{ type: "text", text: builtOutput.text }],
|
|
175
175
|
details: {
|
|
176
|
-
|
|
176
|
+
readSeekValue: builtOutput.readSeekValue,
|
|
177
177
|
},
|
|
178
178
|
};
|
|
179
179
|
} catch (err: any) {
|
|
@@ -184,11 +184,7 @@ export async function executeSg(opts: ExecuteSgOptions): Promise<any> {
|
|
|
184
184
|
|
|
185
185
|
export function registerSgTool(pi: ExtensionAPI, options: SgToolOptions = {}) {
|
|
186
186
|
const tool = registerReadSeekTool(pi, {
|
|
187
|
-
|
|
188
|
-
pythonName: "search",
|
|
189
|
-
defaultExposure: "opt-in",
|
|
190
|
-
}, {
|
|
191
|
-
name: "search",
|
|
187
|
+
name: "readSeek_search",
|
|
192
188
|
label: "Structural Search",
|
|
193
189
|
description: SG_PROMPT_METADATA.description,
|
|
194
190
|
promptSnippet: SG_PROMPT_METADATA.promptSnippet,
|
|
@@ -197,7 +193,7 @@ export function registerSgTool(pi: ExtensionAPI, options: SgToolOptions = {}) {
|
|
|
197
193
|
pattern: Type.String({ description: "AST pattern" }),
|
|
198
194
|
lang: langParam(),
|
|
199
195
|
path: searchPathParam(),
|
|
200
|
-
...
|
|
196
|
+
...readSeekGitSearchParams(),
|
|
201
197
|
}),
|
|
202
198
|
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
203
199
|
return executeSg({ params, signal, cwd: ctx.cwd, onFileAnchored: options.onFileAnchored });
|
|
@@ -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
|
@@ -80,7 +80,7 @@ export interface WriteResult extends WriteDiffFields {
|
|
|
80
80
|
text: string;
|
|
81
81
|
warnings: string[];
|
|
82
82
|
writeState?: "created" | "overwritten";
|
|
83
|
-
|
|
83
|
+
readSeekValue: {
|
|
84
84
|
tool: "write";
|
|
85
85
|
path: string;
|
|
86
86
|
lines: ReadSeekLine[];
|
|
@@ -143,24 +143,24 @@ export async function executeWrite(opts: {
|
|
|
143
143
|
|
|
144
144
|
const { path: filePath, content, map: requestMap, cwd } = opts;
|
|
145
145
|
const warnings: string[] = [];
|
|
146
|
-
const
|
|
146
|
+
const readSeekWarnings: ReadSeekWarning[] = [];
|
|
147
147
|
|
|
148
148
|
if (hasBareCarriageReturn(content)) {
|
|
149
|
-
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.";
|
|
150
150
|
warnings.push(message);
|
|
151
|
-
|
|
151
|
+
readSeekWarnings.push(buildReadSeekWarning("bare-cr", message));
|
|
152
152
|
return buildToolErrorResult("write", "bare-cr", `Cannot write ${filePath}\n⚠️ ${message}`, {
|
|
153
153
|
path: filePath,
|
|
154
|
-
extra: { lines: [], warnings:
|
|
154
|
+
extra: { lines: [], warnings: readSeekWarnings },
|
|
155
155
|
});
|
|
156
156
|
}
|
|
157
157
|
if (looksLikeBinary(Buffer.from(content, "utf-8"))) {
|
|
158
158
|
const message = "File content appears to be binary.";
|
|
159
159
|
warnings.push(message);
|
|
160
|
-
|
|
160
|
+
readSeekWarnings.push(buildReadSeekWarning("binary-content", message));
|
|
161
161
|
return buildToolErrorResult("write", "binary-content", `Cannot write ${filePath}\n⚠️ ${message} — refusing to write.`, {
|
|
162
162
|
path: filePath,
|
|
163
|
-
extra: { lines: [], warnings:
|
|
163
|
+
extra: { lines: [], warnings: readSeekWarnings },
|
|
164
164
|
});
|
|
165
165
|
}
|
|
166
166
|
|
|
@@ -173,13 +173,13 @@ export async function executeWrite(opts: {
|
|
|
173
173
|
await writeFile(filePath, content, "utf-8");
|
|
174
174
|
// Compute hashlines
|
|
175
175
|
const rawLines = content.split("\n");
|
|
176
|
-
const
|
|
176
|
+
const readSeekLines: ReadSeekLine[] = [];
|
|
177
177
|
const displayLines: string[] = [];
|
|
178
178
|
|
|
179
179
|
for (let i = 0; i < rawLines.length; i++) {
|
|
180
180
|
const lineNum = i + 1;
|
|
181
|
-
const
|
|
182
|
-
|
|
181
|
+
const readSeekLine = buildReadSeekLine(lineNum, rawLines[i]);
|
|
182
|
+
readSeekLines.push(readSeekLine);
|
|
183
183
|
displayLines.push(formatHashlineDisplay(lineNum, rawLines[i]));
|
|
184
184
|
}
|
|
185
185
|
|
|
@@ -188,9 +188,9 @@ export async function executeWrite(opts: {
|
|
|
188
188
|
let text = truncated.content;
|
|
189
189
|
if (truncated.truncated) {
|
|
190
190
|
if (truncated.truncatedBy === "lines") {
|
|
191
|
-
text += `\n[… ${truncated.totalLines - truncated.outputLines} more lines not shown — full anchors in
|
|
191
|
+
text += `\n[… ${truncated.totalLines - truncated.outputLines} more lines not shown — full anchors in readSeekValue]`;
|
|
192
192
|
} else {
|
|
193
|
-
text += "\n[… output truncated at 50 KB — full anchors in
|
|
193
|
+
text += "\n[… output truncated at 50 KB — full anchors in readSeekValue]";
|
|
194
194
|
}
|
|
195
195
|
}
|
|
196
196
|
|
|
@@ -228,11 +228,11 @@ export async function executeWrite(opts: {
|
|
|
228
228
|
writeState: existedBeforeWrite ? "overwritten" : "created",
|
|
229
229
|
diff: diffResult.diff,
|
|
230
230
|
diffData,
|
|
231
|
-
|
|
231
|
+
readSeekValue: {
|
|
232
232
|
tool: "write",
|
|
233
233
|
path: displayPath,
|
|
234
|
-
lines:
|
|
235
|
-
warnings:
|
|
234
|
+
lines: readSeekLines,
|
|
235
|
+
warnings: readSeekWarnings,
|
|
236
236
|
diff: diffResult.diff,
|
|
237
237
|
diffData,
|
|
238
238
|
...(requestMap ? { map: { appended: mapAppended } } : {}),
|
|
@@ -242,11 +242,7 @@ export async function executeWrite(opts: {
|
|
|
242
242
|
|
|
243
243
|
export function registerWriteTool(pi: ExtensionAPI, options: WriteToolOptions = {}) {
|
|
244
244
|
const tool = registerReadSeekTool(pi, {
|
|
245
|
-
|
|
246
|
-
pythonName: "write",
|
|
247
|
-
defaultExposure: "not-safe-by-default",
|
|
248
|
-
}, {
|
|
249
|
-
name: "write",
|
|
245
|
+
name: "readSeek_write",
|
|
250
246
|
label: "write",
|
|
251
247
|
description: WRITE_PROMPT_METADATA.description,
|
|
252
248
|
promptSnippet: WRITE_PROMPT_METADATA.promptSnippet,
|
|
@@ -275,7 +271,7 @@ export function registerWriteTool(pi: ExtensionAPI, options: WriteToolOptions =
|
|
|
275
271
|
|
|
276
272
|
if (isToolErrorResult(result)) return result;
|
|
277
273
|
|
|
278
|
-
if (result.
|
|
274
|
+
if (result.readSeekValue.lines.length > 0) {
|
|
279
275
|
options.onFileAnchored?.(absolutePath);
|
|
280
276
|
}
|
|
281
277
|
|
|
@@ -285,7 +281,7 @@ export function registerWriteTool(pi: ExtensionAPI, options: WriteToolOptions =
|
|
|
285
281
|
...(result.diff !== undefined ? { diff: result.diff } : {}),
|
|
286
282
|
...(result.diffData !== undefined ? { diffData: result.diffData } : {}),
|
|
287
283
|
...(result.writeState ? { writeState: result.writeState } : {}),
|
|
288
|
-
|
|
284
|
+
readSeekValue: result.readSeekValue,
|
|
289
285
|
warnings: result.warnings,
|
|
290
286
|
},
|
|
291
287
|
};
|
|
@@ -326,7 +322,7 @@ export function registerWriteTool(pi: ExtensionAPI, options: WriteToolOptions =
|
|
|
326
322
|
if (isPartial) return renderPendingResult("pending write", width);
|
|
327
323
|
const details = result.details ?? {};
|
|
328
324
|
const output = result.content?.[0]?.type === "text" ? result.content[0].text : "";
|
|
329
|
-
if (result.isError || details.
|
|
325
|
+
if (result.isError || details.readSeekValue?.ok === false) {
|
|
330
326
|
return renderErrorResult(output, { expanded, width, fallback: "write failed" });
|
|
331
327
|
}
|
|
332
328
|
const diffData = details.diffData;
|
|
@@ -335,12 +331,12 @@ export function registerWriteTool(pi: ExtensionAPI, options: WriteToolOptions =
|
|
|
335
331
|
// instead of a diff body — every line is an add, so the gutter, line
|
|
336
332
|
// numbers, and red/green tinting are noise.
|
|
337
333
|
if (state === "created") {
|
|
338
|
-
const
|
|
339
|
-
const hasContent =
|
|
334
|
+
const readSeekLines = (details.readSeekValue?.lines ?? []) as Array<{ raw: string }>;
|
|
335
|
+
const hasContent = readSeekLines.length > 0;
|
|
340
336
|
const header = summaryLine(state, { hidden: hasContent && !expanded });
|
|
341
337
|
const lines = header.split("\n");
|
|
342
338
|
if (expanded && hasContent) {
|
|
343
|
-
const content =
|
|
339
|
+
const content = readSeekLines.map((l) => l.raw).join("\n");
|
|
344
340
|
lines.push(...formatContentPreviewLines(content, theme));
|
|
345
341
|
}
|
|
346
342
|
return new Text(clampLinesToWidth(lines, width).join("\n"), 0, 0);
|