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.
- package/dist/cli/commands.js +60 -1
- package/dist/cli/handlers.js +240 -5
- package/dist/cli/skills-cli.js +72 -0
- package/dist/constants.js +2 -1
- package/dist/core/ClaudePluginMigration.js +229 -0
- package/dist/core/ClaudePluginSync.js +1 -34
- package/dist/core/ClaudeProjectSync.js +37 -58
- package/dist/core/ConfigLoader.js +4 -4
- package/dist/core/FileSystemUtils.js +19 -48
- package/dist/core/FrontmatterParser.js +11 -1
- package/dist/core/LegacyClaudePluginState.js +123 -0
- package/dist/core/RulesToSkillsMigration.js +173 -0
- package/dist/core/SkillOwnership.js +397 -0
- package/dist/core/SkillsManifest.js +79 -23
- package/dist/core/SkillsProcessor.js +565 -317
- package/dist/core/SkillsUtils.js +6 -11
- package/dist/core/UnifiedConfigLoader.js +6 -5
- package/dist/core/project-paths.js +8 -0
- package/dist/lib.js +10 -16
- package/package.json +8 -8
|
@@ -485,40 +485,7 @@ async function loadManagedEntries(projectRoot, targetSkillsDir, dryRun) {
|
|
|
485
485
|
return { pluginEntries, otherEntries };
|
|
486
486
|
}
|
|
487
487
|
async function discoverLocalSkillNames(projectRoot) {
|
|
488
|
-
|
|
489
|
-
if (!(await fileExists(localSkillsDir)))
|
|
490
|
-
return new Set();
|
|
491
|
-
const names = new Set();
|
|
492
|
-
async function walk(current, depth) {
|
|
493
|
-
if (depth >= constants_1.MAX_RECURSION_DEPTH)
|
|
494
|
-
return;
|
|
495
|
-
let entries;
|
|
496
|
-
try {
|
|
497
|
-
entries = await fs.readdir(current, { withFileTypes: true });
|
|
498
|
-
}
|
|
499
|
-
catch {
|
|
500
|
-
return;
|
|
501
|
-
}
|
|
502
|
-
const hasSkillMd = entries.some((e) => e.isFile() && e.name === 'SKILL.md');
|
|
503
|
-
if (hasSkillMd) {
|
|
504
|
-
const rel = path.relative(localSkillsDir, current).replace(/\\/g, '/');
|
|
505
|
-
const segments = rel.split('/').filter(Boolean);
|
|
506
|
-
if (segments.length > 0) {
|
|
507
|
-
names.add(segments.map(sanitizeId).join('-'));
|
|
508
|
-
}
|
|
509
|
-
else {
|
|
510
|
-
names.add(sanitizeId(path.basename(current)));
|
|
511
|
-
}
|
|
512
|
-
return;
|
|
513
|
-
}
|
|
514
|
-
for (const entry of entries) {
|
|
515
|
-
if (!entry.isDirectory())
|
|
516
|
-
continue;
|
|
517
|
-
await walk(path.join(current, entry.name), depth + 1);
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
await walk(localSkillsDir, 0);
|
|
521
|
-
return names;
|
|
488
|
+
return new Set(await (0, SkillsManifest_1.loadLocalSkillNames)(projectRoot));
|
|
522
489
|
}
|
|
523
490
|
async function discoverLocalCommandNames(projectRoot) {
|
|
524
491
|
const localCommandsDir = path.join(projectRoot, '.claude', 'commands');
|
|
@@ -40,7 +40,7 @@ const yaml = __importStar(require("js-yaml"));
|
|
|
40
40
|
const constants_1 = require("../constants");
|
|
41
41
|
const FrontmatterParser_1 = require("./FrontmatterParser");
|
|
42
42
|
const SkillsManifest_1 = require("./SkillsManifest");
|
|
43
|
-
const
|
|
43
|
+
const project_paths_1 = require("./project-paths");
|
|
44
44
|
function sanitizeId(value) {
|
|
45
45
|
return value.replace(/[^A-Za-z0-9._-]+/g, '_');
|
|
46
46
|
}
|
|
@@ -171,42 +171,21 @@ async function writeMarkdownAsSkill(srcPath, destDir, generatedName, kindLabel,
|
|
|
171
171
|
return;
|
|
172
172
|
await fs.writeFile(path.join(destDir, 'SKILL.md'), next, 'utf8');
|
|
173
173
|
}
|
|
174
|
-
async function readPluginManagedDestNames(projectRoot, targetSkillsDir) {
|
|
175
|
-
const names = new Set();
|
|
176
|
-
for (const entry of await (0, SkillsManifest_1.loadSkillsManifestEntries)(projectRoot, targetSkillsDir)) {
|
|
177
|
-
if ((0, SkillsManifest_1.isPluginManifestEntry)(entry)) {
|
|
178
|
-
names.add(entry.destRelPath);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
// Legacy: prior versions wrote per-skill plugin marker files. Treat any
|
|
182
|
-
// folder containing one as plugin-managed so project items can take over.
|
|
183
|
-
try {
|
|
184
|
-
const dirents = await fs.readdir(targetSkillsDir, { withFileTypes: true });
|
|
185
|
-
for (const d of dirents) {
|
|
186
|
-
if (!d.isDirectory())
|
|
187
|
-
continue;
|
|
188
|
-
try {
|
|
189
|
-
await fs.access(path.join(targetSkillsDir, d.name, LEGACY_PLUGIN_MARKER_FILENAME));
|
|
190
|
-
names.add(d.name);
|
|
191
|
-
}
|
|
192
|
-
catch {
|
|
193
|
-
// ignore
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
catch {
|
|
198
|
-
// ignore
|
|
199
|
-
}
|
|
200
|
-
return names;
|
|
201
|
-
}
|
|
202
174
|
async function discoverLocalSkillNames(projectRoot) {
|
|
203
|
-
|
|
204
|
-
|
|
175
|
+
return new Set(await (0, SkillsManifest_1.loadLocalSkillNames)(projectRoot));
|
|
176
|
+
}
|
|
177
|
+
async function discoverCanonicalSkillNames(projectRoot) {
|
|
178
|
+
const skillsRoot = path.join(projectRoot, project_paths_1.CANONICAL_SKILLER_DIR, 'skills');
|
|
179
|
+
if (!(await fileExists(skillsRoot)))
|
|
205
180
|
return new Set();
|
|
206
181
|
const names = new Set();
|
|
207
|
-
async function walk(current, depth) {
|
|
182
|
+
async function walk(current, rel, depth) {
|
|
208
183
|
if (depth >= constants_1.MAX_RECURSION_DEPTH)
|
|
209
184
|
return;
|
|
185
|
+
if (rel && (await fileExists(path.join(current, 'SKILL.md')))) {
|
|
186
|
+
names.add(rel.split('/').filter(Boolean).map(sanitizeId).join('-'));
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
210
189
|
let entries;
|
|
211
190
|
try {
|
|
212
191
|
entries = await fs.readdir(current, { withFileTypes: true });
|
|
@@ -214,30 +193,20 @@ async function discoverLocalSkillNames(projectRoot) {
|
|
|
214
193
|
catch {
|
|
215
194
|
return;
|
|
216
195
|
}
|
|
217
|
-
const hasSkillMd = entries.some((e) => e.isFile() && e.name === 'SKILL.md');
|
|
218
|
-
if (hasSkillMd) {
|
|
219
|
-
const rel = path.relative(localSkillsDir, current).replace(/\\/g, '/');
|
|
220
|
-
const segments = rel.split('/').filter(Boolean);
|
|
221
|
-
if (segments.length > 0) {
|
|
222
|
-
names.add(segments.map(sanitizeId).join('-'));
|
|
223
|
-
}
|
|
224
|
-
else {
|
|
225
|
-
names.add(sanitizeId(path.basename(current)));
|
|
226
|
-
}
|
|
227
|
-
return;
|
|
228
|
-
}
|
|
229
196
|
for (const entry of entries) {
|
|
230
197
|
if (!entry.isDirectory())
|
|
231
198
|
continue;
|
|
232
|
-
|
|
199
|
+
const nextRel = rel ? `${rel}/${entry.name}` : entry.name;
|
|
200
|
+
await walk(path.join(current, entry.name), nextRel, depth + 1);
|
|
233
201
|
}
|
|
234
202
|
}
|
|
235
|
-
await walk(
|
|
203
|
+
await walk(skillsRoot, '', 0);
|
|
236
204
|
return names;
|
|
237
205
|
}
|
|
238
206
|
async function syncClaudeProjectCommandsAndAgentsToSkillsDirs(args) {
|
|
239
207
|
const { projectRoot, targetSkillsDirs, verbose, dryRun } = args;
|
|
240
208
|
const localSkillNames = await discoverLocalSkillNames(projectRoot);
|
|
209
|
+
const canonicalSkillNames = await discoverCanonicalSkillNames(projectRoot);
|
|
241
210
|
const commands = await discoverCommandFiles(projectRoot);
|
|
242
211
|
const agents = await discoverAgentFiles(projectRoot);
|
|
243
212
|
const expectedItems = [];
|
|
@@ -284,9 +253,23 @@ async function syncClaudeProjectCommandsAndAgentsToSkillsDirs(args) {
|
|
|
284
253
|
prevDestByItemKey.set(makeItemKey(entry.sourceKind, entry.sourceRelPath), entry.destRelPath);
|
|
285
254
|
}
|
|
286
255
|
const managedDest = new Set(managedEntries.map((e) => e.destRelPath));
|
|
287
|
-
const
|
|
288
|
-
|
|
289
|
-
|
|
256
|
+
const reservedCanonicalNames = new Set([
|
|
257
|
+
...canonicalSkillNames,
|
|
258
|
+
...localSkillNames,
|
|
259
|
+
]);
|
|
260
|
+
const canonicalSkillsDir = path.join(projectRoot, project_paths_1.CANONICAL_SKILLER_DIR, 'skills');
|
|
261
|
+
if (path.resolve(targetSkillsDir) === path.resolve(canonicalSkillsDir)) {
|
|
262
|
+
for (const entry of managedEntries) {
|
|
263
|
+
reservedCanonicalNames.delete(entry.destRelPath);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
const activeItems = sortedItems.filter((item) => {
|
|
267
|
+
if (!reservedCanonicalNames.has(item.baseName)) {
|
|
268
|
+
return true;
|
|
269
|
+
}
|
|
270
|
+
(0, constants_1.logVerboseInfo)(`Skipping claude ${item.sourceKind} '${item.baseName}' because canonical skills already own that name`, verbose, dryRun);
|
|
271
|
+
return false;
|
|
272
|
+
});
|
|
290
273
|
const reserved = new Set(localSkillNames);
|
|
291
274
|
if (targetExists) {
|
|
292
275
|
let dirents = [];
|
|
@@ -299,9 +282,7 @@ async function syncClaudeProjectCommandsAndAgentsToSkillsDirs(args) {
|
|
|
299
282
|
for (const d of dirents) {
|
|
300
283
|
if (!d.isDirectory())
|
|
301
284
|
continue;
|
|
302
|
-
|
|
303
|
-
// folders (project should be able to take those over).
|
|
304
|
-
if (!managedDest.has(d.name) && !pluginManagedDest.has(d.name)) {
|
|
285
|
+
if (!managedDest.has(d.name)) {
|
|
305
286
|
reserved.add(d.name);
|
|
306
287
|
}
|
|
307
288
|
}
|
|
@@ -309,7 +290,7 @@ async function syncClaudeProjectCommandsAndAgentsToSkillsDirs(args) {
|
|
|
309
290
|
const taken = new Set(reserved);
|
|
310
291
|
const assignedDestByItemKey = new Map();
|
|
311
292
|
// Preserve previous destinations when they are still available.
|
|
312
|
-
for (const item of
|
|
293
|
+
for (const item of activeItems) {
|
|
313
294
|
const prev = prevDestByItemKey.get(item.itemKey);
|
|
314
295
|
if (!prev)
|
|
315
296
|
continue;
|
|
@@ -324,7 +305,7 @@ async function syncClaudeProjectCommandsAndAgentsToSkillsDirs(args) {
|
|
|
324
305
|
taken.add(prev);
|
|
325
306
|
}
|
|
326
307
|
// Assign baseName, otherwise namespace with "claude-".
|
|
327
|
-
for (const item of
|
|
308
|
+
for (const item of activeItems) {
|
|
328
309
|
if (assignedDestByItemKey.has(item.itemKey))
|
|
329
310
|
continue;
|
|
330
311
|
const base = item.baseName;
|
|
@@ -342,7 +323,7 @@ async function syncClaudeProjectCommandsAndAgentsToSkillsDirs(args) {
|
|
|
342
323
|
assignedDestByItemKey.set(item.itemKey, candidate);
|
|
343
324
|
taken.add(candidate);
|
|
344
325
|
}
|
|
345
|
-
const assignedItems =
|
|
326
|
+
const assignedItems = activeItems.map((item) => ({
|
|
346
327
|
...item,
|
|
347
328
|
destRelPath: assignedDestByItemKey.get(item.itemKey),
|
|
348
329
|
}));
|
|
@@ -358,9 +339,7 @@ async function syncClaudeProjectCommandsAndAgentsToSkillsDirs(args) {
|
|
|
358
339
|
const destRelPath = item.destRelPath;
|
|
359
340
|
const destDir = path.join(targetSkillsDir, destRelPath);
|
|
360
341
|
if (await fileExists(destDir)) {
|
|
361
|
-
|
|
362
|
-
const isManagedByPlugin = pluginManagedDest.has(destRelPath);
|
|
363
|
-
if (!isManagedByProject && !isManagedByPlugin) {
|
|
342
|
+
if (!managedDest.has(destRelPath)) {
|
|
364
343
|
(0, constants_1.logWarn)(`[claude] Destination exists but is not skiller-managed, skipping: ${destDir}`, dryRun);
|
|
365
344
|
continue;
|
|
366
345
|
}
|
|
@@ -40,6 +40,7 @@ const os = __importStar(require("os"));
|
|
|
40
40
|
const toml_1 = require("@iarna/toml");
|
|
41
41
|
const zod_1 = require("zod");
|
|
42
42
|
const constants_1 = require("../constants");
|
|
43
|
+
const project_paths_1 = require("./project-paths");
|
|
43
44
|
const mcpConfigSchema = zod_1.z
|
|
44
45
|
.object({
|
|
45
46
|
enabled: zod_1.z.boolean().optional(),
|
|
@@ -121,8 +122,7 @@ async function loadConfig(options) {
|
|
|
121
122
|
configFile = path.resolve(configPath);
|
|
122
123
|
}
|
|
123
124
|
else {
|
|
124
|
-
|
|
125
|
-
const localConfigFile = path.join(projectRoot, '.claude', 'skiller.toml');
|
|
125
|
+
const localConfigFile = path.join(projectRoot, project_paths_1.CANONICAL_SKILLER_DIR, project_paths_1.SKILLER_CONFIG_FILE);
|
|
126
126
|
try {
|
|
127
127
|
await fs_1.promises.access(localConfigFile);
|
|
128
128
|
configFile = localConfigFile;
|
|
@@ -236,10 +236,10 @@ async function loadConfig(options) {
|
|
|
236
236
|
}
|
|
237
237
|
// Deprecation warnings for removed config options
|
|
238
238
|
if ('generate_from_rules' in rawSkillsSection) {
|
|
239
|
-
console.warn(`[skiller] Warning: skills.generate_from_rules is deprecated and has no effect.
|
|
239
|
+
console.warn(`[skiller] Warning: skills.generate_from_rules is deprecated and has no effect. Local rule sources in .agents/rules/ compile automatically into .agents/skills/.`);
|
|
240
240
|
}
|
|
241
241
|
if ('prune' in rawSkillsSection) {
|
|
242
|
-
console.warn(`[skiller] Warning: skills.prune is deprecated and has no effect. Skills in .
|
|
242
|
+
console.warn(`[skiller] Warning: skills.prune is deprecated and has no effect. Skills in .agents/skills/ are never auto-deleted.`);
|
|
243
243
|
}
|
|
244
244
|
const rawRulesSection = raw.rules && typeof raw.rules === 'object' && !Array.isArray(raw.rules)
|
|
245
245
|
? raw.rules
|
|
@@ -48,6 +48,7 @@ const path = __importStar(require("path"));
|
|
|
48
48
|
const os = __importStar(require("os"));
|
|
49
49
|
const FrontmatterParser_1 = require("./FrontmatterParser");
|
|
50
50
|
const constants_1 = require("../constants");
|
|
51
|
+
const project_paths_1 = require("./project-paths");
|
|
51
52
|
/**
|
|
52
53
|
* Gets the XDG config directory path, falling back to ~/.config if XDG_CONFIG_HOME is not set.
|
|
53
54
|
*/
|
|
@@ -55,31 +56,30 @@ function getXdgConfigDir() {
|
|
|
55
56
|
return process.env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config');
|
|
56
57
|
}
|
|
57
58
|
/**
|
|
58
|
-
* Searches upwards from startPath to find a .
|
|
59
|
+
* Searches upwards from startPath to find a .agents directory with skiller.toml.
|
|
59
60
|
* If not found locally and checkGlobal is true, checks for global config at XDG_CONFIG_HOME/skiller.
|
|
60
61
|
* Returns the path to the found directory, or null if not found.
|
|
61
62
|
*/
|
|
62
63
|
async function findSkillerDir(startPath, checkGlobal = true) {
|
|
63
|
-
// Search upwards from startPath for local .
|
|
64
|
+
// Search upwards from startPath for local .agents directory with skiller.toml
|
|
64
65
|
let current = startPath;
|
|
65
66
|
while (current) {
|
|
66
|
-
const
|
|
67
|
+
const skillerCandidate = path.join(current, project_paths_1.CANONICAL_SKILLER_DIR);
|
|
67
68
|
try {
|
|
68
|
-
const stat = await fs_1.promises.stat(
|
|
69
|
+
const stat = await fs_1.promises.stat(skillerCandidate);
|
|
69
70
|
if (stat.isDirectory()) {
|
|
70
|
-
|
|
71
|
-
const tomlPath = path.join(claudeCandidate, 'skiller.toml');
|
|
71
|
+
const tomlPath = path.join(skillerCandidate, project_paths_1.SKILLER_CONFIG_FILE);
|
|
72
72
|
try {
|
|
73
73
|
await fs_1.promises.stat(tomlPath);
|
|
74
|
-
return
|
|
74
|
+
return skillerCandidate;
|
|
75
75
|
}
|
|
76
76
|
catch {
|
|
77
|
-
// .
|
|
77
|
+
// .agents exists but no skiller.toml, continue searching
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
81
|
catch {
|
|
82
|
-
// ignore errors when checking for .
|
|
82
|
+
// ignore errors when checking for .agents directory
|
|
83
83
|
}
|
|
84
84
|
const parent = path.dirname(current);
|
|
85
85
|
if (parent === current) {
|
|
@@ -87,7 +87,7 @@ async function findSkillerDir(startPath, checkGlobal = true) {
|
|
|
87
87
|
}
|
|
88
88
|
current = parent;
|
|
89
89
|
}
|
|
90
|
-
// If no local .
|
|
90
|
+
// If no local .agents found and checkGlobal is true, check global config directory
|
|
91
91
|
if (checkGlobal) {
|
|
92
92
|
const globalConfigDir = path.join(getXdgConfigDir(), 'skiller');
|
|
93
93
|
try {
|
|
@@ -96,8 +96,8 @@ async function findSkillerDir(startPath, checkGlobal = true) {
|
|
|
96
96
|
return globalConfigDir;
|
|
97
97
|
}
|
|
98
98
|
}
|
|
99
|
-
catch
|
|
100
|
-
|
|
99
|
+
catch {
|
|
100
|
+
// ignore if global config doesn't exist
|
|
101
101
|
}
|
|
102
102
|
}
|
|
103
103
|
return null;
|
|
@@ -259,8 +259,8 @@ async function readMarkdownFiles(skillerDir, options) {
|
|
|
259
259
|
// 2. If AGENTS.md absent but legacy instructions.md present, use it (no longer emits a warning; legacy accepted silently).
|
|
260
260
|
// 3. Include any remaining .md files (excluding whichever of the above was used if present) in
|
|
261
261
|
// sorted order AFTER the preferred primary file so that new concatenation priority starts with AGENTS.md.
|
|
262
|
-
const topLevelAgents = path.join(skillerDir,
|
|
263
|
-
const topLevelLegacy = path.join(skillerDir,
|
|
262
|
+
const topLevelAgents = path.join(skillerDir, project_paths_1.PROJECT_AGENTS_FILE);
|
|
263
|
+
const topLevelLegacy = path.join(skillerDir, project_paths_1.LEGACY_INSTRUCTIONS_FILE);
|
|
264
264
|
// Separate primary candidates from others
|
|
265
265
|
let primaryFile = null;
|
|
266
266
|
const others = [];
|
|
@@ -284,35 +284,7 @@ async function readMarkdownFiles(skillerDir, options) {
|
|
|
284
284
|
}
|
|
285
285
|
// Sort the remaining others for stable deterministic concatenation order.
|
|
286
286
|
others.sort((a, b) => a.path.localeCompare(b.path));
|
|
287
|
-
|
|
288
|
-
// NEW: Prepend repository root AGENTS.md (outside .claude) if it exists and is not identical path.
|
|
289
|
-
try {
|
|
290
|
-
const repoRoot = path.dirname(skillerDir); // .claude parent
|
|
291
|
-
const rootAgentsPath = path.join(repoRoot, 'AGENTS.md');
|
|
292
|
-
if (path.resolve(rootAgentsPath) !== path.resolve(topLevelAgents)) {
|
|
293
|
-
const stat = await fs_1.promises.stat(rootAgentsPath);
|
|
294
|
-
if (stat.isFile()) {
|
|
295
|
-
const content = await fs_1.promises.readFile(rootAgentsPath, 'utf8');
|
|
296
|
-
// Check if this is a generated file and we have other .claude files
|
|
297
|
-
const isGenerated = content.startsWith('<!-- Generated by Skiller -->');
|
|
298
|
-
const hasSkillerFiles = others.length > 0 || primaryFile !== null;
|
|
299
|
-
// Additional check: if AGENTS.md contains skiller source comments and we have skiller files,
|
|
300
|
-
// it's likely a corrupted generated file that should be skipped
|
|
301
|
-
const containsSkillerSources = content.includes('<!-- Source: .claude/') ||
|
|
302
|
-
content.includes('<!-- Source: claude/');
|
|
303
|
-
const isProbablyGenerated = isGenerated || (containsSkillerSources && hasSkillerFiles);
|
|
304
|
-
// Skip generated AGENTS.md if we have other files in .claude
|
|
305
|
-
if (!isProbablyGenerated || !hasSkillerFiles) {
|
|
306
|
-
// Prepend so it has highest precedence
|
|
307
|
-
ordered = [{ path: rootAgentsPath, content }, ...ordered];
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
catch {
|
|
313
|
-
// ignore if root AGENTS.md not present
|
|
314
|
-
}
|
|
315
|
-
return ordered;
|
|
287
|
+
return primaryFile ? [primaryFile, ...others] : others;
|
|
316
288
|
}
|
|
317
289
|
/**
|
|
318
290
|
* Writes content to filePath, creating parent directories if necessary.
|
|
@@ -359,7 +331,7 @@ async function findGlobalSkillerDir() {
|
|
|
359
331
|
// Alias for backward compatibility
|
|
360
332
|
exports.findGlobalRulerDir = findGlobalSkillerDir;
|
|
361
333
|
/**
|
|
362
|
-
* Searches the entire directory tree from startPath to find all .
|
|
334
|
+
* Searches the entire directory tree from startPath to find all .agents directories with skiller.toml.
|
|
363
335
|
* Returns an array of directory paths from most specific to least specific.
|
|
364
336
|
*/
|
|
365
337
|
async function findAllSkillerDirs(startPath) {
|
|
@@ -375,15 +347,14 @@ async function findAllSkillerDirs(startPath) {
|
|
|
375
347
|
for (const entry of entries) {
|
|
376
348
|
const fullPath = path.join(dir, entry.name);
|
|
377
349
|
if (entry.isDirectory()) {
|
|
378
|
-
if (entry.name ===
|
|
379
|
-
|
|
380
|
-
const tomlPath = path.join(fullPath, 'skiller.toml');
|
|
350
|
+
if (entry.name === project_paths_1.CANONICAL_SKILLER_DIR) {
|
|
351
|
+
const tomlPath = path.join(fullPath, project_paths_1.SKILLER_CONFIG_FILE);
|
|
381
352
|
try {
|
|
382
353
|
await fs_1.promises.stat(tomlPath);
|
|
383
354
|
skillerDirs.push(fullPath);
|
|
384
355
|
}
|
|
385
356
|
catch {
|
|
386
|
-
// .
|
|
357
|
+
// .agents exists but no skiller.toml
|
|
387
358
|
}
|
|
388
359
|
}
|
|
389
360
|
else {
|
|
@@ -79,7 +79,8 @@ function parseFrontmatter(content) {
|
|
|
79
79
|
try {
|
|
80
80
|
// Fix common issue: globs as comma-separated unquoted strings
|
|
81
81
|
// Pattern: globs: *.tsx,**/path -> globs: ["*.tsx", "**/path"]
|
|
82
|
-
const fixedYaml = yamlContent
|
|
82
|
+
const fixedYaml = yamlContent
|
|
83
|
+
.replace(/^(\s*globs\s*:\s*)([^\n[{]+)$/gm, (match, prefix, value) => {
|
|
83
84
|
// Check if value looks like comma-separated patterns (contains * or commas)
|
|
84
85
|
if (value.includes('*') || value.includes(',')) {
|
|
85
86
|
// Split by comma and quote each part
|
|
@@ -92,6 +93,15 @@ function parseFrontmatter(content) {
|
|
|
92
93
|
return `${prefix}[${patterns}]`;
|
|
93
94
|
}
|
|
94
95
|
return match;
|
|
96
|
+
})
|
|
97
|
+
.replace(/^(\s*[\w.-]+\s*:\s*)([^\n"'[{>|].*:\s.*)$/gm, (match, prefix, value) => {
|
|
98
|
+
const trimmed = value.trim();
|
|
99
|
+
if (trimmed.length === 0 ||
|
|
100
|
+
/^(true|false|null)$/i.test(trimmed) ||
|
|
101
|
+
/^-?\d+(\.\d+)?$/.test(trimmed)) {
|
|
102
|
+
return match;
|
|
103
|
+
}
|
|
104
|
+
return `${prefix}${JSON.stringify(trimmed)}`;
|
|
95
105
|
});
|
|
96
106
|
// Try parsing again with fixed YAML
|
|
97
107
|
if (fixedYaml !== yamlContent) {
|
|
@@ -0,0 +1,123 @@
|
|
|
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.assertNoLegacyClaudePluginState = assertNoLegacyClaudePluginState;
|
|
37
|
+
const fs = __importStar(require("fs/promises"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const project_paths_1 = require("./project-paths");
|
|
40
|
+
const SkillsManifest_1 = require("./SkillsManifest");
|
|
41
|
+
async function readEnabledPluginIds(projectRoot) {
|
|
42
|
+
const settingsPath = path.join(projectRoot, project_paths_1.LEGACY_SKILLER_DIR, 'settings.json');
|
|
43
|
+
try {
|
|
44
|
+
const raw = JSON.parse(await fs.readFile(settingsPath, 'utf8'));
|
|
45
|
+
if (!raw || typeof raw !== 'object')
|
|
46
|
+
return [];
|
|
47
|
+
const enabledPlugins = raw.enabledPlugins;
|
|
48
|
+
if (!enabledPlugins || typeof enabledPlugins !== 'object')
|
|
49
|
+
return [];
|
|
50
|
+
return Object.entries(enabledPlugins)
|
|
51
|
+
.filter(([, enabled]) => enabled === true)
|
|
52
|
+
.map(([pluginId]) => pluginId)
|
|
53
|
+
.sort((a, b) => a.localeCompare(b));
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return [];
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function readPluginIdsFromManifestRaw(raw) {
|
|
60
|
+
if (!raw || typeof raw !== 'object')
|
|
61
|
+
return [];
|
|
62
|
+
const targets = raw.targets;
|
|
63
|
+
if (!targets || typeof targets !== 'object')
|
|
64
|
+
return [];
|
|
65
|
+
const pluginIds = new Set();
|
|
66
|
+
for (const rawEntries of Object.values(targets)) {
|
|
67
|
+
if (!Array.isArray(rawEntries))
|
|
68
|
+
continue;
|
|
69
|
+
for (const entry of rawEntries) {
|
|
70
|
+
if (!entry || typeof entry !== 'object')
|
|
71
|
+
continue;
|
|
72
|
+
const sourceType = entry.sourceType;
|
|
73
|
+
const pluginId = entry.pluginId;
|
|
74
|
+
if (sourceType !== 'plugin' || typeof pluginId !== 'string')
|
|
75
|
+
continue;
|
|
76
|
+
pluginIds.add(pluginId);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return [...pluginIds].sort((a, b) => a.localeCompare(b));
|
|
80
|
+
}
|
|
81
|
+
async function readPluginManifestLocations(projectRoot) {
|
|
82
|
+
const manifestPaths = [
|
|
83
|
+
path.join(projectRoot, project_paths_1.CANONICAL_SKILLER_DIR, SkillsManifest_1.SKILLS_MANIFEST_FILENAME),
|
|
84
|
+
path.join(projectRoot, project_paths_1.LEGACY_SKILLER_DIR, SkillsManifest_1.SKILLS_MANIFEST_FILENAME),
|
|
85
|
+
];
|
|
86
|
+
const locations = [];
|
|
87
|
+
for (const manifestPath of manifestPaths) {
|
|
88
|
+
try {
|
|
89
|
+
const raw = JSON.parse(await fs.readFile(manifestPath, 'utf8'));
|
|
90
|
+
const pluginIds = readPluginIdsFromManifestRaw(raw);
|
|
91
|
+
if (pluginIds.length === 0)
|
|
92
|
+
continue;
|
|
93
|
+
locations.push({
|
|
94
|
+
manifestPath,
|
|
95
|
+
pluginIds,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
// Ignore missing or invalid manifests here. Validation lives elsewhere.
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return locations;
|
|
103
|
+
}
|
|
104
|
+
async function assertNoLegacyClaudePluginState(projectRoot) {
|
|
105
|
+
const enabledPluginIds = await readEnabledPluginIds(projectRoot);
|
|
106
|
+
const manifestLocations = await readPluginManifestLocations(projectRoot);
|
|
107
|
+
if (enabledPluginIds.length === 0 && manifestLocations.length === 0) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
const lines = [
|
|
111
|
+
'Claude plugin sync is no longer supported.',
|
|
112
|
+
'',
|
|
113
|
+
'Found legacy Claude plugin state:',
|
|
114
|
+
];
|
|
115
|
+
if (enabledPluginIds.length > 0) {
|
|
116
|
+
lines.push(`- enabled plugins in .claude/settings.json: ${enabledPluginIds.join(', ')}`);
|
|
117
|
+
}
|
|
118
|
+
for (const location of manifestLocations) {
|
|
119
|
+
lines.push(`- plugin manifest entries in ${path.relative(projectRoot, location.manifestPath)}: ${location.pluginIds.join(', ')}`);
|
|
120
|
+
}
|
|
121
|
+
lines.push('', 'Migrate manually:', '1. Run `skiller migrate claude-plugins` to preview the repo installs, then rerun it with `--execute`', '2. Remove the plugin from .claude/settings.json', '3. Rerun `skiller apply`');
|
|
122
|
+
throw new Error(lines.join('\n'));
|
|
123
|
+
}
|