Skip to content

Commit 2702fb2

Browse files
authored
Merge pull request #6394 from uinstinct/prefix-folder
feat: prefix folder path for colocated rules
2 parents 0e0302f + 4d54232 commit 2702fb2

File tree

3 files changed

+137
-14
lines changed

3 files changed

+137
-14
lines changed

packages/config-yaml/src/markdown/createMarkdownRule.test.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { markdownToRule } from "./markdownToRule.js";
99
// Mock package identifier for testing
1010
const mockPackageId: PackageIdentifier = {
1111
uriType: "file",
12-
filePath: "/path/to/file",
12+
filePath: "/path/to/folder",
1313
};
1414

1515
describe("sanitizeRuleName", () => {
@@ -85,7 +85,9 @@ Just markdown content.`;
8585

8686
expect(parsed.name).toBe(originalFrontmatter.name);
8787
expect(parsed.description).toBe(originalFrontmatter.description);
88-
expect(parsed.globs).toEqual(originalFrontmatter.globs);
88+
expect(parsed.globs).toEqual(
89+
originalFrontmatter.globs.map((glob) => `/path/to/**/${glob}`),
90+
);
8991
expect(parsed.alwaysApply).toBe(originalFrontmatter.alwaysApply);
9092
expect(parsed.rule).toBe(originalMarkdown);
9193
});
@@ -102,7 +104,7 @@ describe("createRuleMarkdown", () => {
102104
const parsed = markdownToRule(result, mockPackageId);
103105

104106
expect(parsed.description).toBe("Test description");
105-
expect(parsed.globs).toEqual(["*.ts", "*.js"]);
107+
expect(parsed.globs).toEqual(["/path/to/**/*.ts", "/path/to/**/*.js"]);
106108
expect(parsed.alwaysApply).toBe(true);
107109
expect(parsed.rule).toBe("This is the rule content");
108110
});
@@ -113,7 +115,7 @@ describe("createRuleMarkdown", () => {
113115
const parsed = markdownToRule(result, mockPackageId);
114116

115117
expect(parsed.description).toBeUndefined();
116-
expect(parsed.globs).toBeUndefined();
118+
expect(parsed.globs).toBe("/path/to/**/*");
117119
expect(parsed.alwaysApply).toBeUndefined();
118120
expect(parsed.rule).toBe("Simple content");
119121
});
@@ -124,7 +126,7 @@ describe("createRuleMarkdown", () => {
124126
});
125127

126128
const parsed = markdownToRule(result, mockPackageId);
127-
expect(parsed.globs).toBe("*.py");
129+
expect(parsed.globs).toBe("/path/to/**/*.py");
128130
});
129131

130132
it("should trim description and globs", () => {
@@ -135,7 +137,7 @@ describe("createRuleMarkdown", () => {
135137

136138
const parsed = markdownToRule(result, mockPackageId);
137139
expect(parsed.description).toBe("spaced description");
138-
expect(parsed.globs).toBe("*.ts");
140+
expect(parsed.globs).toBe("/path/to/**/*.ts");
139141
});
140142

141143
it("should handle alwaysApply false explicitly", () => {

packages/config-yaml/src/markdown/markdownToRule.test.ts

Lines changed: 98 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ This is a test rule.`;
2020

2121
const result = markdownToRule(content, mockId);
2222
expect(result.rule).toBe("# Test Rule\n\nThis is a test rule.");
23-
expect(result.globs).toBe("**/test/**/*.kt");
23+
expect(result.globs).toBe("/path/to/**/test/**/*.kt");
2424
expect(result.name).toBe("Custom Name");
2525
});
2626

@@ -34,7 +34,7 @@ globs: "**/test/**/*.kt"
3434
This is a test rule.`;
3535

3636
const result = markdownToRule(content, mockId);
37-
expect(result.globs).toBe("**/test/**/*.kt");
37+
expect(result.globs).toBe("/path/to/**/test/**/*.kt");
3838
expect(result.rule).toBe("# Test Rule\n\nThis is a test rule.");
3939
expect(result.name).toBe("to/file"); // Should use last two path segments
4040
});
@@ -45,7 +45,7 @@ This is a test rule.`;
4545
This is a test rule without frontmatter.`;
4646

4747
const result = markdownToRule(content, mockId);
48-
expect(result.globs).toBeUndefined();
48+
expect(result.globs).toBe("/path/to/**/*");
4949
expect(result.rule).toBe(content);
5050
expect(result.name).toBe("to/file"); // Should use last two path segments
5151
});
@@ -59,13 +59,104 @@ This is a test rule without frontmatter.`;
5959
This is a test rule with empty frontmatter.`;
6060

6161
const result = markdownToRule(content, mockId);
62-
expect(result.globs).toBeUndefined();
62+
expect(result.globs).toBe("/path/to/**/*");
6363
expect(result.rule).toBe(
6464
"# Test Rule\n\nThis is a test rule with empty frontmatter.",
6565
);
6666
expect(result.name).toBe("to/file"); // Should use last two path segments
6767
});
6868

69+
describe("glob patterns", () => {
70+
// we want to ensure the following glob changes happen
71+
// *.ts -> path/to/**/*.ts
72+
// **/subdir/** -> path/to/**/**/subdir/** OR path/to/**/subdir/**
73+
// myfile -> path/to/**/myfile (any depth including 0)
74+
// mydir/ -> path/to/**/mydir/**
75+
// **abc** -> path/to/**abc**
76+
// *xyz* -> path/to/**/*xyz*
77+
78+
it("should match glob pattern for file extensions", () => {
79+
const content = `---
80+
globs: "*.ts"
81+
name: glob pattern testing
82+
---
83+
84+
# Test Rule
85+
86+
This is a test rule.`;
87+
88+
const result = markdownToRule(content, mockId);
89+
expect(result.globs).toBe("/path/to/**/*.ts");
90+
});
91+
92+
it("should match glob pattern for dotfiles", () => {
93+
const content = `---
94+
globs: ".gitignore"
95+
name: glob pattern testing
96+
---
97+
98+
# Test Rule
99+
100+
This is a test rule.`;
101+
102+
const result = markdownToRule(content, mockId);
103+
expect(result.globs).toBe("/path/to/**/.gitignore");
104+
});
105+
106+
it("should also work in root as base directory", () => {
107+
const content = `---
108+
globs: "src/**/Dockerfile"
109+
name: glob pattern testing
110+
---
111+
112+
# Test Rule
113+
114+
This is a test rule.`;
115+
116+
const result = markdownToRule(content, {
117+
uriType: "file",
118+
filePath: "/",
119+
});
120+
expect(result.globs).toBe("/**/src/**/Dockerfile");
121+
});
122+
123+
it("should match for multiple globs", () => {
124+
const content = `---
125+
globs: ["**/nested/**/deeper/**/*.rs", ".zshrc1", "**abc**", "*xyz*"]
126+
name: glob pattern testing
127+
---
128+
129+
# Test Rule
130+
131+
This is a test rule.`;
132+
133+
const result = markdownToRule(content, mockId);
134+
expect(result.globs).toEqual([
135+
"/path/to/**/nested/**/deeper/**/*.rs",
136+
"/path/to/**/.zshrc1",
137+
"/path/to/**abc**",
138+
"/path/to/**/*xyz*",
139+
]);
140+
});
141+
142+
it("should not prepend when inside .continue", () => {
143+
const content = `---
144+
globs: ".git"
145+
name: glob pattern testing
146+
---
147+
148+
# Test Rule
149+
150+
This is a test rule.`;
151+
152+
const result = markdownToRule(content, {
153+
uriType: "file",
154+
filePath: "/Documents/myproject/.continue/rules/rule1.md",
155+
});
156+
expect(result.globs).toBe(".git");
157+
});
158+
});
159+
69160
it("should handle frontmatter with whitespace", () => {
70161
const content = `---
71162
globs: "**/test/**/*.kt"
@@ -76,7 +167,7 @@ globs: "**/test/**/*.kt"
76167
This is a test rule.`;
77168

78169
const result = markdownToRule(content, mockId);
79-
expect(result.globs).toBe("**/test/**/*.kt");
170+
expect(result.globs).toBe("/path/to/**/test/**/*.kt");
80171
expect(result.rule).toBe("# Test Rule\n\nThis is a test rule.");
81172
expect(result.name).toBe("to/file"); // Should use last two path segments
82173
});
@@ -92,7 +183,7 @@ globs: "**/test/**/*.kt"\r
92183
This is a test rule.`;
93184

94185
const result = markdownToRule(content, mockId);
95-
expect(result.globs).toBe("**/test/**/*.kt");
186+
expect(result.globs).toBe("/path/to/**/test/**/*.kt");
96187
// The result should be normalized to \n
97188
expect(result.rule).toBe("# Test Rule\n\nThis is a test rule.");
98189
expect(result.name).toBe("to/file"); // Should use last two path segments
@@ -110,7 +201,7 @@ This is a test rule.`;
110201

111202
// Should treat as only markdown when frontmatter is malformed
112203
const result = markdownToRule(content, mockId);
113-
expect(result.globs).toBeUndefined();
204+
expect(result.globs).toBe("/path/to/**/*");
114205
expect(result.rule).toBe(content);
115206
expect(result.name).toBe("to/file"); // Should use last two path segments
116207
});

packages/config-yaml/src/markdown/markdownToRule.ts

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import path from "path";
12
import * as YAML from "yaml";
23
import {
34
PackageIdentifier,
@@ -69,6 +70,35 @@ export function getRuleName(
6970
return displayName;
7071
}
7172

73+
function getGlobPattern(
74+
globs: RuleFrontmatter["globs"],
75+
id: PackageIdentifier,
76+
) {
77+
if (id.uriType !== "file") {
78+
return globs;
79+
}
80+
let dir = path.dirname(id.filePath);
81+
if (dir.includes(".continue")) {
82+
return globs;
83+
}
84+
if (!dir.endsWith("/")) {
85+
dir = dir.concat("/");
86+
}
87+
const prependDirAndApplyGlobstar = (glob: string) => {
88+
if (glob.startsWith("**")) {
89+
return dir.concat(glob);
90+
}
91+
return dir.concat("**/", glob);
92+
};
93+
if (!globs) {
94+
return dir.concat("**/*");
95+
}
96+
if (Array.isArray(globs)) {
97+
return globs.map(prependDirAndApplyGlobstar);
98+
}
99+
return prependDirAndApplyGlobstar(globs);
100+
}
101+
72102
export function markdownToRule(
73103
rule: string,
74104
id: PackageIdentifier,
@@ -78,7 +108,7 @@ export function markdownToRule(
78108
return {
79109
name: getRuleName(frontmatter, id),
80110
rule: markdown,
81-
globs: frontmatter.globs,
111+
globs: getGlobPattern(frontmatter.globs, id),
82112
regex: frontmatter.regex,
83113
description: frontmatter.description,
84114
alwaysApply: frontmatter.alwaysApply,

0 commit comments

Comments
 (0)