skiller 0.7.1 → 0.7.3
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/dist/core/SkillsProcessor.js +154 -14
- package/dist/core/SkillsUtils.js +32 -2
- package/dist/lib.js +6 -6
- package/package.json +1 -1
|
@@ -38,7 +38,10 @@ exports.syncMdcToSkillMd = syncMdcToSkillMd;
|
|
|
38
38
|
exports.discoverSkills = discoverSkills;
|
|
39
39
|
exports.getSkillsGitignorePaths = getSkillsGitignorePaths;
|
|
40
40
|
exports.propagateSkills = propagateSkills;
|
|
41
|
+
exports.migrateRulesToSkills = migrateRulesToSkills;
|
|
41
42
|
exports.copySkillFoldersFromRules = copySkillFoldersFromRules;
|
|
43
|
+
exports.copyMdcFilesFromRules = copyMdcFilesFromRules;
|
|
44
|
+
exports.deleteRulesDir = deleteRulesDir;
|
|
42
45
|
const path = __importStar(require("path"));
|
|
43
46
|
const fs = __importStar(require("fs/promises"));
|
|
44
47
|
const yaml = __importStar(require("js-yaml"));
|
|
@@ -140,8 +143,15 @@ async function syncMdcToSkillMd(skillsDir, verbose, dryRun) {
|
|
|
140
143
|
}
|
|
141
144
|
try {
|
|
142
145
|
if (siblingMdcContent !== null && skillMdContent === null) {
|
|
143
|
-
// Case 1: Sibling .mdc exists but no SKILL.md
|
|
146
|
+
// Case 1: Sibling .mdc exists but no SKILL.md
|
|
144
147
|
const { frontmatter: mdcFrontmatter } = (0, FrontmatterParser_1.parseFrontmatter)(siblingMdcContent);
|
|
148
|
+
// Skip SKILL.md generation for .mdc files with alwaysApply: true
|
|
149
|
+
// These are Cursor-style rules, not Claude Code skills
|
|
150
|
+
if (mdcFrontmatter?.alwaysApply === true) {
|
|
151
|
+
(0, constants_1.logVerboseInfo)(`Skipping SKILL.md generation for ${skillName} (alwaysApply rule)`, verbose, dryRun);
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
// Generate SKILL.md with @reference (absolute path)
|
|
145
155
|
const skillFrontmatter = {
|
|
146
156
|
name: skillName,
|
|
147
157
|
description: mdcFrontmatter?.description || `Skill: ${skillName}`,
|
|
@@ -150,14 +160,14 @@ async function syncMdcToSkillMd(skillsDir, verbose, dryRun) {
|
|
|
150
160
|
${yaml.dump(skillFrontmatter, { lineWidth: -1, noRefs: true }).trim()}
|
|
151
161
|
---
|
|
152
162
|
|
|
153
|
-
|
|
163
|
+
@.claude/skills/${skillName}/${skillName}.mdc
|
|
154
164
|
`;
|
|
155
165
|
if (dryRun) {
|
|
156
166
|
(0, constants_1.logVerboseInfo)(`DRY RUN: Would generate ${skillName}/SKILL.md with @reference`, verbose, dryRun);
|
|
157
167
|
}
|
|
158
168
|
else {
|
|
159
169
|
await fs.writeFile(skillMdPath, newSkillMd, 'utf8');
|
|
160
|
-
(0, constants_1.logVerboseInfo)(`Generated ${skillName}/SKILL.md with
|
|
170
|
+
(0, constants_1.logVerboseInfo)(`Generated ${skillName}/SKILL.md with @.claude/skills/${skillName}/${skillName}.mdc reference`, verbose, dryRun);
|
|
161
171
|
}
|
|
162
172
|
synced.push(skillName);
|
|
163
173
|
}
|
|
@@ -167,22 +177,32 @@ ${yaml.dump(skillFrontmatter, { lineWidth: -1, noRefs: true }).trim()}
|
|
|
167
177
|
const refCheck = isReferenceBody(skillBody);
|
|
168
178
|
if (refCheck.isReference) {
|
|
169
179
|
// Case 2: SKILL.md is @reference → source file is truth
|
|
170
|
-
|
|
180
|
+
// Check for both relative and absolute sibling reference patterns
|
|
181
|
+
const isRelativeSiblingRef = refCheck.referencePath === `./${skillName}.mdc`;
|
|
182
|
+
const isAbsoluteSiblingRef = refCheck.referencePath ===
|
|
183
|
+
`.claude/skills/${skillName}/${skillName}.mdc`;
|
|
184
|
+
if (isRelativeSiblingRef || isAbsoluteSiblingRef) {
|
|
171
185
|
// Sibling reference pattern - validate/update frontmatter if .mdc changed
|
|
172
186
|
if (siblingMdcContent !== null) {
|
|
173
187
|
const { frontmatter: mdcFrontmatter } = (0, FrontmatterParser_1.parseFrontmatter)(siblingMdcContent);
|
|
174
188
|
// Update SKILL.md frontmatter if description changed in .mdc
|
|
175
|
-
if
|
|
176
|
-
|
|
189
|
+
// Also migrate from relative to absolute path if needed
|
|
190
|
+
const needsUpdate = (mdcFrontmatter?.description &&
|
|
191
|
+
mdcFrontmatter.description !==
|
|
192
|
+
skillFrontmatter?.description) ||
|
|
193
|
+
isRelativeSiblingRef; // Migrate old relative refs to absolute
|
|
194
|
+
if (needsUpdate) {
|
|
177
195
|
const newFrontmatter = {
|
|
178
196
|
name: skillFrontmatter?.name || skillName,
|
|
179
|
-
description: mdcFrontmatter
|
|
197
|
+
description: mdcFrontmatter?.description ||
|
|
198
|
+
skillFrontmatter?.description ||
|
|
199
|
+
`Skill: ${skillName}`,
|
|
180
200
|
};
|
|
181
201
|
const newSkillMd = `---
|
|
182
202
|
${yaml.dump(newFrontmatter, { lineWidth: -1, noRefs: true }).trim()}
|
|
183
203
|
---
|
|
184
204
|
|
|
185
|
-
|
|
205
|
+
@.claude/skills/${skillName}/${skillName}.mdc
|
|
186
206
|
`;
|
|
187
207
|
if (dryRun) {
|
|
188
208
|
(0, constants_1.logVerboseInfo)(`DRY RUN: Would update ${skillName}/SKILL.md frontmatter from .mdc`, verbose, dryRun);
|
|
@@ -215,8 +235,7 @@ ${yaml.dump(newFrontmatter, { lineWidth: -1, noRefs: true }).trim()}
|
|
|
215
235
|
const { frontmatter: refFrontmatter, body: refBody } = (0, FrontmatterParser_1.parseFrontmatter)(referencedContent);
|
|
216
236
|
// Create sibling .mdc with the content
|
|
217
237
|
let mdcContent;
|
|
218
|
-
if (refFrontmatter &&
|
|
219
|
-
Object.keys(refFrontmatter).length > 0) {
|
|
238
|
+
if (refFrontmatter && Object.keys(refFrontmatter).length > 0) {
|
|
220
239
|
const mdcFrontmatterData = {};
|
|
221
240
|
if (refFrontmatter.description) {
|
|
222
241
|
mdcFrontmatterData.description = refFrontmatter.description;
|
|
@@ -242,7 +261,7 @@ ${refBody}
|
|
|
242
261
|
else {
|
|
243
262
|
mdcContent = referencedContent;
|
|
244
263
|
}
|
|
245
|
-
// Update SKILL.md to point to sibling .mdc
|
|
264
|
+
// Update SKILL.md to point to sibling .mdc (absolute path)
|
|
246
265
|
const newFrontmatter = {
|
|
247
266
|
name: skillFrontmatter?.name || skillName,
|
|
248
267
|
description: refFrontmatter?.description ||
|
|
@@ -253,7 +272,7 @@ ${refBody}
|
|
|
253
272
|
${yaml.dump(newFrontmatter, { lineWidth: -1, noRefs: true }).trim()}
|
|
254
273
|
---
|
|
255
274
|
|
|
256
|
-
|
|
275
|
+
@.claude/skills/${skillName}/${skillName}.mdc
|
|
257
276
|
`;
|
|
258
277
|
if (dryRun) {
|
|
259
278
|
(0, constants_1.logVerboseInfo)(`DRY RUN: Would migrate ${skillName} from ${refCheck.referencePath} to sibling pattern`, verbose, dryRun);
|
|
@@ -290,7 +309,7 @@ ${skillBody}
|
|
|
290
309
|
else {
|
|
291
310
|
mdcContent = skillBody;
|
|
292
311
|
}
|
|
293
|
-
// Update SKILL.md to @reference
|
|
312
|
+
// Update SKILL.md to @reference (absolute path)
|
|
294
313
|
const newSkillFrontmatter = {
|
|
295
314
|
name: skillFrontmatter?.name || skillName,
|
|
296
315
|
description: skillFrontmatter?.description || `Skill: ${skillName}`,
|
|
@@ -299,7 +318,7 @@ ${skillBody}
|
|
|
299
318
|
${yaml.dump(newSkillFrontmatter, { lineWidth: -1, noRefs: true }).trim()}
|
|
300
319
|
---
|
|
301
320
|
|
|
302
|
-
|
|
321
|
+
@.claude/skills/${skillName}/${skillName}.mdc
|
|
303
322
|
`;
|
|
304
323
|
if (dryRun) {
|
|
305
324
|
(0, constants_1.logVerboseInfo)(`DRY RUN: Would generate ${skillName}/${skillName}.mdc and update SKILL.md`, verbose, dryRun);
|
|
@@ -428,6 +447,27 @@ async function findSkillFoldersInRules(dir, depth = 0) {
|
|
|
428
447
|
}
|
|
429
448
|
return skillFolders;
|
|
430
449
|
}
|
|
450
|
+
/**
|
|
451
|
+
* Migrates all content from .claude/rules to .claude/skills and deletes the rules directory.
|
|
452
|
+
* This is the main entry point for rules migration - it only processes if rules directory exists.
|
|
453
|
+
*/
|
|
454
|
+
async function migrateRulesToSkills(skillerDir, verbose, dryRun) {
|
|
455
|
+
const rulesDir = path.join(skillerDir, 'rules');
|
|
456
|
+
// Check if rules directory exists - early exit if not
|
|
457
|
+
try {
|
|
458
|
+
await fs.access(rulesDir);
|
|
459
|
+
}
|
|
460
|
+
catch {
|
|
461
|
+
// No rules directory - nothing to migrate
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
// Copy skill folders (folders with SKILL.md)
|
|
465
|
+
await copySkillFoldersFromRules(skillerDir, verbose, dryRun);
|
|
466
|
+
// Copy standalone .mdc files
|
|
467
|
+
await copyMdcFilesFromRules(skillerDir, verbose, dryRun);
|
|
468
|
+
// Delete the rules directory after migration
|
|
469
|
+
await deleteRulesDir(skillerDir, verbose, dryRun);
|
|
470
|
+
}
|
|
431
471
|
/**
|
|
432
472
|
* Copies skill folders (folders containing SKILL.md) from .claude/rules to .claude/skills.
|
|
433
473
|
* This allows users to organize skills in the rules directory and have them automatically
|
|
@@ -465,3 +505,103 @@ async function copySkillFoldersFromRules(skillerDir, verbose, dryRun) {
|
|
|
465
505
|
}
|
|
466
506
|
(0, constants_1.logVerboseInfo)(`Copied ${skillFolders.length} skill folder(s) from rules to skills`, verbose, dryRun);
|
|
467
507
|
}
|
|
508
|
+
/**
|
|
509
|
+
* Copies standalone .mdc files from .claude/rules to .claude/skills/name/name.mdc.
|
|
510
|
+
* These are rule files (not skill folders) that should be available in the skills directory.
|
|
511
|
+
* No SKILL.md is generated - these remain as .mdc files only.
|
|
512
|
+
*/
|
|
513
|
+
async function copyMdcFilesFromRules(skillerDir, verbose, dryRun) {
|
|
514
|
+
const rulesDir = path.join(skillerDir, 'rules');
|
|
515
|
+
const skillsDir = path.join(skillerDir, 'skills');
|
|
516
|
+
const copiedNames = [];
|
|
517
|
+
// Check if rules directory exists
|
|
518
|
+
try {
|
|
519
|
+
await fs.access(rulesDir);
|
|
520
|
+
}
|
|
521
|
+
catch {
|
|
522
|
+
return copiedNames;
|
|
523
|
+
}
|
|
524
|
+
const entries = await fs.readdir(rulesDir, { withFileTypes: true });
|
|
525
|
+
// Find .mdc files at rules root (not in subdirectories)
|
|
526
|
+
const mdcFiles = entries.filter((e) => e.isFile() && e.name.endsWith('.mdc'));
|
|
527
|
+
for (const mdcFile of mdcFiles) {
|
|
528
|
+
const skillName = path.basename(mdcFile.name, '.mdc');
|
|
529
|
+
const sourcePath = path.join(rulesDir, mdcFile.name);
|
|
530
|
+
const targetDir = path.join(skillsDir, skillName);
|
|
531
|
+
const targetPath = path.join(targetDir, mdcFile.name);
|
|
532
|
+
try {
|
|
533
|
+
const content = await fs.readFile(sourcePath, 'utf8');
|
|
534
|
+
// Parse and clean frontmatter - remove globs and alwaysApply: false
|
|
535
|
+
const { frontmatter, body } = (0, FrontmatterParser_1.parseFrontmatter)(content);
|
|
536
|
+
let cleanedContent;
|
|
537
|
+
if (frontmatter && Object.keys(frontmatter).length > 0) {
|
|
538
|
+
const cleanedFrontmatter = {};
|
|
539
|
+
// Only keep description and alwaysApply: true
|
|
540
|
+
if (frontmatter.description) {
|
|
541
|
+
cleanedFrontmatter.description = frontmatter.description;
|
|
542
|
+
}
|
|
543
|
+
if (frontmatter.alwaysApply === true) {
|
|
544
|
+
cleanedFrontmatter.alwaysApply = true;
|
|
545
|
+
}
|
|
546
|
+
// Note: globs and alwaysApply: false are intentionally omitted
|
|
547
|
+
if (Object.keys(cleanedFrontmatter).length > 0) {
|
|
548
|
+
cleanedContent = `---
|
|
549
|
+
${yaml.dump(cleanedFrontmatter, { lineWidth: -1, noRefs: true }).trim()}
|
|
550
|
+
---
|
|
551
|
+
|
|
552
|
+
${body}
|
|
553
|
+
`;
|
|
554
|
+
}
|
|
555
|
+
else {
|
|
556
|
+
cleanedContent = body;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
else {
|
|
560
|
+
cleanedContent = content;
|
|
561
|
+
}
|
|
562
|
+
if (dryRun) {
|
|
563
|
+
(0, constants_1.logVerboseInfo)(`DRY RUN: Would copy ${mdcFile.name} from rules to skills/${skillName}/${mdcFile.name}`, verbose, dryRun);
|
|
564
|
+
}
|
|
565
|
+
else {
|
|
566
|
+
await fs.mkdir(targetDir, { recursive: true });
|
|
567
|
+
await fs.writeFile(targetPath, cleanedContent, 'utf8');
|
|
568
|
+
(0, constants_1.logVerboseInfo)(`Copied ${mdcFile.name} from rules to skills/${skillName}/${mdcFile.name}`, verbose, dryRun);
|
|
569
|
+
}
|
|
570
|
+
copiedNames.push(skillName);
|
|
571
|
+
}
|
|
572
|
+
catch (err) {
|
|
573
|
+
(0, constants_1.logWarn)(`Failed to copy ${mdcFile.name}: ${err.message}`, dryRun);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
if (copiedNames.length > 0) {
|
|
577
|
+
(0, constants_1.logVerboseInfo)(`Copied ${copiedNames.length} .mdc file(s) from rules to skills`, verbose, dryRun);
|
|
578
|
+
}
|
|
579
|
+
return copiedNames;
|
|
580
|
+
}
|
|
581
|
+
/**
|
|
582
|
+
* Deletes the .claude/rules directory after content has been migrated to .claude/skills.
|
|
583
|
+
* This completes the migration from the old rules-based structure to the new skills-based structure.
|
|
584
|
+
*/
|
|
585
|
+
async function deleteRulesDir(skillerDir, verbose, dryRun) {
|
|
586
|
+
const rulesDir = path.join(skillerDir, 'rules');
|
|
587
|
+
// Check if rules directory exists
|
|
588
|
+
try {
|
|
589
|
+
await fs.access(rulesDir);
|
|
590
|
+
}
|
|
591
|
+
catch {
|
|
592
|
+
return false; // No rules directory to delete
|
|
593
|
+
}
|
|
594
|
+
if (dryRun) {
|
|
595
|
+
(0, constants_1.logVerboseInfo)(`DRY RUN: Would delete .claude/rules directory after migration`, verbose, dryRun);
|
|
596
|
+
return true;
|
|
597
|
+
}
|
|
598
|
+
try {
|
|
599
|
+
await fs.rm(rulesDir, { recursive: true, force: true });
|
|
600
|
+
(0, constants_1.logVerboseInfo)(`Deleted .claude/rules directory after migration to .claude/skills`, verbose, dryRun);
|
|
601
|
+
return true;
|
|
602
|
+
}
|
|
603
|
+
catch (err) {
|
|
604
|
+
(0, constants_1.logWarn)(`Failed to delete .claude/rules: ${err.message}`, dryRun);
|
|
605
|
+
return false;
|
|
606
|
+
}
|
|
607
|
+
}
|
package/dist/core/SkillsUtils.js
CHANGED
|
@@ -34,6 +34,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.hasSkillMd = hasSkillMd;
|
|
37
|
+
exports.hasAlwaysApplyMdc = hasAlwaysApplyMdc;
|
|
37
38
|
exports.isGroupingDir = isGroupingDir;
|
|
38
39
|
exports.walkSkillsTree = walkSkillsTree;
|
|
39
40
|
exports.formatValidationWarnings = formatValidationWarnings;
|
|
@@ -42,6 +43,7 @@ exports.copySkillsDirectoryWithTransform = copySkillsDirectoryWithTransform;
|
|
|
42
43
|
const path = __importStar(require("path"));
|
|
43
44
|
const fs = __importStar(require("fs/promises"));
|
|
44
45
|
const constants_1 = require("../constants");
|
|
46
|
+
const FrontmatterParser_1 = require("./FrontmatterParser");
|
|
45
47
|
/**
|
|
46
48
|
* Checks if a directory contains a SKILL.md file.
|
|
47
49
|
*/
|
|
@@ -55,6 +57,30 @@ async function hasSkillMd(dirPath) {
|
|
|
55
57
|
return false;
|
|
56
58
|
}
|
|
57
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* Checks if a directory contains an .mdc file with alwaysApply: true.
|
|
62
|
+
* These directories are valid without SKILL.md since alwaysApply rules
|
|
63
|
+
* are Cursor-style rules, not Claude Code skills.
|
|
64
|
+
*/
|
|
65
|
+
async function hasAlwaysApplyMdc(dirPath) {
|
|
66
|
+
try {
|
|
67
|
+
const entries = await fs.readdir(dirPath, { withFileTypes: true });
|
|
68
|
+
for (const entry of entries) {
|
|
69
|
+
if (entry.isFile() && entry.name.endsWith('.mdc')) {
|
|
70
|
+
const mdcPath = path.join(dirPath, entry.name);
|
|
71
|
+
const content = await fs.readFile(mdcPath, 'utf8');
|
|
72
|
+
const { frontmatter } = (0, FrontmatterParser_1.parseFrontmatter)(content);
|
|
73
|
+
if (frontmatter?.alwaysApply === true) {
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
58
84
|
/**
|
|
59
85
|
* Checks if a directory is a grouping directory (contains subdirectories with SKILL.md).
|
|
60
86
|
*/
|
|
@@ -121,8 +147,12 @@ async function walkSkillsTree(root) {
|
|
|
121
147
|
await walk(entryPath, entryRelativePath, depth + 1);
|
|
122
148
|
}
|
|
123
149
|
else {
|
|
124
|
-
//
|
|
125
|
-
|
|
150
|
+
// Check if this is a valid alwaysApply directory (no SKILL.md expected)
|
|
151
|
+
const hasAlwaysApply = await hasAlwaysApplyMdc(entryPath);
|
|
152
|
+
if (!hasAlwaysApply) {
|
|
153
|
+
// This is neither a skill nor a grouping directory - warn about it
|
|
154
|
+
warnings.push(`Directory '${entryRelativePath}' in .claude/skills has no SKILL.md and contains no sub-skills. It may be malformed or stray.`);
|
|
155
|
+
}
|
|
126
156
|
}
|
|
127
157
|
}
|
|
128
158
|
}
|
package/dist/lib.js
CHANGED
|
@@ -102,11 +102,11 @@ async function applyAllAgentConfigs(projectRoot, includedAgents, configPath, cli
|
|
|
102
102
|
(0, constants_1.logVerbose)(`Selected ${selectedAgents.length} agents: ${selectedAgents.map((a) => a.getName()).join(', ')}`, verbose);
|
|
103
103
|
// Propagate skills (or cleanup if disabled) - do this for each nested directory
|
|
104
104
|
const skillsEnabledResolved = resolveSkillsEnabled(skillsEnabled, rootConfig.skills?.enabled);
|
|
105
|
-
const { propagateSkills,
|
|
106
|
-
//
|
|
105
|
+
const { propagateSkills, migrateRulesToSkills } = await Promise.resolve().then(() => __importStar(require('./core/SkillsProcessor')));
|
|
106
|
+
// Migrate content from .claude/rules to .claude/skills (only if rules exists)
|
|
107
107
|
if (skillsEnabledResolved) {
|
|
108
108
|
for (const configEntry of hierarchicalConfigs) {
|
|
109
|
-
await
|
|
109
|
+
await migrateRulesToSkills(configEntry.skillerDir, verbose, dryRun);
|
|
110
110
|
}
|
|
111
111
|
}
|
|
112
112
|
// Propagate skills for each nested .claude directory (or cleanup if disabled)
|
|
@@ -130,10 +130,10 @@ async function applyAllAgentConfigs(projectRoot, includedAgents, configPath, cli
|
|
|
130
130
|
(0, constants_1.logVerbose)(`Selected ${selectedAgents.length} agents: ${selectedAgents.map((a) => a.getName()).join(', ')}`, verbose);
|
|
131
131
|
// Propagate skills (or cleanup if disabled)
|
|
132
132
|
const skillsEnabledResolved = resolveSkillsEnabled(skillsEnabled, singleConfig.config.skills?.enabled);
|
|
133
|
-
const { propagateSkills,
|
|
134
|
-
//
|
|
133
|
+
const { propagateSkills, migrateRulesToSkills } = await Promise.resolve().then(() => __importStar(require('./core/SkillsProcessor')));
|
|
134
|
+
// Migrate content from .claude/rules to .claude/skills (only if rules exists)
|
|
135
135
|
if (skillsEnabledResolved) {
|
|
136
|
-
await
|
|
136
|
+
await migrateRulesToSkills(singleConfig.skillerDir, verbose, dryRun);
|
|
137
137
|
}
|
|
138
138
|
// Always call propagateSkills - it handles cleanup when disabled
|
|
139
139
|
await propagateSkills(projectRoot, selectedAgents, skillsEnabledResolved, verbose, dryRun, singleConfig.skillerDir);
|