@intellectronica/ruler 0.3.25 → 0.3.27

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,38 +54,38 @@ 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
- | Pi Coding Agent | `AGENTS.md` | - |
64
- | Jules | `AGENTS.md` | - |
65
- | Cursor | `AGENTS.md` | `.cursor/mcp.json` |
66
- | Windsurf | `AGENTS.md` | `.windsurf/mcp_config.json` |
67
- | Cline | `.clinerules` | - |
68
- | Crush | `CRUSH.md` | `.crush.json` |
69
- | Amp | `AGENTS.md` | - |
70
- | Antigravity | `.agent/rules/ruler.md` | - |
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` |
76
- | Junie | `.junie/guidelines.md` | - |
77
- | AugmentCode | `.augment/rules/ruler_augment_instructions.md` | - |
78
- | Kilo Code | `.kilocode/rules/ruler_kilocode_instructions.md` | `.kilocode/mcp.json` |
79
- | OpenCode | `AGENTS.md` | `opencode.json` |
80
- | Goose | `.goosehints` | - |
81
- | Qwen Code | `AGENTS.md` | `.qwen/settings.json` |
82
- | RooCode | `AGENTS.md` | `.roo/mcp.json` |
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
- | Mistral Vibe | `AGENTS.md` | `.vibe/config.toml` |
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` | - |
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` | - | - |
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/skill/` |
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
+ | Mistral Vibe | `AGENTS.md` | `.vibe/config.toml` | `.vibe/skills/` |
89
89
 
90
90
  ## Getting Started
91
91
 
@@ -318,15 +318,15 @@ ruler revert [options]
318
318
 
319
319
  ### Options
320
320
 
321
- | Option | Description |
322
- | ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
323
- | `--project-root <path>` | Path to your project's root (default: current directory) |
321
+ | Option | Description |
322
+ | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
323
+ | `--project-root <path>` | Path to your project's root (default: current directory) |
324
324
  | `--agents <agent1,agent2,...>` | Comma-separated list of agent names to revert (agentsmd, aider, amazonqcli, amp, antigravity, augmentcode, claude, cline, codex, copilot, crush, cursor, firebase, firebender, gemini-cli, goose, jules, junie, kilocode, kiro, mistral, opencode, openhands, pi, qwen, roo, trae, warp, windsurf, zed) |
325
- | `--config <path>` | Path to a custom `ruler.toml` configuration file |
326
- | `--keep-backups` | Keep backup files (.bak) after restoration (default: false) |
327
- | `--dry-run` | Preview changes without actually reverting files |
328
- | `--verbose` / `-v` | Display detailed output during execution |
329
- | `--local-only` | Only search for local .ruler directories, ignore global config |
325
+ | `--config <path>` | Path to a custom `ruler.toml` configuration file |
326
+ | `--keep-backups` | Keep backup files (.bak) after restoration (default: false) |
327
+ | `--dry-run` | Preview changes without actually reverting files |
328
+ | `--verbose` / `-v` | Display detailed output during execution |
329
+ | `--local-only` | Only search for local .ruler directories, ignore global config |
330
330
 
331
331
  ### Common Examples
332
332
 
@@ -449,7 +449,7 @@ enabled = false
449
449
 
450
450
  [agents.kilocode]
451
451
  enabled = true
452
- output_path = ".kilocode/rules/ruler_kilocode_instructions.md"
452
+ output_path = "AGENTS.md"
453
453
 
454
454
  [agents.warp]
455
455
  enabled = true
@@ -559,7 +559,7 @@ export CODEX_HOME="$(pwd)/.codex"
559
559
 
560
560
  ## Skills Support (Experimental)
561
561
 
562
- **⚠️ 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 (agents without native skills support).
562
+ **⚠️ Experimental Feature**: Skills support is currently experimental. Skills are only propagated to agents with native skills support; other agents are skipped with a warning.
563
563
 
564
564
  Ruler can manage and propagate skills to supported AI agents. Skills are stored in `.ruler/skills/` and are automatically distributed to compatible agents when you run `ruler apply`.
565
565
 
@@ -576,11 +576,11 @@ Skills are specialized knowledge packages that extend AI agent capabilities with
576
576
  - **Pi Coding Agent**: `.pi/skills/`
577
577
  - **Goose**: `.agents/skills/`
578
578
  - **Amp**: `.agents/skills/` (shared with Goose)
579
+ - **Antigravity**: `.agent/skills/`
579
580
  - **Mistral Vibe**: `.vibe/skills/`
580
581
  - **Roo Code**: `.roo/skills/`
581
582
  - **Gemini CLI**: `.gemini/skills/`
582
583
  - **Cursor**: `.cursor/skills/`
583
- - **Other MCP-compatible agents**: Skills are copied to `.skillz/` and a Skillz MCP server is automatically configured via `uvx`
584
584
 
585
585
  ### Skills Directory Structure
586
586
 
@@ -627,23 +627,9 @@ ruler apply --no-skills
627
627
  enabled = true # or false to disable
628
628
  ```
629
629
 
630
- ### Skillz MCP Server
631
-
632
- For agents that support MCP but don't have native skills support, Ruler automatically:
633
-
634
- 1. Copies skills to `.skillz/` directory
635
- 2. Configures a Skillz MCP server in the agent's configuration
636
- 3. Uses `uvx` to launch the server with the project-relative path to `.skillz`
637
-
638
- Agents using native skills support (Claude Code, GitHub Copilot, Kilo Code, OpenAI Codex CLI, OpenCode, Pi Coding Agent, Goose, Amp, Mistral Vibe, Roo Code, Gemini CLI, and Cursor) **do not** use the Skillz MCP server and instead use their own native skills directories.
630
+ ### Non-native Agents
639
631
 
640
- Example auto-generated MCP server configuration:
641
-
642
- ```toml
643
- [mcp_servers.skillz]
644
- command = "uvx"
645
- args = ["skillz@latest", ".skillz"]
646
- ```
632
+ If you run Ruler for agents that do not support native skills, Ruler logs a warning and skips skills propagation for those agents.
647
633
 
648
634
  ### `.gitignore` Integration
649
635
 
@@ -654,22 +640,17 @@ When skills support is enabled and gitignore integration is active, Ruler automa
654
640
  - `.opencode/skill/` (for OpenCode)
655
641
  - `.pi/skills/` (for Pi Coding Agent)
656
642
  - `.agents/skills/` (for Goose and Amp)
643
+ - `.agent/skills/` (for Antigravity)
657
644
  - `.vibe/skills/` (for Mistral Vibe)
658
645
  - `.roo/skills/` (for Roo Code)
659
646
  - `.gemini/skills/` (for Gemini CLI)
660
647
  - `.cursor/skills/` (for Cursor)
661
- - `.skillz/` (for other MCP-based agents)
662
648
 
663
649
  to your `.gitignore` file within the managed Ruler block.
664
650
 
665
651
  ### Requirements
666
652
 
667
- - **For agents with native skills support** (Claude Code, GitHub Copilot, Kilo Code, OpenAI Codex CLI, OpenCode, Pi Coding Agent, Goose, Amp, Mistral Vibe, Roo Code, Gemini CLI, Cursor): No additional requirements
668
- - **For other MCP agents**: `uv` must be installed and available in your PATH
669
- ```bash
670
- # Install uv if needed
671
- curl -LsSf https://astral.sh/uv/install.sh | sh
672
- ```
653
+ - **For agents with native skills support** (Claude Code, GitHub Copilot, Kilo Code, OpenAI Codex CLI, OpenCode, Pi Coding Agent, Goose, Amp, Antigravity, Mistral Vibe, Roo Code, Gemini CLI, Cursor): No additional requirements.
673
654
 
674
655
  ### Validation
675
656
 
@@ -688,7 +669,7 @@ Test skills propagation without making changes:
688
669
  ruler apply --dry-run
689
670
  ```
690
671
 
691
- This shows which skills would be copied and which MCP servers would be configured.
672
+ This shows which skills would be copied.
692
673
 
693
674
  ### Example Workflow
694
675
 
@@ -717,11 +698,11 @@ ruler apply
717
698
  # - OpenCode: .opencode/skill/my-skill/
718
699
  # - Pi Coding Agent: .pi/skills/my-skill/
719
700
  # - Goose & Amp: .agents/skills/my-skill/
701
+ # - Antigravity: .agent/skills/my-skill/
720
702
  # - Mistral Vibe: .vibe/skills/my-skill/
721
703
  # - Roo Code: .roo/skills/my-skill/
722
704
  # - Gemini CLI: .gemini/skills/my-skill/
723
705
  # - Cursor: .cursor/skills/my-skill/
724
- # - Other MCP agents: .skillz/my-skill/ + Skillz MCP server configured
725
706
  ```
726
707
 
727
708
  ## `.gitignore` Integration
@@ -49,5 +49,8 @@ class AntigravityAgent extends AbstractAgent_1.AbstractAgent {
49
49
  getDefaultOutputPath(projectRoot) {
50
50
  return path.join(projectRoot, '.agent', 'rules', 'ruler.md');
51
51
  }
52
+ supportsNativeSkills() {
53
+ return true;
54
+ }
52
55
  }
53
56
  exports.AntigravityAgent = AntigravityAgent;
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FactoryDroidAgent = void 0;
4
+ const AgentsMdAgent_1 = require("./AgentsMdAgent");
5
+ /**
6
+ * Factory Droid agent adapter.
7
+ * Uses the root-level AGENTS.md for instructions.
8
+ */
9
+ class FactoryDroidAgent extends AgentsMdAgent_1.AgentsMdAgent {
10
+ getIdentifier() {
11
+ return 'factory';
12
+ }
13
+ getName() {
14
+ return 'Factory Droid';
15
+ }
16
+ getMcpServerKey() {
17
+ return 'mcpServers';
18
+ }
19
+ supportsMcpStdio() {
20
+ return true;
21
+ }
22
+ supportsMcpRemote() {
23
+ return true;
24
+ }
25
+ supportsNativeSkills() {
26
+ return true;
27
+ }
28
+ }
29
+ exports.FactoryDroidAgent = FactoryDroidAgent;
@@ -35,12 +35,12 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.KiloCodeAgent = void 0;
37
37
  const path = __importStar(require("path"));
38
- const AbstractAgent_1 = require("./AbstractAgent");
38
+ const AgentsMdAgent_1 = require("./AgentsMdAgent");
39
39
  /**
40
40
  * Kilo Code agent adapter.
41
- * Generates ruler_kilocode_instructions.md configuration file in .kilocode/rules/ directory.
41
+ * Uses AGENTS.md for instructions and .kilocode/mcp.json for MCP configuration.
42
42
  */
43
- class KiloCodeAgent extends AbstractAgent_1.AbstractAgent {
43
+ class KiloCodeAgent extends AgentsMdAgent_1.AgentsMdAgent {
44
44
  getIdentifier() {
45
45
  return 'kilocode';
46
46
  }
@@ -48,7 +48,7 @@ class KiloCodeAgent extends AbstractAgent_1.AbstractAgent {
48
48
  return 'Kilo Code';
49
49
  }
50
50
  getDefaultOutputPath(projectRoot) {
51
- return path.join(projectRoot, '.kilocode', 'rules', 'ruler_kilocode_instructions.md');
51
+ return path.join(projectRoot, 'AGENTS.md');
52
52
  }
53
53
  getMcpServerKey() {
54
54
  return 'mcpServers';
@@ -31,6 +31,7 @@ const RooCodeAgent_1 = require("./RooCodeAgent");
31
31
  const TraeAgent_1 = require("./TraeAgent");
32
32
  const AmazonQCliAgent_1 = require("./AmazonQCliAgent");
33
33
  const FirebenderAgent_1 = require("./FirebenderAgent");
34
+ const FactoryDroidAgent_1 = require("./FactoryDroidAgent");
34
35
  const AntigravityAgent_1 = require("./AntigravityAgent");
35
36
  const MistralVibeAgent_1 = require("./MistralVibeAgent");
36
37
  const PiAgent_1 = require("./PiAgent");
@@ -62,6 +63,7 @@ exports.allAgents = [
62
63
  new TraeAgent_1.TraeAgent(),
63
64
  new AmazonQCliAgent_1.AmazonQCliAgent(),
64
65
  new FirebenderAgent_1.FirebenderAgent(),
66
+ new FactoryDroidAgent_1.FactoryDroidAgent(),
65
67
  new AntigravityAgent_1.AntigravityAgent(),
66
68
  new MistralVibeAgent_1.MistralVibeAgent(),
67
69
  new PiAgent_1.PiAgent(),
package/dist/constants.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.SKILLZ_MCP_SERVER_NAME = exports.SKILL_MD_FILENAME = exports.SKILLZ_DIR = exports.CURSOR_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.SKILL_MD_FILENAME = exports.ANTIGRAVITY_SKILLS_PATH = exports.FACTORY_SKILLS_PATH = exports.CURSOR_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;
@@ -61,6 +61,6 @@ exports.VIBE_SKILLS_PATH = '.vibe/skills';
61
61
  exports.ROO_SKILLS_PATH = '.roo/skills';
62
62
  exports.GEMINI_SKILLS_PATH = '.gemini/skills';
63
63
  exports.CURSOR_SKILLS_PATH = '.cursor/skills';
64
- exports.SKILLZ_DIR = '.skillz';
64
+ exports.FACTORY_SKILLS_PATH = '.factory/skills';
65
+ exports.ANTIGRAVITY_SKILLS_PATH = '.agent/skills';
65
66
  exports.SKILL_MD_FILENAME = 'SKILL.md';
66
- exports.SKILLZ_MCP_SERVER_NAME = 'skillz';
@@ -45,8 +45,8 @@ exports.propagateSkillsForVibe = propagateSkillsForVibe;
45
45
  exports.propagateSkillsForRoo = propagateSkillsForRoo;
46
46
  exports.propagateSkillsForGemini = propagateSkillsForGemini;
47
47
  exports.propagateSkillsForCursor = propagateSkillsForCursor;
48
- exports.propagateSkillsForSkillz = propagateSkillsForSkillz;
49
- exports.buildSkillzMcpConfig = buildSkillzMcpConfig;
48
+ exports.propagateSkillsForFactory = propagateSkillsForFactory;
49
+ exports.propagateSkillsForAntigravity = propagateSkillsForAntigravity;
50
50
  const path = __importStar(require("path"));
51
51
  const fs = __importStar(require("fs/promises"));
52
52
  const constants_1 = require("../constants");
@@ -82,7 +82,7 @@ async function getSkillsGitignorePaths(projectRoot) {
82
82
  return [];
83
83
  }
84
84
  // Import here to avoid circular dependency
85
- const { CLAUDE_SKILLS_PATH, CODEX_SKILLS_PATH, OPENCODE_SKILLS_PATH, PI_SKILLS_PATH, GOOSE_SKILLS_PATH, VIBE_SKILLS_PATH, ROO_SKILLS_PATH, GEMINI_SKILLS_PATH, CURSOR_SKILLS_PATH, SKILLZ_DIR, } = await Promise.resolve().then(() => __importStar(require('../constants')));
85
+ const { CLAUDE_SKILLS_PATH, CODEX_SKILLS_PATH, OPENCODE_SKILLS_PATH, PI_SKILLS_PATH, GOOSE_SKILLS_PATH, VIBE_SKILLS_PATH, ROO_SKILLS_PATH, GEMINI_SKILLS_PATH, CURSOR_SKILLS_PATH, FACTORY_SKILLS_PATH, ANTIGRAVITY_SKILLS_PATH, } = await Promise.resolve().then(() => __importStar(require('../constants')));
86
86
  return [
87
87
  path.join(projectRoot, CLAUDE_SKILLS_PATH),
88
88
  path.join(projectRoot, CODEX_SKILLS_PATH),
@@ -93,7 +93,8 @@ async function getSkillsGitignorePaths(projectRoot) {
93
93
  path.join(projectRoot, ROO_SKILLS_PATH),
94
94
  path.join(projectRoot, GEMINI_SKILLS_PATH),
95
95
  path.join(projectRoot, CURSOR_SKILLS_PATH),
96
- path.join(projectRoot, SKILLZ_DIR),
96
+ path.join(projectRoot, FACTORY_SKILLS_PATH),
97
+ path.join(projectRoot, ANTIGRAVITY_SKILLS_PATH),
97
98
  ];
98
99
  }
99
100
  /**
@@ -104,16 +105,15 @@ async function getSkillsGitignorePaths(projectRoot) {
104
105
  */
105
106
  let hasWarnedExperimental = false;
106
107
  /**
107
- * Warns once per process about experimental skills features and uv requirement.
108
+ * Warns once per process about experimental skills support.
108
109
  * Uses module-level state to prevent duplicate warnings within the same process.
109
110
  */
110
- function warnOnceExperimentalAndUv(verbose, dryRun) {
111
+ function warnOnceExperimental(verbose, dryRun) {
111
112
  if (hasWarnedExperimental) {
112
113
  return;
113
114
  }
114
115
  hasWarnedExperimental = true;
115
116
  (0, constants_1.logWarn)('Skills support is experimental and behavior may change in future releases.', dryRun);
116
- (0, constants_1.logWarn)('Skills MCP server (Skillz) requires uv. Install: https://github.com/astral-sh/uv', dryRun);
117
117
  }
118
118
  /**
119
119
  * Cleans up skills directories when skills are disabled.
@@ -129,7 +129,8 @@ async function cleanupSkillsDirectories(projectRoot, dryRun, verbose) {
129
129
  const rooSkillsPath = path.join(projectRoot, constants_1.ROO_SKILLS_PATH);
130
130
  const geminiSkillsPath = path.join(projectRoot, constants_1.GEMINI_SKILLS_PATH);
131
131
  const cursorSkillsPath = path.join(projectRoot, constants_1.CURSOR_SKILLS_PATH);
132
- const skillzPath = path.join(projectRoot, constants_1.SKILLZ_DIR);
132
+ const factorySkillsPath = path.join(projectRoot, constants_1.FACTORY_SKILLS_PATH);
133
+ const antigravitySkillsPath = path.join(projectRoot, constants_1.ANTIGRAVITY_SKILLS_PATH);
133
134
  // Clean up .claude/skills
134
135
  try {
135
136
  await fs.access(claudeSkillsPath);
@@ -256,15 +257,29 @@ async function cleanupSkillsDirectories(projectRoot, dryRun, verbose) {
256
257
  catch {
257
258
  // Directory doesn't exist, nothing to clean
258
259
  }
259
- // Clean up .skillz
260
+ // Clean up .factory/skills
260
261
  try {
261
- await fs.access(skillzPath);
262
+ await fs.access(factorySkillsPath);
262
263
  if (dryRun) {
263
- (0, constants_1.logVerboseInfo)(`DRY RUN: Would remove ${constants_1.SKILLZ_DIR}`, verbose, dryRun);
264
+ (0, constants_1.logVerboseInfo)(`DRY RUN: Would remove ${constants_1.FACTORY_SKILLS_PATH}`, verbose, dryRun);
264
265
  }
265
266
  else {
266
- await fs.rm(skillzPath, { recursive: true, force: true });
267
- (0, constants_1.logVerboseInfo)(`Removed ${constants_1.SKILLZ_DIR} (skills disabled)`, verbose, dryRun);
267
+ await fs.rm(factorySkillsPath, { recursive: true, force: true });
268
+ (0, constants_1.logVerboseInfo)(`Removed ${constants_1.FACTORY_SKILLS_PATH} (skills disabled)`, verbose, dryRun);
269
+ }
270
+ }
271
+ catch {
272
+ // Directory doesn't exist, nothing to clean
273
+ }
274
+ // Clean up .agent/skills
275
+ try {
276
+ await fs.access(antigravitySkillsPath);
277
+ if (dryRun) {
278
+ (0, constants_1.logVerboseInfo)(`DRY RUN: Would remove ${constants_1.ANTIGRAVITY_SKILLS_PATH}`, verbose, dryRun);
279
+ }
280
+ else {
281
+ await fs.rm(antigravitySkillsPath, { recursive: true, force: true });
282
+ (0, constants_1.logVerboseInfo)(`Removed ${constants_1.ANTIGRAVITY_SKILLS_PATH} (skills disabled)`, verbose, dryRun);
268
283
  }
269
284
  }
270
285
  catch {
@@ -301,17 +316,20 @@ async function propagateSkills(projectRoot, agents, skillsEnabled, verbose, dryR
301
316
  return;
302
317
  }
303
318
  (0, constants_1.logVerboseInfo)(`Discovered ${skills.length} skill(s)`, verbose, dryRun);
304
- // Check if any agents need skills
305
319
  const hasNativeSkillsAgent = agents.some((a) => a.supportsNativeSkills?.());
306
- const hasMcpAgent = agents.some((a) => a.supportsMcpStdio?.() && !a.supportsNativeSkills?.());
307
- if (!hasNativeSkillsAgent && !hasMcpAgent) {
308
- (0, constants_1.logVerboseInfo)('No agents require skills support', verbose, dryRun);
320
+ const nonNativeAgents = agents.filter((agent) => !agent.supportsNativeSkills?.());
321
+ if (nonNativeAgents.length > 0) {
322
+ const agentList = nonNativeAgents
323
+ .map((agent) => agent.getName())
324
+ .join(', ');
325
+ (0, constants_1.logWarn)(`Skills are configured, but the following agents do not support native skills and will be skipped: ${agentList}`, dryRun);
326
+ }
327
+ if (!hasNativeSkillsAgent) {
328
+ (0, constants_1.logVerboseInfo)('No agents support native skills, skipping skills propagation', verbose, dryRun);
309
329
  return;
310
330
  }
311
331
  // Warn about experimental features
312
- if (hasMcpAgent) {
313
- warnOnceExperimentalAndUv(verbose, dryRun);
314
- }
332
+ warnOnceExperimental(verbose, dryRun);
315
333
  // Copy to Claude skills directory if needed
316
334
  if (hasNativeSkillsAgent) {
317
335
  (0, constants_1.logVerboseInfo)(`Copying skills to ${constants_1.CLAUDE_SKILLS_PATH} for Claude Code, GitHub Copilot, and Kilo Code`, verbose, dryRun);
@@ -332,12 +350,12 @@ async function propagateSkills(projectRoot, agents, skillsEnabled, verbose, dryR
332
350
  await propagateSkillsForGemini(projectRoot, { dryRun });
333
351
  (0, constants_1.logVerboseInfo)(`Copying skills to ${constants_1.CURSOR_SKILLS_PATH} for Cursor`, verbose, dryRun);
334
352
  await propagateSkillsForCursor(projectRoot, { dryRun });
353
+ (0, constants_1.logVerboseInfo)(`Copying skills to ${constants_1.FACTORY_SKILLS_PATH} for Factory Droid`, verbose, dryRun);
354
+ await propagateSkillsForFactory(projectRoot, { dryRun });
355
+ (0, constants_1.logVerboseInfo)(`Copying skills to ${constants_1.ANTIGRAVITY_SKILLS_PATH} for Antigravity`, verbose, dryRun);
356
+ await propagateSkillsForAntigravity(projectRoot, { dryRun });
335
357
  }
336
- // Copy to .skillz directory if needed
337
- if (hasMcpAgent) {
338
- (0, constants_1.logVerboseInfo)(`Copying skills to ${constants_1.SKILLZ_DIR} for MCP agents`, verbose, dryRun);
339
- await propagateSkillsForSkillz(projectRoot, { dryRun });
340
- }
358
+ // No MCP-based propagation; only native skills are supported.
341
359
  }
342
360
  /**
343
361
  * Propagates skills for Claude Code by copying .ruler/skills to .claude/skills.
@@ -790,13 +808,14 @@ async function propagateSkillsForCursor(projectRoot, options) {
790
808
  return [];
791
809
  }
792
810
  /**
793
- * Propagates skills for MCP agents by copying .ruler/skills to .skillz.
811
+ * Propagates skills for Factory Droid by copying .ruler/skills to .factory/skills.
794
812
  * Uses atomic replace to ensure safe overwriting of existing skills.
795
813
  * Returns dry-run steps if dryRun is true, otherwise returns empty array.
796
814
  */
797
- async function propagateSkillsForSkillz(projectRoot, options) {
815
+ async function propagateSkillsForFactory(projectRoot, options) {
798
816
  const skillsDir = path.join(projectRoot, constants_1.RULER_SKILLS_PATH);
799
- const skillzPath = path.join(projectRoot, constants_1.SKILLZ_DIR);
817
+ const factorySkillsPath = path.join(projectRoot, constants_1.FACTORY_SKILLS_PATH);
818
+ const factoryDir = path.dirname(factorySkillsPath);
800
819
  // Check if source skills directory exists
801
820
  try {
802
821
  await fs.access(skillsDir);
@@ -806,26 +825,25 @@ async function propagateSkillsForSkillz(projectRoot, options) {
806
825
  return [];
807
826
  }
808
827
  if (options.dryRun) {
809
- return [
810
- `Copy skills from ${constants_1.RULER_SKILLS_PATH} to ${constants_1.SKILLZ_DIR}`,
811
- `Configure Skillz MCP server with path to ${constants_1.SKILLZ_DIR}`,
812
- ];
828
+ return [`Copy skills from ${constants_1.RULER_SKILLS_PATH} to ${constants_1.FACTORY_SKILLS_PATH}`];
813
829
  }
830
+ // Ensure .factory directory exists
831
+ await fs.mkdir(factoryDir, { recursive: true });
814
832
  // Use atomic replace: copy to temp, then rename
815
- const tempDir = path.join(projectRoot, `${constants_1.SKILLZ_DIR}.tmp-${Date.now()}`);
833
+ const tempDir = path.join(factoryDir, `skills.tmp-${Date.now()}`);
816
834
  try {
817
835
  // Copy to temp directory
818
836
  await (0, SkillsUtils_1.copySkillsDirectory)(skillsDir, tempDir);
819
837
  // Atomically replace the target
820
838
  // First, remove existing target if it exists
821
839
  try {
822
- await fs.rm(skillzPath, { recursive: true, force: true });
840
+ await fs.rm(factorySkillsPath, { recursive: true, force: true });
823
841
  }
824
842
  catch {
825
843
  // Target didn't exist, that's fine
826
844
  }
827
845
  // Rename temp to target
828
- await fs.rename(tempDir, skillzPath);
846
+ await fs.rename(tempDir, factorySkillsPath);
829
847
  }
830
848
  catch (error) {
831
849
  // Clean up temp directory on error
@@ -840,14 +858,54 @@ async function propagateSkillsForSkillz(projectRoot, options) {
840
858
  return [];
841
859
  }
842
860
  /**
843
- * Builds MCP config for Skillz server.
861
+ * Propagates skills for Antigravity by copying .ruler/skills to .agent/skills.
862
+ * Uses atomic replace to ensure safe overwriting of existing skills.
863
+ * Returns dry-run steps if dryRun is true, otherwise returns empty array.
844
864
  */
845
- function buildSkillzMcpConfig(projectRoot) {
846
- void projectRoot;
847
- return {
848
- [constants_1.SKILLZ_MCP_SERVER_NAME]: {
849
- command: 'uvx',
850
- args: ['skillz@latest', constants_1.SKILLZ_DIR],
851
- },
852
- };
865
+ async function propagateSkillsForAntigravity(projectRoot, options) {
866
+ const skillsDir = path.join(projectRoot, constants_1.RULER_SKILLS_PATH);
867
+ const antigravitySkillsPath = path.join(projectRoot, constants_1.ANTIGRAVITY_SKILLS_PATH);
868
+ const antigravityDir = path.dirname(antigravitySkillsPath);
869
+ // Check if source skills directory exists
870
+ try {
871
+ await fs.access(skillsDir);
872
+ }
873
+ catch {
874
+ // No skills directory - return empty
875
+ return [];
876
+ }
877
+ if (options.dryRun) {
878
+ return [
879
+ `Copy skills from ${constants_1.RULER_SKILLS_PATH} to ${constants_1.ANTIGRAVITY_SKILLS_PATH}`,
880
+ ];
881
+ }
882
+ // Ensure .agent directory exists
883
+ await fs.mkdir(antigravityDir, { recursive: true });
884
+ // Use atomic replace: copy to temp, then rename
885
+ const tempDir = path.join(antigravityDir, `skills.tmp-${Date.now()}`);
886
+ try {
887
+ // Copy to temp directory
888
+ await (0, SkillsUtils_1.copySkillsDirectory)(skillsDir, tempDir);
889
+ // Atomically replace the target
890
+ // First, remove existing target if it exists
891
+ try {
892
+ await fs.rm(antigravitySkillsPath, { recursive: true, force: true });
893
+ }
894
+ catch {
895
+ // Target didn't exist, that's fine
896
+ }
897
+ // Rename temp to target
898
+ await fs.rename(tempDir, antigravitySkillsPath);
899
+ }
900
+ catch (error) {
901
+ // Clean up temp directory on error
902
+ try {
903
+ await fs.rm(tempDir, { recursive: true, force: true });
904
+ }
905
+ catch {
906
+ // Ignore cleanup errors
907
+ }
908
+ throw error;
909
+ }
910
+ return [];
853
911
  }
@@ -233,12 +233,12 @@ async function loadSingleConfiguration(projectRoot, configPath, localOnly) {
233
233
  * @param cliMcpStrategy MCP strategy from CLI
234
234
  * @returns Promise resolving to array of generated file paths
235
235
  */
236
- async function processHierarchicalConfigurations(agents, configurations, verbose, dryRun, cliMcpEnabled, cliMcpStrategy, backup = true, skillsEnabled = true) {
236
+ async function processHierarchicalConfigurations(agents, configurations, verbose, dryRun, cliMcpEnabled, cliMcpStrategy, backup = true) {
237
237
  const allGeneratedPaths = [];
238
238
  for (const config of configurations) {
239
239
  (0, constants_1.logVerboseInfo)(`Processing .ruler directory: ${config.rulerDir}`, verbose, dryRun);
240
240
  const rulerRoot = path.dirname(config.rulerDir);
241
- const paths = await applyConfigurationsToAgents(agents, config.concatenatedRules, config.rulerMcpJson, config.config, rulerRoot, verbose, dryRun, cliMcpEnabled, cliMcpStrategy, backup, skillsEnabled);
241
+ const paths = await applyConfigurationsToAgents(agents, config.concatenatedRules, config.rulerMcpJson, config.config, rulerRoot, verbose, dryRun, cliMcpEnabled, cliMcpStrategy, backup);
242
242
  const normalizedPaths = paths.map((p) => path.isAbsolute(p) ? p : path.join(rulerRoot, p));
243
243
  allGeneratedPaths.push(...normalizedPaths);
244
244
  }
@@ -256,42 +256,8 @@ async function processHierarchicalConfigurations(agents, configurations, verbose
256
256
  * @param cliMcpStrategy MCP strategy from CLI
257
257
  * @returns Promise resolving to array of generated file paths
258
258
  */
259
- async function processSingleConfiguration(agents, configuration, projectRoot, verbose, dryRun, cliMcpEnabled, cliMcpStrategy, backup = true, skillsEnabled = true) {
260
- return await applyConfigurationsToAgents(agents, configuration.concatenatedRules, configuration.rulerMcpJson, configuration.config, projectRoot, verbose, dryRun, cliMcpEnabled, cliMcpStrategy, backup, skillsEnabled);
261
- }
262
- /**
263
- * Adds Skillz MCP server to rulerMcpJson if skills exist and this agent needs it.
264
- * Returns augmented MCP config or original if no changes needed.
265
- */
266
- async function addSkillzMcpServerIfNeeded(rulerMcpJson, projectRoot, agent, verbose) {
267
- // Check if this agent supports MCP stdio but not native skills
268
- if (!agent.supportsMcpStdio?.() || agent.supportsNativeSkills?.()) {
269
- return rulerMcpJson;
270
- }
271
- // Check if .skillz directory exists
272
- try {
273
- const { SKILLZ_DIR } = await Promise.resolve().then(() => __importStar(require('../constants')));
274
- const skillzPath = path.join(projectRoot, SKILLZ_DIR);
275
- await fs_1.promises.access(skillzPath);
276
- // Skills exist, add Skillz MCP server
277
- const { buildSkillzMcpConfig } = await Promise.resolve().then(() => __importStar(require('./SkillsProcessor')));
278
- const skillzMcp = buildSkillzMcpConfig(projectRoot);
279
- // Initialize empty config if null
280
- const baseConfig = rulerMcpJson || { mcpServers: {} };
281
- const mcpServers = baseConfig.mcpServers || {};
282
- (0, constants_1.logVerbose)(`Adding Skillz MCP server to configuration for ${agent.getName()}`, verbose);
283
- return {
284
- ...baseConfig,
285
- mcpServers: {
286
- ...mcpServers,
287
- ...skillzMcp,
288
- },
289
- };
290
- }
291
- catch {
292
- // No .skillz directory, return original config
293
- return rulerMcpJson;
294
- }
259
+ async function processSingleConfiguration(agents, configuration, projectRoot, verbose, dryRun, cliMcpEnabled, cliMcpStrategy, backup = true) {
260
+ return await applyConfigurationsToAgents(agents, configuration.concatenatedRules, configuration.rulerMcpJson, configuration.config, projectRoot, verbose, dryRun, cliMcpEnabled, cliMcpStrategy, backup);
295
261
  }
296
262
  /**
297
263
  * Applies configurations to the selected agents (internal function).
@@ -304,16 +270,14 @@ async function addSkillzMcpServerIfNeeded(rulerMcpJson, projectRoot, agent, verb
304
270
  * @param dryRun Whether to perform a dry run
305
271
  * @returns Promise resolving to array of generated file paths
306
272
  */
307
- async function applyConfigurationsToAgents(agents, concatenatedRules, rulerMcpJson, config, projectRoot, verbose, dryRun, cliMcpEnabled = true, cliMcpStrategy, backup = true, skillsEnabled = true) {
273
+ async function applyConfigurationsToAgents(agents, concatenatedRules, rulerMcpJson, config, projectRoot, verbose, dryRun, cliMcpEnabled = true, cliMcpStrategy, backup = true) {
308
274
  const generatedPaths = [];
309
275
  let agentsMdWritten = false;
310
276
  for (const agent of agents) {
311
277
  (0, constants_1.logInfo)(`Applying rules for ${agent.getName()}...`, dryRun);
312
278
  (0, constants_1.logVerbose)(`Processing agent: ${agent.getName()}`, verbose);
313
279
  const agentConfig = config.agentConfigs[agent.getIdentifier()];
314
- const agentRulerMcpJson = skillsEnabled && !dryRun
315
- ? await addSkillzMcpServerIfNeeded(rulerMcpJson, projectRoot, agent, verbose)
316
- : rulerMcpJson;
280
+ const agentRulerMcpJson = rulerMcpJson;
317
281
  // Collect output paths for .gitignore
318
282
  const outputPaths = (0, agent_utils_1.getAgentOutputPaths)(agent, projectRoot, agentConfig);
319
283
  (0, constants_1.logVerbose)(`Agent ${agent.getName()} output paths: ${outputPaths.join(', ')}`, verbose);
@@ -357,11 +321,11 @@ async function applyConfigurationsToAgents(agents, concatenatedRules, rulerMcpJs
357
321
  }
358
322
  }
359
323
  // Handle MCP configuration
360
- await handleMcpConfiguration(agent, agentConfig, config, agentRulerMcpJson, projectRoot, generatedPaths, verbose, dryRun, cliMcpEnabled, cliMcpStrategy, backup, skillsEnabled);
324
+ await handleMcpConfiguration(agent, agentConfig, config, agentRulerMcpJson, projectRoot, generatedPaths, verbose, dryRun, cliMcpEnabled, cliMcpStrategy, backup);
361
325
  }
362
326
  return generatedPaths;
363
327
  }
364
- async function handleMcpConfiguration(agent, agentConfig, config, rulerMcpJson, projectRoot, generatedPaths, verbose, dryRun, cliMcpEnabled = true, cliMcpStrategy, backup = true, skillsEnabled = true) {
328
+ async function handleMcpConfiguration(agent, agentConfig, config, rulerMcpJson, projectRoot, generatedPaths, verbose, dryRun, cliMcpEnabled = true, cliMcpStrategy, backup = true) {
365
329
  if (!(0, capabilities_1.agentSupportsMcp)(agent)) {
366
330
  (0, constants_1.logVerbose)(`Agent ${agent.getName()} does not support MCP - skipping MCP configuration`, verbose);
367
331
  return;
@@ -371,41 +335,9 @@ async function handleMcpConfiguration(agent, agentConfig, config, rulerMcpJson,
371
335
  if (!dest || !mcpEnabledForAgent) {
372
336
  return;
373
337
  }
374
- let filteredMcpJson = rulerMcpJson
338
+ const filteredMcpJson = rulerMcpJson
375
339
  ? (0, capabilities_1.filterMcpConfigForAgent)(rulerMcpJson, agent)
376
340
  : null;
377
- // Add Skillz MCP server for agents that support stdio but not native skills
378
- // Only add if skills are enabled
379
- if (skillsEnabled &&
380
- agent.supportsMcpStdio?.() &&
381
- !agent.supportsNativeSkills?.()) {
382
- // Check if .skillz directory exists
383
- try {
384
- const { SKILLZ_DIR } = await Promise.resolve().then(() => __importStar(require('../constants')));
385
- const skillzPath = path.join(projectRoot, SKILLZ_DIR);
386
- await fs_1.promises.access(skillzPath);
387
- // Skills exist, add Skillz MCP server
388
- const { buildSkillzMcpConfig } = await Promise.resolve().then(() => __importStar(require('./SkillsProcessor')));
389
- const skillzMcp = buildSkillzMcpConfig(projectRoot);
390
- // Merge Skillz server into MCP config
391
- // Initialize empty config if null
392
- if (!filteredMcpJson) {
393
- filteredMcpJson = { mcpServers: {} };
394
- }
395
- const mcpServers = filteredMcpJson.mcpServers || {};
396
- filteredMcpJson = {
397
- ...filteredMcpJson,
398
- mcpServers: {
399
- ...mcpServers,
400
- ...skillzMcp,
401
- },
402
- };
403
- (0, constants_1.logVerboseInfo)(`Added Skillz MCP server for ${agent.getName()}`, verbose, dryRun);
404
- }
405
- catch {
406
- // No .skillz directory, skip adding Skillz server
407
- }
408
- }
409
341
  if (!filteredMcpJson) {
410
342
  (0, constants_1.logVerbose)(`No compatible MCP servers found for ${agent.getName()} - skipping MCP configuration`, verbose);
411
343
  return;
@@ -557,6 +489,34 @@ function transformMcpForKiloCode(mcpJson) {
557
489
  transformedMcp.mcpServers = transformedServers;
558
490
  return transformedMcp;
559
491
  }
492
+ /**
493
+ * Transform MCP server types for Factory Droid compatibility.
494
+ * Factory Droid expects "http" for remote HTTP servers, not "remote".
495
+ */
496
+ function transformMcpForFactoryDroid(mcpJson) {
497
+ if (!mcpJson.mcpServers || typeof mcpJson.mcpServers !== 'object') {
498
+ return mcpJson;
499
+ }
500
+ const transformedMcp = { ...mcpJson };
501
+ const transformedServers = {};
502
+ for (const [name, serverDef] of Object.entries(mcpJson.mcpServers)) {
503
+ if (serverDef && typeof serverDef === 'object') {
504
+ const server = serverDef;
505
+ const transformedServer = { ...server };
506
+ if (server.type === 'remote' &&
507
+ server.url &&
508
+ typeof server.url === 'string') {
509
+ transformedServer.type = 'http';
510
+ }
511
+ transformedServers[name] = transformedServer;
512
+ }
513
+ else {
514
+ transformedServers[name] = serverDef;
515
+ }
516
+ }
517
+ transformedMcp.mcpServers = transformedServers;
518
+ return transformedMcp;
519
+ }
560
520
  async function applyStandardMcpConfiguration(agent, filteredMcpJson, dest, agentConfig, config, cliMcpStrategy, dryRun, verbose, backup = true) {
561
521
  const strategy = cliMcpStrategy ??
562
522
  agentConfig?.mcp?.strategy ??
@@ -581,6 +541,9 @@ async function applyStandardMcpConfiguration(agent, filteredMcpJson, dest, agent
581
541
  else if (agent.getIdentifier() === 'kilocode') {
582
542
  mcpToMerge = transformMcpForKiloCode(filteredMcpJson);
583
543
  }
544
+ else if (agent.getIdentifier() === 'factory') {
545
+ mcpToMerge = transformMcpForFactoryDroid(filteredMcpJson);
546
+ }
584
547
  const existing = await (0, mcp_1.readNativeMcp)(dest);
585
548
  const merged = (0, merge_1.mergeMcp)(existing, mcpToMerge, strategy, serverKey);
586
549
  // Firebase Studio (IDX) expects no "type" fields in .idx/mcp.json server entries.
package/dist/lib.js CHANGED
@@ -100,7 +100,7 @@ async function applyAllAgentConfigs(projectRoot, includedAgents, configPath, cli
100
100
  await propagateSkills(nestedRoot, selectedAgents, skillsEnabledResolved, verbose, dryRun);
101
101
  }
102
102
  }
103
- generatedPaths = await (0, apply_engine_1.processHierarchicalConfigurations)(selectedAgents, hierarchicalConfigs, verbose, dryRun, cliMcpEnabled, cliMcpStrategy, backup, skillsEnabledResolved);
103
+ generatedPaths = await (0, apply_engine_1.processHierarchicalConfigurations)(selectedAgents, hierarchicalConfigs, verbose, dryRun, cliMcpEnabled, cliMcpStrategy, backup);
104
104
  }
105
105
  else {
106
106
  const singleConfig = await (0, apply_engine_1.loadSingleConfiguration)(projectRoot, configPath, localOnly);
@@ -117,7 +117,7 @@ async function applyAllAgentConfigs(projectRoot, includedAgents, configPath, cli
117
117
  const { propagateSkills } = await Promise.resolve().then(() => __importStar(require('./core/SkillsProcessor')));
118
118
  await propagateSkills(projectRoot, selectedAgents, skillsEnabledResolved, verbose, dryRun);
119
119
  }
120
- generatedPaths = await (0, apply_engine_1.processSingleConfiguration)(selectedAgents, singleConfig, projectRoot, verbose, dryRun, cliMcpEnabled, cliMcpStrategy, backup, skillsEnabledResolved);
120
+ generatedPaths = await (0, apply_engine_1.processSingleConfiguration)(selectedAgents, singleConfig, projectRoot, verbose, dryRun, cliMcpEnabled, cliMcpStrategy, backup);
121
121
  }
122
122
  // Add skills-generated paths to gitignore if skills are enabled
123
123
  let allGeneratedPaths = generatedPaths;
package/dist/paths/mcp.js CHANGED
@@ -86,6 +86,9 @@ async function getNativeMcpPath(adapterName, projectRoot) {
86
86
  case 'Firebase Studio':
87
87
  candidates.push(path.join(projectRoot, '.idx', 'mcp.json'));
88
88
  break;
89
+ case 'Factory Droid':
90
+ candidates.push(path.join(projectRoot, '.factory', 'mcp.json'));
91
+ break;
89
92
  case 'Zed':
90
93
  // Only consider project-local Zed settings (avoid writing to user home directory)
91
94
  candidates.push(path.join(projectRoot, '.zed', 'settings.json'));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intellectronica/ruler",
3
- "version": "0.3.25",
3
+ "version": "0.3.27",
4
4
  "description": "Ruler — apply the same rules to all coding agents",
5
5
  "main": "dist/lib.js",
6
6
  "scripts": {