Skip to content

Commit feb9ad7

Browse files
Merge pull request #5375 from continuedev/pe/rule-tool-globs
feat: add `globs` to create rule tool
2 parents bfc6db8 + 520aa81 commit feb9ad7

File tree

5 files changed

+115
-27
lines changed

5 files changed

+115
-27
lines changed

core/tools/definitions/createRuleBlock.ts

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,32 +4,35 @@ import { BUILT_IN_GROUP_NAME, BuiltInToolNames } from "../builtIn";
44
export const createRuleBlock: Tool = {
55
type: "function",
66
displayTitle: "Create Rule Block",
7-
wouldLikeTo: 'create a rule block for "{{{ rule_name }}}"',
8-
isCurrently: 'creating a rule block for "{{{ rule_name }}}"',
9-
hasAlready: 'created a rule block for "{{{ rule_name }}}"',
7+
wouldLikeTo: 'create a rule block for "{{{ name }}}"',
8+
isCurrently: 'creating a rule block for "{{{ name }}}"',
9+
hasAlready: 'created a rule block for "{{{ name }}}"',
1010
readonly: false,
1111
isInstant: true,
1212
group: BUILT_IN_GROUP_NAME,
1313
function: {
1414
name: BuiltInToolNames.CreateRuleBlock,
1515
description:
16-
"Creates a persistent rule that applies to all future conversations. " +
17-
"Use for establishing ongoing code standards or preferences (e.g. 'use named exports', 'follow Google docstrings'). " +
18-
"Not for one-time instructions. Rules are stored as YAML in `.continue/rules`.",
16+
"Creates a persistent rule for all future conversations. For establishing code standards or preferences that should be applied consistently.",
1917
parameters: {
2018
type: "object",
21-
required: ["rule_name", "rule_content"],
19+
required: ["name", "rule"],
2220
properties: {
23-
rule_name: {
21+
name: {
2422
type: "string",
2523
description:
2624
"Short, descriptive name summarizing the rule's purpose (e.g. 'React Standards', 'Type Hints')",
2725
},
28-
rule_content: {
26+
rule: {
2927
type: "string",
3028
description:
3129
"Clear, imperative instruction for future code generation (e.g. 'Use named exports', 'Add Python type hints'). Each rule should focus on one specific standard.",
3230
},
31+
globs: {
32+
type: "string",
33+
description:
34+
"Optional file patterns to which this rule applies (e.g. ['**/*.{ts,tsx}'] or ['src/**/*.ts', 'tests/**/*.ts'])",
35+
},
3336
},
3437
},
3538
},
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import * as YAML from "yaml";
2+
import { createRuleBlockImpl } from "./createRuleBlock";
3+
4+
// Mock the extras parameter with necessary functions
5+
const mockIde = {
6+
getWorkspaceDirs: jest.fn().mockResolvedValue(["/"]),
7+
writeFile: jest.fn().mockResolvedValue(undefined),
8+
openFile: jest.fn().mockResolvedValue(undefined),
9+
};
10+
11+
const mockExtras = {
12+
ide: mockIde,
13+
};
14+
15+
describe("createRuleBlockImpl", () => {
16+
beforeEach(() => {
17+
jest.clearAllMocks();
18+
});
19+
20+
it("should create a basic rule with name and rule content", async () => {
21+
const args = {
22+
name: "Test Rule",
23+
rule: "Always write tests",
24+
};
25+
26+
const result = await createRuleBlockImpl(args, mockExtras as any);
27+
28+
// Verify that writeFile was called with the correct YAML
29+
expect(mockIde.writeFile).toHaveBeenCalled();
30+
const yamlContent = mockIde.writeFile.mock.calls[0][1];
31+
const parsedYaml = YAML.parse(yamlContent);
32+
33+
// Verify the structure of the YAML
34+
expect(parsedYaml).toEqual({
35+
name: "Test Rule",
36+
version: "0.0.1",
37+
schema: "v1",
38+
rules: [
39+
{
40+
name: "Test Rule",
41+
rule: "Always write tests",
42+
},
43+
],
44+
});
45+
});
46+
47+
it("should create a rule with glob pattern", async () => {
48+
const args = {
49+
name: "TypeScript Rule",
50+
rule: "Use interfaces for object shapes",
51+
globs: "**/*.{ts,tsx}",
52+
};
53+
54+
await createRuleBlockImpl(args, mockExtras as any);
55+
56+
// Verify the YAML structure with globs
57+
const yamlContent = mockIde.writeFile.mock.calls[0][1];
58+
const parsedYaml = YAML.parse(yamlContent);
59+
expect(parsedYaml.rules[0]).toEqual({
60+
name: "TypeScript Rule",
61+
rule: "Use interfaces for object shapes",
62+
globs: "**/*.{ts,tsx}",
63+
});
64+
});
65+
66+
it("should create a filename based on sanitized rule name", async () => {
67+
const args = {
68+
name: "Special Ch@racters & Spaces",
69+
rule: "Handle special characters",
70+
};
71+
72+
await createRuleBlockImpl(args, mockExtras as any);
73+
74+
// Check that the filename is sanitized
75+
const fileUri = mockIde.writeFile.mock.calls[0][0];
76+
expect(fileUri).toContain("special-chracters-spaces.yaml");
77+
});
78+
});

core/tools/implementations/createRuleBlock.ts

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,31 +4,34 @@ import { ToolImpl } from ".";
44
import { joinPathsToUri } from "../../util/uri";
55

66
export interface CreateRuleBlockArgs {
7-
rule_name: string;
8-
rule_content: string;
7+
name: string;
8+
rule: string;
9+
globs?: string;
910
}
1011

1112
export const createRuleBlockImpl: ToolImpl = async (
1213
args: CreateRuleBlockArgs,
1314
extras,
1415
) => {
1516
// Sanitize rule name for use in filename (remove special chars, replace spaces with dashes)
16-
const safeRuleName = args.rule_name
17+
const safeRuleName = args.name
1718
.toLowerCase()
1819
.replace(/[^a-z0-9\s-]/g, "")
1920
.replace(/\s+/g, "-");
2021

22+
const ruleObject = {
23+
name: args.name,
24+
rule: args.rule,
25+
...(args.globs ? { globs: args.globs.trim() } : {}),
26+
};
27+
2128
const ruleBlock: ConfigYaml = {
22-
name: args.rule_name,
29+
name: args.name,
2330
version: "0.0.1",
24-
rules: [
25-
// This can be either a string or an object with {name, rule}
26-
// Since we want a simple rule, we use the string format
27-
args.rule_content,
28-
],
31+
schema: "v1",
32+
rules: [ruleObject],
2933
};
3034

31-
// Convert the rule block to YAML
3235
const ruleYaml = YAML.stringify(ruleBlock);
3336

3437
const [localContinueDir] = await extras.ide.getWorkspaceDirs();
@@ -44,9 +47,13 @@ export const createRuleBlockImpl: ToolImpl = async (
4447

4548
return [
4649
{
47-
name: "Rule Block Created",
48-
description: `Created ${args.rule_name} rule`,
49-
content: ruleYaml,
50+
name: "New Rule Block",
51+
description: "", // No description throws an error in the GUI
52+
uri: {
53+
type: "file",
54+
value: rulesDirUri,
55+
},
56+
content: "Rule created successfully",
5057
},
5158
];
5259
};

docs/docs/customize/deep-dives/rules.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
description: Learn how to customize the system prompt with a `.continuerules` file
2+
description: Rules are used to provide instructions to the model for Chat, Edit, and Agent requests.
33
keywords: [rules, .continuerules, system, prompt, message]
44
---
55

@@ -20,9 +20,9 @@ To form the system message, rules are joined with new lines, in the order they a
2020
Rules can be added to an Assistant on the Continue Hub. Explore available rules [here](https://hub.continue.dev/explore/rules), or [create your own](https://hub.continue.dev/new?type=block&blockType=rules) in the Hub. These blocks are defined using the [`config.yaml` syntax](../../reference.md#rules) and can also be created locally.
2121

2222
:::info Automatically create local rule blocks
23-
When in Agent mode, you can simply prompt the agent to create a rule for you.
23+
When in Agent mode, you can simply prompt the agent to create a rule for you using the `builtin_create_rule_block` tool if enabled.
2424

25-
For example, you can say "Create a rule for this", and a rule will be created for you in `~/.continue/rules` based on your conversation.
25+
For example, you can say "Create a rule for this", and a rule will be created for you in `.continue/rules` based on your conversation.
2626
:::
2727

2828
### Syntax

gui/src/pages/gui/ToolCallDiv/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ import {
88
FolderIcon,
99
FolderOpenIcon,
1010
GlobeAltIcon,
11-
ListBulletIcon,
1211
MagnifyingGlassIcon,
1312
MapIcon,
13+
PencilIcon,
1414
XMarkIcon,
1515
} from "@heroicons/react/24/outline";
1616
import {
@@ -45,7 +45,7 @@ const toolCallIcons: Record<string, ComponentType> = {
4545
[BuiltInToolNames.ViewDiff]: CodeBracketIcon,
4646
[BuiltInToolNames.ViewRepoMap]: MapIcon,
4747
[BuiltInToolNames.ViewSubdirectory]: FolderOpenIcon,
48-
[BuiltInToolNames.CreateRuleBlock]: ListBulletIcon,
48+
[BuiltInToolNames.CreateRuleBlock]: PencilIcon,
4949
// EditExistingFile = "builtin_edit_existing_file",
5050
// CreateNewFile = "builtin_create_new_file",
5151
// RunTerminalCommand = "builtin_run_terminal_command",

0 commit comments

Comments
 (0)