@intellectronica/ruler 0.3.36 → 0.3.38

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
@@ -63,7 +63,7 @@ Ruler solves this by providing a **single source of truth** for all your AI agen
63
63
  | Pi Coding Agent | `AGENTS.md` | - | `.pi/skills/` |
64
64
  | Jules | `AGENTS.md` | - | - |
65
65
  | Cursor | `AGENTS.md` | `.cursor/mcp.json` | `.cursor/skills/` |
66
- | Windsurf | `AGENTS.md` | `.windsurf/mcp_config.json` | - |
66
+ | Windsurf | `AGENTS.md` | `.windsurf/mcp_config.json` | `.windsurf/skills/` |
67
67
  | Cline | `.clinerules` | - | - |
68
68
  | Crush | `CRUSH.md` | `.crush.json` | - |
69
69
  | Amp | `AGENTS.md` | - | `.agents/skills/` |
@@ -73,7 +73,7 @@ Ruler solves this by providing a **single source of truth** for all your AI agen
73
73
  | Firebase Studio | `.idx/airules.md` | `.idx/mcp.json` | - |
74
74
  | Open Hands | `.openhands/microagents/repo.md` | `config.toml` | - |
75
75
  | Gemini CLI | `AGENTS.md` | `.gemini/settings.json` | `.gemini/skills/` |
76
- | Junie | `.junie/guidelines.md` | `.junie/mcp/mcp.json` | - |
76
+ | Junie | `.junie/guidelines.md` | `.junie/mcp/mcp.json` | `.junie/skills/` |
77
77
  | AugmentCode | `.augment/rules/ruler_augment_instructions.md` | - | - |
78
78
  | Kilo Code | `AGENTS.md` | `.kilocode/mcp.json` | `.claude/skills/` |
79
79
  | OpenCode | `AGENTS.md` | `opencode.json` | `.opencode/skills/` |
@@ -325,15 +325,15 @@ ruler revert [options]
325
325
 
326
326
  ### Options
327
327
 
328
- | Option | Description |
329
- | ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
330
- | `--project-root <path>` | Path to your project's root (default: current directory) |
328
+ | Option | Description |
329
+ | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
330
+ | `--project-root <path>` | Path to your project's root (default: current directory) |
331
331
  | `--agents <agent1,agent2,...>` | Comma-separated list of agent names to revert (agentsmd, aider, amazonqcli, amp, antigravity, augmentcode, claude, cline, codex, copilot, crush, cursor, factory, firebase, firebender, gemini-cli, goose, jetbrains-ai, jules, junie, kilocode, kiro, mistral, opencode, openhands, pi, qwen, roo, trae, warp, windsurf, zed) |
332
- | `--config <path>` | Path to a custom `ruler.toml` configuration file |
333
- | `--keep-backups` | Keep backup files (.bak) after restoration (default: false) |
334
- | `--dry-run` | Preview changes without actually reverting files |
335
- | `--verbose` / `-v` | Display detailed output during execution |
336
- | `--local-only` | Only search for local .ruler directories, ignore global config |
332
+ | `--config <path>` | Path to a custom `ruler.toml` configuration file |
333
+ | `--keep-backups` | Keep backup files (.bak) after restoration (default: false) |
334
+ | `--dry-run` | Preview changes without actually reverting files |
335
+ | `--verbose` / `-v` | Display detailed output during execution |
336
+ | `--local-only` | Only search for local .ruler directories, ignore global config |
337
337
 
338
338
  ### Common Examples
339
339
 
@@ -594,7 +594,9 @@ Skills are specialized knowledge packages that extend AI agent capabilities with
594
594
  - **Mistral Vibe**: `.vibe/skills/`
595
595
  - **Roo Code**: `.roo/skills/`
596
596
  - **Gemini CLI**: `.gemini/skills/`
597
+ - **Junie**: `.junie/skills/`
597
598
  - **Cursor**: `.cursor/skills/`
599
+ - **Windsurf**: `.windsurf/skills/`
598
600
 
599
601
  ### Skills Directory Structure
600
602
 
@@ -659,13 +661,14 @@ When skills support is enabled and gitignore integration is active, Ruler automa
659
661
  - `.vibe/skills/` (for Mistral Vibe)
660
662
  - `.roo/skills/` (for Roo Code)
661
663
  - `.gemini/skills/` (for Gemini CLI)
664
+ - `.junie/skills/` (for Junie)
662
665
  - `.cursor/skills/` (for Cursor)
663
666
 
664
667
  to your `.gitignore` file within the managed Ruler block.
665
668
 
666
669
  ### Requirements
667
670
 
668
- - **For agents with native skills support** (Claude Code, GitHub Copilot, Kilo Code, OpenAI Codex CLI, OpenCode, Pi Coding Agent, Goose, Amp, Antigravity, Factory Droid, Mistral Vibe, Roo Code, Gemini CLI, Cursor): No additional requirements.
671
+ - **For agents with native skills support** (Claude Code, GitHub Copilot, Kilo Code, OpenAI Codex CLI, OpenCode, Pi Coding Agent, Goose, Amp, Antigravity, Factory Droid, Mistral Vibe, Roo Code, Gemini CLI, Junie, Cursor): No additional requirements.
669
672
 
670
673
  ### Validation
671
674
 
@@ -718,6 +721,7 @@ ruler apply
718
721
  # - Mistral Vibe: .vibe/skills/my-skill/
719
722
  # - Roo Code: .roo/skills/my-skill/
720
723
  # - Gemini CLI: .gemini/skills/my-skill/
724
+ # - Junie: .junie/skills/my-skill/
721
725
  # - Cursor: .cursor/skills/my-skill/
722
726
  ```
723
727
 
@@ -55,5 +55,8 @@ class JunieAgent extends AbstractAgent_1.AbstractAgent {
55
55
  supportsMcpRemote() {
56
56
  return true;
57
57
  }
58
+ supportsNativeSkills() {
59
+ return true;
60
+ }
58
61
  }
59
62
  exports.JunieAgent = JunieAgent;
@@ -23,5 +23,8 @@ class WindsurfAgent extends AgentsMdAgent_1.AgentsMdAgent {
23
23
  supportsMcpRemote() {
24
24
  return true;
25
25
  }
26
+ supportsNativeSkills() {
27
+ return true;
28
+ }
26
29
  }
27
30
  exports.WindsurfAgent = WindsurfAgent;
package/dist/constants.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
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;
3
+ exports.SKILL_MD_FILENAME = exports.ANTIGRAVITY_SKILLS_PATH = exports.FACTORY_SKILLS_PATH = exports.WINDSURF_SKILLS_PATH = exports.CURSOR_SKILLS_PATH = exports.JUNIE_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;
@@ -60,7 +60,9 @@ exports.GOOSE_SKILLS_PATH = '.agents/skills';
60
60
  exports.VIBE_SKILLS_PATH = '.vibe/skills';
61
61
  exports.ROO_SKILLS_PATH = '.roo/skills';
62
62
  exports.GEMINI_SKILLS_PATH = '.gemini/skills';
63
+ exports.JUNIE_SKILLS_PATH = '.junie/skills';
63
64
  exports.CURSOR_SKILLS_PATH = '.cursor/skills';
65
+ exports.WINDSURF_SKILLS_PATH = '.windsurf/skills';
64
66
  exports.FACTORY_SKILLS_PATH = '.factory/skills';
65
67
  exports.ANTIGRAVITY_SKILLS_PATH = '.agent/skills';
66
68
  exports.SKILL_MD_FILENAME = 'SKILL.md';
@@ -44,7 +44,9 @@ exports.propagateSkillsForGoose = propagateSkillsForGoose;
44
44
  exports.propagateSkillsForVibe = propagateSkillsForVibe;
45
45
  exports.propagateSkillsForRoo = propagateSkillsForRoo;
46
46
  exports.propagateSkillsForGemini = propagateSkillsForGemini;
47
+ exports.propagateSkillsForJunie = propagateSkillsForJunie;
47
48
  exports.propagateSkillsForCursor = propagateSkillsForCursor;
49
+ exports.propagateSkillsForWindsurf = propagateSkillsForWindsurf;
48
50
  exports.propagateSkillsForFactory = propagateSkillsForFactory;
49
51
  exports.propagateSkillsForAntigravity = propagateSkillsForAntigravity;
50
52
  const path = __importStar(require("path"));
@@ -82,7 +84,7 @@ async function getSkillsGitignorePaths(projectRoot, agents) {
82
84
  return [];
83
85
  }
84
86
  // 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, FACTORY_SKILLS_PATH, ANTIGRAVITY_SKILLS_PATH, } = await Promise.resolve().then(() => __importStar(require('../constants')));
87
+ 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, JUNIE_SKILLS_PATH, CURSOR_SKILLS_PATH, WINDSURF_SKILLS_PATH, FACTORY_SKILLS_PATH, ANTIGRAVITY_SKILLS_PATH, } = await Promise.resolve().then(() => __importStar(require('../constants')));
86
88
  const selectedTargets = getSelectedSkillTargets(agents);
87
89
  const targetPaths = {
88
90
  claude: CLAUDE_SKILLS_PATH,
@@ -93,7 +95,9 @@ async function getSkillsGitignorePaths(projectRoot, agents) {
93
95
  vibe: VIBE_SKILLS_PATH,
94
96
  roo: ROO_SKILLS_PATH,
95
97
  gemini: GEMINI_SKILLS_PATH,
98
+ junie: JUNIE_SKILLS_PATH,
96
99
  cursor: CURSOR_SKILLS_PATH,
100
+ windsurf: WINDSURF_SKILLS_PATH,
97
101
  factory: FACTORY_SKILLS_PATH,
98
102
  antigravity: ANTIGRAVITY_SKILLS_PATH,
99
103
  };
@@ -126,7 +130,9 @@ const SKILL_TARGET_TO_IDENTIFIERS = new Map([
126
130
  ['vibe', ['mistral']],
127
131
  ['roo', ['roo']],
128
132
  ['gemini', ['gemini-cli']],
133
+ ['junie', ['junie']],
129
134
  ['cursor', ['cursor']],
135
+ ['windsurf', ['windsurf']],
130
136
  ['factory', ['factory']],
131
137
  ['antigravity', ['antigravity']],
132
138
  ]);
@@ -155,7 +161,9 @@ async function cleanupSkillsDirectories(projectRoot, dryRun, verbose) {
155
161
  const vibeSkillsPath = path.join(projectRoot, constants_1.VIBE_SKILLS_PATH);
156
162
  const rooSkillsPath = path.join(projectRoot, constants_1.ROO_SKILLS_PATH);
157
163
  const geminiSkillsPath = path.join(projectRoot, constants_1.GEMINI_SKILLS_PATH);
164
+ const junieSkillsPath = path.join(projectRoot, constants_1.JUNIE_SKILLS_PATH);
158
165
  const cursorSkillsPath = path.join(projectRoot, constants_1.CURSOR_SKILLS_PATH);
166
+ const windsurfSkillsPath = path.join(projectRoot, constants_1.WINDSURF_SKILLS_PATH);
159
167
  const factorySkillsPath = path.join(projectRoot, constants_1.FACTORY_SKILLS_PATH);
160
168
  const antigravitySkillsPath = path.join(projectRoot, constants_1.ANTIGRAVITY_SKILLS_PATH);
161
169
  // Clean up .claude/skills
@@ -270,6 +278,20 @@ async function cleanupSkillsDirectories(projectRoot, dryRun, verbose) {
270
278
  catch {
271
279
  // Directory doesn't exist, nothing to clean
272
280
  }
281
+ // Clean up .junie/skills
282
+ try {
283
+ await fs.access(junieSkillsPath);
284
+ if (dryRun) {
285
+ (0, constants_1.logVerboseInfo)(`DRY RUN: Would remove ${constants_1.JUNIE_SKILLS_PATH}`, verbose, dryRun);
286
+ }
287
+ else {
288
+ await fs.rm(junieSkillsPath, { recursive: true, force: true });
289
+ (0, constants_1.logVerboseInfo)(`Removed ${constants_1.JUNIE_SKILLS_PATH} (skills disabled)`, verbose, dryRun);
290
+ }
291
+ }
292
+ catch {
293
+ // Directory doesn't exist, nothing to clean
294
+ }
273
295
  // Clean up .cursor/skills
274
296
  try {
275
297
  await fs.access(cursorSkillsPath);
@@ -284,6 +306,20 @@ async function cleanupSkillsDirectories(projectRoot, dryRun, verbose) {
284
306
  catch {
285
307
  // Directory doesn't exist, nothing to clean
286
308
  }
309
+ // Clean up .windsurf/skills
310
+ try {
311
+ await fs.access(windsurfSkillsPath);
312
+ if (dryRun) {
313
+ (0, constants_1.logVerboseInfo)(`DRY RUN: Would remove ${constants_1.WINDSURF_SKILLS_PATH}`, verbose, dryRun);
314
+ }
315
+ else {
316
+ await fs.rm(windsurfSkillsPath, { recursive: true, force: true });
317
+ (0, constants_1.logVerboseInfo)(`Removed ${constants_1.WINDSURF_SKILLS_PATH} (skills disabled)`, verbose, dryRun);
318
+ }
319
+ }
320
+ catch {
321
+ // Directory doesn't exist, nothing to clean
322
+ }
287
323
  // Clean up .factory/skills
288
324
  try {
289
325
  await fs.access(factorySkillsPath);
@@ -395,10 +431,18 @@ async function propagateSkills(projectRoot, agents, skillsEnabled, verbose, dryR
395
431
  (0, constants_1.logVerboseInfo)(`Copying skills to ${constants_1.GEMINI_SKILLS_PATH} for Gemini CLI`, verbose, dryRun);
396
432
  await propagateSkillsForGemini(projectRoot, { dryRun });
397
433
  }
434
+ if (selectedTargets.has('junie')) {
435
+ (0, constants_1.logVerboseInfo)(`Copying skills to ${constants_1.JUNIE_SKILLS_PATH} for Junie`, verbose, dryRun);
436
+ await propagateSkillsForJunie(projectRoot, { dryRun });
437
+ }
398
438
  if (selectedTargets.has('cursor')) {
399
439
  (0, constants_1.logVerboseInfo)(`Copying skills to ${constants_1.CURSOR_SKILLS_PATH} for Cursor`, verbose, dryRun);
400
440
  await propagateSkillsForCursor(projectRoot, { dryRun });
401
441
  }
442
+ if (selectedTargets.has('windsurf')) {
443
+ (0, constants_1.logVerboseInfo)(`Copying skills to ${constants_1.WINDSURF_SKILLS_PATH} for Windsurf`, verbose, dryRun);
444
+ await propagateSkillsForWindsurf(projectRoot, { dryRun });
445
+ }
402
446
  if (selectedTargets.has('factory')) {
403
447
  (0, constants_1.logVerboseInfo)(`Copying skills to ${constants_1.FACTORY_SKILLS_PATH} for Factory Droid`, verbose, dryRun);
404
448
  await propagateSkillsForFactory(projectRoot, { dryRun });
@@ -809,6 +853,47 @@ async function propagateSkillsForGemini(projectRoot, options) {
809
853
  }
810
854
  return [];
811
855
  }
856
+ /**
857
+ * Propagates skills for Junie by copying .ruler/skills to .junie/skills.
858
+ * Uses atomic replace to ensure safe overwriting of existing skills.
859
+ * Returns dry-run steps if dryRun is true, otherwise returns empty array.
860
+ */
861
+ async function propagateSkillsForJunie(projectRoot, options) {
862
+ const skillsDir = path.join(projectRoot, constants_1.RULER_SKILLS_PATH);
863
+ const junieSkillsPath = path.join(projectRoot, constants_1.JUNIE_SKILLS_PATH);
864
+ const junieDir = path.dirname(junieSkillsPath);
865
+ try {
866
+ await fs.access(skillsDir);
867
+ }
868
+ catch {
869
+ return [];
870
+ }
871
+ if (options.dryRun) {
872
+ return [`Copy skills from ${constants_1.RULER_SKILLS_PATH} to ${constants_1.JUNIE_SKILLS_PATH}`];
873
+ }
874
+ await fs.mkdir(junieDir, { recursive: true });
875
+ const tempDir = path.join(junieDir, `skills.tmp-${Date.now()}`);
876
+ try {
877
+ await (0, SkillsUtils_1.copySkillsDirectory)(skillsDir, tempDir);
878
+ try {
879
+ await fs.rm(junieSkillsPath, { recursive: true, force: true });
880
+ }
881
+ catch {
882
+ // Target didn't exist, that's fine
883
+ }
884
+ await fs.rename(tempDir, junieSkillsPath);
885
+ }
886
+ catch (error) {
887
+ try {
888
+ await fs.rm(tempDir, { recursive: true, force: true });
889
+ }
890
+ catch {
891
+ // Ignore cleanup errors
892
+ }
893
+ throw error;
894
+ }
895
+ return [];
896
+ }
812
897
  /**
813
898
  * Propagates skills for Cursor by copying .ruler/skills to .cursor/skills.
814
899
  * Uses atomic replace to ensure safe overwriting of existing skills.
@@ -859,6 +944,56 @@ async function propagateSkillsForCursor(projectRoot, options) {
859
944
  }
860
945
  return [];
861
946
  }
947
+ /**
948
+ * Propagates skills for Windsurf by copying .ruler/skills to .windsurf/skills.
949
+ * Uses atomic replace to ensure safe overwriting of existing skills.
950
+ * Returns dry-run steps if dryRun is true, otherwise returns empty array.
951
+ */
952
+ async function propagateSkillsForWindsurf(projectRoot, options) {
953
+ const skillsDir = path.join(projectRoot, constants_1.RULER_SKILLS_PATH);
954
+ const windsurfSkillsPath = path.join(projectRoot, constants_1.WINDSURF_SKILLS_PATH);
955
+ const windsurfDir = path.dirname(windsurfSkillsPath);
956
+ // Check if source skills directory exists
957
+ try {
958
+ await fs.access(skillsDir);
959
+ }
960
+ catch {
961
+ // No skills directory - return empty
962
+ return [];
963
+ }
964
+ if (options.dryRun) {
965
+ return [`Copy skills from ${constants_1.RULER_SKILLS_PATH} to ${constants_1.WINDSURF_SKILLS_PATH}`];
966
+ }
967
+ // Ensure .windsurf directory exists
968
+ await fs.mkdir(windsurfDir, { recursive: true });
969
+ // Use atomic replace: copy to temp, then rename
970
+ const tempDir = path.join(windsurfDir, `skills.tmp-${Date.now()}`);
971
+ try {
972
+ // Copy to temp directory
973
+ await (0, SkillsUtils_1.copySkillsDirectory)(skillsDir, tempDir);
974
+ // Atomically replace the target
975
+ // First, remove existing target if it exists
976
+ try {
977
+ await fs.rm(windsurfSkillsPath, { recursive: true, force: true });
978
+ }
979
+ catch {
980
+ // Target didn't exist, that's fine
981
+ }
982
+ // Rename temp to target
983
+ await fs.rename(tempDir, windsurfSkillsPath);
984
+ }
985
+ catch (error) {
986
+ // Clean up temp directory on error
987
+ try {
988
+ await fs.rm(tempDir, { recursive: true, force: true });
989
+ }
990
+ catch {
991
+ // Ignore cleanup errors
992
+ }
993
+ throw error;
994
+ }
995
+ return [];
996
+ }
862
997
  /**
863
998
  * Propagates skills for Factory Droid by copying .ruler/skills to .factory/skills.
864
999
  * 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.36",
3
+ "version": "0.3.38",
4
4
  "description": "Ruler — apply the same rules to all coding agents",
5
5
  "main": "dist/lib.js",
6
6
  "scripts": {