@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 +15 -11
- package/dist/agents/JunieAgent.js +3 -0
- package/dist/agents/WindsurfAgent.js +3 -0
- package/dist/constants.js +3 -1
- package/dist/core/SkillsProcessor.js +136 -1
- package/package.json +1 -1
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
|
|
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.
|