pi-readseek 0.3.2 → 0.3.4
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 +30 -18
- package/index.ts +16 -136
- package/package.json +4 -4
- package/src/confusable-hyphens.ts +6 -0
- package/src/edit-classify.ts +0 -2
- package/src/edit-diff.ts +3 -6
- package/src/edit-output.ts +1 -7
- package/src/edit.ts +3 -16
- package/src/grep-output.ts +0 -26
- package/src/grep.ts +1 -13
- package/src/hashline.ts +1 -10
- package/src/read-output.ts +1 -27
- package/src/read.ts +1 -10
- package/src/readseek-client.ts +1 -1
- package/src/readseek-settings.ts +6 -134
- package/src/sg-output.ts +0 -30
- package/src/sg.ts +14 -22
- package/src/tool-prompt-metadata.ts +2 -8
- package/src/write.ts +1 -13
- package/prompts/find.md +0 -18
- package/prompts/ls.md +0 -11
- package/src/context-application.ts +0 -70
- package/src/context-hygiene.ts +0 -512
- package/src/doom-loop-suggestions.ts +0 -42
- package/src/doom-loop.ts +0 -216
- package/src/find.ts +0 -613
- package/src/ls.ts +0 -293
- package/src/readseek-command.ts +0 -155
- package/src/readseek-repo.ts +0 -50
package/src/read.ts
CHANGED
|
@@ -20,7 +20,7 @@ import { formatFileMapWithBudget } from "./readseek/formatter.js";
|
|
|
20
20
|
import { findSymbol, type SymbolMatch } from "./readseek/symbol-lookup.js";
|
|
21
21
|
import { formatAmbiguous, formatNotFound } from "./readseek/symbol-error-format.js";
|
|
22
22
|
import { buildReadOutput } from "./read-output.js";
|
|
23
|
-
|
|
23
|
+
|
|
24
24
|
import { buildLocalBundle } from "./read-local-bundle.js";
|
|
25
25
|
import { coerceObviousBase10Int } from "./coerce-obvious-int.js";
|
|
26
26
|
import { readseekRead } from "./readseek-client.js";
|
|
@@ -681,14 +681,6 @@ export function registerReadTool(pi: ExtensionAPI, options: ReadToolOptions = {}
|
|
|
681
681
|
text: mapText,
|
|
682
682
|
},
|
|
683
683
|
...(bundleMetadata ? { bundle: bundleMetadata } : {}),
|
|
684
|
-
rehydrate: buildReadRehydrateDescriptor({
|
|
685
|
-
path: p.path,
|
|
686
|
-
offset: p.offset,
|
|
687
|
-
limit: p.limit,
|
|
688
|
-
symbol: p.symbol,
|
|
689
|
-
map: p.map,
|
|
690
|
-
bundle: p.bundle,
|
|
691
|
-
}),
|
|
692
684
|
});
|
|
693
685
|
|
|
694
686
|
return succeed({
|
|
@@ -696,7 +688,6 @@ export function registerReadTool(pi: ExtensionAPI, options: ReadToolOptions = {}
|
|
|
696
688
|
details: {
|
|
697
689
|
truncation: truncation.truncated ? truncation : undefined,
|
|
698
690
|
readseekValue: readOutput.readseekValue,
|
|
699
|
-
contextHygiene: readOutput.contextHygiene,
|
|
700
691
|
},
|
|
701
692
|
});
|
|
702
693
|
},
|
package/src/readseek-client.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { DetailLevel } from "./readseek/enums.js";
|
|
|
6
6
|
import type { FileMap, FileSymbol } from "./readseek/types.js";
|
|
7
7
|
import { SymbolKind } from "./readseek/enums.js";
|
|
8
8
|
|
|
9
|
-
interface ReadseekHashline {
|
|
9
|
+
export interface ReadseekHashline {
|
|
10
10
|
line: number;
|
|
11
11
|
hash: string;
|
|
12
12
|
text: string;
|
package/src/readseek-settings.ts
CHANGED
|
@@ -48,144 +48,16 @@ function invalid(source: string, path: string): ReadseekSettingsWarning {
|
|
|
48
48
|
return { source, path, message: `Invalid readseek setting at ${path}` };
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
function readJsonObjectEnd(text: string, open: number): number {
|
|
52
|
-
let depth = 0;
|
|
53
|
-
let inString = false;
|
|
54
|
-
let escaped = false;
|
|
55
|
-
|
|
56
|
-
for (let i = open; i < text.length; i += 1) {
|
|
57
|
-
const char = text[i];
|
|
58
|
-
if (inString) {
|
|
59
|
-
if (escaped) escaped = false;
|
|
60
|
-
else if (char === "\\") escaped = true;
|
|
61
|
-
else if (char === '"') inString = false;
|
|
62
|
-
continue;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
if (char === '"') {
|
|
66
|
-
inString = true;
|
|
67
|
-
} else if (char === "{") {
|
|
68
|
-
depth += 1;
|
|
69
|
-
} else if (char === "}") {
|
|
70
|
-
depth -= 1;
|
|
71
|
-
if (depth === 0) return i;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return -1;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function readJsonStringEnd(text: string, quote: number): number {
|
|
79
|
-
let escaped = false;
|
|
80
|
-
for (let i = quote + 1; i < text.length; i += 1) {
|
|
81
|
-
const char = text[i];
|
|
82
|
-
if (escaped) escaped = false;
|
|
83
|
-
else if (char === "\\") escaped = true;
|
|
84
|
-
else if (char === '"') return i;
|
|
85
|
-
}
|
|
86
|
-
return -1;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function readTopLevelObjectBodies(rawText: string, section: string): string[] {
|
|
90
|
-
const bodies: string[] = [];
|
|
91
|
-
let depth = 0;
|
|
92
|
-
|
|
93
|
-
for (let i = 0; i < rawText.length; i += 1) {
|
|
94
|
-
const char = rawText[i];
|
|
95
|
-
if (char === '"') {
|
|
96
|
-
const end = readJsonStringEnd(rawText, i);
|
|
97
|
-
if (end < 0) return bodies;
|
|
98
|
-
|
|
99
|
-
if (depth === 1) {
|
|
100
|
-
const fieldName = rawText.slice(i + 1, end);
|
|
101
|
-
let cursor = end + 1;
|
|
102
|
-
while (/\s/.test(rawText[cursor] ?? "")) cursor += 1;
|
|
103
|
-
|
|
104
|
-
if (rawText[cursor] === ":") {
|
|
105
|
-
cursor += 1;
|
|
106
|
-
while (/\s/.test(rawText[cursor] ?? "")) cursor += 1;
|
|
107
|
-
if (fieldName === section && rawText[cursor] === "{") {
|
|
108
|
-
const objectEnd = readJsonObjectEnd(rawText, cursor);
|
|
109
|
-
if (objectEnd < 0) return bodies;
|
|
110
|
-
bodies.push(rawText.slice(cursor + 1, objectEnd));
|
|
111
|
-
i = objectEnd;
|
|
112
|
-
continue;
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
i = end;
|
|
117
|
-
} else if (char === "{") {
|
|
118
|
-
depth += 1;
|
|
119
|
-
} else if (char === "}") {
|
|
120
|
-
depth = Math.max(0, depth - 1);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
return bodies;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
function rawFieldTokens(rawText: string, path: string): string[] {
|
|
128
|
-
const [section, key] = path.split(".");
|
|
129
|
-
const bodies = readTopLevelObjectBodies(rawText, section);
|
|
130
|
-
if (bodies.length !== 1) return [];
|
|
131
|
-
|
|
132
|
-
const body = bodies[0];
|
|
133
|
-
const tokens: string[] = [];
|
|
134
|
-
let depth = 0;
|
|
135
|
-
|
|
136
|
-
for (let i = 0; i < body.length; i += 1) {
|
|
137
|
-
const char = body[i];
|
|
138
|
-
if (char === '"') {
|
|
139
|
-
const end = readJsonStringEnd(body, i);
|
|
140
|
-
if (end < 0) return tokens;
|
|
141
|
-
|
|
142
|
-
if (depth === 0) {
|
|
143
|
-
const fieldName = body.slice(i + 1, end);
|
|
144
|
-
let cursor = end + 1;
|
|
145
|
-
while (/\s/.test(body[cursor] ?? "")) cursor += 1;
|
|
146
|
-
|
|
147
|
-
if (body[cursor] === ":") {
|
|
148
|
-
cursor += 1;
|
|
149
|
-
while (/\s/.test(body[cursor] ?? "")) cursor += 1;
|
|
150
|
-
if (fieldName === key) {
|
|
151
|
-
const valueStart = cursor;
|
|
152
|
-
while (cursor < body.length && !/[,}\s]/.test(body[cursor])) cursor += 1;
|
|
153
|
-
tokens.push(body.slice(valueStart, cursor));
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
i = end;
|
|
158
|
-
} else if (char === "{" || char === "[") {
|
|
159
|
-
depth += 1;
|
|
160
|
-
} else if (char === "}" || char === "]") {
|
|
161
|
-
depth = Math.max(0, depth - 1);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
return tokens;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
function isStrictJsonPositiveInteger(rawText: string, path: string, value: unknown): value is number {
|
|
169
|
-
const tokens = rawFieldTokens(rawText, path);
|
|
170
|
-
return (
|
|
171
|
-
typeof value === "number" &&
|
|
172
|
-
Number.isSafeInteger(value) &&
|
|
173
|
-
value > 0 &&
|
|
174
|
-
tokens.length === 1 &&
|
|
175
|
-
/^[1-9][0-9]*$/.test(tokens[0])
|
|
176
|
-
);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
51
|
function readPositive(
|
|
180
52
|
raw: Record<string, unknown>,
|
|
181
53
|
key: string,
|
|
182
54
|
path: string,
|
|
183
55
|
source: string,
|
|
184
|
-
rawText: string,
|
|
185
56
|
warnings: ReadseekSettingsWarning[],
|
|
186
57
|
): number | undefined {
|
|
187
58
|
if (!(key in raw)) return undefined;
|
|
188
|
-
|
|
59
|
+
const val = raw[key];
|
|
60
|
+
if (typeof val === "number" && Number.isSafeInteger(val) && val > 0) return val;
|
|
189
61
|
warnings.push(invalid(source, path));
|
|
190
62
|
return undefined;
|
|
191
63
|
}
|
|
@@ -203,16 +75,16 @@ function readBoolean(
|
|
|
203
75
|
return undefined;
|
|
204
76
|
}
|
|
205
77
|
|
|
206
|
-
function validateSettings(raw: unknown, source: string
|
|
78
|
+
function validateSettings(raw: unknown, source: string): ReadseekSettingsResult {
|
|
207
79
|
const settings: ReadseekJsonSettings = {};
|
|
208
80
|
const warnings: ReadseekSettingsWarning[] = [];
|
|
209
81
|
if (!isRecord(raw)) return { settings, warnings };
|
|
210
82
|
|
|
211
83
|
if (isRecord(raw.grep)) {
|
|
212
84
|
const grep: NonNullable<ReadseekJsonSettings["grep"]> = {};
|
|
213
|
-
const maxLines = readPositive(raw.grep, "maxLines", "grep.maxLines", source,
|
|
85
|
+
const maxLines = readPositive(raw.grep, "maxLines", "grep.maxLines", source, warnings);
|
|
214
86
|
if (maxLines !== undefined) grep.maxLines = maxLines;
|
|
215
|
-
const maxBytes = readPositive(raw.grep, "maxBytes", "grep.maxBytes", source,
|
|
87
|
+
const maxBytes = readPositive(raw.grep, "maxBytes", "grep.maxBytes", source, warnings);
|
|
216
88
|
if (maxBytes !== undefined) grep.maxBytes = maxBytes;
|
|
217
89
|
if (Object.keys(grep).length > 0) settings.grep = grep;
|
|
218
90
|
}
|
|
@@ -246,7 +118,7 @@ function readSettingsFile(path: string): ReadseekSettingsResult {
|
|
|
246
118
|
|
|
247
119
|
try {
|
|
248
120
|
const text = readFileSync(path, "utf8");
|
|
249
|
-
return validateSettings(JSON.parse(text) as unknown, path
|
|
121
|
+
return validateSettings(JSON.parse(text) as unknown, path);
|
|
250
122
|
} catch (error) {
|
|
251
123
|
const message = error instanceof Error ? error.message : String(error);
|
|
252
124
|
return { settings: {}, warnings: [{ source: path, message: `Invalid JSON: ${message}` }] };
|
package/src/sg-output.ts
CHANGED
|
@@ -1,12 +1,4 @@
|
|
|
1
1
|
import type { ReadseekLine, ReadseekRange } from "./readseek-value.js";
|
|
2
|
-
import {
|
|
3
|
-
buildContextHygieneMetadata,
|
|
4
|
-
buildFileResource,
|
|
5
|
-
buildSymbolResource,
|
|
6
|
-
type ContextHygieneMetadata,
|
|
7
|
-
type ContextHygieneRehydrateDescriptor,
|
|
8
|
-
type ContextHygieneResource,
|
|
9
|
-
} from "./context-hygiene.js";
|
|
10
2
|
|
|
11
3
|
export interface SgOutputFile {
|
|
12
4
|
displayPath: string;
|
|
@@ -19,7 +11,6 @@ export interface SgOutputFile {
|
|
|
19
11
|
export interface BuildSgOutputInput {
|
|
20
12
|
pattern: string;
|
|
21
13
|
files: SgOutputFile[];
|
|
22
|
-
rehydrate?: ContextHygieneRehydrateDescriptor | null;
|
|
23
14
|
}
|
|
24
15
|
|
|
25
16
|
export interface SgOutputResult {
|
|
@@ -32,7 +23,6 @@ export interface SgOutputResult {
|
|
|
32
23
|
lines: ReadseekLine[];
|
|
33
24
|
}>;
|
|
34
25
|
};
|
|
35
|
-
contextHygiene: ContextHygieneMetadata;
|
|
36
26
|
}
|
|
37
27
|
|
|
38
28
|
export function buildSgOutput(input: BuildSgOutputInput): SgOutputResult {
|
|
@@ -43,12 +33,6 @@ export function buildSgOutput(input: BuildSgOutputInput): SgOutputResult {
|
|
|
43
33
|
tool: "search",
|
|
44
34
|
files: [],
|
|
45
35
|
},
|
|
46
|
-
contextHygiene: buildContextHygieneMetadata({
|
|
47
|
-
tool: "search",
|
|
48
|
-
classification: "search-context",
|
|
49
|
-
resources: [],
|
|
50
|
-
rehydrate: input.rehydrate ?? undefined,
|
|
51
|
-
}),
|
|
52
36
|
};
|
|
53
37
|
}
|
|
54
38
|
|
|
@@ -60,14 +44,6 @@ export function buildSgOutput(input: BuildSgOutputInput): SgOutputResult {
|
|
|
60
44
|
}
|
|
61
45
|
}
|
|
62
46
|
|
|
63
|
-
const contextHygieneResources: ContextHygieneResource[] = [];
|
|
64
|
-
for (const file of input.files) {
|
|
65
|
-
contextHygieneResources.push(buildFileResource(file.path));
|
|
66
|
-
for (const symbol of file.symbols ?? []) {
|
|
67
|
-
contextHygieneResources.push(buildSymbolResource(file.path, symbol.name, symbol.kind));
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
47
|
return {
|
|
72
48
|
text: blocks.join("\n"),
|
|
73
49
|
readseekValue: {
|
|
@@ -78,11 +54,5 @@ export function buildSgOutput(input: BuildSgOutputInput): SgOutputResult {
|
|
|
78
54
|
lines: file.lines.map((line) => ({ ...line })),
|
|
79
55
|
})),
|
|
80
56
|
},
|
|
81
|
-
contextHygiene: buildContextHygieneMetadata({
|
|
82
|
-
tool: "search",
|
|
83
|
-
classification: "search-context",
|
|
84
|
-
resources: contextHygieneResources,
|
|
85
|
-
rehydrate: input.rehydrate ?? undefined,
|
|
86
|
-
}),
|
|
87
57
|
};
|
|
88
58
|
}
|
package/src/sg.ts
CHANGED
|
@@ -4,12 +4,12 @@ import { Type } from "@sinclair/typebox";
|
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { stat as fsStat } from "node:fs/promises";
|
|
6
6
|
import { defineToolPromptMetadata } from "./tool-prompt-metadata.js";
|
|
7
|
-
import {
|
|
8
|
-
import { buildReadseekError,
|
|
7
|
+
import { escapeControlCharsForDisplay } from "./hashline.js";
|
|
8
|
+
import { buildReadseekError, type ReadseekLine } from "./readseek-value.js";
|
|
9
9
|
import { resolveToCwd } from "./path-utils.js";
|
|
10
|
-
import { isReadseekAvailable, readseekSearch, type ReadseekSearchFileOutput } from "./readseek-client.js";
|
|
10
|
+
import { isReadseekAvailable, readseekSearch, type ReadseekHashline, type ReadseekSearchFileOutput } from "./readseek-client.js";
|
|
11
11
|
import { buildSgOutput } from "./sg-output.js";
|
|
12
|
-
|
|
12
|
+
|
|
13
13
|
import { clampLineToWidth, clampLinesToWidth, isRendererExpanded, renderToolLabel, summaryLine } from "./tui-render-utils.js";
|
|
14
14
|
|
|
15
15
|
type SgParams = { pattern: string; lang?: string; path?: string; cached?: boolean; others?: boolean; ignored?: boolean };
|
|
@@ -62,8 +62,14 @@ interface SgToolOptions {
|
|
|
62
62
|
onFileAnchored?: (absolutePath: string) => void;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
function readseekLineFromSearch(line:
|
|
66
|
-
return
|
|
65
|
+
function readseekLineFromSearch(line: ReadseekHashline): ReadseekLine {
|
|
66
|
+
return {
|
|
67
|
+
line: line.line,
|
|
68
|
+
hash: line.hash,
|
|
69
|
+
anchor: `${line.line}:${line.hash}`,
|
|
70
|
+
raw: line.text,
|
|
71
|
+
display: escapeControlCharsForDisplay(line.text),
|
|
72
|
+
};
|
|
67
73
|
}
|
|
68
74
|
|
|
69
75
|
function linesFromSearchResult(result: ReadseekSearchFileOutput, ranges: SgRange[]): ReadseekLine[] {
|
|
@@ -119,7 +125,6 @@ export function registerSgTool(pi: ExtensionAPI, options: SgToolOptions = {}) {
|
|
|
119
125
|
}),
|
|
120
126
|
ptc: toolConfig,
|
|
121
127
|
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
122
|
-
await ensureHashInit();
|
|
123
128
|
const p = params as SgParams;
|
|
124
129
|
if (p.ignored && !p.others) {
|
|
125
130
|
const message = "Error: search parameter 'ignored' requires 'others'";
|
|
@@ -135,15 +140,6 @@ export function registerSgTool(pi: ExtensionAPI, options: SgToolOptions = {}) {
|
|
|
135
140
|
},
|
|
136
141
|
};
|
|
137
142
|
}
|
|
138
|
-
const rehydrate = buildSearchRehydrateDescriptor({
|
|
139
|
-
pattern: p.pattern,
|
|
140
|
-
lang: p.lang,
|
|
141
|
-
path: p.path,
|
|
142
|
-
cached: p.cached,
|
|
143
|
-
others: p.others,
|
|
144
|
-
ignored: p.ignored,
|
|
145
|
-
});
|
|
146
|
-
|
|
147
143
|
const searchPath = resolveToCwd(p.path ?? ".", ctx.cwd);
|
|
148
144
|
let searchPathIsFile = false;
|
|
149
145
|
|
|
@@ -206,12 +202,11 @@ export function registerSgTool(pi: ExtensionAPI, options: SgToolOptions = {}) {
|
|
|
206
202
|
signal,
|
|
207
203
|
});
|
|
208
204
|
if (results.length === 0) {
|
|
209
|
-
const emptyOutput = buildSgOutput({ pattern: p.pattern, files: []
|
|
205
|
+
const emptyOutput = buildSgOutput({ pattern: p.pattern, files: [] });
|
|
210
206
|
return {
|
|
211
207
|
content: [{ type: "text", text: emptyOutput.text }],
|
|
212
208
|
details: {
|
|
213
209
|
readseekValue: emptyOutput.readseekValue,
|
|
214
|
-
contextHygiene: emptyOutput.contextHygiene,
|
|
215
210
|
},
|
|
216
211
|
};
|
|
217
212
|
}
|
|
@@ -240,12 +235,11 @@ export function registerSgTool(pi: ExtensionAPI, options: SgToolOptions = {}) {
|
|
|
240
235
|
}
|
|
241
236
|
|
|
242
237
|
if (readseekFiles.length === 0) {
|
|
243
|
-
const emptyOutput = buildSgOutput({ pattern: p.pattern, files: []
|
|
238
|
+
const emptyOutput = buildSgOutput({ pattern: p.pattern, files: [] });
|
|
244
239
|
return {
|
|
245
240
|
content: [{ type: "text", text: emptyOutput.text }],
|
|
246
241
|
details: {
|
|
247
242
|
readseekValue: emptyOutput.readseekValue,
|
|
248
|
-
contextHygiene: emptyOutput.contextHygiene,
|
|
249
243
|
},
|
|
250
244
|
};
|
|
251
245
|
}
|
|
@@ -253,7 +247,6 @@ export function registerSgTool(pi: ExtensionAPI, options: SgToolOptions = {}) {
|
|
|
253
247
|
const builtOutput = buildSgOutput({
|
|
254
248
|
pattern: p.pattern,
|
|
255
249
|
files: readseekFiles,
|
|
256
|
-
rehydrate,
|
|
257
250
|
});
|
|
258
251
|
for (const readseekFile of readseekFiles) {
|
|
259
252
|
options.onFileAnchored?.(readseekFile.path);
|
|
@@ -262,7 +255,6 @@ export function registerSgTool(pi: ExtensionAPI, options: SgToolOptions = {}) {
|
|
|
262
255
|
content: [{ type: "text", text: builtOutput.text }],
|
|
263
256
|
details: {
|
|
264
257
|
readseekValue: builtOutput.readseekValue,
|
|
265
|
-
contextHygiene: builtOutput.contextHygiene,
|
|
266
258
|
},
|
|
267
259
|
};
|
|
268
260
|
} catch (err: any) {
|
|
@@ -5,8 +5,7 @@ const COMPACT_DESCRIPTIONS: Record<string, string> = {
|
|
|
5
5
|
"read.md": "Read text files/images by path; text has LINE:HASH anchors, images return attachments.",
|
|
6
6
|
"edit.md": "Edit existing text files using fresh LINE:HASH anchors from read, grep, search, or write.",
|
|
7
7
|
"grep.md": "Search file contents; non-summary results include LINE:HASH anchors for edits.",
|
|
8
|
-
|
|
9
|
-
"ls.md": "List one directory with directories first and dotfiles included.",
|
|
8
|
+
|
|
10
9
|
"write.md": "Create or overwrite a complete file and return anchors.",
|
|
11
10
|
"sg.md": "Search code by AST pattern and return anchored matches.",
|
|
12
11
|
};
|
|
@@ -25,12 +24,7 @@ const COMPACT_GUIDELINES: Record<string, string[]> = {
|
|
|
25
24
|
"Use grep for text search and edit-ready matching anchors.",
|
|
26
25
|
"Use grep summary mode for broad count/file discovery before narrowing.",
|
|
27
26
|
],
|
|
28
|
-
|
|
29
|
-
"Use find for recursive file discovery by basename glob.",
|
|
30
|
-
],
|
|
31
|
-
"ls.md": [
|
|
32
|
-
"Use ls to inspect one directory; use find for recursion.",
|
|
33
|
-
],
|
|
27
|
+
|
|
34
28
|
"write.md": [
|
|
35
29
|
"Use write to create files or intentionally overwrite whole files.",
|
|
36
30
|
"Use edit rather than write for small changes or appends to existing files.",
|
package/src/write.ts
CHANGED
|
@@ -9,7 +9,7 @@ import { buildReadseekError, buildReadseekLine, buildReadseekWarning, type Reads
|
|
|
9
9
|
import { looksLikeBinary } from "./binary-detect.js";
|
|
10
10
|
import { getOrGenerateMap } from "./map-cache.js";
|
|
11
11
|
import { formatFileMapWithBudget } from "./readseek/formatter.js";
|
|
12
|
-
|
|
12
|
+
|
|
13
13
|
import { defineToolPromptMetadata } from "./tool-prompt-metadata.js";
|
|
14
14
|
import { buildPendingWritePreviewData, buildWritePreviewKey, resolvePendingDiffPreview, type PendingDiffPreviewResult } from "./pending-diff-preview.js";
|
|
15
15
|
import { generateCompactOrFullDiff, normalizeToLF, hasBareCarriageReturn } from "./edit-diff.js";
|
|
@@ -91,7 +91,6 @@ export interface WriteResult extends WriteDiffFields {
|
|
|
91
91
|
diffData?: DiffData;
|
|
92
92
|
map?: { appended: boolean };
|
|
93
93
|
};
|
|
94
|
-
contextHygiene: ContextHygieneMetadata;
|
|
95
94
|
}
|
|
96
95
|
|
|
97
96
|
function readPreviousTextForDiff(filePath: string): string {
|
|
@@ -185,11 +184,6 @@ export async function executeWrite(opts: {
|
|
|
185
184
|
const { path: filePath, content, map: requestMap, cwd } = opts;
|
|
186
185
|
const warnings: string[] = [];
|
|
187
186
|
const readseekWarnings: ReadseekWarning[] = [];
|
|
188
|
-
const contextHygiene = buildContextHygieneMetadata({
|
|
189
|
-
tool: "write",
|
|
190
|
-
classification: "mutation",
|
|
191
|
-
resources: [buildFileResource(filePath)],
|
|
192
|
-
});
|
|
193
187
|
|
|
194
188
|
if (hasBareCarriageReturn(content)) {
|
|
195
189
|
const message = "File content contains bare CR (\\r) line endings; write refuses to emit anchors that read/edit would normalize differently.";
|
|
@@ -204,7 +198,6 @@ export async function executeWrite(opts: {
|
|
|
204
198
|
lines: [],
|
|
205
199
|
warnings: readseekWarnings,
|
|
206
200
|
},
|
|
207
|
-
contextHygiene,
|
|
208
201
|
};
|
|
209
202
|
}
|
|
210
203
|
const previousContent = readPreviousTextForDiff(filePath);
|
|
@@ -239,7 +232,6 @@ export async function executeWrite(opts: {
|
|
|
239
232
|
lines: [],
|
|
240
233
|
warnings: readseekWarnings,
|
|
241
234
|
},
|
|
242
|
-
contextHygiene,
|
|
243
235
|
};
|
|
244
236
|
}
|
|
245
237
|
|
|
@@ -309,7 +301,6 @@ export async function executeWrite(opts: {
|
|
|
309
301
|
diffData,
|
|
310
302
|
...(requestMap !== undefined ? { map: { appended: mapAppended } } : {}),
|
|
311
303
|
},
|
|
312
|
-
contextHygiene,
|
|
313
304
|
};
|
|
314
305
|
}
|
|
315
306
|
|
|
@@ -391,7 +382,6 @@ export function registerWriteTool(pi: ExtensionAPI, options: WriteToolOptions =
|
|
|
391
382
|
error: buildReadseekError("binary-content", binaryWarning.message),
|
|
392
383
|
},
|
|
393
384
|
warnings: result.warnings,
|
|
394
|
-
contextHygiene: result.contextHygiene,
|
|
395
385
|
},
|
|
396
386
|
};
|
|
397
387
|
}
|
|
@@ -408,7 +398,6 @@ export function registerWriteTool(pi: ExtensionAPI, options: WriteToolOptions =
|
|
|
408
398
|
error: buildReadseekError("bare-cr", bareCrWarning.message),
|
|
409
399
|
},
|
|
410
400
|
warnings: result.warnings,
|
|
411
|
-
contextHygiene: result.contextHygiene,
|
|
412
401
|
},
|
|
413
402
|
};
|
|
414
403
|
}
|
|
@@ -421,7 +410,6 @@ export function registerWriteTool(pi: ExtensionAPI, options: WriteToolOptions =
|
|
|
421
410
|
...(result.writeState ? { writeState: result.writeState } : {}),
|
|
422
411
|
readseekValue: result.readseekValue,
|
|
423
412
|
warnings: result.warnings,
|
|
424
|
-
contextHygiene: result.contextHygiene,
|
|
425
413
|
},
|
|
426
414
|
};
|
|
427
415
|
});
|
package/prompts/find.md
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
Find files or directories recursively by basename. Uses glob patterns by default, respects nested `.gitignore`, includes hidden entries, and returns relative paths.
|
|
2
|
-
|
|
3
|
-
## Parameters
|
|
4
|
-
|
|
5
|
-
- `pattern` — required. Glob by default; with `regex: true`, JavaScript regex against each basename.
|
|
6
|
-
- `path` — directory to search, default cwd.
|
|
7
|
-
- `type` — `"file"` default, `"dir"`, or `"any"`.
|
|
8
|
-
- `limit` — max returned entries after filtering/sorting, default 1000.
|
|
9
|
-
- `maxDepth` — non-negative directory depth limit.
|
|
10
|
-
- `sortBy` — `"name"` default, `"mtime"`, or `"size"`; use `reverse: true` for descending/newest/largest first.
|
|
11
|
-
- `modifiedSince` — keep entries modified strictly after an ISO date/time or relative age like `30m`, `1h`, `24h`, `7d`.
|
|
12
|
-
- `minSize` / `maxSize` — inclusive file-size filters; numbers are bytes, strings accept 1024-based `KB`, `MB`, `GB`, etc. Directories are not removed by size filters.
|
|
13
|
-
|
|
14
|
-
## Output and usage
|
|
15
|
-
|
|
16
|
-
Output is one relative path per line. Directories end with `/`. Filtering and sorting happen before `limit`, so newest/largest queries work as expected.
|
|
17
|
-
|
|
18
|
-
Use `find` for recursive name discovery, `ls` for one directory, `grep` or `search` for contents, and `read` for file content. Remember: `pattern` matches basenames, not full paths.
|
package/prompts/ls.md
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
List one directory. Output is directories first (with `/`), then files, sorted alphabetically; dotfiles are included.
|
|
2
|
-
|
|
3
|
-
## Parameters
|
|
4
|
-
|
|
5
|
-
- `path` — directory to list, default cwd.
|
|
6
|
-
- `limit` — max entries, default 500; must be positive.
|
|
7
|
-
- `glob` — optional entry-name filter such as `*.ts`, `.env*`, or `test-*`.
|
|
8
|
-
|
|
9
|
-
## Usage
|
|
10
|
-
|
|
11
|
-
Use `ls` to inspect one known directory. Use `find` for recursive discovery, `grep` or `search` for contents, and `read` for file content. If output exceeds `limit` or 50 KB, narrow with `glob` or switch to `find`.
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
renderStaleContextPlaceholder,
|
|
3
|
-
type ContextHygieneReport,
|
|
4
|
-
type ContextHygieneStaleRecord,
|
|
5
|
-
} from "./context-hygiene.js";
|
|
6
|
-
|
|
7
|
-
type ContextToolResultMessage = {
|
|
8
|
-
role?: unknown;
|
|
9
|
-
toolCallId?: unknown;
|
|
10
|
-
toolName?: unknown;
|
|
11
|
-
content?: unknown;
|
|
12
|
-
details?: unknown;
|
|
13
|
-
[key: string]: unknown;
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
17
|
-
return !!value && typeof value === "object";
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function isMaskableStaleTool(tool: string): boolean {
|
|
21
|
-
return tool === "read" || tool === "grep" || tool === "search";
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function staleRecordsByResultId(report: ContextHygieneReport): Map<string, ContextHygieneStaleRecord> {
|
|
25
|
-
const records = new Map<string, ContextHygieneStaleRecord>();
|
|
26
|
-
for (const candidate of report.staleCandidates) {
|
|
27
|
-
for (const record of candidate.staleResults) {
|
|
28
|
-
if (!record.originalResultId || !isMaskableStaleTool(record.originalTool)) continue;
|
|
29
|
-
const existing = records.get(record.originalResultId);
|
|
30
|
-
if (!existing || existing.invalidatingMutationEventId < record.invalidatingMutationEventId) {
|
|
31
|
-
records.set(record.originalResultId, record);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
return records;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function maskStaleToolResultMessage<T extends ContextToolResultMessage>(message: T, record: ContextHygieneStaleRecord): T {
|
|
39
|
-
const details = isRecord(message.details) ? message.details : {};
|
|
40
|
-
return {
|
|
41
|
-
...message,
|
|
42
|
-
content: [{ type: "text" as const, text: renderStaleContextPlaceholder(record) }],
|
|
43
|
-
details: {
|
|
44
|
-
...details,
|
|
45
|
-
contextHygieneStale: record,
|
|
46
|
-
},
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export function applyContextHygieneStaleContext<T extends ContextToolResultMessage>(
|
|
51
|
-
messages: readonly T[],
|
|
52
|
-
report: ContextHygieneReport,
|
|
53
|
-
): T[] {
|
|
54
|
-
const staleByResultId = staleRecordsByResultId(report);
|
|
55
|
-
if (staleByResultId.size === 0) return messages as T[];
|
|
56
|
-
|
|
57
|
-
let changed = false;
|
|
58
|
-
const nextMessages = messages.map((message) => {
|
|
59
|
-
if (message.role !== "toolResult" || typeof message.toolCallId !== "string") return message;
|
|
60
|
-
const staleRecord = staleByResultId.get(message.toolCallId);
|
|
61
|
-
if (staleRecord) {
|
|
62
|
-
if (message.toolName !== staleRecord.originalTool) return message;
|
|
63
|
-
changed = true;
|
|
64
|
-
return maskStaleToolResultMessage(message, staleRecord);
|
|
65
|
-
}
|
|
66
|
-
return message;
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
return changed ? nextMessages : (messages as T[]);
|
|
70
|
-
}
|