@pieai/pro-gov 0.5.2 → 0.5.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js
CHANGED
|
@@ -3585,25 +3585,35 @@ import {
|
|
|
3585
3585
|
statSync as statSync4,
|
|
3586
3586
|
writeFileSync as writeFileSync7
|
|
3587
3587
|
} from "node:fs";
|
|
3588
|
+
import { homedir } from "node:os";
|
|
3588
3589
|
import { dirname as dirname12, join as join20, relative as relative8, resolve as resolve4, sep } from "node:path";
|
|
3589
3590
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
3590
3591
|
function inspectPortfolioAiHealth(options) {
|
|
3591
3592
|
const endpoints = collectEndpoints(options.manifest);
|
|
3592
3593
|
const secretsRoot = options.secretsRoot ?? join20(dirname12(options.manifest.controlPlane?.path ?? endpoints[0]?.endpoint.path ?? process.cwd()), ".secrets");
|
|
3593
|
-
const homeDir = options.homeDir ?? process.env.HOME ??
|
|
3594
|
+
const homeDir = options.homeDir ?? process.env.HOME ?? homedir();
|
|
3595
|
+
const grokVersion = commandVersion("grok");
|
|
3594
3596
|
const executionEngineRoot = options.manifest.executionEngine?.path;
|
|
3595
3597
|
const skillRegistry = inspectSkillRegistry(executionEngineRoot);
|
|
3596
3598
|
const expectedPackageVersion = packageVersion(join20(executionEngineRoot ?? "", "packages/pro-gov/package.json"));
|
|
3597
|
-
const repositories = endpoints.map(({ endpoint, role }) => inspectRepository(
|
|
3599
|
+
const repositories = endpoints.map(({ endpoint, role }) => inspectRepository(
|
|
3600
|
+
endpoint,
|
|
3601
|
+
role,
|
|
3602
|
+
secretsRoot,
|
|
3603
|
+
homeDir,
|
|
3604
|
+
expectedPackageVersion,
|
|
3605
|
+
options.manifest.technologyGovernance,
|
|
3606
|
+
grokVersion
|
|
3607
|
+
));
|
|
3598
3608
|
const summary = { healthy: 0, attention: 0, unhealthy: 0 };
|
|
3599
3609
|
for (const repository of repositories) summary[repository.status] += 1;
|
|
3600
3610
|
return {
|
|
3601
|
-
schemaVersion:
|
|
3611
|
+
schemaVersion: 2,
|
|
3602
3612
|
portfolioId: options.manifest.portfolioId,
|
|
3603
3613
|
generatedAt: options.generatedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
3604
|
-
privacy: "Names, paths, counts, and configuration
|
|
3614
|
+
privacy: "Names, paths, counts, scopes, and configuration origins only. Secret values, environment values, MCP commands, arguments, URLs, headers, and MCP environment maps are never retained or rendered.",
|
|
3605
3615
|
secretsRoot: inspectSecretsRoot(secretsRoot),
|
|
3606
|
-
|
|
3616
|
+
hostEnvironment: inspectHostEnvironment(homeDir, grokVersion),
|
|
3607
3617
|
skillRegistry,
|
|
3608
3618
|
technologyGovernance: {
|
|
3609
3619
|
strategySource: options.manifest.technologyGovernance?.strategySource,
|
|
@@ -3643,17 +3653,21 @@ function collectEndpoints(manifest) {
|
|
|
3643
3653
|
return true;
|
|
3644
3654
|
});
|
|
3645
3655
|
}
|
|
3646
|
-
function inspectRepository(endpoint, role, secretsRoot, expectedPackageVersion, technologyGovernance) {
|
|
3656
|
+
function inspectRepository(endpoint, role, secretsRoot, homeDir, expectedPackageVersion, technologyGovernance, grokVersion) {
|
|
3647
3657
|
const root = endpoint.path;
|
|
3648
3658
|
const git = inspectGit2(root);
|
|
3649
3659
|
const entries = inspectEntries(root);
|
|
3650
|
-
const
|
|
3660
|
+
const grokInspection = inspectGrokProject(root, homeDir, grokVersion);
|
|
3661
|
+
const skills = inspectSkills(root, grokInspection);
|
|
3651
3662
|
const hooks = inspectHooks(root);
|
|
3652
3663
|
const docs = inspectDocs(root, role === "execution-engine" ? void 0 : expectedPackageVersion);
|
|
3653
3664
|
const mcp = {
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3665
|
+
codexProject: tomlMcpNames(join20(root, ".codex/config.toml")),
|
|
3666
|
+
claudeCodeProjectShared: jsonObjectKeys(join20(root, ".mcp.json"), "mcpServers"),
|
|
3667
|
+
claudeCodeProjectLocal: claudeProjectLocalMcpNames(homeDir, root),
|
|
3668
|
+
grokProject: tomlMcpNames(join20(root, ".grok/config.toml")),
|
|
3669
|
+
grokEffective: grokInspection.effectiveMcp,
|
|
3670
|
+
grokInspection: grokInspection.inspection
|
|
3657
3671
|
};
|
|
3658
3672
|
const secrets = inspectRepositorySecrets(root, endpoint.id, secretsRoot, git.isRepository, endpoint.environmentPolicy);
|
|
3659
3673
|
const projectModel = inspectProjectModel(root, endpoint, technologyGovernance);
|
|
@@ -3694,7 +3708,7 @@ function inspectRepository(endpoint, role, secretsRoot, expectedPackageVersion,
|
|
|
3694
3708
|
role,
|
|
3695
3709
|
path: root,
|
|
3696
3710
|
profile: "profile" in endpoint ? endpoint.profile : void 0,
|
|
3697
|
-
status: deriveStatus(entries, git, hooks, skills, secrets, projectModel, Boolean(technologyGovernance)),
|
|
3711
|
+
status: deriveStatus(role, entries, git, hooks, skills, secrets, docs, projectModel, Boolean(technologyGovernance)),
|
|
3698
3712
|
recommendations,
|
|
3699
3713
|
git,
|
|
3700
3714
|
entries,
|
|
@@ -3706,13 +3720,15 @@ function inspectRepository(endpoint, role, secretsRoot, expectedPackageVersion,
|
|
|
3706
3720
|
projectModel
|
|
3707
3721
|
};
|
|
3708
3722
|
}
|
|
3709
|
-
function deriveStatus(entries, git, hooks, skills, secrets, projectModel, technologyGovernanceConfigured) {
|
|
3723
|
+
function deriveStatus(role, entries, git, hooks, skills, secrets, docs, projectModel, technologyGovernanceConfigured) {
|
|
3710
3724
|
if (!git.isRepository || entries.agents === "missing" || entries.claude === "dangling-symlink" || entries.gemini === "dangling-symlink" || skills.canonical.some((item) => item.kind === "dangling-symlink") || secrets.repositoryEnvFiles.some((file) => file.tracked && !file.template && !file.fixture) || hasUnsafeCentralSecretPermissions(secrets)) return "unhealthy";
|
|
3711
3725
|
const missingBaseline = projectModel.baseline.some((technology) => !technology.detected && !hasBaselineException(projectModel, technology.id));
|
|
3712
3726
|
const missingSelected = projectModel.optionalCapabilities.some((technology) => technology.selected && !technology.detected);
|
|
3713
3727
|
const liveEnv = secrets.repositoryEnvFiles.filter((file) => !file.template && !file.fixture);
|
|
3714
3728
|
const secretMaterializationNeedsReview = liveEnv.some((file) => !file.centralized && !file.localOnly);
|
|
3715
|
-
|
|
3729
|
+
const packageVersionNeedsReview = docs.packages.expected !== void 0 && !docs.packages.aligned;
|
|
3730
|
+
const targetManifestMissing = role === "target" && !docs.manifest;
|
|
3731
|
+
if (entries.agents !== "pgs-router" || entries.claude !== "agents-symlink" || hasWorkflowReminderHooks(hooks) || skills.claudeCompatibility === "duplicate-directory" || skills.claudeCompatibility === "dangling-symlink" || git.branches.length > 1 || git.worktrees.length > 1 || git.dirtyPaths.length > 0 || (git.ahead ?? 0) > 0 || secretMaterializationNeedsReview || technologyGovernanceConfigured && !projectModel.projectType || missingBaseline || missingSelected || packageVersionNeedsReview || targetManifestMissing) return "attention";
|
|
3716
3732
|
return "healthy";
|
|
3717
3733
|
}
|
|
3718
3734
|
function inspectProjectModel(root, endpoint, governance) {
|
|
@@ -3819,7 +3835,7 @@ function inspectOptionalEntry(root, filename, agentsPath) {
|
|
|
3819
3835
|
const content = safeRead(path);
|
|
3820
3836
|
return /AGENTS\.md/.test(content) && content.length < 2e3 ? "thin-adapter" : "custom";
|
|
3821
3837
|
}
|
|
3822
|
-
function inspectSkills(root) {
|
|
3838
|
+
function inspectSkills(root, grokInspection) {
|
|
3823
3839
|
const lock = readJson3(join20(root, ".pro-gov/assets.lock.json"));
|
|
3824
3840
|
const managed = /* @__PURE__ */ new Set();
|
|
3825
3841
|
const bundleIds = stringArray(isRecord3(lock) ? lock.bundleIds : void 0);
|
|
@@ -3855,6 +3871,12 @@ function inspectSkills(root) {
|
|
|
3855
3871
|
installed: canonical.filter((item) => item.managed && item.kind !== "dangling-symlink").length,
|
|
3856
3872
|
discoverable: canonical.filter((item) => item.kind !== "dangling-symlink").length,
|
|
3857
3873
|
runtime: "unobservable"
|
|
3874
|
+
},
|
|
3875
|
+
hosts: {
|
|
3876
|
+
codexProject: canonical.filter((item) => item.kind !== "dangling-symlink").length,
|
|
3877
|
+
claudeCodeProject: inspectSkillRoot(join20(root, ".claude/skills")).names.length,
|
|
3878
|
+
grokNativeProject: inspectSkillRoot(join20(root, ".grok/skills")).names.length,
|
|
3879
|
+
grokEffective: grokInspection.skills
|
|
3858
3880
|
}
|
|
3859
3881
|
};
|
|
3860
3882
|
}
|
|
@@ -3965,13 +3987,147 @@ function hasUnsafeCentralSecretPermissions(secrets) {
|
|
|
3965
3987
|
function inspectSecretsRoot(path) {
|
|
3966
3988
|
return existsSync21(path) ? { path, exists: true, mode: modeString(statSync4(path).mode) } : { path, exists: false };
|
|
3967
3989
|
}
|
|
3968
|
-
function
|
|
3969
|
-
|
|
3990
|
+
function inspectHostEnvironment(homeDir, grokVersion) {
|
|
3991
|
+
const codexConfig = join20(homeDir, ".codex/config.toml");
|
|
3992
|
+
const claudeConfig = join20(homeDir, ".claude.json");
|
|
3993
|
+
const grokConfig = join20(homeDir, ".grok/config.toml");
|
|
3970
3994
|
return {
|
|
3971
|
-
|
|
3972
|
-
|
|
3995
|
+
mcp: {
|
|
3996
|
+
codexUser: { path: codexConfig, names: tomlMcpNames(codexConfig) },
|
|
3997
|
+
claudeCodeUser: { path: claudeConfig, names: jsonObjectKeys(claudeConfig, "mcpServers") },
|
|
3998
|
+
grokUser: { path: grokConfig, names: tomlMcpNames(grokConfig) }
|
|
3999
|
+
},
|
|
4000
|
+
skills: {
|
|
4001
|
+
codexUser: inspectSkillRoot(join20(homeDir, ".agents/skills")),
|
|
4002
|
+
claudeCodeUser: inspectSkillRoot(join20(homeDir, ".claude/skills")),
|
|
4003
|
+
grokUser: inspectSkillRoot(join20(homeDir, ".grok/skills")),
|
|
4004
|
+
grokAgentsCompatibility: inspectSkillRoot(join20(homeDir, ".agents/skills")),
|
|
4005
|
+
grokClaudeCompatibility: inspectSkillRoot(join20(homeDir, ".claude/skills"))
|
|
4006
|
+
},
|
|
4007
|
+
grok: {
|
|
4008
|
+
available: grokVersion !== void 0,
|
|
4009
|
+
...grokVersion ? { version: grokVersion } : {},
|
|
4010
|
+
inspectionCommand: "grok inspect --json"
|
|
4011
|
+
}
|
|
3973
4012
|
};
|
|
3974
4013
|
}
|
|
4014
|
+
function inspectSkillRoot(path) {
|
|
4015
|
+
const exists = pathLexists(path) && safeIsDirectory(path);
|
|
4016
|
+
const names = exists ? safeReadDir(path).filter((name) => !name.startsWith(".") && existsSync21(join20(path, name, "SKILL.md"))) : [];
|
|
4017
|
+
return { path, exists, names };
|
|
4018
|
+
}
|
|
4019
|
+
function claudeProjectLocalMcpNames(homeDir, root) {
|
|
4020
|
+
if (!homeDir) return [];
|
|
4021
|
+
const value = readJson3(join20(homeDir, ".claude.json"));
|
|
4022
|
+
if (!isRecord3(value) || !isRecord3(value.projects)) return [];
|
|
4023
|
+
const candidates = new Set([resolve4(root), safeRealpath(root)].filter((path) => Boolean(path)));
|
|
4024
|
+
const names = /* @__PURE__ */ new Set();
|
|
4025
|
+
for (const [path, project] of Object.entries(value.projects)) {
|
|
4026
|
+
const projectPaths = [resolve4(path), safeRealpath(path)].filter((candidate) => Boolean(candidate));
|
|
4027
|
+
if (!projectPaths.some((candidate) => candidates.has(candidate)) || !isRecord3(project) || !isRecord3(project.mcpServers)) continue;
|
|
4028
|
+
for (const name of Object.keys(project.mcpServers)) names.add(name);
|
|
4029
|
+
}
|
|
4030
|
+
return [...names].sort();
|
|
4031
|
+
}
|
|
4032
|
+
function commandVersion(command2) {
|
|
4033
|
+
try {
|
|
4034
|
+
const output = execFileSync(command2, ["version"], {
|
|
4035
|
+
encoding: "utf8",
|
|
4036
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
4037
|
+
timeout: 3e3
|
|
4038
|
+
});
|
|
4039
|
+
return output.match(/\b\d+\.\d+\.\d+\b/)?.[0];
|
|
4040
|
+
} catch {
|
|
4041
|
+
return void 0;
|
|
4042
|
+
}
|
|
4043
|
+
}
|
|
4044
|
+
function inspectGrokProject(root, homeDir, grokVersion) {
|
|
4045
|
+
const empty = (inspection) => ({
|
|
4046
|
+
inspection,
|
|
4047
|
+
effectiveMcp: [],
|
|
4048
|
+
skills: { inspection, total: 0, project: 0, userOrCompatible: 0, plugin: 0, bundled: 0 },
|
|
4049
|
+
claudeCompatibility: "unknown",
|
|
4050
|
+
cursorCompatibility: "unknown"
|
|
4051
|
+
});
|
|
4052
|
+
if (!grokVersion) return empty("unavailable");
|
|
4053
|
+
try {
|
|
4054
|
+
const value = JSON.parse(execFileSync("grok", ["inspect", "--json"], {
|
|
4055
|
+
cwd: root,
|
|
4056
|
+
encoding: "utf8",
|
|
4057
|
+
env: { ...process.env, HOME: homeDir, GROK_HOME: join20(homeDir, ".grok") },
|
|
4058
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
4059
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
4060
|
+
timeout: 8e3
|
|
4061
|
+
}));
|
|
4062
|
+
if (!isRecord3(value)) return empty("failed");
|
|
4063
|
+
const userClaudeNames = new Set(jsonObjectKeys(join20(homeDir, ".claude.json"), "mcpServers"));
|
|
4064
|
+
const localClaudeNames = new Set(claudeProjectLocalMcpNames(homeDir, root));
|
|
4065
|
+
const effectiveMcp = Array.isArray(value.mcpServers) ? value.mcpServers.flatMap((item) => {
|
|
4066
|
+
if (!isRecord3(item) || typeof item.name !== "string") return [];
|
|
4067
|
+
const source = isRecord3(item.source) ? item.source : {};
|
|
4068
|
+
const sourceType = typeof source.type === "string" ? source.type : "unknown";
|
|
4069
|
+
const sourcePath = typeof source.path === "string" ? source.path : void 0;
|
|
4070
|
+
const vendor = typeof item.vendor === "string" ? item.vendor : sourceType;
|
|
4071
|
+
return [{
|
|
4072
|
+
name: item.name,
|
|
4073
|
+
vendor,
|
|
4074
|
+
scope: inferGrokMcpScope(item.name, sourceType, sourcePath, root, homeDir, userClaudeNames, localClaudeNames),
|
|
4075
|
+
sourceType,
|
|
4076
|
+
...sourcePath ? { sourcePath } : {}
|
|
4077
|
+
}];
|
|
4078
|
+
}) : [];
|
|
4079
|
+
const skillCounts = { project: 0, userOrCompatible: 0, plugin: 0, bundled: 0 };
|
|
4080
|
+
if (Array.isArray(value.skills)) {
|
|
4081
|
+
for (const item of value.skills) {
|
|
4082
|
+
if (!isRecord3(item) || !isRecord3(item.source)) continue;
|
|
4083
|
+
const type = item.source.type;
|
|
4084
|
+
const path = item.source.path;
|
|
4085
|
+
if (typeof path === "string" && path.includes(sep + ".grok" + sep + "bundled" + sep)) skillCounts.bundled += 1;
|
|
4086
|
+
else if (type === "project") skillCounts.project += 1;
|
|
4087
|
+
else if (type === "plugin") skillCounts.plugin += 1;
|
|
4088
|
+
else skillCounts.userOrCompatible += 1;
|
|
4089
|
+
}
|
|
4090
|
+
}
|
|
4091
|
+
const cells = isRecord3(value.externalCompat) && Array.isArray(value.externalCompat.cells) ? value.externalCompat.cells : [];
|
|
4092
|
+
return {
|
|
4093
|
+
inspection: "ok",
|
|
4094
|
+
effectiveMcp,
|
|
4095
|
+
skills: {
|
|
4096
|
+
inspection: "ok",
|
|
4097
|
+
total: skillCounts.project + skillCounts.userOrCompatible + skillCounts.plugin + skillCounts.bundled,
|
|
4098
|
+
...skillCounts
|
|
4099
|
+
},
|
|
4100
|
+
claudeCompatibility: compatibilityEnabled(cells, "claude"),
|
|
4101
|
+
cursorCompatibility: compatibilityEnabled(cells, "cursor")
|
|
4102
|
+
};
|
|
4103
|
+
} catch {
|
|
4104
|
+
return empty("failed");
|
|
4105
|
+
}
|
|
4106
|
+
}
|
|
4107
|
+
function compatibilityEnabled(cells, vendor) {
|
|
4108
|
+
const matching = cells.filter((cell) => isRecord3(cell) && cell.vendor === vendor);
|
|
4109
|
+
if (matching.length === 0) return "unknown";
|
|
4110
|
+
return matching.some((cell) => isRecord3(cell) && cell.enabled === true);
|
|
4111
|
+
}
|
|
4112
|
+
function inferGrokMcpScope(name, sourceType, sourcePath, root, homeDir, userClaudeNames, localClaudeNames) {
|
|
4113
|
+
if (!sourcePath) {
|
|
4114
|
+
if (sourceType === "project") return "project";
|
|
4115
|
+
if (localClaudeNames.has(name)) return "project-local";
|
|
4116
|
+
if (userClaudeNames.has(name)) return "user";
|
|
4117
|
+
return "unknown";
|
|
4118
|
+
}
|
|
4119
|
+
const resolvedSource = safeRealpath(sourcePath) ?? resolve4(sourcePath);
|
|
4120
|
+
const resolvedRoot = safeRealpath(root) ?? resolve4(root);
|
|
4121
|
+
if (resolvedSource === join20(resolvedRoot, ".mcp.json")) return "project-shared";
|
|
4122
|
+
if (resolvedSource.startsWith(resolvedRoot + sep)) return "project";
|
|
4123
|
+
if (homeDir && resolvedSource === join20(resolve4(homeDir), ".claude.json")) {
|
|
4124
|
+
if (localClaudeNames.has(name)) return "project-local";
|
|
4125
|
+
if (userClaudeNames.has(name)) return "user";
|
|
4126
|
+
}
|
|
4127
|
+
if (homeDir && resolvedSource.startsWith(resolve4(homeDir) + sep)) return "user";
|
|
4128
|
+
if (sourceType === "project") return "project";
|
|
4129
|
+
return "unknown";
|
|
4130
|
+
}
|
|
3975
4131
|
var HOOK_EVENT_NAMES = /* @__PURE__ */ new Set([
|
|
3976
4132
|
"PreToolUse",
|
|
3977
4133
|
"PostToolUse",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pieai/pro-gov",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.4",
|
|
4
4
|
"description": "Project-level distribution kit for Project Governance System.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai-agents",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"access": "public"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@pieai/doc-gov": "^0.5.
|
|
38
|
+
"@pieai/doc-gov": "^0.5.4"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@pieai/swimmer-ui-kit": "1.0.1",
|