@pieai/pro-gov 0.7.1 → 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}`);
@@ -3735,7 +3733,7 @@ import { spawnSync as spawnSync6 } from "node:child_process";
3735
3733
  import { existsSync as existsSync22, readFileSync as readFileSync16 } from "node:fs";
3736
3734
  import { createRequire as createRequire2 } from "node:module";
3737
3735
  import { homedir as homedir3 } from "node:os";
3738
- import { dirname as dirname12, join as join23 } from "node:path";
3736
+ import { dirname as dirname13, join as join23 } from "node:path";
3739
3737
  import { fileURLToPath as fileURLToPath3 } from "node:url";
3740
3738
 
3741
3739
  // src/host-tooling/inventory.ts
@@ -3909,26 +3907,50 @@ function pathIsSymlink(path) {
3909
3907
 
3910
3908
  // src/portfolio/version-policy.ts
3911
3909
  import { spawnSync as spawnSync5 } from "node:child_process";
3912
- import { existsSync as existsSync21, readFileSync as readFileSync15 } from "node:fs";
3913
- import { join as join22 } from "node:path";
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";
3914
3912
  function inspectVersionPolicy(root, policy, projectType) {
3915
3913
  if (!policy) return { status: "compliant", packages: [], runtimes: [], attentionCount: 0 };
3916
- const packageJson = readJson2(join22(root, "package.json"));
3914
+ const packageManifests = collectPackageManifests(root);
3915
+ const packageJson = packageManifests.find(
3916
+ (manifest) => manifest.path === "package.json"
3917
+ )?.packageJson;
3917
3918
  const packageManager = policy.packageManager ? inspectPackageManager(packageJson, policy.packageManager.name, policy.packageManager.version) : void 0;
3918
3919
  const runtime = policy.runtime ? inspectRuntime(policy.runtime.name, policy.runtime.version) : void 0;
3919
3920
  const runtimes = (policy.runtimes ?? []).map((requirement) => {
3920
3921
  if (requirement.appliesTo && (!projectType || !requirement.appliesTo.includes(projectType))) {
3921
- return { name: requirement.name, expected: requirement.version, status: "not-applicable" };
3922
+ return {
3923
+ name: requirement.name,
3924
+ expected: requirement.version,
3925
+ status: "not-applicable"
3926
+ };
3922
3927
  }
3923
3928
  return inspectRuntime(requirement.name, requirement.version);
3924
3929
  });
3925
3930
  const packages = policy.packages.map((requirement) => {
3926
3931
  if (requirement.appliesTo && (!projectType || !requirement.appliesTo.includes(projectType))) {
3927
- 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
+ };
3928
3937
  }
3929
- const declared = findDeclaredVersion(packageJson, requirement.name);
3930
- const installed = readInstalledVersion(root, requirement.name);
3931
- 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";
3932
3954
  return {
3933
3955
  name: requirement.name,
3934
3956
  expected: requirement.version,
@@ -3938,8 +3960,12 @@ function inspectVersionPolicy(root, policy, projectType) {
3938
3960
  status
3939
3961
  };
3940
3962
  });
3941
- const all = [packageManager, runtime, ...runtimes, ...packages].filter((item) => item !== void 0);
3942
- 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;
3943
3969
  return {
3944
3970
  status: attentionCount === 0 ? "compliant" : "attention",
3945
3971
  packageManager,
@@ -3977,15 +4003,78 @@ function readRuntimeVersion(name) {
3977
4003
  return /^deno\s+(\d+\.\d+\.\d+)/m.exec(result.stdout)?.[1];
3978
4004
  }
3979
4005
  function findDeclaredVersion(packageJson, name) {
3980
- for (const section of ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]) {
4006
+ for (const section of [
4007
+ "dependencies",
4008
+ "devDependencies",
4009
+ "peerDependencies",
4010
+ "optionalDependencies"
4011
+ ]) {
3981
4012
  const value = packageJson?.[section]?.[name];
3982
4013
  if (typeof value === "string") return value;
3983
4014
  }
3984
4015
  return void 0;
3985
4016
  }
3986
- function readInstalledVersion(root, name) {
3987
- const packageJson = readJson2(join22(root, "node_modules", name, "package.json"));
3988
- 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)];
3989
4078
  }
3990
4079
  function readJson2(path) {
3991
4080
  if (!existsSync21(path)) return void 0;
@@ -4157,11 +4246,11 @@ function getExpectedPackageVersions() {
4157
4246
  };
4158
4247
  }
4159
4248
  function findOwnPackageJson() {
4160
- let current = dirname12(fileURLToPath3(import.meta.url));
4249
+ let current = dirname13(fileURLToPath3(import.meta.url));
4161
4250
  for (let depth = 0; depth < 5; depth += 1) {
4162
4251
  const candidate = join23(current, "package.json");
4163
4252
  if (existsSync22(candidate)) return candidate;
4164
- current = dirname12(current);
4253
+ current = dirname13(current);
4165
4254
  }
4166
4255
  return "";
4167
4256
  }
@@ -4191,13 +4280,13 @@ import {
4191
4280
  lstatSync as lstatSync8,
4192
4281
  mkdirSync as mkdirSync8,
4193
4282
  readFileSync as readFileSync17,
4194
- readdirSync as readdirSync9,
4283
+ readdirSync as readdirSync10,
4195
4284
  realpathSync as realpathSync4,
4196
4285
  statSync as statSync5,
4197
4286
  writeFileSync as writeFileSync7
4198
4287
  } from "node:fs";
4199
4288
  import { homedir as homedir4 } from "node:os";
4200
- 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";
4201
4290
  import { fileURLToPath as fileURLToPath4 } from "node:url";
4202
4291
  var CURRENT_ROUTER_VERSION = "1.1";
4203
4292
  function inspectPortfolioAiHealth(options) {
@@ -4206,21 +4295,30 @@ function inspectPortfolioAiHealth(options) {
4206
4295
  if (options.targetId && options.targetId !== "all" && endpoints.length === 0) {
4207
4296
  throw new Error(`Unknown portfolio target: ${options.targetId}`);
4208
4297
  }
4209
- 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
+ );
4210
4304
  const homeDir = options.homeDir ?? process.env.HOME ?? homedir4();
4211
4305
  const grokVersion = commandVersion("grok");
4212
4306
  const executionEngineRoot = options.manifest.executionEngine?.path;
4213
4307
  const skillRegistry = inspectSkillRegistry(executionEngineRoot);
4214
- const expectedPackageVersion = packageVersion(join24(executionEngineRoot ?? "", "packages/pro-gov/package.json"));
4215
- const repositories = endpoints.map(({ endpoint, role }) => inspectRepository(
4216
- endpoint,
4217
- role,
4218
- secretsRoot,
4219
- homeDir,
4220
- expectedPackageVersion,
4221
- options.manifest.technologyGovernance,
4222
- grokVersion
4223
- ));
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
+ );
4224
4322
  const summary = { healthy: 0, attention: 0, unhealthy: 0 };
4225
4323
  for (const repository of repositories) summary[repository.status] += 1;
4226
4324
  return {
@@ -4245,6 +4343,8 @@ function inspectPortfolioAiHealth(options) {
4245
4343
  technologyGovernance: {
4246
4344
  strategySource: options.manifest.technologyGovernance?.strategySource,
4247
4345
  versionPolicy: options.manifest.technologyGovernance?.versionPolicy,
4346
+ catalog: options.manifest.technologyGovernance?.technologies ?? [],
4347
+ matrix: buildTechnologyMatrix(options.manifest.technologyGovernance, repositories),
4248
4348
  projectTypes: options.manifest.technologyGovernance?.projectTypes.length ?? 0,
4249
4349
  technologies: options.manifest.technologyGovernance?.technologies.length ?? 0
4250
4350
  },
@@ -4254,7 +4354,8 @@ function inspectPortfolioAiHealth(options) {
4254
4354
  }
4255
4355
  function mergePortfolioAiHealthReport(existing, latest, allRepositoryIds) {
4256
4356
  const repositoriesById = /* @__PURE__ */ new Map();
4257
- for (const repository of existing?.repositories ?? []) repositoriesById.set(repository.id, repository);
4357
+ for (const repository of existing?.repositories ?? [])
4358
+ repositoriesById.set(repository.id, repository);
4258
4359
  for (const repository of latest.repositories) repositoriesById.set(repository.id, repository);
4259
4360
  const repositories = allRepositoryIds.map((id) => repositoriesById.get(id)).filter((repository) => repository !== void 0);
4260
4361
  const coveredIds = /* @__PURE__ */ new Set([
@@ -4265,8 +4366,20 @@ function mergePortfolioAiHealthReport(existing, latest, allRepositoryIds) {
4265
4366
  const summary = { healthy: 0, attention: 0, unhealthy: 0 };
4266
4367
  for (const repository of repositories) summary[repository.status] += 1;
4267
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;
4268
4380
  return {
4269
4381
  ...latest,
4382
+ technologyGovernance,
4270
4383
  repositories,
4271
4384
  summary,
4272
4385
  coverage: {
@@ -4289,14 +4402,19 @@ function writePortfolioAiHealthReport(report, outDir) {
4289
4402
  const htmlPath = join24(outDir, "index.html");
4290
4403
  writeFileSync7(jsonPath, `${JSON.stringify(report, null, 2)}
4291
4404
  `);
4292
- writeFileSync7(join24(outDir, "data.js"), `window.__PORTFOLIO_AI_HEALTH__ = ${safeJavaScriptJson(report)};
4293
- `);
4405
+ writeFileSync7(
4406
+ join24(outDir, "data.js"),
4407
+ `window.__PORTFOLIO_AI_HEALTH__ = ${safeJavaScriptJson(report)};
4408
+ `
4409
+ );
4294
4410
  return { jsonPath, htmlPath };
4295
4411
  }
4296
4412
  function collectEndpoints(manifest) {
4297
4413
  const result = [];
4298
- if (manifest.controlPlane) result.push({ endpoint: manifest.controlPlane, role: "control-plane" });
4299
- 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" });
4300
4418
  for (const target of manifest.targets) result.push({ endpoint: target, role: "target" });
4301
4419
  const seen = /* @__PURE__ */ new Set();
4302
4420
  return result.filter(({ endpoint }) => {
@@ -4323,56 +4441,156 @@ function inspectRepository(endpoint, role, secretsRoot, homeDir, expectedPackage
4323
4441
  grokEffective: grokInspection.effectiveMcp,
4324
4442
  grokInspection: grokInspection.inspection
4325
4443
  };
4326
- 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
+ );
4327
4451
  const projectModel = inspectProjectModel(root, endpoint, technologyGovernance);
4328
- const versions = inspectVersionPolicy(root, technologyGovernance?.versionPolicy, endpoint.projectType);
4452
+ const versions = inspectVersionPolicy(
4453
+ root,
4454
+ technologyGovernance?.versionPolicy,
4455
+ endpoint.projectType
4456
+ );
4329
4457
  const verification = inspectProjectVerification(root);
4330
4458
  const redundancy = inspectProjectRedundancy(root, { homeDir });
4331
4459
  const recommendations = [];
4332
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");
4333
- 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`);
4334
- 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`);
4335
- 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`);
4336
- 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`);
4337
- 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`);
4338
4479
  if (entries.agents === "missing") recommendations.push("\u7F3A\u5C11 AGENTS.md\uFF1B\u65E0\u6CD5\u53D1\u73B0\u9879\u76EE\u5165\u53E3\u89C4\u5219\u3002");
4339
- 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");
4340
- 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");
4341
- 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");
4342
- 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");
4343
- 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");
4344
- 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");
4345
- 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");
4346
- if (skills.canonical.some((skill) => skill.kind === "dangling-symlink")) recommendations.push("`.agents/skills` \u4E2D\u5B58\u5728\u65AD\u5F00\u7684\u6280\u80FD\u94FE\u63A5\u3002");
4347
- 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");
4348
- if (skills.claudeCompatibility === "dangling-symlink") recommendations.push("`.claude/skills` \u662F\u65AD\u5F00\u7684\u94FE\u63A5\u3002");
4349
- 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`);
4350
- 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`);
4351
- if (hostSsot.canonicalSkills.status !== "directory") recommendations.push(`\u7F3A\u5C11\u89C4\u8303\u7684 .agents/skills \u6280\u80FD\u76EE\u5F55\uFF08${hostSsot.canonicalSkills.status}\uFF09\u3002`);
4352
- 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
+ );
4353
4522
  const liveEnv = secrets.repositoryEnvFiles.filter((file) => !file.template && !file.fixture);
4354
4523
  const envNeedsCentralReview = liveEnv.filter((file) => !file.localOnly);
4355
- 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");
4356
- 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");
4357
- 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");
4358
- 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");
4359
- 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`);
4360
- 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`);
4361
- 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");
4362
- 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");
4363
- const missingBaseline = projectModel.baseline.filter((technology) => !technology.detected && !hasBaselineException(projectModel, technology.id));
4364
- 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`);
4365
- const missingSelected = projectModel.optionalCapabilities.filter((technology) => technology.selected && !technology.detected);
4366
- 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`);
4367
- 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`);
4368
- 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`);
4369
- 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
+ );
4370
4574
  return {
4371
4575
  id: endpoint.id,
4372
4576
  role,
4373
4577
  path: root,
4374
4578
  profile: "profile" in endpoint ? endpoint.profile : void 0,
4375
- 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
+ ),
4376
4594
  recommendations,
4377
4595
  git,
4378
4596
  entries,
@@ -4388,22 +4606,161 @@ function inspectRepository(endpoint, role, secretsRoot, homeDir, expectedPackage
4388
4606
  redundancy
4389
4607
  };
4390
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
+ }
4391
4736
  function deriveStatus(role, entries, git, hooks, skills, hostSsot, secrets, docs, projectModel, technologyGovernanceConfigured, versions, verification, redundancy) {
4392
- 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";
4393
- const missingBaseline = projectModel.baseline.some((technology) => !technology.detected && !hasBaselineException(projectModel, technology.id));
4394
- 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
+ );
4395
4745
  const liveEnv = secrets.repositoryEnvFiles.filter((file) => !file.template && !file.fixture);
4396
- const secretMaterializationNeedsReview = liveEnv.some((file) => !file.centralized && !file.localOnly);
4746
+ const secretMaterializationNeedsReview = liveEnv.some(
4747
+ (file) => !file.centralized && !file.localOnly
4748
+ );
4397
4749
  const packageVersionNeedsReview = docs.packages.expected !== void 0 && !docs.packages.aligned;
4398
4750
  const routerVersionNeedsReview = !docs.routerAligned;
4399
4751
  const targetManifestMissing = role === "target" && !docs.manifest;
4400
- 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";
4401
4754
  return "healthy";
4402
4755
  }
4403
4756
  function inspectProjectModel(root, endpoint, governance) {
4404
- const projectType = governance?.projectTypes.find((candidate) => candidate.id === endpoint.projectType);
4757
+ const projectType = governance?.projectTypes.find(
4758
+ (candidate) => candidate.id === endpoint.projectType
4759
+ );
4405
4760
  const packages = collectPackageNames(root);
4406
- 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
+ );
4407
4764
  const detection = (id) => {
4408
4765
  const technology = technologyById.get(id);
4409
4766
  const packageMatch = technology?.packages?.some((name) => packages.has(name)) ?? false;
@@ -4415,7 +4772,10 @@ function inspectProjectModel(root, endpoint, governance) {
4415
4772
  projectType: projectType ? { id: projectType.id, title: projectType.title, summary: projectType.summary } : void 0,
4416
4773
  boundary: endpoint.boundary,
4417
4774
  baseline: (projectType?.baseline ?? []).map(detection),
4418
- 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
+ })),
4419
4779
  exceptions: endpoint.technologyExceptions ?? []
4420
4780
  };
4421
4781
  }
@@ -4426,16 +4786,14 @@ function hasBaselineException(projectModel, technologyId) {
4426
4786
  }
4427
4787
  function collectPackageNames(root) {
4428
4788
  const names = /* @__PURE__ */ new Set();
4429
- let files = [];
4430
- try {
4431
- files = splitLines(execFileSync("git", ["ls-files", "*package.json"], { cwd: root, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }));
4432
- } catch {
4433
- if (existsSync23(join24(root, "package.json"))) files = ["package.json"];
4434
- }
4435
- for (const file of files) {
4436
- const packageJson = readJson4(join24(root, file));
4789
+ for (const { packageJson } of collectPackageManifests(root)) {
4437
4790
  if (!isRecord3(packageJson)) continue;
4438
- for (const section of ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]) {
4791
+ for (const section of [
4792
+ "dependencies",
4793
+ "devDependencies",
4794
+ "peerDependencies",
4795
+ "optionalDependencies"
4796
+ ]) {
4439
4797
  const dependencies = packageJson[section];
4440
4798
  if (!isRecord3(dependencies)) continue;
4441
4799
  for (const name of Object.keys(dependencies)) names.add(name);
@@ -4444,17 +4802,31 @@ function collectPackageNames(root) {
4444
4802
  return names;
4445
4803
  }
4446
4804
  function hasWorkflowReminderHooks(hooks) {
4447
- 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
+ );
4448
4808
  }
4449
4809
  function inspectGit2(root) {
4450
4810
  const git = (...args) => {
4451
4811
  try {
4452
- 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();
4453
4817
  } catch {
4454
4818
  return void 0;
4455
4819
  }
4456
4820
  };
4457
- 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
+ };
4458
4830
  const status = git("status", "--porcelain=v1", "-uall") ?? "";
4459
4831
  const worktreeText = git("worktree", "list", "--porcelain") ?? "";
4460
4832
  const upstreamCounts = git("rev-list", "--left-right", "--count", "@{upstream}...HEAD")?.split(/\s+/).map(Number);
@@ -4462,8 +4834,12 @@ function inspectGit2(root) {
4462
4834
  isRepository: true,
4463
4835
  branch: git("branch", "--show-current") || "(detached)",
4464
4836
  branches: splitLines(git("for-each-ref", "--format=%(refname:short)", "refs/heads")),
4465
- mergedBranches: splitLines(git("for-each-ref", "--merged=HEAD", "--format=%(refname:short)", "refs/heads")),
4466
- 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
+ ),
4467
4843
  worktrees: splitLines(worktreeText).filter((line) => line.startsWith("worktree ")).map((line) => line.slice(9)),
4468
4844
  dirtyPaths: splitLines(status).map((line) => line.slice(3)),
4469
4845
  behind: upstreamCounts?.[0],
@@ -4589,14 +4965,29 @@ function inspectRepositorySecrets(root, id, secretsRoot, isRepository, environme
4589
4965
  repositoryEnvFiles: envFiles
4590
4966
  };
4591
4967
  }
4592
- 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
+ ]);
4593
4983
  function collectEnvironmentFiles(root, current = root, depth = 0) {
4594
4984
  if (depth > 5) return [];
4595
4985
  const found = [];
4596
4986
  try {
4597
- for (const entry of readdirSync9(current, { withFileTypes: true })) {
4987
+ for (const entry of readdirSync10(current, { withFileTypes: true })) {
4598
4988
  if (entry.isDirectory()) {
4599
- 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));
4600
4991
  } else if (isEnvironmentFilename(entry.name) && !isProviderGeneratedEnvironmentFile(entry.name)) {
4601
4992
  found.push(relative8(root, join24(current, entry.name)));
4602
4993
  }
@@ -4610,7 +5001,7 @@ function collectCentralSecretFiles(root, current = root, depth = 0) {
4610
5001
  if (depth > 3) return [];
4611
5002
  const found = [];
4612
5003
  try {
4613
- for (const entry of readdirSync9(current, { withFileTypes: true })) {
5004
+ for (const entry of readdirSync10(current, { withFileTypes: true })) {
4614
5005
  const path = join24(current, entry.name);
4615
5006
  if (entry.isDirectory()) found.push(...collectCentralSecretFiles(root, path, depth + 1));
4616
5007
  else found.push({ path: relative8(root, path), mode: modeString(lstatSync8(path).mode) });
@@ -4696,17 +5087,25 @@ function inspectDevSpaceHealth(options) {
4696
5087
  const authPath = join24(configDirectory, "auth.json");
4697
5088
  const installedResult = run("devspace", ["--version"], 3e3);
4698
5089
  const installedVersion = installedResult.ok ? installedResult.stdout.match(/\b\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?\b/)?.[0] : void 0;
4699
- 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
+ );
4700
5095
  const latestVersion = latestResult.ok ? latestResult.stdout.match(/\b\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?\b/)?.[0] : void 0;
4701
5096
  const processResult = run("pgrep", ["-f", "devspace serve"], 3e3);
4702
5097
  const pid = processResult.ok ? processResult.stdout.match(/\b\d+\b/)?.[0] : void 0;
4703
5098
  const processEnvironment = pid ? run("ps", ["eww", "-p", pid, "-o", "command="], 3e3) : void 0;
4704
- 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;
4705
5102
  const doctor = !installedResult.ok ? "unavailable" : run("devspace", ["doctor"], 1e4).ok ? "ok" : "failed";
4706
5103
  const configValue = readJson4(configPath);
4707
5104
  const configExists = isRecord3(configValue);
4708
5105
  const allowedRoots = configExists && Array.isArray(configValue.allowedRoots) ? configValue.allowedRoots.filter((value) => typeof value === "string") : [];
4709
- 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";
4710
5109
  const bind = configExists && typeof configValue.host === "string" ? isLoopbackHost(configValue.host) ? "loopback" : "non-loopback" : "unknown";
4711
5110
  const directoryMode = existsSync23(configDirectory) ? modeString(statSync5(configDirectory).mode) : void 0;
4712
5111
  const fileMode = existsSync23(configPath) ? modeString(statSync5(configPath).mode) : void 0;
@@ -4725,17 +5124,24 @@ function inspectDevSpaceHealth(options) {
4725
5124
  if (!installedResult.ok) unhealthy("\u672C\u673A\u672A\u53D1\u73B0 DevSpace\uFF1B\u65E0\u6CD5\u4F7F\u7528\u5BBF\u4E3B\u5DE5\u4F5C\u533A\u670D\u52A1\u3002");
4726
5125
  if (!configExists) unhealthy("\u7F3A\u5C11 ~/.devspace/config.json\u3002");
4727
5126
  if (!existsSync23(authPath)) unhealthy("\u7F3A\u5C11 ~/.devspace/auth.json\u3002");
4728
- 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`);
4729
5129
  if (fileMode && fileMode !== "600") unhealthy(`DevSpace \u914D\u7F6E\u6587\u4EF6\u6743\u9650\u4E3A ${fileMode}\uFF0C\u5E94\u4E3A 600\u3002`);
4730
5130
  if (authMode && authMode !== "600") unhealthy(`DevSpace \u8BA4\u8BC1\u6587\u4EF6\u6743\u9650\u4E3A ${authMode}\uFF0C\u5E94\u4E3A 600\u3002`);
4731
- 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");
4732
5133
  if (doctor === "failed") unhealthy("devspace doctor \u672A\u901A\u8FC7\u3002");
4733
5134
  if (portfolioCoverage === "partial") attention("DevSpace allowedRoots \u6CA1\u6709\u8986\u76D6\u5168\u90E8\u5DF2\u767B\u8BB0\u4ED3\u5E93\u3002");
4734
5135
  if (installedResult.ok && !pid) attention("DevSpace \u5DF2\u5B89\u88C5\u4F46\u5F53\u524D\u6CA1\u6709\u8FD0\u884C\u3002");
4735
5136
  if (options.expectedToolMode && pid && processToolMode !== options.expectedToolMode) {
4736
- 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
+ );
4737
5140
  }
4738
- 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
+ );
4739
5145
  return {
4740
5146
  status,
4741
5147
  installed: installedResult.ok,
@@ -4784,18 +5190,25 @@ function isPathInside(path, root) {
4784
5190
  }
4785
5191
  function inspectSkillRoot(path) {
4786
5192
  const exists = pathLexists(path) && safeIsDirectory(path);
4787
- 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
+ ) : [];
4788
5196
  return { path, exists, names };
4789
5197
  }
4790
5198
  function claudeProjectLocalMcpNames(homeDir, root) {
4791
5199
  if (!homeDir) return [];
4792
5200
  const value = readJson4(join24(homeDir, ".claude.json"));
4793
5201
  if (!isRecord3(value) || !isRecord3(value.projects)) return [];
4794
- 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
+ );
4795
5205
  const names = /* @__PURE__ */ new Set();
4796
5206
  for (const [path, project] of Object.entries(value.projects)) {
4797
- const projectPaths = [resolve5(path), safeRealpath(path)].filter((candidate) => Boolean(candidate));
4798
- 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;
4799
5212
  for (const name of Object.keys(project.mcpServers)) names.add(name);
4800
5213
  }
4801
5214
  return [...names].sort();
@@ -4822,14 +5235,16 @@ function inspectGrokProject(root, homeDir, grokVersion) {
4822
5235
  });
4823
5236
  if (!grokVersion) return empty("unavailable");
4824
5237
  try {
4825
- const value = JSON.parse(execFileSync("grok", ["inspect", "--json"], {
4826
- cwd: root,
4827
- encoding: "utf8",
4828
- env: { ...process.env, HOME: homeDir, GROK_HOME: join24(homeDir, ".grok") },
4829
- maxBuffer: 10 * 1024 * 1024,
4830
- stdio: ["ignore", "pipe", "ignore"],
4831
- timeout: 8e3
4832
- }));
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
+ );
4833
5248
  if (!isRecord3(value)) return empty("failed");
4834
5249
  const userClaudeNames = new Set(jsonObjectKeys(join24(homeDir, ".claude.json"), "mcpServers"));
4835
5250
  const localClaudeNames = new Set(claudeProjectLocalMcpNames(homeDir, root));
@@ -4839,13 +5254,23 @@ function inspectGrokProject(root, homeDir, grokVersion) {
4839
5254
  const sourceType = typeof source.type === "string" ? source.type : "unknown";
4840
5255
  const sourcePath = typeof source.path === "string" ? source.path : void 0;
4841
5256
  const vendor = typeof item.vendor === "string" ? item.vendor : sourceType;
4842
- return [{
4843
- name: item.name,
4844
- vendor,
4845
- scope: inferGrokMcpScope(item.name, sourceType, sourcePath, root, homeDir, userClaudeNames, localClaudeNames),
4846
- sourceType,
4847
- ...sourcePath ? { sourcePath } : {}
4848
- }];
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
+ ];
4849
5274
  }) : [];
4850
5275
  const skillCounts = { project: 0, userOrCompatible: 0, plugin: 0, bundled: 0 };
4851
5276
  if (Array.isArray(value.skills)) {
@@ -4853,7 +5278,8 @@ function inspectGrokProject(root, homeDir, grokVersion) {
4853
5278
  if (!isRecord3(item) || !isRecord3(item.source)) continue;
4854
5279
  const type = item.source.type;
4855
5280
  const path = item.source.path;
4856
- 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;
4857
5283
  else if (type === "project") skillCounts.project += 1;
4858
5284
  else if (type === "plugin") skillCounts.plugin += 1;
4859
5285
  else skillCounts.userOrCompatible += 1;
@@ -4993,12 +5419,20 @@ function inspectSkillRegistry(executionEngineRoot) {
4993
5419
  if (!isRecord3(bundle) || !Array.isArray(bundle.assets)) continue;
4994
5420
  for (const id of bundle.assets) if (typeof id === "string") bundledIds.add(id);
4995
5421
  }
4996
- const sourceRoots = [join24(agentAssetsRoot, "skills/pie-skills"), join24(agentAssetsRoot, "skills/npx-skills/.agents/skills")];
4997
- 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
+ );
4998
5430
  return {
4999
5431
  source,
5000
5432
  registered: registeredSkills.length,
5001
- 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,
5002
5436
  bundles: bundleFiles.length
5003
5437
  };
5004
5438
  }
@@ -5011,7 +5445,7 @@ function tomlMcpNames(path) {
5011
5445
  if (!existsSync23(path)) return [];
5012
5446
  const names = /* @__PURE__ */ new Set();
5013
5447
  for (const line of safeRead(path).split(/\r?\n/)) {
5014
- const match = line.match(/^\s*\[mcp_servers\.(?:"([^"]+)"|([^\.\]]+))\]\s*$/);
5448
+ const match = line.match(/^\s*\[mcp_servers\.(?:"([^"]+)"|([^.\]]+))\]\s*$/);
5015
5449
  const name = match?.[1] ?? match?.[2];
5016
5450
  if (name) names.add(name);
5017
5451
  }
@@ -5033,7 +5467,7 @@ function safeRead(path) {
5033
5467
  }
5034
5468
  function safeReadDir(path) {
5035
5469
  try {
5036
- return readdirSync9(path).sort();
5470
+ return readdirSync10(path).sort();
5037
5471
  } catch {
5038
5472
  return [];
5039
5473
  }
@@ -5055,7 +5489,10 @@ function pathLexists(path) {
5055
5489
  }
5056
5490
  function gitTracks(root, path) {
5057
5491
  try {
5058
- 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
+ });
5059
5496
  return true;
5060
5497
  } catch {
5061
5498
  return false;
@@ -5074,7 +5511,7 @@ function isRecord3(value) {
5074
5511
  return typeof value === "object" && value !== null && !Array.isArray(value);
5075
5512
  }
5076
5513
  function findDashboardAssets() {
5077
- const packageRoot2 = dirname13(dirname13(fileURLToPath4(import.meta.url)));
5514
+ const packageRoot2 = dirname14(dirname14(fileURLToPath4(import.meta.url)));
5078
5515
  const candidates = [
5079
5516
  process.env.PGS_DASHBOARD_ASSETS_DIR,
5080
5517
  join24(packageRoot2, ".dashboard-build"),
@@ -5085,7 +5522,10 @@ function findDashboardAssets() {
5085
5522
  join24(process.cwd(), "packages/pro-gov/assets/portfolio-dashboard")
5086
5523
  ].filter((value) => Boolean(value));
5087
5524
  const match = candidates.find((path) => existsSync23(join24(path, "index.html")));
5088
- 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
+ );
5089
5529
  return match;
5090
5530
  }
5091
5531
  function safeJavaScriptJson(value) {