Skip to content

feat: generate rule dialog #6421

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 24 commits into from
Jul 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
a2adf2f
feat: generate rule button + dialog scaffolding
Patrick-Erichsen Jul 1, 2025
44094aa
feat: streaming input w/ history to rule dialog
Patrick-Erichsen Jul 1, 2025
0d8a1cc
feat: more polish
Patrick-Erichsen Jul 2, 2025
2e691bf
feat: update sys msg
Patrick-Erichsen Jul 2, 2025
fd94ef2
feat: move rule type logic into `config-yaml`
Patrick-Erichsen Jul 2, 2025
287839c
Merge branch 'main' into pe/rule-dialog
Patrick-Erichsen Jul 2, 2025
aaaa44f
Update ruleTemplates.ts
Patrick-Erichsen Jul 2, 2025
15bbf19
update tests and remove debugger
Patrick-Erichsen Jul 2, 2025
e2a7d47
Update GenerationScreen.tsx
Patrick-Erichsen Jul 2, 2025
b13f1aa
feat: only show btn if last history item
Patrick-Erichsen Jul 3, 2025
f6ba92b
smaller fonts, move chips up, truncate text
Patrick-Erichsen Jul 3, 2025
8ae963c
feat: cmd palette action to create rule
Patrick-Erichsen Jul 3, 2025
1b50ed5
only show 1 spinner
Patrick-Erichsen Jul 3, 2025
0b20c4d
remove useEffect
Patrick-Erichsen Jul 3, 2025
48b950b
only show rule gen btn if model supports tools
Patrick-Erichsen Jul 3, 2025
9722eaa
use `constructMessages`
Patrick-Erichsen Jul 3, 2025
2fb364d
update rule gen sys prompt to be more concise
Patrick-Erichsen Jul 3, 2025
6df32dd
feat: allow for manual rule creation
Patrick-Erichsen Jul 3, 2025
2c4207b
display form errors to user
Patrick-Erichsen Jul 3, 2025
8073dda
remove stale code
Patrick-Erichsen Jul 3, 2025
f582dc9
only update when done generating
Patrick-Erichsen Jul 3, 2025
8332a71
preserve form state when switching rule type
Patrick-Erichsen Jul 3, 2025
b13bec5
fix alwaysApply logic bug
Patrick-Erichsen Jul 3, 2025
e7e3540
prettier
Patrick-Erichsen Jul 3, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions .continue/rules/use-effect.md
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hahaha it had a good run

This file was deleted.

1 change: 1 addition & 0 deletions core/protocol/ideWebview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,5 @@ export type ToWebviewFromIdeProtocol = ToWebviewFromIdeOrCoreProtocol & {
updateApplyState: [ApplyState, void];
exitEditMode: [undefined, void];
focusEdit: [undefined, void];
generateRule: [undefined, void];
};
10 changes: 8 additions & 2 deletions core/tools/definitions/createRuleBlock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const createRuleBlock: Tool = {
function: {
name: BuiltInToolNames.CreateRuleBlock,
description:
'Creates a "rule" that can be referenced in future conversations. This should be used whenever you want to establish code standards / preferences that should be applied consistently, or when you want to avoid making a mistake again. To modify existing rules, use the edit tool instead.',
'Creates a "rule" that can be referenced in future conversations. This should be used whenever you want to establish code standards / preferences that should be applied consistently, or when you want to avoid making a mistake again. To modify existing rules, use the edit tool instead.\n\nRule Types:\n- Always: Include only "rule" (always included in model context)\n- Auto Attached: Include "rule", "globs", and/or "regex" (included when files match patterns)\n- Agent Requested: Include "rule" and "description" (AI decides when to apply based on description)\n- Manual: Include only "rule" (only included when explicitly mentioned using @ruleName)',
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

change alwaysApply to a more readable "rule_type" arg

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Patrick-Erichsen saving this change for later?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nvm saw your comment

parameters: {
type: "object",
required: ["name", "rule", "description"],
Expand All @@ -30,7 +30,8 @@ export const createRuleBlock: Tool = {
},
description: {
type: "string",
description: "Short description of the rule",
description:
"Description of when this rule should be applied. Required for Agent Requested rules (AI decides when to apply). Optional for other types.",
},
globs: {
type: "string",
Expand All @@ -42,6 +43,11 @@ export const createRuleBlock: Tool = {
description:
"Optional regex patterns to match against file content. Rule applies only to files whose content matches the pattern (e.g. 'useEffect' for React hooks or '\\bclass\\b' for class definitions)",
},
alwaysApply: {
type: "boolean",
description:
"Whether this rule should always be applied. Set to false for Agent Requested and Manual rules. Omit or set to true for Always and Auto Attached rules.",
},
},
},
},
Expand Down
4 changes: 4 additions & 0 deletions core/tools/implementations/createRuleBlock.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ test("createRuleBlockImpl should create a rule with glob pattern", async () => {
const { frontmatter, markdown } = parseMarkdownRule(fileContent);

expect(frontmatter).toEqual({
alwaysApply: true,
description: "Always use interfaces",
globs: "**/*.{ts,tsx}",
});
Expand Down Expand Up @@ -72,6 +73,7 @@ test("createRuleBlockImpl should create a rule with description pattern", async
const { frontmatter, markdown } = parseMarkdownRule(fileContent);

expect(frontmatter).toEqual({
alwaysApply: true,
description: "This is a detailed explanation of the rule",
});

Expand All @@ -94,6 +96,7 @@ test("createRuleBlockImpl should include both globs and description in frontmatt
const { frontmatter, markdown } = parseMarkdownRule(fileContent);

expect(frontmatter).toEqual({
alwaysApply: false,
description: "This rule enforces our team standards",
globs: "**/*.js",
});
Expand All @@ -116,6 +119,7 @@ test("createRuleBlockImpl should create a rule with alwaysApply set to false", a
const { frontmatter } = parseMarkdownRule(fileContent);

expect(frontmatter).toEqual({
alwaysApply: false,
description: "Optional rule",
});
});
23 changes: 6 additions & 17 deletions core/tools/implementations/createRuleBlock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,37 +5,26 @@ import { createRuleFilePath } from "../../config/markdown/utils";

export type CreateRuleBlockArgs = Pick<
Required<RuleWithSource>,
"rule" | "description" | "alwaysApply" | "name"
"rule" | "name"
> &
Pick<RuleWithSource, "globs" | "regex">;
Pick<RuleWithSource, "globs" | "regex" | "description" | "alwaysApply">;

export const createRuleBlockImpl: ToolImpl = async (
args: CreateRuleBlockArgs,
{ name, rule, ...otherArgs }: CreateRuleBlockArgs,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is the advantage over saying otherArgs syntax vs just saying { name, rule, description }

extras,
) => {
// Create options object with the fields that createRuleMarkdown expects
const options: any = {
description: args.description,
globs: args.globs,
};

// Add regex if provided
if (args.regex) {
options.regex = args.regex;
}

const fileContent = createRuleMarkdown(args.name, args.rule, options);
const fileContent = createRuleMarkdown(name, rule, otherArgs);

const [localContinueDir] = await extras.ide.getWorkspaceDirs();
const ruleFilePath = createRuleFilePath(localContinueDir, args.name);
const ruleFilePath = createRuleFilePath(localContinueDir, name);

await extras.ide.writeFile(ruleFilePath, fileContent);
await extras.ide.openFile(ruleFilePath);

return [
{
name: "New Rule Block",
description: args.description || "",
description: otherArgs.description || "",
uri: {
type: "file",
value: ruleFilePath,
Expand Down
9 changes: 9 additions & 0 deletions extensions/vscode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,12 @@
"command": "continue.forceNextEdit",
"category": "Continue",
"title": "Continue: Force Next Edit"
},
{
"command": "continue.generateRule",
"category": "Continue",
"title": "Generate Rule",
"group": "Continue"
}
],
"keybindings": [
Expand Down Expand Up @@ -478,6 +484,9 @@
},
{
"command": "continue.enterEnterpriseLicenseKey"
},
{
"command": "continue.generateRule"
}
],
"editor/context": [
Expand Down
5 changes: 5 additions & 0 deletions extensions/vscode/src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,11 @@ const getCommandsMap: (
editDecorationManager.clear();
void sidebar.webviewProtocol?.request("exitEditMode", undefined);
},
"continue.generateRule": async () => {
captureCommandTelemetry("generateRule");
focusGUI();
void sidebar.webviewProtocol?.request("generateRule", undefined);
},
"continue.writeCommentsForCode": async () => {
captureCommandTelemetry("writeCommentsForCode");

Expand Down
6 changes: 3 additions & 3 deletions gui/src/components/FeedbackButtons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export interface FeedbackButtonsProps {
item: ChatHistoryItem;
}

export default function FeedbackButtons({ item }: FeedbackButtonsProps) {
export function FeedbackButtons({ item }: FeedbackButtonsProps) {
const [feedback, setFeedback] = useState<boolean | undefined>(undefined);
const ideMessenger = useContext(IdeMessengerContext);
const sessionId = useAppSelector((store) => store.session.id);
Expand Down Expand Up @@ -41,7 +41,7 @@ export default function FeedbackButtons({ item }: FeedbackButtonsProps) {
onClick={() => sendFeedback(true)}
>
<HandThumbUpIcon
className={`mx-0.5 h-3.5 w-3.5 ${feedback === true ? "text-green-400" : "text-gray-500"}`}
className={`mx-0.5 h-3.5 w-3.5 ${feedback === true ? "text-success" : "text-description-muted"}`}
/>
</HeaderButtonWithToolTip>
<HeaderButtonWithToolTip
Expand All @@ -50,7 +50,7 @@ export default function FeedbackButtons({ item }: FeedbackButtonsProps) {
onClick={() => sendFeedback(false)}
>
<HandThumbDownIcon
className={`h-3.5 w-3.5 ${feedback === false ? "text-red-400" : "text-gray-500"}`}
className={`h-3.5 w-3.5 ${feedback === false ? "text-error" : "text-description-muted"}`}
/>
</HeaderButtonWithToolTip>
</>
Expand Down
Loading
Loading