From 53ad34cbee460b0758129020a24d18e1b9a04ff7 Mon Sep 17 00:00:00 2001 From: Tien Dung Dao Date: Fri, 21 Aug 2026 16:57:29 +0700 Subject: [PATCH] fix(mcp): match fidelity globs with path.Match, not filepath.Match MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit matchFidelityGlob normalizes both the pattern and the path to forward slashes, then hands them to filepath.Match — whose separator is the platform's. On Windows '/' is an ordinary character to that matcher, so a single `*` crosses it: matchFidelityGlob("internal/*.go", "internal/sub/x.go") = true on windows, false on linux/macos The file's own doc-comment states the assumption this breaks: "Go's filepath.Match never crosses `/`". That holds on POSIX and is why the linux/macos matrix has never seen it. Everything around it already works in slash space — ToSlash on entry, Split(rel, "/"), the `**` prefix and suffix handling — so path.Match and path.Base are the matching primitives that space calls for. Identical to filepath.Match on POSIX, where the separator already is '/'. fidelity_globs is a public tool parameter on read_file and get_editing_context, so on Windows a documented `internal/*.go` rule silently applied to the whole subtree beneath internal/. Whole package on windows: TestMatchFidelityGlob flips, newly-broken set empty. --- internal/mcp/fidelity_globs.go | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/internal/mcp/fidelity_globs.go b/internal/mcp/fidelity_globs.go index 61313aa0a..c30023533 100644 --- a/internal/mcp/fidelity_globs.go +++ b/internal/mcp/fidelity_globs.go @@ -1,6 +1,7 @@ package mcp import ( + "path" "path/filepath" "strings" @@ -89,7 +90,8 @@ func fidelityDecideForPath(rules []fidelityRule, relPath string) func(elide.Decl // matchFidelityGlob matches a glob against a forward-slash relative // path. It extends matchPathPattern's basename/prefix semantics with // explicit `**` support so the documented `internal/**` / `**/*.go` -// forms work as written (Go's filepath.Match never crosses `/`). +// forms work as written (a single `*` never crosses `/` — see +// matchSegmentGlob for why that requires path.Match, not filepath.Match). func matchFidelityGlob(pattern, rel string) bool { pattern = filepath.ToSlash(pattern) rel = filepath.ToSlash(rel) @@ -125,13 +127,20 @@ func matchFidelityGlob(pattern, rel string) bool { } // matchSegmentGlob applies the single-segment glob semantics shared -// with matchPathPattern: filepath.Match against the full path and the +// with matchPathPattern: a glob match against the full path and the // basename, plus a bare directory-prefix shortcut. +// +// path.Match, not filepath.Match. Both callers hand this function a +// forward-slash path, and filepath.Match's separator is the platform's: +// on Windows '/' is an ordinary character there, so `*` crosses it and +// `internal/*.go` matches `internal/sub/x.go`. path.Match's separator is +// always '/', which is the semantics this file's `**` handling — and its +// own doc-comment — already assume. func matchSegmentGlob(pattern, rel string) bool { - if ok, _ := filepath.Match(pattern, rel); ok { + if ok, _ := path.Match(pattern, rel); ok { return true } - if ok, _ := filepath.Match(pattern, filepath.Base(rel)); ok { + if ok, _ := path.Match(pattern, path.Base(rel)); ok { return true } if strings.HasSuffix(pattern, "/*") {