@pieai/pro-gov 0.7.0 → 0.7.2
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/assets/portfolio-dashboard/app.css +1 -1
- package/assets/portfolio-dashboard/app.js +8 -8
- package/dist/cli.js +642 -153
- package/package.json +9 -9
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}`);
|
|
@@ -3471,12 +3469,13 @@ function validateVersionPolicy(value, projectTypes, issues) {
|
|
|
3471
3469
|
issues.push({ type: "invalid-field", field: "technologyGovernance.versionPolicy", message: "Technology versionPolicy must be an object." });
|
|
3472
3470
|
return;
|
|
3473
3471
|
}
|
|
3474
|
-
validateAllowedFields(value, "versionPolicy", ["schemaVersion", "packageManager", "runtime", "packages"], issues);
|
|
3472
|
+
validateAllowedFields(value, "versionPolicy", ["schemaVersion", "packageManager", "runtime", "runtimes", "packages"], issues);
|
|
3475
3473
|
if (value.schemaVersion !== 1) {
|
|
3476
3474
|
issues.push({ type: "invalid-field", field: "technologyGovernance.versionPolicy.schemaVersion", message: "Technology versionPolicy schemaVersion must be 1." });
|
|
3477
3475
|
}
|
|
3478
3476
|
validateVersionRequirement(value.packageManager, "technologyGovernance.versionPolicy.packageManager", issues);
|
|
3479
3477
|
validateVersionRequirement(value.runtime, "technologyGovernance.versionPolicy.runtime", issues);
|
|
3478
|
+
validateRuntimeRequirements(value.runtimes, projectTypes, issues);
|
|
3480
3479
|
if (!Array.isArray(value.packages)) {
|
|
3481
3480
|
issues.push({ type: "invalid-field", field: "technologyGovernance.versionPolicy.packages", message: "Technology versionPolicy packages must be an array." });
|
|
3482
3481
|
return;
|
|
@@ -3507,6 +3506,39 @@ function validateVersionPolicy(value, projectTypes, issues) {
|
|
|
3507
3506
|
}
|
|
3508
3507
|
}
|
|
3509
3508
|
}
|
|
3509
|
+
function validateRuntimeRequirements(value, projectTypes, issues) {
|
|
3510
|
+
if (value === void 0) return;
|
|
3511
|
+
const field = "technologyGovernance.versionPolicy.runtimes";
|
|
3512
|
+
if (!Array.isArray(value)) {
|
|
3513
|
+
issues.push({ type: "invalid-field", field, message: "Technology versionPolicy runtimes must be an array." });
|
|
3514
|
+
return;
|
|
3515
|
+
}
|
|
3516
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3517
|
+
for (const entry of value) {
|
|
3518
|
+
if (!isRecord(entry)) {
|
|
3519
|
+
issues.push({ type: "invalid-field", field, message: "Version policy runtime entries must be objects." });
|
|
3520
|
+
continue;
|
|
3521
|
+
}
|
|
3522
|
+
validateAllowedFields(entry, "versionPolicy.runtimeEntry", ["name", "version", "appliesTo"], issues);
|
|
3523
|
+
const name = typeof entry.name === "string" ? entry.name : "";
|
|
3524
|
+
if (!name || seen.has(name)) {
|
|
3525
|
+
issues.push({ type: "invalid-field", field: `${field}.name`, message: `Version policy runtime name must be non-empty and unique: ${String(entry.name)}` });
|
|
3526
|
+
} else seen.add(name);
|
|
3527
|
+
if (typeof entry.version !== "string" || !isExactVersion(entry.version)) {
|
|
3528
|
+
issues.push({ type: "invalid-field", field: `${field}.version`, message: `Version policy runtime version must be exact semver: ${String(entry.version)}` });
|
|
3529
|
+
}
|
|
3530
|
+
if (entry.appliesTo !== void 0) {
|
|
3531
|
+
validateOptionalStringArray(entry.appliesTo, name, `${field}.appliesTo`, issues);
|
|
3532
|
+
if (Array.isArray(entry.appliesTo)) {
|
|
3533
|
+
for (const projectType of entry.appliesTo) {
|
|
3534
|
+
if (typeof projectType === "string" && !projectTypes.has(projectType)) {
|
|
3535
|
+
issues.push({ type: "invalid-field", field: `${field}.appliesTo`, message: `Version policy references unknown project type: ${projectType}` });
|
|
3536
|
+
}
|
|
3537
|
+
}
|
|
3538
|
+
}
|
|
3539
|
+
}
|
|
3540
|
+
}
|
|
3541
|
+
}
|
|
3510
3542
|
function validateVersionRequirement(value, field, issues) {
|
|
3511
3543
|
if (value === void 0) return;
|
|
3512
3544
|
if (!isRecord(value)) {
|
|
@@ -3697,11 +3729,11 @@ function isRecord(value) {
|
|
|
3697
3729
|
}
|
|
3698
3730
|
|
|
3699
3731
|
// src/portfolio/doctor.ts
|
|
3700
|
-
import { spawnSync as
|
|
3732
|
+
import { spawnSync as spawnSync6 } from "node:child_process";
|
|
3701
3733
|
import { existsSync as existsSync22, readFileSync as readFileSync16 } from "node:fs";
|
|
3702
3734
|
import { createRequire as createRequire2 } from "node:module";
|
|
3703
3735
|
import { homedir as homedir3 } from "node:os";
|
|
3704
|
-
import { dirname as
|
|
3736
|
+
import { dirname as dirname13, join as join23 } from "node:path";
|
|
3705
3737
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
3706
3738
|
|
|
3707
3739
|
// src/host-tooling/inventory.ts
|
|
@@ -3874,20 +3906,51 @@ function pathIsSymlink(path) {
|
|
|
3874
3906
|
}
|
|
3875
3907
|
|
|
3876
3908
|
// src/portfolio/version-policy.ts
|
|
3877
|
-
import {
|
|
3878
|
-
import {
|
|
3909
|
+
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
3910
|
+
import { existsSync as existsSync21, readFileSync as readFileSync15, readdirSync as readdirSync9 } from "node:fs";
|
|
3911
|
+
import { dirname as dirname12, join as join22 } from "node:path";
|
|
3879
3912
|
function inspectVersionPolicy(root, policy, projectType) {
|
|
3880
|
-
if (!policy) return { status: "compliant", packages: [], attentionCount: 0 };
|
|
3881
|
-
const
|
|
3913
|
+
if (!policy) return { status: "compliant", packages: [], runtimes: [], attentionCount: 0 };
|
|
3914
|
+
const packageManifests = collectPackageManifests(root);
|
|
3915
|
+
const packageJson = packageManifests.find(
|
|
3916
|
+
(manifest) => manifest.path === "package.json"
|
|
3917
|
+
)?.packageJson;
|
|
3882
3918
|
const packageManager = policy.packageManager ? inspectPackageManager(packageJson, policy.packageManager.name, policy.packageManager.version) : void 0;
|
|
3883
3919
|
const runtime = policy.runtime ? inspectRuntime(policy.runtime.name, policy.runtime.version) : void 0;
|
|
3920
|
+
const runtimes = (policy.runtimes ?? []).map((requirement) => {
|
|
3921
|
+
if (requirement.appliesTo && (!projectType || !requirement.appliesTo.includes(projectType))) {
|
|
3922
|
+
return {
|
|
3923
|
+
name: requirement.name,
|
|
3924
|
+
expected: requirement.version,
|
|
3925
|
+
status: "not-applicable"
|
|
3926
|
+
};
|
|
3927
|
+
}
|
|
3928
|
+
return inspectRuntime(requirement.name, requirement.version);
|
|
3929
|
+
});
|
|
3884
3930
|
const packages = policy.packages.map((requirement) => {
|
|
3885
3931
|
if (requirement.appliesTo && (!projectType || !requirement.appliesTo.includes(projectType))) {
|
|
3886
|
-
return {
|
|
3932
|
+
return {
|
|
3933
|
+
name: requirement.name,
|
|
3934
|
+
expected: requirement.version,
|
|
3935
|
+
status: "not-applicable"
|
|
3936
|
+
};
|
|
3887
3937
|
}
|
|
3888
|
-
const
|
|
3889
|
-
|
|
3890
|
-
|
|
3938
|
+
const declarations = packageManifests.map((manifest) => ({
|
|
3939
|
+
manifest,
|
|
3940
|
+
declared: findDeclaredVersion(manifest.packageJson, requirement.name)
|
|
3941
|
+
})).filter(
|
|
3942
|
+
(item) => item.declared !== void 0
|
|
3943
|
+
);
|
|
3944
|
+
const declaredValues = unique(declarations.map((item) => item.declared));
|
|
3945
|
+
const installedValues = unique(
|
|
3946
|
+
declarations.map((item) => readInstalledVersion(root, requirement.name, item.manifest.directory)).filter((value) => value !== void 0)
|
|
3947
|
+
);
|
|
3948
|
+
const hasMissingInstall = declarations.some(
|
|
3949
|
+
(item) => readInstalledVersion(root, requirement.name, item.manifest.directory) === void 0
|
|
3950
|
+
);
|
|
3951
|
+
const declared = declaredValues.length ? declaredValues.join(" \xB7 ") : void 0;
|
|
3952
|
+
const installed = installedValues.length ? installedValues.join(" \xB7 ") : void 0;
|
|
3953
|
+
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";
|
|
3891
3954
|
return {
|
|
3892
3955
|
name: requirement.name,
|
|
3893
3956
|
expected: requirement.version,
|
|
@@ -3897,12 +3960,17 @@ function inspectVersionPolicy(root, policy, projectType) {
|
|
|
3897
3960
|
status
|
|
3898
3961
|
};
|
|
3899
3962
|
});
|
|
3900
|
-
const all = [packageManager, runtime, ...packages].filter(
|
|
3901
|
-
|
|
3963
|
+
const all = [packageManager, runtime, ...runtimes, ...packages].filter(
|
|
3964
|
+
(item) => item !== void 0
|
|
3965
|
+
);
|
|
3966
|
+
const attentionCount = all.filter(
|
|
3967
|
+
(item) => item.status !== "compliant" && item.status !== "not-applicable"
|
|
3968
|
+
).length;
|
|
3902
3969
|
return {
|
|
3903
3970
|
status: attentionCount === 0 ? "compliant" : "attention",
|
|
3904
3971
|
packageManager,
|
|
3905
3972
|
runtime,
|
|
3973
|
+
runtimes,
|
|
3906
3974
|
packages,
|
|
3907
3975
|
attentionCount
|
|
3908
3976
|
};
|
|
@@ -3919,7 +3987,7 @@ function inspectPackageManager(packageJson, expectedName, expectedVersion) {
|
|
|
3919
3987
|
};
|
|
3920
3988
|
}
|
|
3921
3989
|
function inspectRuntime(expectedName, expectedVersion) {
|
|
3922
|
-
const actual = expectedName
|
|
3990
|
+
const actual = readRuntimeVersion(expectedName);
|
|
3923
3991
|
return {
|
|
3924
3992
|
name: expectedName,
|
|
3925
3993
|
expected: expectedVersion,
|
|
@@ -3927,16 +3995,86 @@ function inspectRuntime(expectedName, expectedVersion) {
|
|
|
3927
3995
|
status: actual === void 0 ? "missing" : actual === expectedVersion ? "compliant" : "drift"
|
|
3928
3996
|
};
|
|
3929
3997
|
}
|
|
3998
|
+
function readRuntimeVersion(name) {
|
|
3999
|
+
if (name === "node") return process.versions.node;
|
|
4000
|
+
if (name !== "deno") return void 0;
|
|
4001
|
+
const result = spawnSync5("deno", ["--version"], { encoding: "utf8" });
|
|
4002
|
+
if (result.status !== 0) return void 0;
|
|
4003
|
+
return /^deno\s+(\d+\.\d+\.\d+)/m.exec(result.stdout)?.[1];
|
|
4004
|
+
}
|
|
3930
4005
|
function findDeclaredVersion(packageJson, name) {
|
|
3931
|
-
for (const section of [
|
|
4006
|
+
for (const section of [
|
|
4007
|
+
"dependencies",
|
|
4008
|
+
"devDependencies",
|
|
4009
|
+
"peerDependencies",
|
|
4010
|
+
"optionalDependencies"
|
|
4011
|
+
]) {
|
|
3932
4012
|
const value = packageJson?.[section]?.[name];
|
|
3933
4013
|
if (typeof value === "string") return value;
|
|
3934
4014
|
}
|
|
3935
4015
|
return void 0;
|
|
3936
4016
|
}
|
|
3937
|
-
function readInstalledVersion(root, name) {
|
|
3938
|
-
|
|
3939
|
-
|
|
4017
|
+
function readInstalledVersion(root, name, fromDirectory = root) {
|
|
4018
|
+
let current = fromDirectory;
|
|
4019
|
+
while (true) {
|
|
4020
|
+
const packageJson = readJson2(join22(current, "node_modules", name, "package.json"));
|
|
4021
|
+
if (typeof packageJson?.version === "string") return packageJson.version;
|
|
4022
|
+
if (current === root) return void 0;
|
|
4023
|
+
const parent = dirname12(current);
|
|
4024
|
+
if (parent === current) return void 0;
|
|
4025
|
+
current = parent;
|
|
4026
|
+
}
|
|
4027
|
+
}
|
|
4028
|
+
function collectPackageManifests(root) {
|
|
4029
|
+
const manifests = [];
|
|
4030
|
+
const ignored = /* @__PURE__ */ new Set([
|
|
4031
|
+
".git",
|
|
4032
|
+
".next",
|
|
4033
|
+
".nuxt",
|
|
4034
|
+
".output",
|
|
4035
|
+
".pnpm",
|
|
4036
|
+
".turbo",
|
|
4037
|
+
".vercel",
|
|
4038
|
+
".worktrees",
|
|
4039
|
+
"build",
|
|
4040
|
+
"coverage",
|
|
4041
|
+
"dist",
|
|
4042
|
+
"node_modules",
|
|
4043
|
+
"out",
|
|
4044
|
+
"target",
|
|
4045
|
+
"tmp",
|
|
4046
|
+
".cache",
|
|
4047
|
+
".pnpm-store",
|
|
4048
|
+
".tmp-repos"
|
|
4049
|
+
]);
|
|
4050
|
+
const visit = (directory, depth) => {
|
|
4051
|
+
if (depth > 6) return;
|
|
4052
|
+
let entries;
|
|
4053
|
+
try {
|
|
4054
|
+
entries = readdirSync9(directory, { withFileTypes: true });
|
|
4055
|
+
} catch {
|
|
4056
|
+
return;
|
|
4057
|
+
}
|
|
4058
|
+
for (const entry of entries) {
|
|
4059
|
+
const path = join22(directory, entry.name);
|
|
4060
|
+
if (entry.isFile() && entry.name === "package.json") {
|
|
4061
|
+
const packageJson = readJson2(path);
|
|
4062
|
+
if (packageJson)
|
|
4063
|
+
manifests.push({
|
|
4064
|
+
path: path.slice(root.length + 1) || "package.json",
|
|
4065
|
+
directory,
|
|
4066
|
+
packageJson
|
|
4067
|
+
});
|
|
4068
|
+
} else if (entry.isDirectory() && !ignored.has(entry.name)) {
|
|
4069
|
+
visit(path, depth + 1);
|
|
4070
|
+
}
|
|
4071
|
+
}
|
|
4072
|
+
};
|
|
4073
|
+
visit(root, 0);
|
|
4074
|
+
return manifests.sort((a, b) => a.path.localeCompare(b.path));
|
|
4075
|
+
}
|
|
4076
|
+
function unique(values) {
|
|
4077
|
+
return [...new Set(values)];
|
|
3940
4078
|
}
|
|
3941
4079
|
function readJson2(path) {
|
|
3942
4080
|
if (!existsSync21(path)) return void 0;
|
|
@@ -4070,7 +4208,7 @@ function runTargetChecks(target) {
|
|
|
4070
4208
|
];
|
|
4071
4209
|
return commands.map((command2) => {
|
|
4072
4210
|
if (!existsSync22(command2.cli)) return { name: command2.name, status: null };
|
|
4073
|
-
const result =
|
|
4211
|
+
const result = spawnSync6(process.execPath, [command2.cli, ...command2.args], {
|
|
4074
4212
|
cwd: target.path,
|
|
4075
4213
|
encoding: "utf8",
|
|
4076
4214
|
timeout: 3e4
|
|
@@ -4079,13 +4217,13 @@ function runTargetChecks(target) {
|
|
|
4079
4217
|
});
|
|
4080
4218
|
}
|
|
4081
4219
|
function inspectGit(path) {
|
|
4082
|
-
const inside =
|
|
4220
|
+
const inside = spawnSync6("git", ["rev-parse", "--is-inside-work-tree"], {
|
|
4083
4221
|
cwd: path,
|
|
4084
4222
|
encoding: "utf8"
|
|
4085
4223
|
});
|
|
4086
4224
|
if (inside.status !== 0) return { isRepository: false, dirty: false };
|
|
4087
|
-
const status =
|
|
4088
|
-
const branch =
|
|
4225
|
+
const status = spawnSync6("git", ["status", "--porcelain"], { cwd: path, encoding: "utf8" });
|
|
4226
|
+
const branch = spawnSync6("git", ["branch", "--show-current"], { cwd: path, encoding: "utf8" });
|
|
4089
4227
|
return {
|
|
4090
4228
|
isRepository: true,
|
|
4091
4229
|
dirty: status.stdout.trim().length > 0,
|
|
@@ -4108,11 +4246,11 @@ function getExpectedPackageVersions() {
|
|
|
4108
4246
|
};
|
|
4109
4247
|
}
|
|
4110
4248
|
function findOwnPackageJson() {
|
|
4111
|
-
let current =
|
|
4249
|
+
let current = dirname13(fileURLToPath3(import.meta.url));
|
|
4112
4250
|
for (let depth = 0; depth < 5; depth += 1) {
|
|
4113
4251
|
const candidate = join23(current, "package.json");
|
|
4114
4252
|
if (existsSync22(candidate)) return candidate;
|
|
4115
|
-
current =
|
|
4253
|
+
current = dirname13(current);
|
|
4116
4254
|
}
|
|
4117
4255
|
return "";
|
|
4118
4256
|
}
|
|
@@ -4142,13 +4280,13 @@ import {
|
|
|
4142
4280
|
lstatSync as lstatSync8,
|
|
4143
4281
|
mkdirSync as mkdirSync8,
|
|
4144
4282
|
readFileSync as readFileSync17,
|
|
4145
|
-
readdirSync as
|
|
4283
|
+
readdirSync as readdirSync10,
|
|
4146
4284
|
realpathSync as realpathSync4,
|
|
4147
4285
|
statSync as statSync5,
|
|
4148
4286
|
writeFileSync as writeFileSync7
|
|
4149
4287
|
} from "node:fs";
|
|
4150
4288
|
import { homedir as homedir4 } from "node:os";
|
|
4151
|
-
import { dirname as
|
|
4289
|
+
import { dirname as dirname14, join as join24, relative as relative8, resolve as resolve5, sep } from "node:path";
|
|
4152
4290
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
4153
4291
|
var CURRENT_ROUTER_VERSION = "1.1";
|
|
4154
4292
|
function inspectPortfolioAiHealth(options) {
|
|
@@ -4157,21 +4295,30 @@ function inspectPortfolioAiHealth(options) {
|
|
|
4157
4295
|
if (options.targetId && options.targetId !== "all" && endpoints.length === 0) {
|
|
4158
4296
|
throw new Error(`Unknown portfolio target: ${options.targetId}`);
|
|
4159
4297
|
}
|
|
4160
|
-
const secretsRoot = options.secretsRoot ?? join24(
|
|
4298
|
+
const secretsRoot = options.secretsRoot ?? join24(
|
|
4299
|
+
dirname14(
|
|
4300
|
+
options.manifest.controlPlane?.path ?? allEndpoints[0]?.endpoint.path ?? process.cwd()
|
|
4301
|
+
),
|
|
4302
|
+
".secrets"
|
|
4303
|
+
);
|
|
4161
4304
|
const homeDir = options.homeDir ?? process.env.HOME ?? homedir4();
|
|
4162
4305
|
const grokVersion = commandVersion("grok");
|
|
4163
4306
|
const executionEngineRoot = options.manifest.executionEngine?.path;
|
|
4164
4307
|
const skillRegistry = inspectSkillRegistry(executionEngineRoot);
|
|
4165
|
-
const expectedPackageVersion = packageVersion(
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
|
|
4173
|
-
|
|
4174
|
-
|
|
4308
|
+
const expectedPackageVersion = packageVersion(
|
|
4309
|
+
join24(executionEngineRoot ?? "", "packages/pro-gov/package.json")
|
|
4310
|
+
);
|
|
4311
|
+
const repositories = endpoints.map(
|
|
4312
|
+
({ endpoint, role }) => inspectRepository(
|
|
4313
|
+
endpoint,
|
|
4314
|
+
role,
|
|
4315
|
+
secretsRoot,
|
|
4316
|
+
homeDir,
|
|
4317
|
+
expectedPackageVersion,
|
|
4318
|
+
options.manifest.technologyGovernance,
|
|
4319
|
+
grokVersion
|
|
4320
|
+
)
|
|
4321
|
+
);
|
|
4175
4322
|
const summary = { healthy: 0, attention: 0, unhealthy: 0 };
|
|
4176
4323
|
for (const repository of repositories) summary[repository.status] += 1;
|
|
4177
4324
|
return {
|
|
@@ -4196,6 +4343,8 @@ function inspectPortfolioAiHealth(options) {
|
|
|
4196
4343
|
technologyGovernance: {
|
|
4197
4344
|
strategySource: options.manifest.technologyGovernance?.strategySource,
|
|
4198
4345
|
versionPolicy: options.manifest.technologyGovernance?.versionPolicy,
|
|
4346
|
+
catalog: options.manifest.technologyGovernance?.technologies ?? [],
|
|
4347
|
+
matrix: buildTechnologyMatrix(options.manifest.technologyGovernance, repositories),
|
|
4199
4348
|
projectTypes: options.manifest.technologyGovernance?.projectTypes.length ?? 0,
|
|
4200
4349
|
technologies: options.manifest.technologyGovernance?.technologies.length ?? 0
|
|
4201
4350
|
},
|
|
@@ -4205,7 +4354,8 @@ function inspectPortfolioAiHealth(options) {
|
|
|
4205
4354
|
}
|
|
4206
4355
|
function mergePortfolioAiHealthReport(existing, latest, allRepositoryIds) {
|
|
4207
4356
|
const repositoriesById = /* @__PURE__ */ new Map();
|
|
4208
|
-
for (const repository of existing?.repositories ?? [])
|
|
4357
|
+
for (const repository of existing?.repositories ?? [])
|
|
4358
|
+
repositoriesById.set(repository.id, repository);
|
|
4209
4359
|
for (const repository of latest.repositories) repositoriesById.set(repository.id, repository);
|
|
4210
4360
|
const repositories = allRepositoryIds.map((id) => repositoriesById.get(id)).filter((repository) => repository !== void 0);
|
|
4211
4361
|
const coveredIds = /* @__PURE__ */ new Set([
|
|
@@ -4216,8 +4366,20 @@ function mergePortfolioAiHealthReport(existing, latest, allRepositoryIds) {
|
|
|
4216
4366
|
const summary = { healthy: 0, attention: 0, unhealthy: 0 };
|
|
4217
4367
|
for (const repository of repositories) summary[repository.status] += 1;
|
|
4218
4368
|
const complete = coveredRepositoryIds.length === allRepositoryIds.length;
|
|
4369
|
+
const technologyGovernance = (latest.technologyGovernance.catalog?.length ?? 0) > 0 ? {
|
|
4370
|
+
...latest.technologyGovernance,
|
|
4371
|
+
matrix: buildTechnologyMatrix(
|
|
4372
|
+
{
|
|
4373
|
+
technologies: latest.technologyGovernance.catalog,
|
|
4374
|
+
projectTypes: [],
|
|
4375
|
+
versionPolicy: latest.technologyGovernance.versionPolicy
|
|
4376
|
+
},
|
|
4377
|
+
repositories
|
|
4378
|
+
)
|
|
4379
|
+
} : latest.technologyGovernance;
|
|
4219
4380
|
return {
|
|
4220
4381
|
...latest,
|
|
4382
|
+
technologyGovernance,
|
|
4221
4383
|
repositories,
|
|
4222
4384
|
summary,
|
|
4223
4385
|
coverage: {
|
|
@@ -4240,14 +4402,19 @@ function writePortfolioAiHealthReport(report, outDir) {
|
|
|
4240
4402
|
const htmlPath = join24(outDir, "index.html");
|
|
4241
4403
|
writeFileSync7(jsonPath, `${JSON.stringify(report, null, 2)}
|
|
4242
4404
|
`);
|
|
4243
|
-
writeFileSync7(
|
|
4244
|
-
|
|
4405
|
+
writeFileSync7(
|
|
4406
|
+
join24(outDir, "data.js"),
|
|
4407
|
+
`window.__PORTFOLIO_AI_HEALTH__ = ${safeJavaScriptJson(report)};
|
|
4408
|
+
`
|
|
4409
|
+
);
|
|
4245
4410
|
return { jsonPath, htmlPath };
|
|
4246
4411
|
}
|
|
4247
4412
|
function collectEndpoints(manifest) {
|
|
4248
4413
|
const result = [];
|
|
4249
|
-
if (manifest.controlPlane)
|
|
4250
|
-
|
|
4414
|
+
if (manifest.controlPlane)
|
|
4415
|
+
result.push({ endpoint: manifest.controlPlane, role: "control-plane" });
|
|
4416
|
+
if (manifest.executionEngine)
|
|
4417
|
+
result.push({ endpoint: manifest.executionEngine, role: "execution-engine" });
|
|
4251
4418
|
for (const target of manifest.targets) result.push({ endpoint: target, role: "target" });
|
|
4252
4419
|
const seen = /* @__PURE__ */ new Set();
|
|
4253
4420
|
return result.filter(({ endpoint }) => {
|
|
@@ -4274,56 +4441,156 @@ function inspectRepository(endpoint, role, secretsRoot, homeDir, expectedPackage
|
|
|
4274
4441
|
grokEffective: grokInspection.effectiveMcp,
|
|
4275
4442
|
grokInspection: grokInspection.inspection
|
|
4276
4443
|
};
|
|
4277
|
-
const secrets = inspectRepositorySecrets(
|
|
4444
|
+
const secrets = inspectRepositorySecrets(
|
|
4445
|
+
root,
|
|
4446
|
+
endpoint.id,
|
|
4447
|
+
secretsRoot,
|
|
4448
|
+
git.isRepository,
|
|
4449
|
+
endpoint.environmentPolicy
|
|
4450
|
+
);
|
|
4278
4451
|
const projectModel = inspectProjectModel(root, endpoint, technologyGovernance);
|
|
4279
|
-
const versions = inspectVersionPolicy(
|
|
4452
|
+
const versions = inspectVersionPolicy(
|
|
4453
|
+
root,
|
|
4454
|
+
technologyGovernance?.versionPolicy,
|
|
4455
|
+
endpoint.projectType
|
|
4456
|
+
);
|
|
4280
4457
|
const verification = inspectProjectVerification(root);
|
|
4281
4458
|
const redundancy = inspectProjectRedundancy(root, { homeDir });
|
|
4282
4459
|
const recommendations = [];
|
|
4283
4460
|
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");
|
|
4284
|
-
if (git.unmergedBranches.length > 0)
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
if (
|
|
4461
|
+
if (git.unmergedBranches.length > 0)
|
|
4462
|
+
recommendations.push(
|
|
4463
|
+
`\u6709 ${git.unmergedBranches.length} \u6761\u5206\u652F\u5C1A\u672A\u5408\u5165\u5F53\u524D HEAD\uFF1A${git.unmergedBranches.join(", ")}\u3002`
|
|
4464
|
+
);
|
|
4465
|
+
else if (git.branches.length > 1)
|
|
4466
|
+
recommendations.push(
|
|
4467
|
+
`\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`
|
|
4468
|
+
);
|
|
4469
|
+
if (git.worktrees.length > 1)
|
|
4470
|
+
recommendations.push(
|
|
4471
|
+
`\u6709 ${git.worktrees.length} \u4E2A worktree\uFF1B\u5B8C\u6210\u5E76\u5408\u5E76\u540E\u518D\u62C6\u9664\u4E34\u65F6 worktree\u3002`
|
|
4472
|
+
);
|
|
4473
|
+
if (git.dirtyPaths.length > 0)
|
|
4474
|
+
recommendations.push(
|
|
4475
|
+
`\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`
|
|
4476
|
+
);
|
|
4477
|
+
if ((git.ahead ?? 0) > 0)
|
|
4478
|
+
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`);
|
|
4289
4479
|
if (entries.agents === "missing") recommendations.push("\u7F3A\u5C11 AGENTS.md\uFF1B\u65E0\u6CD5\u53D1\u73B0\u9879\u76EE\u5165\u53E3\u89C4\u5219\u3002");
|
|
4290
|
-
if (entries.agents === "custom")
|
|
4291
|
-
|
|
4292
|
-
|
|
4293
|
-
|
|
4294
|
-
if (entries.claude === "
|
|
4295
|
-
|
|
4296
|
-
if (entries.
|
|
4297
|
-
|
|
4298
|
-
if (
|
|
4299
|
-
|
|
4300
|
-
|
|
4301
|
-
|
|
4302
|
-
if (
|
|
4303
|
-
|
|
4480
|
+
if (entries.agents === "custom")
|
|
4481
|
+
recommendations.push(
|
|
4482
|
+
"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"
|
|
4483
|
+
);
|
|
4484
|
+
if (entries.claude === "missing")
|
|
4485
|
+
recommendations.push("\u7F3A\u5C11 CLAUDE.md\uFF1B\u5EFA\u8BAE\u94FE\u63A5\u5230 AGENTS.md\uFF0C\u907F\u514D\u4E24\u4EFD\u5165\u53E3\u6F02\u79FB\u3002");
|
|
4486
|
+
if (entries.claude === "thin-adapter")
|
|
4487
|
+
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");
|
|
4488
|
+
if (entries.claude === "custom")
|
|
4489
|
+
recommendations.push(
|
|
4490
|
+
"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"
|
|
4491
|
+
);
|
|
4492
|
+
if (entries.claude === "dangling-symlink")
|
|
4493
|
+
recommendations.push("CLAUDE.md \u662F\u65AD\u5F00\u7684\u94FE\u63A5\uFF1B\u9700\u8981\u91CD\u65B0\u6307\u5411 AGENTS.md\u3002");
|
|
4494
|
+
if (entries.gemini === "custom")
|
|
4495
|
+
recommendations.push("GEMINI.md \u662F\u72EC\u7ACB\u5165\u53E3\uFF1B\u82E5\u6CA1\u6709 Gemini \u4E13\u5C5E\u5DEE\u5F02\uFF0C\u5EFA\u8BAE\u94FE\u63A5\u5230 AGENTS.md\u3002");
|
|
4496
|
+
if (entries.gemini === "dangling-symlink")
|
|
4497
|
+
recommendations.push("GEMINI.md \u662F\u65AD\u5F00\u7684\u94FE\u63A5\uFF1B\u9700\u8981\u91CD\u65B0\u6307\u5411 AGENTS.md\u3002");
|
|
4498
|
+
if (skills.canonical.some((skill) => skill.kind === "dangling-symlink"))
|
|
4499
|
+
recommendations.push("`.agents/skills` \u4E2D\u5B58\u5728\u65AD\u5F00\u7684\u6280\u80FD\u94FE\u63A5\u3002");
|
|
4500
|
+
if (skills.claudeCompatibility === "duplicate-directory")
|
|
4501
|
+
recommendations.push(
|
|
4502
|
+
"`.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"
|
|
4503
|
+
);
|
|
4504
|
+
if (skills.claudeCompatibility === "dangling-symlink")
|
|
4505
|
+
recommendations.push("`.claude/skills` \u662F\u65AD\u5F00\u7684\u94FE\u63A5\u3002");
|
|
4506
|
+
if (!hostSsot.claudeEntry.compliant)
|
|
4507
|
+
recommendations.push(
|
|
4508
|
+
`CLAUDE.md \u4E0D\u662F\u89C4\u8303\u7684\u76F8\u5BF9\u94FE\u63A5 AGENTS.md\uFF08${hostSsot.claudeEntry.status}\uFF09\u3002`
|
|
4509
|
+
);
|
|
4510
|
+
if (!hostSsot.claudeSkills.compliant)
|
|
4511
|
+
recommendations.push(
|
|
4512
|
+
`.claude/skills \u4E0D\u662F\u89C4\u8303\u7684\u76F8\u5BF9\u94FE\u63A5 ../.agents/skills\uFF08${hostSsot.claudeSkills.status}\uFF09\u3002`
|
|
4513
|
+
);
|
|
4514
|
+
if (hostSsot.canonicalSkills.status !== "directory")
|
|
4515
|
+
recommendations.push(
|
|
4516
|
+
`\u7F3A\u5C11\u89C4\u8303\u7684 .agents/skills \u6280\u80FD\u76EE\u5F55\uFF08${hostSsot.canonicalSkills.status}\uFF09\u3002`
|
|
4517
|
+
);
|
|
4518
|
+
if (hasWorkflowReminderHooks(hooks))
|
|
4519
|
+
recommendations.push(
|
|
4520
|
+
"\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"
|
|
4521
|
+
);
|
|
4304
4522
|
const liveEnv = secrets.repositoryEnvFiles.filter((file) => !file.template && !file.fixture);
|
|
4305
4523
|
const envNeedsCentralReview = liveEnv.filter((file) => !file.localOnly);
|
|
4306
|
-
if (liveEnv.some((file) => file.tracked))
|
|
4307
|
-
|
|
4308
|
-
|
|
4309
|
-
|
|
4310
|
-
|
|
4311
|
-
|
|
4312
|
-
if (
|
|
4313
|
-
|
|
4314
|
-
|
|
4315
|
-
|
|
4316
|
-
|
|
4317
|
-
|
|
4318
|
-
if (
|
|
4319
|
-
|
|
4320
|
-
|
|
4524
|
+
if (liveEnv.some((file) => file.tracked))
|
|
4525
|
+
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");
|
|
4526
|
+
if (envNeedsCentralReview.length > 0 && secrets.centralDirectory === "absent")
|
|
4527
|
+
recommendations.push(
|
|
4528
|
+
"\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"
|
|
4529
|
+
);
|
|
4530
|
+
else if (envNeedsCentralReview.some((file) => !file.centralized))
|
|
4531
|
+
recommendations.push(
|
|
4532
|
+
"\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"
|
|
4533
|
+
);
|
|
4534
|
+
if (hasUnsafeCentralSecretPermissions(secrets))
|
|
4535
|
+
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");
|
|
4536
|
+
if (!docs.packages.aligned)
|
|
4537
|
+
recommendations.push(
|
|
4538
|
+
`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`
|
|
4539
|
+
);
|
|
4540
|
+
if (!docs.routerAligned)
|
|
4541
|
+
recommendations.push(
|
|
4542
|
+
`PGS Router \u7248\u672C\u4E3A ${docs.routerVersion ? `v${docs.routerVersion}` : "\u672A\u8BC6\u522B"}\uFF0C\u5E94\u540C\u6B65\u5230 v${docs.expectedRouterVersion}\u3002`
|
|
4543
|
+
);
|
|
4544
|
+
if (role === "target" && !docs.manifest)
|
|
4545
|
+
recommendations.push("\u7F3A\u5C11 docs/governance/MANIFEST.yml\uFF1B\u6587\u6863\u6E05\u5355\u65E0\u6CD5\u8BC1\u660E\u5DF2\u540C\u6B65\u3002");
|
|
4546
|
+
if (technologyGovernance && !projectModel.projectType)
|
|
4547
|
+
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");
|
|
4548
|
+
const missingBaseline = projectModel.baseline.filter(
|
|
4549
|
+
(technology) => !technology.detected && !hasBaselineException(projectModel, technology.id)
|
|
4550
|
+
);
|
|
4551
|
+
if (missingBaseline.length > 0)
|
|
4552
|
+
recommendations.push(
|
|
4553
|
+
`\u6280\u672F\u57FA\u7EBF\u7F3A\u5C11\u53EF\u9A8C\u8BC1\u4FE1\u53F7\uFF1A${missingBaseline.map((technology) => technology.label).join("\u3001")}\u3002`
|
|
4554
|
+
);
|
|
4555
|
+
const missingSelected = projectModel.optionalCapabilities.filter(
|
|
4556
|
+
(technology) => technology.selected && !technology.detected
|
|
4557
|
+
);
|
|
4558
|
+
if (missingSelected.length > 0)
|
|
4559
|
+
recommendations.push(
|
|
4560
|
+
`\u5DF2\u9009\u80FD\u529B\u7F3A\u5C11\u53EF\u9A8C\u8BC1\u4FE1\u53F7\uFF1A${missingSelected.map((technology) => technology.label).join("\u3001")}\u3002`
|
|
4561
|
+
);
|
|
4562
|
+
if (versions.status === "attention")
|
|
4563
|
+
recommendations.push(
|
|
4564
|
+
`\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`
|
|
4565
|
+
);
|
|
4566
|
+
if (verification.status === "attention")
|
|
4567
|
+
recommendations.push(
|
|
4568
|
+
`\u9A8C\u8BC1\u811A\u672C\u7F3A\u5931\uFF1A${verification.missing.join("\u3001")}\uFF1BPGS \u8981\u6C42 typecheck\u3001lint\u3001format:check\u3001verify \u53EF\u53D1\u73B0\u3002`
|
|
4569
|
+
);
|
|
4570
|
+
if (redundancy.status === "attention")
|
|
4571
|
+
recommendations.push(
|
|
4572
|
+
"\u53D1\u73B0\u65E7 AI \u76EE\u5F55\u6216\u5927\u578B Playwright \u7F13\u5B58\uFF1B\u4EC5\u63D0\u4F9B\u8BC1\u636E\uFF0C\u786E\u8BA4\u5F52\u5C5E\u540E\u518D\u7531\u4EBA\u5DE5\u6E05\u7406\u3002"
|
|
4573
|
+
);
|
|
4321
4574
|
return {
|
|
4322
4575
|
id: endpoint.id,
|
|
4323
4576
|
role,
|
|
4324
4577
|
path: root,
|
|
4325
4578
|
profile: "profile" in endpoint ? endpoint.profile : void 0,
|
|
4326
|
-
status: deriveStatus(
|
|
4579
|
+
status: deriveStatus(
|
|
4580
|
+
role,
|
|
4581
|
+
entries,
|
|
4582
|
+
git,
|
|
4583
|
+
hooks,
|
|
4584
|
+
skills,
|
|
4585
|
+
hostSsot,
|
|
4586
|
+
secrets,
|
|
4587
|
+
docs,
|
|
4588
|
+
projectModel,
|
|
4589
|
+
Boolean(technologyGovernance),
|
|
4590
|
+
versions,
|
|
4591
|
+
verification,
|
|
4592
|
+
redundancy
|
|
4593
|
+
),
|
|
4327
4594
|
recommendations,
|
|
4328
4595
|
git,
|
|
4329
4596
|
entries,
|
|
@@ -4339,22 +4606,161 @@ function inspectRepository(endpoint, role, secretsRoot, homeDir, expectedPackage
|
|
|
4339
4606
|
redundancy
|
|
4340
4607
|
};
|
|
4341
4608
|
}
|
|
4609
|
+
function buildTechnologyMatrix(governance, repositories) {
|
|
4610
|
+
if (!governance) return [];
|
|
4611
|
+
const policy = governance.versionPolicy;
|
|
4612
|
+
return governance.technologies.map((technology) => {
|
|
4613
|
+
const projects = repositories.flatMap((repository) => {
|
|
4614
|
+
const packageManifests = collectPackageManifests(repository.path);
|
|
4615
|
+
const packageSignals = (technology.packages ?? []).map((name) => {
|
|
4616
|
+
const requirement = policy?.packages.find((item) => item.name === name);
|
|
4617
|
+
return packageManifests.map((manifest) => {
|
|
4618
|
+
const declared = findDeclaredVersion(manifest.packageJson, name);
|
|
4619
|
+
return declared === void 0 ? void 0 : {
|
|
4620
|
+
name,
|
|
4621
|
+
source: manifest.path,
|
|
4622
|
+
declared,
|
|
4623
|
+
installed: readInstalledVersion(repository.path, name, manifest.directory),
|
|
4624
|
+
expected: requirement?.version
|
|
4625
|
+
};
|
|
4626
|
+
}).filter((item) => item !== void 0);
|
|
4627
|
+
}).flat();
|
|
4628
|
+
const fileSignal = (technology.files ?? []).some(
|
|
4629
|
+
(path) => hasUsableTechnologyFile(join24(repository.path, path)) || packageManifests.some(
|
|
4630
|
+
(manifest) => hasUsableTechnologyFile(join24(manifest.directory, path))
|
|
4631
|
+
)
|
|
4632
|
+
);
|
|
4633
|
+
const modelSignal = [
|
|
4634
|
+
...repository.projectModel.baseline,
|
|
4635
|
+
...repository.projectModel.optionalCapabilities
|
|
4636
|
+
].find((item) => item.id === technology.id)?.detected ?? false;
|
|
4637
|
+
const runtimeRequirement2 = policy?.runtimes?.find(
|
|
4638
|
+
(item) => item.name === technology.id || technology.id === "deno-edge" && item.name === "deno"
|
|
4639
|
+
);
|
|
4640
|
+
const runtimeEvidence = runtimeRequirement2 ? repository.versions.runtimes.find((item) => item.name === runtimeRequirement2.name) : void 0;
|
|
4641
|
+
const uses = modelSignal || fileSignal || packageSignals.length > 0;
|
|
4642
|
+
if (!uses) return [];
|
|
4643
|
+
const packageStatuses = packageSignals.map(
|
|
4644
|
+
(item) => item.expected === void 0 ? "unversioned" : item.declared !== item.expected ? "drift" : item.installed === void 0 ? "unknown" : item.installed !== item.expected ? "drift" : "aligned"
|
|
4645
|
+
);
|
|
4646
|
+
const statuses = runtimeEvidence ? [
|
|
4647
|
+
...packageStatuses,
|
|
4648
|
+
runtimeEvidence.status === "compliant" ? "aligned" : runtimeEvidence.status === "not-applicable" ? "unknown" : "drift"
|
|
4649
|
+
] : packageStatuses;
|
|
4650
|
+
const status2 = statuses.includes("drift") ? "drift" : statuses.includes("unversioned") ? "unversioned" : statuses.includes("unknown") || statuses.length === 0 ? "unknown" : "aligned";
|
|
4651
|
+
const values = {
|
|
4652
|
+
sources: [...new Set(packageSignals.map((item) => item.source))],
|
|
4653
|
+
packages: [...new Set(packageSignals.map((item) => item.name))],
|
|
4654
|
+
declared: [
|
|
4655
|
+
...new Set(
|
|
4656
|
+
packageSignals.flatMap(
|
|
4657
|
+
(item) => item.declared ? [`${item.name}@${item.declared}`] : []
|
|
4658
|
+
)
|
|
4659
|
+
)
|
|
4660
|
+
],
|
|
4661
|
+
installed: [
|
|
4662
|
+
...new Set(
|
|
4663
|
+
packageSignals.flatMap(
|
|
4664
|
+
(item) => item.installed ? [`${item.name}@${item.installed}`] : []
|
|
4665
|
+
)
|
|
4666
|
+
)
|
|
4667
|
+
],
|
|
4668
|
+
expected: [
|
|
4669
|
+
...new Set(
|
|
4670
|
+
packageSignals.flatMap(
|
|
4671
|
+
(item) => item.expected ? [`${item.name}@${item.expected}`] : []
|
|
4672
|
+
)
|
|
4673
|
+
)
|
|
4674
|
+
]
|
|
4675
|
+
};
|
|
4676
|
+
if (runtimeEvidence && runtimeRequirement2) {
|
|
4677
|
+
values.expected.push(`${runtimeRequirement2.name}@${runtimeRequirement2.version}`);
|
|
4678
|
+
if (runtimeEvidence.actual)
|
|
4679
|
+
values.installed.push(`${runtimeRequirement2.name}@${runtimeEvidence.actual}`);
|
|
4680
|
+
}
|
|
4681
|
+
return [
|
|
4682
|
+
{
|
|
4683
|
+
repositoryId: repository.id,
|
|
4684
|
+
sources: values.sources,
|
|
4685
|
+
packages: values.packages,
|
|
4686
|
+
declared: values.declared.length ? values.declared.join(" \xB7 ") : void 0,
|
|
4687
|
+
installed: values.installed.length ? values.installed.join(" \xB7 ") : void 0,
|
|
4688
|
+
expected: values.expected.length ? values.expected.join(" \xB7 ") : void 0,
|
|
4689
|
+
status: status2
|
|
4690
|
+
}
|
|
4691
|
+
];
|
|
4692
|
+
});
|
|
4693
|
+
const expected = /* @__PURE__ */ new Set();
|
|
4694
|
+
for (const requirement of policy?.packages ?? []) {
|
|
4695
|
+
if ((technology.packages ?? []).includes(requirement.name))
|
|
4696
|
+
expected.add(`${requirement.name}@${requirement.version}`);
|
|
4697
|
+
}
|
|
4698
|
+
const runtimeRequirement = policy?.runtimes?.find(
|
|
4699
|
+
(item) => item.name === technology.id || technology.id === "deno-edge" && item.name === "deno"
|
|
4700
|
+
);
|
|
4701
|
+
if (runtimeRequirement)
|
|
4702
|
+
expected.add(`${runtimeRequirement.name}@${runtimeRequirement.version}`);
|
|
4703
|
+
for (const project of projects) {
|
|
4704
|
+
for (const value of project.expected?.split(" \xB7 ") ?? []) expected.add(value);
|
|
4705
|
+
}
|
|
4706
|
+
const status = projects.some(
|
|
4707
|
+
(project) => project.status === "drift"
|
|
4708
|
+
) ? "drift" : projects.some((project) => project.status === "unversioned") ? "unversioned" : projects.some((project) => project.status === "unknown") ? "unknown" : projects.length ? "aligned" : "not-applicable";
|
|
4709
|
+
return {
|
|
4710
|
+
id: technology.id,
|
|
4711
|
+
label: technology.label,
|
|
4712
|
+
packages: technology.packages ?? [],
|
|
4713
|
+
expected: [...expected],
|
|
4714
|
+
projectCount: projects.length,
|
|
4715
|
+
status,
|
|
4716
|
+
projects
|
|
4717
|
+
};
|
|
4718
|
+
}).filter((technology) => technology.projectCount > 0);
|
|
4719
|
+
}
|
|
4720
|
+
function hasUsableTechnologyFile(path) {
|
|
4721
|
+
if (!existsSync23(path)) return false;
|
|
4722
|
+
try {
|
|
4723
|
+
const info = statSync5(path);
|
|
4724
|
+
if (info.isFile()) return true;
|
|
4725
|
+
if (!info.isDirectory()) return false;
|
|
4726
|
+
return readdirSync10(path, { withFileTypes: true }).some((entry) => {
|
|
4727
|
+
if (entry.name.startsWith(".")) return false;
|
|
4728
|
+
const child = join24(path, entry.name);
|
|
4729
|
+
if (entry.isDirectory()) return hasUsableTechnologyFile(child);
|
|
4730
|
+
return entry.name.toLowerCase() !== "readme.md";
|
|
4731
|
+
});
|
|
4732
|
+
} catch {
|
|
4733
|
+
return false;
|
|
4734
|
+
}
|
|
4735
|
+
}
|
|
4342
4736
|
function deriveStatus(role, entries, git, hooks, skills, hostSsot, secrets, docs, projectModel, technologyGovernanceConfigured, versions, verification, redundancy) {
|
|
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))
|
|
4344
|
-
|
|
4345
|
-
const
|
|
4737
|
+
if (!git.isRepository || entries.agents === "missing" || entries.claude === "dangling-symlink" || entries.gemini === "dangling-symlink" || skills.canonical.some((item) => item.kind === "dangling-symlink") || secrets.repositoryEnvFiles.some((file) => file.tracked && !file.template && !file.fixture) || hasUnsafeCentralSecretPermissions(secrets))
|
|
4738
|
+
return "unhealthy";
|
|
4739
|
+
const missingBaseline = projectModel.baseline.some(
|
|
4740
|
+
(technology) => !technology.detected && !hasBaselineException(projectModel, technology.id)
|
|
4741
|
+
);
|
|
4742
|
+
const missingSelected = projectModel.optionalCapabilities.some(
|
|
4743
|
+
(technology) => technology.selected && !technology.detected
|
|
4744
|
+
);
|
|
4346
4745
|
const liveEnv = secrets.repositoryEnvFiles.filter((file) => !file.template && !file.fixture);
|
|
4347
|
-
const secretMaterializationNeedsReview = liveEnv.some(
|
|
4746
|
+
const secretMaterializationNeedsReview = liveEnv.some(
|
|
4747
|
+
(file) => !file.centralized && !file.localOnly
|
|
4748
|
+
);
|
|
4348
4749
|
const packageVersionNeedsReview = docs.packages.expected !== void 0 && !docs.packages.aligned;
|
|
4349
4750
|
const routerVersionNeedsReview = !docs.routerAligned;
|
|
4350
4751
|
const targetManifestMissing = role === "target" && !docs.manifest;
|
|
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)
|
|
4752
|
+
if (entries.agents !== "pgs-router" || entries.claude !== "agents-symlink" || hasWorkflowReminderHooks(hooks) || skills.claudeCompatibility === "duplicate-directory" || skills.claudeCompatibility === "dangling-symlink" || !hostSsot.compliant || git.branches.length > 1 || git.worktrees.length > 1 || git.dirtyPaths.length > 0 || (git.ahead ?? 0) > 0 || secretMaterializationNeedsReview || technologyGovernanceConfigured && !projectModel.projectType || missingBaseline || missingSelected || packageVersionNeedsReview || routerVersionNeedsReview || targetManifestMissing || versions.status === "attention" || verification.status === "attention" || redundancy.legacyDirectories.length > 0)
|
|
4753
|
+
return "attention";
|
|
4352
4754
|
return "healthy";
|
|
4353
4755
|
}
|
|
4354
4756
|
function inspectProjectModel(root, endpoint, governance) {
|
|
4355
|
-
const projectType = governance?.projectTypes.find(
|
|
4757
|
+
const projectType = governance?.projectTypes.find(
|
|
4758
|
+
(candidate) => candidate.id === endpoint.projectType
|
|
4759
|
+
);
|
|
4356
4760
|
const packages = collectPackageNames(root);
|
|
4357
|
-
const technologyById = new Map(
|
|
4761
|
+
const technologyById = new Map(
|
|
4762
|
+
(governance?.technologies ?? []).map((technology) => [technology.id, technology])
|
|
4763
|
+
);
|
|
4358
4764
|
const detection = (id) => {
|
|
4359
4765
|
const technology = technologyById.get(id);
|
|
4360
4766
|
const packageMatch = technology?.packages?.some((name) => packages.has(name)) ?? false;
|
|
@@ -4366,7 +4772,10 @@ function inspectProjectModel(root, endpoint, governance) {
|
|
|
4366
4772
|
projectType: projectType ? { id: projectType.id, title: projectType.title, summary: projectType.summary } : void 0,
|
|
4367
4773
|
boundary: endpoint.boundary,
|
|
4368
4774
|
baseline: (projectType?.baseline ?? []).map(detection),
|
|
4369
|
-
optionalCapabilities: (projectType?.optionalCapabilities ?? []).map((id) => ({
|
|
4775
|
+
optionalCapabilities: (projectType?.optionalCapabilities ?? []).map((id) => ({
|
|
4776
|
+
...detection(id),
|
|
4777
|
+
selected: selected.has(id)
|
|
4778
|
+
})),
|
|
4370
4779
|
exceptions: endpoint.technologyExceptions ?? []
|
|
4371
4780
|
};
|
|
4372
4781
|
}
|
|
@@ -4377,16 +4786,14 @@ function hasBaselineException(projectModel, technologyId) {
|
|
|
4377
4786
|
}
|
|
4378
4787
|
function collectPackageNames(root) {
|
|
4379
4788
|
const names = /* @__PURE__ */ new Set();
|
|
4380
|
-
|
|
4381
|
-
try {
|
|
4382
|
-
files = splitLines(execFileSync("git", ["ls-files", "*package.json"], { cwd: root, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }));
|
|
4383
|
-
} catch {
|
|
4384
|
-
if (existsSync23(join24(root, "package.json"))) files = ["package.json"];
|
|
4385
|
-
}
|
|
4386
|
-
for (const file of files) {
|
|
4387
|
-
const packageJson = readJson4(join24(root, file));
|
|
4789
|
+
for (const { packageJson } of collectPackageManifests(root)) {
|
|
4388
4790
|
if (!isRecord3(packageJson)) continue;
|
|
4389
|
-
for (const section of [
|
|
4791
|
+
for (const section of [
|
|
4792
|
+
"dependencies",
|
|
4793
|
+
"devDependencies",
|
|
4794
|
+
"peerDependencies",
|
|
4795
|
+
"optionalDependencies"
|
|
4796
|
+
]) {
|
|
4390
4797
|
const dependencies = packageJson[section];
|
|
4391
4798
|
if (!isRecord3(dependencies)) continue;
|
|
4392
4799
|
for (const name of Object.keys(dependencies)) names.add(name);
|
|
@@ -4395,17 +4802,31 @@ function collectPackageNames(root) {
|
|
|
4395
4802
|
return names;
|
|
4396
4803
|
}
|
|
4397
4804
|
function hasWorkflowReminderHooks(hooks) {
|
|
4398
|
-
return hooks.some(
|
|
4805
|
+
return hooks.some(
|
|
4806
|
+
(hook) => hook.events.some((event) => event.name === "Stop" || event.name === "SubagentStop")
|
|
4807
|
+
);
|
|
4399
4808
|
}
|
|
4400
4809
|
function inspectGit2(root) {
|
|
4401
4810
|
const git = (...args) => {
|
|
4402
4811
|
try {
|
|
4403
|
-
return execFileSync("git", args, {
|
|
4812
|
+
return execFileSync("git", args, {
|
|
4813
|
+
cwd: root,
|
|
4814
|
+
encoding: "utf8",
|
|
4815
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
4816
|
+
}).trimEnd();
|
|
4404
4817
|
} catch {
|
|
4405
4818
|
return void 0;
|
|
4406
4819
|
}
|
|
4407
4820
|
};
|
|
4408
|
-
if (git("rev-parse", "--is-inside-work-tree") !== "true")
|
|
4821
|
+
if (git("rev-parse", "--is-inside-work-tree") !== "true")
|
|
4822
|
+
return {
|
|
4823
|
+
isRepository: false,
|
|
4824
|
+
branches: [],
|
|
4825
|
+
mergedBranches: [],
|
|
4826
|
+
unmergedBranches: [],
|
|
4827
|
+
worktrees: [],
|
|
4828
|
+
dirtyPaths: []
|
|
4829
|
+
};
|
|
4409
4830
|
const status = git("status", "--porcelain=v1", "-uall") ?? "";
|
|
4410
4831
|
const worktreeText = git("worktree", "list", "--porcelain") ?? "";
|
|
4411
4832
|
const upstreamCounts = git("rev-list", "--left-right", "--count", "@{upstream}...HEAD")?.split(/\s+/).map(Number);
|
|
@@ -4413,8 +4834,12 @@ function inspectGit2(root) {
|
|
|
4413
4834
|
isRepository: true,
|
|
4414
4835
|
branch: git("branch", "--show-current") || "(detached)",
|
|
4415
4836
|
branches: splitLines(git("for-each-ref", "--format=%(refname:short)", "refs/heads")),
|
|
4416
|
-
mergedBranches: splitLines(
|
|
4417
|
-
|
|
4837
|
+
mergedBranches: splitLines(
|
|
4838
|
+
git("for-each-ref", "--merged=HEAD", "--format=%(refname:short)", "refs/heads")
|
|
4839
|
+
),
|
|
4840
|
+
unmergedBranches: splitLines(
|
|
4841
|
+
git("for-each-ref", "--no-merged=HEAD", "--format=%(refname:short)", "refs/heads")
|
|
4842
|
+
),
|
|
4418
4843
|
worktrees: splitLines(worktreeText).filter((line) => line.startsWith("worktree ")).map((line) => line.slice(9)),
|
|
4419
4844
|
dirtyPaths: splitLines(status).map((line) => line.slice(3)),
|
|
4420
4845
|
behind: upstreamCounts?.[0],
|
|
@@ -4540,14 +4965,29 @@ function inspectRepositorySecrets(root, id, secretsRoot, isRepository, environme
|
|
|
4540
4965
|
repositoryEnvFiles: envFiles
|
|
4541
4966
|
};
|
|
4542
4967
|
}
|
|
4543
|
-
var SKIP_ENV_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
4968
|
+
var SKIP_ENV_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
4969
|
+
".git",
|
|
4970
|
+
".next",
|
|
4971
|
+
".nuxt",
|
|
4972
|
+
".output",
|
|
4973
|
+
".turbo",
|
|
4974
|
+
".vercel",
|
|
4975
|
+
".worktrees",
|
|
4976
|
+
"build",
|
|
4977
|
+
"coverage",
|
|
4978
|
+
"dist",
|
|
4979
|
+
"node_modules",
|
|
4980
|
+
"out",
|
|
4981
|
+
"target"
|
|
4982
|
+
]);
|
|
4544
4983
|
function collectEnvironmentFiles(root, current = root, depth = 0) {
|
|
4545
4984
|
if (depth > 5) return [];
|
|
4546
4985
|
const found = [];
|
|
4547
4986
|
try {
|
|
4548
|
-
for (const entry of
|
|
4987
|
+
for (const entry of readdirSync10(current, { withFileTypes: true })) {
|
|
4549
4988
|
if (entry.isDirectory()) {
|
|
4550
|
-
if (!SKIP_ENV_DIRECTORIES.has(entry.name))
|
|
4989
|
+
if (!SKIP_ENV_DIRECTORIES.has(entry.name))
|
|
4990
|
+
found.push(...collectEnvironmentFiles(root, join24(current, entry.name), depth + 1));
|
|
4551
4991
|
} else if (isEnvironmentFilename(entry.name) && !isProviderGeneratedEnvironmentFile(entry.name)) {
|
|
4552
4992
|
found.push(relative8(root, join24(current, entry.name)));
|
|
4553
4993
|
}
|
|
@@ -4561,7 +5001,7 @@ function collectCentralSecretFiles(root, current = root, depth = 0) {
|
|
|
4561
5001
|
if (depth > 3) return [];
|
|
4562
5002
|
const found = [];
|
|
4563
5003
|
try {
|
|
4564
|
-
for (const entry of
|
|
5004
|
+
for (const entry of readdirSync10(current, { withFileTypes: true })) {
|
|
4565
5005
|
const path = join24(current, entry.name);
|
|
4566
5006
|
if (entry.isDirectory()) found.push(...collectCentralSecretFiles(root, path, depth + 1));
|
|
4567
5007
|
else found.push({ path: relative8(root, path), mode: modeString(lstatSync8(path).mode) });
|
|
@@ -4647,17 +5087,25 @@ function inspectDevSpaceHealth(options) {
|
|
|
4647
5087
|
const authPath = join24(configDirectory, "auth.json");
|
|
4648
5088
|
const installedResult = run("devspace", ["--version"], 3e3);
|
|
4649
5089
|
const installedVersion = installedResult.ok ? installedResult.stdout.match(/\b\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?\b/)?.[0] : void 0;
|
|
4650
|
-
const latestResult = run(
|
|
5090
|
+
const latestResult = run(
|
|
5091
|
+
"npm",
|
|
5092
|
+
["view", "@waishnav/devspace", "version", "--registry=https://registry.npmjs.org/"],
|
|
5093
|
+
5e3
|
|
5094
|
+
);
|
|
4651
5095
|
const latestVersion = latestResult.ok ? latestResult.stdout.match(/\b\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?\b/)?.[0] : void 0;
|
|
4652
5096
|
const processResult = run("pgrep", ["-f", "devspace serve"], 3e3);
|
|
4653
5097
|
const pid = processResult.ok ? processResult.stdout.match(/\b\d+\b/)?.[0] : void 0;
|
|
4654
5098
|
const processEnvironment = pid ? run("ps", ["eww", "-p", pid, "-o", "command="], 3e3) : void 0;
|
|
4655
|
-
const processToolMode = processEnvironment?.ok ? processEnvironment.stdout.match(
|
|
5099
|
+
const processToolMode = processEnvironment?.ok ? processEnvironment.stdout.match(
|
|
5100
|
+
/(?:^|\s)DEVSPACE_TOOL_MODE=(minimal|full|codex)(?:\s|$)/
|
|
5101
|
+
)?.[1] : void 0;
|
|
4656
5102
|
const doctor = !installedResult.ok ? "unavailable" : run("devspace", ["doctor"], 1e4).ok ? "ok" : "failed";
|
|
4657
5103
|
const configValue = readJson4(configPath);
|
|
4658
5104
|
const configExists = isRecord3(configValue);
|
|
4659
5105
|
const allowedRoots = configExists && Array.isArray(configValue.allowedRoots) ? configValue.allowedRoots.filter((value) => typeof value === "string") : [];
|
|
4660
|
-
const portfolioCoverage = !configExists || allowedRoots.length === 0 ? "unknown" : options.repositoryPaths.every(
|
|
5106
|
+
const portfolioCoverage = !configExists || allowedRoots.length === 0 ? "unknown" : options.repositoryPaths.every(
|
|
5107
|
+
(repositoryPath) => allowedRoots.some((root) => isPathInside(repositoryPath, root))
|
|
5108
|
+
) ? "complete" : "partial";
|
|
4661
5109
|
const bind = configExists && typeof configValue.host === "string" ? isLoopbackHost(configValue.host) ? "loopback" : "non-loopback" : "unknown";
|
|
4662
5110
|
const directoryMode = existsSync23(configDirectory) ? modeString(statSync5(configDirectory).mode) : void 0;
|
|
4663
5111
|
const fileMode = existsSync23(configPath) ? modeString(statSync5(configPath).mode) : void 0;
|
|
@@ -4676,17 +5124,24 @@ function inspectDevSpaceHealth(options) {
|
|
|
4676
5124
|
if (!installedResult.ok) unhealthy("\u672C\u673A\u672A\u53D1\u73B0 DevSpace\uFF1B\u65E0\u6CD5\u4F7F\u7528\u5BBF\u4E3B\u5DE5\u4F5C\u533A\u670D\u52A1\u3002");
|
|
4677
5125
|
if (!configExists) unhealthy("\u7F3A\u5C11 ~/.devspace/config.json\u3002");
|
|
4678
5126
|
if (!existsSync23(authPath)) unhealthy("\u7F3A\u5C11 ~/.devspace/auth.json\u3002");
|
|
4679
|
-
if (directoryMode && directoryMode !== "700")
|
|
5127
|
+
if (directoryMode && directoryMode !== "700")
|
|
5128
|
+
unhealthy(`~/.devspace \u76EE\u5F55\u6743\u9650\u4E3A ${directoryMode}\uFF0C\u5E94\u6536\u7D27\u4E3A 700\u3002`);
|
|
4680
5129
|
if (fileMode && fileMode !== "600") unhealthy(`DevSpace \u914D\u7F6E\u6587\u4EF6\u6743\u9650\u4E3A ${fileMode}\uFF0C\u5E94\u4E3A 600\u3002`);
|
|
4681
5130
|
if (authMode && authMode !== "600") unhealthy(`DevSpace \u8BA4\u8BC1\u6587\u4EF6\u6743\u9650\u4E3A ${authMode}\uFF0C\u5E94\u4E3A 600\u3002`);
|
|
4682
|
-
if (bind === "non-loopback")
|
|
5131
|
+
if (bind === "non-loopback")
|
|
5132
|
+
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");
|
|
4683
5133
|
if (doctor === "failed") unhealthy("devspace doctor \u672A\u901A\u8FC7\u3002");
|
|
4684
5134
|
if (portfolioCoverage === "partial") attention("DevSpace allowedRoots \u6CA1\u6709\u8986\u76D6\u5168\u90E8\u5DF2\u767B\u8BB0\u4ED3\u5E93\u3002");
|
|
4685
5135
|
if (installedResult.ok && !pid) attention("DevSpace \u5DF2\u5B89\u88C5\u4F46\u5F53\u524D\u6CA1\u6709\u8FD0\u884C\u3002");
|
|
4686
5136
|
if (options.expectedToolMode && pid && processToolMode !== options.expectedToolMode) {
|
|
4687
|
-
attention(
|
|
5137
|
+
attention(
|
|
5138
|
+
`\u8FD0\u884C\u4E2D\u7684 DevSpace \u5DE5\u5177\u6A21\u5F0F\u4E3A ${processToolMode ?? "unknown"}\uFF0C\u671F\u671B ${options.expectedToolMode}\u3002`
|
|
5139
|
+
);
|
|
4688
5140
|
}
|
|
4689
|
-
if (update === "available")
|
|
5141
|
+
if (update === "available")
|
|
5142
|
+
attention(
|
|
5143
|
+
`DevSpace \u6709\u65B0\u7248\u672C\uFF1A${installedVersion} \u2192 ${latestVersion}\uFF1B\u5347\u7EA7\u524D\u4ECD\u5E94\u68C0\u67E5\u53D8\u66F4\u4E0E\u517C\u5BB9\u6027\u3002`
|
|
5144
|
+
);
|
|
4690
5145
|
return {
|
|
4691
5146
|
status,
|
|
4692
5147
|
installed: installedResult.ok,
|
|
@@ -4735,18 +5190,25 @@ function isPathInside(path, root) {
|
|
|
4735
5190
|
}
|
|
4736
5191
|
function inspectSkillRoot(path) {
|
|
4737
5192
|
const exists = pathLexists(path) && safeIsDirectory(path);
|
|
4738
|
-
const names = exists ? safeReadDir(path).filter(
|
|
5193
|
+
const names = exists ? safeReadDir(path).filter(
|
|
5194
|
+
(name) => !name.startsWith(".") && existsSync23(join24(path, name, "SKILL.md"))
|
|
5195
|
+
) : [];
|
|
4739
5196
|
return { path, exists, names };
|
|
4740
5197
|
}
|
|
4741
5198
|
function claudeProjectLocalMcpNames(homeDir, root) {
|
|
4742
5199
|
if (!homeDir) return [];
|
|
4743
5200
|
const value = readJson4(join24(homeDir, ".claude.json"));
|
|
4744
5201
|
if (!isRecord3(value) || !isRecord3(value.projects)) return [];
|
|
4745
|
-
const candidates = new Set(
|
|
5202
|
+
const candidates = new Set(
|
|
5203
|
+
[resolve5(root), safeRealpath(root)].filter((path) => Boolean(path))
|
|
5204
|
+
);
|
|
4746
5205
|
const names = /* @__PURE__ */ new Set();
|
|
4747
5206
|
for (const [path, project] of Object.entries(value.projects)) {
|
|
4748
|
-
const projectPaths = [resolve5(path), safeRealpath(path)].filter(
|
|
4749
|
-
|
|
5207
|
+
const projectPaths = [resolve5(path), safeRealpath(path)].filter(
|
|
5208
|
+
(candidate) => Boolean(candidate)
|
|
5209
|
+
);
|
|
5210
|
+
if (!projectPaths.some((candidate) => candidates.has(candidate)) || !isRecord3(project) || !isRecord3(project.mcpServers))
|
|
5211
|
+
continue;
|
|
4750
5212
|
for (const name of Object.keys(project.mcpServers)) names.add(name);
|
|
4751
5213
|
}
|
|
4752
5214
|
return [...names].sort();
|
|
@@ -4773,14 +5235,16 @@ function inspectGrokProject(root, homeDir, grokVersion) {
|
|
|
4773
5235
|
});
|
|
4774
5236
|
if (!grokVersion) return empty("unavailable");
|
|
4775
5237
|
try {
|
|
4776
|
-
const value = JSON.parse(
|
|
4777
|
-
|
|
4778
|
-
|
|
4779
|
-
|
|
4780
|
-
|
|
4781
|
-
|
|
4782
|
-
|
|
4783
|
-
|
|
5238
|
+
const value = JSON.parse(
|
|
5239
|
+
execFileSync("grok", ["inspect", "--json"], {
|
|
5240
|
+
cwd: root,
|
|
5241
|
+
encoding: "utf8",
|
|
5242
|
+
env: { ...process.env, HOME: homeDir, GROK_HOME: join24(homeDir, ".grok") },
|
|
5243
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
5244
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
5245
|
+
timeout: 8e3
|
|
5246
|
+
})
|
|
5247
|
+
);
|
|
4784
5248
|
if (!isRecord3(value)) return empty("failed");
|
|
4785
5249
|
const userClaudeNames = new Set(jsonObjectKeys(join24(homeDir, ".claude.json"), "mcpServers"));
|
|
4786
5250
|
const localClaudeNames = new Set(claudeProjectLocalMcpNames(homeDir, root));
|
|
@@ -4790,13 +5254,23 @@ function inspectGrokProject(root, homeDir, grokVersion) {
|
|
|
4790
5254
|
const sourceType = typeof source.type === "string" ? source.type : "unknown";
|
|
4791
5255
|
const sourcePath = typeof source.path === "string" ? source.path : void 0;
|
|
4792
5256
|
const vendor = typeof item.vendor === "string" ? item.vendor : sourceType;
|
|
4793
|
-
return [
|
|
4794
|
-
|
|
4795
|
-
|
|
4796
|
-
|
|
4797
|
-
|
|
4798
|
-
|
|
4799
|
-
|
|
5257
|
+
return [
|
|
5258
|
+
{
|
|
5259
|
+
name: item.name,
|
|
5260
|
+
vendor,
|
|
5261
|
+
scope: inferGrokMcpScope(
|
|
5262
|
+
item.name,
|
|
5263
|
+
sourceType,
|
|
5264
|
+
sourcePath,
|
|
5265
|
+
root,
|
|
5266
|
+
homeDir,
|
|
5267
|
+
userClaudeNames,
|
|
5268
|
+
localClaudeNames
|
|
5269
|
+
),
|
|
5270
|
+
sourceType,
|
|
5271
|
+
...sourcePath ? { sourcePath } : {}
|
|
5272
|
+
}
|
|
5273
|
+
];
|
|
4800
5274
|
}) : [];
|
|
4801
5275
|
const skillCounts = { project: 0, userOrCompatible: 0, plugin: 0, bundled: 0 };
|
|
4802
5276
|
if (Array.isArray(value.skills)) {
|
|
@@ -4804,7 +5278,8 @@ function inspectGrokProject(root, homeDir, grokVersion) {
|
|
|
4804
5278
|
if (!isRecord3(item) || !isRecord3(item.source)) continue;
|
|
4805
5279
|
const type = item.source.type;
|
|
4806
5280
|
const path = item.source.path;
|
|
4807
|
-
if (typeof path === "string" && path.includes(sep + ".grok" + sep + "bundled" + sep))
|
|
5281
|
+
if (typeof path === "string" && path.includes(sep + ".grok" + sep + "bundled" + sep))
|
|
5282
|
+
skillCounts.bundled += 1;
|
|
4808
5283
|
else if (type === "project") skillCounts.project += 1;
|
|
4809
5284
|
else if (type === "plugin") skillCounts.plugin += 1;
|
|
4810
5285
|
else skillCounts.userOrCompatible += 1;
|
|
@@ -4944,12 +5419,20 @@ function inspectSkillRegistry(executionEngineRoot) {
|
|
|
4944
5419
|
if (!isRecord3(bundle) || !Array.isArray(bundle.assets)) continue;
|
|
4945
5420
|
for (const id of bundle.assets) if (typeof id === "string") bundledIds.add(id);
|
|
4946
5421
|
}
|
|
4947
|
-
const sourceRoots = [
|
|
4948
|
-
|
|
5422
|
+
const sourceRoots = [
|
|
5423
|
+
join24(agentAssetsRoot, "skills/pie-skills"),
|
|
5424
|
+
join24(agentAssetsRoot, "skills/npx-skills/.agents/skills")
|
|
5425
|
+
];
|
|
5426
|
+
const source = sourceRoots.reduce(
|
|
5427
|
+
(count, root) => count + safeReadDir(root).filter((name) => existsSync23(join24(root, name, "SKILL.md"))).length,
|
|
5428
|
+
0
|
|
5429
|
+
);
|
|
4949
5430
|
return {
|
|
4950
5431
|
source,
|
|
4951
5432
|
registered: registeredSkills.length,
|
|
4952
|
-
bundled: registeredSkills.filter(
|
|
5433
|
+
bundled: registeredSkills.filter(
|
|
5434
|
+
(asset) => isRecord3(asset) && typeof asset.id === "string" && bundledIds.has(asset.id)
|
|
5435
|
+
).length,
|
|
4953
5436
|
bundles: bundleFiles.length
|
|
4954
5437
|
};
|
|
4955
5438
|
}
|
|
@@ -4962,7 +5445,7 @@ function tomlMcpNames(path) {
|
|
|
4962
5445
|
if (!existsSync23(path)) return [];
|
|
4963
5446
|
const names = /* @__PURE__ */ new Set();
|
|
4964
5447
|
for (const line of safeRead(path).split(/\r?\n/)) {
|
|
4965
|
-
const match = line.match(/^\s*\[mcp_servers\.(?:"([^"]+)"|([
|
|
5448
|
+
const match = line.match(/^\s*\[mcp_servers\.(?:"([^"]+)"|([^.\]]+))\]\s*$/);
|
|
4966
5449
|
const name = match?.[1] ?? match?.[2];
|
|
4967
5450
|
if (name) names.add(name);
|
|
4968
5451
|
}
|
|
@@ -4984,7 +5467,7 @@ function safeRead(path) {
|
|
|
4984
5467
|
}
|
|
4985
5468
|
function safeReadDir(path) {
|
|
4986
5469
|
try {
|
|
4987
|
-
return
|
|
5470
|
+
return readdirSync10(path).sort();
|
|
4988
5471
|
} catch {
|
|
4989
5472
|
return [];
|
|
4990
5473
|
}
|
|
@@ -5006,7 +5489,10 @@ function pathLexists(path) {
|
|
|
5006
5489
|
}
|
|
5007
5490
|
function gitTracks(root, path) {
|
|
5008
5491
|
try {
|
|
5009
|
-
execFileSync("git", ["ls-files", "--error-unmatch", "--", path], {
|
|
5492
|
+
execFileSync("git", ["ls-files", "--error-unmatch", "--", path], {
|
|
5493
|
+
cwd: root,
|
|
5494
|
+
stdio: "ignore"
|
|
5495
|
+
});
|
|
5010
5496
|
return true;
|
|
5011
5497
|
} catch {
|
|
5012
5498
|
return false;
|
|
@@ -5025,7 +5511,7 @@ function isRecord3(value) {
|
|
|
5025
5511
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5026
5512
|
}
|
|
5027
5513
|
function findDashboardAssets() {
|
|
5028
|
-
const packageRoot2 =
|
|
5514
|
+
const packageRoot2 = dirname14(dirname14(fileURLToPath4(import.meta.url)));
|
|
5029
5515
|
const candidates = [
|
|
5030
5516
|
process.env.PGS_DASHBOARD_ASSETS_DIR,
|
|
5031
5517
|
join24(packageRoot2, ".dashboard-build"),
|
|
@@ -5036,7 +5522,10 @@ function findDashboardAssets() {
|
|
|
5036
5522
|
join24(process.cwd(), "packages/pro-gov/assets/portfolio-dashboard")
|
|
5037
5523
|
].filter((value) => Boolean(value));
|
|
5038
5524
|
const match = candidates.find((path) => existsSync23(join24(path, "index.html")));
|
|
5039
|
-
if (!match)
|
|
5525
|
+
if (!match)
|
|
5526
|
+
throw new Error(
|
|
5527
|
+
"Portfolio dashboard assets were not built. Run pnpm --filter @pieai/pro-gov build."
|
|
5528
|
+
);
|
|
5040
5529
|
return match;
|
|
5041
5530
|
}
|
|
5042
5531
|
function safeJavaScriptJson(value) {
|