Skip to content

Commit b2b51eb

Browse files
committed
test: util — findUp, globUp directory traversal and overlaps path boundary
findUp/globUp power instruction file discovery (AGENTS.md, CLAUDE.md). If they fail silently or stop early, users in nested project dirs lose all project context. overlaps() governs project boundary detection; incorrect results could cause cross-project context bleeding. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> https://claude.ai/code/session_01Hfr4bad38JKSQzQejhHzjC
1 parent 37dd7db commit b2b51eb

1 file changed

Lines changed: 145 additions & 0 deletions

File tree

packages/opencode/test/util/filesystem.test.ts

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -555,4 +555,149 @@ describe("filesystem", () => {
555555
expect(() => Filesystem.resolve(path.join(file, "child"))).toThrow()
556556
})
557557
})
558+
559+
describe("findUp()", () => {
560+
test("finds file at start directory", async () => {
561+
await using tmp = await tmpdir()
562+
const target = path.join(tmp.path, "AGENTS.md")
563+
await fs.writeFile(target, "root instructions")
564+
565+
const results = await Filesystem.findUp("AGENTS.md", tmp.path)
566+
expect(results).toEqual([target])
567+
})
568+
569+
test("finds files at multiple levels walking upward", async () => {
570+
await using tmp = await tmpdir()
571+
// Create nested dirs: root/a/b/c
572+
const dirA = path.join(tmp.path, "a")
573+
const dirB = path.join(tmp.path, "a", "b")
574+
const dirC = path.join(tmp.path, "a", "b", "c")
575+
await fs.mkdir(dirC, { recursive: true })
576+
577+
// Place AGENTS.md at root and at a/b (but not at a or a/b/c)
578+
await fs.writeFile(path.join(tmp.path, "AGENTS.md"), "root")
579+
await fs.writeFile(path.join(dirB, "AGENTS.md"), "mid")
580+
581+
// Start from a/b/c, walk up to root
582+
const results = await Filesystem.findUp("AGENTS.md", dirC, tmp.path)
583+
// Should find mid-level first (closer to start), then root
584+
expect(results).toEqual([
585+
path.join(dirB, "AGENTS.md"),
586+
path.join(tmp.path, "AGENTS.md"),
587+
])
588+
})
589+
590+
test("stop directory is inclusive (file at stop level is found)", async () => {
591+
await using tmp = await tmpdir()
592+
const child = path.join(tmp.path, "child")
593+
await fs.mkdir(child)
594+
await fs.writeFile(path.join(tmp.path, "AGENTS.md"), "at stop level")
595+
596+
const results = await Filesystem.findUp("AGENTS.md", child, tmp.path)
597+
expect(results).toEqual([path.join(tmp.path, "AGENTS.md")])
598+
})
599+
600+
test("returns empty when file does not exist anywhere", async () => {
601+
await using tmp = await tmpdir()
602+
const child = path.join(tmp.path, "nested")
603+
await fs.mkdir(child)
604+
605+
const results = await Filesystem.findUp("AGENTS.md", child, tmp.path)
606+
expect(results).toEqual([])
607+
})
608+
609+
test("does not traverse past stop directory", async () => {
610+
await using tmp = await tmpdir()
611+
const stopDir = path.join(tmp.path, "stop")
612+
const startDir = path.join(tmp.path, "stop", "deep")
613+
await fs.mkdir(startDir, { recursive: true })
614+
615+
// Place file above the stop directory — should NOT be found
616+
await fs.writeFile(path.join(tmp.path, "AGENTS.md"), "above stop")
617+
618+
const results = await Filesystem.findUp("AGENTS.md", startDir, stopDir)
619+
expect(results).toEqual([])
620+
})
621+
})
622+
623+
describe("globUp()", () => {
624+
test("finds files matching glob pattern at multiple levels", async () => {
625+
await using tmp = await tmpdir()
626+
const child = path.join(tmp.path, "sub")
627+
await fs.mkdir(child)
628+
629+
await fs.writeFile(path.join(tmp.path, "notes.md"), "root notes")
630+
await fs.writeFile(path.join(child, "readme.md"), "sub readme")
631+
632+
const results = await Filesystem.globUp("*.md", child, tmp.path)
633+
// Child level first, then root level
634+
expect(results.length).toBe(2)
635+
expect(results).toContain(path.join(child, "readme.md"))
636+
expect(results).toContain(path.join(tmp.path, "notes.md"))
637+
})
638+
639+
test("wildcard matches multiple files at one level", async () => {
640+
await using tmp = await tmpdir()
641+
await fs.writeFile(path.join(tmp.path, "a.md"), "a")
642+
await fs.writeFile(path.join(tmp.path, "b.md"), "b")
643+
await fs.writeFile(path.join(tmp.path, "c.txt"), "c")
644+
645+
const results = await Filesystem.globUp("*.md", tmp.path, tmp.path)
646+
expect(results.length).toBe(2)
647+
expect(results.some((r) => r.endsWith("a.md"))).toBe(true)
648+
expect(results.some((r) => r.endsWith("b.md"))).toBe(true)
649+
})
650+
651+
test("returns empty when no matches exist", async () => {
652+
await using tmp = await tmpdir()
653+
const results = await Filesystem.globUp("*.md", tmp.path, tmp.path)
654+
expect(results).toEqual([])
655+
})
656+
657+
test("does not traverse past stop directory", async () => {
658+
await using tmp = await tmpdir()
659+
const stopDir = path.join(tmp.path, "stop")
660+
const startDir = path.join(tmp.path, "stop", "deep")
661+
await fs.mkdir(startDir, { recursive: true })
662+
663+
await fs.writeFile(path.join(tmp.path, "notes.md"), "above stop")
664+
665+
const results = await Filesystem.globUp("*.md", startDir, stopDir)
666+
expect(results).toEqual([])
667+
})
668+
})
669+
670+
describe("overlaps()", () => {
671+
test("same path overlaps with itself", () => {
672+
expect(Filesystem.overlaps("/a/b", "/a/b")).toBe(true)
673+
})
674+
675+
test("parent and child overlap", () => {
676+
expect(Filesystem.overlaps("/a", "/a/b")).toBe(true)
677+
expect(Filesystem.overlaps("/a/b", "/a")).toBe(true)
678+
})
679+
680+
test("sibling directories do not overlap", () => {
681+
expect(Filesystem.overlaps("/a/b", "/a/c")).toBe(false)
682+
})
683+
684+
test("unrelated paths do not overlap", () => {
685+
expect(Filesystem.overlaps("/foo", "/bar")).toBe(false)
686+
})
687+
688+
test("path that is a string prefix but not an ancestor does not overlap", () => {
689+
// /foo/bar and /foo/barbaz — "barbaz" starts with "bar" but is a sibling
690+
expect(Filesystem.overlaps("/foo/bar", "/foo/barbaz")).toBe(false)
691+
})
692+
693+
test("trailing slash does not affect result", () => {
694+
expect(Filesystem.overlaps("/a/b/", "/a/b")).toBe(true)
695+
expect(Filesystem.overlaps("/a/b", "/a/b/")).toBe(true)
696+
})
697+
698+
test("root overlaps with everything", () => {
699+
expect(Filesystem.overlaps("/", "/any/path")).toBe(true)
700+
expect(Filesystem.overlaps("/any/path", "/")).toBe(true)
701+
})
702+
})
558703
})

0 commit comments

Comments
 (0)