From 4af0bd134f9d65daf5d570f7110c0db292b26872 Mon Sep 17 00:00:00 2001 From: Tatlatat Date: Thu, 4 Jun 2026 00:27:30 +0700 Subject: [PATCH 1/2] fix(glob): match the pattern relative to the path base dir, not the root MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The glob tool documents its path argument as "Base directory to walk … The pattern matches relative to this path." But globFiles walked from startAbs while testing the pattern against the path relative to ctx.rootDir. So glob(path: "src", pattern: "*.ts") compared "src/index.ts" to "*.ts", matched nothing, and returned "(no matches)" even though index.ts matches *.ts relative to src. Match against a path relative to startAbs (the walk root) while keeping the displayed result relative to rootDir, so output still shows "src/index.ts". When no path is given (startAbs == rootDir) behavior is unchanged. Add a test that globbing *.ts under path "src" finds src/index.ts. --- src/tools/fs/glob.ts | 3 ++- tests/filesystem-tools.test.ts | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/tools/fs/glob.ts b/src/tools/fs/glob.ts index 7a730cfca..a0d9ad67b 100644 --- a/src/tools/fs/glob.ts +++ b/src/tools/fs/glob.ts @@ -51,7 +51,8 @@ export async function globFiles( } if (!e.isFile() && !e.isSymbolicLink()) continue; const rel = displayRel(ctx.rootDir, full); - if (!isMatch(rel)) continue; + const matchRel = displayRel(startAbs, full); + if (!isMatch(matchRel)) continue; let mtimeMs = 0; if (sortBy === "mtime") { try { diff --git a/tests/filesystem-tools.test.ts b/tests/filesystem-tools.test.ts index 29c941826..4a634749e 100644 --- a/tests/filesystem-tools.test.ts +++ b/tests/filesystem-tools.test.ts @@ -1309,6 +1309,12 @@ describe("filesystem tools (built-in, sandbox-enforced)", () => { expect(lines.length).toBe(3); expect(lines[lines.length - 1]).toMatch(/3 more matches/); }); + + it("matches relative to a custom path base directory", async () => { + const out = await tools.dispatch("glob", JSON.stringify({ path: "src", pattern: "*.ts" })); + expect(out).toContain("src/index.ts"); + expect(out).not.toContain("(no matches)"); + }); }); describe("search_content — context lines (-A/-B/-C semantics)", () => { From b4b4ee0a694830a4048e9822570618c0603f1ff7 Mon Sep 17 00:00:00 2001 From: Tatlatat Date: Thu, 4 Jun 2026 09:21:21 +0700 Subject: [PATCH 2/2] test(glob): cover absolute path arg and dot path for relative matching Per review feedback: add a case where path is an absolute directory (the match must still resolve relative to it) and a path "." case confirming top-level *.ts matches while nested files are not pulled in by the non-recursive pattern. --- tests/filesystem-tools.test.ts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/filesystem-tools.test.ts b/tests/filesystem-tools.test.ts index 4a634749e..f6acf2120 100644 --- a/tests/filesystem-tools.test.ts +++ b/tests/filesystem-tools.test.ts @@ -1315,6 +1315,27 @@ describe("filesystem tools (built-in, sandbox-enforced)", () => { expect(out).toContain("src/index.ts"); expect(out).not.toContain("(no matches)"); }); + + it("matches relative to an absolute path argument", async () => { + const absDir = join(root, "absdir"); + await fs.mkdir(absDir, { recursive: true }); + await fs.writeFile(join(absDir, "testfile.ts"), "export const a = 1;"); + const out = await tools.dispatch("glob", JSON.stringify({ path: absDir, pattern: "*.ts" })); + expect(out).toContain("absdir/testfile.ts"); + expect(out).not.toContain("(no matches)"); + }); + + it("matches relative to dot path '.' and does not recurse unless requested", async () => { + await fs.writeFile(join(root, "top.ts"), "x"); + await fs.mkdir(join(root, "sub"), { recursive: true }); + await fs.writeFile(join(root, "sub", "a.ts"), "y"); + + const out = await tools.dispatch("glob", JSON.stringify({ path: ".", pattern: "*.ts" })); + const lines = out.split("\n").filter((l) => l.trim()); + expect(lines).toContain("top.ts"); + expect(lines).not.toContain("sub/a.ts"); + expect(lines).not.toContain("src/index.ts"); + }); }); describe("search_content — context lines (-A/-B/-C semantics)", () => {