skiller 0.9.5 → 0.9.6

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.
@@ -71,6 +71,15 @@ async function executeSkillsWrapper(projectRoot, args) {
71
71
  function buildClaudePluginMigrationArgs(source) {
72
72
  return ['add', source, '--agent', 'universal', '--skill', '*', '-y'];
73
73
  }
74
+ function resolveRegistryMatchSource(match) {
75
+ if (match.source)
76
+ return match.source;
77
+ const parts = match.slug.split('/').filter(Boolean);
78
+ if (parts.length >= 2) {
79
+ return `${parts[0]}/${parts[1]}`;
80
+ }
81
+ return match.slug;
82
+ }
74
83
  function formatInstalls(count) {
75
84
  if (!count || count <= 0)
76
85
  return '0 installs';
@@ -168,6 +177,34 @@ async function cleanupLegacyClaudePluginState(projectRoot, pluginIds) {
168
177
  await fs.writeFile(manifestPath, JSON.stringify(manifest, null, 2) + '\n');
169
178
  }
170
179
  }
180
+ async function cleanupMigratedPluginAuxiliaryRules(projectRoot, sources) {
181
+ const candidateRuleNames = await (0, ClaudePluginMigration_1.listClaudePluginAuxiliaryRuleNames)(sources);
182
+ if (candidateRuleNames.length === 0)
183
+ return [];
184
+ const sourceSet = new Set(sources);
185
+ const plan = await (0, RulesToSkillsMigration_1.planRulesToSkillsMigration)(projectRoot, candidateRuleNames);
186
+ const removable = new Set(plan.unmatched);
187
+ for (const candidate of plan.candidates) {
188
+ const exactMatchSources = candidate.matches.map(resolveRegistryMatchSource);
189
+ if (exactMatchSources.length === 0 ||
190
+ exactMatchSources.every((source) => sourceSet.has(source))) {
191
+ removable.add(candidate.ruleName);
192
+ }
193
+ }
194
+ const removed = [...removable].sort((a, b) => a.localeCompare(b));
195
+ for (const ruleName of removed) {
196
+ await (0, RulesToSkillsMigration_1.removeLocalRuleReplacementState)(projectRoot, ruleName, false);
197
+ await fs.rm(path.join(projectRoot, '.agents', 'skills', ruleName), {
198
+ force: true,
199
+ recursive: true,
200
+ });
201
+ await fs.rm(path.join(projectRoot, '.claude', 'skills', ruleName), {
202
+ force: true,
203
+ recursive: true,
204
+ });
205
+ }
206
+ return removed;
207
+ }
171
208
  async function promptLine(message) {
172
209
  const rl = readline.createInterface({
173
210
  input: process.stdin,
@@ -415,6 +452,10 @@ async function migrateClaudePluginsHandler(argv) {
415
452
  ...plan.installs.flatMap((install) => install.pluginIds),
416
453
  ...plan.unresolved.map((entry) => entry.pluginId),
417
454
  ]);
455
+ const removedAuxiliaryRules = await cleanupMigratedPluginAuxiliaryRules(projectRoot, plan.installs.map((install) => install.source));
456
+ if (removedAuxiliaryRules.length > 0) {
457
+ console.log(`[skiller] Removed stale plugin-derived local rules:\n${removedAuxiliaryRules.map((name) => `- ${name}`).join('\n')}`);
458
+ }
418
459
  if (plan.unresolved.length > 0) {
419
460
  console.log(`[skiller] Skipped unresolved plugins:\n${plan.unresolved.map((entry) => `- ${entry.pluginId}: ${entry.reason}`).join('\n')}`);
420
461
  }
@@ -33,6 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.listClaudePluginAuxiliaryRuleNames = listClaudePluginAuxiliaryRuleNames;
36
37
  exports.planClaudePluginSkillsMigration = planClaudePluginSkillsMigration;
37
38
  const fs = __importStar(require("fs/promises"));
38
39
  const os = __importStar(require("os"));
@@ -86,6 +87,20 @@ function normalizeCloneSource(source) {
86
87
  }
87
88
  return source;
88
89
  }
90
+ function normalizeSkillNameForDir(value) {
91
+ return value.replace(/:/g, '-').trim();
92
+ }
93
+ function extractAuxiliarySkillNames(skillMd) {
94
+ const names = new Set();
95
+ const pattern = /\b[a-z0-9-]+:[a-z0-9-]+:([a-z0-9-]+)\b/g;
96
+ for (const match of skillMd.toLowerCase().matchAll(pattern)) {
97
+ const candidate = match[1]?.trim();
98
+ if (!candidate || !/[a-z]/.test(candidate))
99
+ continue;
100
+ names.add(candidate);
101
+ }
102
+ return [...names];
103
+ }
89
104
  function formatInspectionError(source, err) {
90
105
  if (typeof err === 'object' && err !== null) {
91
106
  const stderr = 'stderr' in err ? err.stderr : '';
@@ -112,18 +127,30 @@ async function inspectSkillsInstallSource(source) {
112
127
  repoDir,
113
128
  ]);
114
129
  const { skills } = await (0, SkillsUtils_1.walkSkillsTree)(repoDir);
130
+ const publishedSkillNames = new Set();
131
+ const auxiliarySkillNames = new Set();
115
132
  for (const skill of skills) {
116
133
  try {
117
134
  const skillMd = await fs.readFile(path.join(skill.path, 'SKILL.md'), 'utf8');
118
135
  const { frontmatter } = (0, FrontmatterParser_1.parseFrontmatter)(skillMd);
119
136
  if (frontmatter?.name && frontmatter.description) {
120
- return { installable: true };
137
+ publishedSkillNames.add(normalizeSkillNameForDir(String(frontmatter.name)));
138
+ for (const auxiliaryName of extractAuxiliarySkillNames(skillMd)) {
139
+ auxiliarySkillNames.add(auxiliaryName);
140
+ }
121
141
  }
122
142
  }
123
143
  catch {
124
144
  // Keep scanning. One malformed skill should not hide valid siblings.
125
145
  }
126
146
  }
147
+ if (publishedSkillNames.size > 0) {
148
+ return {
149
+ installable: true,
150
+ auxiliarySkillNames: [...auxiliarySkillNames].sort((a, b) => a.localeCompare(b)),
151
+ publishedSkillNames: [...publishedSkillNames].sort((a, b) => a.localeCompare(b)),
152
+ };
153
+ }
127
154
  return {
128
155
  installable: false,
129
156
  reason: `Resolved source ${source} has no valid SKILL.md files with name and description`,
@@ -139,6 +166,23 @@ async function inspectSkillsInstallSource(source) {
139
166
  await fs.rm(tmpDir, { recursive: true, force: true });
140
167
  }
141
168
  }
169
+ async function listClaudePluginAuxiliaryRuleNames(sources, options = {}) {
170
+ const inspectSource = options.inspectSource ?? inspectSkillsInstallSource;
171
+ const auxiliarySkillNames = new Set();
172
+ for (const source of [...new Set(sources)].sort((a, b) => a.localeCompare(b))) {
173
+ const inspection = await inspectSource(source);
174
+ if (!inspection.installable)
175
+ continue;
176
+ const published = new Set((inspection.publishedSkillNames ?? []).map(normalizeSkillNameForDir));
177
+ for (const auxiliaryName of inspection.auxiliarySkillNames ?? []) {
178
+ const normalized = normalizeSkillNameForDir(auxiliaryName);
179
+ if (!normalized || published.has(normalized))
180
+ continue;
181
+ auxiliarySkillNames.add(normalized);
182
+ }
183
+ }
184
+ return [...auxiliarySkillNames].sort((a, b) => a.localeCompare(b));
185
+ }
142
186
  async function readEnabledPluginIds(projectRoot) {
143
187
  const settingsPath = path.join(projectRoot, project_paths_1.LEGACY_SKILLER_DIR, 'settings.json');
144
188
  const raw = await readJsonFile(settingsPath);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skiller",
3
- "version": "0.9.5",
3
+ "version": "0.9.6",
4
4
  "description": "Skiller — apply the same rules to all coding agents",
5
5
  "main": "dist/lib.js",
6
6
  "publishConfig": {