@intellectronica/ruler 0.3.10 → 0.3.12
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 +196 -35
- package/dist/agents/AbstractAgent.js +8 -2
- package/dist/agents/AgentsMdAgent.js +1 -2
- package/dist/agents/AugmentCodeAgent.js +1 -2
- package/dist/agents/ClaudeAgent.js +3 -0
- package/dist/agents/CursorAgent.js +1 -2
- package/dist/agents/QwenCodeAgent.js +1 -2
- package/dist/agents/WindsurfAgent.js +6 -137
- package/dist/cli/commands.js +5 -2
- package/dist/cli/handlers.js +31 -2
- package/dist/constants.js +8 -1
- package/dist/core/ConfigLoader.js +40 -2
- package/dist/core/SkillsProcessor.js +301 -0
- package/dist/core/SkillsUtils.js +161 -0
- package/dist/core/UnifiedConfigLoader.js +12 -0
- package/dist/core/apply-engine.js +195 -32
- package/dist/lib.js +105 -7
- package/package.json +17 -13
package/README.md
CHANGED
|
@@ -62,7 +62,7 @@ Ruler solves this by providing a **single source of truth** for all your AI agen
|
|
|
62
62
|
| OpenAI Codex CLI | `AGENTS.md` | `.codex/config.toml` |
|
|
63
63
|
| Jules | `AGENTS.md` | - |
|
|
64
64
|
| Cursor | `.cursor/rules/ruler_cursor_instructions.mdc` | `.cursor/mcp.json` |
|
|
65
|
-
| Windsurf | `.windsurf/
|
|
65
|
+
| Windsurf | `AGENTS.md` | `.windsurf/mcp_config.json` |
|
|
66
66
|
| Cline | `.clinerules` | - |
|
|
67
67
|
| Crush | `CRUSH.md` | `.crush.json` |
|
|
68
68
|
| Amp | `AGENTS.md` | - |
|
|
@@ -82,7 +82,7 @@ Ruler solves this by providing a **single source of truth** for all your AI agen
|
|
|
82
82
|
| Trae AI | `.trae/rules/project_rules.md` | - |
|
|
83
83
|
| Warp | `WARP.md` | - |
|
|
84
84
|
| Kiro | `.kiro/steering/ruler_kiro_instructions.md` | - |
|
|
85
|
-
| Firebender | `firebender.json` |
|
|
85
|
+
| Firebender | `firebender.json` | `firebender.json` (rules and MCP in same file) |
|
|
86
86
|
|
|
87
87
|
## Getting Started
|
|
88
88
|
|
|
@@ -105,10 +105,11 @@ npx @intellectronica/ruler apply
|
|
|
105
105
|
1. Navigate to your project's root directory
|
|
106
106
|
2. Run `ruler init`
|
|
107
107
|
3. This creates:
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
108
|
+
|
|
109
|
+
- `.ruler/` directory
|
|
110
|
+
- `.ruler/AGENTS.md`: The primary starter Markdown file for your rules
|
|
111
|
+
- `.ruler/ruler.toml`: The main configuration file for Ruler (now contains sample MCP server sections; legacy `.ruler/mcp.json` no longer scaffolded)
|
|
112
|
+
- (Optional legacy fallback) If you previously used `.ruler/instructions.md`, it is still respected when `AGENTS.md` is absent. (The prior runtime warning was removed.)
|
|
112
113
|
|
|
113
114
|
Additionally, you can create a global configuration to use when no local `.ruler/` directory is found:
|
|
114
115
|
|
|
@@ -160,7 +161,15 @@ project/
|
|
|
160
161
|
|
|
161
162
|
- Discover all `.ruler/` directories in the project hierarchy
|
|
162
163
|
- Load and concatenate rules from each directory in order
|
|
163
|
-
-
|
|
164
|
+
- Decide whether nested mode is enabled using the following precedence:
|
|
165
|
+
1. `ruler apply --nested` (or `--no-nested`) takes top priority
|
|
166
|
+
2. `nested = true` in `ruler.toml`
|
|
167
|
+
3. Default to disabled when neither option is provided
|
|
168
|
+
- When a run is nested, downstream configs are forced to keep `nested = true`. If a child config attempts to disable it, Ruler keeps nested processing active and emits a warning in the logs.
|
|
169
|
+
- Nested processing carries forward each directory's own MCP bundle and configuration settings so that generated files remain scoped to their source directories while being normalized back to the project root.
|
|
170
|
+
|
|
171
|
+
> [!CAUTION]
|
|
172
|
+
> Nested mode is experimental and may change in future releases. The CLI logs this warning the first time a nested run is detected so you know the behavior may evolve.
|
|
164
173
|
|
|
165
174
|
**Perfect for:**
|
|
166
175
|
|
|
@@ -212,21 +221,22 @@ The `apply` command looks for `.ruler/` in the current directory tree, reading t
|
|
|
212
221
|
|
|
213
222
|
### Options
|
|
214
223
|
|
|
215
|
-
| Option | Description
|
|
216
|
-
| ------------------------------ |
|
|
217
|
-
| `--project-root <path>` |
|
|
218
|
-
| `--agents <agent1,agent2,...>` | Comma-separated
|
|
219
|
-
| `--config <path>` |
|
|
220
|
-
| `--mcp` / `--with-mcp` | Enable applying MCP server configurations (default: true)
|
|
221
|
-
| `--no-mcp` | Disable applying MCP server configurations
|
|
222
|
-
| `--mcp-overwrite` | Overwrite native MCP config
|
|
223
|
-
| `--gitignore` | Enable automatic .gitignore updates (default: true)
|
|
224
|
-
| `--no-gitignore` | Disable automatic .gitignore updates
|
|
225
|
-
| `--nested` | Enable nested rule loading
|
|
226
|
-
| `--
|
|
227
|
-
| `--
|
|
228
|
-
| `--
|
|
229
|
-
| `--
|
|
224
|
+
| Option | Description |
|
|
225
|
+
| ------------------------------ | ---------------------------------------------------------------------- |
|
|
226
|
+
| `--project-root <path>` | Project root path (default: current directory). |
|
|
227
|
+
| `--agents <agent1,agent2,...>` | Comma-separated agent names to target (see supported list below). |
|
|
228
|
+
| `--config <path>` | Custom `ruler.toml` path. |
|
|
229
|
+
| `--mcp` / `--with-mcp` | Enable applying MCP server configurations (default: true). |
|
|
230
|
+
| `--no-mcp` | Disable applying MCP server configurations. |
|
|
231
|
+
| `--mcp-overwrite` | Overwrite native MCP config instead of merging. |
|
|
232
|
+
| `--gitignore` | Enable automatic .gitignore updates (default: true). |
|
|
233
|
+
| `--no-gitignore` | Disable automatic .gitignore updates. |
|
|
234
|
+
| `--nested` | Enable nested rule loading (default: inherit from config or disabled). |
|
|
235
|
+
| `--no-nested` | Disable nested rule loading even if `nested = true` in config. |
|
|
236
|
+
| `--backup` | Toggle creation of `.bak` backup files (default: enabled). |
|
|
237
|
+
| `--dry-run` | Preview changes without writing files. |
|
|
238
|
+
| `--local-only` | Skip `$XDG_CONFIG_HOME` when looking for configuration. |
|
|
239
|
+
| `--verbose` / `-v` | Display detailed output during execution. |
|
|
230
240
|
|
|
231
241
|
### Common Examples
|
|
232
242
|
|
|
@@ -305,15 +315,15 @@ ruler revert [options]
|
|
|
305
315
|
|
|
306
316
|
### Options
|
|
307
317
|
|
|
308
|
-
| Option | Description
|
|
309
|
-
| ------------------------------ |
|
|
310
|
-
| `--project-root <path>` | Path to your project's root (default: current directory)
|
|
318
|
+
| Option | Description |
|
|
319
|
+
| ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
320
|
+
| `--project-root <path>` | Path to your project's root (default: current directory) |
|
|
311
321
|
| `--agents <agent1,agent2,...>` | Comma-separated list of agent names to revert (agentsmd, aider, amazonqcli, amp, augmentcode, claude, cline, codex, copilot, crush, cursor, firebase, firebender, gemini-cli, goose, jules, junie, kilocode, kiro, opencode, openhands, qwen, roo, trae, warp, windsurf, zed) |
|
|
312
|
-
| `--config <path>` | Path to a custom `ruler.toml` configuration file
|
|
313
|
-
| `--keep-backups` | Keep backup files (.bak) after restoration (default: false)
|
|
314
|
-
| `--dry-run` | Preview changes without actually reverting files
|
|
315
|
-
| `--verbose` / `-v` | Display detailed output during execution
|
|
316
|
-
| `--local-only` | Only search for local .ruler directories, ignore global config
|
|
322
|
+
| `--config <path>` | Path to a custom `ruler.toml` configuration file |
|
|
323
|
+
| `--keep-backups` | Keep backup files (.bak) after restoration (default: false) |
|
|
324
|
+
| `--dry-run` | Preview changes without actually reverting files |
|
|
325
|
+
| `--verbose` / `-v` | Display detailed output during execution |
|
|
326
|
+
| `--local-only` | Only search for local .ruler directories, ignore global config |
|
|
317
327
|
|
|
318
328
|
### Common Examples
|
|
319
329
|
|
|
@@ -525,6 +535,7 @@ DEBUG = "1"
|
|
|
525
535
|
```
|
|
526
536
|
|
|
527
537
|
**Remote servers** require a `url` field (headers optional; bearer Authorization token auto-extracted for OpenHands when possible):
|
|
538
|
+
|
|
528
539
|
```toml
|
|
529
540
|
[mcp_servers.remote_server]
|
|
530
541
|
url = "https://api.example.com"
|
|
@@ -543,6 +554,143 @@ Ruler uses this configuration with the `merge` (default) or `overwrite` strategy
|
|
|
543
554
|
export CODEX_HOME="$(pwd)/.codex"
|
|
544
555
|
```
|
|
545
556
|
|
|
557
|
+
## Skills Support (Experimental)
|
|
558
|
+
|
|
559
|
+
**⚠️ Experimental Feature**: Skills support is currently experimental and requires `uv` (the Python package manager) to be installed on your system for MCP-based agent integration.
|
|
560
|
+
|
|
561
|
+
Ruler can manage and propagate Claude Code-compatible skills to supported AI agents. Skills are stored in `.ruler/skills/` and are automatically distributed to compatible agents when you run `ruler apply`.
|
|
562
|
+
|
|
563
|
+
### How It Works
|
|
564
|
+
|
|
565
|
+
Skills are specialized knowledge packages that extend AI agent capabilities with domain-specific expertise, workflows, or tool integrations. Ruler discovers skills in your `.ruler/skills/` directory and propagates them to compatible agents:
|
|
566
|
+
|
|
567
|
+
- **Claude Code agents**: Skills are copied to `.claude/skills/` in their native format
|
|
568
|
+
- **Other MCP-compatible agents**: Skills are copied to `.skillz/` and a Skillz MCP server is automatically configured via `uvx`
|
|
569
|
+
|
|
570
|
+
### Skills Directory Structure
|
|
571
|
+
|
|
572
|
+
Skills can be organized flat or nested:
|
|
573
|
+
|
|
574
|
+
```
|
|
575
|
+
.ruler/skills/
|
|
576
|
+
├── my-skill/
|
|
577
|
+
│ ├── SKILL.md # Required: skill instructions/knowledge
|
|
578
|
+
│ ├── helper.py # Optional: additional resources (scripts)
|
|
579
|
+
│ └── reference.md # Optional: additional resources (docs)
|
|
580
|
+
└── another-skill/
|
|
581
|
+
└── SKILL.md
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
Each skill must contain:
|
|
585
|
+
|
|
586
|
+
- `SKILL.md` - Primary skill file with instructions or knowledge base
|
|
587
|
+
|
|
588
|
+
Skills can optionally include additional resources like:
|
|
589
|
+
|
|
590
|
+
- Markdown files with supplementary documentation
|
|
591
|
+
- Python, JavaScript, or other scripts
|
|
592
|
+
- Configuration files or data
|
|
593
|
+
|
|
594
|
+
### Configuration
|
|
595
|
+
|
|
596
|
+
Skills support is **enabled by default** but can be controlled via:
|
|
597
|
+
|
|
598
|
+
**CLI flags:**
|
|
599
|
+
|
|
600
|
+
```bash
|
|
601
|
+
# Enable skills (default)
|
|
602
|
+
ruler apply --skills
|
|
603
|
+
|
|
604
|
+
# Disable skills
|
|
605
|
+
ruler apply --no-skills
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
**Configuration in `ruler.toml`:**
|
|
609
|
+
|
|
610
|
+
```toml
|
|
611
|
+
[skills]
|
|
612
|
+
enabled = true # or false to disable
|
|
613
|
+
```
|
|
614
|
+
|
|
615
|
+
### Skillz MCP Server
|
|
616
|
+
|
|
617
|
+
For agents that support MCP but don't have native skills support (all agents except Claude Code), Ruler automatically:
|
|
618
|
+
|
|
619
|
+
1. Copies skills to `.skillz/` directory
|
|
620
|
+
2. Configures a Skillz MCP server in the agent's configuration
|
|
621
|
+
3. Uses `uvx` to launch the server with the absolute path to `.skillz`
|
|
622
|
+
|
|
623
|
+
Example auto-generated MCP server configuration:
|
|
624
|
+
|
|
625
|
+
```toml
|
|
626
|
+
[mcp_servers.skillz]
|
|
627
|
+
command = "uvx"
|
|
628
|
+
args = ["skillz@latest", "/absolute/path/to/project/.skillz"]
|
|
629
|
+
```
|
|
630
|
+
|
|
631
|
+
### `.gitignore` Integration
|
|
632
|
+
|
|
633
|
+
When skills support is enabled and gitignore integration is active, Ruler automatically adds:
|
|
634
|
+
|
|
635
|
+
- `.claude/skills/` (for Claude Code agents)
|
|
636
|
+
- `.skillz/` (for MCP-based agents)
|
|
637
|
+
|
|
638
|
+
to your `.gitignore` file within the managed Ruler block.
|
|
639
|
+
|
|
640
|
+
### Requirements
|
|
641
|
+
|
|
642
|
+
- **For Claude Code**: No additional requirements
|
|
643
|
+
- **For MCP agents**: `uv` must be installed and available in your PATH
|
|
644
|
+
```bash
|
|
645
|
+
# Install uv if needed
|
|
646
|
+
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
647
|
+
```
|
|
648
|
+
|
|
649
|
+
### Validation
|
|
650
|
+
|
|
651
|
+
Ruler validates discovered skills and issues warnings for:
|
|
652
|
+
|
|
653
|
+
- Missing required file (`SKILL.md`)
|
|
654
|
+
- Invalid directory structures (directories without `SKILL.md` and no sub-skills)
|
|
655
|
+
|
|
656
|
+
Warnings don't prevent propagation but help identify potential issues.
|
|
657
|
+
|
|
658
|
+
### Dry-Run Mode
|
|
659
|
+
|
|
660
|
+
Test skills propagation without making changes:
|
|
661
|
+
|
|
662
|
+
```bash
|
|
663
|
+
ruler apply --dry-run
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
This shows which skills would be copied and which MCP servers would be configured.
|
|
667
|
+
|
|
668
|
+
### Example Workflow
|
|
669
|
+
|
|
670
|
+
```bash
|
|
671
|
+
# 1. Add a skill to your project
|
|
672
|
+
mkdir -p .ruler/skills/my-skill
|
|
673
|
+
cat > .ruler/skills/my-skill/SKILL.md << 'EOF'
|
|
674
|
+
# My Custom Skill
|
|
675
|
+
|
|
676
|
+
This skill provides specialized knowledge for...
|
|
677
|
+
|
|
678
|
+
## Usage
|
|
679
|
+
|
|
680
|
+
When working on this project, always follow these guidelines:
|
|
681
|
+
- Use TypeScript for all new code
|
|
682
|
+
- Write tests for all features
|
|
683
|
+
- Follow the existing code style
|
|
684
|
+
EOF
|
|
685
|
+
|
|
686
|
+
# 2. Apply to all agents (skills enabled by default)
|
|
687
|
+
ruler apply
|
|
688
|
+
|
|
689
|
+
# 3. Skills are now available to compatible agents:
|
|
690
|
+
# - Claude Code: .claude/skills/my-skill/
|
|
691
|
+
# - Other MCP agents: .skillz/my-skill/ + Skillz MCP server configured
|
|
692
|
+
```
|
|
693
|
+
|
|
546
694
|
## `.gitignore` Integration
|
|
547
695
|
|
|
548
696
|
Ruler automatically manages your `.gitignore` file to keep generated agent configuration files out of version control.
|
|
@@ -599,7 +747,7 @@ ruler apply
|
|
|
599
747
|
|
|
600
748
|
### Scenario 2: Complex Projects with Nested Rules
|
|
601
749
|
|
|
602
|
-
For large projects with multiple components or services,
|
|
750
|
+
For large projects with multiple components or services, enable nested rule loading so each directory keeps its own rules and MCP bundle:
|
|
603
751
|
|
|
604
752
|
```bash
|
|
605
753
|
# Set up nested .ruler directories
|
|
@@ -609,12 +757,25 @@ mkdir -p src/.ruler tests/.ruler docs/.ruler
|
|
|
609
757
|
echo "# API Design Guidelines" > src/.ruler/api_rules.md
|
|
610
758
|
echo "# Testing Best Practices" > tests/.ruler/test_rules.md
|
|
611
759
|
echo "# Documentation Standards" > docs/.ruler/docs_rules.md
|
|
760
|
+
```
|
|
761
|
+
|
|
762
|
+
```toml
|
|
763
|
+
# .ruler/ruler.toml
|
|
764
|
+
nested = true
|
|
765
|
+
```
|
|
612
766
|
|
|
613
|
-
|
|
614
|
-
|
|
767
|
+
```bash
|
|
768
|
+
# The CLI inherits nested mode from ruler.toml
|
|
769
|
+
ruler apply --verbose
|
|
770
|
+
|
|
771
|
+
# Override from the CLI at any time
|
|
772
|
+
ruler apply --no-nested
|
|
615
773
|
```
|
|
616
774
|
|
|
617
|
-
This creates context-specific instructions for different parts of your project while maintaining global rules in the root `.ruler/` directory.
|
|
775
|
+
This creates context-specific instructions for different parts of your project while maintaining global rules in the root `.ruler/` directory. Nested runs automatically keep every nested config enabled even if a child tries to disable it.
|
|
776
|
+
|
|
777
|
+
> [!NOTE]
|
|
778
|
+
> The CLI prints "Nested mode is experimental and may change in future releases." the first time nested processing runs. Expect refinements in future versions.
|
|
618
779
|
|
|
619
780
|
### Scenario 3: Team Standardization
|
|
620
781
|
|
|
@@ -719,7 +880,7 @@ This shows:
|
|
|
719
880
|
A: Currently, all agents receive the same concatenated rules. For agent-specific instructions, include sections in your rule files like "## GitHub Copilot Specific" or "## Aider Configuration".
|
|
720
881
|
|
|
721
882
|
**Q: How do I set up different instructions for different parts of my project?**
|
|
722
|
-
A:
|
|
883
|
+
A: Enable nested mode either by setting `nested = true` in `ruler.toml` or by passing `ruler apply --nested`. The CLI inherits the config setting by default, but `--no-nested` always wins if you need to opt out for a run. Nested mode keeps loading rules (and MCP settings) from every `.ruler/` directory in the hierarchy, forces child configs to remain nested, and logs "Nested mode is experimental and may change in future releases." if any nested processing occurs.
|
|
723
884
|
|
|
724
885
|
**Q: How do I temporarily disable Ruler for an agent?**
|
|
725
886
|
A: Set `enabled = false` in `ruler.toml` under `[agents.agentname]`, or use `--agents` flag to specify only the agents you want.
|
|
@@ -49,8 +49,7 @@ class AbstractAgent {
|
|
|
49
49
|
* 3. Backing up the existing file
|
|
50
50
|
* 4. Writing the new content
|
|
51
51
|
*/
|
|
52
|
-
async applyRulerConfig(concatenatedRules, projectRoot,
|
|
53
|
-
agentConfig, backup = true) {
|
|
52
|
+
async applyRulerConfig(concatenatedRules, projectRoot, _rulerMcpJson, agentConfig, backup = true) {
|
|
54
53
|
const output = agentConfig?.outputPath ?? this.getDefaultOutputPath(projectRoot);
|
|
55
54
|
const absolutePath = path.resolve(projectRoot, output);
|
|
56
55
|
await (0, FileSystemUtils_1.ensureDirExists)(path.dirname(absolutePath));
|
|
@@ -80,5 +79,12 @@ class AbstractAgent {
|
|
|
80
79
|
supportsMcpRemote() {
|
|
81
80
|
return false;
|
|
82
81
|
}
|
|
82
|
+
/**
|
|
83
|
+
* Returns whether this agent has native skills support.
|
|
84
|
+
* Defaults to false if not overridden.
|
|
85
|
+
*/
|
|
86
|
+
supportsNativeSkills() {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
83
89
|
}
|
|
84
90
|
exports.AbstractAgent = AbstractAgent;
|
|
@@ -53,8 +53,7 @@ class AgentsMdAgent extends AbstractAgent_1.AbstractAgent {
|
|
|
53
53
|
getDefaultOutputPath(projectRoot) {
|
|
54
54
|
return path.join(projectRoot, 'AGENTS.md');
|
|
55
55
|
}
|
|
56
|
-
async applyRulerConfig(concatenatedRules, projectRoot,
|
|
57
|
-
agentConfig, backup = true) {
|
|
56
|
+
async applyRulerConfig(concatenatedRules, projectRoot, _rulerMcpJson, agentConfig, backup = true) {
|
|
58
57
|
const output = agentConfig?.outputPath ?? this.getDefaultOutputPath(projectRoot);
|
|
59
58
|
const absolutePath = path.resolve(projectRoot, output);
|
|
60
59
|
await (0, FileSystemUtils_1.ensureDirExists)(path.dirname(absolutePath));
|
|
@@ -47,8 +47,7 @@ class AugmentCodeAgent {
|
|
|
47
47
|
getName() {
|
|
48
48
|
return 'AugmentCode';
|
|
49
49
|
}
|
|
50
|
-
async applyRulerConfig(concatenatedRules, projectRoot,
|
|
51
|
-
agentConfig, backup = true) {
|
|
50
|
+
async applyRulerConfig(concatenatedRules, projectRoot, _rulerMcpJson, agentConfig, backup = true) {
|
|
52
51
|
const output = agentConfig?.outputPath ?? this.getDefaultOutputPath(projectRoot);
|
|
53
52
|
if (backup) {
|
|
54
53
|
await (0, FileSystemUtils_1.backupFile)(output);
|
|
@@ -47,8 +47,7 @@ class CursorAgent extends AbstractAgent_1.AbstractAgent {
|
|
|
47
47
|
getName() {
|
|
48
48
|
return 'Cursor';
|
|
49
49
|
}
|
|
50
|
-
async applyRulerConfig(concatenatedRules, projectRoot,
|
|
51
|
-
agentConfig, backup = true) {
|
|
50
|
+
async applyRulerConfig(concatenatedRules, projectRoot, _rulerMcpJson, agentConfig, backup = true) {
|
|
52
51
|
const output = agentConfig?.outputPath ?? this.getDefaultOutputPath(projectRoot);
|
|
53
52
|
const absolutePath = path.resolve(projectRoot, output);
|
|
54
53
|
// Cursor expects a YAML front-matter block with an `alwaysApply` flag.
|
|
@@ -44,8 +44,7 @@ class QwenCodeAgent extends AgentsMdAgent_1.AgentsMdAgent {
|
|
|
44
44
|
getName() {
|
|
45
45
|
return 'Qwen Code';
|
|
46
46
|
}
|
|
47
|
-
async applyRulerConfig(concatenatedRules, projectRoot,
|
|
48
|
-
agentConfig) {
|
|
47
|
+
async applyRulerConfig(concatenatedRules, projectRoot, _rulerMcpJson, agentConfig) {
|
|
49
48
|
// First, perform idempotent write of AGENTS.md via base class
|
|
50
49
|
await super.applyRulerConfig(concatenatedRules, projectRoot, null, {
|
|
51
50
|
outputPath: agentConfig?.outputPath,
|
|
@@ -1,88 +1,21 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
3
|
exports.WindsurfAgent = void 0;
|
|
37
|
-
const
|
|
38
|
-
const AbstractAgent_1 = require("./AbstractAgent");
|
|
39
|
-
const FileSystemUtils_1 = require("../core/FileSystemUtils");
|
|
4
|
+
const AgentsMdAgent_1 = require("./AgentsMdAgent");
|
|
40
5
|
/**
|
|
41
6
|
* Windsurf agent adapter.
|
|
7
|
+
* Now uses AGENTS.md format like other agents.
|
|
42
8
|
*/
|
|
43
|
-
class WindsurfAgent extends
|
|
9
|
+
class WindsurfAgent extends AgentsMdAgent_1.AgentsMdAgent {
|
|
44
10
|
getIdentifier() {
|
|
45
11
|
return 'windsurf';
|
|
46
12
|
}
|
|
47
13
|
getName() {
|
|
48
14
|
return 'Windsurf';
|
|
49
15
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const absolutePath = path.resolve(projectRoot, output);
|
|
54
|
-
// Windsurf expects a YAML front-matter block with a `trigger` flag.
|
|
55
|
-
const frontMatter = ['---', 'trigger: always_on', '---', ''].join('\n');
|
|
56
|
-
const content = `${frontMatter}${concatenatedRules.trimStart()}`;
|
|
57
|
-
const maxFileSize = 10000; // 10K characters
|
|
58
|
-
await (0, FileSystemUtils_1.ensureDirExists)(path.dirname(absolutePath));
|
|
59
|
-
if (backup) {
|
|
60
|
-
await (0, FileSystemUtils_1.backupFile)(absolutePath);
|
|
61
|
-
}
|
|
62
|
-
// Check if content exceeds the 10K limit
|
|
63
|
-
if (content.length <= maxFileSize) {
|
|
64
|
-
// Content fits in single file - use original behavior
|
|
65
|
-
await (0, FileSystemUtils_1.writeGeneratedFile)(absolutePath, content);
|
|
66
|
-
}
|
|
67
|
-
else {
|
|
68
|
-
// Content exceeds limit - split into multiple files
|
|
69
|
-
console.warn(`[ruler] Warning: Windsurf rule content exceeds ${maxFileSize} characters (${content.length}). Splitting into multiple files.`);
|
|
70
|
-
const files = this.splitContentIntoFiles(concatenatedRules.trimStart(), frontMatter, maxFileSize);
|
|
71
|
-
// Write each split file
|
|
72
|
-
const rulesDir = path.dirname(absolutePath);
|
|
73
|
-
const baseName = path.basename(absolutePath, '.md');
|
|
74
|
-
for (let i = 0; i < files.length; i++) {
|
|
75
|
-
const fileName = `${baseName}_${i.toString().padStart(2, '0')}.md`;
|
|
76
|
-
const filePath = path.join(rulesDir, fileName);
|
|
77
|
-
if (backup) {
|
|
78
|
-
await (0, FileSystemUtils_1.backupFile)(filePath);
|
|
79
|
-
}
|
|
80
|
-
await (0, FileSystemUtils_1.writeGeneratedFile)(filePath, files[i]);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
getDefaultOutputPath(projectRoot) {
|
|
85
|
-
return path.join(projectRoot, '.windsurf', 'rules', 'ruler_windsurf_instructions.md');
|
|
16
|
+
// Windsurf supports MCP configuration
|
|
17
|
+
getMcpServerKey() {
|
|
18
|
+
return 'mcpServers';
|
|
86
19
|
}
|
|
87
20
|
supportsMcpStdio() {
|
|
88
21
|
return true;
|
|
@@ -90,69 +23,5 @@ class WindsurfAgent extends AbstractAgent_1.AbstractAgent {
|
|
|
90
23
|
supportsMcpRemote() {
|
|
91
24
|
return true;
|
|
92
25
|
}
|
|
93
|
-
/**
|
|
94
|
-
* Gets all actual output paths that will be created, including split files.
|
|
95
|
-
* This allows the gitignore system to know about split files before they're created.
|
|
96
|
-
*/
|
|
97
|
-
getActualOutputPaths(concatenatedRules, projectRoot, agentConfig) {
|
|
98
|
-
const output = agentConfig?.outputPath ?? this.getDefaultOutputPath(projectRoot);
|
|
99
|
-
const absolutePath = path.resolve(projectRoot, output);
|
|
100
|
-
// Windsurf expects a YAML front-matter block with a `trigger` flag.
|
|
101
|
-
const frontMatter = ['---', 'trigger: always_on', '---', ''].join('\n');
|
|
102
|
-
const content = `${frontMatter}${concatenatedRules.trimStart()}`;
|
|
103
|
-
const maxFileSize = 10000; // 10K characters
|
|
104
|
-
// Check if content will be split
|
|
105
|
-
if (content.length <= maxFileSize) {
|
|
106
|
-
// Content fits in single file
|
|
107
|
-
return [absolutePath];
|
|
108
|
-
}
|
|
109
|
-
else {
|
|
110
|
-
// Content will be split - calculate how many files will be created
|
|
111
|
-
const files = this.splitContentIntoFiles(concatenatedRules.trimStart(), frontMatter, maxFileSize);
|
|
112
|
-
const rulesDir = path.dirname(absolutePath);
|
|
113
|
-
const baseName = path.basename(absolutePath, '.md');
|
|
114
|
-
const splitPaths = [];
|
|
115
|
-
for (let i = 0; i < files.length; i++) {
|
|
116
|
-
const fileName = `${baseName}_${i.toString().padStart(2, '0')}.md`;
|
|
117
|
-
const filePath = path.join(rulesDir, fileName);
|
|
118
|
-
splitPaths.push(filePath);
|
|
119
|
-
}
|
|
120
|
-
return splitPaths;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
/**
|
|
124
|
-
* Splits content into multiple files, each under the specified size limit.
|
|
125
|
-
* Splits at the closest newline within the limit.
|
|
126
|
-
* Each file gets its own front-matter.
|
|
127
|
-
*/
|
|
128
|
-
splitContentIntoFiles(rules, frontMatter, maxFileSize) {
|
|
129
|
-
const files = [];
|
|
130
|
-
const availableSpace = maxFileSize - frontMatter.length;
|
|
131
|
-
let remainingRules = rules;
|
|
132
|
-
while (remainingRules.length > 0) {
|
|
133
|
-
if (remainingRules.length <= availableSpace) {
|
|
134
|
-
// Remaining content fits in one file
|
|
135
|
-
files.push(`${frontMatter}${remainingRules}`);
|
|
136
|
-
break;
|
|
137
|
-
}
|
|
138
|
-
// Find the last newline within the available space
|
|
139
|
-
let splitIndex = availableSpace;
|
|
140
|
-
const searchSpace = remainingRules.substring(0, availableSpace);
|
|
141
|
-
const lastNewline = searchSpace.lastIndexOf('\n');
|
|
142
|
-
if (lastNewline > 0) {
|
|
143
|
-
// Split at the newline (include the newline in the current file)
|
|
144
|
-
splitIndex = lastNewline + 1;
|
|
145
|
-
}
|
|
146
|
-
else {
|
|
147
|
-
// No newline found within limit - split at the limit
|
|
148
|
-
// This shouldn't happen often but we handle it gracefully
|
|
149
|
-
splitIndex = availableSpace;
|
|
150
|
-
}
|
|
151
|
-
const chunk = remainingRules.substring(0, splitIndex);
|
|
152
|
-
files.push(`${frontMatter}${chunk}`);
|
|
153
|
-
remainingRules = remainingRules.substring(splitIndex);
|
|
154
|
-
}
|
|
155
|
-
return files;
|
|
156
|
-
}
|
|
157
26
|
}
|
|
158
27
|
exports.WindsurfAgent = WindsurfAgent;
|
package/dist/cli/commands.js
CHANGED
|
@@ -63,13 +63,16 @@ function run() {
|
|
|
63
63
|
})
|
|
64
64
|
.option('nested', {
|
|
65
65
|
type: 'boolean',
|
|
66
|
-
description: 'Enable nested rule loading from nested .ruler directories (default: disabled)',
|
|
67
|
-
default: false,
|
|
66
|
+
description: 'Enable nested rule loading from nested .ruler directories (default: from config or disabled)',
|
|
68
67
|
})
|
|
69
68
|
.option('backup', {
|
|
70
69
|
type: 'boolean',
|
|
71
70
|
description: 'Enable/disable creation of .bak backup files (default: enabled)',
|
|
72
71
|
default: true,
|
|
72
|
+
})
|
|
73
|
+
.option('skills', {
|
|
74
|
+
type: 'boolean',
|
|
75
|
+
description: 'Enable/disable skills support (experimental, default: enabled)',
|
|
73
76
|
});
|
|
74
77
|
}, handlers_1.applyHandler)
|
|
75
78
|
.command('init', 'Scaffold a .ruler directory with default files', (y) => {
|
package/dist/cli/handlers.js
CHANGED
|
@@ -42,6 +42,7 @@ const path = __importStar(require("path"));
|
|
|
42
42
|
const os = __importStar(require("os"));
|
|
43
43
|
const fs = __importStar(require("fs/promises"));
|
|
44
44
|
const constants_1 = require("../constants");
|
|
45
|
+
const ConfigLoader_1 = require("../core/ConfigLoader");
|
|
45
46
|
/**
|
|
46
47
|
* Handler for the 'apply' command.
|
|
47
48
|
*/
|
|
@@ -58,7 +59,6 @@ async function applyHandler(argv) {
|
|
|
58
59
|
const verbose = argv.verbose;
|
|
59
60
|
const dryRun = argv['dry-run'];
|
|
60
61
|
const localOnly = argv['local-only'];
|
|
61
|
-
const nested = argv.nested;
|
|
62
62
|
const backup = argv.backup;
|
|
63
63
|
// Determine gitignore preference: CLI > TOML > Default (enabled)
|
|
64
64
|
// yargs handles --no-gitignore by setting gitignore to false
|
|
@@ -69,8 +69,37 @@ async function applyHandler(argv) {
|
|
|
69
69
|
else {
|
|
70
70
|
gitignorePreference = undefined; // Let TOML/default decide
|
|
71
71
|
}
|
|
72
|
+
// Determine nested preference: CLI > TOML > Default (false)
|
|
73
|
+
let nested;
|
|
74
|
+
if (argv.nested !== undefined) {
|
|
75
|
+
// CLI explicitly set nested (either --nested or --no-nested)
|
|
76
|
+
nested = argv.nested;
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
// CLI didn't set nested, check TOML configuration
|
|
80
|
+
try {
|
|
81
|
+
const config = await (0, ConfigLoader_1.loadConfig)({
|
|
82
|
+
projectRoot,
|
|
83
|
+
configPath,
|
|
84
|
+
});
|
|
85
|
+
// Use TOML setting if available, otherwise default to false
|
|
86
|
+
nested = config.nested ?? false;
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
// If config loading fails, use default (false)
|
|
90
|
+
nested = false;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
// Determine skills preference: CLI > TOML > Default (enabled)
|
|
94
|
+
let skillsEnabled;
|
|
95
|
+
if (argv.skills !== undefined) {
|
|
96
|
+
skillsEnabled = argv.skills;
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
skillsEnabled = undefined; // Let config/default decide
|
|
100
|
+
}
|
|
72
101
|
try {
|
|
73
|
-
await (0, lib_1.applyAllAgentConfigs)(projectRoot, agents, configPath, mcpEnabled, mcpStrategy, gitignorePreference, verbose, dryRun, localOnly, nested, backup);
|
|
102
|
+
await (0, lib_1.applyAllAgentConfigs)(projectRoot, agents, configPath, mcpEnabled, mcpStrategy, gitignorePreference, verbose, dryRun, localOnly, nested, backup, skillsEnabled);
|
|
74
103
|
console.log('Ruler apply completed successfully.');
|
|
75
104
|
}
|
|
76
105
|
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.DEFAULT_RULES_FILENAME = exports.ERROR_PREFIX = void 0;
|
|
3
|
+
exports.SKILLZ_MCP_SERVER_NAME = exports.SKILL_MD_FILENAME = exports.SKILLZ_DIR = 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;
|
|
@@ -49,3 +49,10 @@ function logVerboseInfo(message, isVerbose, dryRun = false) {
|
|
|
49
49
|
console.log(`${prefix} ${message}`);
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
|
+
// Skills-related constants
|
|
53
|
+
exports.SKILLS_DIR = 'skills';
|
|
54
|
+
exports.RULER_SKILLS_PATH = '.ruler/skills';
|
|
55
|
+
exports.CLAUDE_SKILLS_PATH = '.claude/skills';
|
|
56
|
+
exports.SKILLZ_DIR = '.skillz';
|
|
57
|
+
exports.SKILL_MD_FILENAME = 'SKILL.md';
|
|
58
|
+
exports.SKILLZ_MCP_SERVER_NAME = 'skillz';
|