@intellectronica/ruler 0.3.37 → 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/` |
@@ -596,6 +596,7 @@ Skills are specialized knowledge packages that extend AI agent capabilities with
596
596
  - **Gemini CLI**: `.gemini/skills/`
597
597
  - **Junie**: `.junie/skills/`
598
598
  - **Cursor**: `.cursor/skills/`
599
+ - **Windsurf**: `.windsurf/skills/`
599
600
 
600
601
  ### Skills Directory Structure
601
602
 
@@ -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.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;
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;
@@ -62,6 +62,7 @@ exports.ROO_SKILLS_PATH = '.roo/skills';
62
62
  exports.GEMINI_SKILLS_PATH = '.gemini/skills';
63
63
  exports.JUNIE_SKILLS_PATH = '.junie/skills';
64
64
  exports.CURSOR_SKILLS_PATH = '.cursor/skills';
65
+ exports.WINDSURF_SKILLS_PATH = '.windsurf/skills';
65
66
  exports.FACTORY_SKILLS_PATH = '.factory/skills';
66
67
  exports.ANTIGRAVITY_SKILLS_PATH = '.agent/skills';
67
68
  exports.SKILL_MD_FILENAME = 'SKILL.md';
@@ -46,6 +46,7 @@ exports.propagateSkillsForRoo = propagateSkillsForRoo;
46
46
  exports.propagateSkillsForGemini = propagateSkillsForGemini;
47
47
  exports.propagateSkillsForJunie = propagateSkillsForJunie;
48
48
  exports.propagateSkillsForCursor = propagateSkillsForCursor;
49
+ exports.propagateSkillsForWindsurf = propagateSkillsForWindsurf;
49
50
  exports.propagateSkillsForFactory = propagateSkillsForFactory;
50
51
  exports.propagateSkillsForAntigravity = propagateSkillsForAntigravity;
51
52
  const path = __importStar(require("path"));
@@ -83,7 +84,7 @@ async function getSkillsGitignorePaths(projectRoot, agents) {
83
84
  return [];
84
85
  }
85
86
  // Import here to avoid circular dependency
86
- 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, 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')));
87
88
  const selectedTargets = getSelectedSkillTargets(agents);
88
89
  const targetPaths = {
89
90
  claude: CLAUDE_SKILLS_PATH,
@@ -96,6 +97,7 @@ async function getSkillsGitignorePaths(projectRoot, agents) {
96
97
  gemini: GEMINI_SKILLS_PATH,
97
98
  junie: JUNIE_SKILLS_PATH,
98
99
  cursor: CURSOR_SKILLS_PATH,
100
+ windsurf: WINDSURF_SKILLS_PATH,
99
101
  factory: FACTORY_SKILLS_PATH,
100
102
  antigravity: ANTIGRAVITY_SKILLS_PATH,
101
103
  };
@@ -130,6 +132,7 @@ const SKILL_TARGET_TO_IDENTIFIERS = new Map([
130
132
  ['gemini', ['gemini-cli']],
131
133
  ['junie', ['junie']],
132
134
  ['cursor', ['cursor']],
135
+ ['windsurf', ['windsurf']],
133
136
  ['factory', ['factory']],
134
137
  ['antigravity', ['antigravity']],
135
138
  ]);
@@ -160,6 +163,7 @@ async function cleanupSkillsDirectories(projectRoot, dryRun, verbose) {
160
163
  const geminiSkillsPath = path.join(projectRoot, constants_1.GEMINI_SKILLS_PATH);
161
164
  const junieSkillsPath = path.join(projectRoot, constants_1.JUNIE_SKILLS_PATH);
162
165
  const cursorSkillsPath = path.join(projectRoot, constants_1.CURSOR_SKILLS_PATH);
166
+ const windsurfSkillsPath = path.join(projectRoot, constants_1.WINDSURF_SKILLS_PATH);
163
167
  const factorySkillsPath = path.join(projectRoot, constants_1.FACTORY_SKILLS_PATH);
164
168
  const antigravitySkillsPath = path.join(projectRoot, constants_1.ANTIGRAVITY_SKILLS_PATH);
165
169
  // Clean up .claude/skills
@@ -302,6 +306,20 @@ async function cleanupSkillsDirectories(projectRoot, dryRun, verbose) {
302
306
  catch {
303
307
  // Directory doesn't exist, nothing to clean
304
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
+ }
305
323
  // Clean up .factory/skills
306
324
  try {
307
325
  await fs.access(factorySkillsPath);
@@ -421,6 +439,10 @@ async function propagateSkills(projectRoot, agents, skillsEnabled, verbose, dryR
421
439
  (0, constants_1.logVerboseInfo)(`Copying skills to ${constants_1.CURSOR_SKILLS_PATH} for Cursor`, verbose, dryRun);
422
440
  await propagateSkillsForCursor(projectRoot, { dryRun });
423
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
+ }
424
446
  if (selectedTargets.has('factory')) {
425
447
  (0, constants_1.logVerboseInfo)(`Copying skills to ${constants_1.FACTORY_SKILLS_PATH} for Factory Droid`, verbose, dryRun);
426
448
  await propagateSkillsForFactory(projectRoot, { dryRun });
@@ -922,6 +944,56 @@ async function propagateSkillsForCursor(projectRoot, options) {
922
944
  }
923
945
  return [];
924
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
+ }
925
997
  /**
926
998
  * Propagates skills for Factory Droid by copying .ruler/skills to .factory/skills.
927
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.37",
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": {