Skip to content

Commit a15d7c7

Browse files
authored
Merge pull request #6070 from continuedev/nate/rules-as-context-providers
RulesContextProvider
2 parents 78da281 + 1bd881f commit a15d7c7

File tree

6 files changed

+141
-12
lines changed

6 files changed

+141
-12
lines changed

core/config/profile/doLoadConfig.ts

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,14 @@ import {
1414
IDE,
1515
IdeSettings,
1616
ILLMLogger,
17+
RuleWithSource,
1718
SerializedContinueConfig,
1819
Tool,
1920
} from "../../";
2021
import { constructMcpSlashCommand } from "../../commands/slash/mcp";
2122
import { MCPManagerSingleton } from "../../context/mcp/MCPManagerSingleton";
2223
import MCPContextProvider from "../../context/providers/MCPContextProvider";
24+
import RulesContextProvider from "../../context/providers/RulesContextProvider";
2325
import { ControlPlaneProxyInfo } from "../../control-plane/analytics/IAnalyticsProvider.js";
2426
import { ControlPlaneClient } from "../../control-plane/client.js";
2527
import { getControlPlaneEnv } from "../../control-plane/env.js";
@@ -39,6 +41,24 @@ import { migrateJsonSharedConfig } from "../migrateSharedConfig";
3941
import { rectifySelectedModelsFromGlobalContext } from "../selectedModels";
4042
import { loadContinueConfigFromYaml } from "../yaml/loadYaml";
4143

44+
async function loadRules(ide: IDE) {
45+
const rules: RuleWithSource[] = [];
46+
const errors = [];
47+
48+
// Add rules from .continuerules files
49+
const { rules: yamlRules, errors: continueRulesErrors } =
50+
await getWorkspaceContinueRuleDotFiles(ide);
51+
rules.unshift(...yamlRules);
52+
errors.push(...continueRulesErrors);
53+
54+
// Add rules from markdown files in .continue/rules
55+
const { rules: markdownRules, errors: markdownRulesErrors } =
56+
await loadMarkdownRules(ide);
57+
rules.unshift(...markdownRules);
58+
errors.push(...markdownRulesErrors);
59+
60+
return { rules, errors };
61+
}
4262
export default async function doLoadConfig(options: {
4363
ide: IDE;
4464
ideSettingsPromise: Promise<IdeSettings>;
@@ -125,17 +145,11 @@ export default async function doLoadConfig(options: {
125145
// Remove ability have undefined errors, just have an array
126146
errors = [...(errors ?? [])];
127147

128-
// Add rules from .continuerules files
129-
const { rules, errors: continueRulesErrors } =
130-
await getWorkspaceContinueRuleDotFiles(ide);
148+
// Load rules and always include the RulesContextProvider
149+
const { rules, errors: rulesErrors } = await loadRules(ide);
150+
errors.push(...rulesErrors);
131151
newConfig.rules.unshift(...rules);
132-
errors.push(...continueRulesErrors);
133-
134-
// Add rules from markdown files in .continue/rules
135-
const { rules: markdownRules, errors: markdownRulesErrors } =
136-
await loadMarkdownRules(ide);
137-
newConfig.rules.unshift(...markdownRules);
138-
errors.push(...markdownRulesErrors);
152+
newConfig.contextProviders.push(new RulesContextProvider({}));
139153

140154
// Rectify model selections for each role
141155
newConfig = rectifySelectedModelsFromGlobalContext(newConfig, profileId);
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import { BaseContextProvider } from "..";
2+
import {
3+
ContextItem,
4+
ContextItemUri,
5+
ContextProviderDescription,
6+
ContextProviderExtras,
7+
ContextSubmenuItem,
8+
LoadSubmenuItemsArgs,
9+
RuleWithSource,
10+
} from "../..";
11+
import { getControlPlaneEnv } from "../../control-plane/env";
12+
13+
class RulesContextProvider extends BaseContextProvider {
14+
static description: ContextProviderDescription = {
15+
title: "rules",
16+
displayTitle: "Rules",
17+
description: "Mention rules files",
18+
type: "submenu",
19+
renderInlineAs: "",
20+
};
21+
22+
// This is only used within this class. Worst case if there are exact duplicates is that one always calls the other, but this is an extreme edge case
23+
// Can eventually pull in more metadata, but this is experimental
24+
private getIdFromRule(rule: RuleWithSource): string {
25+
return rule.ruleFile ?? rule.slug ?? rule.name ?? rule.rule;
26+
}
27+
28+
private getNameFromRule(rule: RuleWithSource): string {
29+
return rule.name ?? rule.slug ?? rule.ruleFile ?? rule.source;
30+
}
31+
32+
private getDescriptionFromRule(rule: RuleWithSource): string {
33+
return rule.description ?? rule.name ?? "";
34+
}
35+
36+
private getUriFromRule(
37+
rule: RuleWithSource,
38+
appUrl: string,
39+
): ContextItemUri | undefined {
40+
if (rule.ruleFile) {
41+
return {
42+
type: "file",
43+
value: rule.ruleFile,
44+
};
45+
}
46+
47+
if (rule.slug) {
48+
let url = `${appUrl}${rule.slug}`;
49+
return {
50+
type: "url",
51+
value: url,
52+
};
53+
}
54+
55+
return undefined;
56+
}
57+
58+
async getContextItems(
59+
query: string,
60+
extras: ContextProviderExtras,
61+
): Promise<ContextItem[]> {
62+
const rule = extras.config.rules.find(
63+
(rule) => this.getIdFromRule(rule) === query,
64+
);
65+
if (!rule) {
66+
return [];
67+
}
68+
69+
const env = await getControlPlaneEnv(extras.ide.getIdeSettings());
70+
return [
71+
{
72+
name: this.getNameFromRule(rule),
73+
content: rule.rule,
74+
description: this.getDescriptionFromRule(rule),
75+
uri: this.getUriFromRule(rule, env.APP_URL),
76+
},
77+
];
78+
}
79+
80+
async loadSubmenuItems(
81+
args: LoadSubmenuItemsArgs,
82+
): Promise<ContextSubmenuItem[]> {
83+
return args.config.rules.map((rule) => ({
84+
id: this.getIdFromRule(rule),
85+
description: this.getDescriptionFromRule(rule),
86+
title: this.getNameFromRule(rule),
87+
}));
88+
}
89+
}
90+
91+
export default RulesContextProvider;

core/context/providers/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import OSContextProvider from "./OSContextProvider";
2525
import PostgresContextProvider from "./PostgresContextProvider";
2626
import ProblemsContextProvider from "./ProblemsContextProvider";
2727
import RepoMapContextProvider from "./RepoMapContextProvider";
28+
import RulesContextProvider from "./RulesContextProvider";
2829
import SearchContextProvider from "./SearchContextProvider";
2930
import TerminalContextProvider from "./TerminalContextProvider";
3031
import URLContextProvider from "./URLContextProvider";
@@ -66,6 +67,7 @@ export const Providers: (typeof BaseContextProvider)[] = [
6667
MCPContextProvider,
6768
GitCommitContextProvider,
6869
ClipboardContextProvider,
70+
RulesContextProvider,
6971
];
7072

7173
export function contextProviderClassFromName(

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.

gui/src/components/mainInput/AtMentionDropdown/index.tsx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,26 @@ const AtMentionDropdown = forwardRef((props: AtMentionDropdownProps, ref) => {
292292
},
293293
description: "Create a new .prompt file",
294294
});
295+
} else if (subMenuTitle === "Mention rules files") {
296+
items.push({
297+
title: "Add new rule",
298+
type: "action",
299+
action: () => {
300+
void ideMessenger.request("config/addLocalWorkspaceBlock", {
301+
blockType: "rules",
302+
});
303+
const { tr } = props.editor.view.state;
304+
const text = tr.doc.textBetween(0, tr.selection.from);
305+
const start = text.lastIndexOf("@");
306+
if (start !== -1) {
307+
props.editor.view.dispatch(
308+
tr.delete(start, tr.selection.from).scrollIntoView(),
309+
);
310+
}
311+
props.onClose(); // Escape the mention list after creating a new rule
312+
},
313+
description: "Creates a rule file",
314+
});
295315
}
296316
setLoadingSubmenuItem(items.find((item) => item.id === "loading"));
297317
setAllItems(items.filter((item) => item.id !== "loading"));

gui/src/components/mainInput/icons.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
GlobeAltIcon,
1515
MagnifyingGlassIcon,
1616
PaperClipIcon,
17+
PencilIcon,
1718
PlusIcon,
1819
SparklesIcon,
1920
TrashIcon,
@@ -42,6 +43,7 @@ export const NAMED_ICONS: { [key: string]: any } = {
4243
debugger: BugAntIcon,
4344
os: CpuChipIcon,
4445
tree: Bars3BottomLeftIcon,
46+
rules: PencilIcon,
4547
"prompt-files": DocumentTextIcon,
4648
"repo-map": FolderIcon,
4749
"/clear": TrashIcon,

0 commit comments

Comments
 (0)