-
Notifications
You must be signed in to change notification settings - Fork 35
Add Kilo Code rules support #751
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
Add Kilo Code rules support #751
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codex Review
Here are some automated review suggestions for this pull request.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| this.buildToolRuleParamsDefault({ | ||
| baseDir, | ||
| rulesyncRule, | ||
| validate, | ||
| nonRootPath: this.getSettablePaths().nonRoot, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Global Kilo export ignores non-root rules
Kilo is now marked as supportsGlobal, but fromRulesyncRule never sets a Kilo-specific root path—root rules fall back to ToolRule’s default ./AGENTS.md and global mode in RulesProcessor only forwards root rules (see loadRulesyncFiles at lines 612‑618). Running the processor with toolTarget: "kilo" and global: true (e.g., rulesync generate --targets kilo --global) will emit a single AGENTS.md in the base directory and drop every non-root rulesync rule, so no files are written under ~/.kilocode/rules even though Kilo expects its global rules there. This leaves global Kilo generation non-functional.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR adds support for Kilo Code as a new tool target in Rulesync, enabling generation of Kilo Code rules from unified rulesync rules.
Key Changes:
- Adds
KiloRuleclass implementing Kilo Code's.kilocode/rules/*.mddirectory-based rules format - Configures Kilo Code with global mode support (
supportsGlobal: true) and auto rule discovery mode - Updates all necessary configuration files, tests, and documentation
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
src/types/tool-targets.ts |
Adds "kilo" to the list of supported tool targets in alphabetical order |
src/types/tool-targets.test.ts |
Updates test expectations to include "kilo" target |
src/features/rules/rules-processor.ts |
Integrates KiloRule with processor, imports class, adds to factory map with global support enabled |
src/features/rules/rules-processor.test.ts |
Updates global targets tests to include "kilo" among the 5 global-supporting tools |
src/features/rules/kilo-rule.ts |
New file implementing KiloRule class for .kilocode/rules/*.md generation, but missing global parameter handling |
src/features/rules/kilo-rule.test.ts |
New file with test coverage for basic KiloRule operations, but missing global mode tests |
cspell.json |
Adds Kilo-related words to the spell-check dictionary |
config-schema.json |
Adds "kilo" to the JSON schema enum for valid tool targets |
README.md |
Documents Kilo Code support in the features table and adds section explaining .kilocode/rules/*.md structure |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
src/features/rules/kilo-rule.ts
Outdated
| static getSettablePaths(): KiloRuleSettablePaths { | ||
| return { | ||
| nonRoot: { | ||
| relativeDirPath: join(".kilocode", "rules"), | ||
| }, | ||
| }; | ||
| } |
Copilot
AI
Dec 29, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The getSettablePaths method should accept a global parameter since this rule is configured with supportsGlobal: true in rules-processor.ts. When global mode is enabled, rules-processor calls getSettablePaths with a global parameter. Other rules that support global mode (like GeminiCliRule and CodexcliRule) implement getSettablePaths with signature: getSettablePaths({ global }: { global?: boolean } = {}). The method should return the same path structure regardless of global mode, as the baseDir will be set to the home directory by ConfigResolver when global is true.
| static async fromFile({ | ||
| baseDir = process.cwd(), | ||
| relativeFilePath, | ||
| validate = true, | ||
| }: ToolRuleFromFileParams): Promise<KiloRule> { | ||
| const fileContent = await readFileContent( | ||
| join(baseDir, this.getSettablePaths().nonRoot.relativeDirPath, relativeFilePath), | ||
| ); | ||
|
|
||
| return new KiloRule({ | ||
| baseDir, | ||
| relativeDirPath: this.getSettablePaths().nonRoot.relativeDirPath, | ||
| relativeFilePath: relativeFilePath, | ||
| fileContent, | ||
| validate, | ||
| }); | ||
| } |
Copilot
AI
Dec 29, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The fromFile method should accept and pass through a global parameter. Rules-processor passes global: this.global when calling fromFile. Other global-supporting rules like GeminiCliRule and CodexcliRule include global = false in their ToolRuleFromFileParams. This parameter should be passed to the KiloRule constructor.
| static fromRulesyncRule({ | ||
| baseDir = process.cwd(), | ||
| rulesyncRule, | ||
| validate = true, | ||
| }: ToolRuleFromRulesyncRuleParams): KiloRule { | ||
| return new KiloRule( | ||
| this.buildToolRuleParamsDefault({ | ||
| baseDir, | ||
| rulesyncRule, | ||
| validate, | ||
| nonRootPath: this.getSettablePaths().nonRoot, | ||
| }), | ||
| ); | ||
| } |
Copilot
AI
Dec 29, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The fromRulesyncRule method should accept a global parameter. Rules-processor passes global: this.global when calling fromRulesyncRule. Other global-supporting rules like GeminiCliRule and CodexcliRule include global = false in their ToolRuleFromRulesyncRuleParams. This parameter should be passed through to buildToolRuleParamsDefault and the KiloRule constructor.
| static forDeletion({ | ||
| baseDir = process.cwd(), | ||
| relativeDirPath, | ||
| relativeFilePath, | ||
| }: ToolRuleForDeletionParams): KiloRule { | ||
| return new KiloRule({ | ||
| baseDir, | ||
| relativeDirPath, | ||
| relativeFilePath, | ||
| fileContent: "", | ||
| validate: false, | ||
| }); | ||
| } |
Copilot
AI
Dec 29, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The forDeletion method should accept a global parameter. Rules-processor passes global: this.global when calling forDeletion. Other global-supporting rules like GeminiCliRule and CodexcliRule include global = false in their ToolRuleForDeletionParams. This parameter should be passed to the KiloRule constructor.
| import { join } from "node:path"; | ||
| import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; | ||
| import { | ||
| RULESYNC_RELATIVE_DIR_PATH, | ||
| RULESYNC_RULES_RELATIVE_DIR_PATH, | ||
| } from "../../constants/rulesync-paths.js"; | ||
| import { setupTestDirectory } from "../../test-utils/test-directories.js"; | ||
| import { ensureDir, writeFileContent } from "../../utils/file.js"; | ||
| import { KiloRule } from "./kilo-rule.js"; | ||
| import { RulesyncRule } from "./rulesync-rule.js"; | ||
|
|
||
| describe("KiloRule", () => { | ||
| let testDir: string; | ||
| let cleanup: () => Promise<void>; | ||
|
|
||
| beforeEach(async () => { | ||
| ({ testDir, cleanup } = await setupTestDirectory()); | ||
| vi.spyOn(process, "cwd").mockReturnValue(testDir); | ||
| }); | ||
|
|
||
| afterEach(async () => { | ||
| await cleanup(); | ||
| vi.restoreAllMocks(); | ||
| }); | ||
|
|
||
| describe("fromFile", () => { | ||
| it("should read rule content from the .kilocode/rules directory", async () => { | ||
| const rulesDir = join(testDir, ".kilocode/rules"); | ||
| await ensureDir(rulesDir); | ||
| const filePath = join(rulesDir, "test-rule.md"); | ||
| await writeFileContent(filePath, "# Kilo Rule\n\nBody"); | ||
|
|
||
| const kiloRule = await KiloRule.fromFile({ | ||
| baseDir: testDir, | ||
| relativeFilePath: "test-rule.md", | ||
| }); | ||
|
|
||
| expect(kiloRule.getRelativeDirPath()).toBe(".kilocode/rules"); | ||
| expect(kiloRule.getRelativeFilePath()).toBe("test-rule.md"); | ||
| expect(kiloRule.getFileContent()).toBe("# Kilo Rule\n\nBody"); | ||
| expect(kiloRule.getFilePath()).toBe(filePath); | ||
| }); | ||
| }); | ||
|
|
||
| describe("fromRulesyncRule", () => { | ||
| it("should build rule parameters from a Rulesync rule", () => { | ||
| const rulesyncRule = new RulesyncRule({ | ||
| baseDir: testDir, | ||
| relativeDirPath: RULESYNC_RELATIVE_DIR_PATH, | ||
| relativeFilePath: "kilo.md", | ||
| frontmatter: { targets: ["kilo"] }, | ||
| body: "# From Rulesync", | ||
| }); | ||
|
|
||
| const kiloRule = KiloRule.fromRulesyncRule({ | ||
| baseDir: testDir, | ||
| rulesyncRule, | ||
| }); | ||
|
|
||
| expect(kiloRule.getRelativeDirPath()).toBe(".kilocode/rules"); | ||
| expect(kiloRule.getRelativeFilePath()).toBe("kilo.md"); | ||
| expect(kiloRule.getFileContent()).toBe("# From Rulesync"); | ||
| }); | ||
| }); | ||
|
|
||
| describe("toRulesyncRule", () => { | ||
| it("should convert back to a RulesyncRule", () => { | ||
| const kiloRule = new KiloRule({ | ||
| baseDir: testDir, | ||
| relativeDirPath: ".kilocode/rules", | ||
| relativeFilePath: "team.md", | ||
| fileContent: "# Team Rules", | ||
| }); | ||
|
|
||
| const rulesyncRule = kiloRule.toRulesyncRule(); | ||
|
|
||
| expect(rulesyncRule.getRelativeDirPath()).toBe(RULESYNC_RULES_RELATIVE_DIR_PATH); | ||
| expect(rulesyncRule.getRelativeFilePath()).toBe("team.md"); | ||
| expect(rulesyncRule.getBody()).toBe("# Team Rules"); | ||
| expect(rulesyncRule.getFrontmatter().targets).toEqual(["*"]); | ||
| }); | ||
| }); | ||
|
|
||
| describe("forDeletion", () => { | ||
| it("should create a non-validated rule for cleanup", () => { | ||
| const rule = KiloRule.forDeletion({ | ||
| baseDir: testDir, | ||
| relativeDirPath: ".kilocode/rules", | ||
| relativeFilePath: "obsolete.md", | ||
| }); | ||
|
|
||
| expect(rule.isDeletable()).toBe(true); | ||
| expect(rule.getFilePath()).toBe(join(testDir, ".kilocode/rules/obsolete.md")); | ||
| }); | ||
| }); | ||
|
|
||
| describe("isTargetedByRulesyncRule", () => { | ||
| it("should detect rulesync frontmatter targeting Kilo Code", () => { | ||
| const rulesyncRule = new RulesyncRule({ | ||
| baseDir: testDir, | ||
| relativeDirPath: RULESYNC_RULES_RELATIVE_DIR_PATH, | ||
| relativeFilePath: "kilo.md", | ||
| frontmatter: { targets: ["kilo"] }, | ||
| body: "# Targeted", | ||
| }); | ||
|
|
||
| expect(KiloRule.isTargetedByRulesyncRule(rulesyncRule)).toBe(true); | ||
| }); | ||
| }); | ||
| }); |
Copilot
AI
Dec 29, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test suite is missing coverage for global mode functionality. Since KiloRule is configured with supportsGlobal: true in rules-processor.ts, tests should verify the behavior when global parameter is true. Compare with codexcli-rule.test.ts which includes tests for getSettablePaths with global flag, fromFile with global flag, and fromRulesyncRule with global flag. These tests ensure that global mode paths are correctly resolved and that the rule can be properly instantiated in global mode.
- Fix README.md table formatting by removing empty line between rows - Add global option parameter to getSettablePaths for consistency with base class - Add test case for getSettablePaths global mode behavior 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Remove unused RULESYNC_RELATIVE_DIR_PATH import from kilo-rule.test.ts - Remove unused KiloRuleParams type export from kilo-rule.ts - Add comprehensive tests following cline-rule.test.ts pattern: - constructor tests with various parameters - validate() method tests including edge cases - fromFile tests with validation and nested directories - fromRulesyncRule tests with custom baseDir - isTargetedByRulesyncRule edge cases - ToolRule base class integration tests 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
…-feature-based-on-issue-185 # Conflicts: # .devcontainer/Dockerfile # README.md
Add "kilorules" word to the cspell dictionary to fix spell check failures for the Kilo Code legacy file name format in tests. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
Summary
.kilocode/ruleslayoutsTesting
Resolves #185
Codex Task