@pieai/pro-gov 0.3.10 → 0.3.12
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
|
|
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), {
|
|
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 {
|
|
540
|
-
|
|
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 =
|
|
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(
|
|
794
|
+
mkdirSync2(dirname4(targetAbsolutePath), { recursive: true });
|
|
557
795
|
writeFileSync(targetAbsolutePath, action.content);
|
|
558
796
|
return;
|
|
559
797
|
}
|
|
560
|
-
mkdirSync2(
|
|
561
|
-
const sourceAbsolutePath =
|
|
562
|
-
const symlinkTarget = relative4(realpathSync(
|
|
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 (
|
|
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 (!
|
|
808
|
+
if (!pathExistsEvenIfDanglingSymlink3(targetAbsolutePath)) {
|
|
571
809
|
symlinkSync(symlinkTarget, targetAbsolutePath);
|
|
572
810
|
return;
|
|
573
811
|
}
|
|
574
|
-
const stats =
|
|
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
|
|
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
|
-
|
|
836
|
+
lstatSync3(path);
|
|
584
837
|
return true;
|
|
585
838
|
} catch {
|
|
586
|
-
return
|
|
839
|
+
return existsSync8(path);
|
|
587
840
|
}
|
|
588
841
|
}
|
|
589
842
|
|
|
590
843
|
// src/asset-targets/check.ts
|
|
591
|
-
import { existsSync as
|
|
592
|
-
import { basename, join as
|
|
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 =
|
|
595
|
-
if (!
|
|
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(
|
|
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 =
|
|
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 (!
|
|
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 =
|
|
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 (!
|
|
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 =
|
|
679
|
-
if (!
|
|
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 =
|
|
970
|
+
const skillName = basename2(asset.sourcePath);
|
|
718
971
|
const autoPath = `.agents/skills/${skillName}`;
|
|
719
972
|
const manualPath = `.agents/manual-skills/${skillName}`;
|
|
720
|
-
if (
|
|
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,
|
|
@@ -736,192 +989,35 @@ function checkHostFolder(host, kind, targetPath, id) {
|
|
|
736
989
|
type: "unsupported-host-folder",
|
|
737
990
|
id,
|
|
738
991
|
targetPath,
|
|
739
|
-
message: `Lockfile host is unsupported for managed skill target: ${host ?? "missing"}`
|
|
740
|
-
};
|
|
741
|
-
}
|
|
742
|
-
if (!expectedPrefixes.some((prefix) => targetPath.startsWith(prefix))) {
|
|
743
|
-
return {
|
|
744
|
-
type: "unsupported-host-folder",
|
|
745
|
-
id,
|
|
746
|
-
targetPath,
|
|
747
|
-
message: `Managed skill target ${targetPath} does not match host ${host}; expected ${expectedPrefixes.join(" or ")}`
|
|
748
|
-
};
|
|
749
|
-
}
|
|
750
|
-
return void 0;
|
|
751
|
-
}
|
|
752
|
-
function expectedSkillTargetPrefixes(host) {
|
|
753
|
-
if (host === "claude-code") return [".claude/skills/"];
|
|
754
|
-
if (host === "codex" || host === "gemini-cli" || host === "antigravity") {
|
|
755
|
-
return [".agents/skills/", ".agents/manual-skills/"];
|
|
756
|
-
}
|
|
757
|
-
return void 0;
|
|
758
|
-
}
|
|
759
|
-
function expectedRegistrySkillTargetPath(host, sourcePath, placement) {
|
|
760
|
-
const skillName = basename(sourcePath);
|
|
761
|
-
if (host === "claude-code") {
|
|
762
|
-
return placement === "manual" ? void 0 : `.claude/skills/${skillName}`;
|
|
763
|
-
}
|
|
764
|
-
if (host === "codex" || host === "gemini-cli" || host === "antigravity") {
|
|
765
|
-
return placement === "manual" ? `.agents/manual-skills/${skillName}` : `.agents/skills/${skillName}`;
|
|
766
|
-
}
|
|
767
|
-
return void 0;
|
|
768
|
-
}
|
|
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)}`;
|
|
992
|
+
message: `Lockfile host is unsupported for managed skill target: ${host ?? "missing"}`
|
|
993
|
+
};
|
|
897
994
|
}
|
|
898
|
-
if (
|
|
899
|
-
return
|
|
995
|
+
if (!expectedPrefixes.some((prefix) => targetPath.startsWith(prefix))) {
|
|
996
|
+
return {
|
|
997
|
+
type: "unsupported-host-folder",
|
|
998
|
+
id,
|
|
999
|
+
targetPath,
|
|
1000
|
+
message: `Managed skill target ${targetPath} does not match host ${host}; expected ${expectedPrefixes.join(" or ")}`
|
|
1001
|
+
};
|
|
900
1002
|
}
|
|
901
|
-
return
|
|
902
|
-
}
|
|
903
|
-
function resolveSkillPlacement(asset, placement) {
|
|
904
|
-
if (placement !== "registry") return placement;
|
|
905
|
-
return asset.defaultPlacement ?? "auto";
|
|
1003
|
+
return void 0;
|
|
906
1004
|
}
|
|
907
|
-
function
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
const directory = dirname4(action.targetPath);
|
|
912
|
-
if (directory !== ".") directories.add(directory);
|
|
1005
|
+
function expectedSkillTargetPrefixes(host) {
|
|
1006
|
+
if (host === "claude-code") return [".claude/skills/"];
|
|
1007
|
+
if (host === "codex" || host === "gemini-cli" || host === "antigravity") {
|
|
1008
|
+
return [".agents/skills/", ".agents/manual-skills/"];
|
|
913
1009
|
}
|
|
914
|
-
return
|
|
1010
|
+
return void 0;
|
|
915
1011
|
}
|
|
916
|
-
function
|
|
917
|
-
const
|
|
918
|
-
if (
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
return /* @__PURE__ */ new Set();
|
|
1012
|
+
function expectedRegistrySkillTargetPath(host, sourcePath, placement) {
|
|
1013
|
+
const skillName = basename2(sourcePath);
|
|
1014
|
+
if (host === "claude-code") {
|
|
1015
|
+
return placement === "manual" ? void 0 : `.claude/skills/${skillName}`;
|
|
1016
|
+
}
|
|
1017
|
+
if (host === "codex" || host === "gemini-cli" || host === "antigravity") {
|
|
1018
|
+
return placement === "manual" ? `.agents/manual-skills/${skillName}` : `.agents/skills/${skillName}`;
|
|
924
1019
|
}
|
|
1020
|
+
return void 0;
|
|
925
1021
|
}
|
|
926
1022
|
function pathExistsEvenIfDanglingSymlink4(path) {
|
|
927
1023
|
try {
|
|
@@ -1628,6 +1724,9 @@ function checkStrictHostHooks(root) {
|
|
|
1628
1724
|
if (!content.includes("pro-gov host-hook") || !content.includes(`--host ${entry.host}`)) {
|
|
1629
1725
|
issues.push(`host-hooks: ${entry.path} does not call pro-gov host-hook for ${entry.host}`);
|
|
1630
1726
|
}
|
|
1727
|
+
if (entry.host === "antigravity" && !content.includes("PGS_HOST_HOOK_DEBUG=1")) {
|
|
1728
|
+
issues.push(`host-hooks: ${entry.path} does not enable PGS_HOST_HOOK_DEBUG=1 for Antigravity diagnostics`);
|
|
1729
|
+
}
|
|
1631
1730
|
}
|
|
1632
1731
|
return issues;
|
|
1633
1732
|
}
|
|
@@ -1636,7 +1735,10 @@ function isEngineeringRuntimeProject(root) {
|
|
|
1636
1735
|
}
|
|
1637
1736
|
|
|
1638
1737
|
// src/commands/host-hook.ts
|
|
1639
|
-
import {
|
|
1738
|
+
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
1739
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync4, readFileSync as readFileSync10, writeFileSync as writeFileSync3 } from "node:fs";
|
|
1740
|
+
import { tmpdir as tmpdir2 } from "node:os";
|
|
1741
|
+
import { basename as basename3, join as join13, resolve as resolve3 } from "node:path";
|
|
1640
1742
|
|
|
1641
1743
|
// src/host-hooks/host-hook-runner.ts
|
|
1642
1744
|
import { closeSync, openSync, readFileSync as readFileSync9, readSync, statSync as statSync3 } from "node:fs";
|
|
@@ -1645,7 +1747,11 @@ var completionSignalPatterns = [
|
|
|
1645
1747
|
/\b(done|completed|implemented|fixed|verified|validated|shipped|pushed|committed)\b/i,
|
|
1646
1748
|
/\b(tests?|typecheck|build|lint|doctor|pack)\b.*\b(pass|passed|green|succeed|succeeded|ok)\b/i,
|
|
1647
1749
|
/\b(changed|updated|modified|created|deleted|refactored)\b.*\b(files?|docs?|tests?|hooks?|configs?)\b/i,
|
|
1648
|
-
|
|
1750
|
+
/已完成|完成了|修好了|实现了|验证通过|测试通过|已经提交|已经推送|提交并推送|已提交|已推送/
|
|
1751
|
+
];
|
|
1752
|
+
var negativeCompletionSignalPatterns = [
|
|
1753
|
+
/\b(not done|not completed|not implemented|not fixed|did not complete|didn't complete|have not completed|haven't completed)\b/i,
|
|
1754
|
+
/没有完成|未完成|还没完成|没有改动|未改动|没有修改|未修改/
|
|
1649
1755
|
];
|
|
1650
1756
|
var compoundGateInstruction = [
|
|
1651
1757
|
"Before final reporting, pass the PGS Compound Gate.",
|
|
@@ -1730,6 +1836,9 @@ function normalizeStopInput(input) {
|
|
|
1730
1836
|
};
|
|
1731
1837
|
}
|
|
1732
1838
|
function looksLikeCompletedEngineeringWork(message) {
|
|
1839
|
+
if (negativeCompletionSignalPatterns.some((pattern) => pattern.test(message))) {
|
|
1840
|
+
return false;
|
|
1841
|
+
}
|
|
1733
1842
|
return completionSignalPatterns.some((pattern) => pattern.test(message));
|
|
1734
1843
|
}
|
|
1735
1844
|
function findString(input, keys) {
|
|
@@ -1843,7 +1952,9 @@ function isHostHookEvent(value) {
|
|
|
1843
1952
|
}
|
|
1844
1953
|
|
|
1845
1954
|
// src/commands/host-hook.ts
|
|
1846
|
-
|
|
1955
|
+
var defaultStdinTimeoutMs = 750;
|
|
1956
|
+
var maxDebugRawInputBytes = 256 * 1024;
|
|
1957
|
+
async function runHostHook(args) {
|
|
1847
1958
|
const host = readOption(args, "--host");
|
|
1848
1959
|
const event = readOption(args, "--event");
|
|
1849
1960
|
if (!isHostHookHost(host)) {
|
|
@@ -1854,9 +1965,18 @@ function runHostHook(args) {
|
|
|
1854
1965
|
console.error("Expected --event <Stop|SubagentStop|PreToolUse|PostToolUse|UserPromptSubmit>");
|
|
1855
1966
|
return 1;
|
|
1856
1967
|
}
|
|
1857
|
-
const
|
|
1968
|
+
const rawInput = await readStdinText(defaultStdinTimeoutMs);
|
|
1969
|
+
const input = parseStdinJson(rawInput);
|
|
1858
1970
|
const decision = evaluateHostHook({ host, event, input });
|
|
1859
1971
|
const output = formatHostHookOutput(host, event, decision);
|
|
1972
|
+
writeDebugLogIfRequested(args, {
|
|
1973
|
+
decision,
|
|
1974
|
+
event,
|
|
1975
|
+
host,
|
|
1976
|
+
input,
|
|
1977
|
+
output,
|
|
1978
|
+
rawInput
|
|
1979
|
+
});
|
|
1860
1980
|
console.log(`${JSON.stringify(output)}
|
|
1861
1981
|
`);
|
|
1862
1982
|
return 0;
|
|
@@ -1866,8 +1986,7 @@ function readOption(args, name) {
|
|
|
1866
1986
|
if (index < 0) return void 0;
|
|
1867
1987
|
return args[index + 1];
|
|
1868
1988
|
}
|
|
1869
|
-
function
|
|
1870
|
-
const raw = readFileSync10(0, "utf8").trim();
|
|
1989
|
+
function parseStdinJson(raw) {
|
|
1871
1990
|
if (!raw) return {};
|
|
1872
1991
|
try {
|
|
1873
1992
|
return JSON.parse(raw);
|
|
@@ -1875,10 +1994,106 @@ function readStdinJson() {
|
|
|
1875
1994
|
return {};
|
|
1876
1995
|
}
|
|
1877
1996
|
}
|
|
1997
|
+
function readStdinText(timeoutMs) {
|
|
1998
|
+
if (process.stdin.isTTY) {
|
|
1999
|
+
return Promise.resolve("");
|
|
2000
|
+
}
|
|
2001
|
+
process.stdin.setEncoding("utf8");
|
|
2002
|
+
return new Promise((resolveText) => {
|
|
2003
|
+
let settled = false;
|
|
2004
|
+
let content = "";
|
|
2005
|
+
const settle = () => {
|
|
2006
|
+
if (settled) return;
|
|
2007
|
+
settled = true;
|
|
2008
|
+
clearTimeout(timer);
|
|
2009
|
+
process.stdin.off("data", onData);
|
|
2010
|
+
process.stdin.off("end", settle);
|
|
2011
|
+
process.stdin.off("error", settle);
|
|
2012
|
+
process.stdin.pause();
|
|
2013
|
+
resolveText(content.trim());
|
|
2014
|
+
};
|
|
2015
|
+
const onData = (chunk) => {
|
|
2016
|
+
content += chunk.toString();
|
|
2017
|
+
};
|
|
2018
|
+
const timer = setTimeout(settle, timeoutMs);
|
|
2019
|
+
timer.unref();
|
|
2020
|
+
process.stdin.on("data", onData);
|
|
2021
|
+
process.stdin.on("end", settle);
|
|
2022
|
+
process.stdin.on("error", settle);
|
|
2023
|
+
process.stdin.resume();
|
|
2024
|
+
});
|
|
2025
|
+
}
|
|
2026
|
+
function writeDebugLogIfRequested(args, record) {
|
|
2027
|
+
const explicitPath = readOption(args, "--debug-log");
|
|
2028
|
+
const enabled = explicitPath !== void 0 || process.env.PGS_HOST_HOOK_DEBUG === "1";
|
|
2029
|
+
if (!enabled) return;
|
|
2030
|
+
const debugDir = explicitPath && explicitPath.trim().length > 0 ? explicitPath : defaultDebugDir();
|
|
2031
|
+
try {
|
|
2032
|
+
mkdirSync4(debugDir, { recursive: true });
|
|
2033
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
2034
|
+
const fileName = `${timestamp}-${process.pid}-${record.host}-${record.event}.json`;
|
|
2035
|
+
writeFileSync3(
|
|
2036
|
+
join13(debugDir, fileName),
|
|
2037
|
+
`${JSON.stringify(
|
|
2038
|
+
{
|
|
2039
|
+
schemaVersion: 1,
|
|
2040
|
+
cwd: process.cwd(),
|
|
2041
|
+
event: record.event,
|
|
2042
|
+
host: record.host,
|
|
2043
|
+
nodeVersion: process.version,
|
|
2044
|
+
packageVersion: readPackageVersion(),
|
|
2045
|
+
rawInput: truncateDebugRawInput(record.rawInput),
|
|
2046
|
+
input: record.input,
|
|
2047
|
+
decision: record.decision,
|
|
2048
|
+
output: record.output
|
|
2049
|
+
},
|
|
2050
|
+
null,
|
|
2051
|
+
2
|
|
2052
|
+
)}
|
|
2053
|
+
`,
|
|
2054
|
+
"utf8"
|
|
2055
|
+
);
|
|
2056
|
+
} catch {
|
|
2057
|
+
}
|
|
2058
|
+
}
|
|
2059
|
+
function defaultDebugDir() {
|
|
2060
|
+
const gitPath = spawnSync3("git", ["rev-parse", "--git-path", "pro-gov-hook-debug"], {
|
|
2061
|
+
encoding: "utf8",
|
|
2062
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
2063
|
+
});
|
|
2064
|
+
const value = gitPath.status === 0 ? gitPath.stdout.trim() : "";
|
|
2065
|
+
if (value) {
|
|
2066
|
+
return resolve3(process.cwd(), value);
|
|
2067
|
+
}
|
|
2068
|
+
return join13(tmpdir2(), "pro-gov-hook-debug", basename3(process.cwd()));
|
|
2069
|
+
}
|
|
2070
|
+
function readPackageVersion() {
|
|
2071
|
+
const packageJsonPath = resolvePackageJsonPath();
|
|
2072
|
+
if (!packageJsonPath) return void 0;
|
|
2073
|
+
try {
|
|
2074
|
+
const parsed = JSON.parse(readFileSync10(packageJsonPath, "utf8"));
|
|
2075
|
+
return typeof parsed.version === "string" ? parsed.version : void 0;
|
|
2076
|
+
} catch {
|
|
2077
|
+
return void 0;
|
|
2078
|
+
}
|
|
2079
|
+
}
|
|
2080
|
+
function resolvePackageJsonPath() {
|
|
2081
|
+
const candidates = [
|
|
2082
|
+
resolve3(process.cwd(), "packages/pro-gov/package.json"),
|
|
2083
|
+
resolve3(process.cwd(), "node_modules/@pieai/pro-gov/package.json")
|
|
2084
|
+
];
|
|
2085
|
+
return candidates.find((candidate) => existsSync13(candidate));
|
|
2086
|
+
}
|
|
2087
|
+
function truncateDebugRawInput(value) {
|
|
2088
|
+
const buffer = Buffer.from(value);
|
|
2089
|
+
if (buffer.byteLength <= maxDebugRawInputBytes) return value;
|
|
2090
|
+
return `${buffer.subarray(0, maxDebugRawInputBytes).toString("utf8")}
|
|
2091
|
+
[truncated]`;
|
|
2092
|
+
}
|
|
1878
2093
|
|
|
1879
2094
|
// src/commands/init.ts
|
|
1880
|
-
import { existsSync as
|
|
1881
|
-
import { basename as
|
|
2095
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync5, readFileSync as readFileSync11, writeFileSync as writeFileSync4 } from "node:fs";
|
|
2096
|
+
import { basename as basename4, dirname as dirname7, join as join14 } from "node:path";
|
|
1882
2097
|
|
|
1883
2098
|
// src/commands/shared.ts
|
|
1884
2099
|
function planStarterFiles(profile) {
|
|
@@ -1950,7 +2165,7 @@ function runInit(args) {
|
|
|
1950
2165
|
}
|
|
1951
2166
|
function applyStarterFiles(files, profile) {
|
|
1952
2167
|
const root = process.cwd();
|
|
1953
|
-
const conflicts = files.filter((file) =>
|
|
2168
|
+
const conflicts = files.filter((file) => existsSync14(join14(root, file.targetPath)));
|
|
1954
2169
|
if (conflicts.length > 0) {
|
|
1955
2170
|
console.error("pro-gov init is refusing to overwrite existing project files:");
|
|
1956
2171
|
for (const file of conflicts) console.error(` ${file.targetPath}`);
|
|
@@ -1958,11 +2173,11 @@ function applyStarterFiles(files, profile) {
|
|
|
1958
2173
|
return 1;
|
|
1959
2174
|
}
|
|
1960
2175
|
for (const file of files) {
|
|
1961
|
-
const targetPath =
|
|
1962
|
-
|
|
2176
|
+
const targetPath = join14(root, file.targetPath);
|
|
2177
|
+
mkdirSync5(dirname7(targetPath), { recursive: true });
|
|
1963
2178
|
const source = readFileSync11(file.absoluteSourcePath);
|
|
1964
|
-
const content = file.targetPath === "AGENTS.md" ? renderAgentsTemplate(source.toString("utf8"),
|
|
1965
|
-
|
|
2179
|
+
const content = file.targetPath === "AGENTS.md" ? renderAgentsTemplate(source.toString("utf8"), basename4(root), profile) : source;
|
|
2180
|
+
writeFileSync4(targetPath, content);
|
|
1966
2181
|
}
|
|
1967
2182
|
console.log("pro-gov init APPLIED");
|
|
1968
2183
|
console.log(`profile: ${profile}`);
|
|
@@ -1990,12 +2205,12 @@ function readFlag(args, flag) {
|
|
|
1990
2205
|
}
|
|
1991
2206
|
|
|
1992
2207
|
// src/commands/lens.ts
|
|
1993
|
-
import { mkdirSync as
|
|
2208
|
+
import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync6 } from "node:fs";
|
|
1994
2209
|
import { dirname as dirname9 } from "node:path";
|
|
1995
2210
|
|
|
1996
2211
|
// src/lens/audit.ts
|
|
1997
|
-
import { existsSync as
|
|
1998
|
-
import { basename as
|
|
2212
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync6, readFileSync as readFileSync12, writeFileSync as writeFileSync5 } from "node:fs";
|
|
2213
|
+
import { basename as basename5, dirname as dirname8, join as join15 } from "node:path";
|
|
1999
2214
|
var REQUIRED_ARTIFACTS = [
|
|
2000
2215
|
"manifest.md",
|
|
2001
2216
|
"raw/project-lens/architecture-lens.md",
|
|
@@ -2015,20 +2230,20 @@ function createProjectLensAuditPackage(targetDir, auditDir) {
|
|
|
2015
2230
|
version: 1,
|
|
2016
2231
|
target: {
|
|
2017
2232
|
path: targetDir,
|
|
2018
|
-
name:
|
|
2233
|
+
name: basename5(targetDir) || "target"
|
|
2019
2234
|
},
|
|
2020
2235
|
requiredArtifacts: [...REQUIRED_ARTIFACTS]
|
|
2021
2236
|
};
|
|
2022
|
-
|
|
2023
|
-
writeJson(
|
|
2237
|
+
mkdirSync6(auditDir, { recursive: true });
|
|
2238
|
+
writeJson(join15(auditDir, "audit.contract.json"), contract);
|
|
2024
2239
|
for (const artifactPath of REQUIRED_ARTIFACTS) {
|
|
2025
|
-
writeTemplate(
|
|
2240
|
+
writeTemplate(join15(auditDir, artifactPath), renderArtifactTemplate(artifactPath, contract));
|
|
2026
2241
|
}
|
|
2027
2242
|
return contract;
|
|
2028
2243
|
}
|
|
2029
2244
|
function checkProjectLensAuditPackage(auditDir, options = {}) {
|
|
2030
|
-
const contractPath =
|
|
2031
|
-
if (!
|
|
2245
|
+
const contractPath = join15(auditDir, "audit.contract.json");
|
|
2246
|
+
if (!existsSync15(contractPath)) {
|
|
2032
2247
|
return {
|
|
2033
2248
|
ok: false,
|
|
2034
2249
|
auditDir,
|
|
@@ -2075,8 +2290,8 @@ function checkProjectLensAuditPackage(auditDir, options = {}) {
|
|
|
2075
2290
|
}
|
|
2076
2291
|
}
|
|
2077
2292
|
for (const artifactPath of REQUIRED_ARTIFACTS) {
|
|
2078
|
-
const absolutePath =
|
|
2079
|
-
if (!
|
|
2293
|
+
const absolutePath = join15(auditDir, artifactPath);
|
|
2294
|
+
if (!existsSync15(absolutePath)) {
|
|
2080
2295
|
issues.push({ type: "missing-required-artifact", path: artifactPath });
|
|
2081
2296
|
continue;
|
|
2082
2297
|
}
|
|
@@ -2096,13 +2311,13 @@ function checkProjectLensAuditPackage(auditDir, options = {}) {
|
|
|
2096
2311
|
};
|
|
2097
2312
|
}
|
|
2098
2313
|
function writeJson(path, value) {
|
|
2099
|
-
|
|
2100
|
-
|
|
2314
|
+
mkdirSync6(dirname8(path), { recursive: true });
|
|
2315
|
+
writeFileSync5(path, `${JSON.stringify(value, null, 2)}
|
|
2101
2316
|
`);
|
|
2102
2317
|
}
|
|
2103
2318
|
function writeTemplate(path, content) {
|
|
2104
|
-
|
|
2105
|
-
|
|
2319
|
+
mkdirSync6(dirname8(path), { recursive: true });
|
|
2320
|
+
writeFileSync5(path, content);
|
|
2106
2321
|
}
|
|
2107
2322
|
function renderArtifactTemplate(artifactPath, contract) {
|
|
2108
2323
|
const title = artifactPath.replace(/\.md$/, "").split("/").map((part) => part.replaceAll("-", " ")).join(" / ");
|
|
@@ -2339,9 +2554,9 @@ function bulletList(values) {
|
|
|
2339
2554
|
}
|
|
2340
2555
|
|
|
2341
2556
|
// src/lens/scan.ts
|
|
2342
|
-
import { spawnSync as
|
|
2343
|
-
import { existsSync as
|
|
2344
|
-
import { join as
|
|
2557
|
+
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
2558
|
+
import { existsSync as existsSync16, readdirSync as readdirSync6, readFileSync as readFileSync13, statSync as statSync4 } from "node:fs";
|
|
2559
|
+
import { join as join16, relative as relative5 } from "node:path";
|
|
2345
2560
|
var ignoredDirectories = /* @__PURE__ */ new Set([
|
|
2346
2561
|
".git",
|
|
2347
2562
|
".next",
|
|
@@ -2358,22 +2573,22 @@ function scanProjectLensTarget(targetDir, options = {}) {
|
|
|
2358
2573
|
return {
|
|
2359
2574
|
targetDir,
|
|
2360
2575
|
aiEntryFiles: ["AGENTS.md", "CLAUDE.md"].filter(
|
|
2361
|
-
(file) =>
|
|
2576
|
+
(file) => existsSync16(join16(targetDir, file))
|
|
2362
2577
|
),
|
|
2363
2578
|
aiConfigFiles: [],
|
|
2364
2579
|
packageJson,
|
|
2365
2580
|
docs: {
|
|
2366
|
-
hasDocsDirectory:
|
|
2581
|
+
hasDocsDirectory: existsSync16(join16(targetDir, "docs")),
|
|
2367
2582
|
markdownFileCount: markdownFiles.length,
|
|
2368
2583
|
governanceFiles: markdownFiles.filter((file) => file.startsWith("docs/governance/") || file.startsWith("docs/policy/")).sort()
|
|
2369
2584
|
},
|
|
2370
2585
|
git: readGitState(targetDir),
|
|
2371
|
-
largeFiles: files.map((file) => ({ path: file, bytes: statSync4(
|
|
2586
|
+
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)
|
|
2372
2587
|
};
|
|
2373
2588
|
}
|
|
2374
2589
|
function readPackageJson(targetDir) {
|
|
2375
|
-
const packageJsonPath =
|
|
2376
|
-
if (!
|
|
2590
|
+
const packageJsonPath = join16(targetDir, "package.json");
|
|
2591
|
+
if (!existsSync16(packageJsonPath)) return void 0;
|
|
2377
2592
|
try {
|
|
2378
2593
|
const packageJson = JSON.parse(readFileSync13(packageJsonPath, "utf8"));
|
|
2379
2594
|
return {
|
|
@@ -2398,7 +2613,7 @@ function readGitState(targetDir) {
|
|
|
2398
2613
|
};
|
|
2399
2614
|
}
|
|
2400
2615
|
function runGit(targetDir, args) {
|
|
2401
|
-
const result =
|
|
2616
|
+
const result = spawnSync4("git", ["-C", targetDir, ...args], {
|
|
2402
2617
|
encoding: "utf8"
|
|
2403
2618
|
});
|
|
2404
2619
|
if (result.status !== 0) return { ok: false };
|
|
@@ -2410,13 +2625,13 @@ function listProjectFiles(targetDir) {
|
|
|
2410
2625
|
return files.sort();
|
|
2411
2626
|
}
|
|
2412
2627
|
function collectFiles2(rootDir, currentDir, files) {
|
|
2413
|
-
if (!
|
|
2628
|
+
if (!existsSync16(currentDir)) return;
|
|
2414
2629
|
for (const entry of readdirSync6(currentDir, { withFileTypes: true })) {
|
|
2415
2630
|
if (entry.isDirectory()) {
|
|
2416
2631
|
if (ignoredDirectories.has(entry.name)) continue;
|
|
2417
|
-
collectFiles2(rootDir,
|
|
2632
|
+
collectFiles2(rootDir, join16(currentDir, entry.name), files);
|
|
2418
2633
|
} else if (entry.isFile()) {
|
|
2419
|
-
files.push(toUnixPath4(relative5(rootDir,
|
|
2634
|
+
files.push(toUnixPath4(relative5(rootDir, join16(currentDir, entry.name))));
|
|
2420
2635
|
}
|
|
2421
2636
|
}
|
|
2422
2637
|
}
|
|
@@ -2468,8 +2683,8 @@ function runLensReport(args) {
|
|
|
2468
2683
|
}
|
|
2469
2684
|
const report = scanProjectLensTarget(options.value.targetDir);
|
|
2470
2685
|
const markdown = renderProjectLensMarkdownReport(report);
|
|
2471
|
-
|
|
2472
|
-
|
|
2686
|
+
mkdirSync7(dirname9(options.value.outPath), { recursive: true });
|
|
2687
|
+
writeFileSync6(options.value.outPath, markdown);
|
|
2473
2688
|
console.log(`report: ${options.value.outPath}`);
|
|
2474
2689
|
return 0;
|
|
2475
2690
|
}
|
|
@@ -2572,12 +2787,12 @@ function printUsage2() {
|
|
|
2572
2787
|
}
|
|
2573
2788
|
|
|
2574
2789
|
// src/commands/portfolio.ts
|
|
2575
|
-
import { existsSync as
|
|
2576
|
-
import { join as
|
|
2790
|
+
import { existsSync as existsSync20 } from "node:fs";
|
|
2791
|
+
import { join as join19 } from "node:path";
|
|
2577
2792
|
|
|
2578
2793
|
// src/portfolio/manifest.ts
|
|
2579
|
-
import { existsSync as
|
|
2580
|
-
import { dirname as dirname10, isAbsolute as isAbsolute3, resolve as
|
|
2794
|
+
import { existsSync as existsSync17, readFileSync as readFileSync14 } from "node:fs";
|
|
2795
|
+
import { dirname as dirname10, isAbsolute as isAbsolute3, resolve as resolve4 } from "node:path";
|
|
2581
2796
|
function loadPortfolioManifest(configPath) {
|
|
2582
2797
|
let parsed;
|
|
2583
2798
|
try {
|
|
@@ -2593,7 +2808,7 @@ function loadPortfolioManifest(configPath) {
|
|
|
2593
2808
|
]
|
|
2594
2809
|
};
|
|
2595
2810
|
}
|
|
2596
|
-
const normalized = resolveManifestPaths(parsed, dirname10(
|
|
2811
|
+
const normalized = resolveManifestPaths(parsed, dirname10(resolve4(configPath)));
|
|
2597
2812
|
const issues = validatePortfolioManifest(normalized);
|
|
2598
2813
|
return {
|
|
2599
2814
|
configPath,
|
|
@@ -2607,7 +2822,7 @@ function resolveManifestPaths(value, configDir) {
|
|
|
2607
2822
|
if (!isRecord2(endpoint) || typeof endpoint.path !== "string" || isAbsolute3(endpoint.path)) {
|
|
2608
2823
|
return endpoint;
|
|
2609
2824
|
}
|
|
2610
|
-
return { ...endpoint, path:
|
|
2825
|
+
return { ...endpoint, path: resolve4(configDir, endpoint.path) };
|
|
2611
2826
|
};
|
|
2612
2827
|
return {
|
|
2613
2828
|
...value,
|
|
@@ -2641,9 +2856,15 @@ function validatePortfolioManifest(value) {
|
|
|
2641
2856
|
message: "Portfolio manifest portfolioId must be a non-empty string."
|
|
2642
2857
|
});
|
|
2643
2858
|
}
|
|
2644
|
-
validateAllowedFields(
|
|
2859
|
+
validateAllowedFields(
|
|
2860
|
+
value,
|
|
2861
|
+
"root",
|
|
2862
|
+
["schemaVersion", "portfolioId", "controlPlane", "executionEngine", "hostTooling", "targets"],
|
|
2863
|
+
issues
|
|
2864
|
+
);
|
|
2645
2865
|
validateEndpoint(value.controlPlane, "controlPlane", issues);
|
|
2646
2866
|
validateEndpoint(value.executionEngine, "executionEngine", issues);
|
|
2867
|
+
validateHostTooling(value.hostTooling, issues);
|
|
2647
2868
|
if (!Array.isArray(value.targets)) {
|
|
2648
2869
|
issues.push({
|
|
2649
2870
|
type: "invalid-field",
|
|
@@ -2735,7 +2956,7 @@ function validateEndpoint(value, field, issues) {
|
|
|
2735
2956
|
});
|
|
2736
2957
|
return;
|
|
2737
2958
|
}
|
|
2738
|
-
if (!
|
|
2959
|
+
if (!existsSync17(value.path)) {
|
|
2739
2960
|
issues.push({
|
|
2740
2961
|
type: "missing-path",
|
|
2741
2962
|
id: typeof value.id === "string" ? value.id : void 0,
|
|
@@ -2755,19 +2976,462 @@ function validateOptionalStringArray(value, id, field, issues) {
|
|
|
2755
2976
|
});
|
|
2756
2977
|
}
|
|
2757
2978
|
}
|
|
2979
|
+
function validateHostTooling(value, issues) {
|
|
2980
|
+
if (value === void 0) return;
|
|
2981
|
+
if (!Array.isArray(value)) {
|
|
2982
|
+
issues.push({
|
|
2983
|
+
type: "invalid-field",
|
|
2984
|
+
field: "hostTooling",
|
|
2985
|
+
message: "Portfolio hostTooling must be an array."
|
|
2986
|
+
});
|
|
2987
|
+
return;
|
|
2988
|
+
}
|
|
2989
|
+
const seenHosts = /* @__PURE__ */ new Set();
|
|
2990
|
+
for (const entry of value) {
|
|
2991
|
+
if (!isRecord2(entry)) {
|
|
2992
|
+
issues.push({
|
|
2993
|
+
type: "invalid-field",
|
|
2994
|
+
field: "hostTooling",
|
|
2995
|
+
message: "Portfolio hostTooling entry must be an object."
|
|
2996
|
+
});
|
|
2997
|
+
continue;
|
|
2998
|
+
}
|
|
2999
|
+
validateAllowedFields(entry, "hostTooling", ["host", "plugins"], issues);
|
|
3000
|
+
if (entry.host !== "codex" && entry.host !== "claude-code") {
|
|
3001
|
+
issues.push({
|
|
3002
|
+
type: "invalid-field",
|
|
3003
|
+
field: "hostTooling.host",
|
|
3004
|
+
message: "Portfolio hostTooling host must be codex or claude-code."
|
|
3005
|
+
});
|
|
3006
|
+
} else if (seenHosts.has(entry.host)) {
|
|
3007
|
+
issues.push({
|
|
3008
|
+
type: "invalid-field",
|
|
3009
|
+
field: "hostTooling",
|
|
3010
|
+
message: `Duplicate portfolio hostTooling entry: ${entry.host}`
|
|
3011
|
+
});
|
|
3012
|
+
} else {
|
|
3013
|
+
seenHosts.add(entry.host);
|
|
3014
|
+
}
|
|
3015
|
+
if (!Array.isArray(entry.plugins) || !entry.plugins.every((plugin) => typeof plugin === "string" && plugin.length > 0)) {
|
|
3016
|
+
issues.push({
|
|
3017
|
+
type: "invalid-field",
|
|
3018
|
+
field: "hostTooling.plugins",
|
|
3019
|
+
message: "Portfolio hostTooling plugins must be an array of non-empty strings."
|
|
3020
|
+
});
|
|
3021
|
+
}
|
|
3022
|
+
}
|
|
3023
|
+
}
|
|
2758
3024
|
function isRecord2(value) {
|
|
2759
3025
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2760
3026
|
}
|
|
2761
3027
|
|
|
3028
|
+
// src/portfolio/doctor.ts
|
|
3029
|
+
import { spawnSync as spawnSync6 } from "node:child_process";
|
|
3030
|
+
import { existsSync as existsSync19, readFileSync as readFileSync16 } from "node:fs";
|
|
3031
|
+
import { createRequire as createRequire2 } from "node:module";
|
|
3032
|
+
import { dirname as dirname11, join as join18 } from "node:path";
|
|
3033
|
+
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
3034
|
+
|
|
3035
|
+
// src/host-tooling/inventory.ts
|
|
3036
|
+
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
3037
|
+
function inspectHostTooling(requirements, runner = defaultRunner2) {
|
|
3038
|
+
const hosts = [];
|
|
3039
|
+
const issues = [];
|
|
3040
|
+
for (const requirement of requirements) {
|
|
3041
|
+
const command2 = requirement.host === "codex" ? ["codex", "plugin", "list", "--json"] : ["claude", "plugin", "list", "--json"];
|
|
3042
|
+
const result = runner({ host: requirement.host, command: command2 });
|
|
3043
|
+
if (result.status !== 0) {
|
|
3044
|
+
issues.push({
|
|
3045
|
+
type: "host-tooling-command-failed",
|
|
3046
|
+
host: requirement.host,
|
|
3047
|
+
message: `Unable to inspect ${requirement.host} plugins: ${result.stderr || `exit ${result.status}`}`
|
|
3048
|
+
});
|
|
3049
|
+
hosts.push({ host: requirement.host, plugins: [] });
|
|
3050
|
+
continue;
|
|
3051
|
+
}
|
|
3052
|
+
let plugins;
|
|
3053
|
+
try {
|
|
3054
|
+
plugins = parseHostPlugins(requirement.host, JSON.parse(result.stdout));
|
|
3055
|
+
} catch (error) {
|
|
3056
|
+
issues.push({
|
|
3057
|
+
type: "host-tooling-command-failed",
|
|
3058
|
+
host: requirement.host,
|
|
3059
|
+
message: `Unable to parse ${requirement.host} plugin inventory: ${error instanceof Error ? error.message : String(error)}`
|
|
3060
|
+
});
|
|
3061
|
+
hosts.push({ host: requirement.host, plugins: [] });
|
|
3062
|
+
continue;
|
|
3063
|
+
}
|
|
3064
|
+
const byId = new Map(plugins.map((plugin) => [plugin.id, plugin]));
|
|
3065
|
+
hosts.push({
|
|
3066
|
+
host: requirement.host,
|
|
3067
|
+
plugins: requirement.plugins.flatMap((pluginId) => {
|
|
3068
|
+
const plugin = byId.get(pluginId);
|
|
3069
|
+
return plugin ? [plugin] : [];
|
|
3070
|
+
})
|
|
3071
|
+
});
|
|
3072
|
+
for (const pluginId of requirement.plugins) {
|
|
3073
|
+
const plugin = byId.get(pluginId);
|
|
3074
|
+
if (!plugin) {
|
|
3075
|
+
issues.push({
|
|
3076
|
+
type: "host-tooling-missing",
|
|
3077
|
+
host: requirement.host,
|
|
3078
|
+
pluginId,
|
|
3079
|
+
message: `Required ${requirement.host} plugin is missing: ${pluginId}`
|
|
3080
|
+
});
|
|
3081
|
+
} else if (!plugin.enabled) {
|
|
3082
|
+
issues.push({
|
|
3083
|
+
type: "host-tooling-disabled",
|
|
3084
|
+
host: requirement.host,
|
|
3085
|
+
pluginId,
|
|
3086
|
+
message: `Required ${requirement.host} plugin is disabled: ${pluginId}`
|
|
3087
|
+
});
|
|
3088
|
+
}
|
|
3089
|
+
}
|
|
3090
|
+
}
|
|
3091
|
+
return { hosts, issues };
|
|
3092
|
+
}
|
|
3093
|
+
function parseHostPlugins(host, value) {
|
|
3094
|
+
const entries = host === "codex" ? isRecord3(value) && Array.isArray(value.installed) ? value.installed : void 0 : Array.isArray(value) ? value : void 0;
|
|
3095
|
+
if (!entries) throw new Error("expected a plugin array");
|
|
3096
|
+
return entries.flatMap((entry) => {
|
|
3097
|
+
if (!isRecord3(entry)) return [];
|
|
3098
|
+
const id = host === "codex" ? entry.pluginId : entry.id;
|
|
3099
|
+
if (typeof id !== "string") return [];
|
|
3100
|
+
return [{
|
|
3101
|
+
id,
|
|
3102
|
+
version: typeof entry.version === "string" ? entry.version : void 0,
|
|
3103
|
+
enabled: entry.enabled === true
|
|
3104
|
+
}];
|
|
3105
|
+
});
|
|
3106
|
+
}
|
|
3107
|
+
function defaultRunner2({ command: command2 }) {
|
|
3108
|
+
const result = spawnSync5(command2[0] ?? "", command2.slice(1), {
|
|
3109
|
+
encoding: "utf8",
|
|
3110
|
+
timeout: 1e4
|
|
3111
|
+
});
|
|
3112
|
+
return {
|
|
3113
|
+
status: result.status,
|
|
3114
|
+
stdout: result.stdout,
|
|
3115
|
+
stderr: result.stderr || result.error?.message || ""
|
|
3116
|
+
};
|
|
3117
|
+
}
|
|
3118
|
+
function isRecord3(value) {
|
|
3119
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3120
|
+
}
|
|
3121
|
+
|
|
3122
|
+
// src/portfolio/asset-state.ts
|
|
3123
|
+
import { existsSync as existsSync18, lstatSync as lstatSync5, readFileSync as readFileSync15 } from "node:fs";
|
|
3124
|
+
import { join as join17 } from "node:path";
|
|
3125
|
+
function comparePortfolioAssetState(options) {
|
|
3126
|
+
const expectedManifest = readPlanDocument(options.expectedPlan, ".pro-gov/assets.json");
|
|
3127
|
+
const expectedLock = readPlanDocument(options.expectedPlan, ".pro-gov/assets.lock.json");
|
|
3128
|
+
const currentManifest = readJsonFile(join17(options.targetDir, ".pro-gov/assets.json"));
|
|
3129
|
+
const currentLock = readJsonFile(join17(options.targetDir, ".pro-gov/assets.lock.json"));
|
|
3130
|
+
const issues = [];
|
|
3131
|
+
if (!sameStrings(currentManifest?.bundleIds, expectedManifest?.bundleIds)) {
|
|
3132
|
+
issues.push({
|
|
3133
|
+
type: "bundle-drift",
|
|
3134
|
+
message: "Target asset bundles do not match the portfolio manifest."
|
|
3135
|
+
});
|
|
3136
|
+
}
|
|
3137
|
+
if (!sameStrings(currentManifest?.assetIds, expectedManifest?.assetIds)) {
|
|
3138
|
+
issues.push({
|
|
3139
|
+
type: "asset-set-drift",
|
|
3140
|
+
message: "Target asset ids do not match the current bundle definitions."
|
|
3141
|
+
});
|
|
3142
|
+
}
|
|
3143
|
+
if (!sameLock(currentLock, expectedLock)) {
|
|
3144
|
+
issues.push({
|
|
3145
|
+
type: "asset-lock-drift",
|
|
3146
|
+
message: "Target asset lock does not match the current registry, placement, and content hashes."
|
|
3147
|
+
});
|
|
3148
|
+
}
|
|
3149
|
+
const expectedTargets = new Set((expectedLock?.assets ?? []).map((entry) => entry.targetPath));
|
|
3150
|
+
for (const entry of currentLock?.assets ?? []) {
|
|
3151
|
+
if (expectedTargets.has(entry.targetPath)) continue;
|
|
3152
|
+
const targetAbsolutePath = join17(options.targetDir, entry.targetPath);
|
|
3153
|
+
if (!pathIsSymlink(targetAbsolutePath)) continue;
|
|
3154
|
+
issues.push({
|
|
3155
|
+
type: "orphaned-managed-symlink",
|
|
3156
|
+
targetPath: entry.targetPath,
|
|
3157
|
+
message: `Previously managed symlink is absent from the expected bundle state: ${entry.targetPath}`
|
|
3158
|
+
});
|
|
3159
|
+
}
|
|
3160
|
+
return { issues };
|
|
3161
|
+
}
|
|
3162
|
+
function readPlanDocument(plan, targetPath) {
|
|
3163
|
+
const action = plan.actions.find(
|
|
3164
|
+
(candidate) => candidate.type === "write-file" && candidate.targetPath === targetPath
|
|
3165
|
+
);
|
|
3166
|
+
if (!action || action.type !== "write-file") return void 0;
|
|
3167
|
+
try {
|
|
3168
|
+
return JSON.parse(action.content);
|
|
3169
|
+
} catch {
|
|
3170
|
+
return void 0;
|
|
3171
|
+
}
|
|
3172
|
+
}
|
|
3173
|
+
function readJsonFile(path) {
|
|
3174
|
+
if (!existsSync18(path)) return void 0;
|
|
3175
|
+
try {
|
|
3176
|
+
return JSON.parse(readFileSync15(path, "utf8"));
|
|
3177
|
+
} catch {
|
|
3178
|
+
return void 0;
|
|
3179
|
+
}
|
|
3180
|
+
}
|
|
3181
|
+
function sameStrings(left, right) {
|
|
3182
|
+
return JSON.stringify([...left ?? []].sort()) === JSON.stringify([...right ?? []].sort());
|
|
3183
|
+
}
|
|
3184
|
+
function sameLock(left, right) {
|
|
3185
|
+
return JSON.stringify(normalizeLock(left)) === JSON.stringify(normalizeLock(right));
|
|
3186
|
+
}
|
|
3187
|
+
function normalizeLock(lock) {
|
|
3188
|
+
return {
|
|
3189
|
+
host: lock?.host,
|
|
3190
|
+
placement: lock?.placement,
|
|
3191
|
+
bundleIds: [...lock?.bundleIds ?? []].sort(),
|
|
3192
|
+
assets: [...lock?.assets ?? []].sort((a, b) => a.id.localeCompare(b.id))
|
|
3193
|
+
};
|
|
3194
|
+
}
|
|
3195
|
+
function pathIsSymlink(path) {
|
|
3196
|
+
try {
|
|
3197
|
+
return lstatSync5(path).isSymbolicLink();
|
|
3198
|
+
} catch {
|
|
3199
|
+
return false;
|
|
3200
|
+
}
|
|
3201
|
+
}
|
|
3202
|
+
|
|
3203
|
+
// src/portfolio/doctor.ts
|
|
3204
|
+
function inspectPortfolio(options) {
|
|
3205
|
+
const expectedPackageVersions = getExpectedPackageVersions();
|
|
3206
|
+
const hostTooling = inspectHostTooling(options.manifest.hostTooling ?? []);
|
|
3207
|
+
const targets = options.targets.map((target) => inspectTarget({
|
|
3208
|
+
target,
|
|
3209
|
+
agentAssetsDir: options.agentAssetsDir,
|
|
3210
|
+
registry: options.registry,
|
|
3211
|
+
bundles: options.bundles,
|
|
3212
|
+
expectedPackageVersions
|
|
3213
|
+
}));
|
|
3214
|
+
return {
|
|
3215
|
+
ok: hostTooling.issues.length === 0 && targets.every((target) => target.issues.length === 0),
|
|
3216
|
+
portfolioId: options.manifest.portfolioId,
|
|
3217
|
+
expectedPackageVersions,
|
|
3218
|
+
hostTooling,
|
|
3219
|
+
targets
|
|
3220
|
+
};
|
|
3221
|
+
}
|
|
3222
|
+
function inspectTarget(options) {
|
|
3223
|
+
const { target } = options;
|
|
3224
|
+
const issues = [];
|
|
3225
|
+
const packageJson = readJson2(join18(target.path, "package.json"));
|
|
3226
|
+
const packages = {};
|
|
3227
|
+
for (const packageName of ["@pieai/pro-gov", "@pieai/doc-gov"]) {
|
|
3228
|
+
const declared = packageJson?.devDependencies?.[packageName] ?? packageJson?.dependencies?.[packageName];
|
|
3229
|
+
const installedPackage = readJson2(join18(target.path, "node_modules", packageName, "package.json"));
|
|
3230
|
+
const installed = installedPackage?.version;
|
|
3231
|
+
const expected = options.expectedPackageVersions[packageName];
|
|
3232
|
+
packages[packageName] = { declared, installed, expected };
|
|
3233
|
+
if (!declared) {
|
|
3234
|
+
issues.push({
|
|
3235
|
+
type: "package-declaration-missing",
|
|
3236
|
+
packageName,
|
|
3237
|
+
message: `Target does not declare ${packageName}.`
|
|
3238
|
+
});
|
|
3239
|
+
}
|
|
3240
|
+
if (!installed || expected && installed !== expected) {
|
|
3241
|
+
issues.push({
|
|
3242
|
+
type: "package-version-drift",
|
|
3243
|
+
packageName,
|
|
3244
|
+
message: `Target ${packageName} installed version is ${installed ?? "missing"}; expected ${expected ?? "unknown"}.`
|
|
3245
|
+
});
|
|
3246
|
+
}
|
|
3247
|
+
}
|
|
3248
|
+
const checks = runTargetChecks(target);
|
|
3249
|
+
for (const check of checks) {
|
|
3250
|
+
if (check.status === 0) continue;
|
|
3251
|
+
issues.push({
|
|
3252
|
+
type: "target-check-failed",
|
|
3253
|
+
check: check.name,
|
|
3254
|
+
message: `Target check failed: ${check.name} (${check.status ?? "unavailable"}).`
|
|
3255
|
+
});
|
|
3256
|
+
}
|
|
3257
|
+
const assetCheck = checkInstalledAssets({
|
|
3258
|
+
targetDir: target.path,
|
|
3259
|
+
agentAssetsDir: options.agentAssetsDir,
|
|
3260
|
+
registry: options.registry,
|
|
3261
|
+
strictRegistry: true
|
|
3262
|
+
});
|
|
3263
|
+
issues.push(...assetCheck.issues.map((issue) => ({
|
|
3264
|
+
type: issue.type,
|
|
3265
|
+
targetPath: issue.targetPath,
|
|
3266
|
+
message: issue.message
|
|
3267
|
+
})));
|
|
3268
|
+
try {
|
|
3269
|
+
const expectedPlan = createAssetInstallPlan({
|
|
3270
|
+
targetDir: target.path,
|
|
3271
|
+
agentAssetsDir: options.agentAssetsDir,
|
|
3272
|
+
registry: options.registry,
|
|
3273
|
+
bundles: options.bundles,
|
|
3274
|
+
bundleIds: target.assetBundles ?? [],
|
|
3275
|
+
host: "codex"
|
|
3276
|
+
});
|
|
3277
|
+
issues.push(...comparePortfolioAssetState({ targetDir: target.path, expectedPlan }).issues);
|
|
3278
|
+
} catch (error) {
|
|
3279
|
+
issues.push({
|
|
3280
|
+
type: "asset-lock-drift",
|
|
3281
|
+
message: error instanceof Error ? error.message : String(error)
|
|
3282
|
+
});
|
|
3283
|
+
if (!existsSync19(join18(target.path, ".pro-gov/assets.json"))) {
|
|
3284
|
+
issues.push({ type: "bundle-drift", message: "Target asset manifest is missing." });
|
|
3285
|
+
}
|
|
3286
|
+
}
|
|
3287
|
+
return {
|
|
3288
|
+
id: target.id,
|
|
3289
|
+
path: target.path,
|
|
3290
|
+
profile: target.profile,
|
|
3291
|
+
packages,
|
|
3292
|
+
git: inspectGit(target.path),
|
|
3293
|
+
checks,
|
|
3294
|
+
issues: deduplicateIssues(issues)
|
|
3295
|
+
};
|
|
3296
|
+
}
|
|
3297
|
+
function runTargetChecks(target) {
|
|
3298
|
+
const proGovCli = join18(target.path, "node_modules/@pieai/pro-gov/dist/cli.js");
|
|
3299
|
+
const docGovCli = join18(target.path, "node_modules/@pieai/doc-gov/dist/cli.js");
|
|
3300
|
+
const commands = [
|
|
3301
|
+
{
|
|
3302
|
+
name: "pro-gov doctor",
|
|
3303
|
+
cli: proGovCli,
|
|
3304
|
+
args: target.profile === "engineering-runtime" ? ["doctor", "--strict-hooks"] : ["doctor"]
|
|
3305
|
+
},
|
|
3306
|
+
{ name: "doc-gov router-check", cli: docGovCli, args: ["router-check"] },
|
|
3307
|
+
{ name: "doc-gov scan --check", cli: docGovCli, args: ["scan", "--check"] }
|
|
3308
|
+
];
|
|
3309
|
+
return commands.map((command2) => {
|
|
3310
|
+
if (!existsSync19(command2.cli)) return { name: command2.name, status: null };
|
|
3311
|
+
const result = spawnSync6(process.execPath, [command2.cli, ...command2.args], {
|
|
3312
|
+
cwd: target.path,
|
|
3313
|
+
encoding: "utf8",
|
|
3314
|
+
timeout: 3e4
|
|
3315
|
+
});
|
|
3316
|
+
return { name: command2.name, status: result.status };
|
|
3317
|
+
});
|
|
3318
|
+
}
|
|
3319
|
+
function inspectGit(path) {
|
|
3320
|
+
const inside = spawnSync6("git", ["rev-parse", "--is-inside-work-tree"], {
|
|
3321
|
+
cwd: path,
|
|
3322
|
+
encoding: "utf8"
|
|
3323
|
+
});
|
|
3324
|
+
if (inside.status !== 0) return { isRepository: false, dirty: false };
|
|
3325
|
+
const status = spawnSync6("git", ["status", "--porcelain"], { cwd: path, encoding: "utf8" });
|
|
3326
|
+
const branch = spawnSync6("git", ["branch", "--show-current"], { cwd: path, encoding: "utf8" });
|
|
3327
|
+
return {
|
|
3328
|
+
isRepository: true,
|
|
3329
|
+
dirty: status.stdout.trim().length > 0,
|
|
3330
|
+
branch: branch.stdout.trim() || void 0
|
|
3331
|
+
};
|
|
3332
|
+
}
|
|
3333
|
+
function getExpectedPackageVersions() {
|
|
3334
|
+
const proGovPackage = readJson2(findOwnPackageJson());
|
|
3335
|
+
let docGovVersion;
|
|
3336
|
+
try {
|
|
3337
|
+
const require2 = createRequire2(import.meta.url);
|
|
3338
|
+
const docGovPackage = readJson2(require2.resolve("@pieai/doc-gov/package.json"));
|
|
3339
|
+
docGovVersion = docGovPackage?.version;
|
|
3340
|
+
} catch {
|
|
3341
|
+
docGovVersion = void 0;
|
|
3342
|
+
}
|
|
3343
|
+
return {
|
|
3344
|
+
"@pieai/pro-gov": proGovPackage?.version,
|
|
3345
|
+
"@pieai/doc-gov": docGovVersion
|
|
3346
|
+
};
|
|
3347
|
+
}
|
|
3348
|
+
function findOwnPackageJson() {
|
|
3349
|
+
let current = dirname11(fileURLToPath3(import.meta.url));
|
|
3350
|
+
for (let depth = 0; depth < 5; depth += 1) {
|
|
3351
|
+
const candidate = join18(current, "package.json");
|
|
3352
|
+
if (existsSync19(candidate)) return candidate;
|
|
3353
|
+
current = dirname11(current);
|
|
3354
|
+
}
|
|
3355
|
+
return "";
|
|
3356
|
+
}
|
|
3357
|
+
function readJson2(path) {
|
|
3358
|
+
if (!path || !existsSync19(path)) return void 0;
|
|
3359
|
+
try {
|
|
3360
|
+
return JSON.parse(readFileSync16(path, "utf8"));
|
|
3361
|
+
} catch {
|
|
3362
|
+
return void 0;
|
|
3363
|
+
}
|
|
3364
|
+
}
|
|
3365
|
+
function deduplicateIssues(issues) {
|
|
3366
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3367
|
+
return issues.filter((issue) => {
|
|
3368
|
+
const key = `${issue.type}\0${issue.packageName ?? ""}\0${issue.targetPath ?? ""}\0${issue.check ?? ""}\0${issue.message}`;
|
|
3369
|
+
if (seen.has(key)) return false;
|
|
3370
|
+
seen.add(key);
|
|
3371
|
+
return true;
|
|
3372
|
+
});
|
|
3373
|
+
}
|
|
3374
|
+
|
|
2762
3375
|
// src/commands/portfolio.ts
|
|
2763
3376
|
function runPortfolio(args) {
|
|
2764
3377
|
const [subcommand2, ...rest] = args;
|
|
2765
3378
|
if (subcommand2 === "check") return runPortfolioCheck(rest);
|
|
2766
3379
|
if (subcommand2 === "plan") return runPortfolioPlan(rest);
|
|
2767
3380
|
if (subcommand2 === "assets-check") return runPortfolioAssetsCheck(rest);
|
|
3381
|
+
if (subcommand2 === "doctor") return runPortfolioDoctor(rest);
|
|
2768
3382
|
printUsage3();
|
|
2769
3383
|
return 1;
|
|
2770
3384
|
}
|
|
3385
|
+
function runPortfolioDoctor(args) {
|
|
3386
|
+
const options = parsePortfolioOptions(args);
|
|
3387
|
+
if (!options.ok) {
|
|
3388
|
+
console.error(options.error);
|
|
3389
|
+
printUsage3();
|
|
3390
|
+
return 1;
|
|
3391
|
+
}
|
|
3392
|
+
const loaded = loadPortfolioManifest(options.value.configPath);
|
|
3393
|
+
if (loaded.issues.length > 0 || !loaded.manifest) {
|
|
3394
|
+
if (options.value.json) {
|
|
3395
|
+
console.log(JSON.stringify({ ok: false, configPath: loaded.configPath, issues: loaded.issues, targets: [] }, null, 2));
|
|
3396
|
+
} else {
|
|
3397
|
+
for (const issue of loaded.issues) console.error(`${issue.type}: ${issue.message}`);
|
|
3398
|
+
}
|
|
3399
|
+
return 1;
|
|
3400
|
+
}
|
|
3401
|
+
const targets = getDefaultPortfolioTargets(loaded.manifest).filter(
|
|
3402
|
+
(target) => !options.value.targetId || options.value.targetId === "all" || target.id === options.value.targetId
|
|
3403
|
+
);
|
|
3404
|
+
if (targets.length === 0) {
|
|
3405
|
+
console.error(`Unknown portfolio target: ${options.value.targetId}`);
|
|
3406
|
+
return 1;
|
|
3407
|
+
}
|
|
3408
|
+
const loadedAssets = loadAgentAssetRegistry({
|
|
3409
|
+
agentAssetsDir: findPortfolioAgentAssetsDir(loaded.manifest)
|
|
3410
|
+
});
|
|
3411
|
+
if (loadedAssets.issues.length > 0) {
|
|
3412
|
+
for (const issue of loadedAssets.issues) console.error(`${issue.type}: ${issue.message}`);
|
|
3413
|
+
return 1;
|
|
3414
|
+
}
|
|
3415
|
+
const result = inspectPortfolio({
|
|
3416
|
+
manifest: loaded.manifest,
|
|
3417
|
+
targets,
|
|
3418
|
+
agentAssetsDir: loadedAssets.agentAssetsDir,
|
|
3419
|
+
registry: loadedAssets.registry,
|
|
3420
|
+
bundles: loadAgentAssetBundles(loadedAssets.agentAssetsDir)
|
|
3421
|
+
});
|
|
3422
|
+
const output = { configPath: loaded.configPath, ...result };
|
|
3423
|
+
if (options.value.json) {
|
|
3424
|
+
console.log(JSON.stringify(output, null, 2));
|
|
3425
|
+
} else if (result.ok) {
|
|
3426
|
+
console.log(`portfolio doctor passed (${targets.length} targets)`);
|
|
3427
|
+
} else {
|
|
3428
|
+
for (const issue of result.hostTooling.issues) console.log(`${issue.host} ${issue.type}: ${issue.message}`);
|
|
3429
|
+
for (const target of result.targets) {
|
|
3430
|
+
for (const issue of target.issues) console.log(`${target.id} ${issue.type}: ${issue.message}`);
|
|
3431
|
+
}
|
|
3432
|
+
}
|
|
3433
|
+
return result.ok ? 0 : 1;
|
|
3434
|
+
}
|
|
2771
3435
|
function runPortfolioCheck(args) {
|
|
2772
3436
|
const options = parsePortfolioOptions(args);
|
|
2773
3437
|
if (!options.ok) {
|
|
@@ -3010,19 +3674,20 @@ function isHost2(value) {
|
|
|
3010
3674
|
return value === "codex" || value === "claude-code" || value === "gemini-cli" || value === "antigravity";
|
|
3011
3675
|
}
|
|
3012
3676
|
function findPortfolioAgentAssetsDir(manifest) {
|
|
3013
|
-
const agentAssetsDir = manifest?.executionEngine?.path ?
|
|
3014
|
-
return agentAssetsDir &&
|
|
3677
|
+
const agentAssetsDir = manifest?.executionEngine?.path ? join19(manifest.executionEngine.path, "agent-assets") : void 0;
|
|
3678
|
+
return agentAssetsDir && existsSync20(join19(agentAssetsDir, "registry.json")) ? agentAssetsDir : void 0;
|
|
3015
3679
|
}
|
|
3016
3680
|
function printUsage3() {
|
|
3017
3681
|
console.error("Usage:");
|
|
3018
3682
|
console.error(" pro-gov portfolio check --config <path> [--json]");
|
|
3019
3683
|
console.error(" pro-gov portfolio plan --config <path> [--target <id|all>] [--host codex|claude-code|gemini-cli|antigravity] [--json]");
|
|
3020
3684
|
console.error(" pro-gov portfolio assets-check --config <path> [--target <id|all>] [--json]");
|
|
3685
|
+
console.error(" pro-gov portfolio doctor --config <path> [--target <id|all>] [--json]");
|
|
3021
3686
|
}
|
|
3022
3687
|
|
|
3023
3688
|
// src/commands/sync.ts
|
|
3024
|
-
import { existsSync as
|
|
3025
|
-
import { join as
|
|
3689
|
+
import { existsSync as existsSync21, readFileSync as readFileSync17 } from "node:fs";
|
|
3690
|
+
import { join as join20 } from "node:path";
|
|
3026
3691
|
function runSync(args) {
|
|
3027
3692
|
if (!args.includes("--check")) {
|
|
3028
3693
|
console.error("pro-gov sync is read-only and requires --check.");
|
|
@@ -3049,16 +3714,16 @@ function runSync(args) {
|
|
|
3049
3714
|
console.log("pro-gov sync check");
|
|
3050
3715
|
console.log(`profile: ${profile}`);
|
|
3051
3716
|
for (const file of planStarterFiles(profile)) {
|
|
3052
|
-
const targetPath =
|
|
3053
|
-
if (!
|
|
3717
|
+
const targetPath = join20(process.cwd(), file.targetPath);
|
|
3718
|
+
if (!existsSync21(targetPath)) {
|
|
3054
3719
|
if (file.ownership === "optional-guardrail") continue;
|
|
3055
3720
|
console.log(`missing: ${file.targetPath}`);
|
|
3056
3721
|
differences += 1;
|
|
3057
3722
|
continue;
|
|
3058
3723
|
}
|
|
3059
3724
|
if (file.ownership === "project-local-seed") continue;
|
|
3060
|
-
const source =
|
|
3061
|
-
const target =
|
|
3725
|
+
const source = readFileSync17(file.absoluteSourcePath, "utf8");
|
|
3726
|
+
const target = readFileSync17(targetPath, "utf8");
|
|
3062
3727
|
if (source !== target) {
|
|
3063
3728
|
console.log(`different: ${file.targetPath}`);
|
|
3064
3729
|
differences += 1;
|
|
@@ -3073,7 +3738,7 @@ function runSync(args) {
|
|
|
3073
3738
|
}
|
|
3074
3739
|
function inferInstalledProfile(root) {
|
|
3075
3740
|
const installed = ["engineering-runtime", "doc-only"].filter(
|
|
3076
|
-
(profile) =>
|
|
3741
|
+
(profile) => existsSync21(join20(root, `docs/governance/agents-routing/${profile}-v0.9.md`))
|
|
3077
3742
|
);
|
|
3078
3743
|
return installed.length === 1 ? installed[0] : void 0;
|
|
3079
3744
|
}
|
|
@@ -3096,6 +3761,7 @@ var COMMANDS = [
|
|
|
3096
3761
|
"portfolio check --config <path> [--json]",
|
|
3097
3762
|
"portfolio plan --config <path> [--target <id|all>] [--json]",
|
|
3098
3763
|
"portfolio assets-check --config <path> [--target <id|all>] [--json]",
|
|
3764
|
+
"portfolio doctor --config <path> [--target <id|all>] [--json]",
|
|
3099
3765
|
"lens scan [--target <path>] [--json]",
|
|
3100
3766
|
"lens inspect [--target <path>] [--format text|json]",
|
|
3101
3767
|
"lens report --target <path> --out <path>",
|
|
@@ -3107,27 +3773,22 @@ var COMMANDS = [
|
|
|
3107
3773
|
"doctor"
|
|
3108
3774
|
];
|
|
3109
3775
|
var [command, subcommand] = process.argv.slice(2);
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
}
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
} else if (command === "sync") {
|
|
3124
|
-
process.exitCode = runSync(process.argv.slice(3));
|
|
3125
|
-
} else if (command === "doctor") {
|
|
3126
|
-
process.exitCode = runDoctor(process.argv.slice(3));
|
|
3127
|
-
} else {
|
|
3776
|
+
process.exitCode = await main();
|
|
3777
|
+
async function main() {
|
|
3778
|
+
if (!command || command === "--help" || command === "-h") {
|
|
3779
|
+
printHelp();
|
|
3780
|
+
return command ? 0 : 1;
|
|
3781
|
+
}
|
|
3782
|
+
if (command === "assets") return runAssets(process.argv.slice(3));
|
|
3783
|
+
if (command === "lens") return runLens(process.argv.slice(3));
|
|
3784
|
+
if (command === "portfolio") return runPortfolio(process.argv.slice(3));
|
|
3785
|
+
if (command === "host-hook") return runHostHook(process.argv.slice(3));
|
|
3786
|
+
if (command === "init") return runInit(process.argv.slice(3));
|
|
3787
|
+
if (command === "sync") return runSync(process.argv.slice(3));
|
|
3788
|
+
if (command === "doctor") return runDoctor(process.argv.slice(3));
|
|
3128
3789
|
console.error(`Unknown command: ${[command, subcommand].filter(Boolean).join(" ")}`);
|
|
3129
3790
|
printHelp();
|
|
3130
|
-
|
|
3791
|
+
return 1;
|
|
3131
3792
|
}
|
|
3132
3793
|
function printHelp() {
|
|
3133
3794
|
console.log("pro-gov \u2014 project-level distribution kit for Project Governance System");
|