skiller 0.9.5 → 0.9.7
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 +6 -4
- package/dist/cli/commands.js +16 -2
- package/dist/cli/handlers.js +100 -3
- package/dist/core/ClaudePluginMigration.js +45 -1
- package/dist/core/RulesToSkillsMigration.js +1 -5
- package/dist/core/SkillOwnership.js +13 -43
- package/dist/core/SkillsManifest.js +28 -29
- package/dist/core/SkillsProcessor.js +195 -34
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,14 +4,16 @@ Apply the same rules (and skills) to multiple AI coding agents.
|
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
6
|
npx skiller@latest init
|
|
7
|
-
npx skiller@latest
|
|
7
|
+
npx skiller@latest install
|
|
8
8
|
```
|
|
9
9
|
|
|
10
10
|
## Skills
|
|
11
11
|
|
|
12
|
-
- `.
|
|
13
|
-
-
|
|
14
|
-
-
|
|
12
|
+
- `.agents/rules/*.mdc` is local rule authoring
|
|
13
|
+
- `.agents/skills/` is the canonical runtime skill tree
|
|
14
|
+
- `skills-lock.json` is the upstream source of truth for installed skills
|
|
15
|
+
- `skiller install` and `skiller update` use the local `skills` CLI, then auto-run `apply`
|
|
16
|
+
- `skiller apply` stays local and non-destructive
|
|
15
17
|
- See [docs/skills.md](docs/skills.md)
|
|
16
18
|
|
|
17
19
|
## MCP
|
package/dist/cli/commands.js
CHANGED
|
@@ -128,12 +128,26 @@ async function run() {
|
|
|
128
128
|
.command('list [args..]', 'Run the local skills CLI list command', skillsArgsBuilder, handlers_1.listHandler)
|
|
129
129
|
.command('find [args..]', 'Run the local skills CLI find command', skillsArgsBuilder, handlers_1.findHandler)
|
|
130
130
|
.command('check [args..]', 'Run the local skills CLI check command', skillsArgsBuilder, handlers_1.checkHandler)
|
|
131
|
-
.command('
|
|
131
|
+
.command('install [args..]', 'Run the local skills CLI install command, then skiller apply', (y) => skillsArgsBuilder(y)
|
|
132
|
+
.option('verbose', {
|
|
133
|
+
type: 'boolean',
|
|
134
|
+
description: 'Enable verbose logging for the follow-up apply step',
|
|
135
|
+
default: false,
|
|
136
|
+
})
|
|
137
|
+
.alias('verbose', 'v'), handlers_1.installHandler)
|
|
138
|
+
.command('update [args..]', 'Run the local skills CLI update command, then skiller apply', (y) => skillsArgsBuilder(y)
|
|
139
|
+
.option('verbose', {
|
|
140
|
+
type: 'boolean',
|
|
141
|
+
description: 'Enable verbose logging for the follow-up apply step',
|
|
142
|
+
default: false,
|
|
143
|
+
})
|
|
144
|
+
.alias('verbose', 'v'), handlers_1.updateHandler)
|
|
145
|
+
.command('outdated [args..]', 'Run the local skills CLI outdated command', skillsArgsBuilder, handlers_1.outdatedHandler)
|
|
132
146
|
.command('skills <subcommand> [args..]', 'Pass through an arbitrary command to the local skills CLI', (y) => skillsArgsBuilder(y).positional('subcommand', {
|
|
133
147
|
type: 'string',
|
|
134
148
|
description: 'The local skills CLI subcommand to run',
|
|
135
149
|
}), handlers_1.skillsHandler)
|
|
136
|
-
.command('init', 'Scaffold a .
|
|
150
|
+
.command('init', 'Scaffold a .agents directory with default files', (y) => {
|
|
137
151
|
return y
|
|
138
152
|
.option('project-root', {
|
|
139
153
|
type: 'string',
|
package/dist/cli/handlers.js
CHANGED
|
@@ -38,11 +38,13 @@ exports.initHandler = initHandler;
|
|
|
38
38
|
exports.migrateClaudePluginsHandler = migrateClaudePluginsHandler;
|
|
39
39
|
exports.migrateRulesToSkillsHandler = migrateRulesToSkillsHandler;
|
|
40
40
|
exports.addHandler = addHandler;
|
|
41
|
+
exports.installHandler = installHandler;
|
|
41
42
|
exports.removeHandler = removeHandler;
|
|
42
43
|
exports.listHandler = listHandler;
|
|
43
44
|
exports.findHandler = findHandler;
|
|
44
45
|
exports.checkHandler = checkHandler;
|
|
45
46
|
exports.updateHandler = updateHandler;
|
|
47
|
+
exports.outdatedHandler = outdatedHandler;
|
|
46
48
|
exports.skillsHandler = skillsHandler;
|
|
47
49
|
exports.revertHandler = revertHandler;
|
|
48
50
|
const lib_1 = require("../lib");
|
|
@@ -68,9 +70,27 @@ async function executeSkillsWrapper(projectRoot, args) {
|
|
|
68
70
|
process.exit(1);
|
|
69
71
|
}
|
|
70
72
|
}
|
|
73
|
+
async function applyAfterSkillsLifecycleStep(projectRoot, verbose) {
|
|
74
|
+
await applyHandler({
|
|
75
|
+
'project-root': projectRoot,
|
|
76
|
+
verbose,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
71
79
|
function buildClaudePluginMigrationArgs(source) {
|
|
72
80
|
return ['add', source, '--agent', 'universal', '--skill', '*', '-y'];
|
|
73
81
|
}
|
|
82
|
+
const LEGACY_EXTERNAL_RULE_REPLACEMENT_SOURCES = new Set([
|
|
83
|
+
'ratacat/claude-skills',
|
|
84
|
+
]);
|
|
85
|
+
function resolveRegistryMatchSource(match) {
|
|
86
|
+
if (match.source)
|
|
87
|
+
return match.source;
|
|
88
|
+
const parts = match.slug.split('/').filter(Boolean);
|
|
89
|
+
if (parts.length >= 2) {
|
|
90
|
+
return `${parts[0]}/${parts[1]}`;
|
|
91
|
+
}
|
|
92
|
+
return match.slug;
|
|
93
|
+
}
|
|
74
94
|
function formatInstalls(count) {
|
|
75
95
|
if (!count || count <= 0)
|
|
76
96
|
return '0 installs';
|
|
@@ -159,15 +179,70 @@ async function cleanupLegacyClaudePluginState(projectRoot, pluginIds) {
|
|
|
159
179
|
if (!changed)
|
|
160
180
|
continue;
|
|
161
181
|
manifest.targets = nextTargets;
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
if (Object.keys(nextTargets).length === 0 && !hasLocalSkills) {
|
|
182
|
+
delete manifest.localSkills;
|
|
183
|
+
if (Object.keys(nextTargets).length === 0) {
|
|
165
184
|
await fs.rm(manifestPath, { force: true });
|
|
166
185
|
continue;
|
|
167
186
|
}
|
|
168
187
|
await fs.writeFile(manifestPath, JSON.stringify(manifest, null, 2) + '\n');
|
|
169
188
|
}
|
|
170
189
|
}
|
|
190
|
+
async function cleanupMigratedPluginAuxiliaryRules(projectRoot, sources) {
|
|
191
|
+
const candidateRuleNames = await (0, ClaudePluginMigration_1.listClaudePluginAuxiliaryRuleNames)(sources);
|
|
192
|
+
if (candidateRuleNames.length === 0)
|
|
193
|
+
return [];
|
|
194
|
+
const sourceSet = new Set(sources);
|
|
195
|
+
const plan = await (0, RulesToSkillsMigration_1.planRulesToSkillsMigration)(projectRoot, candidateRuleNames);
|
|
196
|
+
const removable = new Set(plan.unmatched);
|
|
197
|
+
for (const candidate of plan.candidates) {
|
|
198
|
+
const exactMatchSources = candidate.matches.map(resolveRegistryMatchSource);
|
|
199
|
+
if (exactMatchSources.length === 0 ||
|
|
200
|
+
exactMatchSources.every((source) => sourceSet.has(source))) {
|
|
201
|
+
removable.add(candidate.ruleName);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
const removed = [...removable].sort((a, b) => a.localeCompare(b));
|
|
205
|
+
for (const ruleName of removed) {
|
|
206
|
+
await (0, RulesToSkillsMigration_1.removeLocalRuleReplacementState)(projectRoot, ruleName, false);
|
|
207
|
+
await fs.rm(path.join(projectRoot, '.agents', 'skills', ruleName), {
|
|
208
|
+
force: true,
|
|
209
|
+
recursive: true,
|
|
210
|
+
});
|
|
211
|
+
await fs.rm(path.join(projectRoot, '.claude', 'skills', ruleName), {
|
|
212
|
+
force: true,
|
|
213
|
+
recursive: true,
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
return removed;
|
|
217
|
+
}
|
|
218
|
+
async function cleanupLegacyExternalRuleMatches(projectRoot) {
|
|
219
|
+
const plan = await (0, RulesToSkillsMigration_1.planRulesToSkillsMigration)(projectRoot);
|
|
220
|
+
const removals = new Map();
|
|
221
|
+
for (const candidate of plan.candidates) {
|
|
222
|
+
if (!candidate.matches.some((match) => LEGACY_EXTERNAL_RULE_REPLACEMENT_SOURCES.has(resolveRegistryMatchSource(match)))) {
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
removals.set(candidate.ruleName, {
|
|
226
|
+
alreadyInstalled: candidate.alreadyInstalled,
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
const removed = [...removals.keys()].sort((a, b) => a.localeCompare(b));
|
|
230
|
+
for (const ruleName of removed) {
|
|
231
|
+
const removal = removals.get(ruleName);
|
|
232
|
+
await (0, RulesToSkillsMigration_1.removeLocalRuleReplacementState)(projectRoot, ruleName, false);
|
|
233
|
+
if (!removal?.alreadyInstalled) {
|
|
234
|
+
await fs.rm(path.join(projectRoot, '.agents', 'skills', ruleName), {
|
|
235
|
+
force: true,
|
|
236
|
+
recursive: true,
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
await fs.rm(path.join(projectRoot, '.claude', 'skills', ruleName), {
|
|
240
|
+
force: true,
|
|
241
|
+
recursive: true,
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
return removed;
|
|
245
|
+
}
|
|
171
246
|
async function promptLine(message) {
|
|
172
247
|
const rl = readline.createInterface({
|
|
173
248
|
input: process.stdin,
|
|
@@ -415,6 +490,14 @@ async function migrateClaudePluginsHandler(argv) {
|
|
|
415
490
|
...plan.installs.flatMap((install) => install.pluginIds),
|
|
416
491
|
...plan.unresolved.map((entry) => entry.pluginId),
|
|
417
492
|
]);
|
|
493
|
+
const removedAuxiliaryRules = await cleanupMigratedPluginAuxiliaryRules(projectRoot, plan.installs.map((install) => install.source));
|
|
494
|
+
if (removedAuxiliaryRules.length > 0) {
|
|
495
|
+
console.log(`[skiller] Removed stale plugin-derived local rules:\n${removedAuxiliaryRules.map((name) => `- ${name}`).join('\n')}`);
|
|
496
|
+
}
|
|
497
|
+
const removedLegacyExternalRules = await cleanupLegacyExternalRuleMatches(projectRoot);
|
|
498
|
+
if (removedLegacyExternalRules.length > 0) {
|
|
499
|
+
console.log(`[skiller] Removed legacy external rule matches:\n${removedLegacyExternalRules.map((name) => `- ${name}`).join('\n')}`);
|
|
500
|
+
}
|
|
418
501
|
if (plan.unresolved.length > 0) {
|
|
419
502
|
console.log(`[skiller] Skipped unresolved plugins:\n${plan.unresolved.map((entry) => `- ${entry.pluginId}: ${entry.reason}`).join('\n')}`);
|
|
420
503
|
}
|
|
@@ -483,6 +566,13 @@ async function addHandler(argv) {
|
|
|
483
566
|
...(argv.args ?? []),
|
|
484
567
|
]);
|
|
485
568
|
}
|
|
569
|
+
async function installHandler(argv) {
|
|
570
|
+
await executeSkillsWrapper(argv['project-root'], [
|
|
571
|
+
'install',
|
|
572
|
+
...(argv.args ?? []),
|
|
573
|
+
]);
|
|
574
|
+
await applyAfterSkillsLifecycleStep(argv['project-root'], argv.verbose ?? false);
|
|
575
|
+
}
|
|
486
576
|
async function removeHandler(argv) {
|
|
487
577
|
await executeSkillsWrapper(argv['project-root'], [
|
|
488
578
|
'remove',
|
|
@@ -512,6 +602,13 @@ async function updateHandler(argv) {
|
|
|
512
602
|
'update',
|
|
513
603
|
...(argv.args ?? []),
|
|
514
604
|
]);
|
|
605
|
+
await applyAfterSkillsLifecycleStep(argv['project-root'], argv.verbose ?? false);
|
|
606
|
+
}
|
|
607
|
+
async function outdatedHandler(argv) {
|
|
608
|
+
await executeSkillsWrapper(argv['project-root'], [
|
|
609
|
+
'outdated',
|
|
610
|
+
...(argv.args ?? []),
|
|
611
|
+
]);
|
|
515
612
|
}
|
|
516
613
|
async function skillsHandler(argv) {
|
|
517
614
|
await executeSkillsWrapper(argv['project-root'], [
|
|
@@ -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
|
-
|
|
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);
|
|
@@ -134,11 +134,7 @@ async function removeLocalRuleReplacementState(projectRoot, ruleName, dryRun) {
|
|
|
134
134
|
if (!dryRun) {
|
|
135
135
|
await fs.rm(rulePath, { force: true });
|
|
136
136
|
}
|
|
137
|
-
|
|
138
|
-
if (!localSkillNames.includes(ruleName)) {
|
|
139
|
-
return;
|
|
140
|
-
}
|
|
141
|
-
await (0, SkillsManifest_1.writeLocalSkillNames)(projectRoot, localSkillNames.filter((name) => name !== ruleName), dryRun);
|
|
137
|
+
await (0, SkillsManifest_1.scrubLegacyLocalSkillsManifest)(projectRoot, dryRun);
|
|
142
138
|
}
|
|
143
139
|
async function planRulesToSkillsMigration(projectRoot, requestedRuleNames) {
|
|
144
140
|
const availableRuleNames = await listRuleNames(projectRoot);
|
|
@@ -37,14 +37,11 @@ exports.getCanonicalSkillsDir = getCanonicalSkillsDir;
|
|
|
37
37
|
exports.getCanonicalRulesDir = getCanonicalRulesDir;
|
|
38
38
|
exports.readUpstreamOwnedSkillNames = readUpstreamOwnedSkillNames;
|
|
39
39
|
exports.resolveSkillOwnership = resolveSkillOwnership;
|
|
40
|
-
exports.adoptSkillerOwnedSkillNames = adoptSkillerOwnedSkillNames;
|
|
41
|
-
exports.syncSkillerOwnedSkillNamesFromRules = syncSkillerOwnedSkillNamesFromRules;
|
|
42
40
|
exports.migrateLegacyProjectState = migrateLegacyProjectState;
|
|
43
41
|
const fs = __importStar(require("fs/promises"));
|
|
44
42
|
const path = __importStar(require("path"));
|
|
45
43
|
const yaml = __importStar(require("js-yaml"));
|
|
46
44
|
const project_paths_1 = require("./project-paths");
|
|
47
|
-
const SkillsManifest_1 = require("./SkillsManifest");
|
|
48
45
|
const FrontmatterParser_1 = require("./FrontmatterParser");
|
|
49
46
|
function normalizeSkillNameForFilesystem(name) {
|
|
50
47
|
return name.replace(/:/g, '-');
|
|
@@ -321,10 +318,7 @@ async function readUpstreamOwnedSkillNames(projectRoot) {
|
|
|
321
318
|
}
|
|
322
319
|
async function resolveSkillOwnership(projectRoot) {
|
|
323
320
|
const upstreamOwned = await readUpstreamOwnedSkillNames(projectRoot);
|
|
324
|
-
const localOwned =
|
|
325
|
-
...(await (0, SkillsManifest_1.loadLocalSkillNames)(projectRoot)),
|
|
326
|
-
...(await readLocalRuleSkillNames(projectRoot)),
|
|
327
|
-
]);
|
|
321
|
+
const localOwned = await readLocalRuleSkillNames(projectRoot);
|
|
328
322
|
const canonicalSkillNames = await readCanonicalSkillNames(projectRoot);
|
|
329
323
|
const allExplicitNames = new Set([...upstreamOwned, ...localOwned]);
|
|
330
324
|
const orphaned = new Set([...canonicalSkillNames]
|
|
@@ -346,11 +340,11 @@ async function resolveSkillOwnership(projectRoot) {
|
|
|
346
340
|
if (upstreamOwned.has(name))
|
|
347
341
|
owners.push('skills-lock.json');
|
|
348
342
|
if (localOwned.has(name))
|
|
349
|
-
owners.push('
|
|
343
|
+
owners.push('.agents/rules');
|
|
350
344
|
return `Skill '${name}' has mixed ownership: ${owners.join(', ')}`;
|
|
351
345
|
}),
|
|
352
346
|
...[...orphaned].map((name) => {
|
|
353
|
-
return `Canonical skill '${name}' is unmanaged; leaving it untouched because it is not in skills-lock.json
|
|
347
|
+
return `Canonical skill '${name}' is unmanaged; leaving it untouched because it is not in skills-lock.json or .agents/rules/${name}.mdc.`;
|
|
354
348
|
}),
|
|
355
349
|
];
|
|
356
350
|
return {
|
|
@@ -361,38 +355,6 @@ async function resolveSkillOwnership(projectRoot) {
|
|
|
361
355
|
warnings,
|
|
362
356
|
};
|
|
363
357
|
}
|
|
364
|
-
async function adoptSkillerOwnedSkillNames(projectRoot, skillNames, dryRun) {
|
|
365
|
-
if (skillNames.length === 0)
|
|
366
|
-
return;
|
|
367
|
-
const ownership = await resolveSkillOwnership(projectRoot);
|
|
368
|
-
const next = new Set(ownership.localOwned);
|
|
369
|
-
for (const name of skillNames) {
|
|
370
|
-
if (ownership.upstreamOwned.has(name))
|
|
371
|
-
continue;
|
|
372
|
-
next.add(name);
|
|
373
|
-
}
|
|
374
|
-
await (0, SkillsManifest_1.writeLocalSkillNames)(projectRoot, [...next], dryRun);
|
|
375
|
-
}
|
|
376
|
-
async function syncSkillerOwnedSkillNamesFromRules(projectRoot, dryRun) {
|
|
377
|
-
const rulesDir = path.join(projectRoot, project_paths_1.CANONICAL_SKILLER_DIR, 'rules');
|
|
378
|
-
let ruleNames = [];
|
|
379
|
-
try {
|
|
380
|
-
const entries = await fs.readdir(rulesDir, { withFileTypes: true });
|
|
381
|
-
ruleNames = entries
|
|
382
|
-
.filter((entry) => entry.isFile() && entry.name.endsWith('.mdc'))
|
|
383
|
-
.map((entry) => path.basename(entry.name, '.mdc'))
|
|
384
|
-
.sort((a, b) => a.localeCompare(b));
|
|
385
|
-
}
|
|
386
|
-
catch {
|
|
387
|
-
ruleNames = [];
|
|
388
|
-
}
|
|
389
|
-
const upstreamOwned = await readUpstreamOwnedSkillNames(projectRoot);
|
|
390
|
-
const nextLocalSkillNames = ruleNames.filter((name) => {
|
|
391
|
-
return !upstreamOwned.has(name);
|
|
392
|
-
});
|
|
393
|
-
await (0, SkillsManifest_1.writeLocalSkillNames)(projectRoot, nextLocalSkillNames, dryRun);
|
|
394
|
-
return nextLocalSkillNames;
|
|
395
|
-
}
|
|
396
358
|
async function migrateLegacyProjectState(projectRoot, dryRun) {
|
|
397
359
|
const legacyDir = path.join(projectRoot, project_paths_1.LEGACY_SKILLER_DIR);
|
|
398
360
|
const canonicalDir = path.join(projectRoot, project_paths_1.CANONICAL_SKILLER_DIR);
|
|
@@ -407,7 +369,10 @@ async function migrateLegacyProjectState(projectRoot, dryRun) {
|
|
|
407
369
|
const plannedWrites = new Map();
|
|
408
370
|
const deletePaths = new Set();
|
|
409
371
|
const conflicts = [];
|
|
410
|
-
|
|
372
|
+
const legacyManifestPath = path.join(legacyDir, '.skiller.json');
|
|
373
|
+
if (await pathExists(legacyManifestPath)) {
|
|
374
|
+
deletePaths.add(legacyManifestPath);
|
|
375
|
+
}
|
|
411
376
|
const legacyConfigPath = path.join(legacyDir, project_paths_1.SKILLER_CONFIG_FILE);
|
|
412
377
|
if (await pathExists(legacyConfigPath)) {
|
|
413
378
|
await planBufferWrite(path.join(canonicalDir, project_paths_1.SKILLER_CONFIG_FILE), Buffer.from(rewriteLegacyAgentIdsInToml(await fs.readFile(legacyConfigPath, 'utf8'))), legacyConfigPath, plannedWrites, conflicts);
|
|
@@ -416,11 +381,16 @@ async function migrateLegacyProjectState(projectRoot, dryRun) {
|
|
|
416
381
|
}
|
|
417
382
|
}
|
|
418
383
|
await planFileMigration(path.join(legacyDir, project_paths_1.PROJECT_AGENTS_FILE), path.join(canonicalDir, project_paths_1.PROJECT_AGENTS_FILE), plannedWrites, deletePaths, conflicts);
|
|
419
|
-
|
|
384
|
+
if (!(await pathExists(canonicalSkillsDir))) {
|
|
385
|
+
await planLegacySkillsMigration(path.join(legacyDir, 'skills'), canonicalSkillsDir, plannedWrites, deletePaths, conflicts);
|
|
386
|
+
}
|
|
420
387
|
await planLegacyRulesMigration(path.join(legacyDir, 'rules'), canonicalRulesDir, canonicalSkillsDir, plannedWrites, deletePaths, conflicts);
|
|
421
388
|
if (conflicts.length > 0) {
|
|
422
389
|
throw new Error(`Legacy .claude migration conflicts:\n- ${conflicts.join('\n- ')}`);
|
|
423
390
|
}
|
|
391
|
+
if (plannedWrites.size === 0 && deletePaths.size === 0) {
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
424
394
|
if (dryRun)
|
|
425
395
|
return;
|
|
426
396
|
for (const [destinationPath, content] of plannedWrites.entries()) {
|
|
@@ -37,8 +37,7 @@ exports.SKILLS_MANIFEST_VERSION = exports.LEGACY_CLAUDE_MANIFEST_FILENAME = expo
|
|
|
37
37
|
exports.isPluginManifestEntry = isPluginManifestEntry;
|
|
38
38
|
exports.isClaudeManifestEntry = isClaudeManifestEntry;
|
|
39
39
|
exports.loadSkillsManifestEntries = loadSkillsManifestEntries;
|
|
40
|
-
exports.
|
|
41
|
-
exports.writeLocalSkillNames = writeLocalSkillNames;
|
|
40
|
+
exports.scrubLegacyLocalSkillsManifest = scrubLegacyLocalSkillsManifest;
|
|
42
41
|
exports.writeSkillsManifestEntries = writeSkillsManifestEntries;
|
|
43
42
|
exports.listSkillDirectories = listSkillDirectories;
|
|
44
43
|
const fs = __importStar(require("fs/promises"));
|
|
@@ -191,16 +190,6 @@ function parseProjectTargets(raw) {
|
|
|
191
190
|
}
|
|
192
191
|
return out;
|
|
193
192
|
}
|
|
194
|
-
function parseLocalSkills(raw) {
|
|
195
|
-
if (!raw || typeof raw !== 'object')
|
|
196
|
-
return [];
|
|
197
|
-
const obj = raw;
|
|
198
|
-
if (!Array.isArray(obj.localSkills))
|
|
199
|
-
return [];
|
|
200
|
-
return [
|
|
201
|
-
...new Set(obj.localSkills.filter((v) => typeof v === 'string')),
|
|
202
|
-
].sort((a, b) => a.localeCompare(b));
|
|
203
|
-
}
|
|
204
193
|
async function readProjectManifestRaw(projectRoot) {
|
|
205
194
|
const canonicalManifestPath = path.join(projectRoot, project_paths_1.CANONICAL_SKILLER_DIR, exports.SKILLS_MANIFEST_FILENAME);
|
|
206
195
|
const legacyManifestPath = path.join(projectRoot, project_paths_1.LEGACY_SKILLER_DIR, exports.SKILLS_MANIFEST_FILENAME);
|
|
@@ -336,25 +325,37 @@ async function loadSkillsManifestEntries(projectRoot, targetSkillsDir) {
|
|
|
336
325
|
// Legacy migration: prior versions stored manifests in the target skills dir.
|
|
337
326
|
return await loadLegacyTargetSkillsManifestEntries(targetSkillsDir);
|
|
338
327
|
}
|
|
339
|
-
async function
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
328
|
+
async function normalizeManifestFile(manifestPath, dryRun) {
|
|
329
|
+
if (!(await fileExists(manifestPath)))
|
|
330
|
+
return;
|
|
331
|
+
let raw;
|
|
332
|
+
try {
|
|
333
|
+
raw = JSON.parse(await fs.readFile(manifestPath, 'utf8'));
|
|
334
|
+
}
|
|
335
|
+
catch {
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
const hasLegacyLocalSkills = !!raw &&
|
|
339
|
+
typeof raw === 'object' &&
|
|
340
|
+
Array.isArray(raw.localSkills);
|
|
341
|
+
if (!hasLegacyLocalSkills)
|
|
342
|
+
return;
|
|
349
343
|
if (dryRun)
|
|
350
344
|
return;
|
|
351
|
-
|
|
345
|
+
const targets = parseProjectTargets(raw);
|
|
346
|
+
if (Object.keys(targets).length === 0) {
|
|
347
|
+
await fs.rm(manifestPath, { force: true });
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
352
350
|
const manifest = {
|
|
353
351
|
version: exports.SKILLS_MANIFEST_VERSION,
|
|
354
|
-
targets
|
|
355
|
-
localSkills: nextLocalSkills,
|
|
352
|
+
targets,
|
|
356
353
|
};
|
|
357
|
-
await fs.writeFile(
|
|
354
|
+
await fs.writeFile(manifestPath, JSON.stringify(manifest, null, 2) + '\n');
|
|
355
|
+
}
|
|
356
|
+
async function scrubLegacyLocalSkillsManifest(projectRoot, dryRun) {
|
|
357
|
+
await normalizeManifestFile(path.join(projectRoot, project_paths_1.CANONICAL_SKILLER_DIR, exports.SKILLS_MANIFEST_FILENAME), dryRun);
|
|
358
|
+
await normalizeManifestFile(path.join(projectRoot, project_paths_1.LEGACY_SKILLER_DIR, exports.SKILLS_MANIFEST_FILENAME), dryRun);
|
|
358
359
|
}
|
|
359
360
|
async function writeSkillsManifestEntries(projectRoot, targetSkillsDir, entries, dryRun) {
|
|
360
361
|
const normalized = normalizeEntries(entries);
|
|
@@ -365,7 +366,6 @@ async function writeSkillsManifestEntries(projectRoot, targetSkillsDir, entries,
|
|
|
365
366
|
let existingTargets = {};
|
|
366
367
|
const raw = await readProjectManifestRaw(projectRoot);
|
|
367
368
|
existingTargets = parseProjectTargets(raw);
|
|
368
|
-
const existingLocalSkills = parseLocalSkills(raw);
|
|
369
369
|
if (normalized.length === 0) {
|
|
370
370
|
delete existingTargets[preferredTargetKey];
|
|
371
371
|
if (preferredTargetKey !== absoluteTargetKey) {
|
|
@@ -383,7 +383,7 @@ async function writeSkillsManifestEntries(projectRoot, targetSkillsDir, entries,
|
|
|
383
383
|
// Ensure `.claude` exists since the manifest lives there.
|
|
384
384
|
await fs.mkdir(projectClaudeDir, { recursive: true });
|
|
385
385
|
const targetKeys = Object.keys(existingTargets).sort((a, b) => a.localeCompare(b));
|
|
386
|
-
if (targetKeys.length === 0
|
|
386
|
+
if (targetKeys.length === 0) {
|
|
387
387
|
await Promise.allSettled([fs.unlink(projectManifestPath)]);
|
|
388
388
|
}
|
|
389
389
|
else {
|
|
@@ -393,7 +393,6 @@ async function writeSkillsManifestEntries(projectRoot, targetSkillsDir, entries,
|
|
|
393
393
|
const manifest = {
|
|
394
394
|
version: exports.SKILLS_MANIFEST_VERSION,
|
|
395
395
|
targets: nextTargets,
|
|
396
|
-
localSkills: existingLocalSkills,
|
|
397
396
|
};
|
|
398
397
|
await fs.writeFile(projectManifestPath, JSON.stringify(manifest, null, 2) + '\n');
|
|
399
398
|
}
|
|
@@ -52,6 +52,7 @@ const yaml = __importStar(require("js-yaml"));
|
|
|
52
52
|
const constants_1 = require("../constants");
|
|
53
53
|
const SkillsUtils_1 = require("./SkillsUtils");
|
|
54
54
|
const FrontmatterParser_1 = require("./FrontmatterParser");
|
|
55
|
+
const SkillsManifest_1 = require("./SkillsManifest");
|
|
55
56
|
const SkillOwnership_1 = require("./SkillOwnership");
|
|
56
57
|
const LEGACY_CODEX_SKILLS_PATH = path.join('.codex', 'skills');
|
|
57
58
|
const UNIVERSAL_AGENTS_SKILLS_PATH = path.join('.agents', 'skills');
|
|
@@ -476,6 +477,184 @@ async function writeFileIfChanged(targetPath, content, dryRun) {
|
|
|
476
477
|
}
|
|
477
478
|
return true;
|
|
478
479
|
}
|
|
480
|
+
async function readNormalizedRuleSourceContent(rulePath) {
|
|
481
|
+
try {
|
|
482
|
+
return normalizeRuleSourceContent(await fs.readFile(rulePath, 'utf8'));
|
|
483
|
+
}
|
|
484
|
+
catch {
|
|
485
|
+
return null;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
function getFrontmatterBlock(content) {
|
|
489
|
+
const match = /^---\n([\s\S]*?)\n---(?:\n|$)/.exec(content);
|
|
490
|
+
return match ? match[1] : null;
|
|
491
|
+
}
|
|
492
|
+
function stripQuotedValue(value) {
|
|
493
|
+
const trimmed = value.trim();
|
|
494
|
+
if ((trimmed.startsWith("'") && trimmed.endsWith("'")) ||
|
|
495
|
+
(trimmed.startsWith('"') && trimmed.endsWith('"'))) {
|
|
496
|
+
return trimmed.slice(1, -1);
|
|
497
|
+
}
|
|
498
|
+
return trimmed;
|
|
499
|
+
}
|
|
500
|
+
function extractSkillerSourceRelPathFromFrontmatter(content) {
|
|
501
|
+
const block = getFrontmatterBlock(content);
|
|
502
|
+
if (!block)
|
|
503
|
+
return null;
|
|
504
|
+
let metadataIndent = null;
|
|
505
|
+
let skillerIndent = null;
|
|
506
|
+
for (const line of block.split('\n')) {
|
|
507
|
+
const trimmed = line.trim();
|
|
508
|
+
if (!trimmed)
|
|
509
|
+
continue;
|
|
510
|
+
const indent = line.length - line.trimStart().length;
|
|
511
|
+
if (metadataIndent !== null &&
|
|
512
|
+
indent <= metadataIndent &&
|
|
513
|
+
trimmed !== 'metadata:') {
|
|
514
|
+
metadataIndent = null;
|
|
515
|
+
skillerIndent = null;
|
|
516
|
+
}
|
|
517
|
+
if (skillerIndent !== null &&
|
|
518
|
+
indent <= skillerIndent &&
|
|
519
|
+
trimmed !== 'skiller:') {
|
|
520
|
+
skillerIndent = null;
|
|
521
|
+
}
|
|
522
|
+
if (trimmed === 'metadata:') {
|
|
523
|
+
metadataIndent = indent;
|
|
524
|
+
skillerIndent = null;
|
|
525
|
+
continue;
|
|
526
|
+
}
|
|
527
|
+
if (metadataIndent !== null && trimmed === 'skiller:') {
|
|
528
|
+
skillerIndent = indent;
|
|
529
|
+
continue;
|
|
530
|
+
}
|
|
531
|
+
if (skillerIndent !== null && trimmed.startsWith('source:')) {
|
|
532
|
+
return stripQuotedValue(trimmed.slice('source:'.length));
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
return null;
|
|
536
|
+
}
|
|
537
|
+
async function pruneDuplicateClaudeAliasRules(projectRoot, targetSkillsDirs, verbose, dryRun) {
|
|
538
|
+
const rulesDir = path.join(projectRoot, '.agents', 'rules');
|
|
539
|
+
const canonicalSkillsDir = path.join(projectRoot, '.agents', 'skills');
|
|
540
|
+
let ruleEntries;
|
|
541
|
+
try {
|
|
542
|
+
ruleEntries = await fs.readdir(rulesDir, { withFileTypes: true });
|
|
543
|
+
}
|
|
544
|
+
catch {
|
|
545
|
+
return [];
|
|
546
|
+
}
|
|
547
|
+
const pruned = [];
|
|
548
|
+
for (const entry of ruleEntries) {
|
|
549
|
+
if (!entry.isFile() || !entry.name.endsWith('.mdc'))
|
|
550
|
+
continue;
|
|
551
|
+
const aliasName = path.basename(entry.name, '.mdc');
|
|
552
|
+
if (!aliasName.startsWith('claude-'))
|
|
553
|
+
continue;
|
|
554
|
+
const baseName = aliasName.slice('claude-'.length);
|
|
555
|
+
if (!baseName)
|
|
556
|
+
continue;
|
|
557
|
+
const aliasRulePath = path.join(rulesDir, `${aliasName}.mdc`);
|
|
558
|
+
const baseRulePath = path.join(rulesDir, `${baseName}.mdc`);
|
|
559
|
+
const [aliasRuleContent, baseRuleContent] = await Promise.all([
|
|
560
|
+
readNormalizedRuleSourceContent(aliasRulePath),
|
|
561
|
+
readNormalizedRuleSourceContent(baseRulePath),
|
|
562
|
+
]);
|
|
563
|
+
if (!aliasRuleContent || !baseRuleContent)
|
|
564
|
+
continue;
|
|
565
|
+
if (aliasRuleContent !== baseRuleContent)
|
|
566
|
+
continue;
|
|
567
|
+
const deletePaths = [
|
|
568
|
+
aliasRulePath,
|
|
569
|
+
path.join(canonicalSkillsDir, aliasName),
|
|
570
|
+
path.join(projectRoot, LEGACY_CODEX_SKILLS_PATH, aliasName),
|
|
571
|
+
...targetSkillsDirs.map((skillsDir) => path.join(skillsDir, aliasName)),
|
|
572
|
+
];
|
|
573
|
+
if (!dryRun) {
|
|
574
|
+
await Promise.all(deletePaths.map((deletePath) => fs.rm(deletePath, { recursive: true, force: true })));
|
|
575
|
+
}
|
|
576
|
+
pruned.push(aliasName);
|
|
577
|
+
(0, constants_1.logVerboseInfo)(dryRun
|
|
578
|
+
? `DRY RUN: Would prune stale claude alias '${aliasName}' because '${baseName}' already exists with identical local rule content`
|
|
579
|
+
: `Pruned stale claude alias '${aliasName}' because '${baseName}' already exists with identical local rule content`, verbose, dryRun);
|
|
580
|
+
}
|
|
581
|
+
return pruned;
|
|
582
|
+
}
|
|
583
|
+
async function pruneCompiledSkillsWithMissingRuleSources(projectRoot, targetSkillsDirs, verbose, dryRun) {
|
|
584
|
+
const canonicalSkillsDir = path.join(projectRoot, '.agents', 'skills');
|
|
585
|
+
let skillEntries;
|
|
586
|
+
try {
|
|
587
|
+
skillEntries = await fs.readdir(canonicalSkillsDir, {
|
|
588
|
+
withFileTypes: true,
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
catch {
|
|
592
|
+
return [];
|
|
593
|
+
}
|
|
594
|
+
const pruned = [];
|
|
595
|
+
for (const entry of skillEntries) {
|
|
596
|
+
if (!entry.isDirectory())
|
|
597
|
+
continue;
|
|
598
|
+
const skillName = entry.name;
|
|
599
|
+
const skillDir = path.join(canonicalSkillsDir, skillName);
|
|
600
|
+
const skillMdPath = path.join(skillDir, constants_1.SKILL_MD_FILENAME);
|
|
601
|
+
let skillMdContent;
|
|
602
|
+
try {
|
|
603
|
+
skillMdContent = await fs.readFile(skillMdPath, 'utf8');
|
|
604
|
+
}
|
|
605
|
+
catch {
|
|
606
|
+
continue;
|
|
607
|
+
}
|
|
608
|
+
const sourceRelPath = extractSkillerSourceRelPathFromFrontmatter(skillMdContent);
|
|
609
|
+
if (!sourceRelPath?.startsWith('.agents/rules/'))
|
|
610
|
+
continue;
|
|
611
|
+
const sourcePath = path.resolve(projectRoot, sourceRelPath);
|
|
612
|
+
if (await pathExists(sourcePath))
|
|
613
|
+
continue;
|
|
614
|
+
const deletePaths = [
|
|
615
|
+
skillDir,
|
|
616
|
+
path.join(projectRoot, LEGACY_CODEX_SKILLS_PATH, skillName),
|
|
617
|
+
...targetSkillsDirs.map((skillsDir) => path.join(skillsDir, skillName)),
|
|
618
|
+
];
|
|
619
|
+
if (!dryRun) {
|
|
620
|
+
await Promise.all(deletePaths.map((deletePath) => fs.rm(deletePath, { recursive: true, force: true })));
|
|
621
|
+
}
|
|
622
|
+
pruned.push(skillName);
|
|
623
|
+
(0, constants_1.logVerboseInfo)(dryRun
|
|
624
|
+
? `DRY RUN: Would prune compiled skill '${skillName}' because its source rule is missing: ${sourceRelPath}`
|
|
625
|
+
: `Pruned compiled skill '${skillName}' because its source rule is missing: ${sourceRelPath}`, verbose, dryRun);
|
|
626
|
+
}
|
|
627
|
+
return pruned;
|
|
628
|
+
}
|
|
629
|
+
async function cleanupLegacyClaudeManagedSkillMirrors(projectRoot, targetSkillsDirs, verbose, dryRun) {
|
|
630
|
+
const cleaned = [];
|
|
631
|
+
for (const targetSkillsDir of targetSkillsDirs) {
|
|
632
|
+
const entries = await (0, SkillsManifest_1.loadSkillsManifestEntries)(projectRoot, targetSkillsDir);
|
|
633
|
+
if (entries.length === 0)
|
|
634
|
+
continue;
|
|
635
|
+
const legacyClaudeEntries = entries.filter(SkillsManifest_1.isClaudeManifestEntry);
|
|
636
|
+
if (legacyClaudeEntries.length === 0)
|
|
637
|
+
continue;
|
|
638
|
+
const nextEntries = entries.filter((entry) => !(0, SkillsManifest_1.isClaudeManifestEntry)(entry));
|
|
639
|
+
const legacyDestPaths = [
|
|
640
|
+
...new Set(legacyClaudeEntries.map((entry) => entry.destRelPath)),
|
|
641
|
+
];
|
|
642
|
+
if (!dryRun) {
|
|
643
|
+
await Promise.all(legacyDestPaths.map((destRelPath) => fs.rm(path.join(targetSkillsDir, destRelPath), {
|
|
644
|
+
recursive: true,
|
|
645
|
+
force: true,
|
|
646
|
+
})));
|
|
647
|
+
}
|
|
648
|
+
await (0, SkillsManifest_1.writeSkillsManifestEntries)(projectRoot, targetSkillsDir, nextEntries, dryRun);
|
|
649
|
+
cleaned.push(...legacyDestPaths.map((destRelPath) => `${targetSkillsDir}:${destRelPath}`));
|
|
650
|
+
for (const destRelPath of legacyDestPaths) {
|
|
651
|
+
(0, constants_1.logVerboseInfo)(dryRun
|
|
652
|
+
? `DRY RUN: Would remove legacy claude-managed skill mirror '${destRelPath}' from ${targetSkillsDir}`
|
|
653
|
+
: `Removed legacy claude-managed skill mirror '${destRelPath}' from ${targetSkillsDir}`, verbose, dryRun);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
return cleaned;
|
|
657
|
+
}
|
|
479
658
|
async function extractLocalRulesFromCanonicalSkills(projectRoot, verbose, dryRun) {
|
|
480
659
|
const warnings = [];
|
|
481
660
|
const extracted = [];
|
|
@@ -489,7 +668,6 @@ async function extractLocalRulesFromCanonicalSkills(projectRoot, verbose, dryRun
|
|
|
489
668
|
return { extracted, warnings };
|
|
490
669
|
}
|
|
491
670
|
const entries = await fs.readdir(skillsDir, { withFileTypes: true });
|
|
492
|
-
const adoptedNames = [];
|
|
493
671
|
for (const entry of entries) {
|
|
494
672
|
if (!entry.isDirectory())
|
|
495
673
|
continue;
|
|
@@ -523,15 +701,11 @@ async function extractLocalRulesFromCanonicalSkills(projectRoot, verbose, dryRun
|
|
|
523
701
|
: `Extracted local skill source ${skillName} to ${toProjectRelative(projectRoot, rulePath)}`, verbose, dryRun);
|
|
524
702
|
}
|
|
525
703
|
extracted.push(skillName);
|
|
526
|
-
adoptedNames.push(skillName);
|
|
527
704
|
}
|
|
528
705
|
catch (err) {
|
|
529
706
|
warnings.push(`Failed to extract local rule source for ${skillName}: ${err.message}`);
|
|
530
707
|
}
|
|
531
708
|
}
|
|
532
|
-
if (adoptedNames.length > 0) {
|
|
533
|
-
await (0, SkillOwnership_1.adoptSkillerOwnedSkillNames)(projectRoot, adoptedNames, dryRun);
|
|
534
|
-
}
|
|
535
709
|
return { extracted, warnings };
|
|
536
710
|
}
|
|
537
711
|
async function compileRulesToSkills(skillerDir, projectRoot, verbose, dryRun) {
|
|
@@ -550,7 +724,6 @@ async function compileRulesToSkills(skillerDir, projectRoot, verbose, dryRun) {
|
|
|
550
724
|
const ruleFiles = entries.filter((entry) => {
|
|
551
725
|
return entry.isFile() && entry.name.endsWith('.mdc');
|
|
552
726
|
});
|
|
553
|
-
const adoptedNames = [];
|
|
554
727
|
for (const ruleFile of ruleFiles) {
|
|
555
728
|
const skillName = path.basename(ruleFile.name, '.mdc');
|
|
556
729
|
if (ownership.upstreamOwned.has(skillName)) {
|
|
@@ -592,10 +765,6 @@ async function compileRulesToSkills(skillerDir, projectRoot, verbose, dryRun) {
|
|
|
592
765
|
: `Compiled ${toProjectRelative(projectRoot, sourcePath)} to ${toProjectRelative(projectRoot, skillMdPath)}`, verbose, dryRun);
|
|
593
766
|
}
|
|
594
767
|
compiled.push(skillName);
|
|
595
|
-
adoptedNames.push(skillName);
|
|
596
|
-
}
|
|
597
|
-
if (adoptedNames.length > 0) {
|
|
598
|
-
await (0, SkillOwnership_1.adoptSkillerOwnedSkillNames)(projectRoot, adoptedNames, dryRun);
|
|
599
768
|
}
|
|
600
769
|
return { compiled, warnings };
|
|
601
770
|
}
|
|
@@ -866,31 +1035,34 @@ async function propagateSkills(projectRoot, agents, skillsEnabled, verbose, dryR
|
|
|
866
1035
|
(0, constants_1.logVerboseInfo)('Skills support disabled', verbose, dryRun);
|
|
867
1036
|
return;
|
|
868
1037
|
}
|
|
869
|
-
if (skillerDir) {
|
|
870
|
-
const extractedResult = await extractLocalRulesFromCanonicalSkills(projectRoot, verbose, dryRun);
|
|
871
|
-
for (const warning of extractedResult.warnings) {
|
|
872
|
-
(0, constants_1.logWarn)(warning, dryRun);
|
|
873
|
-
}
|
|
874
|
-
const compileResult = await compileRulesToSkills(skillerDir, projectRoot, verbose, dryRun);
|
|
875
|
-
for (const warning of compileResult.warnings) {
|
|
876
|
-
(0, constants_1.logWarn)(warning, dryRun);
|
|
877
|
-
}
|
|
878
|
-
await (0, SkillOwnership_1.syncSkillerOwnedSkillNamesFromRules)(projectRoot, dryRun);
|
|
879
|
-
}
|
|
880
1038
|
// Determine canonical skills directory, with legacy fallback for migration.
|
|
881
1039
|
const skillsDir = await resolveProjectSkillsDir(projectRoot, skillerDir);
|
|
882
|
-
// Compute destinations up-front so legacy codex migration can de-duplicate targets.
|
|
1040
|
+
// Compute destinations up-front so cleanup + legacy codex migration can de-duplicate targets.
|
|
883
1041
|
const destinationPaths = new Set();
|
|
884
1042
|
for (const agent of agents) {
|
|
885
1043
|
if (agent.supportsNativeSkills?.() && agent.getSkillsPath) {
|
|
886
1044
|
const targetPath = agent.getSkillsPath(projectRoot);
|
|
887
1045
|
if (targetPath && targetPath !== skillsDir) {
|
|
888
|
-
// Deduplicate shared paths
|
|
889
1046
|
destinationPaths.add(targetPath);
|
|
890
1047
|
}
|
|
891
1048
|
}
|
|
892
1049
|
}
|
|
1050
|
+
if (skillerDir) {
|
|
1051
|
+
await (0, SkillsManifest_1.scrubLegacyLocalSkillsManifest)(projectRoot, dryRun);
|
|
1052
|
+
await pruneCompiledSkillsWithMissingRuleSources(projectRoot, [...destinationPaths], verbose, dryRun);
|
|
1053
|
+
await pruneDuplicateClaudeAliasRules(projectRoot, [...destinationPaths], verbose, dryRun);
|
|
1054
|
+
const compileResult = await compileRulesToSkills(skillerDir, projectRoot, verbose, dryRun);
|
|
1055
|
+
for (const warning of compileResult.warnings) {
|
|
1056
|
+
(0, constants_1.logWarn)(warning, dryRun);
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
893
1059
|
await migrateLegacyCodexSkillsDir(skillsDir, destinationPaths);
|
|
1060
|
+
if (destinationPaths.size > 0) {
|
|
1061
|
+
await cleanupLegacyClaudeManagedSkillMirrors(projectRoot, [...destinationPaths], verbose, dryRun);
|
|
1062
|
+
}
|
|
1063
|
+
if (skillerDir) {
|
|
1064
|
+
await pruneDuplicateClaudeAliasRules(projectRoot, [...destinationPaths], verbose, dryRun);
|
|
1065
|
+
}
|
|
894
1066
|
// Check if skills directory exists
|
|
895
1067
|
let skillsDirExists = true;
|
|
896
1068
|
try {
|
|
@@ -936,17 +1108,6 @@ async function propagateSkills(projectRoot, agents, skillsEnabled, verbose, dryR
|
|
|
936
1108
|
}
|
|
937
1109
|
}
|
|
938
1110
|
}
|
|
939
|
-
// Sync project Claude commands + agents as skills into agent skills dirs.
|
|
940
|
-
// This intentionally does NOT write into the canonical .agents/skills source-of-truth.
|
|
941
|
-
if (destinationPaths.size > 0) {
|
|
942
|
-
const { syncClaudeProjectCommandsAndAgentsToSkillsDirs } = await Promise.resolve().then(() => __importStar(require('./ClaudeProjectSync')));
|
|
943
|
-
await syncClaudeProjectCommandsAndAgentsToSkillsDirs({
|
|
944
|
-
projectRoot,
|
|
945
|
-
targetSkillsDirs: [...destinationPaths],
|
|
946
|
-
verbose,
|
|
947
|
-
dryRun,
|
|
948
|
-
});
|
|
949
|
-
}
|
|
950
1111
|
}
|
|
951
1112
|
/**
|
|
952
1113
|
* Recursively finds all folders containing SKILL.md in a directory.
|