@@ -353,27 +353,44 @@ impl Conflict {
353353
354354/// Check if two sets of file scope patterns overlap.
355355///
356- /// Uses prefix matching: strips glob suffixes (`/**`, `/*`) and checks
357- /// if one prefix starts with the other. Simple but effective for the
358- /// typical patterns the model generates (e.g. `src/auth/**`).
356+ /// Strips glob wildcards (`**`, `*`) and then checks whether the
357+ /// resulting directory prefixes overlap at a path-segment boundary.
358+ /// `src/api/**` vs `src/apiv2/**` → no overlap (different segments).
359+ /// `src/*/handler.rs` strips the `*` and checks prefix boundaries.
359360fn scopes_overlap ( a : & [ String ] , b : & [ String ] ) -> bool {
360361 if a. is_empty ( ) || b. is_empty ( ) {
361362 return false ;
362363 }
363364
364- fn strip_glob ( s : & str ) -> & str {
365- s. trim_end_matches ( '/' )
366- . trim_end_matches ( "**" )
367- . trim_end_matches ( '*' )
368- . trim_end_matches ( '/' )
365+ /// Strip glob wildcards from the end of a pattern, stopping before
366+ /// the last directory separator so path-boundary matching works.
367+ fn normalize_pattern ( s : & str ) -> String {
368+ // Remove trailing globs: /** → nothing, /* → nothing, /*.rs → nothing
369+ let mut p = s. trim_end_matches ( '/' ) . to_string ( ) ;
370+ while p. ends_with ( "**" ) || p. ends_with ( '*' ) {
371+ let trimmed = p. trim_end_matches ( "**" ) . trim_end_matches ( '*' ) ;
372+ if trimmed. len ( ) == p. len ( ) {
373+ break ;
374+ }
375+ p = trimmed. to_string ( ) ;
376+ }
377+ // Ensure we end at a directory boundary for correct prefix matching.
378+ p = p. trim_end_matches ( '/' ) . to_string ( ) ;
379+ p
369380 }
370381
371- let a_prefixes: Vec < & str > = a. iter ( ) . map ( |s| strip_glob ( s) ) . collect ( ) ;
372- let b_prefixes: Vec < & str > = b. iter ( ) . map ( |s| strip_glob ( s) ) . collect ( ) ;
382+ let a_prefixes: Vec < String > = a. iter ( ) . map ( |s| normalize_pattern ( s) ) . collect ( ) ;
383+ let b_prefixes: Vec < String > = b. iter ( ) . map ( |s| normalize_pattern ( s) ) . collect ( ) ;
373384
374385 for ap in & a_prefixes {
375386 for bp in & b_prefixes {
376- if ap. starts_with ( bp) || bp. starts_with ( ap) {
387+ // Only flag as overlapping if one is a path-segment prefix of
388+ // the other, i.e. `src/api` matches `src/api` or `src/api/...`
389+ // but NOT `src/apiv2`.
390+ if ap == bp
391+ || ap. starts_with ( & format ! ( "{}/" , bp) )
392+ || bp. starts_with ( & format ! ( "{}/" , ap) )
393+ {
377394 return true ;
378395 }
379396 }
0 commit comments