Conversation
Implement a comprehensive rules system to manage development guidelines and coding standards across builtin, user, and project levels. Features: - Three-tier rule hierarchy: builtin (~/.config/agents/rules/) -> user -> project (.agents/rules/) - YAML frontmatter support for rule metadata (name, description, paths, priority, extends) - /rules slash commands for listing, viewing, and toggling rules (list, show, on, off, reset) - Automatic rule detection based on file path patterns - State persistence in .agents/rules.state.toml - System prompt injection with size (32 KiB) and count (10) limits Includes: - Bug fixes for async/await issues and KaosPath conversion - UI polish for /rules list display format New modules: src/kimi_cli/rules/ (models, discovery, parser, registry, injector, state)
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f2ffc0ccf8
ℹ️ About Codex in GitHub
Codex has been enabled to automatically 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 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
src/kimi_cli/rules/state.py
Outdated
| for rule_id, state in self._states.items(): | ||
| # Pinned states go to user level unless they came from project | ||
| # For now, save all to user level for simplicity | ||
| # TODO: Track origin level and save accordingly | ||
| user_states[rule_id] = state |
There was a problem hiding this comment.
Save project-scoped states to project state file
RulesStateManager.save() currently copies every in-memory state into user_states, so a /rules on|off action in one repository is persisted to ~/.config/agents/rules.state.toml and leaks into unrelated projects. That breaks the documented per-project override behavior and can silently change rule enablement globally; project-origin states need to be tracked and written to .agents/rules.state.toml instead of always going to user scope.
Useful? React with 👍 / 👎.
src/kimi_cli/rules/state.py
Outdated
| if user_states: | ||
| await self._save_state_file(self.user_state_path, user_states) |
There was a problem hiding this comment.
Persist reset by writing an empty rules state
After /rules reset, the registry clears in-memory states and calls save_states(), but save() skips writing when user_states is empty. If a state file already exists, this leaves stale entries on disk, so the reset is effectively undone on the next session load. save() should overwrite/delete existing state files when no states remain so reset persists across restarts.
Useful? React with 👍 / 👎.
- Add level field to RuleState for state separation by user/project - Fix rule loading priority: project rules now correctly override builtin - Fix injector priority to respect hierarchy (project > user > builtin) - Implement --hard reset to delete state files instead of saving empty - Delete empty state files on save to prevent stale data after reset - Combine nested if statements and use ternary operators for clarity
- Test level separation of user/project states - Test project state priority over user state - Test empty states delete files - Test delete_state_files functionality
|
Above mentioned comments were fixed by new 2 commits. |
docs/en/guides/interaction.md
Outdated
| ``` | ||
|
|
||
| Shell mode also supports some slash commands, including `/help`, `/exit`, `/version`, `/editor`, `/changelog`, `/feedback`, `/export`, `/import`, and `/task`. | ||
| Shell mode also supports some slash commands, including `/help`, `/exit`, `/version`, `/editor`, `/changelog`, `/feedback`, `/export`, `/import`, `/rules`, and `/task`. |
There was a problem hiding this comment.
🟡 Documentation claims /rules works in shell mode but no shell-mode registration exists
The docs at docs/en/guides/interaction.md:22, docs/zh/guides/interaction.md:22, docs/en/reference/slash-commands.md:6, and docs/zh/reference/slash-commands.md:6 all list /rules as available in shell mode. However, /rules is only registered in the soul-level registry at src/kimi_cli/soul/slash.py:293, not in the shell-mode shell_mode_registry at src/kimi_cli/ui/shell/slash.py:36. Other commands listed as shell-mode-available (e.g., /export, /import, /task) have explicit @shell_mode_registry.command registrations in src/kimi_cli/ui/shell/. When a user types /rules in shell mode, the shell UI will display "not available in shell mode" instead of running the command.
Prompt for agents
The /rules command is documented as available in shell mode (in docs/en/guides/interaction.md, docs/zh/guides/interaction.md, docs/en/reference/slash-commands.md, and docs/zh/reference/slash-commands.md) but it's not registered in the shell_mode_registry. Either add a @shell_mode_registry.command registration for /rules in src/kimi_cli/ui/shell/slash.py (following the pattern used by /export, /import, /task in src/kimi_cli/ui/shell/export_import.py and src/kimi_cli/ui/shell/slash.py), or remove /rules from the shell-mode command lists in all four documentation files.
Was this helpful? React with 👍 or 👎 to provide feedback.
Signed-off-by: T0M0R1N <70043311+Nemo4110@users.noreply.github.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a8396c2382
ℹ️ About Codex in GitHub
Codex has been enabled to automatically 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 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| state = self._state.get(rule_id, RuleState()) | ||
| if not state.enabled: |
There was a problem hiding this comment.
Respect auto-enable filter when state is absent
_auto_enable_rules() only creates state entries for matching categories, but get_active_rules() treats missing state as enabled=True, so every rule without an explicit state is still injected. In practice this makes auto_enable_by_path ineffective: language-specific rules that do not match the current project remain active unless users manually disable them, which can bloat prompts and apply wrong guidance.
Useful? React with 👍 / 👎.
| metadata = RuleMetadata( | ||
| name=metadata_dict.get("name"), | ||
| description=metadata_dict.get("description"), | ||
| paths=metadata_dict.get("paths", []), |
There was a problem hiding this comment.
Normalize scalar
paths frontmatter into a list
The parser assigns metadata.paths directly from YAML without type normalization. If a rule uses paths: "**/*.py" (a common single-pattern form), should_apply_rule() iterates the string character-by-character; because '*' is evaluated as a glob, the rule can match essentially any file and become globally active. Coercing scalar strings to a one-item list avoids this incorrect broad application.
Useful? React with 👍 / 👎.
| if root_str.startswith(work_dir_str): | ||
| return "project" |
There was a problem hiding this comment.
Detect project roots by path ancestry, not prefix
Level detection uses root_str.startswith(work_dir_str), which misclassifies user rules as project rules when the working directory is an ancestor of the user config path (for example, running in $HOME makes ~/.config/agents/rules look project-scoped). That causes wrong level reporting and persists toggles to the project state file instead of the user state file in those sessions.
Useful? React with 👍 / 👎.
- Fix KaosPath.to_path() → unsafe_to_local_path() (was causing all rule loading to fail) - Fix max_total_size=0 to mean unlimited (was incorrectly falling back to default) - Add max_active_rules enforcement (was not being applied) - Remove /rules from shell mode documentation (command only available in agent mode)
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 595cdebcf1
ℹ️ About Codex in GitHub
Codex has been enabled to automatically 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 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| name=metadata_dict.get("name"), | ||
| description=metadata_dict.get("description"), | ||
| paths=metadata_dict.get("paths", []), | ||
| priority=metadata_dict.get("priority", 100), |
There was a problem hiding this comment.
Validate frontmatter priority before storing metadata
This assigns priority directly from YAML without type coercion, so a user rule like priority: "10" (or any non-integer) leaves RuleMetadata.priority as a string. Later, both RulesRegistry.get_all_rules() and Runtime.create() sort mixed builtin integer priorities with that string value, which raises TypeError and can break startup or /rules listing whenever such a rule is present.
Useful? React with 👍 / 👎.
| async def _any_file_exists(self, pattern: str) -> bool: | ||
| """Check if any file matching pattern exists in work_dir.""" | ||
| import fnmatch | ||
|
|
||
| try: | ||
| # KaosPath has glob() but not rglob(), use **/* for recursive | ||
| async for item in self.work_dir.glob("**/*"): | ||
| if fnmatch.fnmatch(item.name, pattern): | ||
| return True | ||
| except Exception: | ||
| pass | ||
| return False |
There was a problem hiding this comment.
🔴 Expensive recursive glob scans in _auto_enable_rules produce no observable effect
_detect_project_types() at src/kimi_cli/rules/registry.py:153 iterates all ~11 language categories, calling _any_file_exists() for each pattern. Each call does self.work_dir.glob("**/*") — a full recursive directory traversal — then matches filenames via fnmatch. For non-matching languages (e.g., swift/kotlin in a Python project), the entire project tree is scanned. In a 65k-file repo, this means ~8–9 complete directory scans adding seconds to startup.
Critically, this work has no observable effect: get_active_rules() at line 226 uses self._state.get(rule_id, RuleState()), and the default RuleState() has enabled=True (src/kimi_cli/rules/models.py:44). So rules without a state entry are already enabled by default. The auto-enable step merely writes explicit RuleState(enabled=True) entries for matching categories, which is indistinguishable from the default. Since auto_enable_by_path defaults to True (src/kimi_cli/config.py:183), every CLI startup pays this cost for zero behavioral change.
Prompt for agents
The _any_file_exists method at registry.py:183-194 is called from _detect_project_types (line 153) which is called from _auto_enable_rules (line 134). The entire auto-enable mechanism has no effect because get_active_rules (line 226) already defaults rules to enabled via RuleState() which has enabled=True.
Two possible fixes:
1. If the intent is for ALL rules to be enabled by default (current behavior), remove _auto_enable_rules, _detect_project_types, and _any_file_exists entirely. They add startup latency for no benefit.
2. If the intent is to ONLY enable rules matching the project type, change get_active_rules to treat missing state entries as disabled (not enabled). Then auto-enable would actually gate which rules are active. You'd also want to optimize _any_file_exists to avoid full recursive glob scans — e.g., use a single directory scan and collect all filenames, then check patterns against that set.
Was this helpful? React with 👍 or 👎 to provide feedback.
| logger.info( | ||
| "Loaded {count} active rules ({size} bytes)", | ||
| count=len(active_rules), | ||
| size=total_size, | ||
| ) |
There was a problem hiding this comment.
🟡 Log reports total active rules count instead of actually injected count
In Runtime.create, the log at line 356 reports count=len(active_rules) — the total number of enabled rules from the registry. However, the loop above (lines 341–353) may include fewer rules in lines due to the max_rules limit (line 345) or the max_size byte limit (line 350). The logged count can be higher than the number of rules actually injected into the system prompt, making the log misleading for debugging prompt content.
| logger.info( | |
| "Loaded {count} active rules ({size} bytes)", | |
| count=len(active_rules), | |
| size=total_size, | |
| ) | |
| active_rules_content = "".join(lines).strip() | |
| logger.info( | |
| "Injected {count}/{total} active rules ({size} bytes)", | |
| count=len(lines), | |
| total=len(active_rules), | |
| size=total_size, | |
| ) | |
Was this helpful? React with 👍 or 👎 to provide feedback.
Signed-off-by: T0M0R1N <70043311+Nemo4110@users.noreply.github.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1d9f553574
ℹ️ About Codex in GitHub
Codex has been enabled to automatically 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 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| # Enforce max_active_rules limit | ||
| if rule_count >= max_rules: | ||
| break | ||
| section = f"## {rule.name}\n\n{rule.content}\n\n" |
There was a problem hiding this comment.
Honor include_source when building injected rules
When rules.include_source is enabled in config, injected rule sections are still rendered as ## {rule.name} without any (level/id) annotation, so the new option has no runtime effect. This makes it impossible to tell which layer supplied a rule when overrides exist, even though the configuration advertises this behavior. Please branch the header format here based on config.rules.include_source.
Useful? React with 👍 / 👎.
| for lang, patterns in detection_patterns.items(): | ||
| for pattern in patterns: | ||
| if await self._any_file_exists(pattern): |
There was a problem hiding this comment.
Avoid rescanning the entire tree for each detection pattern
Project-type auto-detection currently calls _any_file_exists() for every pattern, and each call performs a fresh recursive glob("**/*") over the whole workspace. With default auto_enable_by_path=true, startup can do dozens of full-tree traversals in large repos, causing noticeable latency before the first turn. This should be reduced to a single scan (or cached index) so enabling rules does not scale as O(patterns × files).
Useful? React with 👍 / 👎.
Related Issue
Resolve #1747
Description
This PR introduces a three-tier rules system for managing development guidelines and coding standards in Kimi Code CLI.
Features
~/.config/agents/rules/) → project (.agents/rules/)/rules [list|show|on|off|reset]for rule management.agents/rules.state.tomlBuilt-in Rules
common/coding-style: General coding conventionscommon/testing: Testing best practicespython/coding-style: Python-specific guidelinesConfiguration
Technical Details
New modules in src/kimi_cli/rules/:
Slash commands in src/kimi_cli/soul/slash.py:
Testing
Added comprehensive tests in tests/core/test_rules.py covering:
Checklist
uv run kimi)make gen-changelogto update the changelog.make gen-docsto update the user documentation.