@alavida/agentpack 0.1.4 → 0.1.6

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.
@@ -1,4 +1,4 @@
1
- import { existsSync, mkdirSync, rmSync, symlinkSync } from 'node:fs';
1
+ import { existsSync, lstatSync, mkdirSync, rmSync, symlinkSync, unlinkSync } from 'node:fs';
2
2
  import { dirname, join, relative, resolve } from 'node:path';
3
3
  import { writeInstallState } from '../fs/install-state-repository.js';
4
4
 
@@ -7,7 +7,17 @@ function ensureDir(pathValue) {
7
7
  }
8
8
 
9
9
  export function removePathIfExists(pathValue) {
10
- rmSync(pathValue, { recursive: true, force: true });
10
+ try {
11
+ const stat = lstatSync(pathValue);
12
+ if (stat.isSymbolicLink() || stat.isFile()) {
13
+ unlinkSync(pathValue);
14
+ return;
15
+ }
16
+ rmSync(pathValue, { recursive: true, force: true });
17
+ } catch (error) {
18
+ if (error?.code === 'ENOENT') return;
19
+ throw error;
20
+ }
11
21
  }
12
22
 
13
23
  export function ensureSkillLink(repoRoot, baseDir, skillName, skillDir, normalizeDisplayPath) {
@@ -25,9 +35,13 @@ export function removeSkillLinks(repoRoot, name, normalizeDisplayPath) {
25
35
  join(repoRoot, '.claude', 'skills', name),
26
36
  join(repoRoot, '.agents', 'skills', name),
27
37
  ]) {
28
- if (!existsSync(pathValue)) continue;
29
- removePathIfExists(pathValue);
30
- removed.push(normalizeDisplayPath(repoRoot, pathValue));
38
+ try {
39
+ removePathIfExists(pathValue);
40
+ removed.push(normalizeDisplayPath(repoRoot, pathValue));
41
+ } catch (error) {
42
+ if (error?.code === 'ENOENT') continue;
43
+ throw error;
44
+ }
31
45
  }
32
46
  return removed;
33
47
  }
@@ -50,9 +64,13 @@ export function removeSkillLinksByPaths(repoRoot, paths, normalizeDisplayPath) {
50
64
  const pathValue = resolve(repoRoot, relativePath);
51
65
  const inAllowedRoot = allowedRoots.some((root) => pathValue === root || pathValue.startsWith(`${root}/`));
52
66
  if (!inAllowedRoot) continue;
53
- if (!existsSync(pathValue)) continue;
54
- removePathIfExists(pathValue);
55
- removed.push(normalizeDisplayPath(repoRoot, pathValue));
67
+ try {
68
+ removePathIfExists(pathValue);
69
+ removed.push(normalizeDisplayPath(repoRoot, pathValue));
70
+ } catch (error) {
71
+ if (error?.code === 'ENOENT') continue;
72
+ throw error;
73
+ }
56
74
  }
57
75
  return [...new Set(removed)];
58
76
  }
@@ -63,31 +81,62 @@ function ensureSymlink(targetPath, linkPath) {
63
81
  symlinkSync(targetPath, linkPath, 'dir');
64
82
  }
65
83
 
84
+ function inferPackageRuntimeNamespace(packageName) {
85
+ return packageName?.split('/').pop() || null;
86
+ }
87
+
88
+ function buildRuntimeName(packageName, exportedSkills, entry) {
89
+ if (exportedSkills.length <= 1) return entry.name;
90
+
91
+ const namespace = inferPackageRuntimeNamespace(packageName);
92
+ if (!namespace) return entry.name;
93
+ if (entry.name === namespace) return namespace;
94
+ return `${namespace}:${entry.name}`;
95
+ }
96
+
66
97
  export function buildInstallRecord(repoRoot, packageDir, directTargetMap, {
67
- parseSkillFrontmatterFile,
68
98
  readPackageMetadata,
99
+ readInstalledSkillExports,
69
100
  normalizeRelativePath,
70
101
  } = {}) {
71
102
  const packageMetadata = readPackageMetadata(packageDir);
72
103
  if (!packageMetadata.packageName) return null;
73
-
74
- const skillMetadata = parseSkillFrontmatterFile(join(packageDir, 'SKILL.md'));
75
- const skillDirName = skillMetadata.name;
104
+ const exportedSkills = readInstalledSkillExports(packageDir);
105
+ if (exportedSkills.length === 0) return null;
76
106
  const materializations = [];
107
+ const skills = [];
77
108
 
78
- const claudeTargetAbs = join(repoRoot, '.claude', 'skills', skillDirName);
79
- ensureSymlink(packageDir, claudeTargetAbs);
80
- materializations.push({
81
- target: normalizeRelativePath(relative(repoRoot, claudeTargetAbs)),
82
- mode: 'symlink',
83
- });
109
+ for (const entry of exportedSkills) {
110
+ const runtimeName = buildRuntimeName(packageMetadata.packageName, exportedSkills, entry);
111
+ const skillMaterializations = [];
84
112
 
85
- const agentsTargetAbs = join(repoRoot, '.agents', 'skills', skillDirName);
86
- ensureSymlink(packageDir, agentsTargetAbs);
87
- materializations.push({
88
- target: normalizeRelativePath(relative(repoRoot, agentsTargetAbs)),
89
- mode: 'symlink',
90
- });
113
+ const claudeTargetAbs = join(repoRoot, '.claude', 'skills', runtimeName);
114
+ ensureSymlink(entry.skillDir, claudeTargetAbs);
115
+ skillMaterializations.push({
116
+ target: normalizeRelativePath(relative(repoRoot, claudeTargetAbs)),
117
+ mode: 'symlink',
118
+ });
119
+
120
+ const agentsTargetAbs = join(repoRoot, '.agents', 'skills', runtimeName);
121
+ ensureSymlink(entry.skillDir, agentsTargetAbs);
122
+ skillMaterializations.push({
123
+ target: normalizeRelativePath(relative(repoRoot, agentsTargetAbs)),
124
+ mode: 'symlink',
125
+ });
126
+
127
+ materializations.push(...skillMaterializations);
128
+ skills.push({
129
+ name: entry.name,
130
+ runtime_name: runtimeName,
131
+ source_skill_path: normalizeRelativePath(relative(repoRoot, entry.skillDir)),
132
+ source_skill_file: normalizeRelativePath(relative(repoRoot, entry.skillFile)),
133
+ requires: entry.requires,
134
+ status: entry.status,
135
+ replacement: entry.replacement,
136
+ message: entry.message,
137
+ materializations: skillMaterializations,
138
+ });
139
+ }
91
140
 
92
141
  return {
93
142
  packageName: packageMetadata.packageName,
@@ -95,26 +144,23 @@ export function buildInstallRecord(repoRoot, packageDir, directTargetMap, {
95
144
  requestedTarget: directTargetMap.get(packageMetadata.packageName) || null,
96
145
  packageVersion: packageMetadata.packageVersion,
97
146
  sourcePackagePath: normalizeRelativePath(relative(repoRoot, packageDir)),
147
+ skills,
98
148
  materializations,
99
149
  };
100
150
  }
101
151
 
102
152
  export function rebuildInstallState(repoRoot, directTargetMap, {
103
- listInstalledPackageDirs,
104
- parseSkillFrontmatterFile,
153
+ packageDirs = [],
105
154
  readPackageMetadata,
155
+ readInstalledSkillExports,
106
156
  normalizeRelativePath,
107
157
  } = {}) {
108
- const packageDirs = listInstalledPackageDirs(join(repoRoot, 'node_modules'));
109
158
  const installs = {};
110
159
 
111
160
  for (const packageDir of packageDirs) {
112
- const skillFile = join(packageDir, 'SKILL.md');
113
- if (!existsSync(skillFile)) continue;
114
-
115
161
  const record = buildInstallRecord(repoRoot, packageDir, directTargetMap, {
116
- parseSkillFrontmatterFile,
117
162
  readPackageMetadata,
163
+ readInstalledSkillExports,
118
164
  normalizeRelativePath,
119
165
  });
120
166
  if (!record) continue;
@@ -124,6 +170,7 @@ export function rebuildInstallState(repoRoot, directTargetMap, {
124
170
  requested_target: record.requestedTarget,
125
171
  package_version: record.packageVersion,
126
172
  source_package_path: record.sourcePackagePath,
173
+ skills: record.skills,
127
174
  materializations: record.materializations,
128
175
  };
129
176
  }
@@ -1,6 +1,12 @@
1
+ import { writeFileSync } from 'node:fs';
1
2
  import { spawn } from 'node:child_process';
2
3
 
3
4
  export function openBrowser(url) {
5
+ if (process.env.AGENTPACK_BROWSER_CAPTURE_PATH) {
6
+ writeFileSync(process.env.AGENTPACK_BROWSER_CAPTURE_PATH, `${url}\n`);
7
+ return;
8
+ }
9
+
4
10
  if (process.env.AGENTPACK_DISABLE_BROWSER === '1') return;
5
11
 
6
12
  const command = process.platform === 'darwin'
package/src/lib/skills.js CHANGED
@@ -19,11 +19,14 @@ 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 { inspectMaterializedSkills } from '../infrastructure/runtime/inspect-materialized-skills.js';
27
30
  import {
28
31
  buildStateRecordForPackageDir,
29
32
  compareRecordedSources,
@@ -31,6 +34,10 @@ import {
31
34
  readBuildState,
32
35
  writeBuildState,
33
36
  } from '../domain/skills/skill-provenance.js';
37
+ import { readUserConfig } from '../infrastructure/fs/user-config-repository.js';
38
+ import { readUserCredentials } from '../infrastructure/fs/user-credentials-repository.js';
39
+ import { getUserNpmrcPath, readUserNpmrc } from '../infrastructure/fs/user-npmrc-repository.js';
40
+ import { resolveRegistryConfig } from '../domain/auth/registry-resolution.js';
34
41
  import { startSkillDevWorkbench } from '../application/skills/start-skill-dev-workbench.js';
35
42
  import { AgentpackError, EXIT_CODES, NetworkError, NotFoundError, ValidationError } from '../utils/errors.js';
36
43
 
@@ -638,24 +645,75 @@ function readRepoNpmRegistryConfig(repoRoot, scope = null) {
638
645
  : {};
639
646
  const scopes = scope ? [scope] : MANAGED_PACKAGE_SCOPES;
640
647
  const matchedScope = scopes.find((candidate) => config[`${candidate}:registry`]) || scope || scopes[0] || null;
648
+ const registry = matchedScope ? (config[`${matchedScope}:registry`] || null) : null;
649
+ const authKey = registry ? `//${new URL(registry).host}/:_authToken` : null;
641
650
 
642
651
  return {
643
652
  npmrcPath: existsSync(npmrcPath) ? npmrcPath : null,
644
653
  scope: matchedScope,
645
- registry: matchedScope ? (config[`${matchedScope}:registry`] || null) : null,
646
- authToken: config['//npm.pkg.github.com/:_authToken'] || null,
654
+ registry,
655
+ authToken: authKey ? (config[authKey] || null) : null,
647
656
  alwaysAuth: String(config['always-auth'] || '').toLowerCase() === 'true',
648
657
  };
649
658
  }
650
659
 
660
+ function readEffectiveRegistryConfig(repoRoot, scope = null, env = process.env) {
661
+ const userConfig = readUserConfig({ env });
662
+ const userNpmrc = readUserNpmrc({ env });
663
+ const repoConfig = readRepoNpmRegistryConfig(repoRoot, scope);
664
+ const userScope = MANAGED_PACKAGE_SCOPES.find((candidate) => userNpmrc[`${candidate}:registry`]) || null;
665
+ const resolvedScope = scope
666
+ || (repoConfig.registry ? repoConfig.scope : null)
667
+ || userScope
668
+ || userConfig.scope
669
+ || repoConfig.scope
670
+ || MANAGED_PACKAGE_SCOPES[0]
671
+ || null;
672
+ const resolved = resolveRegistryConfig({
673
+ scope: resolvedScope,
674
+ defaults: {
675
+ registry: userConfig.registry,
676
+ verificationPackage: userConfig.verificationPackage,
677
+ },
678
+ userNpmrc,
679
+ repoNpmrc: repoConfig.registry ? {
680
+ [`${repoConfig.scope}:registry`]: repoConfig.registry,
681
+ ...(repoConfig.authToken ? { [`//${new URL(repoConfig.registry).host}/:_authToken`]: repoConfig.authToken } : {}),
682
+ } : {},
683
+ });
684
+
685
+ let npmrcPath = null;
686
+ if (resolved.source === 'repo') {
687
+ npmrcPath = repoConfig.npmrcPath;
688
+ } else if (resolved.source === 'user') {
689
+ npmrcPath = getUserNpmrcPath({ env });
690
+ }
691
+
692
+ const alwaysAuth = resolved.source === 'repo'
693
+ ? repoConfig.alwaysAuth
694
+ : String(userNpmrc['always-auth'] || '').toLowerCase() === 'true';
695
+
696
+ return {
697
+ scope: resolved.scope,
698
+ npmrcPath,
699
+ registry: resolved.registry,
700
+ authToken: resolved.authToken,
701
+ alwaysAuth,
702
+ source: resolved.source,
703
+ configured: resolved.source !== 'default' && Boolean(resolved.registry),
704
+ };
705
+ }
706
+
651
707
  export function inspectRegistryConfig({
652
708
  cwd = process.cwd(),
653
709
  scope = null,
710
+ env = process.env,
654
711
  } = {}) {
655
712
  const repoRoot = findRepoRoot(cwd);
656
- const { npmrcPath, scope: resolvedScope, registry, authToken, alwaysAuth } = readRepoNpmRegistryConfig(
713
+ const { npmrcPath, scope: resolvedScope, registry, authToken, alwaysAuth, source, configured } = readEffectiveRegistryConfig(
657
714
  repoRoot,
658
- scope
715
+ scope,
716
+ env
659
717
  );
660
718
 
661
719
  let auth = {
@@ -688,10 +746,11 @@ export function inspectRegistryConfig({
688
746
  scope: resolvedScope,
689
747
  repoRoot,
690
748
  npmrcPath: npmrcPath ? normalizeDisplayPath(repoRoot, npmrcPath) : null,
691
- configured: Boolean(registry),
692
- registry,
749
+ configured,
750
+ registry: configured ? registry : null,
693
751
  auth,
694
752
  alwaysAuth,
753
+ source,
695
754
  };
696
755
  }
697
756
 
@@ -704,6 +763,22 @@ function resolveRegistryAuthToken(rawValue) {
704
763
  return rawValue;
705
764
  }
706
765
 
766
+ function buildNpmRegistryEnv(repoRoot, env = process.env) {
767
+ const effective = readEffectiveRegistryConfig(repoRoot, null, env);
768
+ const authToken = effective.authToken || null;
769
+ const envMatch = authToken?.match(/^\$\{([^}]+)\}$/) || null;
770
+ if (!envMatch) return env;
771
+ if (env[envMatch[1]]) return env;
772
+
773
+ const credentials = readUserCredentials({ env });
774
+ if (!credentials?.token) return env;
775
+
776
+ return {
777
+ ...env,
778
+ [envMatch[1]]: credentials.token,
779
+ };
780
+ }
781
+
707
782
  async function fetchRegistryLatestVersion(packageName, {
708
783
  registry,
709
784
  authToken,
@@ -1265,6 +1340,36 @@ function listInstalledPackageDirs(nodeModulesDir) {
1265
1340
  return packageDirs;
1266
1341
  }
1267
1342
 
1343
+ function findInstalledPackageDir(nodeModulesDir, packageName) {
1344
+ if (!packageName) return null;
1345
+ const packageDir = join(nodeModulesDir, ...packageName.split('/'));
1346
+ return existsSync(packageDir) ? packageDir : null;
1347
+ }
1348
+
1349
+ function resolveInstalledPackageClosure(repoRoot, directTargetMap) {
1350
+ const nodeModulesDir = join(repoRoot, 'node_modules');
1351
+ const queue = [...directTargetMap.keys()];
1352
+ const seen = new Set();
1353
+ const packageDirs = [];
1354
+
1355
+ while (queue.length > 0) {
1356
+ const packageName = queue.shift();
1357
+ if (seen.has(packageName)) continue;
1358
+ seen.add(packageName);
1359
+
1360
+ const packageDir = findInstalledPackageDir(nodeModulesDir, packageName);
1361
+ if (!packageDir) continue;
1362
+
1363
+ packageDirs.push(packageDir);
1364
+ const packageMetadata = readPackageMetadata(packageDir);
1365
+ for (const dependencyName of Object.keys(packageMetadata.dependencies || {})) {
1366
+ queue.push(dependencyName);
1367
+ }
1368
+ }
1369
+
1370
+ return packageDirs.sort();
1371
+ }
1372
+
1268
1373
  function collectLocalInstallTargets(initialTarget) {
1269
1374
  const resolvedTarget = resolve(initialTarget);
1270
1375
  const queue = [resolvedTarget];
@@ -1342,14 +1447,16 @@ export function installSkills(targets, { cwd = process.cwd() } = {}) {
1342
1447
 
1343
1448
  execFileSync('npm', ['install', '--no-save', ...uniqueInstallTargets], {
1344
1449
  cwd: repoRoot,
1450
+ env: buildNpmRegistryEnv(repoRoot, process.env),
1345
1451
  encoding: 'utf-8',
1346
1452
  stdio: ['pipe', 'pipe', 'pipe'],
1347
1453
  });
1348
1454
 
1455
+ const resolvedPackageDirs = resolveInstalledPackageClosure(repoRoot, directTargetMap);
1349
1456
  return rebuildInstallState(repoRoot, directTargetMap, {
1350
- listInstalledPackageDirs,
1351
- parseSkillFrontmatterFile,
1457
+ packageDirs: resolvedPackageDirs,
1352
1458
  readPackageMetadata,
1459
+ readInstalledSkillExports,
1353
1460
  normalizeRelativePath,
1354
1461
  });
1355
1462
  }
@@ -1360,11 +1467,14 @@ export function inspectSkillsEnv({ cwd = process.cwd() } = {}) {
1360
1467
  const installs = Object.entries(state.installs || {})
1361
1468
  .sort(([a], [b]) => a.localeCompare(b))
1362
1469
  .map(([packageName, install]) => ({
1363
- ...readInstalledSkillLifecycle(repoRoot, install.source_package_path),
1470
+ ...(install.skills?.length > 0
1471
+ ? readInstalledSkillLifecycleFromRecord(install)
1472
+ : readInstalledSkillLifecycle(repoRoot, install.source_package_path)),
1364
1473
  packageName,
1365
1474
  direct: install.direct,
1366
1475
  packageVersion: install.package_version,
1367
1476
  sourcePackagePath: install.source_package_path,
1477
+ skills: install.skills || [],
1368
1478
  materializations: install.materializations || [],
1369
1479
  }));
1370
1480
 
@@ -1403,10 +1513,66 @@ function readInstalledSkillLifecycle(repoRoot, sourcePackagePath) {
1403
1513
  };
1404
1514
  }
1405
1515
 
1516
+ function readInstalledSkillLifecycleFromRecord(install) {
1517
+ const primarySkill = (install.skills || []).find((entry) => entry.runtime_name === entry.name)
1518
+ || (install.skills || [])[0]
1519
+ || null;
1520
+
1521
+ if (!primarySkill) {
1522
+ return {
1523
+ requires: [],
1524
+ status: null,
1525
+ replacement: null,
1526
+ message: null,
1527
+ };
1528
+ }
1529
+
1530
+ return {
1531
+ requires: primarySkill.requires || [],
1532
+ status: primarySkill.status || null,
1533
+ replacement: primarySkill.replacement || null,
1534
+ message: primarySkill.message || null,
1535
+ };
1536
+ }
1537
+
1406
1538
  function buildInstallCommand(packageName) {
1407
1539
  return `agentpack skills install ${packageName}`;
1408
1540
  }
1409
1541
 
1542
+ function buildInstalledRequirementSet(installs) {
1543
+ const installed = new Set();
1544
+
1545
+ for (const install of installs) {
1546
+ if (install.packageName) installed.add(install.packageName);
1547
+ for (const skill of install.skills || []) {
1548
+ const requirement = buildCanonicalSkillRequirement(install.packageName, skill.name);
1549
+ if (requirement) installed.add(requirement);
1550
+ }
1551
+ }
1552
+
1553
+ return installed;
1554
+ }
1555
+
1556
+ function buildInstalledRequirementRecords(installs) {
1557
+ return installs.map((install) => {
1558
+ const requires = new Set();
1559
+
1560
+ for (const skill of install.skills || []) {
1561
+ for (const requirement of skill.requires || []) {
1562
+ requires.add(requirement);
1563
+ }
1564
+ }
1565
+
1566
+ return {
1567
+ packageName: install.packageName,
1568
+ name: null,
1569
+ skillFile: install.sourcePackagePath ? `${install.sourcePackagePath}/SKILL.md` : null,
1570
+ direct: install.direct,
1571
+ requires: [...requires].sort((a, b) => a.localeCompare(b)),
1572
+ };
1573
+ });
1574
+ }
1575
+
1410
1576
  function listLocalWorkbenchSkillRecords(repoRoot) {
1411
1577
  const records = [];
1412
1578
 
@@ -1492,14 +1658,8 @@ export function inspectMissingSkillDependencies({
1492
1658
  } = {}) {
1493
1659
  const repoRoot = findRepoRoot(cwd);
1494
1660
  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
- }));
1661
+ const installed = buildInstalledRequirementSet(env.installs);
1662
+ const installedRecords = buildInstalledRequirementRecords(env.installs);
1503
1663
  const localWorkbenchRecords = listLocalWorkbenchSkillRecords(repoRoot);
1504
1664
 
1505
1665
  let records = [...installedRecords, ...localWorkbenchRecords];
@@ -1600,9 +1760,11 @@ export async function listOutdatedSkills({
1600
1760
 
1601
1761
  export async function inspectSkillsStatus({ cwd = process.cwd() } = {}) {
1602
1762
  const env = inspectSkillsEnv({ cwd });
1763
+ const state = readInstallState(env.repoRoot);
1603
1764
  const registry = inspectRegistryConfig({ cwd });
1604
1765
  const outdatedResult = await listOutdatedSkills({ cwd });
1605
1766
  const missingResult = inspectMissingSkillDependencies({ cwd });
1767
+ const runtimeInspection = inspectMaterializedSkills(env.repoRoot, state);
1606
1768
 
1607
1769
  const installedCount = env.installs.length;
1608
1770
  const directCount = env.installs.filter((install) => install.direct).length;
@@ -1619,13 +1781,23 @@ export async function inspectSkillsStatus({ cwd = process.cwd() } = {}) {
1619
1781
  const deprecatedCount = deprecated.length;
1620
1782
  const incomplete = missingResult.skills;
1621
1783
  const incompleteCount = missingResult.count;
1784
+ const runtimeDrift = runtimeInspection.runtimeDrift;
1785
+ const runtimeDriftCount = runtimeInspection.runtimeDriftCount;
1786
+ const orphanedMaterializations = runtimeInspection.orphanedMaterializations;
1787
+ const orphanedMaterializationCount = runtimeInspection.orphanedMaterializationCount;
1622
1788
 
1623
1789
  let health = 'healthy';
1624
1790
  if (!registry.configured) {
1625
1791
  health = installedCount > 0 || outdatedCount > 0 ? 'attention-needed' : 'needs-config';
1626
- } else if (outdatedCount > 0 || deprecatedCount > 0 || incompleteCount > 0) {
1792
+ } else if (
1793
+ outdatedCount > 0
1794
+ || deprecatedCount > 0
1795
+ || incompleteCount > 0
1796
+ || runtimeDriftCount > 0
1797
+ || orphanedMaterializationCount > 0
1798
+ ) {
1627
1799
  health = 'attention-needed';
1628
- } else if (incompleteCount > 0) {
1800
+ } else if (incompleteCount > 0 || runtimeDriftCount > 0 || orphanedMaterializationCount > 0) {
1629
1801
  health = 'attention-needed';
1630
1802
  }
1631
1803
 
@@ -1637,10 +1809,14 @@ export async function inspectSkillsStatus({ cwd = process.cwd() } = {}) {
1637
1809
  outdatedCount,
1638
1810
  deprecatedCount,
1639
1811
  incompleteCount,
1812
+ runtimeDriftCount,
1813
+ orphanedMaterializationCount,
1640
1814
  registry,
1641
1815
  outdated: outdatedResult.skills,
1642
1816
  deprecated,
1643
1817
  incomplete,
1818
+ runtimeDrift,
1819
+ orphanedMaterializations,
1644
1820
  installs: env.installs,
1645
1821
  health,
1646
1822
  };
@@ -1662,15 +1838,17 @@ export function uninstallSkills(target, { cwd = process.cwd() } = {}) {
1662
1838
  if (nextInstallTargets.length > 0) {
1663
1839
  execFileSync('npm', ['install', '--no-save', ...nextInstallTargets], {
1664
1840
  cwd: repoRoot,
1841
+ env: buildNpmRegistryEnv(repoRoot, process.env),
1665
1842
  encoding: 'utf-8',
1666
1843
  stdio: ['pipe', 'pipe', 'pipe'],
1667
1844
  });
1668
1845
  }
1669
1846
 
1847
+ const resolvedPackageDirs = resolveInstalledPackageClosure(repoRoot, nextDirectTargetMap);
1670
1848
  const nextState = rebuildInstallState(repoRoot, nextDirectTargetMap, {
1671
- listInstalledPackageDirs,
1672
- parseSkillFrontmatterFile,
1849
+ packageDirs: resolvedPackageDirs,
1673
1850
  readPackageMetadata,
1851
+ readInstalledSkillExports,
1674
1852
  normalizeRelativePath,
1675
1853
  });
1676
1854
  const remainingTargets = new Set(