Skip to content

Commit a57eb80

Browse files
authored
Merge pull request #8053 from continuedev/dallin/mcp-ui-updates-and-fixes
feat: rules and MCP fixes and UI updates
2 parents b461fdc + d152835 commit a57eb80

File tree

68 files changed

+793
-1218
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

68 files changed

+793
-1218
lines changed

.vscode/settings.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
"binary/build/**": true,
2424
"binary/out/**": true,
2525
"binary/tmp/**": true,
26+
"core/dist/**": true,
2627
"core/edit/lazy/test-examples/**": true,
2728
"core/llm/llamaTokenizer.js": true,
2829
"core/llm/llamaTokenizer.mjs": true,

core/commands/slash/customSlashCommand.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,6 @@ export function convertCustomCommandToSlashCommand(
1111
description: customCommand.description ?? "",
1212
prompt: customCommand.prompt,
1313
source: "json-custom-command",
14-
promptFile: customCommand.sourceFile, // TODO refactor promptFile to align with sourceFile
14+
sourceFile: customCommand.sourceFile,
1515
};
1616
}

core/commands/slash/promptBlockSlashCommand.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,6 @@ export function convertPromptBlockToSlashCommand(
99
description: prompt.description ?? "",
1010
prompt: prompt.prompt,
1111
source: "yaml-prompt-block",
12-
promptFile: prompt.sourceFile, // Align with the sourceFile property
12+
sourceFile: prompt.sourceFile,
1313
};
1414
}

core/commands/slash/promptFileSlashCommand.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export function slashCommandFromPromptFile(
1515
description,
1616
prompt,
1717
source: version === 1 ? "prompt-file-v1" : "prompt-file-v2",
18-
promptFile: path,
18+
sourceFile: path,
1919
overrideSystemMessage: systemMessage,
2020
};
2121
}

core/commands/slash/ruleBlockSlashCommand.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export function convertRuleBlockToSlashCommand(
1010
description: rule.description ?? "",
1111
prompt: rule.rule,
1212
source: "invokable-rule",
13-
promptFile: rule.ruleFile,
13+
sourceFile: rule.sourceFile,
14+
slug: rule.slug,
1415
};
1516
}

core/commands/slash/ruleBlockSlashCommand.vitest.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ describe("convertRuleBlockToSlashCommand", () => {
99
rule: "Review this code for best practices",
1010
description: "Performs a code review",
1111
source: "rules-block",
12-
ruleFile: "/path/to/rules.yaml",
12+
sourceFile: "file:///path/to/rules.yaml",
1313
invokable: true,
1414
};
1515

@@ -20,7 +20,7 @@ describe("convertRuleBlockToSlashCommand", () => {
2020
description: "Performs a code review",
2121
prompt: "Review this code for best practices",
2222
source: "invokable-rule",
23-
promptFile: "/path/to/rules.yaml",
23+
sourceFile: "file:///path/to/rules.yaml",
2424
});
2525
});
2626

core/config/getWorkspaceContinueRuleDotFiles.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export async function getWorkspaceContinueRuleDotFiles(ide: IDE) {
1616
const content = await ide.readFile(dotFile);
1717
rules.push({
1818
rule: content,
19-
ruleFile: dotFile,
19+
sourceFile: dotFile,
2020
source: ".continuerules",
2121
});
2222
}

core/config/loadLocalAssistants.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,12 @@ import { getGlobalFolderWithName } from "../util/paths";
1212
import { localPathToUri } from "../util/pathToUri";
1313
import { getUriPathBasename, joinPathsToUri } from "../util/uri";
1414
import { SYSTEM_PROMPT_DOT_FILE } from "./getWorkspaceContinueRuleDotFiles";
15+
import { SUPPORTED_AGENT_FILES } from "./markdown";
1516
export function isContinueConfigRelatedUri(uri: string): boolean {
1617
return (
1718
uri.endsWith(".continuerc.json") ||
1819
uri.endsWith(".prompt") ||
19-
uri.endsWith("AGENTS.md") ||
20-
uri.endsWith("AGENT.md") ||
21-
uri.endsWith("CLAUDE.md") ||
20+
!!SUPPORTED_AGENT_FILES.find((file) => uri.endsWith(`/${file}`)) ||
2221
uri.endsWith(SYSTEM_PROMPT_DOT_FILE) ||
2322
(uri.includes(".continue") &&
2423
(uri.endsWith(".yaml") ||

core/config/markdown/loadCodebaseRules.ts

Lines changed: 41 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -26,28 +26,38 @@ export class CodebaseRulesCache {
2626
}
2727
async update(ide: IDE, uri: string) {
2828
const content = await ide.readFile(uri);
29-
const { relativePathOrBasename } = findUriInDirs(
29+
const workspaceDirs = await ide.getWorkspaceDirs();
30+
const { relativePathOrBasename, foundInDir } = findUriInDirs(
3031
uri,
31-
await ide.getWorkspaceDirs(),
32+
workspaceDirs,
33+
);
34+
if (!foundInDir) {
35+
console.warn(
36+
`Failed to load codebase rule ${uri}: URI not found in workspace`,
37+
);
38+
}
39+
const rule = markdownToRule(
40+
content,
41+
{
42+
uriType: "file",
43+
fileUri: uri,
44+
},
45+
relativePathOrBasename,
3246
);
33-
const rule = markdownToRule(content, {
34-
uriType: "file",
35-
fileUri: relativePathOrBasename,
36-
});
3747
const ruleWithSource: RuleWithSource = {
3848
...rule,
3949
source: "colocated-markdown",
40-
ruleFile: uri,
50+
sourceFile: uri,
4151
};
42-
const matchIdx = this.rules.findIndex((r) => r.ruleFile === uri);
52+
const matchIdx = this.rules.findIndex((r) => r.sourceFile === uri);
4353
if (matchIdx === -1) {
4454
this.rules.push(ruleWithSource);
4555
} else {
4656
this.rules[matchIdx] = ruleWithSource;
4757
}
4858
}
4959
remove(uri: string) {
50-
this.rules = this.rules.filter((r) => r.ruleFile !== uri);
60+
this.rules = this.rules.filter((r) => r.sourceFile !== uri);
5161
}
5262
}
5363

@@ -75,20 +85,32 @@ export async function loadCodebaseRules(ide: IDE): Promise<{
7585
for (const filePath of rulesMdFiles) {
7686
try {
7787
const content = await ide.readFile(filePath);
78-
const { relativePathOrBasename } = findUriInDirs(
88+
const { relativePathOrBasename, foundInDir, uri } = findUriInDirs(
7989
filePath,
8090
await ide.getWorkspaceDirs(),
8191
);
82-
const rule = markdownToRule(content, {
83-
uriType: "file",
84-
fileUri: relativePathOrBasename,
85-
});
92+
if (foundInDir) {
93+
const lastSlashIndex = relativePathOrBasename.lastIndexOf("/");
94+
const parentDir = relativePathOrBasename.substring(0, lastSlashIndex);
95+
const rule = markdownToRule(
96+
content,
97+
{
98+
uriType: "file",
99+
fileUri: uri,
100+
},
101+
parentDir,
102+
);
86103

87-
rules.push({
88-
...rule,
89-
source: "colocated-markdown",
90-
ruleFile: filePath,
91-
});
104+
rules.push({
105+
...rule,
106+
source: "colocated-markdown",
107+
sourceFile: filePath,
108+
});
109+
} else {
110+
console.warn(
111+
`Failed to load codebase rule ${uri}: URI not found in workspace dirs`,
112+
);
113+
}
92114
} catch (e) {
93115
errors.push({
94116
fatal: false,

core/config/markdown/loadCodebaseRules.vitest.ts

Lines changed: 55 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import { markdownToRule } from "@continuedev/config-yaml";
22
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
33
import { IDE } from "../..";
44
import { walkDirs } from "../../indexing/walkDir";
5-
import { findUriInDirs, getUriPathBasename } from "../../util/uri";
65
import { loadCodebaseRules } from "./loadCodebaseRules";
76

87
// Mock dependencies
@@ -14,14 +13,10 @@ vi.mock("@continuedev/config-yaml", () => ({
1413
markdownToRule: vi.fn(),
1514
}));
1615

17-
vi.mock("../../util/uri", () => ({
18-
findUriInDirs: vi.fn(),
19-
getUriPathBasename: vi.fn(),
20-
}));
21-
2216
describe("loadCodebaseRules", () => {
2317
// Mock IDE with properly typed mock functions
2418
const mockIde = {
19+
fileExists: vi.fn().mockImplementation(() => true),
2520
readFile: vi.fn() as unknown as IDE["readFile"] & {
2621
mockImplementation: Function;
2722
},
@@ -30,52 +25,45 @@ describe("loadCodebaseRules", () => {
3025
},
3126
} as unknown as IDE;
3227

33-
// Setup test files
34-
const mockFiles = [
35-
"src/rules.md",
36-
"src/redux/rules.md",
37-
"src/components/rules.md",
38-
"src/utils/helper.ts", // Non-rules file
39-
".continue/rules.md", // This should also be loaded
40-
];
41-
4228
// Mock rule content
4329
const mockRuleContent: Record<string, string> = {
44-
"src/rules.md": "# General Rules\nFollow coding standards",
45-
"src/redux/rules.md":
30+
"file:///workspace/src/rules.md":
31+
"# General Rules\nFollow coding standards",
32+
"file:///workspace/src/redux/rules.md":
4633
'---\nglobs: "**/*.{ts,tsx}"\n---\n# Redux Rules\nUse Redux Toolkit',
47-
"src/components/rules.md":
34+
"file:///workspace/src/components/rules.md":
4835
'---\nglobs: ["**/*.tsx", "**/*.jsx"]\n---\n# Component Rules\nUse functional components',
49-
".continue/rules.md": "# Global Rules\nFollow project guidelines",
36+
"file:///workspace/.continue/rules.md":
37+
"# Global Rules\nFollow project guidelines",
5038
};
5139

5240
// Mock converted rules
5341
const mockConvertedRules: Record<string, any> = {
54-
"src/rules.md": {
42+
"file:///workspace/src/rules.md": {
5543
name: "General Rules",
5644
rule: "Follow coding standards",
5745
source: "colocated-markdown",
58-
ruleFile: "src/rules.md",
46+
sourceFile: "file:///workspace/src/rules.md",
5947
},
60-
"src/redux/rules.md": {
48+
"file:///workspace/src/redux/rules.md": {
6149
name: "Redux Rules",
6250
rule: "Use Redux Toolkit",
6351
globs: "**/*.{ts,tsx}",
6452
source: "colocated-markdown",
65-
ruleFile: "src/redux/rules.md",
53+
sourceFile: "file:///workspace/src/redux/rules.md",
6654
},
67-
"src/components/rules.md": {
55+
"file:///workspace/src/components/rules.md": {
6856
name: "Component Rules",
6957
rule: "Use functional components",
7058
globs: ["**/*.tsx", "**/*.jsx"],
7159
source: "colocated-markdown",
72-
ruleFile: "src/components/rules.md",
60+
sourceFile: "file:///workspace/src/components/rules.md",
7361
},
74-
".continue/rules.md": {
62+
"file:///workspace/.continue/rules.md": {
7563
name: "Global Rules",
7664
rule: "Follow project guidelines",
7765
source: "colocated-markdown",
78-
ruleFile: ".continue/rules.md",
66+
sourceFile: "file:///workspace/.continue/rules.md",
7967
},
8068
};
8169

@@ -84,20 +72,13 @@ describe("loadCodebaseRules", () => {
8472
vi.resetAllMocks();
8573

8674
// Mock walkDirs to return our test files
87-
(walkDirs as any).mockResolvedValue(mockFiles);
75+
(walkDirs as any).mockResolvedValue([
76+
...Object.keys(mockRuleContent),
77+
"file:///workspace/src/utils/helper.ts", // Non-rules file
78+
]);
8879

8980
// Mock getWorkspaceDirs to return a workspace directory
90-
(mockIde.getWorkspaceDirs as any).mockResolvedValue(["/workspace"]);
91-
92-
// Mock getUriPathBasename to return just the filename
93-
(getUriPathBasename as any).mockImplementation((path: string) => {
94-
return path.split("/").pop() || "";
95-
});
96-
97-
// Mock findUriInDirs to return relative path
98-
(findUriInDirs as any).mockImplementation((uri: string) => {
99-
return { relativePathOrBasename: uri };
100-
});
81+
(mockIde.getWorkspaceDirs as any).mockResolvedValue(["file:///workspace"]);
10182

10283
// Mock readFile to return content based on path
10384
(mockIde.readFile as any).mockImplementation((path: string) => {
@@ -123,20 +104,36 @@ describe("loadCodebaseRules", () => {
123104

124105
// Should read all rules.md files
125106
expect(mockIde.readFile).toHaveBeenCalledTimes(4);
126-
expect(mockIde.readFile).toHaveBeenCalledWith("src/rules.md");
127-
expect(mockIde.readFile).toHaveBeenCalledWith("src/redux/rules.md");
128-
expect(mockIde.readFile).toHaveBeenCalledWith("src/components/rules.md");
129-
expect(mockIde.readFile).toHaveBeenCalledWith(".continue/rules.md");
107+
expect(mockIde.readFile).toHaveBeenCalledWith(
108+
"file:///workspace/src/rules.md",
109+
);
110+
expect(mockIde.readFile).toHaveBeenCalledWith(
111+
"file:///workspace/src/redux/rules.md",
112+
);
113+
expect(mockIde.readFile).toHaveBeenCalledWith(
114+
"file:///workspace/src/components/rules.md",
115+
);
116+
expect(mockIde.readFile).toHaveBeenCalledWith(
117+
"file:///workspace/.continue/rules.md",
118+
);
130119

131120
// Should convert all rules
132121
expect(markdownToRule).toHaveBeenCalledTimes(4);
133122

134123
// Should return all rules
135124
expect(rules).toHaveLength(4);
136-
expect(rules).toContainEqual(mockConvertedRules["src/rules.md"]);
137-
expect(rules).toContainEqual(mockConvertedRules["src/redux/rules.md"]);
138-
expect(rules).toContainEqual(mockConvertedRules["src/components/rules.md"]);
139-
expect(rules).toContainEqual(mockConvertedRules[".continue/rules.md"]);
125+
expect(rules).toContainEqual(
126+
mockConvertedRules["file:///workspace/src/rules.md"],
127+
);
128+
expect(rules).toContainEqual(
129+
mockConvertedRules["file:///workspace/src/redux/rules.md"],
130+
);
131+
expect(rules).toContainEqual(
132+
mockConvertedRules["file:///workspace/src/components/rules.md"],
133+
);
134+
expect(rules).toContainEqual(
135+
mockConvertedRules["file:///workspace/.continue/rules.md"],
136+
);
140137

141138
// Should not have errors
142139
expect(errors).toHaveLength(0);
@@ -145,7 +142,7 @@ describe("loadCodebaseRules", () => {
145142
it("should handle errors when reading a rule file", async () => {
146143
// Setup mock to throw for a specific file
147144
(mockIde.readFile as any).mockImplementation((path: string) => {
148-
if (path === "src/redux/rules.md") {
145+
if (path === "file:///workspace/src/redux/rules.md") {
149146
return Promise.reject(new Error("Failed to read file"));
150147
}
151148
return Promise.resolve(mockRuleContent[path] || "");
@@ -155,14 +152,20 @@ describe("loadCodebaseRules", () => {
155152

156153
// Should still return other rules
157154
expect(rules).toHaveLength(3);
158-
expect(rules).toContainEqual(mockConvertedRules["src/rules.md"]);
159-
expect(rules).toContainEqual(mockConvertedRules["src/components/rules.md"]);
160-
expect(rules).toContainEqual(mockConvertedRules[".continue/rules.md"]);
155+
expect(rules).toContainEqual(
156+
mockConvertedRules["file:///workspace/src/rules.md"],
157+
);
158+
expect(rules).toContainEqual(
159+
mockConvertedRules["file:///workspace/src/components/rules.md"],
160+
);
161+
expect(rules).toContainEqual(
162+
mockConvertedRules["file:///workspace/.continue/rules.md"],
163+
);
161164

162165
// Should have one error
163166
expect(errors).toHaveLength(1);
164167
expect(errors[0].message).toContain(
165-
"Failed to parse colocated rule file src/redux/rules.md",
168+
"Failed to parse colocated rule file file:///workspace/src/redux/rules.md: Failed to read file",
166169
);
167170
});
168171

0 commit comments

Comments
 (0)