Skip to content

Commit 6c24132

Browse files
fix: avoid parsing md rules as yaml
1 parent 8951394 commit 6c24132

File tree

6 files changed

+274
-21
lines changed

6 files changed

+274
-21
lines changed
Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
import { walkDirCache } from "../indexing/walkDir";
2+
import { testIde } from "../test/fixtures";
3+
import { addToTestDir, setUpTestDir, tearDownTestDir } from "../test/testDir";
4+
import {
5+
getAllDotContinueDefinitionFiles,
6+
LoadAssistantFilesOptions,
7+
} from "./loadLocalAssistants";
8+
describe("getAllDotContinueDefinitionFiles with fileExtType option", () => {
9+
beforeEach(() => {
10+
setUpTestDir();
11+
walkDirCache.invalidate();
12+
13+
// Add test files to the test directory
14+
addToTestDir([
15+
".continue/assistants/",
16+
[".continue/assistants/assistant1.yaml", "yaml content 1"],
17+
[".continue/assistants/assistant2.yml", "yaml content 2"],
18+
[".continue/assistants/assistant3.md", "markdown content 1"],
19+
[".continue/assistants/assistant4.txt", "txt content"],
20+
]);
21+
});
22+
23+
afterEach(() => {
24+
tearDownTestDir();
25+
walkDirCache.invalidate();
26+
});
27+
28+
it("should return only YAML files when fileExtType is 'yaml'", async () => {
29+
const options: LoadAssistantFilesOptions = {
30+
includeGlobal: false, // Only test workspace for simplicity
31+
includeWorkspace: true,
32+
fileExtType: "yaml",
33+
};
34+
35+
const result = await getAllDotContinueDefinitionFiles(
36+
testIde,
37+
options,
38+
"assistants",
39+
);
40+
expect(result).toHaveLength(2);
41+
expect(result.map((f) => f.path.split("/").pop())).toEqual(
42+
expect.arrayContaining(["assistant1.yaml", "assistant2.yml"]),
43+
);
44+
expect(result.map((f) => f.path.split("/").pop())).not.toContain(
45+
"assistant3.md",
46+
);
47+
});
48+
49+
it("should return only Markdown files when fileExtType is 'markdown'", async () => {
50+
const options: LoadAssistantFilesOptions = {
51+
includeGlobal: false,
52+
includeWorkspace: true,
53+
fileExtType: "markdown",
54+
};
55+
56+
const result = await getAllDotContinueDefinitionFiles(
57+
testIde,
58+
options,
59+
"assistants",
60+
);
61+
expect(result).toHaveLength(1);
62+
expect(result.map((f) => f.path.split("/").pop())).toEqual([
63+
"assistant3.md",
64+
]);
65+
expect(result.map((f) => f.path.split("/").pop())).not.toContain(
66+
"assistant1.yaml",
67+
);
68+
expect(result.map((f) => f.path.split("/").pop())).not.toContain(
69+
"assistant2.yml",
70+
);
71+
});
72+
73+
it("should return all supported files when fileExtType is not specified", async () => {
74+
const options: LoadAssistantFilesOptions = {
75+
includeGlobal: false,
76+
includeWorkspace: true,
77+
// fileExtType not specified
78+
};
79+
80+
const result = await getAllDotContinueDefinitionFiles(
81+
testIde,
82+
options,
83+
"assistants",
84+
);
85+
expect(result).toHaveLength(3);
86+
expect(result.map((f) => f.path.split("/").pop())).toEqual(
87+
expect.arrayContaining([
88+
"assistant1.yaml",
89+
"assistant2.yml",
90+
"assistant3.md",
91+
]),
92+
);
93+
// Should not include .txt files
94+
expect(result.map((f) => f.path.split("/").pop())).not.toContain(
95+
"assistant4.txt",
96+
);
97+
});
98+
99+
it("should respect includeWorkspace option with fileExtType", async () => {
100+
// Test with includeWorkspace: false
101+
const workspaceOffOptions: LoadAssistantFilesOptions = {
102+
includeGlobal: false,
103+
includeWorkspace: false,
104+
fileExtType: "yaml",
105+
};
106+
107+
const noWorkspaceResult = await getAllDotContinueDefinitionFiles(
108+
testIde,
109+
workspaceOffOptions,
110+
"assistants",
111+
);
112+
expect(noWorkspaceResult).toHaveLength(0);
113+
114+
// Test with includeWorkspace: true
115+
const workspaceOnOptions: LoadAssistantFilesOptions = {
116+
includeGlobal: false,
117+
includeWorkspace: true,
118+
fileExtType: "yaml",
119+
};
120+
121+
const workspaceResult = await getAllDotContinueDefinitionFiles(
122+
testIde,
123+
workspaceOnOptions,
124+
"assistants",
125+
);
126+
expect(workspaceResult).toHaveLength(2);
127+
expect(workspaceResult.map((f) => f.path.split("/").pop())).toEqual(
128+
expect.arrayContaining(["assistant1.yaml", "assistant2.yml"]),
129+
);
130+
});
131+
132+
it("should return empty array when no files match the specified extension type", async () => {
133+
// Create a test directory with only non-matching files
134+
tearDownTestDir();
135+
walkDirCache.invalidate();
136+
setUpTestDir();
137+
addToTestDir([
138+
".continue/assistants/",
139+
[".continue/assistants/nonmatch1.txt", "txt content"],
140+
[".continue/assistants/nonmatch2.json", "json content"],
141+
]);
142+
143+
const options: LoadAssistantFilesOptions = {
144+
includeGlobal: false,
145+
includeWorkspace: true,
146+
147+
fileExtType: "yaml",
148+
};
149+
150+
const result = await getAllDotContinueDefinitionFiles(
151+
testIde,
152+
options,
153+
"assistants",
154+
);
155+
expect(result).toHaveLength(0);
156+
});
157+
158+
it("should handle directories that don't exist", async () => {
159+
// Create a clean test directory without the assistants folder
160+
tearDownTestDir();
161+
setUpTestDir();
162+
163+
const options: LoadAssistantFilesOptions = {
164+
includeGlobal: false,
165+
includeWorkspace: true,
166+
fileExtType: "yaml",
167+
};
168+
169+
const result = await getAllDotContinueDefinitionFiles(
170+
testIde,
171+
options,
172+
"assistants",
173+
);
174+
expect(result).toHaveLength(0);
175+
});
176+
177+
it("should return correct file content", async () => {
178+
const options: LoadAssistantFilesOptions = {
179+
includeGlobal: false,
180+
includeWorkspace: true,
181+
fileExtType: "yaml",
182+
};
183+
184+
const result = await getAllDotContinueDefinitionFiles(
185+
testIde,
186+
options,
187+
"assistants",
188+
);
189+
expect(result).toHaveLength(2);
190+
const yamlFile = result.find((f) => f.path.includes("assistant1.yaml"));
191+
expect(yamlFile?.content).toBe("yaml content 1");
192+
});
193+
194+
it("should filter by file extension case sensitively", async () => {
195+
// Add files with uppercase extensions
196+
addToTestDir([
197+
[".continue/assistants/assistant5.YAML", "uppercase yaml"],
198+
[".continue/assistants/assistant6.YML", "uppercase yml"],
199+
[".continue/assistants/assistant7.MD", "uppercase md"],
200+
]);
201+
202+
const yamlOptions: LoadAssistantFilesOptions = {
203+
includeGlobal: false,
204+
includeWorkspace: true,
205+
fileExtType: "yaml",
206+
};
207+
208+
const yamlResult = await getAllDotContinueDefinitionFiles(
209+
testIde,
210+
yamlOptions,
211+
"assistants",
212+
);
213+
// Should only get lowercase extensions (current implementation)
214+
expect(yamlResult).toHaveLength(2);
215+
expect(yamlResult.map((f) => f.path.split("/").pop())).toEqual(
216+
expect.arrayContaining(["assistant1.yaml", "assistant2.yml"]),
217+
);
218+
expect(yamlResult.map((f) => f.path.split("/").pop())).not.toContain(
219+
"assistant5.YAML",
220+
);
221+
222+
const markdownOptions: LoadAssistantFilesOptions = {
223+
includeGlobal: false,
224+
includeWorkspace: true,
225+
fileExtType: "markdown",
226+
};
227+
228+
const markdownResult = await getAllDotContinueDefinitionFiles(
229+
testIde,
230+
markdownOptions,
231+
"assistants",
232+
);
233+
expect(markdownResult).toHaveLength(1);
234+
expect(markdownResult.map((f) => f.path.split("/").pop())).toEqual([
235+
"assistant3.md",
236+
]);
237+
expect(markdownResult.map((f) => f.path.split("/").pop())).not.toContain(
238+
"assistant7.MD",
239+
);
240+
});
241+
});

core/config/loadLocalAssistants.ts

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import ignore from "ignore";
2+
import * as URI from "uri-js";
23
import { IDE } from "..";
34
import {
45
DEFAULT_IGNORE_DIRS,
@@ -12,18 +13,19 @@ import { joinPathsToUri } from "../util/uri";
1213
export const ASSISTANTS = "assistants";
1314
export const ASSISTANTS_FOLDER = `.continue/${ASSISTANTS}`;
1415

15-
export function isLocalAssistantFile(uri: string): boolean {
16+
export function isLocalDefinitionFile(uri: string): boolean {
1617
if (!uri.endsWith(".yaml") && !uri.endsWith(".yml") && !uri.endsWith(".md")) {
1718
return false;
1819
}
1920

20-
const normalizedUri = uri.replace(/\\/g, "/");
21+
const normalizedUri = URI.normalize(uri);
2122
return normalizedUri.includes(`/${ASSISTANTS_FOLDER}/`);
2223
}
2324

2425
async function getDefinitionFilesInDir(
2526
ide: IDE,
2627
dir: string,
28+
fileExtType?: "yaml" | "markdown",
2729
): Promise<{ path: string; content: string }[]> {
2830
try {
2931
const exists = await ide.fileExists(dir);
@@ -40,9 +42,19 @@ async function getDefinitionFilesInDir(
4042
overrideDefaultIgnores,
4143
source: "get assistant files",
4244
});
43-
const assistantFilePaths = uris.filter(
44-
(p) => p.endsWith(".yaml") || p.endsWith(".yml") || p.endsWith(".md"),
45-
);
45+
let assistantFilePaths: string[];
46+
if (fileExtType === "yaml") {
47+
assistantFilePaths = uris.filter(
48+
(p) => p.endsWith(".yaml") || p.endsWith(".yml"),
49+
);
50+
} else if (fileExtType === "markdown") {
51+
assistantFilePaths = uris.filter((p) => p.endsWith(".md"));
52+
} else {
53+
assistantFilePaths = uris.filter(
54+
(p) => p.endsWith(".yaml") || p.endsWith(".yml") || p.endsWith(".md"),
55+
);
56+
}
57+
4658
const results = assistantFilePaths.map(async (uri) => {
4759
const content = await ide.readFile(uri); // make a try catch
4860
return { path: uri, content };
@@ -57,6 +69,7 @@ async function getDefinitionFilesInDir(
5769
export interface LoadAssistantFilesOptions {
5870
includeGlobal: boolean;
5971
includeWorkspace: boolean;
72+
fileExtType?: "yaml" | "markdown";
6073
}
6174

6275
export function getDotContinueSubDirs(
@@ -84,7 +97,7 @@ export function getDotContinueSubDirs(
8497

8598
/**
8699
* This method searches in both ~/.continue and workspace .continue
87-
* for all YAML files in the specified subdirctory, for example .continue/assistants or .continue/prompts
100+
* for all YAML/Markdown files in the specified subdirectory, for example .continue/assistants or .continue/prompts
88101
*/
89102
export async function getAllDotContinueDefinitionFiles(
90103
ide: IDE,
@@ -101,15 +114,14 @@ export async function getAllDotContinueDefinitionFiles(
101114
subDirName,
102115
);
103116

104-
// Get all assistant files from the directories
105-
const assistantFiles = (
106-
await Promise.all(fullDirs.map((dir) => getDefinitionFilesInDir(ide, dir)))
117+
// Get all definition files from the directories
118+
const definitionFiles = (
119+
await Promise.all(
120+
fullDirs.map((dir) =>
121+
getDefinitionFilesInDir(ide, dir, options.fileExtType),
122+
),
123+
)
107124
).flat();
108125

109-
return await Promise.all(
110-
assistantFiles.map(async (file) => {
111-
const content = await ide.readFile(file.path);
112-
return { path: file.path, content };
113-
}),
114-
);
126+
return definitionFiles;
115127
}

core/config/markdown/loadMarkdownRules.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export async function loadMarkdownRules(ide: IDE): Promise<{
1717
// Get all .md files from .continue/rules
1818
const markdownFiles = await getAllDotContinueDefinitionFiles(
1919
ide,
20-
{ includeGlobal: true, includeWorkspace: true },
20+
{ includeGlobal: true, includeWorkspace: true, fileExtType: "markdown" },
2121
"rules",
2222
);
2323

core/config/yaml/loadYaml.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ async function loadConfigYaml(options: {
9797
for (const blockType of BLOCK_TYPES) {
9898
const localBlocks = await getAllDotContinueDefinitionFiles(
9999
ide,
100-
{ includeGlobal: true, includeWorkspace: true },
100+
{ includeGlobal: true, includeWorkspace: true, fileExtType: "yaml" },
101101
blockType,
102102
);
103103
allLocalBlocks.push(

core/core.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ import {
4949

5050
import { ConfigYaml } from "@continuedev/config-yaml";
5151
import { getDiffFn, GitDiffCache } from "./autocomplete/snippets/gitDiffCache";
52-
import { isLocalAssistantFile } from "./config/loadLocalAssistants";
52+
import { isLocalDefinitionFile } from "./config/loadLocalAssistants";
5353
import {
5454
setupLocalConfig,
5555
setupProviderConfig,
@@ -600,7 +600,7 @@ export class Core {
600600
// If it's a local assistant being created, we want to reload all assistants so it shows up in the list
601601
let localAssistantCreated = false;
602602
for (const uri of data.uris) {
603-
if (isLocalAssistantFile(uri)) {
603+
if (isLocalDefinitionFile(uri)) {
604604
localAssistantCreated = true;
605605
}
606606
}

extensions/vscode/package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)