@intellectronica/ruler 0.3.38 → 0.3.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +185 -36
- package/dist/agents/ClaudeAgent.js +3 -0
- package/dist/agents/CodexCliAgent.js +3 -0
- package/dist/agents/CopilotAgent.js +3 -0
- package/dist/agents/CursorAgent.js +3 -0
- package/dist/cli/commands.js +4 -0
- package/dist/cli/handlers.js +9 -1
- package/dist/constants.js +7 -1
- package/dist/core/ConfigLoader.js +79 -1
- package/dist/core/FileSystemUtils.js +26 -4
- package/dist/core/SubagentsProcessor.js +440 -0
- package/dist/core/SubagentsUtils.js +195 -0
- package/dist/core/apply-engine.js +17 -14
- package/dist/lib.js +42 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -54,40 +54,40 @@ Ruler solves this by providing a **single source of truth** for all your AI agen
|
|
|
54
54
|
|
|
55
55
|
## Supported AI Agents
|
|
56
56
|
|
|
57
|
-
| Agent | Rules File(s) | MCP Configuration / Notes | Skills Support / Location |
|
|
58
|
-
| ---------------------- | ---------------------------------------------- | ------------------------------------------------ | ------------------------- |
|
|
59
|
-
| AGENTS.md | `AGENTS.md` | (pseudo-agent ensuring root `AGENTS.md` exists) | - |
|
|
60
|
-
| GitHub Copilot | `AGENTS.md` | `.vscode/mcp.json` | `.claude/skills/` |
|
|
61
|
-
| Claude Code | `CLAUDE.md` | `.mcp.json` | `.claude/skills/` |
|
|
62
|
-
| OpenAI Codex CLI | `AGENTS.md` | `.codex/config.toml` | `.codex/skills/` |
|
|
63
|
-
| Pi Coding Agent | `AGENTS.md` | - | `.pi/skills/` |
|
|
64
|
-
| Jules | `AGENTS.md` | - | - |
|
|
65
|
-
| Cursor | `AGENTS.md` | `.cursor/mcp.json` | `.cursor/skills/` |
|
|
66
|
-
| Windsurf | `AGENTS.md` | `.windsurf/mcp_config.json` | `.windsurf/skills/` |
|
|
67
|
-
| Cline | `.clinerules` | - | - |
|
|
68
|
-
| Crush | `CRUSH.md` | `.crush.json` | - |
|
|
69
|
-
| Amp | `AGENTS.md` | - | `.agents/skills/` |
|
|
70
|
-
| Antigravity | `.agent/rules/ruler.md` | - | `.agent/skills/` |
|
|
71
|
-
| Amazon Q CLI | `.amazonq/rules/ruler_q_rules.md` | `.amazonq/mcp.json` | - |
|
|
72
|
-
| Aider | `AGENTS.md`, `.aider.conf.yml` | `.mcp.json` | - |
|
|
73
|
-
| Firebase Studio | `.idx/airules.md` | `.idx/mcp.json` | - |
|
|
74
|
-
| Open Hands | `.openhands/microagents/repo.md` | `config.toml` | - |
|
|
75
|
-
| Gemini CLI | `AGENTS.md` | `.gemini/settings.json` | `.gemini/skills/` |
|
|
76
|
-
| Junie | `.junie/guidelines.md` | `.junie/mcp/mcp.json` | `.junie/skills/` |
|
|
77
|
-
| AugmentCode | `.augment/rules/ruler_augment_instructions.md` | - | - |
|
|
78
|
-
| Kilo Code | `AGENTS.md` | `.kilocode/mcp.json` | `.claude/skills/` |
|
|
79
|
-
| OpenCode | `AGENTS.md` | `opencode.json` | `.opencode/skills/` |
|
|
80
|
-
| Goose | `.goosehints` | - | `.agents/skills/` |
|
|
81
|
-
| Qwen Code | `AGENTS.md` | `.qwen/settings.json` | - |
|
|
82
|
-
| RooCode | `AGENTS.md` | `.roo/mcp.json` | `.roo/skills/` |
|
|
83
|
-
| Zed | `AGENTS.md` | `.zed/settings.json` (project root, never $HOME) | - |
|
|
84
|
-
| Trae AI | `.trae/rules/project_rules.md` | - | - |
|
|
85
|
-
| Warp | `WARP.md` | - | - |
|
|
86
|
-
| Kiro | `.kiro/steering/ruler_kiro_instructions.md` | `.kiro/settings/mcp.json` | - |
|
|
87
|
-
| Firebender | `firebender.json` | `firebender.json` (rules and MCP in same file) | - |
|
|
88
|
-
| Factory Droid | `AGENTS.md` | `.factory/mcp.json` | `.factory/skills/` |
|
|
89
|
-
| Mistral Vibe | `AGENTS.md` | `.vibe/config.toml` | `.vibe/skills/` |
|
|
90
|
-
| JetBrains AI Assistant | `.aiassistant/rules/AGENTS.md` | - | - |
|
|
57
|
+
| Agent | Rules File(s) | MCP Configuration / Notes | Skills Support / Location | Subagents Support / Location |
|
|
58
|
+
| ---------------------- | ---------------------------------------------- | ------------------------------------------------ | ------------------------- | ---------------------------- |
|
|
59
|
+
| AGENTS.md | `AGENTS.md` | (pseudo-agent ensuring root `AGENTS.md` exists) | - | - |
|
|
60
|
+
| GitHub Copilot | `AGENTS.md` | `.vscode/mcp.json` | `.claude/skills/` | `.github/agents/` |
|
|
61
|
+
| Claude Code | `CLAUDE.md` | `.mcp.json` | `.claude/skills/` | `.claude/agents/` |
|
|
62
|
+
| OpenAI Codex CLI | `AGENTS.md` | `.codex/config.toml` | `.codex/skills/` | `.codex/agents/` (`.toml`) |
|
|
63
|
+
| Pi Coding Agent | `AGENTS.md` | - | `.pi/skills/` | - |
|
|
64
|
+
| Jules | `AGENTS.md` | - | - | - |
|
|
65
|
+
| Cursor | `AGENTS.md` | `.cursor/mcp.json` | `.cursor/skills/` | `.cursor/agents/` |
|
|
66
|
+
| Windsurf | `AGENTS.md` | `.windsurf/mcp_config.json` | `.windsurf/skills/` | - |
|
|
67
|
+
| Cline | `.clinerules` | - | - | - |
|
|
68
|
+
| Crush | `CRUSH.md` | `.crush.json` | - | - |
|
|
69
|
+
| Amp | `AGENTS.md` | - | `.agents/skills/` | - |
|
|
70
|
+
| Antigravity | `.agent/rules/ruler.md` | - | `.agent/skills/` | - |
|
|
71
|
+
| Amazon Q CLI | `.amazonq/rules/ruler_q_rules.md` | `.amazonq/mcp.json` | - | - |
|
|
72
|
+
| Aider | `AGENTS.md`, `.aider.conf.yml` | `.mcp.json` | - | - |
|
|
73
|
+
| Firebase Studio | `.idx/airules.md` | `.idx/mcp.json` | - | - |
|
|
74
|
+
| Open Hands | `.openhands/microagents/repo.md` | `config.toml` | - | - |
|
|
75
|
+
| Gemini CLI | `AGENTS.md` | `.gemini/settings.json` | `.gemini/skills/` | - |
|
|
76
|
+
| Junie | `.junie/guidelines.md` | `.junie/mcp/mcp.json` | `.junie/skills/` | - |
|
|
77
|
+
| AugmentCode | `.augment/rules/ruler_augment_instructions.md` | - | - | - |
|
|
78
|
+
| Kilo Code | `AGENTS.md` | `.kilocode/mcp.json` | `.claude/skills/` | - |
|
|
79
|
+
| OpenCode | `AGENTS.md` | `opencode.json` | `.opencode/skills/` | - |
|
|
80
|
+
| Goose | `.goosehints` | - | `.agents/skills/` | - |
|
|
81
|
+
| Qwen Code | `AGENTS.md` | `.qwen/settings.json` | - | - |
|
|
82
|
+
| RooCode | `AGENTS.md` | `.roo/mcp.json` | `.roo/skills/` | - |
|
|
83
|
+
| Zed | `AGENTS.md` | `.zed/settings.json` (project root, never $HOME) | - | - |
|
|
84
|
+
| Trae AI | `.trae/rules/project_rules.md` | - | - | - |
|
|
85
|
+
| Warp | `WARP.md` | - | - | - |
|
|
86
|
+
| Kiro | `.kiro/steering/ruler_kiro_instructions.md` | `.kiro/settings/mcp.json` | - | - |
|
|
87
|
+
| Firebender | `firebender.json` | `firebender.json` (rules and MCP in same file) | - | - |
|
|
88
|
+
| Factory Droid | `AGENTS.md` | `.factory/mcp.json` | `.factory/skills/` | - |
|
|
89
|
+
| Mistral Vibe | `AGENTS.md` | `.vibe/config.toml` | `.vibe/skills/` | - |
|
|
90
|
+
| JetBrains AI Assistant | `.aiassistant/rules/AGENTS.md` | - | - | - |
|
|
91
91
|
|
|
92
92
|
## Getting Started
|
|
93
93
|
|
|
@@ -241,9 +241,12 @@ The `apply` command looks for `.ruler/` in the current directory tree, reading t
|
|
|
241
241
|
| `--gitignore-local` | Write managed ignore entries to `.git/info/exclude` instead. |
|
|
242
242
|
| `--nested` | Enable nested rule loading (default: inherit from config or disabled). |
|
|
243
243
|
| `--no-nested` | Disable nested rule loading even if `nested = true` in config. |
|
|
244
|
-
| `--backup` |
|
|
244
|
+
| `--backup` | Enable creation of `.bak` backup files (default: enabled). |
|
|
245
|
+
| `--no-backup` | Disable creation of `.bak` backup files. |
|
|
245
246
|
| `--skills` | Enable skills support (experimental, default: enabled). |
|
|
246
247
|
| `--no-skills` | Disable skills support. |
|
|
248
|
+
| `--subagents` | Enable subagents support (experimental, default: enabled). |
|
|
249
|
+
| `--no-subagents` | Disable subagents support. |
|
|
247
250
|
| `--dry-run` | Preview changes without writing files. |
|
|
248
251
|
| `--local-only` | Skip `$XDG_CONFIG_HOME` when looking for configuration. |
|
|
249
252
|
| `--verbose` / `-v` | Display detailed output during execution. |
|
|
@@ -663,12 +666,13 @@ When skills support is enabled and gitignore integration is active, Ruler automa
|
|
|
663
666
|
- `.gemini/skills/` (for Gemini CLI)
|
|
664
667
|
- `.junie/skills/` (for Junie)
|
|
665
668
|
- `.cursor/skills/` (for Cursor)
|
|
669
|
+
- `.windsurf/skills/` (for Windsurf)
|
|
666
670
|
|
|
667
671
|
to your `.gitignore` file within the managed Ruler block.
|
|
668
672
|
|
|
669
673
|
### Requirements
|
|
670
674
|
|
|
671
|
-
- **For agents with native skills support** (Claude Code, GitHub Copilot, Kilo Code, OpenAI Codex CLI, OpenCode, Pi Coding Agent, Goose, Amp, Antigravity, Factory Droid, Mistral Vibe, Roo Code, Gemini CLI, Junie, Cursor): No additional requirements.
|
|
675
|
+
- **For agents with native skills support** (Claude Code, GitHub Copilot, Kilo Code, OpenAI Codex CLI, OpenCode, Pi Coding Agent, Goose, Amp, Antigravity, Factory Droid, Mistral Vibe, Roo Code, Gemini CLI, Junie, Cursor, Windsurf): No additional requirements.
|
|
672
676
|
|
|
673
677
|
### Validation
|
|
674
678
|
|
|
@@ -723,8 +727,153 @@ ruler apply
|
|
|
723
727
|
# - Gemini CLI: .gemini/skills/my-skill/
|
|
724
728
|
# - Junie: .junie/skills/my-skill/
|
|
725
729
|
# - Cursor: .cursor/skills/my-skill/
|
|
730
|
+
# - Windsurf: .windsurf/skills/my-skill/
|
|
726
731
|
```
|
|
727
732
|
|
|
733
|
+
## Subagents Support (Experimental)
|
|
734
|
+
|
|
735
|
+
> **⚠️ Experimental:** Subagents support is experimental and behavior may change in future releases.
|
|
736
|
+
|
|
737
|
+
Ruler can distribute named, delegatable **subagents** from a single source of truth (`.ruler/agents/`) to each agent's native subagent location. Each source file is one Markdown file with YAML frontmatter; Ruler transforms it into the format the target agent expects.
|
|
738
|
+
|
|
739
|
+
### How It Works
|
|
740
|
+
|
|
741
|
+
For agents with a native subagent primitive, Ruler writes one file per subagent into the target directory:
|
|
742
|
+
|
|
743
|
+
| Agent | Target location | Format |
|
|
744
|
+
| ----------------- | ------------------------------ | ------ |
|
|
745
|
+
| Claude Code | `.claude/agents/<name>.md` | Markdown + YAML frontmatter |
|
|
746
|
+
| Cursor | `.cursor/agents/<name>.md` | Markdown + YAML frontmatter |
|
|
747
|
+
| OpenAI Codex CLI | `.codex/agents/<name>.toml` | TOML (one self-contained file per agent) |
|
|
748
|
+
| GitHub Copilot | `.github/agents/<name>.md` | Markdown + YAML frontmatter |
|
|
749
|
+
|
|
750
|
+
Other agents (Windsurf, RooCode, Aider, Gemini CLI, …) do not yet have a comparable native subagent primitive and are skipped with a warning. Subagent propagation will be added when those agents ship a comparable file format.
|
|
751
|
+
|
|
752
|
+
### Source Format
|
|
753
|
+
|
|
754
|
+
Author each subagent as `.ruler/agents/<name>.md`:
|
|
755
|
+
|
|
756
|
+
```markdown
|
|
757
|
+
---
|
|
758
|
+
name: code-reviewer
|
|
759
|
+
description: Use PROACTIVELY after a feature/fix is implemented. Reviews against SOLID/DRY/KISS. Read-only.
|
|
760
|
+
tools: [Read, Grep, Glob, Bash]
|
|
761
|
+
model: inherit
|
|
762
|
+
readonly: true
|
|
763
|
+
is_background: false
|
|
764
|
+
---
|
|
765
|
+
|
|
766
|
+
# Code Reviewer
|
|
767
|
+
|
|
768
|
+
You operate in a fresh context window with read-only access. Your job is to
|
|
769
|
+
review the diff and surrounding code against the design principles and return
|
|
770
|
+
a structured verdict.
|
|
771
|
+
```
|
|
772
|
+
|
|
773
|
+
**Required frontmatter fields:**
|
|
774
|
+
|
|
775
|
+
| Field | Type | Notes |
|
|
776
|
+
| ------------- | ------ | ----------------------------------------------------- |
|
|
777
|
+
| `name` | string | Must match the filename stem (`code-reviewer.md` → `name: code-reviewer`). |
|
|
778
|
+
| `description` | string | When the parent agent should delegate to this subagent. |
|
|
779
|
+
|
|
780
|
+
**Optional frontmatter fields:**
|
|
781
|
+
|
|
782
|
+
| Field | Type | Used by | Default behavior |
|
|
783
|
+
| --------------- | ---------------- | ------------------------------------------------ | --------------------------------------------- |
|
|
784
|
+
| `tools` | string[] | Claude (verbatim), Copilot (mapped to aliases) | Cursor / Codex ignore; omitted if absent. |
|
|
785
|
+
| `model` | string | All four targets | Cursor defaults to `inherit`; others omit. |
|
|
786
|
+
| `readonly` | boolean | Cursor (verbatim), Codex (`sandbox_mode`), Copilot (`disable-model-invocation`) | Defaults to `false` for Cursor; omitted otherwise. |
|
|
787
|
+
| `is_background` | boolean | Cursor only | Defaults to `false` for Cursor. |
|
|
788
|
+
|
|
789
|
+
For GitHub Copilot, source `tools` (Claude vocabulary: `Read`, `Grep`, `Bash`, …) are translated to Copilot's aliases (`read`, `search`, `execute`, …). Tools that do not have a Copilot equivalent are dropped silently on a normal apply; pass `--verbose` (or use `--dry-run` to preview) to see which tools were dropped.
|
|
790
|
+
|
|
791
|
+
### Configuration
|
|
792
|
+
|
|
793
|
+
Subagent propagation is **disabled by default**. Opt in via CLI flag or `ruler.toml`:
|
|
794
|
+
|
|
795
|
+
```bash
|
|
796
|
+
ruler apply --subagents # enable subagent propagation for one run
|
|
797
|
+
```
|
|
798
|
+
|
|
799
|
+
```toml
|
|
800
|
+
# .ruler/ruler.toml
|
|
801
|
+
[agents]
|
|
802
|
+
enabled = true
|
|
803
|
+
# include_in_rules = true # also append .ruler/agents/*.md into top-level CLAUDE.md / AGENTS.md (default: false)
|
|
804
|
+
```
|
|
805
|
+
|
|
806
|
+
> **Note:** the previous release used `[subagents]` for these keys. `[subagents]` is still honored as a fallback with a deprecation warning, and will be removed in a future release. Please migrate to `[agents]`.
|
|
807
|
+
|
|
808
|
+
`[agents] enabled` controls only native subagent propagation from `.ruler/agents/`. It is independent from `[agents.<name>] enabled` (which toggles per-coding-agent output like `CLAUDE.md` / `AGENTS.md`).
|
|
809
|
+
|
|
810
|
+
CLI flags take precedence over `ruler.toml`, which takes precedence over the default (disabled).
|
|
811
|
+
|
|
812
|
+
### Validation
|
|
813
|
+
|
|
814
|
+
Source files are validated at discovery time:
|
|
815
|
+
|
|
816
|
+
- Files without YAML frontmatter are skipped with a warning.
|
|
817
|
+
- Files missing required `name` or `description` are skipped with a warning.
|
|
818
|
+
- Files where `name` does not match the filename stem are skipped with a warning.
|
|
819
|
+
- Unknown frontmatter keys are dropped (not errored).
|
|
820
|
+
|
|
821
|
+
### Dry-Run Mode
|
|
822
|
+
|
|
823
|
+
Use `--dry-run` to preview which files would be written without touching disk.
|
|
824
|
+
|
|
825
|
+
### `.gitignore` Integration
|
|
826
|
+
|
|
827
|
+
When subagents are enabled, the four target directories are added to the Ruler-managed block of `.gitignore`:
|
|
828
|
+
|
|
829
|
+
```
|
|
830
|
+
.claude/agents/
|
|
831
|
+
.cursor/agents/
|
|
832
|
+
.codex/agents/
|
|
833
|
+
.github/agents/
|
|
834
|
+
```
|
|
835
|
+
|
|
836
|
+
Use `--no-gitignore` to opt out.
|
|
837
|
+
|
|
838
|
+
### Cleanup
|
|
839
|
+
|
|
840
|
+
Subagent propagation does **not** currently have explicit `ruler revert` support. To remove generated subagent directories, set `[agents] enabled = false` (or pass `--no-subagents`) and run `ruler apply` once. Cleanup will run for all four targets even if no source `.ruler/agents/` directory exists.
|
|
841
|
+
|
|
842
|
+
### Example Workflow
|
|
843
|
+
|
|
844
|
+
```bash
|
|
845
|
+
# 1. Author a subagent in your project
|
|
846
|
+
mkdir -p .ruler/agents
|
|
847
|
+
cat > .ruler/agents/code-reviewer.md << 'EOF'
|
|
848
|
+
---
|
|
849
|
+
name: code-reviewer
|
|
850
|
+
description: Reviews changes against SOLID/DRY/KISS
|
|
851
|
+
tools: [Read, Grep, Glob]
|
|
852
|
+
readonly: true
|
|
853
|
+
---
|
|
854
|
+
|
|
855
|
+
You review code changes for quality.
|
|
856
|
+
EOF
|
|
857
|
+
|
|
858
|
+
# 2. Opt subagents in (default is disabled — see [agents] section above)
|
|
859
|
+
echo -e "\n[agents]\nenabled = true" >> .ruler/ruler.toml
|
|
860
|
+
|
|
861
|
+
# 3. Apply
|
|
862
|
+
ruler apply
|
|
863
|
+
|
|
864
|
+
# 4. The subagent is now available in each agent's native location:
|
|
865
|
+
# - Claude Code: .claude/agents/code-reviewer.md
|
|
866
|
+
# - Cursor: .cursor/agents/code-reviewer.md
|
|
867
|
+
# - Codex CLI: .codex/agents/code-reviewer.toml
|
|
868
|
+
# - GitHub Copilot: .github/agents/code-reviewer.md
|
|
869
|
+
```
|
|
870
|
+
|
|
871
|
+
### Limitations
|
|
872
|
+
|
|
873
|
+
- **No explicit revert command.** Cleanup happens via `[agents] enabled = false` on a subsequent `apply`.
|
|
874
|
+
- **Atomic replace, not merge.** Ruler regenerates each agent's subagent directory from the source on every apply. Manual edits to generated files will be overwritten.
|
|
875
|
+
- **No support yet for agents without a native subagent primitive.** Windsurf, RooCode, Aider, Gemini CLI, and others are skipped with a warning. Propagation will be added when those agents ship a comparable file format.
|
|
876
|
+
|
|
728
877
|
## `.gitignore` Integration
|
|
729
878
|
|
|
730
879
|
Ruler automatically manages your `.gitignore` file to keep generated agent configuration files out of version control.
|
package/dist/cli/commands.js
CHANGED
|
@@ -77,6 +77,10 @@ function run() {
|
|
|
77
77
|
.option('skills', {
|
|
78
78
|
type: 'boolean',
|
|
79
79
|
description: 'Enable/disable skills support (experimental, default: enabled)',
|
|
80
|
+
})
|
|
81
|
+
.option('subagents', {
|
|
82
|
+
type: 'boolean',
|
|
83
|
+
description: 'Enable/disable subagents support (experimental, default: enabled)',
|
|
80
84
|
});
|
|
81
85
|
}, handlers_1.applyHandler)
|
|
82
86
|
.command('init', 'Scaffold a .ruler directory with default files', (y) => {
|
package/dist/cli/handlers.js
CHANGED
|
@@ -114,8 +114,16 @@ async function applyHandler(argv) {
|
|
|
114
114
|
else {
|
|
115
115
|
skillsEnabled = undefined; // Let config/default decide
|
|
116
116
|
}
|
|
117
|
+
// Determine subagents preference: CLI > TOML > Default (enabled)
|
|
118
|
+
let subagentsEnabled;
|
|
119
|
+
if (argv.subagents !== undefined) {
|
|
120
|
+
subagentsEnabled = argv.subagents;
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
subagentsEnabled = undefined; // Let config/default decide
|
|
124
|
+
}
|
|
117
125
|
try {
|
|
118
|
-
await (0, lib_1.applyAllAgentConfigs)(projectRoot, agents, configPath, mcpEnabled, mcpStrategy, gitignorePreference, verbose, dryRun, localOnly, nested, backup, skillsEnabled, gitignoreLocalPreference);
|
|
126
|
+
await (0, lib_1.applyAllAgentConfigs)(projectRoot, agents, configPath, mcpEnabled, mcpStrategy, gitignorePreference, verbose, dryRun, localOnly, nested, backup, skillsEnabled, gitignoreLocalPreference, subagentsEnabled);
|
|
119
127
|
console.log('Ruler apply completed successfully.');
|
|
120
128
|
}
|
|
121
129
|
catch (err) {
|
package/dist/constants.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.SKILL_MD_FILENAME = exports.ANTIGRAVITY_SKILLS_PATH = exports.FACTORY_SKILLS_PATH = exports.WINDSURF_SKILLS_PATH = exports.CURSOR_SKILLS_PATH = exports.JUNIE_SKILLS_PATH = exports.GEMINI_SKILLS_PATH = exports.ROO_SKILLS_PATH = exports.VIBE_SKILLS_PATH = exports.GOOSE_SKILLS_PATH = exports.PI_SKILLS_PATH = exports.OPENCODE_SKILLS_PATH = exports.CODEX_SKILLS_PATH = exports.CLAUDE_SKILLS_PATH = exports.RULER_SKILLS_PATH = exports.SKILLS_DIR = exports.DEFAULT_RULES_FILENAME = exports.ERROR_PREFIX = void 0;
|
|
3
|
+
exports.COPILOT_SUBAGENTS_PATH = exports.CODEX_SUBAGENTS_PATH = exports.CURSOR_SUBAGENTS_PATH = exports.CLAUDE_SUBAGENTS_PATH = exports.RULER_SUBAGENTS_PATH = exports.SKILL_MD_FILENAME = exports.ANTIGRAVITY_SKILLS_PATH = exports.FACTORY_SKILLS_PATH = exports.WINDSURF_SKILLS_PATH = exports.CURSOR_SKILLS_PATH = exports.JUNIE_SKILLS_PATH = exports.GEMINI_SKILLS_PATH = exports.ROO_SKILLS_PATH = exports.VIBE_SKILLS_PATH = exports.GOOSE_SKILLS_PATH = exports.PI_SKILLS_PATH = exports.OPENCODE_SKILLS_PATH = exports.CODEX_SKILLS_PATH = exports.CLAUDE_SKILLS_PATH = exports.RULER_SKILLS_PATH = exports.SKILLS_DIR = exports.DEFAULT_RULES_FILENAME = exports.ERROR_PREFIX = void 0;
|
|
4
4
|
exports.actionPrefix = actionPrefix;
|
|
5
5
|
exports.createRulerError = createRulerError;
|
|
6
6
|
exports.logVerbose = logVerbose;
|
|
@@ -66,3 +66,9 @@ exports.WINDSURF_SKILLS_PATH = '.windsurf/skills';
|
|
|
66
66
|
exports.FACTORY_SKILLS_PATH = '.factory/skills';
|
|
67
67
|
exports.ANTIGRAVITY_SKILLS_PATH = '.agent/skills';
|
|
68
68
|
exports.SKILL_MD_FILENAME = 'SKILL.md';
|
|
69
|
+
// Subagents-related constants
|
|
70
|
+
exports.RULER_SUBAGENTS_PATH = '.ruler/agents';
|
|
71
|
+
exports.CLAUDE_SUBAGENTS_PATH = '.claude/agents';
|
|
72
|
+
exports.CURSOR_SUBAGENTS_PATH = '.cursor/agents';
|
|
73
|
+
exports.CODEX_SUBAGENTS_PATH = '.codex/agents';
|
|
74
|
+
exports.COPILOT_SUBAGENTS_PATH = '.github/agents';
|
|
@@ -33,6 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports._resetLegacySubagentsWarningForTests = _resetLegacySubagentsWarningForTests;
|
|
36
37
|
exports.loadConfig = loadConfig;
|
|
37
38
|
const fs_1 = require("fs");
|
|
38
39
|
const path = __importStar(require("path"));
|
|
@@ -40,6 +41,21 @@ const os = __importStar(require("os"));
|
|
|
40
41
|
const toml_1 = require("@iarna/toml");
|
|
41
42
|
const zod_1 = require("zod");
|
|
42
43
|
const constants_1 = require("../constants");
|
|
44
|
+
// One-shot guard so the deprecation message fires once per process even when
|
|
45
|
+
// `loadConfig` is called multiple times (e.g. nested mode walks every
|
|
46
|
+
// `.ruler` directory).
|
|
47
|
+
let _legacySubagentsWarned = false;
|
|
48
|
+
function warnLegacySubagentsSection() {
|
|
49
|
+
if (_legacySubagentsWarned)
|
|
50
|
+
return;
|
|
51
|
+
_legacySubagentsWarned = true;
|
|
52
|
+
(0, constants_1.logWarn)('`[subagents]` is deprecated; rename it to `[agents]` in your ruler.toml. ' +
|
|
53
|
+
'The legacy section is honored for now and will be removed in a future release.');
|
|
54
|
+
}
|
|
55
|
+
/** Test helper — re-arms the deprecation guard so suites can assert it fires. */
|
|
56
|
+
function _resetLegacySubagentsWarningForTests() {
|
|
57
|
+
_legacySubagentsWarned = false;
|
|
58
|
+
}
|
|
43
59
|
const mcpConfigSchema = zod_1.z
|
|
44
60
|
.object({
|
|
45
61
|
enabled: zod_1.z.boolean().optional(),
|
|
@@ -55,9 +71,21 @@ const agentConfigSchema = zod_1.z
|
|
|
55
71
|
mcp: mcpConfigSchema,
|
|
56
72
|
})
|
|
57
73
|
.optional();
|
|
74
|
+
// `[agents]` is a heterogeneous table that holds two unrelated kinds of keys:
|
|
75
|
+
// - reserved subagent-control booleans (`enabled`, `include_in_rules`)
|
|
76
|
+
// - one nested table per coding-agent integration (`[agents.claude]`, etc.)
|
|
77
|
+
// Reserved keys are validated by the object shape; everything else falls
|
|
78
|
+
// through `catchall` and is treated as a per-agent config record.
|
|
79
|
+
const SUBAGENT_RESERVED_KEYS = new Set(['enabled', 'include_in_rules']);
|
|
58
80
|
const rulerConfigSchema = zod_1.z.object({
|
|
59
81
|
default_agents: zod_1.z.array(zod_1.z.string()).optional(),
|
|
60
|
-
agents: zod_1.z
|
|
82
|
+
agents: zod_1.z
|
|
83
|
+
.object({
|
|
84
|
+
enabled: zod_1.z.boolean().optional(),
|
|
85
|
+
include_in_rules: zod_1.z.boolean().optional(),
|
|
86
|
+
})
|
|
87
|
+
.catchall(agentConfigSchema)
|
|
88
|
+
.optional(),
|
|
61
89
|
mcp: zod_1.z
|
|
62
90
|
.object({
|
|
63
91
|
enabled: zod_1.z.boolean().optional(),
|
|
@@ -75,6 +103,16 @@ const rulerConfigSchema = zod_1.z.object({
|
|
|
75
103
|
enabled: zod_1.z.boolean().optional(),
|
|
76
104
|
})
|
|
77
105
|
.optional(),
|
|
106
|
+
// Deprecated: kept in the schema only so that legacy `[subagents]` blocks
|
|
107
|
+
// are preserved through validation. The parser reads from here as a
|
|
108
|
+
// fallback when the new `[agents]` keys are absent and emits a one-time
|
|
109
|
+
// deprecation warning. Remove in the next minor release.
|
|
110
|
+
subagents: zod_1.z
|
|
111
|
+
.object({
|
|
112
|
+
enabled: zod_1.z.boolean().optional(),
|
|
113
|
+
include_in_rules: zod_1.z.boolean().optional(),
|
|
114
|
+
})
|
|
115
|
+
.optional(),
|
|
78
116
|
nested: zod_1.z.boolean().optional(),
|
|
79
117
|
});
|
|
80
118
|
/**
|
|
@@ -150,6 +188,11 @@ async function loadConfig(options) {
|
|
|
150
188
|
: {};
|
|
151
189
|
const agentConfigs = {};
|
|
152
190
|
for (const [name, section] of Object.entries(agentsSection)) {
|
|
191
|
+
// Reserved subagent-control keys live alongside per-agent records in
|
|
192
|
+
// the same `[agents]` table; skip them here so we only process actual
|
|
193
|
+
// coding-agent integrations as agent configs.
|
|
194
|
+
if (SUBAGENT_RESERVED_KEYS.has(name))
|
|
195
|
+
continue;
|
|
153
196
|
if (section && typeof section === 'object') {
|
|
154
197
|
const sectionObj = section;
|
|
155
198
|
const cfg = {};
|
|
@@ -214,6 +257,40 @@ async function loadConfig(options) {
|
|
|
214
257
|
if (typeof rawSkillsSection.enabled === 'boolean') {
|
|
215
258
|
skillsConfig.enabled = rawSkillsSection.enabled;
|
|
216
259
|
}
|
|
260
|
+
// Subagent control lives under `[agents]` (alongside per-agent records).
|
|
261
|
+
// The reserved keys `enabled` and `include_in_rules` are pulled out here
|
|
262
|
+
// and surfaced internally as `LoadedConfig.subagents` for the rest of the
|
|
263
|
+
// codebase, which still uses the `Subagent*` naming.
|
|
264
|
+
//
|
|
265
|
+
// Backward-compatibility: the previous release used `[subagents]` for the
|
|
266
|
+
// same two keys. We still read those as a fallback when the matching
|
|
267
|
+
// `[agents]` key is absent, and emit a one-time deprecation warning so
|
|
268
|
+
// existing configs keep working while users migrate.
|
|
269
|
+
const rawLegacySubagentsSection = raw.subagents &&
|
|
270
|
+
typeof raw.subagents === 'object' &&
|
|
271
|
+
!Array.isArray(raw.subagents)
|
|
272
|
+
? raw.subagents
|
|
273
|
+
: {};
|
|
274
|
+
const legacyHasContent = typeof rawLegacySubagentsSection.enabled === 'boolean' ||
|
|
275
|
+
typeof rawLegacySubagentsSection.include_in_rules === 'boolean';
|
|
276
|
+
if (legacyHasContent) {
|
|
277
|
+
warnLegacySubagentsSection();
|
|
278
|
+
}
|
|
279
|
+
const subagentsConfig = {};
|
|
280
|
+
if (typeof agentsSection.enabled === 'boolean') {
|
|
281
|
+
subagentsConfig.enabled = agentsSection.enabled;
|
|
282
|
+
}
|
|
283
|
+
else if (typeof rawLegacySubagentsSection.enabled === 'boolean') {
|
|
284
|
+
subagentsConfig.enabled = rawLegacySubagentsSection.enabled;
|
|
285
|
+
}
|
|
286
|
+
if (typeof agentsSection.include_in_rules === 'boolean') {
|
|
287
|
+
subagentsConfig.include_in_rules =
|
|
288
|
+
agentsSection.include_in_rules;
|
|
289
|
+
}
|
|
290
|
+
else if (typeof rawLegacySubagentsSection.include_in_rules === 'boolean') {
|
|
291
|
+
subagentsConfig.include_in_rules =
|
|
292
|
+
rawLegacySubagentsSection.include_in_rules;
|
|
293
|
+
}
|
|
217
294
|
const nestedDefined = typeof raw.nested === 'boolean';
|
|
218
295
|
const nested = nestedDefined ? raw.nested : false;
|
|
219
296
|
return {
|
|
@@ -223,6 +300,7 @@ async function loadConfig(options) {
|
|
|
223
300
|
mcp: globalMcpConfig,
|
|
224
301
|
gitignore: gitignoreConfig,
|
|
225
302
|
skills: skillsConfig,
|
|
303
|
+
subagents: subagentsConfig,
|
|
226
304
|
nested,
|
|
227
305
|
nestedDefined,
|
|
228
306
|
};
|
|
@@ -44,6 +44,7 @@ const fs_1 = require("fs");
|
|
|
44
44
|
const path = __importStar(require("path"));
|
|
45
45
|
const os = __importStar(require("os"));
|
|
46
46
|
const constants_1 = require("../constants");
|
|
47
|
+
const SUBAGENTS_DIR_NAME = path.basename(constants_1.RULER_SUBAGENTS_PATH);
|
|
47
48
|
/**
|
|
48
49
|
* Gets the XDG config directory path, falling back to ~/.config if XDG_CONFIG_HOME is not set.
|
|
49
50
|
*/
|
|
@@ -93,9 +94,18 @@ async function findRulerDir(startPath, checkGlobal = true) {
|
|
|
93
94
|
/**
|
|
94
95
|
* Recursively reads all Markdown (.md) files in rulerDir, returning their paths and contents.
|
|
95
96
|
* Files are sorted alphabetically by path.
|
|
97
|
+
*
|
|
98
|
+
* `.ruler/skills/` is always skipped (skills are propagated separately).
|
|
99
|
+
* `.ruler/agents/` is skipped unless `options.includeAgents` is `true`.
|
|
96
100
|
*/
|
|
97
|
-
async function readMarkdownFiles(rulerDir) {
|
|
101
|
+
async function readMarkdownFiles(rulerDir, options = {}) {
|
|
98
102
|
const mdFiles = [];
|
|
103
|
+
const includeAgents = options.includeAgents === true;
|
|
104
|
+
// Tracks whether we skipped a `.ruler/agents` subtree so the root-AGENTS.md
|
|
105
|
+
// fallback below still recognises ruler content as present and does not
|
|
106
|
+
// resurrect a previously generated root AGENTS.md (which may itself contain
|
|
107
|
+
// the very agent docs we're now excluding).
|
|
108
|
+
let sawExcludedAgents = false;
|
|
99
109
|
// Gather all markdown files (recursive) first
|
|
100
110
|
async function walk(dir) {
|
|
101
111
|
const entries = await fs_1.promises.readdir(dir, { withFileTypes: true });
|
|
@@ -115,13 +125,22 @@ async function readMarkdownFiles(rulerDir) {
|
|
|
115
125
|
}
|
|
116
126
|
}
|
|
117
127
|
if (isDir) {
|
|
118
|
-
// Skip .ruler/skills; skills are propagated separately and should not be concatenated
|
|
119
128
|
const relativeFromRoot = path.relative(rulerDir, fullPath);
|
|
129
|
+
// Skip .ruler/skills; skills are propagated separately and should not be concatenated
|
|
120
130
|
const isSkillsDir = relativeFromRoot === constants_1.SKILLS_DIR ||
|
|
121
131
|
relativeFromRoot.startsWith(`${constants_1.SKILLS_DIR}${path.sep}`);
|
|
122
132
|
if (isSkillsDir) {
|
|
123
133
|
continue;
|
|
124
134
|
}
|
|
135
|
+
// Skip .ruler/agents unless explicitly opted in via subagents.include_in_rules.
|
|
136
|
+
// Subagents are propagated separately to native locations and should not pollute
|
|
137
|
+
// the top-level rule concatenation by default.
|
|
138
|
+
const isAgentsDir = relativeFromRoot === SUBAGENTS_DIR_NAME ||
|
|
139
|
+
relativeFromRoot.startsWith(`${SUBAGENTS_DIR_NAME}${path.sep}`);
|
|
140
|
+
if (isAgentsDir && !includeAgents) {
|
|
141
|
+
sawExcludedAgents = true;
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
125
144
|
await walk(fullPath);
|
|
126
145
|
}
|
|
127
146
|
else if (isFile && entry.name.endsWith('.md')) {
|
|
@@ -170,9 +189,12 @@ async function readMarkdownFiles(rulerDir) {
|
|
|
170
189
|
const stat = await fs_1.promises.stat(rootAgentsPath);
|
|
171
190
|
if (stat.isFile()) {
|
|
172
191
|
const content = await fs_1.promises.readFile(rootAgentsPath, 'utf8');
|
|
173
|
-
// Check if this is a generated file and we have other .ruler files
|
|
192
|
+
// Check if this is a generated file and we have other .ruler files.
|
|
193
|
+
// `sawExcludedAgents` counts as "ruler content present" so a stale
|
|
194
|
+
// generated root AGENTS.md isn't resurrected when `.ruler/agents` was
|
|
195
|
+
// the only source under `.ruler` and is now being skipped.
|
|
174
196
|
const isGenerated = content.startsWith('<!-- Generated by Ruler -->');
|
|
175
|
-
const hasRulerFiles = others.length > 0 || primaryFile !== null;
|
|
197
|
+
const hasRulerFiles = others.length > 0 || primaryFile !== null || sawExcludedAgents;
|
|
176
198
|
// Additional check: if AGENTS.md contains ruler source comments and we have ruler files,
|
|
177
199
|
// it's likely a corrupted generated file that should be skipped
|
|
178
200
|
const containsRulerSources = content.includes('<!-- Source: .ruler/') ||
|