@pieai/pro-gov 0.7.0 → 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/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 { cpSync, existsSync as existsSync3, mkdirSync, mkdtempSync, readdirSync as readdirSync3, readFileSync as readFileSync2, statSync } from "node:fs";
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(/\u001B\[[0-?]*[ -/]*[@-~]/g, "");
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}`);
@@ -3471,12 +3469,13 @@ function validateVersionPolicy(value, projectTypes, issues) {
3471
3469
  issues.push({ type: "invalid-field", field: "technologyGovernance.versionPolicy", message: "Technology versionPolicy must be an object." });
3472
3470
  return;
3473
3471
  }
3474
- validateAllowedFields(value, "versionPolicy", ["schemaVersion", "packageManager", "runtime", "packages"], issues);
3472
+ validateAllowedFields(value, "versionPolicy", ["schemaVersion", "packageManager", "runtime", "runtimes", "packages"], issues);
3475
3473
  if (value.schemaVersion !== 1) {
3476
3474
  issues.push({ type: "invalid-field", field: "technologyGovernance.versionPolicy.schemaVersion", message: "Technology versionPolicy schemaVersion must be 1." });
3477
3475
  }
3478
3476
  validateVersionRequirement(value.packageManager, "technologyGovernance.versionPolicy.packageManager", issues);
3479
3477
  validateVersionRequirement(value.runtime, "technologyGovernance.versionPolicy.runtime", issues);
3478
+ validateRuntimeRequirements(value.runtimes, projectTypes, issues);
3480
3479
  if (!Array.isArray(value.packages)) {
3481
3480
  issues.push({ type: "invalid-field", field: "technologyGovernance.versionPolicy.packages", message: "Technology versionPolicy packages must be an array." });
3482
3481
  return;
@@ -3507,6 +3506,39 @@ function validateVersionPolicy(value, projectTypes, issues) {
3507
3506
  }
3508
3507
  }
3509
3508
  }
3509
+ function validateRuntimeRequirements(value, projectTypes, issues) {
3510
+ if (value === void 0) return;
3511
+ const field = "technologyGovernance.versionPolicy.runtimes";
3512
+ if (!Array.isArray(value)) {
3513
+ issues.push({ type: "invalid-field", field, message: "Technology versionPolicy runtimes must be an array." });
3514
+ return;
3515
+ }
3516
+ const seen = /* @__PURE__ */ new Set();
3517
+ for (const entry of value) {
3518
+ if (!isRecord(entry)) {
3519
+ issues.push({ type: "invalid-field", field, message: "Version policy runtime entries must be objects." });
3520
+ continue;
3521
+ }
3522
+ validateAllowedFields(entry, "versionPolicy.runtimeEntry", ["name", "version", "appliesTo"], issues);
3523
+ const name = typeof entry.name === "string" ? entry.name : "";
3524
+ if (!name || seen.has(name)) {
3525
+ issues.push({ type: "invalid-field", field: `${field}.name`, message: `Version policy runtime name must be non-empty and unique: ${String(entry.name)}` });
3526
+ } else seen.add(name);
3527
+ if (typeof entry.version !== "string" || !isExactVersion(entry.version)) {
3528
+ issues.push({ type: "invalid-field", field: `${field}.version`, message: `Version policy runtime version must be exact semver: ${String(entry.version)}` });
3529
+ }
3530
+ if (entry.appliesTo !== void 0) {
3531
+ validateOptionalStringArray(entry.appliesTo, name, `${field}.appliesTo`, issues);
3532
+ if (Array.isArray(entry.appliesTo)) {
3533
+ for (const projectType of entry.appliesTo) {
3534
+ if (typeof projectType === "string" && !projectTypes.has(projectType)) {
3535
+ issues.push({ type: "invalid-field", field: `${field}.appliesTo`, message: `Version policy references unknown project type: ${projectType}` });
3536
+ }
3537
+ }
3538
+ }
3539
+ }
3540
+ }
3541
+ }
3510
3542
  function validateVersionRequirement(value, field, issues) {
3511
3543
  if (value === void 0) return;
3512
3544
  if (!isRecord(value)) {
@@ -3697,11 +3729,11 @@ function isRecord(value) {
3697
3729
  }
3698
3730
 
3699
3731
  // src/portfolio/doctor.ts
3700
- import { spawnSync as spawnSync5 } from "node:child_process";
3732
+ import { spawnSync as spawnSync6 } from "node:child_process";
3701
3733
  import { existsSync as existsSync22, readFileSync as readFileSync16 } from "node:fs";
3702
3734
  import { createRequire as createRequire2 } from "node:module";
3703
3735
  import { homedir as homedir3 } from "node:os";
3704
- import { dirname as dirname12, join as join23 } from "node:path";
3736
+ import { dirname as dirname13, join as join23 } from "node:path";
3705
3737
  import { fileURLToPath as fileURLToPath3 } from "node:url";
3706
3738
 
3707
3739
  // src/host-tooling/inventory.ts
@@ -3874,20 +3906,51 @@ function pathIsSymlink(path) {
3874
3906
  }
3875
3907
 
3876
3908
  // src/portfolio/version-policy.ts
3877
- import { existsSync as existsSync21, readFileSync as readFileSync15 } from "node:fs";
3878
- import { join as join22 } from "node:path";
3909
+ import { spawnSync as spawnSync5 } from "node:child_process";
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";
3879
3912
  function inspectVersionPolicy(root, policy, projectType) {
3880
- if (!policy) return { status: "compliant", packages: [], attentionCount: 0 };
3881
- const packageJson = readJson2(join22(root, "package.json"));
3913
+ if (!policy) return { status: "compliant", packages: [], runtimes: [], attentionCount: 0 };
3914
+ const packageManifests = collectPackageManifests(root);
3915
+ const packageJson = packageManifests.find(
3916
+ (manifest) => manifest.path === "package.json"
3917
+ )?.packageJson;
3882
3918
  const packageManager = policy.packageManager ? inspectPackageManager(packageJson, policy.packageManager.name, policy.packageManager.version) : void 0;
3883
3919
  const runtime = policy.runtime ? inspectRuntime(policy.runtime.name, policy.runtime.version) : void 0;
3920
+ const runtimes = (policy.runtimes ?? []).map((requirement) => {
3921
+ if (requirement.appliesTo && (!projectType || !requirement.appliesTo.includes(projectType))) {
3922
+ return {
3923
+ name: requirement.name,
3924
+ expected: requirement.version,
3925
+ status: "not-applicable"
3926
+ };
3927
+ }
3928
+ return inspectRuntime(requirement.name, requirement.version);
3929
+ });
3884
3930
  const packages = policy.packages.map((requirement) => {
3885
3931
  if (requirement.appliesTo && (!projectType || !requirement.appliesTo.includes(projectType))) {
3886
- return { name: requirement.name, expected: requirement.version, status: "not-applicable" };
3932
+ return {
3933
+ name: requirement.name,
3934
+ expected: requirement.version,
3935
+ status: "not-applicable"
3936
+ };
3887
3937
  }
3888
- const declared = findDeclaredVersion(packageJson, requirement.name);
3889
- const installed = readInstalledVersion(root, requirement.name);
3890
- const status = declared === void 0 ? requirement.appliesTo ? "missing" : "not-applicable" : declared !== requirement.version ? "drift" : installed !== requirement.version ? installed === void 0 ? "missing" : "drift" : "compliant";
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";
3891
3954
  return {
3892
3955
  name: requirement.name,
3893
3956
  expected: requirement.version,
@@ -3897,12 +3960,17 @@ function inspectVersionPolicy(root, policy, projectType) {
3897
3960
  status
3898
3961
  };
3899
3962
  });
3900
- const all = [packageManager, runtime, ...packages].filter((item) => item !== void 0);
3901
- const attentionCount = all.filter((item) => item.status !== "compliant" && item.status !== "not-applicable").length;
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;
3902
3969
  return {
3903
3970
  status: attentionCount === 0 ? "compliant" : "attention",
3904
3971
  packageManager,
3905
3972
  runtime,
3973
+ runtimes,
3906
3974
  packages,
3907
3975
  attentionCount
3908
3976
  };
@@ -3919,7 +3987,7 @@ function inspectPackageManager(packageJson, expectedName, expectedVersion) {
3919
3987
  };
3920
3988
  }
3921
3989
  function inspectRuntime(expectedName, expectedVersion) {
3922
- const actual = expectedName === "node" ? process.versions.node : void 0;
3990
+ const actual = readRuntimeVersion(expectedName);
3923
3991
  return {
3924
3992
  name: expectedName,
3925
3993
  expected: expectedVersion,
@@ -3927,16 +3995,86 @@ function inspectRuntime(expectedName, expectedVersion) {
3927
3995
  status: actual === void 0 ? "missing" : actual === expectedVersion ? "compliant" : "drift"
3928
3996
  };
3929
3997
  }
3998
+ function readRuntimeVersion(name) {
3999
+ if (name === "node") return process.versions.node;
4000
+ if (name !== "deno") return void 0;
4001
+ const result = spawnSync5("deno", ["--version"], { encoding: "utf8" });
4002
+ if (result.status !== 0) return void 0;
4003
+ return /^deno\s+(\d+\.\d+\.\d+)/m.exec(result.stdout)?.[1];
4004
+ }
3930
4005
  function findDeclaredVersion(packageJson, name) {
3931
- for (const section of ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]) {
4006
+ for (const section of [
4007
+ "dependencies",
4008
+ "devDependencies",
4009
+ "peerDependencies",
4010
+ "optionalDependencies"
4011
+ ]) {
3932
4012
  const value = packageJson?.[section]?.[name];
3933
4013
  if (typeof value === "string") return value;
3934
4014
  }
3935
4015
  return void 0;
3936
4016
  }
3937
- function readInstalledVersion(root, name) {
3938
- const packageJson = readJson2(join22(root, "node_modules", name, "package.json"));
3939
- return typeof packageJson?.version === "string" ? packageJson.version : void 0;
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)];
3940
4078
  }
3941
4079
  function readJson2(path) {
3942
4080
  if (!existsSync21(path)) return void 0;
@@ -4070,7 +4208,7 @@ function runTargetChecks(target) {
4070
4208
  ];
4071
4209
  return commands.map((command2) => {
4072
4210
  if (!existsSync22(command2.cli)) return { name: command2.name, status: null };
4073
- const result = spawnSync5(process.execPath, [command2.cli, ...command2.args], {
4211
+ const result = spawnSync6(process.execPath, [command2.cli, ...command2.args], {
4074
4212
  cwd: target.path,
4075
4213
  encoding: "utf8",
4076
4214
  timeout: 3e4
@@ -4079,13 +4217,13 @@ function runTargetChecks(target) {
4079
4217
  });
4080
4218
  }
4081
4219
  function inspectGit(path) {
4082
- const inside = spawnSync5("git", ["rev-parse", "--is-inside-work-tree"], {
4220
+ const inside = spawnSync6("git", ["rev-parse", "--is-inside-work-tree"], {
4083
4221
  cwd: path,
4084
4222
  encoding: "utf8"
4085
4223
  });
4086
4224
  if (inside.status !== 0) return { isRepository: false, dirty: false };
4087
- const status = spawnSync5("git", ["status", "--porcelain"], { cwd: path, encoding: "utf8" });
4088
- const branch = spawnSync5("git", ["branch", "--show-current"], { cwd: path, encoding: "utf8" });
4225
+ const status = spawnSync6("git", ["status", "--porcelain"], { cwd: path, encoding: "utf8" });
4226
+ const branch = spawnSync6("git", ["branch", "--show-current"], { cwd: path, encoding: "utf8" });
4089
4227
  return {
4090
4228
  isRepository: true,
4091
4229
  dirty: status.stdout.trim().length > 0,
@@ -4108,11 +4246,11 @@ function getExpectedPackageVersions() {
4108
4246
  };
4109
4247
  }
4110
4248
  function findOwnPackageJson() {
4111
- let current = dirname12(fileURLToPath3(import.meta.url));
4249
+ let current = dirname13(fileURLToPath3(import.meta.url));
4112
4250
  for (let depth = 0; depth < 5; depth += 1) {
4113
4251
  const candidate = join23(current, "package.json");
4114
4252
  if (existsSync22(candidate)) return candidate;
4115
- current = dirname12(current);
4253
+ current = dirname13(current);
4116
4254
  }
4117
4255
  return "";
4118
4256
  }
@@ -4142,13 +4280,13 @@ import {
4142
4280
  lstatSync as lstatSync8,
4143
4281
  mkdirSync as mkdirSync8,
4144
4282
  readFileSync as readFileSync17,
4145
- readdirSync as readdirSync9,
4283
+ readdirSync as readdirSync10,
4146
4284
  realpathSync as realpathSync4,
4147
4285
  statSync as statSync5,
4148
4286
  writeFileSync as writeFileSync7
4149
4287
  } from "node:fs";
4150
4288
  import { homedir as homedir4 } from "node:os";
4151
- import { dirname as dirname13, join as join24, relative as relative8, resolve as resolve5, sep } from "node:path";
4289
+ import { dirname as dirname14, join as join24, relative as relative8, resolve as resolve5, sep } from "node:path";
4152
4290
  import { fileURLToPath as fileURLToPath4 } from "node:url";
4153
4291
  var CURRENT_ROUTER_VERSION = "1.1";
4154
4292
  function inspectPortfolioAiHealth(options) {
@@ -4157,21 +4295,30 @@ function inspectPortfolioAiHealth(options) {
4157
4295
  if (options.targetId && options.targetId !== "all" && endpoints.length === 0) {
4158
4296
  throw new Error(`Unknown portfolio target: ${options.targetId}`);
4159
4297
  }
4160
- const secretsRoot = options.secretsRoot ?? join24(dirname13(options.manifest.controlPlane?.path ?? allEndpoints[0]?.endpoint.path ?? process.cwd()), ".secrets");
4298
+ const secretsRoot = options.secretsRoot ?? join24(
4299
+ dirname14(
4300
+ options.manifest.controlPlane?.path ?? allEndpoints[0]?.endpoint.path ?? process.cwd()
4301
+ ),
4302
+ ".secrets"
4303
+ );
4161
4304
  const homeDir = options.homeDir ?? process.env.HOME ?? homedir4();
4162
4305
  const grokVersion = commandVersion("grok");
4163
4306
  const executionEngineRoot = options.manifest.executionEngine?.path;
4164
4307
  const skillRegistry = inspectSkillRegistry(executionEngineRoot);
4165
- const expectedPackageVersion = packageVersion(join24(executionEngineRoot ?? "", "packages/pro-gov/package.json"));
4166
- const repositories = endpoints.map(({ endpoint, role }) => inspectRepository(
4167
- endpoint,
4168
- role,
4169
- secretsRoot,
4170
- homeDir,
4171
- expectedPackageVersion,
4172
- options.manifest.technologyGovernance,
4173
- grokVersion
4174
- ));
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
+ );
4175
4322
  const summary = { healthy: 0, attention: 0, unhealthy: 0 };
4176
4323
  for (const repository of repositories) summary[repository.status] += 1;
4177
4324
  return {
@@ -4196,6 +4343,8 @@ function inspectPortfolioAiHealth(options) {
4196
4343
  technologyGovernance: {
4197
4344
  strategySource: options.manifest.technologyGovernance?.strategySource,
4198
4345
  versionPolicy: options.manifest.technologyGovernance?.versionPolicy,
4346
+ catalog: options.manifest.technologyGovernance?.technologies ?? [],
4347
+ matrix: buildTechnologyMatrix(options.manifest.technologyGovernance, repositories),
4199
4348
  projectTypes: options.manifest.technologyGovernance?.projectTypes.length ?? 0,
4200
4349
  technologies: options.manifest.technologyGovernance?.technologies.length ?? 0
4201
4350
  },
@@ -4205,7 +4354,8 @@ function inspectPortfolioAiHealth(options) {
4205
4354
  }
4206
4355
  function mergePortfolioAiHealthReport(existing, latest, allRepositoryIds) {
4207
4356
  const repositoriesById = /* @__PURE__ */ new Map();
4208
- for (const repository of existing?.repositories ?? []) repositoriesById.set(repository.id, repository);
4357
+ for (const repository of existing?.repositories ?? [])
4358
+ repositoriesById.set(repository.id, repository);
4209
4359
  for (const repository of latest.repositories) repositoriesById.set(repository.id, repository);
4210
4360
  const repositories = allRepositoryIds.map((id) => repositoriesById.get(id)).filter((repository) => repository !== void 0);
4211
4361
  const coveredIds = /* @__PURE__ */ new Set([
@@ -4216,8 +4366,20 @@ function mergePortfolioAiHealthReport(existing, latest, allRepositoryIds) {
4216
4366
  const summary = { healthy: 0, attention: 0, unhealthy: 0 };
4217
4367
  for (const repository of repositories) summary[repository.status] += 1;
4218
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;
4219
4380
  return {
4220
4381
  ...latest,
4382
+ technologyGovernance,
4221
4383
  repositories,
4222
4384
  summary,
4223
4385
  coverage: {
@@ -4240,14 +4402,19 @@ function writePortfolioAiHealthReport(report, outDir) {
4240
4402
  const htmlPath = join24(outDir, "index.html");
4241
4403
  writeFileSync7(jsonPath, `${JSON.stringify(report, null, 2)}
4242
4404
  `);
4243
- writeFileSync7(join24(outDir, "data.js"), `window.__PORTFOLIO_AI_HEALTH__ = ${safeJavaScriptJson(report)};
4244
- `);
4405
+ writeFileSync7(
4406
+ join24(outDir, "data.js"),
4407
+ `window.__PORTFOLIO_AI_HEALTH__ = ${safeJavaScriptJson(report)};
4408
+ `
4409
+ );
4245
4410
  return { jsonPath, htmlPath };
4246
4411
  }
4247
4412
  function collectEndpoints(manifest) {
4248
4413
  const result = [];
4249
- if (manifest.controlPlane) result.push({ endpoint: manifest.controlPlane, role: "control-plane" });
4250
- if (manifest.executionEngine) result.push({ endpoint: manifest.executionEngine, role: "execution-engine" });
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" });
4251
4418
  for (const target of manifest.targets) result.push({ endpoint: target, role: "target" });
4252
4419
  const seen = /* @__PURE__ */ new Set();
4253
4420
  return result.filter(({ endpoint }) => {
@@ -4274,56 +4441,156 @@ function inspectRepository(endpoint, role, secretsRoot, homeDir, expectedPackage
4274
4441
  grokEffective: grokInspection.effectiveMcp,
4275
4442
  grokInspection: grokInspection.inspection
4276
4443
  };
4277
- const secrets = inspectRepositorySecrets(root, endpoint.id, secretsRoot, git.isRepository, endpoint.environmentPolicy);
4444
+ const secrets = inspectRepositorySecrets(
4445
+ root,
4446
+ endpoint.id,
4447
+ secretsRoot,
4448
+ git.isRepository,
4449
+ endpoint.environmentPolicy
4450
+ );
4278
4451
  const projectModel = inspectProjectModel(root, endpoint, technologyGovernance);
4279
- const versions = inspectVersionPolicy(root, technologyGovernance?.versionPolicy, endpoint.projectType);
4452
+ const versions = inspectVersionPolicy(
4453
+ root,
4454
+ technologyGovernance?.versionPolicy,
4455
+ endpoint.projectType
4456
+ );
4280
4457
  const verification = inspectProjectVerification(root);
4281
4458
  const redundancy = inspectProjectRedundancy(root, { homeDir });
4282
4459
  const recommendations = [];
4283
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");
4284
- 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`);
4285
- else if (git.branches.length > 1) recommendations.push(`\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`);
4286
- if (git.worktrees.length > 1) recommendations.push(`\u6709 ${git.worktrees.length} \u4E2A worktree\uFF1B\u5B8C\u6210\u5E76\u5408\u5E76\u540E\u518D\u62C6\u9664\u4E34\u65F6 worktree\u3002`);
4287
- if (git.dirtyPaths.length > 0) recommendations.push(`\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`);
4288
- if ((git.ahead ?? 0) > 0) 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`);
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`);
4289
4479
  if (entries.agents === "missing") recommendations.push("\u7F3A\u5C11 AGENTS.md\uFF1B\u65E0\u6CD5\u53D1\u73B0\u9879\u76EE\u5165\u53E3\u89C4\u5219\u3002");
4290
- if (entries.agents === "custom") recommendations.push("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");
4291
- if (entries.claude === "missing") recommendations.push("\u7F3A\u5C11 CLAUDE.md\uFF1B\u5EFA\u8BAE\u94FE\u63A5\u5230 AGENTS.md\uFF0C\u907F\u514D\u4E24\u4EFD\u5165\u53E3\u6F02\u79FB\u3002");
4292
- if (entries.claude === "thin-adapter") 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");
4293
- if (entries.claude === "custom") recommendations.push("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");
4294
- if (entries.claude === "dangling-symlink") recommendations.push("CLAUDE.md \u662F\u65AD\u5F00\u7684\u94FE\u63A5\uFF1B\u9700\u8981\u91CD\u65B0\u6307\u5411 AGENTS.md\u3002");
4295
- if (entries.gemini === "custom") 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");
4296
- if (entries.gemini === "dangling-symlink") recommendations.push("GEMINI.md \u662F\u65AD\u5F00\u7684\u94FE\u63A5\uFF1B\u9700\u8981\u91CD\u65B0\u6307\u5411 AGENTS.md\u3002");
4297
- if (skills.canonical.some((skill) => skill.kind === "dangling-symlink")) recommendations.push("`.agents/skills` \u4E2D\u5B58\u5728\u65AD\u5F00\u7684\u6280\u80FD\u94FE\u63A5\u3002");
4298
- 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");
4299
- if (skills.claudeCompatibility === "dangling-symlink") recommendations.push("`.claude/skills` \u662F\u65AD\u5F00\u7684\u94FE\u63A5\u3002");
4300
- if (!hostSsot.claudeEntry.compliant) recommendations.push(`CLAUDE.md \u4E0D\u662F\u89C4\u8303\u7684\u76F8\u5BF9\u94FE\u63A5 AGENTS.md\uFF08${hostSsot.claudeEntry.status}\uFF09\u3002`);
4301
- if (!hostSsot.claudeSkills.compliant) recommendations.push(`.claude/skills \u4E0D\u662F\u89C4\u8303\u7684\u76F8\u5BF9\u94FE\u63A5 ../.agents/skills\uFF08${hostSsot.claudeSkills.status}\uFF09\u3002`);
4302
- if (hostSsot.canonicalSkills.status !== "directory") recommendations.push(`\u7F3A\u5C11\u89C4\u8303\u7684 .agents/skills \u6280\u80FD\u76EE\u5F55\uFF08${hostSsot.canonicalSkills.status}\uFF09\u3002`);
4303
- 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");
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
+ );
4304
4522
  const liveEnv = secrets.repositoryEnvFiles.filter((file) => !file.template && !file.fixture);
4305
4523
  const envNeedsCentralReview = liveEnv.filter((file) => !file.localOnly);
4306
- 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");
4307
- 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");
4308
- 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");
4309
- 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");
4310
- 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`);
4311
- if (!docs.routerAligned) recommendations.push(`PGS Router \u7248\u672C\u4E3A ${docs.routerVersion ? `v${docs.routerVersion}` : "\u672A\u8BC6\u522B"}\uFF0C\u5E94\u540C\u6B65\u5230 v${docs.expectedRouterVersion}\u3002`);
4312
- 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");
4313
- 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");
4314
- const missingBaseline = projectModel.baseline.filter((technology) => !technology.detected && !hasBaselineException(projectModel, technology.id));
4315
- 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`);
4316
- const missingSelected = projectModel.optionalCapabilities.filter((technology) => technology.selected && !technology.detected);
4317
- 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`);
4318
- if (versions.status === "attention") recommendations.push(`\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`);
4319
- if (verification.status === "attention") recommendations.push(`\u9A8C\u8BC1\u811A\u672C\u7F3A\u5931\uFF1A${verification.missing.join("\u3001")}\uFF1BPGS \u8981\u6C42 typecheck\u3001lint\u3001format:check\u3001verify \u53EF\u53D1\u73B0\u3002`);
4320
- if (redundancy.status === "attention") recommendations.push("\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");
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
+ );
4321
4574
  return {
4322
4575
  id: endpoint.id,
4323
4576
  role,
4324
4577
  path: root,
4325
4578
  profile: "profile" in endpoint ? endpoint.profile : void 0,
4326
- status: deriveStatus(role, entries, git, hooks, skills, hostSsot, secrets, docs, projectModel, Boolean(technologyGovernance), versions, verification, redundancy),
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
+ ),
4327
4594
  recommendations,
4328
4595
  git,
4329
4596
  entries,
@@ -4339,22 +4606,161 @@ function inspectRepository(endpoint, role, secretsRoot, homeDir, expectedPackage
4339
4606
  redundancy
4340
4607
  };
4341
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
+ }
4342
4736
  function deriveStatus(role, entries, git, hooks, skills, hostSsot, secrets, docs, projectModel, technologyGovernanceConfigured, versions, verification, redundancy) {
4343
- 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";
4344
- const missingBaseline = projectModel.baseline.some((technology) => !technology.detected && !hasBaselineException(projectModel, technology.id));
4345
- const missingSelected = projectModel.optionalCapabilities.some((technology) => technology.selected && !technology.detected);
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
+ );
4346
4745
  const liveEnv = secrets.repositoryEnvFiles.filter((file) => !file.template && !file.fixture);
4347
- const secretMaterializationNeedsReview = liveEnv.some((file) => !file.centralized && !file.localOnly);
4746
+ const secretMaterializationNeedsReview = liveEnv.some(
4747
+ (file) => !file.centralized && !file.localOnly
4748
+ );
4348
4749
  const packageVersionNeedsReview = docs.packages.expected !== void 0 && !docs.packages.aligned;
4349
4750
  const routerVersionNeedsReview = !docs.routerAligned;
4350
4751
  const targetManifestMissing = role === "target" && !docs.manifest;
4351
- 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) return "attention";
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";
4352
4754
  return "healthy";
4353
4755
  }
4354
4756
  function inspectProjectModel(root, endpoint, governance) {
4355
- const projectType = governance?.projectTypes.find((candidate) => candidate.id === endpoint.projectType);
4757
+ const projectType = governance?.projectTypes.find(
4758
+ (candidate) => candidate.id === endpoint.projectType
4759
+ );
4356
4760
  const packages = collectPackageNames(root);
4357
- const technologyById = new Map((governance?.technologies ?? []).map((technology) => [technology.id, technology]));
4761
+ const technologyById = new Map(
4762
+ (governance?.technologies ?? []).map((technology) => [technology.id, technology])
4763
+ );
4358
4764
  const detection = (id) => {
4359
4765
  const technology = technologyById.get(id);
4360
4766
  const packageMatch = technology?.packages?.some((name) => packages.has(name)) ?? false;
@@ -4366,7 +4772,10 @@ function inspectProjectModel(root, endpoint, governance) {
4366
4772
  projectType: projectType ? { id: projectType.id, title: projectType.title, summary: projectType.summary } : void 0,
4367
4773
  boundary: endpoint.boundary,
4368
4774
  baseline: (projectType?.baseline ?? []).map(detection),
4369
- optionalCapabilities: (projectType?.optionalCapabilities ?? []).map((id) => ({ ...detection(id), selected: selected.has(id) })),
4775
+ optionalCapabilities: (projectType?.optionalCapabilities ?? []).map((id) => ({
4776
+ ...detection(id),
4777
+ selected: selected.has(id)
4778
+ })),
4370
4779
  exceptions: endpoint.technologyExceptions ?? []
4371
4780
  };
4372
4781
  }
@@ -4377,16 +4786,14 @@ function hasBaselineException(projectModel, technologyId) {
4377
4786
  }
4378
4787
  function collectPackageNames(root) {
4379
4788
  const names = /* @__PURE__ */ new Set();
4380
- let files = [];
4381
- try {
4382
- files = splitLines(execFileSync("git", ["ls-files", "*package.json"], { cwd: root, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }));
4383
- } catch {
4384
- if (existsSync23(join24(root, "package.json"))) files = ["package.json"];
4385
- }
4386
- for (const file of files) {
4387
- const packageJson = readJson4(join24(root, file));
4789
+ for (const { packageJson } of collectPackageManifests(root)) {
4388
4790
  if (!isRecord3(packageJson)) continue;
4389
- for (const section of ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]) {
4791
+ for (const section of [
4792
+ "dependencies",
4793
+ "devDependencies",
4794
+ "peerDependencies",
4795
+ "optionalDependencies"
4796
+ ]) {
4390
4797
  const dependencies = packageJson[section];
4391
4798
  if (!isRecord3(dependencies)) continue;
4392
4799
  for (const name of Object.keys(dependencies)) names.add(name);
@@ -4395,17 +4802,31 @@ function collectPackageNames(root) {
4395
4802
  return names;
4396
4803
  }
4397
4804
  function hasWorkflowReminderHooks(hooks) {
4398
- return hooks.some((hook) => hook.events.some((event) => event.name === "Stop" || event.name === "SubagentStop"));
4805
+ return hooks.some(
4806
+ (hook) => hook.events.some((event) => event.name === "Stop" || event.name === "SubagentStop")
4807
+ );
4399
4808
  }
4400
4809
  function inspectGit2(root) {
4401
4810
  const git = (...args) => {
4402
4811
  try {
4403
- return execFileSync("git", args, { cwd: root, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trimEnd();
4812
+ return execFileSync("git", args, {
4813
+ cwd: root,
4814
+ encoding: "utf8",
4815
+ stdio: ["ignore", "pipe", "ignore"]
4816
+ }).trimEnd();
4404
4817
  } catch {
4405
4818
  return void 0;
4406
4819
  }
4407
4820
  };
4408
- if (git("rev-parse", "--is-inside-work-tree") !== "true") return { isRepository: false, branches: [], mergedBranches: [], unmergedBranches: [], worktrees: [], dirtyPaths: [] };
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
+ };
4409
4830
  const status = git("status", "--porcelain=v1", "-uall") ?? "";
4410
4831
  const worktreeText = git("worktree", "list", "--porcelain") ?? "";
4411
4832
  const upstreamCounts = git("rev-list", "--left-right", "--count", "@{upstream}...HEAD")?.split(/\s+/).map(Number);
@@ -4413,8 +4834,12 @@ function inspectGit2(root) {
4413
4834
  isRepository: true,
4414
4835
  branch: git("branch", "--show-current") || "(detached)",
4415
4836
  branches: splitLines(git("for-each-ref", "--format=%(refname:short)", "refs/heads")),
4416
- mergedBranches: splitLines(git("for-each-ref", "--merged=HEAD", "--format=%(refname:short)", "refs/heads")),
4417
- unmergedBranches: splitLines(git("for-each-ref", "--no-merged=HEAD", "--format=%(refname:short)", "refs/heads")),
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
+ ),
4418
4843
  worktrees: splitLines(worktreeText).filter((line) => line.startsWith("worktree ")).map((line) => line.slice(9)),
4419
4844
  dirtyPaths: splitLines(status).map((line) => line.slice(3)),
4420
4845
  behind: upstreamCounts?.[0],
@@ -4540,14 +4965,29 @@ function inspectRepositorySecrets(root, id, secretsRoot, isRepository, environme
4540
4965
  repositoryEnvFiles: envFiles
4541
4966
  };
4542
4967
  }
4543
- var SKIP_ENV_DIRECTORIES = /* @__PURE__ */ new Set([".git", ".next", ".nuxt", ".output", ".turbo", ".vercel", ".worktrees", "build", "coverage", "dist", "node_modules", "out", "target"]);
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
+ ]);
4544
4983
  function collectEnvironmentFiles(root, current = root, depth = 0) {
4545
4984
  if (depth > 5) return [];
4546
4985
  const found = [];
4547
4986
  try {
4548
- for (const entry of readdirSync9(current, { withFileTypes: true })) {
4987
+ for (const entry of readdirSync10(current, { withFileTypes: true })) {
4549
4988
  if (entry.isDirectory()) {
4550
- if (!SKIP_ENV_DIRECTORIES.has(entry.name)) found.push(...collectEnvironmentFiles(root, join24(current, entry.name), depth + 1));
4989
+ if (!SKIP_ENV_DIRECTORIES.has(entry.name))
4990
+ found.push(...collectEnvironmentFiles(root, join24(current, entry.name), depth + 1));
4551
4991
  } else if (isEnvironmentFilename(entry.name) && !isProviderGeneratedEnvironmentFile(entry.name)) {
4552
4992
  found.push(relative8(root, join24(current, entry.name)));
4553
4993
  }
@@ -4561,7 +5001,7 @@ function collectCentralSecretFiles(root, current = root, depth = 0) {
4561
5001
  if (depth > 3) return [];
4562
5002
  const found = [];
4563
5003
  try {
4564
- for (const entry of readdirSync9(current, { withFileTypes: true })) {
5004
+ for (const entry of readdirSync10(current, { withFileTypes: true })) {
4565
5005
  const path = join24(current, entry.name);
4566
5006
  if (entry.isDirectory()) found.push(...collectCentralSecretFiles(root, path, depth + 1));
4567
5007
  else found.push({ path: relative8(root, path), mode: modeString(lstatSync8(path).mode) });
@@ -4647,17 +5087,25 @@ function inspectDevSpaceHealth(options) {
4647
5087
  const authPath = join24(configDirectory, "auth.json");
4648
5088
  const installedResult = run("devspace", ["--version"], 3e3);
4649
5089
  const installedVersion = installedResult.ok ? installedResult.stdout.match(/\b\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?\b/)?.[0] : void 0;
4650
- const latestResult = run("npm", ["view", "@waishnav/devspace", "version", "--registry=https://registry.npmjs.org/"], 5e3);
5090
+ const latestResult = run(
5091
+ "npm",
5092
+ ["view", "@waishnav/devspace", "version", "--registry=https://registry.npmjs.org/"],
5093
+ 5e3
5094
+ );
4651
5095
  const latestVersion = latestResult.ok ? latestResult.stdout.match(/\b\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?\b/)?.[0] : void 0;
4652
5096
  const processResult = run("pgrep", ["-f", "devspace serve"], 3e3);
4653
5097
  const pid = processResult.ok ? processResult.stdout.match(/\b\d+\b/)?.[0] : void 0;
4654
5098
  const processEnvironment = pid ? run("ps", ["eww", "-p", pid, "-o", "command="], 3e3) : void 0;
4655
- const processToolMode = processEnvironment?.ok ? processEnvironment.stdout.match(/(?:^|\s)DEVSPACE_TOOL_MODE=(minimal|full|codex)(?:\s|$)/)?.[1] : void 0;
5099
+ const processToolMode = processEnvironment?.ok ? processEnvironment.stdout.match(
5100
+ /(?:^|\s)DEVSPACE_TOOL_MODE=(minimal|full|codex)(?:\s|$)/
5101
+ )?.[1] : void 0;
4656
5102
  const doctor = !installedResult.ok ? "unavailable" : run("devspace", ["doctor"], 1e4).ok ? "ok" : "failed";
4657
5103
  const configValue = readJson4(configPath);
4658
5104
  const configExists = isRecord3(configValue);
4659
5105
  const allowedRoots = configExists && Array.isArray(configValue.allowedRoots) ? configValue.allowedRoots.filter((value) => typeof value === "string") : [];
4660
- const portfolioCoverage = !configExists || allowedRoots.length === 0 ? "unknown" : options.repositoryPaths.every((repositoryPath) => allowedRoots.some((root) => isPathInside(repositoryPath, root))) ? "complete" : "partial";
5106
+ const portfolioCoverage = !configExists || allowedRoots.length === 0 ? "unknown" : options.repositoryPaths.every(
5107
+ (repositoryPath) => allowedRoots.some((root) => isPathInside(repositoryPath, root))
5108
+ ) ? "complete" : "partial";
4661
5109
  const bind = configExists && typeof configValue.host === "string" ? isLoopbackHost(configValue.host) ? "loopback" : "non-loopback" : "unknown";
4662
5110
  const directoryMode = existsSync23(configDirectory) ? modeString(statSync5(configDirectory).mode) : void 0;
4663
5111
  const fileMode = existsSync23(configPath) ? modeString(statSync5(configPath).mode) : void 0;
@@ -4676,17 +5124,24 @@ function inspectDevSpaceHealth(options) {
4676
5124
  if (!installedResult.ok) unhealthy("\u672C\u673A\u672A\u53D1\u73B0 DevSpace\uFF1B\u65E0\u6CD5\u4F7F\u7528\u5BBF\u4E3B\u5DE5\u4F5C\u533A\u670D\u52A1\u3002");
4677
5125
  if (!configExists) unhealthy("\u7F3A\u5C11 ~/.devspace/config.json\u3002");
4678
5126
  if (!existsSync23(authPath)) unhealthy("\u7F3A\u5C11 ~/.devspace/auth.json\u3002");
4679
- if (directoryMode && directoryMode !== "700") unhealthy(`~/.devspace \u76EE\u5F55\u6743\u9650\u4E3A ${directoryMode}\uFF0C\u5E94\u6536\u7D27\u4E3A 700\u3002`);
5127
+ if (directoryMode && directoryMode !== "700")
5128
+ unhealthy(`~/.devspace \u76EE\u5F55\u6743\u9650\u4E3A ${directoryMode}\uFF0C\u5E94\u6536\u7D27\u4E3A 700\u3002`);
4680
5129
  if (fileMode && fileMode !== "600") unhealthy(`DevSpace \u914D\u7F6E\u6587\u4EF6\u6743\u9650\u4E3A ${fileMode}\uFF0C\u5E94\u4E3A 600\u3002`);
4681
5130
  if (authMode && authMode !== "600") unhealthy(`DevSpace \u8BA4\u8BC1\u6587\u4EF6\u6743\u9650\u4E3A ${authMode}\uFF0C\u5E94\u4E3A 600\u3002`);
4682
- if (bind === "non-loopback") 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");
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");
4683
5133
  if (doctor === "failed") unhealthy("devspace doctor \u672A\u901A\u8FC7\u3002");
4684
5134
  if (portfolioCoverage === "partial") attention("DevSpace allowedRoots \u6CA1\u6709\u8986\u76D6\u5168\u90E8\u5DF2\u767B\u8BB0\u4ED3\u5E93\u3002");
4685
5135
  if (installedResult.ok && !pid) attention("DevSpace \u5DF2\u5B89\u88C5\u4F46\u5F53\u524D\u6CA1\u6709\u8FD0\u884C\u3002");
4686
5136
  if (options.expectedToolMode && pid && processToolMode !== options.expectedToolMode) {
4687
- attention(`\u8FD0\u884C\u4E2D\u7684 DevSpace \u5DE5\u5177\u6A21\u5F0F\u4E3A ${processToolMode ?? "unknown"}\uFF0C\u671F\u671B ${options.expectedToolMode}\u3002`);
5137
+ attention(
5138
+ `\u8FD0\u884C\u4E2D\u7684 DevSpace \u5DE5\u5177\u6A21\u5F0F\u4E3A ${processToolMode ?? "unknown"}\uFF0C\u671F\u671B ${options.expectedToolMode}\u3002`
5139
+ );
4688
5140
  }
4689
- if (update === "available") attention(`DevSpace \u6709\u65B0\u7248\u672C\uFF1A${installedVersion} \u2192 ${latestVersion}\uFF1B\u5347\u7EA7\u524D\u4ECD\u5E94\u68C0\u67E5\u53D8\u66F4\u4E0E\u517C\u5BB9\u6027\u3002`);
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
+ );
4690
5145
  return {
4691
5146
  status,
4692
5147
  installed: installedResult.ok,
@@ -4735,18 +5190,25 @@ function isPathInside(path, root) {
4735
5190
  }
4736
5191
  function inspectSkillRoot(path) {
4737
5192
  const exists = pathLexists(path) && safeIsDirectory(path);
4738
- const names = exists ? safeReadDir(path).filter((name) => !name.startsWith(".") && existsSync23(join24(path, name, "SKILL.md"))) : [];
5193
+ const names = exists ? safeReadDir(path).filter(
5194
+ (name) => !name.startsWith(".") && existsSync23(join24(path, name, "SKILL.md"))
5195
+ ) : [];
4739
5196
  return { path, exists, names };
4740
5197
  }
4741
5198
  function claudeProjectLocalMcpNames(homeDir, root) {
4742
5199
  if (!homeDir) return [];
4743
5200
  const value = readJson4(join24(homeDir, ".claude.json"));
4744
5201
  if (!isRecord3(value) || !isRecord3(value.projects)) return [];
4745
- const candidates = new Set([resolve5(root), safeRealpath(root)].filter((path) => Boolean(path)));
5202
+ const candidates = new Set(
5203
+ [resolve5(root), safeRealpath(root)].filter((path) => Boolean(path))
5204
+ );
4746
5205
  const names = /* @__PURE__ */ new Set();
4747
5206
  for (const [path, project] of Object.entries(value.projects)) {
4748
- const projectPaths = [resolve5(path), safeRealpath(path)].filter((candidate) => Boolean(candidate));
4749
- if (!projectPaths.some((candidate) => candidates.has(candidate)) || !isRecord3(project) || !isRecord3(project.mcpServers)) continue;
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;
4750
5212
  for (const name of Object.keys(project.mcpServers)) names.add(name);
4751
5213
  }
4752
5214
  return [...names].sort();
@@ -4773,14 +5235,16 @@ function inspectGrokProject(root, homeDir, grokVersion) {
4773
5235
  });
4774
5236
  if (!grokVersion) return empty("unavailable");
4775
5237
  try {
4776
- const value = JSON.parse(execFileSync("grok", ["inspect", "--json"], {
4777
- cwd: root,
4778
- encoding: "utf8",
4779
- env: { ...process.env, HOME: homeDir, GROK_HOME: join24(homeDir, ".grok") },
4780
- maxBuffer: 10 * 1024 * 1024,
4781
- stdio: ["ignore", "pipe", "ignore"],
4782
- timeout: 8e3
4783
- }));
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
+ );
4784
5248
  if (!isRecord3(value)) return empty("failed");
4785
5249
  const userClaudeNames = new Set(jsonObjectKeys(join24(homeDir, ".claude.json"), "mcpServers"));
4786
5250
  const localClaudeNames = new Set(claudeProjectLocalMcpNames(homeDir, root));
@@ -4790,13 +5254,23 @@ function inspectGrokProject(root, homeDir, grokVersion) {
4790
5254
  const sourceType = typeof source.type === "string" ? source.type : "unknown";
4791
5255
  const sourcePath = typeof source.path === "string" ? source.path : void 0;
4792
5256
  const vendor = typeof item.vendor === "string" ? item.vendor : sourceType;
4793
- return [{
4794
- name: item.name,
4795
- vendor,
4796
- scope: inferGrokMcpScope(item.name, sourceType, sourcePath, root, homeDir, userClaudeNames, localClaudeNames),
4797
- sourceType,
4798
- ...sourcePath ? { sourcePath } : {}
4799
- }];
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
+ ];
4800
5274
  }) : [];
4801
5275
  const skillCounts = { project: 0, userOrCompatible: 0, plugin: 0, bundled: 0 };
4802
5276
  if (Array.isArray(value.skills)) {
@@ -4804,7 +5278,8 @@ function inspectGrokProject(root, homeDir, grokVersion) {
4804
5278
  if (!isRecord3(item) || !isRecord3(item.source)) continue;
4805
5279
  const type = item.source.type;
4806
5280
  const path = item.source.path;
4807
- if (typeof path === "string" && path.includes(sep + ".grok" + sep + "bundled" + sep)) skillCounts.bundled += 1;
5281
+ if (typeof path === "string" && path.includes(sep + ".grok" + sep + "bundled" + sep))
5282
+ skillCounts.bundled += 1;
4808
5283
  else if (type === "project") skillCounts.project += 1;
4809
5284
  else if (type === "plugin") skillCounts.plugin += 1;
4810
5285
  else skillCounts.userOrCompatible += 1;
@@ -4944,12 +5419,20 @@ function inspectSkillRegistry(executionEngineRoot) {
4944
5419
  if (!isRecord3(bundle) || !Array.isArray(bundle.assets)) continue;
4945
5420
  for (const id of bundle.assets) if (typeof id === "string") bundledIds.add(id);
4946
5421
  }
4947
- const sourceRoots = [join24(agentAssetsRoot, "skills/pie-skills"), join24(agentAssetsRoot, "skills/npx-skills/.agents/skills")];
4948
- const source = sourceRoots.reduce((count, root) => count + safeReadDir(root).filter((name) => existsSync23(join24(root, name, "SKILL.md"))).length, 0);
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
+ );
4949
5430
  return {
4950
5431
  source,
4951
5432
  registered: registeredSkills.length,
4952
- bundled: registeredSkills.filter((asset) => isRecord3(asset) && typeof asset.id === "string" && bundledIds.has(asset.id)).length,
5433
+ bundled: registeredSkills.filter(
5434
+ (asset) => isRecord3(asset) && typeof asset.id === "string" && bundledIds.has(asset.id)
5435
+ ).length,
4953
5436
  bundles: bundleFiles.length
4954
5437
  };
4955
5438
  }
@@ -4962,7 +5445,7 @@ function tomlMcpNames(path) {
4962
5445
  if (!existsSync23(path)) return [];
4963
5446
  const names = /* @__PURE__ */ new Set();
4964
5447
  for (const line of safeRead(path).split(/\r?\n/)) {
4965
- const match = line.match(/^\s*\[mcp_servers\.(?:"([^"]+)"|([^\.\]]+))\]\s*$/);
5448
+ const match = line.match(/^\s*\[mcp_servers\.(?:"([^"]+)"|([^.\]]+))\]\s*$/);
4966
5449
  const name = match?.[1] ?? match?.[2];
4967
5450
  if (name) names.add(name);
4968
5451
  }
@@ -4984,7 +5467,7 @@ function safeRead(path) {
4984
5467
  }
4985
5468
  function safeReadDir(path) {
4986
5469
  try {
4987
- return readdirSync9(path).sort();
5470
+ return readdirSync10(path).sort();
4988
5471
  } catch {
4989
5472
  return [];
4990
5473
  }
@@ -5006,7 +5489,10 @@ function pathLexists(path) {
5006
5489
  }
5007
5490
  function gitTracks(root, path) {
5008
5491
  try {
5009
- execFileSync("git", ["ls-files", "--error-unmatch", "--", path], { cwd: root, stdio: "ignore" });
5492
+ execFileSync("git", ["ls-files", "--error-unmatch", "--", path], {
5493
+ cwd: root,
5494
+ stdio: "ignore"
5495
+ });
5010
5496
  return true;
5011
5497
  } catch {
5012
5498
  return false;
@@ -5025,7 +5511,7 @@ function isRecord3(value) {
5025
5511
  return typeof value === "object" && value !== null && !Array.isArray(value);
5026
5512
  }
5027
5513
  function findDashboardAssets() {
5028
- const packageRoot2 = dirname13(dirname13(fileURLToPath4(import.meta.url)));
5514
+ const packageRoot2 = dirname14(dirname14(fileURLToPath4(import.meta.url)));
5029
5515
  const candidates = [
5030
5516
  process.env.PGS_DASHBOARD_ASSETS_DIR,
5031
5517
  join24(packageRoot2, ".dashboard-build"),
@@ -5036,7 +5522,10 @@ function findDashboardAssets() {
5036
5522
  join24(process.cwd(), "packages/pro-gov/assets/portfolio-dashboard")
5037
5523
  ].filter((value) => Boolean(value));
5038
5524
  const match = candidates.find((path) => existsSync23(join24(path, "index.html")));
5039
- if (!match) throw new Error("Portfolio dashboard assets were not built. Run pnpm --filter @pieai/pro-gov build.");
5525
+ if (!match)
5526
+ throw new Error(
5527
+ "Portfolio dashboard assets were not built. Run pnpm --filter @pieai/pro-gov build."
5528
+ );
5040
5529
  return match;
5041
5530
  }
5042
5531
  function safeJavaScriptJson(value) {