@@ -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