@pieai/pro-gov 0.8.0 → 0.9.0

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
@@ -8,8 +8,8 @@ var packageRoot = dirname(dirname(fileURLToPath(import.meta.url)));
8
8
  var sourceRoot = join(packageRoot, "..", "..");
9
9
  var packagedAssetsRoot = join(packageRoot, "assets");
10
10
  var assetRoots = ["starter", "profiles", "integrations", "docs/reference/adoption"];
11
- function listAssets() {
12
- const root = existsSync(join(sourceRoot, "starter")) ? sourceRoot : packagedAssetsRoot;
11
+ function listAssets(source = "auto") {
12
+ const root = source === "packaged" ? packagedAssetsRoot : source === "source" ? sourceRoot : existsSync(join(sourceRoot, "starter")) ? sourceRoot : packagedAssetsRoot;
13
13
  return assetRoots.flatMap((assetRoot) => {
14
14
  const absoluteRoot = join(root, assetRoot);
15
15
  if (!existsSync(absoluteRoot)) return [];
@@ -247,6 +247,7 @@ var supportedVisibilities = /* @__PURE__ */ new Set([
247
247
  var supportedSourceKinds = /* @__PURE__ */ new Set(["local", "local-pack", "npx"]);
248
248
  var supportedSkillPlacements = /* @__PURE__ */ new Set(["auto", "manual"]);
249
249
  var supportedSkillScopes = /* @__PURE__ */ new Set(["project", "user"]);
250
+ var supportedDeliveries = /* @__PURE__ */ new Set(["symlink", "snapshot"]);
250
251
  var supportedHosts = /* @__PURE__ */ new Set([
251
252
  "codex",
252
253
  "claude-code",
@@ -254,12 +255,35 @@ var supportedHosts = /* @__PURE__ */ new Set([
254
255
  "antigravity"
255
256
  ]);
256
257
  var skillInstallNamePattern = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
258
+ var safeProjectTargetFilenamePattern = /^[^./\\][^/\\]*$/;
257
259
  function assetSkillInstallName(asset) {
258
260
  return asset.installName ?? posix.basename(asset.sourcePath);
259
261
  }
262
+ function isValidAssetProjectTargetPath(kind, projectTargetPath) {
263
+ if (typeof projectTargetPath !== "string" || projectTargetPath.length === 0 || isAbsolute(projectTargetPath) || projectTargetPath.includes("\\")) {
264
+ return false;
265
+ }
266
+ const segments = projectTargetPath.split("/");
267
+ if (segments.some((segment) => segment.length === 0 || segment === "." || segment === "..")) {
268
+ return false;
269
+ }
270
+ const filename = segments.at(-1);
271
+ if (!filename || !safeProjectTargetFilenamePattern.test(filename)) return false;
272
+ if (kind === "rule") {
273
+ return segments.length === 4 && segments[0] === "docs" && segments[1] === "policy" && segments[2] === "shared-rules" && filename.endsWith(".md") || segments.length === 4 && segments[0] === ".pro-gov" && segments[1] === "agent-assets" && segments[2] === "rules";
274
+ }
275
+ if (kind === "command") {
276
+ return segments.length === 4 && segments[0] === ".pro-gov" && segments[1] === "agent-assets" && segments[2] === "commands";
277
+ }
278
+ return false;
279
+ }
280
+ function isValidSnapshotProjectTargetPath(projectTargetPath) {
281
+ return typeof projectTargetPath === "string" && projectTargetPath.startsWith("docs/policy/shared-rules/") && isValidAssetProjectTargetPath("rule", projectTargetPath);
282
+ }
260
283
  function validateAssetRegistry(registry, options = {}) {
261
284
  const issues = [];
262
285
  const seenIds = /* @__PURE__ */ new Set();
286
+ const projectTargetOwners = /* @__PURE__ */ new Map();
263
287
  for (const asset of registry.assets) {
264
288
  if (seenIds.has(asset.id)) {
265
289
  issues.push({
@@ -311,6 +335,21 @@ function validateAssetRegistry(registry, options = {}) {
311
335
  message: `Unsupported asset source kind: ${asset.sourceKind}`
312
336
  });
313
337
  }
338
+ if (asset.delivery !== void 0 && !supportedDeliveries.has(asset.delivery)) {
339
+ issues.push({
340
+ type: "unsupported-enum",
341
+ id: asset.id,
342
+ message: `Unsupported asset delivery: ${asset.delivery}`
343
+ });
344
+ }
345
+ if (asset.delivery === "snapshot" && (asset.kind !== "rule" || !isValidSnapshotProjectTargetPath(asset.projectTargetPath))) {
346
+ issues.push({
347
+ type: "invalid-snapshot-delivery",
348
+ id: asset.id,
349
+ path: asset.projectTargetPath,
350
+ message: `Snapshot delivery is only allowed for rule assets targeting docs/policy/shared-rules/<name>.md: ${asset.id}`
351
+ });
352
+ }
314
353
  for (const host of asset.hosts) {
315
354
  if (!supportedHosts.has(host)) {
316
355
  issues.push({
@@ -349,6 +388,26 @@ function validateAssetRegistry(registry, options = {}) {
349
388
  message: `Asset source path escapes agent-assets: ${asset.sourcePath}`
350
389
  });
351
390
  }
391
+ if (asset.projectTargetPath !== void 0 && !isValidAssetProjectTargetPath(asset.kind, asset.projectTargetPath)) {
392
+ issues.push({
393
+ type: "invalid-project-target-path",
394
+ id: asset.id,
395
+ path: asset.projectTargetPath,
396
+ message: `Asset project target path is not allowed for ${asset.kind}: ${asset.projectTargetPath}`
397
+ });
398
+ } else if (asset.projectTargetPath !== void 0) {
399
+ const existingOwner = projectTargetOwners.get(asset.projectTargetPath);
400
+ if (existingOwner !== void 0 && existingOwner !== asset.id) {
401
+ issues.push({
402
+ type: "duplicate-project-target-path",
403
+ id: asset.id,
404
+ path: asset.projectTargetPath,
405
+ message: `Project target path is already owned by ${existingOwner}: ${asset.projectTargetPath}`
406
+ });
407
+ } else {
408
+ projectTargetOwners.set(asset.projectTargetPath, asset.id);
409
+ }
410
+ }
352
411
  if (asset.visibility !== "public" && asset.publishable) {
353
412
  issues.push({
354
413
  type: "non-public-publishable",
@@ -434,6 +493,23 @@ function pathExistsEvenIfDanglingSymlink(path) {
434
493
  }
435
494
 
436
495
  // src/asset-registry/loader.ts
496
+ function createAgentAssetRegistryProvenance(registry, selectedAssetIds) {
497
+ const selectedIds = new Set(selectedAssetIds);
498
+ const selectedAssets = registry.assets.filter((asset) => selectedIds.has(asset.id));
499
+ const canonicalRegistry = canonicalizeValue({
500
+ schemaVersion: registry.schemaVersion,
501
+ assets: [...selectedAssets].sort(
502
+ (left, right) => left.id < right.id ? -1 : left.id > right.id ? 1 : 0
503
+ )
504
+ });
505
+ const hash = createHash2("sha256").update(JSON.stringify(canonicalRegistry)).digest("hex");
506
+ return {
507
+ schema: "agent-assets-registry",
508
+ version: registry.schemaVersion,
509
+ hash: `sha256:${hash}`,
510
+ assetCount: selectedAssets.length
511
+ };
512
+ }
437
513
  function loadAgentAssetRegistry(options = {}) {
438
514
  const agentAssetsDir = options.agentAssetsDir ?? findDefaultAgentAssetsDir();
439
515
  const registryPath = join5(agentAssetsDir, "registry.json");
@@ -458,6 +534,7 @@ function createAgentAssetLockEntries(registry, agentAssetsDir, assetIds) {
458
534
  return registry.assets.filter((asset) => !wantedIds || wantedIds.has(asset.id)).map((asset) => ({
459
535
  id: asset.id,
460
536
  sourcePath: asset.sourcePath,
537
+ delivery: asset.delivery ?? "symlink",
461
538
  contentHash: hashAgentAssetContent(asset, agentAssetsDir)
462
539
  })).sort((a, b) => a.id.localeCompare(b.id));
463
540
  }
@@ -475,6 +552,15 @@ function hashAssetPathContent(sourceAbsolutePath) {
475
552
  }
476
553
  return `sha256:${hash.digest("hex")}`;
477
554
  }
555
+ function canonicalizeValue(value) {
556
+ if (Array.isArray(value)) return value.map((item) => canonicalizeValue(item));
557
+ if (value !== null && typeof value === "object") {
558
+ return Object.fromEntries(
559
+ Object.entries(value).filter(([, item]) => item !== void 0).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0).map(([key, item]) => [key, canonicalizeValue(item)])
560
+ );
561
+ }
562
+ return value;
563
+ }
478
564
  function findDefaultAgentAssetsDir() {
479
565
  const packageRoot2 = findPackageRoot(dirname2(fileURLToPath2(import.meta.url)));
480
566
  const repoRoot = join5(packageRoot2, "..", "..");
@@ -620,6 +706,7 @@ function resolveSafePath(root, sourcePath) {
620
706
  }
621
707
 
622
708
  // src/asset-targets/apply.ts
709
+ import { createHash as createHash3 } from "node:crypto";
623
710
  import {
624
711
  existsSync as existsSync8,
625
712
  lstatSync as lstatSync3,
@@ -633,7 +720,7 @@ import {
633
720
  import { dirname as dirname4, join as join8, relative as relative4, resolve as resolve2 } from "node:path";
634
721
 
635
722
  // src/asset-targets/install-plan.ts
636
- import { existsSync as existsSync7, lstatSync as lstatSync2, readFileSync as readFileSync4, readlinkSync, realpathSync } from "node:fs";
723
+ import { existsSync as existsSync7, lstatSync as lstatSync2, readFileSync as readFileSync4, readlinkSync, realpathSync, statSync as statSync3 } from "node:fs";
637
724
  import { basename, dirname as dirname3, join as join7, resolve } from "node:path";
638
725
  function createAssetInstallPlan(options) {
639
726
  const placement = options.placement ?? "registry";
@@ -653,9 +740,9 @@ function createAssetInstallPlan(options) {
653
740
  options.agentAssetsDir,
654
741
  assetIds
655
742
  );
743
+ const registryProvenance = createAgentAssetRegistryProvenance(options.registry, assetIds);
656
744
  const managedLock = readManagedLock(options.targetDir);
657
745
  const managedEntries = managedLock.entries;
658
- const managedTargets = new Set(managedEntries.map((entry) => entry.targetPath));
659
746
  const legacyAdoptions = createLegacyClaudeAdoptions({
660
747
  targetDir: options.targetDir,
661
748
  agentAssetsDir: options.agentAssetsDir,
@@ -671,7 +758,7 @@ function createAssetInstallPlan(options) {
671
758
  options.targetDir,
672
759
  options.host,
673
760
  placement,
674
- managedTargets
761
+ managedEntries
675
762
  )
676
763
  );
677
764
  const manifest = {
@@ -686,6 +773,7 @@ function createAssetInstallPlan(options) {
686
773
  host: options.host,
687
774
  placement,
688
775
  bundleIds: [...options.bundleIds],
776
+ registryProvenance,
689
777
  assets: lockEntries.map((entry) => {
690
778
  const action = assetActions.find(
691
779
  (candidate) => "assetId" in candidate && candidate.assetId === entry.id
@@ -747,7 +835,7 @@ function resolveAssetIds(bundleIds, explicitAssetIds, bundlesById) {
747
835
  }
748
836
  return [...ids].sort();
749
837
  }
750
- function createAssetAction(asset, agentAssetsDir, targetDir, host, placement, managedTargets) {
838
+ function createAssetAction(asset, agentAssetsDir, targetDir, host, placement, managedEntries) {
751
839
  if (asset.kind === "skill" && asset.defaultScope === "user") {
752
840
  throw new Error(
753
841
  `User-scoped asset ${asset.id} must be linked at the user level, not installed into a project target.`
@@ -757,9 +845,23 @@ function createAssetAction(asset, agentAssetsDir, targetDir, host, placement, ma
757
845
  const targetPath = resolveHostTargetPath(asset, host, placement);
758
846
  const targetAbsolutePath = join7(targetDir, targetPath);
759
847
  const targetExists = pathExistsEvenIfDanglingSymlink2(targetAbsolutePath);
848
+ const managedEntry = managedEntries.find(
849
+ (entry) => entry.id === asset.id && entry.targetPath === targetPath
850
+ );
851
+ if ((asset.delivery ?? "symlink") === "snapshot") {
852
+ return createSnapshotAction({
853
+ asset,
854
+ agentAssetsDir,
855
+ sourcePath,
856
+ targetPath,
857
+ targetAbsolutePath,
858
+ targetExists,
859
+ managedEntry
860
+ });
861
+ }
760
862
  if (targetExists) {
761
863
  const stats = lstatSync2(targetAbsolutePath);
762
- if (stats.isSymbolicLink() && managedTargets.has(targetPath)) {
864
+ if (stats.isSymbolicLink() && managedEntry && (managedEntry.delivery ?? "symlink") === "symlink") {
763
865
  return {
764
866
  type: "update-symlink",
765
867
  assetId: asset.id,
@@ -767,6 +869,9 @@ function createAssetAction(asset, agentAssetsDir, targetDir, host, placement, ma
767
869
  targetPath
768
870
  };
769
871
  }
872
+ if (managedEntry && managedEntry.delivery === "snapshot") {
873
+ throw new Error(`Refusing to replace managed snapshot with a symlink: ${targetPath}`);
874
+ }
770
875
  if (stats.isSymbolicLink() && existsSync7(targetAbsolutePath) && realpathSync(targetAbsolutePath) === realpathSync(sourcePath)) {
771
876
  return {
772
877
  type: "adopt-existing-symlink",
@@ -784,7 +889,78 @@ function createAssetAction(asset, agentAssetsDir, targetDir, host, placement, ma
784
889
  targetPath
785
890
  };
786
891
  }
892
+ function createSnapshotAction(options) {
893
+ if (options.asset.kind !== "rule" || !isValidSnapshotProjectTargetPath(options.asset.projectTargetPath)) {
894
+ throw new Error(
895
+ `Snapshot delivery requires a rule target under docs/policy/shared-rules/: ${options.asset.id}`
896
+ );
897
+ }
898
+ if (!statSync3(options.sourcePath).isFile()) {
899
+ throw new Error(`Snapshot source must be a regular file: ${options.asset.sourcePath}`);
900
+ }
901
+ const content = readFileSync4(options.sourcePath);
902
+ const contentBase64 = content.toString("base64");
903
+ const contentHash = hashAgentAssetContent(options.asset, options.agentAssetsDir);
904
+ const managedDelivery = options.managedEntry?.delivery ?? "symlink";
905
+ if (!options.targetExists) {
906
+ return {
907
+ type: "snapshot",
908
+ assetId: options.asset.id,
909
+ targetPath: options.targetPath,
910
+ contentBase64,
911
+ contentHash
912
+ };
913
+ }
914
+ const stats = lstatSync2(options.targetAbsolutePath);
915
+ if (stats.isSymbolicLink()) {
916
+ if (existsSync7(options.targetAbsolutePath) && realpathSync(options.targetAbsolutePath) === realpathSync(options.sourcePath) && hashAssetPathContent(options.targetAbsolutePath) === contentHash) {
917
+ return {
918
+ type: "migrate-symlink-to-snapshot",
919
+ assetId: options.asset.id,
920
+ sourcePath: options.sourcePath,
921
+ targetPath: options.targetPath,
922
+ contentBase64,
923
+ contentHash
924
+ };
925
+ }
926
+ throw new Error(`Refusing to overwrite unmanaged target: ${options.targetPath}`);
927
+ }
928
+ if (!stats.isFile()) {
929
+ throw new Error(`Refusing to overwrite unmanaged target: ${options.targetPath}`);
930
+ }
931
+ const currentTargetHash = hashAssetPathContent(options.targetAbsolutePath);
932
+ if (currentTargetHash === contentHash) {
933
+ return {
934
+ type: "adopt-snapshot",
935
+ assetId: options.asset.id,
936
+ targetPath: options.targetPath,
937
+ contentHash
938
+ };
939
+ }
940
+ if (managedDelivery === "snapshot" && options.managedEntry?.contentHash) {
941
+ if (currentTargetHash !== options.managedEntry.contentHash) {
942
+ throw new Error(`Refusing to overwrite locally drifted snapshot: ${options.targetPath}`);
943
+ }
944
+ return {
945
+ type: "update-snapshot",
946
+ assetId: options.asset.id,
947
+ targetPath: options.targetPath,
948
+ contentBase64,
949
+ contentHash,
950
+ expectedContentHash: options.managedEntry.contentHash
951
+ };
952
+ }
953
+ throw new Error(`Refusing to overwrite unmanaged target: ${options.targetPath}`);
954
+ }
787
955
  function resolveHostTargetPath(asset, _host, placement) {
956
+ if (asset.projectTargetPath !== void 0) {
957
+ if (!isValidAssetProjectTargetPath(asset.kind, asset.projectTargetPath)) {
958
+ throw new Error(
959
+ `Invalid project target path for asset ${asset.id}: ${asset.projectTargetPath}`
960
+ );
961
+ }
962
+ return asset.projectTargetPath;
963
+ }
788
964
  if (asset.kind === "skill") {
789
965
  const effectivePlacement = resolveSkillPlacement(asset, placement);
790
966
  if (effectivePlacement === "manual") {
@@ -818,7 +994,7 @@ function readManagedLock(targetDir) {
818
994
  return {
819
995
  host: typeof lockfile.host === "string" ? lockfile.host : void 0,
820
996
  entries: (lockfile.assets ?? []).filter(
821
- (entry) => typeof entry.id === "string" && typeof entry.sourcePath === "string" && typeof entry.targetPath === "string"
997
+ (entry) => typeof entry.id === "string" && typeof entry.sourcePath === "string" && typeof entry.targetPath === "string" && (entry.delivery === void 0 || entry.delivery === "symlink" || entry.delivery === "snapshot") && (entry.contentHash === void 0 || typeof entry.contentHash === "string")
822
998
  )
823
999
  };
824
1000
  } catch {
@@ -900,6 +1076,29 @@ function createRemovalActions(targetDir, agentAssetsDir, managedEntries, expecte
900
1076
  const targetAbsolutePath = join7(targetDir, entry.targetPath);
901
1077
  if (!pathExistsEvenIfDanglingSymlink2(targetAbsolutePath)) continue;
902
1078
  const stats = lstatSync2(targetAbsolutePath);
1079
+ if ((entry.delivery ?? "symlink") === "snapshot") {
1080
+ if (!isValidSnapshotProjectTargetPath(entry.targetPath)) {
1081
+ throw new Error(
1082
+ `Refusing to remove snapshot outside live shared rules: ${entry.targetPath}`
1083
+ );
1084
+ }
1085
+ if (!stats.isFile() || !entry.contentHash) {
1086
+ throw new Error(
1087
+ `Refusing to remove managed snapshot without its locked file hash: ${entry.targetPath}`
1088
+ );
1089
+ }
1090
+ const currentTargetHash = hashAssetPathContent(targetAbsolutePath);
1091
+ if (currentTargetHash !== entry.contentHash) {
1092
+ throw new Error(`Refusing to remove locally drifted managed snapshot: ${entry.targetPath}`);
1093
+ }
1094
+ actions.push({
1095
+ type: "remove-snapshot",
1096
+ assetId: entry.id,
1097
+ targetPath: entry.targetPath,
1098
+ expectedContentHash: entry.contentHash
1099
+ });
1100
+ continue;
1101
+ }
903
1102
  if (!stats.isSymbolicLink()) {
904
1103
  throw new Error(
905
1104
  `Refusing to remove path that is no longer a managed symlink: ${entry.targetPath}`
@@ -922,12 +1121,12 @@ function createRemovalActions(targetDir, agentAssetsDir, managedEntries, expecte
922
1121
  return actions.sort((a, b) => a.targetPath.localeCompare(b.targetPath));
923
1122
  }
924
1123
  function isManagedAssetTargetPath(path) {
925
- return /^(?:\.agents\/(?:skills|manual-skills)|\.pro-gov\/agent-assets\/(?:rules|commands))\/[^/.][^/]*$/.test(
1124
+ return /^(?:(?:\.agents\/(?:skills|manual-skills)|\.pro-gov\/agent-assets\/(?:rules|commands))\/[^./\\][^/\\]*|docs\/policy\/shared-rules\/[^./\\][^/\\]*\.md)$/.test(
926
1125
  path
927
1126
  );
928
1127
  }
929
1128
  function isLegacyClaudeSkillTargetPath(path) {
930
- return /^\.claude\/skills\/[^/.][^/]*$/.test(path);
1129
+ return /^\.claude\/skills\/[^./\\][^/\\]*$/.test(path);
931
1130
  }
932
1131
  function pathExistsEvenIfDanglingSymlink2(path) {
933
1132
  try {
@@ -944,6 +1143,9 @@ function applyAssetInstallPlan(plan) {
944
1143
  for (const action of plan.actions) {
945
1144
  if (action.type === "adopt-symlink") validateAdoptedSymlink(plan.targetDir, action);
946
1145
  if (action.type === "adopt-existing-symlink") validateExistingSymlink(plan.targetDir, action);
1146
+ if (action.type === "snapshot" || action.type === "update-snapshot" || action.type === "adopt-snapshot" || action.type === "migrate-symlink-to-snapshot" || action.type === "remove-snapshot") {
1147
+ validateSnapshotAction(plan.targetDir, action);
1148
+ }
947
1149
  }
948
1150
  for (const action of plan.actions) {
949
1151
  applyAction(plan.targetDir, action);
@@ -958,6 +1160,10 @@ function applyAction(targetDir, action) {
958
1160
  return;
959
1161
  }
960
1162
  if (action.type === "adopt-symlink" || action.type === "adopt-existing-symlink") return;
1163
+ if (action.type === "snapshot" || action.type === "update-snapshot" || action.type === "adopt-snapshot" || action.type === "migrate-symlink-to-snapshot" || action.type === "remove-snapshot") {
1164
+ applySnapshotAction(targetDir, action);
1165
+ return;
1166
+ }
961
1167
  if (action.type === "create-dir") {
962
1168
  mkdirSync2(targetAbsolutePath, { recursive: true });
963
1169
  return;
@@ -997,6 +1203,67 @@ function validateExistingSymlink(targetDir, action) {
997
1203
  throw new Error(`Existing skill symlink changed before apply: ${action.targetPath}`);
998
1204
  }
999
1205
  }
1206
+ function validateSnapshotAction(targetDir, action) {
1207
+ if (!isValidSnapshotProjectTargetPath(action.targetPath)) {
1208
+ throw new Error(`Refusing unsafe snapshot target: ${action.targetPath}`);
1209
+ }
1210
+ if ("contentBase64" in action && hashSnapshotBytes(Buffer.from(action.contentBase64, "base64")) !== action.contentHash) {
1211
+ throw new Error(`Snapshot content hash is invalid: ${action.targetPath}`);
1212
+ }
1213
+ const targetAbsolutePath = join8(targetDir, action.targetPath);
1214
+ if (action.type === "snapshot") {
1215
+ if (pathExistsEvenIfDanglingSymlink3(targetAbsolutePath)) {
1216
+ throw new Error(`Snapshot target changed before apply: ${action.targetPath}`);
1217
+ }
1218
+ return;
1219
+ }
1220
+ if (action.type === "update-snapshot") {
1221
+ assertRegularSnapshotHash(targetAbsolutePath, action.expectedContentHash, action.targetPath);
1222
+ return;
1223
+ }
1224
+ if (action.type === "adopt-snapshot") {
1225
+ assertRegularSnapshotHash(targetAbsolutePath, action.contentHash, action.targetPath);
1226
+ return;
1227
+ }
1228
+ if (action.type === "migrate-symlink-to-snapshot") {
1229
+ if (!pathExistsEvenIfDanglingSymlink3(targetAbsolutePath) || !lstatSync3(targetAbsolutePath).isSymbolicLink() || !existsSync8(targetAbsolutePath) || realpathSync2(targetAbsolutePath) !== realpathSync2(action.sourcePath) || hashAssetPathContent(targetAbsolutePath) !== action.contentHash) {
1230
+ throw new Error(`Snapshot symlink changed before apply: ${action.targetPath}`);
1231
+ }
1232
+ return;
1233
+ }
1234
+ assertRegularSnapshotHash(targetAbsolutePath, action.expectedContentHash, action.targetPath);
1235
+ }
1236
+ function applySnapshotAction(targetDir, action) {
1237
+ const targetAbsolutePath = join8(targetDir, action.targetPath);
1238
+ if (action.type === "adopt-snapshot") return;
1239
+ if (action.type === "remove-snapshot") {
1240
+ unlinkSync(targetAbsolutePath);
1241
+ return;
1242
+ }
1243
+ if (action.type === "migrate-symlink-to-snapshot") unlinkSync(targetAbsolutePath);
1244
+ mkdirSync2(dirname4(targetAbsolutePath), { recursive: true });
1245
+ if (action.type === "snapshot" && pathExistsEvenIfDanglingSymlink3(targetAbsolutePath)) {
1246
+ throw new Error(`Refusing to overwrite unmanaged target: ${action.targetPath}`);
1247
+ }
1248
+ writeFileSync(targetAbsolutePath, Buffer.from(action.contentBase64, "base64"));
1249
+ }
1250
+ function assertRegularSnapshotHash(targetAbsolutePath, expectedHash, targetPath) {
1251
+ if (!pathExistsEvenIfDanglingSymlink3(targetAbsolutePath)) {
1252
+ throw new Error(`Snapshot target changed before apply: ${targetPath}`);
1253
+ }
1254
+ const stats = lstatSync3(targetAbsolutePath);
1255
+ if (!stats.isFile() || hashAssetPathContent(targetAbsolutePath) !== expectedHash) {
1256
+ throw new Error(`Snapshot target hash changed before apply: ${targetPath}`);
1257
+ }
1258
+ }
1259
+ function hashSnapshotBytes(content) {
1260
+ const hash = createHash3("sha256");
1261
+ hash.update("");
1262
+ hash.update("\0");
1263
+ hash.update(content);
1264
+ hash.update("\0");
1265
+ return `sha256:${hash.digest("hex")}`;
1266
+ }
1000
1267
  function validateAdoptedSymlink(targetDir, action) {
1001
1268
  if (!isManagedAssetTargetPath(action.targetPath) || !isLegacyClaudeSkillTargetPath(action.legacyTargetPath) || action.compatibilityRootPath !== ".claude/skills" || action.expectedCompatibilityRawTarget !== "../.agents/skills") {
1002
1269
  throw new Error(`Refusing unsafe legacy Claude adoption: ${action.legacyTargetPath}`);
@@ -1064,10 +1331,54 @@ function checkInstalledAssets(options) {
1064
1331
  const lockfile = JSON.parse(readFileSync5(lockfilePath, "utf8"));
1065
1332
  const issues = [];
1066
1333
  const strictRegistry = options.strictRegistry ?? false;
1334
+ const selectedAssetIds = (lockfile.assets ?? []).map((entry) => entry.id);
1335
+ const hasRegistryProvenance2 = Object.prototype.hasOwnProperty.call(
1336
+ lockfile,
1337
+ "registryProvenance"
1338
+ );
1339
+ const registryProvenanceMismatch = strictRegistry && hasRegistryProvenance2 && !matchesRegistryProvenance(
1340
+ lockfile.registryProvenance,
1341
+ createAgentAssetRegistryProvenance(options.registry, selectedAssetIds)
1342
+ );
1343
+ if (registryProvenanceMismatch) {
1344
+ issues.push({
1345
+ type: "registry-provenance-mismatch",
1346
+ message: "Asset lock registry provenance does not match the registry available to this checker; registry-dependent checks were skipped."
1347
+ });
1348
+ }
1067
1349
  for (const entry of lockfile.assets ?? []) {
1350
+ if (!isManagedAssetTargetPath(entry.targetPath) && !isLegacyClaudeSkillTargetPath(entry.targetPath)) {
1351
+ issues.push({
1352
+ type: "unsafe-target-path",
1353
+ id: entry.id,
1354
+ targetPath: entry.targetPath,
1355
+ message: `Managed asset target is outside supported roots: ${entry.targetPath}`
1356
+ });
1357
+ continue;
1358
+ }
1068
1359
  const asset = registryById.get(entry.id);
1069
1360
  const targetAbsolutePath = join9(options.targetDir, entry.targetPath);
1070
- if (!asset && strictRegistry) {
1361
+ const delivery = entry.delivery ?? "symlink";
1362
+ const portableDeferredSkill = delivery === "symlink" && !strictRegistry && isProjectSkillTarget(entry.targetPath);
1363
+ if (delivery !== "symlink" && delivery !== "snapshot") {
1364
+ issues.push({
1365
+ type: "unsupported-delivery",
1366
+ id: entry.id,
1367
+ targetPath: entry.targetPath,
1368
+ message: `Managed asset delivery is unsupported: ${delivery}`
1369
+ });
1370
+ continue;
1371
+ }
1372
+ if (delivery === "snapshot" && !isValidSnapshotProjectTargetPath(entry.targetPath)) {
1373
+ issues.push({
1374
+ type: "unsafe-target-path",
1375
+ id: entry.id,
1376
+ targetPath: entry.targetPath,
1377
+ message: `Managed snapshot target is outside live shared rules: ${entry.targetPath}`
1378
+ });
1379
+ continue;
1380
+ }
1381
+ if (!asset && strictRegistry && !registryProvenanceMismatch) {
1071
1382
  issues.push({
1072
1383
  type: "unknown-asset",
1073
1384
  id: entry.id,
@@ -1075,7 +1386,7 @@ function checkInstalledAssets(options) {
1075
1386
  message: `Lockfile references unknown asset: ${entry.id}`
1076
1387
  });
1077
1388
  }
1078
- if (asset?.kind === "skill" && asset.defaultScope === "user") {
1389
+ if (!registryProvenanceMismatch && asset?.kind === "skill" && asset.defaultScope === "user") {
1079
1390
  issues.push({
1080
1391
  type: "user-scoped-asset-in-project-lock",
1081
1392
  id: entry.id,
@@ -1083,7 +1394,7 @@ function checkInstalledAssets(options) {
1083
1394
  message: `User-scoped skill is still locked into this project; move it to the user skill roots: ${entry.id}`
1084
1395
  });
1085
1396
  }
1086
- if (asset) {
1397
+ if (asset && !registryProvenanceMismatch) {
1087
1398
  const hostFolderIssue = checkHostFolder(
1088
1399
  lockfile.host,
1089
1400
  asset.kind,
@@ -1099,6 +1410,7 @@ function checkInstalledAssets(options) {
1099
1410
  }
1100
1411
  }
1101
1412
  if (!pathExistsEvenIfDanglingSymlink4(targetAbsolutePath)) {
1413
+ if (portableDeferredSkill) continue;
1102
1414
  issues.push({
1103
1415
  type: "missing-target",
1104
1416
  id: entry.id,
@@ -1108,7 +1420,16 @@ function checkInstalledAssets(options) {
1108
1420
  continue;
1109
1421
  }
1110
1422
  const targetStats = lstatSync4(targetAbsolutePath);
1111
- if (!targetStats.isSymbolicLink()) {
1423
+ if (delivery === "snapshot" && !targetStats.isFile()) {
1424
+ issues.push({
1425
+ type: "snapshot-not-regular-file",
1426
+ id: entry.id,
1427
+ targetPath: entry.targetPath,
1428
+ message: `Managed snapshot is not a regular file: ${entry.targetPath}`
1429
+ });
1430
+ continue;
1431
+ }
1432
+ if (delivery === "symlink" && !targetStats.isSymbolicLink()) {
1112
1433
  issues.push({
1113
1434
  type: "unmanaged-conflict",
1114
1435
  id: entry.id,
@@ -1117,7 +1438,8 @@ function checkInstalledAssets(options) {
1117
1438
  });
1118
1439
  continue;
1119
1440
  }
1120
- if (!existsSync9(targetAbsolutePath)) {
1441
+ if (delivery === "symlink" && !existsSync9(targetAbsolutePath)) {
1442
+ if (portableDeferredSkill) continue;
1121
1443
  issues.push({
1122
1444
  type: "dangling-symlink",
1123
1445
  id: entry.id,
@@ -1136,7 +1458,7 @@ function checkInstalledAssets(options) {
1136
1458
  message: `Managed asset hash drifted: ${entry.id}`
1137
1459
  });
1138
1460
  }
1139
- if (!asset || !strictRegistry) continue;
1461
+ if (!asset || !strictRegistry || registryProvenanceMismatch) continue;
1140
1462
  const sourceAbsolutePath = join9(options.agentAssetsDir, asset.sourcePath);
1141
1463
  if (!existsSync9(sourceAbsolutePath)) {
1142
1464
  issues.push({
@@ -1148,7 +1470,7 @@ function checkInstalledAssets(options) {
1148
1470
  continue;
1149
1471
  }
1150
1472
  const currentSourceHash = hashAgentAssetContent(asset, options.agentAssetsDir);
1151
- if (targetHashMatchesLock && currentSourceHash !== entry.contentHash) {
1473
+ if ((delivery === "snapshot" || targetHashMatchesLock) && currentSourceHash !== entry.contentHash) {
1152
1474
  issues.push({
1153
1475
  type: "hash-drift",
1154
1476
  id: entry.id,
@@ -1157,9 +1479,16 @@ function checkInstalledAssets(options) {
1157
1479
  });
1158
1480
  }
1159
1481
  }
1160
- issues.push(...checkDuplicateSkillPlacements(options.targetDir, options.registry));
1482
+ if (!registryProvenanceMismatch) {
1483
+ issues.push(...checkDuplicateSkillPlacements(options.targetDir, options.registry));
1484
+ }
1161
1485
  return { targetDir: options.targetDir, issues };
1162
1486
  }
1487
+ function matchesRegistryProvenance(value, expected) {
1488
+ if (value === null || typeof value !== "object") return false;
1489
+ const provenance = value;
1490
+ return provenance.schema === expected.schema && provenance.version === expected.version && provenance.hash === expected.hash && provenance.assetCount === expected.assetCount;
1491
+ }
1163
1492
  function checkRegistryPlacement(lockfile, asset, targetPath) {
1164
1493
  if (lockfile.placement !== "registry") return void 0;
1165
1494
  if (asset.kind !== "skill") return void 0;
@@ -1235,6 +1564,9 @@ function pathExistsEvenIfDanglingSymlink4(path) {
1235
1564
  return false;
1236
1565
  }
1237
1566
  }
1567
+ function isProjectSkillTarget(targetPath) {
1568
+ return targetPath.startsWith(".agents/skills/") || targetPath.startsWith(".agents/manual-skills/");
1569
+ }
1238
1570
 
1239
1571
  // src/asset-targets/recommend.ts
1240
1572
  import { existsSync as existsSync10, readdirSync as readdirSync6, readFileSync as readFileSync6 } from "node:fs";
@@ -1301,13 +1633,6 @@ function recommendBundlesForTarget(targetDir) {
1301
1633
  reasons: signals.frontendSignals.map((signal) => `frontend dependency: ${signal}`)
1302
1634
  });
1303
1635
  }
1304
- if (signals.researchSignals.length > 0) {
1305
- recommendations.push({
1306
- bundleId: "research-docs",
1307
- confidence: "high",
1308
- reasons: signals.researchSignals
1309
- });
1310
- }
1311
1636
  if (signals.writingSignals.length > 0) {
1312
1637
  recommendations.push({
1313
1638
  bundleId: "novel-writing",
@@ -1877,11 +2202,12 @@ var REQUIRED_ASSETS = [
1877
2202
  "profiles/doc-only/profile.md",
1878
2203
  "docs/reference/adoption/migration-v1.1.md"
1879
2204
  ];
1880
- function runDoctor(_args) {
1881
- const assets = listAssets();
2205
+ function runPackageDoctor(_args, dependencies = {}) {
2206
+ const assets = dependencies.assets ?? listAssets("packaged");
1882
2207
  const assetPaths = new Set(assets.map((asset) => asset.path));
1883
2208
  const missing = REQUIRED_ASSETS.filter((assetPath) => !assetPaths.has(assetPath));
1884
- console.log("pro-gov doctor");
2209
+ const docGov = dependencies.docGov ?? checkDocGov();
2210
+ console.log("pro-gov package-doctor");
1885
2211
  console.log(`assets: ${assets.length}`);
1886
2212
  if (missing.length > 0) {
1887
2213
  for (const assetPath of missing) {
@@ -1890,8 +2216,11 @@ function runDoctor(_args) {
1890
2216
  } else {
1891
2217
  console.log("assets: required project-governance assets found");
1892
2218
  }
1893
- console.log(checkDocGov());
1894
- return missing.length > 0 ? 1 : 0;
2219
+ console.log(docGov.message);
2220
+ return missing.length > 0 || !docGov.ok ? 1 : 0;
2221
+ }
2222
+ function runDoctor(args) {
2223
+ return runPackageDoctor(args);
1895
2224
  }
1896
2225
  function checkDocGov() {
1897
2226
  const fromPath = spawnSync2("doc-gov", ["--help"], {
@@ -1899,20 +2228,26 @@ function checkDocGov() {
1899
2228
  stdio: "ignore"
1900
2229
  });
1901
2230
  if (!fromPath.error && fromPath.status === 0) {
1902
- return "doc-gov: available on PATH";
2231
+ return { ok: true, message: "doc-gov: available on PATH" };
1903
2232
  }
1904
2233
  const dependencyCli = resolveDocGovDependencyCli();
1905
2234
  if (!dependencyCli) {
1906
- return "doc-gov: not found; install @pieai/doc-gov beside @pieai/pro-gov for validation.";
2235
+ return {
2236
+ ok: false,
2237
+ message: "doc-gov: not found; install @pieai/doc-gov beside @pieai/pro-gov for validation."
2238
+ };
1907
2239
  }
1908
2240
  const fromDependency = spawnSync2(process.execPath, [dependencyCli, "--help"], {
1909
2241
  encoding: "utf8",
1910
2242
  stdio: "ignore"
1911
2243
  });
1912
2244
  if (!fromDependency.error && fromDependency.status === 0) {
1913
- return "doc-gov: available via package dependency";
2245
+ return { ok: true, message: "doc-gov: available via package dependency" };
1914
2246
  }
1915
- return `doc-gov: dependency found but returned status ${fromDependency.status ?? "unknown"}`;
2247
+ return {
2248
+ ok: false,
2249
+ message: `doc-gov: dependency found but returned status ${fromDependency.status ?? "unknown"}`
2250
+ };
1916
2251
  }
1917
2252
  function resolveDocGovDependencyCli() {
1918
2253
  try {
@@ -2077,7 +2412,7 @@ import {
2077
2412
  lstatSync as lstatSync6,
2078
2413
  mkdirSync as mkdirSync5,
2079
2414
  readdirSync as readdirSync7,
2080
- statSync as statSync3,
2415
+ statSync as statSync4,
2081
2416
  writeFileSync as writeFileSync4
2082
2417
  } from "node:fs";
2083
2418
  import { homedir } from "node:os";
@@ -2504,7 +2839,7 @@ function fallbackMeasure(root) {
2504
2839
  if (entry.isDirectory()) pending.push(path);
2505
2840
  else if (entry.isFile()) {
2506
2841
  try {
2507
- bytes += statSync3(path).size;
2842
+ bytes += statSync4(path).size;
2508
2843
  } catch {
2509
2844
  }
2510
2845
  }
@@ -4417,10 +4752,10 @@ function formatLink(link) {
4417
4752
  }
4418
4753
 
4419
4754
  // src/lens/scan.ts
4420
- import { spawnSync as spawnSync3 } from "node:child_process";
4421
- import { existsSync as existsSync20, readdirSync as readdirSync10, readFileSync as readFileSync13, statSync as statSync5 } from "node:fs";
4755
+ import { spawnSync as spawnSync4 } from "node:child_process";
4756
+ import { existsSync as existsSync21, readFileSync as readFileSync13, statSync as statSync6 } from "node:fs";
4422
4757
  import { homedir as homedir3 } from "node:os";
4423
- import { join as join21, relative as relative8 } from "node:path";
4758
+ import { join as join22 } from "node:path";
4424
4759
 
4425
4760
  // src/host-ssot.ts
4426
4761
  import { lstatSync as lstatSync7, readlinkSync as readlinkSync3, realpathSync as realpathSync3 } from "node:fs";
@@ -4542,7 +4877,7 @@ function safeLstat2(path) {
4542
4877
  }
4543
4878
 
4544
4879
  // src/portfolio/redundancy.ts
4545
- import { existsSync as existsSync18, readdirSync as readdirSync9, statSync as statSync4 } from "node:fs";
4880
+ import { existsSync as existsSync18, readdirSync as readdirSync9, statSync as statSync5 } from "node:fs";
4546
4881
  import { homedir as homedir2 } from "node:os";
4547
4882
  import { join as join19 } from "node:path";
4548
4883
  var DEFAULT_CACHE_THRESHOLD_BYTES = 1e9;
@@ -4657,7 +4992,7 @@ function collectDirectoryStats(root) {
4657
4992
  } else if (entry.isFile()) {
4658
4993
  fileCount += 1;
4659
4994
  try {
4660
- bytes += statSync4(path).size;
4995
+ bytes += statSync5(path).size;
4661
4996
  } catch {
4662
4997
  }
4663
4998
  }
@@ -4698,6 +5033,111 @@ function readPackageJson(path) {
4698
5033
  }
4699
5034
  }
4700
5035
 
5036
+ // src/repository-files.ts
5037
+ import { spawnSync as spawnSync3 } from "node:child_process";
5038
+ import { existsSync as existsSync20, readdirSync as readdirSync10 } from "node:fs";
5039
+ import { isAbsolute as isAbsolute5, join as join21, posix as posix3, relative as relative8 } from "node:path";
5040
+ var gitMaxBufferBytes = 64 * 1024 * 1024;
5041
+ var RepositoryFileDiscoveryError = class extends Error {
5042
+ constructor(message) {
5043
+ super(message);
5044
+ this.name = "RepositoryFileDiscoveryError";
5045
+ }
5046
+ };
5047
+ function discoverRepositoryFiles(root, options = {}) {
5048
+ const probe = runGit(root, ["rev-parse", "--is-inside-work-tree"]);
5049
+ if (!probe.ok) {
5050
+ if (!probe.notRepository || existsSync20(join21(root, ".git"))) {
5051
+ throw new RepositoryFileDiscoveryError(probe.message);
5052
+ }
5053
+ return {
5054
+ source: "filesystem",
5055
+ files: discoverFilesystemFiles(root, options)
5056
+ };
5057
+ }
5058
+ if (probe.stdout.trim() !== "true") {
5059
+ throw new RepositoryFileDiscoveryError(
5060
+ "Git repository discovery failed: target is a Git repository without a worktree."
5061
+ );
5062
+ }
5063
+ const gitFiles = runGit(root, [
5064
+ "ls-files",
5065
+ "--cached",
5066
+ "--others",
5067
+ "--exclude-standard",
5068
+ "-z",
5069
+ ...options.gitPathspecs?.length ? ["--", ...options.gitPathspecs] : []
5070
+ ]);
5071
+ if (!gitFiles.ok) throw new RepositoryFileDiscoveryError(gitFiles.message);
5072
+ const files = /* @__PURE__ */ new Set();
5073
+ for (const path of gitFiles.stdout.split("\0")) {
5074
+ if (!path) continue;
5075
+ const normalized = normalizeRepositoryRelativePath(path);
5076
+ if (!isSafeRepositoryRelativePath(normalized)) {
5077
+ throw new RepositoryFileDiscoveryError(
5078
+ `Git returned a path outside the repository boundary: ${path}`
5079
+ );
5080
+ }
5081
+ if (existsSync20(join21(root, normalized))) files.add(normalized);
5082
+ }
5083
+ return { source: "git", files: [...files].sort() };
5084
+ }
5085
+ function normalizeRepositoryRelativePath(path) {
5086
+ return posix3.normalize(path.replaceAll("\\", "/").replace(/^\.\/+/, ""));
5087
+ }
5088
+ function discoverFilesystemFiles(root, options) {
5089
+ const files = /* @__PURE__ */ new Set();
5090
+ const maxDepth = options.fallbackMaxDepth ?? Number.POSITIVE_INFINITY;
5091
+ const ignoredDirectories2 = options.fallbackIgnoredDirectories ?? /* @__PURE__ */ new Set();
5092
+ const visit = (directory, depth) => {
5093
+ if (depth > maxDepth || !existsSync20(directory)) return;
5094
+ let entries;
5095
+ try {
5096
+ entries = readdirSync10(directory, { withFileTypes: true });
5097
+ } catch {
5098
+ return;
5099
+ }
5100
+ for (const entry of entries) {
5101
+ const absolutePath = join21(directory, entry.name);
5102
+ if (entry.isDirectory()) {
5103
+ if (!ignoredDirectories2.has(entry.name)) visit(absolutePath, depth + 1);
5104
+ continue;
5105
+ }
5106
+ if (!entry.isFile()) continue;
5107
+ const relativePath = normalizeRepositoryRelativePath(relative8(root, absolutePath));
5108
+ if (isSafeRepositoryRelativePath(relativePath) && (options.fallbackIncludeFile?.(relativePath) ?? true)) {
5109
+ files.add(relativePath);
5110
+ }
5111
+ }
5112
+ };
5113
+ visit(root, 0);
5114
+ return [...files].sort();
5115
+ }
5116
+ function isSafeRepositoryRelativePath(path) {
5117
+ return path !== "" && path !== "." && !isAbsolute5(path) && !/^[a-zA-Z]:\//.test(path) && path !== ".." && !path.startsWith("../");
5118
+ }
5119
+ function runGit(root, args) {
5120
+ const result = spawnSync3("git", ["-C", root, ...args], {
5121
+ encoding: "utf8",
5122
+ maxBuffer: gitMaxBufferBytes,
5123
+ env: { ...process.env, LANG: "C", LC_ALL: "C" }
5124
+ });
5125
+ if (result.error) {
5126
+ return {
5127
+ ok: false,
5128
+ notRepository: false,
5129
+ message: `Git repository discovery failed: ${result.error.message}`
5130
+ };
5131
+ }
5132
+ if (result.status === 0) return { ok: true, stdout: result.stdout };
5133
+ const stderr = result.stderr.trim();
5134
+ return {
5135
+ ok: false,
5136
+ notRepository: /not a git repository/i.test(stderr),
5137
+ message: `Git repository discovery failed (${result.status ?? "unknown status"}): ${stderr || "no diagnostic output"}`
5138
+ };
5139
+ }
5140
+
4701
5141
  // src/lens/scan.ts
4702
5142
  var ignoredDirectories = /* @__PURE__ */ new Set([".git", ".next", ".turbo", "dist", "node_modules", "coverage"]);
4703
5143
  function scanProjectLensTarget(targetDir, options = {}) {
@@ -4714,7 +5154,7 @@ function scanProjectLensTarget(targetDir, options = {}) {
4714
5154
  includedFileCount: files.length,
4715
5155
  excludedFileCount: candidateFiles.length - files.length
4716
5156
  },
4717
- aiEntryFiles: ["AGENTS.md", "CLAUDE.md"].filter((file) => existsSync20(join21(targetDir, file))),
5157
+ aiEntryFiles: ["AGENTS.md", "CLAUDE.md"].filter((file) => existsSync21(join22(targetDir, file))),
4718
5158
  aiConfigFiles: [],
4719
5159
  hostSsot: inspectProjectHostSsot(targetDir),
4720
5160
  userHostSsot: inspectUserSkillsSsot(options.homeDir ?? process.env.HOME ?? homedir3()),
@@ -4725,17 +5165,17 @@ function scanProjectLensTarget(targetDir, options = {}) {
4725
5165
  }),
4726
5166
  packageJson,
4727
5167
  docs: {
4728
- hasDocsDirectory: existsSync20(join21(targetDir, "docs")),
5168
+ hasDocsDirectory: existsSync21(join22(targetDir, "docs")),
4729
5169
  markdownFileCount: markdownFiles.length,
4730
5170
  governanceFiles: markdownFiles.filter((file) => file.startsWith("docs/governance/") || file.startsWith("docs/policy/")).sort()
4731
5171
  },
4732
5172
  git: readGitState(targetDir),
4733
- largeFiles: files.map((file) => ({ path: file, bytes: statSync5(join21(targetDir, file)).size })).filter((file) => file.bytes >= largeFileBytes).sort((a, b) => b.bytes - a.bytes || a.path.localeCompare(b.path)).slice(0, 25)
5173
+ largeFiles: files.map((file) => ({ path: file, bytes: statSync6(join22(targetDir, file)).size })).filter((file) => file.bytes >= largeFileBytes).sort((a, b) => b.bytes - a.bytes || a.path.localeCompare(b.path)).slice(0, 25)
4734
5174
  };
4735
5175
  }
4736
5176
  function readPackageJson2(targetDir) {
4737
- const packageJsonPath = join21(targetDir, "package.json");
4738
- if (!existsSync20(packageJsonPath)) return void 0;
5177
+ const packageJsonPath = join22(targetDir, "package.json");
5178
+ if (!existsSync21(packageJsonPath)) return void 0;
4739
5179
  try {
4740
5180
  const packageJson = JSON.parse(readFileSync13(packageJsonPath, "utf8"));
4741
5181
  return {
@@ -4748,10 +5188,10 @@ function readPackageJson2(targetDir) {
4748
5188
  }
4749
5189
  }
4750
5190
  function readGitState(targetDir) {
4751
- const branch = runGit(targetDir, ["rev-parse", "--abbrev-ref", "HEAD"]);
5191
+ const branch = runGit2(targetDir, ["rev-parse", "--abbrev-ref", "HEAD"]);
4752
5192
  if (!branch.ok) return { available: false };
4753
- const head = runGit(targetDir, ["log", "-1", "--format=%H %s"]);
4754
- const status = runGit(targetDir, ["status", "-sb"]);
5193
+ const head = runGit2(targetDir, ["log", "-1", "--format=%H %s"]);
5194
+ const status = runGit2(targetDir, ["status", "-sb"]);
4755
5195
  return {
4756
5196
  available: true,
4757
5197
  branch: branch.stdout,
@@ -4759,27 +5199,18 @@ function readGitState(targetDir) {
4759
5199
  statusShort: status.ok ? status.stdout : void 0
4760
5200
  };
4761
5201
  }
4762
- function runGit(targetDir, args) {
4763
- const result = spawnSync3("git", ["-C", targetDir, ...args], {
4764
- encoding: "utf8"
5202
+ function runGit2(targetDir, args) {
5203
+ const result = spawnSync4("git", ["-C", targetDir, ...args], {
5204
+ encoding: "utf8",
5205
+ maxBuffer: 64 * 1024 * 1024
4765
5206
  });
4766
5207
  if (result.status !== 0) return { ok: false };
4767
5208
  return { ok: true, stdout: result.stdout.trim() };
4768
5209
  }
4769
5210
  function listProjectFiles(targetDir) {
4770
- const gitFiles = runGit(targetDir, [
4771
- "ls-files",
4772
- "--cached",
4773
- "--others",
4774
- "--exclude-standard",
4775
- "-z"
4776
- ]);
4777
- if (gitFiles.ok) {
4778
- return gitFiles.stdout.split("\0").filter(Boolean).map(toUnixPath4).filter((file) => existsSync20(join21(targetDir, file))).sort();
4779
- }
4780
- const files = [];
4781
- collectFiles2(targetDir, targetDir, files);
4782
- return files.sort();
5211
+ return discoverRepositoryFiles(targetDir, {
5212
+ fallbackIgnoredDirectories: ignoredDirectories
5213
+ }).files;
4783
5214
  }
4784
5215
  var excludedEvidencePrefixes = [
4785
5216
  ".agents/manual-skills/",
@@ -4792,20 +5223,6 @@ var excludedEvidencePrefixes = [
4792
5223
  function isFirstPartyEvidenceFile(file) {
4793
5224
  return !excludedEvidencePrefixes.some((prefix) => file.startsWith(prefix));
4794
5225
  }
4795
- function collectFiles2(rootDir, currentDir, files) {
4796
- if (!existsSync20(currentDir)) return;
4797
- for (const entry of readdirSync10(currentDir, { withFileTypes: true })) {
4798
- if (entry.isDirectory()) {
4799
- if (ignoredDirectories.has(entry.name)) continue;
4800
- collectFiles2(rootDir, join21(currentDir, entry.name), files);
4801
- } else if (entry.isFile()) {
4802
- files.push(toUnixPath4(relative8(rootDir, join21(currentDir, entry.name))));
4803
- }
4804
- }
4805
- }
4806
- function toUnixPath4(path) {
4807
- return path.replaceAll("\\", "/");
4808
- }
4809
5226
 
4810
5227
  // src/commands/lens.ts
4811
5228
  function runLens(args) {
@@ -4957,19 +5374,19 @@ function printUsage4() {
4957
5374
  }
4958
5375
 
4959
5376
  // src/commands/portfolio.ts
4960
- import { existsSync as existsSync32, readFileSync as readFileSync18 } from "node:fs";
4961
- import { join as join33 } from "node:path";
5377
+ import { existsSync as existsSync33, readFileSync as readFileSync18 } from "node:fs";
5378
+ import { join as join34 } from "node:path";
4962
5379
 
4963
5380
  // src/portfolio/doctor.ts
4964
- import { spawnSync as spawnSync6 } from "node:child_process";
4965
- import { existsSync as existsSync23, readFileSync as readFileSync16 } from "node:fs";
5381
+ import { spawnSync as spawnSync7 } from "node:child_process";
5382
+ import { existsSync as existsSync24, readFileSync as readFileSync16 } from "node:fs";
4966
5383
  import { createRequire as createRequire2 } from "node:module";
4967
5384
  import { homedir as homedir4 } from "node:os";
4968
- import { dirname as dirname15, join as join24 } from "node:path";
5385
+ import { dirname as dirname15, join as join25 } from "node:path";
4969
5386
  import { fileURLToPath as fileURLToPath4 } from "node:url";
4970
5387
 
4971
5388
  // src/host-tooling/inventory.ts
4972
- import { spawnSync as spawnSync4 } from "node:child_process";
5389
+ import { spawnSync as spawnSync5 } from "node:child_process";
4973
5390
  function inspectHostTooling(requirements, runner = defaultRunner2) {
4974
5391
  const hosts = [];
4975
5392
  const issues = [];
@@ -5043,7 +5460,7 @@ function parseHostPlugins(host, value) {
5043
5460
  });
5044
5461
  }
5045
5462
  function defaultRunner2({ command: command2 }) {
5046
- const result = spawnSync4(command2[0] ?? "", command2.slice(1), {
5463
+ const result = spawnSync5(command2[0] ?? "", command2.slice(1), {
5047
5464
  encoding: "utf8",
5048
5465
  timeout: 1e4
5049
5466
  });
@@ -5058,8 +5475,8 @@ function isRecord2(value) {
5058
5475
  }
5059
5476
 
5060
5477
  // src/portfolio/asset-state.ts
5061
- import { existsSync as existsSync21, lstatSync as lstatSync8, readFileSync as readFileSync14 } from "node:fs";
5062
- import { join as join22 } from "node:path";
5478
+ import { existsSync as existsSync22, lstatSync as lstatSync8, readFileSync as readFileSync14 } from "node:fs";
5479
+ import { join as join23 } from "node:path";
5063
5480
  function comparePortfolioAssetState(options) {
5064
5481
  const expectedManifest = readPlanDocument(
5065
5482
  options.expectedPlan,
@@ -5070,10 +5487,10 @@ function comparePortfolioAssetState(options) {
5070
5487
  ".pro-gov/assets.lock.json"
5071
5488
  );
5072
5489
  const currentManifest = readJsonFile(
5073
- join22(options.targetDir, ".pro-gov/assets.json")
5490
+ join23(options.targetDir, ".pro-gov/assets.json")
5074
5491
  );
5075
5492
  const currentLock = readJsonFile(
5076
- join22(options.targetDir, ".pro-gov/assets.lock.json")
5493
+ join23(options.targetDir, ".pro-gov/assets.lock.json")
5077
5494
  );
5078
5495
  const issues = [];
5079
5496
  if (!sameStrings(currentManifest?.bundleIds, expectedManifest?.bundleIds)) {
@@ -5101,7 +5518,7 @@ function comparePortfolioAssetState(options) {
5101
5518
  (action) => action.type === "adopt-symlink" && action.assetId === entry.id && action.legacyTargetPath === entry.targetPath
5102
5519
  ))
5103
5520
  continue;
5104
- const targetAbsolutePath = join22(options.targetDir, entry.targetPath);
5521
+ const targetAbsolutePath = join23(options.targetDir, entry.targetPath);
5105
5522
  if (!pathIsSymlink(targetAbsolutePath)) continue;
5106
5523
  issues.push({
5107
5524
  type: "orphaned-managed-symlink",
@@ -5123,7 +5540,7 @@ function readPlanDocument(plan, targetPath) {
5123
5540
  }
5124
5541
  }
5125
5542
  function readJsonFile(path) {
5126
- if (!existsSync21(path)) return void 0;
5543
+ if (!existsSync22(path)) return void 0;
5127
5544
  try {
5128
5545
  return JSON.parse(readFileSync14(path, "utf8"));
5129
5546
  } catch {
@@ -5134,7 +5551,12 @@ function sameStrings(left, right) {
5134
5551
  return JSON.stringify([...left ?? []].sort()) === JSON.stringify([...right ?? []].sort());
5135
5552
  }
5136
5553
  function sameLock(left, right) {
5137
- return JSON.stringify(normalizeLock(left)) === JSON.stringify(normalizeLock(right));
5554
+ if (JSON.stringify(normalizeLock(left)) !== JSON.stringify(normalizeLock(right))) return false;
5555
+ if (!hasRegistryProvenance(left) || !hasRegistryProvenance(right)) return true;
5556
+ return JSON.stringify(left.registryProvenance) === JSON.stringify(right.registryProvenance);
5557
+ }
5558
+ function hasRegistryProvenance(lock) {
5559
+ return lock !== void 0 && Object.prototype.hasOwnProperty.call(lock, "registryProvenance");
5138
5560
  }
5139
5561
  function normalizeLock(lock) {
5140
5562
  return {
@@ -5153,9 +5575,9 @@ function pathIsSymlink(path) {
5153
5575
  }
5154
5576
 
5155
5577
  // src/portfolio/version-policy.ts
5156
- import { spawnSync as spawnSync5 } from "node:child_process";
5157
- import { existsSync as existsSync22, readFileSync as readFileSync15, readdirSync as readdirSync11 } from "node:fs";
5158
- import { dirname as dirname14, join as join23 } from "node:path";
5578
+ import { spawnSync as spawnSync6 } from "node:child_process";
5579
+ import { existsSync as existsSync23, lstatSync as lstatSync9, readFileSync as readFileSync15 } from "node:fs";
5580
+ import { dirname as dirname14, join as join24 } from "node:path";
5159
5581
  function inspectVersionPolicy(root, policy, projectType) {
5160
5582
  if (!policy) return { status: "compliant", packages: [], runtimes: [], attentionCount: 0 };
5161
5583
  const packageManifests = collectPackageManifests(root);
@@ -5245,7 +5667,7 @@ function inspectRuntime(expectedName, expectedVersion) {
5245
5667
  function readRuntimeVersion(name) {
5246
5668
  if (name === "node") return process.versions.node;
5247
5669
  if (name !== "deno") return void 0;
5248
- const result = spawnSync5("deno", ["--version"], { encoding: "utf8" });
5670
+ const result = spawnSync6("deno", ["--version"], { encoding: "utf8" });
5249
5671
  if (result.status !== 0) return void 0;
5250
5672
  return /^deno\s+(\d+\.\d+\.\d+)/m.exec(result.stdout)?.[1];
5251
5673
  }
@@ -5264,7 +5686,7 @@ function findDeclaredVersion(packageJson, name) {
5264
5686
  function readInstalledVersion(root, name, fromDirectory = root) {
5265
5687
  let current = fromDirectory;
5266
5688
  while (true) {
5267
- const packageJson = readJson2(join23(current, "node_modules", name, "package.json"));
5689
+ const packageJson = readJson2(join24(current, "node_modules", name, "package.json"));
5268
5690
  if (typeof packageJson?.version === "string") return packageJson.version;
5269
5691
  if (current === root) return void 0;
5270
5692
  const parent = dirname14(current);
@@ -5273,7 +5695,6 @@ function readInstalledVersion(root, name, fromDirectory = root) {
5273
5695
  }
5274
5696
  }
5275
5697
  function collectPackageManifests(root) {
5276
- const manifests = [];
5277
5698
  const ignored = /* @__PURE__ */ new Set([
5278
5699
  ".git",
5279
5700
  ".next",
@@ -5297,37 +5718,36 @@ function collectPackageManifests(root) {
5297
5718
  ".pnpm-store",
5298
5719
  ".tmp-repos"
5299
5720
  ]);
5300
- const visit = (directory, depth) => {
5301
- if (depth > 6) return;
5302
- let entries;
5721
+ const manifests = /* @__PURE__ */ new Map();
5722
+ const addManifest = (relativePath) => {
5723
+ const directorySegments = relativePath.split("/").slice(0, -1);
5724
+ if (relativePath.split("/").at(-1) !== "package.json" || directorySegments.length > 6 || directorySegments.some((segment) => ignored.has(segment))) {
5725
+ return;
5726
+ }
5727
+ const path = join24(root, relativePath);
5303
5728
  try {
5304
- entries = readdirSync11(directory, { withFileTypes: true });
5729
+ if (!lstatSync9(path).isFile()) return;
5305
5730
  } catch {
5306
5731
  return;
5307
5732
  }
5308
- for (const entry of entries) {
5309
- const path = join23(directory, entry.name);
5310
- if (entry.isFile() && entry.name === "package.json") {
5311
- const packageJson = readJson2(path);
5312
- if (packageJson)
5313
- manifests.push({
5314
- path: path.slice(root.length + 1) || "package.json",
5315
- directory,
5316
- packageJson
5317
- });
5318
- } else if (entry.isDirectory() && !ignored.has(entry.name)) {
5319
- visit(path, depth + 1);
5320
- }
5321
- }
5733
+ const packageJson = readJson2(path);
5734
+ if (!packageJson) return;
5735
+ manifests.set(relativePath, { path: relativePath, directory: dirname14(path), packageJson });
5322
5736
  };
5323
- visit(root, 0);
5324
- return manifests.sort((a, b) => a.path.localeCompare(b.path));
5737
+ const discovery = discoverRepositoryFiles(root, {
5738
+ gitPathspecs: ["package.json", ":(glob)**/package.json"],
5739
+ fallbackIgnoredDirectories: ignored,
5740
+ fallbackMaxDepth: 6,
5741
+ fallbackIncludeFile: (path) => path.split("/").at(-1) === "package.json"
5742
+ });
5743
+ for (const relativePath of discovery.files) addManifest(relativePath);
5744
+ return [...manifests.values()].sort((a, b) => a.path.localeCompare(b.path));
5325
5745
  }
5326
5746
  function unique(values) {
5327
5747
  return [...new Set(values)];
5328
5748
  }
5329
5749
  function readJson2(path) {
5330
- if (!existsSync22(path)) return void 0;
5750
+ if (!existsSync23(path)) return void 0;
5331
5751
  try {
5332
5752
  return JSON.parse(readFileSync15(path, "utf8"));
5333
5753
  } catch {
@@ -5362,12 +5782,12 @@ function inspectTarget(options) {
5362
5782
  const { target } = options;
5363
5783
  const hostSsot = inspectProjectHostSsot(target.path);
5364
5784
  const issues = [];
5365
- const packageJson = readJson3(join24(target.path, "package.json"));
5785
+ const packageJson = readJson3(join25(target.path, "package.json"));
5366
5786
  const packages = {};
5367
5787
  for (const packageName of ["@pieai/pro-gov", "@pieai/doc-gov"]) {
5368
5788
  const declared = packageJson?.devDependencies?.[packageName] ?? packageJson?.dependencies?.[packageName];
5369
5789
  const installedPackage = readJson3(
5370
- join24(target.path, "node_modules", packageName, "package.json")
5790
+ join25(target.path, "node_modules", packageName, "package.json")
5371
5791
  );
5372
5792
  const installed = installedPackage?.version;
5373
5793
  const expected = options.expectedPackageVersions[packageName];
@@ -5427,7 +5847,7 @@ function inspectTarget(options) {
5427
5847
  type: "asset-lock-drift",
5428
5848
  message: error instanceof Error ? error.message : String(error)
5429
5849
  });
5430
- if (!existsSync23(join24(target.path, ".pro-gov/assets.json"))) {
5850
+ if (!existsSync24(join25(target.path, ".pro-gov/assets.json"))) {
5431
5851
  issues.push({ type: "bundle-drift", message: "Target asset manifest is missing." });
5432
5852
  }
5433
5853
  }
@@ -5445,15 +5865,15 @@ function inspectTarget(options) {
5445
5865
  };
5446
5866
  }
5447
5867
  function readTargetAssetHost(targetDir) {
5448
- const lockfile = readJson3(join24(targetDir, ".pro-gov/assets.lock.json"));
5868
+ const lockfile = readJson3(join25(targetDir, ".pro-gov/assets.lock.json"));
5449
5869
  return isAssetRegistryHost(lockfile?.host) ? lockfile.host : void 0;
5450
5870
  }
5451
5871
  function isAssetRegistryHost(value) {
5452
5872
  return value === "codex" || value === "claude-code" || value === "gemini-cli" || value === "antigravity";
5453
5873
  }
5454
5874
  function runTargetChecks(target) {
5455
- const proGovCli = join24(target.path, "node_modules/@pieai/pro-gov/dist/cli.js");
5456
- const docGovCli = join24(target.path, "node_modules/@pieai/doc-gov/dist/cli.js");
5875
+ const proGovCli = join25(target.path, "node_modules/@pieai/pro-gov/dist/cli.js");
5876
+ const docGovCli = join25(target.path, "node_modules/@pieai/doc-gov/dist/cli.js");
5457
5877
  const commands = [
5458
5878
  {
5459
5879
  name: "pro-gov doctor",
@@ -5464,8 +5884,8 @@ function runTargetChecks(target) {
5464
5884
  { name: "doc-gov scan --check", cli: docGovCli, args: ["scan", "--check"] }
5465
5885
  ];
5466
5886
  return commands.map((command2) => {
5467
- if (!existsSync23(command2.cli)) return { name: command2.name, status: null };
5468
- const result = spawnSync6(process.execPath, [command2.cli, ...command2.args], {
5887
+ if (!existsSync24(command2.cli)) return { name: command2.name, status: null };
5888
+ const result = spawnSync7(process.execPath, [command2.cli, ...command2.args], {
5469
5889
  cwd: target.path,
5470
5890
  encoding: "utf8",
5471
5891
  timeout: 3e4
@@ -5474,13 +5894,13 @@ function runTargetChecks(target) {
5474
5894
  });
5475
5895
  }
5476
5896
  function inspectGit(path) {
5477
- const inside = spawnSync6("git", ["rev-parse", "--is-inside-work-tree"], {
5897
+ const inside = spawnSync7("git", ["rev-parse", "--is-inside-work-tree"], {
5478
5898
  cwd: path,
5479
5899
  encoding: "utf8"
5480
5900
  });
5481
5901
  if (inside.status !== 0) return { isRepository: false, dirty: false };
5482
- const status = spawnSync6("git", ["status", "--porcelain"], { cwd: path, encoding: "utf8" });
5483
- const branch = spawnSync6("git", ["branch", "--show-current"], { cwd: path, encoding: "utf8" });
5902
+ const status = spawnSync7("git", ["status", "--porcelain"], { cwd: path, encoding: "utf8" });
5903
+ const branch = spawnSync7("git", ["branch", "--show-current"], { cwd: path, encoding: "utf8" });
5484
5904
  return {
5485
5905
  isRepository: true,
5486
5906
  dirty: status.stdout.trim().length > 0,
@@ -5505,14 +5925,14 @@ function getExpectedPackageVersions() {
5505
5925
  function findOwnPackageJson() {
5506
5926
  let current = dirname15(fileURLToPath4(import.meta.url));
5507
5927
  for (let depth = 0; depth < 5; depth += 1) {
5508
- const candidate = join24(current, "package.json");
5509
- if (existsSync23(candidate)) return candidate;
5928
+ const candidate = join25(current, "package.json");
5929
+ if (existsSync24(candidate)) return candidate;
5510
5930
  current = dirname15(current);
5511
5931
  }
5512
5932
  return "";
5513
5933
  }
5514
5934
  function readJson3(path) {
5515
- if (!path || !existsSync23(path)) return void 0;
5935
+ if (!path || !existsSync24(path)) return void 0;
5516
5936
  try {
5517
5937
  return JSON.parse(readFileSync16(path, "utf8"));
5518
5938
  } catch {
@@ -5531,15 +5951,15 @@ function deduplicateIssues(issues) {
5531
5951
 
5532
5952
  // src/portfolio/ai-health/index.ts
5533
5953
  import { homedir as homedir5 } from "node:os";
5534
- import { dirname as dirname17, join as join32, resolve as resolve9 } from "node:path";
5954
+ import { dirname as dirname17, join as join33, resolve as resolve9 } from "node:path";
5535
5955
 
5536
5956
  // src/portfolio/ai-health/entries.ts
5537
- import { existsSync as existsSync25, lstatSync as lstatSync10, realpathSync as realpathSync5 } from "node:fs";
5538
- import { join as join25 } from "node:path";
5957
+ import { existsSync as existsSync26, lstatSync as lstatSync11, realpathSync as realpathSync5 } from "node:fs";
5958
+ import { join as join26 } from "node:path";
5539
5959
 
5540
5960
  // src/portfolio/ai-health/shared.ts
5541
5961
  import { execFileSync as execFileSync2 } from "node:child_process";
5542
- import { existsSync as existsSync24, lstatSync as lstatSync9, readFileSync as readFileSync17, readdirSync as readdirSync12, realpathSync as realpathSync4, statSync as statSync6 } from "node:fs";
5962
+ import { existsSync as existsSync25, lstatSync as lstatSync10, readFileSync as readFileSync17, readdirSync as readdirSync11, realpathSync as realpathSync4, statSync as statSync7 } from "node:fs";
5543
5963
  function safeRealpath(path) {
5544
5964
  try {
5545
5965
  return realpathSync4(path);
@@ -5569,7 +5989,7 @@ function jsonObjectKeys(path, key) {
5569
5989
  return Object.keys(value[key]).sort();
5570
5990
  }
5571
5991
  function tomlMcpNames(path) {
5572
- if (!existsSync24(path)) return [];
5992
+ if (!existsSync25(path)) return [];
5573
5993
  const names = /* @__PURE__ */ new Set();
5574
5994
  for (const line of safeRead(path).split(/\r?\n/)) {
5575
5995
  const match = line.match(/^\s*\[mcp_servers\.(?:"([^"]+)"|([^.\]]+))\]\s*$/);
@@ -5594,21 +6014,21 @@ function safeRead(path) {
5594
6014
  }
5595
6015
  function safeReadDir(path) {
5596
6016
  try {
5597
- return readdirSync12(path).sort();
6017
+ return readdirSync11(path).sort();
5598
6018
  } catch {
5599
6019
  return [];
5600
6020
  }
5601
6021
  }
5602
6022
  function safeIsDirectory(path) {
5603
6023
  try {
5604
- return statSync6(path).isDirectory();
6024
+ return statSync7(path).isDirectory();
5605
6025
  } catch {
5606
6026
  return false;
5607
6027
  }
5608
6028
  }
5609
6029
  function pathLexists(path) {
5610
6030
  try {
5611
- lstatSync9(path);
6031
+ lstatSync10(path);
5612
6032
  return true;
5613
6033
  } catch {
5614
6034
  return false;
@@ -5646,12 +6066,12 @@ function hasWorkflowReminderHooks(hooks) {
5646
6066
  );
5647
6067
  }
5648
6068
  function inspectEntries(root) {
5649
- const agentsPath = join25(root, "AGENTS.md");
5650
- const agents = !existsSync25(agentsPath) ? "missing" : safeRead(agentsPath).includes("PGS-ROUTER:BEGIN") ? "pgs-router" : "custom";
5651
- const claudePath = join25(root, "CLAUDE.md");
6069
+ const agentsPath = join26(root, "AGENTS.md");
6070
+ const agents = !existsSync26(agentsPath) ? "missing" : safeRead(agentsPath).includes("PGS-ROUTER:BEGIN") ? "pgs-router" : "custom";
6071
+ const claudePath = join26(root, "CLAUDE.md");
5652
6072
  let claude = "missing";
5653
6073
  if (pathLexists(claudePath)) {
5654
- const info = lstatSync10(claudePath);
6074
+ const info = lstatSync11(claudePath);
5655
6075
  if (info.isSymbolicLink()) {
5656
6076
  try {
5657
6077
  claude = realpathSync5(claudePath) === realpathSync5(agentsPath) ? "agents-symlink" : "custom";
@@ -5665,10 +6085,45 @@ function inspectEntries(root) {
5665
6085
  }
5666
6086
  return { agents, claude, gemini: inspectOptionalEntry(root, "GEMINI.md", agentsPath) };
5667
6087
  }
6088
+ var AGENT_LINK_ROOTS = [".agents/workflows", ".agents/commands", ".claude/commands"];
6089
+ function inspectAgentLinks(root) {
6090
+ const entries = AGENT_LINK_ROOTS.flatMap((directory) => {
6091
+ const directoryPath = join26(root, directory);
6092
+ if (!pathLexists(directoryPath)) return [];
6093
+ try {
6094
+ if (!lstatSync11(directoryPath).isDirectory()) return [];
6095
+ } catch {
6096
+ return [];
6097
+ }
6098
+ return safeReadDir(directoryPath).filter((name) => !name.startsWith(".")).flatMap((name) => {
6099
+ const relativePath = `${directory}/${name}`;
6100
+ const path = join26(root, relativePath);
6101
+ let stat;
6102
+ try {
6103
+ stat = lstatSync11(path);
6104
+ } catch {
6105
+ return [];
6106
+ }
6107
+ let kind = stat.isSymbolicLink() ? "symlink" : stat.isDirectory() ? "directory" : "file";
6108
+ if (stat.isSymbolicLink()) {
6109
+ try {
6110
+ realpathSync5(path);
6111
+ } catch {
6112
+ kind = "dangling-symlink";
6113
+ }
6114
+ }
6115
+ return [{ path: relativePath, kind, tracked: gitTracks(root, relativePath) }];
6116
+ });
6117
+ });
6118
+ return {
6119
+ entries,
6120
+ trackedDangling: entries.filter((entry) => entry.tracked && entry.kind === "dangling-symlink").map((entry) => entry.path)
6121
+ };
6122
+ }
5668
6123
  function inspectOptionalEntry(root, filename, agentsPath) {
5669
- const path = join25(root, filename);
6124
+ const path = join26(root, filename);
5670
6125
  if (!pathLexists(path)) return "missing";
5671
- const info = lstatSync10(path);
6126
+ const info = lstatSync11(path);
5672
6127
  if (info.isSymbolicLink()) {
5673
6128
  try {
5674
6129
  return realpathSync5(path) === realpathSync5(agentsPath) ? "agents-symlink" : "custom";
@@ -5706,7 +6161,7 @@ function inspectHooks(root) {
5706
6161
  { host: "codex", path: ".codex/hooks.json" }
5707
6162
  ];
5708
6163
  return configs.map((config) => {
5709
- const value = readJson4(join25(root, config.path));
6164
+ const value = readJson4(join26(root, config.path));
5710
6165
  const counts = /* @__PURE__ */ new Map();
5711
6166
  collectHookEvents(value, counts);
5712
6167
  return {
@@ -5727,18 +6182,18 @@ function collectHookEvents(value, counts) {
5727
6182
  }
5728
6183
  }
5729
6184
  function inspectDocs(root, expected) {
5730
- const packageJson = readJson4(join25(root, "package.json"));
6185
+ const packageJson = readJson4(join26(root, "package.json"));
5731
6186
  const dependencies = isRecord3(packageJson) ? { ...recordOrEmpty(packageJson.dependencies), ...recordOrEmpty(packageJson.devDependencies) } : {};
5732
6187
  const docGov = dependencyVersion(dependencies["@pieai/doc-gov"]);
5733
6188
  const proGov = dependencyVersion(dependencies["@pieai/pro-gov"]);
5734
- const routerMatch = safeRead(join25(root, "AGENTS.md")).match(/PGS-ROUTER:BEGIN\s+v([0-9.]+)/);
6189
+ const routerMatch = safeRead(join26(root, "AGENTS.md")).match(/PGS-ROUTER:BEGIN\s+v([0-9.]+)/);
5735
6190
  const declared = [docGov, proGov].filter((value) => Boolean(value));
5736
6191
  return {
5737
6192
  routerVersion: routerMatch?.[1],
5738
6193
  expectedRouterVersion: CURRENT_ROUTER_VERSION,
5739
6194
  routerAligned: routerMatch?.[1] === CURRENT_ROUTER_VERSION,
5740
- manifest: existsSync25(join25(root, "docs/governance/MANIFEST.yml")),
5741
- currentWork: existsSync25(join25(root, "docs/reference/execution/current-work.md")),
6195
+ manifest: existsSync26(join26(root, "docs/governance/MANIFEST.yml")),
6196
+ currentWork: existsSync26(join26(root, "docs/reference/execution/current-work.md")),
5742
6197
  packages: {
5743
6198
  expected,
5744
6199
  docGov,
@@ -5801,18 +6256,18 @@ function inspectGit2(root) {
5801
6256
 
5802
6257
  // src/portfolio/ai-health/hosts.ts
5803
6258
  import { execFileSync as execFileSync5 } from "node:child_process";
5804
- import { existsSync as existsSync27 } from "node:fs";
5805
- import { join as join27, resolve as resolve8, sep as sep2 } from "node:path";
6259
+ import { existsSync as existsSync28 } from "node:fs";
6260
+ import { join as join28, resolve as resolve8, sep as sep2 } from "node:path";
5806
6261
 
5807
6262
  // src/portfolio/ai-health/devspace.ts
5808
6263
  import { execFileSync as execFileSync4 } from "node:child_process";
5809
- import { existsSync as existsSync26, statSync as statSync7 } from "node:fs";
5810
- import { join as join26, relative as relative9, resolve as resolve7, sep } from "node:path";
6264
+ import { existsSync as existsSync27, statSync as statSync8 } from "node:fs";
6265
+ import { join as join27, relative as relative9, resolve as resolve7, sep } from "node:path";
5811
6266
  function inspectDevSpaceHealth(options) {
5812
6267
  const run = options.run ?? runDevSpaceCommand;
5813
- const configDirectory = join26(options.homeDir, ".devspace");
5814
- const configPath = join26(configDirectory, "config.json");
5815
- const authPath = join26(configDirectory, "auth.json");
6268
+ const configDirectory = join27(options.homeDir, ".devspace");
6269
+ const configPath = join27(configDirectory, "config.json");
6270
+ const authPath = join27(configDirectory, "auth.json");
5816
6271
  const installedResult = run("devspace", ["--version"], 3e3);
5817
6272
  const installedVersion = installedResult.ok ? installedResult.stdout.match(/\b\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?\b/)?.[0] : void 0;
5818
6273
  const latestResult = run(
@@ -5835,9 +6290,9 @@ function inspectDevSpaceHealth(options) {
5835
6290
  (repositoryPath) => allowedRoots.some((root) => isPathInside(repositoryPath, root))
5836
6291
  ) ? "complete" : "partial";
5837
6292
  const bind = configExists && typeof configValue.host === "string" ? isLoopbackHost(configValue.host) ? "loopback" : "non-loopback" : "unknown";
5838
- const directoryMode = existsSync26(configDirectory) ? modeString(statSync7(configDirectory).mode) : void 0;
5839
- const fileMode = existsSync26(configPath) ? modeString(statSync7(configPath).mode) : void 0;
5840
- const authMode = existsSync26(authPath) ? modeString(statSync7(authPath).mode) : void 0;
6293
+ const directoryMode = existsSync27(configDirectory) ? modeString(statSync8(configDirectory).mode) : void 0;
6294
+ const fileMode = existsSync27(configPath) ? modeString(statSync8(configPath).mode) : void 0;
6295
+ const authMode = existsSync27(authPath) ? modeString(statSync8(authPath).mode) : void 0;
5841
6296
  const update = installedVersion && latestVersion ? installedVersion === latestVersion ? "current" : "available" : "unknown";
5842
6297
  const recommendations = [];
5843
6298
  let status = "healthy";
@@ -5851,7 +6306,7 @@ function inspectDevSpaceHealth(options) {
5851
6306
  };
5852
6307
  if (!installedResult.ok) unhealthy("\u672C\u673A\u672A\u53D1\u73B0 DevSpace\uFF1B\u65E0\u6CD5\u4F7F\u7528\u5BBF\u4E3B\u5DE5\u4F5C\u533A\u670D\u52A1\u3002");
5853
6308
  if (!configExists) unhealthy("\u7F3A\u5C11 ~/.devspace/config.json\u3002");
5854
- if (!existsSync26(authPath)) unhealthy("\u7F3A\u5C11 ~/.devspace/auth.json\u3002");
6309
+ if (!existsSync27(authPath)) unhealthy("\u7F3A\u5C11 ~/.devspace/auth.json\u3002");
5855
6310
  if (directoryMode && directoryMode !== "700")
5856
6311
  unhealthy(`~/.devspace \u76EE\u5F55\u6743\u9650\u4E3A ${directoryMode}\uFF0C\u5E94\u6536\u7D27\u4E3A 700\u3002`);
5857
6312
  if (fileMode && fileMode !== "600") unhealthy(`DevSpace \u914D\u7F6E\u6587\u4EF6\u6743\u9650\u4E3A ${fileMode}\uFF0C\u5E94\u4E3A 600\u3002`);
@@ -5884,7 +6339,7 @@ function inspectDevSpaceHealth(options) {
5884
6339
  exists: configExists,
5885
6340
  ...directoryMode ? { directoryMode } : {},
5886
6341
  ...fileMode ? { fileMode } : {},
5887
- authExists: existsSync26(authPath),
6342
+ authExists: existsSync27(authPath),
5888
6343
  ...authMode ? { authMode } : {},
5889
6344
  bind,
5890
6345
  portValid: configExists && typeof configValue.port === "number" && Number.isInteger(configValue.port) && configValue.port > 0 && configValue.port <= 65535,
@@ -5931,9 +6386,9 @@ var MCP_DISCOVERY_PATHS = {
5931
6386
  }
5932
6387
  };
5933
6388
  function inspectHostEnvironment(homeDir, grokVersion, repositoryPaths, devspaceSettings) {
5934
- const codexConfig = join27(homeDir, MCP_DISCOVERY_PATHS.user.codex);
5935
- const claudeConfig = join27(homeDir, MCP_DISCOVERY_PATHS.user.claudeCode);
5936
- const grokConfig = join27(homeDir, MCP_DISCOVERY_PATHS.user.grok);
6389
+ const codexConfig = join28(homeDir, MCP_DISCOVERY_PATHS.user.codex);
6390
+ const claudeConfig = join28(homeDir, MCP_DISCOVERY_PATHS.user.claudeCode);
6391
+ const grokConfig = join28(homeDir, MCP_DISCOVERY_PATHS.user.grok);
5937
6392
  const hostEnvironment = {
5938
6393
  mcp: {
5939
6394
  codexUser: { path: codexConfig, names: tomlMcpNames(codexConfig) },
@@ -5941,11 +6396,11 @@ function inspectHostEnvironment(homeDir, grokVersion, repositoryPaths, devspaceS
5941
6396
  grokUser: { path: grokConfig, names: tomlMcpNames(grokConfig) }
5942
6397
  },
5943
6398
  skills: {
5944
- codexUser: inspectSkillRoot(join27(homeDir, ".agents/skills")),
5945
- claudeCodeUser: inspectSkillRoot(join27(homeDir, ".claude/skills")),
5946
- grokUser: inspectSkillRoot(join27(homeDir, ".grok/skills")),
5947
- grokAgentsCompatibility: inspectSkillRoot(join27(homeDir, ".agents/skills")),
5948
- grokClaudeCompatibility: inspectSkillRoot(join27(homeDir, ".claude/skills")),
6399
+ codexUser: inspectSkillRoot(join28(homeDir, ".agents/skills")),
6400
+ claudeCodeUser: inspectSkillRoot(join28(homeDir, ".claude/skills")),
6401
+ grokUser: inspectSkillRoot(join28(homeDir, ".grok/skills")),
6402
+ grokAgentsCompatibility: inspectSkillRoot(join28(homeDir, ".agents/skills")),
6403
+ grokClaudeCompatibility: inspectSkillRoot(join28(homeDir, ".claude/skills")),
5949
6404
  ssot: inspectUserSkillsSsot(homeDir)
5950
6405
  },
5951
6406
  grok: {
@@ -5966,13 +6421,13 @@ function inspectHostEnvironment(homeDir, grokVersion, repositoryPaths, devspaceS
5966
6421
  function inspectSkillRoot(path) {
5967
6422
  const exists = pathLexists(path) && safeIsDirectory(path);
5968
6423
  const names = exists ? safeReadDir(path).filter(
5969
- (name) => !name.startsWith(".") && existsSync27(join27(path, name, "SKILL.md"))
6424
+ (name) => !name.startsWith(".") && existsSync28(join28(path, name, "SKILL.md"))
5970
6425
  ) : [];
5971
6426
  return { path, exists, names };
5972
6427
  }
5973
6428
  function claudeProjectLocalMcpNames(homeDir, root) {
5974
6429
  if (!homeDir) return [];
5975
- const value = readJson4(join27(homeDir, MCP_DISCOVERY_PATHS.user.claudeCode));
6430
+ const value = readJson4(join28(homeDir, MCP_DISCOVERY_PATHS.user.claudeCode));
5976
6431
  if (!isRecord3(value) || !isRecord3(value.projects)) return [];
5977
6432
  const candidates = new Set(
5978
6433
  [resolve8(root), safeRealpath(root)].filter((path) => Boolean(path))
@@ -6002,7 +6457,7 @@ function inspectGrokProject(root, homeDir, grokVersion) {
6002
6457
  execFileSync5("grok", ["inspect", "--json"], {
6003
6458
  cwd: root,
6004
6459
  encoding: "utf8",
6005
- env: { ...process.env, HOME: homeDir, GROK_HOME: join27(homeDir, ".grok") },
6460
+ env: { ...process.env, HOME: homeDir, GROK_HOME: join28(homeDir, ".grok") },
6006
6461
  maxBuffer: 10 * 1024 * 1024,
6007
6462
  stdio: ["ignore", "pipe", "ignore"],
6008
6463
  timeout: 8e3
@@ -6010,7 +6465,7 @@ function inspectGrokProject(root, homeDir, grokVersion) {
6010
6465
  );
6011
6466
  if (!isRecord3(value)) return empty("failed");
6012
6467
  const userClaudeNames = new Set(
6013
- jsonObjectKeys(join27(homeDir, MCP_DISCOVERY_PATHS.user.claudeCode), "mcpServers")
6468
+ jsonObjectKeys(join28(homeDir, MCP_DISCOVERY_PATHS.user.claudeCode), "mcpServers")
6014
6469
  );
6015
6470
  const localClaudeNames = new Set(claudeProjectLocalMcpNames(homeDir, root));
6016
6471
  const effectiveMcp = Array.isArray(value.mcpServers) ? value.mcpServers.flatMap((item) => {
@@ -6080,10 +6535,10 @@ function inferGrokMcpScope(name, sourceType, sourcePath, root, homeDir, userClau
6080
6535
  }
6081
6536
  const resolvedSource = safeRealpath(sourcePath) ?? resolve8(sourcePath);
6082
6537
  const resolvedRoot = safeRealpath(root) ?? resolve8(root);
6083
- if (resolvedSource === join27(resolvedRoot, MCP_DISCOVERY_PATHS.project.claudeCodeShared))
6538
+ if (resolvedSource === join28(resolvedRoot, MCP_DISCOVERY_PATHS.project.claudeCodeShared))
6084
6539
  return "project-shared";
6085
6540
  if (resolvedSource.startsWith(resolvedRoot + sep2)) return "project";
6086
- if (homeDir && resolvedSource === join27(resolve8(homeDir), MCP_DISCOVERY_PATHS.user.claudeCode)) {
6541
+ if (homeDir && resolvedSource === join28(resolve8(homeDir), MCP_DISCOVERY_PATHS.user.claudeCode)) {
6087
6542
  if (localClaudeNames.has(name)) return "project-local";
6088
6543
  if (userClaudeNames.has(name)) return "user";
6089
6544
  }
@@ -6093,10 +6548,10 @@ function inferGrokMcpScope(name, sourceType, sourcePath, root, homeDir, userClau
6093
6548
  }
6094
6549
 
6095
6550
  // src/portfolio/ai-health/secrets.ts
6096
- import { existsSync as existsSync28, lstatSync as lstatSync11, readdirSync as readdirSync13, statSync as statSync8 } from "node:fs";
6097
- import { join as join28, relative as relative10, sep as sep3 } from "node:path";
6551
+ import { existsSync as existsSync29, lstatSync as lstatSync12, readdirSync as readdirSync12, statSync as statSync9 } from "node:fs";
6552
+ import { join as join29, relative as relative10, sep as sep3 } from "node:path";
6098
6553
  function inspectRepositorySecrets(root, id, secretsRoot, isRepository, environmentPolicy) {
6099
- const centralPath = join28(secretsRoot, id);
6554
+ const centralPath = join29(secretsRoot, id);
6100
6555
  const centralRealPath = safeRealpath(centralPath);
6101
6556
  const localOnlyReasons = new Map(
6102
6557
  (environmentPolicy?.localOnly ?? []).map((entry) => [entry.path, entry.reason])
@@ -6108,16 +6563,16 @@ function inspectRepositorySecrets(root, id, secretsRoot, isRepository, environme
6108
6563
  tracked: isRepository ? gitTracks(root, path) : false,
6109
6564
  template: isEnvironmentTemplate(path),
6110
6565
  fixture: isEnvironmentFixture(path),
6111
- symlink: lstatSync11(join28(root, path)).isSymbolicLink(),
6112
- centralized: pointsInside(join28(root, path), centralRealPath),
6566
+ symlink: lstatSync12(join29(root, path)).isSymbolicLink(),
6567
+ centralized: pointsInside(join29(root, path), centralRealPath),
6113
6568
  localOnly: localOnlyReason !== void 0,
6114
6569
  ...localOnlyReason !== void 0 ? { localOnlyReason } : {}
6115
6570
  };
6116
6571
  });
6117
6572
  return {
6118
- centralDirectory: existsSync28(centralPath) ? "present" : "absent",
6119
- centralMode: existsSync28(centralPath) ? modeString(statSync8(centralPath).mode) : void 0,
6120
- centralFiles: existsSync28(centralPath) ? collectCentralSecretFiles(centralPath) : [],
6573
+ centralDirectory: existsSync29(centralPath) ? "present" : "absent",
6574
+ centralMode: existsSync29(centralPath) ? modeString(statSync9(centralPath).mode) : void 0,
6575
+ centralFiles: existsSync29(centralPath) ? collectCentralSecretFiles(centralPath) : [],
6121
6576
  repositoryEnvFiles: envFiles
6122
6577
  };
6123
6578
  }
@@ -6140,12 +6595,12 @@ function collectEnvironmentFiles(root, current = root, depth = 0) {
6140
6595
  if (depth > 5) return [];
6141
6596
  const found = [];
6142
6597
  try {
6143
- for (const entry of readdirSync13(current, { withFileTypes: true })) {
6598
+ for (const entry of readdirSync12(current, { withFileTypes: true })) {
6144
6599
  if (entry.isDirectory()) {
6145
6600
  if (!SKIP_ENV_DIRECTORIES.has(entry.name))
6146
- found.push(...collectEnvironmentFiles(root, join28(current, entry.name), depth + 1));
6601
+ found.push(...collectEnvironmentFiles(root, join29(current, entry.name), depth + 1));
6147
6602
  } else if (isEnvironmentFilename(entry.name) && !isProviderGeneratedEnvironmentFile(entry.name)) {
6148
- found.push(relative10(root, join28(current, entry.name)));
6603
+ found.push(relative10(root, join29(current, entry.name)));
6149
6604
  }
6150
6605
  }
6151
6606
  } catch {
@@ -6157,10 +6612,10 @@ function collectCentralSecretFiles(root, current = root, depth = 0) {
6157
6612
  if (depth > 3) return [];
6158
6613
  const found = [];
6159
6614
  try {
6160
- for (const entry of readdirSync13(current, { withFileTypes: true })) {
6161
- const path = join28(current, entry.name);
6615
+ for (const entry of readdirSync12(current, { withFileTypes: true })) {
6616
+ const path = join29(current, entry.name);
6162
6617
  if (entry.isDirectory()) found.push(...collectCentralSecretFiles(root, path, depth + 1));
6163
- else found.push({ path: relative10(root, path), mode: modeString(lstatSync11(path).mode) });
6618
+ else found.push({ path: relative10(root, path), mode: modeString(lstatSync12(path).mode) });
6164
6619
  }
6165
6620
  } catch {
6166
6621
  return found;
@@ -6184,7 +6639,7 @@ function isEnvironmentFixture(path) {
6184
6639
  return /(^|[\\/])(?:tests?|__tests__)[\\/]fixtures?[\\/]/i.test(path) || /(^|[\\/])__fixtures__[\\/]/i.test(path);
6185
6640
  }
6186
6641
  function pointsInside(path, expectedRoot) {
6187
- if (!expectedRoot || !lstatSync11(path).isSymbolicLink()) return false;
6642
+ if (!expectedRoot || !lstatSync12(path).isSymbolicLink()) return false;
6188
6643
  const target = safeRealpath(path);
6189
6644
  if (!target) return false;
6190
6645
  const fromRoot = relative10(expectedRoot, target);
@@ -6194,20 +6649,20 @@ function hasUnsafeCentralSecretPermissions(secrets) {
6194
6649
  return secrets.centralDirectory === "present" && (secrets.centralMode !== "700" || secrets.centralFiles.some((file) => file.mode !== "600"));
6195
6650
  }
6196
6651
  function inspectSecretsRoot(path) {
6197
- return existsSync28(path) ? { path, exists: true, mode: modeString(statSync8(path).mode) } : { path, exists: false };
6652
+ return existsSync29(path) ? { path, exists: true, mode: modeString(statSync9(path).mode) } : { path, exists: false };
6198
6653
  }
6199
6654
 
6200
6655
  // src/portfolio/ai-health/skills.ts
6201
- import { existsSync as existsSync29, lstatSync as lstatSync12, realpathSync as realpathSync6 } from "node:fs";
6202
- import { join as join29 } from "node:path";
6656
+ import { existsSync as existsSync30, lstatSync as lstatSync13, realpathSync as realpathSync6 } from "node:fs";
6657
+ import { join as join30 } from "node:path";
6203
6658
  function countAutomaticSkillsNeedingReview(skills) {
6204
6659
  return skills.automatic.filter(
6205
6660
  (item) => !item.managed || !item.registryId || item.expectedPlacement !== "auto" || item.expectedScope === "user"
6206
6661
  ).length;
6207
6662
  }
6208
6663
  function inspectSkills(root, grokInspection, registeredSkills, userSkills) {
6209
- const lock = readJson4(join29(root, ".pro-gov/assets.lock.json"));
6210
- const assetManifest = readJson4(join29(root, ".pro-gov/assets.json"));
6664
+ const lock = readJson4(join30(root, ".pro-gov/assets.lock.json"));
6665
+ const assetManifest = readJson4(join30(root, ".pro-gov/assets.json"));
6211
6666
  const managed = /* @__PURE__ */ new Set();
6212
6667
  const bundleIds = stringArray(isRecord3(lock) ? lock.bundleIds : void 0);
6213
6668
  if (isRecord3(lock) && Array.isArray(lock.assets)) {
@@ -6219,7 +6674,7 @@ function inspectSkills(root, grokInspection, registeredSkills, userSkills) {
6219
6674
  }
6220
6675
  const inspectPlacement = (placement) => {
6221
6676
  const directory = placement === "auto" ? "skills" : "manual-skills";
6222
- const skillRoot = join29(root, ".agents", directory);
6677
+ const skillRoot = join30(root, ".agents", directory);
6223
6678
  if (!pathLexists(skillRoot) || !safeIsDirectory(skillRoot)) return [];
6224
6679
  return safeReadDir(skillRoot).filter((name) => !name.startsWith(".")).map((name) => inspectSkillItem(skillRoot, directory, name, managed, registeredSkills));
6225
6680
  };
@@ -6274,15 +6729,15 @@ function inspectSkills(root, grokInspection, registeredSkills, userSkills) {
6274
6729
  },
6275
6730
  hosts: {
6276
6731
  codexProject: automatic.filter((item) => item.kind !== "dangling-symlink").length,
6277
- claudeCodeProject: inspectSkillRoot(join29(root, ".claude/skills")).names.length,
6278
- grokNativeProject: inspectSkillRoot(join29(root, ".grok/skills")).names.length,
6732
+ claudeCodeProject: inspectSkillRoot(join30(root, ".claude/skills")).names.length,
6733
+ grokNativeProject: inspectSkillRoot(join30(root, ".grok/skills")).names.length,
6279
6734
  grokEffective: grokInspection.skills
6280
6735
  }
6281
6736
  };
6282
6737
  }
6283
6738
  function inspectSkillItem(skillRoot, directory, name, managed, registeredSkills) {
6284
- const path = join29(skillRoot, name);
6285
- const stat = lstatSync12(path);
6739
+ const path = join30(skillRoot, name);
6740
+ const stat = lstatSync13(path);
6286
6741
  let kind = stat.isSymbolicLink() ? "symlink" : stat.isDirectory() ? "directory" : "file";
6287
6742
  let realPath;
6288
6743
  try {
@@ -6291,7 +6746,7 @@ function inspectSkillItem(skillRoot, directory, name, managed, registeredSkills)
6291
6746
  if (stat.isSymbolicLink()) kind = "dangling-symlink";
6292
6747
  }
6293
6748
  const registered = realPath ? registeredSkills.find((skill) => skill.sourceRealPath === realPath) : void 0;
6294
- const classification = registered ? void 0 : realPath && isPluginPack(realPath) ? "plugin-pack" : kind === "directory" && existsSync29(join29(path, "SKILL.md")) ? "project-local" : void 0;
6749
+ const classification = registered ? void 0 : realPath && isPluginPack(realPath) ? "plugin-pack" : kind === "directory" && existsSync30(join30(path, "SKILL.md")) ? "project-local" : void 0;
6295
6750
  return {
6296
6751
  name,
6297
6752
  kind,
@@ -6303,11 +6758,11 @@ function inspectSkillItem(skillRoot, directory, name, managed, registeredSkills)
6303
6758
  };
6304
6759
  }
6305
6760
  function isPluginPack(path) {
6306
- const skillsRoot = join29(path, "skills");
6307
- return existsSync29(join29(path, ".codex-plugin/plugin.json")) && safeIsDirectory(skillsRoot) && safeReadDir(skillsRoot).some((name) => existsSync29(join29(skillsRoot, name, "SKILL.md")));
6761
+ const skillsRoot = join30(path, "skills");
6762
+ return existsSync30(join30(path, ".codex-plugin/plugin.json")) && safeIsDirectory(skillsRoot) && safeReadDir(skillsRoot).some((name) => existsSync30(join30(skillsRoot, name, "SKILL.md")));
6308
6763
  }
6309
6764
  function inspectInvalidSkillEntries(root, directory) {
6310
- const skillRoot = join29(root, ".agents", directory);
6765
+ const skillRoot = join30(root, ".agents", directory);
6311
6766
  if (!pathLexists(skillRoot) || !safeIsDirectory(skillRoot)) return [];
6312
6767
  return safeReadDir(skillRoot).flatMap((name) => {
6313
6768
  if (name === ".gitkeep") return [];
@@ -6315,14 +6770,14 @@ function inspectInvalidSkillEntries(root, directory) {
6315
6770
  return [{ path: `.agents/${directory}/${name}`, reason: "metadata-junk" }];
6316
6771
  if (name.startsWith("."))
6317
6772
  return [{ path: `.agents/${directory}/${name}`, reason: "unexpected-file" }];
6318
- const path = join29(skillRoot, name);
6319
- return !lstatSync12(path).isDirectory() && !lstatSync12(path).isSymbolicLink() ? [{ path: `.agents/${directory}/${name}`, reason: "unexpected-file" }] : [];
6773
+ const path = join30(skillRoot, name);
6774
+ return !lstatSync13(path).isDirectory() && !lstatSync13(path).isSymbolicLink() ? [{ path: `.agents/${directory}/${name}`, reason: "unexpected-file" }] : [];
6320
6775
  });
6321
6776
  }
6322
6777
  function skillDuplicatesUser(item, root, userSkills) {
6323
6778
  if (userSkills.names.has(item.name)) return true;
6324
- const automatic = join29(root, ".agents/skills", item.name);
6325
- const manual = join29(root, ".agents/manual-skills", item.name);
6779
+ const automatic = join30(root, ".agents/skills", item.name);
6780
+ const manual = join30(root, ".agents/manual-skills", item.name);
6326
6781
  const realPath = safeRealpath(pathLexists(automatic) ? automatic : manual);
6327
6782
  return realPath ? userSkills.realPaths.has(realPath) : false;
6328
6783
  }
@@ -6342,13 +6797,13 @@ function skillPlacementDrift(item, actualPlacement) {
6342
6797
  return [];
6343
6798
  }
6344
6799
  function inspectClaudeSkillRoot(root) {
6345
- const path = join29(root, ".claude/skills");
6800
+ const path = join30(root, ".claude/skills");
6346
6801
  if (!pathLexists(path)) return "missing";
6347
- const stat = lstatSync12(path);
6802
+ const stat = lstatSync13(path);
6348
6803
  if (stat.isSymbolicLink()) {
6349
6804
  try {
6350
6805
  const target = realpathSync6(path);
6351
- return target === realpathSync6(join29(root, ".agents/skills")) ? "shared-root" : "other";
6806
+ return target === realpathSync6(join30(root, ".agents/skills")) ? "shared-root" : "other";
6352
6807
  } catch {
6353
6808
  return "dangling-symlink";
6354
6809
  }
@@ -6361,27 +6816,27 @@ function inspectSkillRegistry(executionEngineRoot) {
6361
6816
  health: { source: 0, registered: 0, bundled: 0, bundles: 0 },
6362
6817
  skills: []
6363
6818
  };
6364
- const agentAssetsRoot = join29(executionEngineRoot, "agent-assets");
6365
- const registry = readJson4(join29(agentAssetsRoot, "registry.json"));
6819
+ const agentAssetsRoot = join30(executionEngineRoot, "agent-assets");
6820
+ const registry = readJson4(join30(agentAssetsRoot, "registry.json"));
6366
6821
  const assets = isRecord3(registry) && Array.isArray(registry.assets) ? registry.assets : [];
6367
6822
  const registeredSkills = assets.filter((asset) => isRecord3(asset) && asset.kind === "skill");
6368
- const bundleRoot = join29(agentAssetsRoot, "bundles");
6823
+ const bundleRoot = join30(agentAssetsRoot, "bundles");
6369
6824
  const bundleFiles = safeReadDir(bundleRoot).filter((file) => file.endsWith(".json"));
6370
6825
  const bundledIds = /* @__PURE__ */ new Set();
6371
6826
  for (const file of bundleFiles) {
6372
- const bundle = readJson4(join29(bundleRoot, file));
6827
+ const bundle = readJson4(join30(bundleRoot, file));
6373
6828
  if (!isRecord3(bundle) || !Array.isArray(bundle.assets)) continue;
6374
6829
  for (const id of bundle.assets) if (typeof id === "string") bundledIds.add(id);
6375
6830
  }
6376
6831
  const sourceRoots = [
6377
- join29(agentAssetsRoot, "skills/pie-skills"),
6378
- join29(agentAssetsRoot, "skills/npx-skills/.agents/skills")
6832
+ join30(agentAssetsRoot, "skills/pie-skills"),
6833
+ join30(agentAssetsRoot, "skills/npx-skills/.agents/skills")
6379
6834
  ];
6380
6835
  const source = sourceRoots.reduce(
6381
- (count, root) => count + safeReadDir(root).filter((name) => existsSync29(join29(root, name, "SKILL.md"))).length,
6836
+ (count, root) => count + safeReadDir(root).filter((name) => existsSync30(join30(root, name, "SKILL.md"))).length,
6382
6837
  0
6383
6838
  ) + registeredSkills.filter(
6384
- (asset) => isRecord3(asset) && asset.sourceKind === "local-pack" && typeof asset.sourcePath === "string" && isPluginPack(join29(agentAssetsRoot, asset.sourcePath))
6839
+ (asset) => isRecord3(asset) && asset.sourceKind === "local-pack" && typeof asset.sourcePath === "string" && isPluginPack(join30(agentAssetsRoot, asset.sourcePath))
6385
6840
  ).length;
6386
6841
  return {
6387
6842
  health: {
@@ -6398,7 +6853,7 @@ function inspectSkillRegistry(executionEngineRoot) {
6398
6853
  return [
6399
6854
  {
6400
6855
  id: asset.id,
6401
- sourceRealPath: safeRealpath(join29(agentAssetsRoot, asset.sourcePath)),
6856
+ sourceRealPath: safeRealpath(join30(agentAssetsRoot, asset.sourcePath)),
6402
6857
  defaultPlacement: asset.defaultPlacement,
6403
6858
  defaultScope: asset.defaultScope === "user" ? "user" : "project"
6404
6859
  }
@@ -6413,21 +6868,27 @@ function inspectUserSkillEvidence(root) {
6413
6868
  for (const name of safeReadDir(root)) {
6414
6869
  if (name.startsWith(".")) continue;
6415
6870
  names.add(name);
6416
- const realPath = safeRealpath(join29(root, name));
6871
+ const realPath = safeRealpath(join30(root, name));
6417
6872
  if (realPath) realPaths.add(realPath);
6418
6873
  }
6419
6874
  return { names, realPaths };
6420
6875
  }
6421
6876
 
6422
6877
  // src/portfolio/ai-health/technology.ts
6423
- import { existsSync as existsSync30, readdirSync as readdirSync14, statSync as statSync9 } from "node:fs";
6424
- import { join as join30 } from "node:path";
6878
+ import { existsSync as existsSync31, readdirSync as readdirSync13, statSync as statSync10 } from "node:fs";
6879
+ import { join as join31 } from "node:path";
6425
6880
  function buildTechnologyMatrix(governance, repositories) {
6426
- if (!governance) return [];
6881
+ if (!governance || governance.technologies.length === 0) return [];
6427
6882
  const policy = governance.versionPolicy;
6883
+ const packageManifestsByRepository = /* @__PURE__ */ new Map();
6884
+ for (const repository of repositories) {
6885
+ if (!packageManifestsByRepository.has(repository.path)) {
6886
+ packageManifestsByRepository.set(repository.path, collectPackageManifests(repository.path));
6887
+ }
6888
+ }
6428
6889
  return governance.technologies.map((technology) => {
6429
6890
  const projects = repositories.flatMap((repository) => {
6430
- const packageManifests = collectPackageManifests(repository.path);
6891
+ const packageManifests = packageManifestsByRepository.get(repository.path) ?? [];
6431
6892
  const packageSignals = (technology.packages ?? []).map((name) => {
6432
6893
  const requirement = policy?.packages.find((item) => item.name === name);
6433
6894
  return packageManifests.map((manifest) => {
@@ -6442,8 +6903,8 @@ function buildTechnologyMatrix(governance, repositories) {
6442
6903
  }).filter((item) => item !== void 0);
6443
6904
  }).flat();
6444
6905
  const fileSignal = (technology.files ?? []).some(
6445
- (path) => hasUsableTechnologyFile(join30(repository.path, path)) || packageManifests.some(
6446
- (manifest) => hasUsableTechnologyFile(join30(manifest.directory, path))
6906
+ (path) => hasUsableTechnologyFile(join31(repository.path, path)) || packageManifests.some(
6907
+ (manifest) => hasUsableTechnologyFile(join31(manifest.directory, path))
6447
6908
  )
6448
6909
  );
6449
6910
  const modelSignal = [
@@ -6534,14 +6995,14 @@ function buildTechnologyMatrix(governance, repositories) {
6534
6995
  }).filter((technology) => technology.projectCount > 0);
6535
6996
  }
6536
6997
  function hasUsableTechnologyFile(path) {
6537
- if (!existsSync30(path)) return false;
6998
+ if (!existsSync31(path)) return false;
6538
6999
  try {
6539
- const info = statSync9(path);
7000
+ const info = statSync10(path);
6540
7001
  if (info.isFile()) return true;
6541
7002
  if (!info.isDirectory()) return false;
6542
- return readdirSync14(path, { withFileTypes: true }).some((entry) => {
7003
+ return readdirSync13(path, { withFileTypes: true }).some((entry) => {
6543
7004
  if (entry.name.startsWith(".")) return false;
6544
- const child = join30(path, entry.name);
7005
+ const child = join31(path, entry.name);
6545
7006
  if (entry.isDirectory()) return hasUsableTechnologyFile(child);
6546
7007
  return entry.name.toLowerCase() !== "readme.md";
6547
7008
  });
@@ -6553,7 +7014,7 @@ function inspectExclusiveOwnership(root, endpoint, governance) {
6553
7014
  const projectType = endpoint.projectType;
6554
7015
  return (governance?.exclusiveOwnership ?? []).flatMap((rule) => {
6555
7016
  if (projectType && rule.allowedProjectTypes.includes(projectType)) return [];
6556
- const paths = rule.paths.filter((path) => existsSync30(join30(root, path)));
7017
+ const paths = rule.paths.filter((path) => existsSync31(join31(root, path)));
6557
7018
  return paths.length > 0 ? [{ rule, paths }] : [];
6558
7019
  });
6559
7020
  }
@@ -6568,7 +7029,7 @@ function inspectProjectModel(root, endpoint, governance) {
6568
7029
  const detection = (id) => {
6569
7030
  const technology = technologyById.get(id);
6570
7031
  const packageMatch = technology?.packages?.some((name) => packages.has(name)) ?? false;
6571
- const fileMatch = technology?.files?.some((path) => existsSync30(join30(root, path))) ?? false;
7032
+ const fileMatch = technology?.files?.some((path) => existsSync31(join31(root, path))) ?? false;
6572
7033
  return { id, label: technology?.label ?? id, detected: packageMatch || fileMatch };
6573
7034
  };
6574
7035
  const selected = new Set(endpoint.capabilities ?? []);
@@ -6607,8 +7068,8 @@ function collectPackageNames(root) {
6607
7068
  }
6608
7069
 
6609
7070
  // src/portfolio/ai-health/report.ts
6610
- import { cpSync as cpSync3, existsSync as existsSync31, mkdirSync as mkdirSync10, writeFileSync as writeFileSync9 } from "node:fs";
6611
- import { dirname as dirname16, join as join31 } from "node:path";
7071
+ import { cpSync as cpSync3, existsSync as existsSync32, mkdirSync as mkdirSync10, writeFileSync as writeFileSync9 } from "node:fs";
7072
+ import { dirname as dirname16, join as join32 } from "node:path";
6612
7073
  import { fileURLToPath as fileURLToPath5 } from "node:url";
6613
7074
  function mergePortfolioAiHealthReport(existing, latest, allRepositoryIds) {
6614
7075
  const repositoriesById = /* @__PURE__ */ new Map();
@@ -6652,16 +7113,16 @@ function writePortfolioAiHealthReport(report, outDir) {
6652
7113
  mkdirSync10(outDir, { recursive: true });
6653
7114
  const dashboardAssets = findDashboardAssets();
6654
7115
  for (const file of ["index.html", "app.js", "app.css"]) {
6655
- const source = join31(dashboardAssets, file);
6656
- if (!existsSync31(source)) throw new Error(`Portfolio dashboard asset is missing: ${source}`);
6657
- cpSync3(source, join31(outDir, file));
7116
+ const source = join32(dashboardAssets, file);
7117
+ if (!existsSync32(source)) throw new Error(`Portfolio dashboard asset is missing: ${source}`);
7118
+ cpSync3(source, join32(outDir, file));
6658
7119
  }
6659
- const jsonPath = join31(outDir, "portfolio-ai-health.json");
6660
- const htmlPath = join31(outDir, "index.html");
7120
+ const jsonPath = join32(outDir, "portfolio-ai-health.json");
7121
+ const htmlPath = join32(outDir, "index.html");
6661
7122
  writeFileSync9(jsonPath, `${JSON.stringify(report, null, 2)}
6662
7123
  `);
6663
7124
  writeFileSync9(
6664
- join31(outDir, "data.js"),
7125
+ join32(outDir, "data.js"),
6665
7126
  `window.__PORTFOLIO_AI_HEALTH__ = ${safeJavaScriptJson2(report)};
6666
7127
  `
6667
7128
  );
@@ -6671,14 +7132,14 @@ function findDashboardAssets() {
6671
7132
  const packageRoot2 = dirname16(dirname16(fileURLToPath5(import.meta.url)));
6672
7133
  const candidates = [
6673
7134
  process.env.PGS_DASHBOARD_ASSETS_DIR,
6674
- join31(packageRoot2, ".dashboard-build"),
6675
- join31(packageRoot2, "assets/portfolio-dashboard"),
6676
- join31(process.cwd(), ".dashboard-build"),
6677
- join31(process.cwd(), "assets/portfolio-dashboard"),
6678
- join31(process.cwd(), "packages/pro-gov/.dashboard-build"),
6679
- join31(process.cwd(), "packages/pro-gov/assets/portfolio-dashboard")
7135
+ join32(packageRoot2, ".dashboard-build"),
7136
+ join32(packageRoot2, "assets/portfolio-dashboard"),
7137
+ join32(process.cwd(), ".dashboard-build"),
7138
+ join32(process.cwd(), "assets/portfolio-dashboard"),
7139
+ join32(process.cwd(), "packages/pro-gov/.dashboard-build"),
7140
+ join32(process.cwd(), "packages/pro-gov/assets/portfolio-dashboard")
6680
7141
  ].filter((value) => Boolean(value));
6681
- const match = candidates.find((path) => existsSync31(join31(path, "index.html")));
7142
+ const match = candidates.find((path) => existsSync32(join32(path, "index.html")));
6682
7143
  if (!match)
6683
7144
  throw new Error(
6684
7145
  "Portfolio dashboard assets were not built. Run pnpm --filter @pieai/pro-gov build."
@@ -6696,7 +7157,7 @@ function inspectPortfolioAiHealth(options) {
6696
7157
  if (options.targetId && options.targetId !== "all" && endpoints.length === 0) {
6697
7158
  throw new Error(`Unknown portfolio target: ${options.targetId}`);
6698
7159
  }
6699
- const secretsRoot = options.secretsRoot ?? join32(
7160
+ const secretsRoot = options.secretsRoot ?? join33(
6700
7161
  dirname17(
6701
7162
  options.manifest.controlPlane?.path ?? allEndpoints[0]?.endpoint.path ?? process.cwd()
6702
7163
  ),
@@ -6706,9 +7167,9 @@ function inspectPortfolioAiHealth(options) {
6706
7167
  const grokVersion = commandVersion("grok");
6707
7168
  const executionEngineRoot = options.manifest.executionEngine?.path;
6708
7169
  const skillRegistry = inspectSkillRegistry(executionEngineRoot);
6709
- const userSkills = inspectUserSkillEvidence(join32(homeDir, ".agents/skills"));
7170
+ const userSkills = inspectUserSkillEvidence(join33(homeDir, ".agents/skills"));
6710
7171
  const expectedPackageVersion = packageVersion(
6711
- join32(executionEngineRoot ?? "", "packages/pro-gov/package.json")
7172
+ join33(executionEngineRoot ?? "", "packages/pro-gov/package.json")
6712
7173
  );
6713
7174
  const repositories = endpoints.map(
6714
7175
  ({ endpoint, role }) => inspectRepository(
@@ -6726,7 +7187,7 @@ function inspectPortfolioAiHealth(options) {
6726
7187
  const summary = { healthy: 0, attention: 0, unhealthy: 0 };
6727
7188
  for (const repository of repositories) summary[repository.status] += 1;
6728
7189
  return {
6729
- schemaVersion: 7,
7190
+ schemaVersion: 8,
6730
7191
  portfolioId: options.manifest.portfolioId,
6731
7192
  generatedAt: options.generatedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
6732
7193
  coverage: {
@@ -6775,19 +7236,20 @@ function inspectRepository(endpoint, role, secretsRoot, homeDir, expectedPackage
6775
7236
  const root = endpoint.path;
6776
7237
  const git = inspectGit2(root);
6777
7238
  const entries = inspectEntries(root);
7239
+ const agentLinks = inspectAgentLinks(root);
6778
7240
  const grokInspection = inspectGrokProject(root, homeDir, grokVersion);
6779
7241
  const skills = inspectSkills(root, grokInspection, registeredSkills, userSkills);
6780
7242
  const hostSsot = inspectProjectHostSsot(root);
6781
7243
  const hooks = inspectHooks(root);
6782
7244
  const docs = inspectDocs(root, role === "execution-engine" ? void 0 : expectedPackageVersion);
6783
7245
  const mcp = {
6784
- codexProject: tomlMcpNames(join32(root, MCP_DISCOVERY_PATHS.project.codex)),
7246
+ codexProject: tomlMcpNames(join33(root, MCP_DISCOVERY_PATHS.project.codex)),
6785
7247
  claudeCodeProjectShared: jsonObjectKeys(
6786
- join32(root, MCP_DISCOVERY_PATHS.project.claudeCodeShared),
7248
+ join33(root, MCP_DISCOVERY_PATHS.project.claudeCodeShared),
6787
7249
  "mcpServers"
6788
7250
  ),
6789
7251
  claudeCodeProjectLocal: claudeProjectLocalMcpNames(homeDir, root),
6790
- grokProject: tomlMcpNames(join32(root, MCP_DISCOVERY_PATHS.project.grok)),
7252
+ grokProject: tomlMcpNames(join33(root, MCP_DISCOVERY_PATHS.project.grok)),
6791
7253
  grokEffective: grokInspection.effectiveMcp,
6792
7254
  grokInspection: grokInspection.inspection
6793
7255
  };
@@ -6855,6 +7317,10 @@ function inspectRepository(endpoint, role, secretsRoot, homeDir, expectedPackage
6855
7317
  recommendations.push("GEMINI.md \u662F\u65AD\u5F00\u7684\u94FE\u63A5\uFF1B\u9700\u8981\u91CD\u65B0\u6307\u5411 AGENTS.md\u3002");
6856
7318
  if ([...skills.automatic, ...skills.manual].some((skill) => skill.kind === "dangling-symlink"))
6857
7319
  recommendations.push("`.agents/skills` \u6216 `.agents/manual-skills` \u4E2D\u5B58\u5728\u65AD\u5F00\u7684\u6280\u80FD\u94FE\u63A5\u3002");
7320
+ if (agentLinks.trackedDangling.length > 0)
7321
+ recommendations.push(
7322
+ `\u53D1\u73B0 ${agentLinks.trackedDangling.length} \u4E2A\u53D7 Git \u8DDF\u8E2A\u7684 agent workflow/command \u65AD\u5F00\u94FE\u63A5\uFF1A${agentLinks.trackedDangling.join("\u3001")}\uFF1B\u786E\u8BA4\u662F\u5426\u5E94\u5220\u9664\u65E7\u5165\u53E3\u6216\u6062\u590D canonical \u76EE\u6807\u3002`
7323
+ );
6858
7324
  if (skills.invalidEntries.length > 0)
6859
7325
  recommendations.push(
6860
7326
  `\u6280\u80FD\u76EE\u5F55\u4E2D\u6709 ${skills.invalidEntries.length} \u4E2A\u5783\u573E\u6216\u975E\u6280\u80FD\u6587\u4EF6\uFF1A${skills.invalidEntries.map((entry) => entry.path).join("\u3001")}\u3002`
@@ -6967,6 +7433,7 @@ function inspectRepository(endpoint, role, secretsRoot, homeDir, expectedPackage
6967
7433
  status: deriveStatus(
6968
7434
  role,
6969
7435
  entries,
7436
+ agentLinks,
6970
7437
  git,
6971
7438
  hooks,
6972
7439
  skills,
@@ -6983,6 +7450,7 @@ function inspectRepository(endpoint, role, secretsRoot, homeDir, expectedPackage
6983
7450
  recommendations,
6984
7451
  git,
6985
7452
  entries,
7453
+ agentLinks,
6986
7454
  hooks,
6987
7455
  mcp,
6988
7456
  skills,
@@ -6995,8 +7463,8 @@ function inspectRepository(endpoint, role, secretsRoot, homeDir, expectedPackage
6995
7463
  redundancy
6996
7464
  };
6997
7465
  }
6998
- function deriveStatus(role, entries, git, hooks, skills, hostSsot, secrets, docs, projectModel, exclusiveOwnershipViolations, technologyGovernanceConfigured, versions, verification, redundancy) {
6999
- if (!git.isRepository || entries.agents === "missing" || entries.claude === "dangling-symlink" || entries.gemini === "dangling-symlink" || [...skills.automatic, ...skills.manual].some((item) => item.kind === "dangling-symlink") || exclusiveOwnershipViolations.length > 0 || secrets.repositoryEnvFiles.some((file) => file.tracked && !file.template && !file.fixture) || hasUnsafeCentralSecretPermissions(secrets))
7466
+ function deriveStatus(role, entries, agentLinks, git, hooks, skills, hostSsot, secrets, docs, projectModel, exclusiveOwnershipViolations, technologyGovernanceConfigured, versions, verification, redundancy) {
7467
+ if (!git.isRepository || entries.agents === "missing" || entries.claude === "dangling-symlink" || entries.gemini === "dangling-symlink" || agentLinks.trackedDangling.length > 0 || [...skills.automatic, ...skills.manual].some((item) => item.kind === "dangling-symlink") || exclusiveOwnershipViolations.length > 0 || secrets.repositoryEnvFiles.some((file) => file.tracked && !file.template && !file.fixture) || hasUnsafeCentralSecretPermissions(secrets))
7000
7468
  return "unhealthy";
7001
7469
  const missingBaseline = projectModel.baseline.some(
7002
7470
  (technology) => !technology.detected && !hasBaselineException(projectModel, technology.id)
@@ -7403,8 +7871,8 @@ function isHost2(value) {
7403
7871
  return value === "codex" || value === "claude-code" || value === "gemini-cli" || value === "antigravity";
7404
7872
  }
7405
7873
  function findPortfolioAgentAssetsDir(manifest) {
7406
- const agentAssetsDir = manifest?.executionEngine?.path ? join33(manifest.executionEngine.path, "agent-assets") : void 0;
7407
- return agentAssetsDir && existsSync32(join33(agentAssetsDir, "registry.json")) ? agentAssetsDir : void 0;
7874
+ const agentAssetsDir = manifest?.executionEngine?.path ? join34(manifest.executionEngine.path, "agent-assets") : void 0;
7875
+ return agentAssetsDir && existsSync33(join34(agentAssetsDir, "registry.json")) ? agentAssetsDir : void 0;
7408
7876
  }
7409
7877
  function printUsage5() {
7410
7878
  console.error("Usage:");
@@ -7420,8 +7888,8 @@ function printUsage5() {
7420
7888
  }
7421
7889
  function readExistingAiHealthReport(outDir, portfolioId) {
7422
7890
  if (!outDir) return void 0;
7423
- const path = join33(outDir, "portfolio-ai-health.json");
7424
- if (!existsSync32(path)) return void 0;
7891
+ const path = join34(outDir, "portfolio-ai-health.json");
7892
+ if (!existsSync33(path)) return void 0;
7425
7893
  try {
7426
7894
  const value = JSON.parse(readFileSync18(path, "utf8"));
7427
7895
  if (!value || typeof value !== "object" || value.portfolioId !== portfolioId || !Array.isArray(value.repositories))
@@ -7433,8 +7901,8 @@ function readExistingAiHealthReport(outDir, portfolioId) {
7433
7901
  }
7434
7902
 
7435
7903
  // src/commands/sync.ts
7436
- import { existsSync as existsSync33, lstatSync as lstatSync13, readFileSync as readFileSync19, readlinkSync as readlinkSync4 } from "node:fs";
7437
- import { join as join34 } from "node:path";
7904
+ import { existsSync as existsSync34, lstatSync as lstatSync14, readFileSync as readFileSync19, readlinkSync as readlinkSync4 } from "node:fs";
7905
+ import { join as join35 } from "node:path";
7438
7906
  function runSync(args) {
7439
7907
  const check = args.includes("--check");
7440
7908
  if (!check) {
@@ -7462,7 +7930,7 @@ function runSync(args) {
7462
7930
  console.log("pro-gov sync check");
7463
7931
  console.log(`profile: ${profile}`);
7464
7932
  for (const file of planStarterFiles(profile)) {
7465
- const targetPath = join34(process.cwd(), file.targetPath);
7933
+ const targetPath = join35(process.cwd(), file.targetPath);
7466
7934
  const stat = safeLstat3(targetPath);
7467
7935
  if (!stat) {
7468
7936
  if (file.ownership === "optional-guardrail") continue;
@@ -7521,13 +7989,13 @@ function normalizeMarkdownTableCell(cell) {
7521
7989
  }
7522
7990
  function inferInstalledProfile(root) {
7523
7991
  const installed = ["engineering-runtime", "doc-only"].filter(
7524
- (profile) => existsSync33(join34(root, `docs/governance/agents-routing/${profile}-v1.1.md`))
7992
+ (profile) => existsSync34(join35(root, `docs/governance/agents-routing/${profile}-v1.1.md`))
7525
7993
  );
7526
7994
  return installed.length === 1 ? installed[0] : void 0;
7527
7995
  }
7528
7996
  function safeLstat3(path) {
7529
7997
  try {
7530
- return lstatSync13(path);
7998
+ return lstatSync14(path);
7531
7999
  } catch {
7532
8000
  return void 0;
7533
8001
  }
@@ -7565,7 +8033,8 @@ var COMMANDS = [
7565
8033
  "lens audit check --dir <path> [--json]",
7566
8034
  "init --profile <engineering-runtime|doc-only> <--dry-run|--apply>",
7567
8035
  "sync --check [--profile <engineering-runtime|doc-only>]",
7568
- "doctor"
8036
+ "package-doctor",
8037
+ "doctor (legacy alias for package-doctor)"
7569
8038
  ];
7570
8039
  var [command, subcommand] = process.argv.slice(2);
7571
8040
  process.exitCode = await main();
@@ -7581,6 +8050,7 @@ async function main() {
7581
8050
  if (command === "host-lens") return runHostLens(process.argv.slice(3));
7582
8051
  if (command === "init") return runInit(process.argv.slice(3));
7583
8052
  if (command === "sync") return runSync(process.argv.slice(3));
8053
+ if (command === "package-doctor") return runPackageDoctor(process.argv.slice(3));
7584
8054
  if (command === "doctor") return runDoctor(process.argv.slice(3));
7585
8055
  console.error(`Unknown command: ${[command, subcommand].filter(Boolean).join(" ")}`);
7586
8056
  printHelp();