Skip to content

Commit 1f6276d

Browse files
sestinjRomneyDa
andauthored
feat: support for AGENTS.md (#7717)
* feat: support for AGENTS.md * chore: rules source display name cleanup * refactor: use constant --------- Co-authored-by: Dallin Romney <[email protected]>
1 parent 7926bfa commit 1f6276d

File tree

6 files changed

+91
-57
lines changed

6 files changed

+91
-57
lines changed

core/config/markdown/loadMarkdownRules.ts

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@ import {
33
markdownToRule,
44
} from "@continuedev/config-yaml";
55
import { IDE, RuleWithSource } from "../..";
6-
import { findUriInDirs } from "../../util/uri";
6+
import { findUriInDirs, joinPathsToUri } from "../../util/uri";
77
import { getAllDotContinueDefinitionFiles } from "../loadLocalAssistants";
88

9+
export const SUPPORTED_AGENT_FILES = ["AGENTS.md", "AGENT.md", "CLAUDE.md"];
910
/**
1011
* Loads rules from markdown files in the .continue/rules directory
12+
* and agent files (AGENTS.md, AGENT.md, CLAUDE.md) at workspace root
1113
*/
1214
export async function loadMarkdownRules(ide: IDE): Promise<{
1315
rules: RuleWithSource[];
@@ -16,6 +18,38 @@ export async function loadMarkdownRules(ide: IDE): Promise<{
1618
const errors: ConfigValidationError[] = [];
1719
const rules: RuleWithSource[] = [];
1820

21+
// First, try to load agent files from workspace root
22+
const workspaceDirs = await ide.getWorkspaceDirs();
23+
24+
for (const workspaceDir of workspaceDirs) {
25+
let agentFileFound = false;
26+
for (const fileName of SUPPORTED_AGENT_FILES) {
27+
try {
28+
const agentFilePath = joinPathsToUri(workspaceDir, fileName);
29+
const agentContent = await ide.readFile(agentFilePath);
30+
31+
const rule = markdownToRule(agentContent, {
32+
uriType: "file",
33+
fileUri: agentFilePath,
34+
});
35+
rules.push({
36+
...rule,
37+
source: "agent-file",
38+
ruleFile: agentFilePath,
39+
alwaysApply: true,
40+
});
41+
agentFileFound = true;
42+
break; // Use the first found agent file in this workspace
43+
} catch (e) {
44+
// File doesn't exist or can't be read, continue to next file
45+
}
46+
}
47+
48+
if (agentFileFound) {
49+
break; // Use agent file from first workspace that has one
50+
}
51+
}
52+
1953
try {
2054
// Get all .md files from .continue/rules
2155
const markdownFiles = await getAllDotContinueDefinitionFiles(

core/core.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1239,6 +1239,9 @@ export class Core {
12391239
if (
12401240
uri.endsWith(".continuerc.json") ||
12411241
uri.endsWith(".prompt") ||
1242+
uri.endsWith("AGENTS.md") ||
1243+
uri.endsWith("AGENT.md") ||
1244+
uri.endsWith("CLAUDE.md") ||
12421245
uri.endsWith(SYSTEM_PROMPT_DOT_FILE) ||
12431246
(uri.includes(".continue") &&
12441247
(uri.endsWith(".yaml") || uri.endsWith("yml"))) ||

core/index.d.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1811,7 +1811,8 @@ export type RuleSource =
18111811
| "rules-block"
18121812
| "colocated-markdown"
18131813
| "json-systemMessage"
1814-
| ".continuerules";
1814+
| ".continuerules"
1815+
| "agent-file";
18151816

18161817
export interface RuleWithSource {
18171818
name?: string;

core/llm/rules/rules-utils.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { RuleWithSource } from "../..";
2+
import { getLastNPathParts } from "../../util/uri";
3+
4+
export function getRuleDisplayName(rule: RuleWithSource): string {
5+
if (rule.name) {
6+
return rule.name;
7+
}
8+
return getRuleSourceDisplayName(rule);
9+
}
10+
11+
export function getRuleSourceDisplayName(rule: RuleWithSource): string {
12+
switch (rule.source) {
13+
case ".continuerules":
14+
return "Project rules";
15+
case "default-chat":
16+
return "Default chat system message";
17+
case "default-plan":
18+
return "Default plan mode system message";
19+
case "default-agent":
20+
return "Default agent system message";
21+
case "json-systemMessage":
22+
return "System Message (JSON)";
23+
case "model-options-agent":
24+
return "Base System Agent Message";
25+
case "model-options-plan":
26+
return "Base System Plan Message";
27+
case "model-options-chat":
28+
return "Base System Chat Message";
29+
case "agent-file":
30+
if (rule.ruleFile) {
31+
return getLastNPathParts(rule.ruleFile, 2);
32+
} else {
33+
return "Agent file";
34+
}
35+
case "colocated-markdown":
36+
if (rule.ruleFile) {
37+
return getLastNPathParts(rule.ruleFile, 2);
38+
} else {
39+
return "rules.md";
40+
}
41+
case "rules-block":
42+
return "Rules Block";
43+
default:
44+
return rule.source;
45+
}
46+
}

gui/src/components/mainInput/belowMainInput/RulesPeek.tsx

Lines changed: 2 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { DocumentTextIcon, GlobeAltIcon } from "@heroicons/react/24/outline";
22
import { RuleWithSource } from "core";
3-
import { getLastNPathParts } from "core/util/uri";
3+
import { getRuleSourceDisplayName } from "core/llm/rules/rules-utils";
44
import { ComponentType, useMemo, useState } from "react";
55
import ToggleDiv from "../../ToggleDiv";
66

@@ -13,36 +13,6 @@ interface RulesPeekItemProps {
1313
rule: RuleWithSource;
1414
}
1515

16-
// Convert technical source to user-friendly text
17-
const getSourceLabel = (rule: RuleWithSource): string => {
18-
switch (rule.source) {
19-
case "default-chat":
20-
return "Default Chat";
21-
case "default-agent":
22-
return "Default Agent";
23-
case "model-options-chat":
24-
return "Model Chat Options";
25-
case "model-options-plan":
26-
return "Model Plan Options";
27-
case "model-options-agent":
28-
return "Model Agent Options";
29-
case "rules-block":
30-
return "Rules Block";
31-
case "colocated-markdown":
32-
if (rule.ruleFile) {
33-
return getLastNPathParts(rule.ruleFile, 2);
34-
} else {
35-
return "rules.md";
36-
}
37-
case "json-systemMessage":
38-
return "System Message";
39-
case ".continuerules":
40-
return "Project Rules";
41-
default:
42-
return rule.source;
43-
}
44-
};
45-
4616
export function RulesPeekItem({ rule }: RulesPeekItemProps) {
4717
const isGlobal = rule.alwaysApply ?? !rule.globs;
4818
const [expanded, setExpanded] = useState(false);
@@ -102,7 +72,7 @@ export function RulesPeekItem({ rule }: RulesPeekItemProps) {
10272
)}
10373
</div>
10474
<div className="mt-1 pl-6 pr-2 text-xs text-gray-500">
105-
Source: {getSourceLabel(rule)}
75+
Source: {getRuleSourceDisplayName(rule)}
10676
</div>
10777
</div>
10878
);

gui/src/pages/config/sections/RulesSection.tsx

Lines changed: 3 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import {
44
BookmarkIcon as BookmarkOutline,
55
EyeIcon,
66
PencilIcon,
7-
PlusIcon,
87
} from "@heroicons/react/24/outline";
98
import { BookmarkIcon as BookmarkSolid } from "@heroicons/react/24/solid";
109
import {
@@ -19,11 +18,12 @@ import {
1918
DEFAULT_PLAN_SYSTEM_MESSAGE,
2019
DEFAULT_SYSTEM_MESSAGES_URL,
2120
} from "core/llm/defaultSystemMessages";
21+
import { getRuleDisplayName } from "core/llm/rules/rules-utils";
2222
import { useContext, useMemo } from "react";
2323
import HeaderButtonWithToolTip from "../../../components/gui/HeaderButtonWithToolTip";
2424
import Switch from "../../../components/gui/Switch";
2525
import { useMainEditor } from "../../../components/mainInput/TipTapEditor";
26-
import { Button, Card, EmptyState, useFontSize } from "../../../components/ui";
26+
import { Card, EmptyState, useFontSize } from "../../../components/ui";
2727
import { useAuth } from "../../../context/Auth";
2828
import { IdeMessengerContext } from "../../../context/IdeMessenger";
2929
import { useBookmarkedSlashCommands } from "../../../hooks/useBookmarkedSlashCommands";
@@ -167,27 +167,7 @@ const RuleCard: React.FC<RuleCardProps> = ({ rule }) => {
167167
};
168168

169169
const title = useMemo(() => {
170-
if (rule.name) {
171-
return rule.name;
172-
} else {
173-
if (rule.source === ".continuerules") {
174-
return "Project rules";
175-
} else if (rule.source === "default-chat") {
176-
return "Default chat system message";
177-
} else if (rule.source === "default-agent") {
178-
return "Default agent system message";
179-
} else if (rule.source === "json-systemMessage") {
180-
return "JSON systemMessage)";
181-
} else if (rule.source === "model-options-agent") {
182-
return "Base System Agent Message";
183-
} else if (rule.source === "model-options-plan") {
184-
return "Base System Plan Message";
185-
} else if (rule.source === "model-options-chat") {
186-
return "Base System Chat Message";
187-
} else {
188-
return "Agent rule";
189-
}
190-
}
170+
return getRuleDisplayName(rule);
191171
}, [rule]);
192172

193173
function onClickExpand() {

0 commit comments

Comments
 (0)