skiller 0.9.3 → 0.9.5

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.
@@ -86,6 +86,88 @@ function formatMatch(match) {
86
86
  const source = match.source || match.slug;
87
87
  return `${source}@${match.name} (${formatInstalls(match.installs)})`;
88
88
  }
89
+ async function readJsonObject(filePath) {
90
+ try {
91
+ const raw = JSON.parse(await fs.readFile(filePath, 'utf8'));
92
+ return raw && typeof raw === 'object'
93
+ ? raw
94
+ : null;
95
+ }
96
+ catch {
97
+ return null;
98
+ }
99
+ }
100
+ async function cleanupLegacyClaudePluginState(projectRoot, pluginIds) {
101
+ const pluginIdSet = new Set(pluginIds);
102
+ if (pluginIdSet.size === 0)
103
+ return;
104
+ const settingsPath = path.join(projectRoot, '.claude', 'settings.json');
105
+ const settings = await readJsonObject(settingsPath);
106
+ if (settings) {
107
+ const enabledPlugins = settings.enabledPlugins;
108
+ if (enabledPlugins && typeof enabledPlugins === 'object') {
109
+ const nextEnabledPlugins = Object.fromEntries(Object.entries(enabledPlugins).filter(([pluginId]) => !pluginIdSet.has(pluginId)));
110
+ if (Object.keys(nextEnabledPlugins).length !==
111
+ Object.keys(enabledPlugins).length) {
112
+ if (Object.keys(nextEnabledPlugins).length === 0) {
113
+ delete settings.enabledPlugins;
114
+ }
115
+ else {
116
+ settings.enabledPlugins = nextEnabledPlugins;
117
+ }
118
+ await fs.writeFile(settingsPath, JSON.stringify(settings, null, 2) + '\n');
119
+ }
120
+ }
121
+ }
122
+ for (const manifestPath of [
123
+ path.join(projectRoot, '.agents', '.skiller.json'),
124
+ path.join(projectRoot, '.claude', '.skiller.json'),
125
+ ]) {
126
+ const manifest = await readJsonObject(manifestPath);
127
+ if (!manifest)
128
+ continue;
129
+ const targets = manifest.targets;
130
+ if (!targets || typeof targets !== 'object')
131
+ continue;
132
+ let changed = false;
133
+ const nextTargets = {};
134
+ for (const [targetKey, rawEntries] of Object.entries(targets)) {
135
+ if (!Array.isArray(rawEntries)) {
136
+ nextTargets[targetKey] = rawEntries;
137
+ continue;
138
+ }
139
+ const filteredEntries = rawEntries.filter((entry) => {
140
+ if (!entry || typeof entry !== 'object')
141
+ return true;
142
+ const sourceType = entry.sourceType;
143
+ const pluginId = entry.pluginId;
144
+ if (sourceType !== 'plugin' || typeof pluginId !== 'string') {
145
+ return true;
146
+ }
147
+ return !pluginIdSet.has(pluginId);
148
+ });
149
+ if (filteredEntries.length !== rawEntries.length) {
150
+ changed = true;
151
+ }
152
+ if (filteredEntries.length > 0) {
153
+ nextTargets[targetKey] = filteredEntries;
154
+ }
155
+ else {
156
+ changed = true;
157
+ }
158
+ }
159
+ if (!changed)
160
+ continue;
161
+ manifest.targets = nextTargets;
162
+ const localSkills = manifest.localSkills;
163
+ const hasLocalSkills = Array.isArray(localSkills) && localSkills.length > 0;
164
+ if (Object.keys(nextTargets).length === 0 && !hasLocalSkills) {
165
+ await fs.rm(manifestPath, { force: true });
166
+ continue;
167
+ }
168
+ await fs.writeFile(manifestPath, JSON.stringify(manifest, null, 2) + '\n');
169
+ }
170
+ }
89
171
  async function promptLine(message) {
90
172
  const rl = readline.createInterface({
91
173
  input: process.stdin,
@@ -323,13 +405,20 @@ async function migrateClaudePluginsHandler(argv) {
323
405
  console.log('[skiller] Run again with --execute to install the resolved repos through skills.');
324
406
  return;
325
407
  }
326
- if (plan.unresolved.length > 0) {
327
- throw new Error(`Cannot execute migration until all plugins resolve:\n${plan.unresolved.map((entry) => `- ${entry.pluginId}: ${entry.reason}`).join('\n')}`);
408
+ if (plan.installs.length === 0 && plan.unresolved.length > 0) {
409
+ throw new Error(`Cannot execute migration because no installable plugin repos were found:\n${plan.unresolved.map((entry) => `- ${entry.pluginId}: ${entry.reason}`).join('\n')}`);
328
410
  }
329
411
  for (const install of plan.installs) {
330
412
  await (0, skills_cli_1.runSkillsCli)(projectRoot, buildClaudePluginMigrationArgs(install.source));
331
413
  }
332
- console.log('[skiller] Claude plugin repo migration completed. Remove the plugin entries from .claude/settings.json, then rerun skiller apply.');
414
+ await cleanupLegacyClaudePluginState(projectRoot, [
415
+ ...plan.installs.flatMap((install) => install.pluginIds),
416
+ ...plan.unresolved.map((entry) => entry.pluginId),
417
+ ]);
418
+ if (plan.unresolved.length > 0) {
419
+ console.log(`[skiller] Skipped unresolved plugins:\n${plan.unresolved.map((entry) => `- ${entry.pluginId}: ${entry.reason}`).join('\n')}`);
420
+ }
421
+ console.log('[skiller] Claude plugin repo migration completed. Legacy Claude plugin entries were removed from settings/manifests; rerun skiller apply.');
333
422
  }
334
423
  catch (err) {
335
424
  const message = err instanceof Error ? err.message : String(err);
@@ -218,6 +218,27 @@ async function planDirectoryMigration(sourceDir, destinationDir, plannedWrites,
218
218
  deletePaths.add(sourceDir);
219
219
  }
220
220
  }
221
+ async function planLegacySkillsMigration(legacySkillsDir, canonicalSkillsDir, plannedWrites, deletePaths, conflicts) {
222
+ if (!(await pathExists(legacySkillsDir)))
223
+ return;
224
+ const entries = await fs.readdir(legacySkillsDir, { withFileTypes: true });
225
+ for (const entry of entries) {
226
+ if (!entry.isDirectory())
227
+ continue;
228
+ const sourceDir = path.join(legacySkillsDir, entry.name);
229
+ const destinationDir = path.join(canonicalSkillsDir, entry.name);
230
+ if (await pathExists(destinationDir)) {
231
+ // Canonical .agents/skills already owns this name now. Legacy
232
+ // .claude/skills duplicates are stale migration debris.
233
+ deletePaths.add(sourceDir);
234
+ continue;
235
+ }
236
+ await planDirectoryMigration(sourceDir, destinationDir, plannedWrites, deletePaths, conflicts);
237
+ }
238
+ if (conflicts.length === 0) {
239
+ deletePaths.add(legacySkillsDir);
240
+ }
241
+ }
221
242
  async function findRuleSkillFolders(dir) {
222
243
  const folders = [];
223
244
  const entries = await fs.readdir(dir, { withFileTypes: true });
@@ -395,7 +416,7 @@ async function migrateLegacyProjectState(projectRoot, dryRun) {
395
416
  }
396
417
  }
397
418
  await planFileMigration(path.join(legacyDir, project_paths_1.PROJECT_AGENTS_FILE), path.join(canonicalDir, project_paths_1.PROJECT_AGENTS_FILE), plannedWrites, deletePaths, conflicts);
398
- await planDirectoryMigration(path.join(legacyDir, 'skills'), canonicalSkillsDir, plannedWrites, deletePaths, conflicts);
419
+ await planLegacySkillsMigration(path.join(legacyDir, 'skills'), canonicalSkillsDir, plannedWrites, deletePaths, conflicts);
399
420
  await planLegacyRulesMigration(path.join(legacyDir, 'rules'), canonicalRulesDir, canonicalSkillsDir, plannedWrites, deletePaths, conflicts);
400
421
  if (conflicts.length > 0) {
401
422
  throw new Error(`Legacy .claude migration conflicts:\n- ${conflicts.join('\n- ')}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skiller",
3
- "version": "0.9.3",
3
+ "version": "0.9.5",
4
4
  "description": "Skiller — apply the same rules to all coding agents",
5
5
  "main": "dist/lib.js",
6
6
  "publishConfig": {