skiller 0.8.2 → 0.9.0

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.
@@ -0,0 +1,173 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.buildRulesReplacementInstallArgs = buildRulesReplacementInstallArgs;
37
+ exports.removeLocalRuleReplacementState = removeLocalRuleReplacementState;
38
+ exports.planRulesToSkillsMigration = planRulesToSkillsMigration;
39
+ const fs = __importStar(require("fs/promises"));
40
+ const path = __importStar(require("path"));
41
+ const SkillsManifest_1 = require("./SkillsManifest");
42
+ const project_paths_1 = require("./project-paths");
43
+ const SEARCH_API_BASE = process.env.SKILLS_API_URL || 'https://skills.sh';
44
+ function normalizeRuleName(value) {
45
+ return value.endsWith('.mdc') ? path.basename(value, '.mdc') : value;
46
+ }
47
+ function normalizeInstalledSkillName(value) {
48
+ return value.replace(/:/g, '-');
49
+ }
50
+ function getRulesDir(projectRoot) {
51
+ return path.join(projectRoot, project_paths_1.CANONICAL_SKILLER_DIR, 'rules');
52
+ }
53
+ async function listRuleNames(projectRoot) {
54
+ const rulesDir = getRulesDir(projectRoot);
55
+ try {
56
+ const entries = await fs.readdir(rulesDir, { withFileTypes: true });
57
+ return entries
58
+ .filter((entry) => entry.isFile() && entry.name.endsWith('.mdc'))
59
+ .map((entry) => path.basename(entry.name, '.mdc'))
60
+ .sort((a, b) => a.localeCompare(b));
61
+ }
62
+ catch {
63
+ return [];
64
+ }
65
+ }
66
+ async function loadInstalledSkillNames(projectRoot) {
67
+ const skillsLockPath = path.join(projectRoot, 'skills-lock.json');
68
+ try {
69
+ const raw = JSON.parse(await fs.readFile(skillsLockPath, 'utf8'));
70
+ return new Set(Object.keys(raw.skills ?? {}).map(normalizeInstalledSkillName));
71
+ }
72
+ catch {
73
+ return new Set();
74
+ }
75
+ }
76
+ function mapRegistrySkill(raw) {
77
+ if (!raw || typeof raw !== 'object')
78
+ return null;
79
+ const record = raw;
80
+ if (typeof record.name !== 'string' ||
81
+ typeof record.id !== 'string' ||
82
+ (record.source !== undefined && typeof record.source !== 'string') ||
83
+ (record.installs !== undefined && typeof record.installs !== 'number')) {
84
+ return null;
85
+ }
86
+ return {
87
+ installs: typeof record.installs === 'number' ? record.installs : 0,
88
+ name: record.name,
89
+ slug: record.id,
90
+ source: typeof record.source === 'string' ? record.source : '',
91
+ };
92
+ }
93
+ async function searchSkillsApi(query) {
94
+ try {
95
+ const url = `${SEARCH_API_BASE}/api/search?q=${encodeURIComponent(query)}&limit=10`;
96
+ const response = await fetch(url);
97
+ if (!response.ok)
98
+ return [];
99
+ const payload = (await response.json());
100
+ return (payload.skills ?? [])
101
+ .map(mapRegistrySkill)
102
+ .filter((skill) => skill !== null)
103
+ .sort((a, b) => b.installs - a.installs);
104
+ }
105
+ catch {
106
+ return [];
107
+ }
108
+ }
109
+ function isExactRuleMatch(ruleName, match) {
110
+ const normalizedRuleName = ruleName.toLowerCase();
111
+ const normalizedSkillName = match.name.toLowerCase();
112
+ const slugParts = match.slug.split('/').filter(Boolean);
113
+ const normalizedSlugName = (slugParts.length > 0 ? slugParts[slugParts.length - 1] : '').toLowerCase();
114
+ return (normalizedSkillName === normalizedRuleName ||
115
+ normalizedSlugName === normalizedRuleName);
116
+ }
117
+ function dedupeNames(values) {
118
+ return [...new Set(values.map(normalizeRuleName))].sort((a, b) => a.localeCompare(b));
119
+ }
120
+ function sourceFromSlug(slug) {
121
+ const parts = slug.split('/').filter(Boolean);
122
+ if (parts.length >= 2) {
123
+ return `${parts[0]}/${parts[1]}`;
124
+ }
125
+ return slug;
126
+ }
127
+ function buildRulesReplacementInstallArgs(match) {
128
+ const source = match.source || sourceFromSlug(match.slug);
129
+ return ['add', source, '--agent', 'universal', '--skill', match.name, '-y'];
130
+ }
131
+ async function removeLocalRuleReplacementState(projectRoot, ruleName, dryRun) {
132
+ const rulesDir = getRulesDir(projectRoot);
133
+ const rulePath = path.join(rulesDir, `${ruleName}.mdc`);
134
+ if (!dryRun) {
135
+ await fs.rm(rulePath, { force: true });
136
+ }
137
+ const localSkillNames = await (0, SkillsManifest_1.loadLocalSkillNames)(projectRoot);
138
+ if (!localSkillNames.includes(ruleName)) {
139
+ return;
140
+ }
141
+ await (0, SkillsManifest_1.writeLocalSkillNames)(projectRoot, localSkillNames.filter((name) => name !== ruleName), dryRun);
142
+ }
143
+ async function planRulesToSkillsMigration(projectRoot, requestedRuleNames) {
144
+ const availableRuleNames = await listRuleNames(projectRoot);
145
+ const selectedRuleNames = requestedRuleNames && requestedRuleNames.length > 0
146
+ ? dedupeNames(requestedRuleNames)
147
+ : availableRuleNames;
148
+ const availableRuleSet = new Set(availableRuleNames);
149
+ const missingRequested = selectedRuleNames.filter((ruleName) => !availableRuleSet.has(ruleName));
150
+ const installedSkillNames = await loadInstalledSkillNames(projectRoot);
151
+ const candidates = [];
152
+ const unmatched = [];
153
+ for (const ruleName of selectedRuleNames) {
154
+ if (!availableRuleSet.has(ruleName))
155
+ continue;
156
+ const exactMatches = (await searchSkillsApi(ruleName)).filter((match) => isExactRuleMatch(ruleName, match));
157
+ if (exactMatches.length === 0) {
158
+ unmatched.push(ruleName);
159
+ continue;
160
+ }
161
+ candidates.push({
162
+ alreadyInstalled: installedSkillNames.has(ruleName),
163
+ matches: exactMatches,
164
+ ruleName,
165
+ });
166
+ }
167
+ return {
168
+ candidates,
169
+ missingRequested,
170
+ scannedRules: selectedRuleNames.filter((ruleName) => availableRuleSet.has(ruleName)),
171
+ unmatched,
172
+ };
173
+ }
@@ -0,0 +1,397 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.getCanonicalSkillsDir = getCanonicalSkillsDir;
37
+ exports.getCanonicalRulesDir = getCanonicalRulesDir;
38
+ exports.readUpstreamOwnedSkillNames = readUpstreamOwnedSkillNames;
39
+ exports.resolveSkillOwnership = resolveSkillOwnership;
40
+ exports.adoptSkillerOwnedSkillNames = adoptSkillerOwnedSkillNames;
41
+ exports.migrateLegacyProjectState = migrateLegacyProjectState;
42
+ const fs = __importStar(require("fs/promises"));
43
+ const path = __importStar(require("path"));
44
+ const yaml = __importStar(require("js-yaml"));
45
+ const project_paths_1 = require("./project-paths");
46
+ const SkillsManifest_1 = require("./SkillsManifest");
47
+ const FrontmatterParser_1 = require("./FrontmatterParser");
48
+ function normalizeSkillNameForFilesystem(name) {
49
+ return name.replace(/:/g, '-');
50
+ }
51
+ function getSkillsLockPath(projectRoot) {
52
+ return path.join(projectRoot, 'skills-lock.json');
53
+ }
54
+ async function pathExists(targetPath) {
55
+ try {
56
+ await fs.access(targetPath);
57
+ return true;
58
+ }
59
+ catch {
60
+ return false;
61
+ }
62
+ }
63
+ function buffersEqual(a, b) {
64
+ return a.length === b.length && a.equals(b);
65
+ }
66
+ const LEGACY_AGENT_ID_MAP = {
67
+ claude: 'claude-code',
68
+ copilot: 'github-copilot',
69
+ augmentcode: 'augment',
70
+ kilocode: 'kilo',
71
+ kiro: 'kiro-cli',
72
+ qwen: 'qwen-code',
73
+ };
74
+ function escapeRegExp(value) {
75
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
76
+ }
77
+ function rewriteLegacyAgentIdsInToml(content) {
78
+ let rewritten = content;
79
+ rewritten = rewritten.replace(/^(default_agents\s*=\s*\[)([\s\S]*?)(\])/m, (_match, prefix, body, suffix) => {
80
+ const rewrittenBody = body.replace(/"([^"]+)"/g, (quoted, id) => {
81
+ const mapped = LEGACY_AGENT_ID_MAP[id];
82
+ return mapped ? `"${mapped}"` : quoted;
83
+ });
84
+ return `${prefix}${rewrittenBody}${suffix}`;
85
+ });
86
+ for (const [legacyId, canonicalId] of Object.entries(LEGACY_AGENT_ID_MAP)) {
87
+ const pattern = new RegExp(`^(\\[agents\\.)${escapeRegExp(legacyId)}(?=(?:\\.|\\]))`, 'gm');
88
+ rewritten = rewritten.replace(pattern, `$1${canonicalId}`);
89
+ }
90
+ return rewritten;
91
+ }
92
+ function isReferenceBody(body) {
93
+ const lines = body.split('\n').filter((line) => line.trim().length > 0);
94
+ if (lines.length !== 1)
95
+ return null;
96
+ const line = lines[0].trim();
97
+ return line.startsWith('@') ? line.slice(1) : null;
98
+ }
99
+ function inferProjectRootFromSkillFile(filePath) {
100
+ let current = path.dirname(filePath);
101
+ while (true) {
102
+ const base = path.basename(current);
103
+ if (base === project_paths_1.CANONICAL_SKILLER_DIR || base === project_paths_1.LEGACY_SKILLER_DIR) {
104
+ return path.dirname(current);
105
+ }
106
+ const parent = path.dirname(current);
107
+ if (parent === current) {
108
+ return path.dirname(path.dirname(filePath));
109
+ }
110
+ current = parent;
111
+ }
112
+ }
113
+ async function normalizeSkillDocumentContent(filePath, content) {
114
+ if (path.basename(filePath) !== 'SKILL.md') {
115
+ return content;
116
+ }
117
+ const text = content.toString('utf8');
118
+ const { frontmatter, rawFrontmatter, body } = (0, FrontmatterParser_1.parseFrontmatter)(text);
119
+ const referencePath = isReferenceBody(body);
120
+ let normalizedBody = body;
121
+ if (referencePath) {
122
+ const skillFolderPath = path.dirname(filePath);
123
+ const projectRoot = inferProjectRootFromSkillFile(filePath);
124
+ const absoluteRefPath = referencePath.startsWith('./') || referencePath.startsWith('../')
125
+ ? path.resolve(skillFolderPath, referencePath)
126
+ : path.resolve(projectRoot, referencePath);
127
+ try {
128
+ const referencedContent = await fs.readFile(absoluteRefPath, 'utf8');
129
+ normalizedBody = (0, FrontmatterParser_1.parseFrontmatter)(referencedContent).body;
130
+ }
131
+ catch {
132
+ return content;
133
+ }
134
+ }
135
+ const frontmatterData = rawFrontmatter && Object.keys(rawFrontmatter).length > 0
136
+ ? rawFrontmatter
137
+ : frontmatter && Object.keys(frontmatter).length > 0
138
+ ? frontmatter
139
+ : null;
140
+ if (!frontmatterData) {
141
+ return Buffer.from(`${normalizedBody.trimEnd()}\n`);
142
+ }
143
+ return Buffer.from(`---
144
+ ${yaml.dump(frontmatterData, { lineWidth: -1, noRefs: true }).trim()}
145
+ ---
146
+
147
+ ${normalizedBody.trimEnd()}
148
+ `);
149
+ }
150
+ async function filesAreEquivalent(leftPath, leftContent, rightPath, rightContent) {
151
+ if (buffersEqual(leftContent, rightContent))
152
+ return true;
153
+ const [normalizedLeft, normalizedRight] = await Promise.all([
154
+ normalizeSkillDocumentContent(leftPath, leftContent),
155
+ normalizeSkillDocumentContent(rightPath, rightContent),
156
+ ]);
157
+ return buffersEqual(normalizedLeft, normalizedRight);
158
+ }
159
+ async function collectFilesRecursive(dir, prefix = '') {
160
+ const collected = [];
161
+ const entries = await fs.readdir(dir, { withFileTypes: true });
162
+ for (const entry of entries) {
163
+ const relativePath = prefix ? path.join(prefix, entry.name) : entry.name;
164
+ const fullPath = path.join(dir, entry.name);
165
+ if (entry.isDirectory()) {
166
+ collected.push(...(await collectFilesRecursive(fullPath, relativePath)));
167
+ continue;
168
+ }
169
+ if (entry.isFile()) {
170
+ collected.push({
171
+ relativePath,
172
+ content: await fs.readFile(fullPath),
173
+ });
174
+ }
175
+ }
176
+ return collected;
177
+ }
178
+ async function planBufferWrite(destinationPath, content, sourceLabel, plannedWrites, conflicts) {
179
+ const planned = plannedWrites.get(destinationPath);
180
+ if (planned) {
181
+ if (!buffersEqual(planned, content)) {
182
+ conflicts.push(`${sourceLabel} conflicts with planned write ${destinationPath}`);
183
+ }
184
+ return;
185
+ }
186
+ try {
187
+ const stat = await fs.stat(destinationPath);
188
+ if (!stat.isFile()) {
189
+ conflicts.push(`${sourceLabel} conflicts with existing directory ${destinationPath}`);
190
+ return;
191
+ }
192
+ const existing = await fs.readFile(destinationPath);
193
+ if (!(await filesAreEquivalent(sourceLabel, content, destinationPath, existing))) {
194
+ conflicts.push(`${sourceLabel} conflicts with existing file ${destinationPath}`);
195
+ }
196
+ return;
197
+ }
198
+ catch {
199
+ plannedWrites.set(destinationPath, content);
200
+ }
201
+ }
202
+ async function planFileMigration(sourcePath, destinationPath, plannedWrites, deletePaths, conflicts) {
203
+ if (!(await pathExists(sourcePath)))
204
+ return;
205
+ await planBufferWrite(destinationPath, await fs.readFile(sourcePath), sourcePath, plannedWrites, conflicts);
206
+ if (conflicts.length === 0) {
207
+ deletePaths.add(sourcePath);
208
+ }
209
+ }
210
+ async function planDirectoryMigration(sourceDir, destinationDir, plannedWrites, deletePaths, conflicts) {
211
+ if (!(await pathExists(sourceDir)))
212
+ return;
213
+ for (const entry of await collectFilesRecursive(sourceDir)) {
214
+ await planBufferWrite(path.join(destinationDir, entry.relativePath), entry.content, path.join(sourceDir, entry.relativePath), plannedWrites, conflicts);
215
+ }
216
+ if (conflicts.length === 0) {
217
+ deletePaths.add(sourceDir);
218
+ }
219
+ }
220
+ async function findRuleSkillFolders(dir) {
221
+ const folders = [];
222
+ const entries = await fs.readdir(dir, { withFileTypes: true });
223
+ for (const entry of entries) {
224
+ if (!entry.isDirectory())
225
+ continue;
226
+ const fullPath = path.join(dir, entry.name);
227
+ if (await pathExists(path.join(fullPath, 'SKILL.md'))) {
228
+ folders.push(fullPath);
229
+ continue;
230
+ }
231
+ folders.push(...(await findRuleSkillFolders(fullPath)));
232
+ }
233
+ return folders;
234
+ }
235
+ async function planLegacyRulesMigration(legacyRulesDir, canonicalRulesDir, canonicalSkillsDir, plannedWrites, deletePaths, conflicts) {
236
+ if (!(await pathExists(legacyRulesDir)))
237
+ return;
238
+ const entries = await fs.readdir(legacyRulesDir, { withFileTypes: true });
239
+ for (const entry of entries) {
240
+ if (!entry.isFile() || !entry.name.endsWith('.mdc'))
241
+ continue;
242
+ const sourcePath = path.join(legacyRulesDir, entry.name);
243
+ const ruleDestination = path.join(canonicalRulesDir, entry.name);
244
+ await planBufferWrite(ruleDestination, await fs.readFile(sourcePath), sourcePath, plannedWrites, conflicts);
245
+ }
246
+ const skillFolders = await findRuleSkillFolders(legacyRulesDir);
247
+ for (const folder of skillFolders) {
248
+ const folderName = path.basename(folder);
249
+ await planDirectoryMigration(folder, path.join(canonicalSkillsDir, folderName), plannedWrites, deletePaths, conflicts);
250
+ }
251
+ if (conflicts.length === 0) {
252
+ deletePaths.add(legacyRulesDir);
253
+ }
254
+ }
255
+ function getCanonicalSkillsDir(projectRoot) {
256
+ return path.join(projectRoot, project_paths_1.CANONICAL_SKILLER_DIR, 'skills');
257
+ }
258
+ function getCanonicalRulesDir(projectRoot) {
259
+ return path.join(projectRoot, project_paths_1.CANONICAL_SKILLER_DIR, 'rules');
260
+ }
261
+ async function readLocalRuleSkillNames(projectRoot) {
262
+ try {
263
+ const entries = await fs.readdir(getCanonicalRulesDir(projectRoot), {
264
+ withFileTypes: true,
265
+ });
266
+ return new Set(entries
267
+ .filter((entry) => entry.isFile() && entry.name.endsWith('.mdc'))
268
+ .map((entry) => path.basename(entry.name, '.mdc'))
269
+ .sort((a, b) => a.localeCompare(b)));
270
+ }
271
+ catch {
272
+ return new Set();
273
+ }
274
+ }
275
+ async function readCanonicalSkillNames(projectRoot) {
276
+ try {
277
+ const entries = await fs.readdir(getCanonicalSkillsDir(projectRoot), {
278
+ withFileTypes: true,
279
+ });
280
+ return new Set(entries
281
+ .filter((entry) => entry.isDirectory())
282
+ .map((entry) => entry.name)
283
+ .sort((a, b) => a.localeCompare(b)));
284
+ }
285
+ catch {
286
+ return new Set();
287
+ }
288
+ }
289
+ async function readUpstreamOwnedSkillNames(projectRoot) {
290
+ try {
291
+ const raw = JSON.parse(await fs.readFile(getSkillsLockPath(projectRoot), 'utf8'));
292
+ return new Set(Object.keys(raw.skills ?? {})
293
+ .map(normalizeSkillNameForFilesystem)
294
+ .sort((a, b) => a.localeCompare(b)));
295
+ }
296
+ catch {
297
+ return new Set();
298
+ }
299
+ }
300
+ async function resolveSkillOwnership(projectRoot) {
301
+ const upstreamOwned = await readUpstreamOwnedSkillNames(projectRoot);
302
+ const localOwned = new Set([
303
+ ...(await (0, SkillsManifest_1.loadLocalSkillNames)(projectRoot)),
304
+ ...(await readLocalRuleSkillNames(projectRoot)),
305
+ ]);
306
+ const canonicalSkillNames = await readCanonicalSkillNames(projectRoot);
307
+ const allExplicitNames = new Set([...upstreamOwned, ...localOwned]);
308
+ const orphaned = new Set([...canonicalSkillNames]
309
+ .filter((name) => !allExplicitNames.has(name))
310
+ .sort((a, b) => a.localeCompare(b)));
311
+ const conflicts = [...allExplicitNames]
312
+ .filter((name) => {
313
+ let owners = 0;
314
+ if (upstreamOwned.has(name))
315
+ owners += 1;
316
+ if (localOwned.has(name))
317
+ owners += 1;
318
+ return owners > 1;
319
+ })
320
+ .sort((a, b) => a.localeCompare(b));
321
+ const warnings = [
322
+ ...conflicts.map((name) => {
323
+ const owners = [];
324
+ if (upstreamOwned.has(name))
325
+ owners.push('skills-lock.json');
326
+ if (localOwned.has(name))
327
+ owners.push('local rules/.agents/.skiller.json');
328
+ return `Skill '${name}' has mixed ownership: ${owners.join(', ')}`;
329
+ }),
330
+ ...[...orphaned].map((name) => {
331
+ return `Canonical skill '${name}' is unmanaged; leaving it untouched because it is not in skills-lock.json, .agents/rules/${name}.mdc, or .agents/.skiller.json localSkills.`;
332
+ }),
333
+ ];
334
+ return {
335
+ upstreamOwned,
336
+ localOwned,
337
+ orphaned,
338
+ conflicts,
339
+ warnings,
340
+ };
341
+ }
342
+ async function adoptSkillerOwnedSkillNames(projectRoot, skillNames, dryRun) {
343
+ if (skillNames.length === 0)
344
+ return;
345
+ const ownership = await resolveSkillOwnership(projectRoot);
346
+ const next = new Set(ownership.localOwned);
347
+ for (const name of skillNames) {
348
+ if (ownership.upstreamOwned.has(name))
349
+ continue;
350
+ next.add(name);
351
+ }
352
+ await (0, SkillsManifest_1.writeLocalSkillNames)(projectRoot, [...next], dryRun);
353
+ }
354
+ async function migrateLegacyProjectState(projectRoot, dryRun) {
355
+ const legacyDir = path.join(projectRoot, project_paths_1.LEGACY_SKILLER_DIR);
356
+ const canonicalDir = path.join(projectRoot, project_paths_1.CANONICAL_SKILLER_DIR);
357
+ const canonicalSkillsDir = path.join(canonicalDir, 'skills');
358
+ const canonicalRulesDir = path.join(canonicalDir, 'rules');
359
+ try {
360
+ await fs.access(legacyDir);
361
+ }
362
+ catch {
363
+ return;
364
+ }
365
+ const plannedWrites = new Map();
366
+ const deletePaths = new Set();
367
+ const conflicts = [];
368
+ await planFileMigration(path.join(legacyDir, '.skiller.json'), path.join(canonicalDir, '.skiller.json'), plannedWrites, deletePaths, conflicts);
369
+ const legacyConfigPath = path.join(legacyDir, project_paths_1.SKILLER_CONFIG_FILE);
370
+ if (await pathExists(legacyConfigPath)) {
371
+ await planBufferWrite(path.join(canonicalDir, project_paths_1.SKILLER_CONFIG_FILE), Buffer.from(rewriteLegacyAgentIdsInToml(await fs.readFile(legacyConfigPath, 'utf8'))), legacyConfigPath, plannedWrites, conflicts);
372
+ if (conflicts.length === 0) {
373
+ deletePaths.add(legacyConfigPath);
374
+ }
375
+ }
376
+ await planFileMigration(path.join(legacyDir, project_paths_1.PROJECT_AGENTS_FILE), path.join(canonicalDir, project_paths_1.PROJECT_AGENTS_FILE), plannedWrites, deletePaths, conflicts);
377
+ await planDirectoryMigration(path.join(legacyDir, 'skills'), canonicalSkillsDir, plannedWrites, deletePaths, conflicts);
378
+ await planLegacyRulesMigration(path.join(legacyDir, 'rules'), canonicalRulesDir, canonicalSkillsDir, plannedWrites, deletePaths, conflicts);
379
+ if (conflicts.length > 0) {
380
+ throw new Error(`Legacy .claude migration conflicts:\n- ${conflicts.join('\n- ')}`);
381
+ }
382
+ if (dryRun)
383
+ return;
384
+ for (const [destinationPath, content] of plannedWrites.entries()) {
385
+ await fs.mkdir(path.dirname(destinationPath), { recursive: true });
386
+ await fs.writeFile(destinationPath, content);
387
+ }
388
+ for (const deletePath of [...deletePaths].sort((a, b) => b.length - a.length)) {
389
+ await fs.rm(deletePath, { recursive: true, force: true });
390
+ }
391
+ if (await pathExists(canonicalSkillsDir)) {
392
+ const { compileRulesToSkills, extractLocalRulesFromCanonicalSkills, normalizeCanonicalSkills, } = await Promise.resolve().then(() => __importStar(require('./SkillsProcessor')));
393
+ await extractLocalRulesFromCanonicalSkills(projectRoot, false, false);
394
+ await compileRulesToSkills(canonicalDir, projectRoot, false, false);
395
+ await normalizeCanonicalSkills(projectRoot, canonicalSkillsDir, false, false);
396
+ }
397
+ }