@pieai/pro-gov 0.3.11 → 0.3.13

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
@@ -79,7 +79,14 @@ function createNpxSkillsMaintenancePlan(options) {
79
79
  cpSync(options.npxRoot, tempRoot, { recursive: true, dereference: false });
80
80
  const command2 = buildNpxCommand(options);
81
81
  const runner = options.runner ?? defaultRunner;
82
- const result = runner({ command: command2, cwd: tempRoot });
82
+ const timeoutMs = options.timeoutMs ?? 3e5;
83
+ const result = runner({ command: command2, cwd: tempRoot, timeoutMs });
84
+ if (result.timedOut) {
85
+ throw new Error(`npx skills ${options.operation} timed out after ${timeoutMs}ms`);
86
+ }
87
+ if (result.status === null && result.signal) {
88
+ throw new Error(`npx skills ${options.operation} terminated by ${result.signal}`);
89
+ }
83
90
  if (result.status !== 0) {
84
91
  throw new Error(`npx skills ${options.operation} failed with exit code ${result.status}`);
85
92
  }
@@ -128,12 +135,18 @@ function buildNpxCommand(options) {
128
135
  if (options.skill) command2.push(options.skill);
129
136
  return command2;
130
137
  }
131
- function defaultRunner({ command: command2, cwd }) {
132
- const result = spawnSync(command2[0] ?? "npx", command2.slice(1), { cwd, encoding: "utf8" });
138
+ function defaultRunner({ command: command2, cwd, timeoutMs }) {
139
+ const result = spawnSync(command2[0] ?? "npx", command2.slice(1), {
140
+ cwd,
141
+ encoding: "utf8",
142
+ timeout: timeoutMs
143
+ });
133
144
  return {
134
145
  status: result.status,
135
146
  stdout: result.stdout,
136
- stderr: result.stderr
147
+ stderr: result.stderr,
148
+ signal: result.signal,
149
+ timedOut: result.error?.code === "ETIMEDOUT"
137
150
  };
138
151
  }
139
152
  function snapshotFiles(root) {
@@ -536,8 +549,229 @@ function resolveSafePath(root, sourcePath) {
536
549
  }
537
550
 
538
551
  // src/asset-targets/apply.ts
539
- import { existsSync as existsSync7, lstatSync as lstatSync2, mkdirSync as mkdirSync2, realpathSync, symlinkSync, unlinkSync, writeFileSync } from "node:fs";
540
- import { dirname as dirname3, join as join7, relative as relative4, resolve } from "node:path";
552
+ import {
553
+ existsSync as existsSync8,
554
+ lstatSync as lstatSync3,
555
+ mkdirSync as mkdirSync2,
556
+ readlinkSync as readlinkSync2,
557
+ realpathSync,
558
+ symlinkSync,
559
+ unlinkSync,
560
+ writeFileSync
561
+ } from "node:fs";
562
+ import { dirname as dirname4, join as join8, relative as relative4, resolve as resolve2 } from "node:path";
563
+
564
+ // src/asset-targets/install-plan.ts
565
+ import { existsSync as existsSync7, lstatSync as lstatSync2, readFileSync as readFileSync4, readlinkSync } from "node:fs";
566
+ import { basename, dirname as dirname3, join as join7, resolve } from "node:path";
567
+ function createAssetInstallPlan(options) {
568
+ const placement = options.placement ?? "registry";
569
+ const assetsById = new Map(options.registry.assets.map((asset) => [asset.id, asset]));
570
+ const bundlesById = new Map(options.bundles.map((bundle) => [bundle.id, bundle]));
571
+ const assetIds = resolveBundleAssetIds(options.bundleIds, bundlesById);
572
+ const assets = assetIds.map((assetId) => {
573
+ const asset = assetsById.get(assetId);
574
+ if (!asset) throw new Error(`Unknown asset id in bundle: ${assetId}`);
575
+ if (asset.kind === "skill" && !asset.hosts.includes(options.host)) {
576
+ throw new Error(`Asset ${assetId} does not support host ${options.host}`);
577
+ }
578
+ return asset;
579
+ });
580
+ const lockEntries = createAgentAssetLockEntries(options.registry, options.agentAssetsDir, assetIds);
581
+ const managedEntries = readManagedEntries(options.targetDir);
582
+ const managedTargets = new Set(managedEntries.map((entry) => entry.targetPath));
583
+ const assetActions = assets.map(
584
+ (asset) => createAssetAction(asset, options.agentAssetsDir, options.targetDir, options.host, placement, managedTargets)
585
+ );
586
+ const manifest = {
587
+ schemaVersion: 1,
588
+ host: options.host,
589
+ placement,
590
+ bundleIds: [...options.bundleIds],
591
+ assetIds
592
+ };
593
+ const lockfile = {
594
+ schemaVersion: 1,
595
+ host: options.host,
596
+ placement,
597
+ bundleIds: [...options.bundleIds],
598
+ assets: lockEntries.map((entry) => {
599
+ const action = assetActions.find((candidate) => "assetId" in candidate && candidate.assetId === entry.id);
600
+ return {
601
+ ...entry,
602
+ targetPath: action && "targetPath" in action ? action.targetPath : ""
603
+ };
604
+ })
605
+ };
606
+ const writeActions = [
607
+ {
608
+ type: "write-file",
609
+ targetPath: ".pro-gov/assets.json",
610
+ content: `${JSON.stringify(manifest, null, 2)}
611
+ `
612
+ },
613
+ {
614
+ type: "write-file",
615
+ targetPath: ".pro-gov/assets.lock.json",
616
+ content: `${JSON.stringify(lockfile, null, 2)}
617
+ `
618
+ }
619
+ ];
620
+ const expectedTargetPaths = new Set(
621
+ assetActions.filter((action) => "assetId" in action).map((action) => action.targetPath)
622
+ );
623
+ const removalActions = createRemovalActions(
624
+ options.targetDir,
625
+ options.agentAssetsDir,
626
+ managedEntries,
627
+ expectedTargetPaths
628
+ );
629
+ return {
630
+ schemaVersion: 1,
631
+ dryRun: true,
632
+ targetDir: options.targetDir,
633
+ host: options.host,
634
+ placement,
635
+ bundleIds: [...options.bundleIds],
636
+ assetIds,
637
+ actions: [
638
+ ...createDirectoryActions([...assetActions, ...writeActions]),
639
+ ...removalActions,
640
+ ...assetActions,
641
+ ...writeActions
642
+ ]
643
+ };
644
+ }
645
+ function resolveBundleAssetIds(bundleIds, bundlesById) {
646
+ const ids = /* @__PURE__ */ new Set();
647
+ for (const bundleId of bundleIds) {
648
+ const bundle = bundlesById.get(bundleId);
649
+ if (!bundle) throw new Error(`Unknown bundle id: ${bundleId}`);
650
+ for (const assetId of bundle.assets) {
651
+ ids.add(assetId);
652
+ }
653
+ }
654
+ return [...ids].sort();
655
+ }
656
+ function createAssetAction(asset, agentAssetsDir, targetDir, host, placement, managedTargets) {
657
+ if (asset.kind === "skill" && asset.defaultScope === "user") {
658
+ throw new Error(
659
+ `User-scoped asset ${asset.id} must be linked at the user level, not installed into a project target.`
660
+ );
661
+ }
662
+ const sourcePath = join7(agentAssetsDir, asset.sourcePath);
663
+ const targetPath = resolveHostTargetPath(asset, host, placement);
664
+ const targetAbsolutePath = join7(targetDir, targetPath);
665
+ const targetExists = pathExistsEvenIfDanglingSymlink2(targetAbsolutePath);
666
+ if (targetExists) {
667
+ const stats = lstatSync2(targetAbsolutePath);
668
+ if (stats.isSymbolicLink() && managedTargets.has(targetPath)) {
669
+ return {
670
+ type: "update-symlink",
671
+ assetId: asset.id,
672
+ sourcePath,
673
+ targetPath
674
+ };
675
+ }
676
+ throw new Error(`Refusing to overwrite unmanaged target: ${targetPath}`);
677
+ }
678
+ return {
679
+ type: "symlink",
680
+ assetId: asset.id,
681
+ sourcePath,
682
+ targetPath
683
+ };
684
+ }
685
+ function resolveHostTargetPath(asset, host, placement) {
686
+ if (asset.kind === "skill") {
687
+ const effectivePlacement = resolveSkillPlacement(asset, placement);
688
+ if (host === "claude-code") {
689
+ if (placement === "manual") {
690
+ throw new Error("Manual skill placement is only supported for .agents hosts");
691
+ }
692
+ return `.claude/skills/${basename(asset.sourcePath)}`;
693
+ }
694
+ if (effectivePlacement === "manual") {
695
+ return `.agents/manual-skills/${basename(asset.sourcePath)}`;
696
+ }
697
+ return `.agents/skills/${basename(asset.sourcePath)}`;
698
+ }
699
+ if (asset.kind === "rule") {
700
+ return `.pro-gov/agent-assets/rules/${basename(asset.sourcePath)}`;
701
+ }
702
+ return `.pro-gov/agent-assets/commands/${basename(asset.sourcePath)}`;
703
+ }
704
+ function resolveSkillPlacement(asset, placement) {
705
+ if (placement !== "registry") return placement;
706
+ return asset.defaultPlacement ?? "auto";
707
+ }
708
+ function createDirectoryActions(actions) {
709
+ const directories = /* @__PURE__ */ new Set();
710
+ for (const action of actions) {
711
+ if (action.type === "create-dir") continue;
712
+ const directory = dirname3(action.targetPath);
713
+ if (directory !== ".") directories.add(directory);
714
+ }
715
+ return [...directories].sort().map((targetPath) => ({ type: "create-dir", targetPath }));
716
+ }
717
+ function readManagedEntries(targetDir) {
718
+ const lockfilePath = join7(targetDir, ".pro-gov/assets.lock.json");
719
+ if (!existsSync7(lockfilePath)) return [];
720
+ try {
721
+ const lockfile = JSON.parse(readFileSync4(lockfilePath, "utf8"));
722
+ return (lockfile.assets ?? []).filter(
723
+ (entry) => typeof entry.id === "string" && typeof entry.sourcePath === "string" && typeof entry.targetPath === "string"
724
+ );
725
+ } catch {
726
+ return [];
727
+ }
728
+ }
729
+ function createRemovalActions(targetDir, agentAssetsDir, managedEntries, expectedTargetPaths) {
730
+ const actions = [];
731
+ for (const entry of managedEntries) {
732
+ if (expectedTargetPaths.has(entry.targetPath)) continue;
733
+ if (!isManagedAssetTargetPath(entry.targetPath)) {
734
+ throw new Error(`Refusing to remove managed asset outside supported roots: ${entry.targetPath}`);
735
+ }
736
+ const targetAbsolutePath = join7(targetDir, entry.targetPath);
737
+ if (!pathExistsEvenIfDanglingSymlink2(targetAbsolutePath)) continue;
738
+ const stats = lstatSync2(targetAbsolutePath);
739
+ if (!stats.isSymbolicLink()) {
740
+ throw new Error(`Refusing to remove path that is no longer a managed symlink: ${entry.targetPath}`);
741
+ }
742
+ const expectedSourcePath = join7(agentAssetsDir, entry.sourcePath);
743
+ const actualSourcePath = resolve(dirname3(targetAbsolutePath), readlinkSync(targetAbsolutePath));
744
+ if (actualSourcePath !== resolve(expectedSourcePath)) {
745
+ throw new Error(`Refusing to remove managed symlink with changed target: ${entry.targetPath}`);
746
+ }
747
+ actions.push({
748
+ type: "remove-symlink",
749
+ assetId: entry.id,
750
+ expectedSourcePath,
751
+ targetPath: entry.targetPath
752
+ });
753
+ }
754
+ return actions.sort((a, b) => a.targetPath.localeCompare(b.targetPath));
755
+ }
756
+ function isManagedAssetTargetPath(path) {
757
+ return [
758
+ ".agents/skills/",
759
+ ".agents/manual-skills/",
760
+ ".claude/skills/",
761
+ ".pro-gov/agent-assets/rules/",
762
+ ".pro-gov/agent-assets/commands/"
763
+ ].some((prefix) => path.startsWith(prefix));
764
+ }
765
+ function pathExistsEvenIfDanglingSymlink2(path) {
766
+ try {
767
+ lstatSync2(path);
768
+ return true;
769
+ } catch {
770
+ return false;
771
+ }
772
+ }
773
+
774
+ // src/asset-targets/apply.ts
541
775
  function applyAssetInstallPlan(plan) {
542
776
  const appliedActions = [];
543
777
  for (const action of plan.actions) {
@@ -547,52 +781,71 @@ function applyAssetInstallPlan(plan) {
547
781
  return { appliedActions };
548
782
  }
549
783
  function applyAction(targetDir, action) {
550
- const targetAbsolutePath = join7(targetDir, action.targetPath);
784
+ const targetAbsolutePath = join8(targetDir, action.targetPath);
785
+ if (action.type === "remove-symlink") {
786
+ removeManagedSymlink(targetAbsolutePath, action);
787
+ return;
788
+ }
551
789
  if (action.type === "create-dir") {
552
790
  mkdirSync2(targetAbsolutePath, { recursive: true });
553
791
  return;
554
792
  }
555
793
  if (action.type === "write-file") {
556
- mkdirSync2(dirname3(targetAbsolutePath), { recursive: true });
794
+ mkdirSync2(dirname4(targetAbsolutePath), { recursive: true });
557
795
  writeFileSync(targetAbsolutePath, action.content);
558
796
  return;
559
797
  }
560
- mkdirSync2(dirname3(targetAbsolutePath), { recursive: true });
561
- const sourceAbsolutePath = resolve(action.sourcePath);
562
- const symlinkTarget = relative4(realpathSync(dirname3(targetAbsolutePath)), realpathSync(sourceAbsolutePath)) || ".";
798
+ mkdirSync2(dirname4(targetAbsolutePath), { recursive: true });
799
+ const sourceAbsolutePath = resolve2(action.sourcePath);
800
+ const symlinkTarget = relative4(realpathSync(dirname4(targetAbsolutePath)), realpathSync(sourceAbsolutePath)) || ".";
563
801
  if (action.type === "symlink") {
564
- if (pathExistsEvenIfDanglingSymlink2(targetAbsolutePath)) {
802
+ if (pathExistsEvenIfDanglingSymlink3(targetAbsolutePath)) {
565
803
  throw new Error(`Refusing to overwrite unmanaged target: ${action.targetPath}`);
566
804
  }
567
805
  symlinkSync(symlinkTarget, targetAbsolutePath);
568
806
  return;
569
807
  }
570
- if (!pathExistsEvenIfDanglingSymlink2(targetAbsolutePath)) {
808
+ if (!pathExistsEvenIfDanglingSymlink3(targetAbsolutePath)) {
571
809
  symlinkSync(symlinkTarget, targetAbsolutePath);
572
810
  return;
573
811
  }
574
- const stats = lstatSync2(targetAbsolutePath);
812
+ const stats = lstatSync3(targetAbsolutePath);
575
813
  if (!stats.isSymbolicLink()) {
576
814
  throw new Error(`Refusing to overwrite unmanaged target: ${action.targetPath}`);
577
815
  }
578
816
  unlinkSync(targetAbsolutePath);
579
817
  symlinkSync(symlinkTarget, targetAbsolutePath);
580
818
  }
581
- function pathExistsEvenIfDanglingSymlink2(path) {
819
+ function removeManagedSymlink(targetAbsolutePath, action) {
820
+ if (!isManagedAssetTargetPath(action.targetPath)) {
821
+ throw new Error(`Refusing to remove managed asset outside supported roots: ${action.targetPath}`);
822
+ }
823
+ if (!pathExistsEvenIfDanglingSymlink3(targetAbsolutePath)) return;
824
+ const stats = lstatSync3(targetAbsolutePath);
825
+ if (!stats.isSymbolicLink()) {
826
+ throw new Error(`Refusing to remove path that is no longer a managed symlink: ${action.targetPath}`);
827
+ }
828
+ const actualSourcePath = resolve2(dirname4(targetAbsolutePath), readlinkSync2(targetAbsolutePath));
829
+ if (actualSourcePath !== resolve2(action.expectedSourcePath)) {
830
+ throw new Error(`Refusing to remove managed symlink with changed target: ${action.targetPath}`);
831
+ }
832
+ unlinkSync(targetAbsolutePath);
833
+ }
834
+ function pathExistsEvenIfDanglingSymlink3(path) {
582
835
  try {
583
- lstatSync2(path);
836
+ lstatSync3(path);
584
837
  return true;
585
838
  } catch {
586
- return existsSync7(path);
839
+ return existsSync8(path);
587
840
  }
588
841
  }
589
842
 
590
843
  // src/asset-targets/check.ts
591
- import { existsSync as existsSync8, lstatSync as lstatSync3, readFileSync as readFileSync4 } from "node:fs";
592
- import { basename, join as join8 } from "node:path";
844
+ import { existsSync as existsSync9, lstatSync as lstatSync4, readFileSync as readFileSync5 } from "node:fs";
845
+ import { basename as basename2, join as join9 } from "node:path";
593
846
  function checkInstalledAssets(options) {
594
- const lockfilePath = join8(options.targetDir, ".pro-gov/assets.lock.json");
595
- if (!existsSync8(lockfilePath)) {
847
+ const lockfilePath = join9(options.targetDir, ".pro-gov/assets.lock.json");
848
+ if (!existsSync9(lockfilePath)) {
596
849
  return {
597
850
  targetDir: options.targetDir,
598
851
  issues: [
@@ -604,12 +857,12 @@ function checkInstalledAssets(options) {
604
857
  };
605
858
  }
606
859
  const registryById = new Map(options.registry.assets.map((asset) => [asset.id, asset]));
607
- const lockfile = JSON.parse(readFileSync4(lockfilePath, "utf8"));
860
+ const lockfile = JSON.parse(readFileSync5(lockfilePath, "utf8"));
608
861
  const issues = [];
609
862
  const strictRegistry = options.strictRegistry ?? false;
610
863
  for (const entry of lockfile.assets ?? []) {
611
864
  const asset = registryById.get(entry.id);
612
- const targetAbsolutePath = join8(options.targetDir, entry.targetPath);
865
+ const targetAbsolutePath = join9(options.targetDir, entry.targetPath);
613
866
  if (!asset && strictRegistry) {
614
867
  issues.push({
615
868
  type: "unknown-asset",
@@ -636,7 +889,7 @@ function checkInstalledAssets(options) {
636
889
  issues.push(placementDriftIssue);
637
890
  }
638
891
  }
639
- if (!pathExistsEvenIfDanglingSymlink3(targetAbsolutePath)) {
892
+ if (!pathExistsEvenIfDanglingSymlink4(targetAbsolutePath)) {
640
893
  issues.push({
641
894
  type: "missing-target",
642
895
  id: entry.id,
@@ -645,7 +898,7 @@ function checkInstalledAssets(options) {
645
898
  });
646
899
  continue;
647
900
  }
648
- const targetStats = lstatSync3(targetAbsolutePath);
901
+ const targetStats = lstatSync4(targetAbsolutePath);
649
902
  if (!targetStats.isSymbolicLink()) {
650
903
  issues.push({
651
904
  type: "unmanaged-conflict",
@@ -655,7 +908,7 @@ function checkInstalledAssets(options) {
655
908
  });
656
909
  continue;
657
910
  }
658
- if (!existsSync8(targetAbsolutePath)) {
911
+ if (!existsSync9(targetAbsolutePath)) {
659
912
  issues.push({
660
913
  type: "dangling-symlink",
661
914
  id: entry.id,
@@ -675,8 +928,8 @@ function checkInstalledAssets(options) {
675
928
  });
676
929
  }
677
930
  if (!asset || !strictRegistry) continue;
678
- const sourceAbsolutePath = join8(options.agentAssetsDir, asset.sourcePath);
679
- if (!existsSync8(sourceAbsolutePath)) {
931
+ const sourceAbsolutePath = join9(options.agentAssetsDir, asset.sourcePath);
932
+ if (!existsSync9(sourceAbsolutePath)) {
680
933
  issues.push({
681
934
  type: "missing-source",
682
935
  id: entry.id,
@@ -714,10 +967,10 @@ function checkDuplicateSkillPlacements(targetDir, registry) {
714
967
  const issues = [];
715
968
  for (const asset of registry.assets) {
716
969
  if (asset.kind !== "skill") continue;
717
- const skillName = basename(asset.sourcePath);
970
+ const skillName = basename2(asset.sourcePath);
718
971
  const autoPath = `.agents/skills/${skillName}`;
719
972
  const manualPath = `.agents/manual-skills/${skillName}`;
720
- if (pathExistsEvenIfDanglingSymlink3(join8(targetDir, autoPath)) && pathExistsEvenIfDanglingSymlink3(join8(targetDir, manualPath))) {
973
+ if (pathExistsEvenIfDanglingSymlink4(join9(targetDir, autoPath)) && pathExistsEvenIfDanglingSymlink4(join9(targetDir, manualPath))) {
721
974
  issues.push({
722
975
  type: "duplicate-skill-placement",
723
976
  id: asset.id,
@@ -757,7 +1010,7 @@ function expectedSkillTargetPrefixes(host) {
757
1010
  return void 0;
758
1011
  }
759
1012
  function expectedRegistrySkillTargetPath(host, sourcePath, placement) {
760
- const skillName = basename(sourcePath);
1013
+ const skillName = basename2(sourcePath);
761
1014
  if (host === "claude-code") {
762
1015
  return placement === "manual" ? void 0 : `.claude/skills/${skillName}`;
763
1016
  }
@@ -766,163 +1019,6 @@ function expectedRegistrySkillTargetPath(host, sourcePath, placement) {
766
1019
  }
767
1020
  return void 0;
768
1021
  }
769
- function pathExistsEvenIfDanglingSymlink3(path) {
770
- try {
771
- lstatSync3(path);
772
- return true;
773
- } catch {
774
- return false;
775
- }
776
- }
777
-
778
- // src/asset-targets/install-plan.ts
779
- import { existsSync as existsSync9, lstatSync as lstatSync4, readFileSync as readFileSync5 } from "node:fs";
780
- import { basename as basename2, dirname as dirname4, join as join9 } from "node:path";
781
- function createAssetInstallPlan(options) {
782
- const placement = options.placement ?? "registry";
783
- const assetsById = new Map(options.registry.assets.map((asset) => [asset.id, asset]));
784
- const bundlesById = new Map(options.bundles.map((bundle) => [bundle.id, bundle]));
785
- const assetIds = resolveBundleAssetIds(options.bundleIds, bundlesById);
786
- const assets = assetIds.map((assetId) => {
787
- const asset = assetsById.get(assetId);
788
- if (!asset) throw new Error(`Unknown asset id in bundle: ${assetId}`);
789
- if (asset.kind === "skill" && !asset.hosts.includes(options.host)) {
790
- throw new Error(`Asset ${assetId} does not support host ${options.host}`);
791
- }
792
- return asset;
793
- });
794
- const lockEntries = createAgentAssetLockEntries(options.registry, options.agentAssetsDir, assetIds);
795
- const managedTargets = readManagedTargets(options.targetDir);
796
- const assetActions = assets.map(
797
- (asset) => createAssetAction(asset, options.agentAssetsDir, options.targetDir, options.host, placement, managedTargets)
798
- );
799
- const manifest = {
800
- schemaVersion: 1,
801
- host: options.host,
802
- placement,
803
- bundleIds: [...options.bundleIds],
804
- assetIds
805
- };
806
- const lockfile = {
807
- schemaVersion: 1,
808
- host: options.host,
809
- placement,
810
- bundleIds: [...options.bundleIds],
811
- assets: lockEntries.map((entry) => {
812
- const action = assetActions.find((candidate) => "assetId" in candidate && candidate.assetId === entry.id);
813
- return {
814
- ...entry,
815
- targetPath: action && "targetPath" in action ? action.targetPath : ""
816
- };
817
- })
818
- };
819
- const writeActions = [
820
- {
821
- type: "write-file",
822
- targetPath: ".pro-gov/assets.json",
823
- content: `${JSON.stringify(manifest, null, 2)}
824
- `
825
- },
826
- {
827
- type: "write-file",
828
- targetPath: ".pro-gov/assets.lock.json",
829
- content: `${JSON.stringify(lockfile, null, 2)}
830
- `
831
- }
832
- ];
833
- return {
834
- schemaVersion: 1,
835
- dryRun: true,
836
- targetDir: options.targetDir,
837
- host: options.host,
838
- placement,
839
- bundleIds: [...options.bundleIds],
840
- assetIds,
841
- actions: [...createDirectoryActions([...assetActions, ...writeActions]), ...assetActions, ...writeActions]
842
- };
843
- }
844
- function resolveBundleAssetIds(bundleIds, bundlesById) {
845
- const ids = /* @__PURE__ */ new Set();
846
- for (const bundleId of bundleIds) {
847
- const bundle = bundlesById.get(bundleId);
848
- if (!bundle) throw new Error(`Unknown bundle id: ${bundleId}`);
849
- for (const assetId of bundle.assets) {
850
- ids.add(assetId);
851
- }
852
- }
853
- return [...ids].sort();
854
- }
855
- function createAssetAction(asset, agentAssetsDir, targetDir, host, placement, managedTargets) {
856
- if (asset.kind === "skill" && asset.defaultScope === "user") {
857
- throw new Error(
858
- `User-scoped asset ${asset.id} must be linked at the user level, not installed into a project target.`
859
- );
860
- }
861
- const sourcePath = join9(agentAssetsDir, asset.sourcePath);
862
- const targetPath = resolveHostTargetPath(asset, host, placement);
863
- const targetAbsolutePath = join9(targetDir, targetPath);
864
- const targetExists = pathExistsEvenIfDanglingSymlink4(targetAbsolutePath);
865
- if (targetExists) {
866
- const stats = lstatSync4(targetAbsolutePath);
867
- if (stats.isSymbolicLink() && managedTargets.has(targetPath)) {
868
- return {
869
- type: "update-symlink",
870
- assetId: asset.id,
871
- sourcePath,
872
- targetPath
873
- };
874
- }
875
- throw new Error(`Refusing to overwrite unmanaged target: ${targetPath}`);
876
- }
877
- return {
878
- type: "symlink",
879
- assetId: asset.id,
880
- sourcePath,
881
- targetPath
882
- };
883
- }
884
- function resolveHostTargetPath(asset, host, placement) {
885
- if (asset.kind === "skill") {
886
- const effectivePlacement = resolveSkillPlacement(asset, placement);
887
- if (host === "claude-code") {
888
- if (placement === "manual") {
889
- throw new Error("Manual skill placement is only supported for .agents hosts");
890
- }
891
- return `.claude/skills/${basename2(asset.sourcePath)}`;
892
- }
893
- if (effectivePlacement === "manual") {
894
- return `.agents/manual-skills/${basename2(asset.sourcePath)}`;
895
- }
896
- return `.agents/skills/${basename2(asset.sourcePath)}`;
897
- }
898
- if (asset.kind === "rule") {
899
- return `.pro-gov/agent-assets/rules/${basename2(asset.sourcePath)}`;
900
- }
901
- return `.pro-gov/agent-assets/commands/${basename2(asset.sourcePath)}`;
902
- }
903
- function resolveSkillPlacement(asset, placement) {
904
- if (placement !== "registry") return placement;
905
- return asset.defaultPlacement ?? "auto";
906
- }
907
- function createDirectoryActions(actions) {
908
- const directories = /* @__PURE__ */ new Set();
909
- for (const action of actions) {
910
- if (action.type === "create-dir") continue;
911
- const directory = dirname4(action.targetPath);
912
- if (directory !== ".") directories.add(directory);
913
- }
914
- return [...directories].sort().map((targetPath) => ({ type: "create-dir", targetPath }));
915
- }
916
- function readManagedTargets(targetDir) {
917
- const lockfilePath = join9(targetDir, ".pro-gov/assets.lock.json");
918
- if (!existsSync9(lockfilePath)) return /* @__PURE__ */ new Set();
919
- try {
920
- const lockfile = JSON.parse(readFileSync5(lockfilePath, "utf8"));
921
- return new Set((lockfile.assets ?? []).map((asset) => asset.targetPath).filter(Boolean));
922
- } catch {
923
- return /* @__PURE__ */ new Set();
924
- }
925
- }
926
1022
  function pathExistsEvenIfDanglingSymlink4(path) {
927
1023
  try {
928
1024
  lstatSync4(path);
@@ -1642,7 +1738,7 @@ function isEngineeringRuntimeProject(root) {
1642
1738
  import { spawnSync as spawnSync3 } from "node:child_process";
1643
1739
  import { existsSync as existsSync13, mkdirSync as mkdirSync4, readFileSync as readFileSync10, writeFileSync as writeFileSync3 } from "node:fs";
1644
1740
  import { tmpdir as tmpdir2 } from "node:os";
1645
- import { basename as basename3, join as join13, resolve as resolve2 } from "node:path";
1741
+ import { basename as basename3, join as join13, resolve as resolve3 } from "node:path";
1646
1742
 
1647
1743
  // src/host-hooks/host-hook-runner.ts
1648
1744
  import { closeSync, openSync, readFileSync as readFileSync9, readSync, statSync as statSync3 } from "node:fs";
@@ -1967,7 +2063,7 @@ function defaultDebugDir() {
1967
2063
  });
1968
2064
  const value = gitPath.status === 0 ? gitPath.stdout.trim() : "";
1969
2065
  if (value) {
1970
- return resolve2(process.cwd(), value);
2066
+ return resolve3(process.cwd(), value);
1971
2067
  }
1972
2068
  return join13(tmpdir2(), "pro-gov-hook-debug", basename3(process.cwd()));
1973
2069
  }
@@ -1983,8 +2079,8 @@ function readPackageVersion() {
1983
2079
  }
1984
2080
  function resolvePackageJsonPath() {
1985
2081
  const candidates = [
1986
- resolve2(process.cwd(), "packages/pro-gov/package.json"),
1987
- resolve2(process.cwd(), "node_modules/@pieai/pro-gov/package.json")
2082
+ resolve3(process.cwd(), "packages/pro-gov/package.json"),
2083
+ resolve3(process.cwd(), "node_modules/@pieai/pro-gov/package.json")
1988
2084
  ];
1989
2085
  return candidates.find((candidate) => existsSync13(candidate));
1990
2086
  }
@@ -2076,36 +2172,253 @@ function applyStarterFiles(files, profile) {
2076
2172
  console.error("No files were written. Use --dry-run and migrate existing files deliberately.");
2077
2173
  return 1;
2078
2174
  }
2079
- for (const file of files) {
2080
- const targetPath = join14(root, file.targetPath);
2081
- mkdirSync5(dirname7(targetPath), { recursive: true });
2082
- const source = readFileSync11(file.absoluteSourcePath);
2083
- const content = file.targetPath === "AGENTS.md" ? renderAgentsTemplate(source.toString("utf8"), basename4(root), profile) : source;
2084
- writeFileSync4(targetPath, content);
2175
+ for (const file of files) {
2176
+ const targetPath = join14(root, file.targetPath);
2177
+ mkdirSync5(dirname7(targetPath), { recursive: true });
2178
+ const source = readFileSync11(file.absoluteSourcePath);
2179
+ const content = file.targetPath === "AGENTS.md" ? renderAgentsTemplate(source.toString("utf8"), basename4(root), profile) : source;
2180
+ writeFileSync4(targetPath, content);
2181
+ }
2182
+ console.log("pro-gov init APPLIED");
2183
+ console.log(`profile: ${profile}`);
2184
+ console.log(`created-files: ${files.length}`);
2185
+ console.log("Existing project files were not overwritten.");
2186
+ console.log("Next: customize project-local policy/current-work, run doc-gov scan, then run doc-gov doctor.");
2187
+ return 0;
2188
+ }
2189
+ function renderAgentsTemplate(template, projectName, profile) {
2190
+ const selectedRoute = `docs/governance/agents-routing/${profile}-v0.9.md`;
2191
+ return template.replace("# PROJECT_NAME AI Router", `# ${projectName} AI Router`).replace(
2192
+ /6\. The selected agents routing file:\n - `docs\/governance\/agents-routing\/engineering-runtime-v0\.9\.md`, or\n - `docs\/governance\/agents-routing\/doc-only-v0\.9\.md`/,
2193
+ `6. The selected agents routing file: \`${selectedRoute}\``
2194
+ ).replace(
2195
+ "- Name this project's adopted profile: `engineering-runtime` or `doc-only`.",
2196
+ `- This project adopts the \`${profile}\` profile.`
2197
+ );
2198
+ }
2199
+ function readFlag(args, flag) {
2200
+ const index = args.indexOf(flag);
2201
+ if (index === -1) return null;
2202
+ const value = args[index + 1];
2203
+ if (!value || value.startsWith("--")) return null;
2204
+ return value;
2205
+ }
2206
+
2207
+ // src/learning/recall.ts
2208
+ import { existsSync as existsSync15, readdirSync as readdirSync6, readFileSync as readFileSync12 } from "node:fs";
2209
+ import { basename as basename5, join as join15, relative as relative5 } from "node:path";
2210
+ function recallLearnings(root, options) {
2211
+ const query = options.query.trim();
2212
+ const terms = tokenize(query);
2213
+ const limit = Math.max(1, options.limit ?? 5);
2214
+ if (terms.length === 0) {
2215
+ return { query, hits: [] };
2216
+ }
2217
+ const records = loadLearningRecords(root);
2218
+ const hits = records.map((record) => {
2219
+ const score = scoreRecord(record, terms);
2220
+ return {
2221
+ relativePath: record.relativePath,
2222
+ title: record.title,
2223
+ score,
2224
+ summary: summarizeRecord(record, terms)
2225
+ };
2226
+ }).filter((hit) => hit.score > 0).sort((a, b) => b.score - a.score || a.relativePath.localeCompare(b.relativePath)).slice(0, limit);
2227
+ return { query, hits };
2228
+ }
2229
+ function loadLearningRecords(root) {
2230
+ const records = [];
2231
+ const solutionsDir = join15(root, "docs/solutions");
2232
+ if (existsSync15(solutionsDir)) {
2233
+ for (const path of listMarkdownFiles(solutionsDir)) {
2234
+ records.push(readLearningRecord(root, path));
2235
+ }
2236
+ }
2237
+ const conceptsPath = join15(root, "CONCEPTS.md");
2238
+ if (existsSync15(conceptsPath)) {
2239
+ records.push(readLearningRecord(root, conceptsPath));
2240
+ }
2241
+ return records;
2242
+ }
2243
+ function listMarkdownFiles(dir) {
2244
+ const files = [];
2245
+ for (const entry of readdirSync6(dir, { withFileTypes: true })) {
2246
+ const absolutePath = join15(dir, entry.name);
2247
+ if (entry.isDirectory()) {
2248
+ files.push(...listMarkdownFiles(absolutePath));
2249
+ } else if (entry.isFile() && entry.name.endsWith(".md")) {
2250
+ files.push(absolutePath);
2251
+ }
2252
+ }
2253
+ return files.sort();
2254
+ }
2255
+ function readLearningRecord(root, absolutePath) {
2256
+ const content = readFileSync12(absolutePath, "utf8");
2257
+ const parsed = splitFrontmatter(content);
2258
+ const body = parsed.body;
2259
+ return {
2260
+ relativePath: normalizePath(relative5(root, absolutePath)),
2261
+ title: findTitle(parsed.frontmatter, body) ?? titleFromPath(absolutePath),
2262
+ metadata: parsed.frontmatter,
2263
+ body
2264
+ };
2265
+ }
2266
+ function splitFrontmatter(content) {
2267
+ if (!content.startsWith("---\n")) {
2268
+ return { frontmatter: "", body: content };
2269
+ }
2270
+ const end = content.indexOf("\n---", 4);
2271
+ if (end === -1) {
2272
+ return { frontmatter: "", body: content };
2273
+ }
2274
+ return {
2275
+ frontmatter: content.slice(4, end).trim(),
2276
+ body: content.slice(end + "\n---".length).trim()
2277
+ };
2278
+ }
2279
+ function findTitle(frontmatter, body) {
2280
+ const titleLine = frontmatter.split(/\r?\n/).find((line) => line.trim().toLowerCase().startsWith("title:"));
2281
+ if (titleLine) {
2282
+ return titleLine.slice(titleLine.indexOf(":") + 1).trim().replace(/^["']|["']$/g, "");
2283
+ }
2284
+ const heading = body.split(/\r?\n/).find((line) => line.startsWith("# "));
2285
+ return heading ? heading.slice(2).trim() : void 0;
2286
+ }
2287
+ function titleFromPath(path) {
2288
+ return basename5(path, ".md").split(/[-_]/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
2289
+ }
2290
+ function scoreRecord(record, terms) {
2291
+ const title = record.title.toLowerCase();
2292
+ const metadata = record.metadata.toLowerCase();
2293
+ const path = record.relativePath.toLowerCase();
2294
+ const body = record.body.toLowerCase();
2295
+ let score = 0;
2296
+ for (const term of terms) {
2297
+ if (title.includes(term)) score += 6;
2298
+ if (metadata.includes(term)) score += 4;
2299
+ if (path.includes(term)) score += 2;
2300
+ if (body.includes(term)) score += 1;
2301
+ }
2302
+ return score;
2303
+ }
2304
+ function summarizeRecord(record, terms) {
2305
+ const lines = record.body.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0);
2306
+ const best = lines.map((line, index) => ({
2307
+ line,
2308
+ index,
2309
+ score: countTermMatches(line, terms) + (line.startsWith("#") ? 0.5 : 0)
2310
+ })).filter((candidate) => candidate.score > 0).sort((a, b) => b.score - a.score || a.index - b.index)[0];
2311
+ if (!best) {
2312
+ return truncate(cleanMarkdownLine(lines[0] ?? record.title), 180);
2313
+ }
2314
+ const cleaned = cleanMarkdownLine(best.line);
2315
+ if (!best.line.startsWith("#")) {
2316
+ return truncate(cleaned, 180);
2317
+ }
2318
+ const nextLine = lines.slice(best.index + 1).find((line) => !line.startsWith("#"));
2319
+ return truncate([cleaned, nextLine].filter(Boolean).join(" - "), 180);
2320
+ }
2321
+ function tokenize(input) {
2322
+ return [...new Set(input.toLowerCase().match(/[a-z0-9\u4e00-\u9fff]+/g) ?? [])].filter((term) => term.length > 1);
2323
+ }
2324
+ function truncate(input, maxLength) {
2325
+ return input.length <= maxLength ? input : `${input.slice(0, maxLength - 1)}\u2026`;
2326
+ }
2327
+ function normalizePath(path) {
2328
+ return path.split("\\").join("/");
2329
+ }
2330
+ function countTermMatches(input, terms) {
2331
+ const lower = input.toLowerCase();
2332
+ return terms.filter((term) => lower.includes(term)).length;
2333
+ }
2334
+ function cleanMarkdownLine(input) {
2335
+ return input.replace(/^#+\s*/, "").trim();
2336
+ }
2337
+
2338
+ // src/commands/learn.ts
2339
+ function runLearn(args) {
2340
+ const [subcommand2, ...rest] = args;
2341
+ if (subcommand2 === "recall") return runLearnRecall(rest);
2342
+ printUsage2();
2343
+ return 1;
2344
+ }
2345
+ function runLearnRecall(args) {
2346
+ const options = parseRecallOptions(args);
2347
+ if (!options.ok) {
2348
+ console.error(options.error);
2349
+ printUsage2();
2350
+ return 1;
2351
+ }
2352
+ const result = recallLearnings(options.value.targetDir, {
2353
+ query: options.value.query,
2354
+ limit: options.value.limit
2355
+ });
2356
+ if (options.value.json) {
2357
+ console.log(JSON.stringify(result, null, 2));
2358
+ return 0;
2359
+ }
2360
+ console.log(`Learning Recall: ${result.hits.length} hit${result.hits.length === 1 ? "" : "s"}`);
2361
+ for (const hit of result.hits) {
2362
+ console.log(`${hit.relativePath} ${hit.score} ${hit.title}`);
2363
+ if (hit.summary) console.log(` ${hit.summary}`);
2364
+ }
2365
+ if (result.hits.length === 0) {
2366
+ console.log("No relevant learning records found.");
2085
2367
  }
2086
- console.log("pro-gov init APPLIED");
2087
- console.log(`profile: ${profile}`);
2088
- console.log(`created-files: ${files.length}`);
2089
- console.log("Existing project files were not overwritten.");
2090
- console.log("Next: customize project-local policy/current-work, run doc-gov scan, then run doc-gov doctor.");
2091
2368
  return 0;
2092
2369
  }
2093
- function renderAgentsTemplate(template, projectName, profile) {
2094
- const selectedRoute = `docs/governance/agents-routing/${profile}-v0.9.md`;
2095
- return template.replace("# PROJECT_NAME AI Router", `# ${projectName} AI Router`).replace(
2096
- /6\. The selected agents routing file:\n - `docs\/governance\/agents-routing\/engineering-runtime-v0\.9\.md`, or\n - `docs\/governance\/agents-routing\/doc-only-v0\.9\.md`/,
2097
- `6. The selected agents routing file: \`${selectedRoute}\``
2098
- ).replace(
2099
- "- Name this project's adopted profile: `engineering-runtime` or `doc-only`.",
2100
- `- This project adopts the \`${profile}\` profile.`
2101
- );
2370
+ function parseRecallOptions(args) {
2371
+ let targetDir = process.cwd();
2372
+ let query = "";
2373
+ let limit = 5;
2374
+ let json = false;
2375
+ const positional = [];
2376
+ for (let index = 0; index < args.length; index += 1) {
2377
+ const arg = args[index];
2378
+ if (arg === "--target") {
2379
+ const value = readFlagValue(args, index, "--target");
2380
+ if (!value.ok) return value;
2381
+ targetDir = value.value;
2382
+ index += 1;
2383
+ } else if (arg === "--query") {
2384
+ const value = readFlagValue(args, index, "--query");
2385
+ if (!value.ok) return value;
2386
+ query = value.value;
2387
+ index += 1;
2388
+ } else if (arg === "--limit") {
2389
+ const parsed = readFlagValue(args, index, "--limit");
2390
+ if (!parsed.ok) return parsed;
2391
+ const value = Number.parseInt(parsed.value, 10);
2392
+ if (!Number.isFinite(value) || value < 1) return { ok: false, error: "--limit must be a positive integer" };
2393
+ limit = value;
2394
+ index += 1;
2395
+ } else if (arg === "--json") {
2396
+ json = true;
2397
+ } else if (arg === "--help" || arg === "-h") {
2398
+ return { ok: false, error: "Usage requested" };
2399
+ } else if (arg.startsWith("--")) {
2400
+ return { ok: false, error: `Unknown option: ${arg}` };
2401
+ } else {
2402
+ positional.push(arg);
2403
+ }
2404
+ }
2405
+ if (!query && positional.length > 0) {
2406
+ query = positional.join(" ");
2407
+ }
2408
+ if (!query.trim()) {
2409
+ return { ok: false, error: "Missing required --query <text>" };
2410
+ }
2411
+ return { ok: true, value: { targetDir, query, limit, json } };
2102
2412
  }
2103
- function readFlag(args, flag) {
2104
- const index = args.indexOf(flag);
2105
- if (index === -1) return null;
2413
+ function readFlagValue(args, index, flag) {
2106
2414
  const value = args[index + 1];
2107
- if (!value || value.startsWith("--")) return null;
2108
- return value;
2415
+ if (!value || value.startsWith("--")) {
2416
+ return { ok: false, error: `Missing value for ${flag}` };
2417
+ }
2418
+ return { ok: true, value };
2419
+ }
2420
+ function printUsage2() {
2421
+ console.error("Usage: pro-gov learn recall --query <text> [--target <path>] [--limit <n>] [--json]");
2109
2422
  }
2110
2423
 
2111
2424
  // src/commands/lens.ts
@@ -2113,8 +2426,8 @@ import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync6 } from "node:f
2113
2426
  import { dirname as dirname9 } from "node:path";
2114
2427
 
2115
2428
  // src/lens/audit.ts
2116
- import { existsSync as existsSync15, mkdirSync as mkdirSync6, readFileSync as readFileSync12, writeFileSync as writeFileSync5 } from "node:fs";
2117
- import { basename as basename5, dirname as dirname8, join as join15 } from "node:path";
2429
+ import { existsSync as existsSync16, mkdirSync as mkdirSync6, readFileSync as readFileSync13, writeFileSync as writeFileSync5 } from "node:fs";
2430
+ import { basename as basename6, dirname as dirname8, join as join16 } from "node:path";
2118
2431
  var REQUIRED_ARTIFACTS = [
2119
2432
  "manifest.md",
2120
2433
  "raw/project-lens/architecture-lens.md",
@@ -2134,20 +2447,20 @@ function createProjectLensAuditPackage(targetDir, auditDir) {
2134
2447
  version: 1,
2135
2448
  target: {
2136
2449
  path: targetDir,
2137
- name: basename5(targetDir) || "target"
2450
+ name: basename6(targetDir) || "target"
2138
2451
  },
2139
2452
  requiredArtifacts: [...REQUIRED_ARTIFACTS]
2140
2453
  };
2141
2454
  mkdirSync6(auditDir, { recursive: true });
2142
- writeJson(join15(auditDir, "audit.contract.json"), contract);
2455
+ writeJson(join16(auditDir, "audit.contract.json"), contract);
2143
2456
  for (const artifactPath of REQUIRED_ARTIFACTS) {
2144
- writeTemplate(join15(auditDir, artifactPath), renderArtifactTemplate(artifactPath, contract));
2457
+ writeTemplate(join16(auditDir, artifactPath), renderArtifactTemplate(artifactPath, contract));
2145
2458
  }
2146
2459
  return contract;
2147
2460
  }
2148
2461
  function checkProjectLensAuditPackage(auditDir, options = {}) {
2149
- const contractPath = join15(auditDir, "audit.contract.json");
2150
- if (!existsSync15(contractPath)) {
2462
+ const contractPath = join16(auditDir, "audit.contract.json");
2463
+ if (!existsSync16(contractPath)) {
2151
2464
  return {
2152
2465
  ok: false,
2153
2466
  auditDir,
@@ -2161,7 +2474,7 @@ function checkProjectLensAuditPackage(auditDir, options = {}) {
2161
2474
  }
2162
2475
  let contract;
2163
2476
  try {
2164
- contract = JSON.parse(readFileSync12(contractPath, "utf8"));
2477
+ contract = JSON.parse(readFileSync13(contractPath, "utf8"));
2165
2478
  } catch (error) {
2166
2479
  return {
2167
2480
  ok: false,
@@ -2194,12 +2507,12 @@ function checkProjectLensAuditPackage(auditDir, options = {}) {
2194
2507
  }
2195
2508
  }
2196
2509
  for (const artifactPath of REQUIRED_ARTIFACTS) {
2197
- const absolutePath = join15(auditDir, artifactPath);
2198
- if (!existsSync15(absolutePath)) {
2510
+ const absolutePath = join16(auditDir, artifactPath);
2511
+ if (!existsSync16(absolutePath)) {
2199
2512
  issues.push({ type: "missing-required-artifact", path: artifactPath });
2200
2513
  continue;
2201
2514
  }
2202
- const content = readFileSync12(absolutePath, "utf8");
2515
+ const content = readFileSync13(absolutePath, "utf8");
2203
2516
  if (isPendingArtifact(content)) {
2204
2517
  issues.push({ type: "artifact-not-complete", path: artifactPath });
2205
2518
  } else if (hasTemplateBody(content)) {
@@ -2459,8 +2772,8 @@ function bulletList(values) {
2459
2772
 
2460
2773
  // src/lens/scan.ts
2461
2774
  import { spawnSync as spawnSync4 } from "node:child_process";
2462
- import { existsSync as existsSync16, readdirSync as readdirSync6, readFileSync as readFileSync13, statSync as statSync4 } from "node:fs";
2463
- import { join as join16, relative as relative5 } from "node:path";
2775
+ import { existsSync as existsSync17, readdirSync as readdirSync7, readFileSync as readFileSync14, statSync as statSync4 } from "node:fs";
2776
+ import { join as join17, relative as relative6 } from "node:path";
2464
2777
  var ignoredDirectories = /* @__PURE__ */ new Set([
2465
2778
  ".git",
2466
2779
  ".next",
@@ -2477,24 +2790,24 @@ function scanProjectLensTarget(targetDir, options = {}) {
2477
2790
  return {
2478
2791
  targetDir,
2479
2792
  aiEntryFiles: ["AGENTS.md", "CLAUDE.md"].filter(
2480
- (file) => existsSync16(join16(targetDir, file))
2793
+ (file) => existsSync17(join17(targetDir, file))
2481
2794
  ),
2482
2795
  aiConfigFiles: [],
2483
2796
  packageJson,
2484
2797
  docs: {
2485
- hasDocsDirectory: existsSync16(join16(targetDir, "docs")),
2798
+ hasDocsDirectory: existsSync17(join17(targetDir, "docs")),
2486
2799
  markdownFileCount: markdownFiles.length,
2487
2800
  governanceFiles: markdownFiles.filter((file) => file.startsWith("docs/governance/") || file.startsWith("docs/policy/")).sort()
2488
2801
  },
2489
2802
  git: readGitState(targetDir),
2490
- largeFiles: files.map((file) => ({ path: file, bytes: statSync4(join16(targetDir, file)).size })).filter((file) => file.bytes >= largeFileBytes).sort((a, b) => b.bytes - a.bytes || a.path.localeCompare(b.path)).slice(0, 25)
2803
+ largeFiles: files.map((file) => ({ path: file, bytes: statSync4(join17(targetDir, file)).size })).filter((file) => file.bytes >= largeFileBytes).sort((a, b) => b.bytes - a.bytes || a.path.localeCompare(b.path)).slice(0, 25)
2491
2804
  };
2492
2805
  }
2493
2806
  function readPackageJson(targetDir) {
2494
- const packageJsonPath = join16(targetDir, "package.json");
2495
- if (!existsSync16(packageJsonPath)) return void 0;
2807
+ const packageJsonPath = join17(targetDir, "package.json");
2808
+ if (!existsSync17(packageJsonPath)) return void 0;
2496
2809
  try {
2497
- const packageJson = JSON.parse(readFileSync13(packageJsonPath, "utf8"));
2810
+ const packageJson = JSON.parse(readFileSync14(packageJsonPath, "utf8"));
2498
2811
  return {
2499
2812
  scripts: Object.keys(packageJson.scripts ?? {}).sort(),
2500
2813
  dependencies: Object.keys(packageJson.dependencies ?? {}).sort(),
@@ -2529,13 +2842,13 @@ function listProjectFiles(targetDir) {
2529
2842
  return files.sort();
2530
2843
  }
2531
2844
  function collectFiles2(rootDir, currentDir, files) {
2532
- if (!existsSync16(currentDir)) return;
2533
- for (const entry of readdirSync6(currentDir, { withFileTypes: true })) {
2845
+ if (!existsSync17(currentDir)) return;
2846
+ for (const entry of readdirSync7(currentDir, { withFileTypes: true })) {
2534
2847
  if (entry.isDirectory()) {
2535
2848
  if (ignoredDirectories.has(entry.name)) continue;
2536
- collectFiles2(rootDir, join16(currentDir, entry.name), files);
2849
+ collectFiles2(rootDir, join17(currentDir, entry.name), files);
2537
2850
  } else if (entry.isFile()) {
2538
- files.push(toUnixPath4(relative5(rootDir, join16(currentDir, entry.name))));
2851
+ files.push(toUnixPath4(relative6(rootDir, join17(currentDir, entry.name))));
2539
2852
  }
2540
2853
  }
2541
2854
  }
@@ -2555,14 +2868,14 @@ function runLens(args) {
2555
2868
  if (subcommand2 === "audit") {
2556
2869
  return runLensAudit(rest);
2557
2870
  }
2558
- printUsage2();
2871
+ printUsage3();
2559
2872
  return 1;
2560
2873
  }
2561
2874
  function runLensInspect(args, subcommand2) {
2562
2875
  const options = parseLensOptions(args, subcommand2);
2563
2876
  if (!options.ok) {
2564
2877
  console.error(options.error);
2565
- printUsage2();
2878
+ printUsage3();
2566
2879
  return 1;
2567
2880
  }
2568
2881
  const report = scanProjectLensTarget(options.value.targetDir);
@@ -2577,12 +2890,12 @@ function runLensReport(args) {
2577
2890
  const options = parseLensOptions(args, "report");
2578
2891
  if (!options.ok) {
2579
2892
  console.error(options.error);
2580
- printUsage2();
2893
+ printUsage3();
2581
2894
  return 1;
2582
2895
  }
2583
2896
  if (!options.value.outPath) {
2584
2897
  console.error("Expected --out <path>");
2585
- printUsage2();
2898
+ printUsage3();
2586
2899
  return 1;
2587
2900
  }
2588
2901
  const report = scanProjectLensTarget(options.value.targetDir);
@@ -2598,12 +2911,12 @@ function runLensAudit(args) {
2598
2911
  const options = parseLensOptions(rest, "audit init");
2599
2912
  if (!options.ok) {
2600
2913
  console.error(options.error);
2601
- printUsage2();
2914
+ printUsage3();
2602
2915
  return 1;
2603
2916
  }
2604
2917
  if (!options.value.outPath) {
2605
2918
  console.error("Expected --out <path>");
2606
- printUsage2();
2919
+ printUsage3();
2607
2920
  return 1;
2608
2921
  }
2609
2922
  createProjectLensAuditPackage(options.value.targetDir, options.value.outPath);
@@ -2614,12 +2927,12 @@ function runLensAudit(args) {
2614
2927
  const options = parseLensOptions(rest, "audit check");
2615
2928
  if (!options.ok) {
2616
2929
  console.error(options.error);
2617
- printUsage2();
2930
+ printUsage3();
2618
2931
  return 1;
2619
2932
  }
2620
2933
  if (!options.value.auditDir) {
2621
2934
  console.error("Expected --dir <path>");
2622
- printUsage2();
2935
+ printUsage3();
2623
2936
  return 1;
2624
2937
  }
2625
2938
  const result = checkProjectLensAuditPackage(options.value.auditDir, { mode: options.value.auditMode });
@@ -2630,7 +2943,7 @@ function runLensAudit(args) {
2630
2943
  }
2631
2944
  return result.ok ? 0 : 1;
2632
2945
  }
2633
- printUsage2();
2946
+ printUsage3();
2634
2947
  return 1;
2635
2948
  }
2636
2949
  function parseLensOptions(args, subcommand2) {
@@ -2682,7 +2995,7 @@ function parseLensOptions(args, subcommand2) {
2682
2995
  }
2683
2996
  return { ok: true, value: options };
2684
2997
  }
2685
- function printUsage2() {
2998
+ function printUsage3() {
2686
2999
  console.error("Usage: pro-gov lens scan [--target <path>] [--json]");
2687
3000
  console.error("Usage: pro-gov lens inspect [--target <path>] [--format text|json]");
2688
3001
  console.error("Usage: pro-gov lens report --target <path> --out <path>");
@@ -2691,16 +3004,16 @@ function printUsage2() {
2691
3004
  }
2692
3005
 
2693
3006
  // src/commands/portfolio.ts
2694
- import { existsSync as existsSync18 } from "node:fs";
2695
- import { join as join17 } from "node:path";
3007
+ import { existsSync as existsSync21 } from "node:fs";
3008
+ import { join as join20 } from "node:path";
2696
3009
 
2697
3010
  // src/portfolio/manifest.ts
2698
- import { existsSync as existsSync17, readFileSync as readFileSync14 } from "node:fs";
2699
- import { dirname as dirname10, isAbsolute as isAbsolute3, resolve as resolve3 } from "node:path";
3011
+ import { existsSync as existsSync18, readFileSync as readFileSync15 } from "node:fs";
3012
+ import { dirname as dirname10, isAbsolute as isAbsolute3, resolve as resolve4 } from "node:path";
2700
3013
  function loadPortfolioManifest(configPath) {
2701
3014
  let parsed;
2702
3015
  try {
2703
- parsed = JSON.parse(readFileSync14(configPath, "utf8"));
3016
+ parsed = JSON.parse(readFileSync15(configPath, "utf8"));
2704
3017
  } catch (error) {
2705
3018
  return {
2706
3019
  configPath,
@@ -2712,7 +3025,7 @@ function loadPortfolioManifest(configPath) {
2712
3025
  ]
2713
3026
  };
2714
3027
  }
2715
- const normalized = resolveManifestPaths(parsed, dirname10(resolve3(configPath)));
3028
+ const normalized = resolveManifestPaths(parsed, dirname10(resolve4(configPath)));
2716
3029
  const issues = validatePortfolioManifest(normalized);
2717
3030
  return {
2718
3031
  configPath,
@@ -2726,7 +3039,7 @@ function resolveManifestPaths(value, configDir) {
2726
3039
  if (!isRecord2(endpoint) || typeof endpoint.path !== "string" || isAbsolute3(endpoint.path)) {
2727
3040
  return endpoint;
2728
3041
  }
2729
- return { ...endpoint, path: resolve3(configDir, endpoint.path) };
3042
+ return { ...endpoint, path: resolve4(configDir, endpoint.path) };
2730
3043
  };
2731
3044
  return {
2732
3045
  ...value,
@@ -2760,9 +3073,15 @@ function validatePortfolioManifest(value) {
2760
3073
  message: "Portfolio manifest portfolioId must be a non-empty string."
2761
3074
  });
2762
3075
  }
2763
- validateAllowedFields(value, "root", ["schemaVersion", "portfolioId", "controlPlane", "executionEngine", "targets"], issues);
3076
+ validateAllowedFields(
3077
+ value,
3078
+ "root",
3079
+ ["schemaVersion", "portfolioId", "controlPlane", "executionEngine", "hostTooling", "targets"],
3080
+ issues
3081
+ );
2764
3082
  validateEndpoint(value.controlPlane, "controlPlane", issues);
2765
3083
  validateEndpoint(value.executionEngine, "executionEngine", issues);
3084
+ validateHostTooling(value.hostTooling, issues);
2766
3085
  if (!Array.isArray(value.targets)) {
2767
3086
  issues.push({
2768
3087
  type: "invalid-field",
@@ -2854,7 +3173,7 @@ function validateEndpoint(value, field, issues) {
2854
3173
  });
2855
3174
  return;
2856
3175
  }
2857
- if (!existsSync17(value.path)) {
3176
+ if (!existsSync18(value.path)) {
2858
3177
  issues.push({
2859
3178
  type: "missing-path",
2860
3179
  id: typeof value.id === "string" ? value.id : void 0,
@@ -2874,24 +3193,474 @@ function validateOptionalStringArray(value, id, field, issues) {
2874
3193
  });
2875
3194
  }
2876
3195
  }
3196
+ function validateHostTooling(value, issues) {
3197
+ if (value === void 0) return;
3198
+ if (!Array.isArray(value)) {
3199
+ issues.push({
3200
+ type: "invalid-field",
3201
+ field: "hostTooling",
3202
+ message: "Portfolio hostTooling must be an array."
3203
+ });
3204
+ return;
3205
+ }
3206
+ const seenHosts = /* @__PURE__ */ new Set();
3207
+ for (const entry of value) {
3208
+ if (!isRecord2(entry)) {
3209
+ issues.push({
3210
+ type: "invalid-field",
3211
+ field: "hostTooling",
3212
+ message: "Portfolio hostTooling entry must be an object."
3213
+ });
3214
+ continue;
3215
+ }
3216
+ validateAllowedFields(entry, "hostTooling", ["host", "plugins"], issues);
3217
+ if (entry.host !== "codex" && entry.host !== "claude-code") {
3218
+ issues.push({
3219
+ type: "invalid-field",
3220
+ field: "hostTooling.host",
3221
+ message: "Portfolio hostTooling host must be codex or claude-code."
3222
+ });
3223
+ } else if (seenHosts.has(entry.host)) {
3224
+ issues.push({
3225
+ type: "invalid-field",
3226
+ field: "hostTooling",
3227
+ message: `Duplicate portfolio hostTooling entry: ${entry.host}`
3228
+ });
3229
+ } else {
3230
+ seenHosts.add(entry.host);
3231
+ }
3232
+ if (!Array.isArray(entry.plugins) || !entry.plugins.every((plugin) => typeof plugin === "string" && plugin.length > 0)) {
3233
+ issues.push({
3234
+ type: "invalid-field",
3235
+ field: "hostTooling.plugins",
3236
+ message: "Portfolio hostTooling plugins must be an array of non-empty strings."
3237
+ });
3238
+ }
3239
+ }
3240
+ }
2877
3241
  function isRecord2(value) {
2878
3242
  return typeof value === "object" && value !== null && !Array.isArray(value);
2879
3243
  }
2880
3244
 
3245
+ // src/portfolio/doctor.ts
3246
+ import { spawnSync as spawnSync6 } from "node:child_process";
3247
+ import { existsSync as existsSync20, readFileSync as readFileSync17 } from "node:fs";
3248
+ import { createRequire as createRequire2 } from "node:module";
3249
+ import { dirname as dirname11, join as join19 } from "node:path";
3250
+ import { fileURLToPath as fileURLToPath3 } from "node:url";
3251
+
3252
+ // src/host-tooling/inventory.ts
3253
+ import { spawnSync as spawnSync5 } from "node:child_process";
3254
+ function inspectHostTooling(requirements, runner = defaultRunner2) {
3255
+ const hosts = [];
3256
+ const issues = [];
3257
+ for (const requirement of requirements) {
3258
+ const command2 = requirement.host === "codex" ? ["codex", "plugin", "list", "--json"] : ["claude", "plugin", "list", "--json"];
3259
+ const result = runner({ host: requirement.host, command: command2 });
3260
+ if (result.status !== 0) {
3261
+ issues.push({
3262
+ type: "host-tooling-command-failed",
3263
+ host: requirement.host,
3264
+ message: `Unable to inspect ${requirement.host} plugins: ${result.stderr || `exit ${result.status}`}`
3265
+ });
3266
+ hosts.push({ host: requirement.host, plugins: [] });
3267
+ continue;
3268
+ }
3269
+ let plugins;
3270
+ try {
3271
+ plugins = parseHostPlugins(requirement.host, JSON.parse(result.stdout));
3272
+ } catch (error) {
3273
+ issues.push({
3274
+ type: "host-tooling-command-failed",
3275
+ host: requirement.host,
3276
+ message: `Unable to parse ${requirement.host} plugin inventory: ${error instanceof Error ? error.message : String(error)}`
3277
+ });
3278
+ hosts.push({ host: requirement.host, plugins: [] });
3279
+ continue;
3280
+ }
3281
+ const byId = new Map(plugins.map((plugin) => [plugin.id, plugin]));
3282
+ hosts.push({
3283
+ host: requirement.host,
3284
+ plugins: requirement.plugins.flatMap((pluginId) => {
3285
+ const plugin = byId.get(pluginId);
3286
+ return plugin ? [plugin] : [];
3287
+ })
3288
+ });
3289
+ for (const pluginId of requirement.plugins) {
3290
+ const plugin = byId.get(pluginId);
3291
+ if (!plugin) {
3292
+ issues.push({
3293
+ type: "host-tooling-missing",
3294
+ host: requirement.host,
3295
+ pluginId,
3296
+ message: `Required ${requirement.host} plugin is missing: ${pluginId}`
3297
+ });
3298
+ } else if (!plugin.enabled) {
3299
+ issues.push({
3300
+ type: "host-tooling-disabled",
3301
+ host: requirement.host,
3302
+ pluginId,
3303
+ message: `Required ${requirement.host} plugin is disabled: ${pluginId}`
3304
+ });
3305
+ }
3306
+ }
3307
+ }
3308
+ return { hosts, issues };
3309
+ }
3310
+ function parseHostPlugins(host, value) {
3311
+ const entries = host === "codex" ? isRecord3(value) && Array.isArray(value.installed) ? value.installed : void 0 : Array.isArray(value) ? value : void 0;
3312
+ if (!entries) throw new Error("expected a plugin array");
3313
+ return entries.flatMap((entry) => {
3314
+ if (!isRecord3(entry)) return [];
3315
+ const id = host === "codex" ? entry.pluginId : entry.id;
3316
+ if (typeof id !== "string") return [];
3317
+ return [{
3318
+ id,
3319
+ version: typeof entry.version === "string" ? entry.version : void 0,
3320
+ enabled: entry.enabled === true
3321
+ }];
3322
+ });
3323
+ }
3324
+ function defaultRunner2({ command: command2 }) {
3325
+ const result = spawnSync5(command2[0] ?? "", command2.slice(1), {
3326
+ encoding: "utf8",
3327
+ timeout: 1e4
3328
+ });
3329
+ return {
3330
+ status: result.status,
3331
+ stdout: result.stdout,
3332
+ stderr: result.stderr || result.error?.message || ""
3333
+ };
3334
+ }
3335
+ function isRecord3(value) {
3336
+ return typeof value === "object" && value !== null && !Array.isArray(value);
3337
+ }
3338
+
3339
+ // src/portfolio/asset-state.ts
3340
+ import { existsSync as existsSync19, lstatSync as lstatSync5, readFileSync as readFileSync16 } from "node:fs";
3341
+ import { join as join18 } from "node:path";
3342
+ function comparePortfolioAssetState(options) {
3343
+ const expectedManifest = readPlanDocument(options.expectedPlan, ".pro-gov/assets.json");
3344
+ const expectedLock = readPlanDocument(options.expectedPlan, ".pro-gov/assets.lock.json");
3345
+ const currentManifest = readJsonFile(join18(options.targetDir, ".pro-gov/assets.json"));
3346
+ const currentLock = readJsonFile(join18(options.targetDir, ".pro-gov/assets.lock.json"));
3347
+ const issues = [];
3348
+ if (!sameStrings(currentManifest?.bundleIds, expectedManifest?.bundleIds)) {
3349
+ issues.push({
3350
+ type: "bundle-drift",
3351
+ message: "Target asset bundles do not match the portfolio manifest."
3352
+ });
3353
+ }
3354
+ if (!sameStrings(currentManifest?.assetIds, expectedManifest?.assetIds)) {
3355
+ issues.push({
3356
+ type: "asset-set-drift",
3357
+ message: "Target asset ids do not match the current bundle definitions."
3358
+ });
3359
+ }
3360
+ if (!sameLock(currentLock, expectedLock)) {
3361
+ issues.push({
3362
+ type: "asset-lock-drift",
3363
+ message: "Target asset lock does not match the current registry, placement, and content hashes."
3364
+ });
3365
+ }
3366
+ const expectedTargets = new Set((expectedLock?.assets ?? []).map((entry) => entry.targetPath));
3367
+ for (const entry of currentLock?.assets ?? []) {
3368
+ if (expectedTargets.has(entry.targetPath)) continue;
3369
+ const targetAbsolutePath = join18(options.targetDir, entry.targetPath);
3370
+ if (!pathIsSymlink(targetAbsolutePath)) continue;
3371
+ issues.push({
3372
+ type: "orphaned-managed-symlink",
3373
+ targetPath: entry.targetPath,
3374
+ message: `Previously managed symlink is absent from the expected bundle state: ${entry.targetPath}`
3375
+ });
3376
+ }
3377
+ return { issues };
3378
+ }
3379
+ function readPlanDocument(plan, targetPath) {
3380
+ const action = plan.actions.find(
3381
+ (candidate) => candidate.type === "write-file" && candidate.targetPath === targetPath
3382
+ );
3383
+ if (!action || action.type !== "write-file") return void 0;
3384
+ try {
3385
+ return JSON.parse(action.content);
3386
+ } catch {
3387
+ return void 0;
3388
+ }
3389
+ }
3390
+ function readJsonFile(path) {
3391
+ if (!existsSync19(path)) return void 0;
3392
+ try {
3393
+ return JSON.parse(readFileSync16(path, "utf8"));
3394
+ } catch {
3395
+ return void 0;
3396
+ }
3397
+ }
3398
+ function sameStrings(left, right) {
3399
+ return JSON.stringify([...left ?? []].sort()) === JSON.stringify([...right ?? []].sort());
3400
+ }
3401
+ function sameLock(left, right) {
3402
+ return JSON.stringify(normalizeLock(left)) === JSON.stringify(normalizeLock(right));
3403
+ }
3404
+ function normalizeLock(lock) {
3405
+ return {
3406
+ host: lock?.host,
3407
+ placement: lock?.placement,
3408
+ bundleIds: [...lock?.bundleIds ?? []].sort(),
3409
+ assets: [...lock?.assets ?? []].sort((a, b) => a.id.localeCompare(b.id))
3410
+ };
3411
+ }
3412
+ function pathIsSymlink(path) {
3413
+ try {
3414
+ return lstatSync5(path).isSymbolicLink();
3415
+ } catch {
3416
+ return false;
3417
+ }
3418
+ }
3419
+
3420
+ // src/portfolio/doctor.ts
3421
+ function inspectPortfolio(options) {
3422
+ const expectedPackageVersions = getExpectedPackageVersions();
3423
+ const hostTooling = inspectHostTooling(options.manifest.hostTooling ?? []);
3424
+ const targets = options.targets.map((target) => inspectTarget({
3425
+ target,
3426
+ agentAssetsDir: options.agentAssetsDir,
3427
+ registry: options.registry,
3428
+ bundles: options.bundles,
3429
+ expectedPackageVersions
3430
+ }));
3431
+ return {
3432
+ ok: hostTooling.issues.length === 0 && targets.every((target) => target.issues.length === 0),
3433
+ portfolioId: options.manifest.portfolioId,
3434
+ expectedPackageVersions,
3435
+ hostTooling,
3436
+ targets
3437
+ };
3438
+ }
3439
+ function inspectTarget(options) {
3440
+ const { target } = options;
3441
+ const issues = [];
3442
+ const packageJson = readJson2(join19(target.path, "package.json"));
3443
+ const packages = {};
3444
+ for (const packageName of ["@pieai/pro-gov", "@pieai/doc-gov"]) {
3445
+ const declared = packageJson?.devDependencies?.[packageName] ?? packageJson?.dependencies?.[packageName];
3446
+ const installedPackage = readJson2(join19(target.path, "node_modules", packageName, "package.json"));
3447
+ const installed = installedPackage?.version;
3448
+ const expected = options.expectedPackageVersions[packageName];
3449
+ packages[packageName] = { declared, installed, expected };
3450
+ if (!declared) {
3451
+ issues.push({
3452
+ type: "package-declaration-missing",
3453
+ packageName,
3454
+ message: `Target does not declare ${packageName}.`
3455
+ });
3456
+ }
3457
+ if (!installed || expected && installed !== expected) {
3458
+ issues.push({
3459
+ type: "package-version-drift",
3460
+ packageName,
3461
+ message: `Target ${packageName} installed version is ${installed ?? "missing"}; expected ${expected ?? "unknown"}.`
3462
+ });
3463
+ }
3464
+ }
3465
+ const checks = runTargetChecks(target);
3466
+ for (const check of checks) {
3467
+ if (check.status === 0) continue;
3468
+ issues.push({
3469
+ type: "target-check-failed",
3470
+ check: check.name,
3471
+ message: `Target check failed: ${check.name} (${check.status ?? "unavailable"}).`
3472
+ });
3473
+ }
3474
+ const assetCheck = checkInstalledAssets({
3475
+ targetDir: target.path,
3476
+ agentAssetsDir: options.agentAssetsDir,
3477
+ registry: options.registry,
3478
+ strictRegistry: true
3479
+ });
3480
+ issues.push(...assetCheck.issues.map((issue) => ({
3481
+ type: issue.type,
3482
+ targetPath: issue.targetPath,
3483
+ message: issue.message
3484
+ })));
3485
+ try {
3486
+ const expectedPlan = createAssetInstallPlan({
3487
+ targetDir: target.path,
3488
+ agentAssetsDir: options.agentAssetsDir,
3489
+ registry: options.registry,
3490
+ bundles: options.bundles,
3491
+ bundleIds: target.assetBundles ?? [],
3492
+ host: readTargetAssetHost(target.path) ?? "codex"
3493
+ });
3494
+ issues.push(...comparePortfolioAssetState({ targetDir: target.path, expectedPlan }).issues);
3495
+ } catch (error) {
3496
+ issues.push({
3497
+ type: "asset-lock-drift",
3498
+ message: error instanceof Error ? error.message : String(error)
3499
+ });
3500
+ if (!existsSync20(join19(target.path, ".pro-gov/assets.json"))) {
3501
+ issues.push({ type: "bundle-drift", message: "Target asset manifest is missing." });
3502
+ }
3503
+ }
3504
+ return {
3505
+ id: target.id,
3506
+ path: target.path,
3507
+ profile: target.profile,
3508
+ packages,
3509
+ git: inspectGit(target.path),
3510
+ checks,
3511
+ issues: deduplicateIssues(issues)
3512
+ };
3513
+ }
3514
+ function readTargetAssetHost(targetDir) {
3515
+ const lockfile = readJson2(join19(targetDir, ".pro-gov/assets.lock.json"));
3516
+ return isAssetRegistryHost(lockfile?.host) ? lockfile.host : void 0;
3517
+ }
3518
+ function isAssetRegistryHost(value) {
3519
+ return value === "codex" || value === "claude-code" || value === "gemini-cli" || value === "antigravity";
3520
+ }
3521
+ function runTargetChecks(target) {
3522
+ const proGovCli = join19(target.path, "node_modules/@pieai/pro-gov/dist/cli.js");
3523
+ const docGovCli = join19(target.path, "node_modules/@pieai/doc-gov/dist/cli.js");
3524
+ const commands = [
3525
+ {
3526
+ name: "pro-gov doctor",
3527
+ cli: proGovCli,
3528
+ args: target.profile === "engineering-runtime" ? ["doctor", "--strict-hooks"] : ["doctor"]
3529
+ },
3530
+ { name: "doc-gov router-check", cli: docGovCli, args: ["router-check"] },
3531
+ { name: "doc-gov scan --check", cli: docGovCli, args: ["scan", "--check"] }
3532
+ ];
3533
+ return commands.map((command2) => {
3534
+ if (!existsSync20(command2.cli)) return { name: command2.name, status: null };
3535
+ const result = spawnSync6(process.execPath, [command2.cli, ...command2.args], {
3536
+ cwd: target.path,
3537
+ encoding: "utf8",
3538
+ timeout: 3e4
3539
+ });
3540
+ return { name: command2.name, status: result.status };
3541
+ });
3542
+ }
3543
+ function inspectGit(path) {
3544
+ const inside = spawnSync6("git", ["rev-parse", "--is-inside-work-tree"], {
3545
+ cwd: path,
3546
+ encoding: "utf8"
3547
+ });
3548
+ if (inside.status !== 0) return { isRepository: false, dirty: false };
3549
+ const status = spawnSync6("git", ["status", "--porcelain"], { cwd: path, encoding: "utf8" });
3550
+ const branch = spawnSync6("git", ["branch", "--show-current"], { cwd: path, encoding: "utf8" });
3551
+ return {
3552
+ isRepository: true,
3553
+ dirty: status.stdout.trim().length > 0,
3554
+ branch: branch.stdout.trim() || void 0
3555
+ };
3556
+ }
3557
+ function getExpectedPackageVersions() {
3558
+ const proGovPackage = readJson2(findOwnPackageJson());
3559
+ let docGovVersion;
3560
+ try {
3561
+ const require2 = createRequire2(import.meta.url);
3562
+ const docGovPackage = readJson2(require2.resolve("@pieai/doc-gov/package.json"));
3563
+ docGovVersion = docGovPackage?.version;
3564
+ } catch {
3565
+ docGovVersion = void 0;
3566
+ }
3567
+ return {
3568
+ "@pieai/pro-gov": proGovPackage?.version,
3569
+ "@pieai/doc-gov": docGovVersion
3570
+ };
3571
+ }
3572
+ function findOwnPackageJson() {
3573
+ let current = dirname11(fileURLToPath3(import.meta.url));
3574
+ for (let depth = 0; depth < 5; depth += 1) {
3575
+ const candidate = join19(current, "package.json");
3576
+ if (existsSync20(candidate)) return candidate;
3577
+ current = dirname11(current);
3578
+ }
3579
+ return "";
3580
+ }
3581
+ function readJson2(path) {
3582
+ if (!path || !existsSync20(path)) return void 0;
3583
+ try {
3584
+ return JSON.parse(readFileSync17(path, "utf8"));
3585
+ } catch {
3586
+ return void 0;
3587
+ }
3588
+ }
3589
+ function deduplicateIssues(issues) {
3590
+ const seen = /* @__PURE__ */ new Set();
3591
+ return issues.filter((issue) => {
3592
+ const key = `${issue.type}\0${issue.packageName ?? ""}\0${issue.targetPath ?? ""}\0${issue.check ?? ""}\0${issue.message}`;
3593
+ if (seen.has(key)) return false;
3594
+ seen.add(key);
3595
+ return true;
3596
+ });
3597
+ }
3598
+
2881
3599
  // src/commands/portfolio.ts
2882
3600
  function runPortfolio(args) {
2883
3601
  const [subcommand2, ...rest] = args;
2884
3602
  if (subcommand2 === "check") return runPortfolioCheck(rest);
2885
3603
  if (subcommand2 === "plan") return runPortfolioPlan(rest);
2886
3604
  if (subcommand2 === "assets-check") return runPortfolioAssetsCheck(rest);
2887
- printUsage3();
3605
+ if (subcommand2 === "doctor") return runPortfolioDoctor(rest);
3606
+ printUsage4();
2888
3607
  return 1;
2889
3608
  }
3609
+ function runPortfolioDoctor(args) {
3610
+ const options = parsePortfolioOptions(args);
3611
+ if (!options.ok) {
3612
+ console.error(options.error);
3613
+ printUsage4();
3614
+ return 1;
3615
+ }
3616
+ const loaded = loadPortfolioManifest(options.value.configPath);
3617
+ if (loaded.issues.length > 0 || !loaded.manifest) {
3618
+ if (options.value.json) {
3619
+ console.log(JSON.stringify({ ok: false, configPath: loaded.configPath, issues: loaded.issues, targets: [] }, null, 2));
3620
+ } else {
3621
+ for (const issue of loaded.issues) console.error(`${issue.type}: ${issue.message}`);
3622
+ }
3623
+ return 1;
3624
+ }
3625
+ const targets = getDefaultPortfolioTargets(loaded.manifest).filter(
3626
+ (target) => !options.value.targetId || options.value.targetId === "all" || target.id === options.value.targetId
3627
+ );
3628
+ if (targets.length === 0) {
3629
+ console.error(`Unknown portfolio target: ${options.value.targetId}`);
3630
+ return 1;
3631
+ }
3632
+ const loadedAssets = loadAgentAssetRegistry({
3633
+ agentAssetsDir: findPortfolioAgentAssetsDir(loaded.manifest)
3634
+ });
3635
+ if (loadedAssets.issues.length > 0) {
3636
+ for (const issue of loadedAssets.issues) console.error(`${issue.type}: ${issue.message}`);
3637
+ return 1;
3638
+ }
3639
+ const result = inspectPortfolio({
3640
+ manifest: loaded.manifest,
3641
+ targets,
3642
+ agentAssetsDir: loadedAssets.agentAssetsDir,
3643
+ registry: loadedAssets.registry,
3644
+ bundles: loadAgentAssetBundles(loadedAssets.agentAssetsDir)
3645
+ });
3646
+ const output = { configPath: loaded.configPath, ...result };
3647
+ if (options.value.json) {
3648
+ console.log(JSON.stringify(output, null, 2));
3649
+ } else if (result.ok) {
3650
+ console.log(`portfolio doctor passed (${targets.length} targets)`);
3651
+ } else {
3652
+ for (const issue of result.hostTooling.issues) console.log(`${issue.host} ${issue.type}: ${issue.message}`);
3653
+ for (const target of result.targets) {
3654
+ for (const issue of target.issues) console.log(`${target.id} ${issue.type}: ${issue.message}`);
3655
+ }
3656
+ }
3657
+ return result.ok ? 0 : 1;
3658
+ }
2890
3659
  function runPortfolioCheck(args) {
2891
3660
  const options = parsePortfolioOptions(args);
2892
3661
  if (!options.ok) {
2893
3662
  console.error(options.error);
2894
- printUsage3();
3663
+ printUsage4();
2895
3664
  return 1;
2896
3665
  }
2897
3666
  const loaded = loadPortfolioManifest(options.value.configPath);
@@ -2923,7 +3692,7 @@ function runPortfolioPlan(args) {
2923
3692
  const options = parsePortfolioOptions(args);
2924
3693
  if (!options.ok) {
2925
3694
  console.error(options.error);
2926
- printUsage3();
3695
+ printUsage4();
2927
3696
  return 1;
2928
3697
  }
2929
3698
  const loaded = loadPortfolioManifest(options.value.configPath);
@@ -2997,7 +3766,7 @@ function runPortfolioAssetsCheck(args) {
2997
3766
  const options = parsePortfolioOptions(args);
2998
3767
  if (!options.ok) {
2999
3768
  console.error(options.error);
3000
- printUsage3();
3769
+ printUsage4();
3001
3770
  return 1;
3002
3771
  }
3003
3772
  const loaded = loadPortfolioManifest(options.value.configPath);
@@ -3113,7 +3882,7 @@ function parsePortfolioOptions(args) {
3113
3882
  index += 1;
3114
3883
  } else if (arg === "--host") {
3115
3884
  const host = args[index + 1];
3116
- if (!isHost2(host)) return { ok: false, error: "Expected --host codex|claude-code" };
3885
+ if (!isHost2(host)) return { ok: false, error: "Expected --host codex|claude-code|gemini-cli|antigravity" };
3117
3886
  options.host = host;
3118
3887
  index += 1;
3119
3888
  } else if (arg === "--json") {
@@ -3129,19 +3898,20 @@ function isHost2(value) {
3129
3898
  return value === "codex" || value === "claude-code" || value === "gemini-cli" || value === "antigravity";
3130
3899
  }
3131
3900
  function findPortfolioAgentAssetsDir(manifest) {
3132
- const agentAssetsDir = manifest?.executionEngine?.path ? join17(manifest.executionEngine.path, "agent-assets") : void 0;
3133
- return agentAssetsDir && existsSync18(join17(agentAssetsDir, "registry.json")) ? agentAssetsDir : void 0;
3901
+ const agentAssetsDir = manifest?.executionEngine?.path ? join20(manifest.executionEngine.path, "agent-assets") : void 0;
3902
+ return agentAssetsDir && existsSync21(join20(agentAssetsDir, "registry.json")) ? agentAssetsDir : void 0;
3134
3903
  }
3135
- function printUsage3() {
3904
+ function printUsage4() {
3136
3905
  console.error("Usage:");
3137
3906
  console.error(" pro-gov portfolio check --config <path> [--json]");
3138
3907
  console.error(" pro-gov portfolio plan --config <path> [--target <id|all>] [--host codex|claude-code|gemini-cli|antigravity] [--json]");
3139
3908
  console.error(" pro-gov portfolio assets-check --config <path> [--target <id|all>] [--json]");
3909
+ console.error(" pro-gov portfolio doctor --config <path> [--target <id|all>] [--json]");
3140
3910
  }
3141
3911
 
3142
3912
  // src/commands/sync.ts
3143
- import { existsSync as existsSync19, readFileSync as readFileSync15 } from "node:fs";
3144
- import { join as join18 } from "node:path";
3913
+ import { existsSync as existsSync22, readFileSync as readFileSync18 } from "node:fs";
3914
+ import { join as join21 } from "node:path";
3145
3915
  function runSync(args) {
3146
3916
  if (!args.includes("--check")) {
3147
3917
  console.error("pro-gov sync is read-only and requires --check.");
@@ -3168,16 +3938,16 @@ function runSync(args) {
3168
3938
  console.log("pro-gov sync check");
3169
3939
  console.log(`profile: ${profile}`);
3170
3940
  for (const file of planStarterFiles(profile)) {
3171
- const targetPath = join18(process.cwd(), file.targetPath);
3172
- if (!existsSync19(targetPath)) {
3941
+ const targetPath = join21(process.cwd(), file.targetPath);
3942
+ if (!existsSync22(targetPath)) {
3173
3943
  if (file.ownership === "optional-guardrail") continue;
3174
3944
  console.log(`missing: ${file.targetPath}`);
3175
3945
  differences += 1;
3176
3946
  continue;
3177
3947
  }
3178
3948
  if (file.ownership === "project-local-seed") continue;
3179
- const source = readFileSync15(file.absoluteSourcePath, "utf8");
3180
- const target = readFileSync15(targetPath, "utf8");
3949
+ const source = readFileSync18(file.absoluteSourcePath, "utf8");
3950
+ const target = readFileSync18(targetPath, "utf8");
3181
3951
  if (source !== target) {
3182
3952
  console.log(`different: ${file.targetPath}`);
3183
3953
  differences += 1;
@@ -3192,7 +3962,7 @@ function runSync(args) {
3192
3962
  }
3193
3963
  function inferInstalledProfile(root) {
3194
3964
  const installed = ["engineering-runtime", "doc-only"].filter(
3195
- (profile) => existsSync19(join18(root, `docs/governance/agents-routing/${profile}-v0.9.md`))
3965
+ (profile) => existsSync22(join21(root, `docs/governance/agents-routing/${profile}-v0.9.md`))
3196
3966
  );
3197
3967
  return installed.length === 1 ? installed[0] : void 0;
3198
3968
  }
@@ -3215,6 +3985,8 @@ var COMMANDS = [
3215
3985
  "portfolio check --config <path> [--json]",
3216
3986
  "portfolio plan --config <path> [--target <id|all>] [--json]",
3217
3987
  "portfolio assets-check --config <path> [--target <id|all>] [--json]",
3988
+ "portfolio doctor --config <path> [--target <id|all>] [--json]",
3989
+ "learn recall --query <text> [--target <path>] [--limit <n>] [--json]",
3218
3990
  "lens scan [--target <path>] [--json]",
3219
3991
  "lens inspect [--target <path>] [--format text|json]",
3220
3992
  "lens report --target <path> --out <path>",
@@ -3233,6 +4005,7 @@ async function main() {
3233
4005
  return command ? 0 : 1;
3234
4006
  }
3235
4007
  if (command === "assets") return runAssets(process.argv.slice(3));
4008
+ if (command === "learn") return runLearn(process.argv.slice(3));
3236
4009
  if (command === "lens") return runLens(process.argv.slice(3));
3237
4010
  if (command === "portfolio") return runPortfolio(process.argv.slice(3));
3238
4011
  if (command === "host-hook") return runHostHook(process.argv.slice(3));