@alavida/agentpack 0.1.5 → 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/src/lib/skills.js CHANGED
@@ -19,11 +19,16 @@ import {
19
19
  removeSkillLinksByNames,
20
20
  } from '../infrastructure/runtime/materialize-skills.js';
21
21
  import {
22
+ buildCanonicalSkillRequirement,
22
23
  normalizeDisplayPath,
23
24
  normalizeRepoPath,
24
25
  parseSkillFrontmatterFile,
26
+ readInstalledSkillExports,
25
27
  readPackageMetadata,
26
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';
31
+ import { inspectMaterializedSkills } from '../infrastructure/runtime/inspect-materialized-skills.js';
27
32
  import {
28
33
  buildStateRecordForPackageDir,
29
34
  compareRecordedSources,
@@ -31,6 +36,10 @@ import {
31
36
  readBuildState,
32
37
  writeBuildState,
33
38
  } from '../domain/skills/skill-provenance.js';
39
+ import { readUserConfig } from '../infrastructure/fs/user-config-repository.js';
40
+ import { readUserCredentials } from '../infrastructure/fs/user-credentials-repository.js';
41
+ import { getUserNpmrcPath, readUserNpmrc } from '../infrastructure/fs/user-npmrc-repository.js';
42
+ import { resolveRegistryConfig } from '../domain/auth/registry-resolution.js';
34
43
  import { startSkillDevWorkbench } from '../application/skills/start-skill-dev-workbench.js';
35
44
  import { AgentpackError, EXIT_CODES, NetworkError, NotFoundError, ValidationError } from '../utils/errors.js';
36
45
 
@@ -89,24 +98,15 @@ function resolveDevLinkedSkills(repoRoot, rootSkillDir) {
89
98
  }
90
99
 
91
100
  function resolveLocalPackagedSkillDir(repoRoot, target) {
92
- const skillFile = resolveSkillFileTarget(repoRoot, target);
93
- if (!skillFile) {
94
- throw new AgentpackError(`SKILL.md not found for target: ${target}`, {
95
- code: 'skill_not_found',
96
- exitCode: EXIT_CODES.GENERAL,
97
- });
98
- }
99
-
100
- const skillDir = dirname(skillFile);
101
- const packageJsonPath = join(skillDir, 'package.json');
102
- if (!existsSync(packageJsonPath)) {
103
- throw new AgentpackError(`package.json not found: ${packageJsonPath}`, {
104
- code: 'package_json_not_found',
105
- exitCode: EXIT_CODES.GENERAL,
106
- });
107
- }
108
-
109
- 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
+ };
110
110
  }
111
111
 
112
112
  function resolveSkillFileTarget(repoRoot, target) {
@@ -268,11 +268,11 @@ function reconcileDevSession(repoRoot) {
268
268
  }
269
269
 
270
270
  export function syncSkillDependencies(skillDir) {
271
- const skillFile = join(skillDir, 'SKILL.md');
272
- const metadata = parseSkillFrontmatterFile(skillFile);
271
+ const required = [...new Set(
272
+ readInstalledSkillExports(skillDir).flatMap((entry) => entry.requires || [])
273
+ )].sort((a, b) => a.localeCompare(b));
273
274
  const { packageJsonPath, packageJson } = readPackageJson(skillDir);
274
275
  const nextDependencies = { ...(packageJson.dependencies || {}) };
275
- const required = [...new Set(metadata.requires || [])].sort((a, b) => a.localeCompare(b));
276
276
  const requiredSet = new Set(required);
277
277
  const added = [];
278
278
  const removed = [];
@@ -313,14 +313,14 @@ export function devSkill(target, {
313
313
  } = {}) {
314
314
  const repoRoot = findRepoRoot(cwd);
315
315
  try {
316
- const { skillDir } = resolveLocalPackagedSkillDir(repoRoot, target);
316
+ const { skillDir, packageDir } = resolveLocalPackagedSkillDir(repoRoot, target);
317
317
  const { linkedSkills, unresolved } = resolveDevLinkedSkills(repoRoot, skillDir);
318
318
  const rootSkill = linkedSkills.find((entry) => entry.skillDir === skillDir);
319
319
  const synced = sync
320
- ? syncSkillDependencies(skillDir)
320
+ ? syncSkillDependencies(packageDir)
321
321
  : {
322
- skillDir,
323
- packageJsonPath: join(skillDir, 'package.json'),
322
+ skillDir: packageDir,
323
+ packageJsonPath: join(packageDir, 'package.json'),
324
324
  added: [],
325
325
  removed: [],
326
326
  unchanged: true,
@@ -365,7 +365,20 @@ export function startSkillDev(target, {
365
365
  onRebuild = () => {},
366
366
  } = {}) {
367
367
  const outerRepoRoot = findRepoRoot(cwd);
368
- 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
+ }
369
382
  const repoRoot = findRepoRoot(skillDir);
370
383
  reconcileDevSession(repoRoot);
371
384
  let closed = false;
@@ -638,24 +651,75 @@ function readRepoNpmRegistryConfig(repoRoot, scope = null) {
638
651
  : {};
639
652
  const scopes = scope ? [scope] : MANAGED_PACKAGE_SCOPES;
640
653
  const matchedScope = scopes.find((candidate) => config[`${candidate}:registry`]) || scope || scopes[0] || null;
654
+ const registry = matchedScope ? (config[`${matchedScope}:registry`] || null) : null;
655
+ const authKey = registry ? `//${new URL(registry).host}/:_authToken` : null;
641
656
 
642
657
  return {
643
658
  npmrcPath: existsSync(npmrcPath) ? npmrcPath : null,
644
659
  scope: matchedScope,
645
- registry: matchedScope ? (config[`${matchedScope}:registry`] || null) : null,
646
- authToken: config['//npm.pkg.github.com/:_authToken'] || null,
660
+ registry,
661
+ authToken: authKey ? (config[authKey] || null) : null,
647
662
  alwaysAuth: String(config['always-auth'] || '').toLowerCase() === 'true',
648
663
  };
649
664
  }
650
665
 
666
+ function readEffectiveRegistryConfig(repoRoot, scope = null, env = process.env) {
667
+ const userConfig = readUserConfig({ env });
668
+ const userNpmrc = readUserNpmrc({ env });
669
+ const repoConfig = readRepoNpmRegistryConfig(repoRoot, scope);
670
+ const userScope = MANAGED_PACKAGE_SCOPES.find((candidate) => userNpmrc[`${candidate}:registry`]) || null;
671
+ const resolvedScope = scope
672
+ || (repoConfig.registry ? repoConfig.scope : null)
673
+ || userScope
674
+ || userConfig.scope
675
+ || repoConfig.scope
676
+ || MANAGED_PACKAGE_SCOPES[0]
677
+ || null;
678
+ const resolved = resolveRegistryConfig({
679
+ scope: resolvedScope,
680
+ defaults: {
681
+ registry: userConfig.registry,
682
+ verificationPackage: userConfig.verificationPackage,
683
+ },
684
+ userNpmrc,
685
+ repoNpmrc: repoConfig.registry ? {
686
+ [`${repoConfig.scope}:registry`]: repoConfig.registry,
687
+ ...(repoConfig.authToken ? { [`//${new URL(repoConfig.registry).host}/:_authToken`]: repoConfig.authToken } : {}),
688
+ } : {},
689
+ });
690
+
691
+ let npmrcPath = null;
692
+ if (resolved.source === 'repo') {
693
+ npmrcPath = repoConfig.npmrcPath;
694
+ } else if (resolved.source === 'user') {
695
+ npmrcPath = getUserNpmrcPath({ env });
696
+ }
697
+
698
+ const alwaysAuth = resolved.source === 'repo'
699
+ ? repoConfig.alwaysAuth
700
+ : String(userNpmrc['always-auth'] || '').toLowerCase() === 'true';
701
+
702
+ return {
703
+ scope: resolved.scope,
704
+ npmrcPath,
705
+ registry: resolved.registry,
706
+ authToken: resolved.authToken,
707
+ alwaysAuth,
708
+ source: resolved.source,
709
+ configured: resolved.source !== 'default' && Boolean(resolved.registry),
710
+ };
711
+ }
712
+
651
713
  export function inspectRegistryConfig({
652
714
  cwd = process.cwd(),
653
715
  scope = null,
716
+ env = process.env,
654
717
  } = {}) {
655
718
  const repoRoot = findRepoRoot(cwd);
656
- const { npmrcPath, scope: resolvedScope, registry, authToken, alwaysAuth } = readRepoNpmRegistryConfig(
719
+ const { npmrcPath, scope: resolvedScope, registry, authToken, alwaysAuth, source, configured } = readEffectiveRegistryConfig(
657
720
  repoRoot,
658
- scope
721
+ scope,
722
+ env
659
723
  );
660
724
 
661
725
  let auth = {
@@ -688,10 +752,11 @@ export function inspectRegistryConfig({
688
752
  scope: resolvedScope,
689
753
  repoRoot,
690
754
  npmrcPath: npmrcPath ? normalizeDisplayPath(repoRoot, npmrcPath) : null,
691
- configured: Boolean(registry),
692
- registry,
755
+ configured,
756
+ registry: configured ? registry : null,
693
757
  auth,
694
758
  alwaysAuth,
759
+ source,
695
760
  };
696
761
  }
697
762
 
@@ -704,6 +769,22 @@ function resolveRegistryAuthToken(rawValue) {
704
769
  return rawValue;
705
770
  }
706
771
 
772
+ function buildNpmRegistryEnv(repoRoot, env = process.env) {
773
+ const effective = readEffectiveRegistryConfig(repoRoot, null, env);
774
+ const authToken = effective.authToken || null;
775
+ const envMatch = authToken?.match(/^\$\{([^}]+)\}$/) || null;
776
+ if (!envMatch) return env;
777
+ if (env[envMatch[1]]) return env;
778
+
779
+ const credentials = readUserCredentials({ env });
780
+ if (!credentials?.token) return env;
781
+
782
+ return {
783
+ ...env,
784
+ [envMatch[1]]: credentials.token,
785
+ };
786
+ }
787
+
707
788
  async function fetchRegistryLatestVersion(packageName, {
708
789
  registry,
709
790
  authToken,
@@ -740,37 +821,40 @@ async function fetchRegistryLatestVersion(packageName, {
740
821
 
741
822
  export function inspectSkill(target, { cwd = process.cwd() } = {}) {
742
823
  const repoRoot = findRepoRoot(cwd);
824
+ const resolved = resolveSkillTarget(repoRoot, target);
743
825
 
744
- let skillFile = resolveSkillFileTarget(repoRoot, target);
745
-
746
- if (!skillFile && target.startsWith('@')) {
747
- const packageDir = findPackageDirByName(repoRoot, target);
748
- if (packageDir) {
749
- skillFile = join(packageDir, 'SKILL.md');
750
- }
751
- }
752
-
753
- if (!skillFile) {
754
- throw new NotFoundError('skill not found', {
755
- code: 'skill_not_found',
756
- suggestion: `Target: ${target}`,
757
- });
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
+ };
758
840
  }
759
841
 
760
- const metadata = parseSkillFrontmatterFile(skillFile);
761
- const packageMetadata = readPackageMetadata(dirname(skillFile));
842
+ const entry = resolved.kind === 'export' ? resolved.export : resolved.exports[0];
762
843
 
763
844
  return {
764
- name: metadata.name,
765
- description: metadata.description,
766
- packageName: packageMetadata.packageName,
767
- packageVersion: packageMetadata.packageVersion,
768
- skillFile: normalizeDisplayPath(repoRoot, skillFile),
769
- sources: metadata.sources,
770
- requires: metadata.requires,
771
- status: metadata.status,
772
- replacement: metadata.replacement,
773
- 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,
774
858
  };
775
859
  }
776
860
 
@@ -885,55 +969,14 @@ export function inspectSkillDependencies(target, {
885
969
  };
886
970
  }
887
971
 
888
- function resolvePackagedSkillTarget(repoRoot, target) {
889
- let skillFile = null;
890
-
891
- if (target) {
892
- skillFile = resolveSkillFileTarget(repoRoot, target);
893
-
894
- if (!skillFile && target.startsWith('@')) {
895
- const packageDir = findPackageDirByName(repoRoot, target);
896
- if (packageDir) {
897
- skillFile = join(packageDir, 'SKILL.md');
898
- }
899
- }
900
-
901
- if (!skillFile) {
902
- throw new NotFoundError('skill not found', {
903
- code: 'skill_not_found',
904
- suggestion: `Target: ${target}`,
905
- });
906
- }
907
-
908
- const packageDir = dirname(skillFile);
909
- const packageMetadata = readPackageMetadata(packageDir);
910
- if (!packageMetadata.packageName) {
911
- throw new ValidationError('validate target is not a packaged skill', {
912
- code: 'invalid_validate_target',
913
- suggestion: `Target: ${target}`,
914
- });
915
- }
916
-
917
- return [packageDir];
918
- }
919
-
920
- return listPackagedSkillDirs(repoRoot);
972
+ function isPublishedSkillFile(files, relativeSkillFile) {
973
+ if (!files) return true;
974
+ return files.some((entry) => entry === relativeSkillFile || relativeSkillFile.startsWith(`${entry}/`));
921
975
  }
922
976
 
923
- function validatePackagedSkillDir(repoRoot, packageDir) {
924
- const skillFile = join(packageDir, 'SKILL.md');
925
- const packageMetadata = readPackageMetadata(packageDir);
977
+ function validatePackagedSkillExport(repoRoot, pkg, skillExport) {
978
+ const packageMetadata = readPackageMetadata(pkg.packageDir);
926
979
  const issues = [];
927
- let skillMetadata = null;
928
-
929
- try {
930
- skillMetadata = parseSkillFrontmatterFile(skillFile);
931
- } catch (error) {
932
- issues.push({
933
- code: error.code || 'invalid_skill_file',
934
- message: error.message,
935
- });
936
- }
937
980
 
938
981
  if (!packageMetadata.packageName) {
939
982
  issues.push({
@@ -949,10 +992,10 @@ function validatePackagedSkillDir(repoRoot, packageDir) {
949
992
  });
950
993
  }
951
994
 
952
- if (packageMetadata.files && !packageMetadata.files.includes('SKILL.md')) {
995
+ if (!isPublishedSkillFile(packageMetadata.files, skillExport.relativeSkillFile)) {
953
996
  issues.push({
954
997
  code: 'skill_not_published',
955
- message: 'package.json files does not include SKILL.md',
998
+ message: `package.json files does not include ${skillExport.relativeSkillFile}`,
956
999
  });
957
1000
  }
958
1001
 
@@ -972,51 +1015,50 @@ function validatePackagedSkillDir(repoRoot, packageDir) {
972
1015
  }
973
1016
  }
974
1017
 
975
- if (skillMetadata) {
976
- 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))) {
977
1034
  issues.push({
978
- code: 'invalid_skill_status',
979
- message: 'metadata.status must be "deprecated" or "retired"',
1035
+ code: 'missing_source',
1036
+ message: 'declared source file does not exist',
1037
+ path: sourcePath,
980
1038
  });
981
1039
  }
1040
+ }
982
1041
 
983
- if (skillMetadata.replacement && !skillMetadata.replacement.startsWith('@')) {
1042
+ for (const requirement of skillExport.requires || []) {
1043
+ if (!packageMetadata.dependencies[requirement]) {
984
1044
  issues.push({
985
- code: 'invalid_replacement',
986
- 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,
987
1048
  });
988
1049
  }
989
-
990
- for (const sourcePath of skillMetadata.sources) {
991
- if (!existsSync(join(repoRoot, sourcePath))) {
992
- issues.push({
993
- code: 'missing_source',
994
- message: 'declared source file does not exist',
995
- path: sourcePath,
996
- });
997
- }
998
- }
999
-
1000
- for (const requirement of skillMetadata.requires) {
1001
- if (!packageMetadata.dependencies[requirement]) {
1002
- issues.push({
1003
- code: 'missing_dependency_declaration',
1004
- message: 'required skill is not declared in package dependencies',
1005
- dependency: requirement,
1006
- });
1007
- }
1008
- }
1009
1050
  }
1010
1051
 
1011
1052
  return {
1012
1053
  valid: issues.length === 0,
1013
- name: skillMetadata?.name || null,
1054
+ key: skillExport.key,
1055
+ name: skillExport.name || null,
1014
1056
  packageName: packageMetadata.packageName,
1015
1057
  packageVersion: packageMetadata.packageVersion,
1016
- skillFile: normalizeDisplayPath(repoRoot, skillFile),
1017
- packagePath: normalizeDisplayPath(repoRoot, packageDir),
1018
- status: skillMetadata?.status || null,
1019
- replacement: skillMetadata?.replacement || null,
1058
+ skillFile: skillExport.skillFile,
1059
+ packagePath: pkg.packagePath,
1060
+ status: skillExport.status || null,
1061
+ replacement: skillExport.replacement || null,
1020
1062
  nextSteps: buildValidateNextSteps(packageMetadata, issues.length === 0),
1021
1063
  issues,
1022
1064
  };
@@ -1024,15 +1066,25 @@ function validatePackagedSkillDir(repoRoot, packageDir) {
1024
1066
 
1025
1067
  export function validateSkills(target, { cwd = process.cwd() } = {}) {
1026
1068
  const repoRoot = findRepoRoot(cwd);
1027
- const packageDirs = resolvePackagedSkillTarget(repoRoot, target);
1069
+ let targets = [];
1028
1070
 
1029
- 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))]) {
1030
1082
  syncSkillDependencies(packageDir);
1031
1083
  }
1032
1084
 
1033
- const skills = packageDirs
1034
- .map((packageDir) => validatePackagedSkillDir(repoRoot, packageDir))
1035
- .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));
1036
1088
 
1037
1089
  const validCount = skills.filter((skill) => skill.valid).length;
1038
1090
  const invalidCount = skills.length - validCount;
@@ -1040,18 +1092,32 @@ export function validateSkills(target, { cwd = process.cwd() } = {}) {
1040
1092
  if (validCount > 0) {
1041
1093
  const buildState = readBuildState(repoRoot);
1042
1094
 
1043
- for (const packageDir of packageDirs) {
1044
- const packageMetadata = readPackageMetadata(packageDir);
1045
- 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);
1046
1105
  if (!result?.valid) continue;
1047
1106
 
1048
- const { packageName, record } = buildStateRecordForPackageDir(repoRoot, packageDir, {
1049
- parseSkillFrontmatterFile,
1050
- readPackageMetadata,
1051
- normalizeDisplayPath,
1052
- });
1053
- if (!packageName) continue;
1054
- 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
+ };
1055
1121
  }
1056
1122
 
1057
1123
  writeBuildState(repoRoot, buildState);
@@ -1113,28 +1179,22 @@ function listPackagedSkillDirs(repoRoot) {
1113
1179
  }
1114
1180
 
1115
1181
  function listAuthoredPackagedSkills(repoRoot) {
1116
- return listPackagedSkillDirs(repoRoot)
1117
- .map((packageDir) => {
1118
- const skillFile = join(packageDir, 'SKILL.md');
1119
- const metadata = parseSkillFrontmatterFile(skillFile);
1120
- const packageMetadata = readPackageMetadata(packageDir);
1121
-
1122
- if (!packageMetadata.packageName || !packageMetadata.packageVersion) {
1123
- return null;
1124
- }
1125
-
1126
- return {
1127
- name: metadata.name,
1128
- description: metadata.description,
1129
- packageName: packageMetadata.packageName,
1130
- packageVersion: packageMetadata.packageVersion,
1131
- skillPath: normalizeDisplayPath(repoRoot, packageDir),
1132
- skillFile: normalizeDisplayPath(repoRoot, skillFile),
1133
- sources: metadata.sources,
1134
- requires: metadata.requires,
1135
- };
1136
- })
1137
- .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
+ })))
1138
1198
  .sort((a, b) => a.packageName.localeCompare(b.packageName));
1139
1199
  }
1140
1200
 
@@ -1143,7 +1203,7 @@ export function generateSkillsCatalog({ cwd = process.cwd() } = {}) {
1143
1203
  const skills = {};
1144
1204
 
1145
1205
  for (const skill of listAuthoredPackagedSkills(repoRoot)) {
1146
- skills[skill.packageName] = {
1206
+ skills[skill.key] = {
1147
1207
  name: skill.name,
1148
1208
  description: skill.description,
1149
1209
  path: skill.skillPath,
@@ -1152,6 +1212,8 @@ export function generateSkillsCatalog({ cwd = process.cwd() } = {}) {
1152
1212
  package_version: skill.packageVersion,
1153
1213
  sources: skill.sources,
1154
1214
  requires: skill.requires,
1215
+ ...(skill.wraps ? { wraps: skill.wraps } : {}),
1216
+ ...(skill.overrides?.length ? { overrides: skill.overrides } : {}),
1155
1217
  };
1156
1218
  }
1157
1219
 
@@ -1173,12 +1235,14 @@ export function generateBuildState({ cwd = process.cwd() } = {}) {
1173
1235
  };
1174
1236
  }
1175
1237
 
1176
- skills[skill.packageName] = {
1238
+ skills[skill.key] = {
1177
1239
  package_version: skill.packageVersion,
1178
1240
  skill_path: skill.skillPath,
1179
1241
  skill_file: skill.skillFile,
1180
1242
  sources,
1181
1243
  requires: skill.requires,
1244
+ ...(skill.wraps ? { wraps: skill.wraps } : {}),
1245
+ ...(skill.overrides?.length ? { overrides: skill.overrides } : {}),
1182
1246
  };
1183
1247
  }
1184
1248
 
@@ -1265,6 +1329,36 @@ function listInstalledPackageDirs(nodeModulesDir) {
1265
1329
  return packageDirs;
1266
1330
  }
1267
1331
 
1332
+ function findInstalledPackageDir(nodeModulesDir, packageName) {
1333
+ if (!packageName) return null;
1334
+ const packageDir = join(nodeModulesDir, ...packageName.split('/'));
1335
+ return existsSync(packageDir) ? packageDir : null;
1336
+ }
1337
+
1338
+ function resolveInstalledPackageClosure(repoRoot, directTargetMap) {
1339
+ const nodeModulesDir = join(repoRoot, 'node_modules');
1340
+ const queue = [...directTargetMap.keys()];
1341
+ const seen = new Set();
1342
+ const packageDirs = [];
1343
+
1344
+ while (queue.length > 0) {
1345
+ const packageName = queue.shift();
1346
+ if (seen.has(packageName)) continue;
1347
+ seen.add(packageName);
1348
+
1349
+ const packageDir = findInstalledPackageDir(nodeModulesDir, packageName);
1350
+ if (!packageDir) continue;
1351
+
1352
+ packageDirs.push(packageDir);
1353
+ const packageMetadata = readPackageMetadata(packageDir);
1354
+ for (const dependencyName of Object.keys(packageMetadata.dependencies || {})) {
1355
+ queue.push(dependencyName);
1356
+ }
1357
+ }
1358
+
1359
+ return packageDirs.sort();
1360
+ }
1361
+
1268
1362
  function collectLocalInstallTargets(initialTarget) {
1269
1363
  const resolvedTarget = resolve(initialTarget);
1270
1364
  const queue = [resolvedTarget];
@@ -1342,14 +1436,16 @@ export function installSkills(targets, { cwd = process.cwd() } = {}) {
1342
1436
 
1343
1437
  execFileSync('npm', ['install', '--no-save', ...uniqueInstallTargets], {
1344
1438
  cwd: repoRoot,
1439
+ env: buildNpmRegistryEnv(repoRoot, process.env),
1345
1440
  encoding: 'utf-8',
1346
1441
  stdio: ['pipe', 'pipe', 'pipe'],
1347
1442
  });
1348
1443
 
1444
+ const resolvedPackageDirs = resolveInstalledPackageClosure(repoRoot, directTargetMap);
1349
1445
  return rebuildInstallState(repoRoot, directTargetMap, {
1350
- listInstalledPackageDirs,
1351
- parseSkillFrontmatterFile,
1446
+ packageDirs: resolvedPackageDirs,
1352
1447
  readPackageMetadata,
1448
+ readInstalledSkillExports,
1353
1449
  normalizeRelativePath,
1354
1450
  });
1355
1451
  }
@@ -1360,11 +1456,14 @@ export function inspectSkillsEnv({ cwd = process.cwd() } = {}) {
1360
1456
  const installs = Object.entries(state.installs || {})
1361
1457
  .sort(([a], [b]) => a.localeCompare(b))
1362
1458
  .map(([packageName, install]) => ({
1363
- ...readInstalledSkillLifecycle(repoRoot, install.source_package_path),
1459
+ ...(install.skills?.length > 0
1460
+ ? readInstalledSkillLifecycleFromRecord(install)
1461
+ : readInstalledSkillLifecycle(repoRoot, install.source_package_path)),
1364
1462
  packageName,
1365
1463
  direct: install.direct,
1366
1464
  packageVersion: install.package_version,
1367
1465
  sourcePackagePath: install.source_package_path,
1466
+ skills: install.skills || [],
1368
1467
  materializations: install.materializations || [],
1369
1468
  }));
1370
1469
 
@@ -1403,10 +1502,66 @@ function readInstalledSkillLifecycle(repoRoot, sourcePackagePath) {
1403
1502
  };
1404
1503
  }
1405
1504
 
1505
+ function readInstalledSkillLifecycleFromRecord(install) {
1506
+ const primarySkill = (install.skills || []).find((entry) => entry.runtime_name === entry.name)
1507
+ || (install.skills || [])[0]
1508
+ || null;
1509
+
1510
+ if (!primarySkill) {
1511
+ return {
1512
+ requires: [],
1513
+ status: null,
1514
+ replacement: null,
1515
+ message: null,
1516
+ };
1517
+ }
1518
+
1519
+ return {
1520
+ requires: primarySkill.requires || [],
1521
+ status: primarySkill.status || null,
1522
+ replacement: primarySkill.replacement || null,
1523
+ message: primarySkill.message || null,
1524
+ };
1525
+ }
1526
+
1406
1527
  function buildInstallCommand(packageName) {
1407
1528
  return `agentpack skills install ${packageName}`;
1408
1529
  }
1409
1530
 
1531
+ function buildInstalledRequirementSet(installs) {
1532
+ const installed = new Set();
1533
+
1534
+ for (const install of installs) {
1535
+ if (install.packageName) installed.add(install.packageName);
1536
+ for (const skill of install.skills || []) {
1537
+ const requirement = buildCanonicalSkillRequirement(install.packageName, skill.name);
1538
+ if (requirement) installed.add(requirement);
1539
+ }
1540
+ }
1541
+
1542
+ return installed;
1543
+ }
1544
+
1545
+ function buildInstalledRequirementRecords(installs) {
1546
+ return installs.map((install) => {
1547
+ const requires = new Set();
1548
+
1549
+ for (const skill of install.skills || []) {
1550
+ for (const requirement of skill.requires || []) {
1551
+ requires.add(requirement);
1552
+ }
1553
+ }
1554
+
1555
+ return {
1556
+ packageName: install.packageName,
1557
+ name: null,
1558
+ skillFile: install.sourcePackagePath ? `${install.sourcePackagePath}/SKILL.md` : null,
1559
+ direct: install.direct,
1560
+ requires: [...requires].sort((a, b) => a.localeCompare(b)),
1561
+ };
1562
+ });
1563
+ }
1564
+
1410
1565
  function listLocalWorkbenchSkillRecords(repoRoot) {
1411
1566
  const records = [];
1412
1567
 
@@ -1492,14 +1647,8 @@ export function inspectMissingSkillDependencies({
1492
1647
  } = {}) {
1493
1648
  const repoRoot = findRepoRoot(cwd);
1494
1649
  const env = inspectSkillsEnv({ cwd });
1495
- const installed = new Set(env.installs.map((install) => install.packageName));
1496
- const installedRecords = env.installs.map((install) => ({
1497
- packageName: install.packageName,
1498
- name: null,
1499
- skillFile: install.sourcePackagePath ? `${install.sourcePackagePath}/SKILL.md` : null,
1500
- direct: install.direct,
1501
- requires: install.requires,
1502
- }));
1650
+ const installed = buildInstalledRequirementSet(env.installs);
1651
+ const installedRecords = buildInstalledRequirementRecords(env.installs);
1503
1652
  const localWorkbenchRecords = listLocalWorkbenchSkillRecords(repoRoot);
1504
1653
 
1505
1654
  let records = [...installedRecords, ...localWorkbenchRecords];
@@ -1600,9 +1749,11 @@ export async function listOutdatedSkills({
1600
1749
 
1601
1750
  export async function inspectSkillsStatus({ cwd = process.cwd() } = {}) {
1602
1751
  const env = inspectSkillsEnv({ cwd });
1752
+ const state = readInstallState(env.repoRoot);
1603
1753
  const registry = inspectRegistryConfig({ cwd });
1604
1754
  const outdatedResult = await listOutdatedSkills({ cwd });
1605
1755
  const missingResult = inspectMissingSkillDependencies({ cwd });
1756
+ const runtimeInspection = inspectMaterializedSkills(env.repoRoot, state);
1606
1757
 
1607
1758
  const installedCount = env.installs.length;
1608
1759
  const directCount = env.installs.filter((install) => install.direct).length;
@@ -1619,13 +1770,23 @@ export async function inspectSkillsStatus({ cwd = process.cwd() } = {}) {
1619
1770
  const deprecatedCount = deprecated.length;
1620
1771
  const incomplete = missingResult.skills;
1621
1772
  const incompleteCount = missingResult.count;
1773
+ const runtimeDrift = runtimeInspection.runtimeDrift;
1774
+ const runtimeDriftCount = runtimeInspection.runtimeDriftCount;
1775
+ const orphanedMaterializations = runtimeInspection.orphanedMaterializations;
1776
+ const orphanedMaterializationCount = runtimeInspection.orphanedMaterializationCount;
1622
1777
 
1623
1778
  let health = 'healthy';
1624
1779
  if (!registry.configured) {
1625
1780
  health = installedCount > 0 || outdatedCount > 0 ? 'attention-needed' : 'needs-config';
1626
- } else if (outdatedCount > 0 || deprecatedCount > 0 || incompleteCount > 0) {
1781
+ } else if (
1782
+ outdatedCount > 0
1783
+ || deprecatedCount > 0
1784
+ || incompleteCount > 0
1785
+ || runtimeDriftCount > 0
1786
+ || orphanedMaterializationCount > 0
1787
+ ) {
1627
1788
  health = 'attention-needed';
1628
- } else if (incompleteCount > 0) {
1789
+ } else if (incompleteCount > 0 || runtimeDriftCount > 0 || orphanedMaterializationCount > 0) {
1629
1790
  health = 'attention-needed';
1630
1791
  }
1631
1792
 
@@ -1637,10 +1798,14 @@ export async function inspectSkillsStatus({ cwd = process.cwd() } = {}) {
1637
1798
  outdatedCount,
1638
1799
  deprecatedCount,
1639
1800
  incompleteCount,
1801
+ runtimeDriftCount,
1802
+ orphanedMaterializationCount,
1640
1803
  registry,
1641
1804
  outdated: outdatedResult.skills,
1642
1805
  deprecated,
1643
1806
  incomplete,
1807
+ runtimeDrift,
1808
+ orphanedMaterializations,
1644
1809
  installs: env.installs,
1645
1810
  health,
1646
1811
  };
@@ -1662,15 +1827,17 @@ export function uninstallSkills(target, { cwd = process.cwd() } = {}) {
1662
1827
  if (nextInstallTargets.length > 0) {
1663
1828
  execFileSync('npm', ['install', '--no-save', ...nextInstallTargets], {
1664
1829
  cwd: repoRoot,
1830
+ env: buildNpmRegistryEnv(repoRoot, process.env),
1665
1831
  encoding: 'utf-8',
1666
1832
  stdio: ['pipe', 'pipe', 'pipe'],
1667
1833
  });
1668
1834
  }
1669
1835
 
1836
+ const resolvedPackageDirs = resolveInstalledPackageClosure(repoRoot, nextDirectTargetMap);
1670
1837
  const nextState = rebuildInstallState(repoRoot, nextDirectTargetMap, {
1671
- listInstalledPackageDirs,
1672
- parseSkillFrontmatterFile,
1838
+ packageDirs: resolvedPackageDirs,
1673
1839
  readPackageMetadata,
1840
+ readInstalledSkillExports,
1674
1841
  normalizeRelativePath,
1675
1842
  });
1676
1843
  const remainingTargets = new Set(