@pieai/pro-gov 0.3.4 → 0.3.6

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.
Files changed (28) hide show
  1. package/README.md +27 -4
  2. package/assets/docs/reference/adoption/adoption-playbook.md +14 -3
  3. package/assets/docs/reference/adoption/project-relationship.md +7 -5
  4. package/assets/docs/reference/adoption/public-release-checklist.md +27 -2
  5. package/assets/public-agent-assets/README.md +40 -0
  6. package/assets/public-agent-assets/bundles/.gitkeep +0 -0
  7. package/assets/public-agent-assets/bundles/base-governance.json +11 -0
  8. package/assets/public-agent-assets/commands/pie-commands/README.md +3 -0
  9. package/assets/public-agent-assets/registry.json +132 -0
  10. package/assets/public-agent-assets/rules/pie-rules/.gitkeep +0 -0
  11. package/assets/public-agent-assets/rules/pie-rules/ai-in-the-loop.md +66 -0
  12. package/assets/public-agent-assets/rules/pie-rules/rule-evolution-methodology.md +74 -0
  13. package/assets/public-agent-assets/skills/dokobot/.gitkeep +0 -0
  14. package/assets/public-agent-assets/skills/npx-skills/README.md +3 -0
  15. package/assets/public-agent-assets/skills/pie-skills/.gitkeep +0 -0
  16. package/assets/public-agent-assets/skills/pie-skills/beginner-friendly-docs/SKILL.md +225 -0
  17. package/assets/public-agent-assets/skills/pie-skills/doc-cross-validator/SKILL.md +194 -0
  18. package/assets/starter/.github/workflows/docs-check.yml +2 -4
  19. package/assets/starter/docs/governance/agents-routing/doc-only-v0.9.md +2 -3
  20. package/assets/starter/docs/governance/agents-routing/engineering-runtime-v0.9.md +2 -3
  21. package/assets/starter/docs/governance/boundary.md +2 -2
  22. package/assets/starter/docs/governance/ssot-v0.9.md +2 -2
  23. package/assets/starter/lefthook.template.yml +2 -2
  24. package/cli-guide.md +18 -3
  25. package/dist/cli.js +670 -79
  26. package/package.json +12 -13
  27. package/assets/docs/reference/adoption/downstream-project-registry.md +0 -85
  28. package/assets/starter/.gemini/settings.json +0 -5
package/dist/cli.js CHANGED
@@ -48,8 +48,8 @@ function isPlatformMetadata(name) {
48
48
  }
49
49
 
50
50
  // src/commands/assets.ts
51
- import { mkdirSync as mkdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync2 } from "node:fs";
52
- import { dirname as dirname5 } from "node:path";
51
+ import { existsSync as existsSync11, mkdirSync as mkdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync2 } from "node:fs";
52
+ import { dirname as dirname5, join as join11 } from "node:path";
53
53
 
54
54
  // src/asset-bundles/bundles.ts
55
55
  import { existsSync as existsSync2, readdirSync as readdirSync2, readFileSync } from "node:fs";
@@ -202,7 +202,6 @@ import { existsSync as existsSync4, lstatSync } from "node:fs";
202
202
  import { isAbsolute, join as join4, posix } from "node:path";
203
203
  var supportedFamilies = /* @__PURE__ */ new Set([
204
204
  "pie-skills",
205
- "dokobot",
206
205
  "npx-skills",
207
206
  "pie-rules",
208
207
  "pie-commands"
@@ -214,6 +213,8 @@ var supportedVisibilities = /* @__PURE__ */ new Set([
214
213
  "third-party"
215
214
  ]);
216
215
  var supportedSourceKinds = /* @__PURE__ */ new Set(["local", "local-pack", "npx"]);
216
+ var supportedSkillPlacements = /* @__PURE__ */ new Set(["auto", "manual"]);
217
+ var supportedSkillScopes = /* @__PURE__ */ new Set(["project", "user"]);
217
218
  var supportedHosts = /* @__PURE__ */ new Set([
218
219
  "codex",
219
220
  "claude-code",
@@ -269,6 +270,20 @@ function validateAssetRegistry(registry, options = {}) {
269
270
  });
270
271
  }
271
272
  }
273
+ if (asset.kind === "skill" && !supportedSkillPlacements.has(asset.defaultPlacement)) {
274
+ issues.push({
275
+ type: "unsupported-skill-placement",
276
+ id: asset.id,
277
+ message: `Skill asset must declare defaultPlacement auto or manual: ${asset.id}`
278
+ });
279
+ }
280
+ if (asset.defaultScope && asset.kind !== "skill" || asset.kind === "skill" && asset.defaultScope && !supportedSkillScopes.has(asset.defaultScope)) {
281
+ issues.push({
282
+ type: "unsupported-skill-scope",
283
+ id: asset.id,
284
+ message: `Skill asset defaultScope must be project or user: ${asset.id}`
285
+ });
286
+ }
272
287
  if (!isSafeRegistrySourcePath(asset.sourcePath)) {
273
288
  issues.push({
274
289
  type: "unsafe-source-path",
@@ -363,7 +378,9 @@ function createAgentAssetLockEntries(registry, agentAssetsDir, assetIds) {
363
378
  })).sort((a, b) => a.id.localeCompare(b.id));
364
379
  }
365
380
  function hashAgentAssetContent(asset, agentAssetsDir) {
366
- const sourceAbsolutePath = join5(agentAssetsDir, asset.sourcePath);
381
+ return hashAssetPathContent(join5(agentAssetsDir, asset.sourcePath));
382
+ }
383
+ function hashAssetPathContent(sourceAbsolutePath) {
367
384
  const hash = createHash2("sha256");
368
385
  for (const filePath of listFiles3(sourceAbsolutePath)) {
369
386
  const relativePath = toUnixPath3(relative3(sourceAbsolutePath, filePath));
@@ -377,7 +394,12 @@ function hashAgentAssetContent(asset, agentAssetsDir) {
377
394
  function findDefaultAgentAssetsDir() {
378
395
  const packageRoot2 = findPackageRoot(dirname2(fileURLToPath2(import.meta.url)));
379
396
  const repoRoot = join5(packageRoot2, "..", "..");
380
- const candidates = [join5(packageRoot2, "assets/agent-assets"), join5(repoRoot, "agent-assets")];
397
+ const candidates = [
398
+ join5(packageRoot2, "assets/agent-assets"),
399
+ join5(repoRoot, "agent-assets"),
400
+ join5(packageRoot2, "assets/public-agent-assets"),
401
+ join5(repoRoot, "public-agent-assets")
402
+ ];
381
403
  return candidates.find((candidate) => existsSync5(join5(candidate, "registry.json"))) ?? candidates[0];
382
404
  }
383
405
  function findPackageRoot(startDir) {
@@ -413,9 +435,105 @@ function toUnixPath3(path) {
413
435
  return path.replaceAll("\\", "/");
414
436
  }
415
437
 
438
+ // src/asset-registry/public-promotion.ts
439
+ import { existsSync as existsSync6 } from "node:fs";
440
+ import { isAbsolute as isAbsolute2, join as join6, posix as posix2 } from "node:path";
441
+ function checkPublicAssetPromotions(options) {
442
+ const issues = [];
443
+ let checked = 0;
444
+ for (const asset of options.registry.assets) {
445
+ if (!needsPromotionCheck(asset)) continue;
446
+ checked += 1;
447
+ if (!asset.promotion) {
448
+ issues.push({
449
+ type: "missing-promotion",
450
+ id: asset.id,
451
+ message: `Public asset is missing promotion metadata: ${asset.id}`
452
+ });
453
+ continue;
454
+ }
455
+ const publicPathResult = resolveSafePath(options.publicAgentAssetsDir, asset.sourcePath);
456
+ if (!publicPathResult.ok) {
457
+ issues.push({
458
+ type: "unsafe-public-source-path",
459
+ id: asset.id,
460
+ path: asset.sourcePath,
461
+ message: `Public asset source path is unsafe: ${asset.sourcePath}`
462
+ });
463
+ continue;
464
+ }
465
+ const privatePathResult = resolveSafePath(
466
+ options.privateAgentAssetsDir,
467
+ asset.promotion.privateSourcePath
468
+ );
469
+ if (!privatePathResult.ok) {
470
+ issues.push({
471
+ type: "unsafe-private-source-path",
472
+ id: asset.id,
473
+ path: asset.promotion.privateSourcePath,
474
+ message: `Private promotion source path is unsafe: ${asset.promotion.privateSourcePath}`
475
+ });
476
+ continue;
477
+ }
478
+ if (!existsSync6(privatePathResult.path)) {
479
+ issues.push({
480
+ type: "missing-private-source",
481
+ id: asset.id,
482
+ path: asset.promotion.privateSourcePath,
483
+ message: `Private promotion source does not exist: ${asset.promotion.privateSourcePath}`
484
+ });
485
+ } else {
486
+ const actualPrivateHash = hashAssetPathContent(privatePathResult.path);
487
+ if (actualPrivateHash !== asset.promotion.privateSourceHash) {
488
+ issues.push({
489
+ type: "source-drift",
490
+ id: asset.id,
491
+ path: asset.promotion.privateSourcePath,
492
+ expected: asset.promotion.privateSourceHash,
493
+ actual: actualPrivateHash,
494
+ message: `Private source changed after public promotion: ${asset.id}`
495
+ });
496
+ }
497
+ }
498
+ if (!existsSync6(publicPathResult.path)) {
499
+ issues.push({
500
+ type: "missing-public-source",
501
+ id: asset.id,
502
+ path: asset.sourcePath,
503
+ message: `Public promoted asset does not exist: ${asset.sourcePath}`
504
+ });
505
+ } else {
506
+ const actualPublicHash = hashAssetPathContent(publicPathResult.path);
507
+ if (actualPublicHash !== asset.promotion.publicHash) {
508
+ issues.push({
509
+ type: "public-drift",
510
+ id: asset.id,
511
+ path: asset.sourcePath,
512
+ expected: asset.promotion.publicHash,
513
+ actual: actualPublicHash,
514
+ message: `Public promoted asset changed after review: ${asset.id}`
515
+ });
516
+ }
517
+ }
518
+ }
519
+ return { checked, issues };
520
+ }
521
+ function needsPromotionCheck(asset) {
522
+ return asset.visibility === "public" && asset.publishable;
523
+ }
524
+ function resolveSafePath(root, sourcePath) {
525
+ if (!sourcePath || isAbsolute2(sourcePath) || sourcePath.startsWith("/")) return { ok: false };
526
+ const normalized = posix2.normalize(sourcePath.replaceAll("\\", "/"));
527
+ if (normalized === "." || normalized === ".." || normalized.startsWith("../")) {
528
+ return { ok: false };
529
+ }
530
+ if (normalized.split("/").includes("..")) return { ok: false };
531
+ return { ok: true, path: join6(root, normalized) };
532
+ }
533
+
416
534
  // src/asset-targets/apply.ts
417
- import { existsSync as existsSync6, lstatSync as lstatSync2, mkdirSync as mkdirSync2, symlinkSync, unlinkSync, writeFileSync } from "node:fs";
418
- import { dirname as dirname3, join as join6, resolve } from "node:path";
535
+ import { existsSync as existsSync7, lstatSync as lstatSync2, mkdirSync as mkdirSync2, symlinkSync, unlinkSync, writeFileSync } from "node:fs";
536
+ import { dirname as dirname3, join as join7, resolve } from "node:path";
419
537
  function applyAssetInstallPlan(plan) {
420
538
  const appliedActions = [];
421
539
  for (const action of plan.actions) {
@@ -425,7 +543,7 @@ function applyAssetInstallPlan(plan) {
425
543
  return { appliedActions };
426
544
  }
427
545
  function applyAction(targetDir, action) {
428
- const targetAbsolutePath = join6(targetDir, action.targetPath);
546
+ const targetAbsolutePath = join7(targetDir, action.targetPath);
429
547
  if (action.type === "create-dir") {
430
548
  mkdirSync2(targetAbsolutePath, { recursive: true });
431
549
  return;
@@ -460,16 +578,16 @@ function pathExistsEvenIfDanglingSymlink2(path) {
460
578
  lstatSync2(path);
461
579
  return true;
462
580
  } catch {
463
- return existsSync6(path);
581
+ return existsSync7(path);
464
582
  }
465
583
  }
466
584
 
467
585
  // src/asset-targets/check.ts
468
- import { existsSync as existsSync7, lstatSync as lstatSync3, readFileSync as readFileSync4 } from "node:fs";
469
- import { join as join7 } from "node:path";
586
+ import { existsSync as existsSync8, lstatSync as lstatSync3, readFileSync as readFileSync4 } from "node:fs";
587
+ import { basename, join as join8 } from "node:path";
470
588
  function checkInstalledAssets(options) {
471
- const lockfilePath = join7(options.targetDir, ".pro-gov/assets.lock.json");
472
- if (!existsSync7(lockfilePath)) {
589
+ const lockfilePath = join8(options.targetDir, ".pro-gov/assets.lock.json");
590
+ if (!existsSync8(lockfilePath)) {
473
591
  return {
474
592
  targetDir: options.targetDir,
475
593
  issues: [
@@ -485,8 +603,8 @@ function checkInstalledAssets(options) {
485
603
  const issues = [];
486
604
  for (const entry of lockfile.assets ?? []) {
487
605
  const asset = registryById.get(entry.id);
488
- const targetAbsolutePath = join7(options.targetDir, entry.targetPath);
489
- const sourceAbsolutePath = join7(options.agentAssetsDir, entry.sourcePath);
606
+ const targetAbsolutePath = join8(options.targetDir, entry.targetPath);
607
+ const sourceAbsolutePath = join8(options.agentAssetsDir, entry.sourcePath);
490
608
  if (!asset) {
491
609
  issues.push({
492
610
  type: "unknown-asset",
@@ -496,10 +614,22 @@ function checkInstalledAssets(options) {
496
614
  });
497
615
  continue;
498
616
  }
617
+ if (asset.kind === "skill" && asset.defaultScope === "user") {
618
+ issues.push({
619
+ type: "user-scoped-asset-in-project-lock",
620
+ id: entry.id,
621
+ targetPath: entry.targetPath,
622
+ message: `User-scoped skill is still locked into this project; move it to the user skill roots: ${entry.id}`
623
+ });
624
+ }
499
625
  const hostFolderIssue = checkHostFolder(lockfile.host, asset.kind, entry.targetPath, entry.id);
500
626
  if (hostFolderIssue) {
501
627
  issues.push(hostFolderIssue);
502
628
  }
629
+ const placementDriftIssue = checkRegistryPlacement(lockfile, asset, entry.targetPath);
630
+ if (placementDriftIssue) {
631
+ issues.push(placementDriftIssue);
632
+ }
503
633
  if (!pathExistsEvenIfDanglingSymlink3(targetAbsolutePath)) {
504
634
  issues.push({
505
635
  type: "missing-target",
@@ -519,7 +649,7 @@ function checkInstalledAssets(options) {
519
649
  });
520
650
  continue;
521
651
  }
522
- if (!existsSync7(targetAbsolutePath)) {
652
+ if (!existsSync8(targetAbsolutePath)) {
523
653
  issues.push({
524
654
  type: "dangling-symlink",
525
655
  id: entry.id,
@@ -528,7 +658,7 @@ function checkInstalledAssets(options) {
528
658
  });
529
659
  continue;
530
660
  }
531
- if (!existsSync7(sourceAbsolutePath)) {
661
+ if (!existsSync8(sourceAbsolutePath)) {
532
662
  issues.push({
533
663
  type: "missing-source",
534
664
  id: entry.id,
@@ -547,12 +677,43 @@ function checkInstalledAssets(options) {
547
677
  });
548
678
  }
549
679
  }
680
+ issues.push(...checkDuplicateSkillPlacements(options.targetDir, options.registry));
550
681
  return { targetDir: options.targetDir, issues };
551
682
  }
683
+ function checkRegistryPlacement(lockfile, asset, targetPath) {
684
+ if (lockfile.placement !== "registry") return void 0;
685
+ if (asset.kind !== "skill") return void 0;
686
+ const expectedPath = expectedRegistrySkillTargetPath(lockfile.host, asset.sourcePath, asset.defaultPlacement);
687
+ if (!expectedPath || targetPath === expectedPath) return void 0;
688
+ return {
689
+ type: "skill-placement-drift",
690
+ id: asset.id,
691
+ targetPath,
692
+ message: `Managed skill target ${targetPath} does not match registry placement; expected ${expectedPath}`
693
+ };
694
+ }
695
+ function checkDuplicateSkillPlacements(targetDir, registry) {
696
+ const issues = [];
697
+ for (const asset of registry.assets) {
698
+ if (asset.kind !== "skill") continue;
699
+ const skillName = basename(asset.sourcePath);
700
+ const autoPath = `.agents/skills/${skillName}`;
701
+ const manualPath = `.agents/manual-skills/${skillName}`;
702
+ if (pathExistsEvenIfDanglingSymlink3(join8(targetDir, autoPath)) && pathExistsEvenIfDanglingSymlink3(join8(targetDir, manualPath))) {
703
+ issues.push({
704
+ type: "duplicate-skill-placement",
705
+ id: asset.id,
706
+ targetPath: `${autoPath} + ${manualPath}`,
707
+ message: `Skill is linked in both auto and manual locations: ${skillName}`
708
+ });
709
+ }
710
+ }
711
+ return issues;
712
+ }
552
713
  function checkHostFolder(host, kind, targetPath, id) {
553
714
  if (kind !== "skill") return void 0;
554
- const expectedPrefix = expectedSkillTargetPrefix(host);
555
- if (!expectedPrefix) {
715
+ const expectedPrefixes = expectedSkillTargetPrefixes(host);
716
+ if (!expectedPrefixes) {
556
717
  return {
557
718
  type: "unsupported-host-folder",
558
719
  id,
@@ -560,20 +721,30 @@ function checkHostFolder(host, kind, targetPath, id) {
560
721
  message: `Lockfile host is unsupported for managed skill target: ${host ?? "missing"}`
561
722
  };
562
723
  }
563
- if (!targetPath.startsWith(expectedPrefix)) {
724
+ if (!expectedPrefixes.some((prefix) => targetPath.startsWith(prefix))) {
564
725
  return {
565
726
  type: "unsupported-host-folder",
566
727
  id,
567
728
  targetPath,
568
- message: `Managed skill target ${targetPath} does not match host ${host}; expected ${expectedPrefix}`
729
+ message: `Managed skill target ${targetPath} does not match host ${host}; expected ${expectedPrefixes.join(" or ")}`
569
730
  };
570
731
  }
571
732
  return void 0;
572
733
  }
573
- function expectedSkillTargetPrefix(host) {
574
- if (host === "claude-code") return ".claude/skills/";
734
+ function expectedSkillTargetPrefixes(host) {
735
+ if (host === "claude-code") return [".claude/skills/"];
575
736
  if (host === "codex" || host === "gemini-cli" || host === "antigravity") {
576
- return ".agents/skills/";
737
+ return [".agents/skills/", ".agents/manual-skills/"];
738
+ }
739
+ return void 0;
740
+ }
741
+ function expectedRegistrySkillTargetPath(host, sourcePath, placement) {
742
+ const skillName = basename(sourcePath);
743
+ if (host === "claude-code") {
744
+ return placement === "manual" ? void 0 : `.claude/skills/${skillName}`;
745
+ }
746
+ if (host === "codex" || host === "gemini-cli" || host === "antigravity") {
747
+ return placement === "manual" ? `.agents/manual-skills/${skillName}` : `.agents/skills/${skillName}`;
577
748
  }
578
749
  return void 0;
579
750
  }
@@ -587,9 +758,10 @@ function pathExistsEvenIfDanglingSymlink3(path) {
587
758
  }
588
759
 
589
760
  // src/asset-targets/install-plan.ts
590
- import { existsSync as existsSync8, lstatSync as lstatSync4, readFileSync as readFileSync5 } from "node:fs";
591
- import { basename, dirname as dirname4, join as join8 } from "node:path";
761
+ import { existsSync as existsSync9, lstatSync as lstatSync4, readFileSync as readFileSync5 } from "node:fs";
762
+ import { basename as basename2, dirname as dirname4, join as join9 } from "node:path";
592
763
  function createAssetInstallPlan(options) {
764
+ const placement = options.placement ?? "registry";
593
765
  const assetsById = new Map(options.registry.assets.map((asset) => [asset.id, asset]));
594
766
  const bundlesById = new Map(options.bundles.map((bundle) => [bundle.id, bundle]));
595
767
  const assetIds = resolveBundleAssetIds(options.bundleIds, bundlesById);
@@ -604,17 +776,19 @@ function createAssetInstallPlan(options) {
604
776
  const lockEntries = createAgentAssetLockEntries(options.registry, options.agentAssetsDir, assetIds);
605
777
  const managedTargets = readManagedTargets(options.targetDir);
606
778
  const assetActions = assets.map(
607
- (asset) => createAssetAction(asset, options.agentAssetsDir, options.targetDir, options.host, managedTargets)
779
+ (asset) => createAssetAction(asset, options.agentAssetsDir, options.targetDir, options.host, placement, managedTargets)
608
780
  );
609
781
  const manifest = {
610
782
  schemaVersion: 1,
611
783
  host: options.host,
784
+ placement,
612
785
  bundleIds: [...options.bundleIds],
613
786
  assetIds
614
787
  };
615
788
  const lockfile = {
616
789
  schemaVersion: 1,
617
790
  host: options.host,
791
+ placement,
618
792
  bundleIds: [...options.bundleIds],
619
793
  assets: lockEntries.map((entry) => {
620
794
  const action = assetActions.find((candidate) => "assetId" in candidate && candidate.assetId === entry.id);
@@ -643,6 +817,7 @@ function createAssetInstallPlan(options) {
643
817
  dryRun: true,
644
818
  targetDir: options.targetDir,
645
819
  host: options.host,
820
+ placement,
646
821
  bundleIds: [...options.bundleIds],
647
822
  assetIds,
648
823
  actions: [...createDirectoryActions([...assetActions, ...writeActions]), ...assetActions, ...writeActions]
@@ -659,10 +834,15 @@ function resolveBundleAssetIds(bundleIds, bundlesById) {
659
834
  }
660
835
  return [...ids].sort();
661
836
  }
662
- function createAssetAction(asset, agentAssetsDir, targetDir, host, managedTargets) {
663
- const sourcePath = join8(agentAssetsDir, asset.sourcePath);
664
- const targetPath = resolveHostTargetPath(asset, host);
665
- const targetAbsolutePath = join8(targetDir, targetPath);
837
+ function createAssetAction(asset, agentAssetsDir, targetDir, host, placement, managedTargets) {
838
+ if (asset.kind === "skill" && asset.defaultScope === "user") {
839
+ throw new Error(
840
+ `User-scoped asset ${asset.id} must be linked at the user level, not installed into a project target.`
841
+ );
842
+ }
843
+ const sourcePath = join9(agentAssetsDir, asset.sourcePath);
844
+ const targetPath = resolveHostTargetPath(asset, host, placement);
845
+ const targetAbsolutePath = join9(targetDir, targetPath);
666
846
  const targetExists = pathExistsEvenIfDanglingSymlink4(targetAbsolutePath);
667
847
  if (targetExists) {
668
848
  const stats = lstatSync4(targetAbsolutePath);
@@ -683,17 +863,28 @@ function createAssetAction(asset, agentAssetsDir, targetDir, host, managedTarget
683
863
  targetPath
684
864
  };
685
865
  }
686
- function resolveHostTargetPath(asset, host) {
866
+ function resolveHostTargetPath(asset, host, placement) {
687
867
  if (asset.kind === "skill") {
868
+ const effectivePlacement = resolveSkillPlacement(asset, placement);
688
869
  if (host === "claude-code") {
689
- return `.claude/skills/${basename(asset.sourcePath)}`;
870
+ if (placement === "manual") {
871
+ throw new Error("Manual skill placement is only supported for .agents hosts");
872
+ }
873
+ return `.claude/skills/${basename2(asset.sourcePath)}`;
690
874
  }
691
- return `.agents/skills/${basename(asset.sourcePath)}`;
875
+ if (effectivePlacement === "manual") {
876
+ return `.agents/manual-skills/${basename2(asset.sourcePath)}`;
877
+ }
878
+ return `.agents/skills/${basename2(asset.sourcePath)}`;
692
879
  }
693
880
  if (asset.kind === "rule") {
694
- return `.pro-gov/agent-assets/rules/${basename(asset.sourcePath)}`;
881
+ return `.pro-gov/agent-assets/rules/${basename2(asset.sourcePath)}`;
695
882
  }
696
- return `.pro-gov/agent-assets/commands/${basename(asset.sourcePath)}`;
883
+ return `.pro-gov/agent-assets/commands/${basename2(asset.sourcePath)}`;
884
+ }
885
+ function resolveSkillPlacement(asset, placement) {
886
+ if (placement !== "registry") return placement;
887
+ return asset.defaultPlacement ?? "auto";
697
888
  }
698
889
  function createDirectoryActions(actions) {
699
890
  const directories = /* @__PURE__ */ new Set();
@@ -705,8 +896,8 @@ function createDirectoryActions(actions) {
705
896
  return [...directories].sort().map((targetPath) => ({ type: "create-dir", targetPath }));
706
897
  }
707
898
  function readManagedTargets(targetDir) {
708
- const lockfilePath = join8(targetDir, ".pro-gov/assets.lock.json");
709
- if (!existsSync8(lockfilePath)) return /* @__PURE__ */ new Set();
899
+ const lockfilePath = join9(targetDir, ".pro-gov/assets.lock.json");
900
+ if (!existsSync9(lockfilePath)) return /* @__PURE__ */ new Set();
710
901
  try {
711
902
  const lockfile = JSON.parse(readFileSync5(lockfilePath, "utf8"));
712
903
  return new Set((lockfile.assets ?? []).map((asset) => asset.targetPath).filter(Boolean));
@@ -724,8 +915,8 @@ function pathExistsEvenIfDanglingSymlink4(path) {
724
915
  }
725
916
 
726
917
  // src/asset-targets/recommend.ts
727
- import { existsSync as existsSync9, readdirSync as readdirSync5, readFileSync as readFileSync6 } from "node:fs";
728
- import { join as join9 } from "node:path";
918
+ import { existsSync as existsSync10, readdirSync as readdirSync5, readFileSync as readFileSync6 } from "node:fs";
919
+ import { join as join10 } from "node:path";
729
920
  var frontendPackages = /* @__PURE__ */ new Set([
730
921
  "@vitejs/plugin-react",
731
922
  "astro",
@@ -737,23 +928,23 @@ var frontendPackages = /* @__PURE__ */ new Set([
737
928
  "vite",
738
929
  "vue"
739
930
  ]);
740
- var agentEntryCandidates = ["AGENTS.md", "CLAUDE.md", ".gemini/settings.json", "GEMINI.md"];
931
+ var agentEntryCandidates = ["AGENTS.md", "CLAUDE.md"];
741
932
  function discoverTargetSignals(targetDir) {
742
- const packageJson = readJson(join9(targetDir, "package.json"));
933
+ const packageJson = readJson(join10(targetDir, "package.json"));
743
934
  const dependencyNames = packageJson ? Object.keys({ ...packageJson.dependencies, ...packageJson.devDependencies }) : [];
744
935
  const frontendSignals = dependencyNames.filter((name) => frontendPackages.has(name)).sort();
745
- const hasAgentEntry = agentEntryCandidates.some((file) => existsSync9(join9(targetDir, file)));
936
+ const hasAgentEntry = agentEntryCandidates.some((file) => existsSync10(join10(targetDir, file)));
746
937
  const researchSignals = [
747
- existsSync9(join9(targetDir, "docs/research")) ? "docs/research" : "",
748
- existsSync9(join9(targetDir, "research")) ? "research" : "",
938
+ existsSync10(join10(targetDir, "docs/research")) ? "docs/research" : "",
939
+ existsSync10(join10(targetDir, "research")) ? "research" : "",
749
940
  hasBookChildDirectory(targetDir, "research") ? "books/*/research" : "",
750
- textFileIncludes(join9(targetDir, "README.md"), ["research", "\u8C03\u7814"]) ? "README research" : ""
941
+ textFileIncludes(join10(targetDir, "README.md"), ["research", "\u8C03\u7814"]) ? "README research" : ""
751
942
  ].filter(Boolean);
752
943
  const writingSignals = [
753
- existsSync9(join9(targetDir, "chapters")) ? "chapters" : "",
754
- existsSync9(join9(targetDir, "src/chapters")) ? "src/chapters" : "",
944
+ existsSync10(join10(targetDir, "chapters")) ? "chapters" : "",
945
+ existsSync10(join10(targetDir, "src/chapters")) ? "src/chapters" : "",
755
946
  hasBookChildDirectory(targetDir, "chapters") ? "books/*/chapters" : "",
756
- textFileIncludes(join9(targetDir, "AGENTS.md"), ["writing mode", "novel chapter", "book content"]) ? "AGENTS writing" : ""
947
+ textFileIncludes(join10(targetDir, "AGENTS.md"), ["writing mode", "novel chapter", "book content"]) ? "AGENTS writing" : ""
757
948
  ].filter(Boolean);
758
949
  return {
759
950
  targetDir,
@@ -801,7 +992,7 @@ function recommendBundlesForTarget(targetDir) {
801
992
  return recommendations;
802
993
  }
803
994
  function readJson(path) {
804
- if (!existsSync9(path)) return void 0;
995
+ if (!existsSync10(path)) return void 0;
805
996
  try {
806
997
  return JSON.parse(readFileSync6(path, "utf8"));
807
998
  } catch {
@@ -809,16 +1000,16 @@ function readJson(path) {
809
1000
  }
810
1001
  }
811
1002
  function textFileIncludes(path, needles) {
812
- if (!existsSync9(path)) return false;
1003
+ if (!existsSync10(path)) return false;
813
1004
  const contents = readFileSync6(path, "utf8").toLowerCase();
814
1005
  return needles.some((needle) => contents.includes(needle.toLowerCase()));
815
1006
  }
816
1007
  function hasBookChildDirectory(targetDir, childName) {
817
- const booksDir = join9(targetDir, "books");
818
- if (!existsSync9(booksDir)) return false;
1008
+ const booksDir = join10(targetDir, "books");
1009
+ if (!existsSync10(booksDir)) return false;
819
1010
  try {
820
1011
  return readdirSync5(booksDir, { withFileTypes: true }).some(
821
- (entry) => entry.isDirectory() && existsSync9(join9(booksDir, entry.name, childName))
1012
+ (entry) => entry.isDirectory() && existsSync10(join10(booksDir, entry.name, childName))
822
1013
  );
823
1014
  } catch {
824
1015
  return false;
@@ -846,12 +1037,55 @@ function runAssets(args) {
846
1037
  if (subcommand2 === "check") {
847
1038
  return runAssetsCheck(rest);
848
1039
  }
1040
+ if (subcommand2 === "public-check") {
1041
+ return runAssetsPublicCheck(rest);
1042
+ }
849
1043
  if (subcommand2 === "npx") {
850
1044
  return runAssetsNpx(rest);
851
1045
  }
852
1046
  printUsage();
853
1047
  return 1;
854
1048
  }
1049
+ function runAssetsPublicCheck(args) {
1050
+ const options = parsePublicCheckOptions(args);
1051
+ if (!options.ok) {
1052
+ console.error(options.error);
1053
+ printUsage();
1054
+ return 1;
1055
+ }
1056
+ const registryLoad = loadAgentAssetRegistry({ agentAssetsDir: options.value.publicRoot });
1057
+ if (registryLoad.issues.length > 0) {
1058
+ for (const issue of registryLoad.issues) {
1059
+ console.error(`${issue.type}: ${issue.message}`);
1060
+ }
1061
+ return 1;
1062
+ }
1063
+ const result = checkPublicAssetPromotions({
1064
+ publicAgentAssetsDir: options.value.publicRoot,
1065
+ privateAgentAssetsDir: options.value.privateRoot,
1066
+ registry: registryLoad.registry
1067
+ });
1068
+ if (options.value.json) {
1069
+ console.log(
1070
+ JSON.stringify(
1071
+ {
1072
+ publicRoot: options.value.publicRoot,
1073
+ privateRoot: options.value.privateRoot,
1074
+ ...result
1075
+ },
1076
+ null,
1077
+ 2
1078
+ )
1079
+ );
1080
+ } else if (result.issues.length === 0) {
1081
+ console.log(`public assets check passed (${result.checked} checked)`);
1082
+ } else {
1083
+ for (const issue of result.issues) {
1084
+ console.log(`${issue.type}: ${issue.message}`);
1085
+ }
1086
+ }
1087
+ return result.issues.length === 0 ? 0 : 1;
1088
+ }
855
1089
  function runAssetsNpx(args) {
856
1090
  const [operation, ...rest] = args;
857
1091
  if (!operation || operation === "--help" || operation === "-h" || rest.includes("--help")) {
@@ -999,13 +1233,15 @@ function runAssetsPlan(args) {
999
1233
  registry: loaded.registry,
1000
1234
  bundles: loadAgentAssetBundles(loaded.agentAssetsDir),
1001
1235
  bundleIds: options.value.bundleIds,
1002
- host: options.value.host
1236
+ host: options.value.host,
1237
+ placement: options.value.placement
1003
1238
  });
1004
1239
  if (options.value.json) {
1005
1240
  console.log(JSON.stringify(plan, null, 2));
1006
1241
  } else {
1007
1242
  console.log(`target: ${plan.targetDir}`);
1008
1243
  console.log(`host: ${plan.host}`);
1244
+ console.log(`placement: ${plan.placement}`);
1009
1245
  console.log(`bundles: ${plan.bundleIds.join(", ")}`);
1010
1246
  console.log(`assets: ${plan.assetIds.length}`);
1011
1247
  console.log(`actions: ${plan.actions.length}`);
@@ -1101,6 +1337,16 @@ function parsePlanOptions(args) {
1101
1337
  }
1102
1338
  options.host = host;
1103
1339
  index += 1;
1340
+ } else if (arg === "--placement") {
1341
+ const placement = args[index + 1];
1342
+ if (!isSkillPlacement(placement)) {
1343
+ return {
1344
+ ok: false,
1345
+ error: "Expected --placement auto|manual"
1346
+ };
1347
+ }
1348
+ options.placement = placement;
1349
+ index += 1;
1104
1350
  } else if (arg === "--out") {
1105
1351
  const outPath = args[index + 1];
1106
1352
  if (!outPath) return { ok: false, error: "Expected --out <path>" };
@@ -1167,6 +1413,52 @@ function parseNpxOptions(operation, args) {
1167
1413
  }
1168
1414
  return { ok: true, value: options };
1169
1415
  }
1416
+ function parsePublicCheckOptions(args) {
1417
+ const defaults = getDefaultPublicCheckRoots();
1418
+ const options = {
1419
+ publicRoot: defaults.publicRoot,
1420
+ privateRoot: defaults.privateRoot,
1421
+ json: false
1422
+ };
1423
+ for (let index = 0; index < args.length; index += 1) {
1424
+ const arg = args[index];
1425
+ if (arg === "--public-root") {
1426
+ const publicRoot = args[index + 1];
1427
+ if (!publicRoot) return { ok: false, error: "Expected --public-root <path>" };
1428
+ options.publicRoot = publicRoot;
1429
+ index += 1;
1430
+ } else if (arg === "--private-root") {
1431
+ const privateRoot = args[index + 1];
1432
+ if (!privateRoot) return { ok: false, error: "Expected --private-root <path>" };
1433
+ options.privateRoot = privateRoot;
1434
+ index += 1;
1435
+ } else if (arg === "--json") {
1436
+ options.json = true;
1437
+ } else {
1438
+ return { ok: false, error: `Unknown assets public-check option: ${arg}` };
1439
+ }
1440
+ }
1441
+ if (!existsSync11(options.publicRoot)) {
1442
+ return { ok: false, error: `Public agent assets root does not exist: ${options.publicRoot}` };
1443
+ }
1444
+ if (!existsSync11(options.privateRoot)) {
1445
+ return { ok: false, error: `Private agent assets root does not exist: ${options.privateRoot}` };
1446
+ }
1447
+ return { ok: true, value: options };
1448
+ }
1449
+ function getDefaultPublicCheckRoots() {
1450
+ const defaultRegistryRoot = loadAgentAssetRegistry().agentAssetsDir;
1451
+ if (defaultRegistryRoot.endsWith("public-agent-assets")) {
1452
+ return {
1453
+ privateRoot: join11(dirname5(defaultRegistryRoot), "agent-assets"),
1454
+ publicRoot: defaultRegistryRoot
1455
+ };
1456
+ }
1457
+ return {
1458
+ privateRoot: defaultRegistryRoot,
1459
+ publicRoot: join11(dirname5(defaultRegistryRoot), "public-agent-assets")
1460
+ };
1461
+ }
1170
1462
  function listRegistryAssets(options) {
1171
1463
  const loaded = loadAgentAssetRegistry();
1172
1464
  if (loaded.issues.length > 0) {
@@ -1205,14 +1497,18 @@ function isVisibilityFilter(value) {
1205
1497
  function isHost(value) {
1206
1498
  return value === "codex" || value === "claude-code" || value === "gemini-cli" || value === "antigravity";
1207
1499
  }
1500
+ function isSkillPlacement(value) {
1501
+ return value === "auto" || value === "manual";
1502
+ }
1208
1503
  function printUsage() {
1209
1504
  console.error("Usage:");
1210
1505
  console.error(" pro-gov assets list [--registry] [--json] [--visibility public|private|third-party|all]");
1211
1506
  console.error(" pro-gov assets discover [--target <path>] [--json]");
1212
1507
  console.error(" pro-gov assets recommend [--target <path>] [--json]");
1213
- console.error(" pro-gov assets plan --bundle <bundle-id> [--bundle <bundle-id>] [--target <path>] [--host codex|claude-code|gemini-cli|antigravity] [--out <path>] [--json]");
1508
+ console.error(" pro-gov assets plan --bundle <bundle-id> [--bundle <bundle-id>] [--target <path>] [--host codex|claude-code|gemini-cli|antigravity] [--placement auto|manual] [--out <path>] [--json]");
1214
1509
  console.error(" pro-gov assets apply --plan <path>");
1215
1510
  console.error(" pro-gov assets check [--target <path>] [--json]");
1511
+ console.error(" pro-gov assets public-check [--public-root <path>] [--private-root <path>] [--json]");
1216
1512
  console.error(" pro-gov assets npx add <source> [--skill <name>] --plan [--root <path>]");
1217
1513
  console.error(" pro-gov assets npx update [--skill <name>] --plan [--root <path>]");
1218
1514
  }
@@ -1225,12 +1521,11 @@ function printNpxUsage() {
1225
1521
 
1226
1522
  // src/commands/doctor.ts
1227
1523
  import { spawnSync as spawnSync2 } from "node:child_process";
1228
- import { existsSync as existsSync10 } from "node:fs";
1524
+ import { existsSync as existsSync12 } from "node:fs";
1229
1525
  import { createRequire } from "node:module";
1230
- import { dirname as dirname6, join as join10 } from "node:path";
1526
+ import { dirname as dirname6, join as join12 } from "node:path";
1231
1527
  var REQUIRED_ASSETS = [
1232
1528
  "starter/AGENTS.template.md",
1233
- "starter/.gemini/settings.json",
1234
1529
  "starter/docs/governance/ssot-v0.9.md",
1235
1530
  "starter/docs/governance/agents-routing/engineering-runtime-v0.9.md",
1236
1531
  "starter/docs/governance/agents-routing/doc-only-v0.9.md",
@@ -1278,8 +1573,8 @@ function resolveDocGovDependencyCli() {
1278
1573
  try {
1279
1574
  const require2 = createRequire(import.meta.url);
1280
1575
  const packageJsonPath = require2.resolve("@pieai/doc-gov/package.json");
1281
- const cliPath = join10(dirname6(packageJsonPath), "dist/cli.js");
1282
- return existsSync10(cliPath) ? cliPath : null;
1576
+ const cliPath = join12(dirname6(packageJsonPath), "dist/cli.js");
1577
+ return existsSync12(cliPath) ? cliPath : null;
1283
1578
  } catch {
1284
1579
  return null;
1285
1580
  }
@@ -1421,8 +1716,8 @@ function bulletList(values) {
1421
1716
 
1422
1717
  // src/lens/scan.ts
1423
1718
  import { spawnSync as spawnSync3 } from "node:child_process";
1424
- import { existsSync as existsSync11, readdirSync as readdirSync6, readFileSync as readFileSync8, statSync as statSync3 } from "node:fs";
1425
- import { join as join11, relative as relative4 } from "node:path";
1719
+ import { existsSync as existsSync13, readdirSync as readdirSync6, readFileSync as readFileSync8, statSync as statSync3 } from "node:fs";
1720
+ import { join as join13, relative as relative4 } from "node:path";
1426
1721
  var ignoredDirectories = /* @__PURE__ */ new Set([
1427
1722
  ".git",
1428
1723
  ".next",
@@ -1438,23 +1733,23 @@ function scanProjectLensTarget(targetDir, options = {}) {
1438
1733
  const packageJson = readPackageJson(targetDir);
1439
1734
  return {
1440
1735
  targetDir,
1441
- aiEntryFiles: ["AGENTS.md", "CLAUDE.md", "GEMINI.md"].filter(
1442
- (file) => existsSync11(join11(targetDir, file))
1736
+ aiEntryFiles: ["AGENTS.md", "CLAUDE.md"].filter(
1737
+ (file) => existsSync13(join13(targetDir, file))
1443
1738
  ),
1444
- aiConfigFiles: [".gemini/settings.json"].filter((file) => existsSync11(join11(targetDir, file))),
1739
+ aiConfigFiles: [],
1445
1740
  packageJson,
1446
1741
  docs: {
1447
- hasDocsDirectory: existsSync11(join11(targetDir, "docs")),
1742
+ hasDocsDirectory: existsSync13(join13(targetDir, "docs")),
1448
1743
  markdownFileCount: markdownFiles.length,
1449
1744
  governanceFiles: markdownFiles.filter((file) => file.startsWith("docs/governance/") || file.startsWith("docs/policy/")).sort()
1450
1745
  },
1451
1746
  git: readGitState(targetDir),
1452
- largeFiles: files.map((file) => ({ path: file, bytes: statSync3(join11(targetDir, file)).size })).filter((file) => file.bytes >= largeFileBytes).sort((a, b) => b.bytes - a.bytes || a.path.localeCompare(b.path)).slice(0, 25)
1747
+ largeFiles: files.map((file) => ({ path: file, bytes: statSync3(join13(targetDir, file)).size })).filter((file) => file.bytes >= largeFileBytes).sort((a, b) => b.bytes - a.bytes || a.path.localeCompare(b.path)).slice(0, 25)
1453
1748
  };
1454
1749
  }
1455
1750
  function readPackageJson(targetDir) {
1456
- const packageJsonPath = join11(targetDir, "package.json");
1457
- if (!existsSync11(packageJsonPath)) return void 0;
1751
+ const packageJsonPath = join13(targetDir, "package.json");
1752
+ if (!existsSync13(packageJsonPath)) return void 0;
1458
1753
  try {
1459
1754
  const packageJson = JSON.parse(readFileSync8(packageJsonPath, "utf8"));
1460
1755
  return {
@@ -1491,13 +1786,13 @@ function listProjectFiles(targetDir) {
1491
1786
  return files.sort();
1492
1787
  }
1493
1788
  function collectFiles2(rootDir, currentDir, files) {
1494
- if (!existsSync11(currentDir)) return;
1789
+ if (!existsSync13(currentDir)) return;
1495
1790
  for (const entry of readdirSync6(currentDir, { withFileTypes: true })) {
1496
1791
  if (entry.isDirectory()) {
1497
1792
  if (ignoredDirectories.has(entry.name)) continue;
1498
- collectFiles2(rootDir, join11(currentDir, entry.name), files);
1793
+ collectFiles2(rootDir, join13(currentDir, entry.name), files);
1499
1794
  } else if (entry.isFile()) {
1500
- files.push(toUnixPath4(relative4(rootDir, join11(currentDir, entry.name))));
1795
+ files.push(toUnixPath4(relative4(rootDir, join13(currentDir, entry.name))));
1501
1796
  }
1502
1797
  }
1503
1798
  }
@@ -1591,9 +1886,300 @@ function printUsage2() {
1591
1886
  console.error("Usage: pro-gov lens report --target <path> --out <path>");
1592
1887
  }
1593
1888
 
1889
+ // src/portfolio/manifest.ts
1890
+ import { existsSync as existsSync14, readFileSync as readFileSync9 } from "node:fs";
1891
+ function loadPortfolioManifest(configPath) {
1892
+ let parsed;
1893
+ try {
1894
+ parsed = JSON.parse(readFileSync9(configPath, "utf8"));
1895
+ } catch (error) {
1896
+ return {
1897
+ configPath,
1898
+ issues: [
1899
+ {
1900
+ type: "invalid-json",
1901
+ message: error instanceof Error ? error.message : String(error)
1902
+ }
1903
+ ]
1904
+ };
1905
+ }
1906
+ const issues = validatePortfolioManifest(parsed);
1907
+ return {
1908
+ configPath,
1909
+ manifest: issues.length === 0 ? parsed : void 0,
1910
+ issues
1911
+ };
1912
+ }
1913
+ function validatePortfolioManifest(value) {
1914
+ const issues = [];
1915
+ if (!isRecord(value)) {
1916
+ return [
1917
+ {
1918
+ type: "invalid-field",
1919
+ field: "root",
1920
+ message: "Portfolio manifest must be a JSON object."
1921
+ }
1922
+ ];
1923
+ }
1924
+ if (value.schemaVersion !== 1) {
1925
+ issues.push({
1926
+ type: "invalid-field",
1927
+ field: "schemaVersion",
1928
+ message: "Portfolio manifest schemaVersion must be 1."
1929
+ });
1930
+ }
1931
+ if (typeof value.portfolioId !== "string" || value.portfolioId.length === 0) {
1932
+ issues.push({
1933
+ type: "invalid-field",
1934
+ field: "portfolioId",
1935
+ message: "Portfolio manifest portfolioId must be a non-empty string."
1936
+ });
1937
+ }
1938
+ validateEndpoint(value.controlPlane, "controlPlane", issues);
1939
+ validateEndpoint(value.executionEngine, "executionEngine", issues);
1940
+ if (!Array.isArray(value.targets)) {
1941
+ issues.push({
1942
+ type: "invalid-field",
1943
+ field: "targets",
1944
+ message: "Portfolio manifest targets must be an array."
1945
+ });
1946
+ return issues;
1947
+ }
1948
+ const seenTargetIds = /* @__PURE__ */ new Set();
1949
+ for (const target of value.targets) {
1950
+ if (!isRecord(target)) {
1951
+ issues.push({
1952
+ type: "invalid-field",
1953
+ field: "targets",
1954
+ message: "Portfolio target must be an object."
1955
+ });
1956
+ continue;
1957
+ }
1958
+ validateEndpoint(target, "targets", issues);
1959
+ if (typeof target.id === "string") {
1960
+ if (seenTargetIds.has(target.id)) {
1961
+ issues.push({
1962
+ type: "duplicate-target-id",
1963
+ id: target.id,
1964
+ message: `Duplicate portfolio target id: ${target.id}`
1965
+ });
1966
+ }
1967
+ seenTargetIds.add(target.id);
1968
+ }
1969
+ validateOptionalStringArray(target.assetBundles, target.id, "assetBundles", issues);
1970
+ validateOptionalStringArray(target.sharedRules, target.id, "sharedRules", issues);
1971
+ }
1972
+ return issues;
1973
+ }
1974
+ function getDefaultPortfolioTargets(manifest) {
1975
+ return manifest?.targets ?? [];
1976
+ }
1977
+ function validateEndpoint(value, field, issues) {
1978
+ if (value === void 0) return;
1979
+ if (!isRecord(value)) {
1980
+ issues.push({
1981
+ type: "invalid-field",
1982
+ field,
1983
+ message: `Portfolio ${field} must be an object.`
1984
+ });
1985
+ return;
1986
+ }
1987
+ if (typeof value.id !== "string" || value.id.length === 0) {
1988
+ issues.push({
1989
+ type: "invalid-field",
1990
+ field: `${field}.id`,
1991
+ message: `Portfolio ${field} id must be a non-empty string.`
1992
+ });
1993
+ }
1994
+ if (typeof value.path !== "string" || value.path.length === 0) {
1995
+ issues.push({
1996
+ type: "invalid-field",
1997
+ id: typeof value.id === "string" ? value.id : void 0,
1998
+ field: `${field}.path`,
1999
+ message: `Portfolio ${field} path must be a non-empty string.`
2000
+ });
2001
+ return;
2002
+ }
2003
+ if (!existsSync14(value.path)) {
2004
+ issues.push({
2005
+ type: "missing-path",
2006
+ id: typeof value.id === "string" ? value.id : void 0,
2007
+ field: `${field}.path`,
2008
+ message: `Portfolio ${field} path does not exist: ${value.path}`
2009
+ });
2010
+ }
2011
+ }
2012
+ function validateOptionalStringArray(value, id, field, issues) {
2013
+ if (value === void 0) return;
2014
+ if (!Array.isArray(value) || !value.every((entry) => typeof entry === "string")) {
2015
+ issues.push({
2016
+ type: "invalid-field",
2017
+ id: typeof id === "string" ? id : void 0,
2018
+ field,
2019
+ message: `Portfolio target ${field} must be an array of strings.`
2020
+ });
2021
+ }
2022
+ }
2023
+ function isRecord(value) {
2024
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2025
+ }
2026
+
2027
+ // src/commands/portfolio.ts
2028
+ function runPortfolio(args) {
2029
+ const [subcommand2, ...rest] = args;
2030
+ if (subcommand2 === "check") return runPortfolioCheck(rest);
2031
+ if (subcommand2 === "plan") return runPortfolioPlan(rest);
2032
+ printUsage3();
2033
+ return 1;
2034
+ }
2035
+ function runPortfolioCheck(args) {
2036
+ const options = parsePortfolioOptions(args);
2037
+ if (!options.ok) {
2038
+ console.error(options.error);
2039
+ printUsage3();
2040
+ return 1;
2041
+ }
2042
+ const loaded = loadPortfolioManifest(options.value.configPath);
2043
+ const targetIds = getDefaultPortfolioTargets(loaded.manifest).map((target) => target.id);
2044
+ if (options.value.json) {
2045
+ console.log(
2046
+ JSON.stringify(
2047
+ {
2048
+ ok: loaded.issues.length === 0,
2049
+ configPath: loaded.configPath,
2050
+ portfolioId: loaded.manifest?.portfolioId,
2051
+ targetIds,
2052
+ issues: loaded.issues
2053
+ },
2054
+ null,
2055
+ 2
2056
+ )
2057
+ );
2058
+ } else if (loaded.issues.length === 0) {
2059
+ console.log(`portfolio check passed (${targetIds.length} targets)`);
2060
+ } else {
2061
+ for (const issue of loaded.issues) {
2062
+ console.log(`${issue.type}: ${issue.message}`);
2063
+ }
2064
+ }
2065
+ return loaded.issues.length === 0 ? 0 : 1;
2066
+ }
2067
+ function runPortfolioPlan(args) {
2068
+ const options = parsePortfolioOptions(args);
2069
+ if (!options.ok) {
2070
+ console.error(options.error);
2071
+ printUsage3();
2072
+ return 1;
2073
+ }
2074
+ const loaded = loadPortfolioManifest(options.value.configPath);
2075
+ if (loaded.issues.length > 0 || !loaded.manifest) {
2076
+ for (const issue of loaded.issues) {
2077
+ console.error(`${issue.type}: ${issue.message}`);
2078
+ }
2079
+ return 1;
2080
+ }
2081
+ const targets = getDefaultPortfolioTargets(loaded.manifest).filter(
2082
+ (target) => !options.value.targetId || options.value.targetId === "all" || target.id === options.value.targetId
2083
+ );
2084
+ if (targets.length === 0) {
2085
+ console.error(`Unknown portfolio target: ${options.value.targetId}`);
2086
+ return 1;
2087
+ }
2088
+ const loadedAssets = loadAgentAssetRegistry();
2089
+ if (loadedAssets.issues.length > 0) {
2090
+ for (const issue of loadedAssets.issues) {
2091
+ console.error(`${issue.type}: ${issue.message}`);
2092
+ }
2093
+ return 1;
2094
+ }
2095
+ const bundles = loadAgentAssetBundles(loadedAssets.agentAssetsDir);
2096
+ try {
2097
+ const plans = targets.map((target) => {
2098
+ const bundleIds = target.assetBundles ?? [];
2099
+ if (bundleIds.length === 0) {
2100
+ throw new Error(`Portfolio target ${target.id} has no assetBundles`);
2101
+ }
2102
+ return {
2103
+ id: target.id,
2104
+ path: target.path,
2105
+ plan: createAssetInstallPlan({
2106
+ targetDir: target.path,
2107
+ agentAssetsDir: loadedAssets.agentAssetsDir,
2108
+ registry: loadedAssets.registry,
2109
+ bundles,
2110
+ bundleIds,
2111
+ host: options.value.host
2112
+ })
2113
+ };
2114
+ });
2115
+ if (options.value.json) {
2116
+ console.log(
2117
+ JSON.stringify(
2118
+ {
2119
+ configPath: loaded.configPath,
2120
+ portfolioId: loaded.manifest.portfolioId,
2121
+ targets: plans
2122
+ },
2123
+ null,
2124
+ 2
2125
+ )
2126
+ );
2127
+ } else {
2128
+ console.log(`portfolio plan targets: ${plans.length}`);
2129
+ for (const target of plans) {
2130
+ console.log(`${target.id} ${target.plan.actions.length} actions dry-run`);
2131
+ }
2132
+ }
2133
+ return 0;
2134
+ } catch (error) {
2135
+ console.error(error instanceof Error ? error.message : String(error));
2136
+ return 1;
2137
+ }
2138
+ }
2139
+ function parsePortfolioOptions(args) {
2140
+ const options = {
2141
+ configPath: "",
2142
+ host: "codex",
2143
+ json: false
2144
+ };
2145
+ for (let index = 0; index < args.length; index += 1) {
2146
+ const arg = args[index];
2147
+ if (arg === "--config") {
2148
+ const configPath = args[index + 1];
2149
+ if (!configPath) return { ok: false, error: "Expected --config <path>" };
2150
+ options.configPath = configPath;
2151
+ index += 1;
2152
+ } else if (arg === "--target") {
2153
+ const targetId = args[index + 1];
2154
+ if (!targetId) return { ok: false, error: "Expected --target <id|all>" };
2155
+ options.targetId = targetId;
2156
+ index += 1;
2157
+ } else if (arg === "--host") {
2158
+ const host = args[index + 1];
2159
+ if (!isHost2(host)) return { ok: false, error: "Expected --host codex|claude-code" };
2160
+ options.host = host;
2161
+ index += 1;
2162
+ } else if (arg === "--json") {
2163
+ options.json = true;
2164
+ } else {
2165
+ return { ok: false, error: `Unknown portfolio option: ${arg}` };
2166
+ }
2167
+ }
2168
+ if (!options.configPath) return { ok: false, error: "Expected --config <path>" };
2169
+ return { ok: true, value: options };
2170
+ }
2171
+ function isHost2(value) {
2172
+ return value === "codex" || value === "claude-code" || value === "gemini-cli" || value === "antigravity";
2173
+ }
2174
+ function printUsage3() {
2175
+ console.error("Usage:");
2176
+ console.error(" pro-gov portfolio check --config <path> [--json]");
2177
+ console.error(" pro-gov portfolio plan --config <path> [--target <id|all>] [--host codex|claude-code|gemini-cli|antigravity] [--json]");
2178
+ }
2179
+
1594
2180
  // src/commands/sync.ts
1595
- import { existsSync as existsSync12, readFileSync as readFileSync9 } from "node:fs";
1596
- import { join as join12 } from "node:path";
2181
+ import { existsSync as existsSync15, readFileSync as readFileSync10 } from "node:fs";
2182
+ import { join as join14 } from "node:path";
1597
2183
  function runSync(args) {
1598
2184
  if (!args.includes("--check")) {
1599
2185
  console.error("pro-gov sync requires --check in this first read-only release.");
@@ -1602,14 +2188,14 @@ function runSync(args) {
1602
2188
  let differences = 0;
1603
2189
  console.log("pro-gov sync check");
1604
2190
  for (const file of planStarterFiles()) {
1605
- const targetPath = join12(process.cwd(), file.targetPath);
1606
- if (!existsSync12(targetPath)) {
2191
+ const targetPath = join14(process.cwd(), file.targetPath);
2192
+ if (!existsSync15(targetPath)) {
1607
2193
  console.log(`missing: ${file.targetPath}`);
1608
2194
  differences += 1;
1609
2195
  continue;
1610
2196
  }
1611
- const source = readFileSync9(file.absoluteSourcePath, "utf8");
1612
- const target = readFileSync9(targetPath, "utf8");
2197
+ const source = readFileSync10(file.absoluteSourcePath, "utf8");
2198
+ const target = readFileSync10(targetPath, "utf8");
1613
2199
  if (source !== target) {
1614
2200
  console.log(`different: ${file.targetPath}`);
1615
2201
  differences += 1;
@@ -1631,7 +2217,10 @@ var COMMANDS = [
1631
2217
  "assets plan --bundle <bundle-id> [--target <path>] [--json]",
1632
2218
  "assets apply --plan <path>",
1633
2219
  "assets check [--target <path>] [--json]",
2220
+ "assets public-check [--public-root <path>] [--private-root <path>] [--json]",
1634
2221
  "assets npx add|update ... --plan",
2222
+ "portfolio check --config <path> [--json]",
2223
+ "portfolio plan --config <path> [--target <id|all>] [--json]",
1635
2224
  "lens scan [--target <path>] [--json]",
1636
2225
  "lens inspect [--target <path>] [--format text|json]",
1637
2226
  "lens report --target <path> --out <path>",
@@ -1647,6 +2236,8 @@ if (!command || command === "--help" || command === "-h") {
1647
2236
  process.exitCode = runAssets(process.argv.slice(3));
1648
2237
  } else if (command === "lens") {
1649
2238
  process.exitCode = runLens(process.argv.slice(3));
2239
+ } else if (command === "portfolio") {
2240
+ process.exitCode = runPortfolio(process.argv.slice(3));
1650
2241
  } else if (command === "init") {
1651
2242
  process.exitCode = runInit(process.argv.slice(3));
1652
2243
  } else if (command === "sync") {