@pieai/pro-gov 0.6.0 → 0.7.0
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 +2 -2
- package/assets/portfolio-dashboard/app.js +3 -3
- package/cli-guide.md +13 -9
- package/dist/cli.js +508 -158
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -2584,6 +2584,9 @@ function formatProjectLensInspection(report) {
|
|
|
2584
2584
|
`ai-host-ssot: ${report.hostSsot.compliant ? "compliant" : "non-compliant"}`,
|
|
2585
2585
|
`claude-entry-link: ${formatLink(report.hostSsot.claudeEntry)}`,
|
|
2586
2586
|
`claude-skills-link: ${formatLink(report.hostSsot.claudeSkills)}`,
|
|
2587
|
+
`user-host-ssot: ${report.userHostSsot.compliant ? "compliant" : "non-compliant"}`,
|
|
2588
|
+
`verification: ${report.verification.status} (missing: ${formatList(report.verification.missing)})`,
|
|
2589
|
+
`redundancy: ${report.redundancy.status}`,
|
|
2587
2590
|
`package-scripts: ${formatList(report.packageJson?.scripts ?? [])}`,
|
|
2588
2591
|
`dependencies: ${formatList(report.packageJson?.dependencies ?? [])}`,
|
|
2589
2592
|
`dev-dependencies: ${formatList(report.packageJson?.devDependencies ?? [])}`,
|
|
@@ -2624,6 +2627,20 @@ function renderProjectLensMarkdownReport(report) {
|
|
|
2624
2627
|
`- Canonical skills: \`${report.hostSsot.canonicalSkills.path}\` (${report.hostSsot.canonicalSkills.status})`,
|
|
2625
2628
|
`- Claude skills: ${formatLink(report.hostSsot.claudeSkills)}`,
|
|
2626
2629
|
`- Issues: ${formatList(report.hostSsot.issues)}`,
|
|
2630
|
+
`- User skills SSOT: ${report.userHostSsot.compliant ? "compliant" : "non-compliant"}`,
|
|
2631
|
+
`- User SSOT issues: ${formatList(report.userHostSsot.issues)}`,
|
|
2632
|
+
"",
|
|
2633
|
+
"## Verification Gates",
|
|
2634
|
+
"",
|
|
2635
|
+
`- Status: ${report.verification.status}`,
|
|
2636
|
+
`- Required scripts: ${formatList(report.verification.requiredScripts)}`,
|
|
2637
|
+
`- Missing scripts: ${formatList(report.verification.missing)}`,
|
|
2638
|
+
"",
|
|
2639
|
+
"## Redundancy Evidence",
|
|
2640
|
+
"",
|
|
2641
|
+
`- Status: ${report.redundancy.status}`,
|
|
2642
|
+
`- Legacy directories: ${formatList(report.redundancy.legacyDirectories.map((entry) => entry.path))}`,
|
|
2643
|
+
`- Playwright caches: ${formatList(report.redundancy.playwrightCaches.filter((entry) => entry.exists).map((entry) => `${entry.path} (${entry.bytes} bytes)`))}`,
|
|
2627
2644
|
"",
|
|
2628
2645
|
"## Package",
|
|
2629
2646
|
"",
|
|
@@ -2669,8 +2686,9 @@ function formatLink(link) {
|
|
|
2669
2686
|
|
|
2670
2687
|
// src/lens/scan.ts
|
|
2671
2688
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
2672
|
-
import { existsSync as
|
|
2673
|
-
import {
|
|
2689
|
+
import { existsSync as existsSync18, readdirSync as readdirSync8, readFileSync as readFileSync12, statSync as statSync4 } from "node:fs";
|
|
2690
|
+
import { homedir as homedir2 } from "node:os";
|
|
2691
|
+
import { join as join20, relative as relative7 } from "node:path";
|
|
2674
2692
|
|
|
2675
2693
|
// src/host-ssot.ts
|
|
2676
2694
|
import {
|
|
@@ -2795,6 +2813,117 @@ function safeLstat2(path) {
|
|
|
2795
2813
|
}
|
|
2796
2814
|
}
|
|
2797
2815
|
|
|
2816
|
+
// src/portfolio/redundancy.ts
|
|
2817
|
+
import { existsSync as existsSync16, readdirSync as readdirSync7, statSync as statSync3 } from "node:fs";
|
|
2818
|
+
import { homedir } from "node:os";
|
|
2819
|
+
import { join as join18 } from "node:path";
|
|
2820
|
+
var DEFAULT_CACHE_THRESHOLD_BYTES = 1e9;
|
|
2821
|
+
var MAX_CACHE_ENTRIES = 2e4;
|
|
2822
|
+
function inspectProjectRedundancy(root, options = {}) {
|
|
2823
|
+
const legacyDirectories = inspectLegacyDirectories(root);
|
|
2824
|
+
const homeDir = options.homeDir ?? homedir();
|
|
2825
|
+
const cachePaths = getPlaywrightCachePaths(homeDir, options.playwrightBrowsersPath ?? process.env.PLAYWRIGHT_BROWSERS_PATH);
|
|
2826
|
+
const playwrightCaches = cachePaths.map((path) => inspectPlaywrightCache(path));
|
|
2827
|
+
const cacheThresholdBytes = options.cacheThresholdBytes ?? DEFAULT_CACHE_THRESHOLD_BYTES;
|
|
2828
|
+
const status = legacyDirectories.length > 0 || playwrightCaches.some((cache) => cache.exists && (cache.bytes >= cacheThresholdBytes || cache.truncated)) ? "attention" : "clean";
|
|
2829
|
+
return { status, legacyDirectories, playwrightCaches };
|
|
2830
|
+
}
|
|
2831
|
+
function inspectLegacyDirectories(root) {
|
|
2832
|
+
const relativePath = ".agent";
|
|
2833
|
+
const path = join18(root, relativePath);
|
|
2834
|
+
if (!existsSync16(path)) return [];
|
|
2835
|
+
const stats = collectDirectoryStats(path);
|
|
2836
|
+
return [{
|
|
2837
|
+
path: relativePath,
|
|
2838
|
+
kind: "legacy-ai-directory",
|
|
2839
|
+
fileCount: stats.fileCount,
|
|
2840
|
+
bytes: stats.bytes,
|
|
2841
|
+
reason: "\u9879\u76EE\u7EA7\u65E7 AI \u5BBF\u4E3B\u76EE\u5F55\uFF1B\u5E94\u4E0E .agents/skills \u7684 SSOT \u9010\u9879\u6BD4\u5BF9\u540E\u518D\u51B3\u5B9A\u662F\u5426\u8FC1\u79FB\uFF0C\u626B\u63CF\u5668\u4E0D\u81EA\u52A8\u5220\u9664\u3002"
|
|
2842
|
+
}];
|
|
2843
|
+
}
|
|
2844
|
+
function getPlaywrightCachePaths(homeDir, configuredPath) {
|
|
2845
|
+
const candidates = [
|
|
2846
|
+
configuredPath && configuredPath !== "0" ? configuredPath : void 0,
|
|
2847
|
+
join18(homeDir, "Library/Caches/ms-playwright"),
|
|
2848
|
+
join18(homeDir, ".cache/ms-playwright"),
|
|
2849
|
+
join18(homeDir, "AppData/Local/ms-playwright")
|
|
2850
|
+
].filter((path) => Boolean(path));
|
|
2851
|
+
return [...new Set(candidates)];
|
|
2852
|
+
}
|
|
2853
|
+
function inspectPlaywrightCache(path) {
|
|
2854
|
+
if (!existsSync16(path)) {
|
|
2855
|
+
return { path, exists: false, fileCount: 0, bytes: 0, revisionCount: 0, truncated: false };
|
|
2856
|
+
}
|
|
2857
|
+
const stats = collectDirectoryStats(path);
|
|
2858
|
+
let revisionCount = 0;
|
|
2859
|
+
try {
|
|
2860
|
+
revisionCount = readdirSync7(path, { withFileTypes: true }).filter((entry) => entry.isDirectory()).length;
|
|
2861
|
+
} catch {
|
|
2862
|
+
revisionCount = 0;
|
|
2863
|
+
}
|
|
2864
|
+
return { path, exists: true, fileCount: stats.fileCount, bytes: stats.bytes, revisionCount, truncated: stats.truncated };
|
|
2865
|
+
}
|
|
2866
|
+
function collectDirectoryStats(root) {
|
|
2867
|
+
let fileCount = 0;
|
|
2868
|
+
let bytes = 0;
|
|
2869
|
+
let truncated = false;
|
|
2870
|
+
const pending = [root];
|
|
2871
|
+
while (pending.length > 0) {
|
|
2872
|
+
const current = pending.pop();
|
|
2873
|
+
if (!current) continue;
|
|
2874
|
+
let entries;
|
|
2875
|
+
try {
|
|
2876
|
+
entries = readdirSync7(current, { withFileTypes: true });
|
|
2877
|
+
} catch {
|
|
2878
|
+
continue;
|
|
2879
|
+
}
|
|
2880
|
+
for (const entry of entries) {
|
|
2881
|
+
if (fileCount >= MAX_CACHE_ENTRIES) {
|
|
2882
|
+
truncated = true;
|
|
2883
|
+
break;
|
|
2884
|
+
}
|
|
2885
|
+
const path = join18(current, entry.name);
|
|
2886
|
+
if (entry.isDirectory()) {
|
|
2887
|
+
pending.push(path);
|
|
2888
|
+
} else if (entry.isFile()) {
|
|
2889
|
+
fileCount += 1;
|
|
2890
|
+
try {
|
|
2891
|
+
bytes += statSync3(path).size;
|
|
2892
|
+
} catch {
|
|
2893
|
+
}
|
|
2894
|
+
}
|
|
2895
|
+
}
|
|
2896
|
+
if (truncated) break;
|
|
2897
|
+
}
|
|
2898
|
+
return { fileCount, bytes, truncated };
|
|
2899
|
+
}
|
|
2900
|
+
|
|
2901
|
+
// src/portfolio/verification.ts
|
|
2902
|
+
import { existsSync as existsSync17, readFileSync as readFileSync11 } from "node:fs";
|
|
2903
|
+
import { join as join19 } from "node:path";
|
|
2904
|
+
var REQUIRED_PROJECT_SCRIPTS = ["typecheck", "lint", "format:check", "verify"];
|
|
2905
|
+
function inspectProjectVerification(root) {
|
|
2906
|
+
const packageJson = readPackageJson(join19(root, "package.json"));
|
|
2907
|
+
const scripts = Object.fromEntries(
|
|
2908
|
+
REQUIRED_PROJECT_SCRIPTS.map((name) => [name, typeof packageJson?.scripts?.[name] === "string" ? packageJson.scripts[name] : void 0])
|
|
2909
|
+
);
|
|
2910
|
+
const missing = REQUIRED_PROJECT_SCRIPTS.filter((name) => typeof scripts[name] !== "string" || scripts[name]?.trim().length === 0);
|
|
2911
|
+
return {
|
|
2912
|
+
status: packageJson ? missing.length === 0 ? "compliant" : "attention" : "unknown",
|
|
2913
|
+
requiredScripts: REQUIRED_PROJECT_SCRIPTS,
|
|
2914
|
+
scripts,
|
|
2915
|
+
missing
|
|
2916
|
+
};
|
|
2917
|
+
}
|
|
2918
|
+
function readPackageJson(path) {
|
|
2919
|
+
if (!existsSync17(path)) return void 0;
|
|
2920
|
+
try {
|
|
2921
|
+
return JSON.parse(readFileSync11(path, "utf8"));
|
|
2922
|
+
} catch {
|
|
2923
|
+
return void 0;
|
|
2924
|
+
}
|
|
2925
|
+
}
|
|
2926
|
+
|
|
2798
2927
|
// src/lens/scan.ts
|
|
2799
2928
|
var ignoredDirectories = /* @__PURE__ */ new Set([
|
|
2800
2929
|
".git",
|
|
@@ -2811,7 +2940,7 @@ function scanProjectLensTarget(targetDir, options = {}) {
|
|
|
2811
2940
|
const markdownFiles = files.filter(
|
|
2812
2941
|
(file) => file.startsWith("docs/") && file.endsWith(".md")
|
|
2813
2942
|
);
|
|
2814
|
-
const packageJson =
|
|
2943
|
+
const packageJson = readPackageJson2(targetDir);
|
|
2815
2944
|
return {
|
|
2816
2945
|
targetDir,
|
|
2817
2946
|
scanScope: {
|
|
@@ -2821,25 +2950,31 @@ function scanProjectLensTarget(targetDir, options = {}) {
|
|
|
2821
2950
|
excludedFileCount: candidateFiles.length - files.length
|
|
2822
2951
|
},
|
|
2823
2952
|
aiEntryFiles: ["AGENTS.md", "CLAUDE.md"].filter(
|
|
2824
|
-
(file) =>
|
|
2953
|
+
(file) => existsSync18(join20(targetDir, file))
|
|
2825
2954
|
),
|
|
2826
2955
|
aiConfigFiles: [],
|
|
2827
2956
|
hostSsot: inspectProjectHostSsot(targetDir),
|
|
2957
|
+
userHostSsot: inspectUserSkillsSsot(options.homeDir ?? process.env.HOME ?? homedir2()),
|
|
2958
|
+
verification: inspectProjectVerification(targetDir),
|
|
2959
|
+
redundancy: inspectProjectRedundancy(targetDir, {
|
|
2960
|
+
homeDir: options.homeDir,
|
|
2961
|
+
playwrightBrowsersPath: options.playwrightBrowsersPath
|
|
2962
|
+
}),
|
|
2828
2963
|
packageJson,
|
|
2829
2964
|
docs: {
|
|
2830
|
-
hasDocsDirectory:
|
|
2965
|
+
hasDocsDirectory: existsSync18(join20(targetDir, "docs")),
|
|
2831
2966
|
markdownFileCount: markdownFiles.length,
|
|
2832
2967
|
governanceFiles: markdownFiles.filter((file) => file.startsWith("docs/governance/") || file.startsWith("docs/policy/")).sort()
|
|
2833
2968
|
},
|
|
2834
2969
|
git: readGitState(targetDir),
|
|
2835
|
-
largeFiles: files.map((file) => ({ path: file, bytes:
|
|
2970
|
+
largeFiles: files.map((file) => ({ path: file, bytes: statSync4(join20(targetDir, file)).size })).filter((file) => file.bytes >= largeFileBytes).sort((a, b) => b.bytes - a.bytes || a.path.localeCompare(b.path)).slice(0, 25)
|
|
2836
2971
|
};
|
|
2837
2972
|
}
|
|
2838
|
-
function
|
|
2839
|
-
const packageJsonPath =
|
|
2840
|
-
if (!
|
|
2973
|
+
function readPackageJson2(targetDir) {
|
|
2974
|
+
const packageJsonPath = join20(targetDir, "package.json");
|
|
2975
|
+
if (!existsSync18(packageJsonPath)) return void 0;
|
|
2841
2976
|
try {
|
|
2842
|
-
const packageJson = JSON.parse(
|
|
2977
|
+
const packageJson = JSON.parse(readFileSync12(packageJsonPath, "utf8"));
|
|
2843
2978
|
return {
|
|
2844
2979
|
scripts: Object.keys(packageJson.scripts ?? {}).sort(),
|
|
2845
2980
|
dependencies: Object.keys(packageJson.dependencies ?? {}).sort(),
|
|
@@ -2877,7 +3012,7 @@ function listProjectFiles(targetDir) {
|
|
|
2877
3012
|
"-z"
|
|
2878
3013
|
]);
|
|
2879
3014
|
if (gitFiles.ok) {
|
|
2880
|
-
return gitFiles.stdout.split("\0").filter(Boolean).map(toUnixPath4).filter((file) =>
|
|
3015
|
+
return gitFiles.stdout.split("\0").filter(Boolean).map(toUnixPath4).filter((file) => existsSync18(join20(targetDir, file))).sort();
|
|
2881
3016
|
}
|
|
2882
3017
|
const files = [];
|
|
2883
3018
|
collectFiles2(targetDir, targetDir, files);
|
|
@@ -2895,13 +3030,13 @@ function isFirstPartyEvidenceFile(file) {
|
|
|
2895
3030
|
return !excludedEvidencePrefixes.some((prefix) => file.startsWith(prefix));
|
|
2896
3031
|
}
|
|
2897
3032
|
function collectFiles2(rootDir, currentDir, files) {
|
|
2898
|
-
if (!
|
|
2899
|
-
for (const entry of
|
|
3033
|
+
if (!existsSync18(currentDir)) return;
|
|
3034
|
+
for (const entry of readdirSync8(currentDir, { withFileTypes: true })) {
|
|
2900
3035
|
if (entry.isDirectory()) {
|
|
2901
3036
|
if (ignoredDirectories.has(entry.name)) continue;
|
|
2902
|
-
collectFiles2(rootDir,
|
|
3037
|
+
collectFiles2(rootDir, join20(currentDir, entry.name), files);
|
|
2903
3038
|
} else if (entry.isFile()) {
|
|
2904
|
-
files.push(toUnixPath4(relative7(rootDir,
|
|
3039
|
+
files.push(toUnixPath4(relative7(rootDir, join20(currentDir, entry.name))));
|
|
2905
3040
|
}
|
|
2906
3041
|
}
|
|
2907
3042
|
}
|
|
@@ -3057,16 +3192,16 @@ function printUsage3() {
|
|
|
3057
3192
|
}
|
|
3058
3193
|
|
|
3059
3194
|
// src/commands/portfolio.ts
|
|
3060
|
-
import { existsSync as
|
|
3061
|
-
import { join as
|
|
3195
|
+
import { existsSync as existsSync24, readFileSync as readFileSync18 } from "node:fs";
|
|
3196
|
+
import { join as join25 } from "node:path";
|
|
3062
3197
|
|
|
3063
3198
|
// src/portfolio/manifest.ts
|
|
3064
|
-
import { existsSync as
|
|
3199
|
+
import { existsSync as existsSync19, readFileSync as readFileSync13 } from "node:fs";
|
|
3065
3200
|
import { dirname as dirname11, isAbsolute as isAbsolute4, resolve as resolve4 } from "node:path";
|
|
3066
3201
|
function loadPortfolioManifest(configPath) {
|
|
3067
3202
|
let parsed;
|
|
3068
3203
|
try {
|
|
3069
|
-
parsed = JSON.parse(
|
|
3204
|
+
parsed = JSON.parse(readFileSync13(configPath, "utf8"));
|
|
3070
3205
|
} catch (error) {
|
|
3071
3206
|
return {
|
|
3072
3207
|
configPath,
|
|
@@ -3235,7 +3370,7 @@ function validateEndpoint(value, field, issues, technologyCatalog) {
|
|
|
3235
3370
|
});
|
|
3236
3371
|
return;
|
|
3237
3372
|
}
|
|
3238
|
-
if (!
|
|
3373
|
+
if (!existsSync19(value.path)) {
|
|
3239
3374
|
issues.push({
|
|
3240
3375
|
type: "missing-path",
|
|
3241
3376
|
id: typeof value.id === "string" ? value.id : void 0,
|
|
@@ -3264,7 +3399,7 @@ function validateTechnologyGovernance(value, issues) {
|
|
|
3264
3399
|
issues.push({ type: "invalid-field", field: "technologyGovernance", message: "Portfolio technologyGovernance must be an object." });
|
|
3265
3400
|
return { technologies, projectTypes };
|
|
3266
3401
|
}
|
|
3267
|
-
validateAllowedFields(value, "technologyGovernance", ["strategySource", "technologies", "projectTypes"], issues);
|
|
3402
|
+
validateAllowedFields(value, "technologyGovernance", ["strategySource", "versionPolicy", "technologies", "projectTypes"], issues);
|
|
3268
3403
|
if (value.strategySource !== void 0 && (typeof value.strategySource !== "string" || value.strategySource.length === 0)) {
|
|
3269
3404
|
issues.push({ type: "invalid-field", field: "technologyGovernance.strategySource", message: "Technology strategySource must be a non-empty string." });
|
|
3270
3405
|
}
|
|
@@ -3327,8 +3462,68 @@ function validateTechnologyGovernance(value, issues) {
|
|
|
3327
3462
|
if (!technologies.has(technology)) issues.push({ type: "invalid-field", field: "technologyGovernance.projectTypes", message: `Project type ${projectType.id} references unknown technology: ${technology}` });
|
|
3328
3463
|
}
|
|
3329
3464
|
}
|
|
3465
|
+
validateVersionPolicy(value.versionPolicy, projectTypes, issues);
|
|
3330
3466
|
return { technologies, projectTypes };
|
|
3331
3467
|
}
|
|
3468
|
+
function validateVersionPolicy(value, projectTypes, issues) {
|
|
3469
|
+
if (value === void 0) return;
|
|
3470
|
+
if (!isRecord(value)) {
|
|
3471
|
+
issues.push({ type: "invalid-field", field: "technologyGovernance.versionPolicy", message: "Technology versionPolicy must be an object." });
|
|
3472
|
+
return;
|
|
3473
|
+
}
|
|
3474
|
+
validateAllowedFields(value, "versionPolicy", ["schemaVersion", "packageManager", "runtime", "packages"], issues);
|
|
3475
|
+
if (value.schemaVersion !== 1) {
|
|
3476
|
+
issues.push({ type: "invalid-field", field: "technologyGovernance.versionPolicy.schemaVersion", message: "Technology versionPolicy schemaVersion must be 1." });
|
|
3477
|
+
}
|
|
3478
|
+
validateVersionRequirement(value.packageManager, "technologyGovernance.versionPolicy.packageManager", issues);
|
|
3479
|
+
validateVersionRequirement(value.runtime, "technologyGovernance.versionPolicy.runtime", issues);
|
|
3480
|
+
if (!Array.isArray(value.packages)) {
|
|
3481
|
+
issues.push({ type: "invalid-field", field: "technologyGovernance.versionPolicy.packages", message: "Technology versionPolicy packages must be an array." });
|
|
3482
|
+
return;
|
|
3483
|
+
}
|
|
3484
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3485
|
+
for (const entry of value.packages) {
|
|
3486
|
+
if (!isRecord(entry)) {
|
|
3487
|
+
issues.push({ type: "invalid-field", field: "technologyGovernance.versionPolicy.packages", message: "Version policy package entries must be objects." });
|
|
3488
|
+
continue;
|
|
3489
|
+
}
|
|
3490
|
+
validateAllowedFields(entry, "versionPolicy.package", ["name", "version", "appliesTo"], issues);
|
|
3491
|
+
const name = typeof entry.name === "string" ? entry.name : "";
|
|
3492
|
+
if (!name || seen.has(name)) {
|
|
3493
|
+
issues.push({ type: "invalid-field", field: "technologyGovernance.versionPolicy.packages.name", message: `Version policy package name must be non-empty and unique: ${String(entry.name)}` });
|
|
3494
|
+
} else seen.add(name);
|
|
3495
|
+
if (typeof entry.version !== "string" || !isExactVersion(entry.version)) {
|
|
3496
|
+
issues.push({ type: "invalid-field", field: "technologyGovernance.versionPolicy.packages.version", message: `Version policy package version must be exact semver: ${String(entry.version)}` });
|
|
3497
|
+
}
|
|
3498
|
+
if (entry.appliesTo !== void 0) {
|
|
3499
|
+
validateOptionalStringArray(entry.appliesTo, name, "technologyGovernance.versionPolicy.packages.appliesTo", issues);
|
|
3500
|
+
if (Array.isArray(entry.appliesTo)) {
|
|
3501
|
+
for (const projectType of entry.appliesTo) {
|
|
3502
|
+
if (typeof projectType === "string" && !projectTypes.has(projectType)) {
|
|
3503
|
+
issues.push({ type: "invalid-field", field: "technologyGovernance.versionPolicy.packages.appliesTo", message: `Version policy references unknown project type: ${projectType}` });
|
|
3504
|
+
}
|
|
3505
|
+
}
|
|
3506
|
+
}
|
|
3507
|
+
}
|
|
3508
|
+
}
|
|
3509
|
+
}
|
|
3510
|
+
function validateVersionRequirement(value, field, issues) {
|
|
3511
|
+
if (value === void 0) return;
|
|
3512
|
+
if (!isRecord(value)) {
|
|
3513
|
+
issues.push({ type: "invalid-field", field, message: "Version requirement must be an object." });
|
|
3514
|
+
return;
|
|
3515
|
+
}
|
|
3516
|
+
validateAllowedFields(value, field, ["name", "version"], issues);
|
|
3517
|
+
if (typeof value.name !== "string" || value.name.length === 0) {
|
|
3518
|
+
issues.push({ type: "invalid-field", field: `${field}.name`, message: "Version requirement name must be non-empty." });
|
|
3519
|
+
}
|
|
3520
|
+
if (typeof value.version !== "string" || !isExactVersion(value.version)) {
|
|
3521
|
+
issues.push({ type: "invalid-field", field: `${field}.version`, message: `Version requirement version must be exact semver: ${String(value.version)}` });
|
|
3522
|
+
}
|
|
3523
|
+
}
|
|
3524
|
+
function isExactVersion(value) {
|
|
3525
|
+
return /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(value);
|
|
3526
|
+
}
|
|
3332
3527
|
function isRepositoryRelativePath(value) {
|
|
3333
3528
|
if (value.length === 0 || isAbsolute4(value)) return false;
|
|
3334
3529
|
const segments = value.replaceAll("\\", "/").split("/");
|
|
@@ -3503,10 +3698,10 @@ function isRecord(value) {
|
|
|
3503
3698
|
|
|
3504
3699
|
// src/portfolio/doctor.ts
|
|
3505
3700
|
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
3506
|
-
import { existsSync as
|
|
3701
|
+
import { existsSync as existsSync22, readFileSync as readFileSync16 } from "node:fs";
|
|
3507
3702
|
import { createRequire as createRequire2 } from "node:module";
|
|
3508
|
-
import { homedir } from "node:os";
|
|
3509
|
-
import { dirname as dirname12, join as
|
|
3703
|
+
import { homedir as homedir3 } from "node:os";
|
|
3704
|
+
import { dirname as dirname12, join as join23 } from "node:path";
|
|
3510
3705
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
3511
3706
|
|
|
3512
3707
|
// src/host-tooling/inventory.ts
|
|
@@ -3597,13 +3792,13 @@ function isRecord2(value) {
|
|
|
3597
3792
|
}
|
|
3598
3793
|
|
|
3599
3794
|
// src/portfolio/asset-state.ts
|
|
3600
|
-
import { existsSync as
|
|
3601
|
-
import { join as
|
|
3795
|
+
import { existsSync as existsSync20, lstatSync as lstatSync7, readFileSync as readFileSync14 } from "node:fs";
|
|
3796
|
+
import { join as join21 } from "node:path";
|
|
3602
3797
|
function comparePortfolioAssetState(options) {
|
|
3603
3798
|
const expectedManifest = readPlanDocument(options.expectedPlan, ".pro-gov/assets.json");
|
|
3604
3799
|
const expectedLock = readPlanDocument(options.expectedPlan, ".pro-gov/assets.lock.json");
|
|
3605
|
-
const currentManifest = readJsonFile(
|
|
3606
|
-
const currentLock = readJsonFile(
|
|
3800
|
+
const currentManifest = readJsonFile(join21(options.targetDir, ".pro-gov/assets.json"));
|
|
3801
|
+
const currentLock = readJsonFile(join21(options.targetDir, ".pro-gov/assets.lock.json"));
|
|
3607
3802
|
const issues = [];
|
|
3608
3803
|
if (!sameStrings(currentManifest?.bundleIds, expectedManifest?.bundleIds)) {
|
|
3609
3804
|
issues.push({
|
|
@@ -3627,7 +3822,7 @@ function comparePortfolioAssetState(options) {
|
|
|
3627
3822
|
for (const entry of currentLock?.assets ?? []) {
|
|
3628
3823
|
if (expectedTargets.has(entry.targetPath)) continue;
|
|
3629
3824
|
if (options.expectedPlan.actions.some((action) => action.type === "adopt-symlink" && action.assetId === entry.id && action.legacyTargetPath === entry.targetPath)) continue;
|
|
3630
|
-
const targetAbsolutePath =
|
|
3825
|
+
const targetAbsolutePath = join21(options.targetDir, entry.targetPath);
|
|
3631
3826
|
if (!pathIsSymlink(targetAbsolutePath)) continue;
|
|
3632
3827
|
issues.push({
|
|
3633
3828
|
type: "orphaned-managed-symlink",
|
|
@@ -3649,9 +3844,9 @@ function readPlanDocument(plan, targetPath) {
|
|
|
3649
3844
|
}
|
|
3650
3845
|
}
|
|
3651
3846
|
function readJsonFile(path) {
|
|
3652
|
-
if (!
|
|
3847
|
+
if (!existsSync20(path)) return void 0;
|
|
3653
3848
|
try {
|
|
3654
|
-
return JSON.parse(
|
|
3849
|
+
return JSON.parse(readFileSync14(path, "utf8"));
|
|
3655
3850
|
} catch {
|
|
3656
3851
|
return void 0;
|
|
3657
3852
|
}
|
|
@@ -3678,6 +3873,80 @@ function pathIsSymlink(path) {
|
|
|
3678
3873
|
}
|
|
3679
3874
|
}
|
|
3680
3875
|
|
|
3876
|
+
// src/portfolio/version-policy.ts
|
|
3877
|
+
import { existsSync as existsSync21, readFileSync as readFileSync15 } from "node:fs";
|
|
3878
|
+
import { join as join22 } from "node:path";
|
|
3879
|
+
function inspectVersionPolicy(root, policy, projectType) {
|
|
3880
|
+
if (!policy) return { status: "compliant", packages: [], attentionCount: 0 };
|
|
3881
|
+
const packageJson = readJson2(join22(root, "package.json"));
|
|
3882
|
+
const packageManager = policy.packageManager ? inspectPackageManager(packageJson, policy.packageManager.name, policy.packageManager.version) : void 0;
|
|
3883
|
+
const runtime = policy.runtime ? inspectRuntime(policy.runtime.name, policy.runtime.version) : void 0;
|
|
3884
|
+
const packages = policy.packages.map((requirement) => {
|
|
3885
|
+
if (requirement.appliesTo && (!projectType || !requirement.appliesTo.includes(projectType))) {
|
|
3886
|
+
return { name: requirement.name, expected: requirement.version, status: "not-applicable" };
|
|
3887
|
+
}
|
|
3888
|
+
const declared = findDeclaredVersion(packageJson, requirement.name);
|
|
3889
|
+
const installed = readInstalledVersion(root, requirement.name);
|
|
3890
|
+
const status = declared === void 0 ? requirement.appliesTo ? "missing" : "not-applicable" : declared !== requirement.version ? "drift" : installed !== requirement.version ? installed === void 0 ? "missing" : "drift" : "compliant";
|
|
3891
|
+
return {
|
|
3892
|
+
name: requirement.name,
|
|
3893
|
+
expected: requirement.version,
|
|
3894
|
+
declared,
|
|
3895
|
+
installed,
|
|
3896
|
+
actual: installed,
|
|
3897
|
+
status
|
|
3898
|
+
};
|
|
3899
|
+
});
|
|
3900
|
+
const all = [packageManager, runtime, ...packages].filter((item) => item !== void 0);
|
|
3901
|
+
const attentionCount = all.filter((item) => item.status !== "compliant" && item.status !== "not-applicable").length;
|
|
3902
|
+
return {
|
|
3903
|
+
status: attentionCount === 0 ? "compliant" : "attention",
|
|
3904
|
+
packageManager,
|
|
3905
|
+
runtime,
|
|
3906
|
+
packages,
|
|
3907
|
+
attentionCount
|
|
3908
|
+
};
|
|
3909
|
+
}
|
|
3910
|
+
function inspectPackageManager(packageJson, expectedName, expectedVersion) {
|
|
3911
|
+
const declared = typeof packageJson?.packageManager === "string" ? packageJson.packageManager : void 0;
|
|
3912
|
+
const expected = `${expectedName}@${expectedVersion}`;
|
|
3913
|
+
return {
|
|
3914
|
+
name: expectedName,
|
|
3915
|
+
expected: expectedVersion,
|
|
3916
|
+
actual: declared,
|
|
3917
|
+
declared,
|
|
3918
|
+
status: declared === void 0 ? "missing" : declared === expected ? "compliant" : "drift"
|
|
3919
|
+
};
|
|
3920
|
+
}
|
|
3921
|
+
function inspectRuntime(expectedName, expectedVersion) {
|
|
3922
|
+
const actual = expectedName === "node" ? process.versions.node : void 0;
|
|
3923
|
+
return {
|
|
3924
|
+
name: expectedName,
|
|
3925
|
+
expected: expectedVersion,
|
|
3926
|
+
actual,
|
|
3927
|
+
status: actual === void 0 ? "missing" : actual === expectedVersion ? "compliant" : "drift"
|
|
3928
|
+
};
|
|
3929
|
+
}
|
|
3930
|
+
function findDeclaredVersion(packageJson, name) {
|
|
3931
|
+
for (const section of ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]) {
|
|
3932
|
+
const value = packageJson?.[section]?.[name];
|
|
3933
|
+
if (typeof value === "string") return value;
|
|
3934
|
+
}
|
|
3935
|
+
return void 0;
|
|
3936
|
+
}
|
|
3937
|
+
function readInstalledVersion(root, name) {
|
|
3938
|
+
const packageJson = readJson2(join22(root, "node_modules", name, "package.json"));
|
|
3939
|
+
return typeof packageJson?.version === "string" ? packageJson.version : void 0;
|
|
3940
|
+
}
|
|
3941
|
+
function readJson2(path) {
|
|
3942
|
+
if (!existsSync21(path)) return void 0;
|
|
3943
|
+
try {
|
|
3944
|
+
return JSON.parse(readFileSync15(path, "utf8"));
|
|
3945
|
+
} catch {
|
|
3946
|
+
return void 0;
|
|
3947
|
+
}
|
|
3948
|
+
}
|
|
3949
|
+
|
|
3681
3950
|
// src/portfolio/doctor.ts
|
|
3682
3951
|
function inspectPortfolio(options) {
|
|
3683
3952
|
const expectedPackageVersions = getExpectedPackageVersions();
|
|
@@ -3687,14 +3956,15 @@ function inspectPortfolio(options) {
|
|
|
3687
3956
|
agentAssetsDir: options.agentAssetsDir,
|
|
3688
3957
|
registry: options.registry,
|
|
3689
3958
|
bundles: options.bundles,
|
|
3690
|
-
expectedPackageVersions
|
|
3959
|
+
expectedPackageVersions,
|
|
3960
|
+
versionPolicy: options.manifest.technologyGovernance?.versionPolicy
|
|
3691
3961
|
}));
|
|
3692
3962
|
return {
|
|
3693
3963
|
ok: hostTooling.issues.length === 0 && targets.every((target) => target.issues.length === 0),
|
|
3694
3964
|
portfolioId: options.manifest.portfolioId,
|
|
3695
3965
|
expectedPackageVersions,
|
|
3696
3966
|
hostTooling,
|
|
3697
|
-
hostSsot: { userSkills: inspectUserSkillsSsot(options.homeDir ??
|
|
3967
|
+
hostSsot: { userSkills: inspectUserSkillsSsot(options.homeDir ?? homedir3()) },
|
|
3698
3968
|
targets
|
|
3699
3969
|
};
|
|
3700
3970
|
}
|
|
@@ -3702,11 +3972,11 @@ function inspectTarget(options) {
|
|
|
3702
3972
|
const { target } = options;
|
|
3703
3973
|
const hostSsot = inspectProjectHostSsot(target.path);
|
|
3704
3974
|
const issues = [];
|
|
3705
|
-
const packageJson =
|
|
3975
|
+
const packageJson = readJson3(join23(target.path, "package.json"));
|
|
3706
3976
|
const packages = {};
|
|
3707
3977
|
for (const packageName of ["@pieai/pro-gov", "@pieai/doc-gov"]) {
|
|
3708
3978
|
const declared = packageJson?.devDependencies?.[packageName] ?? packageJson?.dependencies?.[packageName];
|
|
3709
|
-
const installedPackage =
|
|
3979
|
+
const installedPackage = readJson3(join23(target.path, "node_modules", packageName, "package.json"));
|
|
3710
3980
|
const installed = installedPackage?.version;
|
|
3711
3981
|
const expected = options.expectedPackageVersions[packageName];
|
|
3712
3982
|
packages[packageName] = { declared, installed, expected };
|
|
@@ -3726,6 +3996,8 @@ function inspectTarget(options) {
|
|
|
3726
3996
|
}
|
|
3727
3997
|
}
|
|
3728
3998
|
const checks = runTargetChecks(target);
|
|
3999
|
+
const versions = inspectVersionPolicy(target.path, options.versionPolicy, target.projectType);
|
|
4000
|
+
const verification = inspectProjectVerification(target.path);
|
|
3729
4001
|
for (const check of checks) {
|
|
3730
4002
|
if (check.status === 0) continue;
|
|
3731
4003
|
issues.push({
|
|
@@ -3760,7 +4032,7 @@ function inspectTarget(options) {
|
|
|
3760
4032
|
type: "asset-lock-drift",
|
|
3761
4033
|
message: error instanceof Error ? error.message : String(error)
|
|
3762
4034
|
});
|
|
3763
|
-
if (!
|
|
4035
|
+
if (!existsSync22(join23(target.path, ".pro-gov/assets.json"))) {
|
|
3764
4036
|
issues.push({ type: "bundle-drift", message: "Target asset manifest is missing." });
|
|
3765
4037
|
}
|
|
3766
4038
|
}
|
|
@@ -3772,19 +4044,21 @@ function inspectTarget(options) {
|
|
|
3772
4044
|
git: inspectGit(target.path),
|
|
3773
4045
|
checks,
|
|
3774
4046
|
hostSsot,
|
|
4047
|
+
versions,
|
|
4048
|
+
verification,
|
|
3775
4049
|
issues: deduplicateIssues(issues)
|
|
3776
4050
|
};
|
|
3777
4051
|
}
|
|
3778
4052
|
function readTargetAssetHost(targetDir) {
|
|
3779
|
-
const lockfile =
|
|
4053
|
+
const lockfile = readJson3(join23(targetDir, ".pro-gov/assets.lock.json"));
|
|
3780
4054
|
return isAssetRegistryHost(lockfile?.host) ? lockfile.host : void 0;
|
|
3781
4055
|
}
|
|
3782
4056
|
function isAssetRegistryHost(value) {
|
|
3783
4057
|
return value === "codex" || value === "claude-code" || value === "gemini-cli" || value === "antigravity";
|
|
3784
4058
|
}
|
|
3785
4059
|
function runTargetChecks(target) {
|
|
3786
|
-
const proGovCli =
|
|
3787
|
-
const docGovCli =
|
|
4060
|
+
const proGovCli = join23(target.path, "node_modules/@pieai/pro-gov/dist/cli.js");
|
|
4061
|
+
const docGovCli = join23(target.path, "node_modules/@pieai/doc-gov/dist/cli.js");
|
|
3788
4062
|
const commands = [
|
|
3789
4063
|
{
|
|
3790
4064
|
name: "pro-gov doctor",
|
|
@@ -3795,7 +4069,7 @@ function runTargetChecks(target) {
|
|
|
3795
4069
|
{ name: "doc-gov scan --check", cli: docGovCli, args: ["scan", "--check"] }
|
|
3796
4070
|
];
|
|
3797
4071
|
return commands.map((command2) => {
|
|
3798
|
-
if (!
|
|
4072
|
+
if (!existsSync22(command2.cli)) return { name: command2.name, status: null };
|
|
3799
4073
|
const result = spawnSync5(process.execPath, [command2.cli, ...command2.args], {
|
|
3800
4074
|
cwd: target.path,
|
|
3801
4075
|
encoding: "utf8",
|
|
@@ -3819,11 +4093,11 @@ function inspectGit(path) {
|
|
|
3819
4093
|
};
|
|
3820
4094
|
}
|
|
3821
4095
|
function getExpectedPackageVersions() {
|
|
3822
|
-
const proGovPackage =
|
|
4096
|
+
const proGovPackage = readJson3(findOwnPackageJson());
|
|
3823
4097
|
let docGovVersion;
|
|
3824
4098
|
try {
|
|
3825
4099
|
const require2 = createRequire2(import.meta.url);
|
|
3826
|
-
const docGovPackage =
|
|
4100
|
+
const docGovPackage = readJson3(require2.resolve("@pieai/doc-gov/package.json"));
|
|
3827
4101
|
docGovVersion = docGovPackage?.version;
|
|
3828
4102
|
} catch {
|
|
3829
4103
|
docGovVersion = void 0;
|
|
@@ -3836,16 +4110,16 @@ function getExpectedPackageVersions() {
|
|
|
3836
4110
|
function findOwnPackageJson() {
|
|
3837
4111
|
let current = dirname12(fileURLToPath3(import.meta.url));
|
|
3838
4112
|
for (let depth = 0; depth < 5; depth += 1) {
|
|
3839
|
-
const candidate =
|
|
3840
|
-
if (
|
|
4113
|
+
const candidate = join23(current, "package.json");
|
|
4114
|
+
if (existsSync22(candidate)) return candidate;
|
|
3841
4115
|
current = dirname12(current);
|
|
3842
4116
|
}
|
|
3843
4117
|
return "";
|
|
3844
4118
|
}
|
|
3845
|
-
function
|
|
3846
|
-
if (!path || !
|
|
4119
|
+
function readJson3(path) {
|
|
4120
|
+
if (!path || !existsSync22(path)) return void 0;
|
|
3847
4121
|
try {
|
|
3848
|
-
return JSON.parse(
|
|
4122
|
+
return JSON.parse(readFileSync16(path, "utf8"));
|
|
3849
4123
|
} catch {
|
|
3850
4124
|
return void 0;
|
|
3851
4125
|
}
|
|
@@ -3864,27 +4138,31 @@ function deduplicateIssues(issues) {
|
|
|
3864
4138
|
import { execFileSync } from "node:child_process";
|
|
3865
4139
|
import {
|
|
3866
4140
|
cpSync as cpSync2,
|
|
3867
|
-
existsSync as
|
|
4141
|
+
existsSync as existsSync23,
|
|
3868
4142
|
lstatSync as lstatSync8,
|
|
3869
4143
|
mkdirSync as mkdirSync8,
|
|
3870
|
-
readFileSync as
|
|
3871
|
-
readdirSync as
|
|
4144
|
+
readFileSync as readFileSync17,
|
|
4145
|
+
readdirSync as readdirSync9,
|
|
3872
4146
|
realpathSync as realpathSync4,
|
|
3873
|
-
statSync as
|
|
4147
|
+
statSync as statSync5,
|
|
3874
4148
|
writeFileSync as writeFileSync7
|
|
3875
4149
|
} from "node:fs";
|
|
3876
|
-
import { homedir as
|
|
3877
|
-
import { dirname as dirname13, join as
|
|
4150
|
+
import { homedir as homedir4 } from "node:os";
|
|
4151
|
+
import { dirname as dirname13, join as join24, relative as relative8, resolve as resolve5, sep } from "node:path";
|
|
3878
4152
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
3879
4153
|
var CURRENT_ROUTER_VERSION = "1.1";
|
|
3880
4154
|
function inspectPortfolioAiHealth(options) {
|
|
3881
|
-
const
|
|
3882
|
-
const
|
|
3883
|
-
|
|
4155
|
+
const allEndpoints = collectEndpoints(options.manifest);
|
|
4156
|
+
const endpoints = options.targetId && options.targetId !== "all" ? allEndpoints.filter(({ endpoint }) => endpoint.id === options.targetId) : allEndpoints;
|
|
4157
|
+
if (options.targetId && options.targetId !== "all" && endpoints.length === 0) {
|
|
4158
|
+
throw new Error(`Unknown portfolio target: ${options.targetId}`);
|
|
4159
|
+
}
|
|
4160
|
+
const secretsRoot = options.secretsRoot ?? join24(dirname13(options.manifest.controlPlane?.path ?? allEndpoints[0]?.endpoint.path ?? process.cwd()), ".secrets");
|
|
4161
|
+
const homeDir = options.homeDir ?? process.env.HOME ?? homedir4();
|
|
3884
4162
|
const grokVersion = commandVersion("grok");
|
|
3885
4163
|
const executionEngineRoot = options.manifest.executionEngine?.path;
|
|
3886
4164
|
const skillRegistry = inspectSkillRegistry(executionEngineRoot);
|
|
3887
|
-
const expectedPackageVersion = packageVersion(
|
|
4165
|
+
const expectedPackageVersion = packageVersion(join24(executionEngineRoot ?? "", "packages/pro-gov/package.json"));
|
|
3888
4166
|
const repositories = endpoints.map(({ endpoint, role }) => inspectRepository(
|
|
3889
4167
|
endpoint,
|
|
3890
4168
|
role,
|
|
@@ -3897,20 +4175,27 @@ function inspectPortfolioAiHealth(options) {
|
|
|
3897
4175
|
const summary = { healthy: 0, attention: 0, unhealthy: 0 };
|
|
3898
4176
|
for (const repository of repositories) summary[repository.status] += 1;
|
|
3899
4177
|
return {
|
|
3900
|
-
schemaVersion:
|
|
4178
|
+
schemaVersion: 5,
|
|
3901
4179
|
portfolioId: options.manifest.portfolioId,
|
|
3902
4180
|
generatedAt: options.generatedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
4181
|
+
coverage: {
|
|
4182
|
+
mode: options.targetId && options.targetId !== "all" ? "single" : "all",
|
|
4183
|
+
targetId: options.targetId && options.targetId !== "all" ? options.targetId : void 0,
|
|
4184
|
+
coveredRepositoryIds: repositories.map((repository) => repository.id),
|
|
4185
|
+
totalRepositoryCount: allEndpoints.length
|
|
4186
|
+
},
|
|
3903
4187
|
privacy: "Names, paths, counts, scopes, safe health states, and configuration origins only. Secret values, environment values, MCP commands, arguments, URLs, headers, MCP environment maps, and raw process environments are never retained or rendered.",
|
|
3904
4188
|
secretsRoot: inspectSecretsRoot(secretsRoot),
|
|
3905
4189
|
hostEnvironment: inspectHostEnvironment(
|
|
3906
4190
|
homeDir,
|
|
3907
4191
|
grokVersion,
|
|
3908
|
-
|
|
4192
|
+
allEndpoints.map(({ endpoint }) => endpoint.path),
|
|
3909
4193
|
options.manifest.specialistChecks?.devspace
|
|
3910
4194
|
),
|
|
3911
4195
|
skillRegistry,
|
|
3912
4196
|
technologyGovernance: {
|
|
3913
4197
|
strategySource: options.manifest.technologyGovernance?.strategySource,
|
|
4198
|
+
versionPolicy: options.manifest.technologyGovernance?.versionPolicy,
|
|
3914
4199
|
projectTypes: options.manifest.technologyGovernance?.projectTypes.length ?? 0,
|
|
3915
4200
|
technologies: options.manifest.technologyGovernance?.technologies.length ?? 0
|
|
3916
4201
|
},
|
|
@@ -3918,19 +4203,44 @@ function inspectPortfolioAiHealth(options) {
|
|
|
3918
4203
|
repositories
|
|
3919
4204
|
};
|
|
3920
4205
|
}
|
|
4206
|
+
function mergePortfolioAiHealthReport(existing, latest, allRepositoryIds) {
|
|
4207
|
+
const repositoriesById = /* @__PURE__ */ new Map();
|
|
4208
|
+
for (const repository of existing?.repositories ?? []) repositoriesById.set(repository.id, repository);
|
|
4209
|
+
for (const repository of latest.repositories) repositoriesById.set(repository.id, repository);
|
|
4210
|
+
const repositories = allRepositoryIds.map((id) => repositoriesById.get(id)).filter((repository) => repository !== void 0);
|
|
4211
|
+
const coveredIds = /* @__PURE__ */ new Set([
|
|
4212
|
+
...existing?.coverage?.coveredRepositoryIds ?? [],
|
|
4213
|
+
...latest.coverage.coveredRepositoryIds
|
|
4214
|
+
]);
|
|
4215
|
+
const coveredRepositoryIds = allRepositoryIds.filter((id) => coveredIds.has(id));
|
|
4216
|
+
const summary = { healthy: 0, attention: 0, unhealthy: 0 };
|
|
4217
|
+
for (const repository of repositories) summary[repository.status] += 1;
|
|
4218
|
+
const complete = coveredRepositoryIds.length === allRepositoryIds.length;
|
|
4219
|
+
return {
|
|
4220
|
+
...latest,
|
|
4221
|
+
repositories,
|
|
4222
|
+
summary,
|
|
4223
|
+
coverage: {
|
|
4224
|
+
mode: complete ? "all" : "single",
|
|
4225
|
+
targetId: complete ? void 0 : latest.coverage.targetId,
|
|
4226
|
+
coveredRepositoryIds,
|
|
4227
|
+
totalRepositoryCount: allRepositoryIds.length
|
|
4228
|
+
}
|
|
4229
|
+
};
|
|
4230
|
+
}
|
|
3921
4231
|
function writePortfolioAiHealthReport(report, outDir) {
|
|
3922
4232
|
mkdirSync8(outDir, { recursive: true });
|
|
3923
4233
|
const dashboardAssets = findDashboardAssets();
|
|
3924
4234
|
for (const file of ["index.html", "app.js", "app.css"]) {
|
|
3925
|
-
const source =
|
|
3926
|
-
if (!
|
|
3927
|
-
cpSync2(source,
|
|
4235
|
+
const source = join24(dashboardAssets, file);
|
|
4236
|
+
if (!existsSync23(source)) throw new Error(`Portfolio dashboard asset is missing: ${source}`);
|
|
4237
|
+
cpSync2(source, join24(outDir, file));
|
|
3928
4238
|
}
|
|
3929
|
-
const jsonPath =
|
|
3930
|
-
const htmlPath =
|
|
4239
|
+
const jsonPath = join24(outDir, "portfolio-ai-health.json");
|
|
4240
|
+
const htmlPath = join24(outDir, "index.html");
|
|
3931
4241
|
writeFileSync7(jsonPath, `${JSON.stringify(report, null, 2)}
|
|
3932
4242
|
`);
|
|
3933
|
-
writeFileSync7(
|
|
4243
|
+
writeFileSync7(join24(outDir, "data.js"), `window.__PORTFOLIO_AI_HEALTH__ = ${safeJavaScriptJson(report)};
|
|
3934
4244
|
`);
|
|
3935
4245
|
return { jsonPath, htmlPath };
|
|
3936
4246
|
}
|
|
@@ -3957,15 +4267,18 @@ function inspectRepository(endpoint, role, secretsRoot, homeDir, expectedPackage
|
|
|
3957
4267
|
const hooks = inspectHooks(root);
|
|
3958
4268
|
const docs = inspectDocs(root, role === "execution-engine" ? void 0 : expectedPackageVersion);
|
|
3959
4269
|
const mcp = {
|
|
3960
|
-
codexProject: tomlMcpNames(
|
|
3961
|
-
claudeCodeProjectShared: jsonObjectKeys(
|
|
4270
|
+
codexProject: tomlMcpNames(join24(root, ".codex/config.toml")),
|
|
4271
|
+
claudeCodeProjectShared: jsonObjectKeys(join24(root, ".mcp.json"), "mcpServers"),
|
|
3962
4272
|
claudeCodeProjectLocal: claudeProjectLocalMcpNames(homeDir, root),
|
|
3963
|
-
grokProject: tomlMcpNames(
|
|
4273
|
+
grokProject: tomlMcpNames(join24(root, ".grok/config.toml")),
|
|
3964
4274
|
grokEffective: grokInspection.effectiveMcp,
|
|
3965
4275
|
grokInspection: grokInspection.inspection
|
|
3966
4276
|
};
|
|
3967
4277
|
const secrets = inspectRepositorySecrets(root, endpoint.id, secretsRoot, git.isRepository, endpoint.environmentPolicy);
|
|
3968
4278
|
const projectModel = inspectProjectModel(root, endpoint, technologyGovernance);
|
|
4279
|
+
const versions = inspectVersionPolicy(root, technologyGovernance?.versionPolicy, endpoint.projectType);
|
|
4280
|
+
const verification = inspectProjectVerification(root);
|
|
4281
|
+
const redundancy = inspectProjectRedundancy(root, { homeDir });
|
|
3969
4282
|
const recommendations = [];
|
|
3970
4283
|
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");
|
|
3971
4284
|
if (git.unmergedBranches.length > 0) recommendations.push(`\u6709 ${git.unmergedBranches.length} \u6761\u5206\u652F\u5C1A\u672A\u5408\u5165\u5F53\u524D HEAD\uFF1A${git.unmergedBranches.join(", ")}\u3002`);
|
|
@@ -4002,12 +4315,15 @@ function inspectRepository(endpoint, role, secretsRoot, homeDir, expectedPackage
|
|
|
4002
4315
|
if (missingBaseline.length > 0) recommendations.push(`\u6280\u672F\u57FA\u7EBF\u7F3A\u5C11\u53EF\u9A8C\u8BC1\u4FE1\u53F7\uFF1A${missingBaseline.map((technology) => technology.label).join("\u3001")}\u3002`);
|
|
4003
4316
|
const missingSelected = projectModel.optionalCapabilities.filter((technology) => technology.selected && !technology.detected);
|
|
4004
4317
|
if (missingSelected.length > 0) recommendations.push(`\u5DF2\u9009\u80FD\u529B\u7F3A\u5C11\u53EF\u9A8C\u8BC1\u4FE1\u53F7\uFF1A${missingSelected.map((technology) => technology.label).join("\u3001")}\u3002`);
|
|
4318
|
+
if (versions.status === "attention") recommendations.push(`\u6280\u672F\u7248\u672C\u7B56\u7565\u6709 ${versions.attentionCount} \u9879\u6F02\u79FB\u6216\u7F3A\u5931\uFF1B\u58F0\u660E\u7248\u672C\u4E0E\u5DF2\u5B89\u88C5\u7248\u672C\u5FC5\u987B\u7CBE\u786E\u5BF9\u9F50\u3002`);
|
|
4319
|
+
if (verification.status === "attention") recommendations.push(`\u9A8C\u8BC1\u811A\u672C\u7F3A\u5931\uFF1A${verification.missing.join("\u3001")}\uFF1BPGS \u8981\u6C42 typecheck\u3001lint\u3001format:check\u3001verify \u53EF\u53D1\u73B0\u3002`);
|
|
4320
|
+
if (redundancy.status === "attention") recommendations.push("\u53D1\u73B0\u65E7 AI \u76EE\u5F55\u6216\u5927\u578B Playwright \u7F13\u5B58\uFF1B\u4EC5\u63D0\u4F9B\u8BC1\u636E\uFF0C\u786E\u8BA4\u5F52\u5C5E\u540E\u518D\u7531\u4EBA\u5DE5\u6E05\u7406\u3002");
|
|
4005
4321
|
return {
|
|
4006
4322
|
id: endpoint.id,
|
|
4007
4323
|
role,
|
|
4008
4324
|
path: root,
|
|
4009
4325
|
profile: "profile" in endpoint ? endpoint.profile : void 0,
|
|
4010
|
-
status: deriveStatus(role, entries, git, hooks, skills, hostSsot, secrets, docs, projectModel, Boolean(technologyGovernance)),
|
|
4326
|
+
status: deriveStatus(role, entries, git, hooks, skills, hostSsot, secrets, docs, projectModel, Boolean(technologyGovernance), versions, verification, redundancy),
|
|
4011
4327
|
recommendations,
|
|
4012
4328
|
git,
|
|
4013
4329
|
entries,
|
|
@@ -4017,10 +4333,13 @@ function inspectRepository(endpoint, role, secretsRoot, homeDir, expectedPackage
|
|
|
4017
4333
|
hostSsot,
|
|
4018
4334
|
secrets,
|
|
4019
4335
|
docs,
|
|
4020
|
-
projectModel
|
|
4336
|
+
projectModel,
|
|
4337
|
+
versions,
|
|
4338
|
+
verification,
|
|
4339
|
+
redundancy
|
|
4021
4340
|
};
|
|
4022
4341
|
}
|
|
4023
|
-
function deriveStatus(role, entries, git, hooks, skills, hostSsot, secrets, docs, projectModel, technologyGovernanceConfigured) {
|
|
4342
|
+
function deriveStatus(role, entries, git, hooks, skills, hostSsot, secrets, docs, projectModel, technologyGovernanceConfigured, versions, verification, redundancy) {
|
|
4024
4343
|
if (!git.isRepository || entries.agents === "missing" || entries.claude === "dangling-symlink" || entries.gemini === "dangling-symlink" || skills.canonical.some((item) => item.kind === "dangling-symlink") || secrets.repositoryEnvFiles.some((file) => file.tracked && !file.template && !file.fixture) || hasUnsafeCentralSecretPermissions(secrets)) return "unhealthy";
|
|
4025
4344
|
const missingBaseline = projectModel.baseline.some((technology) => !technology.detected && !hasBaselineException(projectModel, technology.id));
|
|
4026
4345
|
const missingSelected = projectModel.optionalCapabilities.some((technology) => technology.selected && !technology.detected);
|
|
@@ -4029,7 +4348,7 @@ function deriveStatus(role, entries, git, hooks, skills, hostSsot, secrets, docs
|
|
|
4029
4348
|
const packageVersionNeedsReview = docs.packages.expected !== void 0 && !docs.packages.aligned;
|
|
4030
4349
|
const routerVersionNeedsReview = !docs.routerAligned;
|
|
4031
4350
|
const targetManifestMissing = role === "target" && !docs.manifest;
|
|
4032
|
-
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) return "attention";
|
|
4351
|
+
if (entries.agents !== "pgs-router" || entries.claude !== "agents-symlink" || hasWorkflowReminderHooks(hooks) || skills.claudeCompatibility === "duplicate-directory" || skills.claudeCompatibility === "dangling-symlink" || !hostSsot.compliant || git.branches.length > 1 || git.worktrees.length > 1 || git.dirtyPaths.length > 0 || (git.ahead ?? 0) > 0 || secretMaterializationNeedsReview || technologyGovernanceConfigured && !projectModel.projectType || missingBaseline || missingSelected || packageVersionNeedsReview || routerVersionNeedsReview || targetManifestMissing || versions.status === "attention" || verification.status === "attention" || redundancy.legacyDirectories.length > 0) return "attention";
|
|
4033
4352
|
return "healthy";
|
|
4034
4353
|
}
|
|
4035
4354
|
function inspectProjectModel(root, endpoint, governance) {
|
|
@@ -4039,7 +4358,7 @@ function inspectProjectModel(root, endpoint, governance) {
|
|
|
4039
4358
|
const detection = (id) => {
|
|
4040
4359
|
const technology = technologyById.get(id);
|
|
4041
4360
|
const packageMatch = technology?.packages?.some((name) => packages.has(name)) ?? false;
|
|
4042
|
-
const fileMatch = technology?.files?.some((path) =>
|
|
4361
|
+
const fileMatch = technology?.files?.some((path) => existsSync23(join24(root, path))) ?? false;
|
|
4043
4362
|
return { id, label: technology?.label ?? id, detected: packageMatch || fileMatch };
|
|
4044
4363
|
};
|
|
4045
4364
|
const selected = new Set(endpoint.capabilities ?? []);
|
|
@@ -4062,10 +4381,10 @@ function collectPackageNames(root) {
|
|
|
4062
4381
|
try {
|
|
4063
4382
|
files = splitLines(execFileSync("git", ["ls-files", "*package.json"], { cwd: root, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }));
|
|
4064
4383
|
} catch {
|
|
4065
|
-
if (
|
|
4384
|
+
if (existsSync23(join24(root, "package.json"))) files = ["package.json"];
|
|
4066
4385
|
}
|
|
4067
4386
|
for (const file of files) {
|
|
4068
|
-
const packageJson =
|
|
4387
|
+
const packageJson = readJson4(join24(root, file));
|
|
4069
4388
|
if (!isRecord3(packageJson)) continue;
|
|
4070
4389
|
for (const section of ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]) {
|
|
4071
4390
|
const dependencies = packageJson[section];
|
|
@@ -4103,9 +4422,9 @@ function inspectGit2(root) {
|
|
|
4103
4422
|
};
|
|
4104
4423
|
}
|
|
4105
4424
|
function inspectEntries(root) {
|
|
4106
|
-
const agentsPath =
|
|
4107
|
-
const agents = !
|
|
4108
|
-
const claudePath =
|
|
4425
|
+
const agentsPath = join24(root, "AGENTS.md");
|
|
4426
|
+
const agents = !existsSync23(agentsPath) ? "missing" : safeRead(agentsPath).includes("PGS-ROUTER:BEGIN") ? "pgs-router" : "custom";
|
|
4427
|
+
const claudePath = join24(root, "CLAUDE.md");
|
|
4109
4428
|
let claude = "missing";
|
|
4110
4429
|
if (pathLexists(claudePath)) {
|
|
4111
4430
|
const info = lstatSync8(claudePath);
|
|
@@ -4123,7 +4442,7 @@ function inspectEntries(root) {
|
|
|
4123
4442
|
return { agents, claude, gemini: inspectOptionalEntry(root, "GEMINI.md", agentsPath) };
|
|
4124
4443
|
}
|
|
4125
4444
|
function inspectOptionalEntry(root, filename, agentsPath) {
|
|
4126
|
-
const path =
|
|
4445
|
+
const path = join24(root, filename);
|
|
4127
4446
|
if (!pathLexists(path)) return "missing";
|
|
4128
4447
|
const info = lstatSync8(path);
|
|
4129
4448
|
if (info.isSymbolicLink()) {
|
|
@@ -4137,7 +4456,7 @@ function inspectOptionalEntry(root, filename, agentsPath) {
|
|
|
4137
4456
|
return /AGENTS\.md/.test(content) && content.length < 2e3 ? "thin-adapter" : "custom";
|
|
4138
4457
|
}
|
|
4139
4458
|
function inspectSkills(root, grokInspection) {
|
|
4140
|
-
const lock =
|
|
4459
|
+
const lock = readJson4(join24(root, ".pro-gov/assets.lock.json"));
|
|
4141
4460
|
const managed = /* @__PURE__ */ new Set();
|
|
4142
4461
|
const bundleIds = stringArray(isRecord3(lock) ? lock.bundleIds : void 0);
|
|
4143
4462
|
if (isRecord3(lock) && Array.isArray(lock.assets)) {
|
|
@@ -4147,9 +4466,9 @@ function inspectSkills(root, grokInspection) {
|
|
|
4147
4466
|
if (match) managed.add(match[1]);
|
|
4148
4467
|
}
|
|
4149
4468
|
}
|
|
4150
|
-
const skillRoot =
|
|
4469
|
+
const skillRoot = join24(root, ".agents/skills");
|
|
4151
4470
|
const canonical = pathLexists(skillRoot) && safeIsDirectory(skillRoot) ? safeReadDir(skillRoot).filter((name) => !name.startsWith(".")).map((name) => {
|
|
4152
|
-
const path =
|
|
4471
|
+
const path = join24(skillRoot, name);
|
|
4153
4472
|
const stat = lstatSync8(path);
|
|
4154
4473
|
let kind = stat.isSymbolicLink() ? "symlink" : stat.isDirectory() ? "directory" : "file";
|
|
4155
4474
|
if (stat.isSymbolicLink()) {
|
|
@@ -4175,20 +4494,20 @@ function inspectSkills(root, grokInspection) {
|
|
|
4175
4494
|
},
|
|
4176
4495
|
hosts: {
|
|
4177
4496
|
codexProject: canonical.filter((item) => item.kind !== "dangling-symlink").length,
|
|
4178
|
-
claudeCodeProject: inspectSkillRoot(
|
|
4179
|
-
grokNativeProject: inspectSkillRoot(
|
|
4497
|
+
claudeCodeProject: inspectSkillRoot(join24(root, ".claude/skills")).names.length,
|
|
4498
|
+
grokNativeProject: inspectSkillRoot(join24(root, ".grok/skills")).names.length,
|
|
4180
4499
|
grokEffective: grokInspection.skills
|
|
4181
4500
|
}
|
|
4182
4501
|
};
|
|
4183
4502
|
}
|
|
4184
4503
|
function inspectClaudeSkillRoot(root) {
|
|
4185
|
-
const path =
|
|
4504
|
+
const path = join24(root, ".claude/skills");
|
|
4186
4505
|
if (!pathLexists(path)) return "missing";
|
|
4187
4506
|
const stat = lstatSync8(path);
|
|
4188
4507
|
if (stat.isSymbolicLink()) {
|
|
4189
4508
|
try {
|
|
4190
4509
|
const target = realpathSync4(path);
|
|
4191
|
-
return target === realpathSync4(
|
|
4510
|
+
return target === realpathSync4(join24(root, ".agents/skills")) ? "shared-root" : "other";
|
|
4192
4511
|
} catch {
|
|
4193
4512
|
return "dangling-symlink";
|
|
4194
4513
|
}
|
|
@@ -4196,7 +4515,7 @@ function inspectClaudeSkillRoot(root) {
|
|
|
4196
4515
|
return stat.isDirectory() ? "duplicate-directory" : "other";
|
|
4197
4516
|
}
|
|
4198
4517
|
function inspectRepositorySecrets(root, id, secretsRoot, isRepository, environmentPolicy) {
|
|
4199
|
-
const centralPath =
|
|
4518
|
+
const centralPath = join24(secretsRoot, id);
|
|
4200
4519
|
const centralRealPath = safeRealpath(centralPath);
|
|
4201
4520
|
const localOnlyReasons = new Map(
|
|
4202
4521
|
(environmentPolicy?.localOnly ?? []).map((entry) => [entry.path, entry.reason])
|
|
@@ -4208,16 +4527,16 @@ function inspectRepositorySecrets(root, id, secretsRoot, isRepository, environme
|
|
|
4208
4527
|
tracked: isRepository ? gitTracks(root, path) : false,
|
|
4209
4528
|
template: isEnvironmentTemplate(path),
|
|
4210
4529
|
fixture: isEnvironmentFixture(path),
|
|
4211
|
-
symlink: lstatSync8(
|
|
4212
|
-
centralized: pointsInside(
|
|
4530
|
+
symlink: lstatSync8(join24(root, path)).isSymbolicLink(),
|
|
4531
|
+
centralized: pointsInside(join24(root, path), centralRealPath),
|
|
4213
4532
|
localOnly: localOnlyReason !== void 0,
|
|
4214
4533
|
...localOnlyReason !== void 0 ? { localOnlyReason } : {}
|
|
4215
4534
|
};
|
|
4216
4535
|
});
|
|
4217
4536
|
return {
|
|
4218
|
-
centralDirectory:
|
|
4219
|
-
centralMode:
|
|
4220
|
-
centralFiles:
|
|
4537
|
+
centralDirectory: existsSync23(centralPath) ? "present" : "absent",
|
|
4538
|
+
centralMode: existsSync23(centralPath) ? modeString(statSync5(centralPath).mode) : void 0,
|
|
4539
|
+
centralFiles: existsSync23(centralPath) ? collectCentralSecretFiles(centralPath) : [],
|
|
4221
4540
|
repositoryEnvFiles: envFiles
|
|
4222
4541
|
};
|
|
4223
4542
|
}
|
|
@@ -4226,11 +4545,11 @@ function collectEnvironmentFiles(root, current = root, depth = 0) {
|
|
|
4226
4545
|
if (depth > 5) return [];
|
|
4227
4546
|
const found = [];
|
|
4228
4547
|
try {
|
|
4229
|
-
for (const entry of
|
|
4548
|
+
for (const entry of readdirSync9(current, { withFileTypes: true })) {
|
|
4230
4549
|
if (entry.isDirectory()) {
|
|
4231
|
-
if (!SKIP_ENV_DIRECTORIES.has(entry.name)) found.push(...collectEnvironmentFiles(root,
|
|
4550
|
+
if (!SKIP_ENV_DIRECTORIES.has(entry.name)) found.push(...collectEnvironmentFiles(root, join24(current, entry.name), depth + 1));
|
|
4232
4551
|
} else if (isEnvironmentFilename(entry.name) && !isProviderGeneratedEnvironmentFile(entry.name)) {
|
|
4233
|
-
found.push(relative8(root,
|
|
4552
|
+
found.push(relative8(root, join24(current, entry.name)));
|
|
4234
4553
|
}
|
|
4235
4554
|
}
|
|
4236
4555
|
} catch {
|
|
@@ -4242,8 +4561,8 @@ function collectCentralSecretFiles(root, current = root, depth = 0) {
|
|
|
4242
4561
|
if (depth > 3) return [];
|
|
4243
4562
|
const found = [];
|
|
4244
4563
|
try {
|
|
4245
|
-
for (const entry of
|
|
4246
|
-
const path =
|
|
4564
|
+
for (const entry of readdirSync9(current, { withFileTypes: true })) {
|
|
4565
|
+
const path = join24(current, entry.name);
|
|
4247
4566
|
if (entry.isDirectory()) found.push(...collectCentralSecretFiles(root, path, depth + 1));
|
|
4248
4567
|
else found.push({ path: relative8(root, path), mode: modeString(lstatSync8(path).mode) });
|
|
4249
4568
|
}
|
|
@@ -4286,12 +4605,12 @@ function hasUnsafeCentralSecretPermissions(secrets) {
|
|
|
4286
4605
|
return secrets.centralDirectory === "present" && (secrets.centralMode !== "700" || secrets.centralFiles.some((file) => file.mode !== "600"));
|
|
4287
4606
|
}
|
|
4288
4607
|
function inspectSecretsRoot(path) {
|
|
4289
|
-
return
|
|
4608
|
+
return existsSync23(path) ? { path, exists: true, mode: modeString(statSync5(path).mode) } : { path, exists: false };
|
|
4290
4609
|
}
|
|
4291
4610
|
function inspectHostEnvironment(homeDir, grokVersion, repositoryPaths, devspaceSettings) {
|
|
4292
|
-
const codexConfig =
|
|
4293
|
-
const claudeConfig =
|
|
4294
|
-
const grokConfig =
|
|
4611
|
+
const codexConfig = join24(homeDir, ".codex/config.toml");
|
|
4612
|
+
const claudeConfig = join24(homeDir, ".claude.json");
|
|
4613
|
+
const grokConfig = join24(homeDir, ".grok/config.toml");
|
|
4295
4614
|
const hostEnvironment = {
|
|
4296
4615
|
mcp: {
|
|
4297
4616
|
codexUser: { path: codexConfig, names: tomlMcpNames(codexConfig) },
|
|
@@ -4299,11 +4618,11 @@ function inspectHostEnvironment(homeDir, grokVersion, repositoryPaths, devspaceS
|
|
|
4299
4618
|
grokUser: { path: grokConfig, names: tomlMcpNames(grokConfig) }
|
|
4300
4619
|
},
|
|
4301
4620
|
skills: {
|
|
4302
|
-
codexUser: inspectSkillRoot(
|
|
4303
|
-
claudeCodeUser: inspectSkillRoot(
|
|
4304
|
-
grokUser: inspectSkillRoot(
|
|
4305
|
-
grokAgentsCompatibility: inspectSkillRoot(
|
|
4306
|
-
grokClaudeCompatibility: inspectSkillRoot(
|
|
4621
|
+
codexUser: inspectSkillRoot(join24(homeDir, ".agents/skills")),
|
|
4622
|
+
claudeCodeUser: inspectSkillRoot(join24(homeDir, ".claude/skills")),
|
|
4623
|
+
grokUser: inspectSkillRoot(join24(homeDir, ".grok/skills")),
|
|
4624
|
+
grokAgentsCompatibility: inspectSkillRoot(join24(homeDir, ".agents/skills")),
|
|
4625
|
+
grokClaudeCompatibility: inspectSkillRoot(join24(homeDir, ".claude/skills")),
|
|
4307
4626
|
ssot: inspectUserSkillsSsot(homeDir)
|
|
4308
4627
|
},
|
|
4309
4628
|
grok: {
|
|
@@ -4323,9 +4642,9 @@ function inspectHostEnvironment(homeDir, grokVersion, repositoryPaths, devspaceS
|
|
|
4323
4642
|
}
|
|
4324
4643
|
function inspectDevSpaceHealth(options) {
|
|
4325
4644
|
const run = options.run ?? runDevSpaceCommand;
|
|
4326
|
-
const configDirectory =
|
|
4327
|
-
const configPath =
|
|
4328
|
-
const authPath =
|
|
4645
|
+
const configDirectory = join24(options.homeDir, ".devspace");
|
|
4646
|
+
const configPath = join24(configDirectory, "config.json");
|
|
4647
|
+
const authPath = join24(configDirectory, "auth.json");
|
|
4329
4648
|
const installedResult = run("devspace", ["--version"], 3e3);
|
|
4330
4649
|
const installedVersion = installedResult.ok ? installedResult.stdout.match(/\b\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?\b/)?.[0] : void 0;
|
|
4331
4650
|
const latestResult = run("npm", ["view", "@waishnav/devspace", "version", "--registry=https://registry.npmjs.org/"], 5e3);
|
|
@@ -4335,14 +4654,14 @@ function inspectDevSpaceHealth(options) {
|
|
|
4335
4654
|
const processEnvironment = pid ? run("ps", ["eww", "-p", pid, "-o", "command="], 3e3) : void 0;
|
|
4336
4655
|
const processToolMode = processEnvironment?.ok ? processEnvironment.stdout.match(/(?:^|\s)DEVSPACE_TOOL_MODE=(minimal|full|codex)(?:\s|$)/)?.[1] : void 0;
|
|
4337
4656
|
const doctor = !installedResult.ok ? "unavailable" : run("devspace", ["doctor"], 1e4).ok ? "ok" : "failed";
|
|
4338
|
-
const configValue =
|
|
4657
|
+
const configValue = readJson4(configPath);
|
|
4339
4658
|
const configExists = isRecord3(configValue);
|
|
4340
4659
|
const allowedRoots = configExists && Array.isArray(configValue.allowedRoots) ? configValue.allowedRoots.filter((value) => typeof value === "string") : [];
|
|
4341
4660
|
const portfolioCoverage = !configExists || allowedRoots.length === 0 ? "unknown" : options.repositoryPaths.every((repositoryPath) => allowedRoots.some((root) => isPathInside(repositoryPath, root))) ? "complete" : "partial";
|
|
4342
4661
|
const bind = configExists && typeof configValue.host === "string" ? isLoopbackHost(configValue.host) ? "loopback" : "non-loopback" : "unknown";
|
|
4343
|
-
const directoryMode =
|
|
4344
|
-
const fileMode =
|
|
4345
|
-
const authMode =
|
|
4662
|
+
const directoryMode = existsSync23(configDirectory) ? modeString(statSync5(configDirectory).mode) : void 0;
|
|
4663
|
+
const fileMode = existsSync23(configPath) ? modeString(statSync5(configPath).mode) : void 0;
|
|
4664
|
+
const authMode = existsSync23(authPath) ? modeString(statSync5(authPath).mode) : void 0;
|
|
4346
4665
|
const update = installedVersion && latestVersion ? installedVersion === latestVersion ? "current" : "available" : "unknown";
|
|
4347
4666
|
const recommendations = [];
|
|
4348
4667
|
let status = "healthy";
|
|
@@ -4356,7 +4675,7 @@ function inspectDevSpaceHealth(options) {
|
|
|
4356
4675
|
};
|
|
4357
4676
|
if (!installedResult.ok) unhealthy("\u672C\u673A\u672A\u53D1\u73B0 DevSpace\uFF1B\u65E0\u6CD5\u4F7F\u7528\u5BBF\u4E3B\u5DE5\u4F5C\u533A\u670D\u52A1\u3002");
|
|
4358
4677
|
if (!configExists) unhealthy("\u7F3A\u5C11 ~/.devspace/config.json\u3002");
|
|
4359
|
-
if (!
|
|
4678
|
+
if (!existsSync23(authPath)) unhealthy("\u7F3A\u5C11 ~/.devspace/auth.json\u3002");
|
|
4360
4679
|
if (directoryMode && directoryMode !== "700") unhealthy(`~/.devspace \u76EE\u5F55\u6743\u9650\u4E3A ${directoryMode}\uFF0C\u5E94\u6536\u7D27\u4E3A 700\u3002`);
|
|
4361
4680
|
if (fileMode && fileMode !== "600") unhealthy(`DevSpace \u914D\u7F6E\u6587\u4EF6\u6743\u9650\u4E3A ${fileMode}\uFF0C\u5E94\u4E3A 600\u3002`);
|
|
4362
4681
|
if (authMode && authMode !== "600") unhealthy(`DevSpace \u8BA4\u8BC1\u6587\u4EF6\u6743\u9650\u4E3A ${authMode}\uFF0C\u5E94\u4E3A 600\u3002`);
|
|
@@ -4382,7 +4701,7 @@ function inspectDevSpaceHealth(options) {
|
|
|
4382
4701
|
exists: configExists,
|
|
4383
4702
|
...directoryMode ? { directoryMode } : {},
|
|
4384
4703
|
...fileMode ? { fileMode } : {},
|
|
4385
|
-
authExists:
|
|
4704
|
+
authExists: existsSync23(authPath),
|
|
4386
4705
|
...authMode ? { authMode } : {},
|
|
4387
4706
|
bind,
|
|
4388
4707
|
portValid: configExists && typeof configValue.port === "number" && Number.isInteger(configValue.port) && configValue.port > 0 && configValue.port <= 65535,
|
|
@@ -4416,12 +4735,12 @@ function isPathInside(path, root) {
|
|
|
4416
4735
|
}
|
|
4417
4736
|
function inspectSkillRoot(path) {
|
|
4418
4737
|
const exists = pathLexists(path) && safeIsDirectory(path);
|
|
4419
|
-
const names = exists ? safeReadDir(path).filter((name) => !name.startsWith(".") &&
|
|
4738
|
+
const names = exists ? safeReadDir(path).filter((name) => !name.startsWith(".") && existsSync23(join24(path, name, "SKILL.md"))) : [];
|
|
4420
4739
|
return { path, exists, names };
|
|
4421
4740
|
}
|
|
4422
4741
|
function claudeProjectLocalMcpNames(homeDir, root) {
|
|
4423
4742
|
if (!homeDir) return [];
|
|
4424
|
-
const value =
|
|
4743
|
+
const value = readJson4(join24(homeDir, ".claude.json"));
|
|
4425
4744
|
if (!isRecord3(value) || !isRecord3(value.projects)) return [];
|
|
4426
4745
|
const candidates = new Set([resolve5(root), safeRealpath(root)].filter((path) => Boolean(path)));
|
|
4427
4746
|
const names = /* @__PURE__ */ new Set();
|
|
@@ -4457,13 +4776,13 @@ function inspectGrokProject(root, homeDir, grokVersion) {
|
|
|
4457
4776
|
const value = JSON.parse(execFileSync("grok", ["inspect", "--json"], {
|
|
4458
4777
|
cwd: root,
|
|
4459
4778
|
encoding: "utf8",
|
|
4460
|
-
env: { ...process.env, HOME: homeDir, GROK_HOME:
|
|
4779
|
+
env: { ...process.env, HOME: homeDir, GROK_HOME: join24(homeDir, ".grok") },
|
|
4461
4780
|
maxBuffer: 10 * 1024 * 1024,
|
|
4462
4781
|
stdio: ["ignore", "pipe", "ignore"],
|
|
4463
4782
|
timeout: 8e3
|
|
4464
4783
|
}));
|
|
4465
4784
|
if (!isRecord3(value)) return empty("failed");
|
|
4466
|
-
const userClaudeNames = new Set(jsonObjectKeys(
|
|
4785
|
+
const userClaudeNames = new Set(jsonObjectKeys(join24(homeDir, ".claude.json"), "mcpServers"));
|
|
4467
4786
|
const localClaudeNames = new Set(claudeProjectLocalMcpNames(homeDir, root));
|
|
4468
4787
|
const effectiveMcp = Array.isArray(value.mcpServers) ? value.mcpServers.flatMap((item) => {
|
|
4469
4788
|
if (!isRecord3(item) || typeof item.name !== "string") return [];
|
|
@@ -4521,9 +4840,9 @@ function inferGrokMcpScope(name, sourceType, sourcePath, root, homeDir, userClau
|
|
|
4521
4840
|
}
|
|
4522
4841
|
const resolvedSource = safeRealpath(sourcePath) ?? resolve5(sourcePath);
|
|
4523
4842
|
const resolvedRoot = safeRealpath(root) ?? resolve5(root);
|
|
4524
|
-
if (resolvedSource ===
|
|
4843
|
+
if (resolvedSource === join24(resolvedRoot, ".mcp.json")) return "project-shared";
|
|
4525
4844
|
if (resolvedSource.startsWith(resolvedRoot + sep)) return "project";
|
|
4526
|
-
if (homeDir && resolvedSource ===
|
|
4845
|
+
if (homeDir && resolvedSource === join24(resolve5(homeDir), ".claude.json")) {
|
|
4527
4846
|
if (localClaudeNames.has(name)) return "project-local";
|
|
4528
4847
|
if (userClaudeNames.has(name)) return "user";
|
|
4529
4848
|
}
|
|
@@ -4558,7 +4877,7 @@ function inspectHooks(root) {
|
|
|
4558
4877
|
{ host: "codex", path: ".codex/hooks.json" }
|
|
4559
4878
|
];
|
|
4560
4879
|
return configs.map((config) => {
|
|
4561
|
-
const value =
|
|
4880
|
+
const value = readJson4(join24(root, config.path));
|
|
4562
4881
|
const counts = /* @__PURE__ */ new Map();
|
|
4563
4882
|
collectHookEvents(value, counts);
|
|
4564
4883
|
return {
|
|
@@ -4579,18 +4898,18 @@ function collectHookEvents(value, counts) {
|
|
|
4579
4898
|
}
|
|
4580
4899
|
}
|
|
4581
4900
|
function inspectDocs(root, expected) {
|
|
4582
|
-
const packageJson =
|
|
4901
|
+
const packageJson = readJson4(join24(root, "package.json"));
|
|
4583
4902
|
const dependencies = isRecord3(packageJson) ? { ...recordOrEmpty(packageJson.dependencies), ...recordOrEmpty(packageJson.devDependencies) } : {};
|
|
4584
4903
|
const docGov = dependencyVersion(dependencies["@pieai/doc-gov"]);
|
|
4585
4904
|
const proGov = dependencyVersion(dependencies["@pieai/pro-gov"]);
|
|
4586
|
-
const routerMatch = safeRead(
|
|
4905
|
+
const routerMatch = safeRead(join24(root, "AGENTS.md")).match(/PGS-ROUTER:BEGIN\s+v([0-9.]+)/);
|
|
4587
4906
|
const declared = [docGov, proGov].filter((value) => Boolean(value));
|
|
4588
4907
|
return {
|
|
4589
4908
|
routerVersion: routerMatch?.[1],
|
|
4590
4909
|
expectedRouterVersion: CURRENT_ROUTER_VERSION,
|
|
4591
4910
|
routerAligned: routerMatch?.[1] === CURRENT_ROUTER_VERSION,
|
|
4592
|
-
manifest:
|
|
4593
|
-
currentWork:
|
|
4911
|
+
manifest: existsSync23(join24(root, "docs/governance/MANIFEST.yml")),
|
|
4912
|
+
currentWork: existsSync23(join24(root, "docs/reference/execution/current-work.md")),
|
|
4594
4913
|
packages: {
|
|
4595
4914
|
expected,
|
|
4596
4915
|
docGov,
|
|
@@ -4605,7 +4924,7 @@ function dependencyVersion(value) {
|
|
|
4605
4924
|
return match?.[1];
|
|
4606
4925
|
}
|
|
4607
4926
|
function packageVersion(path) {
|
|
4608
|
-
const value =
|
|
4927
|
+
const value = readJson4(path);
|
|
4609
4928
|
return isRecord3(value) && typeof value.version === "string" ? value.version : void 0;
|
|
4610
4929
|
}
|
|
4611
4930
|
function recordOrEmpty(value) {
|
|
@@ -4613,20 +4932,20 @@ function recordOrEmpty(value) {
|
|
|
4613
4932
|
}
|
|
4614
4933
|
function inspectSkillRegistry(executionEngineRoot) {
|
|
4615
4934
|
if (!executionEngineRoot) return { source: 0, registered: 0, bundled: 0, bundles: 0 };
|
|
4616
|
-
const agentAssetsRoot =
|
|
4617
|
-
const registry =
|
|
4935
|
+
const agentAssetsRoot = join24(executionEngineRoot, "agent-assets");
|
|
4936
|
+
const registry = readJson4(join24(agentAssetsRoot, "registry.json"));
|
|
4618
4937
|
const assets = isRecord3(registry) && Array.isArray(registry.assets) ? registry.assets : [];
|
|
4619
4938
|
const registeredSkills = assets.filter((asset) => isRecord3(asset) && asset.kind === "skill");
|
|
4620
|
-
const bundleRoot =
|
|
4939
|
+
const bundleRoot = join24(agentAssetsRoot, "bundles");
|
|
4621
4940
|
const bundleFiles = safeReadDir(bundleRoot).filter((file) => file.endsWith(".json"));
|
|
4622
4941
|
const bundledIds = /* @__PURE__ */ new Set();
|
|
4623
4942
|
for (const file of bundleFiles) {
|
|
4624
|
-
const bundle =
|
|
4943
|
+
const bundle = readJson4(join24(bundleRoot, file));
|
|
4625
4944
|
if (!isRecord3(bundle) || !Array.isArray(bundle.assets)) continue;
|
|
4626
4945
|
for (const id of bundle.assets) if (typeof id === "string") bundledIds.add(id);
|
|
4627
4946
|
}
|
|
4628
|
-
const sourceRoots = [
|
|
4629
|
-
const source = sourceRoots.reduce((count, root) => count + safeReadDir(root).filter((name) =>
|
|
4947
|
+
const sourceRoots = [join24(agentAssetsRoot, "skills/pie-skills"), join24(agentAssetsRoot, "skills/npx-skills/.agents/skills")];
|
|
4948
|
+
const source = sourceRoots.reduce((count, root) => count + safeReadDir(root).filter((name) => existsSync23(join24(root, name, "SKILL.md"))).length, 0);
|
|
4630
4949
|
return {
|
|
4631
4950
|
source,
|
|
4632
4951
|
registered: registeredSkills.length,
|
|
@@ -4635,12 +4954,12 @@ function inspectSkillRegistry(executionEngineRoot) {
|
|
|
4635
4954
|
};
|
|
4636
4955
|
}
|
|
4637
4956
|
function jsonObjectKeys(path, key) {
|
|
4638
|
-
const value =
|
|
4957
|
+
const value = readJson4(path);
|
|
4639
4958
|
if (!isRecord3(value) || !isRecord3(value[key])) return [];
|
|
4640
4959
|
return Object.keys(value[key]).sort();
|
|
4641
4960
|
}
|
|
4642
4961
|
function tomlMcpNames(path) {
|
|
4643
|
-
if (!
|
|
4962
|
+
if (!existsSync23(path)) return [];
|
|
4644
4963
|
const names = /* @__PURE__ */ new Set();
|
|
4645
4964
|
for (const line of safeRead(path).split(/\r?\n/)) {
|
|
4646
4965
|
const match = line.match(/^\s*\[mcp_servers\.(?:"([^"]+)"|([^\.\]]+))\]\s*$/);
|
|
@@ -4649,30 +4968,30 @@ function tomlMcpNames(path) {
|
|
|
4649
4968
|
}
|
|
4650
4969
|
return [...names].sort();
|
|
4651
4970
|
}
|
|
4652
|
-
function
|
|
4971
|
+
function readJson4(path) {
|
|
4653
4972
|
try {
|
|
4654
|
-
return JSON.parse(
|
|
4973
|
+
return JSON.parse(readFileSync17(path, "utf8"));
|
|
4655
4974
|
} catch {
|
|
4656
4975
|
return void 0;
|
|
4657
4976
|
}
|
|
4658
4977
|
}
|
|
4659
4978
|
function safeRead(path) {
|
|
4660
4979
|
try {
|
|
4661
|
-
return
|
|
4980
|
+
return readFileSync17(path, "utf8");
|
|
4662
4981
|
} catch {
|
|
4663
4982
|
return "";
|
|
4664
4983
|
}
|
|
4665
4984
|
}
|
|
4666
4985
|
function safeReadDir(path) {
|
|
4667
4986
|
try {
|
|
4668
|
-
return
|
|
4987
|
+
return readdirSync9(path).sort();
|
|
4669
4988
|
} catch {
|
|
4670
4989
|
return [];
|
|
4671
4990
|
}
|
|
4672
4991
|
}
|
|
4673
4992
|
function safeIsDirectory(path) {
|
|
4674
4993
|
try {
|
|
4675
|
-
return
|
|
4994
|
+
return statSync5(path).isDirectory();
|
|
4676
4995
|
} catch {
|
|
4677
4996
|
return false;
|
|
4678
4997
|
}
|
|
@@ -4709,14 +5028,14 @@ function findDashboardAssets() {
|
|
|
4709
5028
|
const packageRoot2 = dirname13(dirname13(fileURLToPath4(import.meta.url)));
|
|
4710
5029
|
const candidates = [
|
|
4711
5030
|
process.env.PGS_DASHBOARD_ASSETS_DIR,
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
|
|
4715
|
-
|
|
4716
|
-
|
|
4717
|
-
|
|
5031
|
+
join24(packageRoot2, ".dashboard-build"),
|
|
5032
|
+
join24(packageRoot2, "assets/portfolio-dashboard"),
|
|
5033
|
+
join24(process.cwd(), ".dashboard-build"),
|
|
5034
|
+
join24(process.cwd(), "assets/portfolio-dashboard"),
|
|
5035
|
+
join24(process.cwd(), "packages/pro-gov/.dashboard-build"),
|
|
5036
|
+
join24(process.cwd(), "packages/pro-gov/assets/portfolio-dashboard")
|
|
4718
5037
|
].filter((value) => Boolean(value));
|
|
4719
|
-
const match = candidates.find((path) =>
|
|
5038
|
+
const match = candidates.find((path) => existsSync23(join24(path, "index.html")));
|
|
4720
5039
|
if (!match) throw new Error("Portfolio dashboard assets were not built. Run pnpm --filter @pieai/pro-gov build.");
|
|
4721
5040
|
return match;
|
|
4722
5041
|
}
|
|
@@ -4752,10 +5071,23 @@ function runPortfolioAiHealth(args) {
|
|
|
4752
5071
|
for (const issue of loaded.issues) console.error(`${issue.type}: ${issue.message}`);
|
|
4753
5072
|
return 1;
|
|
4754
5073
|
}
|
|
4755
|
-
const
|
|
5074
|
+
const targetId = options.value.targetId && options.value.targetId !== "all" ? options.value.targetId : void 0;
|
|
5075
|
+
if (targetId && !loaded.manifest.targets.some((target) => target.id === targetId)) {
|
|
5076
|
+
console.error(`Unknown portfolio target: ${targetId}`);
|
|
5077
|
+
return 1;
|
|
5078
|
+
}
|
|
5079
|
+
const latest = inspectPortfolioAiHealth({
|
|
4756
5080
|
manifest: loaded.manifest,
|
|
4757
|
-
secretsRoot: options.value.secretsRoot
|
|
5081
|
+
secretsRoot: options.value.secretsRoot,
|
|
5082
|
+
targetId
|
|
4758
5083
|
});
|
|
5084
|
+
const allRepositoryIds = [
|
|
5085
|
+
loaded.manifest.controlPlane?.id,
|
|
5086
|
+
loaded.manifest.executionEngine?.id,
|
|
5087
|
+
...loaded.manifest.targets.map((target) => target.id)
|
|
5088
|
+
].filter((id) => Boolean(id));
|
|
5089
|
+
const existing = targetId ? readExistingAiHealthReport(options.value.outDir, loaded.manifest.portfolioId) : void 0;
|
|
5090
|
+
const report = targetId ? mergePortfolioAiHealthReport(existing, latest, allRepositoryIds) : latest;
|
|
4759
5091
|
const written = writePortfolioAiHealthReport(report, options.value.outDir);
|
|
4760
5092
|
if (options.value.json) {
|
|
4761
5093
|
console.log(JSON.stringify({ ok: true, ...written, summary: report.summary }, null, 2));
|
|
@@ -4814,6 +5146,12 @@ function runPortfolioDoctor(args) {
|
|
|
4814
5146
|
for (const warning of target.hostSsot.issues) {
|
|
4815
5147
|
console.log(`${target.id} host-ssot-warning: ${warning}`);
|
|
4816
5148
|
}
|
|
5149
|
+
if (target.verification.status === "attention") {
|
|
5150
|
+
console.log(`${target.id} verification-warning: missing ${target.verification.missing.join(", ")}`);
|
|
5151
|
+
}
|
|
5152
|
+
if (target.versions.status === "attention") {
|
|
5153
|
+
console.log(`${target.id} version-policy-warning: ${target.versions.attentionCount} drift(s)`);
|
|
5154
|
+
}
|
|
4817
5155
|
}
|
|
4818
5156
|
if (result.ok) {
|
|
4819
5157
|
console.log(`portfolio doctor passed (${targets.length} targets)`);
|
|
@@ -5078,8 +5416,8 @@ function isHost2(value) {
|
|
|
5078
5416
|
return value === "codex" || value === "claude-code" || value === "gemini-cli" || value === "antigravity";
|
|
5079
5417
|
}
|
|
5080
5418
|
function findPortfolioAgentAssetsDir(manifest) {
|
|
5081
|
-
const agentAssetsDir = manifest?.executionEngine?.path ?
|
|
5082
|
-
return agentAssetsDir &&
|
|
5419
|
+
const agentAssetsDir = manifest?.executionEngine?.path ? join25(manifest.executionEngine.path, "agent-assets") : void 0;
|
|
5420
|
+
return agentAssetsDir && existsSync24(join25(agentAssetsDir, "registry.json")) ? agentAssetsDir : void 0;
|
|
5083
5421
|
}
|
|
5084
5422
|
function printUsage4() {
|
|
5085
5423
|
console.error("Usage:");
|
|
@@ -5087,12 +5425,24 @@ function printUsage4() {
|
|
|
5087
5425
|
console.error(" pro-gov portfolio plan --config <path> [--target <id|all>] [--host codex|claude-code|gemini-cli|antigravity] [--json]");
|
|
5088
5426
|
console.error(" pro-gov portfolio assets-check --config <path> [--target <id|all>] [--json]");
|
|
5089
5427
|
console.error(" pro-gov portfolio doctor --config <path> [--target <id|all>] [--json]");
|
|
5090
|
-
console.error(" pro-gov portfolio ai-health --config <path> --out <directory> [--secrets-root <directory>] [--json]");
|
|
5428
|
+
console.error(" pro-gov portfolio ai-health --config <path> --out <directory> [--target <id|all>] [--secrets-root <directory>] [--json]");
|
|
5429
|
+
}
|
|
5430
|
+
function readExistingAiHealthReport(outDir, portfolioId) {
|
|
5431
|
+
if (!outDir) return void 0;
|
|
5432
|
+
const path = join25(outDir, "portfolio-ai-health.json");
|
|
5433
|
+
if (!existsSync24(path)) return void 0;
|
|
5434
|
+
try {
|
|
5435
|
+
const value = JSON.parse(readFileSync18(path, "utf8"));
|
|
5436
|
+
if (!value || typeof value !== "object" || value.portfolioId !== portfolioId || !Array.isArray(value.repositories)) return void 0;
|
|
5437
|
+
return value;
|
|
5438
|
+
} catch {
|
|
5439
|
+
return void 0;
|
|
5440
|
+
}
|
|
5091
5441
|
}
|
|
5092
5442
|
|
|
5093
5443
|
// src/commands/sync.ts
|
|
5094
|
-
import { existsSync as
|
|
5095
|
-
import { join as
|
|
5444
|
+
import { existsSync as existsSync25, lstatSync as lstatSync9, readFileSync as readFileSync19, readlinkSync as readlinkSync4 } from "node:fs";
|
|
5445
|
+
import { join as join26 } from "node:path";
|
|
5096
5446
|
function runSync(args) {
|
|
5097
5447
|
const check = args.includes("--check");
|
|
5098
5448
|
if (!check) {
|
|
@@ -5120,7 +5470,7 @@ function runSync(args) {
|
|
|
5120
5470
|
console.log("pro-gov sync check");
|
|
5121
5471
|
console.log(`profile: ${profile}`);
|
|
5122
5472
|
for (const file of planStarterFiles(profile)) {
|
|
5123
|
-
const targetPath =
|
|
5473
|
+
const targetPath = join26(process.cwd(), file.targetPath);
|
|
5124
5474
|
const stat = safeLstat3(targetPath);
|
|
5125
5475
|
if (!stat) {
|
|
5126
5476
|
if (file.ownership === "optional-guardrail") continue;
|
|
@@ -5144,8 +5494,8 @@ function runSync(args) {
|
|
|
5144
5494
|
}
|
|
5145
5495
|
continue;
|
|
5146
5496
|
}
|
|
5147
|
-
const source =
|
|
5148
|
-
const target =
|
|
5497
|
+
const source = readFileSync19(file.absoluteSourcePath, "utf8");
|
|
5498
|
+
const target = readFileSync19(targetPath, "utf8");
|
|
5149
5499
|
if (!matchesExpectedContent(file.targetPath, source, target)) {
|
|
5150
5500
|
console.log(`different: ${file.targetPath}`);
|
|
5151
5501
|
differences += 1;
|
|
@@ -5179,7 +5529,7 @@ function normalizeMarkdownTableCell(cell) {
|
|
|
5179
5529
|
}
|
|
5180
5530
|
function inferInstalledProfile(root) {
|
|
5181
5531
|
const installed = ["engineering-runtime", "doc-only"].filter(
|
|
5182
|
-
(profile) =>
|
|
5532
|
+
(profile) => existsSync25(join26(root, `docs/governance/agents-routing/${profile}-v1.1.md`))
|
|
5183
5533
|
);
|
|
5184
5534
|
return installed.length === 1 ? installed[0] : void 0;
|
|
5185
5535
|
}
|