@intellectronica/ruler 0.3.9 → 0.3.11

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
@@ -54,16 +54,16 @@ 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 |
58
- | ---------------- | ------------------------------------------------ | ------------------------------------------------ |
59
- | AGENTS.md | `AGENTS.md` | (pseudo-agent ensuring root `AGENTS.md` exists) |
60
- | GitHub Copilot | `AGENTS.md` | `.vscode/mcp.json` |
61
- | Claude Code | `CLAUDE.md` | `.mcp.json` |
62
- | OpenAI Codex CLI | `AGENTS.md` | `.codex/config.toml` |
63
- | Jules | `AGENTS.md` | - |
64
- | Cursor | `.cursor/rules/ruler_cursor_instructions.mdc` | `.cursor/mcp.json` |
65
- | Windsurf | `.windsurf/rules/ruler_windsurf_instructions.md` | - |
66
- | Cline | `.clinerules` | - |
57
+ | Agent | Rules File(s) | MCP Configuration / Notes |
58
+ | ---------------- | --------------------------------------------- | ------------------------------------------------ |
59
+ | AGENTS.md | `AGENTS.md` | (pseudo-agent ensuring root `AGENTS.md` exists) |
60
+ | GitHub Copilot | `AGENTS.md` | `.vscode/mcp.json` |
61
+ | Claude Code | `CLAUDE.md` | `.mcp.json` |
62
+ | OpenAI Codex CLI | `AGENTS.md` | `.codex/config.toml` |
63
+ | Jules | `AGENTS.md` | - |
64
+ | Cursor | `.cursor/rules/ruler_cursor_instructions.mdc` | `.cursor/mcp.json` |
65
+ | Windsurf | `AGENTS.md` | `.windsurf/mcp_config.json` |
66
+ | Cline | `.clinerules` | - |
67
67
  | Crush | `CRUSH.md` | `.crush.json` |
68
68
  | Amp | `AGENTS.md` | - |
69
69
  | Amazon Q CLI | `.amazonq/rules/ruler_q_rules.md` | `.amazonq/mcp.json` |
@@ -82,6 +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
86
 
86
87
  ## Getting Started
87
88
 
@@ -104,10 +105,11 @@ npx @intellectronica/ruler apply
104
105
  1. Navigate to your project's root directory
105
106
  2. Run `ruler init`
106
107
  3. This creates:
107
- - `.ruler/` directory
108
- - `.ruler/AGENTS.md`: The primary starter Markdown file for your rules
109
- - `.ruler/ruler.toml`: The main configuration file for Ruler (now contains sample MCP server sections; legacy `.ruler/mcp.json` no longer scaffolded)
110
- - (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.)
111
113
 
112
114
  Additionally, you can create a global configuration to use when no local `.ruler/` directory is found:
113
115
 
@@ -159,7 +161,15 @@ project/
159
161
 
160
162
  - Discover all `.ruler/` directories in the project hierarchy
161
163
  - Load and concatenate rules from each directory in order
162
- - 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.
163
173
 
164
174
  **Perfect for:**
165
175
 
@@ -211,21 +221,22 @@ The `apply` command looks for `.ruler/` in the current directory tree, reading t
211
221
 
212
222
  ### Options
213
223
 
214
- | Option | Description |
215
- | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
216
- | `--project-root <path>` | Path to your project's root (default: current directory) |
217
- | `--agents <agent1,agent2,...>` | Comma-separated list of agent names to target (agentsmd, aider, amazonqcli, amp, augmentcode, claude, cline, codex, copilot, crush, cursor, firebase, gemini-cli, goose, jules, junie, kilocode, kiro, opencode, openhands, qwen, roo, trae, warp, windsurf, zed) |
218
- | `--config <path>` | Path to a custom `ruler.toml` configuration file |
219
- | `--mcp` / `--with-mcp` | Enable applying MCP server configurations (default: true) |
220
- | `--no-mcp` | Disable applying MCP server configurations |
221
- | `--mcp-overwrite` | Overwrite native MCP config entirely instead of merging |
222
- | `--gitignore` | Enable automatic .gitignore updates (default: true) |
223
- | `--no-gitignore` | Disable automatic .gitignore updates |
224
- | `--nested` | Enable nested rule loading from nested .ruler directories (default: disabled) |
225
- | `--backup` | Enable/disable creation of .bak backup files (default: enabled) |
226
- | `--dry-run` | Preview changes without writing files |
227
- | `--local-only` | Do not look for configuration in `$XDG_CONFIG_HOME` |
228
- | `--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. |
229
240
 
230
241
  ### Common Examples
231
242
 
@@ -304,15 +315,15 @@ ruler revert [options]
304
315
 
305
316
  ### Options
306
317
 
307
- | Option | Description |
308
- | ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
309
- | `--project-root <path>` | Path to your project's root (default: current directory) |
310
- | `--agents <agent1,agent2,...>` | Comma-separated list of agent names to revert (agentsmd, aider, amazonqcli, amp, augmentcode, claude, cline, codex, copilot, crush, cursor, firebase, gemini-cli, goose, jules, junie, kilocode, kiro, opencode, openhands, qwen, roo, trae, warp, windsurf, zed) |
311
- | `--config <path>` | Path to a custom `ruler.toml` configuration file |
312
- | `--keep-backups` | Keep backup files (.bak) after restoration (default: false) |
313
- | `--dry-run` | Preview changes without actually reverting files |
314
- | `--verbose` / `-v` | Display detailed output during execution |
315
- | `--local-only` | Only search for local .ruler directories, ignore global config |
318
+ | Option | Description |
319
+ | ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
320
+ | `--project-root <path>` | Path to your project's root (default: current directory) |
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) |
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 |
316
327
 
317
328
  ### Common Examples
318
329
 
@@ -524,6 +535,7 @@ DEBUG = "1"
524
535
  ```
525
536
 
526
537
  **Remote servers** require a `url` field (headers optional; bearer Authorization token auto-extracted for OpenHands when possible):
538
+
527
539
  ```toml
528
540
  [mcp_servers.remote_server]
529
541
  url = "https://api.example.com"
@@ -542,6 +554,135 @@ Ruler uses this configuration with the `merge` (default) or `overwrite` strategy
542
554
  export CODEX_HOME="$(pwd)/.codex"
543
555
  ```
544
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
+ - `SKILL.md` - Primary skill file with instructions or knowledge base
586
+
587
+ Skills can optionally include additional resources like:
588
+ - Markdown files with supplementary documentation
589
+ - Python, JavaScript, or other scripts
590
+ - Configuration files or data
591
+
592
+ ### Configuration
593
+
594
+ Skills support is **enabled by default** but can be controlled via:
595
+
596
+ **CLI flags:**
597
+ ```bash
598
+ # Enable skills (default)
599
+ ruler apply --skills
600
+
601
+ # Disable skills
602
+ ruler apply --no-skills
603
+ ```
604
+
605
+ **Configuration in `ruler.toml`:**
606
+ ```toml
607
+ [skills]
608
+ enabled = true # or false to disable
609
+ ```
610
+
611
+ ### Skillz MCP Server
612
+
613
+ For agents that support MCP but don't have native skills support (all agents except Claude Code), Ruler automatically:
614
+
615
+ 1. Copies skills to `.skillz/` directory
616
+ 2. Configures a Skillz MCP server in the agent's configuration
617
+ 3. Uses `uvx` to launch the server with the absolute path to `.skillz`
618
+
619
+ Example auto-generated MCP server configuration:
620
+ ```toml
621
+ [mcp_servers.skillz]
622
+ command = "uvx"
623
+ args = ["skillz@latest", "/absolute/path/to/project/.skillz"]
624
+ ```
625
+
626
+ ### `.gitignore` Integration
627
+
628
+ When skills support is enabled and gitignore integration is active, Ruler automatically adds:
629
+ - `.claude/skills/` (for Claude Code agents)
630
+ - `.skillz/` (for MCP-based agents)
631
+
632
+ to your `.gitignore` file within the managed Ruler block.
633
+
634
+ ### Requirements
635
+
636
+ - **For Claude Code**: No additional requirements
637
+ - **For MCP agents**: `uv` must be installed and available in your PATH
638
+ ```bash
639
+ # Install uv if needed
640
+ curl -LsSf https://astral.sh/uv/install.sh | sh
641
+ ```
642
+
643
+ ### Validation
644
+
645
+ Ruler validates discovered skills and issues warnings for:
646
+ - Missing required file (`SKILL.md`)
647
+ - Invalid directory structures (directories without `SKILL.md` and no sub-skills)
648
+
649
+ Warnings don't prevent propagation but help identify potential issues.
650
+
651
+ ### Dry-Run Mode
652
+
653
+ Test skills propagation without making changes:
654
+ ```bash
655
+ ruler apply --dry-run
656
+ ```
657
+
658
+ This shows which skills would be copied and which MCP servers would be configured.
659
+
660
+ ### Example Workflow
661
+
662
+ ```bash
663
+ # 1. Add a skill to your project
664
+ mkdir -p .ruler/skills/my-skill
665
+ cat > .ruler/skills/my-skill/SKILL.md << 'EOF'
666
+ # My Custom Skill
667
+
668
+ This skill provides specialized knowledge for...
669
+
670
+ ## Usage
671
+
672
+ When working on this project, always follow these guidelines:
673
+ - Use TypeScript for all new code
674
+ - Write tests for all features
675
+ - Follow the existing code style
676
+ EOF
677
+
678
+ # 2. Apply to all agents (skills enabled by default)
679
+ ruler apply
680
+
681
+ # 3. Skills are now available to compatible agents:
682
+ # - Claude Code: .claude/skills/my-skill/
683
+ # - Other MCP agents: .skillz/my-skill/ + Skillz MCP server configured
684
+ ```
685
+
545
686
  ## `.gitignore` Integration
546
687
 
547
688
  Ruler automatically manages your `.gitignore` file to keep generated agent configuration files out of version control.
@@ -598,7 +739,7 @@ ruler apply
598
739
 
599
740
  ### Scenario 2: Complex Projects with Nested Rules
600
741
 
601
- For large projects with multiple components or services, use nested rule loading:
742
+ For large projects with multiple components or services, enable nested rule loading so each directory keeps its own rules and MCP bundle:
602
743
 
603
744
  ```bash
604
745
  # Set up nested .ruler directories
@@ -608,12 +749,25 @@ mkdir -p src/.ruler tests/.ruler docs/.ruler
608
749
  echo "# API Design Guidelines" > src/.ruler/api_rules.md
609
750
  echo "# Testing Best Practices" > tests/.ruler/test_rules.md
610
751
  echo "# Documentation Standards" > docs/.ruler/docs_rules.md
752
+ ```
611
753
 
612
- # Apply with nested loading
613
- ruler apply --nested --verbose
754
+ ```toml
755
+ # .ruler/ruler.toml
756
+ nested = true
614
757
  ```
615
758
 
616
- This creates context-specific instructions for different parts of your project while maintaining global rules in the root `.ruler/` directory.
759
+ ```bash
760
+ # The CLI inherits nested mode from ruler.toml
761
+ ruler apply --verbose
762
+
763
+ # Override from the CLI at any time
764
+ ruler apply --no-nested
765
+ ```
766
+
767
+ 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.
768
+
769
+ > [!NOTE]
770
+ > The CLI prints "Nested mode is experimental and may change in future releases." the first time nested processing runs. Expect refinements in future versions.
617
771
 
618
772
  ### Scenario 3: Team Standardization
619
773
 
@@ -718,7 +872,7 @@ This shows:
718
872
  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".
719
873
 
720
874
  **Q: How do I set up different instructions for different parts of my project?**
721
- 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.
875
+ 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.
722
876
 
723
877
  **Q: How do I temporarily disable Ruler for an agent?**
724
878
  A: Set `enabled = false` in `ruler.toml` under `[agents.agentname]`, or use `--agents` flag to specify only the agents you want.
@@ -80,5 +80,12 @@ class AbstractAgent {
80
80
  supportsMcpRemote() {
81
81
  return false;
82
82
  }
83
+ /**
84
+ * Returns whether this agent has native skills support.
85
+ * Defaults to false if not overridden.
86
+ */
87
+ supportsNativeSkills() {
88
+ return false;
89
+ }
83
90
  }
84
91
  exports.AbstractAgent = AbstractAgent;
@@ -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;
@@ -0,0 +1,205 @@
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
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.FirebenderAgent = void 0;
37
+ const path = __importStar(require("path"));
38
+ const fs = __importStar(require("fs"));
39
+ const FileSystemUtils_1 = require("../core/FileSystemUtils");
40
+ /**
41
+ * Firebender agent adapter.
42
+ */
43
+ class FirebenderAgent {
44
+ /**
45
+ * Type guard function to safely check if an object is a FirebenderRule.
46
+ */
47
+ isFirebenderRule(rule) {
48
+ return (typeof rule === 'object' &&
49
+ rule !== null &&
50
+ 'filePathMatches' in rule &&
51
+ 'rulesPaths' in rule &&
52
+ typeof rule.filePathMatches === 'string' &&
53
+ typeof rule.rulesPaths === 'string');
54
+ }
55
+ getIdentifier() {
56
+ return 'firebender';
57
+ }
58
+ getName() {
59
+ return 'Firebender';
60
+ }
61
+ async applyRulerConfig(concatenatedRules, projectRoot, rulerMcpJson, agentConfig, backup = true) {
62
+ const rulesPath = this.resolveOutputPath(projectRoot, agentConfig);
63
+ await (0, FileSystemUtils_1.ensureDirExists)(path.dirname(rulesPath));
64
+ const firebenderConfig = await this.loadExistingConfig(rulesPath);
65
+ const newRules = this.createRulesFromConcatenatedRules(concatenatedRules, projectRoot);
66
+ firebenderConfig.rules.push(...newRules);
67
+ this.removeDuplicateRules(firebenderConfig);
68
+ const mcpEnabled = agentConfig?.mcp?.enabled ?? true;
69
+ if (mcpEnabled && rulerMcpJson) {
70
+ await this.handleMcpConfiguration(firebenderConfig, rulerMcpJson, agentConfig);
71
+ }
72
+ await this.saveConfig(rulesPath, firebenderConfig, backup);
73
+ }
74
+ resolveOutputPath(projectRoot, agentConfig) {
75
+ const outputPaths = this.getDefaultOutputPath(projectRoot);
76
+ const output = agentConfig?.outputPath ??
77
+ agentConfig?.outputPathInstructions ??
78
+ outputPaths['instructions'];
79
+ return path.resolve(projectRoot, output);
80
+ }
81
+ async loadExistingConfig(rulesPath) {
82
+ try {
83
+ const existingContent = await fs.promises.readFile(rulesPath, 'utf8');
84
+ const config = JSON.parse(existingContent);
85
+ if (!config.rules) {
86
+ config.rules = [];
87
+ }
88
+ return config;
89
+ }
90
+ catch (error) {
91
+ if (error &&
92
+ typeof error === 'object' &&
93
+ 'code' in error &&
94
+ error.code === 'ENOENT') {
95
+ return { rules: [] };
96
+ }
97
+ console.warn(`Failed to read/parse existing firebender.json: ${error}`);
98
+ return { rules: [] };
99
+ }
100
+ }
101
+ createRulesFromConcatenatedRules(concatenatedRules, projectRoot) {
102
+ const filePaths = this.extractFilePathsFromRules(concatenatedRules, projectRoot);
103
+ if (filePaths.length > 0) {
104
+ return this.createRuleObjectsFromFilePaths(filePaths);
105
+ }
106
+ else {
107
+ return this.createRulesFromPlainText(concatenatedRules);
108
+ }
109
+ }
110
+ createRuleObjectsFromFilePaths(filePaths) {
111
+ return filePaths.map((filePath) => ({
112
+ filePathMatches: '**/*',
113
+ rulesPaths: filePath,
114
+ }));
115
+ }
116
+ createRulesFromPlainText(concatenatedRules) {
117
+ return concatenatedRules.split('\n').filter((rule) => rule.trim());
118
+ }
119
+ removeDuplicateRules(firebenderConfig) {
120
+ const seen = new Set();
121
+ firebenderConfig.rules = firebenderConfig.rules.filter((rule) => {
122
+ let key;
123
+ if (this.isFirebenderRule(rule)) {
124
+ const filePathMatchesPart = rule.filePathMatches;
125
+ const rulesPathsPart = rule.rulesPaths;
126
+ key = `${filePathMatchesPart}::${rulesPathsPart}`;
127
+ }
128
+ else {
129
+ key = String(rule);
130
+ }
131
+ if (seen.has(key)) {
132
+ return false;
133
+ }
134
+ seen.add(key);
135
+ return true;
136
+ });
137
+ }
138
+ async saveConfig(rulesPath, config, backup) {
139
+ const updatedContent = JSON.stringify(config, null, 2);
140
+ if (backup) {
141
+ await (0, FileSystemUtils_1.backupFile)(rulesPath);
142
+ }
143
+ await (0, FileSystemUtils_1.writeGeneratedFile)(rulesPath, updatedContent);
144
+ }
145
+ /**
146
+ * Handle MCP server configuration for Firebender.
147
+ * Merges or overwrites MCP servers in the firebender.json configuration based on strategy.
148
+ */
149
+ async handleMcpConfiguration(firebenderConfig, rulerMcpJson, agentConfig) {
150
+ const strategy = agentConfig?.mcp?.strategy ?? 'merge';
151
+ const incomingServers = rulerMcpJson.mcpServers || {};
152
+ if (!firebenderConfig.mcpServers) {
153
+ firebenderConfig.mcpServers = {};
154
+ }
155
+ if (strategy === 'overwrite') {
156
+ firebenderConfig.mcpServers = { ...incomingServers };
157
+ }
158
+ else if (strategy === 'merge') {
159
+ const existingServers = firebenderConfig.mcpServers || {};
160
+ firebenderConfig.mcpServers = { ...existingServers, ...incomingServers };
161
+ }
162
+ }
163
+ getDefaultOutputPath(projectRoot) {
164
+ return {
165
+ instructions: path.join(projectRoot, 'firebender.json'),
166
+ mcp: path.join(projectRoot, 'firebender.json'),
167
+ };
168
+ }
169
+ getMcpServerKey() {
170
+ return 'mcpServers';
171
+ }
172
+ supportsMcpStdio() {
173
+ return true;
174
+ }
175
+ supportsMcpRemote() {
176
+ return true;
177
+ }
178
+ /**
179
+ * Extracts file paths from concatenated rules by parsing HTML source comments.
180
+ * @param concatenatedRules The concatenated rules string with HTML comments
181
+ * @param projectRoot The project root directory
182
+ * @returns Array of file paths relative to project root
183
+ */
184
+ extractFilePathsFromRules(concatenatedRules, projectRoot) {
185
+ const sourceCommentRegex = /<!-- Source: (.+?) -->/g;
186
+ const filePaths = [];
187
+ let match;
188
+ while ((match = sourceCommentRegex.exec(concatenatedRules)) !== null) {
189
+ const relativePath = match[1];
190
+ const absolutePath = path.resolve(projectRoot, relativePath);
191
+ const normalizedProjectRoot = path.resolve(projectRoot);
192
+ // Ensure the absolutePath is within the project root (cross-platform compatible)
193
+ // This prevents path traversal attacks while handling Windows/Unix path differences
194
+ const isWithinProject = absolutePath.startsWith(normalizedProjectRoot) &&
195
+ (absolutePath.length === normalizedProjectRoot.length ||
196
+ absolutePath[normalizedProjectRoot.length] === path.sep);
197
+ if (isWithinProject) {
198
+ const projectRelativePath = path.relative(projectRoot, absolutePath);
199
+ filePaths.push(projectRelativePath);
200
+ }
201
+ }
202
+ return filePaths;
203
+ }
204
+ }
205
+ exports.FirebenderAgent = FirebenderAgent;