@pieai/pro-gov 0.7.1 → 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 +586 -146
- 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}`);
|
|
@@ -3735,7 +3733,7 @@ import { spawnSync as spawnSync6 } from "node:child_process";
|
|
|
3735
3733
|
import { existsSync as existsSync22, readFileSync as readFileSync16 } from "node:fs";
|
|
3736
3734
|
import { createRequire as createRequire2 } from "node:module";
|
|
3737
3735
|
import { homedir as homedir3 } from "node:os";
|
|
3738
|
-
import { dirname as
|
|
3736
|
+
import { dirname as dirname13, join as join23 } from "node:path";
|
|
3739
3737
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
3740
3738
|
|
|
3741
3739
|
// src/host-tooling/inventory.ts
|
|
@@ -3909,26 +3907,50 @@ function pathIsSymlink(path) {
|
|
|
3909
3907
|
|
|
3910
3908
|
// src/portfolio/version-policy.ts
|
|
3911
3909
|
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
3912
|
-
import { existsSync as existsSync21, readFileSync as readFileSync15 } from "node:fs";
|
|
3913
|
-
import { join as join22 } from "node:path";
|
|
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";
|
|
3914
3912
|
function inspectVersionPolicy(root, policy, projectType) {
|
|
3915
3913
|
if (!policy) return { status: "compliant", packages: [], runtimes: [], attentionCount: 0 };
|
|
3916
|
-
const
|
|
3914
|
+
const packageManifests = collectPackageManifests(root);
|
|
3915
|
+
const packageJson = packageManifests.find(
|
|
3916
|
+
(manifest) => manifest.path === "package.json"
|
|
3917
|
+
)?.packageJson;
|
|
3917
3918
|
const packageManager = policy.packageManager ? inspectPackageManager(packageJson, policy.packageManager.name, policy.packageManager.version) : void 0;
|
|
3918
3919
|
const runtime = policy.runtime ? inspectRuntime(policy.runtime.name, policy.runtime.version) : void 0;
|
|
3919
3920
|
const runtimes = (policy.runtimes ?? []).map((requirement) => {
|
|
3920
3921
|
if (requirement.appliesTo && (!projectType || !requirement.appliesTo.includes(projectType))) {
|
|
3921
|
-
return {
|
|
3922
|
+
return {
|
|
3923
|
+
name: requirement.name,
|
|
3924
|
+
expected: requirement.version,
|
|
3925
|
+
status: "not-applicable"
|
|
3926
|
+
};
|
|
3922
3927
|
}
|
|
3923
3928
|
return inspectRuntime(requirement.name, requirement.version);
|
|
3924
3929
|
});
|
|
3925
3930
|
const packages = policy.packages.map((requirement) => {
|
|
3926
3931
|
if (requirement.appliesTo && (!projectType || !requirement.appliesTo.includes(projectType))) {
|
|
3927
|
-
return {
|
|
3932
|
+
return {
|
|
3933
|
+
name: requirement.name,
|
|
3934
|
+
expected: requirement.version,
|
|
3935
|
+
status: "not-applicable"
|
|
3936
|
+
};
|
|
3928
3937
|
}
|
|
3929
|
-
const
|
|
3930
|
-
|
|
3931
|
-
|
|
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";
|
|
3932
3954
|
return {
|
|
3933
3955
|
name: requirement.name,
|
|
3934
3956
|
expected: requirement.version,
|
|
@@ -3938,8 +3960,12 @@ function inspectVersionPolicy(root, policy, projectType) {
|
|
|
3938
3960
|
status
|
|
3939
3961
|
};
|
|
3940
3962
|
});
|
|
3941
|
-
const all = [packageManager, runtime, ...runtimes, ...packages].filter(
|
|
3942
|
-
|
|
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;
|
|
3943
3969
|
return {
|
|
3944
3970
|
status: attentionCount === 0 ? "compliant" : "attention",
|
|
3945
3971
|
packageManager,
|
|
@@ -3977,15 +4003,78 @@ function readRuntimeVersion(name) {
|
|
|
3977
4003
|
return /^deno\s+(\d+\.\d+\.\d+)/m.exec(result.stdout)?.[1];
|
|
3978
4004
|
}
|
|
3979
4005
|
function findDeclaredVersion(packageJson, name) {
|
|
3980
|
-
for (const section of [
|
|
4006
|
+
for (const section of [
|
|
4007
|
+
"dependencies",
|
|
4008
|
+
"devDependencies",
|
|
4009
|
+
"peerDependencies",
|
|
4010
|
+
"optionalDependencies"
|
|
4011
|
+
]) {
|
|
3981
4012
|
const value = packageJson?.[section]?.[name];
|
|
3982
4013
|
if (typeof value === "string") return value;
|
|
3983
4014
|
}
|
|
3984
4015
|
return void 0;
|
|
3985
4016
|
}
|
|
3986
|
-
function readInstalledVersion(root, name) {
|
|
3987
|
-
|
|
3988
|
-
|
|
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)];
|
|
3989
4078
|
}
|
|
3990
4079
|
function readJson2(path) {
|
|
3991
4080
|
if (!existsSync21(path)) return void 0;
|
|
@@ -4157,11 +4246,11 @@ function getExpectedPackageVersions() {
|
|
|
4157
4246
|
};
|
|
4158
4247
|
}
|
|
4159
4248
|
function findOwnPackageJson() {
|
|
4160
|
-
let current =
|
|
4249
|
+
let current = dirname13(fileURLToPath3(import.meta.url));
|
|
4161
4250
|
for (let depth = 0; depth < 5; depth += 1) {
|
|
4162
4251
|
const candidate = join23(current, "package.json");
|
|
4163
4252
|
if (existsSync22(candidate)) return candidate;
|
|
4164
|
-
current =
|
|
4253
|
+
current = dirname13(current);
|
|
4165
4254
|
}
|
|
4166
4255
|
return "";
|
|
4167
4256
|
}
|
|
@@ -4191,13 +4280,13 @@ import {
|
|
|
4191
4280
|
lstatSync as lstatSync8,
|
|
4192
4281
|
mkdirSync as mkdirSync8,
|
|
4193
4282
|
readFileSync as readFileSync17,
|
|
4194
|
-
readdirSync as
|
|
4283
|
+
readdirSync as readdirSync10,
|
|
4195
4284
|
realpathSync as realpathSync4,
|
|
4196
4285
|
statSync as statSync5,
|
|
4197
4286
|
writeFileSync as writeFileSync7
|
|
4198
4287
|
} from "node:fs";
|
|
4199
4288
|
import { homedir as homedir4 } from "node:os";
|
|
4200
|
-
import { dirname as
|
|
4289
|
+
import { dirname as dirname14, join as join24, relative as relative8, resolve as resolve5, sep } from "node:path";
|
|
4201
4290
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
4202
4291
|
var CURRENT_ROUTER_VERSION = "1.1";
|
|
4203
4292
|
function inspectPortfolioAiHealth(options) {
|
|
@@ -4206,21 +4295,30 @@ function inspectPortfolioAiHealth(options) {
|
|
|
4206
4295
|
if (options.targetId && options.targetId !== "all" && endpoints.length === 0) {
|
|
4207
4296
|
throw new Error(`Unknown portfolio target: ${options.targetId}`);
|
|
4208
4297
|
}
|
|
4209
|
-
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
|
+
);
|
|
4210
4304
|
const homeDir = options.homeDir ?? process.env.HOME ?? homedir4();
|
|
4211
4305
|
const grokVersion = commandVersion("grok");
|
|
4212
4306
|
const executionEngineRoot = options.manifest.executionEngine?.path;
|
|
4213
4307
|
const skillRegistry = inspectSkillRegistry(executionEngineRoot);
|
|
4214
|
-
const expectedPackageVersion = packageVersion(
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
|
|
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
|
+
);
|
|
4224
4322
|
const summary = { healthy: 0, attention: 0, unhealthy: 0 };
|
|
4225
4323
|
for (const repository of repositories) summary[repository.status] += 1;
|
|
4226
4324
|
return {
|
|
@@ -4245,6 +4343,8 @@ function inspectPortfolioAiHealth(options) {
|
|
|
4245
4343
|
technologyGovernance: {
|
|
4246
4344
|
strategySource: options.manifest.technologyGovernance?.strategySource,
|
|
4247
4345
|
versionPolicy: options.manifest.technologyGovernance?.versionPolicy,
|
|
4346
|
+
catalog: options.manifest.technologyGovernance?.technologies ?? [],
|
|
4347
|
+
matrix: buildTechnologyMatrix(options.manifest.technologyGovernance, repositories),
|
|
4248
4348
|
projectTypes: options.manifest.technologyGovernance?.projectTypes.length ?? 0,
|
|
4249
4349
|
technologies: options.manifest.technologyGovernance?.technologies.length ?? 0
|
|
4250
4350
|
},
|
|
@@ -4254,7 +4354,8 @@ function inspectPortfolioAiHealth(options) {
|
|
|
4254
4354
|
}
|
|
4255
4355
|
function mergePortfolioAiHealthReport(existing, latest, allRepositoryIds) {
|
|
4256
4356
|
const repositoriesById = /* @__PURE__ */ new Map();
|
|
4257
|
-
for (const repository of existing?.repositories ?? [])
|
|
4357
|
+
for (const repository of existing?.repositories ?? [])
|
|
4358
|
+
repositoriesById.set(repository.id, repository);
|
|
4258
4359
|
for (const repository of latest.repositories) repositoriesById.set(repository.id, repository);
|
|
4259
4360
|
const repositories = allRepositoryIds.map((id) => repositoriesById.get(id)).filter((repository) => repository !== void 0);
|
|
4260
4361
|
const coveredIds = /* @__PURE__ */ new Set([
|
|
@@ -4265,8 +4366,20 @@ function mergePortfolioAiHealthReport(existing, latest, allRepositoryIds) {
|
|
|
4265
4366
|
const summary = { healthy: 0, attention: 0, unhealthy: 0 };
|
|
4266
4367
|
for (const repository of repositories) summary[repository.status] += 1;
|
|
4267
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;
|
|
4268
4380
|
return {
|
|
4269
4381
|
...latest,
|
|
4382
|
+
technologyGovernance,
|
|
4270
4383
|
repositories,
|
|
4271
4384
|
summary,
|
|
4272
4385
|
coverage: {
|
|
@@ -4289,14 +4402,19 @@ function writePortfolioAiHealthReport(report, outDir) {
|
|
|
4289
4402
|
const htmlPath = join24(outDir, "index.html");
|
|
4290
4403
|
writeFileSync7(jsonPath, `${JSON.stringify(report, null, 2)}
|
|
4291
4404
|
`);
|
|
4292
|
-
writeFileSync7(
|
|
4293
|
-
|
|
4405
|
+
writeFileSync7(
|
|
4406
|
+
join24(outDir, "data.js"),
|
|
4407
|
+
`window.__PORTFOLIO_AI_HEALTH__ = ${safeJavaScriptJson(report)};
|
|
4408
|
+
`
|
|
4409
|
+
);
|
|
4294
4410
|
return { jsonPath, htmlPath };
|
|
4295
4411
|
}
|
|
4296
4412
|
function collectEndpoints(manifest) {
|
|
4297
4413
|
const result = [];
|
|
4298
|
-
if (manifest.controlPlane)
|
|
4299
|
-
|
|
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" });
|
|
4300
4418
|
for (const target of manifest.targets) result.push({ endpoint: target, role: "target" });
|
|
4301
4419
|
const seen = /* @__PURE__ */ new Set();
|
|
4302
4420
|
return result.filter(({ endpoint }) => {
|
|
@@ -4323,56 +4441,156 @@ function inspectRepository(endpoint, role, secretsRoot, homeDir, expectedPackage
|
|
|
4323
4441
|
grokEffective: grokInspection.effectiveMcp,
|
|
4324
4442
|
grokInspection: grokInspection.inspection
|
|
4325
4443
|
};
|
|
4326
|
-
const secrets = inspectRepositorySecrets(
|
|
4444
|
+
const secrets = inspectRepositorySecrets(
|
|
4445
|
+
root,
|
|
4446
|
+
endpoint.id,
|
|
4447
|
+
secretsRoot,
|
|
4448
|
+
git.isRepository,
|
|
4449
|
+
endpoint.environmentPolicy
|
|
4450
|
+
);
|
|
4327
4451
|
const projectModel = inspectProjectModel(root, endpoint, technologyGovernance);
|
|
4328
|
-
const versions = inspectVersionPolicy(
|
|
4452
|
+
const versions = inspectVersionPolicy(
|
|
4453
|
+
root,
|
|
4454
|
+
technologyGovernance?.versionPolicy,
|
|
4455
|
+
endpoint.projectType
|
|
4456
|
+
);
|
|
4329
4457
|
const verification = inspectProjectVerification(root);
|
|
4330
4458
|
const redundancy = inspectProjectRedundancy(root, { homeDir });
|
|
4331
4459
|
const recommendations = [];
|
|
4332
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");
|
|
4333
|
-
if (git.unmergedBranches.length > 0)
|
|
4334
|
-
|
|
4335
|
-
|
|
4336
|
-
|
|
4337
|
-
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`);
|
|
4338
4479
|
if (entries.agents === "missing") recommendations.push("\u7F3A\u5C11 AGENTS.md\uFF1B\u65E0\u6CD5\u53D1\u73B0\u9879\u76EE\u5165\u53E3\u89C4\u5219\u3002");
|
|
4339
|
-
if (entries.agents === "custom")
|
|
4340
|
-
|
|
4341
|
-
|
|
4342
|
-
|
|
4343
|
-
if (entries.claude === "
|
|
4344
|
-
|
|
4345
|
-
if (entries.
|
|
4346
|
-
|
|
4347
|
-
if (
|
|
4348
|
-
|
|
4349
|
-
|
|
4350
|
-
|
|
4351
|
-
if (
|
|
4352
|
-
|
|
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
|
+
);
|
|
4353
4522
|
const liveEnv = secrets.repositoryEnvFiles.filter((file) => !file.template && !file.fixture);
|
|
4354
4523
|
const envNeedsCentralReview = liveEnv.filter((file) => !file.localOnly);
|
|
4355
|
-
if (liveEnv.some((file) => file.tracked))
|
|
4356
|
-
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
|
|
4361
|
-
if (
|
|
4362
|
-
|
|
4363
|
-
|
|
4364
|
-
|
|
4365
|
-
|
|
4366
|
-
|
|
4367
|
-
if (
|
|
4368
|
-
|
|
4369
|
-
|
|
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
|
+
);
|
|
4370
4574
|
return {
|
|
4371
4575
|
id: endpoint.id,
|
|
4372
4576
|
role,
|
|
4373
4577
|
path: root,
|
|
4374
4578
|
profile: "profile" in endpoint ? endpoint.profile : void 0,
|
|
4375
|
-
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
|
+
),
|
|
4376
4594
|
recommendations,
|
|
4377
4595
|
git,
|
|
4378
4596
|
entries,
|
|
@@ -4388,22 +4606,161 @@ function inspectRepository(endpoint, role, secretsRoot, homeDir, expectedPackage
|
|
|
4388
4606
|
redundancy
|
|
4389
4607
|
};
|
|
4390
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
|
+
}
|
|
4391
4736
|
function deriveStatus(role, entries, git, hooks, skills, hostSsot, secrets, docs, projectModel, technologyGovernanceConfigured, versions, verification, redundancy) {
|
|
4392
|
-
if (!git.isRepository || entries.agents === "missing" || entries.claude === "dangling-symlink" || entries.gemini === "dangling-symlink" || skills.canonical.some((item) => item.kind === "dangling-symlink") || secrets.repositoryEnvFiles.some((file) => file.tracked && !file.template && !file.fixture) || hasUnsafeCentralSecretPermissions(secrets))
|
|
4393
|
-
|
|
4394
|
-
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
|
+
);
|
|
4395
4745
|
const liveEnv = secrets.repositoryEnvFiles.filter((file) => !file.template && !file.fixture);
|
|
4396
|
-
const secretMaterializationNeedsReview = liveEnv.some(
|
|
4746
|
+
const secretMaterializationNeedsReview = liveEnv.some(
|
|
4747
|
+
(file) => !file.centralized && !file.localOnly
|
|
4748
|
+
);
|
|
4397
4749
|
const packageVersionNeedsReview = docs.packages.expected !== void 0 && !docs.packages.aligned;
|
|
4398
4750
|
const routerVersionNeedsReview = !docs.routerAligned;
|
|
4399
4751
|
const targetManifestMissing = role === "target" && !docs.manifest;
|
|
4400
|
-
if (entries.agents !== "pgs-router" || entries.claude !== "agents-symlink" || hasWorkflowReminderHooks(hooks) || skills.claudeCompatibility === "duplicate-directory" || skills.claudeCompatibility === "dangling-symlink" || !hostSsot.compliant || git.branches.length > 1 || git.worktrees.length > 1 || git.dirtyPaths.length > 0 || (git.ahead ?? 0) > 0 || secretMaterializationNeedsReview || technologyGovernanceConfigured && !projectModel.projectType || missingBaseline || missingSelected || packageVersionNeedsReview || routerVersionNeedsReview || targetManifestMissing || versions.status === "attention" || verification.status === "attention" || redundancy.legacyDirectories.length > 0)
|
|
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";
|
|
4401
4754
|
return "healthy";
|
|
4402
4755
|
}
|
|
4403
4756
|
function inspectProjectModel(root, endpoint, governance) {
|
|
4404
|
-
const projectType = governance?.projectTypes.find(
|
|
4757
|
+
const projectType = governance?.projectTypes.find(
|
|
4758
|
+
(candidate) => candidate.id === endpoint.projectType
|
|
4759
|
+
);
|
|
4405
4760
|
const packages = collectPackageNames(root);
|
|
4406
|
-
const technologyById = new Map(
|
|
4761
|
+
const technologyById = new Map(
|
|
4762
|
+
(governance?.technologies ?? []).map((technology) => [technology.id, technology])
|
|
4763
|
+
);
|
|
4407
4764
|
const detection = (id) => {
|
|
4408
4765
|
const technology = technologyById.get(id);
|
|
4409
4766
|
const packageMatch = technology?.packages?.some((name) => packages.has(name)) ?? false;
|
|
@@ -4415,7 +4772,10 @@ function inspectProjectModel(root, endpoint, governance) {
|
|
|
4415
4772
|
projectType: projectType ? { id: projectType.id, title: projectType.title, summary: projectType.summary } : void 0,
|
|
4416
4773
|
boundary: endpoint.boundary,
|
|
4417
4774
|
baseline: (projectType?.baseline ?? []).map(detection),
|
|
4418
|
-
optionalCapabilities: (projectType?.optionalCapabilities ?? []).map((id) => ({
|
|
4775
|
+
optionalCapabilities: (projectType?.optionalCapabilities ?? []).map((id) => ({
|
|
4776
|
+
...detection(id),
|
|
4777
|
+
selected: selected.has(id)
|
|
4778
|
+
})),
|
|
4419
4779
|
exceptions: endpoint.technologyExceptions ?? []
|
|
4420
4780
|
};
|
|
4421
4781
|
}
|
|
@@ -4426,16 +4786,14 @@ function hasBaselineException(projectModel, technologyId) {
|
|
|
4426
4786
|
}
|
|
4427
4787
|
function collectPackageNames(root) {
|
|
4428
4788
|
const names = /* @__PURE__ */ new Set();
|
|
4429
|
-
|
|
4430
|
-
try {
|
|
4431
|
-
files = splitLines(execFileSync("git", ["ls-files", "*package.json"], { cwd: root, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }));
|
|
4432
|
-
} catch {
|
|
4433
|
-
if (existsSync23(join24(root, "package.json"))) files = ["package.json"];
|
|
4434
|
-
}
|
|
4435
|
-
for (const file of files) {
|
|
4436
|
-
const packageJson = readJson4(join24(root, file));
|
|
4789
|
+
for (const { packageJson } of collectPackageManifests(root)) {
|
|
4437
4790
|
if (!isRecord3(packageJson)) continue;
|
|
4438
|
-
for (const section of [
|
|
4791
|
+
for (const section of [
|
|
4792
|
+
"dependencies",
|
|
4793
|
+
"devDependencies",
|
|
4794
|
+
"peerDependencies",
|
|
4795
|
+
"optionalDependencies"
|
|
4796
|
+
]) {
|
|
4439
4797
|
const dependencies = packageJson[section];
|
|
4440
4798
|
if (!isRecord3(dependencies)) continue;
|
|
4441
4799
|
for (const name of Object.keys(dependencies)) names.add(name);
|
|
@@ -4444,17 +4802,31 @@ function collectPackageNames(root) {
|
|
|
4444
4802
|
return names;
|
|
4445
4803
|
}
|
|
4446
4804
|
function hasWorkflowReminderHooks(hooks) {
|
|
4447
|
-
return hooks.some(
|
|
4805
|
+
return hooks.some(
|
|
4806
|
+
(hook) => hook.events.some((event) => event.name === "Stop" || event.name === "SubagentStop")
|
|
4807
|
+
);
|
|
4448
4808
|
}
|
|
4449
4809
|
function inspectGit2(root) {
|
|
4450
4810
|
const git = (...args) => {
|
|
4451
4811
|
try {
|
|
4452
|
-
return execFileSync("git", args, {
|
|
4812
|
+
return execFileSync("git", args, {
|
|
4813
|
+
cwd: root,
|
|
4814
|
+
encoding: "utf8",
|
|
4815
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
4816
|
+
}).trimEnd();
|
|
4453
4817
|
} catch {
|
|
4454
4818
|
return void 0;
|
|
4455
4819
|
}
|
|
4456
4820
|
};
|
|
4457
|
-
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
|
+
};
|
|
4458
4830
|
const status = git("status", "--porcelain=v1", "-uall") ?? "";
|
|
4459
4831
|
const worktreeText = git("worktree", "list", "--porcelain") ?? "";
|
|
4460
4832
|
const upstreamCounts = git("rev-list", "--left-right", "--count", "@{upstream}...HEAD")?.split(/\s+/).map(Number);
|
|
@@ -4462,8 +4834,12 @@ function inspectGit2(root) {
|
|
|
4462
4834
|
isRepository: true,
|
|
4463
4835
|
branch: git("branch", "--show-current") || "(detached)",
|
|
4464
4836
|
branches: splitLines(git("for-each-ref", "--format=%(refname:short)", "refs/heads")),
|
|
4465
|
-
mergedBranches: splitLines(
|
|
4466
|
-
|
|
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
|
+
),
|
|
4467
4843
|
worktrees: splitLines(worktreeText).filter((line) => line.startsWith("worktree ")).map((line) => line.slice(9)),
|
|
4468
4844
|
dirtyPaths: splitLines(status).map((line) => line.slice(3)),
|
|
4469
4845
|
behind: upstreamCounts?.[0],
|
|
@@ -4589,14 +4965,29 @@ function inspectRepositorySecrets(root, id, secretsRoot, isRepository, environme
|
|
|
4589
4965
|
repositoryEnvFiles: envFiles
|
|
4590
4966
|
};
|
|
4591
4967
|
}
|
|
4592
|
-
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
|
+
]);
|
|
4593
4983
|
function collectEnvironmentFiles(root, current = root, depth = 0) {
|
|
4594
4984
|
if (depth > 5) return [];
|
|
4595
4985
|
const found = [];
|
|
4596
4986
|
try {
|
|
4597
|
-
for (const entry of
|
|
4987
|
+
for (const entry of readdirSync10(current, { withFileTypes: true })) {
|
|
4598
4988
|
if (entry.isDirectory()) {
|
|
4599
|
-
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));
|
|
4600
4991
|
} else if (isEnvironmentFilename(entry.name) && !isProviderGeneratedEnvironmentFile(entry.name)) {
|
|
4601
4992
|
found.push(relative8(root, join24(current, entry.name)));
|
|
4602
4993
|
}
|
|
@@ -4610,7 +5001,7 @@ function collectCentralSecretFiles(root, current = root, depth = 0) {
|
|
|
4610
5001
|
if (depth > 3) return [];
|
|
4611
5002
|
const found = [];
|
|
4612
5003
|
try {
|
|
4613
|
-
for (const entry of
|
|
5004
|
+
for (const entry of readdirSync10(current, { withFileTypes: true })) {
|
|
4614
5005
|
const path = join24(current, entry.name);
|
|
4615
5006
|
if (entry.isDirectory()) found.push(...collectCentralSecretFiles(root, path, depth + 1));
|
|
4616
5007
|
else found.push({ path: relative8(root, path), mode: modeString(lstatSync8(path).mode) });
|
|
@@ -4696,17 +5087,25 @@ function inspectDevSpaceHealth(options) {
|
|
|
4696
5087
|
const authPath = join24(configDirectory, "auth.json");
|
|
4697
5088
|
const installedResult = run("devspace", ["--version"], 3e3);
|
|
4698
5089
|
const installedVersion = installedResult.ok ? installedResult.stdout.match(/\b\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?\b/)?.[0] : void 0;
|
|
4699
|
-
const latestResult = run(
|
|
5090
|
+
const latestResult = run(
|
|
5091
|
+
"npm",
|
|
5092
|
+
["view", "@waishnav/devspace", "version", "--registry=https://registry.npmjs.org/"],
|
|
5093
|
+
5e3
|
|
5094
|
+
);
|
|
4700
5095
|
const latestVersion = latestResult.ok ? latestResult.stdout.match(/\b\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?\b/)?.[0] : void 0;
|
|
4701
5096
|
const processResult = run("pgrep", ["-f", "devspace serve"], 3e3);
|
|
4702
5097
|
const pid = processResult.ok ? processResult.stdout.match(/\b\d+\b/)?.[0] : void 0;
|
|
4703
5098
|
const processEnvironment = pid ? run("ps", ["eww", "-p", pid, "-o", "command="], 3e3) : void 0;
|
|
4704
|
-
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;
|
|
4705
5102
|
const doctor = !installedResult.ok ? "unavailable" : run("devspace", ["doctor"], 1e4).ok ? "ok" : "failed";
|
|
4706
5103
|
const configValue = readJson4(configPath);
|
|
4707
5104
|
const configExists = isRecord3(configValue);
|
|
4708
5105
|
const allowedRoots = configExists && Array.isArray(configValue.allowedRoots) ? configValue.allowedRoots.filter((value) => typeof value === "string") : [];
|
|
4709
|
-
const portfolioCoverage = !configExists || allowedRoots.length === 0 ? "unknown" : options.repositoryPaths.every(
|
|
5106
|
+
const portfolioCoverage = !configExists || allowedRoots.length === 0 ? "unknown" : options.repositoryPaths.every(
|
|
5107
|
+
(repositoryPath) => allowedRoots.some((root) => isPathInside(repositoryPath, root))
|
|
5108
|
+
) ? "complete" : "partial";
|
|
4710
5109
|
const bind = configExists && typeof configValue.host === "string" ? isLoopbackHost(configValue.host) ? "loopback" : "non-loopback" : "unknown";
|
|
4711
5110
|
const directoryMode = existsSync23(configDirectory) ? modeString(statSync5(configDirectory).mode) : void 0;
|
|
4712
5111
|
const fileMode = existsSync23(configPath) ? modeString(statSync5(configPath).mode) : void 0;
|
|
@@ -4725,17 +5124,24 @@ function inspectDevSpaceHealth(options) {
|
|
|
4725
5124
|
if (!installedResult.ok) unhealthy("\u672C\u673A\u672A\u53D1\u73B0 DevSpace\uFF1B\u65E0\u6CD5\u4F7F\u7528\u5BBF\u4E3B\u5DE5\u4F5C\u533A\u670D\u52A1\u3002");
|
|
4726
5125
|
if (!configExists) unhealthy("\u7F3A\u5C11 ~/.devspace/config.json\u3002");
|
|
4727
5126
|
if (!existsSync23(authPath)) unhealthy("\u7F3A\u5C11 ~/.devspace/auth.json\u3002");
|
|
4728
|
-
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`);
|
|
4729
5129
|
if (fileMode && fileMode !== "600") unhealthy(`DevSpace \u914D\u7F6E\u6587\u4EF6\u6743\u9650\u4E3A ${fileMode}\uFF0C\u5E94\u4E3A 600\u3002`);
|
|
4730
5130
|
if (authMode && authMode !== "600") unhealthy(`DevSpace \u8BA4\u8BC1\u6587\u4EF6\u6743\u9650\u4E3A ${authMode}\uFF0C\u5E94\u4E3A 600\u3002`);
|
|
4731
|
-
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");
|
|
4732
5133
|
if (doctor === "failed") unhealthy("devspace doctor \u672A\u901A\u8FC7\u3002");
|
|
4733
5134
|
if (portfolioCoverage === "partial") attention("DevSpace allowedRoots \u6CA1\u6709\u8986\u76D6\u5168\u90E8\u5DF2\u767B\u8BB0\u4ED3\u5E93\u3002");
|
|
4734
5135
|
if (installedResult.ok && !pid) attention("DevSpace \u5DF2\u5B89\u88C5\u4F46\u5F53\u524D\u6CA1\u6709\u8FD0\u884C\u3002");
|
|
4735
5136
|
if (options.expectedToolMode && pid && processToolMode !== options.expectedToolMode) {
|
|
4736
|
-
attention(
|
|
5137
|
+
attention(
|
|
5138
|
+
`\u8FD0\u884C\u4E2D\u7684 DevSpace \u5DE5\u5177\u6A21\u5F0F\u4E3A ${processToolMode ?? "unknown"}\uFF0C\u671F\u671B ${options.expectedToolMode}\u3002`
|
|
5139
|
+
);
|
|
4737
5140
|
}
|
|
4738
|
-
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
|
+
);
|
|
4739
5145
|
return {
|
|
4740
5146
|
status,
|
|
4741
5147
|
installed: installedResult.ok,
|
|
@@ -4784,18 +5190,25 @@ function isPathInside(path, root) {
|
|
|
4784
5190
|
}
|
|
4785
5191
|
function inspectSkillRoot(path) {
|
|
4786
5192
|
const exists = pathLexists(path) && safeIsDirectory(path);
|
|
4787
|
-
const names = exists ? safeReadDir(path).filter(
|
|
5193
|
+
const names = exists ? safeReadDir(path).filter(
|
|
5194
|
+
(name) => !name.startsWith(".") && existsSync23(join24(path, name, "SKILL.md"))
|
|
5195
|
+
) : [];
|
|
4788
5196
|
return { path, exists, names };
|
|
4789
5197
|
}
|
|
4790
5198
|
function claudeProjectLocalMcpNames(homeDir, root) {
|
|
4791
5199
|
if (!homeDir) return [];
|
|
4792
5200
|
const value = readJson4(join24(homeDir, ".claude.json"));
|
|
4793
5201
|
if (!isRecord3(value) || !isRecord3(value.projects)) return [];
|
|
4794
|
-
const candidates = new Set(
|
|
5202
|
+
const candidates = new Set(
|
|
5203
|
+
[resolve5(root), safeRealpath(root)].filter((path) => Boolean(path))
|
|
5204
|
+
);
|
|
4795
5205
|
const names = /* @__PURE__ */ new Set();
|
|
4796
5206
|
for (const [path, project] of Object.entries(value.projects)) {
|
|
4797
|
-
const projectPaths = [resolve5(path), safeRealpath(path)].filter(
|
|
4798
|
-
|
|
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;
|
|
4799
5212
|
for (const name of Object.keys(project.mcpServers)) names.add(name);
|
|
4800
5213
|
}
|
|
4801
5214
|
return [...names].sort();
|
|
@@ -4822,14 +5235,16 @@ function inspectGrokProject(root, homeDir, grokVersion) {
|
|
|
4822
5235
|
});
|
|
4823
5236
|
if (!grokVersion) return empty("unavailable");
|
|
4824
5237
|
try {
|
|
4825
|
-
const value = JSON.parse(
|
|
4826
|
-
|
|
4827
|
-
|
|
4828
|
-
|
|
4829
|
-
|
|
4830
|
-
|
|
4831
|
-
|
|
4832
|
-
|
|
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
|
+
);
|
|
4833
5248
|
if (!isRecord3(value)) return empty("failed");
|
|
4834
5249
|
const userClaudeNames = new Set(jsonObjectKeys(join24(homeDir, ".claude.json"), "mcpServers"));
|
|
4835
5250
|
const localClaudeNames = new Set(claudeProjectLocalMcpNames(homeDir, root));
|
|
@@ -4839,13 +5254,23 @@ function inspectGrokProject(root, homeDir, grokVersion) {
|
|
|
4839
5254
|
const sourceType = typeof source.type === "string" ? source.type : "unknown";
|
|
4840
5255
|
const sourcePath = typeof source.path === "string" ? source.path : void 0;
|
|
4841
5256
|
const vendor = typeof item.vendor === "string" ? item.vendor : sourceType;
|
|
4842
|
-
return [
|
|
4843
|
-
|
|
4844
|
-
|
|
4845
|
-
|
|
4846
|
-
|
|
4847
|
-
|
|
4848
|
-
|
|
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
|
+
];
|
|
4849
5274
|
}) : [];
|
|
4850
5275
|
const skillCounts = { project: 0, userOrCompatible: 0, plugin: 0, bundled: 0 };
|
|
4851
5276
|
if (Array.isArray(value.skills)) {
|
|
@@ -4853,7 +5278,8 @@ function inspectGrokProject(root, homeDir, grokVersion) {
|
|
|
4853
5278
|
if (!isRecord3(item) || !isRecord3(item.source)) continue;
|
|
4854
5279
|
const type = item.source.type;
|
|
4855
5280
|
const path = item.source.path;
|
|
4856
|
-
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;
|
|
4857
5283
|
else if (type === "project") skillCounts.project += 1;
|
|
4858
5284
|
else if (type === "plugin") skillCounts.plugin += 1;
|
|
4859
5285
|
else skillCounts.userOrCompatible += 1;
|
|
@@ -4993,12 +5419,20 @@ function inspectSkillRegistry(executionEngineRoot) {
|
|
|
4993
5419
|
if (!isRecord3(bundle) || !Array.isArray(bundle.assets)) continue;
|
|
4994
5420
|
for (const id of bundle.assets) if (typeof id === "string") bundledIds.add(id);
|
|
4995
5421
|
}
|
|
4996
|
-
const sourceRoots = [
|
|
4997
|
-
|
|
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
|
+
);
|
|
4998
5430
|
return {
|
|
4999
5431
|
source,
|
|
5000
5432
|
registered: registeredSkills.length,
|
|
5001
|
-
bundled: registeredSkills.filter(
|
|
5433
|
+
bundled: registeredSkills.filter(
|
|
5434
|
+
(asset) => isRecord3(asset) && typeof asset.id === "string" && bundledIds.has(asset.id)
|
|
5435
|
+
).length,
|
|
5002
5436
|
bundles: bundleFiles.length
|
|
5003
5437
|
};
|
|
5004
5438
|
}
|
|
@@ -5011,7 +5445,7 @@ function tomlMcpNames(path) {
|
|
|
5011
5445
|
if (!existsSync23(path)) return [];
|
|
5012
5446
|
const names = /* @__PURE__ */ new Set();
|
|
5013
5447
|
for (const line of safeRead(path).split(/\r?\n/)) {
|
|
5014
|
-
const match = line.match(/^\s*\[mcp_servers\.(?:"([^"]+)"|([
|
|
5448
|
+
const match = line.match(/^\s*\[mcp_servers\.(?:"([^"]+)"|([^.\]]+))\]\s*$/);
|
|
5015
5449
|
const name = match?.[1] ?? match?.[2];
|
|
5016
5450
|
if (name) names.add(name);
|
|
5017
5451
|
}
|
|
@@ -5033,7 +5467,7 @@ function safeRead(path) {
|
|
|
5033
5467
|
}
|
|
5034
5468
|
function safeReadDir(path) {
|
|
5035
5469
|
try {
|
|
5036
|
-
return
|
|
5470
|
+
return readdirSync10(path).sort();
|
|
5037
5471
|
} catch {
|
|
5038
5472
|
return [];
|
|
5039
5473
|
}
|
|
@@ -5055,7 +5489,10 @@ function pathLexists(path) {
|
|
|
5055
5489
|
}
|
|
5056
5490
|
function gitTracks(root, path) {
|
|
5057
5491
|
try {
|
|
5058
|
-
execFileSync("git", ["ls-files", "--error-unmatch", "--", path], {
|
|
5492
|
+
execFileSync("git", ["ls-files", "--error-unmatch", "--", path], {
|
|
5493
|
+
cwd: root,
|
|
5494
|
+
stdio: "ignore"
|
|
5495
|
+
});
|
|
5059
5496
|
return true;
|
|
5060
5497
|
} catch {
|
|
5061
5498
|
return false;
|
|
@@ -5074,7 +5511,7 @@ function isRecord3(value) {
|
|
|
5074
5511
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5075
5512
|
}
|
|
5076
5513
|
function findDashboardAssets() {
|
|
5077
|
-
const packageRoot2 =
|
|
5514
|
+
const packageRoot2 = dirname14(dirname14(fileURLToPath4(import.meta.url)));
|
|
5078
5515
|
const candidates = [
|
|
5079
5516
|
process.env.PGS_DASHBOARD_ASSETS_DIR,
|
|
5080
5517
|
join24(packageRoot2, ".dashboard-build"),
|
|
@@ -5085,7 +5522,10 @@ function findDashboardAssets() {
|
|
|
5085
5522
|
join24(process.cwd(), "packages/pro-gov/assets/portfolio-dashboard")
|
|
5086
5523
|
].filter((value) => Boolean(value));
|
|
5087
5524
|
const match = candidates.find((path) => existsSync23(join24(path, "index.html")));
|
|
5088
|
-
if (!match)
|
|
5525
|
+
if (!match)
|
|
5526
|
+
throw new Error(
|
|
5527
|
+
"Portfolio dashboard assets were not built. Run pnpm --filter @pieai/pro-gov build."
|
|
5528
|
+
);
|
|
5089
5529
|
return match;
|
|
5090
5530
|
}
|
|
5091
5531
|
function safeJavaScriptJson(value) {
|