skiller 0.7.10 → 0.7.12

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
@@ -46,6 +46,14 @@ A Claude-centric fork of [ruler](https://github.com/intellectronica/ruler) with
46
46
 
47
47
  - `[backup].enabled = false` disables `.bak` files
48
48
 
49
+ ## 8. Multi-Agent Skills Propagation
50
+
51
+ - `.claude/skills/` is the source of truth — skills are automatically copied to agent-specific directories on `skiller apply`
52
+ - Supported agent paths: `.codex/skills`, `.cursor/skills`, `.opencode/skill`, `.roo/skills`, `.gemini/skills`, `.agents/skills`
53
+ - Shared paths are deduplicated (Claude/Copilot/Kilo share `.claude/skills`, Goose/Amp share `.agents/skills`)
54
+ - Agent skills directories are auto-added to `.gitignore` (excluding `.claude/skills`)
55
+ - Validates skill structure — warns on missing `SKILL.md`
56
+
49
57
  ---
50
58
 
51
59
  # Skiller: Centralise Your AI Coding Assistant Instructions
@@ -79,35 +87,35 @@ Skiller solves this by providing a **single source of truth** for all your AI ag
79
87
 
80
88
  ## Supported AI Agents
81
89
 
82
- | Agent | Rules File(s) | MCP Configuration / Notes |
83
- | ---------------- | -------------------------------------------------- | ------------------------------------------------ |
84
- | AGENTS.md | `AGENTS.md` | (pseudo-agent ensuring root `AGENTS.md` exists) |
85
- | GitHub Copilot | `AGENTS.md` | `.vscode/mcp.json` |
86
- | Claude Code | `CLAUDE.md` (@filename references) | `.mcp.json` |
87
- | OpenAI Codex CLI | `AGENTS.md` | `.codex/config.toml` |
88
- | Jules | `AGENTS.md` | - |
89
- | Cursor | `AGENTS.md` | `.cursor/mcp.json` |
90
- | Windsurf | `AGENTS.md` | `.windsurf/mcp_config.json` |
91
- | Cline | `.clinerules` | - |
92
- | Crush | `CRUSH.md` | `.crush.json` |
93
- | Amp | `AGENTS.md` | - |
94
- | Amazon Q CLI | `.amazonq/rules/skiller_q_rules.md` | `.amazonq/mcp.json` |
95
- | Aider | `AGENTS.md`, `.aider.conf.yml` | `.mcp.json` |
96
- | Firebase Studio | `.idx/airules.md` | `.idx/mcp.json` |
97
- | Open Hands | `.openhands/microagents/repo.md` | `config.toml` |
98
- | Gemini CLI | `AGENTS.md` | `.gemini/settings.json` |
99
- | Junie | `.junie/guidelines.md` | - |
100
- | AugmentCode | `.augment/rules/skiller_augment_instructions.md` | - |
101
- | Kilo Code | `.kilocode/rules/skiller_kilocode_instructions.md` | `.kilocode/mcp.json` |
102
- | OpenCode | `AGENTS.md` | `opencode.json` |
103
- | Goose | `.goosehints` | - |
104
- | Qwen Code | `AGENTS.md` | `.qwen/settings.json` |
105
- | RooCode | `AGENTS.md` | `.roo/mcp.json` |
106
- | Zed | `AGENTS.md` | `.zed/settings.json` (project root, never $HOME) |
107
- | Trae AI | `.trae/rules/project_rules.md` | - |
108
- | Warp | `WARP.md` | - |
109
- | Kiro | `.kiro/steering/skiller_kiro_instructions.md` | - |
110
- | Firebender | `firebender.json` | `firebender.json` (rules and MCP in same file) |
90
+ | Agent | Rules File(s) | MCP Configuration / Notes | Skills Location |
91
+ | ---------------- | -------------------------------------------------- | ------------------------------------------------ | ------------------ |
92
+ | AGENTS.md | `AGENTS.md` | (pseudo-agent ensuring root `AGENTS.md` exists) | - |
93
+ | GitHub Copilot | `AGENTS.md` | `.vscode/mcp.json` | `.claude/skills/` |
94
+ | Claude Code | `CLAUDE.md` (@filename references) | `.mcp.json` | `.claude/skills/` |
95
+ | OpenAI Codex CLI | `AGENTS.md` | `.codex/config.toml` | `.codex/skills/` |
96
+ | Jules | `AGENTS.md` | - | - |
97
+ | Cursor | `AGENTS.md` | `.cursor/mcp.json` | `.cursor/skills/` |
98
+ | Windsurf | `AGENTS.md` | `.windsurf/mcp_config.json` | - |
99
+ | Cline | `.clinerules` | - | - |
100
+ | Crush | `CRUSH.md` | `.crush.json` | - |
101
+ | Amp | `AGENTS.md` | - | `.agents/skills/` |
102
+ | Amazon Q CLI | `.amazonq/rules/skiller_q_rules.md` | `.amazonq/mcp.json` | - |
103
+ | Aider | `AGENTS.md`, `.aider.conf.yml` | `.mcp.json` | - |
104
+ | Firebase Studio | `.idx/airules.md` | `.idx/mcp.json` | - |
105
+ | Open Hands | `.openhands/microagents/repo.md` | `config.toml` | - |
106
+ | Gemini CLI | `AGENTS.md` | `.gemini/settings.json` | `.gemini/skills/` |
107
+ | Junie | `.junie/guidelines.md` | - | - |
108
+ | AugmentCode | `.augment/rules/skiller_augment_instructions.md` | - | - |
109
+ | Kilo Code | `.kilocode/rules/skiller_kilocode_instructions.md` | `.kilocode/mcp.json` | `.claude/skills/` |
110
+ | OpenCode | `AGENTS.md` | `opencode.json` | `.opencode/skill/` |
111
+ | Goose | `.goosehints` | - | `.agents/skills/` |
112
+ | Qwen Code | `AGENTS.md` | `.qwen/settings.json` | - |
113
+ | RooCode | `AGENTS.md` | `.roo/mcp.json` | `.roo/skills/` |
114
+ | Zed | `AGENTS.md` | `.zed/settings.json` (project root, never $HOME) | - |
115
+ | Trae AI | `.trae/rules/project_rules.md` | - | - |
116
+ | Warp | `WARP.md` | - | - |
117
+ | Kiro | `.kiro/steering/skiller_kiro_instructions.md` | - | - |
118
+ | Firebender | `firebender.json` | `firebender.json` (rules and MCP in same file) | - |
111
119
 
112
120
  ## Getting Started
113
121
 
@@ -555,11 +563,21 @@ export CODEX_HOME="$(pwd)/.codex"
555
563
 
556
564
  ## Skills Support
557
565
 
558
- Skiller can manage and propagate Claude Code-compatible skills to supported AI agents. Skills are stored in `.claude/skills/` as the **committed source of truth** and are automatically discovered by Claude Code.
566
+ Skiller can manage and propagate skills to supported AI agents. Skills are stored in `.claude/skills/` as the **committed source of truth** and automatically copied to agent-specific directories on `skiller apply`.
559
567
 
560
568
  ### How It Works
561
569
 
562
- Skills are specialized knowledge packages that extend AI agent capabilities with domain-specific expertise, workflows, or tool integrations. Skiller discovers skills in your `.claude/skills/` directory and keeps them in sync.
570
+ Skills are specialized knowledge packages that extend AI agent capabilities. Skiller discovers skills in your `.claude/skills/` directory, keeps them in sync via bidirectional `.mdc`/`SKILL.md` sync, and propagates them to all agents with native skills support:
571
+
572
+ - **Claude Code, GitHub Copilot, Kilo Code**: `.claude/skills/` (shared, source of truth)
573
+ - **OpenAI Codex CLI**: `.codex/skills/`
574
+ - **Cursor**: `.cursor/skills/`
575
+ - **OpenCode**: `.opencode/skill/`
576
+ - **Goose, Amp**: `.agents/skills/` (shared)
577
+ - **Roo Code**: `.roo/skills/`
578
+ - **Gemini CLI**: `.gemini/skills/`
579
+
580
+ Shared paths are deduplicated — agents sharing the same directory only trigger one copy operation.
563
581
 
564
582
  ### Skills Directory Structure
565
583
 
@@ -586,11 +604,11 @@ Each skill can be defined in two ways:
586
604
 
587
605
  Skiller provides bidirectional sync between `.mdc` files and `SKILL.md` folders:
588
606
 
589
- | Scenario | Sync Direction |
590
- |----------|---------------|
591
- | `.mdc` exists, no `SKILL.md` | → Generate `SKILL.md` with `@reference` to .mdc |
592
- | `SKILL.md` body is `@reference` | .mdc is source of truth (frontmatter in SKILL.md) |
593
- | `SKILL.md` has full content | → Generate .mdc from body, update SKILL.md to `@reference` |
607
+ | Scenario | Sync Direction |
608
+ | ------------------------------- | ---------------------------------------------------------- |
609
+ | `.mdc` exists, no `SKILL.md` | → Generate `SKILL.md` with `@reference` to .mdc |
610
+ | `SKILL.md` body is `@reference` | .mdc is source of truth (frontmatter in SKILL.md) |
611
+ | `SKILL.md` has full content | → Generate .mdc from body, update SKILL.md to `@reference` |
594
612
 
595
613
  The `@reference` body pattern indicates that the `.mdc` file contains the skill content:
596
614
 
@@ -656,6 +674,12 @@ Skiller validates discovered skills and issues warnings for:
656
674
 
657
675
  Warnings don't prevent propagation but help identify potential issues.
658
676
 
677
+ ### `.gitignore` Integration
678
+
679
+ When skills propagation is enabled, agent skills directories are automatically added to `.gitignore` (excluding `.claude/skills/` which is the committed source of truth):
680
+
681
+ - `.codex/skills/`, `.cursor/skills/`, `.opencode/skill/`, `.agents/skills/`, `.roo/skills/`, `.gemini/skills/`
682
+
659
683
  ### Dry-Run Mode
660
684
 
661
685
  Test skills propagation without making changes:
@@ -664,23 +688,12 @@ Test skills propagation without making changes:
664
688
  skiller apply --dry-run
665
689
  ```
666
690
 
667
- This shows which skills would be synced and validated.
691
+ This shows which skills would be synced, validated, and copied to each agent directory.
668
692
 
669
693
  ### Example Workflow
670
694
 
671
695
  ```bash
672
- # Option 1: Create a skill using .mdc file
673
- cat > .claude/skills/my-skill.mdc << 'EOF'
674
- ---
675
- description: My custom skill
676
- ---
677
-
678
- # My Custom Skill
679
-
680
- This skill provides specialized knowledge for...
681
- EOF
682
-
683
- # Option 2: Create a skill folder directly
696
+ # 1. Create a skill folder
684
697
  mkdir -p .claude/skills/my-skill
685
698
  cat > .claude/skills/my-skill/SKILL.md << 'EOF'
686
699
  ---
@@ -693,10 +706,17 @@ description: My custom skill
693
706
  This skill provides specialized knowledge for...
694
707
  EOF
695
708
 
696
- # Apply to sync skills (runs bidirectional sync)
709
+ # 2. Apply to sync and propagate skills
697
710
  skiller apply
698
711
 
699
- # Skills are now available to Claude Code via .claude/skills/
712
+ # 3. Skills are now available to all compatible agents:
713
+ # - Claude Code, Copilot, Kilo Code: .claude/skills/my-skill/
714
+ # - Codex CLI: .codex/skills/my-skill/
715
+ # - Cursor: .cursor/skills/my-skill/
716
+ # - OpenCode: .opencode/skill/my-skill/
717
+ # - Goose, Amp: .agents/skills/my-skill/
718
+ # - Roo Code: .roo/skills/my-skill/
719
+ # - Gemini CLI: .gemini/skills/my-skill/
700
720
  ```
701
721
 
702
722
  ## `.gitignore` Integration
@@ -1,6 +1,40 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
2
35
  Object.defineProperty(exports, "__esModule", { value: true });
3
36
  exports.AmpAgent = void 0;
37
+ const path = __importStar(require("path"));
4
38
  const AgentsMdAgent_1 = require("./AgentsMdAgent");
5
39
  class AmpAgent extends AgentsMdAgent_1.AgentsMdAgent {
6
40
  getIdentifier() {
@@ -9,5 +43,11 @@ class AmpAgent extends AgentsMdAgent_1.AgentsMdAgent {
9
43
  getName() {
10
44
  return 'Amp';
11
45
  }
46
+ supportsNativeSkills() {
47
+ return true;
48
+ }
49
+ getSkillsPath(projectRoot) {
50
+ return path.join(projectRoot, '.agents/skills');
51
+ }
12
52
  }
13
53
  exports.AmpAgent = AmpAgent;
@@ -139,5 +139,11 @@ class CodexCliAgent {
139
139
  supportsMcpRemote() {
140
140
  return false; // Codex CLI only supports STDIO based on PR description
141
141
  }
142
+ supportsNativeSkills() {
143
+ return true;
144
+ }
145
+ getSkillsPath(projectRoot) {
146
+ return path.join(projectRoot, '.codex/skills');
147
+ }
142
148
  }
143
149
  exports.CodexCliAgent = CodexCliAgent;
@@ -1,6 +1,40 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
2
35
  Object.defineProperty(exports, "__esModule", { value: true });
3
36
  exports.CopilotAgent = void 0;
37
+ const path = __importStar(require("path"));
4
38
  const AgentsMdAgent_1 = require("./AgentsMdAgent");
5
39
  /**
6
40
  * GitHub Copilot agent adapter.
@@ -39,5 +73,11 @@ class CopilotAgent {
39
73
  supportsMcpRemote() {
40
74
  return true;
41
75
  }
76
+ supportsNativeSkills() {
77
+ return true;
78
+ }
79
+ getSkillsPath(projectRoot) {
80
+ return path.join(projectRoot, '.claude/skills');
81
+ }
42
82
  }
43
83
  exports.CopilotAgent = CopilotAgent;
@@ -89,5 +89,8 @@ class CursorAgent extends AgentsMdAgent_1.AgentsMdAgent {
89
89
  // Cursor has native support for rules via .cursor/rules/
90
90
  return true;
91
91
  }
92
+ getSkillsPath(projectRoot) {
93
+ return path.join(projectRoot, '.cursor/skills');
94
+ }
92
95
  }
93
96
  exports.CursorAgent = CursorAgent;
@@ -95,5 +95,11 @@ class GeminiCliAgent extends AgentsMdAgent_1.AgentsMdAgent {
95
95
  supportsMcpRemote() {
96
96
  return true;
97
97
  }
98
+ supportsNativeSkills() {
99
+ return true;
100
+ }
101
+ getSkillsPath(projectRoot) {
102
+ return path.join(projectRoot, '.gemini/skills');
103
+ }
98
104
  }
99
105
  exports.GeminiCliAgent = GeminiCliAgent;
@@ -54,5 +54,11 @@ class GooseAgent extends AbstractAgent_1.AbstractAgent {
54
54
  // Goose doesn't support MCP configuration via local config files
55
55
  return '';
56
56
  }
57
+ supportsNativeSkills() {
58
+ return true;
59
+ }
60
+ getSkillsPath(projectRoot) {
61
+ return path.join(projectRoot, '.agents/skills');
62
+ }
57
63
  }
58
64
  exports.GooseAgent = GooseAgent;
@@ -59,5 +59,11 @@ class KiloCodeAgent extends AbstractAgent_1.AbstractAgent {
59
59
  supportsMcpRemote() {
60
60
  return true;
61
61
  }
62
+ supportsNativeSkills() {
63
+ return true;
64
+ }
65
+ getSkillsPath(projectRoot) {
66
+ return path.join(projectRoot, '.claude/skills');
67
+ }
62
68
  }
63
69
  exports.KiloCodeAgent = KiloCodeAgent;
@@ -95,5 +95,11 @@ class OpenCodeAgent {
95
95
  supportsMcpRemote() {
96
96
  return true;
97
97
  }
98
+ supportsNativeSkills() {
99
+ return true;
100
+ }
101
+ getSkillsPath(projectRoot) {
102
+ return path.join(projectRoot, '.opencode/skill');
103
+ }
98
104
  }
99
105
  exports.OpenCodeAgent = OpenCodeAgent;
@@ -135,5 +135,11 @@ class RooCodeAgent {
135
135
  getMcpServerKey() {
136
136
  return 'mcpServers';
137
137
  }
138
+ supportsNativeSkills() {
139
+ return true;
140
+ }
141
+ getSkillsPath(projectRoot) {
142
+ return path.join(projectRoot, '.roo/skills');
143
+ }
138
144
  }
139
145
  exports.RooCodeAgent = RooCodeAgent;
@@ -1,18 +1,18 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
3
  exports.run = run;
7
- const yargs_1 = __importDefault(require("yargs"));
8
- const helpers_1 = require("yargs/helpers");
9
4
  const handlers_1 = require("./handlers");
10
5
  const index_1 = require("../agents/index");
11
6
  /**
12
7
  * Sets up and parses CLI commands.
13
8
  */
14
- function run() {
15
- (0, yargs_1.default)((0, helpers_1.hideBin)(process.argv))
9
+ async function run() {
10
+ const dynamicImport = new Function('modulePath', 'return import(modulePath);');
11
+ const [{ default: yargs }, { hideBin }] = await Promise.all([
12
+ dynamicImport('yargs'),
13
+ dynamicImport('yargs/helpers'),
14
+ ]);
15
+ yargs(hideBin(process.argv))
16
16
  .scriptName('skiller')
17
17
  .usage('$0 <command> [options]')
18
18
  .command('apply', 'Apply skiller configurations to supported AI agents', (y) => {
package/dist/cli/index.js CHANGED
@@ -2,4 +2,7 @@
2
2
  "use strict";
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
4
  const commands_1 = require("./commands");
5
- (0, commands_1.run)();
5
+ (0, commands_1.run)().catch((error) => {
6
+ console.error(error);
7
+ process.exit(1);
8
+ });
@@ -36,6 +36,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.isReferenceBody = isReferenceBody;
37
37
  exports.syncMdcToSkillMd = syncMdcToSkillMd;
38
38
  exports.discoverSkills = discoverSkills;
39
+ exports.copySkillsToAgent = copySkillsToAgent;
39
40
  exports.getSkillsGitignorePaths = getSkillsGitignorePaths;
40
41
  exports.propagateSkills = propagateSkills;
41
42
  exports.migrateRulesToSkills = migrateRulesToSkills;
@@ -48,6 +49,92 @@ const yaml = __importStar(require("js-yaml"));
48
49
  const constants_1 = require("../constants");
49
50
  const SkillsUtils_1 = require("./SkillsUtils");
50
51
  const FrontmatterParser_1 = require("./FrontmatterParser");
52
+ /**
53
+ * For non-Claude agents, compile a wrapper SKILL.md (body is a single @reference)
54
+ * into a standalone SKILL.md with the referenced file's body inlined.
55
+ *
56
+ * We intentionally keep this conservative: only expand when the body is *just*
57
+ * an @reference line, to avoid accidentally treating email addresses or
58
+ * "@mentions" inside real content as file references.
59
+ */
60
+ async function compileSkillMdForNonClaudeAgents(skillMdContent, projectRoot, skillFolderPath) {
61
+ const { frontmatter, rawFrontmatter, body } = (0, FrontmatterParser_1.parseFrontmatter)(skillMdContent);
62
+ const refCheck = isReferenceBody(body);
63
+ if (!refCheck.isReference || !refCheck.referencePath) {
64
+ return skillMdContent;
65
+ }
66
+ const referencePath = refCheck.referencePath;
67
+ const absoluteRefPath = referencePath.startsWith('./') || referencePath.startsWith('../')
68
+ ? path.resolve(skillFolderPath, referencePath)
69
+ : path.resolve(projectRoot, referencePath);
70
+ // Security: only inline references within the project root.
71
+ const normalizedProjectRoot = path.resolve(projectRoot);
72
+ const normalizedAbsoluteRefPath = path.resolve(absoluteRefPath);
73
+ if (!normalizedAbsoluteRefPath.startsWith(normalizedProjectRoot + path.sep)) {
74
+ return skillMdContent;
75
+ }
76
+ let referencedContent;
77
+ try {
78
+ referencedContent = await fs.readFile(normalizedAbsoluteRefPath, 'utf8');
79
+ }
80
+ catch {
81
+ return skillMdContent;
82
+ }
83
+ const { body: referencedBody } = (0, FrontmatterParser_1.parseFrontmatter)(referencedContent);
84
+ const fmData = rawFrontmatter && Object.keys(rawFrontmatter).length > 0
85
+ ? rawFrontmatter
86
+ : frontmatter && Object.keys(frontmatter).length > 0
87
+ ? frontmatter
88
+ : null;
89
+ if (fmData) {
90
+ return `---
91
+ ${yaml.dump(fmData, { lineWidth: -1, noRefs: true }).trim()}
92
+ ---
93
+
94
+ ${referencedBody}
95
+ `;
96
+ }
97
+ return `${referencedBody}\n`;
98
+ }
99
+ /**
100
+ * Copies a single skill directory to an agent skill directory:
101
+ * - SKILL.md is compiled (inlines @reference wrapper content)
102
+ * - .mdc files are excluded (Claude-only sources)
103
+ * - all other files are copied as-is
104
+ */
105
+ async function copySkillDirectoryForNonClaudeAgents(src, dest, projectRoot, skillFolderPath, depth = 0) {
106
+ // Security: Prevent DoS via deeply nested directories
107
+ if (depth >= constants_1.MAX_RECURSION_DEPTH) {
108
+ return;
109
+ }
110
+ const stat = await fs.stat(src);
111
+ if (stat.isDirectory()) {
112
+ await fs.mkdir(dest, { recursive: true });
113
+ const entries = await fs.readdir(src, { withFileTypes: true });
114
+ for (const entry of entries) {
115
+ // Exclude all .mdc files from agent skills directories.
116
+ if (entry.isFile() && entry.name.endsWith('.mdc')) {
117
+ continue;
118
+ }
119
+ const srcPath = path.join(src, entry.name);
120
+ const destPath = path.join(dest, entry.name);
121
+ await copySkillDirectoryForNonClaudeAgents(srcPath, destPath, projectRoot, skillFolderPath, depth + 1);
122
+ }
123
+ return;
124
+ }
125
+ // Files
126
+ if (path.basename(src) === constants_1.SKILL_MD_FILENAME) {
127
+ const content = await fs.readFile(src, 'utf8');
128
+ const compiled = await compileSkillMdForNonClaudeAgents(content, projectRoot, skillFolderPath);
129
+ await fs.writeFile(dest, compiled, 'utf8');
130
+ return;
131
+ }
132
+ // Extra guard: skip .mdc even if reached via recursion.
133
+ if (src.endsWith('.mdc')) {
134
+ return;
135
+ }
136
+ await fs.copyFile(src, dest);
137
+ }
51
138
  /**
52
139
  * Check if SKILL.md body is just a reference (single non-empty line starting with @).
53
140
  * This replaces the previous synced: true frontmatter detection.
@@ -237,21 +324,25 @@ ${yaml.dump(skillFrontmatter || { name: skillName }, { lineWidth: -1, noRefs: tr
237
324
  else {
238
325
  referencedPath = path.resolve(skillFolderPath, refCheck.referencePath);
239
326
  }
240
- // One-time migration: for old @.claude/rules/ references, look at the
241
- // already-migrated skills location. After migration, SKILL.md will have
242
- // @.claude/skills/ references, so this code path won't be hit again.
243
- let actualPath = referencedPath;
327
+ // One-time migration: for old @.claude/rules/ references, prefer the
328
+ // original rules path if it exists, then fall back to migrated skills.
329
+ const candidatePaths = [referencedPath];
244
330
  if (refCheck.referencePath?.includes('/rules/')) {
245
331
  const refFileName = path.basename(refCheck.referencePath);
246
332
  const refBaseName = path.basename(refFileName, '.mdc');
247
- actualPath = path.join(skillsDir, refBaseName, refFileName);
333
+ candidatePaths.push(path.join(skillsDir, refBaseName, refFileName));
248
334
  }
249
335
  let referencedContent = null;
250
- try {
251
- referencedContent = await fs.readFile(actualPath, 'utf8');
252
- }
253
- catch {
254
- // File not found
336
+ let actualPath = referencedPath;
337
+ for (const candidatePath of candidatePaths) {
338
+ try {
339
+ referencedContent = await fs.readFile(candidatePath, 'utf8');
340
+ actualPath = candidatePath;
341
+ break;
342
+ }
343
+ catch {
344
+ // Try next candidate
345
+ }
255
346
  }
256
347
  if (referencedContent === null) {
257
348
  warnings.push(`Cannot migrate ${skillName}: referenced file not found at ${actualPath}`);
@@ -364,24 +455,76 @@ async function discoverSkills(projectRoot, skillerDir) {
364
455
  // Walk the skills tree
365
456
  return await (0, SkillsUtils_1.walkSkillsTree)(skillsPath);
366
457
  }
458
+ /**
459
+ * Copies skills from source directory to target agent's skills directory.
460
+ * Validates skill structure and returns copy count and warnings.
461
+ */
462
+ async function copySkillsToAgent(sourceSkillsDir, targetSkillsDir, projectRoot, verbose, dryRun) {
463
+ const warnings = [];
464
+ let copied = 0;
465
+ try {
466
+ await fs.access(sourceSkillsDir);
467
+ }
468
+ catch {
469
+ // Source directory doesn't exist
470
+ return { copied: 0, warnings: [] };
471
+ }
472
+ // Use walkSkillsTree to discover skills
473
+ const skillsTree = await (0, SkillsUtils_1.walkSkillsTree)(sourceSkillsDir);
474
+ // Validate and copy each skill
475
+ for (const skill of skillsTree.skills) {
476
+ // skill.path is absolute, use it directly
477
+ const skillPath = skill.path;
478
+ const skillMdPath = path.join(skillPath, constants_1.SKILL_MD_FILENAME);
479
+ // Validate: skill must have SKILL.md
480
+ try {
481
+ await fs.access(skillMdPath);
482
+ }
483
+ catch {
484
+ warnings.push(`Skill '${skill.name}' missing required SKILL.md file, skipping`);
485
+ continue;
486
+ }
487
+ // Copy skill directory to target using relative path
488
+ const relativeSkillPath = path.relative(sourceSkillsDir, skill.path);
489
+ const targetSkillPath = path.join(targetSkillsDir, relativeSkillPath);
490
+ if (!dryRun) {
491
+ await copySkillDirectoryForNonClaudeAgents(skillPath, targetSkillPath, projectRoot, skillPath);
492
+ }
493
+ (0, constants_1.logVerboseInfo)(dryRun
494
+ ? `DRY RUN: Would copy skill '${skill.name}' to ${targetSkillsDir}`
495
+ : `Copied skill '${skill.name}' to ${targetSkillsDir}`, verbose, dryRun);
496
+ copied++;
497
+ }
498
+ return { copied, warnings };
499
+ }
367
500
  /**
368
501
  * Gets the paths that skills will generate, for gitignore purposes.
369
- * In the new architecture, .claude/skills is the source of truth and should NOT be gitignored.
370
- * This function now returns an empty array as skills are committed.
502
+ * Collects paths from all agents with native skills support, excluding the source (.claude/skills).
371
503
  */
372
- async function getSkillsGitignorePaths(
373
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
374
- _projectRoot) {
375
- // In the new architecture, .claude/skills is the source of truth and committed.
376
- // No skills-related paths need to be gitignored.
377
- return [];
504
+ function getSkillsGitignorePaths(projectRoot, agents) {
505
+ const paths = [];
506
+ const sourceSkillsPath = path.join(projectRoot, constants_1.CLAUDE_SKILLS_PATH);
507
+ for (const agent of agents) {
508
+ if (agent.supportsNativeSkills?.() && agent.getSkillsPath) {
509
+ const skillsPath = agent.getSkillsPath(projectRoot);
510
+ if (skillsPath && skillsPath !== sourceSkillsPath) {
511
+ // Convert to relative path for gitignore
512
+ const relativePath = path.relative(projectRoot, skillsPath);
513
+ // Deduplicate paths
514
+ if (!paths.includes(relativePath)) {
515
+ paths.push(relativePath);
516
+ }
517
+ }
518
+ }
519
+ }
520
+ return paths;
378
521
  }
379
522
  /**
380
523
  * Propagates skills for agents that need them.
381
524
  * In the new architecture, skills are committed to .claude/skills and discovered by agents natively.
382
525
  * This function now only discovers and validates skills.
383
526
  */
384
- async function propagateSkills(projectRoot, _agents, skillsEnabled, verbose, dryRun, skillerDir) {
527
+ async function propagateSkills(projectRoot, agents, skillsEnabled, verbose, dryRun, skillerDir) {
385
528
  if (!skillsEnabled) {
386
529
  (0, constants_1.logVerboseInfo)('Skills support disabled', verbose, dryRun);
387
530
  return;
@@ -422,6 +565,27 @@ async function propagateSkills(projectRoot, _agents, skillsEnabled, verbose, dry
422
565
  return;
423
566
  }
424
567
  (0, constants_1.logVerboseInfo)(`Discovered ${skills.length} skill(s)`, verbose, dryRun);
568
+ // Copy skills to all agents with native skills support
569
+ const destinationPaths = new Set();
570
+ for (const agent of agents) {
571
+ if (agent.supportsNativeSkills?.() && agent.getSkillsPath) {
572
+ const targetPath = agent.getSkillsPath(projectRoot);
573
+ if (targetPath && targetPath !== skillsDir) {
574
+ // Deduplicate shared paths
575
+ destinationPaths.add(targetPath);
576
+ }
577
+ }
578
+ }
579
+ // Copy skills to each unique destination
580
+ for (const targetPath of destinationPaths) {
581
+ const result = await copySkillsToAgent(skillsDir, targetPath, projectRoot, verbose, dryRun);
582
+ if (result.copied > 0) {
583
+ (0, constants_1.logVerboseInfo)(`Copied ${result.copied} skill(s) to ${targetPath}`, verbose, dryRun);
584
+ }
585
+ for (const warning of result.warnings) {
586
+ (0, constants_1.logWarn)(warning, dryRun);
587
+ }
588
+ }
425
589
  }
426
590
  /**
427
591
  * Recursively finds all folders containing SKILL.md in a directory.
package/dist/lib.js CHANGED
@@ -147,7 +147,7 @@ async function applyAllAgentConfigs(projectRoot, includedAgents, configPath, cli
147
147
  if (skillsEnabledForGitignore) {
148
148
  // Skills enabled by default or explicitly
149
149
  const { getSkillsGitignorePaths } = await Promise.resolve().then(() => __importStar(require('./core/SkillsProcessor')));
150
- const skillsPaths = await getSkillsGitignorePaths(projectRoot);
150
+ const skillsPaths = getSkillsGitignorePaths(projectRoot, selectedAgents);
151
151
  allGeneratedPaths = [...generatedPaths, ...skillsPaths];
152
152
  }
153
153
  await (0, apply_engine_1.updateGitignore)(projectRoot, allGeneratedPaths, loadedConfig, cliGitignoreEnabled, dryRun);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skiller",
3
- "version": "0.7.10",
3
+ "version": "0.7.12",
4
4
  "description": "Skiller — apply the same rules to all coding agents",
5
5
  "main": "dist/lib.js",
6
6
  "publishConfig": {