@pieai/pro-gov 0.7.2 → 0.7.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -577,10 +577,10 @@ function createAssetInstallPlan(options) {
577
577
  const placement = options.placement ?? "registry";
578
578
  const assetsById = new Map(options.registry.assets.map((asset) => [asset.id, asset]));
579
579
  const bundlesById = new Map(options.bundles.map((bundle) => [bundle.id, bundle]));
580
- const assetIds = resolveBundleAssetIds(options.bundleIds, bundlesById);
580
+ const assetIds = resolveAssetIds(options.bundleIds, options.assetIds ?? [], bundlesById);
581
581
  const assets = assetIds.map((assetId) => {
582
582
  const asset = assetsById.get(assetId);
583
- if (!asset) throw new Error(`Unknown asset id in bundle: ${assetId}`);
583
+ if (!asset) throw new Error(`Unknown asset id in install plan: ${assetId}`);
584
584
  if (asset.kind === "skill" && !asset.hosts.includes(options.host)) {
585
585
  throw new Error(`Asset ${assetId} does not support host ${options.host}`);
586
586
  }
@@ -661,8 +661,8 @@ function createAssetInstallPlan(options) {
661
661
  ]
662
662
  };
663
663
  }
664
- function resolveBundleAssetIds(bundleIds, bundlesById) {
665
- const ids = /* @__PURE__ */ new Set();
664
+ function resolveAssetIds(bundleIds, explicitAssetIds, bundlesById) {
665
+ const ids = new Set(explicitAssetIds);
666
666
  for (const bundleId of bundleIds) {
667
667
  const bundle = bundlesById.get(bundleId);
668
668
  if (!bundle) throw new Error(`Unknown bundle id: ${bundleId}`);
@@ -1437,6 +1437,7 @@ function runAssetsPlan(args) {
1437
1437
  registry: loaded.registry,
1438
1438
  bundles: loadAgentAssetBundles(loaded.agentAssetsDir),
1439
1439
  bundleIds: options.value.bundleIds,
1440
+ assetIds: options.value.assetIds,
1440
1441
  host: options.value.host,
1441
1442
  placement: options.value.placement
1442
1443
  });
@@ -1519,6 +1520,7 @@ function parsePlanOptions(args) {
1519
1520
  targetDir: process.cwd(),
1520
1521
  json: false,
1521
1522
  bundleIds: [],
1523
+ assetIds: [],
1522
1524
  host: "codex"
1523
1525
  };
1524
1526
  for (let index = 0; index < args.length; index += 1) {
@@ -1533,6 +1535,11 @@ function parsePlanOptions(args) {
1533
1535
  if (!bundleId) return { ok: false, error: "Expected --bundle <bundle-id>" };
1534
1536
  options.bundleIds.push(bundleId);
1535
1537
  index += 1;
1538
+ } else if (arg === "--asset") {
1539
+ const assetId = args[index + 1];
1540
+ if (!assetId) return { ok: false, error: "Expected --asset <asset-id>" };
1541
+ options.assetIds.push(assetId);
1542
+ index += 1;
1536
1543
  } else if (arg === "--host") {
1537
1544
  const host = args[index + 1];
1538
1545
  if (!isHost(host)) {
@@ -1564,8 +1571,8 @@ function parsePlanOptions(args) {
1564
1571
  return { ok: false, error: `Unknown assets plan option: ${arg}` };
1565
1572
  }
1566
1573
  }
1567
- if (options.bundleIds.length === 0) {
1568
- return { ok: false, error: "Expected at least one --bundle <bundle-id>" };
1574
+ if (options.bundleIds.length === 0 && options.assetIds.length === 0) {
1575
+ return { ok: false, error: "Expected at least one --bundle <bundle-id> or --asset <asset-id>" };
1569
1576
  }
1570
1577
  return { ok: true, value: options };
1571
1578
  }
@@ -1711,7 +1718,7 @@ function printUsage() {
1711
1718
  console.error(" pro-gov assets list [--registry] [--json] [--visibility public|private|third-party|all]");
1712
1719
  console.error(" pro-gov assets discover [--target <path>] [--json]");
1713
1720
  console.error(" pro-gov assets recommend [--target <path>] [--json]");
1714
- console.error(" pro-gov assets plan --bundle <bundle-id> [--bundle <bundle-id>] [--target <path>] [--host codex|claude-code|gemini-cli|antigravity] [--placement auto|manual] [--out <path>] [--json]");
1721
+ console.error(" pro-gov assets plan [--bundle <bundle-id>] [--asset <asset-id>] [--target <path>] [--host codex|claude-code|gemini-cli|antigravity] [--placement auto|manual] [--out <path>] [--json]");
1715
1722
  console.error(" pro-gov assets apply --plan <path>");
1716
1723
  console.error(" pro-gov assets check [--target <path>] [--json]");
1717
1724
  console.error(" pro-gov assets public-check [--public-root <path>] [--private-root <path>] [--json]");
@@ -2817,13 +2824,30 @@ import { homedir } from "node:os";
2817
2824
  import { join as join18 } from "node:path";
2818
2825
  var DEFAULT_CACHE_THRESHOLD_BYTES = 1e9;
2819
2826
  var MAX_CACHE_ENTRIES = 2e4;
2820
- function inspectProjectRedundancy(root, options = {}) {
2821
- const legacyDirectories = inspectLegacyDirectories(root);
2827
+ function inspectHostRedundancy(options = {}) {
2822
2828
  const homeDir = options.homeDir ?? homedir();
2823
- const cachePaths = getPlaywrightCachePaths(homeDir, options.playwrightBrowsersPath ?? process.env.PLAYWRIGHT_BROWSERS_PATH);
2824
- const playwrightCaches = cachePaths.map((path) => inspectPlaywrightCache(path));
2829
+ const cachePaths = getPlaywrightCachePaths(
2830
+ homeDir,
2831
+ options.playwrightBrowsersPath ?? process.env.PLAYWRIGHT_BROWSERS_PATH
2832
+ );
2833
+ const playwrightCaches = cachePaths.map((path) => inspectPlaywrightCache(path, options.cache));
2825
2834
  const cacheThresholdBytes = options.cacheThresholdBytes ?? DEFAULT_CACHE_THRESHOLD_BYTES;
2826
- const status = legacyDirectories.length > 0 || playwrightCaches.some((cache) => cache.exists && (cache.bytes >= cacheThresholdBytes || cache.truncated)) ? "attention" : "clean";
2835
+ const status = playwrightCaches.some(
2836
+ (cache) => cache.exists && (cache.bytes >= cacheThresholdBytes || cache.truncated)
2837
+ ) ? "attention" : "clean";
2838
+ return {
2839
+ status,
2840
+ playwrightCaches,
2841
+ recommendations: status === "attention" ? [
2842
+ "\u53D1\u73B0\u5927\u578B\u6216\u672A\u5B8C\u6574\u8BA1\u6570\u7684 Playwright \u6D4F\u89C8\u5668\u7F13\u5B58\uFF1B\u786E\u8BA4\u6CA1\u6709\u6D4B\u8BD5\u4EFB\u52A1\u5360\u7528\u540E\uFF0C\u6309\u5BBF\u4E3B\u7EA7\u7F13\u5B58\u7EDF\u4E00\u6E05\u7406\u3002"
2843
+ ] : []
2844
+ };
2845
+ }
2846
+ function inspectProjectRedundancy(root, options = {}) {
2847
+ const legacyDirectories = inspectLegacyDirectories(root);
2848
+ const hostRedundancy = options.includePlaywrightCaches === false ? void 0 : inspectHostRedundancy(options);
2849
+ const playwrightCaches = hostRedundancy?.playwrightCaches ?? [];
2850
+ const status = legacyDirectories.length > 0 || hostRedundancy?.status === "attention" ? "attention" : "clean";
2827
2851
  return { status, legacyDirectories, playwrightCaches };
2828
2852
  }
2829
2853
  function inspectLegacyDirectories(root) {
@@ -2831,13 +2855,15 @@ function inspectLegacyDirectories(root) {
2831
2855
  const path = join18(root, relativePath);
2832
2856
  if (!existsSync16(path)) return [];
2833
2857
  const stats = collectDirectoryStats(path);
2834
- return [{
2835
- path: relativePath,
2836
- kind: "legacy-ai-directory",
2837
- fileCount: stats.fileCount,
2838
- bytes: stats.bytes,
2839
- reason: "\u9879\u76EE\u7EA7\u65E7 AI \u5BBF\u4E3B\u76EE\u5F55\uFF1B\u5E94\u4E0E .agents/skills \u7684 SSOT \u9010\u9879\u6BD4\u5BF9\u540E\u518D\u51B3\u5B9A\u662F\u5426\u8FC1\u79FB\uFF0C\u626B\u63CF\u5668\u4E0D\u81EA\u52A8\u5220\u9664\u3002"
2840
- }];
2858
+ return [
2859
+ {
2860
+ path: relativePath,
2861
+ kind: "legacy-ai-directory",
2862
+ fileCount: stats.fileCount,
2863
+ bytes: stats.bytes,
2864
+ reason: "\u9879\u76EE\u7EA7\u65E7 AI \u5BBF\u4E3B\u76EE\u5F55\uFF1B\u5E94\u4E0E .agents/skills \u7684 SSOT \u9010\u9879\u6BD4\u5BF9\u540E\u518D\u51B3\u5B9A\u662F\u5426\u8FC1\u79FB\uFF0C\u626B\u63CF\u5668\u4E0D\u81EA\u52A8\u5220\u9664\u3002"
2865
+ }
2866
+ ];
2841
2867
  }
2842
2868
  function getPlaywrightCachePaths(homeDir, configuredPath) {
2843
2869
  const candidates = [
@@ -2848,18 +2874,40 @@ function getPlaywrightCachePaths(homeDir, configuredPath) {
2848
2874
  ].filter((path) => Boolean(path));
2849
2875
  return [...new Set(candidates)];
2850
2876
  }
2851
- function inspectPlaywrightCache(path) {
2877
+ function inspectPlaywrightCache(path, cache) {
2878
+ const cached = cache?.get(path);
2879
+ if (cached) return cached;
2852
2880
  if (!existsSync16(path)) {
2853
- return { path, exists: false, fileCount: 0, bytes: 0, revisionCount: 0, truncated: false };
2881
+ const missing = {
2882
+ path,
2883
+ exists: false,
2884
+ fileCount: 0,
2885
+ bytes: 0,
2886
+ revisionCount: 0,
2887
+ truncated: false
2888
+ };
2889
+ cache?.set(path, missing);
2890
+ return missing;
2854
2891
  }
2855
2892
  const stats = collectDirectoryStats(path);
2856
2893
  let revisionCount = 0;
2857
2894
  try {
2858
- revisionCount = readdirSync7(path, { withFileTypes: true }).filter((entry) => entry.isDirectory()).length;
2895
+ revisionCount = readdirSync7(path, { withFileTypes: true }).filter(
2896
+ (entry) => entry.isDirectory()
2897
+ ).length;
2859
2898
  } catch {
2860
2899
  revisionCount = 0;
2861
2900
  }
2862
- return { path, exists: true, fileCount: stats.fileCount, bytes: stats.bytes, revisionCount, truncated: stats.truncated };
2901
+ const evidence = {
2902
+ path,
2903
+ exists: true,
2904
+ fileCount: stats.fileCount,
2905
+ bytes: stats.bytes,
2906
+ revisionCount,
2907
+ truncated: stats.truncated
2908
+ };
2909
+ cache?.set(path, evidence);
2910
+ return evidence;
2863
2911
  }
2864
2912
  function collectDirectoryStats(root) {
2865
2913
  let fileCount = 0;
@@ -3397,7 +3445,7 @@ function validateTechnologyGovernance(value, issues) {
3397
3445
  issues.push({ type: "invalid-field", field: "technologyGovernance", message: "Portfolio technologyGovernance must be an object." });
3398
3446
  return { technologies, projectTypes };
3399
3447
  }
3400
- validateAllowedFields(value, "technologyGovernance", ["strategySource", "versionPolicy", "technologies", "projectTypes"], issues);
3448
+ validateAllowedFields(value, "technologyGovernance", ["strategySource", "versionPolicy", "exclusiveOwnership", "technologies", "projectTypes"], issues);
3401
3449
  if (value.strategySource !== void 0 && (typeof value.strategySource !== "string" || value.strategySource.length === 0)) {
3402
3450
  issues.push({ type: "invalid-field", field: "technologyGovernance.strategySource", message: "Technology strategySource must be a non-empty string." });
3403
3451
  }
@@ -3460,9 +3508,65 @@ function validateTechnologyGovernance(value, issues) {
3460
3508
  if (!technologies.has(technology)) issues.push({ type: "invalid-field", field: "technologyGovernance.projectTypes", message: `Project type ${projectType.id} references unknown technology: ${technology}` });
3461
3509
  }
3462
3510
  }
3511
+ validateExclusiveOwnership(value.exclusiveOwnership, projectTypes, issues);
3463
3512
  validateVersionPolicy(value.versionPolicy, projectTypes, issues);
3464
3513
  return { technologies, projectTypes };
3465
3514
  }
3515
+ function validateExclusiveOwnership(value, projectTypes, issues) {
3516
+ if (value === void 0) return;
3517
+ const field = "technologyGovernance.exclusiveOwnership";
3518
+ if (!Array.isArray(value)) {
3519
+ issues.push({ type: "invalid-field", field, message: "Technology exclusiveOwnership must be an array." });
3520
+ return;
3521
+ }
3522
+ const seenIds = /* @__PURE__ */ new Set();
3523
+ for (const rule of value) {
3524
+ if (!isRecord(rule)) {
3525
+ issues.push({ type: "invalid-field", field, message: "Exclusive ownership rule must be an object." });
3526
+ continue;
3527
+ }
3528
+ validateAllowedFields(rule, "exclusiveOwnership", ["id", "label", "allowedProjectTypes", "paths"], issues);
3529
+ const id = typeof rule.id === "string" ? rule.id : "";
3530
+ if (!id || seenIds.has(id)) {
3531
+ issues.push({ type: "invalid-field", field: `${field}.id`, message: `Exclusive ownership rule id must be non-empty and unique: ${String(rule.id)}` });
3532
+ } else {
3533
+ seenIds.add(id);
3534
+ }
3535
+ if (typeof rule.label !== "string" || rule.label.trim().length === 0) {
3536
+ issues.push({ type: "invalid-field", field: `${field}.label`, message: "Exclusive ownership rule label must be a non-empty string." });
3537
+ }
3538
+ validateOptionalStringArray(rule.allowedProjectTypes, id, `${field}.allowedProjectTypes`, issues);
3539
+ if (!Array.isArray(rule.allowedProjectTypes) || rule.allowedProjectTypes.length === 0) {
3540
+ issues.push({ type: "invalid-field", field: `${field}.allowedProjectTypes`, message: "Exclusive ownership rule must allow at least one project type." });
3541
+ } else {
3542
+ for (const projectType of rule.allowedProjectTypes) {
3543
+ if (typeof projectType === "string" && !projectTypes.has(projectType)) {
3544
+ issues.push({ type: "invalid-field", field: `${field}.allowedProjectTypes`, message: `Exclusive ownership rule references unknown project type: ${projectType}` });
3545
+ }
3546
+ }
3547
+ }
3548
+ validateOptionalStringArray(rule.paths, id, `${field}.paths`, issues);
3549
+ if (!Array.isArray(rule.paths) || rule.paths.length === 0) {
3550
+ issues.push({ type: "invalid-field", field: `${field}.paths`, message: "Exclusive ownership rule must declare at least one repository-relative path." });
3551
+ } else {
3552
+ const seenPaths = /* @__PURE__ */ new Set();
3553
+ for (const path of rule.paths) {
3554
+ if (typeof path !== "string") continue;
3555
+ if (!isExactRepositoryRelativePath(path)) {
3556
+ issues.push({
3557
+ type: "invalid-field",
3558
+ field: `${field}.paths`,
3559
+ message: `Exclusive ownership path must be an exact repository-relative path: ${path}`
3560
+ });
3561
+ } else if (seenPaths.has(path)) {
3562
+ issues.push({ type: "invalid-field", field: `${field}.paths`, message: `Duplicate exclusive ownership path: ${path}` });
3563
+ } else {
3564
+ seenPaths.add(path);
3565
+ }
3566
+ }
3567
+ }
3568
+ }
3569
+ }
3466
3570
  function validateVersionPolicy(value, projectTypes, issues) {
3467
3571
  if (value === void 0) return;
3468
3572
  if (!isRecord(value)) {
@@ -4039,9 +4143,12 @@ function collectPackageManifests(root) {
4039
4143
  "build",
4040
4144
  "coverage",
4041
4145
  "dist",
4146
+ "examples",
4147
+ "fixtures",
4042
4148
  "node_modules",
4043
4149
  "out",
4044
4150
  "target",
4151
+ "templates",
4045
4152
  "tmp",
4046
4153
  ".cache",
4047
4154
  ".pnpm-store",
@@ -4289,6 +4396,18 @@ import { homedir as homedir4 } from "node:os";
4289
4396
  import { dirname as dirname14, join as join24, relative as relative8, resolve as resolve5, sep } from "node:path";
4290
4397
  import { fileURLToPath as fileURLToPath4 } from "node:url";
4291
4398
  var CURRENT_ROUTER_VERSION = "1.1";
4399
+ var MCP_DISCOVERY_PATHS = {
4400
+ project: {
4401
+ codex: ".codex/config.toml",
4402
+ claudeCodeShared: ".mcp.json",
4403
+ grok: ".grok/config.toml"
4404
+ },
4405
+ user: {
4406
+ codex: ".codex/config.toml",
4407
+ claudeCode: ".claude.json",
4408
+ grok: ".grok/config.toml"
4409
+ }
4410
+ };
4292
4411
  function inspectPortfolioAiHealth(options) {
4293
4412
  const allEndpoints = collectEndpoints(options.manifest);
4294
4413
  const endpoints = options.targetId && options.targetId !== "all" ? allEndpoints.filter(({ endpoint }) => endpoint.id === options.targetId) : allEndpoints;
@@ -4305,6 +4424,7 @@ function inspectPortfolioAiHealth(options) {
4305
4424
  const grokVersion = commandVersion("grok");
4306
4425
  const executionEngineRoot = options.manifest.executionEngine?.path;
4307
4426
  const skillRegistry = inspectSkillRegistry(executionEngineRoot);
4427
+ const userSkills = inspectUserSkillEvidence(join24(homeDir, ".agents/skills"));
4308
4428
  const expectedPackageVersion = packageVersion(
4309
4429
  join24(executionEngineRoot ?? "", "packages/pro-gov/package.json")
4310
4430
  );
@@ -4316,13 +4436,15 @@ function inspectPortfolioAiHealth(options) {
4316
4436
  homeDir,
4317
4437
  expectedPackageVersion,
4318
4438
  options.manifest.technologyGovernance,
4319
- grokVersion
4439
+ grokVersion,
4440
+ skillRegistry.skills,
4441
+ userSkills
4320
4442
  )
4321
4443
  );
4322
4444
  const summary = { healthy: 0, attention: 0, unhealthy: 0 };
4323
4445
  for (const repository of repositories) summary[repository.status] += 1;
4324
4446
  return {
4325
- schemaVersion: 5,
4447
+ schemaVersion: 6,
4326
4448
  portfolioId: options.manifest.portfolioId,
4327
4449
  generatedAt: options.generatedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
4328
4450
  coverage: {
@@ -4339,7 +4461,7 @@ function inspectPortfolioAiHealth(options) {
4339
4461
  allEndpoints.map(({ endpoint }) => endpoint.path),
4340
4462
  options.manifest.specialistChecks?.devspace
4341
4463
  ),
4342
- skillRegistry,
4464
+ skillRegistry: skillRegistry.health,
4343
4465
  technologyGovernance: {
4344
4466
  strategySource: options.manifest.technologyGovernance?.strategySource,
4345
4467
  versionPolicy: options.manifest.technologyGovernance?.versionPolicy,
@@ -4424,20 +4546,23 @@ function collectEndpoints(manifest) {
4424
4546
  return true;
4425
4547
  });
4426
4548
  }
4427
- function inspectRepository(endpoint, role, secretsRoot, homeDir, expectedPackageVersion, technologyGovernance, grokVersion) {
4549
+ function inspectRepository(endpoint, role, secretsRoot, homeDir, expectedPackageVersion, technologyGovernance, grokVersion, registeredSkills, userSkills) {
4428
4550
  const root = endpoint.path;
4429
4551
  const git = inspectGit2(root);
4430
4552
  const entries = inspectEntries(root);
4431
4553
  const grokInspection = inspectGrokProject(root, homeDir, grokVersion);
4432
- const skills = inspectSkills(root, grokInspection);
4554
+ const skills = inspectSkills(root, grokInspection, registeredSkills, userSkills);
4433
4555
  const hostSsot = inspectProjectHostSsot(root);
4434
4556
  const hooks = inspectHooks(root);
4435
4557
  const docs = inspectDocs(root, role === "execution-engine" ? void 0 : expectedPackageVersion);
4436
4558
  const mcp = {
4437
- codexProject: tomlMcpNames(join24(root, ".codex/config.toml")),
4438
- claudeCodeProjectShared: jsonObjectKeys(join24(root, ".mcp.json"), "mcpServers"),
4559
+ codexProject: tomlMcpNames(join24(root, MCP_DISCOVERY_PATHS.project.codex)),
4560
+ claudeCodeProjectShared: jsonObjectKeys(
4561
+ join24(root, MCP_DISCOVERY_PATHS.project.claudeCodeShared),
4562
+ "mcpServers"
4563
+ ),
4439
4564
  claudeCodeProjectLocal: claudeProjectLocalMcpNames(homeDir, root),
4440
- grokProject: tomlMcpNames(join24(root, ".grok/config.toml")),
4565
+ grokProject: tomlMcpNames(join24(root, MCP_DISCOVERY_PATHS.project.grok)),
4441
4566
  grokEffective: grokInspection.effectiveMcp,
4442
4567
  grokInspection: grokInspection.inspection
4443
4568
  };
@@ -4449,13 +4574,21 @@ function inspectRepository(endpoint, role, secretsRoot, homeDir, expectedPackage
4449
4574
  endpoint.environmentPolicy
4450
4575
  );
4451
4576
  const projectModel = inspectProjectModel(root, endpoint, technologyGovernance);
4577
+ const exclusiveOwnershipViolations = inspectExclusiveOwnership(
4578
+ root,
4579
+ endpoint,
4580
+ technologyGovernance
4581
+ );
4452
4582
  const versions = inspectVersionPolicy(
4453
4583
  root,
4454
4584
  technologyGovernance?.versionPolicy,
4455
4585
  endpoint.projectType
4456
4586
  );
4457
4587
  const verification = inspectProjectVerification(root);
4458
- const redundancy = inspectProjectRedundancy(root, { homeDir });
4588
+ const redundancy = inspectProjectRedundancy(root, {
4589
+ homeDir,
4590
+ includePlaywrightCaches: false
4591
+ });
4459
4592
  const recommendations = [];
4460
4593
  if (!git.isRepository) recommendations.push("\u8BE5\u8DEF\u5F84\u4E0D\u662F Git \u4ED3\u5E93\uFF1B\u786E\u8BA4\u6E05\u5355\u8DEF\u5F84\u662F\u5426\u6B63\u786E\u3002");
4461
4594
  if (git.unmergedBranches.length > 0)
@@ -4495,8 +4628,32 @@ function inspectRepository(endpoint, role, secretsRoot, homeDir, expectedPackage
4495
4628
  recommendations.push("GEMINI.md \u662F\u72EC\u7ACB\u5165\u53E3\uFF1B\u82E5\u6CA1\u6709 Gemini \u4E13\u5C5E\u5DEE\u5F02\uFF0C\u5EFA\u8BAE\u94FE\u63A5\u5230 AGENTS.md\u3002");
4496
4629
  if (entries.gemini === "dangling-symlink")
4497
4630
  recommendations.push("GEMINI.md \u662F\u65AD\u5F00\u7684\u94FE\u63A5\uFF1B\u9700\u8981\u91CD\u65B0\u6307\u5411 AGENTS.md\u3002");
4498
- if (skills.canonical.some((skill) => skill.kind === "dangling-symlink"))
4499
- recommendations.push("`.agents/skills` \u4E2D\u5B58\u5728\u65AD\u5F00\u7684\u6280\u80FD\u94FE\u63A5\u3002");
4631
+ if ([...skills.automatic, ...skills.manual].some((skill) => skill.kind === "dangling-symlink"))
4632
+ recommendations.push("`.agents/skills` \u6216 `.agents/manual-skills` \u4E2D\u5B58\u5728\u65AD\u5F00\u7684\u6280\u80FD\u94FE\u63A5\u3002");
4633
+ if (skills.invalidEntries.length > 0)
4634
+ recommendations.push(
4635
+ `\u6280\u80FD\u76EE\u5F55\u4E2D\u6709 ${skills.invalidEntries.length} \u4E2A\u5783\u573E\u6216\u975E\u6280\u80FD\u6587\u4EF6\uFF1A${skills.invalidEntries.map((entry) => entry.path).join("\u3001")}\u3002`
4636
+ );
4637
+ if (skills.userDuplicates.length > 0)
4638
+ recommendations.push(
4639
+ `\u9879\u76EE\u6280\u80FD\u4E0E\u7528\u6237\u7EA7\u6280\u80FD\u91CD\u590D\uFF1A${skills.userDuplicates.join("\u3001")}\uFF1B\u4F18\u5148\u4FDD\u7559\u7528\u6237\u7EA7\u5355\u4E00\u5165\u53E3\u3002`
4640
+ );
4641
+ if (skills.crossPlacementDuplicates.length > 0)
4642
+ recommendations.push(
4643
+ `\u540C\u540D\u6280\u80FD\u540C\u65F6\u51FA\u73B0\u5728 auto \u4E0E manual\uFF1A${skills.crossPlacementDuplicates.join("\u3001")}\uFF1B\u53EA\u80FD\u4FDD\u7559\u4E00\u4E2A\u671F\u671B\u4F4D\u7F6E\u3002`
4644
+ );
4645
+ if (skills.placementDrift.length > 0)
4646
+ recommendations.push(
4647
+ `\u6709 ${skills.placementDrift.length} \u4E2A\u6280\u80FD\u672A\u653E\u5728\u6CE8\u518C\u8868\u89C4\u5B9A\u7684\u4F4D\u7F6E\uFF1A${skills.placementDrift.map((item) => `${item.name} \u5E94\u4E3A ${item.reason === "scope" ? "\u7528\u6237\u7EA7" : `\u9879\u76EE ${item.expectedPlacement}`}`).join("\u3001")}\u3002`
4648
+ );
4649
+ if (skills.automatic.length > skills.automaticReviewThreshold)
4650
+ recommendations.push(
4651
+ `\u81EA\u52A8\u53D1\u73B0\u6280\u80FD\u6709 ${skills.automatic.length} \u4E2A\uFF0C\u8D85\u8FC7 ${skills.automaticReviewThreshold} \u4E2A\u590D\u6838\u7EBF\uFF1B\u9010\u9879\u786E\u8BA4\u662F\u5426\u9AD8\u9891\u4E14\u5141\u8BB8\u4E3B\u52A8\u89E6\u53D1\u3002`
4652
+ );
4653
+ if (skills.unregistered.length > 0)
4654
+ recommendations.push(
4655
+ `\u6709 ${skills.unregistered.length} \u4E2A\u6280\u80FD\u65E0\u6CD5\u6620\u5C04\u5230 PGS \u6CE8\u518C\u6E90\uFF1B\u786E\u8BA4\u5B83\u4EEC\u662F\u5408\u7406\u7684\u9879\u76EE\u672C\u5730\u6280\u80FD\u8FD8\u662F\u5F85\u767B\u8BB0\u8D44\u4EA7\u3002`
4656
+ );
4500
4657
  if (skills.claudeCompatibility === "duplicate-directory")
4501
4658
  recommendations.push(
4502
4659
  "`.claude/skills` \u662F\u72EC\u7ACB\u526F\u672C\uFF1B\u5EFA\u8BAE\u94FE\u63A5\u5230 `.agents/skills`\uFF0C\u907F\u514D\u53CC\u4EFD\u6280\u80FD\u6F02\u79FB\u3002"
@@ -4545,6 +4702,11 @@ function inspectRepository(endpoint, role, secretsRoot, homeDir, expectedPackage
4545
4702
  recommendations.push("\u7F3A\u5C11 docs/governance/MANIFEST.yml\uFF1B\u6587\u6863\u6E05\u5355\u65E0\u6CD5\u8BC1\u660E\u5DF2\u540C\u6B65\u3002");
4546
4703
  if (technologyGovernance && !projectModel.projectType)
4547
4704
  recommendations.push("\u672A\u58F0\u660E\u9879\u76EE\u7C7B\u578B\uFF1B\u65E0\u6CD5\u628A\u5B9E\u9645\u6280\u672F\u4E0E\u4EA7\u54C1\u7EBF\u57FA\u7EBF\u8FDB\u884C\u6BD4\u8F83\u3002");
4705
+ for (const violation of exclusiveOwnershipViolations) {
4706
+ recommendations.push(
4707
+ `\u4ED3\u5E93\u8D8A\u754C\u62E5\u6709\u201C${violation.rule.label}\u201D\uFF1A${violation.paths.join("\u3001")}\uFF1B\u4EC5\u5141\u8BB8\u9879\u76EE\u7C7B\u578B ${violation.rule.allowedProjectTypes.join("\u3001")} \u6301\u6709\u3002`
4708
+ );
4709
+ }
4548
4710
  const missingBaseline = projectModel.baseline.filter(
4549
4711
  (technology) => !technology.detected && !hasBaselineException(projectModel, technology.id)
4550
4712
  );
@@ -4569,7 +4731,7 @@ function inspectRepository(endpoint, role, secretsRoot, homeDir, expectedPackage
4569
4731
  );
4570
4732
  if (redundancy.status === "attention")
4571
4733
  recommendations.push(
4572
- "\u53D1\u73B0\u65E7 AI \u76EE\u5F55\u6216\u5927\u578B Playwright \u7F13\u5B58\uFF1B\u4EC5\u63D0\u4F9B\u8BC1\u636E\uFF0C\u786E\u8BA4\u5F52\u5C5E\u540E\u518D\u7531\u4EBA\u5DE5\u6E05\u7406\u3002"
4734
+ "\u53D1\u73B0\u9879\u76EE\u7EA7\u65E7 AI \u76EE\u5F55\uFF1B\u4EC5\u63D0\u4F9B\u8BC1\u636E\uFF0C\u4E0E .agents/skills \u7684 SSOT \u6BD4\u5BF9\u540E\u518D\u7531\u4EBA\u5DE5\u8FC1\u79FB\u6216\u6E05\u7406\u3002"
4573
4735
  );
4574
4736
  return {
4575
4737
  id: endpoint.id,
@@ -4586,6 +4748,7 @@ function inspectRepository(endpoint, role, secretsRoot, homeDir, expectedPackage
4586
4748
  secrets,
4587
4749
  docs,
4588
4750
  projectModel,
4751
+ exclusiveOwnershipViolations,
4589
4752
  Boolean(technologyGovernance),
4590
4753
  versions,
4591
4754
  verification,
@@ -4733,8 +4896,8 @@ function hasUsableTechnologyFile(path) {
4733
4896
  return false;
4734
4897
  }
4735
4898
  }
4736
- function deriveStatus(role, entries, git, hooks, skills, hostSsot, secrets, docs, projectModel, technologyGovernanceConfigured, versions, verification, redundancy) {
4737
- if (!git.isRepository || entries.agents === "missing" || entries.claude === "dangling-symlink" || entries.gemini === "dangling-symlink" || skills.canonical.some((item) => item.kind === "dangling-symlink") || secrets.repositoryEnvFiles.some((file) => file.tracked && !file.template && !file.fixture) || hasUnsafeCentralSecretPermissions(secrets))
4899
+ function deriveStatus(role, entries, git, hooks, skills, hostSsot, secrets, docs, projectModel, exclusiveOwnershipViolations, technologyGovernanceConfigured, versions, verification, redundancy) {
4900
+ if (!git.isRepository || entries.agents === "missing" || entries.claude === "dangling-symlink" || entries.gemini === "dangling-symlink" || [...skills.automatic, ...skills.manual].some((item) => item.kind === "dangling-symlink") || exclusiveOwnershipViolations.length > 0 || secrets.repositoryEnvFiles.some((file) => file.tracked && !file.template && !file.fixture) || hasUnsafeCentralSecretPermissions(secrets))
4738
4901
  return "unhealthy";
4739
4902
  const missingBaseline = projectModel.baseline.some(
4740
4903
  (technology) => !technology.detected && !hasBaselineException(projectModel, technology.id)
@@ -4749,10 +4912,18 @@ function deriveStatus(role, entries, git, hooks, skills, hostSsot, secrets, docs
4749
4912
  const packageVersionNeedsReview = docs.packages.expected !== void 0 && !docs.packages.aligned;
4750
4913
  const routerVersionNeedsReview = !docs.routerAligned;
4751
4914
  const targetManifestMissing = role === "target" && !docs.manifest;
4752
- if (entries.agents !== "pgs-router" || entries.claude !== "agents-symlink" || hasWorkflowReminderHooks(hooks) || skills.claudeCompatibility === "duplicate-directory" || skills.claudeCompatibility === "dangling-symlink" || !hostSsot.compliant || git.branches.length > 1 || git.worktrees.length > 1 || git.dirtyPaths.length > 0 || (git.ahead ?? 0) > 0 || secretMaterializationNeedsReview || technologyGovernanceConfigured && !projectModel.projectType || missingBaseline || missingSelected || packageVersionNeedsReview || routerVersionNeedsReview || targetManifestMissing || versions.status === "attention" || verification.status === "attention" || redundancy.legacyDirectories.length > 0)
4915
+ if (entries.agents !== "pgs-router" || entries.claude !== "agents-symlink" || hasWorkflowReminderHooks(hooks) || skills.claudeCompatibility === "duplicate-directory" || skills.claudeCompatibility === "dangling-symlink" || skills.invalidEntries.length > 0 || skills.userDuplicates.length > 0 || skills.crossPlacementDuplicates.length > 0 || skills.placementDrift.length > 0 || skills.automatic.length > skills.automaticReviewThreshold || !hostSsot.compliant || git.branches.length > 1 || git.worktrees.length > 1 || git.dirtyPaths.length > 0 || (git.ahead ?? 0) > 0 || secretMaterializationNeedsReview || technologyGovernanceConfigured && !projectModel.projectType || missingBaseline || missingSelected || packageVersionNeedsReview || routerVersionNeedsReview || targetManifestMissing || versions.status === "attention" || verification.status === "attention" || redundancy.legacyDirectories.length > 0)
4753
4916
  return "attention";
4754
4917
  return "healthy";
4755
4918
  }
4919
+ function inspectExclusiveOwnership(root, endpoint, governance) {
4920
+ const projectType = endpoint.projectType;
4921
+ return (governance?.exclusiveOwnership ?? []).flatMap((rule) => {
4922
+ if (projectType && rule.allowedProjectTypes.includes(projectType)) return [];
4923
+ const paths = rule.paths.filter((path) => existsSync23(join24(root, path)));
4924
+ return paths.length > 0 ? [{ rule, paths }] : [];
4925
+ });
4926
+ }
4756
4927
  function inspectProjectModel(root, endpoint, governance) {
4757
4928
  const projectType = governance?.projectTypes.find(
4758
4929
  (candidate) => candidate.id === endpoint.projectType
@@ -4880,51 +5051,142 @@ function inspectOptionalEntry(root, filename, agentsPath) {
4880
5051
  const content = safeRead(path);
4881
5052
  return /AGENTS\.md/.test(content) && content.length < 2e3 ? "thin-adapter" : "custom";
4882
5053
  }
4883
- function inspectSkills(root, grokInspection) {
5054
+ function inspectSkills(root, grokInspection, registeredSkills, userSkills) {
4884
5055
  const lock = readJson4(join24(root, ".pro-gov/assets.lock.json"));
5056
+ const assetManifest = readJson4(join24(root, ".pro-gov/assets.json"));
4885
5057
  const managed = /* @__PURE__ */ new Set();
4886
5058
  const bundleIds = stringArray(isRecord3(lock) ? lock.bundleIds : void 0);
4887
5059
  if (isRecord3(lock) && Array.isArray(lock.assets)) {
4888
5060
  for (const asset of lock.assets) {
4889
5061
  if (!isRecord3(asset) || typeof asset.targetPath !== "string") continue;
4890
- const match = asset.targetPath.match(/^\.agents\/skills\/([^/]+)$/);
4891
- if (match) managed.add(match[1]);
5062
+ const match = asset.targetPath.match(/^\.agents\/(skills|manual-skills)\/([^/]+)$/);
5063
+ if (match) managed.add(`${match[1]}:${match[2]}`);
4892
5064
  }
4893
5065
  }
4894
- const skillRoot = join24(root, ".agents/skills");
4895
- const canonical = pathLexists(skillRoot) && safeIsDirectory(skillRoot) ? safeReadDir(skillRoot).filter((name) => !name.startsWith(".")).map((name) => {
4896
- const path = join24(skillRoot, name);
4897
- const stat = lstatSync8(path);
4898
- let kind = stat.isSymbolicLink() ? "symlink" : stat.isDirectory() ? "directory" : "file";
4899
- if (stat.isSymbolicLink()) {
4900
- try {
4901
- realpathSync4(path);
4902
- } catch {
4903
- kind = "dangling-symlink";
4904
- }
4905
- }
4906
- return { name, kind, managed: managed.has(name) };
4907
- }) : [];
4908
- const locked = isRecord3(lock) && Array.isArray(lock.assets) ? lock.assets.length : 0;
5066
+ const inspectPlacement = (placement) => {
5067
+ const directory = placement === "auto" ? "skills" : "manual-skills";
5068
+ const skillRoot = join24(root, ".agents", directory);
5069
+ if (!pathLexists(skillRoot) || !safeIsDirectory(skillRoot)) return [];
5070
+ return safeReadDir(skillRoot).filter((name) => !name.startsWith(".")).map((name) => inspectSkillItem(skillRoot, directory, name, managed, registeredSkills));
5071
+ };
5072
+ const automatic = inspectPlacement("auto");
5073
+ const manual = inspectPlacement("manual");
5074
+ const invalidEntries = [
5075
+ ...inspectInvalidSkillEntries(root, "skills"),
5076
+ ...inspectInvalidSkillEntries(root, "manual-skills")
5077
+ ];
5078
+ const userDuplicates = [...automatic, ...manual].filter((item) => skillDuplicatesUser(item, root, userSkills)).map((item) => item.name).filter((name, index, names) => names.indexOf(name) === index).sort();
5079
+ const automaticNames = new Set(automatic.map((item) => item.name));
5080
+ const crossPlacementDuplicates = manual.map((item) => item.name).filter((name) => automaticNames.has(name)).sort();
5081
+ const placementDrift = [
5082
+ ...automatic.flatMap((item) => skillPlacementDrift(item, "auto")),
5083
+ ...manual.flatMap((item) => skillPlacementDrift(item, "manual"))
5084
+ ];
5085
+ const unregistered = [
5086
+ ...automatic.flatMap(
5087
+ (item) => item.registryId || item.classification ? [] : [{ name: item.name, placement: "auto" }]
5088
+ ),
5089
+ ...manual.flatMap(
5090
+ (item) => item.registryId || item.classification ? [] : [{ name: item.name, placement: "manual" }]
5091
+ )
5092
+ ];
5093
+ const lockedSkills = isRecord3(lock) && Array.isArray(lock.assets) ? lock.assets.filter(
5094
+ (asset) => isRecord3(asset) && typeof asset.targetPath === "string" && /^\.agents\/(skills|manual-skills)\/[^/]+$/.test(asset.targetPath)
5095
+ ) : [];
5096
+ const desiredAssetIds = new Set(
5097
+ isRecord3(assetManifest) ? stringArray(assetManifest.assetIds) : []
5098
+ );
5099
+ const desired = registeredSkills.filter(
5100
+ (skill) => desiredAssetIds.has(skill.id) && skill.defaultScope === "project"
5101
+ ).length;
5102
+ const allInstalled = [...automatic, ...manual];
4909
5103
  return {
4910
- canonical,
5104
+ automatic,
5105
+ manual,
5106
+ invalidEntries,
5107
+ userDuplicates,
5108
+ crossPlacementDuplicates,
5109
+ placementDrift,
5110
+ unregistered,
5111
+ automaticReviewThreshold: 10,
4911
5112
  claudeCompatibility: inspectClaudeSkillRoot(root),
4912
5113
  bundleIds,
4913
5114
  lifecycle: {
4914
- desired: bundleIds.length,
4915
- locked,
4916
- installed: canonical.filter((item) => item.managed && item.kind !== "dangling-symlink").length,
4917
- discoverable: canonical.filter((item) => item.kind !== "dangling-symlink").length,
5115
+ desired,
5116
+ locked: lockedSkills.length,
5117
+ installed: allInstalled.filter((item) => item.managed && item.kind !== "dangling-symlink").length,
5118
+ discoverable: automatic.filter((item) => item.kind !== "dangling-symlink").length,
4918
5119
  runtime: "unobservable"
4919
5120
  },
4920
5121
  hosts: {
4921
- codexProject: canonical.filter((item) => item.kind !== "dangling-symlink").length,
5122
+ codexProject: automatic.filter((item) => item.kind !== "dangling-symlink").length,
4922
5123
  claudeCodeProject: inspectSkillRoot(join24(root, ".claude/skills")).names.length,
4923
5124
  grokNativeProject: inspectSkillRoot(join24(root, ".grok/skills")).names.length,
4924
5125
  grokEffective: grokInspection.skills
4925
5126
  }
4926
5127
  };
4927
5128
  }
5129
+ function inspectSkillItem(skillRoot, directory, name, managed, registeredSkills) {
5130
+ const path = join24(skillRoot, name);
5131
+ const stat = lstatSync8(path);
5132
+ let kind = stat.isSymbolicLink() ? "symlink" : stat.isDirectory() ? "directory" : "file";
5133
+ let realPath;
5134
+ try {
5135
+ realPath = realpathSync4(path);
5136
+ } catch {
5137
+ if (stat.isSymbolicLink()) kind = "dangling-symlink";
5138
+ }
5139
+ const registered = realPath ? registeredSkills.find((skill) => skill.sourceRealPath === realPath) : void 0;
5140
+ const classification = registered ? void 0 : realPath && isPluginPack(realPath) ? "plugin-pack" : kind === "directory" && existsSync23(join24(path, "SKILL.md")) ? "project-local" : void 0;
5141
+ return {
5142
+ name,
5143
+ kind,
5144
+ managed: managed.has(`${directory}:${name}`),
5145
+ classification,
5146
+ registryId: registered?.id,
5147
+ expectedPlacement: registered?.defaultPlacement,
5148
+ expectedScope: registered?.defaultScope
5149
+ };
5150
+ }
5151
+ function isPluginPack(path) {
5152
+ const skillsRoot = join24(path, "skills");
5153
+ return existsSync23(join24(path, ".codex-plugin/plugin.json")) && safeIsDirectory(skillsRoot) && safeReadDir(skillsRoot).some((name) => existsSync23(join24(skillsRoot, name, "SKILL.md")));
5154
+ }
5155
+ function inspectInvalidSkillEntries(root, directory) {
5156
+ const skillRoot = join24(root, ".agents", directory);
5157
+ if (!pathLexists(skillRoot) || !safeIsDirectory(skillRoot)) return [];
5158
+ return safeReadDir(skillRoot).flatMap((name) => {
5159
+ if (name === ".gitkeep") return [];
5160
+ if (name === ".DS_Store")
5161
+ return [{ path: `.agents/${directory}/${name}`, reason: "metadata-junk" }];
5162
+ if (name.startsWith("."))
5163
+ return [{ path: `.agents/${directory}/${name}`, reason: "unexpected-file" }];
5164
+ const path = join24(skillRoot, name);
5165
+ return !lstatSync8(path).isDirectory() && !lstatSync8(path).isSymbolicLink() ? [{ path: `.agents/${directory}/${name}`, reason: "unexpected-file" }] : [];
5166
+ });
5167
+ }
5168
+ function skillDuplicatesUser(item, root, userSkills) {
5169
+ if (userSkills.names.has(item.name)) return true;
5170
+ const automatic = join24(root, ".agents/skills", item.name);
5171
+ const manual = join24(root, ".agents/manual-skills", item.name);
5172
+ const realPath = safeRealpath(pathLexists(automatic) ? automatic : manual);
5173
+ return realPath ? userSkills.realPaths.has(realPath) : false;
5174
+ }
5175
+ function skillPlacementDrift(item, actualPlacement) {
5176
+ if (!item.registryId || !item.expectedPlacement) return [];
5177
+ if (item.expectedScope === "user" || item.expectedPlacement !== actualPlacement)
5178
+ return [
5179
+ {
5180
+ name: item.name,
5181
+ registryId: item.registryId,
5182
+ actualPlacement,
5183
+ expectedPlacement: item.expectedPlacement,
5184
+ expectedScope: item.expectedScope ?? "project",
5185
+ reason: item.expectedScope === "user" ? "scope" : "placement"
5186
+ }
5187
+ ];
5188
+ return [];
5189
+ }
4928
5190
  function inspectClaudeSkillRoot(root) {
4929
5191
  const path = join24(root, ".claude/skills");
4930
5192
  if (!pathLexists(path)) return "missing";
@@ -5048,9 +5310,9 @@ function inspectSecretsRoot(path) {
5048
5310
  return existsSync23(path) ? { path, exists: true, mode: modeString(statSync5(path).mode) } : { path, exists: false };
5049
5311
  }
5050
5312
  function inspectHostEnvironment(homeDir, grokVersion, repositoryPaths, devspaceSettings) {
5051
- const codexConfig = join24(homeDir, ".codex/config.toml");
5052
- const claudeConfig = join24(homeDir, ".claude.json");
5053
- const grokConfig = join24(homeDir, ".grok/config.toml");
5313
+ const codexConfig = join24(homeDir, MCP_DISCOVERY_PATHS.user.codex);
5314
+ const claudeConfig = join24(homeDir, MCP_DISCOVERY_PATHS.user.claudeCode);
5315
+ const grokConfig = join24(homeDir, MCP_DISCOVERY_PATHS.user.grok);
5054
5316
  const hostEnvironment = {
5055
5317
  mcp: {
5056
5318
  codexUser: { path: codexConfig, names: tomlMcpNames(codexConfig) },
@@ -5069,7 +5331,8 @@ function inspectHostEnvironment(homeDir, grokVersion, repositoryPaths, devspaceS
5069
5331
  available: grokVersion !== void 0,
5070
5332
  ...grokVersion ? { version: grokVersion } : {},
5071
5333
  inspectionCommand: "grok inspect --json"
5072
- }
5334
+ },
5335
+ redundancy: inspectHostRedundancy({ homeDir })
5073
5336
  };
5074
5337
  if (devspaceSettings) {
5075
5338
  hostEnvironment.devspace = inspectDevSpaceHealth({
@@ -5197,7 +5460,7 @@ function inspectSkillRoot(path) {
5197
5460
  }
5198
5461
  function claudeProjectLocalMcpNames(homeDir, root) {
5199
5462
  if (!homeDir) return [];
5200
- const value = readJson4(join24(homeDir, ".claude.json"));
5463
+ const value = readJson4(join24(homeDir, MCP_DISCOVERY_PATHS.user.claudeCode));
5201
5464
  if (!isRecord3(value) || !isRecord3(value.projects)) return [];
5202
5465
  const candidates = new Set(
5203
5466
  [resolve5(root), safeRealpath(root)].filter((path) => Boolean(path))
@@ -5246,7 +5509,9 @@ function inspectGrokProject(root, homeDir, grokVersion) {
5246
5509
  })
5247
5510
  );
5248
5511
  if (!isRecord3(value)) return empty("failed");
5249
- const userClaudeNames = new Set(jsonObjectKeys(join24(homeDir, ".claude.json"), "mcpServers"));
5512
+ const userClaudeNames = new Set(
5513
+ jsonObjectKeys(join24(homeDir, MCP_DISCOVERY_PATHS.user.claudeCode), "mcpServers")
5514
+ );
5250
5515
  const localClaudeNames = new Set(claudeProjectLocalMcpNames(homeDir, root));
5251
5516
  const effectiveMcp = Array.isArray(value.mcpServers) ? value.mcpServers.flatMap((item) => {
5252
5517
  if (!isRecord3(item) || typeof item.name !== "string") return [];
@@ -5315,9 +5580,10 @@ function inferGrokMcpScope(name, sourceType, sourcePath, root, homeDir, userClau
5315
5580
  }
5316
5581
  const resolvedSource = safeRealpath(sourcePath) ?? resolve5(sourcePath);
5317
5582
  const resolvedRoot = safeRealpath(root) ?? resolve5(root);
5318
- if (resolvedSource === join24(resolvedRoot, ".mcp.json")) return "project-shared";
5583
+ if (resolvedSource === join24(resolvedRoot, MCP_DISCOVERY_PATHS.project.claudeCodeShared))
5584
+ return "project-shared";
5319
5585
  if (resolvedSource.startsWith(resolvedRoot + sep)) return "project";
5320
- if (homeDir && resolvedSource === join24(resolve5(homeDir), ".claude.json")) {
5586
+ if (homeDir && resolvedSource === join24(resolve5(homeDir), MCP_DISCOVERY_PATHS.user.claudeCode)) {
5321
5587
  if (localClaudeNames.has(name)) return "project-local";
5322
5588
  if (userClaudeNames.has(name)) return "user";
5323
5589
  }
@@ -5406,7 +5672,11 @@ function recordOrEmpty(value) {
5406
5672
  return isRecord3(value) ? value : {};
5407
5673
  }
5408
5674
  function inspectSkillRegistry(executionEngineRoot) {
5409
- if (!executionEngineRoot) return { source: 0, registered: 0, bundled: 0, bundles: 0 };
5675
+ if (!executionEngineRoot)
5676
+ return {
5677
+ health: { source: 0, registered: 0, bundled: 0, bundles: 0 },
5678
+ skills: []
5679
+ };
5410
5680
  const agentAssetsRoot = join24(executionEngineRoot, "agent-assets");
5411
5681
  const registry = readJson4(join24(agentAssetsRoot, "registry.json"));
5412
5682
  const assets = isRecord3(registry) && Array.isArray(registry.assets) ? registry.assets : [];
@@ -5428,14 +5698,40 @@ function inspectSkillRegistry(executionEngineRoot) {
5428
5698
  0
5429
5699
  );
5430
5700
  return {
5431
- source,
5432
- registered: registeredSkills.length,
5433
- bundled: registeredSkills.filter(
5434
- (asset) => isRecord3(asset) && typeof asset.id === "string" && bundledIds.has(asset.id)
5435
- ).length,
5436
- bundles: bundleFiles.length
5701
+ health: {
5702
+ source,
5703
+ registered: registeredSkills.length,
5704
+ bundled: registeredSkills.filter(
5705
+ (asset) => isRecord3(asset) && typeof asset.id === "string" && bundledIds.has(asset.id)
5706
+ ).length,
5707
+ bundles: bundleFiles.length
5708
+ },
5709
+ skills: registeredSkills.flatMap((asset) => {
5710
+ if (!isRecord3(asset) || typeof asset.id !== "string" || typeof asset.sourcePath !== "string" || asset.defaultPlacement !== "auto" && asset.defaultPlacement !== "manual")
5711
+ return [];
5712
+ return [
5713
+ {
5714
+ id: asset.id,
5715
+ sourceRealPath: safeRealpath(join24(agentAssetsRoot, asset.sourcePath)),
5716
+ defaultPlacement: asset.defaultPlacement,
5717
+ defaultScope: asset.defaultScope === "user" ? "user" : "project"
5718
+ }
5719
+ ];
5720
+ })
5437
5721
  };
5438
5722
  }
5723
+ function inspectUserSkillEvidence(root) {
5724
+ const names = /* @__PURE__ */ new Set();
5725
+ const realPaths = /* @__PURE__ */ new Set();
5726
+ if (!pathLexists(root) || !safeIsDirectory(root)) return { names, realPaths };
5727
+ for (const name of safeReadDir(root)) {
5728
+ if (name.startsWith(".")) continue;
5729
+ names.add(name);
5730
+ const realPath = safeRealpath(join24(root, name));
5731
+ if (realPath) realPaths.add(realPath);
5732
+ }
5733
+ return { names, realPaths };
5734
+ }
5439
5735
  function jsonObjectKeys(path, key) {
5440
5736
  const value = readJson4(path);
5441
5737
  if (!isRecord3(value) || !isRecord3(value[key])) return [];
@@ -5561,7 +5857,12 @@ function runPortfolioAiHealth(args) {
5561
5857
  return 1;
5562
5858
  }
5563
5859
  const targetId = options.value.targetId && options.value.targetId !== "all" ? options.value.targetId : void 0;
5564
- if (targetId && !loaded.manifest.targets.some((target) => target.id === targetId)) {
5860
+ const allRepositoryIds = [
5861
+ loaded.manifest.controlPlane?.id,
5862
+ loaded.manifest.executionEngine?.id,
5863
+ ...loaded.manifest.targets.map((target) => target.id)
5864
+ ].filter((id) => Boolean(id));
5865
+ if (targetId && !allRepositoryIds.includes(targetId)) {
5565
5866
  console.error(`Unknown portfolio target: ${targetId}`);
5566
5867
  return 1;
5567
5868
  }
@@ -5570,11 +5871,6 @@ function runPortfolioAiHealth(args) {
5570
5871
  secretsRoot: options.value.secretsRoot,
5571
5872
  targetId
5572
5873
  });
5573
- const allRepositoryIds = [
5574
- loaded.manifest.controlPlane?.id,
5575
- loaded.manifest.executionEngine?.id,
5576
- ...loaded.manifest.targets.map((target) => target.id)
5577
- ].filter((id) => Boolean(id));
5578
5874
  const existing = targetId ? readExistingAiHealthReport(options.value.outDir, loaded.manifest.portfolioId) : void 0;
5579
5875
  const report = targetId ? mergePortfolioAiHealthReport(existing, latest, allRepositoryIds) : latest;
5580
5876
  const written = writePortfolioAiHealthReport(report, options.value.outDir);
@@ -6040,7 +6336,7 @@ var COMMANDS = [
6040
6336
  "assets list [--json] [--visibility public|private|third-party|all]",
6041
6337
  "assets discover [--target <path>] [--json]",
6042
6338
  "assets recommend [--target <path>] [--json]",
6043
- "assets plan --bundle <bundle-id> [--target <path>] [--json]",
6339
+ "assets plan [--bundle <bundle-id>] [--asset <asset-id>] [--target <path>] [--json]",
6044
6340
  "assets apply --plan <path>",
6045
6341
  "assets check [--target <path>] [--strict-registry] [--json]",
6046
6342
  "assets public-check [--public-root <path>] [--private-root <path>] [--json]",