Skip to content

Commit 4f6770a

Browse files
Fix false positive warnings from parsing inline code/URLs as file paths (#68) (#69)
* Fix false positive warnings from parsing inline code/URLs as file paths (#68) Add looksLikeURL helper to detect protocol-prefixed URLs and domain-like patterns (e.g., example.com/path) so they are skipped during file reference extraction. Apply filtering to both backtick matches and markdown link matches. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add Windows-style path test case for looksLikeURL Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent a4d66db commit 4f6770a

2 files changed

Lines changed: 89 additions & 1 deletion

File tree

internal/skill/folder.go

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,25 @@ var (
4444
mdLinkRe = regexp.MustCompile(`\]\(([^)]+)\)`)
4545
)
4646

47+
// looksLikeURL reports whether s looks like a URL rather than a file path.
48+
// It checks for protocol prefixes (http://, https://, ftp://) and domain-like
49+
// patterns (e.g., example.com/path).
50+
func looksLikeURL(s string) bool {
51+
if strings.HasPrefix(s, "http://") || strings.HasPrefix(s, "https://") || strings.HasPrefix(s, "ftp://") {
52+
return true
53+
}
54+
// Check for domain-like pattern: word with dot before the first slash
55+
// e.g., "example.com/path", "docs.example.io/guide"
56+
slashIdx := strings.Index(s, "/")
57+
if slashIdx > 0 {
58+
prefix := s[:slashIdx]
59+
if strings.Contains(prefix, ".") && !strings.HasPrefix(prefix, ".") && !strings.HasPrefix(prefix, "..") {
60+
return true
61+
}
62+
}
63+
return false
64+
}
65+
4766
// ExtractFileReferences extracts path-like references from a markdown body.
4867
// It looks for backtick-enclosed paths containing '/' and markdown link targets
4968
// that are not URLs or anchors.
@@ -53,6 +72,9 @@ func ExtractFileReferences(body string) []string {
5372

5473
for _, m := range backtickPathRe.FindAllStringSubmatch(body, -1) {
5574
p := m[1]
75+
if looksLikeURL(p) {
76+
continue
77+
}
5678
if !seen[p] {
5779
seen[p] = true
5880
refs = append(refs, p)
@@ -61,7 +83,7 @@ func ExtractFileReferences(body string) []string {
6183

6284
for _, m := range mdLinkRe.FindAllStringSubmatch(body, -1) {
6385
p := m[1]
64-
if strings.HasPrefix(p, "http") || strings.HasPrefix(p, "#") {
86+
if looksLikeURL(p) || strings.HasPrefix(p, "#") {
6587
continue
6688
}
6789
if !seen[p] {

internal/skill/folder_test.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,41 @@ func TestExtractFileReferences(t *testing.T) {
123123
body: "Run `scripts/run.py` or see [script](scripts/run.py).",
124124
expected: []string{"scripts/run.py"},
125125
},
126+
{
127+
name: "backtick URL with protocol skipped",
128+
body: "See `https://example.com/docs/guide` for details.",
129+
expected: nil,
130+
},
131+
{
132+
name: "backtick URL without protocol skipped",
133+
body: "Use `example.com/api/v1` as the endpoint.",
134+
expected: nil,
135+
},
136+
{
137+
name: "backtick domain-like paths skipped",
138+
body: "Check `docs.example.io/guide` and `pkg.go.dev/encoding/json` for reference.",
139+
expected: nil,
140+
},
141+
{
142+
name: "markdown link with domain-like URL skipped",
143+
body: "See [docs](docs.example.com/guide) for more info.",
144+
expected: nil,
145+
},
146+
{
147+
name: "backtick URL mixed with real path",
148+
body: "Use `example.com/api` for the API and `scripts/run.py` for processing.",
149+
expected: []string{"scripts/run.py"},
150+
},
151+
{
152+
name: "relative path with dots not skipped",
153+
body: "Use `./scripts/run.py` and `../lib/utils.py` as helpers.",
154+
expected: []string{"./scripts/run.py", "../lib/utils.py"},
155+
},
156+
{
157+
name: "ftp URL in backtick skipped",
158+
body: "Download from `ftp://files.example.com/data`.",
159+
expected: nil,
160+
},
126161
}
127162

128163
for _, tt := range tests {
@@ -133,6 +168,31 @@ func TestExtractFileReferences(t *testing.T) {
133168
}
134169
}
135170

171+
func TestLooksLikeURL(t *testing.T) {
172+
tests := []struct {
173+
input string
174+
want bool
175+
}{
176+
{"https://example.com/path", true},
177+
{"http://example.com/path", true},
178+
{"ftp://files.example.com/data", true},
179+
{"example.com/path", true},
180+
{"docs.example.io/guide", true},
181+
{"pkg.go.dev/encoding/json", true},
182+
{"scripts/run.py", false},
183+
{"./scripts/run.py", false},
184+
{"../lib/utils.py", false},
185+
{"assets/template.json", false},
186+
{"C://Windows//Users//", false},
187+
}
188+
189+
for _, tt := range tests {
190+
t.Run(tt.input, func(t *testing.T) {
191+
assert.Equal(t, tt.want, looksLikeURL(tt.input))
192+
})
193+
}
194+
}
195+
136196
func TestValidateFolder(t *testing.T) {
137197
tests := []struct {
138198
name string
@@ -161,6 +221,12 @@ func TestValidateFolder(t *testing.T) {
161221
setup: func(dir string) {},
162222
wantIssues: 0,
163223
},
224+
{
225+
name: "URLs in backticks do not warn",
226+
body: "See `https://example.com/docs` and `example.com/api/v1` for details.",
227+
setup: func(dir string) {},
228+
wantIssues: 0,
229+
},
164230
}
165231

166232
for _, tt := range tests {

0 commit comments

Comments
 (0)