skiller 0.9.9 → 0.9.10

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.
@@ -126,14 +126,14 @@ async function run() {
126
126
  description: 'Enable/disable skills support (experimental, default: enabled)',
127
127
  });
128
128
  }, handlers_1.applyHandler)
129
- .command('add [args..]', 'Run the local skills CLI add command', (y) => skillsArgsBuilder(y)
129
+ .command('add [args..]', 'Run the local skills CLI add command and install compatible agent sources into .agents/skills', (y) => skillsArgsBuilder(y)
130
130
  .option('verbose', {
131
131
  type: 'boolean',
132
132
  description: 'Enable verbose logging for the follow-up apply step',
133
133
  default: false,
134
134
  })
135
135
  .alias('verbose', 'v'), handlers_1.addHandler)
136
- .command('remove [args..]', 'Run the local skills CLI remove command', (y) => skillsArgsBuilder(y)
136
+ .command('remove [args..]', 'Run the local skills CLI remove command, prune compatible agent installs, then skiller apply', (y) => skillsArgsBuilder(y)
137
137
  .option('verbose', {
138
138
  type: 'boolean',
139
139
  description: 'Enable verbose logging for the follow-up apply step',
@@ -143,21 +143,21 @@ async function run() {
143
143
  .command('list [args..]', 'Run the local skills CLI list command', skillsArgsBuilder, handlers_1.listHandler)
144
144
  .command('find [args..]', 'Run the local skills CLI find command', skillsArgsBuilder, handlers_1.findHandler)
145
145
  .command('check [args..]', 'Run the local skills CLI check command', skillsArgsBuilder, handlers_1.checkHandler)
146
- .command('install [args..]', 'Restore lock-backed skills with the local skills CLI experimental_install command, then skiller apply', (y) => skillsArgsBuilder(y)
146
+ .command('install [args..]', 'Restore lock-backed skills plus skiller-managed agent installs from lockfiles, then skiller apply', (y) => skillsArgsBuilder(y)
147
147
  .option('verbose', {
148
148
  type: 'boolean',
149
149
  description: 'Enable verbose logging for the follow-up apply step',
150
150
  default: false,
151
151
  })
152
152
  .alias('verbose', 'v'), handlers_1.installHandler)
153
- .command('update [args..]', 'Run the local skills CLI update command, then skiller apply', (y) => skillsArgsBuilder(y)
153
+ .command('update [args..]', 'Update local skills CLI installs plus skiller-managed agent installs, then skiller apply', (y) => skillsArgsBuilder(y)
154
154
  .option('verbose', {
155
155
  type: 'boolean',
156
156
  description: 'Enable verbose logging for the follow-up apply step',
157
157
  default: false,
158
158
  })
159
159
  .alias('verbose', 'v'), handlers_1.updateHandler)
160
- .command('outdated [args..]', 'Run the local skills CLI outdated command', skillsArgsBuilder, handlers_1.outdatedHandler)
160
+ .command('outdated [args..]', 'Run the local skills CLI outdated command and report outdated skiller-managed agent installs', skillsArgsBuilder, handlers_1.outdatedHandler)
161
161
  .command('skills <subcommand> [args..]', 'Pass through an arbitrary command to the local skills CLI', (y) => skillsArgsBuilder(y).positional('subcommand', {
162
162
  type: 'string',
163
163
  description: 'The local skills CLI subcommand to run',
@@ -61,6 +61,7 @@ const agents_2 = require("../agents");
61
61
  const skills_cli_1 = require("./skills-cli");
62
62
  const project_paths_1 = require("../core/project-paths");
63
63
  const SkillOwnership_1 = require("../core/SkillOwnership");
64
+ const AgentSourceCompatibility_1 = require("../core/AgentSourceCompatibility");
64
65
  const readline = __importStar(require("readline/promises"));
65
66
  async function executeSkillsWrapper(projectRoot, args) {
66
67
  try {
@@ -630,27 +631,88 @@ async function migrateRulesToSkillsHandler(argv) {
630
631
  }
631
632
  }
632
633
  async function addHandler(argv) {
633
- await executeSkillsWrapper(argv['project-root'], [
634
- 'add',
635
- ...(argv.args ?? []),
636
- ]);
637
- await applyAfterSkillsLifecycleStep(argv['project-root'], argv.verbose ?? false);
634
+ const projectRoot = argv['project-root'];
635
+ const args = argv.args ?? [];
636
+ if ((0, AgentSourceCompatibility_1.hasGlobalFlag)(args)) {
637
+ throw new Error('Agent-compatible installs are project-scoped only. Drop --global and retry.');
638
+ }
639
+ const source = (0, AgentSourceCompatibility_1.extractAddSource)(args);
640
+ let inspection;
641
+ let installedAgentSkills = false;
642
+ try {
643
+ if (source) {
644
+ inspection = await (0, AgentSourceCompatibility_1.inspectCompatibleSource)(source, args);
645
+ }
646
+ if (inspection && (0, AgentSourceCompatibility_1.hasListFlag)(args)) {
647
+ if (inspection.nativeSkillNames.length > 0) {
648
+ await executeSkillsWrapper(projectRoot, [
649
+ 'add',
650
+ ...(0, AgentSourceCompatibility_1.buildAdjustedSkillsAddArgs)(args, inspection.nativeSkillNames),
651
+ ]);
652
+ }
653
+ if (inspection.agentSkills.length > 0) {
654
+ console.log('[skiller] Compatible agent-derived skills:');
655
+ for (const agentSkill of inspection.agentSkills) {
656
+ console.log(`- ${agentSkill.installName}`);
657
+ }
658
+ }
659
+ return;
660
+ }
661
+ if (inspection && inspection.nativeSkillNames.length > 0) {
662
+ await executeSkillsWrapper(projectRoot, [
663
+ 'add',
664
+ ...(0, AgentSourceCompatibility_1.buildAdjustedSkillsAddArgs)(args, inspection.nativeSkillNames),
665
+ ]);
666
+ }
667
+ else if (!inspection || inspection.agentSkills.length === 0) {
668
+ await executeSkillsWrapper(projectRoot, ['add', ...args]);
669
+ }
670
+ if (inspection && inspection.agentSkills.length > 0) {
671
+ const installed = await (0, AgentSourceCompatibility_1.installAgentSkillsFromInspection)(projectRoot, inspection);
672
+ installedAgentSkills = true;
673
+ console.log(`[skiller] Installed ${installed.length} agent-derived skill(s): ${installed.join(', ')}`);
674
+ }
675
+ }
676
+ finally {
677
+ if (inspection && !installedAgentSkills) {
678
+ await inspection.workspace.cleanup();
679
+ }
680
+ }
681
+ await applyAfterSkillsLifecycleStep(projectRoot, argv.verbose ?? false);
638
682
  }
639
683
  async function installHandler(argv) {
640
684
  await executeSkillsWrapper(argv['project-root'], [
641
685
  'experimental_install',
642
686
  ...(argv.args ?? []),
643
687
  ]);
688
+ const restored = await (0, AgentSourceCompatibility_1.restoreAgentSkillsFromLock)(argv['project-root']);
689
+ if (restored.restored.length > 0) {
690
+ console.log(`[skiller] Restored ${restored.restored.length} agent-derived skill(s): ${restored.restored.join(', ')}`);
691
+ }
692
+ if (restored.warnings.length > 0) {
693
+ console.log(restored.warnings.map((warning) => `[skiller] ${warning}`).join('\n'));
694
+ }
644
695
  await applyAfterSkillsLifecycleStep(argv['project-root'], argv.verbose ?? false);
645
696
  }
646
697
  async function removeHandler(argv) {
647
- await executeSkillsWrapper(argv['project-root'], [
648
- 'remove',
649
- ...(argv.args ?? []),
650
- ]);
698
+ const projectRoot = argv['project-root'];
699
+ const args = argv.args ?? [];
700
+ const requestedNames = normalizeRequestedSkillNames(args);
701
+ const ownership = await (0, SkillOwnership_1.resolveSkillOwnership)(projectRoot);
702
+ const shouldRunSkillsRemove = requestedNames.length === 0 ||
703
+ args.some((arg) => arg === '--all' ||
704
+ arg === '--agent' ||
705
+ arg === '-a' ||
706
+ arg === '--skill' ||
707
+ arg === '-s') ||
708
+ requestedNames.some((name) => ownership.upstreamOwned.has(name));
709
+ if (shouldRunSkillsRemove) {
710
+ await executeSkillsWrapper(projectRoot, ['remove', ...args]);
711
+ }
651
712
  await scrubRequestedSkillsLockEntries(argv['project-root'], argv.args ?? []);
652
- await pruneRequestedUnmanagedSkillOutputs(argv['project-root'], argv.args ?? []);
653
- await applyAfterSkillsLifecycleStep(argv['project-root'], argv.verbose ?? false);
713
+ await pruneRequestedUnmanagedSkillOutputs(projectRoot, args);
714
+ await (0, AgentSourceCompatibility_1.removeAgentManagedSkills)(projectRoot, requestedNames);
715
+ await applyAfterSkillsLifecycleStep(projectRoot, argv.verbose ?? false);
654
716
  }
655
717
  async function listHandler(argv) {
656
718
  await executeSkillsWrapper(argv['project-root'], [
@@ -675,6 +737,13 @@ async function updateHandler(argv) {
675
737
  'update',
676
738
  ...(argv.args ?? []),
677
739
  ]);
740
+ const updated = await (0, AgentSourceCompatibility_1.updateAgentSkillsFromLock)(argv['project-root']);
741
+ if (updated.updated.length > 0) {
742
+ console.log(`[skiller] Updated ${updated.updated.length} agent-derived skill(s): ${updated.updated.join(', ')}`);
743
+ }
744
+ if (updated.warnings.length > 0) {
745
+ console.log(updated.warnings.map((warning) => `[skiller] ${warning}`).join('\n'));
746
+ }
678
747
  await applyAfterSkillsLifecycleStep(argv['project-root'], argv.verbose ?? false);
679
748
  }
680
749
  async function outdatedHandler(argv) {
@@ -682,6 +751,13 @@ async function outdatedHandler(argv) {
682
751
  'outdated',
683
752
  ...(argv.args ?? []),
684
753
  ]);
754
+ const outdated = await (0, AgentSourceCompatibility_1.getOutdatedAgentSkills)(argv['project-root']);
755
+ if (outdated.outdated.length > 0) {
756
+ console.log(`[skiller] Agent-derived updates available:\n${outdated.outdated.map((name) => `- ${name}`).join('\n')}`);
757
+ }
758
+ if (outdated.warnings.length > 0) {
759
+ console.log(outdated.warnings.map((warning) => `[skiller] ${warning}`).join('\n'));
760
+ }
685
761
  }
686
762
  async function skillsHandler(argv) {
687
763
  await executeSkillsWrapper(argv['project-root'], [
@@ -0,0 +1,710 @@
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.parseCompatibleSource = parseCompatibleSource;
37
+ exports.hasListFlag = hasListFlag;
38
+ exports.hasGlobalFlag = hasGlobalFlag;
39
+ exports.extractAddSource = extractAddSource;
40
+ exports.buildAdjustedSkillsAddArgs = buildAdjustedSkillsAddArgs;
41
+ exports.inspectCompatibleSource = inspectCompatibleSource;
42
+ exports.installAgentSkillsFromInspection = installAgentSkillsFromInspection;
43
+ exports.restoreAgentSkillsFromLock = restoreAgentSkillsFromLock;
44
+ exports.getOutdatedAgentSkills = getOutdatedAgentSkills;
45
+ exports.updateAgentSkillsFromLock = updateAgentSkillsFromLock;
46
+ exports.removeAgentManagedSkills = removeAgentManagedSkills;
47
+ const crypto = __importStar(require("crypto"));
48
+ const fs = __importStar(require("fs/promises"));
49
+ const path = __importStar(require("path"));
50
+ const os = __importStar(require("os"));
51
+ const yaml = __importStar(require("js-yaml"));
52
+ const child_process_1 = require("child_process");
53
+ const FrontmatterParser_1 = require("./FrontmatterParser");
54
+ const SkillerLock_1 = require("./SkillerLock");
55
+ const CANONICAL_SKILLS_DIR = path.join('.agents', 'skills');
56
+ const LEGACY_CLAUDE_SKILLS_DIR = path.join('.claude', 'skills');
57
+ const SKIP_DIRS = new Set([
58
+ '.git',
59
+ '.next',
60
+ 'build',
61
+ 'coverage',
62
+ 'dist',
63
+ 'fixtures',
64
+ 'node_modules',
65
+ 'test',
66
+ 'tests',
67
+ 'tmp',
68
+ 'tmp-fixtures',
69
+ ]);
70
+ function hashContent(content) {
71
+ return crypto.createHash('sha256').update(content, 'utf8').digest('hex');
72
+ }
73
+ function normalizeSkillNameForFilesystem(name) {
74
+ return name.trim().replace(/:/g, '-');
75
+ }
76
+ function isLocalPath(input) {
77
+ return (path.isAbsolute(input) ||
78
+ input.startsWith('./') ||
79
+ input.startsWith('../') ||
80
+ input === '.' ||
81
+ input === '..' ||
82
+ /^[a-zA-Z]:[/\\]/.test(input));
83
+ }
84
+ function sanitizeSubpath(subpath) {
85
+ const normalized = subpath.replace(/\\/g, '/');
86
+ for (const segment of normalized.split('/')) {
87
+ if (segment === '..') {
88
+ throw new Error(`Unsafe subpath '${subpath}'`);
89
+ }
90
+ }
91
+ return normalized;
92
+ }
93
+ function inferLocalSourceRootFromFile(filePath) {
94
+ const normalized = filePath.replace(/\\/g, '/');
95
+ const agentMarker = '/agents/';
96
+ const skillsMarker = '/skills/';
97
+ if (normalized.includes(agentMarker)) {
98
+ return filePath.slice(0, normalized.indexOf(agentMarker));
99
+ }
100
+ if (normalized.includes(skillsMarker)) {
101
+ return filePath.slice(0, normalized.indexOf(skillsMarker));
102
+ }
103
+ return path.dirname(filePath);
104
+ }
105
+ function parseCompatibleSource(input) {
106
+ const trimmed = input.trim();
107
+ if (isLocalPath(trimmed)) {
108
+ return {
109
+ source: path.resolve(trimmed),
110
+ type: 'local',
111
+ url: path.resolve(trimmed),
112
+ };
113
+ }
114
+ const githubPrefixMatch = trimmed.match(/^github:(.+)$/);
115
+ if (githubPrefixMatch) {
116
+ return parseCompatibleSource(githubPrefixMatch[1]);
117
+ }
118
+ const githubBlobMatch = trimmed.match(/github\.com\/([^/]+)\/([^/]+)\/blob\/([^/]+)\/(.+)/);
119
+ if (githubBlobMatch) {
120
+ const [, owner, repo, ref, subpath] = githubBlobMatch;
121
+ return {
122
+ ref,
123
+ source: `${owner}/${repo.replace(/\.git$/, '')}`,
124
+ subpath: sanitizeSubpath(subpath),
125
+ type: 'github',
126
+ url: `https://github.com/${owner}/${repo.replace(/\.git$/, '')}.git`,
127
+ };
128
+ }
129
+ const githubTreeWithPathMatch = trimmed.match(/github\.com\/([^/]+)\/([^/]+)\/tree\/([^/]+)\/(.+)/);
130
+ if (githubTreeWithPathMatch) {
131
+ const [, owner, repo, ref, subpath] = githubTreeWithPathMatch;
132
+ return {
133
+ ref,
134
+ source: `${owner}/${repo.replace(/\.git$/, '')}`,
135
+ subpath: sanitizeSubpath(subpath),
136
+ type: 'github',
137
+ url: `https://github.com/${owner}/${repo.replace(/\.git$/, '')}.git`,
138
+ };
139
+ }
140
+ const githubTreeMatch = trimmed.match(/github\.com\/([^/]+)\/([^/]+)\/tree\/([^/]+)$/);
141
+ if (githubTreeMatch) {
142
+ const [, owner, repo, ref] = githubTreeMatch;
143
+ return {
144
+ ref,
145
+ source: `${owner}/${repo.replace(/\.git$/, '')}`,
146
+ type: 'github',
147
+ url: `https://github.com/${owner}/${repo.replace(/\.git$/, '')}.git`,
148
+ };
149
+ }
150
+ const githubRepoMatch = trimmed.match(/github\.com\/([^/]+)\/([^/]+)/);
151
+ if (githubRepoMatch) {
152
+ const [, owner, repo] = githubRepoMatch;
153
+ return {
154
+ source: `${owner}/${repo.replace(/\.git$/, '')}`,
155
+ type: 'github',
156
+ url: `https://github.com/${owner}/${repo.replace(/\.git$/, '')}.git`,
157
+ };
158
+ }
159
+ const shorthandMatch = trimmed.match(/^([^/]+)\/([^/]+)$/);
160
+ if (shorthandMatch) {
161
+ const [, owner, repo] = shorthandMatch;
162
+ return {
163
+ source: `${owner}/${repo}`,
164
+ type: 'github',
165
+ url: `https://github.com/${owner}/${repo}.git`,
166
+ };
167
+ }
168
+ if (trimmed.startsWith('git@') ||
169
+ trimmed.startsWith('https://') ||
170
+ trimmed.startsWith('http://')) {
171
+ return {
172
+ source: trimmed,
173
+ type: 'git',
174
+ url: trimmed,
175
+ };
176
+ }
177
+ throw new Error(`Unsupported source '${input}'`);
178
+ }
179
+ async function runGitClone(url, ref) {
180
+ const cloneDir = await fs.mkdtemp(path.join(os.tmpdir(), 'skiller-agents-'));
181
+ const args = ['clone', '--depth', '1'];
182
+ if (ref) {
183
+ args.push('--branch', ref);
184
+ }
185
+ args.push(url, cloneDir);
186
+ await new Promise((resolve, reject) => {
187
+ const child = (0, child_process_1.spawn)('git', args, {
188
+ env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
189
+ stdio: 'pipe',
190
+ });
191
+ let stderr = '';
192
+ child.stderr.on('data', (chunk) => {
193
+ stderr += chunk.toString();
194
+ });
195
+ child.on('error', reject);
196
+ child.on('exit', (code) => {
197
+ if (code === 0) {
198
+ resolve();
199
+ return;
200
+ }
201
+ reject(new Error(stderr.trim() || `git clone failed with exit code ${code}`));
202
+ });
203
+ });
204
+ return cloneDir;
205
+ }
206
+ async function withSourceWorkspace(rawSource) {
207
+ const parsed = parseCompatibleSource(rawSource);
208
+ return withParsedSourceWorkspace(parsed);
209
+ }
210
+ async function withParsedSourceWorkspace(parsed) {
211
+ if (parsed.type === 'local') {
212
+ const targetPath = parsed.subpath
213
+ ? path.join(parsed.url, parsed.subpath)
214
+ : parsed.url;
215
+ let rootPath = parsed.url;
216
+ let searchPath = targetPath;
217
+ try {
218
+ const stats = await fs.stat(targetPath);
219
+ if (stats.isFile()) {
220
+ rootPath = inferLocalSourceRootFromFile(targetPath);
221
+ searchPath = targetPath;
222
+ }
223
+ }
224
+ catch {
225
+ // Let downstream callers surface missing paths consistently.
226
+ }
227
+ return {
228
+ cleanup: async () => undefined,
229
+ parsed,
230
+ rootPath,
231
+ searchPath,
232
+ };
233
+ }
234
+ const cloneDir = await runGitClone(parsed.url, parsed.ref);
235
+ const searchPath = parsed.subpath
236
+ ? path.join(cloneDir, parsed.subpath)
237
+ : cloneDir;
238
+ return {
239
+ cleanup: async () => {
240
+ await fs.rm(cloneDir, { force: true, recursive: true });
241
+ },
242
+ parsed,
243
+ rootPath: cloneDir,
244
+ searchPath,
245
+ };
246
+ }
247
+ function parseSourceFromLockEntry(entry) {
248
+ if (entry.sourceType === 'local') {
249
+ return {
250
+ ref: entry.ref,
251
+ source: entry.source,
252
+ subpath: entry.subpath,
253
+ type: 'local',
254
+ url: entry.source,
255
+ };
256
+ }
257
+ if (entry.sourceType === 'github') {
258
+ return {
259
+ ref: entry.ref,
260
+ source: entry.source,
261
+ subpath: entry.subpath,
262
+ type: 'github',
263
+ url: `https://github.com/${entry.source}.git`,
264
+ };
265
+ }
266
+ return {
267
+ ref: entry.ref,
268
+ source: entry.source,
269
+ subpath: entry.subpath,
270
+ type: 'git',
271
+ url: entry.source,
272
+ };
273
+ }
274
+ function cloneRecord(value) {
275
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
276
+ return {};
277
+ }
278
+ return { ...value };
279
+ }
280
+ function buildCompiledSkillContent(name, rawFrontmatter, body, sourceRelPath) {
281
+ const frontmatter = rawFrontmatter ? { ...rawFrontmatter } : {};
282
+ if (typeof frontmatter.name !== 'string' || frontmatter.name.length === 0) {
283
+ frontmatter.name = name;
284
+ }
285
+ if (typeof frontmatter.description !== 'string' ||
286
+ frontmatter.description.length === 0) {
287
+ frontmatter.description = `Skill: ${name}`;
288
+ }
289
+ const metadata = cloneRecord(frontmatter.metadata);
290
+ const skiller = cloneRecord(metadata.skiller);
291
+ skiller.source = sourceRelPath;
292
+ metadata.skiller = skiller;
293
+ frontmatter.metadata = metadata;
294
+ return `---
295
+ ${yaml.dump(frontmatter, { lineWidth: -1, noRefs: true }).trim()}
296
+ ---
297
+
298
+ ${body.trim()}
299
+ `;
300
+ }
301
+ function shouldSkipDir(name) {
302
+ return SKIP_DIRS.has(name);
303
+ }
304
+ function isValidRelativePath(value) {
305
+ return value.startsWith('./');
306
+ }
307
+ async function collectDeclaredPluginBases(basePath) {
308
+ const pluginBases = new Set();
309
+ try {
310
+ const marketplace = JSON.parse(await fs.readFile(path.join(basePath, '.claude-plugin', 'marketplace.json'), 'utf8'));
311
+ const pluginRoot = marketplace.metadata?.pluginRoot;
312
+ const validPluginRoot = pluginRoot === undefined || isValidRelativePath(pluginRoot);
313
+ if (validPluginRoot) {
314
+ for (const plugin of marketplace.plugins ?? []) {
315
+ if (typeof plugin.source !== 'string')
316
+ continue;
317
+ if (!isValidRelativePath(plugin.source))
318
+ continue;
319
+ pluginBases.add(path.join(basePath, pluginRoot ?? '', plugin.source));
320
+ }
321
+ }
322
+ }
323
+ catch {
324
+ // Ignore invalid or missing marketplace manifests.
325
+ }
326
+ try {
327
+ await fs.access(path.join(basePath, '.claude-plugin', 'plugin.json'));
328
+ pluginBases.add(basePath);
329
+ }
330
+ catch {
331
+ // Ignore.
332
+ }
333
+ return [...pluginBases];
334
+ }
335
+ async function discoverAgentFiles(basePath, rootPath = basePath) {
336
+ try {
337
+ const stats = await fs.stat(basePath);
338
+ if (stats.isFile()) {
339
+ if (!basePath.endsWith('.md'))
340
+ return [];
341
+ return [
342
+ {
343
+ absolutePath: basePath,
344
+ relativePath: path.relative(rootPath, basePath).replace(/\\/g, '/'),
345
+ },
346
+ ];
347
+ }
348
+ }
349
+ catch {
350
+ return [];
351
+ }
352
+ const discovered = new Map();
353
+ const candidateDirs = new Set([path.join(basePath, 'agents')]);
354
+ for (const pluginBase of await collectDeclaredPluginBases(basePath)) {
355
+ candidateDirs.add(path.join(pluginBase, 'agents'));
356
+ }
357
+ const walk = async (currentDir, depth) => {
358
+ if (depth > 8)
359
+ return;
360
+ let entries;
361
+ try {
362
+ entries = await fs.readdir(currentDir, { withFileTypes: true });
363
+ }
364
+ catch {
365
+ return;
366
+ }
367
+ for (const entry of entries) {
368
+ const absolutePath = path.join(currentDir, entry.name);
369
+ if (entry.isDirectory()) {
370
+ if (shouldSkipDir(entry.name))
371
+ continue;
372
+ if (entry.name === 'agents') {
373
+ candidateDirs.add(absolutePath);
374
+ }
375
+ await walk(absolutePath, depth + 1);
376
+ continue;
377
+ }
378
+ }
379
+ };
380
+ await walk(basePath, 0);
381
+ const collectMarkdown = async (dir, depth) => {
382
+ if (depth > 8)
383
+ return;
384
+ let entries;
385
+ try {
386
+ entries = await fs.readdir(dir, { withFileTypes: true });
387
+ }
388
+ catch {
389
+ return;
390
+ }
391
+ for (const entry of entries) {
392
+ const absolutePath = path.join(dir, entry.name);
393
+ if (entry.isDirectory()) {
394
+ if (shouldSkipDir(entry.name))
395
+ continue;
396
+ await collectMarkdown(absolutePath, depth + 1);
397
+ continue;
398
+ }
399
+ if (!entry.isFile() || !entry.name.endsWith('.md'))
400
+ continue;
401
+ const relativePath = path
402
+ .relative(basePath, absolutePath)
403
+ .replace(/\\/g, '/');
404
+ discovered.set(relativePath, { absolutePath, relativePath });
405
+ }
406
+ };
407
+ for (const candidateDir of candidateDirs) {
408
+ await collectMarkdown(candidateDir, 0);
409
+ }
410
+ return [...discovered.values()].sort((a, b) => a.relativePath.localeCompare(b.relativePath));
411
+ }
412
+ async function discoverSkillNames(basePath) {
413
+ try {
414
+ const stats = await fs.stat(basePath);
415
+ if (stats.isFile()) {
416
+ if (path.basename(basePath) !== 'SKILL.md')
417
+ return [];
418
+ const parsed = (0, FrontmatterParser_1.parseFrontmatter)(await fs.readFile(basePath, 'utf8'));
419
+ const declaredName = typeof parsed.frontmatter?.name === 'string'
420
+ ? parsed.frontmatter.name
421
+ : path.basename(path.dirname(basePath));
422
+ return [normalizeSkillNameForFilesystem(declaredName)];
423
+ }
424
+ }
425
+ catch {
426
+ // Ignore missing paths.
427
+ }
428
+ const discovered = new Set();
429
+ const candidateDirs = new Set([path.join(basePath, 'skills')]);
430
+ try {
431
+ const parsed = (0, FrontmatterParser_1.parseFrontmatter)(await fs.readFile(path.join(basePath, 'SKILL.md'), 'utf8'));
432
+ const declaredName = typeof parsed.frontmatter?.name === 'string'
433
+ ? parsed.frontmatter.name
434
+ : path.basename(basePath);
435
+ discovered.add(normalizeSkillNameForFilesystem(declaredName));
436
+ }
437
+ catch {
438
+ // Ignore missing root SKILL.md.
439
+ }
440
+ for (const pluginBase of await collectDeclaredPluginBases(basePath)) {
441
+ candidateDirs.add(path.join(pluginBase, 'skills'));
442
+ }
443
+ for (const candidateDir of candidateDirs) {
444
+ let entries;
445
+ try {
446
+ entries = await fs.readdir(candidateDir, { withFileTypes: true });
447
+ }
448
+ catch {
449
+ continue;
450
+ }
451
+ for (const entry of entries) {
452
+ if (!entry.isDirectory())
453
+ continue;
454
+ const skillMdPath = path.join(candidateDir, entry.name, 'SKILL.md');
455
+ try {
456
+ const parsed = (0, FrontmatterParser_1.parseFrontmatter)(await fs.readFile(skillMdPath, 'utf8'));
457
+ const declaredName = typeof parsed.frontmatter?.name === 'string'
458
+ ? parsed.frontmatter.name
459
+ : entry.name;
460
+ discovered.add(normalizeSkillNameForFilesystem(declaredName));
461
+ }
462
+ catch {
463
+ continue;
464
+ }
465
+ }
466
+ }
467
+ return [...discovered].sort((a, b) => a.localeCompare(b));
468
+ }
469
+ async function discoverAgentSkillCandidates(basePath, rootPath = basePath) {
470
+ const agentFiles = await discoverAgentFiles(basePath, rootPath);
471
+ const candidates = [];
472
+ for (const agentFile of agentFiles) {
473
+ const parsed = (0, FrontmatterParser_1.parseFrontmatter)(await fs.readFile(agentFile.absolutePath, 'utf8'));
474
+ const declaredName = parsed.frontmatter?.name;
475
+ const description = parsed.frontmatter?.description;
476
+ if (typeof declaredName !== 'string' || typeof description !== 'string') {
477
+ continue;
478
+ }
479
+ const compiledContent = buildCompiledSkillContent(declaredName, parsed.rawFrontmatter, parsed.body, agentFile.relativePath);
480
+ candidates.push({
481
+ compiledContent,
482
+ computedHash: hashContent(compiledContent),
483
+ description,
484
+ installName: normalizeSkillNameForFilesystem(declaredName),
485
+ name: declaredName,
486
+ sourceRelPath: agentFile.relativePath,
487
+ });
488
+ }
489
+ return candidates.sort((a, b) => a.installName.localeCompare(b.installName));
490
+ }
491
+ function parseRequestedSkillFilters(args) {
492
+ const names = new Set();
493
+ let wildcard = false;
494
+ const argv = args ?? [];
495
+ for (let index = 0; index < argv.length; index += 1) {
496
+ const current = argv[index];
497
+ if (current !== '--skill' && current !== '-s')
498
+ continue;
499
+ for (let valueIndex = index + 1; valueIndex < argv.length; valueIndex += 1) {
500
+ const value = argv[valueIndex];
501
+ if (value.startsWith('-'))
502
+ break;
503
+ if (value === '*') {
504
+ wildcard = true;
505
+ continue;
506
+ }
507
+ names.add(normalizeSkillNameForFilesystem(value));
508
+ }
509
+ }
510
+ return { names, wildcard };
511
+ }
512
+ function hasListFlag(args) {
513
+ return (args ?? []).some((arg) => arg === '--list' || arg === '-l');
514
+ }
515
+ function hasGlobalFlag(args) {
516
+ return (args ?? []).some((arg) => arg === '--global' || arg === '-g');
517
+ }
518
+ function extractAddSource(args) {
519
+ for (const arg of args ?? []) {
520
+ if (arg.startsWith('-'))
521
+ continue;
522
+ return arg;
523
+ }
524
+ return null;
525
+ }
526
+ function buildAdjustedSkillsAddArgs(originalArgs, nativeSkillNames) {
527
+ const args = [...(originalArgs ?? [])];
528
+ const next = [];
529
+ for (let index = 0; index < args.length; index += 1) {
530
+ const current = args[index];
531
+ if (current === '--skill' || current === '-s') {
532
+ index += 1;
533
+ while (index < args.length && !args[index].startsWith('-')) {
534
+ index += 1;
535
+ }
536
+ index -= 1;
537
+ continue;
538
+ }
539
+ next.push(current);
540
+ }
541
+ if (nativeSkillNames.length > 0) {
542
+ next.push('--skill', ...nativeSkillNames);
543
+ }
544
+ return next;
545
+ }
546
+ async function inspectCompatibleSource(rawSource, args) {
547
+ const workspace = await withSourceWorkspace(rawSource);
548
+ const [nativeSkillNames, discoveredAgentSkills] = await Promise.all([
549
+ discoverSkillNames(workspace.searchPath),
550
+ discoverAgentSkillCandidates(workspace.searchPath, workspace.rootPath),
551
+ ]);
552
+ const requested = parseRequestedSkillFilters(args);
553
+ const agentSkills = discoveredAgentSkills.filter((agentSkill) => {
554
+ if (requested.wildcard || requested.names.size === 0)
555
+ return true;
556
+ return requested.names.has(agentSkill.installName);
557
+ });
558
+ return {
559
+ agentSkills: agentSkills.sort((a, b) => a.installName.localeCompare(b.installName)),
560
+ nativeSkillNames: requested.wildcard
561
+ ? nativeSkillNames
562
+ : nativeSkillNames.filter((name) => requested.names.size === 0 || requested.names.has(name)),
563
+ workspace,
564
+ };
565
+ }
566
+ async function installAgentSkillsFromInspection(projectRoot, inspection) {
567
+ const installed = [];
568
+ const lockEntries = {};
569
+ const canonicalSkillsDir = path.join(projectRoot, CANONICAL_SKILLS_DIR);
570
+ for (const agentSkill of inspection.agentSkills) {
571
+ const skillDir = path.join(canonicalSkillsDir, agentSkill.installName);
572
+ await fs.rm(skillDir, { force: true, recursive: true });
573
+ await fs.mkdir(skillDir, { recursive: true });
574
+ await fs.writeFile(path.join(skillDir, 'SKILL.md'), agentSkill.compiledContent, 'utf8');
575
+ lockEntries[agentSkill.installName] = {
576
+ computedHash: agentSkill.computedHash,
577
+ ref: inspection.workspace.parsed.ref,
578
+ source: inspection.workspace.parsed.source,
579
+ sourceRelPath: agentSkill.sourceRelPath,
580
+ sourceType: inspection.workspace.parsed.type,
581
+ subpath: inspection.workspace.parsed.subpath,
582
+ };
583
+ installed.push(agentSkill.installName);
584
+ }
585
+ await (0, SkillerLock_1.upsertSkillerLockEntries)(projectRoot, lockEntries);
586
+ await inspection.workspace.cleanup();
587
+ return installed.sort((a, b) => a.localeCompare(b));
588
+ }
589
+ function groupLockEntriesBySource(skills) {
590
+ const groups = new Map();
591
+ for (const [skillName, entry] of Object.entries(skills)) {
592
+ const key = JSON.stringify([
593
+ entry.source,
594
+ entry.sourceType,
595
+ entry.ref ?? '',
596
+ entry.subpath ?? '',
597
+ ]);
598
+ const existing = groups.get(key) ?? [];
599
+ existing.push([skillName, entry]);
600
+ groups.set(key, existing);
601
+ }
602
+ return groups;
603
+ }
604
+ async function installSingleAgentSkill(projectRoot, skillName, candidate, entry) {
605
+ const skillDir = path.join(projectRoot, CANONICAL_SKILLS_DIR, skillName);
606
+ await fs.rm(skillDir, { force: true, recursive: true });
607
+ await fs.mkdir(skillDir, { recursive: true });
608
+ await fs.writeFile(path.join(skillDir, 'SKILL.md'), candidate.compiledContent, 'utf8');
609
+ await (0, SkillerLock_1.upsertSkillerLockEntries)(projectRoot, {
610
+ [skillName]: {
611
+ ...entry,
612
+ computedHash: candidate.computedHash,
613
+ },
614
+ });
615
+ }
616
+ async function restoreAgentSkillsFromLock(projectRoot) {
617
+ const lock = await (0, SkillerLock_1.readSkillerLock)(projectRoot);
618
+ const restored = [];
619
+ const warnings = [];
620
+ for (const entries of groupLockEntriesBySource(lock.skills).values()) {
621
+ const [, entry] = entries[0];
622
+ const workspace = await withParsedSourceWorkspace(parseSourceFromLockEntry(entry));
623
+ const agentSkills = await discoverAgentSkillCandidates(workspace.searchPath, workspace.rootPath);
624
+ const candidates = new Map(agentSkills.map((candidate) => [candidate.sourceRelPath, candidate]));
625
+ for (const [skillName, lockEntry] of entries) {
626
+ const candidate = candidates.get(lockEntry.sourceRelPath);
627
+ if (!candidate) {
628
+ warnings.push(`Could not restore '${skillName}' from ${lockEntry.source}: missing ${lockEntry.sourceRelPath}`);
629
+ continue;
630
+ }
631
+ await installSingleAgentSkill(projectRoot, skillName, candidate, lockEntry);
632
+ restored.push(skillName);
633
+ }
634
+ await workspace.cleanup();
635
+ }
636
+ return {
637
+ restored: restored.sort((a, b) => a.localeCompare(b)),
638
+ warnings,
639
+ };
640
+ }
641
+ async function getOutdatedAgentSkills(projectRoot) {
642
+ const lock = await (0, SkillerLock_1.readSkillerLock)(projectRoot);
643
+ const outdated = [];
644
+ const warnings = [];
645
+ for (const entries of groupLockEntriesBySource(lock.skills).values()) {
646
+ const [, entry] = entries[0];
647
+ const workspace = await withParsedSourceWorkspace(parseSourceFromLockEntry(entry));
648
+ const agentSkills = await discoverAgentSkillCandidates(workspace.searchPath, workspace.rootPath);
649
+ const candidates = new Map(agentSkills.map((candidate) => [candidate.sourceRelPath, candidate]));
650
+ for (const [skillName, lockEntry] of entries) {
651
+ const candidate = candidates.get(lockEntry.sourceRelPath);
652
+ if (!candidate) {
653
+ warnings.push(`Could not check '${skillName}' from ${lockEntry.source}: missing ${lockEntry.sourceRelPath}`);
654
+ continue;
655
+ }
656
+ if (candidate.computedHash !== lockEntry.computedHash) {
657
+ outdated.push(skillName);
658
+ }
659
+ }
660
+ await workspace.cleanup();
661
+ }
662
+ return {
663
+ outdated: outdated.sort((a, b) => a.localeCompare(b)),
664
+ warnings,
665
+ };
666
+ }
667
+ async function updateAgentSkillsFromLock(projectRoot) {
668
+ const lock = await (0, SkillerLock_1.readSkillerLock)(projectRoot);
669
+ const updated = [];
670
+ const warnings = [];
671
+ for (const entries of groupLockEntriesBySource(lock.skills).values()) {
672
+ const [, entry] = entries[0];
673
+ const workspace = await withParsedSourceWorkspace(parseSourceFromLockEntry(entry));
674
+ const agentSkills = await discoverAgentSkillCandidates(workspace.searchPath, workspace.rootPath);
675
+ const candidates = new Map(agentSkills.map((candidate) => [candidate.sourceRelPath, candidate]));
676
+ for (const [skillName, lockEntry] of entries) {
677
+ const candidate = candidates.get(lockEntry.sourceRelPath);
678
+ if (!candidate) {
679
+ warnings.push(`Could not update '${skillName}' from ${lockEntry.source}: missing ${lockEntry.sourceRelPath}`);
680
+ continue;
681
+ }
682
+ if (candidate.computedHash === lockEntry.computedHash) {
683
+ continue;
684
+ }
685
+ await installSingleAgentSkill(projectRoot, skillName, candidate, lockEntry);
686
+ updated.push(skillName);
687
+ }
688
+ await workspace.cleanup();
689
+ }
690
+ return {
691
+ updated: updated.sort((a, b) => a.localeCompare(b)),
692
+ warnings,
693
+ };
694
+ }
695
+ async function removeAgentManagedSkills(projectRoot, skillNames) {
696
+ const removed = await (0, SkillerLock_1.removeSkillerLockEntries)(projectRoot, skillNames);
697
+ if (removed.length === 0)
698
+ return [];
699
+ for (const skillName of removed) {
700
+ await fs.rm(path.join(projectRoot, CANONICAL_SKILLS_DIR, skillName), {
701
+ force: true,
702
+ recursive: true,
703
+ });
704
+ await fs.rm(path.join(projectRoot, LEGACY_CLAUDE_SKILLS_DIR, skillName), {
705
+ force: true,
706
+ recursive: true,
707
+ });
708
+ }
709
+ return removed;
710
+ }
@@ -43,6 +43,7 @@ const path = __importStar(require("path"));
43
43
  const yaml = __importStar(require("js-yaml"));
44
44
  const project_paths_1 = require("./project-paths");
45
45
  const FrontmatterParser_1 = require("./FrontmatterParser");
46
+ const SkillerLock_1 = require("./SkillerLock");
46
47
  function normalizeSkillNameForFilesystem(name) {
47
48
  return name.replace(/:/g, '-');
48
49
  }
@@ -306,15 +307,20 @@ async function readCanonicalSkillNames(projectRoot) {
306
307
  }
307
308
  }
308
309
  async function readUpstreamOwnedSkillNames(projectRoot) {
310
+ const upstreamOwned = new Set();
309
311
  try {
310
312
  const raw = JSON.parse(await fs.readFile(getSkillsLockPath(projectRoot), 'utf8'));
311
- return new Set(Object.keys(raw.skills ?? {})
312
- .map(normalizeSkillNameForFilesystem)
313
- .sort((a, b) => a.localeCompare(b)));
313
+ for (const key of Object.keys(raw.skills ?? {})) {
314
+ upstreamOwned.add(normalizeSkillNameForFilesystem(key));
315
+ }
314
316
  }
315
317
  catch {
316
- return new Set();
318
+ // Ignore missing or invalid skills-lock.json.
319
+ }
320
+ for (const key of await (0, SkillerLock_1.readSkillerLockNames)(projectRoot)) {
321
+ upstreamOwned.add(normalizeSkillNameForFilesystem(key));
317
322
  }
323
+ return new Set([...upstreamOwned].sort((a, b) => a.localeCompare(b)));
318
324
  }
319
325
  async function resolveSkillOwnership(projectRoot) {
320
326
  const upstreamOwned = await readUpstreamOwnedSkillNames(projectRoot);
@@ -338,13 +344,13 @@ async function resolveSkillOwnership(projectRoot) {
338
344
  ...conflicts.map((name) => {
339
345
  const owners = [];
340
346
  if (upstreamOwned.has(name))
341
- owners.push('skills-lock.json');
347
+ owners.push('upstream lock');
342
348
  if (localOwned.has(name))
343
349
  owners.push('.agents/rules');
344
350
  return `Skill '${name}' has mixed ownership: ${owners.join(', ')}`;
345
351
  }),
346
352
  ...[...orphaned].map((name) => {
347
- return `Canonical skill '${name}' is unmanaged; leaving it untouched because it is not in skills-lock.json or .agents/rules/${name}.mdc.`;
353
+ return `Canonical skill '${name}' is unmanaged; leaving it untouched because it is not in skills-lock.json, skiller-lock.json, or .agents/rules/${name}.mdc.`;
348
354
  }),
349
355
  ];
350
356
  return {
@@ -0,0 +1,112 @@
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.SKILLER_LOCK_FILENAME = void 0;
37
+ exports.getSkillerLockPath = getSkillerLockPath;
38
+ exports.readSkillerLock = readSkillerLock;
39
+ exports.writeSkillerLock = writeSkillerLock;
40
+ exports.upsertSkillerLockEntries = upsertSkillerLockEntries;
41
+ exports.removeSkillerLockEntries = removeSkillerLockEntries;
42
+ exports.readSkillerLockNames = readSkillerLockNames;
43
+ const fs = __importStar(require("fs/promises"));
44
+ const path = __importStar(require("path"));
45
+ exports.SKILLER_LOCK_FILENAME = 'skiller-lock.json';
46
+ const SKILLER_LOCK_VERSION = 1;
47
+ function createEmptyLock() {
48
+ return {
49
+ version: SKILLER_LOCK_VERSION,
50
+ skills: {},
51
+ };
52
+ }
53
+ function getSkillerLockPath(projectRoot) {
54
+ return path.join(projectRoot, exports.SKILLER_LOCK_FILENAME);
55
+ }
56
+ async function readSkillerLock(projectRoot) {
57
+ try {
58
+ const raw = JSON.parse(await fs.readFile(getSkillerLockPath(projectRoot), 'utf8'));
59
+ if (raw.version !== SKILLER_LOCK_VERSION || !raw.skills) {
60
+ return createEmptyLock();
61
+ }
62
+ return raw;
63
+ }
64
+ catch {
65
+ return createEmptyLock();
66
+ }
67
+ }
68
+ async function writeSkillerLock(projectRoot, lock) {
69
+ const lockPath = getSkillerLockPath(projectRoot);
70
+ const sortedSkills = {};
71
+ for (const key of Object.keys(lock.skills).sort((a, b) => a.localeCompare(b))) {
72
+ sortedSkills[key] = lock.skills[key];
73
+ }
74
+ if (Object.keys(sortedSkills).length === 0) {
75
+ await fs.rm(lockPath, { force: true });
76
+ return;
77
+ }
78
+ await fs.writeFile(lockPath, JSON.stringify({
79
+ version: SKILLER_LOCK_VERSION,
80
+ skills: sortedSkills,
81
+ }, null, 2) + '\n', 'utf8');
82
+ }
83
+ async function upsertSkillerLockEntries(projectRoot, entries) {
84
+ if (Object.keys(entries).length === 0)
85
+ return;
86
+ const lock = await readSkillerLock(projectRoot);
87
+ lock.skills = {
88
+ ...lock.skills,
89
+ ...entries,
90
+ };
91
+ await writeSkillerLock(projectRoot, lock);
92
+ }
93
+ async function removeSkillerLockEntries(projectRoot, skillNames) {
94
+ if (skillNames.length === 0)
95
+ return [];
96
+ const lock = await readSkillerLock(projectRoot);
97
+ const removed = [];
98
+ for (const skillName of skillNames) {
99
+ if (!(skillName in lock.skills))
100
+ continue;
101
+ delete lock.skills[skillName];
102
+ removed.push(skillName);
103
+ }
104
+ if (removed.length > 0) {
105
+ await writeSkillerLock(projectRoot, lock);
106
+ }
107
+ return removed.sort((a, b) => a.localeCompare(b));
108
+ }
109
+ async function readSkillerLockNames(projectRoot) {
110
+ const lock = await readSkillerLock(projectRoot);
111
+ return new Set(Object.keys(lock.skills).sort((a, b) => a.localeCompare(b)));
112
+ }
@@ -727,7 +727,7 @@ async function compileRulesToSkills(skillerDir, projectRoot, verbose, dryRun) {
727
727
  for (const ruleFile of ruleFiles) {
728
728
  const skillName = path.basename(ruleFile.name, '.mdc');
729
729
  if (ownership.upstreamOwned.has(skillName)) {
730
- throw new Error(`Local rule '${skillName}' conflicts with upstream-managed skill '${skillName}' in skills-lock.json`);
730
+ throw new Error(`Local rule '${skillName}' conflicts with lock-managed skill '${skillName}' in skills-lock.json or skiller-lock.json`);
731
731
  }
732
732
  const sourcePath = path.join(rulesDir, ruleFile.name);
733
733
  const sourceContent = await fs.readFile(sourcePath, 'utf8');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skiller",
3
- "version": "0.9.9",
3
+ "version": "0.9.10",
4
4
  "description": "Skiller — apply the same rules to all coding agents",
5
5
  "main": "dist/lib.js",
6
6
  "publishConfig": {