@pieai/pro-gov 0.7.1 → 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
@@ -66,7 +66,15 @@ function loadAgentAssetBundles(agentAssetsDir) {
66
66
  // src/asset-npx/maintenance.ts
67
67
  import { createHash } from "node:crypto";
68
68
  import { spawnSync } from "node:child_process";
69
- import { cpSync, existsSync as existsSync3, mkdirSync, mkdtempSync, readdirSync as readdirSync3, readFileSync as readFileSync2, statSync } from "node:fs";
69
+ import {
70
+ cpSync,
71
+ existsSync as existsSync3,
72
+ mkdirSync,
73
+ mkdtempSync,
74
+ readdirSync as readdirSync3,
75
+ readFileSync as readFileSync2,
76
+ statSync
77
+ } from "node:fs";
70
78
  import { join as join3, relative as relative2 } from "node:path";
71
79
  import { tmpdir } from "node:os";
72
80
  function createNpxSkillsMaintenancePlan(options) {
@@ -93,7 +101,6 @@ function createNpxSkillsMaintenancePlan(options) {
93
101
  if (options.operation === "update") {
94
102
  assertNoReportedPartialUpdateFailure(result.stdout, result.stderr);
95
103
  }
96
- assertNoDeprecatedMattSkills(tempRoot);
97
104
  const after = snapshotFiles(tempRoot);
98
105
  const changes = diffSnapshots(before, after);
99
106
  return {
@@ -111,23 +118,14 @@ function createNpxSkillsMaintenancePlan(options) {
111
118
  };
112
119
  }
113
120
  function assertNoReportedPartialUpdateFailure(stdout, stderr) {
121
+ const ansiEscape = new RegExp(`${String.fromCharCode(27)}\\[[0-?]*[ -/]*[@-~]`, "g");
114
122
  const output = `${stdout}
115
- ${stderr}`.replace(/\u001B\[[0-?]*[ -/]*[@-~]/g, "");
123
+ ${stderr}`.replace(ansiEscape, "");
116
124
  const failure = output.match(/Failed to update\s+\d+\s+skill\(s\)/i);
117
125
  if (failure) {
118
126
  throw new Error(`npx skills update reported a partial failure: ${failure[0]}`);
119
127
  }
120
128
  }
121
- function assertNoDeprecatedMattSkills(npxRoot) {
122
- const lockPath = join3(npxRoot, "skills-lock.json");
123
- const lock = JSON.parse(readFileSync2(lockPath, "utf8"));
124
- const deprecated = Object.entries(lock.skills ?? {}).filter(
125
- ([, entry]) => entry.source === "mattpocock/skills" && entry.skillPath?.startsWith("skills/deprecated/")
126
- ).map(([name]) => name).sort();
127
- if (deprecated.length > 0) {
128
- throw new Error(`npx skills plan contains deprecated mattpocock skills: ${deprecated.join(", ")}`);
129
- }
130
- }
131
129
  function assertNativeNpxRoot(npxRoot) {
132
130
  if (!existsSync3(join3(npxRoot, "skills-lock.json"))) {
133
131
  throw new Error(`npx skills root is missing skills-lock.json: ${npxRoot}`);
@@ -579,10 +577,10 @@ function createAssetInstallPlan(options) {
579
577
  const placement = options.placement ?? "registry";
580
578
  const assetsById = new Map(options.registry.assets.map((asset) => [asset.id, asset]));
581
579
  const bundlesById = new Map(options.bundles.map((bundle) => [bundle.id, bundle]));
582
- const assetIds = resolveBundleAssetIds(options.bundleIds, bundlesById);
580
+ const assetIds = resolveAssetIds(options.bundleIds, options.assetIds ?? [], bundlesById);
583
581
  const assets = assetIds.map((assetId) => {
584
582
  const asset = assetsById.get(assetId);
585
- if (!asset) throw new Error(`Unknown asset id in bundle: ${assetId}`);
583
+ if (!asset) throw new Error(`Unknown asset id in install plan: ${assetId}`);
586
584
  if (asset.kind === "skill" && !asset.hosts.includes(options.host)) {
587
585
  throw new Error(`Asset ${assetId} does not support host ${options.host}`);
588
586
  }
@@ -663,8 +661,8 @@ function createAssetInstallPlan(options) {
663
661
  ]
664
662
  };
665
663
  }
666
- function resolveBundleAssetIds(bundleIds, bundlesById) {
667
- const ids = /* @__PURE__ */ new Set();
664
+ function resolveAssetIds(bundleIds, explicitAssetIds, bundlesById) {
665
+ const ids = new Set(explicitAssetIds);
668
666
  for (const bundleId of bundleIds) {
669
667
  const bundle = bundlesById.get(bundleId);
670
668
  if (!bundle) throw new Error(`Unknown bundle id: ${bundleId}`);
@@ -1439,6 +1437,7 @@ function runAssetsPlan(args) {
1439
1437
  registry: loaded.registry,
1440
1438
  bundles: loadAgentAssetBundles(loaded.agentAssetsDir),
1441
1439
  bundleIds: options.value.bundleIds,
1440
+ assetIds: options.value.assetIds,
1442
1441
  host: options.value.host,
1443
1442
  placement: options.value.placement
1444
1443
  });
@@ -1521,6 +1520,7 @@ function parsePlanOptions(args) {
1521
1520
  targetDir: process.cwd(),
1522
1521
  json: false,
1523
1522
  bundleIds: [],
1523
+ assetIds: [],
1524
1524
  host: "codex"
1525
1525
  };
1526
1526
  for (let index = 0; index < args.length; index += 1) {
@@ -1535,6 +1535,11 @@ function parsePlanOptions(args) {
1535
1535
  if (!bundleId) return { ok: false, error: "Expected --bundle <bundle-id>" };
1536
1536
  options.bundleIds.push(bundleId);
1537
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;
1538
1543
  } else if (arg === "--host") {
1539
1544
  const host = args[index + 1];
1540
1545
  if (!isHost(host)) {
@@ -1566,8 +1571,8 @@ function parsePlanOptions(args) {
1566
1571
  return { ok: false, error: `Unknown assets plan option: ${arg}` };
1567
1572
  }
1568
1573
  }
1569
- if (options.bundleIds.length === 0) {
1570
- 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>" };
1571
1576
  }
1572
1577
  return { ok: true, value: options };
1573
1578
  }
@@ -1713,7 +1718,7 @@ function printUsage() {
1713
1718
  console.error(" pro-gov assets list [--registry] [--json] [--visibility public|private|third-party|all]");
1714
1719
  console.error(" pro-gov assets discover [--target <path>] [--json]");
1715
1720
  console.error(" pro-gov assets recommend [--target <path>] [--json]");
1716
- 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]");
1717
1722
  console.error(" pro-gov assets apply --plan <path>");
1718
1723
  console.error(" pro-gov assets check [--target <path>] [--json]");
1719
1724
  console.error(" pro-gov assets public-check [--public-root <path>] [--private-root <path>] [--json]");
@@ -2819,13 +2824,30 @@ import { homedir } from "node:os";
2819
2824
  import { join as join18 } from "node:path";
2820
2825
  var DEFAULT_CACHE_THRESHOLD_BYTES = 1e9;
2821
2826
  var MAX_CACHE_ENTRIES = 2e4;
2822
- function inspectProjectRedundancy(root, options = {}) {
2823
- const legacyDirectories = inspectLegacyDirectories(root);
2827
+ function inspectHostRedundancy(options = {}) {
2824
2828
  const homeDir = options.homeDir ?? homedir();
2825
- const cachePaths = getPlaywrightCachePaths(homeDir, options.playwrightBrowsersPath ?? process.env.PLAYWRIGHT_BROWSERS_PATH);
2826
- 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));
2827
2834
  const cacheThresholdBytes = options.cacheThresholdBytes ?? DEFAULT_CACHE_THRESHOLD_BYTES;
2828
- 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";
2829
2851
  return { status, legacyDirectories, playwrightCaches };
2830
2852
  }
2831
2853
  function inspectLegacyDirectories(root) {
@@ -2833,13 +2855,15 @@ function inspectLegacyDirectories(root) {
2833
2855
  const path = join18(root, relativePath);
2834
2856
  if (!existsSync16(path)) return [];
2835
2857
  const stats = collectDirectoryStats(path);
2836
- return [{
2837
- path: relativePath,
2838
- kind: "legacy-ai-directory",
2839
- fileCount: stats.fileCount,
2840
- bytes: stats.bytes,
2841
- 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"
2842
- }];
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
+ ];
2843
2867
  }
2844
2868
  function getPlaywrightCachePaths(homeDir, configuredPath) {
2845
2869
  const candidates = [
@@ -2850,18 +2874,40 @@ function getPlaywrightCachePaths(homeDir, configuredPath) {
2850
2874
  ].filter((path) => Boolean(path));
2851
2875
  return [...new Set(candidates)];
2852
2876
  }
2853
- function inspectPlaywrightCache(path) {
2877
+ function inspectPlaywrightCache(path, cache) {
2878
+ const cached = cache?.get(path);
2879
+ if (cached) return cached;
2854
2880
  if (!existsSync16(path)) {
2855
- 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;
2856
2891
  }
2857
2892
  const stats = collectDirectoryStats(path);
2858
2893
  let revisionCount = 0;
2859
2894
  try {
2860
- revisionCount = readdirSync7(path, { withFileTypes: true }).filter((entry) => entry.isDirectory()).length;
2895
+ revisionCount = readdirSync7(path, { withFileTypes: true }).filter(
2896
+ (entry) => entry.isDirectory()
2897
+ ).length;
2861
2898
  } catch {
2862
2899
  revisionCount = 0;
2863
2900
  }
2864
- 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;
2865
2911
  }
2866
2912
  function collectDirectoryStats(root) {
2867
2913
  let fileCount = 0;
@@ -3399,7 +3445,7 @@ function validateTechnologyGovernance(value, issues) {
3399
3445
  issues.push({ type: "invalid-field", field: "technologyGovernance", message: "Portfolio technologyGovernance must be an object." });
3400
3446
  return { technologies, projectTypes };
3401
3447
  }
3402
- validateAllowedFields(value, "technologyGovernance", ["strategySource", "versionPolicy", "technologies", "projectTypes"], issues);
3448
+ validateAllowedFields(value, "technologyGovernance", ["strategySource", "versionPolicy", "exclusiveOwnership", "technologies", "projectTypes"], issues);
3403
3449
  if (value.strategySource !== void 0 && (typeof value.strategySource !== "string" || value.strategySource.length === 0)) {
3404
3450
  issues.push({ type: "invalid-field", field: "technologyGovernance.strategySource", message: "Technology strategySource must be a non-empty string." });
3405
3451
  }
@@ -3462,9 +3508,65 @@ function validateTechnologyGovernance(value, issues) {
3462
3508
  if (!technologies.has(technology)) issues.push({ type: "invalid-field", field: "technologyGovernance.projectTypes", message: `Project type ${projectType.id} references unknown technology: ${technology}` });
3463
3509
  }
3464
3510
  }
3511
+ validateExclusiveOwnership(value.exclusiveOwnership, projectTypes, issues);
3465
3512
  validateVersionPolicy(value.versionPolicy, projectTypes, issues);
3466
3513
  return { technologies, projectTypes };
3467
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
+ }
3468
3570
  function validateVersionPolicy(value, projectTypes, issues) {
3469
3571
  if (value === void 0) return;
3470
3572
  if (!isRecord(value)) {
@@ -3735,7 +3837,7 @@ import { spawnSync as spawnSync6 } from "node:child_process";
3735
3837
  import { existsSync as existsSync22, readFileSync as readFileSync16 } from "node:fs";
3736
3838
  import { createRequire as createRequire2 } from "node:module";
3737
3839
  import { homedir as homedir3 } from "node:os";
3738
- import { dirname as dirname12, join as join23 } from "node:path";
3840
+ import { dirname as dirname13, join as join23 } from "node:path";
3739
3841
  import { fileURLToPath as fileURLToPath3 } from "node:url";
3740
3842
 
3741
3843
  // src/host-tooling/inventory.ts
@@ -3909,26 +4011,50 @@ function pathIsSymlink(path) {
3909
4011
 
3910
4012
  // src/portfolio/version-policy.ts
3911
4013
  import { spawnSync as spawnSync5 } from "node:child_process";
3912
- import { existsSync as existsSync21, readFileSync as readFileSync15 } from "node:fs";
3913
- import { join as join22 } from "node:path";
4014
+ import { existsSync as existsSync21, readFileSync as readFileSync15, readdirSync as readdirSync9 } from "node:fs";
4015
+ import { dirname as dirname12, join as join22 } from "node:path";
3914
4016
  function inspectVersionPolicy(root, policy, projectType) {
3915
4017
  if (!policy) return { status: "compliant", packages: [], runtimes: [], attentionCount: 0 };
3916
- const packageJson = readJson2(join22(root, "package.json"));
4018
+ const packageManifests = collectPackageManifests(root);
4019
+ const packageJson = packageManifests.find(
4020
+ (manifest) => manifest.path === "package.json"
4021
+ )?.packageJson;
3917
4022
  const packageManager = policy.packageManager ? inspectPackageManager(packageJson, policy.packageManager.name, policy.packageManager.version) : void 0;
3918
4023
  const runtime = policy.runtime ? inspectRuntime(policy.runtime.name, policy.runtime.version) : void 0;
3919
4024
  const runtimes = (policy.runtimes ?? []).map((requirement) => {
3920
4025
  if (requirement.appliesTo && (!projectType || !requirement.appliesTo.includes(projectType))) {
3921
- return { name: requirement.name, expected: requirement.version, status: "not-applicable" };
4026
+ return {
4027
+ name: requirement.name,
4028
+ expected: requirement.version,
4029
+ status: "not-applicable"
4030
+ };
3922
4031
  }
3923
4032
  return inspectRuntime(requirement.name, requirement.version);
3924
4033
  });
3925
4034
  const packages = policy.packages.map((requirement) => {
3926
4035
  if (requirement.appliesTo && (!projectType || !requirement.appliesTo.includes(projectType))) {
3927
- return { name: requirement.name, expected: requirement.version, status: "not-applicable" };
4036
+ return {
4037
+ name: requirement.name,
4038
+ expected: requirement.version,
4039
+ status: "not-applicable"
4040
+ };
3928
4041
  }
3929
- const declared = findDeclaredVersion(packageJson, requirement.name);
3930
- const installed = readInstalledVersion(root, requirement.name);
3931
- const status = declared === void 0 ? requirement.appliesTo ? "missing" : "not-applicable" : declared !== requirement.version ? "drift" : installed !== requirement.version ? installed === void 0 ? "missing" : "drift" : "compliant";
4042
+ const declarations = packageManifests.map((manifest) => ({
4043
+ manifest,
4044
+ declared: findDeclaredVersion(manifest.packageJson, requirement.name)
4045
+ })).filter(
4046
+ (item) => item.declared !== void 0
4047
+ );
4048
+ const declaredValues = unique(declarations.map((item) => item.declared));
4049
+ const installedValues = unique(
4050
+ declarations.map((item) => readInstalledVersion(root, requirement.name, item.manifest.directory)).filter((value) => value !== void 0)
4051
+ );
4052
+ const hasMissingInstall = declarations.some(
4053
+ (item) => readInstalledVersion(root, requirement.name, item.manifest.directory) === void 0
4054
+ );
4055
+ const declared = declaredValues.length ? declaredValues.join(" \xB7 ") : void 0;
4056
+ const installed = installedValues.length ? installedValues.join(" \xB7 ") : void 0;
4057
+ const status = declarations.length === 0 ? requirement.appliesTo ? "missing" : "not-applicable" : declarations.some((item) => item.declared !== requirement.version) ? "drift" : hasMissingInstall ? "missing" : installedValues.some((value) => value !== requirement.version) ? "drift" : "compliant";
3932
4058
  return {
3933
4059
  name: requirement.name,
3934
4060
  expected: requirement.version,
@@ -3938,8 +4064,12 @@ function inspectVersionPolicy(root, policy, projectType) {
3938
4064
  status
3939
4065
  };
3940
4066
  });
3941
- const all = [packageManager, runtime, ...runtimes, ...packages].filter((item) => item !== void 0);
3942
- const attentionCount = all.filter((item) => item.status !== "compliant" && item.status !== "not-applicable").length;
4067
+ const all = [packageManager, runtime, ...runtimes, ...packages].filter(
4068
+ (item) => item !== void 0
4069
+ );
4070
+ const attentionCount = all.filter(
4071
+ (item) => item.status !== "compliant" && item.status !== "not-applicable"
4072
+ ).length;
3943
4073
  return {
3944
4074
  status: attentionCount === 0 ? "compliant" : "attention",
3945
4075
  packageManager,
@@ -3977,15 +4107,81 @@ function readRuntimeVersion(name) {
3977
4107
  return /^deno\s+(\d+\.\d+\.\d+)/m.exec(result.stdout)?.[1];
3978
4108
  }
3979
4109
  function findDeclaredVersion(packageJson, name) {
3980
- for (const section of ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]) {
4110
+ for (const section of [
4111
+ "dependencies",
4112
+ "devDependencies",
4113
+ "peerDependencies",
4114
+ "optionalDependencies"
4115
+ ]) {
3981
4116
  const value = packageJson?.[section]?.[name];
3982
4117
  if (typeof value === "string") return value;
3983
4118
  }
3984
4119
  return void 0;
3985
4120
  }
3986
- function readInstalledVersion(root, name) {
3987
- const packageJson = readJson2(join22(root, "node_modules", name, "package.json"));
3988
- return typeof packageJson?.version === "string" ? packageJson.version : void 0;
4121
+ function readInstalledVersion(root, name, fromDirectory = root) {
4122
+ let current = fromDirectory;
4123
+ while (true) {
4124
+ const packageJson = readJson2(join22(current, "node_modules", name, "package.json"));
4125
+ if (typeof packageJson?.version === "string") return packageJson.version;
4126
+ if (current === root) return void 0;
4127
+ const parent = dirname12(current);
4128
+ if (parent === current) return void 0;
4129
+ current = parent;
4130
+ }
4131
+ }
4132
+ function collectPackageManifests(root) {
4133
+ const manifests = [];
4134
+ const ignored = /* @__PURE__ */ new Set([
4135
+ ".git",
4136
+ ".next",
4137
+ ".nuxt",
4138
+ ".output",
4139
+ ".pnpm",
4140
+ ".turbo",
4141
+ ".vercel",
4142
+ ".worktrees",
4143
+ "build",
4144
+ "coverage",
4145
+ "dist",
4146
+ "examples",
4147
+ "fixtures",
4148
+ "node_modules",
4149
+ "out",
4150
+ "target",
4151
+ "templates",
4152
+ "tmp",
4153
+ ".cache",
4154
+ ".pnpm-store",
4155
+ ".tmp-repos"
4156
+ ]);
4157
+ const visit = (directory, depth) => {
4158
+ if (depth > 6) return;
4159
+ let entries;
4160
+ try {
4161
+ entries = readdirSync9(directory, { withFileTypes: true });
4162
+ } catch {
4163
+ return;
4164
+ }
4165
+ for (const entry of entries) {
4166
+ const path = join22(directory, entry.name);
4167
+ if (entry.isFile() && entry.name === "package.json") {
4168
+ const packageJson = readJson2(path);
4169
+ if (packageJson)
4170
+ manifests.push({
4171
+ path: path.slice(root.length + 1) || "package.json",
4172
+ directory,
4173
+ packageJson
4174
+ });
4175
+ } else if (entry.isDirectory() && !ignored.has(entry.name)) {
4176
+ visit(path, depth + 1);
4177
+ }
4178
+ }
4179
+ };
4180
+ visit(root, 0);
4181
+ return manifests.sort((a, b) => a.path.localeCompare(b.path));
4182
+ }
4183
+ function unique(values) {
4184
+ return [...new Set(values)];
3989
4185
  }
3990
4186
  function readJson2(path) {
3991
4187
  if (!existsSync21(path)) return void 0;
@@ -4157,11 +4353,11 @@ function getExpectedPackageVersions() {
4157
4353
  };
4158
4354
  }
4159
4355
  function findOwnPackageJson() {
4160
- let current = dirname12(fileURLToPath3(import.meta.url));
4356
+ let current = dirname13(fileURLToPath3(import.meta.url));
4161
4357
  for (let depth = 0; depth < 5; depth += 1) {
4162
4358
  const candidate = join23(current, "package.json");
4163
4359
  if (existsSync22(candidate)) return candidate;
4164
- current = dirname12(current);
4360
+ current = dirname13(current);
4165
4361
  }
4166
4362
  return "";
4167
4363
  }
@@ -4191,40 +4387,64 @@ import {
4191
4387
  lstatSync as lstatSync8,
4192
4388
  mkdirSync as mkdirSync8,
4193
4389
  readFileSync as readFileSync17,
4194
- readdirSync as readdirSync9,
4390
+ readdirSync as readdirSync10,
4195
4391
  realpathSync as realpathSync4,
4196
4392
  statSync as statSync5,
4197
4393
  writeFileSync as writeFileSync7
4198
4394
  } from "node:fs";
4199
4395
  import { homedir as homedir4 } from "node:os";
4200
- import { dirname as dirname13, join as join24, relative as relative8, resolve as resolve5, sep } from "node:path";
4396
+ import { dirname as dirname14, join as join24, relative as relative8, resolve as resolve5, sep } from "node:path";
4201
4397
  import { fileURLToPath as fileURLToPath4 } from "node:url";
4202
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
+ };
4203
4411
  function inspectPortfolioAiHealth(options) {
4204
4412
  const allEndpoints = collectEndpoints(options.manifest);
4205
4413
  const endpoints = options.targetId && options.targetId !== "all" ? allEndpoints.filter(({ endpoint }) => endpoint.id === options.targetId) : allEndpoints;
4206
4414
  if (options.targetId && options.targetId !== "all" && endpoints.length === 0) {
4207
4415
  throw new Error(`Unknown portfolio target: ${options.targetId}`);
4208
4416
  }
4209
- const secretsRoot = options.secretsRoot ?? join24(dirname13(options.manifest.controlPlane?.path ?? allEndpoints[0]?.endpoint.path ?? process.cwd()), ".secrets");
4417
+ const secretsRoot = options.secretsRoot ?? join24(
4418
+ dirname14(
4419
+ options.manifest.controlPlane?.path ?? allEndpoints[0]?.endpoint.path ?? process.cwd()
4420
+ ),
4421
+ ".secrets"
4422
+ );
4210
4423
  const homeDir = options.homeDir ?? process.env.HOME ?? homedir4();
4211
4424
  const grokVersion = commandVersion("grok");
4212
4425
  const executionEngineRoot = options.manifest.executionEngine?.path;
4213
4426
  const skillRegistry = inspectSkillRegistry(executionEngineRoot);
4214
- const expectedPackageVersion = packageVersion(join24(executionEngineRoot ?? "", "packages/pro-gov/package.json"));
4215
- const repositories = endpoints.map(({ endpoint, role }) => inspectRepository(
4216
- endpoint,
4217
- role,
4218
- secretsRoot,
4219
- homeDir,
4220
- expectedPackageVersion,
4221
- options.manifest.technologyGovernance,
4222
- grokVersion
4223
- ));
4427
+ const userSkills = inspectUserSkillEvidence(join24(homeDir, ".agents/skills"));
4428
+ const expectedPackageVersion = packageVersion(
4429
+ join24(executionEngineRoot ?? "", "packages/pro-gov/package.json")
4430
+ );
4431
+ const repositories = endpoints.map(
4432
+ ({ endpoint, role }) => inspectRepository(
4433
+ endpoint,
4434
+ role,
4435
+ secretsRoot,
4436
+ homeDir,
4437
+ expectedPackageVersion,
4438
+ options.manifest.technologyGovernance,
4439
+ grokVersion,
4440
+ skillRegistry.skills,
4441
+ userSkills
4442
+ )
4443
+ );
4224
4444
  const summary = { healthy: 0, attention: 0, unhealthy: 0 };
4225
4445
  for (const repository of repositories) summary[repository.status] += 1;
4226
4446
  return {
4227
- schemaVersion: 5,
4447
+ schemaVersion: 6,
4228
4448
  portfolioId: options.manifest.portfolioId,
4229
4449
  generatedAt: options.generatedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
4230
4450
  coverage: {
@@ -4241,10 +4461,12 @@ function inspectPortfolioAiHealth(options) {
4241
4461
  allEndpoints.map(({ endpoint }) => endpoint.path),
4242
4462
  options.manifest.specialistChecks?.devspace
4243
4463
  ),
4244
- skillRegistry,
4464
+ skillRegistry: skillRegistry.health,
4245
4465
  technologyGovernance: {
4246
4466
  strategySource: options.manifest.technologyGovernance?.strategySource,
4247
4467
  versionPolicy: options.manifest.technologyGovernance?.versionPolicy,
4468
+ catalog: options.manifest.technologyGovernance?.technologies ?? [],
4469
+ matrix: buildTechnologyMatrix(options.manifest.technologyGovernance, repositories),
4248
4470
  projectTypes: options.manifest.technologyGovernance?.projectTypes.length ?? 0,
4249
4471
  technologies: options.manifest.technologyGovernance?.technologies.length ?? 0
4250
4472
  },
@@ -4254,7 +4476,8 @@ function inspectPortfolioAiHealth(options) {
4254
4476
  }
4255
4477
  function mergePortfolioAiHealthReport(existing, latest, allRepositoryIds) {
4256
4478
  const repositoriesById = /* @__PURE__ */ new Map();
4257
- for (const repository of existing?.repositories ?? []) repositoriesById.set(repository.id, repository);
4479
+ for (const repository of existing?.repositories ?? [])
4480
+ repositoriesById.set(repository.id, repository);
4258
4481
  for (const repository of latest.repositories) repositoriesById.set(repository.id, repository);
4259
4482
  const repositories = allRepositoryIds.map((id) => repositoriesById.get(id)).filter((repository) => repository !== void 0);
4260
4483
  const coveredIds = /* @__PURE__ */ new Set([
@@ -4265,8 +4488,20 @@ function mergePortfolioAiHealthReport(existing, latest, allRepositoryIds) {
4265
4488
  const summary = { healthy: 0, attention: 0, unhealthy: 0 };
4266
4489
  for (const repository of repositories) summary[repository.status] += 1;
4267
4490
  const complete = coveredRepositoryIds.length === allRepositoryIds.length;
4491
+ const technologyGovernance = (latest.technologyGovernance.catalog?.length ?? 0) > 0 ? {
4492
+ ...latest.technologyGovernance,
4493
+ matrix: buildTechnologyMatrix(
4494
+ {
4495
+ technologies: latest.technologyGovernance.catalog,
4496
+ projectTypes: [],
4497
+ versionPolicy: latest.technologyGovernance.versionPolicy
4498
+ },
4499
+ repositories
4500
+ )
4501
+ } : latest.technologyGovernance;
4268
4502
  return {
4269
4503
  ...latest,
4504
+ technologyGovernance,
4270
4505
  repositories,
4271
4506
  summary,
4272
4507
  coverage: {
@@ -4289,14 +4524,19 @@ function writePortfolioAiHealthReport(report, outDir) {
4289
4524
  const htmlPath = join24(outDir, "index.html");
4290
4525
  writeFileSync7(jsonPath, `${JSON.stringify(report, null, 2)}
4291
4526
  `);
4292
- writeFileSync7(join24(outDir, "data.js"), `window.__PORTFOLIO_AI_HEALTH__ = ${safeJavaScriptJson(report)};
4293
- `);
4527
+ writeFileSync7(
4528
+ join24(outDir, "data.js"),
4529
+ `window.__PORTFOLIO_AI_HEALTH__ = ${safeJavaScriptJson(report)};
4530
+ `
4531
+ );
4294
4532
  return { jsonPath, htmlPath };
4295
4533
  }
4296
4534
  function collectEndpoints(manifest) {
4297
4535
  const result = [];
4298
- if (manifest.controlPlane) result.push({ endpoint: manifest.controlPlane, role: "control-plane" });
4299
- if (manifest.executionEngine) result.push({ endpoint: manifest.executionEngine, role: "execution-engine" });
4536
+ if (manifest.controlPlane)
4537
+ result.push({ endpoint: manifest.controlPlane, role: "control-plane" });
4538
+ if (manifest.executionEngine)
4539
+ result.push({ endpoint: manifest.executionEngine, role: "execution-engine" });
4300
4540
  for (const target of manifest.targets) result.push({ endpoint: target, role: "target" });
4301
4541
  const seen = /* @__PURE__ */ new Set();
4302
4542
  return result.filter(({ endpoint }) => {
@@ -4306,73 +4546,214 @@ function collectEndpoints(manifest) {
4306
4546
  return true;
4307
4547
  });
4308
4548
  }
4309
- function inspectRepository(endpoint, role, secretsRoot, homeDir, expectedPackageVersion, technologyGovernance, grokVersion) {
4549
+ function inspectRepository(endpoint, role, secretsRoot, homeDir, expectedPackageVersion, technologyGovernance, grokVersion, registeredSkills, userSkills) {
4310
4550
  const root = endpoint.path;
4311
4551
  const git = inspectGit2(root);
4312
4552
  const entries = inspectEntries(root);
4313
4553
  const grokInspection = inspectGrokProject(root, homeDir, grokVersion);
4314
- const skills = inspectSkills(root, grokInspection);
4554
+ const skills = inspectSkills(root, grokInspection, registeredSkills, userSkills);
4315
4555
  const hostSsot = inspectProjectHostSsot(root);
4316
4556
  const hooks = inspectHooks(root);
4317
4557
  const docs = inspectDocs(root, role === "execution-engine" ? void 0 : expectedPackageVersion);
4318
4558
  const mcp = {
4319
- codexProject: tomlMcpNames(join24(root, ".codex/config.toml")),
4320
- 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
+ ),
4321
4564
  claudeCodeProjectLocal: claudeProjectLocalMcpNames(homeDir, root),
4322
- grokProject: tomlMcpNames(join24(root, ".grok/config.toml")),
4565
+ grokProject: tomlMcpNames(join24(root, MCP_DISCOVERY_PATHS.project.grok)),
4323
4566
  grokEffective: grokInspection.effectiveMcp,
4324
4567
  grokInspection: grokInspection.inspection
4325
4568
  };
4326
- const secrets = inspectRepositorySecrets(root, endpoint.id, secretsRoot, git.isRepository, endpoint.environmentPolicy);
4569
+ const secrets = inspectRepositorySecrets(
4570
+ root,
4571
+ endpoint.id,
4572
+ secretsRoot,
4573
+ git.isRepository,
4574
+ endpoint.environmentPolicy
4575
+ );
4327
4576
  const projectModel = inspectProjectModel(root, endpoint, technologyGovernance);
4328
- const versions = inspectVersionPolicy(root, technologyGovernance?.versionPolicy, endpoint.projectType);
4577
+ const exclusiveOwnershipViolations = inspectExclusiveOwnership(
4578
+ root,
4579
+ endpoint,
4580
+ technologyGovernance
4581
+ );
4582
+ const versions = inspectVersionPolicy(
4583
+ root,
4584
+ technologyGovernance?.versionPolicy,
4585
+ endpoint.projectType
4586
+ );
4329
4587
  const verification = inspectProjectVerification(root);
4330
- const redundancy = inspectProjectRedundancy(root, { homeDir });
4588
+ const redundancy = inspectProjectRedundancy(root, {
4589
+ homeDir,
4590
+ includePlaywrightCaches: false
4591
+ });
4331
4592
  const recommendations = [];
4332
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");
4333
- if (git.unmergedBranches.length > 0) recommendations.push(`\u6709 ${git.unmergedBranches.length} \u6761\u5206\u652F\u5C1A\u672A\u5408\u5165\u5F53\u524D HEAD\uFF1A${git.unmergedBranches.join(", ")}\u3002`);
4334
- else if (git.branches.length > 1) recommendations.push(`\u6709 ${git.branches.length} \u6761\u672C\u5730\u5206\u652F\uFF0C\u5747\u5DF2\u5408\u5165\u5F53\u524D HEAD\uFF1B\u786E\u8BA4\u65E0\u5176\u4ED6\u5BBF\u4E3B\u5360\u7528\u540E\u53EF\u6E05\u7406\u989D\u5916\u5206\u652F\u3002`);
4335
- if (git.worktrees.length > 1) recommendations.push(`\u6709 ${git.worktrees.length} \u4E2A worktree\uFF1B\u5B8C\u6210\u5E76\u5408\u5E76\u540E\u518D\u62C6\u9664\u4E34\u65F6 worktree\u3002`);
4336
- if (git.dirtyPaths.length > 0) recommendations.push(`\u5DE5\u4F5C\u533A\u6709 ${git.dirtyPaths.length} \u4E2A\u53D8\u66F4\u8DEF\u5F84\uFF1B\u5148\u786E\u8BA4\u5F52\u5C5E\uFF0C\u4E0D\u8981\u7531\u5065\u5EB7\u626B\u63CF\u5668\u81EA\u52A8\u6E05\u7406\u3002`);
4337
- if ((git.ahead ?? 0) > 0) recommendations.push(`\u5F53\u524D\u5206\u652F\u9886\u5148\u4E0A\u6E38 ${git.ahead} \u4E2A\u63D0\u4EA4\uFF1B\u786E\u8BA4\u5DE5\u4F5C\u5B8C\u6210\u540E\u518D\u63A8\u9001\u3002`);
4594
+ if (git.unmergedBranches.length > 0)
4595
+ recommendations.push(
4596
+ `\u6709 ${git.unmergedBranches.length} \u6761\u5206\u652F\u5C1A\u672A\u5408\u5165\u5F53\u524D HEAD\uFF1A${git.unmergedBranches.join(", ")}\u3002`
4597
+ );
4598
+ else if (git.branches.length > 1)
4599
+ recommendations.push(
4600
+ `\u6709 ${git.branches.length} \u6761\u672C\u5730\u5206\u652F\uFF0C\u5747\u5DF2\u5408\u5165\u5F53\u524D HEAD\uFF1B\u786E\u8BA4\u65E0\u5176\u4ED6\u5BBF\u4E3B\u5360\u7528\u540E\u53EF\u6E05\u7406\u989D\u5916\u5206\u652F\u3002`
4601
+ );
4602
+ if (git.worktrees.length > 1)
4603
+ recommendations.push(
4604
+ `\u6709 ${git.worktrees.length} \u4E2A worktree\uFF1B\u5B8C\u6210\u5E76\u5408\u5E76\u540E\u518D\u62C6\u9664\u4E34\u65F6 worktree\u3002`
4605
+ );
4606
+ if (git.dirtyPaths.length > 0)
4607
+ recommendations.push(
4608
+ `\u5DE5\u4F5C\u533A\u6709 ${git.dirtyPaths.length} \u4E2A\u53D8\u66F4\u8DEF\u5F84\uFF1B\u5148\u786E\u8BA4\u5F52\u5C5E\uFF0C\u4E0D\u8981\u7531\u5065\u5EB7\u626B\u63CF\u5668\u81EA\u52A8\u6E05\u7406\u3002`
4609
+ );
4610
+ if ((git.ahead ?? 0) > 0)
4611
+ recommendations.push(`\u5F53\u524D\u5206\u652F\u9886\u5148\u4E0A\u6E38 ${git.ahead} \u4E2A\u63D0\u4EA4\uFF1B\u786E\u8BA4\u5DE5\u4F5C\u5B8C\u6210\u540E\u518D\u63A8\u9001\u3002`);
4338
4612
  if (entries.agents === "missing") recommendations.push("\u7F3A\u5C11 AGENTS.md\uFF1B\u65E0\u6CD5\u53D1\u73B0\u9879\u76EE\u5165\u53E3\u89C4\u5219\u3002");
4339
- if (entries.agents === "custom") recommendations.push("AGENTS.md \u672A\u8BC6\u522B\u5230 PGS Router \u6807\u8BB0\uFF1B\u68C0\u67E5\u662F\u5426\u5C1A\u672A\u540C\u6B65\u6216\u5DF2\u88AB\u9879\u76EE\u5185\u5BB9\u8986\u76D6\u3002");
4340
- if (entries.claude === "missing") recommendations.push("\u7F3A\u5C11 CLAUDE.md\uFF1B\u5EFA\u8BAE\u94FE\u63A5\u5230 AGENTS.md\uFF0C\u907F\u514D\u4E24\u4EFD\u5165\u53E3\u6F02\u79FB\u3002");
4341
- if (entries.claude === "thin-adapter") recommendations.push("CLAUDE.md \u662F\u517C\u5BB9\u9002\u914D\u5668\uFF1B\u53EF\u6539\u4E3A\u76F4\u63A5\u94FE\u63A5 AGENTS.md \u4EE5\u8FDB\u4E00\u6B65\u51CF\u5C11\u7EF4\u62A4\u9762\u3002");
4342
- if (entries.claude === "custom") recommendations.push("CLAUDE.md \u662F\u72EC\u7ACB\u5165\u53E3\uFF1B\u5BB9\u6613\u4E0E AGENTS.md \u6F02\u79FB\uFF0C\u5EFA\u8BAE\u53EA\u4FDD\u7559\u9879\u76EE\u786E\u9700\u7684\u5BBF\u4E3B\u5DEE\u5F02\u3002");
4343
- if (entries.claude === "dangling-symlink") recommendations.push("CLAUDE.md \u662F\u65AD\u5F00\u7684\u94FE\u63A5\uFF1B\u9700\u8981\u91CD\u65B0\u6307\u5411 AGENTS.md\u3002");
4344
- if (entries.gemini === "custom") 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");
4345
- if (entries.gemini === "dangling-symlink") recommendations.push("GEMINI.md \u662F\u65AD\u5F00\u7684\u94FE\u63A5\uFF1B\u9700\u8981\u91CD\u65B0\u6307\u5411 AGENTS.md\u3002");
4346
- if (skills.canonical.some((skill) => skill.kind === "dangling-symlink")) recommendations.push("`.agents/skills` \u4E2D\u5B58\u5728\u65AD\u5F00\u7684\u6280\u80FD\u94FE\u63A5\u3002");
4347
- if (skills.claudeCompatibility === "duplicate-directory") recommendations.push("`.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");
4348
- if (skills.claudeCompatibility === "dangling-symlink") recommendations.push("`.claude/skills` \u662F\u65AD\u5F00\u7684\u94FE\u63A5\u3002");
4349
- if (!hostSsot.claudeEntry.compliant) recommendations.push(`CLAUDE.md \u4E0D\u662F\u89C4\u8303\u7684\u76F8\u5BF9\u94FE\u63A5 AGENTS.md\uFF08${hostSsot.claudeEntry.status}\uFF09\u3002`);
4350
- if (!hostSsot.claudeSkills.compliant) recommendations.push(`.claude/skills \u4E0D\u662F\u89C4\u8303\u7684\u76F8\u5BF9\u94FE\u63A5 ../.agents/skills\uFF08${hostSsot.claudeSkills.status}\uFF09\u3002`);
4351
- if (hostSsot.canonicalSkills.status !== "directory") recommendations.push(`\u7F3A\u5C11\u89C4\u8303\u7684 .agents/skills \u6280\u80FD\u76EE\u5F55\uFF08${hostSsot.canonicalSkills.status}\uFF09\u3002`);
4352
- if (hasWorkflowReminderHooks(hooks)) recommendations.push("\u53D1\u73B0 Stop/SubagentStop hook\uFF1B\u786E\u8BA4\u5B83\u662F\u5426\u4ECD\u6709\u9879\u76EE\u4E13\u5C5E\u7528\u9014\uFF0C\u5E76\u79FB\u9664\u9000\u4F11\u7684 PGS \u5DE5\u4F5C\u6D41\u63D0\u9192\u3002");
4613
+ if (entries.agents === "custom")
4614
+ recommendations.push(
4615
+ "AGENTS.md \u672A\u8BC6\u522B\u5230 PGS Router \u6807\u8BB0\uFF1B\u68C0\u67E5\u662F\u5426\u5C1A\u672A\u540C\u6B65\u6216\u5DF2\u88AB\u9879\u76EE\u5185\u5BB9\u8986\u76D6\u3002"
4616
+ );
4617
+ if (entries.claude === "missing")
4618
+ recommendations.push("\u7F3A\u5C11 CLAUDE.md\uFF1B\u5EFA\u8BAE\u94FE\u63A5\u5230 AGENTS.md\uFF0C\u907F\u514D\u4E24\u4EFD\u5165\u53E3\u6F02\u79FB\u3002");
4619
+ if (entries.claude === "thin-adapter")
4620
+ recommendations.push("CLAUDE.md \u662F\u517C\u5BB9\u9002\u914D\u5668\uFF1B\u53EF\u6539\u4E3A\u76F4\u63A5\u94FE\u63A5 AGENTS.md \u4EE5\u8FDB\u4E00\u6B65\u51CF\u5C11\u7EF4\u62A4\u9762\u3002");
4621
+ if (entries.claude === "custom")
4622
+ recommendations.push(
4623
+ "CLAUDE.md \u662F\u72EC\u7ACB\u5165\u53E3\uFF1B\u5BB9\u6613\u4E0E AGENTS.md \u6F02\u79FB\uFF0C\u5EFA\u8BAE\u53EA\u4FDD\u7559\u9879\u76EE\u786E\u9700\u7684\u5BBF\u4E3B\u5DEE\u5F02\u3002"
4624
+ );
4625
+ if (entries.claude === "dangling-symlink")
4626
+ recommendations.push("CLAUDE.md \u662F\u65AD\u5F00\u7684\u94FE\u63A5\uFF1B\u9700\u8981\u91CD\u65B0\u6307\u5411 AGENTS.md\u3002");
4627
+ if (entries.gemini === "custom")
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");
4629
+ if (entries.gemini === "dangling-symlink")
4630
+ recommendations.push("GEMINI.md \u662F\u65AD\u5F00\u7684\u94FE\u63A5\uFF1B\u9700\u8981\u91CD\u65B0\u6307\u5411 AGENTS.md\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
+ );
4657
+ if (skills.claudeCompatibility === "duplicate-directory")
4658
+ recommendations.push(
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"
4660
+ );
4661
+ if (skills.claudeCompatibility === "dangling-symlink")
4662
+ recommendations.push("`.claude/skills` \u662F\u65AD\u5F00\u7684\u94FE\u63A5\u3002");
4663
+ if (!hostSsot.claudeEntry.compliant)
4664
+ recommendations.push(
4665
+ `CLAUDE.md \u4E0D\u662F\u89C4\u8303\u7684\u76F8\u5BF9\u94FE\u63A5 AGENTS.md\uFF08${hostSsot.claudeEntry.status}\uFF09\u3002`
4666
+ );
4667
+ if (!hostSsot.claudeSkills.compliant)
4668
+ recommendations.push(
4669
+ `.claude/skills \u4E0D\u662F\u89C4\u8303\u7684\u76F8\u5BF9\u94FE\u63A5 ../.agents/skills\uFF08${hostSsot.claudeSkills.status}\uFF09\u3002`
4670
+ );
4671
+ if (hostSsot.canonicalSkills.status !== "directory")
4672
+ recommendations.push(
4673
+ `\u7F3A\u5C11\u89C4\u8303\u7684 .agents/skills \u6280\u80FD\u76EE\u5F55\uFF08${hostSsot.canonicalSkills.status}\uFF09\u3002`
4674
+ );
4675
+ if (hasWorkflowReminderHooks(hooks))
4676
+ recommendations.push(
4677
+ "\u53D1\u73B0 Stop/SubagentStop hook\uFF1B\u786E\u8BA4\u5B83\u662F\u5426\u4ECD\u6709\u9879\u76EE\u4E13\u5C5E\u7528\u9014\uFF0C\u5E76\u79FB\u9664\u9000\u4F11\u7684 PGS \u5DE5\u4F5C\u6D41\u63D0\u9192\u3002"
4678
+ );
4353
4679
  const liveEnv = secrets.repositoryEnvFiles.filter((file) => !file.template && !file.fixture);
4354
4680
  const envNeedsCentralReview = liveEnv.filter((file) => !file.localOnly);
4355
- if (liveEnv.some((file) => file.tracked)) recommendations.push("\u53D1\u73B0\u88AB Git \u8DDF\u8E2A\u7684\u771F\u5B9E\u73AF\u5883\u6587\u4EF6\uFF1B\u5E94\u7ACB\u5373\u786E\u8BA4\u5176\u4E2D\u662F\u5426\u5305\u542B\u51ED\u636E\u5E76\u8FC1\u51FA\u4ED3\u5E93\u3002");
4356
- if (envNeedsCentralReview.length > 0 && secrets.centralDirectory === "absent") recommendations.push("\u4ED3\u5E93\u6709\u672C\u5730\u73AF\u5883\u6587\u4EF6\uFF0C\u4F46\u4E2D\u592E `.secrets` \u4E2D\u6CA1\u6709\u5BF9\u5E94\u76EE\u5F55\uFF1B\u786E\u8BA4\u662F\u5426\u9700\u8981\u7EB3\u5165\u5206\u5C42\u7BA1\u7406\u3002");
4357
- else if (envNeedsCentralReview.some((file) => !file.centralized)) recommendations.push("\u53D1\u73B0\u771F\u5B9E\u73AF\u5883\u6587\u4EF6\u5C1A\u672A\u8FDE\u63A5\u5230\u672C\u9879\u76EE\u7684\u4E2D\u592E `.secrets` \u76EE\u5F55\uFF1B\u533A\u5206\u53EF\u4E22\u5F03\u751F\u6210\u7269\u4E0E\u672C\u5730\u4E3B\u6765\u6E90\uFF0C\u9700\u4FDD\u7559\u7684\u6765\u6E90\u5E94\u96C6\u4E2D\u540E\u518D\u63A5\u56DE\u9879\u76EE\u3002");
4358
- if (hasUnsafeCentralSecretPermissions(secrets)) recommendations.push("\u4E2D\u592E\u5BC6\u94A5\u76EE\u5F55\u6216\u6587\u4EF6\u6743\u9650\u8FC7\u5BBD\uFF1B\u76EE\u5F55\u5E94\u4E3A 0700\uFF0C\u914D\u7F6E\u6587\u4EF6\u5E94\u4E3A 0600\u3002");
4359
- if (!docs.packages.aligned) recommendations.push(`PGS \u5305\u7248\u672C\u672A\u4E0E\u6267\u884C\u5F15\u64CE ${docs.packages.expected ?? "\u672A\u77E5\u7248\u672C"} \u5BF9\u9F50\uFF1B\u53D1\u5E03\u4E0A\u6E38\u540E\u518D\u540C\u6B65\u76EE\u6807\u4ED3\u5E93\u3002`);
4360
- if (!docs.routerAligned) recommendations.push(`PGS Router \u7248\u672C\u4E3A ${docs.routerVersion ? `v${docs.routerVersion}` : "\u672A\u8BC6\u522B"}\uFF0C\u5E94\u540C\u6B65\u5230 v${docs.expectedRouterVersion}\u3002`);
4361
- if (role === "target" && !docs.manifest) recommendations.push("\u7F3A\u5C11 docs/governance/MANIFEST.yml\uFF1B\u6587\u6863\u6E05\u5355\u65E0\u6CD5\u8BC1\u660E\u5DF2\u540C\u6B65\u3002");
4362
- if (technologyGovernance && !projectModel.projectType) 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");
4363
- const missingBaseline = projectModel.baseline.filter((technology) => !technology.detected && !hasBaselineException(projectModel, technology.id));
4364
- if (missingBaseline.length > 0) recommendations.push(`\u6280\u672F\u57FA\u7EBF\u7F3A\u5C11\u53EF\u9A8C\u8BC1\u4FE1\u53F7\uFF1A${missingBaseline.map((technology) => technology.label).join("\u3001")}\u3002`);
4365
- const missingSelected = projectModel.optionalCapabilities.filter((technology) => technology.selected && !technology.detected);
4366
- if (missingSelected.length > 0) recommendations.push(`\u5DF2\u9009\u80FD\u529B\u7F3A\u5C11\u53EF\u9A8C\u8BC1\u4FE1\u53F7\uFF1A${missingSelected.map((technology) => technology.label).join("\u3001")}\u3002`);
4367
- if (versions.status === "attention") recommendations.push(`\u6280\u672F\u7248\u672C\u7B56\u7565\u6709 ${versions.attentionCount} \u9879\u6F02\u79FB\u6216\u7F3A\u5931\uFF1B\u58F0\u660E\u7248\u672C\u4E0E\u5DF2\u5B89\u88C5\u7248\u672C\u5FC5\u987B\u7CBE\u786E\u5BF9\u9F50\u3002`);
4368
- if (verification.status === "attention") recommendations.push(`\u9A8C\u8BC1\u811A\u672C\u7F3A\u5931\uFF1A${verification.missing.join("\u3001")}\uFF1BPGS \u8981\u6C42 typecheck\u3001lint\u3001format:check\u3001verify \u53EF\u53D1\u73B0\u3002`);
4369
- if (redundancy.status === "attention") recommendations.push("\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");
4681
+ if (liveEnv.some((file) => file.tracked))
4682
+ recommendations.push("\u53D1\u73B0\u88AB Git \u8DDF\u8E2A\u7684\u771F\u5B9E\u73AF\u5883\u6587\u4EF6\uFF1B\u5E94\u7ACB\u5373\u786E\u8BA4\u5176\u4E2D\u662F\u5426\u5305\u542B\u51ED\u636E\u5E76\u8FC1\u51FA\u4ED3\u5E93\u3002");
4683
+ if (envNeedsCentralReview.length > 0 && secrets.centralDirectory === "absent")
4684
+ recommendations.push(
4685
+ "\u4ED3\u5E93\u6709\u672C\u5730\u73AF\u5883\u6587\u4EF6\uFF0C\u4F46\u4E2D\u592E `.secrets` \u4E2D\u6CA1\u6709\u5BF9\u5E94\u76EE\u5F55\uFF1B\u786E\u8BA4\u662F\u5426\u9700\u8981\u7EB3\u5165\u5206\u5C42\u7BA1\u7406\u3002"
4686
+ );
4687
+ else if (envNeedsCentralReview.some((file) => !file.centralized))
4688
+ recommendations.push(
4689
+ "\u53D1\u73B0\u771F\u5B9E\u73AF\u5883\u6587\u4EF6\u5C1A\u672A\u8FDE\u63A5\u5230\u672C\u9879\u76EE\u7684\u4E2D\u592E `.secrets` \u76EE\u5F55\uFF1B\u533A\u5206\u53EF\u4E22\u5F03\u751F\u6210\u7269\u4E0E\u672C\u5730\u4E3B\u6765\u6E90\uFF0C\u9700\u4FDD\u7559\u7684\u6765\u6E90\u5E94\u96C6\u4E2D\u540E\u518D\u63A5\u56DE\u9879\u76EE\u3002"
4690
+ );
4691
+ if (hasUnsafeCentralSecretPermissions(secrets))
4692
+ recommendations.push("\u4E2D\u592E\u5BC6\u94A5\u76EE\u5F55\u6216\u6587\u4EF6\u6743\u9650\u8FC7\u5BBD\uFF1B\u76EE\u5F55\u5E94\u4E3A 0700\uFF0C\u914D\u7F6E\u6587\u4EF6\u5E94\u4E3A 0600\u3002");
4693
+ if (!docs.packages.aligned)
4694
+ recommendations.push(
4695
+ `PGS \u5305\u7248\u672C\u672A\u4E0E\u6267\u884C\u5F15\u64CE ${docs.packages.expected ?? "\u672A\u77E5\u7248\u672C"} \u5BF9\u9F50\uFF1B\u53D1\u5E03\u4E0A\u6E38\u540E\u518D\u540C\u6B65\u76EE\u6807\u4ED3\u5E93\u3002`
4696
+ );
4697
+ if (!docs.routerAligned)
4698
+ recommendations.push(
4699
+ `PGS Router \u7248\u672C\u4E3A ${docs.routerVersion ? `v${docs.routerVersion}` : "\u672A\u8BC6\u522B"}\uFF0C\u5E94\u540C\u6B65\u5230 v${docs.expectedRouterVersion}\u3002`
4700
+ );
4701
+ if (role === "target" && !docs.manifest)
4702
+ recommendations.push("\u7F3A\u5C11 docs/governance/MANIFEST.yml\uFF1B\u6587\u6863\u6E05\u5355\u65E0\u6CD5\u8BC1\u660E\u5DF2\u540C\u6B65\u3002");
4703
+ if (technologyGovernance && !projectModel.projectType)
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
+ }
4710
+ const missingBaseline = projectModel.baseline.filter(
4711
+ (technology) => !technology.detected && !hasBaselineException(projectModel, technology.id)
4712
+ );
4713
+ if (missingBaseline.length > 0)
4714
+ recommendations.push(
4715
+ `\u6280\u672F\u57FA\u7EBF\u7F3A\u5C11\u53EF\u9A8C\u8BC1\u4FE1\u53F7\uFF1A${missingBaseline.map((technology) => technology.label).join("\u3001")}\u3002`
4716
+ );
4717
+ const missingSelected = projectModel.optionalCapabilities.filter(
4718
+ (technology) => technology.selected && !technology.detected
4719
+ );
4720
+ if (missingSelected.length > 0)
4721
+ recommendations.push(
4722
+ `\u5DF2\u9009\u80FD\u529B\u7F3A\u5C11\u53EF\u9A8C\u8BC1\u4FE1\u53F7\uFF1A${missingSelected.map((technology) => technology.label).join("\u3001")}\u3002`
4723
+ );
4724
+ if (versions.status === "attention")
4725
+ recommendations.push(
4726
+ `\u6280\u672F\u7248\u672C\u7B56\u7565\u6709 ${versions.attentionCount} \u9879\u6F02\u79FB\u6216\u7F3A\u5931\uFF1B\u58F0\u660E\u7248\u672C\u4E0E\u5DF2\u5B89\u88C5\u7248\u672C\u5FC5\u987B\u7CBE\u786E\u5BF9\u9F50\u3002`
4727
+ );
4728
+ if (verification.status === "attention")
4729
+ recommendations.push(
4730
+ `\u9A8C\u8BC1\u811A\u672C\u7F3A\u5931\uFF1A${verification.missing.join("\u3001")}\uFF1BPGS \u8981\u6C42 typecheck\u3001lint\u3001format:check\u3001verify \u53EF\u53D1\u73B0\u3002`
4731
+ );
4732
+ if (redundancy.status === "attention")
4733
+ recommendations.push(
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"
4735
+ );
4370
4736
  return {
4371
4737
  id: endpoint.id,
4372
4738
  role,
4373
4739
  path: root,
4374
4740
  profile: "profile" in endpoint ? endpoint.profile : void 0,
4375
- status: deriveStatus(role, entries, git, hooks, skills, hostSsot, secrets, docs, projectModel, Boolean(technologyGovernance), versions, verification, redundancy),
4741
+ status: deriveStatus(
4742
+ role,
4743
+ entries,
4744
+ git,
4745
+ hooks,
4746
+ skills,
4747
+ hostSsot,
4748
+ secrets,
4749
+ docs,
4750
+ projectModel,
4751
+ exclusiveOwnershipViolations,
4752
+ Boolean(technologyGovernance),
4753
+ versions,
4754
+ verification,
4755
+ redundancy
4756
+ ),
4376
4757
  recommendations,
4377
4758
  git,
4378
4759
  entries,
@@ -4388,22 +4769,169 @@ function inspectRepository(endpoint, role, secretsRoot, homeDir, expectedPackage
4388
4769
  redundancy
4389
4770
  };
4390
4771
  }
4391
- function deriveStatus(role, entries, git, hooks, skills, hostSsot, secrets, docs, projectModel, technologyGovernanceConfigured, versions, verification, redundancy) {
4392
- 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)) return "unhealthy";
4393
- const missingBaseline = projectModel.baseline.some((technology) => !technology.detected && !hasBaselineException(projectModel, technology.id));
4394
- const missingSelected = projectModel.optionalCapabilities.some((technology) => technology.selected && !technology.detected);
4772
+ function buildTechnologyMatrix(governance, repositories) {
4773
+ if (!governance) return [];
4774
+ const policy = governance.versionPolicy;
4775
+ return governance.technologies.map((technology) => {
4776
+ const projects = repositories.flatMap((repository) => {
4777
+ const packageManifests = collectPackageManifests(repository.path);
4778
+ const packageSignals = (technology.packages ?? []).map((name) => {
4779
+ const requirement = policy?.packages.find((item) => item.name === name);
4780
+ return packageManifests.map((manifest) => {
4781
+ const declared = findDeclaredVersion(manifest.packageJson, name);
4782
+ return declared === void 0 ? void 0 : {
4783
+ name,
4784
+ source: manifest.path,
4785
+ declared,
4786
+ installed: readInstalledVersion(repository.path, name, manifest.directory),
4787
+ expected: requirement?.version
4788
+ };
4789
+ }).filter((item) => item !== void 0);
4790
+ }).flat();
4791
+ const fileSignal = (technology.files ?? []).some(
4792
+ (path) => hasUsableTechnologyFile(join24(repository.path, path)) || packageManifests.some(
4793
+ (manifest) => hasUsableTechnologyFile(join24(manifest.directory, path))
4794
+ )
4795
+ );
4796
+ const modelSignal = [
4797
+ ...repository.projectModel.baseline,
4798
+ ...repository.projectModel.optionalCapabilities
4799
+ ].find((item) => item.id === technology.id)?.detected ?? false;
4800
+ const runtimeRequirement2 = policy?.runtimes?.find(
4801
+ (item) => item.name === technology.id || technology.id === "deno-edge" && item.name === "deno"
4802
+ );
4803
+ const runtimeEvidence = runtimeRequirement2 ? repository.versions.runtimes.find((item) => item.name === runtimeRequirement2.name) : void 0;
4804
+ const uses = modelSignal || fileSignal || packageSignals.length > 0;
4805
+ if (!uses) return [];
4806
+ const packageStatuses = packageSignals.map(
4807
+ (item) => item.expected === void 0 ? "unversioned" : item.declared !== item.expected ? "drift" : item.installed === void 0 ? "unknown" : item.installed !== item.expected ? "drift" : "aligned"
4808
+ );
4809
+ const statuses = runtimeEvidence ? [
4810
+ ...packageStatuses,
4811
+ runtimeEvidence.status === "compliant" ? "aligned" : runtimeEvidence.status === "not-applicable" ? "unknown" : "drift"
4812
+ ] : packageStatuses;
4813
+ const status2 = statuses.includes("drift") ? "drift" : statuses.includes("unversioned") ? "unversioned" : statuses.includes("unknown") || statuses.length === 0 ? "unknown" : "aligned";
4814
+ const values = {
4815
+ sources: [...new Set(packageSignals.map((item) => item.source))],
4816
+ packages: [...new Set(packageSignals.map((item) => item.name))],
4817
+ declared: [
4818
+ ...new Set(
4819
+ packageSignals.flatMap(
4820
+ (item) => item.declared ? [`${item.name}@${item.declared}`] : []
4821
+ )
4822
+ )
4823
+ ],
4824
+ installed: [
4825
+ ...new Set(
4826
+ packageSignals.flatMap(
4827
+ (item) => item.installed ? [`${item.name}@${item.installed}`] : []
4828
+ )
4829
+ )
4830
+ ],
4831
+ expected: [
4832
+ ...new Set(
4833
+ packageSignals.flatMap(
4834
+ (item) => item.expected ? [`${item.name}@${item.expected}`] : []
4835
+ )
4836
+ )
4837
+ ]
4838
+ };
4839
+ if (runtimeEvidence && runtimeRequirement2) {
4840
+ values.expected.push(`${runtimeRequirement2.name}@${runtimeRequirement2.version}`);
4841
+ if (runtimeEvidence.actual)
4842
+ values.installed.push(`${runtimeRequirement2.name}@${runtimeEvidence.actual}`);
4843
+ }
4844
+ return [
4845
+ {
4846
+ repositoryId: repository.id,
4847
+ sources: values.sources,
4848
+ packages: values.packages,
4849
+ declared: values.declared.length ? values.declared.join(" \xB7 ") : void 0,
4850
+ installed: values.installed.length ? values.installed.join(" \xB7 ") : void 0,
4851
+ expected: values.expected.length ? values.expected.join(" \xB7 ") : void 0,
4852
+ status: status2
4853
+ }
4854
+ ];
4855
+ });
4856
+ const expected = /* @__PURE__ */ new Set();
4857
+ for (const requirement of policy?.packages ?? []) {
4858
+ if ((technology.packages ?? []).includes(requirement.name))
4859
+ expected.add(`${requirement.name}@${requirement.version}`);
4860
+ }
4861
+ const runtimeRequirement = policy?.runtimes?.find(
4862
+ (item) => item.name === technology.id || technology.id === "deno-edge" && item.name === "deno"
4863
+ );
4864
+ if (runtimeRequirement)
4865
+ expected.add(`${runtimeRequirement.name}@${runtimeRequirement.version}`);
4866
+ for (const project of projects) {
4867
+ for (const value of project.expected?.split(" \xB7 ") ?? []) expected.add(value);
4868
+ }
4869
+ const status = projects.some(
4870
+ (project) => project.status === "drift"
4871
+ ) ? "drift" : projects.some((project) => project.status === "unversioned") ? "unversioned" : projects.some((project) => project.status === "unknown") ? "unknown" : projects.length ? "aligned" : "not-applicable";
4872
+ return {
4873
+ id: technology.id,
4874
+ label: technology.label,
4875
+ packages: technology.packages ?? [],
4876
+ expected: [...expected],
4877
+ projectCount: projects.length,
4878
+ status,
4879
+ projects
4880
+ };
4881
+ }).filter((technology) => technology.projectCount > 0);
4882
+ }
4883
+ function hasUsableTechnologyFile(path) {
4884
+ if (!existsSync23(path)) return false;
4885
+ try {
4886
+ const info = statSync5(path);
4887
+ if (info.isFile()) return true;
4888
+ if (!info.isDirectory()) return false;
4889
+ return readdirSync10(path, { withFileTypes: true }).some((entry) => {
4890
+ if (entry.name.startsWith(".")) return false;
4891
+ const child = join24(path, entry.name);
4892
+ if (entry.isDirectory()) return hasUsableTechnologyFile(child);
4893
+ return entry.name.toLowerCase() !== "readme.md";
4894
+ });
4895
+ } catch {
4896
+ return false;
4897
+ }
4898
+ }
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))
4901
+ return "unhealthy";
4902
+ const missingBaseline = projectModel.baseline.some(
4903
+ (technology) => !technology.detected && !hasBaselineException(projectModel, technology.id)
4904
+ );
4905
+ const missingSelected = projectModel.optionalCapabilities.some(
4906
+ (technology) => technology.selected && !technology.detected
4907
+ );
4395
4908
  const liveEnv = secrets.repositoryEnvFiles.filter((file) => !file.template && !file.fixture);
4396
- const secretMaterializationNeedsReview = liveEnv.some((file) => !file.centralized && !file.localOnly);
4909
+ const secretMaterializationNeedsReview = liveEnv.some(
4910
+ (file) => !file.centralized && !file.localOnly
4911
+ );
4397
4912
  const packageVersionNeedsReview = docs.packages.expected !== void 0 && !docs.packages.aligned;
4398
4913
  const routerVersionNeedsReview = !docs.routerAligned;
4399
4914
  const targetManifestMissing = role === "target" && !docs.manifest;
4400
- 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) return "attention";
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)
4916
+ return "attention";
4401
4917
  return "healthy";
4402
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
+ }
4403
4927
  function inspectProjectModel(root, endpoint, governance) {
4404
- const projectType = governance?.projectTypes.find((candidate) => candidate.id === endpoint.projectType);
4928
+ const projectType = governance?.projectTypes.find(
4929
+ (candidate) => candidate.id === endpoint.projectType
4930
+ );
4405
4931
  const packages = collectPackageNames(root);
4406
- const technologyById = new Map((governance?.technologies ?? []).map((technology) => [technology.id, technology]));
4932
+ const technologyById = new Map(
4933
+ (governance?.technologies ?? []).map((technology) => [technology.id, technology])
4934
+ );
4407
4935
  const detection = (id) => {
4408
4936
  const technology = technologyById.get(id);
4409
4937
  const packageMatch = technology?.packages?.some((name) => packages.has(name)) ?? false;
@@ -4415,7 +4943,10 @@ function inspectProjectModel(root, endpoint, governance) {
4415
4943
  projectType: projectType ? { id: projectType.id, title: projectType.title, summary: projectType.summary } : void 0,
4416
4944
  boundary: endpoint.boundary,
4417
4945
  baseline: (projectType?.baseline ?? []).map(detection),
4418
- optionalCapabilities: (projectType?.optionalCapabilities ?? []).map((id) => ({ ...detection(id), selected: selected.has(id) })),
4946
+ optionalCapabilities: (projectType?.optionalCapabilities ?? []).map((id) => ({
4947
+ ...detection(id),
4948
+ selected: selected.has(id)
4949
+ })),
4419
4950
  exceptions: endpoint.technologyExceptions ?? []
4420
4951
  };
4421
4952
  }
@@ -4426,16 +4957,14 @@ function hasBaselineException(projectModel, technologyId) {
4426
4957
  }
4427
4958
  function collectPackageNames(root) {
4428
4959
  const names = /* @__PURE__ */ new Set();
4429
- let files = [];
4430
- try {
4431
- files = splitLines(execFileSync("git", ["ls-files", "*package.json"], { cwd: root, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }));
4432
- } catch {
4433
- if (existsSync23(join24(root, "package.json"))) files = ["package.json"];
4434
- }
4435
- for (const file of files) {
4436
- const packageJson = readJson4(join24(root, file));
4960
+ for (const { packageJson } of collectPackageManifests(root)) {
4437
4961
  if (!isRecord3(packageJson)) continue;
4438
- for (const section of ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]) {
4962
+ for (const section of [
4963
+ "dependencies",
4964
+ "devDependencies",
4965
+ "peerDependencies",
4966
+ "optionalDependencies"
4967
+ ]) {
4439
4968
  const dependencies = packageJson[section];
4440
4969
  if (!isRecord3(dependencies)) continue;
4441
4970
  for (const name of Object.keys(dependencies)) names.add(name);
@@ -4444,17 +4973,31 @@ function collectPackageNames(root) {
4444
4973
  return names;
4445
4974
  }
4446
4975
  function hasWorkflowReminderHooks(hooks) {
4447
- return hooks.some((hook) => hook.events.some((event) => event.name === "Stop" || event.name === "SubagentStop"));
4976
+ return hooks.some(
4977
+ (hook) => hook.events.some((event) => event.name === "Stop" || event.name === "SubagentStop")
4978
+ );
4448
4979
  }
4449
4980
  function inspectGit2(root) {
4450
4981
  const git = (...args) => {
4451
4982
  try {
4452
- return execFileSync("git", args, { cwd: root, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trimEnd();
4983
+ return execFileSync("git", args, {
4984
+ cwd: root,
4985
+ encoding: "utf8",
4986
+ stdio: ["ignore", "pipe", "ignore"]
4987
+ }).trimEnd();
4453
4988
  } catch {
4454
4989
  return void 0;
4455
4990
  }
4456
4991
  };
4457
- if (git("rev-parse", "--is-inside-work-tree") !== "true") return { isRepository: false, branches: [], mergedBranches: [], unmergedBranches: [], worktrees: [], dirtyPaths: [] };
4992
+ if (git("rev-parse", "--is-inside-work-tree") !== "true")
4993
+ return {
4994
+ isRepository: false,
4995
+ branches: [],
4996
+ mergedBranches: [],
4997
+ unmergedBranches: [],
4998
+ worktrees: [],
4999
+ dirtyPaths: []
5000
+ };
4458
5001
  const status = git("status", "--porcelain=v1", "-uall") ?? "";
4459
5002
  const worktreeText = git("worktree", "list", "--porcelain") ?? "";
4460
5003
  const upstreamCounts = git("rev-list", "--left-right", "--count", "@{upstream}...HEAD")?.split(/\s+/).map(Number);
@@ -4462,8 +5005,12 @@ function inspectGit2(root) {
4462
5005
  isRepository: true,
4463
5006
  branch: git("branch", "--show-current") || "(detached)",
4464
5007
  branches: splitLines(git("for-each-ref", "--format=%(refname:short)", "refs/heads")),
4465
- mergedBranches: splitLines(git("for-each-ref", "--merged=HEAD", "--format=%(refname:short)", "refs/heads")),
4466
- unmergedBranches: splitLines(git("for-each-ref", "--no-merged=HEAD", "--format=%(refname:short)", "refs/heads")),
5008
+ mergedBranches: splitLines(
5009
+ git("for-each-ref", "--merged=HEAD", "--format=%(refname:short)", "refs/heads")
5010
+ ),
5011
+ unmergedBranches: splitLines(
5012
+ git("for-each-ref", "--no-merged=HEAD", "--format=%(refname:short)", "refs/heads")
5013
+ ),
4467
5014
  worktrees: splitLines(worktreeText).filter((line) => line.startsWith("worktree ")).map((line) => line.slice(9)),
4468
5015
  dirtyPaths: splitLines(status).map((line) => line.slice(3)),
4469
5016
  behind: upstreamCounts?.[0],
@@ -4504,51 +5051,142 @@ function inspectOptionalEntry(root, filename, agentsPath) {
4504
5051
  const content = safeRead(path);
4505
5052
  return /AGENTS\.md/.test(content) && content.length < 2e3 ? "thin-adapter" : "custom";
4506
5053
  }
4507
- function inspectSkills(root, grokInspection) {
5054
+ function inspectSkills(root, grokInspection, registeredSkills, userSkills) {
4508
5055
  const lock = readJson4(join24(root, ".pro-gov/assets.lock.json"));
5056
+ const assetManifest = readJson4(join24(root, ".pro-gov/assets.json"));
4509
5057
  const managed = /* @__PURE__ */ new Set();
4510
5058
  const bundleIds = stringArray(isRecord3(lock) ? lock.bundleIds : void 0);
4511
5059
  if (isRecord3(lock) && Array.isArray(lock.assets)) {
4512
5060
  for (const asset of lock.assets) {
4513
5061
  if (!isRecord3(asset) || typeof asset.targetPath !== "string") continue;
4514
- const match = asset.targetPath.match(/^\.agents\/skills\/([^/]+)$/);
4515
- 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]}`);
4516
5064
  }
4517
5065
  }
4518
- const skillRoot = join24(root, ".agents/skills");
4519
- const canonical = pathLexists(skillRoot) && safeIsDirectory(skillRoot) ? safeReadDir(skillRoot).filter((name) => !name.startsWith(".")).map((name) => {
4520
- const path = join24(skillRoot, name);
4521
- const stat = lstatSync8(path);
4522
- let kind = stat.isSymbolicLink() ? "symlink" : stat.isDirectory() ? "directory" : "file";
4523
- if (stat.isSymbolicLink()) {
4524
- try {
4525
- realpathSync4(path);
4526
- } catch {
4527
- kind = "dangling-symlink";
4528
- }
4529
- }
4530
- return { name, kind, managed: managed.has(name) };
4531
- }) : [];
4532
- 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];
4533
5103
  return {
4534
- canonical,
5104
+ automatic,
5105
+ manual,
5106
+ invalidEntries,
5107
+ userDuplicates,
5108
+ crossPlacementDuplicates,
5109
+ placementDrift,
5110
+ unregistered,
5111
+ automaticReviewThreshold: 10,
4535
5112
  claudeCompatibility: inspectClaudeSkillRoot(root),
4536
5113
  bundleIds,
4537
5114
  lifecycle: {
4538
- desired: bundleIds.length,
4539
- locked,
4540
- installed: canonical.filter((item) => item.managed && item.kind !== "dangling-symlink").length,
4541
- 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,
4542
5119
  runtime: "unobservable"
4543
5120
  },
4544
5121
  hosts: {
4545
- codexProject: canonical.filter((item) => item.kind !== "dangling-symlink").length,
5122
+ codexProject: automatic.filter((item) => item.kind !== "dangling-symlink").length,
4546
5123
  claudeCodeProject: inspectSkillRoot(join24(root, ".claude/skills")).names.length,
4547
5124
  grokNativeProject: inspectSkillRoot(join24(root, ".grok/skills")).names.length,
4548
5125
  grokEffective: grokInspection.skills
4549
5126
  }
4550
5127
  };
4551
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
+ }
4552
5190
  function inspectClaudeSkillRoot(root) {
4553
5191
  const path = join24(root, ".claude/skills");
4554
5192
  if (!pathLexists(path)) return "missing";
@@ -4589,14 +5227,29 @@ function inspectRepositorySecrets(root, id, secretsRoot, isRepository, environme
4589
5227
  repositoryEnvFiles: envFiles
4590
5228
  };
4591
5229
  }
4592
- var SKIP_ENV_DIRECTORIES = /* @__PURE__ */ new Set([".git", ".next", ".nuxt", ".output", ".turbo", ".vercel", ".worktrees", "build", "coverage", "dist", "node_modules", "out", "target"]);
5230
+ var SKIP_ENV_DIRECTORIES = /* @__PURE__ */ new Set([
5231
+ ".git",
5232
+ ".next",
5233
+ ".nuxt",
5234
+ ".output",
5235
+ ".turbo",
5236
+ ".vercel",
5237
+ ".worktrees",
5238
+ "build",
5239
+ "coverage",
5240
+ "dist",
5241
+ "node_modules",
5242
+ "out",
5243
+ "target"
5244
+ ]);
4593
5245
  function collectEnvironmentFiles(root, current = root, depth = 0) {
4594
5246
  if (depth > 5) return [];
4595
5247
  const found = [];
4596
5248
  try {
4597
- for (const entry of readdirSync9(current, { withFileTypes: true })) {
5249
+ for (const entry of readdirSync10(current, { withFileTypes: true })) {
4598
5250
  if (entry.isDirectory()) {
4599
- if (!SKIP_ENV_DIRECTORIES.has(entry.name)) found.push(...collectEnvironmentFiles(root, join24(current, entry.name), depth + 1));
5251
+ if (!SKIP_ENV_DIRECTORIES.has(entry.name))
5252
+ found.push(...collectEnvironmentFiles(root, join24(current, entry.name), depth + 1));
4600
5253
  } else if (isEnvironmentFilename(entry.name) && !isProviderGeneratedEnvironmentFile(entry.name)) {
4601
5254
  found.push(relative8(root, join24(current, entry.name)));
4602
5255
  }
@@ -4610,7 +5263,7 @@ function collectCentralSecretFiles(root, current = root, depth = 0) {
4610
5263
  if (depth > 3) return [];
4611
5264
  const found = [];
4612
5265
  try {
4613
- for (const entry of readdirSync9(current, { withFileTypes: true })) {
5266
+ for (const entry of readdirSync10(current, { withFileTypes: true })) {
4614
5267
  const path = join24(current, entry.name);
4615
5268
  if (entry.isDirectory()) found.push(...collectCentralSecretFiles(root, path, depth + 1));
4616
5269
  else found.push({ path: relative8(root, path), mode: modeString(lstatSync8(path).mode) });
@@ -4657,9 +5310,9 @@ function inspectSecretsRoot(path) {
4657
5310
  return existsSync23(path) ? { path, exists: true, mode: modeString(statSync5(path).mode) } : { path, exists: false };
4658
5311
  }
4659
5312
  function inspectHostEnvironment(homeDir, grokVersion, repositoryPaths, devspaceSettings) {
4660
- const codexConfig = join24(homeDir, ".codex/config.toml");
4661
- const claudeConfig = join24(homeDir, ".claude.json");
4662
- 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);
4663
5316
  const hostEnvironment = {
4664
5317
  mcp: {
4665
5318
  codexUser: { path: codexConfig, names: tomlMcpNames(codexConfig) },
@@ -4678,7 +5331,8 @@ function inspectHostEnvironment(homeDir, grokVersion, repositoryPaths, devspaceS
4678
5331
  available: grokVersion !== void 0,
4679
5332
  ...grokVersion ? { version: grokVersion } : {},
4680
5333
  inspectionCommand: "grok inspect --json"
4681
- }
5334
+ },
5335
+ redundancy: inspectHostRedundancy({ homeDir })
4682
5336
  };
4683
5337
  if (devspaceSettings) {
4684
5338
  hostEnvironment.devspace = inspectDevSpaceHealth({
@@ -4696,17 +5350,25 @@ function inspectDevSpaceHealth(options) {
4696
5350
  const authPath = join24(configDirectory, "auth.json");
4697
5351
  const installedResult = run("devspace", ["--version"], 3e3);
4698
5352
  const installedVersion = installedResult.ok ? installedResult.stdout.match(/\b\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?\b/)?.[0] : void 0;
4699
- const latestResult = run("npm", ["view", "@waishnav/devspace", "version", "--registry=https://registry.npmjs.org/"], 5e3);
5353
+ const latestResult = run(
5354
+ "npm",
5355
+ ["view", "@waishnav/devspace", "version", "--registry=https://registry.npmjs.org/"],
5356
+ 5e3
5357
+ );
4700
5358
  const latestVersion = latestResult.ok ? latestResult.stdout.match(/\b\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?\b/)?.[0] : void 0;
4701
5359
  const processResult = run("pgrep", ["-f", "devspace serve"], 3e3);
4702
5360
  const pid = processResult.ok ? processResult.stdout.match(/\b\d+\b/)?.[0] : void 0;
4703
5361
  const processEnvironment = pid ? run("ps", ["eww", "-p", pid, "-o", "command="], 3e3) : void 0;
4704
- const processToolMode = processEnvironment?.ok ? processEnvironment.stdout.match(/(?:^|\s)DEVSPACE_TOOL_MODE=(minimal|full|codex)(?:\s|$)/)?.[1] : void 0;
5362
+ const processToolMode = processEnvironment?.ok ? processEnvironment.stdout.match(
5363
+ /(?:^|\s)DEVSPACE_TOOL_MODE=(minimal|full|codex)(?:\s|$)/
5364
+ )?.[1] : void 0;
4705
5365
  const doctor = !installedResult.ok ? "unavailable" : run("devspace", ["doctor"], 1e4).ok ? "ok" : "failed";
4706
5366
  const configValue = readJson4(configPath);
4707
5367
  const configExists = isRecord3(configValue);
4708
5368
  const allowedRoots = configExists && Array.isArray(configValue.allowedRoots) ? configValue.allowedRoots.filter((value) => typeof value === "string") : [];
4709
- const portfolioCoverage = !configExists || allowedRoots.length === 0 ? "unknown" : options.repositoryPaths.every((repositoryPath) => allowedRoots.some((root) => isPathInside(repositoryPath, root))) ? "complete" : "partial";
5369
+ const portfolioCoverage = !configExists || allowedRoots.length === 0 ? "unknown" : options.repositoryPaths.every(
5370
+ (repositoryPath) => allowedRoots.some((root) => isPathInside(repositoryPath, root))
5371
+ ) ? "complete" : "partial";
4710
5372
  const bind = configExists && typeof configValue.host === "string" ? isLoopbackHost(configValue.host) ? "loopback" : "non-loopback" : "unknown";
4711
5373
  const directoryMode = existsSync23(configDirectory) ? modeString(statSync5(configDirectory).mode) : void 0;
4712
5374
  const fileMode = existsSync23(configPath) ? modeString(statSync5(configPath).mode) : void 0;
@@ -4725,17 +5387,24 @@ function inspectDevSpaceHealth(options) {
4725
5387
  if (!installedResult.ok) unhealthy("\u672C\u673A\u672A\u53D1\u73B0 DevSpace\uFF1B\u65E0\u6CD5\u4F7F\u7528\u5BBF\u4E3B\u5DE5\u4F5C\u533A\u670D\u52A1\u3002");
4726
5388
  if (!configExists) unhealthy("\u7F3A\u5C11 ~/.devspace/config.json\u3002");
4727
5389
  if (!existsSync23(authPath)) unhealthy("\u7F3A\u5C11 ~/.devspace/auth.json\u3002");
4728
- if (directoryMode && directoryMode !== "700") unhealthy(`~/.devspace \u76EE\u5F55\u6743\u9650\u4E3A ${directoryMode}\uFF0C\u5E94\u6536\u7D27\u4E3A 700\u3002`);
5390
+ if (directoryMode && directoryMode !== "700")
5391
+ unhealthy(`~/.devspace \u76EE\u5F55\u6743\u9650\u4E3A ${directoryMode}\uFF0C\u5E94\u6536\u7D27\u4E3A 700\u3002`);
4729
5392
  if (fileMode && fileMode !== "600") unhealthy(`DevSpace \u914D\u7F6E\u6587\u4EF6\u6743\u9650\u4E3A ${fileMode}\uFF0C\u5E94\u4E3A 600\u3002`);
4730
5393
  if (authMode && authMode !== "600") unhealthy(`DevSpace \u8BA4\u8BC1\u6587\u4EF6\u6743\u9650\u4E3A ${authMode}\uFF0C\u5E94\u4E3A 600\u3002`);
4731
- if (bind === "non-loopback") unhealthy("DevSpace \u76F4\u63A5\u7ED1\u5B9A\u5230\u975E\u672C\u673A\u5730\u5740\uFF1B\u5E94\u4F7F\u7528 loopback \u5E76\u7531\u53D7\u63A7\u4EE3\u7406\u66B4\u9732\u3002");
5394
+ if (bind === "non-loopback")
5395
+ unhealthy("DevSpace \u76F4\u63A5\u7ED1\u5B9A\u5230\u975E\u672C\u673A\u5730\u5740\uFF1B\u5E94\u4F7F\u7528 loopback \u5E76\u7531\u53D7\u63A7\u4EE3\u7406\u66B4\u9732\u3002");
4732
5396
  if (doctor === "failed") unhealthy("devspace doctor \u672A\u901A\u8FC7\u3002");
4733
5397
  if (portfolioCoverage === "partial") attention("DevSpace allowedRoots \u6CA1\u6709\u8986\u76D6\u5168\u90E8\u5DF2\u767B\u8BB0\u4ED3\u5E93\u3002");
4734
5398
  if (installedResult.ok && !pid) attention("DevSpace \u5DF2\u5B89\u88C5\u4F46\u5F53\u524D\u6CA1\u6709\u8FD0\u884C\u3002");
4735
5399
  if (options.expectedToolMode && pid && processToolMode !== options.expectedToolMode) {
4736
- attention(`\u8FD0\u884C\u4E2D\u7684 DevSpace \u5DE5\u5177\u6A21\u5F0F\u4E3A ${processToolMode ?? "unknown"}\uFF0C\u671F\u671B ${options.expectedToolMode}\u3002`);
5400
+ attention(
5401
+ `\u8FD0\u884C\u4E2D\u7684 DevSpace \u5DE5\u5177\u6A21\u5F0F\u4E3A ${processToolMode ?? "unknown"}\uFF0C\u671F\u671B ${options.expectedToolMode}\u3002`
5402
+ );
4737
5403
  }
4738
- if (update === "available") attention(`DevSpace \u6709\u65B0\u7248\u672C\uFF1A${installedVersion} \u2192 ${latestVersion}\uFF1B\u5347\u7EA7\u524D\u4ECD\u5E94\u68C0\u67E5\u53D8\u66F4\u4E0E\u517C\u5BB9\u6027\u3002`);
5404
+ if (update === "available")
5405
+ attention(
5406
+ `DevSpace \u6709\u65B0\u7248\u672C\uFF1A${installedVersion} \u2192 ${latestVersion}\uFF1B\u5347\u7EA7\u524D\u4ECD\u5E94\u68C0\u67E5\u53D8\u66F4\u4E0E\u517C\u5BB9\u6027\u3002`
5407
+ );
4739
5408
  return {
4740
5409
  status,
4741
5410
  installed: installedResult.ok,
@@ -4784,18 +5453,25 @@ function isPathInside(path, root) {
4784
5453
  }
4785
5454
  function inspectSkillRoot(path) {
4786
5455
  const exists = pathLexists(path) && safeIsDirectory(path);
4787
- const names = exists ? safeReadDir(path).filter((name) => !name.startsWith(".") && existsSync23(join24(path, name, "SKILL.md"))) : [];
5456
+ const names = exists ? safeReadDir(path).filter(
5457
+ (name) => !name.startsWith(".") && existsSync23(join24(path, name, "SKILL.md"))
5458
+ ) : [];
4788
5459
  return { path, exists, names };
4789
5460
  }
4790
5461
  function claudeProjectLocalMcpNames(homeDir, root) {
4791
5462
  if (!homeDir) return [];
4792
- const value = readJson4(join24(homeDir, ".claude.json"));
5463
+ const value = readJson4(join24(homeDir, MCP_DISCOVERY_PATHS.user.claudeCode));
4793
5464
  if (!isRecord3(value) || !isRecord3(value.projects)) return [];
4794
- const candidates = new Set([resolve5(root), safeRealpath(root)].filter((path) => Boolean(path)));
5465
+ const candidates = new Set(
5466
+ [resolve5(root), safeRealpath(root)].filter((path) => Boolean(path))
5467
+ );
4795
5468
  const names = /* @__PURE__ */ new Set();
4796
5469
  for (const [path, project] of Object.entries(value.projects)) {
4797
- const projectPaths = [resolve5(path), safeRealpath(path)].filter((candidate) => Boolean(candidate));
4798
- if (!projectPaths.some((candidate) => candidates.has(candidate)) || !isRecord3(project) || !isRecord3(project.mcpServers)) continue;
5470
+ const projectPaths = [resolve5(path), safeRealpath(path)].filter(
5471
+ (candidate) => Boolean(candidate)
5472
+ );
5473
+ if (!projectPaths.some((candidate) => candidates.has(candidate)) || !isRecord3(project) || !isRecord3(project.mcpServers))
5474
+ continue;
4799
5475
  for (const name of Object.keys(project.mcpServers)) names.add(name);
4800
5476
  }
4801
5477
  return [...names].sort();
@@ -4822,16 +5498,20 @@ function inspectGrokProject(root, homeDir, grokVersion) {
4822
5498
  });
4823
5499
  if (!grokVersion) return empty("unavailable");
4824
5500
  try {
4825
- const value = JSON.parse(execFileSync("grok", ["inspect", "--json"], {
4826
- cwd: root,
4827
- encoding: "utf8",
4828
- env: { ...process.env, HOME: homeDir, GROK_HOME: join24(homeDir, ".grok") },
4829
- maxBuffer: 10 * 1024 * 1024,
4830
- stdio: ["ignore", "pipe", "ignore"],
4831
- timeout: 8e3
4832
- }));
5501
+ const value = JSON.parse(
5502
+ execFileSync("grok", ["inspect", "--json"], {
5503
+ cwd: root,
5504
+ encoding: "utf8",
5505
+ env: { ...process.env, HOME: homeDir, GROK_HOME: join24(homeDir, ".grok") },
5506
+ maxBuffer: 10 * 1024 * 1024,
5507
+ stdio: ["ignore", "pipe", "ignore"],
5508
+ timeout: 8e3
5509
+ })
5510
+ );
4833
5511
  if (!isRecord3(value)) return empty("failed");
4834
- 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
+ );
4835
5515
  const localClaudeNames = new Set(claudeProjectLocalMcpNames(homeDir, root));
4836
5516
  const effectiveMcp = Array.isArray(value.mcpServers) ? value.mcpServers.flatMap((item) => {
4837
5517
  if (!isRecord3(item) || typeof item.name !== "string") return [];
@@ -4839,13 +5519,23 @@ function inspectGrokProject(root, homeDir, grokVersion) {
4839
5519
  const sourceType = typeof source.type === "string" ? source.type : "unknown";
4840
5520
  const sourcePath = typeof source.path === "string" ? source.path : void 0;
4841
5521
  const vendor = typeof item.vendor === "string" ? item.vendor : sourceType;
4842
- return [{
4843
- name: item.name,
4844
- vendor,
4845
- scope: inferGrokMcpScope(item.name, sourceType, sourcePath, root, homeDir, userClaudeNames, localClaudeNames),
4846
- sourceType,
4847
- ...sourcePath ? { sourcePath } : {}
4848
- }];
5522
+ return [
5523
+ {
5524
+ name: item.name,
5525
+ vendor,
5526
+ scope: inferGrokMcpScope(
5527
+ item.name,
5528
+ sourceType,
5529
+ sourcePath,
5530
+ root,
5531
+ homeDir,
5532
+ userClaudeNames,
5533
+ localClaudeNames
5534
+ ),
5535
+ sourceType,
5536
+ ...sourcePath ? { sourcePath } : {}
5537
+ }
5538
+ ];
4849
5539
  }) : [];
4850
5540
  const skillCounts = { project: 0, userOrCompatible: 0, plugin: 0, bundled: 0 };
4851
5541
  if (Array.isArray(value.skills)) {
@@ -4853,7 +5543,8 @@ function inspectGrokProject(root, homeDir, grokVersion) {
4853
5543
  if (!isRecord3(item) || !isRecord3(item.source)) continue;
4854
5544
  const type = item.source.type;
4855
5545
  const path = item.source.path;
4856
- if (typeof path === "string" && path.includes(sep + ".grok" + sep + "bundled" + sep)) skillCounts.bundled += 1;
5546
+ if (typeof path === "string" && path.includes(sep + ".grok" + sep + "bundled" + sep))
5547
+ skillCounts.bundled += 1;
4857
5548
  else if (type === "project") skillCounts.project += 1;
4858
5549
  else if (type === "plugin") skillCounts.plugin += 1;
4859
5550
  else skillCounts.userOrCompatible += 1;
@@ -4889,9 +5580,10 @@ function inferGrokMcpScope(name, sourceType, sourcePath, root, homeDir, userClau
4889
5580
  }
4890
5581
  const resolvedSource = safeRealpath(sourcePath) ?? resolve5(sourcePath);
4891
5582
  const resolvedRoot = safeRealpath(root) ?? resolve5(root);
4892
- if (resolvedSource === join24(resolvedRoot, ".mcp.json")) return "project-shared";
5583
+ if (resolvedSource === join24(resolvedRoot, MCP_DISCOVERY_PATHS.project.claudeCodeShared))
5584
+ return "project-shared";
4893
5585
  if (resolvedSource.startsWith(resolvedRoot + sep)) return "project";
4894
- if (homeDir && resolvedSource === join24(resolve5(homeDir), ".claude.json")) {
5586
+ if (homeDir && resolvedSource === join24(resolve5(homeDir), MCP_DISCOVERY_PATHS.user.claudeCode)) {
4895
5587
  if (localClaudeNames.has(name)) return "project-local";
4896
5588
  if (userClaudeNames.has(name)) return "user";
4897
5589
  }
@@ -4980,7 +5672,11 @@ function recordOrEmpty(value) {
4980
5672
  return isRecord3(value) ? value : {};
4981
5673
  }
4982
5674
  function inspectSkillRegistry(executionEngineRoot) {
4983
- 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
+ };
4984
5680
  const agentAssetsRoot = join24(executionEngineRoot, "agent-assets");
4985
5681
  const registry = readJson4(join24(agentAssetsRoot, "registry.json"));
4986
5682
  const assets = isRecord3(registry) && Array.isArray(registry.assets) ? registry.assets : [];
@@ -4993,15 +5689,49 @@ function inspectSkillRegistry(executionEngineRoot) {
4993
5689
  if (!isRecord3(bundle) || !Array.isArray(bundle.assets)) continue;
4994
5690
  for (const id of bundle.assets) if (typeof id === "string") bundledIds.add(id);
4995
5691
  }
4996
- const sourceRoots = [join24(agentAssetsRoot, "skills/pie-skills"), join24(agentAssetsRoot, "skills/npx-skills/.agents/skills")];
4997
- const source = sourceRoots.reduce((count, root) => count + safeReadDir(root).filter((name) => existsSync23(join24(root, name, "SKILL.md"))).length, 0);
5692
+ const sourceRoots = [
5693
+ join24(agentAssetsRoot, "skills/pie-skills"),
5694
+ join24(agentAssetsRoot, "skills/npx-skills/.agents/skills")
5695
+ ];
5696
+ const source = sourceRoots.reduce(
5697
+ (count, root) => count + safeReadDir(root).filter((name) => existsSync23(join24(root, name, "SKILL.md"))).length,
5698
+ 0
5699
+ );
4998
5700
  return {
4999
- source,
5000
- registered: registeredSkills.length,
5001
- bundled: registeredSkills.filter((asset) => isRecord3(asset) && typeof asset.id === "string" && bundledIds.has(asset.id)).length,
5002
- 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
+ })
5003
5721
  };
5004
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
+ }
5005
5735
  function jsonObjectKeys(path, key) {
5006
5736
  const value = readJson4(path);
5007
5737
  if (!isRecord3(value) || !isRecord3(value[key])) return [];
@@ -5011,7 +5741,7 @@ function tomlMcpNames(path) {
5011
5741
  if (!existsSync23(path)) return [];
5012
5742
  const names = /* @__PURE__ */ new Set();
5013
5743
  for (const line of safeRead(path).split(/\r?\n/)) {
5014
- const match = line.match(/^\s*\[mcp_servers\.(?:"([^"]+)"|([^\.\]]+))\]\s*$/);
5744
+ const match = line.match(/^\s*\[mcp_servers\.(?:"([^"]+)"|([^.\]]+))\]\s*$/);
5015
5745
  const name = match?.[1] ?? match?.[2];
5016
5746
  if (name) names.add(name);
5017
5747
  }
@@ -5033,7 +5763,7 @@ function safeRead(path) {
5033
5763
  }
5034
5764
  function safeReadDir(path) {
5035
5765
  try {
5036
- return readdirSync9(path).sort();
5766
+ return readdirSync10(path).sort();
5037
5767
  } catch {
5038
5768
  return [];
5039
5769
  }
@@ -5055,7 +5785,10 @@ function pathLexists(path) {
5055
5785
  }
5056
5786
  function gitTracks(root, path) {
5057
5787
  try {
5058
- execFileSync("git", ["ls-files", "--error-unmatch", "--", path], { cwd: root, stdio: "ignore" });
5788
+ execFileSync("git", ["ls-files", "--error-unmatch", "--", path], {
5789
+ cwd: root,
5790
+ stdio: "ignore"
5791
+ });
5059
5792
  return true;
5060
5793
  } catch {
5061
5794
  return false;
@@ -5074,7 +5807,7 @@ function isRecord3(value) {
5074
5807
  return typeof value === "object" && value !== null && !Array.isArray(value);
5075
5808
  }
5076
5809
  function findDashboardAssets() {
5077
- const packageRoot2 = dirname13(dirname13(fileURLToPath4(import.meta.url)));
5810
+ const packageRoot2 = dirname14(dirname14(fileURLToPath4(import.meta.url)));
5078
5811
  const candidates = [
5079
5812
  process.env.PGS_DASHBOARD_ASSETS_DIR,
5080
5813
  join24(packageRoot2, ".dashboard-build"),
@@ -5085,7 +5818,10 @@ function findDashboardAssets() {
5085
5818
  join24(process.cwd(), "packages/pro-gov/assets/portfolio-dashboard")
5086
5819
  ].filter((value) => Boolean(value));
5087
5820
  const match = candidates.find((path) => existsSync23(join24(path, "index.html")));
5088
- if (!match) throw new Error("Portfolio dashboard assets were not built. Run pnpm --filter @pieai/pro-gov build.");
5821
+ if (!match)
5822
+ throw new Error(
5823
+ "Portfolio dashboard assets were not built. Run pnpm --filter @pieai/pro-gov build."
5824
+ );
5089
5825
  return match;
5090
5826
  }
5091
5827
  function safeJavaScriptJson(value) {
@@ -5121,7 +5857,12 @@ function runPortfolioAiHealth(args) {
5121
5857
  return 1;
5122
5858
  }
5123
5859
  const targetId = options.value.targetId && options.value.targetId !== "all" ? options.value.targetId : void 0;
5124
- 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)) {
5125
5866
  console.error(`Unknown portfolio target: ${targetId}`);
5126
5867
  return 1;
5127
5868
  }
@@ -5130,11 +5871,6 @@ function runPortfolioAiHealth(args) {
5130
5871
  secretsRoot: options.value.secretsRoot,
5131
5872
  targetId
5132
5873
  });
5133
- const allRepositoryIds = [
5134
- loaded.manifest.controlPlane?.id,
5135
- loaded.manifest.executionEngine?.id,
5136
- ...loaded.manifest.targets.map((target) => target.id)
5137
- ].filter((id) => Boolean(id));
5138
5874
  const existing = targetId ? readExistingAiHealthReport(options.value.outDir, loaded.manifest.portfolioId) : void 0;
5139
5875
  const report = targetId ? mergePortfolioAiHealthReport(existing, latest, allRepositoryIds) : latest;
5140
5876
  const written = writePortfolioAiHealthReport(report, options.value.outDir);
@@ -5600,7 +6336,7 @@ var COMMANDS = [
5600
6336
  "assets list [--json] [--visibility public|private|third-party|all]",
5601
6337
  "assets discover [--target <path>] [--json]",
5602
6338
  "assets recommend [--target <path>] [--json]",
5603
- "assets plan --bundle <bundle-id> [--target <path>] [--json]",
6339
+ "assets plan [--bundle <bundle-id>] [--asset <asset-id>] [--target <path>] [--json]",
5604
6340
  "assets apply --plan <path>",
5605
6341
  "assets check [--target <path>] [--strict-registry] [--json]",
5606
6342
  "assets public-check [--public-root <path>] [--private-root <path>] [--json]",