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
package/src/readseek-settings.ts
CHANGED
|
@@ -2,23 +2,35 @@ import { readFileSync, statSync, type Stats } from "node:fs";
|
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
|
|
5
|
+
export type ReadSeekOcrMode = "force" | "off" | "auto";
|
|
6
|
+
export type ReadSeekSyntaxValidationMode = "warn" | "block" | "off";
|
|
7
|
+
|
|
8
|
+
interface ReadSeekGrepSettings {
|
|
9
|
+
maxLines?: number;
|
|
10
|
+
maxBytes?: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
5
13
|
interface ReadSeekJsonSettings {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
14
|
+
excludeTools?: string[];
|
|
15
|
+
ocrMode?: ReadSeekOcrMode;
|
|
16
|
+
syntaxValidation?: ReadSeekSyntaxValidationMode;
|
|
17
|
+
timeoutMs?: number;
|
|
18
|
+
grep?: ReadSeekGrepSettings;
|
|
9
19
|
}
|
|
10
20
|
|
|
11
|
-
interface ReadSeekSettingsWarning {
|
|
21
|
+
export interface ReadSeekSettingsWarning {
|
|
12
22
|
source: string;
|
|
13
23
|
message: string;
|
|
14
24
|
path?: string;
|
|
15
25
|
}
|
|
16
26
|
|
|
17
|
-
interface ReadSeekSettingsResult {
|
|
27
|
+
export interface ReadSeekSettingsResult {
|
|
18
28
|
settings: ReadSeekJsonSettings;
|
|
19
29
|
warnings: ReadSeekSettingsWarning[];
|
|
20
30
|
}
|
|
21
31
|
|
|
32
|
+
const READSEEK_KEYS = ["excludeTools", "ocrMode", "syntaxValidation", "timeoutMs", "grep"];
|
|
33
|
+
const READSEEK_GREP_KEYS = ["maxLines", "maxBytes"];
|
|
22
34
|
|
|
23
35
|
function defaultGlobalSettingsPath(): string {
|
|
24
36
|
return join(homedir(), ".pi/agent/readseek/settings.json");
|
|
@@ -36,6 +48,20 @@ function invalid(source: string, path: string): ReadSeekSettingsWarning {
|
|
|
36
48
|
return { source, path, message: `Invalid readseek setting at ${path}` };
|
|
37
49
|
}
|
|
38
50
|
|
|
51
|
+
function warnUnknownKeys(
|
|
52
|
+
raw: Record<string, unknown>,
|
|
53
|
+
known: string[],
|
|
54
|
+
prefix: string,
|
|
55
|
+
source: string,
|
|
56
|
+
warnings: ReadSeekSettingsWarning[],
|
|
57
|
+
): void {
|
|
58
|
+
for (const key of Object.keys(raw)) {
|
|
59
|
+
if (known.includes(key)) continue;
|
|
60
|
+
const path = `${prefix}.${key}`;
|
|
61
|
+
warnings.push({ source, path, message: `Unknown readseek setting at ${path}` });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
39
65
|
function readPositive(
|
|
40
66
|
raw: Record<string, unknown>,
|
|
41
67
|
key: string,
|
|
@@ -50,40 +76,111 @@ function readPositive(
|
|
|
50
76
|
return undefined;
|
|
51
77
|
}
|
|
52
78
|
|
|
79
|
+
function readEnum<T extends string>(
|
|
80
|
+
raw: Record<string, unknown>,
|
|
81
|
+
key: string,
|
|
82
|
+
values: readonly T[],
|
|
83
|
+
path: string,
|
|
84
|
+
source: string,
|
|
85
|
+
warnings: ReadSeekSettingsWarning[],
|
|
86
|
+
): T | undefined {
|
|
87
|
+
if (!(key in raw)) return undefined;
|
|
88
|
+
const val = raw[key];
|
|
89
|
+
if (typeof val === "string" && (values as readonly string[]).includes(val)) return val as T;
|
|
90
|
+
warnings.push(invalid(source, path));
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function readOcrMode(
|
|
95
|
+
raw: Record<string, unknown>,
|
|
96
|
+
source: string,
|
|
97
|
+
warnings: ReadSeekSettingsWarning[],
|
|
98
|
+
): ReadSeekOcrMode | undefined {
|
|
99
|
+
if (!("ocrMode" in raw)) return undefined;
|
|
100
|
+
const val = raw.ocrMode;
|
|
101
|
+
if (val === "on") return "force";
|
|
102
|
+
if (val === "force" || val === "off" || val === "auto") return val;
|
|
103
|
+
warnings.push(invalid(source, "readseek.ocrMode"));
|
|
104
|
+
return undefined;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function readStringArray(
|
|
108
|
+
raw: Record<string, unknown>,
|
|
109
|
+
key: string,
|
|
110
|
+
path: string,
|
|
111
|
+
source: string,
|
|
112
|
+
warnings: ReadSeekSettingsWarning[],
|
|
113
|
+
): string[] | undefined {
|
|
114
|
+
if (!(key in raw)) return undefined;
|
|
115
|
+
const value = raw[key];
|
|
116
|
+
if (!Array.isArray(value)) {
|
|
117
|
+
warnings.push(invalid(source, path));
|
|
118
|
+
return undefined;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const strings: string[] = [];
|
|
122
|
+
value.forEach((item, index) => {
|
|
123
|
+
if (typeof item !== "string" || item.trim() === "") {
|
|
124
|
+
warnings.push(invalid(source, `${path}[${index}]`));
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
strings.push(item);
|
|
128
|
+
});
|
|
129
|
+
return strings;
|
|
130
|
+
}
|
|
53
131
|
|
|
54
132
|
function validateSettings(raw: unknown, source: string): ReadSeekSettingsResult {
|
|
55
133
|
const settings: ReadSeekJsonSettings = {};
|
|
56
134
|
const warnings: ReadSeekSettingsWarning[] = [];
|
|
57
|
-
if (!isRecord(raw))
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const grep: NonNullable<ReadSeekJsonSettings["grep"]> = {};
|
|
61
|
-
const maxLines = readPositive(raw.grep, "maxLines", "grep.maxLines", source, warnings);
|
|
62
|
-
if (maxLines !== undefined) grep.maxLines = maxLines;
|
|
63
|
-
const maxBytes = readPositive(raw.grep, "maxBytes", "grep.maxBytes", source, warnings);
|
|
64
|
-
if (maxBytes !== undefined) grep.maxBytes = maxBytes;
|
|
65
|
-
if (Object.keys(grep).length > 0) settings.grep = grep;
|
|
135
|
+
if (!isRecord(raw)) {
|
|
136
|
+
warnings.push({ source, message: "Invalid readseek settings: expected a JSON object" });
|
|
137
|
+
return { settings, warnings };
|
|
66
138
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
const edit: NonNullable<ReadSeekJsonSettings["edit"]> = {};
|
|
71
|
-
if ("diffDisplay" in raw.edit) {
|
|
72
|
-
const value = raw.edit.diffDisplay;
|
|
73
|
-
if (value === "collapsed" || value === "expanded") edit.diffDisplay = value;
|
|
74
|
-
else warnings.push(invalid(source, "edit.diffDisplay"));
|
|
139
|
+
if (!("readseek" in raw)) {
|
|
140
|
+
if (Object.keys(raw).length > 0) {
|
|
141
|
+
warnings.push({ source, path: "readseek", message: "Missing readseek section: every setting belongs under \"readseek\"" });
|
|
75
142
|
}
|
|
76
|
-
|
|
143
|
+
return { settings, warnings };
|
|
144
|
+
}
|
|
145
|
+
if (!isRecord(raw.readseek)) {
|
|
146
|
+
warnings.push(invalid(source, "readseek"));
|
|
147
|
+
return { settings, warnings };
|
|
77
148
|
}
|
|
78
149
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
150
|
+
const section = raw.readseek;
|
|
151
|
+
warnUnknownKeys(section, READSEEK_KEYS, "readseek", source, warnings);
|
|
152
|
+
|
|
153
|
+
const excludeTools = readStringArray(section, "excludeTools", "readseek.excludeTools", source, warnings);
|
|
154
|
+
if (excludeTools !== undefined) settings.excludeTools = excludeTools;
|
|
155
|
+
|
|
156
|
+
const ocrMode = readOcrMode(section, source, warnings);
|
|
157
|
+
if (ocrMode !== undefined) settings.ocrMode = ocrMode;
|
|
158
|
+
|
|
159
|
+
const syntaxValidation = readEnum(
|
|
160
|
+
section,
|
|
161
|
+
"syntaxValidation",
|
|
162
|
+
["warn", "block", "off"] as const,
|
|
163
|
+
"readseek.syntaxValidation",
|
|
164
|
+
source,
|
|
165
|
+
warnings,
|
|
166
|
+
);
|
|
167
|
+
if (syntaxValidation !== undefined) settings.syntaxValidation = syntaxValidation;
|
|
168
|
+
|
|
169
|
+
const timeoutMs = readPositive(section, "timeoutMs", "readseek.timeoutMs", source, warnings);
|
|
170
|
+
if (timeoutMs !== undefined) settings.timeoutMs = timeoutMs;
|
|
171
|
+
|
|
172
|
+
if ("grep" in section) {
|
|
173
|
+
if (isRecord(section.grep)) {
|
|
174
|
+
warnUnknownKeys(section.grep, READSEEK_GREP_KEYS, "readseek.grep", source, warnings);
|
|
175
|
+
const grep: ReadSeekGrepSettings = {};
|
|
176
|
+
const maxLines = readPositive(section.grep, "maxLines", "readseek.grep.maxLines", source, warnings);
|
|
177
|
+
if (maxLines !== undefined) grep.maxLines = maxLines;
|
|
178
|
+
const maxBytes = readPositive(section.grep, "maxBytes", "readseek.grep.maxBytes", source, warnings);
|
|
179
|
+
if (maxBytes !== undefined) grep.maxBytes = maxBytes;
|
|
180
|
+
if (Object.keys(grep).length > 0) settings.grep = grep;
|
|
181
|
+
} else {
|
|
182
|
+
warnings.push(invalid(source, "readseek.grep"));
|
|
85
183
|
}
|
|
86
|
-
if (Object.keys(read).length > 0) settings.read = read;
|
|
87
184
|
}
|
|
88
185
|
|
|
89
186
|
return { settings, warnings };
|
|
@@ -128,14 +225,10 @@ function readSettingsFile(path: string): ReadSeekSettingsResult {
|
|
|
128
225
|
}
|
|
129
226
|
|
|
130
227
|
function mergeSettings(base: ReadSeekJsonSettings, override: ReadSeekJsonSettings): ReadSeekJsonSettings {
|
|
131
|
-
const
|
|
228
|
+
const settings: ReadSeekJsonSettings = { ...base, ...override };
|
|
132
229
|
const grep = { ...(base.grep ?? {}), ...(override.grep ?? {}) };
|
|
133
|
-
if (Object.keys(grep).length > 0)
|
|
134
|
-
|
|
135
|
-
if (Object.keys(edit).length > 0) merged.edit = edit;
|
|
136
|
-
const read = { ...(base.read ?? {}), ...(override.read ?? {}) };
|
|
137
|
-
if (Object.keys(read).length > 0) merged.read = read;
|
|
138
|
-
return merged;
|
|
230
|
+
if (Object.keys(grep).length > 0) settings.grep = grep;
|
|
231
|
+
return settings;
|
|
139
232
|
}
|
|
140
233
|
|
|
141
234
|
export function resolveReadSeekJsonSettings(): ReadSeekSettingsResult {
|
|
@@ -147,24 +240,14 @@ export function resolveReadSeekJsonSettings(): ReadSeekSettingsResult {
|
|
|
147
240
|
};
|
|
148
241
|
}
|
|
149
242
|
|
|
150
|
-
export function
|
|
151
|
-
|
|
152
|
-
if (typeof raw === "string") {
|
|
153
|
-
const normalized = raw.trim().toLowerCase();
|
|
154
|
-
if (normalized === "expanded" || normalized === "collapsed") return normalized;
|
|
155
|
-
}
|
|
156
|
-
const json = resolveReadSeekJsonSettings().settings.edit?.diffDisplay;
|
|
157
|
-
if (json === "expanded" || json === "collapsed") return json;
|
|
158
|
-
return "collapsed";
|
|
243
|
+
export function resolveReadSeekOcrMode(): ReadSeekOcrMode {
|
|
244
|
+
return resolveReadSeekJsonSettings().settings.ocrMode ?? "force";
|
|
159
245
|
}
|
|
160
246
|
|
|
161
|
-
export function
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
const json = resolveReadSeekJsonSettings().settings.read?.ocrMode;
|
|
168
|
-
if (json === "on" || json === "off" || json === "auto") return json;
|
|
169
|
-
return "on";
|
|
247
|
+
export function resolveReadSeekSyntaxValidation(): ReadSeekSyntaxValidationMode | undefined {
|
|
248
|
+
return resolveReadSeekJsonSettings().settings.syntaxValidation;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export function resolveReadSeekTimeoutMs(): number | undefined {
|
|
252
|
+
return resolveReadSeekJsonSettings().settings.timeoutMs;
|
|
170
253
|
}
|
package/src/readseek-value.ts
CHANGED
|
@@ -39,8 +39,6 @@ export interface ReadSeekRange {
|
|
|
39
39
|
|
|
40
40
|
export interface SemanticSummary {
|
|
41
41
|
classification: "no-op" | "whitespace-only" | "semantic" | "mixed";
|
|
42
|
-
difftasticAvailable: boolean;
|
|
43
|
-
movedBlocks?: number;
|
|
44
42
|
}
|
|
45
43
|
export interface ReadSeekEditResult {
|
|
46
44
|
tool: "edit";
|
|
@@ -131,16 +129,16 @@ export function buildReadSeekError(
|
|
|
131
129
|
export interface ToolErrorResult {
|
|
132
130
|
content: [{ type: "text"; text: string }];
|
|
133
131
|
isError: true;
|
|
134
|
-
details: {
|
|
132
|
+
details: { readSeekValue: Record<string, unknown> };
|
|
135
133
|
}
|
|
136
134
|
|
|
137
135
|
/**
|
|
138
136
|
* Build the standard failure envelope shared by every read-family tool: a text
|
|
139
|
-
* content block plus a `
|
|
137
|
+
* content block plus a `readSeekValue` carrying `ok: false` and a
|
|
140
138
|
* {@link buildReadSeekError} payload.
|
|
141
139
|
*
|
|
142
140
|
* `path` is included only when provided, and `extra` is merged into
|
|
143
|
-
* `
|
|
141
|
+
* `readSeekValue` so callers can attach tool-specific fields (e.g. write's
|
|
144
142
|
* `lines`/`warnings`).
|
|
145
143
|
*/
|
|
146
144
|
export function buildToolErrorResult(
|
|
@@ -153,7 +151,7 @@ export function buildToolErrorResult(
|
|
|
153
151
|
content: [{ type: "text", text: message }],
|
|
154
152
|
isError: true,
|
|
155
153
|
details: {
|
|
156
|
-
|
|
154
|
+
readSeekValue: {
|
|
157
155
|
tool,
|
|
158
156
|
...(opts.extra ?? {}),
|
|
159
157
|
ok: false,
|
package/src/refs-output.ts
CHANGED
|
@@ -17,7 +17,7 @@ interface BuildRefsOutputInput {
|
|
|
17
17
|
|
|
18
18
|
interface RefsOutputResult {
|
|
19
19
|
text: string;
|
|
20
|
-
|
|
20
|
+
readSeekValue: {
|
|
21
21
|
tool: "refs";
|
|
22
22
|
files: Array<{
|
|
23
23
|
path: string;
|
|
@@ -30,13 +30,13 @@ export function buildRefsOutput(input: BuildRefsOutputInput): RefsOutputResult {
|
|
|
30
30
|
if (input.files.length === 0) {
|
|
31
31
|
return {
|
|
32
32
|
text: `No references found for: ${input.name}`,
|
|
33
|
-
|
|
33
|
+
readSeekValue: { tool: "refs", files: [] },
|
|
34
34
|
};
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
return {
|
|
38
38
|
text: formatAnchoredFileBlocks(input.files, (line) => (line.enclosingSymbol ? ` (in ${line.enclosingSymbol})` : "")),
|
|
39
|
-
|
|
39
|
+
readSeekValue: {
|
|
40
40
|
tool: "refs",
|
|
41
41
|
files: input.files.map((file) => ({
|
|
42
42
|
path: file.path,
|
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 });
|