@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/README.md +9 -10
- package/assets/docs/reference/adoption/adoption-playbook.md +8 -8
- package/assets/docs/reference/adoption/migration-v1.0.md +3 -3
- package/assets/docs/reference/adoption/recommended-agent-tooling.md +16 -5
- package/assets/integrations/.gitkeep +1 -0
- package/assets/portfolio-dashboard/app.css +1 -1
- package/assets/portfolio-dashboard/app.js +8 -8
- package/assets/profiles/engineering-runtime/manifest.yml +0 -2
- package/assets/profiles/engineering-runtime/profile.md +5 -4
- package/assets/public-agent-assets/registry.json +1 -1
- package/assets/starter/.github/workflows/docs-check.yml +11 -1
- package/assets/starter/docs/governance/agents-routing/engineering-runtime-v1.1.md +19 -1
- package/assets/starter/docs/governance/boundary.md +3 -4
- package/assets/starter/docs/governance/doc-agent-rules.md +2 -2
- package/assets/starter/docs/governance/doc-types.md +2 -2
- package/assets/starter/docs/governance/ssot-v1.1.md +3 -5
- package/assets/starter/docs/governance/templates/adr.md +24 -2
- package/assets/starter/docs/reference/documentation-map.md +1 -1
- package/dist/cli.js +958 -222
- package/package.json +9 -9
- package/assets/integrations/mattpocock-skills.md +0 -42
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 {
|
|
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(
|
|
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 =
|
|
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
|
|
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
|
|
667
|
-
const ids =
|
|
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> [--
|
|
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
|
|
2823
|
-
const legacyDirectories = inspectLegacyDirectories(root);
|
|
2827
|
+
function inspectHostRedundancy(options = {}) {
|
|
2824
2828
|
const homeDir = options.homeDir ?? homedir();
|
|
2825
|
-
const cachePaths = getPlaywrightCachePaths(
|
|
2826
|
-
|
|
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 =
|
|
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
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
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
|
-
|
|
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(
|
|
2895
|
+
revisionCount = readdirSync7(path, { withFileTypes: true }).filter(
|
|
2896
|
+
(entry) => entry.isDirectory()
|
|
2897
|
+
).length;
|
|
2861
2898
|
} catch {
|
|
2862
2899
|
revisionCount = 0;
|
|
2863
2900
|
}
|
|
2864
|
-
|
|
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
|
|
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
|
|
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 {
|
|
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 {
|
|
4036
|
+
return {
|
|
4037
|
+
name: requirement.name,
|
|
4038
|
+
expected: requirement.version,
|
|
4039
|
+
status: "not-applicable"
|
|
4040
|
+
};
|
|
3928
4041
|
}
|
|
3929
|
-
const
|
|
3930
|
-
|
|
3931
|
-
|
|
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(
|
|
3942
|
-
|
|
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 [
|
|
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
|
-
|
|
3988
|
-
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
4215
|
-
const
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
|
|
4221
|
-
|
|
4222
|
-
|
|
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:
|
|
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 ?? [])
|
|
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(
|
|
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)
|
|
4299
|
-
|
|
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,
|
|
4320
|
-
claudeCodeProjectShared: jsonObjectKeys(
|
|
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,
|
|
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(
|
|
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
|
|
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, {
|
|
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)
|
|
4334
|
-
|
|
4335
|
-
|
|
4336
|
-
|
|
4337
|
-
if (
|
|
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")
|
|
4340
|
-
|
|
4341
|
-
|
|
4342
|
-
|
|
4343
|
-
if (entries.claude === "
|
|
4344
|
-
|
|
4345
|
-
if (entries.
|
|
4346
|
-
|
|
4347
|
-
if (
|
|
4348
|
-
|
|
4349
|
-
|
|
4350
|
-
|
|
4351
|
-
if (
|
|
4352
|
-
|
|
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))
|
|
4356
|
-
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
|
|
4361
|
-
if (
|
|
4362
|
-
|
|
4363
|
-
|
|
4364
|
-
|
|
4365
|
-
|
|
4366
|
-
|
|
4367
|
-
if (
|
|
4368
|
-
|
|
4369
|
-
|
|
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(
|
|
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
|
|
4392
|
-
if (!
|
|
4393
|
-
const
|
|
4394
|
-
|
|
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(
|
|
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)
|
|
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(
|
|
4928
|
+
const projectType = governance?.projectTypes.find(
|
|
4929
|
+
(candidate) => candidate.id === endpoint.projectType
|
|
4930
|
+
);
|
|
4405
4931
|
const packages = collectPackageNames(root);
|
|
4406
|
-
const technologyById = new Map(
|
|
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) => ({
|
|
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
|
-
|
|
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 [
|
|
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(
|
|
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, {
|
|
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")
|
|
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(
|
|
4466
|
-
|
|
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
|
|
4519
|
-
|
|
4520
|
-
const
|
|
4521
|
-
|
|
4522
|
-
|
|
4523
|
-
|
|
4524
|
-
|
|
4525
|
-
|
|
4526
|
-
|
|
4527
|
-
|
|
4528
|
-
|
|
4529
|
-
|
|
4530
|
-
|
|
4531
|
-
|
|
4532
|
-
const
|
|
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
|
-
|
|
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
|
|
4539
|
-
locked,
|
|
4540
|
-
installed:
|
|
4541
|
-
discoverable:
|
|
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:
|
|
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([
|
|
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
|
|
5249
|
+
for (const entry of readdirSync10(current, { withFileTypes: true })) {
|
|
4598
5250
|
if (entry.isDirectory()) {
|
|
4599
|
-
if (!SKIP_ENV_DIRECTORIES.has(entry.name))
|
|
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
|
|
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,
|
|
4661
|
-
const claudeConfig = join24(homeDir,
|
|
4662
|
-
const grokConfig = join24(homeDir,
|
|
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(
|
|
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(
|
|
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(
|
|
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")
|
|
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")
|
|
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(
|
|
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")
|
|
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(
|
|
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,
|
|
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(
|
|
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(
|
|
4798
|
-
|
|
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(
|
|
4826
|
-
|
|
4827
|
-
|
|
4828
|
-
|
|
4829
|
-
|
|
4830
|
-
|
|
4831
|
-
|
|
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(
|
|
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
|
-
|
|
4844
|
-
|
|
4845
|
-
|
|
4846
|
-
|
|
4847
|
-
|
|
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))
|
|
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,
|
|
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),
|
|
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)
|
|
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 = [
|
|
4997
|
-
|
|
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
|
-
|
|
5000
|
-
|
|
5001
|
-
|
|
5002
|
-
|
|
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\.(?:"([^"]+)"|([
|
|
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
|
|
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], {
|
|
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 =
|
|
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)
|
|
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
|
-
|
|
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]",
|