@intellectronica/ruler 0.3.21 → 0.3.23

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
@@ -60,6 +60,7 @@ Ruler solves this by providing a **single source of truth** for all your AI agen
60
60
  | GitHub Copilot | `AGENTS.md` | `.vscode/mcp.json` |
61
61
  | Claude Code | `CLAUDE.md` | `.mcp.json` |
62
62
  | OpenAI Codex CLI | `AGENTS.md` | `.codex/config.toml` |
63
+ | Pi Coding Agent | `AGENTS.md` | - |
63
64
  | Jules | `AGENTS.md` | - |
64
65
  | Cursor | `AGENTS.md` | `.cursor/mcp.json` |
65
66
  | Windsurf | `AGENTS.md` | `.windsurf/mcp_config.json` |
@@ -317,15 +318,15 @@ ruler revert [options]
317
318
 
318
319
  ### Options
319
320
 
320
- | Option | Description |
321
- | ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
322
- | `--project-root <path>` | Path to your project's root (default: current directory) |
323
- | `--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, qwen, roo, trae, warp, windsurf, zed) |
324
- | `--config <path>` | Path to a custom `ruler.toml` configuration file |
325
- | `--keep-backups` | Keep backup files (.bak) after restoration (default: false) |
326
- | `--dry-run` | Preview changes without actually reverting files |
327
- | `--verbose` / `-v` | Display detailed output during execution |
328
- | `--local-only` | Only search for local .ruler directories, ignore global config |
321
+ | Option | Description |
322
+ | ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
323
+ | `--project-root <path>` | Path to your project's root (default: current directory) |
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 |
329
330
 
330
331
  ### Common Examples
331
332
 
@@ -569,9 +570,16 @@ Skills are specialized knowledge packages that extend AI agent capabilities with
569
570
  - **Agents with native skills support**: Skills are copied directly to each agent's native skills directory:
570
571
  - **Claude Code**: `.claude/skills/`
571
572
  - **GitHub Copilot**: `.claude/skills/` (shared with Claude Code)
573
+ - **Kilo Code**: `.claude/skills/` (shared with Claude Code)
572
574
  - **OpenAI Codex CLI**: `.codex/skills/`
573
575
  - **OpenCode**: `.opencode/skill/`
576
+ - **Pi Coding Agent**: `.pi/skills/`
574
577
  - **Goose**: `.agents/skills/`
578
+ - **Amp**: `.agents/skills/` (shared with Goose)
579
+ - **Mistral Vibe**: `.vibe/skills/`
580
+ - **Roo Code**: `.roo/skills/`
581
+ - **Gemini CLI**: `.gemini/skills/`
582
+ - **Cursor**: `.cursor/skills/`
575
583
  - **Other MCP-compatible agents**: Skills are copied to `.skillz/` and a Skillz MCP server is automatically configured via `uvx`
576
584
 
577
585
  ### Skills Directory Structure
@@ -627,7 +635,7 @@ For agents that support MCP but don't have native skills support, Ruler automati
627
635
  2. Configures a Skillz MCP server in the agent's configuration
628
636
  3. Uses `uvx` to launch the server with the absolute path to `.skillz`
629
637
 
630
- Agents using native skills support (Claude Code, GitHub Copilot, OpenAI Codex CLI, OpenCode, and Goose) **do not** use the Skillz MCP server and instead use their own native skills directories.
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.
631
639
 
632
640
  Example auto-generated MCP server configuration:
633
641
 
@@ -641,17 +649,22 @@ args = ["skillz@latest", "/absolute/path/to/project/.skillz"]
641
649
 
642
650
  When skills support is enabled and gitignore integration is active, Ruler automatically adds:
643
651
 
644
- - `.claude/skills/` (for Claude Code and GitHub Copilot)
652
+ - `.claude/skills/` (for Claude Code, GitHub Copilot, and Kilo Code)
645
653
  - `.codex/skills/` (for OpenAI Codex CLI)
646
654
  - `.opencode/skill/` (for OpenCode)
647
- - `.agents/skills/` (for Goose)
655
+ - `.pi/skills/` (for Pi Coding Agent)
656
+ - `.agents/skills/` (for Goose and Amp)
657
+ - `.vibe/skills/` (for Mistral Vibe)
658
+ - `.roo/skills/` (for Roo Code)
659
+ - `.gemini/skills/` (for Gemini CLI)
660
+ - `.cursor/skills/` (for Cursor)
648
661
  - `.skillz/` (for other MCP-based agents)
649
662
 
650
663
  to your `.gitignore` file within the managed Ruler block.
651
664
 
652
665
  ### Requirements
653
666
 
654
- - **For agents with native skills support** (Claude Code, GitHub Copilot, OpenAI Codex CLI, OpenCode, Goose): No additional requirements
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
655
668
  - **For other MCP agents**: `uv` must be installed and available in your PATH
656
669
  ```bash
657
670
  # Install uv if needed
@@ -699,10 +712,15 @@ EOF
699
712
  ruler apply
700
713
 
701
714
  # 3. Skills are now available to compatible agents:
702
- # - Claude Code & GitHub Copilot: .claude/skills/my-skill/
715
+ # - Claude Code, GitHub Copilot & Kilo Code: .claude/skills/my-skill/
703
716
  # - OpenAI Codex CLI: .codex/skills/my-skill/
704
717
  # - OpenCode: .opencode/skill/my-skill/
705
- # - Goose: .agents/skills/my-skill/
718
+ # - Pi Coding Agent: .pi/skills/my-skill/
719
+ # - Goose & Amp: .agents/skills/my-skill/
720
+ # - Mistral Vibe: .vibe/skills/my-skill/
721
+ # - Roo Code: .roo/skills/my-skill/
722
+ # - Gemini CLI: .gemini/skills/my-skill/
723
+ # - Cursor: .cursor/skills/my-skill/
706
724
  # - Other MCP agents: .skillz/my-skill/ + Skillz MCP server configured
707
725
  ```
708
726
 
@@ -9,5 +9,8 @@ class AmpAgent extends AgentsMdAgent_1.AgentsMdAgent {
9
9
  getName() {
10
10
  return 'Amp';
11
11
  }
12
+ supportsNativeSkills() {
13
+ return true;
14
+ }
12
15
  }
13
16
  exports.AmpAgent = AmpAgent;
@@ -30,5 +30,8 @@ class CursorAgent extends AgentsMdAgent_1.AgentsMdAgent {
30
30
  supportsMcpRemote() {
31
31
  return true;
32
32
  }
33
+ supportsNativeSkills() {
34
+ return true;
35
+ }
33
36
  }
34
37
  exports.CursorAgent = CursorAgent;
@@ -113,5 +113,8 @@ class GeminiCliAgent extends AgentsMdAgent_1.AgentsMdAgent {
113
113
  supportsMcpRemote() {
114
114
  return true;
115
115
  }
116
+ supportsNativeSkills() {
117
+ return true;
118
+ }
116
119
  }
117
120
  exports.GeminiCliAgent = GeminiCliAgent;
@@ -59,5 +59,8 @@ class KiloCodeAgent extends AbstractAgent_1.AbstractAgent {
59
59
  supportsMcpRemote() {
60
60
  return true;
61
61
  }
62
+ supportsNativeSkills() {
63
+ return true;
64
+ }
62
65
  }
63
66
  exports.KiloCodeAgent = KiloCodeAgent;
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PiAgent = void 0;
4
+ const AgentsMdAgent_1 = require("./AgentsMdAgent");
5
+ /**
6
+ * Pi Coding Agent adapter.
7
+ */
8
+ class PiAgent extends AgentsMdAgent_1.AgentsMdAgent {
9
+ getIdentifier() {
10
+ return 'pi';
11
+ }
12
+ getName() {
13
+ return 'Pi Coding Agent';
14
+ }
15
+ supportsNativeSkills() {
16
+ return true;
17
+ }
18
+ }
19
+ exports.PiAgent = PiAgent;
@@ -135,5 +135,8 @@ class RooCodeAgent {
135
135
  getMcpServerKey() {
136
136
  return 'mcpServers';
137
137
  }
138
+ supportsNativeSkills() {
139
+ return true;
140
+ }
138
141
  }
139
142
  exports.RooCodeAgent = RooCodeAgent;
@@ -33,6 +33,7 @@ const AmazonQCliAgent_1 = require("./AmazonQCliAgent");
33
33
  const FirebenderAgent_1 = require("./FirebenderAgent");
34
34
  const AntigravityAgent_1 = require("./AntigravityAgent");
35
35
  const MistralVibeAgent_1 = require("./MistralVibeAgent");
36
+ const PiAgent_1 = require("./PiAgent");
36
37
  exports.allAgents = [
37
38
  new CopilotAgent_1.CopilotAgent(),
38
39
  new ClaudeAgent_1.ClaudeAgent(),
@@ -63,6 +64,7 @@ exports.allAgents = [
63
64
  new FirebenderAgent_1.FirebenderAgent(),
64
65
  new AntigravityAgent_1.AntigravityAgent(),
65
66
  new MistralVibeAgent_1.MistralVibeAgent(),
67
+ new PiAgent_1.PiAgent(),
66
68
  ];
67
69
  /**
68
70
  * Generates a comma-separated list of agent identifiers for CLI help text.
@@ -152,7 +152,7 @@ async function initHandler(argv) {
152
152
 
153
153
  # --- Agent Specific Configurations ---
154
154
  # You can enable/disable agents and override their default output paths here.
155
- # Use lowercase agent identifiers: amp, copilot, claude, codex, cursor, windsurf, cline, aider, kilocode
155
+ # Use lowercase agent identifiers: aider, amp, claude, cline, codex, copilot, cursor, kilocode, pi, windsurf
156
156
 
157
157
  # [agents.copilot]
158
158
  # enabled = true
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.VIBE_SKILLS_PATH = exports.GOOSE_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.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;
4
4
  exports.actionPrefix = actionPrefix;
5
5
  exports.createRulerError = createRulerError;
6
6
  exports.logVerbose = logVerbose;
@@ -55,8 +55,12 @@ exports.RULER_SKILLS_PATH = '.ruler/skills';
55
55
  exports.CLAUDE_SKILLS_PATH = '.claude/skills';
56
56
  exports.CODEX_SKILLS_PATH = '.codex/skills';
57
57
  exports.OPENCODE_SKILLS_PATH = '.opencode/skill';
58
+ exports.PI_SKILLS_PATH = '.pi/skills';
58
59
  exports.GOOSE_SKILLS_PATH = '.agents/skills';
59
60
  exports.VIBE_SKILLS_PATH = '.vibe/skills';
61
+ exports.ROO_SKILLS_PATH = '.roo/skills';
62
+ exports.GEMINI_SKILLS_PATH = '.gemini/skills';
63
+ exports.CURSOR_SKILLS_PATH = '.cursor/skills';
60
64
  exports.SKILLZ_DIR = '.skillz';
61
65
  exports.SKILL_MD_FILENAME = 'SKILL.md';
62
66
  exports.SKILLZ_MCP_SERVER_NAME = 'skillz';
@@ -39,8 +39,12 @@ exports.propagateSkills = propagateSkills;
39
39
  exports.propagateSkillsForClaude = propagateSkillsForClaude;
40
40
  exports.propagateSkillsForCodex = propagateSkillsForCodex;
41
41
  exports.propagateSkillsForOpenCode = propagateSkillsForOpenCode;
42
+ exports.propagateSkillsForPi = propagateSkillsForPi;
42
43
  exports.propagateSkillsForGoose = propagateSkillsForGoose;
43
44
  exports.propagateSkillsForVibe = propagateSkillsForVibe;
45
+ exports.propagateSkillsForRoo = propagateSkillsForRoo;
46
+ exports.propagateSkillsForGemini = propagateSkillsForGemini;
47
+ exports.propagateSkillsForCursor = propagateSkillsForCursor;
44
48
  exports.propagateSkillsForSkillz = propagateSkillsForSkillz;
45
49
  exports.buildSkillzMcpConfig = buildSkillzMcpConfig;
46
50
  const path = __importStar(require("path"));
@@ -78,13 +82,17 @@ async function getSkillsGitignorePaths(projectRoot) {
78
82
  return [];
79
83
  }
80
84
  // Import here to avoid circular dependency
81
- const { CLAUDE_SKILLS_PATH, CODEX_SKILLS_PATH, OPENCODE_SKILLS_PATH, GOOSE_SKILLS_PATH, VIBE_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, SKILLZ_DIR, } = await Promise.resolve().then(() => __importStar(require('../constants')));
82
86
  return [
83
87
  path.join(projectRoot, CLAUDE_SKILLS_PATH),
84
88
  path.join(projectRoot, CODEX_SKILLS_PATH),
85
89
  path.join(projectRoot, OPENCODE_SKILLS_PATH),
90
+ path.join(projectRoot, PI_SKILLS_PATH),
86
91
  path.join(projectRoot, GOOSE_SKILLS_PATH),
87
92
  path.join(projectRoot, VIBE_SKILLS_PATH),
93
+ path.join(projectRoot, ROO_SKILLS_PATH),
94
+ path.join(projectRoot, GEMINI_SKILLS_PATH),
95
+ path.join(projectRoot, CURSOR_SKILLS_PATH),
88
96
  path.join(projectRoot, SKILLZ_DIR),
89
97
  ];
90
98
  }
@@ -115,8 +123,12 @@ async function cleanupSkillsDirectories(projectRoot, dryRun, verbose) {
115
123
  const claudeSkillsPath = path.join(projectRoot, constants_1.CLAUDE_SKILLS_PATH);
116
124
  const codexSkillsPath = path.join(projectRoot, constants_1.CODEX_SKILLS_PATH);
117
125
  const opencodeSkillsPath = path.join(projectRoot, constants_1.OPENCODE_SKILLS_PATH);
126
+ const piSkillsPath = path.join(projectRoot, constants_1.PI_SKILLS_PATH);
118
127
  const gooseSkillsPath = path.join(projectRoot, constants_1.GOOSE_SKILLS_PATH);
119
128
  const vibeSkillsPath = path.join(projectRoot, constants_1.VIBE_SKILLS_PATH);
129
+ const rooSkillsPath = path.join(projectRoot, constants_1.ROO_SKILLS_PATH);
130
+ const geminiSkillsPath = path.join(projectRoot, constants_1.GEMINI_SKILLS_PATH);
131
+ const cursorSkillsPath = path.join(projectRoot, constants_1.CURSOR_SKILLS_PATH);
120
132
  const skillzPath = path.join(projectRoot, constants_1.SKILLZ_DIR);
121
133
  // Clean up .claude/skills
122
134
  try {
@@ -160,6 +172,20 @@ async function cleanupSkillsDirectories(projectRoot, dryRun, verbose) {
160
172
  catch {
161
173
  // Directory doesn't exist, nothing to clean
162
174
  }
175
+ // Clean up .pi/skills
176
+ try {
177
+ await fs.access(piSkillsPath);
178
+ if (dryRun) {
179
+ (0, constants_1.logVerboseInfo)(`DRY RUN: Would remove ${constants_1.PI_SKILLS_PATH}`, verbose, dryRun);
180
+ }
181
+ else {
182
+ await fs.rm(piSkillsPath, { recursive: true, force: true });
183
+ (0, constants_1.logVerboseInfo)(`Removed ${constants_1.PI_SKILLS_PATH} (skills disabled)`, verbose, dryRun);
184
+ }
185
+ }
186
+ catch {
187
+ // Directory doesn't exist, nothing to clean
188
+ }
163
189
  // Clean up .agents/skills
164
190
  try {
165
191
  await fs.access(gooseSkillsPath);
@@ -188,6 +214,48 @@ async function cleanupSkillsDirectories(projectRoot, dryRun, verbose) {
188
214
  catch {
189
215
  // Directory doesn't exist, nothing to clean
190
216
  }
217
+ // Clean up .roo/skills
218
+ try {
219
+ await fs.access(rooSkillsPath);
220
+ if (dryRun) {
221
+ (0, constants_1.logVerboseInfo)(`DRY RUN: Would remove ${constants_1.ROO_SKILLS_PATH}`, verbose, dryRun);
222
+ }
223
+ else {
224
+ await fs.rm(rooSkillsPath, { recursive: true, force: true });
225
+ (0, constants_1.logVerboseInfo)(`Removed ${constants_1.ROO_SKILLS_PATH} (skills disabled)`, verbose, dryRun);
226
+ }
227
+ }
228
+ catch {
229
+ // Directory doesn't exist, nothing to clean
230
+ }
231
+ // Clean up .gemini/skills
232
+ try {
233
+ await fs.access(geminiSkillsPath);
234
+ if (dryRun) {
235
+ (0, constants_1.logVerboseInfo)(`DRY RUN: Would remove ${constants_1.GEMINI_SKILLS_PATH}`, verbose, dryRun);
236
+ }
237
+ else {
238
+ await fs.rm(geminiSkillsPath, { recursive: true, force: true });
239
+ (0, constants_1.logVerboseInfo)(`Removed ${constants_1.GEMINI_SKILLS_PATH} (skills disabled)`, verbose, dryRun);
240
+ }
241
+ }
242
+ catch {
243
+ // Directory doesn't exist, nothing to clean
244
+ }
245
+ // Clean up .cursor/skills
246
+ try {
247
+ await fs.access(cursorSkillsPath);
248
+ if (dryRun) {
249
+ (0, constants_1.logVerboseInfo)(`DRY RUN: Would remove ${constants_1.CURSOR_SKILLS_PATH}`, verbose, dryRun);
250
+ }
251
+ else {
252
+ await fs.rm(cursorSkillsPath, { recursive: true, force: true });
253
+ (0, constants_1.logVerboseInfo)(`Removed ${constants_1.CURSOR_SKILLS_PATH} (skills disabled)`, verbose, dryRun);
254
+ }
255
+ }
256
+ catch {
257
+ // Directory doesn't exist, nothing to clean
258
+ }
191
259
  // Clean up .skillz
192
260
  try {
193
261
  await fs.access(skillzPath);
@@ -246,16 +314,24 @@ async function propagateSkills(projectRoot, agents, skillsEnabled, verbose, dryR
246
314
  }
247
315
  // Copy to Claude skills directory if needed
248
316
  if (hasNativeSkillsAgent) {
249
- (0, constants_1.logVerboseInfo)(`Copying skills to ${constants_1.CLAUDE_SKILLS_PATH} for Claude Code and GitHub Copilot`, verbose, dryRun);
317
+ (0, constants_1.logVerboseInfo)(`Copying skills to ${constants_1.CLAUDE_SKILLS_PATH} for Claude Code, GitHub Copilot, and Kilo Code`, verbose, dryRun);
250
318
  await propagateSkillsForClaude(projectRoot, { dryRun });
251
319
  (0, constants_1.logVerboseInfo)(`Copying skills to ${constants_1.CODEX_SKILLS_PATH} for OpenAI Codex CLI`, verbose, dryRun);
252
320
  await propagateSkillsForCodex(projectRoot, { dryRun });
253
321
  (0, constants_1.logVerboseInfo)(`Copying skills to ${constants_1.OPENCODE_SKILLS_PATH} for OpenCode`, verbose, dryRun);
254
322
  await propagateSkillsForOpenCode(projectRoot, { dryRun });
255
- (0, constants_1.logVerboseInfo)(`Copying skills to ${constants_1.GOOSE_SKILLS_PATH} for Goose`, verbose, dryRun);
323
+ (0, constants_1.logVerboseInfo)(`Copying skills to ${constants_1.PI_SKILLS_PATH} for Pi Coding Agent`, verbose, dryRun);
324
+ await propagateSkillsForPi(projectRoot, { dryRun });
325
+ (0, constants_1.logVerboseInfo)(`Copying skills to ${constants_1.GOOSE_SKILLS_PATH} for Goose and Amp`, verbose, dryRun);
256
326
  await propagateSkillsForGoose(projectRoot, { dryRun });
257
327
  (0, constants_1.logVerboseInfo)(`Copying skills to ${constants_1.VIBE_SKILLS_PATH} for Mistral Vibe`, verbose, dryRun);
258
328
  await propagateSkillsForVibe(projectRoot, { dryRun });
329
+ (0, constants_1.logVerboseInfo)(`Copying skills to ${constants_1.ROO_SKILLS_PATH} for Roo Code`, verbose, dryRun);
330
+ await propagateSkillsForRoo(projectRoot, { dryRun });
331
+ (0, constants_1.logVerboseInfo)(`Copying skills to ${constants_1.GEMINI_SKILLS_PATH} for Gemini CLI`, verbose, dryRun);
332
+ await propagateSkillsForGemini(projectRoot, { dryRun });
333
+ (0, constants_1.logVerboseInfo)(`Copying skills to ${constants_1.CURSOR_SKILLS_PATH} for Cursor`, verbose, dryRun);
334
+ await propagateSkillsForCursor(projectRoot, { dryRun });
259
335
  }
260
336
  // Copy to .skillz directory if needed
261
337
  if (hasMcpAgent) {
@@ -413,6 +489,56 @@ async function propagateSkillsForOpenCode(projectRoot, options) {
413
489
  }
414
490
  return [];
415
491
  }
492
+ /**
493
+ * Propagates skills for Pi Coding Agent by copying .ruler/skills to .pi/skills.
494
+ * Uses atomic replace to ensure safe overwriting of existing skills.
495
+ * Returns dry-run steps if dryRun is true, otherwise returns empty array.
496
+ */
497
+ async function propagateSkillsForPi(projectRoot, options) {
498
+ const skillsDir = path.join(projectRoot, constants_1.RULER_SKILLS_PATH);
499
+ const piSkillsPath = path.join(projectRoot, constants_1.PI_SKILLS_PATH);
500
+ const piDir = path.dirname(piSkillsPath);
501
+ // Check if source skills directory exists
502
+ try {
503
+ await fs.access(skillsDir);
504
+ }
505
+ catch {
506
+ // No skills directory - return empty
507
+ return [];
508
+ }
509
+ if (options.dryRun) {
510
+ return [`Copy skills from ${constants_1.RULER_SKILLS_PATH} to ${constants_1.PI_SKILLS_PATH}`];
511
+ }
512
+ // Ensure .pi directory exists
513
+ await fs.mkdir(piDir, { recursive: true });
514
+ // Use atomic replace: copy to temp, then rename
515
+ const tempDir = path.join(piDir, `skills.tmp-${Date.now()}`);
516
+ try {
517
+ // Copy to temp directory
518
+ await (0, SkillsUtils_1.copySkillsDirectory)(skillsDir, tempDir);
519
+ // Atomically replace the target
520
+ // First, remove existing target if it exists
521
+ try {
522
+ await fs.rm(piSkillsPath, { recursive: true, force: true });
523
+ }
524
+ catch {
525
+ // Target didn't exist, that's fine
526
+ }
527
+ // Rename temp to target
528
+ await fs.rename(tempDir, piSkillsPath);
529
+ }
530
+ catch (error) {
531
+ // Clean up temp directory on error
532
+ try {
533
+ await fs.rm(tempDir, { recursive: true, force: true });
534
+ }
535
+ catch {
536
+ // Ignore cleanup errors
537
+ }
538
+ throw error;
539
+ }
540
+ return [];
541
+ }
416
542
  /**
417
543
  * Propagates skills for Goose by copying .ruler/skills to .agents/skills.
418
544
  * Uses atomic replace to ensure safe overwriting of existing skills.
@@ -513,6 +639,156 @@ async function propagateSkillsForVibe(projectRoot, options) {
513
639
  }
514
640
  return [];
515
641
  }
642
+ /**
643
+ * Propagates skills for Roo Code by copying .ruler/skills to .roo/skills.
644
+ * Uses atomic replace to ensure safe overwriting of existing skills.
645
+ * Returns dry-run steps if dryRun is true, otherwise returns empty array.
646
+ */
647
+ async function propagateSkillsForRoo(projectRoot, options) {
648
+ const skillsDir = path.join(projectRoot, constants_1.RULER_SKILLS_PATH);
649
+ const rooSkillsPath = path.join(projectRoot, constants_1.ROO_SKILLS_PATH);
650
+ const rooDir = path.dirname(rooSkillsPath);
651
+ // Check if source skills directory exists
652
+ try {
653
+ await fs.access(skillsDir);
654
+ }
655
+ catch {
656
+ // No skills directory - return empty
657
+ return [];
658
+ }
659
+ if (options.dryRun) {
660
+ return [`Copy skills from ${constants_1.RULER_SKILLS_PATH} to ${constants_1.ROO_SKILLS_PATH}`];
661
+ }
662
+ // Ensure .roo directory exists
663
+ await fs.mkdir(rooDir, { recursive: true });
664
+ // Use atomic replace: copy to temp, then rename
665
+ const tempDir = path.join(rooDir, `skills.tmp-${Date.now()}`);
666
+ try {
667
+ // Copy to temp directory
668
+ await (0, SkillsUtils_1.copySkillsDirectory)(skillsDir, tempDir);
669
+ // Atomically replace the target
670
+ // First, remove existing target if it exists
671
+ try {
672
+ await fs.rm(rooSkillsPath, { recursive: true, force: true });
673
+ }
674
+ catch {
675
+ // Target didn't exist, that's fine
676
+ }
677
+ // Rename temp to target
678
+ await fs.rename(tempDir, rooSkillsPath);
679
+ }
680
+ catch (error) {
681
+ // Clean up temp directory on error
682
+ try {
683
+ await fs.rm(tempDir, { recursive: true, force: true });
684
+ }
685
+ catch {
686
+ // Ignore cleanup errors
687
+ }
688
+ throw error;
689
+ }
690
+ return [];
691
+ }
692
+ /**
693
+ * Propagates skills for Gemini CLI by copying .ruler/skills to .gemini/skills.
694
+ * Uses atomic replace to ensure safe overwriting of existing skills.
695
+ * Returns dry-run steps if dryRun is true, otherwise returns empty array.
696
+ */
697
+ async function propagateSkillsForGemini(projectRoot, options) {
698
+ const skillsDir = path.join(projectRoot, constants_1.RULER_SKILLS_PATH);
699
+ const geminiSkillsPath = path.join(projectRoot, constants_1.GEMINI_SKILLS_PATH);
700
+ const geminiDir = path.dirname(geminiSkillsPath);
701
+ // Check if source skills directory exists
702
+ try {
703
+ await fs.access(skillsDir);
704
+ }
705
+ catch {
706
+ // No skills directory - return empty
707
+ return [];
708
+ }
709
+ if (options.dryRun) {
710
+ return [`Copy skills from ${constants_1.RULER_SKILLS_PATH} to ${constants_1.GEMINI_SKILLS_PATH}`];
711
+ }
712
+ // Ensure .gemini directory exists
713
+ await fs.mkdir(geminiDir, { recursive: true });
714
+ // Use atomic replace: copy to temp, then rename
715
+ const tempDir = path.join(geminiDir, `skills.tmp-${Date.now()}`);
716
+ try {
717
+ // Copy to temp directory
718
+ await (0, SkillsUtils_1.copySkillsDirectory)(skillsDir, tempDir);
719
+ // Atomically replace the target
720
+ // First, remove existing target if it exists
721
+ try {
722
+ await fs.rm(geminiSkillsPath, { recursive: true, force: true });
723
+ }
724
+ catch {
725
+ // Target didn't exist, that's fine
726
+ }
727
+ // Rename temp to target
728
+ await fs.rename(tempDir, geminiSkillsPath);
729
+ }
730
+ catch (error) {
731
+ // Clean up temp directory on error
732
+ try {
733
+ await fs.rm(tempDir, { recursive: true, force: true });
734
+ }
735
+ catch {
736
+ // Ignore cleanup errors
737
+ }
738
+ throw error;
739
+ }
740
+ return [];
741
+ }
742
+ /**
743
+ * Propagates skills for Cursor by copying .ruler/skills to .cursor/skills.
744
+ * Uses atomic replace to ensure safe overwriting of existing skills.
745
+ * Returns dry-run steps if dryRun is true, otherwise returns empty array.
746
+ */
747
+ async function propagateSkillsForCursor(projectRoot, options) {
748
+ const skillsDir = path.join(projectRoot, constants_1.RULER_SKILLS_PATH);
749
+ const cursorSkillsPath = path.join(projectRoot, constants_1.CURSOR_SKILLS_PATH);
750
+ const cursorDir = path.dirname(cursorSkillsPath);
751
+ // Check if source skills directory exists
752
+ try {
753
+ await fs.access(skillsDir);
754
+ }
755
+ catch {
756
+ // No skills directory - return empty
757
+ return [];
758
+ }
759
+ if (options.dryRun) {
760
+ return [`Copy skills from ${constants_1.RULER_SKILLS_PATH} to ${constants_1.CURSOR_SKILLS_PATH}`];
761
+ }
762
+ // Ensure .cursor directory exists
763
+ await fs.mkdir(cursorDir, { recursive: true });
764
+ // Use atomic replace: copy to temp, then rename
765
+ const tempDir = path.join(cursorDir, `skills.tmp-${Date.now()}`);
766
+ try {
767
+ // Copy to temp directory
768
+ await (0, SkillsUtils_1.copySkillsDirectory)(skillsDir, tempDir);
769
+ // Atomically replace the target
770
+ // First, remove existing target if it exists
771
+ try {
772
+ await fs.rm(cursorSkillsPath, { recursive: true, force: true });
773
+ }
774
+ catch {
775
+ // Target didn't exist, that's fine
776
+ }
777
+ // Rename temp to target
778
+ await fs.rename(tempDir, cursorSkillsPath);
779
+ }
780
+ catch (error) {
781
+ // Clean up temp directory on error
782
+ try {
783
+ await fs.rm(tempDir, { recursive: true, force: true });
784
+ }
785
+ catch {
786
+ // Ignore cleanup errors
787
+ }
788
+ throw error;
789
+ }
790
+ return [];
791
+ }
516
792
  /**
517
793
  * Propagates skills for MCP agents by copying .ruler/skills to .skillz.
518
794
  * Uses atomic replace to ensure safe overwriting of existing skills.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intellectronica/ruler",
3
- "version": "0.3.21",
3
+ "version": "0.3.23",
4
4
  "description": "Ruler — apply the same rules to all coding agents",
5
5
  "main": "dist/lib.js",
6
6
  "scripts": {