skiller 0.7.1 → 0.7.2

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.
@@ -39,6 +39,7 @@ exports.discoverSkills = discoverSkills;
39
39
  exports.getSkillsGitignorePaths = getSkillsGitignorePaths;
40
40
  exports.propagateSkills = propagateSkills;
41
41
  exports.copySkillFoldersFromRules = copySkillFoldersFromRules;
42
+ exports.copyMdcFilesFromRules = copyMdcFilesFromRules;
42
43
  const path = __importStar(require("path"));
43
44
  const fs = __importStar(require("fs/promises"));
44
45
  const yaml = __importStar(require("js-yaml"));
@@ -140,8 +141,15 @@ async function syncMdcToSkillMd(skillsDir, verbose, dryRun) {
140
141
  }
141
142
  try {
142
143
  if (siblingMdcContent !== null && skillMdContent === null) {
143
- // Case 1: Sibling .mdc exists but no SKILL.md → generate SKILL.md with @reference
144
+ // Case 1: Sibling .mdc exists but no SKILL.md
144
145
  const { frontmatter: mdcFrontmatter } = (0, FrontmatterParser_1.parseFrontmatter)(siblingMdcContent);
146
+ // Skip SKILL.md generation for .mdc files with alwaysApply: true
147
+ // These are Cursor-style rules, not Claude Code skills
148
+ if (mdcFrontmatter?.alwaysApply === true) {
149
+ (0, constants_1.logVerboseInfo)(`Skipping SKILL.md generation for ${skillName} (alwaysApply rule)`, verbose, dryRun);
150
+ continue;
151
+ }
152
+ // Generate SKILL.md with @reference (absolute path)
145
153
  const skillFrontmatter = {
146
154
  name: skillName,
147
155
  description: mdcFrontmatter?.description || `Skill: ${skillName}`,
@@ -150,14 +158,14 @@ async function syncMdcToSkillMd(skillsDir, verbose, dryRun) {
150
158
  ${yaml.dump(skillFrontmatter, { lineWidth: -1, noRefs: true }).trim()}
151
159
  ---
152
160
 
153
- @./${skillName}.mdc
161
+ @.claude/skills/${skillName}/${skillName}.mdc
154
162
  `;
155
163
  if (dryRun) {
156
164
  (0, constants_1.logVerboseInfo)(`DRY RUN: Would generate ${skillName}/SKILL.md with @reference`, verbose, dryRun);
157
165
  }
158
166
  else {
159
167
  await fs.writeFile(skillMdPath, newSkillMd, 'utf8');
160
- (0, constants_1.logVerboseInfo)(`Generated ${skillName}/SKILL.md with @./${skillName}.mdc reference`, verbose, dryRun);
168
+ (0, constants_1.logVerboseInfo)(`Generated ${skillName}/SKILL.md with @.claude/skills/${skillName}/${skillName}.mdc reference`, verbose, dryRun);
161
169
  }
162
170
  synced.push(skillName);
163
171
  }
@@ -167,22 +175,32 @@ ${yaml.dump(skillFrontmatter, { lineWidth: -1, noRefs: true }).trim()}
167
175
  const refCheck = isReferenceBody(skillBody);
168
176
  if (refCheck.isReference) {
169
177
  // Case 2: SKILL.md is @reference → source file is truth
170
- if (refCheck.referencePath === `./${skillName}.mdc`) {
178
+ // Check for both relative and absolute sibling reference patterns
179
+ const isRelativeSiblingRef = refCheck.referencePath === `./${skillName}.mdc`;
180
+ const isAbsoluteSiblingRef = refCheck.referencePath ===
181
+ `.claude/skills/${skillName}/${skillName}.mdc`;
182
+ if (isRelativeSiblingRef || isAbsoluteSiblingRef) {
171
183
  // Sibling reference pattern - validate/update frontmatter if .mdc changed
172
184
  if (siblingMdcContent !== null) {
173
185
  const { frontmatter: mdcFrontmatter } = (0, FrontmatterParser_1.parseFrontmatter)(siblingMdcContent);
174
186
  // Update SKILL.md frontmatter if description changed in .mdc
175
- if (mdcFrontmatter?.description &&
176
- mdcFrontmatter.description !== skillFrontmatter?.description) {
187
+ // Also migrate from relative to absolute path if needed
188
+ const needsUpdate = (mdcFrontmatter?.description &&
189
+ mdcFrontmatter.description !==
190
+ skillFrontmatter?.description) ||
191
+ isRelativeSiblingRef; // Migrate old relative refs to absolute
192
+ if (needsUpdate) {
177
193
  const newFrontmatter = {
178
194
  name: skillFrontmatter?.name || skillName,
179
- description: mdcFrontmatter.description,
195
+ description: mdcFrontmatter?.description ||
196
+ skillFrontmatter?.description ||
197
+ `Skill: ${skillName}`,
180
198
  };
181
199
  const newSkillMd = `---
182
200
  ${yaml.dump(newFrontmatter, { lineWidth: -1, noRefs: true }).trim()}
183
201
  ---
184
202
 
185
- @./${skillName}.mdc
203
+ @.claude/skills/${skillName}/${skillName}.mdc
186
204
  `;
187
205
  if (dryRun) {
188
206
  (0, constants_1.logVerboseInfo)(`DRY RUN: Would update ${skillName}/SKILL.md frontmatter from .mdc`, verbose, dryRun);
@@ -215,8 +233,7 @@ ${yaml.dump(newFrontmatter, { lineWidth: -1, noRefs: true }).trim()}
215
233
  const { frontmatter: refFrontmatter, body: refBody } = (0, FrontmatterParser_1.parseFrontmatter)(referencedContent);
216
234
  // Create sibling .mdc with the content
217
235
  let mdcContent;
218
- if (refFrontmatter &&
219
- Object.keys(refFrontmatter).length > 0) {
236
+ if (refFrontmatter && Object.keys(refFrontmatter).length > 0) {
220
237
  const mdcFrontmatterData = {};
221
238
  if (refFrontmatter.description) {
222
239
  mdcFrontmatterData.description = refFrontmatter.description;
@@ -242,7 +259,7 @@ ${refBody}
242
259
  else {
243
260
  mdcContent = referencedContent;
244
261
  }
245
- // Update SKILL.md to point to sibling .mdc
262
+ // Update SKILL.md to point to sibling .mdc (absolute path)
246
263
  const newFrontmatter = {
247
264
  name: skillFrontmatter?.name || skillName,
248
265
  description: refFrontmatter?.description ||
@@ -253,7 +270,7 @@ ${refBody}
253
270
  ${yaml.dump(newFrontmatter, { lineWidth: -1, noRefs: true }).trim()}
254
271
  ---
255
272
 
256
- @./${skillName}.mdc
273
+ @.claude/skills/${skillName}/${skillName}.mdc
257
274
  `;
258
275
  if (dryRun) {
259
276
  (0, constants_1.logVerboseInfo)(`DRY RUN: Would migrate ${skillName} from ${refCheck.referencePath} to sibling pattern`, verbose, dryRun);
@@ -290,7 +307,7 @@ ${skillBody}
290
307
  else {
291
308
  mdcContent = skillBody;
292
309
  }
293
- // Update SKILL.md to @reference
310
+ // Update SKILL.md to @reference (absolute path)
294
311
  const newSkillFrontmatter = {
295
312
  name: skillFrontmatter?.name || skillName,
296
313
  description: skillFrontmatter?.description || `Skill: ${skillName}`,
@@ -299,7 +316,7 @@ ${skillBody}
299
316
  ${yaml.dump(newSkillFrontmatter, { lineWidth: -1, noRefs: true }).trim()}
300
317
  ---
301
318
 
302
- @./${skillName}.mdc
319
+ @.claude/skills/${skillName}/${skillName}.mdc
303
320
  `;
304
321
  if (dryRun) {
305
322
  (0, constants_1.logVerboseInfo)(`DRY RUN: Would generate ${skillName}/${skillName}.mdc and update SKILL.md`, verbose, dryRun);
@@ -465,3 +482,48 @@ async function copySkillFoldersFromRules(skillerDir, verbose, dryRun) {
465
482
  }
466
483
  (0, constants_1.logVerboseInfo)(`Copied ${skillFolders.length} skill folder(s) from rules to skills`, verbose, dryRun);
467
484
  }
485
+ /**
486
+ * Copies standalone .mdc files from .claude/rules to .claude/skills/name/name.mdc.
487
+ * These are rule files (not skill folders) that should be available in the skills directory.
488
+ * No SKILL.md is generated - these remain as .mdc files only.
489
+ */
490
+ async function copyMdcFilesFromRules(skillerDir, verbose, dryRun) {
491
+ const rulesDir = path.join(skillerDir, 'rules');
492
+ const skillsDir = path.join(skillerDir, 'skills');
493
+ const copiedNames = [];
494
+ // Check if rules directory exists
495
+ try {
496
+ await fs.access(rulesDir);
497
+ }
498
+ catch {
499
+ return copiedNames;
500
+ }
501
+ const entries = await fs.readdir(rulesDir, { withFileTypes: true });
502
+ // Find .mdc files at rules root (not in subdirectories)
503
+ const mdcFiles = entries.filter((e) => e.isFile() && e.name.endsWith('.mdc'));
504
+ for (const mdcFile of mdcFiles) {
505
+ const skillName = path.basename(mdcFile.name, '.mdc');
506
+ const sourcePath = path.join(rulesDir, mdcFile.name);
507
+ const targetDir = path.join(skillsDir, skillName);
508
+ const targetPath = path.join(targetDir, mdcFile.name);
509
+ try {
510
+ if (dryRun) {
511
+ (0, constants_1.logVerboseInfo)(`DRY RUN: Would copy ${mdcFile.name} from rules to skills/${skillName}/${mdcFile.name}`, verbose, dryRun);
512
+ }
513
+ else {
514
+ await fs.mkdir(targetDir, { recursive: true });
515
+ const content = await fs.readFile(sourcePath, 'utf8');
516
+ await fs.writeFile(targetPath, content, 'utf8');
517
+ (0, constants_1.logVerboseInfo)(`Copied ${mdcFile.name} from rules to skills/${skillName}/${mdcFile.name}`, verbose, dryRun);
518
+ }
519
+ copiedNames.push(skillName);
520
+ }
521
+ catch (err) {
522
+ (0, constants_1.logWarn)(`Failed to copy ${mdcFile.name}: ${err.message}`, dryRun);
523
+ }
524
+ }
525
+ if (copiedNames.length > 0) {
526
+ (0, constants_1.logVerboseInfo)(`Copied ${copiedNames.length} .mdc file(s) from rules to skills`, verbose, dryRun);
527
+ }
528
+ return copiedNames;
529
+ }
package/dist/lib.js CHANGED
@@ -102,11 +102,12 @@ 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, copySkillFoldersFromRules } = await Promise.resolve().then(() => __importStar(require('./core/SkillsProcessor')));
106
- // Copy skill folders from rules to skills
105
+ const { propagateSkills, copySkillFoldersFromRules, copyMdcFilesFromRules, } = await Promise.resolve().then(() => __importStar(require('./core/SkillsProcessor')));
106
+ // Copy skill folders and .mdc files from rules to skills
107
107
  if (skillsEnabledResolved) {
108
108
  for (const configEntry of hierarchicalConfigs) {
109
109
  await copySkillFoldersFromRules(configEntry.skillerDir, verbose, dryRun);
110
+ await copyMdcFilesFromRules(configEntry.skillerDir, verbose, dryRun);
110
111
  }
111
112
  }
112
113
  // Propagate skills for each nested .claude directory (or cleanup if disabled)
@@ -130,10 +131,11 @@ async function applyAllAgentConfigs(projectRoot, includedAgents, configPath, cli
130
131
  (0, constants_1.logVerbose)(`Selected ${selectedAgents.length} agents: ${selectedAgents.map((a) => a.getName()).join(', ')}`, verbose);
131
132
  // Propagate skills (or cleanup if disabled)
132
133
  const skillsEnabledResolved = resolveSkillsEnabled(skillsEnabled, singleConfig.config.skills?.enabled);
133
- const { propagateSkills, copySkillFoldersFromRules } = await Promise.resolve().then(() => __importStar(require('./core/SkillsProcessor')));
134
- // Copy skill folders from rules to skills
134
+ const { propagateSkills, copySkillFoldersFromRules, copyMdcFilesFromRules, } = await Promise.resolve().then(() => __importStar(require('./core/SkillsProcessor')));
135
+ // Copy skill folders and .mdc files from rules to skills
135
136
  if (skillsEnabledResolved) {
136
137
  await copySkillFoldersFromRules(singleConfig.skillerDir, verbose, dryRun);
138
+ await copyMdcFilesFromRules(singleConfig.skillerDir, verbose, dryRun);
137
139
  }
138
140
  // Always call propagateSkills - it handles cleanup when disabled
139
141
  await propagateSkills(projectRoot, selectedAgents, skillsEnabledResolved, verbose, dryRun, singleConfig.skillerDir);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skiller",
3
- "version": "0.7.1",
3
+ "version": "0.7.2",
4
4
  "description": "Skiller — apply the same rules to all coding agents",
5
5
  "main": "dist/lib.js",
6
6
  "publishConfig": {