@pieai/pro-gov 0.4.7 → 0.4.9
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
|
@@ -2824,6 +2824,7 @@ function resolveManifestPaths(value, configDir) {
|
|
|
2824
2824
|
};
|
|
2825
2825
|
return {
|
|
2826
2826
|
...value,
|
|
2827
|
+
technologyGovernance: isRecord(value.technologyGovernance) && typeof value.technologyGovernance.strategySource === "string" && !isAbsolute3(value.technologyGovernance.strategySource) ? { ...value.technologyGovernance, strategySource: resolve3(configDir, value.technologyGovernance.strategySource) } : value.technologyGovernance,
|
|
2827
2828
|
controlPlane: resolveEndpoint(value.controlPlane),
|
|
2828
2829
|
executionEngine: resolveEndpoint(value.executionEngine),
|
|
2829
2830
|
targets: Array.isArray(value.targets) ? value.targets.map(resolveEndpoint) : value.targets
|
|
@@ -2857,11 +2858,12 @@ function validatePortfolioManifest(value) {
|
|
|
2857
2858
|
validateAllowedFields(
|
|
2858
2859
|
value,
|
|
2859
2860
|
"root",
|
|
2860
|
-
["schemaVersion", "portfolioId", "controlPlane", "executionEngine", "hostTooling", "targets"],
|
|
2861
|
+
["schemaVersion", "portfolioId", "technologyGovernance", "controlPlane", "executionEngine", "hostTooling", "targets"],
|
|
2861
2862
|
issues
|
|
2862
2863
|
);
|
|
2863
|
-
|
|
2864
|
-
validateEndpoint(value.
|
|
2864
|
+
const technologyCatalog = validateTechnologyGovernance(value.technologyGovernance, issues);
|
|
2865
|
+
validateEndpoint(value.controlPlane, "controlPlane", issues, technologyCatalog);
|
|
2866
|
+
validateEndpoint(value.executionEngine, "executionEngine", issues, technologyCatalog);
|
|
2865
2867
|
validateHostTooling(value.hostTooling, issues);
|
|
2866
2868
|
if (!Array.isArray(value.targets)) {
|
|
2867
2869
|
issues.push({
|
|
@@ -2881,8 +2883,8 @@ function validatePortfolioManifest(value) {
|
|
|
2881
2883
|
});
|
|
2882
2884
|
continue;
|
|
2883
2885
|
}
|
|
2884
|
-
validateEndpoint(target, "targets", issues);
|
|
2885
|
-
validateAllowedFields(target, "target", ["id", "path", "profile", "assetBundles"], issues);
|
|
2886
|
+
validateEndpoint(target, "targets", issues, technologyCatalog);
|
|
2887
|
+
validateAllowedFields(target, "target", ["id", "path", "profile", "assetBundles", "projectType", "capabilities", "boundary", "technologyExceptions"], issues);
|
|
2886
2888
|
if (typeof target.id === "string") {
|
|
2887
2889
|
if (seenTargetIds.has(target.id)) {
|
|
2888
2890
|
issues.push({
|
|
@@ -2928,7 +2930,7 @@ function validateAllowedFields(value, location, allowedFields, issues) {
|
|
|
2928
2930
|
});
|
|
2929
2931
|
}
|
|
2930
2932
|
}
|
|
2931
|
-
function validateEndpoint(value, field, issues) {
|
|
2933
|
+
function validateEndpoint(value, field, issues, technologyCatalog) {
|
|
2932
2934
|
if (value === void 0) return;
|
|
2933
2935
|
if (!isRecord(value)) {
|
|
2934
2936
|
issues.push({
|
|
@@ -2938,6 +2940,12 @@ function validateEndpoint(value, field, issues) {
|
|
|
2938
2940
|
});
|
|
2939
2941
|
return;
|
|
2940
2942
|
}
|
|
2943
|
+
validateAllowedFields(
|
|
2944
|
+
value,
|
|
2945
|
+
field,
|
|
2946
|
+
field === "targets" ? ["id", "path", "profile", "assetBundles", "projectType", "capabilities", "boundary", "technologyExceptions"] : ["id", "path", "projectType", "capabilities", "boundary", "technologyExceptions"],
|
|
2947
|
+
issues
|
|
2948
|
+
);
|
|
2941
2949
|
if (typeof value.id !== "string" || value.id.length === 0) {
|
|
2942
2950
|
issues.push({
|
|
2943
2951
|
type: "invalid-field",
|
|
@@ -2962,6 +2970,7 @@ function validateEndpoint(value, field, issues) {
|
|
|
2962
2970
|
message: `Portfolio ${field} path does not exist: ${value.path}`
|
|
2963
2971
|
});
|
|
2964
2972
|
}
|
|
2973
|
+
validateRepositoryGovernance(value, field, technologyCatalog, issues);
|
|
2965
2974
|
}
|
|
2966
2975
|
function validateOptionalStringArray(value, id, field, issues) {
|
|
2967
2976
|
if (value === void 0) return;
|
|
@@ -2974,6 +2983,118 @@ function validateOptionalStringArray(value, id, field, issues) {
|
|
|
2974
2983
|
});
|
|
2975
2984
|
}
|
|
2976
2985
|
}
|
|
2986
|
+
function validateTechnologyGovernance(value, issues) {
|
|
2987
|
+
const technologies = /* @__PURE__ */ new Set();
|
|
2988
|
+
const projectTypes = /* @__PURE__ */ new Map();
|
|
2989
|
+
if (value === void 0) return { technologies, projectTypes };
|
|
2990
|
+
if (!isRecord(value)) {
|
|
2991
|
+
issues.push({ type: "invalid-field", field: "technologyGovernance", message: "Portfolio technologyGovernance must be an object." });
|
|
2992
|
+
return { technologies, projectTypes };
|
|
2993
|
+
}
|
|
2994
|
+
validateAllowedFields(value, "technologyGovernance", ["strategySource", "technologies", "projectTypes"], issues);
|
|
2995
|
+
if (value.strategySource !== void 0 && (typeof value.strategySource !== "string" || value.strategySource.length === 0)) {
|
|
2996
|
+
issues.push({ type: "invalid-field", field: "technologyGovernance.strategySource", message: "Technology strategySource must be a non-empty string." });
|
|
2997
|
+
}
|
|
2998
|
+
if (!Array.isArray(value.technologies)) {
|
|
2999
|
+
issues.push({ type: "invalid-field", field: "technologyGovernance.technologies", message: "Technology definitions must be an array." });
|
|
3000
|
+
} else {
|
|
3001
|
+
for (const technology of value.technologies) {
|
|
3002
|
+
if (!isRecord(technology)) {
|
|
3003
|
+
issues.push({ type: "invalid-field", field: "technologyGovernance.technologies", message: "Technology definition must be an object." });
|
|
3004
|
+
continue;
|
|
3005
|
+
}
|
|
3006
|
+
validateAllowedFields(technology, "technology", ["id", "label", "packages", "files"], issues);
|
|
3007
|
+
if (typeof technology.id !== "string" || technology.id.length === 0 || technologies.has(technology.id)) {
|
|
3008
|
+
issues.push({ type: "invalid-field", field: "technologyGovernance.technologies.id", message: `Technology id must be non-empty and unique: ${String(technology.id)}` });
|
|
3009
|
+
} else technologies.add(technology.id);
|
|
3010
|
+
if (typeof technology.label !== "string" || technology.label.length === 0) {
|
|
3011
|
+
issues.push({ type: "invalid-field", field: "technologyGovernance.technologies.label", message: "Technology label must be a non-empty string." });
|
|
3012
|
+
}
|
|
3013
|
+
validateOptionalStringArray(technology.packages, technology.id, "technology.packages", issues);
|
|
3014
|
+
validateOptionalStringArray(technology.files, technology.id, "technology.files", issues);
|
|
3015
|
+
if (Array.isArray(technology.files)) {
|
|
3016
|
+
for (const file of technology.files) {
|
|
3017
|
+
if (typeof file === "string" && !isRepositoryRelativePath(file)) {
|
|
3018
|
+
issues.push({
|
|
3019
|
+
type: "invalid-field",
|
|
3020
|
+
id: typeof technology.id === "string" ? technology.id : void 0,
|
|
3021
|
+
field: "technology.files",
|
|
3022
|
+
message: `Technology file signal must stay inside the repository: ${file}`
|
|
3023
|
+
});
|
|
3024
|
+
}
|
|
3025
|
+
}
|
|
3026
|
+
}
|
|
3027
|
+
}
|
|
3028
|
+
}
|
|
3029
|
+
if (!Array.isArray(value.projectTypes)) {
|
|
3030
|
+
issues.push({ type: "invalid-field", field: "technologyGovernance.projectTypes", message: "Project type definitions must be an array." });
|
|
3031
|
+
} else {
|
|
3032
|
+
for (const projectType of value.projectTypes) {
|
|
3033
|
+
if (!isRecord(projectType)) {
|
|
3034
|
+
issues.push({ type: "invalid-field", field: "technologyGovernance.projectTypes", message: "Project type definition must be an object." });
|
|
3035
|
+
continue;
|
|
3036
|
+
}
|
|
3037
|
+
validateAllowedFields(projectType, "projectType", ["id", "title", "summary", "baseline", "optionalCapabilities"], issues);
|
|
3038
|
+
const id = typeof projectType.id === "string" ? projectType.id : "";
|
|
3039
|
+
if (!id || projectTypes.has(id)) {
|
|
3040
|
+
issues.push({ type: "invalid-field", field: "technologyGovernance.projectTypes.id", message: `Project type id must be non-empty and unique: ${String(projectType.id)}` });
|
|
3041
|
+
}
|
|
3042
|
+
if (typeof projectType.title !== "string" || typeof projectType.summary !== "string") {
|
|
3043
|
+
issues.push({ type: "invalid-field", field: "technologyGovernance.projectTypes", message: `Project type ${id || "(unknown)"} requires title and summary.` });
|
|
3044
|
+
}
|
|
3045
|
+
validateOptionalStringArray(projectType.baseline, id, "projectType.baseline", issues);
|
|
3046
|
+
validateOptionalStringArray(projectType.optionalCapabilities, id, "projectType.optionalCapabilities", issues);
|
|
3047
|
+
if (id && typeof projectType.title === "string" && typeof projectType.summary === "string" && Array.isArray(projectType.baseline) && Array.isArray(projectType.optionalCapabilities)) {
|
|
3048
|
+
projectTypes.set(id, projectType);
|
|
3049
|
+
}
|
|
3050
|
+
}
|
|
3051
|
+
}
|
|
3052
|
+
for (const projectType of projectTypes.values()) {
|
|
3053
|
+
for (const technology of [...projectType.baseline, ...projectType.optionalCapabilities]) {
|
|
3054
|
+
if (!technologies.has(technology)) issues.push({ type: "invalid-field", field: "technologyGovernance.projectTypes", message: `Project type ${projectType.id} references unknown technology: ${technology}` });
|
|
3055
|
+
}
|
|
3056
|
+
}
|
|
3057
|
+
return { technologies, projectTypes };
|
|
3058
|
+
}
|
|
3059
|
+
function isRepositoryRelativePath(value) {
|
|
3060
|
+
if (value.length === 0 || isAbsolute3(value)) return false;
|
|
3061
|
+
const segments = value.replaceAll("\\", "/").split("/");
|
|
3062
|
+
return !segments.includes("..");
|
|
3063
|
+
}
|
|
3064
|
+
function validateRepositoryGovernance(value, field, catalog, issues) {
|
|
3065
|
+
const id = typeof value.id === "string" ? value.id : void 0;
|
|
3066
|
+
if (value.projectType !== void 0 && (typeof value.projectType !== "string" || !catalog.projectTypes.has(value.projectType))) {
|
|
3067
|
+
issues.push({ type: "invalid-field", id, field: `${field}.projectType`, message: `Repository projectType is not defined: ${String(value.projectType)}` });
|
|
3068
|
+
}
|
|
3069
|
+
validateOptionalStringArray(value.capabilities, id, "capabilities", issues);
|
|
3070
|
+
if (Array.isArray(value.capabilities) && typeof value.projectType === "string") {
|
|
3071
|
+
const allowed = new Set(catalog.projectTypes.get(value.projectType)?.optionalCapabilities ?? []);
|
|
3072
|
+
for (const capability of value.capabilities) {
|
|
3073
|
+
if (typeof capability === "string" && !allowed.has(capability)) issues.push({ type: "invalid-field", id, field: `${field}.capabilities`, message: `Capability ${capability} is not optional for project type ${value.projectType}.` });
|
|
3074
|
+
}
|
|
3075
|
+
}
|
|
3076
|
+
if (value.boundary !== void 0) {
|
|
3077
|
+
if (!isRecord(value.boundary)) issues.push({ type: "invalid-field", id, field: `${field}.boundary`, message: "Repository boundary must be an object." });
|
|
3078
|
+
else {
|
|
3079
|
+
validateAllowedFields(value.boundary, "boundary", ["owns", "doesNotOwn"], issues);
|
|
3080
|
+
validateOptionalStringArray(value.boundary.owns, id, "boundary.owns", issues);
|
|
3081
|
+
validateOptionalStringArray(value.boundary.doesNotOwn, id, "boundary.doesNotOwn", issues);
|
|
3082
|
+
}
|
|
3083
|
+
}
|
|
3084
|
+
if (value.technologyExceptions !== void 0) {
|
|
3085
|
+
if (!Array.isArray(value.technologyExceptions)) issues.push({ type: "invalid-field", id, field: `${field}.technologyExceptions`, message: "Technology exceptions must be an array." });
|
|
3086
|
+
else for (const exception of value.technologyExceptions) {
|
|
3087
|
+
if (!isRecord(exception)) {
|
|
3088
|
+
issues.push({ type: "invalid-field", id, field: `${field}.technologyExceptions`, message: "Technology exception must be an object." });
|
|
3089
|
+
continue;
|
|
3090
|
+
}
|
|
3091
|
+
validateAllowedFields(exception, "technologyException", ["technology", "classification", "reason"], issues);
|
|
3092
|
+
if (typeof exception.technology !== "string" || !catalog.technologies.has(exception.technology)) issues.push({ type: "invalid-field", id, field: `${field}.technologyExceptions.technology`, message: `Technology exception references unknown technology: ${String(exception.technology)}` });
|
|
3093
|
+
if (!["acceptable-exception", "observe", "scheduled-migration", "urgent-drift", "bad-standard"].includes(String(exception.classification))) issues.push({ type: "invalid-field", id, field: `${field}.technologyExceptions.classification`, message: `Unsupported technology exception classification: ${String(exception.classification)}` });
|
|
3094
|
+
if (typeof exception.reason !== "string" || exception.reason.length === 0) issues.push({ type: "invalid-field", id, field: `${field}.technologyExceptions.reason`, message: "Technology exception reason must be non-empty." });
|
|
3095
|
+
}
|
|
3096
|
+
}
|
|
3097
|
+
}
|
|
2977
3098
|
function validateHostTooling(value, issues) {
|
|
2978
3099
|
if (value === void 0) return;
|
|
2979
3100
|
if (!Array.isArray(value)) {
|
|
@@ -3399,7 +3520,7 @@ function inspectPortfolioAiHealth(options) {
|
|
|
3399
3520
|
const executionEngineRoot = options.manifest.executionEngine?.path;
|
|
3400
3521
|
const skillRegistry = inspectSkillRegistry(executionEngineRoot);
|
|
3401
3522
|
const expectedPackageVersion = packageVersion(join20(executionEngineRoot ?? "", "packages/pro-gov/package.json"));
|
|
3402
|
-
const repositories = endpoints.map(({ endpoint, role }) => inspectRepository(endpoint, role, secretsRoot, expectedPackageVersion));
|
|
3523
|
+
const repositories = endpoints.map(({ endpoint, role }) => inspectRepository(endpoint, role, secretsRoot, expectedPackageVersion, options.manifest.technologyGovernance));
|
|
3403
3524
|
const summary = { healthy: 0, attention: 0, unhealthy: 0 };
|
|
3404
3525
|
for (const repository of repositories) summary[repository.status] += 1;
|
|
3405
3526
|
return {
|
|
@@ -3410,6 +3531,11 @@ function inspectPortfolioAiHealth(options) {
|
|
|
3410
3531
|
secretsRoot: inspectSecretsRoot(secretsRoot),
|
|
3411
3532
|
userMcp: inspectUserMcp(homeDir),
|
|
3412
3533
|
skillRegistry,
|
|
3534
|
+
technologyGovernance: {
|
|
3535
|
+
strategySource: options.manifest.technologyGovernance?.strategySource,
|
|
3536
|
+
projectTypes: options.manifest.technologyGovernance?.projectTypes.length ?? 0,
|
|
3537
|
+
technologies: options.manifest.technologyGovernance?.technologies.length ?? 0
|
|
3538
|
+
},
|
|
3413
3539
|
summary,
|
|
3414
3540
|
repositories
|
|
3415
3541
|
};
|
|
@@ -3443,19 +3569,20 @@ function collectEndpoints(manifest) {
|
|
|
3443
3569
|
return true;
|
|
3444
3570
|
});
|
|
3445
3571
|
}
|
|
3446
|
-
function inspectRepository(endpoint, role, secretsRoot, expectedPackageVersion) {
|
|
3572
|
+
function inspectRepository(endpoint, role, secretsRoot, expectedPackageVersion, technologyGovernance) {
|
|
3447
3573
|
const root = endpoint.path;
|
|
3448
3574
|
const git = inspectGit2(root);
|
|
3449
3575
|
const entries = inspectEntries(root);
|
|
3450
3576
|
const skills = inspectSkills(root);
|
|
3451
3577
|
const hooks = inspectHooks(root);
|
|
3452
|
-
const docs = inspectDocs(root, expectedPackageVersion);
|
|
3578
|
+
const docs = inspectDocs(root, role === "execution-engine" ? void 0 : expectedPackageVersion);
|
|
3453
3579
|
const mcp = {
|
|
3454
3580
|
root: jsonObjectKeys(join20(root, ".mcp.json"), "mcpServers"),
|
|
3455
3581
|
claudeCode: jsonObjectKeys(join20(root, ".claude/settings.json"), "mcpServers"),
|
|
3456
3582
|
codex: tomlMcpNames(join20(root, ".codex/config.toml"))
|
|
3457
3583
|
};
|
|
3458
3584
|
const secrets = inspectRepositorySecrets(root, endpoint.id, secretsRoot, git.isRepository);
|
|
3585
|
+
const projectModel = inspectProjectModel(root, endpoint, technologyGovernance);
|
|
3459
3586
|
const recommendations = [];
|
|
3460
3587
|
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");
|
|
3461
3588
|
if (git.unmergedBranches.length > 0) recommendations.push(`\u6709 ${git.unmergedBranches.length} \u6761\u5206\u652F\u5C1A\u672A\u5408\u5165\u5F53\u524D HEAD\uFF1A${git.unmergedBranches.join(", ")}\u3002`);
|
|
@@ -3474,17 +3601,23 @@ function inspectRepository(endpoint, role, secretsRoot, expectedPackageVersion)
|
|
|
3474
3601
|
if (skills.canonical.some((skill) => skill.kind === "dangling-symlink")) recommendations.push("`.agents/skills` \u4E2D\u5B58\u5728\u65AD\u5F00\u7684\u6280\u80FD\u94FE\u63A5\u3002");
|
|
3475
3602
|
if (skills.claudeCompatibility === "duplicate-directory") recommendations.push("`.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");
|
|
3476
3603
|
if (skills.claudeCompatibility === "dangling-symlink") recommendations.push("`.claude/skills` \u662F\u65AD\u5F00\u7684\u94FE\u63A5\u3002");
|
|
3604
|
+
if (hasWorkflowReminderHooks(hooks)) recommendations.push("\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");
|
|
3477
3605
|
const liveEnv = secrets.repositoryEnvFiles.filter((file) => !file.template);
|
|
3478
3606
|
if (liveEnv.some((file) => file.tracked)) 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");
|
|
3479
3607
|
else if (liveEnv.length > 0 && secrets.centralDirectory === "absent") recommendations.push("\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");
|
|
3480
3608
|
if (!docs.packages.aligned) recommendations.push(`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`);
|
|
3481
3609
|
if (role === "target" && !docs.manifest) recommendations.push("\u7F3A\u5C11 docs/governance/MANIFEST.yml\uFF1B\u6587\u6863\u6E05\u5355\u65E0\u6CD5\u8BC1\u660E\u5DF2\u540C\u6B65\u3002");
|
|
3610
|
+
if (technologyGovernance && !projectModel.projectType) 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");
|
|
3611
|
+
const missingBaseline = projectModel.baseline.filter((technology) => !technology.detected && !hasBaselineException(projectModel, technology.id));
|
|
3612
|
+
if (missingBaseline.length > 0) recommendations.push(`\u6280\u672F\u57FA\u7EBF\u7F3A\u5C11\u53EF\u9A8C\u8BC1\u4FE1\u53F7\uFF1A${missingBaseline.map((technology) => technology.label).join("\u3001")}\u3002`);
|
|
3613
|
+
const missingSelected = projectModel.optionalCapabilities.filter((technology) => technology.selected && !technology.detected);
|
|
3614
|
+
if (missingSelected.length > 0) recommendations.push(`\u5DF2\u9009\u80FD\u529B\u7F3A\u5C11\u53EF\u9A8C\u8BC1\u4FE1\u53F7\uFF1A${missingSelected.map((technology) => technology.label).join("\u3001")}\u3002`);
|
|
3482
3615
|
return {
|
|
3483
3616
|
id: endpoint.id,
|
|
3484
3617
|
role,
|
|
3485
3618
|
path: root,
|
|
3486
3619
|
profile: "profile" in endpoint ? endpoint.profile : void 0,
|
|
3487
|
-
status: deriveStatus(entries, git, skills, secrets),
|
|
3620
|
+
status: deriveStatus(entries, git, hooks, skills, secrets, projectModel, Boolean(technologyGovernance)),
|
|
3488
3621
|
recommendations,
|
|
3489
3622
|
git,
|
|
3490
3623
|
entries,
|
|
@@ -3492,14 +3625,63 @@ function inspectRepository(endpoint, role, secretsRoot, expectedPackageVersion)
|
|
|
3492
3625
|
mcp,
|
|
3493
3626
|
skills,
|
|
3494
3627
|
secrets,
|
|
3495
|
-
docs
|
|
3628
|
+
docs,
|
|
3629
|
+
projectModel
|
|
3496
3630
|
};
|
|
3497
3631
|
}
|
|
3498
|
-
function deriveStatus(entries, git, skills, secrets) {
|
|
3632
|
+
function deriveStatus(entries, git, hooks, skills, secrets, projectModel, technologyGovernanceConfigured) {
|
|
3499
3633
|
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)) return "unhealthy";
|
|
3500
|
-
|
|
3634
|
+
const missingBaseline = projectModel.baseline.some((technology) => !technology.detected && !hasBaselineException(projectModel, technology.id));
|
|
3635
|
+
const missingSelected = projectModel.optionalCapabilities.some((technology) => technology.selected && !technology.detected);
|
|
3636
|
+
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 || technologyGovernanceConfigured && !projectModel.projectType || missingBaseline || missingSelected) return "attention";
|
|
3501
3637
|
return "healthy";
|
|
3502
3638
|
}
|
|
3639
|
+
function inspectProjectModel(root, endpoint, governance) {
|
|
3640
|
+
const projectType = governance?.projectTypes.find((candidate) => candidate.id === endpoint.projectType);
|
|
3641
|
+
const packages = collectPackageNames(root);
|
|
3642
|
+
const technologyById = new Map((governance?.technologies ?? []).map((technology) => [technology.id, technology]));
|
|
3643
|
+
const detection = (id) => {
|
|
3644
|
+
const technology = technologyById.get(id);
|
|
3645
|
+
const packageMatch = technology?.packages?.some((name) => packages.has(name)) ?? false;
|
|
3646
|
+
const fileMatch = technology?.files?.some((path) => existsSync21(join20(root, path))) ?? false;
|
|
3647
|
+
return { id, label: technology?.label ?? id, detected: packageMatch || fileMatch };
|
|
3648
|
+
};
|
|
3649
|
+
const selected = new Set(endpoint.capabilities ?? []);
|
|
3650
|
+
return {
|
|
3651
|
+
projectType: projectType ? { id: projectType.id, title: projectType.title, summary: projectType.summary } : void 0,
|
|
3652
|
+
boundary: endpoint.boundary,
|
|
3653
|
+
baseline: (projectType?.baseline ?? []).map(detection),
|
|
3654
|
+
optionalCapabilities: (projectType?.optionalCapabilities ?? []).map((id) => ({ ...detection(id), selected: selected.has(id) })),
|
|
3655
|
+
exceptions: endpoint.technologyExceptions ?? []
|
|
3656
|
+
};
|
|
3657
|
+
}
|
|
3658
|
+
function hasBaselineException(projectModel, technologyId) {
|
|
3659
|
+
return projectModel.exceptions.some(
|
|
3660
|
+
(exception) => exception.technology === technologyId && exception.classification !== "urgent-drift"
|
|
3661
|
+
);
|
|
3662
|
+
}
|
|
3663
|
+
function collectPackageNames(root) {
|
|
3664
|
+
const names = /* @__PURE__ */ new Set();
|
|
3665
|
+
let files = [];
|
|
3666
|
+
try {
|
|
3667
|
+
files = splitLines(execFileSync("git", ["ls-files", "*package.json"], { cwd: root, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }));
|
|
3668
|
+
} catch {
|
|
3669
|
+
if (existsSync21(join20(root, "package.json"))) files = ["package.json"];
|
|
3670
|
+
}
|
|
3671
|
+
for (const file of files) {
|
|
3672
|
+
const packageJson = readJson3(join20(root, file));
|
|
3673
|
+
if (!isRecord3(packageJson)) continue;
|
|
3674
|
+
for (const section of ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]) {
|
|
3675
|
+
const dependencies = packageJson[section];
|
|
3676
|
+
if (!isRecord3(dependencies)) continue;
|
|
3677
|
+
for (const name of Object.keys(dependencies)) names.add(name);
|
|
3678
|
+
}
|
|
3679
|
+
}
|
|
3680
|
+
return names;
|
|
3681
|
+
}
|
|
3682
|
+
function hasWorkflowReminderHooks(hooks) {
|
|
3683
|
+
return hooks.some((hook) => hook.events.some((event) => event.name === "Stop" || event.name === "SubagentStop"));
|
|
3684
|
+
}
|
|
3503
3685
|
function inspectGit2(root) {
|
|
3504
3686
|
const git = (...args) => {
|
|
3505
3687
|
try {
|
|
@@ -3626,7 +3808,7 @@ function inspectRepositorySecrets(root, id, secretsRoot, isRepository) {
|
|
|
3626
3808
|
repositoryEnvFiles: envFiles
|
|
3627
3809
|
};
|
|
3628
3810
|
}
|
|
3629
|
-
var SKIP_ENV_DIRECTORIES = /* @__PURE__ */ new Set([".git", ".next", ".nuxt", ".output", ".turbo", ".worktrees", "build", "coverage", "dist", "node_modules", "out", "target"]);
|
|
3811
|
+
var SKIP_ENV_DIRECTORIES = /* @__PURE__ */ new Set([".git", ".next", ".nuxt", ".output", ".turbo", ".vercel", ".worktrees", "build", "coverage", "dist", "node_modules", "out", "target"]);
|
|
3630
3812
|
function collectEnvironmentFiles(root, current = root, depth = 0) {
|
|
3631
3813
|
if (depth > 5) return [];
|
|
3632
3814
|
const found = [];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pieai/pro-gov",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.9",
|
|
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.4.
|
|
38
|
+
"@pieai/doc-gov": "^0.4.9"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@pieai/swimmer-ui-kit": "1.0.1",
|