@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
- validateEndpoint(value.controlPlane, "controlPlane", issues);
2864
- validateEndpoint(value.executionEngine, "executionEngine", issues);
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
- if (entries.agents !== "pgs-router" || entries.claude !== "agents-symlink" || skills.claudeCompatibility === "duplicate-directory" || skills.claudeCompatibility === "dangling-symlink" || git.branches.length > 1 || git.worktrees.length > 1 || git.dirtyPaths.length > 0 || (git.ahead ?? 0) > 0) return "attention";
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.7",
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.7"
38
+ "@pieai/doc-gov": "^0.4.9"
39
39
  },
40
40
  "devDependencies": {
41
41
  "@pieai/swimmer-ui-kit": "1.0.1",