@aaroncql/pim-agent 0.0.1 → 0.2.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 +94 -66
- package/bin/pim.ts +55 -3
- package/package.json +20 -5
- package/src/extensions/_init/index.ts +3 -2
- package/src/extensions/apply-patch/coordinator.ts +49 -0
- package/src/extensions/apply-patch/executor.ts +566 -0
- package/src/extensions/apply-patch/index.ts +74 -0
- package/src/extensions/apply-patch/matcher.ts +66 -0
- package/src/extensions/apply-patch/model.ts +34 -0
- package/src/extensions/apply-patch/parser.ts +381 -0
- package/src/extensions/apply-patch/render.ts +261 -0
- package/src/extensions/apply-patch/schema.ts +43 -0
- package/src/extensions/apply-patch/types.ts +30 -0
- package/src/extensions/bash/index.ts +3 -3
- package/src/extensions/edit/index.ts +2 -1
- package/src/extensions/glob/index.ts +3 -1
- package/src/extensions/glob/schema.ts +2 -1
- package/src/extensions/grep/index.ts +3 -1
- package/src/extensions/grep/render.ts +18 -4
- package/src/extensions/grep/schema.ts +1 -1
- package/src/extensions/read/index.ts +36 -9
- package/src/extensions/read/render.ts +31 -3
- package/src/extensions/subagent/index.ts +4 -1
- package/src/extensions/todo/index.ts +4 -3
- package/src/extensions/web-search/index.ts +2 -1
- package/src/extensions/write/index.ts +2 -1
- package/src/shared/PatchSummary.ts +82 -0
- package/src/telegram/Renderer.ts +190 -4
- package/src/extensions/bash/capture.test.ts +0 -126
- package/src/extensions/bash/format.test.ts +0 -240
- package/src/extensions/bash/run.test.ts +0 -262
- package/src/extensions/command-picker/ranker.test.ts +0 -46
- package/src/extensions/edit/edit.test.ts +0 -285
- package/src/extensions/file-picker/catalog.test.ts +0 -263
- package/src/extensions/file-picker/index.test.ts +0 -168
- package/src/extensions/file-picker/ranker.test.ts +0 -94
- package/src/extensions/footer/git.test.ts +0 -76
- package/src/extensions/footer/index.test.ts +0 -161
- package/src/extensions/footer/segments.test.ts +0 -164
- package/src/extensions/glob/glob.test.ts +0 -171
- package/src/extensions/glob/index.test.ts +0 -68
- package/src/extensions/glob/render.test.ts +0 -126
- package/src/extensions/grep/grep.test.ts +0 -387
- package/src/extensions/grep/index.test.ts +0 -68
- package/src/extensions/grep/render.test.ts +0 -269
- package/src/extensions/read/read.test.ts +0 -177
- package/src/extensions/read/render.test.ts +0 -61
- package/src/extensions/subagent/index.test.ts +0 -44
- package/src/extensions/subagent/render.test.ts +0 -292
- package/src/extensions/subagent/subagent.test.ts +0 -315
- package/src/extensions/system-prompt/prompt.test.ts +0 -64
- package/src/extensions/todo/index.test.ts +0 -244
- package/src/extensions/todo/render.test.ts +0 -180
- package/src/extensions/todo/todo.test.ts +0 -222
- package/src/extensions/tps/index.test.ts +0 -254
- package/src/extensions/web-fetch/WebViewMarkdownSnapshot.test.ts +0 -119
- package/src/extensions/web-fetch/fetch.test.ts +0 -244
- package/src/extensions/web-fetch/render.test.ts +0 -56
- package/src/extensions/web-search/ExaMcpClient.test.ts +0 -143
- package/src/extensions/web-search/render.test.ts +0 -21
- package/src/extensions/web-search/search.test.ts +0 -53
- package/src/extensions/working-indicator/index.test.ts +0 -21
- package/src/extensions/write/render.test.ts +0 -64
- package/src/extensions/write/write.test.ts +0 -108
- package/src/shared/DiffLines.test.ts +0 -193
- package/src/shared/DiffRenderer.test.ts +0 -206
- package/src/shared/EditMatcher.test.ts +0 -123
- package/src/shared/FileScanner.test.ts +0 -158
- package/src/shared/FuzzyMatcher.test.ts +0 -114
- package/src/shared/GitignoreFilter.test.ts +0 -64
- package/src/shared/Lines.test.ts +0 -25
- package/src/shared/McpClient.test.ts +0 -235
- package/src/shared/OutputBudget.test.ts +0 -99
- package/src/shared/Paths.test.ts +0 -51
- package/src/shared/PimSettings.test.ts +0 -90
- package/src/shared/Renderer.test.ts +0 -190
- package/src/shared/SpillCache.test.ts +0 -94
- package/src/shared/Tools.test.ts +0 -392
- package/src/telegram/Config.test.ts +0 -275
- package/src/telegram/Markdown.test.ts +0 -143
- package/src/telegram/Renderer.test.ts +0 -216
- package/src/telegram/SessionRegistry.test.ts +0 -89
- package/src/telegram/TaskScheduler.test.ts +0 -278
- package/src/telegram/TaskTool.test.ts +0 -179
|
@@ -1,387 +0,0 @@
|
|
|
1
|
-
import { mkdir, mkdtemp, rm, utimes, writeFile } from "node:fs/promises";
|
|
2
|
-
import { tmpdir } from "node:os";
|
|
3
|
-
import { join } from "node:path";
|
|
4
|
-
import { afterAll, describe, expect, test } from "bun:test";
|
|
5
|
-
import { buildMatcher, findMatches } from "./grep";
|
|
6
|
-
|
|
7
|
-
const tempRoots: string[] = [];
|
|
8
|
-
|
|
9
|
-
const tempRoot = async (): Promise<string> => {
|
|
10
|
-
const root = await mkdtemp(join(tmpdir(), "pim-grep-tool-"));
|
|
11
|
-
tempRoots.push(root);
|
|
12
|
-
return root;
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
afterAll(async () => {
|
|
16
|
-
await Promise.all(
|
|
17
|
-
tempRoots.map((root) => rm(root, { force: true, recursive: true }))
|
|
18
|
-
);
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
const makeMatcher = (
|
|
22
|
-
pattern: string,
|
|
23
|
-
options?: {
|
|
24
|
-
readonly caseInsensitive?: boolean;
|
|
25
|
-
readonly matchAcrossLines?: boolean;
|
|
26
|
-
}
|
|
27
|
-
) =>
|
|
28
|
-
buildMatcher({
|
|
29
|
-
pattern,
|
|
30
|
-
caseInsensitive: options?.caseInsensitive ?? false,
|
|
31
|
-
matchAcrossLines: options?.matchAcrossLines ?? false,
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
const defaultScanOptions = {
|
|
35
|
-
includeDotfiles: false,
|
|
36
|
-
includeIgnored: false,
|
|
37
|
-
} as const;
|
|
38
|
-
|
|
39
|
-
describe("buildMatcher", () => {
|
|
40
|
-
test("compiles regexes with no flags by default", () => {
|
|
41
|
-
const matcher = makeMatcher("alpha");
|
|
42
|
-
expect(matcher.regex.flags).toBe("");
|
|
43
|
-
expect(matcher.regex.test("alpha")).toBe(true);
|
|
44
|
-
expect(matcher.regex.test("Alpha")).toBe(false);
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
test("applies the i flag for caseInsensitive regexes", () => {
|
|
48
|
-
const matcher = makeMatcher("alpha", { caseInsensitive: true });
|
|
49
|
-
expect(matcher.regex.flags).toBe("i");
|
|
50
|
-
expect(matcher.regex.test("Alpha")).toBe(true);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
test("applies the s flag for matchAcrossLines regexes", () => {
|
|
54
|
-
const matcher = makeMatcher(".", { matchAcrossLines: true });
|
|
55
|
-
expect(matcher.regex.flags).toBe("s");
|
|
56
|
-
expect(matcher.regex.test("\n")).toBe(true);
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
test("throws an actionable error on invalid regex syntax", () => {
|
|
60
|
-
expect(() => makeMatcher("(")).toThrow(/Invalid regular expression/);
|
|
61
|
-
});
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
describe("findMatches", () => {
|
|
65
|
-
test("returns content matches with line numbers", async () => {
|
|
66
|
-
const root = await tempRoot();
|
|
67
|
-
const nested = join(root, "nested");
|
|
68
|
-
const older = join(root, "older.txt");
|
|
69
|
-
const newer = join(nested, "newer.txt");
|
|
70
|
-
|
|
71
|
-
await mkdir(nested);
|
|
72
|
-
await writeFile(older, "alpha\nbeta", "utf8");
|
|
73
|
-
await writeFile(newer, "gamma\nalphabet\nalpha", "utf8");
|
|
74
|
-
await utimes(
|
|
75
|
-
older,
|
|
76
|
-
new Date("2024-01-01T00:00:00Z"),
|
|
77
|
-
new Date("2024-01-01T00:00:00Z")
|
|
78
|
-
);
|
|
79
|
-
await utimes(
|
|
80
|
-
newer,
|
|
81
|
-
new Date("2024-01-02T00:00:00Z"),
|
|
82
|
-
new Date("2024-01-02T00:00:00Z")
|
|
83
|
-
);
|
|
84
|
-
|
|
85
|
-
const matches = await findMatches(
|
|
86
|
-
root,
|
|
87
|
-
undefined,
|
|
88
|
-
makeMatcher("alpha"),
|
|
89
|
-
defaultScanOptions
|
|
90
|
-
);
|
|
91
|
-
|
|
92
|
-
expect(matches.map((match) => match.filePath)).toEqual([newer, older]);
|
|
93
|
-
expect(matches[0]?.lines).toEqual([
|
|
94
|
-
{ lineNumber: 2, text: "alphabet" },
|
|
95
|
-
{ lineNumber: 3, text: "alpha" },
|
|
96
|
-
]);
|
|
97
|
-
expect(matches[1]?.lines).toEqual([{ lineNumber: 1, text: "alpha" }]);
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
test("escapes regex metacharacters when searching literal text", async () => {
|
|
101
|
-
const root = await tempRoot();
|
|
102
|
-
const path = join(root, "code.ts");
|
|
103
|
-
await writeFile(path, "useFoo(\nfoo.bar[0]\n", "utf8");
|
|
104
|
-
|
|
105
|
-
const matches = await findMatches(
|
|
106
|
-
root,
|
|
107
|
-
undefined,
|
|
108
|
-
makeMatcher("foo\\.bar\\[0\\]"),
|
|
109
|
-
defaultScanOptions
|
|
110
|
-
);
|
|
111
|
-
|
|
112
|
-
expect(matches.map((match) => match.filePath)).toEqual([path]);
|
|
113
|
-
expect(matches[0]?.lines).toEqual([{ lineNumber: 2, text: "foo.bar[0]" }]);
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
test("supports regular expressions", async () => {
|
|
117
|
-
const root = await tempRoot();
|
|
118
|
-
const path = join(root, "code.ts");
|
|
119
|
-
await writeFile(path, "alpha\nbeta\n", "utf8");
|
|
120
|
-
|
|
121
|
-
const matches = await findMatches(
|
|
122
|
-
root,
|
|
123
|
-
undefined,
|
|
124
|
-
makeMatcher("^a.*a$"),
|
|
125
|
-
defaultScanOptions
|
|
126
|
-
);
|
|
127
|
-
|
|
128
|
-
expect(matches.map((match) => match.filePath)).toEqual([path]);
|
|
129
|
-
expect(matches[0]?.lines).toEqual([{ lineNumber: 1, text: "alpha" }]);
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
test("matches escaped dots and alternation as regex syntax", async () => {
|
|
133
|
-
const root = await tempRoot();
|
|
134
|
-
const schema = join(root, "src", "extensions", "todo", "schema.ts");
|
|
135
|
-
const helper = join(root, "src", "shared", "arrays.ts");
|
|
136
|
-
|
|
137
|
-
await mkdir(join(root, "src", "extensions", "todo"), { recursive: true });
|
|
138
|
-
await mkdir(join(root, "src", "shared"), { recursive: true });
|
|
139
|
-
await writeFile(schema, "const x = Type.Union([Type.String()]);\n", "utf8");
|
|
140
|
-
await writeFile(
|
|
141
|
-
helper,
|
|
142
|
-
"export const oneOrMany = normalizeArray;\n",
|
|
143
|
-
"utf8"
|
|
144
|
-
);
|
|
145
|
-
|
|
146
|
-
const typeUnionMatches = await findMatches(
|
|
147
|
-
root,
|
|
148
|
-
"src/extensions/**/schema.ts",
|
|
149
|
-
makeMatcher("Type\\.Union"),
|
|
150
|
-
defaultScanOptions
|
|
151
|
-
);
|
|
152
|
-
const alternationMatches = await findMatches(
|
|
153
|
-
root,
|
|
154
|
-
"src/**/*.ts",
|
|
155
|
-
makeMatcher("StringOrArray|OneOrMany|oneOrMany|normalizeArray"),
|
|
156
|
-
defaultScanOptions
|
|
157
|
-
);
|
|
158
|
-
|
|
159
|
-
expect(typeUnionMatches.map((match) => match.filePath)).toEqual([schema]);
|
|
160
|
-
expect(alternationMatches.map((match) => match.filePath)).toEqual([helper]);
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
test("matchAcrossLines enables regex matches spanning line breaks", async () => {
|
|
164
|
-
const root = await tempRoot();
|
|
165
|
-
const path = join(root, "block.txt");
|
|
166
|
-
await writeFile(path, "before\nBEGIN\nmiddle\nEND\nafter\n", "utf8");
|
|
167
|
-
|
|
168
|
-
const withoutAcrossLines = await findMatches(
|
|
169
|
-
root,
|
|
170
|
-
undefined,
|
|
171
|
-
makeMatcher("BEGIN.*END"),
|
|
172
|
-
defaultScanOptions
|
|
173
|
-
);
|
|
174
|
-
const withAcrossLines = await findMatches(
|
|
175
|
-
root,
|
|
176
|
-
undefined,
|
|
177
|
-
makeMatcher("BEGIN.*END", { matchAcrossLines: true }),
|
|
178
|
-
defaultScanOptions
|
|
179
|
-
);
|
|
180
|
-
|
|
181
|
-
expect(withoutAcrossLines).toEqual([]);
|
|
182
|
-
expect(withAcrossLines.map((match) => match.filePath)).toEqual([path]);
|
|
183
|
-
expect(withAcrossLines[0]?.ranges).toEqual([
|
|
184
|
-
{ startLineNumber: 2, endLineNumber: 4 },
|
|
185
|
-
]);
|
|
186
|
-
expect(withAcrossLines[0]?.lines).toEqual([
|
|
187
|
-
{ lineNumber: 2, text: "BEGIN" },
|
|
188
|
-
{ lineNumber: 3, text: "middle" },
|
|
189
|
-
{ lineNumber: 4, text: "END" },
|
|
190
|
-
]);
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
test("matchAcrossLines enables exact regex matches spanning line breaks", async () => {
|
|
194
|
-
const root = await tempRoot();
|
|
195
|
-
const path = join(root, "block.txt");
|
|
196
|
-
await writeFile(path, "BEGIN\nmiddle\nEND\n", "utf8");
|
|
197
|
-
|
|
198
|
-
const matches = await findMatches(
|
|
199
|
-
root,
|
|
200
|
-
undefined,
|
|
201
|
-
makeMatcher("BEGIN\nmiddle", { matchAcrossLines: true }),
|
|
202
|
-
defaultScanOptions
|
|
203
|
-
);
|
|
204
|
-
|
|
205
|
-
expect(matches.map((match) => match.filePath)).toEqual([path]);
|
|
206
|
-
expect(matches[0]?.ranges).toEqual([
|
|
207
|
-
{ startLineNumber: 1, endLineNumber: 2 },
|
|
208
|
-
]);
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
test("respects gitignore, dotfiles, and the always-ignored defaults", async () => {
|
|
212
|
-
const root = await tempRoot();
|
|
213
|
-
const src = join(root, "src");
|
|
214
|
-
const ignored = join(src, "ignored.ts");
|
|
215
|
-
const kept = join(src, "kept.ts");
|
|
216
|
-
const nodeModules = join(root, "node_modules", "pkg", "x.ts");
|
|
217
|
-
const dot = join(root, ".secret", "x.ts");
|
|
218
|
-
|
|
219
|
-
await mkdir(src, { recursive: true });
|
|
220
|
-
await mkdir(join(root, "node_modules", "pkg"), { recursive: true });
|
|
221
|
-
await mkdir(join(root, ".secret"), { recursive: true });
|
|
222
|
-
await writeFile(join(root, ".gitignore"), "ignored.ts\n", "utf8");
|
|
223
|
-
await writeFile(ignored, "needle\n", "utf8");
|
|
224
|
-
await writeFile(kept, "needle\n", "utf8");
|
|
225
|
-
await writeFile(nodeModules, "needle\n", "utf8");
|
|
226
|
-
await writeFile(dot, "needle\n", "utf8");
|
|
227
|
-
|
|
228
|
-
const matches = await findMatches(
|
|
229
|
-
root,
|
|
230
|
-
undefined,
|
|
231
|
-
makeMatcher("needle"),
|
|
232
|
-
defaultScanOptions
|
|
233
|
-
);
|
|
234
|
-
|
|
235
|
-
expect(matches.map((match) => match.filePath)).toEqual([kept]);
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
test("can include dotfiles and ignored paths", async () => {
|
|
239
|
-
const root = await tempRoot();
|
|
240
|
-
const kept = join(root, "kept.ts");
|
|
241
|
-
const ignored = join(root, "ignored.ts");
|
|
242
|
-
const dot = join(root, ".secret", "x.ts");
|
|
243
|
-
|
|
244
|
-
await mkdir(join(root, ".secret"), { recursive: true });
|
|
245
|
-
await writeFile(join(root, ".gitignore"), "ignored.ts\n", "utf8");
|
|
246
|
-
await writeFile(kept, "needle\n", "utf8");
|
|
247
|
-
await writeFile(ignored, "needle\n", "utf8");
|
|
248
|
-
await writeFile(dot, "needle\n", "utf8");
|
|
249
|
-
|
|
250
|
-
const matches = await findMatches(root, undefined, makeMatcher("needle"), {
|
|
251
|
-
includeDotfiles: true,
|
|
252
|
-
includeIgnored: true,
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
expect(matches.map((match) => match.filePath).sort()).toEqual(
|
|
256
|
-
[dot, ignored, kept].sort()
|
|
257
|
-
);
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
test("searches direct file paths even when they are dotfiles or ignored", async () => {
|
|
261
|
-
const root = await tempRoot();
|
|
262
|
-
const ignored = join(root, "ignored.ts");
|
|
263
|
-
const dotfile = join(root, ".env");
|
|
264
|
-
|
|
265
|
-
await writeFile(join(root, ".gitignore"), "ignored.ts\n", "utf8");
|
|
266
|
-
await writeFile(ignored, "needle\n", "utf8");
|
|
267
|
-
await writeFile(dotfile, "needle\n", "utf8");
|
|
268
|
-
|
|
269
|
-
const ignoredMatches = await findMatches(
|
|
270
|
-
ignored,
|
|
271
|
-
undefined,
|
|
272
|
-
makeMatcher("needle"),
|
|
273
|
-
defaultScanOptions
|
|
274
|
-
);
|
|
275
|
-
const dotfileMatches = await findMatches(
|
|
276
|
-
dotfile,
|
|
277
|
-
undefined,
|
|
278
|
-
makeMatcher("needle"),
|
|
279
|
-
defaultScanOptions
|
|
280
|
-
);
|
|
281
|
-
|
|
282
|
-
expect(ignoredMatches.map((match) => match.filePath)).toEqual([ignored]);
|
|
283
|
-
expect(dotfileMatches.map((match) => match.filePath)).toEqual([dotfile]);
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
test("filters by glob", async () => {
|
|
287
|
-
const root = await tempRoot();
|
|
288
|
-
const ts = join(root, "a.ts");
|
|
289
|
-
const md = join(root, "a.md");
|
|
290
|
-
|
|
291
|
-
await writeFile(ts, "needle", "utf8");
|
|
292
|
-
await writeFile(md, "needle", "utf8");
|
|
293
|
-
|
|
294
|
-
const matches = await findMatches(
|
|
295
|
-
root,
|
|
296
|
-
"**/*.ts",
|
|
297
|
-
makeMatcher("needle"),
|
|
298
|
-
defaultScanOptions
|
|
299
|
-
);
|
|
300
|
-
|
|
301
|
-
expect(matches.map((match) => match.filePath)).toEqual([ts]);
|
|
302
|
-
});
|
|
303
|
-
|
|
304
|
-
test("excludes a single glob pattern", async () => {
|
|
305
|
-
const root = await tempRoot();
|
|
306
|
-
const source = join(root, "src", "app.ts");
|
|
307
|
-
const test = join(root, "src", "app.test.ts");
|
|
308
|
-
|
|
309
|
-
await mkdir(join(root, "src"), { recursive: true });
|
|
310
|
-
await writeFile(source, "needle", "utf8");
|
|
311
|
-
await writeFile(test, "needle", "utf8");
|
|
312
|
-
|
|
313
|
-
const matches = await findMatches(root, "**/*.ts", makeMatcher("needle"), {
|
|
314
|
-
...defaultScanOptions,
|
|
315
|
-
exclude: ["**/*.test.ts"],
|
|
316
|
-
});
|
|
317
|
-
|
|
318
|
-
expect(matches.map((match) => match.filePath)).toEqual([source]);
|
|
319
|
-
});
|
|
320
|
-
|
|
321
|
-
test("excludes multiple glob patterns", async () => {
|
|
322
|
-
const root = await tempRoot();
|
|
323
|
-
const source = join(root, "src", "app.ts");
|
|
324
|
-
const test = join(root, "src", "app.test.ts");
|
|
325
|
-
const generated = join(root, "src", "generated", "types.ts");
|
|
326
|
-
|
|
327
|
-
await mkdir(join(root, "src", "generated"), { recursive: true });
|
|
328
|
-
await writeFile(source, "needle", "utf8");
|
|
329
|
-
await writeFile(test, "needle", "utf8");
|
|
330
|
-
await writeFile(generated, "needle", "utf8");
|
|
331
|
-
|
|
332
|
-
const matches = await findMatches(root, "**/*.ts", makeMatcher("needle"), {
|
|
333
|
-
...defaultScanOptions,
|
|
334
|
-
exclude: ["**/*.test.ts", "src/generated/**"],
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
expect(matches.map((match) => match.filePath)).toEqual([source]);
|
|
338
|
-
});
|
|
339
|
-
|
|
340
|
-
test("skips binary files", async () => {
|
|
341
|
-
const root = await tempRoot();
|
|
342
|
-
const text = join(root, "text.txt");
|
|
343
|
-
const binary = join(root, "data.bin");
|
|
344
|
-
|
|
345
|
-
await writeFile(text, "needle\n", "utf8");
|
|
346
|
-
await Bun.write(binary, new Uint8Array([0x6e, 0x00, 0x65, 0x65]));
|
|
347
|
-
|
|
348
|
-
const matches = await findMatches(
|
|
349
|
-
root,
|
|
350
|
-
undefined,
|
|
351
|
-
makeMatcher("n"),
|
|
352
|
-
defaultScanOptions
|
|
353
|
-
);
|
|
354
|
-
|
|
355
|
-
expect(matches.map((match) => match.filePath)).toEqual([text]);
|
|
356
|
-
});
|
|
357
|
-
|
|
358
|
-
test("matches a single file path directly", async () => {
|
|
359
|
-
const root = await tempRoot();
|
|
360
|
-
const path = join(root, "notes.txt");
|
|
361
|
-
await writeFile(path, "alpha\nbeta\nalphabet", "utf8");
|
|
362
|
-
|
|
363
|
-
const matches = await findMatches(
|
|
364
|
-
path,
|
|
365
|
-
undefined,
|
|
366
|
-
makeMatcher("alpha"),
|
|
367
|
-
defaultScanOptions
|
|
368
|
-
);
|
|
369
|
-
|
|
370
|
-
expect(matches.length).toBe(1);
|
|
371
|
-
expect(matches[0]?.lines).toEqual([
|
|
372
|
-
{ lineNumber: 1, text: "alpha" },
|
|
373
|
-
{ lineNumber: 3, text: "alphabet" },
|
|
374
|
-
]);
|
|
375
|
-
});
|
|
376
|
-
|
|
377
|
-
test("throws an actionable error when the path does not exist", async () => {
|
|
378
|
-
const root = await tempRoot();
|
|
379
|
-
const missing = join(root, "nope");
|
|
380
|
-
|
|
381
|
-
await expect(
|
|
382
|
-
findMatches(missing, undefined, makeMatcher("x"), defaultScanOptions)
|
|
383
|
-
).rejects.toThrow(
|
|
384
|
-
`Path not found: ${missing}. Use glob to locate the file or directory, or verify the path.`
|
|
385
|
-
);
|
|
386
|
-
});
|
|
387
|
-
});
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test";
|
|
2
|
-
import type {
|
|
3
|
-
AgentToolResult,
|
|
4
|
-
ExtensionAPI,
|
|
5
|
-
Theme,
|
|
6
|
-
ToolDefinition,
|
|
7
|
-
} from "@earendil-works/pi-coding-agent";
|
|
8
|
-
import registerGrep from "./index";
|
|
9
|
-
|
|
10
|
-
const stubTheme = {
|
|
11
|
-
bold: (text: string) => text,
|
|
12
|
-
fg: (_color: string, text: string) => text,
|
|
13
|
-
} as unknown as Theme;
|
|
14
|
-
|
|
15
|
-
function registeredTool(): ToolDefinition {
|
|
16
|
-
let tool: ToolDefinition | undefined;
|
|
17
|
-
registerGrep({
|
|
18
|
-
registerTool(def: ToolDefinition): void {
|
|
19
|
-
tool = def;
|
|
20
|
-
},
|
|
21
|
-
} as unknown as ExtensionAPI);
|
|
22
|
-
|
|
23
|
-
if (tool === undefined) {
|
|
24
|
-
throw new Error("grep tool was not registered");
|
|
25
|
-
}
|
|
26
|
-
return tool;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
describe("grep tool renderer", () => {
|
|
30
|
-
test("updates the visible call title with the file count when the result renders", () => {
|
|
31
|
-
const tool = registeredTool();
|
|
32
|
-
const args = { pattern: "alpha" };
|
|
33
|
-
const state = {};
|
|
34
|
-
const callContext = {
|
|
35
|
-
args,
|
|
36
|
-
toolCallId: "grep-1",
|
|
37
|
-
invalidate: () => {},
|
|
38
|
-
lastComponent: undefined,
|
|
39
|
-
state,
|
|
40
|
-
cwd: "/repo",
|
|
41
|
-
executionStarted: true,
|
|
42
|
-
argsComplete: true,
|
|
43
|
-
isPartial: false,
|
|
44
|
-
expanded: false,
|
|
45
|
-
showImages: true,
|
|
46
|
-
isError: false,
|
|
47
|
-
};
|
|
48
|
-
const callComponent = tool.renderCall!(args, stubTheme, callContext);
|
|
49
|
-
|
|
50
|
-
expect(callComponent.render(120).join("\n")).not.toContain("(2 files)");
|
|
51
|
-
|
|
52
|
-
const result: AgentToolResult<unknown> = {
|
|
53
|
-
content: [{ type: "text", text: "src/a.ts\nsrc/b.ts" }],
|
|
54
|
-
details: { fileCount: 2 },
|
|
55
|
-
};
|
|
56
|
-
tool.renderResult!(
|
|
57
|
-
result,
|
|
58
|
-
{ expanded: false, isPartial: false },
|
|
59
|
-
stubTheme,
|
|
60
|
-
{
|
|
61
|
-
...callContext,
|
|
62
|
-
lastComponent: undefined,
|
|
63
|
-
}
|
|
64
|
-
);
|
|
65
|
-
|
|
66
|
-
expect(callComponent.render(120).join("\n")).toContain("(2 files)");
|
|
67
|
-
});
|
|
68
|
-
});
|