@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 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/rules/ruler_windsurf_instructions.md` | - |
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
- - `.ruler/` directory
109
- - `.ruler/AGENTS.md`: The primary starter Markdown file for your rules
110
- - `.ruler/ruler.toml`: The main configuration file for Ruler (now contains sample MCP server sections; legacy `.ruler/mcp.json` no longer scaffolded)
111
- - (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.)
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
- - Enable with: `ruler apply --nested`
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>` | Path to your project's root (default: current directory) |
218
- | `--agents <agent1,agent2,...>` | Comma-separated list of agent names to target (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) |
219
- | `--config <path>` | Path to a custom `ruler.toml` configuration file |
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 entirely instead of merging |
223
- | `--gitignore` | Enable automatic .gitignore updates (default: true) |
224
- | `--no-gitignore` | Disable automatic .gitignore updates |
225
- | `--nested` | Enable nested rule loading from nested .ruler directories (default: disabled) |
226
- | `--backup` | Enable/disable creation of .bak backup files (default: enabled) |
227
- | `--dry-run` | Preview changes without writing files |
228
- | `--local-only` | Do not look for configuration in `$XDG_CONFIG_HOME` |
229
- | `--verbose` / `-v` | Display detailed output during execution |
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, use nested rule loading:
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
- # Apply with nested loading
614
- ruler apply --nested --verbose
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: Use the `--nested` flag with `ruler apply --nested`. This enables Ruler to discover and load rules from multiple `.ruler/` directories throughout your project hierarchy. Place component-specific instructions in `src/.ruler/`, test-specific rules in `tests/.ruler/`, etc., while keeping global rules in the root `.ruler/` directory.
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, rulerMcpJson, // eslint-disable-line @typescript-eslint/no-unused-vars
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, rulerMcpJson, // eslint-disable-line @typescript-eslint/no-unused-vars
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, rulerMcpJson, // eslint-disable-line @typescript-eslint/no-unused-vars
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);
@@ -55,5 +55,8 @@ class ClaudeAgent extends AbstractAgent_1.AbstractAgent {
55
55
  supportsMcpRemote() {
56
56
  return true;
57
57
  }
58
+ supportsNativeSkills() {
59
+ return true;
60
+ }
58
61
  }
59
62
  exports.ClaudeAgent = ClaudeAgent;
@@ -47,8 +47,7 @@ class CursorAgent extends AbstractAgent_1.AbstractAgent {
47
47
  getName() {
48
48
  return 'Cursor';
49
49
  }
50
- async applyRulerConfig(concatenatedRules, projectRoot, rulerMcpJson, // eslint-disable-line @typescript-eslint/no-unused-vars
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, rulerMcpJson, // eslint-disable-line @typescript-eslint/no-unused-vars
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 path = __importStar(require("path"));
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 AbstractAgent_1.AbstractAgent {
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
- async applyRulerConfig(concatenatedRules, projectRoot, rulerMcpJson, // eslint-disable-line @typescript-eslint/no-unused-vars
51
- agentConfig, backup = true) {
52
- const output = agentConfig?.outputPath ?? this.getDefaultOutputPath(projectRoot);
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;
@@ -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) => {
@@ -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';