@pieai/pro-gov 0.5.1 → 0.5.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/dist/cli.js CHANGED
@@ -2903,7 +2903,7 @@ function validatePortfolioManifest(value) {
2903
2903
  continue;
2904
2904
  }
2905
2905
  validateEndpoint(target, "targets", issues, technologyCatalog);
2906
- validateAllowedFields(target, "target", ["id", "path", "profile", "assetBundles", "projectType", "capabilities", "boundary", "technologyExceptions"], issues);
2906
+ validateAllowedFields(target, "target", ["id", "path", "profile", "assetBundles", "projectType", "capabilities", "boundary", "technologyExceptions", "environmentPolicy"], issues);
2907
2907
  if (typeof target.id === "string") {
2908
2908
  if (seenTargetIds.has(target.id)) {
2909
2909
  issues.push({
@@ -2962,7 +2962,7 @@ function validateEndpoint(value, field, issues, technologyCatalog) {
2962
2962
  validateAllowedFields(
2963
2963
  value,
2964
2964
  field,
2965
- field === "targets" ? ["id", "path", "profile", "assetBundles", "projectType", "capabilities", "boundary", "technologyExceptions"] : ["id", "path", "projectType", "capabilities", "boundary", "technologyExceptions"],
2965
+ field === "targets" ? ["id", "path", "profile", "assetBundles", "projectType", "capabilities", "boundary", "technologyExceptions", "environmentPolicy"] : ["id", "path", "projectType", "capabilities", "boundary", "technologyExceptions", "environmentPolicy"],
2966
2966
  issues
2967
2967
  );
2968
2968
  if (typeof value.id !== "string" || value.id.length === 0) {
@@ -3080,6 +3080,14 @@ function isRepositoryRelativePath(value) {
3080
3080
  const segments = value.replaceAll("\\", "/").split("/");
3081
3081
  return !segments.includes("..");
3082
3082
  }
3083
+ function isExactRepositoryRelativePath(value) {
3084
+ if (value.length === 0) return false;
3085
+ if (isAbsolute3(value)) return false;
3086
+ if (/^[A-Za-z]:[\\/]/.test(value) || value.startsWith("\\\\") || value.startsWith("//")) return false;
3087
+ if (value.includes("\\")) return false;
3088
+ const segments = value.split("/");
3089
+ return segments.every((segment) => segment.length > 0 && segment !== "." && segment !== "..");
3090
+ }
3083
3091
  function validateRepositoryGovernance(value, field, catalog, issues) {
3084
3092
  const id = typeof value.id === "string" ? value.id : void 0;
3085
3093
  if (value.projectType !== void 0 && (typeof value.projectType !== "string" || !catalog.projectTypes.has(value.projectType))) {
@@ -3113,6 +3121,53 @@ function validateRepositoryGovernance(value, field, catalog, issues) {
3113
3121
  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." });
3114
3122
  }
3115
3123
  }
3124
+ validateEnvironmentPolicy(value.environmentPolicy, field, id, issues);
3125
+ }
3126
+ function validateEnvironmentPolicy(value, field, id, issues) {
3127
+ if (value === void 0) return;
3128
+ if (!isRecord(value)) {
3129
+ issues.push({ type: "invalid-field", id, field: `${field}.environmentPolicy`, message: "Repository environmentPolicy must be an object." });
3130
+ return;
3131
+ }
3132
+ validateAllowedFields(value, "environmentPolicy", ["localOnly"], issues);
3133
+ if (value.localOnly === void 0) return;
3134
+ if (!Array.isArray(value.localOnly)) {
3135
+ issues.push({ type: "invalid-field", id, field: `${field}.environmentPolicy.localOnly`, message: "environmentPolicy.localOnly must be an array." });
3136
+ return;
3137
+ }
3138
+ const seenPaths = /* @__PURE__ */ new Set();
3139
+ for (const entry of value.localOnly) {
3140
+ if (!isRecord(entry)) {
3141
+ issues.push({ type: "invalid-field", id, field: `${field}.environmentPolicy.localOnly`, message: "environmentPolicy.localOnly entry must be an object." });
3142
+ continue;
3143
+ }
3144
+ validateAllowedFields(entry, "environmentPolicy.localOnly", ["path", "reason"], issues);
3145
+ if (typeof entry.path !== "string" || !isExactRepositoryRelativePath(entry.path)) {
3146
+ issues.push({
3147
+ type: "invalid-field",
3148
+ id,
3149
+ field: `${field}.environmentPolicy.localOnly.path`,
3150
+ message: `environmentPolicy.localOnly path must be an exact repository-relative path without absolute, backslash, empty, '.', or '..' segments: ${String(entry.path)}`
3151
+ });
3152
+ } else if (seenPaths.has(entry.path)) {
3153
+ issues.push({
3154
+ type: "invalid-field",
3155
+ id,
3156
+ field: `${field}.environmentPolicy.localOnly.path`,
3157
+ message: `Duplicate environmentPolicy.localOnly path: ${entry.path}`
3158
+ });
3159
+ } else {
3160
+ seenPaths.add(entry.path);
3161
+ }
3162
+ if (typeof entry.reason !== "string" || entry.reason.trim().length === 0) {
3163
+ issues.push({
3164
+ type: "invalid-field",
3165
+ id,
3166
+ field: `${field}.environmentPolicy.localOnly.reason`,
3167
+ message: "environmentPolicy.localOnly reason must be a non-empty string."
3168
+ });
3169
+ }
3170
+ }
3116
3171
  }
3117
3172
  function validateHostTooling(value, issues) {
3118
3173
  if (value === void 0) return;
@@ -3600,7 +3655,7 @@ function inspectRepository(endpoint, role, secretsRoot, expectedPackageVersion,
3600
3655
  claudeCode: jsonObjectKeys(join20(root, ".claude/settings.json"), "mcpServers"),
3601
3656
  codex: tomlMcpNames(join20(root, ".codex/config.toml"))
3602
3657
  };
3603
- const secrets = inspectRepositorySecrets(root, endpoint.id, secretsRoot, git.isRepository);
3658
+ const secrets = inspectRepositorySecrets(root, endpoint.id, secretsRoot, git.isRepository, endpoint.environmentPolicy);
3604
3659
  const projectModel = inspectProjectModel(root, endpoint, technologyGovernance);
3605
3660
  const recommendations = [];
3606
3661
  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");
@@ -3622,9 +3677,10 @@ function inspectRepository(endpoint, role, secretsRoot, expectedPackageVersion,
3622
3677
  if (skills.claudeCompatibility === "dangling-symlink") recommendations.push("`.claude/skills` \u662F\u65AD\u5F00\u7684\u94FE\u63A5\u3002");
3623
3678
  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");
3624
3679
  const liveEnv = secrets.repositoryEnvFiles.filter((file) => !file.template && !file.fixture);
3680
+ const envNeedsCentralReview = liveEnv.filter((file) => !file.localOnly);
3625
3681
  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");
3626
- 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");
3627
- else if (liveEnv.some((file) => !file.centralized)) recommendations.push("\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");
3682
+ if (envNeedsCentralReview.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");
3683
+ else if (envNeedsCentralReview.some((file) => !file.centralized)) recommendations.push("\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");
3628
3684
  if (hasUnsafeCentralSecretPermissions(secrets)) 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");
3629
3685
  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`);
3630
3686
  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");
@@ -3655,7 +3711,7 @@ function deriveStatus(entries, git, hooks, skills, secrets, projectModel, techno
3655
3711
  const missingBaseline = projectModel.baseline.some((technology) => !technology.detected && !hasBaselineException(projectModel, technology.id));
3656
3712
  const missingSelected = projectModel.optionalCapabilities.some((technology) => technology.selected && !technology.detected);
3657
3713
  const liveEnv = secrets.repositoryEnvFiles.filter((file) => !file.template && !file.fixture);
3658
- const secretMaterializationNeedsReview = liveEnv.some((file) => !file.centralized);
3714
+ const secretMaterializationNeedsReview = liveEnv.some((file) => !file.centralized && !file.localOnly);
3659
3715
  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) return "attention";
3660
3716
  return "healthy";
3661
3717
  }
@@ -3816,17 +3872,25 @@ function inspectClaudeSkillRoot(root) {
3816
3872
  }
3817
3873
  return stat.isDirectory() ? "duplicate-directory" : "other";
3818
3874
  }
3819
- function inspectRepositorySecrets(root, id, secretsRoot, isRepository) {
3875
+ function inspectRepositorySecrets(root, id, secretsRoot, isRepository, environmentPolicy) {
3820
3876
  const centralPath = join20(secretsRoot, id);
3821
3877
  const centralRealPath = safeRealpath(centralPath);
3822
- const envFiles = collectEnvironmentFiles(root).map((path) => ({
3823
- path,
3824
- tracked: isRepository ? gitTracks(root, path) : false,
3825
- template: isEnvironmentTemplate(path),
3826
- fixture: isEnvironmentFixture(path),
3827
- symlink: lstatSync6(join20(root, path)).isSymbolicLink(),
3828
- centralized: pointsInside(join20(root, path), centralRealPath)
3829
- }));
3878
+ const localOnlyReasons = new Map(
3879
+ (environmentPolicy?.localOnly ?? []).map((entry) => [entry.path, entry.reason])
3880
+ );
3881
+ const envFiles = collectEnvironmentFiles(root).map((path) => {
3882
+ const localOnlyReason = localOnlyReasons.get(path);
3883
+ return {
3884
+ path,
3885
+ tracked: isRepository ? gitTracks(root, path) : false,
3886
+ template: isEnvironmentTemplate(path),
3887
+ fixture: isEnvironmentFixture(path),
3888
+ symlink: lstatSync6(join20(root, path)).isSymbolicLink(),
3889
+ centralized: pointsInside(join20(root, path), centralRealPath),
3890
+ localOnly: localOnlyReason !== void 0,
3891
+ ...localOnlyReason !== void 0 ? { localOnlyReason } : {}
3892
+ };
3893
+ });
3830
3894
  return {
3831
3895
  centralDirectory: existsSync21(centralPath) ? "present" : "absent",
3832
3896
  centralMode: existsSync21(centralPath) ? modeString(statSync4(centralPath).mode) : void 0,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pieai/pro-gov",
3
- "version": "0.5.1",
3
+ "version": "0.5.2",
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.1"
38
+ "@pieai/doc-gov": "^0.5.2"
39
39
  },
40
40
  "devDependencies": {
41
41
  "@pieai/swimmer-ui-kit": "1.0.1",