@alavida/agentpack 0.1.6 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/intent.js CHANGED
@@ -1,10 +1,35 @@
1
1
  #!/usr/bin/env node
2
- // Auto-generated by @tanstack/intent setup
3
- // Exposes the intent end-user CLI for consumers of this library.
4
- // Commit this file, then add to your package.json:
5
- // "bin": { "intent": "./bin/intent.js" }
2
+ import { spawnSync } from 'node:child_process';
3
+ import { existsSync, readFileSync } from 'node:fs';
4
+ import { dirname, join } from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+
7
+ const packageRoot = dirname(dirname(fileURLToPath(import.meta.url)));
8
+ const intentPackageRoot = join(packageRoot, 'node_modules', '@tanstack', 'intent');
9
+
6
10
  try {
7
- await import('@tanstack/intent/intent-library')
11
+ const packageJsonPath = join(intentPackageRoot, 'package.json');
12
+ if (!existsSync(packageJsonPath)) {
13
+ const error = new Error('@tanstack/intent is not installed');
14
+ error.code = 'MODULE_NOT_FOUND';
15
+ throw error;
16
+ }
17
+
18
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
19
+ const cliRelativePath = typeof packageJson.bin === 'string'
20
+ ? packageJson.bin
21
+ : packageJson.bin?.intent;
22
+
23
+ if (!cliRelativePath) {
24
+ throw new Error('@tanstack/intent does not expose an intent cli binary');
25
+ }
26
+
27
+ const result = spawnSync(process.execPath, [join(intentPackageRoot, cliRelativePath), ...process.argv.slice(2)], {
28
+ stdio: 'inherit',
29
+ });
30
+
31
+ if (result.error) throw result.error;
32
+ process.exit(result.status ?? 0);
8
33
  } catch (e) {
9
34
  if (e?.code === 'ERR_MODULE_NOT_FOUND' || e?.code === 'MODULE_NOT_FOUND') {
10
35
  console.error('@tanstack/intent is not installed.')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alavida/agentpack",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "description": "Package-backed skills lifecycle CLI for agent skills and plugins",
5
5
  "type": "module",
6
6
  "workspaces": [
@@ -23,8 +23,8 @@
23
23
  "smoke:monorepo": "node scripts/smoke-monorepo.mjs",
24
24
  "docs:dev": "cd docs && mint dev",
25
25
  "changeset": "changeset",
26
- "version-packages": "changeset version",
27
- "release": "changeset publish",
26
+ "version-packages": "node scripts/version-packages.mjs",
27
+ "release": "node scripts/release.mjs",
28
28
  "intent:validate": "npx @tanstack/intent validate",
29
29
  "build:dashboard": "node scripts/build-dashboard.mjs"
30
30
  },
@@ -304,6 +304,19 @@ export function skillsCommand() {
304
304
  return;
305
305
  }
306
306
 
307
+ if (result.kind === 'package') {
308
+ output.write(`Package: ${result.packageName}`);
309
+ if (result.packageVersion) output.write(`Version: ${result.packageVersion}`);
310
+ output.write(`Path: ${result.packagePath}`);
311
+ output.write('');
312
+ output.write('Exports:');
313
+ for (const entry of result.exports) {
314
+ output.write(`- ${entry.name}`);
315
+ output.write(` path: ${entry.skillFile}`);
316
+ }
317
+ return;
318
+ }
319
+
307
320
  output.write(`Skill: ${result.name}`);
308
321
  if (result.description) output.write(`Description: ${result.description}`);
309
322
  if (result.packageName) output.write(`Package: ${result.packageName}`);
@@ -311,6 +324,7 @@ export function skillsCommand() {
311
324
  if (result.status) output.write(`Status: ${result.status}`);
312
325
  if (result.replacement) output.write(`Replacement: ${result.replacement}`);
313
326
  if (result.message) output.write(`Message: ${result.message}`);
327
+ if (result.wraps) output.write(`Wraps: ${result.wraps}`);
314
328
  output.write(`Path: ${result.skillFile}`);
315
329
 
316
330
  output.write('');
@@ -328,6 +342,12 @@ export function skillsCommand() {
328
342
  } else {
329
343
  for (const requirement of result.requires) output.write(`- ${requirement}`);
330
344
  }
345
+
346
+ if (result.overrides?.length) {
347
+ output.write('');
348
+ output.write('Overrides:');
349
+ for (const override of result.overrides) output.write(`- ${override}`);
350
+ }
331
351
  });
332
352
 
333
353
  cmd
@@ -387,16 +407,26 @@ export function skillsCommand() {
387
407
  const result = validateSkillsUseCase(target);
388
408
 
389
409
  if (globalOpts.json) {
390
- output.json(
391
- target
392
- ? result.skills[0]
393
- : result
394
- );
410
+ output.json(target && result.count === 1 ? result.skills[0] : result);
395
411
  if (!result.valid) process.exitCode = EXIT_CODES.VALIDATION;
396
412
  return;
397
413
  }
398
414
 
399
415
  if (target) {
416
+ if (result.count > 1) {
417
+ output.write(`Validated Skills: ${result.count}`);
418
+ output.write(`Valid Skills: ${result.validCount}`);
419
+ output.write(`Invalid Skills: ${result.invalidCount}`);
420
+ for (const skill of result.skills) {
421
+ output.write('');
422
+ output.write(`- ${skill.name || skill.packageName || skill.packagePath}`);
423
+ output.write(` status: ${skill.valid ? 'valid' : 'invalid'}`);
424
+ output.write(` path: ${skill.skillFile}`);
425
+ }
426
+ if (!result.valid) process.exitCode = EXIT_CODES.VALIDATION;
427
+ return;
428
+ }
429
+
400
430
  const skill = result.skills[0];
401
431
  output.write(`Skill: ${skill.packageName || skill.packagePath}`);
402
432
  output.write(`Status: ${skill.valid ? 'valid' : 'invalid'}`);
@@ -0,0 +1,116 @@
1
+ import { existsSync, readdirSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import {
4
+ buildCanonicalSkillRequirement,
5
+ normalizeDisplayPath,
6
+ readInstalledSkillExports,
7
+ readPackageMetadata,
8
+ } from './skill-model.js';
9
+
10
+ function isIgnoredEntry(name) {
11
+ return name === '.git' || name === 'node_modules' || name === '.agentpack';
12
+ }
13
+
14
+ function listSkillPackageDirs(repoRoot, { installed = false } = {}) {
15
+ const root = installed ? join(repoRoot, 'node_modules') : repoRoot;
16
+ if (!existsSync(root)) return [];
17
+
18
+ const stack = [root];
19
+ const results = [];
20
+
21
+ while (stack.length > 0) {
22
+ const current = stack.pop();
23
+ let entries = [];
24
+ try {
25
+ entries = readdirSync(current, { withFileTypes: true });
26
+ } catch {
27
+ continue;
28
+ }
29
+
30
+ let hasRootSkillFile = false;
31
+ let packageMetadata = null;
32
+
33
+ for (const entry of entries) {
34
+ if (entry.isDirectory()) {
35
+ if (!installed && isIgnoredEntry(entry.name)) continue;
36
+ stack.push(join(current, entry.name));
37
+ continue;
38
+ }
39
+
40
+ if (entry.name === 'SKILL.md') hasRootSkillFile = true;
41
+ if (entry.name !== 'package.json') continue;
42
+
43
+ try {
44
+ packageMetadata = readPackageMetadata(current);
45
+ } catch {
46
+ packageMetadata = null;
47
+ }
48
+ }
49
+
50
+ if (!packageMetadata?.packageName) continue;
51
+ if (packageMetadata.exportedSkills || hasRootSkillFile) {
52
+ results.push(current);
53
+ }
54
+ }
55
+
56
+ return results.sort();
57
+ }
58
+
59
+ function buildCatalogKey(packageName, exportedSkills, entry) {
60
+ if (!packageName) return null;
61
+ if (exportedSkills.length <= 1) return packageName;
62
+ return buildCanonicalSkillRequirement(packageName, entry.name);
63
+ }
64
+
65
+ export function readSkillPackage(repoRoot, packageDir, { origin = 'authored' } = {}) {
66
+ const packageMetadata = readPackageMetadata(packageDir);
67
+ if (!packageMetadata.packageName) return null;
68
+
69
+ const exportedSkills = readInstalledSkillExports(packageDir);
70
+ if (exportedSkills.length === 0) return null;
71
+
72
+ return {
73
+ origin,
74
+ packageDir,
75
+ packagePath: normalizeDisplayPath(repoRoot, packageDir),
76
+ packageName: packageMetadata.packageName,
77
+ packageVersion: packageMetadata.packageVersion,
78
+ packageMetadata,
79
+ exports: exportedSkills.map((entry) => ({
80
+ ...entry,
81
+ key: buildCatalogKey(packageMetadata.packageName, exportedSkills, entry),
82
+ packageName: packageMetadata.packageName,
83
+ packageVersion: packageMetadata.packageVersion,
84
+ packageDir,
85
+ packagePath: normalizeDisplayPath(repoRoot, packageDir),
86
+ skillDirPath: entry.skillDir,
87
+ skillFilePath: entry.skillFile,
88
+ skillPath: normalizeDisplayPath(repoRoot, entry.skillDir),
89
+ skillFile: normalizeDisplayPath(repoRoot, entry.skillFile),
90
+ })),
91
+ };
92
+ }
93
+
94
+ export function listAuthoredSkillPackages(repoRoot) {
95
+ return listSkillPackageDirs(repoRoot)
96
+ .map((packageDir) => {
97
+ try {
98
+ return readSkillPackage(repoRoot, packageDir);
99
+ } catch {
100
+ return null;
101
+ }
102
+ })
103
+ .filter(Boolean);
104
+ }
105
+
106
+ export function listInstalledSkillPackages(repoRoot) {
107
+ return listSkillPackageDirs(repoRoot, { installed: true })
108
+ .map((packageDir) => {
109
+ try {
110
+ return readSkillPackage(repoRoot, packageDir, { origin: 'installed' });
111
+ } catch {
112
+ return null;
113
+ }
114
+ })
115
+ .filter(Boolean);
116
+ }
@@ -149,6 +149,12 @@ export function parseSkillFrontmatterFile(skillFilePath) {
149
149
  status: typeof fields.metadata?.status === 'string' ? fields.metadata.status : null,
150
150
  replacement: typeof fields.metadata?.replacement === 'string' ? fields.metadata.replacement : null,
151
151
  message: typeof fields.metadata?.message === 'string' ? fields.metadata.message : null,
152
+ wraps: typeof fields.metadata?.wraps === 'string'
153
+ ? fields.metadata.wraps
154
+ : (typeof fields.wraps === 'string' ? fields.wraps : null),
155
+ overrides: Array.isArray(fields.metadata?.overrides)
156
+ ? fields.metadata.overrides
157
+ : (Array.isArray(fields.overrides) ? fields.overrides : []),
152
158
  };
153
159
  }
154
160
 
@@ -210,10 +216,13 @@ export function readInstalledSkillExports(packageDir) {
210
216
  declaredName,
211
217
  name: metadata.name,
212
218
  description: metadata.description,
219
+ sources: metadata.sources,
213
220
  requires: metadata.requires,
214
221
  status: metadata.status,
215
222
  replacement: metadata.replacement,
216
223
  message: metadata.message,
224
+ wraps: metadata.wraps,
225
+ overrides: metadata.overrides,
217
226
  skillDir: dirname(skillFile),
218
227
  skillFile,
219
228
  relativeSkillFile,
@@ -233,10 +242,13 @@ export function readInstalledSkillExports(packageDir) {
233
242
  declaredName: metadata.name,
234
243
  name: metadata.name,
235
244
  description: metadata.description,
245
+ sources: metadata.sources,
236
246
  requires: metadata.requires,
237
247
  status: metadata.status,
238
248
  replacement: metadata.replacement,
239
249
  message: metadata.message,
250
+ wraps: metadata.wraps,
251
+ overrides: metadata.overrides,
240
252
  skillDir: packageDir,
241
253
  skillFile: rootSkillFile,
242
254
  relativeSkillFile: 'SKILL.md',
@@ -0,0 +1,100 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { isAbsolute, resolve } from 'node:path';
3
+ import { listAuthoredSkillPackages, listInstalledSkillPackages } from './skill-catalog.js';
4
+ import { NotFoundError, ValidationError } from '../../utils/errors.js';
5
+
6
+ function dedupePackages(authoredPackages, installedPackages) {
7
+ const seen = new Set(authoredPackages.map((pkg) => pkg.packageName));
8
+ return [
9
+ ...authoredPackages,
10
+ ...installedPackages.filter((pkg) => !seen.has(pkg.packageName)),
11
+ ];
12
+ }
13
+
14
+ export function loadSkillTargetContext(repoRoot, {
15
+ includeAuthored = true,
16
+ includeInstalled = true,
17
+ } = {}) {
18
+ const authoredPackages = includeAuthored ? listAuthoredSkillPackages(repoRoot) : [];
19
+ const installedPackages = includeInstalled ? listInstalledSkillPackages(repoRoot) : [];
20
+
21
+ return {
22
+ authoredPackages,
23
+ installedPackages,
24
+ packages: dedupePackages(authoredPackages, installedPackages),
25
+ };
26
+ }
27
+
28
+ function buildPackageResolution(pkg, source) {
29
+ return {
30
+ kind: 'package',
31
+ source,
32
+ package: pkg,
33
+ exports: pkg.exports,
34
+ };
35
+ }
36
+
37
+ function buildExportResolution(pkg, skillExport, source) {
38
+ return {
39
+ kind: 'export',
40
+ source,
41
+ package: pkg,
42
+ export: skillExport,
43
+ exports: [skillExport],
44
+ };
45
+ }
46
+
47
+ export function resolveSkillTarget(repoRoot, target, options = {}) {
48
+ const context = loadSkillTargetContext(repoRoot, options);
49
+ const { packages } = context;
50
+
51
+ if (typeof target !== 'string' || target.length === 0) {
52
+ throw new NotFoundError('skill not found', {
53
+ code: 'skill_not_found',
54
+ suggestion: `Target: ${target}`,
55
+ });
56
+ }
57
+
58
+ const absoluteTarget = isAbsolute(target) ? target : resolve(repoRoot, target);
59
+
60
+ if (existsSync(absoluteTarget)) {
61
+ for (const pkg of packages) {
62
+ if (pkg.packageDir === absoluteTarget) {
63
+ return buildPackageResolution(pkg, 'package_path');
64
+ }
65
+
66
+ for (const skillExport of pkg.exports) {
67
+ if (skillExport.skillDirPath === absoluteTarget) {
68
+ return buildExportResolution(pkg, skillExport, 'skill_path');
69
+ }
70
+ if (skillExport.skillFilePath === absoluteTarget) {
71
+ return buildExportResolution(pkg, skillExport, 'skill_file');
72
+ }
73
+ }
74
+ }
75
+ }
76
+
77
+ const pkg = packages.find((entry) => entry.packageName === target);
78
+ if (pkg) {
79
+ return buildPackageResolution(pkg, 'package_name');
80
+ }
81
+
82
+ throw new NotFoundError('skill not found', {
83
+ code: 'skill_not_found',
84
+ suggestion: `Target: ${target}`,
85
+ });
86
+ }
87
+
88
+ export function resolveSingleSkillTarget(repoRoot, target, options = {}) {
89
+ const resolved = resolveSkillTarget(repoRoot, target, options);
90
+
91
+ if (resolved.kind === 'export') return resolved;
92
+ if (resolved.exports.length === 1) {
93
+ return buildExportResolution(resolved.package, resolved.exports[0], resolved.source);
94
+ }
95
+
96
+ throw new ValidationError('ambiguous skill target', {
97
+ code: 'ambiguous_skill_target',
98
+ suggestion: resolved.exports.map((entry) => entry.skillPath).join(', '),
99
+ });
100
+ }
package/src/lib/skills.js CHANGED
@@ -26,6 +26,8 @@ import {
26
26
  readInstalledSkillExports,
27
27
  readPackageMetadata,
28
28
  } from '../domain/skills/skill-model.js';
29
+ import { listAuthoredSkillPackages } from '../domain/skills/skill-catalog.js';
30
+ import { resolveSingleSkillTarget, resolveSkillTarget } from '../domain/skills/skill-target-resolution.js';
29
31
  import { inspectMaterializedSkills } from '../infrastructure/runtime/inspect-materialized-skills.js';
30
32
  import {
31
33
  buildStateRecordForPackageDir,
@@ -96,24 +98,15 @@ function resolveDevLinkedSkills(repoRoot, rootSkillDir) {
96
98
  }
97
99
 
98
100
  function resolveLocalPackagedSkillDir(repoRoot, target) {
99
- const skillFile = resolveSkillFileTarget(repoRoot, target);
100
- if (!skillFile) {
101
- throw new AgentpackError(`SKILL.md not found for target: ${target}`, {
102
- code: 'skill_not_found',
103
- exitCode: EXIT_CODES.GENERAL,
104
- });
105
- }
106
-
107
- const skillDir = dirname(skillFile);
108
- const packageJsonPath = join(skillDir, 'package.json');
109
- if (!existsSync(packageJsonPath)) {
110
- throw new AgentpackError(`package.json not found: ${packageJsonPath}`, {
111
- code: 'package_json_not_found',
112
- exitCode: EXIT_CODES.GENERAL,
113
- });
114
- }
115
-
116
- return { skillDir, skillFile, packageJsonPath };
101
+ const resolved = resolveSingleSkillTarget(repoRoot, target, { includeInstalled: false });
102
+ return {
103
+ skillDir: resolved.export.skillDirPath,
104
+ skillFile: resolved.export.skillFilePath,
105
+ packageDir: resolved.package.packageDir,
106
+ packageJsonPath: join(resolved.package.packageDir, 'package.json'),
107
+ packageName: resolved.package.packageName,
108
+ skillName: resolved.export.name,
109
+ };
117
110
  }
118
111
 
119
112
  function resolveSkillFileTarget(repoRoot, target) {
@@ -275,11 +268,11 @@ function reconcileDevSession(repoRoot) {
275
268
  }
276
269
 
277
270
  export function syncSkillDependencies(skillDir) {
278
- const skillFile = join(skillDir, 'SKILL.md');
279
- const metadata = parseSkillFrontmatterFile(skillFile);
271
+ const required = [...new Set(
272
+ readInstalledSkillExports(skillDir).flatMap((entry) => entry.requires || [])
273
+ )].sort((a, b) => a.localeCompare(b));
280
274
  const { packageJsonPath, packageJson } = readPackageJson(skillDir);
281
275
  const nextDependencies = { ...(packageJson.dependencies || {}) };
282
- const required = [...new Set(metadata.requires || [])].sort((a, b) => a.localeCompare(b));
283
276
  const requiredSet = new Set(required);
284
277
  const added = [];
285
278
  const removed = [];
@@ -320,14 +313,14 @@ export function devSkill(target, {
320
313
  } = {}) {
321
314
  const repoRoot = findRepoRoot(cwd);
322
315
  try {
323
- const { skillDir } = resolveLocalPackagedSkillDir(repoRoot, target);
316
+ const { skillDir, packageDir } = resolveLocalPackagedSkillDir(repoRoot, target);
324
317
  const { linkedSkills, unresolved } = resolveDevLinkedSkills(repoRoot, skillDir);
325
318
  const rootSkill = linkedSkills.find((entry) => entry.skillDir === skillDir);
326
319
  const synced = sync
327
- ? syncSkillDependencies(skillDir)
320
+ ? syncSkillDependencies(packageDir)
328
321
  : {
329
- skillDir,
330
- packageJsonPath: join(skillDir, 'package.json'),
322
+ skillDir: packageDir,
323
+ packageJsonPath: join(packageDir, 'package.json'),
331
324
  added: [],
332
325
  removed: [],
333
326
  unchanged: true,
@@ -372,7 +365,20 @@ export function startSkillDev(target, {
372
365
  onRebuild = () => {},
373
366
  } = {}) {
374
367
  const outerRepoRoot = findRepoRoot(cwd);
375
- const { skillDir } = resolveLocalPackagedSkillDir(outerRepoRoot, target);
368
+ let skillDir;
369
+ try {
370
+ ({ skillDir } = resolveLocalPackagedSkillDir(outerRepoRoot, target));
371
+ } catch (error) {
372
+ if (error instanceof AgentpackError && error.exitCode === EXIT_CODES.GENERAL) {
373
+ throw error;
374
+ }
375
+
376
+ throw new AgentpackError(error.message, {
377
+ code: error.code || 'skill_dev_failed',
378
+ exitCode: EXIT_CODES.GENERAL,
379
+ suggestion: error.suggestion,
380
+ });
381
+ }
376
382
  const repoRoot = findRepoRoot(skillDir);
377
383
  reconcileDevSession(repoRoot);
378
384
  let closed = false;
@@ -815,37 +821,40 @@ async function fetchRegistryLatestVersion(packageName, {
815
821
 
816
822
  export function inspectSkill(target, { cwd = process.cwd() } = {}) {
817
823
  const repoRoot = findRepoRoot(cwd);
824
+ const resolved = resolveSkillTarget(repoRoot, target);
818
825
 
819
- let skillFile = resolveSkillFileTarget(repoRoot, target);
820
-
821
- if (!skillFile && target.startsWith('@')) {
822
- const packageDir = findPackageDirByName(repoRoot, target);
823
- if (packageDir) {
824
- skillFile = join(packageDir, 'SKILL.md');
825
- }
826
- }
827
-
828
- if (!skillFile) {
829
- throw new NotFoundError('skill not found', {
830
- code: 'skill_not_found',
831
- suggestion: `Target: ${target}`,
832
- });
826
+ if (resolved.kind === 'package' && resolved.exports.length > 1) {
827
+ return {
828
+ kind: 'package',
829
+ packageName: resolved.package.packageName,
830
+ packageVersion: resolved.package.packageVersion,
831
+ packagePath: resolved.package.packagePath,
832
+ exports: resolved.exports.map((entry) => ({
833
+ name: entry.name,
834
+ declaredName: entry.declaredName,
835
+ skillFile: entry.skillFile,
836
+ skillPath: entry.skillPath,
837
+ requires: entry.requires,
838
+ })),
839
+ };
833
840
  }
834
841
 
835
- const metadata = parseSkillFrontmatterFile(skillFile);
836
- const packageMetadata = readPackageMetadata(dirname(skillFile));
842
+ const entry = resolved.kind === 'export' ? resolved.export : resolved.exports[0];
837
843
 
838
844
  return {
839
- name: metadata.name,
840
- description: metadata.description,
841
- packageName: packageMetadata.packageName,
842
- packageVersion: packageMetadata.packageVersion,
843
- skillFile: normalizeDisplayPath(repoRoot, skillFile),
844
- sources: metadata.sources,
845
- requires: metadata.requires,
846
- status: metadata.status,
847
- replacement: metadata.replacement,
848
- message: metadata.message,
845
+ kind: 'export',
846
+ name: entry.name,
847
+ description: entry.description,
848
+ packageName: resolved.package.packageName,
849
+ packageVersion: resolved.package.packageVersion,
850
+ skillFile: entry.skillFile,
851
+ sources: entry.sources,
852
+ requires: entry.requires,
853
+ status: entry.status,
854
+ replacement: entry.replacement,
855
+ message: entry.message,
856
+ wraps: entry.wraps,
857
+ overrides: entry.overrides,
849
858
  };
850
859
  }
851
860
 
@@ -960,55 +969,14 @@ export function inspectSkillDependencies(target, {
960
969
  };
961
970
  }
962
971
 
963
- function resolvePackagedSkillTarget(repoRoot, target) {
964
- let skillFile = null;
965
-
966
- if (target) {
967
- skillFile = resolveSkillFileTarget(repoRoot, target);
968
-
969
- if (!skillFile && target.startsWith('@')) {
970
- const packageDir = findPackageDirByName(repoRoot, target);
971
- if (packageDir) {
972
- skillFile = join(packageDir, 'SKILL.md');
973
- }
974
- }
975
-
976
- if (!skillFile) {
977
- throw new NotFoundError('skill not found', {
978
- code: 'skill_not_found',
979
- suggestion: `Target: ${target}`,
980
- });
981
- }
982
-
983
- const packageDir = dirname(skillFile);
984
- const packageMetadata = readPackageMetadata(packageDir);
985
- if (!packageMetadata.packageName) {
986
- throw new ValidationError('validate target is not a packaged skill', {
987
- code: 'invalid_validate_target',
988
- suggestion: `Target: ${target}`,
989
- });
990
- }
991
-
992
- return [packageDir];
993
- }
994
-
995
- return listPackagedSkillDirs(repoRoot);
972
+ function isPublishedSkillFile(files, relativeSkillFile) {
973
+ if (!files) return true;
974
+ return files.some((entry) => entry === relativeSkillFile || relativeSkillFile.startsWith(`${entry}/`));
996
975
  }
997
976
 
998
- function validatePackagedSkillDir(repoRoot, packageDir) {
999
- const skillFile = join(packageDir, 'SKILL.md');
1000
- const packageMetadata = readPackageMetadata(packageDir);
977
+ function validatePackagedSkillExport(repoRoot, pkg, skillExport) {
978
+ const packageMetadata = readPackageMetadata(pkg.packageDir);
1001
979
  const issues = [];
1002
- let skillMetadata = null;
1003
-
1004
- try {
1005
- skillMetadata = parseSkillFrontmatterFile(skillFile);
1006
- } catch (error) {
1007
- issues.push({
1008
- code: error.code || 'invalid_skill_file',
1009
- message: error.message,
1010
- });
1011
- }
1012
980
 
1013
981
  if (!packageMetadata.packageName) {
1014
982
  issues.push({
@@ -1024,10 +992,10 @@ function validatePackagedSkillDir(repoRoot, packageDir) {
1024
992
  });
1025
993
  }
1026
994
 
1027
- if (packageMetadata.files && !packageMetadata.files.includes('SKILL.md')) {
995
+ if (!isPublishedSkillFile(packageMetadata.files, skillExport.relativeSkillFile)) {
1028
996
  issues.push({
1029
997
  code: 'skill_not_published',
1030
- message: 'package.json files does not include SKILL.md',
998
+ message: `package.json files does not include ${skillExport.relativeSkillFile}`,
1031
999
  });
1032
1000
  }
1033
1001
 
@@ -1047,51 +1015,50 @@ function validatePackagedSkillDir(repoRoot, packageDir) {
1047
1015
  }
1048
1016
  }
1049
1017
 
1050
- if (skillMetadata) {
1051
- if (skillMetadata.status && !['deprecated', 'retired'].includes(skillMetadata.status)) {
1018
+ if (skillExport.status && !['deprecated', 'retired'].includes(skillExport.status)) {
1019
+ issues.push({
1020
+ code: 'invalid_skill_status',
1021
+ message: 'metadata.status must be "deprecated" or "retired"',
1022
+ });
1023
+ }
1024
+
1025
+ if (skillExport.replacement && !skillExport.replacement.startsWith('@')) {
1026
+ issues.push({
1027
+ code: 'invalid_replacement',
1028
+ message: 'metadata.replacement must be a package name',
1029
+ });
1030
+ }
1031
+
1032
+ for (const sourcePath of skillExport.sources || []) {
1033
+ if (!existsSync(join(repoRoot, sourcePath))) {
1052
1034
  issues.push({
1053
- code: 'invalid_skill_status',
1054
- message: 'metadata.status must be "deprecated" or "retired"',
1035
+ code: 'missing_source',
1036
+ message: 'declared source file does not exist',
1037
+ path: sourcePath,
1055
1038
  });
1056
1039
  }
1040
+ }
1057
1041
 
1058
- if (skillMetadata.replacement && !skillMetadata.replacement.startsWith('@')) {
1042
+ for (const requirement of skillExport.requires || []) {
1043
+ if (!packageMetadata.dependencies[requirement]) {
1059
1044
  issues.push({
1060
- code: 'invalid_replacement',
1061
- message: 'metadata.replacement must be a package name',
1045
+ code: 'missing_dependency_declaration',
1046
+ message: 'required skill is not declared in package dependencies',
1047
+ dependency: requirement,
1062
1048
  });
1063
1049
  }
1064
-
1065
- for (const sourcePath of skillMetadata.sources) {
1066
- if (!existsSync(join(repoRoot, sourcePath))) {
1067
- issues.push({
1068
- code: 'missing_source',
1069
- message: 'declared source file does not exist',
1070
- path: sourcePath,
1071
- });
1072
- }
1073
- }
1074
-
1075
- for (const requirement of skillMetadata.requires) {
1076
- if (!packageMetadata.dependencies[requirement]) {
1077
- issues.push({
1078
- code: 'missing_dependency_declaration',
1079
- message: 'required skill is not declared in package dependencies',
1080
- dependency: requirement,
1081
- });
1082
- }
1083
- }
1084
1050
  }
1085
1051
 
1086
1052
  return {
1087
1053
  valid: issues.length === 0,
1088
- name: skillMetadata?.name || null,
1054
+ key: skillExport.key,
1055
+ name: skillExport.name || null,
1089
1056
  packageName: packageMetadata.packageName,
1090
1057
  packageVersion: packageMetadata.packageVersion,
1091
- skillFile: normalizeDisplayPath(repoRoot, skillFile),
1092
- packagePath: normalizeDisplayPath(repoRoot, packageDir),
1093
- status: skillMetadata?.status || null,
1094
- replacement: skillMetadata?.replacement || null,
1058
+ skillFile: skillExport.skillFile,
1059
+ packagePath: pkg.packagePath,
1060
+ status: skillExport.status || null,
1061
+ replacement: skillExport.replacement || null,
1095
1062
  nextSteps: buildValidateNextSteps(packageMetadata, issues.length === 0),
1096
1063
  issues,
1097
1064
  };
@@ -1099,15 +1066,25 @@ function validatePackagedSkillDir(repoRoot, packageDir) {
1099
1066
 
1100
1067
  export function validateSkills(target, { cwd = process.cwd() } = {}) {
1101
1068
  const repoRoot = findRepoRoot(cwd);
1102
- const packageDirs = resolvePackagedSkillTarget(repoRoot, target);
1069
+ let targets = [];
1103
1070
 
1104
- for (const packageDir of packageDirs) {
1071
+ if (target) {
1072
+ const resolved = resolveSkillTarget(repoRoot, target, { includeInstalled: false });
1073
+ targets = resolved.kind === 'export'
1074
+ ? [{ package: resolved.package, export: resolved.export }]
1075
+ : resolved.exports.map((entry) => ({ package: resolved.package, export: entry }));
1076
+ } else {
1077
+ targets = listAuthoredSkillPackages(repoRoot)
1078
+ .flatMap((pkg) => pkg.exports.map((entry) => ({ package: pkg, export: entry })));
1079
+ }
1080
+
1081
+ for (const packageDir of [...new Set(targets.map((entry) => entry.package.packageDir))]) {
1105
1082
  syncSkillDependencies(packageDir);
1106
1083
  }
1107
1084
 
1108
- const skills = packageDirs
1109
- .map((packageDir) => validatePackagedSkillDir(repoRoot, packageDir))
1110
- .sort((a, b) => (a.packageName || a.packagePath).localeCompare(b.packageName || b.packagePath));
1085
+ const skills = targets
1086
+ .map((entry) => validatePackagedSkillExport(repoRoot, entry.package, entry.export))
1087
+ .sort((a, b) => (a.key || a.packageName || a.packagePath).localeCompare(b.key || b.packageName || b.packagePath));
1111
1088
 
1112
1089
  const validCount = skills.filter((skill) => skill.valid).length;
1113
1090
  const invalidCount = skills.length - validCount;
@@ -1115,18 +1092,32 @@ export function validateSkills(target, { cwd = process.cwd() } = {}) {
1115
1092
  if (validCount > 0) {
1116
1093
  const buildState = readBuildState(repoRoot);
1117
1094
 
1118
- for (const packageDir of packageDirs) {
1119
- const packageMetadata = readPackageMetadata(packageDir);
1120
- const result = skills.find((skill) => skill.packageName === packageMetadata.packageName);
1095
+ if (!target) {
1096
+ const authoredKeys = new Set(targets.map((entry) => entry.export.key));
1097
+ for (const key of Object.keys(buildState.skills || {})) {
1098
+ if (authoredKeys.has(key)) continue;
1099
+ delete buildState.skills[key];
1100
+ }
1101
+ }
1102
+
1103
+ for (const targetEntry of targets) {
1104
+ const result = skills.find((skill) => skill.key === targetEntry.export.key);
1121
1105
  if (!result?.valid) continue;
1122
1106
 
1123
- const { packageName, record } = buildStateRecordForPackageDir(repoRoot, packageDir, {
1124
- parseSkillFrontmatterFile,
1125
- readPackageMetadata,
1126
- normalizeDisplayPath,
1127
- });
1128
- if (!packageName) continue;
1129
- buildState.skills[packageName] = record;
1107
+ const sources = {};
1108
+ for (const sourcePath of targetEntry.export.sources || []) {
1109
+ sources[sourcePath] = {
1110
+ hash: hashFile(join(repoRoot, sourcePath)),
1111
+ };
1112
+ }
1113
+
1114
+ buildState.skills[targetEntry.export.key] = {
1115
+ package_version: targetEntry.package.packageVersion,
1116
+ skill_path: targetEntry.export.skillPath,
1117
+ skill_file: targetEntry.export.skillFile,
1118
+ sources,
1119
+ requires: targetEntry.export.requires,
1120
+ };
1130
1121
  }
1131
1122
 
1132
1123
  writeBuildState(repoRoot, buildState);
@@ -1188,28 +1179,22 @@ function listPackagedSkillDirs(repoRoot) {
1188
1179
  }
1189
1180
 
1190
1181
  function listAuthoredPackagedSkills(repoRoot) {
1191
- return listPackagedSkillDirs(repoRoot)
1192
- .map((packageDir) => {
1193
- const skillFile = join(packageDir, 'SKILL.md');
1194
- const metadata = parseSkillFrontmatterFile(skillFile);
1195
- const packageMetadata = readPackageMetadata(packageDir);
1196
-
1197
- if (!packageMetadata.packageName || !packageMetadata.packageVersion) {
1198
- return null;
1199
- }
1200
-
1201
- return {
1202
- name: metadata.name,
1203
- description: metadata.description,
1204
- packageName: packageMetadata.packageName,
1205
- packageVersion: packageMetadata.packageVersion,
1206
- skillPath: normalizeDisplayPath(repoRoot, packageDir),
1207
- skillFile: normalizeDisplayPath(repoRoot, skillFile),
1208
- sources: metadata.sources,
1209
- requires: metadata.requires,
1210
- };
1211
- })
1212
- .filter(Boolean)
1182
+ return listAuthoredSkillPackages(repoRoot)
1183
+ .flatMap((pkg) => pkg.exports.map((entry) => ({
1184
+ key: entry.key,
1185
+ name: entry.name,
1186
+ description: entry.description,
1187
+ packageName: entry.packageName,
1188
+ packageVersion: entry.packageVersion,
1189
+ skillPath: entry.skillPath,
1190
+ skillFile: entry.skillFile,
1191
+ sources: entry.sources,
1192
+ requires: entry.requires,
1193
+ wraps: entry.wraps,
1194
+ overrides: entry.overrides,
1195
+ declaredName: entry.declaredName,
1196
+ packagePath: pkg.packagePath,
1197
+ })))
1213
1198
  .sort((a, b) => a.packageName.localeCompare(b.packageName));
1214
1199
  }
1215
1200
 
@@ -1218,7 +1203,7 @@ export function generateSkillsCatalog({ cwd = process.cwd() } = {}) {
1218
1203
  const skills = {};
1219
1204
 
1220
1205
  for (const skill of listAuthoredPackagedSkills(repoRoot)) {
1221
- skills[skill.packageName] = {
1206
+ skills[skill.key] = {
1222
1207
  name: skill.name,
1223
1208
  description: skill.description,
1224
1209
  path: skill.skillPath,
@@ -1227,6 +1212,8 @@ export function generateSkillsCatalog({ cwd = process.cwd() } = {}) {
1227
1212
  package_version: skill.packageVersion,
1228
1213
  sources: skill.sources,
1229
1214
  requires: skill.requires,
1215
+ ...(skill.wraps ? { wraps: skill.wraps } : {}),
1216
+ ...(skill.overrides?.length ? { overrides: skill.overrides } : {}),
1230
1217
  };
1231
1218
  }
1232
1219
 
@@ -1248,12 +1235,14 @@ export function generateBuildState({ cwd = process.cwd() } = {}) {
1248
1235
  };
1249
1236
  }
1250
1237
 
1251
- skills[skill.packageName] = {
1238
+ skills[skill.key] = {
1252
1239
  package_version: skill.packageVersion,
1253
1240
  skill_path: skill.skillPath,
1254
1241
  skill_file: skill.skillFile,
1255
1242
  sources,
1256
1243
  requires: skill.requires,
1244
+ ...(skill.wraps ? { wraps: skill.wraps } : {}),
1245
+ ...(skill.overrides?.length ? { overrides: skill.overrides } : {}),
1257
1246
  };
1258
1247
  }
1259
1248