@intellectronica/ruler 0.3.22 → 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
@@ -570,11 +570,16 @@ Skills are specialized knowledge packages that extend AI agent capabilities with
570
570
  - **Agents with native skills support**: Skills are copied directly to each agent's native skills directory:
571
571
  - **Claude Code**: `.claude/skills/`
572
572
  - **GitHub Copilot**: `.claude/skills/` (shared with Claude Code)
573
+ - **Kilo Code**: `.claude/skills/` (shared with Claude Code)
573
574
  - **OpenAI Codex CLI**: `.codex/skills/`
574
575
  - **OpenCode**: `.opencode/skill/`
575
576
  - **Pi Coding Agent**: `.pi/skills/`
576
577
  - **Goose**: `.agents/skills/`
578
+ - **Amp**: `.agents/skills/` (shared with Goose)
577
579
  - **Mistral Vibe**: `.vibe/skills/`
580
+ - **Roo Code**: `.roo/skills/`
581
+ - **Gemini CLI**: `.gemini/skills/`
582
+ - **Cursor**: `.cursor/skills/`
578
583
  - **Other MCP-compatible agents**: Skills are copied to `.skillz/` and a Skillz MCP server is automatically configured via `uvx`
579
584
 
580
585
  ### Skills Directory Structure
@@ -630,7 +635,7 @@ For agents that support MCP but don't have native skills support, Ruler automati
630
635
  2. Configures a Skillz MCP server in the agent's configuration
631
636
  3. Uses `uvx` to launch the server with the absolute path to `.skillz`
632
637
 
633
- Agents using native skills support (Claude Code, GitHub Copilot, OpenAI Codex CLI, OpenCode, Pi Coding Agent, Goose, and Mistral Vibe) **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.
634
639
 
635
640
  Example auto-generated MCP server configuration:
636
641
 
@@ -644,19 +649,22 @@ args = ["skillz@latest", "/absolute/path/to/project/.skillz"]
644
649
 
645
650
  When skills support is enabled and gitignore integration is active, Ruler automatically adds:
646
651
 
647
- - `.claude/skills/` (for Claude Code and GitHub Copilot)
652
+ - `.claude/skills/` (for Claude Code, GitHub Copilot, and Kilo Code)
648
653
  - `.codex/skills/` (for OpenAI Codex CLI)
649
654
  - `.opencode/skill/` (for OpenCode)
650
655
  - `.pi/skills/` (for Pi Coding Agent)
651
- - `.agents/skills/` (for Goose)
656
+ - `.agents/skills/` (for Goose and Amp)
652
657
  - `.vibe/skills/` (for Mistral Vibe)
658
+ - `.roo/skills/` (for Roo Code)
659
+ - `.gemini/skills/` (for Gemini CLI)
660
+ - `.cursor/skills/` (for Cursor)
653
661
  - `.skillz/` (for other MCP-based agents)
654
662
 
655
663
  to your `.gitignore` file within the managed Ruler block.
656
664
 
657
665
  ### Requirements
658
666
 
659
- - **For agents with native skills support** (Claude Code, GitHub Copilot, OpenAI Codex CLI, OpenCode, Pi Coding Agent, Goose, Mistral Vibe): 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
660
668
  - **For other MCP agents**: `uv` must be installed and available in your PATH
661
669
  ```bash
662
670
  # Install uv if needed
@@ -704,12 +712,15 @@ EOF
704
712
  ruler apply
705
713
 
706
714
  # 3. Skills are now available to compatible agents:
707
- # - Claude Code & GitHub Copilot: .claude/skills/my-skill/
715
+ # - Claude Code, GitHub Copilot & Kilo Code: .claude/skills/my-skill/
708
716
  # - OpenAI Codex CLI: .codex/skills/my-skill/
709
717
  # - OpenCode: .opencode/skill/my-skill/
710
718
  # - Pi Coding Agent: .pi/skills/my-skill/
711
- # - Goose: .agents/skills/my-skill/
719
+ # - Goose & Amp: .agents/skills/my-skill/
712
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/
713
724
  # - Other MCP agents: .skillz/my-skill/ + Skillz MCP server configured
714
725
  ```
715
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;
@@ -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;
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.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.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;
@@ -58,6 +58,9 @@ exports.OPENCODE_SKILLS_PATH = '.opencode/skill';
58
58
  exports.PI_SKILLS_PATH = '.pi/skills';
59
59
  exports.GOOSE_SKILLS_PATH = '.agents/skills';
60
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';
61
64
  exports.SKILLZ_DIR = '.skillz';
62
65
  exports.SKILL_MD_FILENAME = 'SKILL.md';
63
66
  exports.SKILLZ_MCP_SERVER_NAME = 'skillz';
@@ -42,6 +42,9 @@ exports.propagateSkillsForOpenCode = propagateSkillsForOpenCode;
42
42
  exports.propagateSkillsForPi = propagateSkillsForPi;
43
43
  exports.propagateSkillsForGoose = propagateSkillsForGoose;
44
44
  exports.propagateSkillsForVibe = propagateSkillsForVibe;
45
+ exports.propagateSkillsForRoo = propagateSkillsForRoo;
46
+ exports.propagateSkillsForGemini = propagateSkillsForGemini;
47
+ exports.propagateSkillsForCursor = propagateSkillsForCursor;
45
48
  exports.propagateSkillsForSkillz = propagateSkillsForSkillz;
46
49
  exports.buildSkillzMcpConfig = buildSkillzMcpConfig;
47
50
  const path = __importStar(require("path"));
@@ -79,7 +82,7 @@ async function getSkillsGitignorePaths(projectRoot) {
79
82
  return [];
80
83
  }
81
84
  // Import here to avoid circular dependency
82
- const { CLAUDE_SKILLS_PATH, CODEX_SKILLS_PATH, OPENCODE_SKILLS_PATH, PI_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')));
83
86
  return [
84
87
  path.join(projectRoot, CLAUDE_SKILLS_PATH),
85
88
  path.join(projectRoot, CODEX_SKILLS_PATH),
@@ -87,6 +90,9 @@ async function getSkillsGitignorePaths(projectRoot) {
87
90
  path.join(projectRoot, PI_SKILLS_PATH),
88
91
  path.join(projectRoot, GOOSE_SKILLS_PATH),
89
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),
90
96
  path.join(projectRoot, SKILLZ_DIR),
91
97
  ];
92
98
  }
@@ -120,6 +126,9 @@ async function cleanupSkillsDirectories(projectRoot, dryRun, verbose) {
120
126
  const piSkillsPath = path.join(projectRoot, constants_1.PI_SKILLS_PATH);
121
127
  const gooseSkillsPath = path.join(projectRoot, constants_1.GOOSE_SKILLS_PATH);
122
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);
123
132
  const skillzPath = path.join(projectRoot, constants_1.SKILLZ_DIR);
124
133
  // Clean up .claude/skills
125
134
  try {
@@ -205,6 +214,48 @@ async function cleanupSkillsDirectories(projectRoot, dryRun, verbose) {
205
214
  catch {
206
215
  // Directory doesn't exist, nothing to clean
207
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
+ }
208
259
  // Clean up .skillz
209
260
  try {
210
261
  await fs.access(skillzPath);
@@ -263,7 +314,7 @@ async function propagateSkills(projectRoot, agents, skillsEnabled, verbose, dryR
263
314
  }
264
315
  // Copy to Claude skills directory if needed
265
316
  if (hasNativeSkillsAgent) {
266
- (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);
267
318
  await propagateSkillsForClaude(projectRoot, { dryRun });
268
319
  (0, constants_1.logVerboseInfo)(`Copying skills to ${constants_1.CODEX_SKILLS_PATH} for OpenAI Codex CLI`, verbose, dryRun);
269
320
  await propagateSkillsForCodex(projectRoot, { dryRun });
@@ -271,10 +322,16 @@ async function propagateSkills(projectRoot, agents, skillsEnabled, verbose, dryR
271
322
  await propagateSkillsForOpenCode(projectRoot, { dryRun });
272
323
  (0, constants_1.logVerboseInfo)(`Copying skills to ${constants_1.PI_SKILLS_PATH} for Pi Coding Agent`, verbose, dryRun);
273
324
  await propagateSkillsForPi(projectRoot, { dryRun });
274
- (0, constants_1.logVerboseInfo)(`Copying skills to ${constants_1.GOOSE_SKILLS_PATH} for Goose`, verbose, dryRun);
325
+ (0, constants_1.logVerboseInfo)(`Copying skills to ${constants_1.GOOSE_SKILLS_PATH} for Goose and Amp`, verbose, dryRun);
275
326
  await propagateSkillsForGoose(projectRoot, { dryRun });
276
327
  (0, constants_1.logVerboseInfo)(`Copying skills to ${constants_1.VIBE_SKILLS_PATH} for Mistral Vibe`, verbose, dryRun);
277
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 });
278
335
  }
279
336
  // Copy to .skillz directory if needed
280
337
  if (hasMcpAgent) {
@@ -582,6 +639,156 @@ async function propagateSkillsForVibe(projectRoot, options) {
582
639
  }
583
640
  return [];
584
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
+ }
585
792
  /**
586
793
  * Propagates skills for MCP agents by copying .ruler/skills to .skillz.
587
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.22",
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": {