@pieai/pro-gov 0.5.4 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/docs/reference/adoption/adoption-playbook.md +17 -9
- package/assets/docs/reference/adoption/migration-v1.1.md +164 -0
- package/assets/portfolio-dashboard/app.css +1 -1
- package/assets/portfolio-dashboard/app.js +7 -7
- package/assets/profiles/doc-only/manifest.yml +1 -1
- package/assets/profiles/doc-only/profile.md +2 -2
- package/assets/profiles/engineering-runtime/manifest.yml +1 -1
- package/assets/profiles/engineering-runtime/profile.md +2 -2
- package/assets/starter/.agents/skills/.gitkeep +1 -0
- package/assets/starter/AGENTS.template.md +6 -2
- package/assets/starter/docs/governance/agents-routing/{doc-only-v1.0.md → doc-only-v1.1.md} +9 -9
- package/assets/starter/docs/governance/agents-routing/{engineering-runtime-v1.0.md → engineering-runtime-v1.1.md} +9 -9
- package/assets/starter/docs/governance/boundary.md +4 -3
- package/assets/starter/docs/governance/{ssot-v1.0.md → ssot-v1.1.md} +84 -29
- package/assets/starter/docs/reference/documentation-map.md +2 -2
- package/cli-guide.md +8 -4
- package/dist/cli.js +649 -209
- package/package.json +3 -3
- package/assets/starter/CLAUDE.template.md +0 -6
package/dist/cli.js
CHANGED
|
@@ -565,7 +565,7 @@ import {
|
|
|
565
565
|
lstatSync as lstatSync3,
|
|
566
566
|
mkdirSync as mkdirSync2,
|
|
567
567
|
readlinkSync as readlinkSync2,
|
|
568
|
-
realpathSync,
|
|
568
|
+
realpathSync as realpathSync2,
|
|
569
569
|
symlinkSync,
|
|
570
570
|
unlinkSync,
|
|
571
571
|
writeFileSync
|
|
@@ -573,7 +573,7 @@ import {
|
|
|
573
573
|
import { dirname as dirname4, join as join8, relative as relative4, resolve as resolve2 } from "node:path";
|
|
574
574
|
|
|
575
575
|
// src/asset-targets/install-plan.ts
|
|
576
|
-
import { existsSync as existsSync7, lstatSync as lstatSync2, readFileSync as readFileSync4, readlinkSync } from "node:fs";
|
|
576
|
+
import { existsSync as existsSync7, lstatSync as lstatSync2, readFileSync as readFileSync4, readlinkSync, realpathSync } from "node:fs";
|
|
577
577
|
import { basename, dirname as dirname3, join as join7, resolve } from "node:path";
|
|
578
578
|
function createAssetInstallPlan(options) {
|
|
579
579
|
const placement = options.placement ?? "registry";
|
|
@@ -589,10 +589,19 @@ function createAssetInstallPlan(options) {
|
|
|
589
589
|
return asset;
|
|
590
590
|
});
|
|
591
591
|
const lockEntries = createAgentAssetLockEntries(options.registry, options.agentAssetsDir, assetIds);
|
|
592
|
-
const
|
|
592
|
+
const managedLock = readManagedLock(options.targetDir);
|
|
593
|
+
const managedEntries = managedLock.entries;
|
|
593
594
|
const managedTargets = new Set(managedEntries.map((entry) => entry.targetPath));
|
|
595
|
+
const legacyAdoptions = createLegacyClaudeAdoptions({
|
|
596
|
+
targetDir: options.targetDir,
|
|
597
|
+
agentAssetsDir: options.agentAssetsDir,
|
|
598
|
+
managedLock,
|
|
599
|
+
assets,
|
|
600
|
+
host: options.host,
|
|
601
|
+
placement
|
|
602
|
+
});
|
|
594
603
|
const assetActions = assets.map(
|
|
595
|
-
(asset) => createAssetAction(asset, options.agentAssetsDir, options.targetDir, options.host, placement, managedTargets)
|
|
604
|
+
(asset) => legacyAdoptions.byAssetId.get(asset.id) ?? createAssetAction(asset, options.agentAssetsDir, options.targetDir, options.host, placement, managedTargets)
|
|
596
605
|
);
|
|
597
606
|
const manifest = {
|
|
598
607
|
schemaVersion: 1,
|
|
@@ -635,7 +644,8 @@ function createAssetInstallPlan(options) {
|
|
|
635
644
|
options.targetDir,
|
|
636
645
|
options.agentAssetsDir,
|
|
637
646
|
managedEntries,
|
|
638
|
-
expectedTargetPaths
|
|
647
|
+
expectedTargetPaths,
|
|
648
|
+
legacyAdoptions.consumedTargetPaths
|
|
639
649
|
);
|
|
640
650
|
return {
|
|
641
651
|
schemaVersion: 1,
|
|
@@ -693,15 +703,9 @@ function createAssetAction(asset, agentAssetsDir, targetDir, host, placement, ma
|
|
|
693
703
|
targetPath
|
|
694
704
|
};
|
|
695
705
|
}
|
|
696
|
-
function resolveHostTargetPath(asset,
|
|
706
|
+
function resolveHostTargetPath(asset, _host, placement) {
|
|
697
707
|
if (asset.kind === "skill") {
|
|
698
708
|
const effectivePlacement = resolveSkillPlacement(asset, placement);
|
|
699
|
-
if (host === "claude-code") {
|
|
700
|
-
if (placement === "manual") {
|
|
701
|
-
throw new Error("Manual skill placement is only supported for .agents hosts");
|
|
702
|
-
}
|
|
703
|
-
return `.claude/skills/${basename(asset.sourcePath)}`;
|
|
704
|
-
}
|
|
705
709
|
if (effectivePlacement === "manual") {
|
|
706
710
|
return `.agents/manual-skills/${basename(asset.sourcePath)}`;
|
|
707
711
|
}
|
|
@@ -725,22 +729,84 @@ function createDirectoryActions(actions) {
|
|
|
725
729
|
}
|
|
726
730
|
return [...directories].sort().map((targetPath) => ({ type: "create-dir", targetPath }));
|
|
727
731
|
}
|
|
728
|
-
function
|
|
732
|
+
function readManagedLock(targetDir) {
|
|
729
733
|
const lockfilePath = join7(targetDir, ".pro-gov/assets.lock.json");
|
|
730
|
-
if (!existsSync7(lockfilePath)) return [];
|
|
734
|
+
if (!existsSync7(lockfilePath)) return { entries: [] };
|
|
731
735
|
try {
|
|
732
736
|
const lockfile = JSON.parse(readFileSync4(lockfilePath, "utf8"));
|
|
733
|
-
return
|
|
734
|
-
|
|
735
|
-
|
|
737
|
+
return {
|
|
738
|
+
host: typeof lockfile.host === "string" ? lockfile.host : void 0,
|
|
739
|
+
entries: (lockfile.assets ?? []).filter(
|
|
740
|
+
(entry) => typeof entry.id === "string" && typeof entry.sourcePath === "string" && typeof entry.targetPath === "string"
|
|
741
|
+
)
|
|
742
|
+
};
|
|
736
743
|
} catch {
|
|
737
|
-
return [];
|
|
744
|
+
return { entries: [] };
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
function createLegacyClaudeAdoptions(options) {
|
|
748
|
+
const byAssetId = /* @__PURE__ */ new Map();
|
|
749
|
+
const consumedTargetPaths = /* @__PURE__ */ new Set();
|
|
750
|
+
const legacyEntries = options.managedLock.entries.filter((entry) => isLegacyClaudeSkillTargetPath(entry.targetPath));
|
|
751
|
+
if (legacyEntries.length === 0) return { byAssetId, consumedTargetPaths };
|
|
752
|
+
if (options.managedLock.host !== "claude-code" || options.host !== "claude-code") {
|
|
753
|
+
throw new Error("Legacy Claude skill targets require a claude-code lock and migration plan.");
|
|
754
|
+
}
|
|
755
|
+
const assetsById = new Map(options.assets.map((asset) => [asset.id, asset]));
|
|
756
|
+
const seenAssetIds = /* @__PURE__ */ new Set();
|
|
757
|
+
for (const entry of legacyEntries) {
|
|
758
|
+
if (seenAssetIds.has(entry.id)) {
|
|
759
|
+
throw new Error(`Duplicate legacy Claude lock entry: ${entry.id}`);
|
|
760
|
+
}
|
|
761
|
+
seenAssetIds.add(entry.id);
|
|
762
|
+
const asset = assetsById.get(entry.id);
|
|
763
|
+
if (!asset) {
|
|
764
|
+
throw new Error(`Legacy Claude asset ${entry.id} must be adopted before it can be removed.`);
|
|
765
|
+
}
|
|
766
|
+
const match = entry.targetPath.match(/^\.claude\/skills\/([^/]+)$/);
|
|
767
|
+
const skillName = match?.[1];
|
|
768
|
+
if (!skillName || asset.kind !== "skill" || entry.sourcePath !== asset.sourcePath || skillName !== basename(entry.sourcePath) || resolveHostTargetPath(asset, options.host, options.placement) !== `.agents/skills/${skillName}`) {
|
|
769
|
+
throw new Error(`Legacy Claude lock entry cannot be safely normalized: ${entry.targetPath}`);
|
|
770
|
+
}
|
|
771
|
+
const targetPath = `.agents/skills/${skillName}`;
|
|
772
|
+
const targetAbsolutePath = join7(options.targetDir, targetPath);
|
|
773
|
+
const compatibilityRootPath = join7(options.targetDir, ".claude/skills");
|
|
774
|
+
const canonicalRootPath = join7(options.targetDir, ".agents/skills");
|
|
775
|
+
if (!lstatSync2(canonicalRootPath).isDirectory() || !lstatSync2(compatibilityRootPath).isSymbolicLink() || readlinkSync(compatibilityRootPath) !== "../.agents/skills" || realpathSync(compatibilityRootPath) !== realpathSync(canonicalRootPath)) {
|
|
776
|
+
throw new Error(`Legacy Claude compatibility root is not the exact canonical alias for ${entry.id}.`);
|
|
777
|
+
}
|
|
778
|
+
if (!pathExistsEvenIfDanglingSymlink2(targetAbsolutePath)) {
|
|
779
|
+
consumedTargetPaths.add(entry.targetPath);
|
|
780
|
+
continue;
|
|
781
|
+
}
|
|
782
|
+
const targetStat = lstatSync2(targetAbsolutePath);
|
|
783
|
+
const legacyAbsolutePath = join7(options.targetDir, entry.targetPath);
|
|
784
|
+
const legacyStat = lstatSync2(legacyAbsolutePath);
|
|
785
|
+
const expectedSourcePath = join7(options.agentAssetsDir, entry.sourcePath);
|
|
786
|
+
if (!targetStat.isSymbolicLink() || !legacyStat.isSymbolicLink() || targetStat.dev !== legacyStat.dev || targetStat.ino !== legacyStat.ino || realpathSync(targetAbsolutePath) !== realpathSync(expectedSourcePath)) {
|
|
787
|
+
throw new Error(`Legacy Claude skill target cannot be safely adopted: ${entry.targetPath}`);
|
|
788
|
+
}
|
|
789
|
+
const action = {
|
|
790
|
+
type: "adopt-symlink",
|
|
791
|
+
assetId: entry.id,
|
|
792
|
+
sourcePath: expectedSourcePath,
|
|
793
|
+
targetPath,
|
|
794
|
+
legacyTargetPath: entry.targetPath,
|
|
795
|
+
compatibilityRootPath: ".claude/skills",
|
|
796
|
+
expectedCompatibilityRawTarget: "../.agents/skills",
|
|
797
|
+
expectedDevice: targetStat.dev,
|
|
798
|
+
expectedInode: targetStat.ino
|
|
799
|
+
};
|
|
800
|
+
byAssetId.set(entry.id, action);
|
|
801
|
+
consumedTargetPaths.add(entry.targetPath);
|
|
738
802
|
}
|
|
803
|
+
return { byAssetId, consumedTargetPaths };
|
|
739
804
|
}
|
|
740
|
-
function createRemovalActions(targetDir, agentAssetsDir, managedEntries, expectedTargetPaths) {
|
|
805
|
+
function createRemovalActions(targetDir, agentAssetsDir, managedEntries, expectedTargetPaths, consumedTargetPaths) {
|
|
741
806
|
const actions = [];
|
|
742
807
|
for (const entry of managedEntries) {
|
|
743
808
|
if (expectedTargetPaths.has(entry.targetPath)) continue;
|
|
809
|
+
if (consumedTargetPaths.has(entry.targetPath)) continue;
|
|
744
810
|
if (!isManagedAssetTargetPath(entry.targetPath)) {
|
|
745
811
|
throw new Error(`Refusing to remove managed asset outside supported roots: ${entry.targetPath}`);
|
|
746
812
|
}
|
|
@@ -765,13 +831,10 @@ function createRemovalActions(targetDir, agentAssetsDir, managedEntries, expecte
|
|
|
765
831
|
return actions.sort((a, b) => a.targetPath.localeCompare(b.targetPath));
|
|
766
832
|
}
|
|
767
833
|
function isManagedAssetTargetPath(path) {
|
|
768
|
-
return [
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
".pro-gov/agent-assets/rules/",
|
|
773
|
-
".pro-gov/agent-assets/commands/"
|
|
774
|
-
].some((prefix) => path.startsWith(prefix));
|
|
834
|
+
return /^(?:\.agents\/(?:skills|manual-skills)|\.pro-gov\/agent-assets\/(?:rules|commands))\/[^/.][^/]*$/.test(path);
|
|
835
|
+
}
|
|
836
|
+
function isLegacyClaudeSkillTargetPath(path) {
|
|
837
|
+
return /^\.claude\/skills\/[^/.][^/]*$/.test(path);
|
|
775
838
|
}
|
|
776
839
|
function pathExistsEvenIfDanglingSymlink2(path) {
|
|
777
840
|
try {
|
|
@@ -785,6 +848,9 @@ function pathExistsEvenIfDanglingSymlink2(path) {
|
|
|
785
848
|
// src/asset-targets/apply.ts
|
|
786
849
|
function applyAssetInstallPlan(plan) {
|
|
787
850
|
const appliedActions = [];
|
|
851
|
+
for (const action of plan.actions) {
|
|
852
|
+
if (action.type === "adopt-symlink") validateAdoptedSymlink(plan.targetDir, action);
|
|
853
|
+
}
|
|
788
854
|
for (const action of plan.actions) {
|
|
789
855
|
applyAction(plan.targetDir, action);
|
|
790
856
|
appliedActions.push(action.type);
|
|
@@ -797,6 +863,7 @@ function applyAction(targetDir, action) {
|
|
|
797
863
|
removeManagedSymlink(targetAbsolutePath, action);
|
|
798
864
|
return;
|
|
799
865
|
}
|
|
866
|
+
if (action.type === "adopt-symlink") return;
|
|
800
867
|
if (action.type === "create-dir") {
|
|
801
868
|
mkdirSync2(targetAbsolutePath, { recursive: true });
|
|
802
869
|
return;
|
|
@@ -808,7 +875,7 @@ function applyAction(targetDir, action) {
|
|
|
808
875
|
}
|
|
809
876
|
mkdirSync2(dirname4(targetAbsolutePath), { recursive: true });
|
|
810
877
|
const sourceAbsolutePath = resolve2(action.sourcePath);
|
|
811
|
-
const symlinkTarget = relative4(
|
|
878
|
+
const symlinkTarget = relative4(realpathSync2(dirname4(targetAbsolutePath)), realpathSync2(sourceAbsolutePath)) || ".";
|
|
812
879
|
if (action.type === "symlink") {
|
|
813
880
|
if (pathExistsEvenIfDanglingSymlink3(targetAbsolutePath)) {
|
|
814
881
|
throw new Error(`Refusing to overwrite unmanaged target: ${action.targetPath}`);
|
|
@@ -827,6 +894,23 @@ function applyAction(targetDir, action) {
|
|
|
827
894
|
unlinkSync(targetAbsolutePath);
|
|
828
895
|
symlinkSync(symlinkTarget, targetAbsolutePath);
|
|
829
896
|
}
|
|
897
|
+
function validateAdoptedSymlink(targetDir, action) {
|
|
898
|
+
if (!isManagedAssetTargetPath(action.targetPath) || !isLegacyClaudeSkillTargetPath(action.legacyTargetPath) || action.compatibilityRootPath !== ".claude/skills" || action.expectedCompatibilityRawTarget !== "../.agents/skills") {
|
|
899
|
+
throw new Error(`Refusing unsafe legacy Claude adoption: ${action.legacyTargetPath}`);
|
|
900
|
+
}
|
|
901
|
+
const canonicalRootPath = join8(targetDir, ".agents/skills");
|
|
902
|
+
const compatibilityRootPath = join8(targetDir, action.compatibilityRootPath);
|
|
903
|
+
const targetAbsolutePath = join8(targetDir, action.targetPath);
|
|
904
|
+
const legacyAbsolutePath = join8(targetDir, action.legacyTargetPath);
|
|
905
|
+
if (!lstatSync3(canonicalRootPath).isDirectory() || !lstatSync3(compatibilityRootPath).isSymbolicLink() || readlinkSync2(compatibilityRootPath) !== action.expectedCompatibilityRawTarget || realpathSync2(compatibilityRootPath) !== realpathSync2(canonicalRootPath)) {
|
|
906
|
+
throw new Error(`Legacy Claude compatibility alias changed before apply: ${action.compatibilityRootPath}`);
|
|
907
|
+
}
|
|
908
|
+
const targetStat = lstatSync3(targetAbsolutePath);
|
|
909
|
+
const legacyStat = lstatSync3(legacyAbsolutePath);
|
|
910
|
+
if (!targetStat.isSymbolicLink() || !legacyStat.isSymbolicLink() || targetStat.dev !== legacyStat.dev || targetStat.ino !== legacyStat.ino || targetStat.dev !== action.expectedDevice || targetStat.ino !== action.expectedInode || realpathSync2(targetAbsolutePath) !== realpathSync2(action.sourcePath)) {
|
|
911
|
+
throw new Error(`Legacy Claude skill target changed before apply: ${action.targetPath}`);
|
|
912
|
+
}
|
|
913
|
+
}
|
|
830
914
|
function removeManagedSymlink(targetAbsolutePath, action) {
|
|
831
915
|
if (!isManagedAssetTargetPath(action.targetPath)) {
|
|
832
916
|
throw new Error(`Refusing to remove managed asset outside supported roots: ${action.targetPath}`);
|
|
@@ -1014,18 +1098,14 @@ function checkHostFolder(host, kind, targetPath, id) {
|
|
|
1014
1098
|
return void 0;
|
|
1015
1099
|
}
|
|
1016
1100
|
function expectedSkillTargetPrefixes(host) {
|
|
1017
|
-
if (host === "claude-code"
|
|
1018
|
-
if (host === "codex" || host === "gemini-cli" || host === "antigravity") {
|
|
1101
|
+
if (host === "codex" || host === "claude-code" || host === "gemini-cli" || host === "antigravity") {
|
|
1019
1102
|
return [".agents/skills/", ".agents/manual-skills/"];
|
|
1020
1103
|
}
|
|
1021
1104
|
return void 0;
|
|
1022
1105
|
}
|
|
1023
1106
|
function expectedRegistrySkillTargetPath(host, sourcePath, placement) {
|
|
1024
1107
|
const skillName = basename2(sourcePath);
|
|
1025
|
-
if (host === "claude-code") {
|
|
1026
|
-
return placement === "manual" ? void 0 : `.claude/skills/${skillName}`;
|
|
1027
|
-
}
|
|
1028
|
-
if (host === "codex" || host === "gemini-cli" || host === "antigravity") {
|
|
1108
|
+
if (host === "codex" || host === "claude-code" || host === "gemini-cli" || host === "antigravity") {
|
|
1029
1109
|
return placement === "manual" ? `.agents/manual-skills/${skillName}` : `.agents/skills/${skillName}`;
|
|
1030
1110
|
}
|
|
1031
1111
|
return void 0;
|
|
@@ -1653,12 +1733,14 @@ import { existsSync as existsSync12 } from "node:fs";
|
|
|
1653
1733
|
import { createRequire } from "node:module";
|
|
1654
1734
|
import { dirname as dirname6, join as join12 } from "node:path";
|
|
1655
1735
|
var REQUIRED_ASSETS = [
|
|
1736
|
+
"starter/.agents/skills/.gitkeep",
|
|
1656
1737
|
"starter/AGENTS.template.md",
|
|
1657
|
-
"starter/docs/governance/ssot-v1.
|
|
1658
|
-
"starter/docs/governance/agents-routing/engineering-runtime-v1.
|
|
1659
|
-
"starter/docs/governance/agents-routing/doc-only-v1.
|
|
1738
|
+
"starter/docs/governance/ssot-v1.1.md",
|
|
1739
|
+
"starter/docs/governance/agents-routing/engineering-runtime-v1.1.md",
|
|
1740
|
+
"starter/docs/governance/agents-routing/doc-only-v1.1.md",
|
|
1660
1741
|
"profiles/engineering-runtime/profile.md",
|
|
1661
|
-
"profiles/doc-only/profile.md"
|
|
1742
|
+
"profiles/doc-only/profile.md",
|
|
1743
|
+
"docs/reference/adoption/migration-v1.1.md"
|
|
1662
1744
|
];
|
|
1663
1745
|
function runDoctor(_args) {
|
|
1664
1746
|
const assets = listAssets();
|
|
@@ -1709,40 +1791,46 @@ function resolveDocGovDependencyCli() {
|
|
|
1709
1791
|
}
|
|
1710
1792
|
|
|
1711
1793
|
// src/commands/init.ts
|
|
1712
|
-
import {
|
|
1794
|
+
import { lstatSync as lstatSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync8, symlinkSync as symlinkSync2, writeFileSync as writeFileSync3 } from "node:fs";
|
|
1713
1795
|
import { basename as basename3, dirname as dirname7, join as join13 } from "node:path";
|
|
1714
1796
|
|
|
1715
1797
|
// src/commands/shared.ts
|
|
1716
1798
|
function planStarterFiles(profile) {
|
|
1717
|
-
|
|
1799
|
+
const files = listAssets().flatMap((asset) => {
|
|
1718
1800
|
const targetPath = starterTargetPath(asset.path);
|
|
1719
1801
|
if (!targetPath) return [];
|
|
1720
1802
|
if (profile && isOtherProfileRouting(targetPath, profile)) return [];
|
|
1721
1803
|
return [
|
|
1722
1804
|
{
|
|
1805
|
+
kind: "file",
|
|
1723
1806
|
sourcePath: asset.path,
|
|
1724
1807
|
targetPath,
|
|
1725
1808
|
absoluteSourcePath: asset.absolutePath,
|
|
1726
1809
|
ownership: classifyOwnership(targetPath)
|
|
1727
1810
|
}
|
|
1728
1811
|
];
|
|
1729
|
-
})
|
|
1812
|
+
});
|
|
1813
|
+
const compatibilityEntries = [
|
|
1814
|
+
{ kind: "directory", targetPath: ".agents/skills", ownership: "shared" },
|
|
1815
|
+
{ kind: "symlink", targetPath: "CLAUDE.md", linkTarget: "AGENTS.md", ownership: "shared" },
|
|
1816
|
+
{ kind: "symlink", targetPath: ".claude/skills", linkTarget: "../.agents/skills", ownership: "shared" }
|
|
1817
|
+
];
|
|
1818
|
+
return [...files, ...compatibilityEntries].sort((a, b) => a.targetPath.localeCompare(b.targetPath));
|
|
1730
1819
|
}
|
|
1731
1820
|
function classifyOwnership(targetPath) {
|
|
1732
1821
|
if (targetPath === "lefthook.yml" || targetPath === ".github/workflows/docs-check.yml") {
|
|
1733
1822
|
return "optional-guardrail";
|
|
1734
1823
|
}
|
|
1735
|
-
if (targetPath === "AGENTS.md" || targetPath === "
|
|
1824
|
+
if (targetPath === "AGENTS.md" || targetPath === "docs/policy/best-practice-for-this-project.md" || targetPath === "docs/reference/documentation-map.md" || targetPath === "docs/reference/execution/current-work.md") {
|
|
1736
1825
|
return "project-local-seed";
|
|
1737
1826
|
}
|
|
1738
1827
|
return "shared";
|
|
1739
1828
|
}
|
|
1740
1829
|
function isOtherProfileRouting(targetPath, profile) {
|
|
1741
|
-
return targetPath.startsWith("docs/governance/agents-routing/") && targetPath !== `docs/governance/agents-routing/${profile}-v1.
|
|
1830
|
+
return targetPath.startsWith("docs/governance/agents-routing/") && targetPath !== `docs/governance/agents-routing/${profile}-v1.1.md`;
|
|
1742
1831
|
}
|
|
1743
1832
|
function starterTargetPath(sourcePath) {
|
|
1744
1833
|
if (sourcePath === "starter/AGENTS.template.md") return "AGENTS.md";
|
|
1745
|
-
if (sourcePath === "starter/CLAUDE.template.md") return "CLAUDE.md";
|
|
1746
1834
|
if (sourcePath === "starter/lefthook.template.yml") return "lefthook.yml";
|
|
1747
1835
|
if (!sourcePath.startsWith("starter/")) return null;
|
|
1748
1836
|
return sourcePath.slice("starter/".length);
|
|
@@ -1772,13 +1860,20 @@ function runInit(args) {
|
|
|
1772
1860
|
console.log("");
|
|
1773
1861
|
console.log("Planned starter files:");
|
|
1774
1862
|
for (const file of files) {
|
|
1775
|
-
console.log(` ${file.targetPath} <- ${file.sourcePath}`);
|
|
1863
|
+
if (file.kind === "file") console.log(` ${file.targetPath} <- ${file.sourcePath}`);
|
|
1864
|
+
if (file.kind === "directory") console.log(` ${file.targetPath} <- directory`);
|
|
1865
|
+
if (file.kind === "symlink") console.log(` ${file.targetPath} <- symlink:${file.linkTarget}`);
|
|
1776
1866
|
}
|
|
1777
1867
|
return 0;
|
|
1778
1868
|
}
|
|
1779
1869
|
function applyStarterFiles(files, profile) {
|
|
1780
1870
|
const root = process.cwd();
|
|
1781
|
-
const conflicts = files.filter((file) =>
|
|
1871
|
+
const conflicts = files.filter((file) => {
|
|
1872
|
+
const targetPath = join13(root, file.targetPath);
|
|
1873
|
+
const stat = safeLstat(targetPath);
|
|
1874
|
+
if (!stat) return false;
|
|
1875
|
+
return file.kind !== "directory" || !stat.isDirectory();
|
|
1876
|
+
});
|
|
1782
1877
|
if (conflicts.length > 0) {
|
|
1783
1878
|
console.error("pro-gov init is refusing to overwrite existing project files:");
|
|
1784
1879
|
for (const file of conflicts) console.error(` ${file.targetPath}`);
|
|
@@ -1788,6 +1883,14 @@ function applyStarterFiles(files, profile) {
|
|
|
1788
1883
|
for (const file of files) {
|
|
1789
1884
|
const targetPath = join13(root, file.targetPath);
|
|
1790
1885
|
mkdirSync4(dirname7(targetPath), { recursive: true });
|
|
1886
|
+
if (file.kind === "directory") {
|
|
1887
|
+
mkdirSync4(targetPath, { recursive: true });
|
|
1888
|
+
continue;
|
|
1889
|
+
}
|
|
1890
|
+
if (file.kind === "symlink") {
|
|
1891
|
+
symlinkSync2(file.linkTarget, targetPath);
|
|
1892
|
+
continue;
|
|
1893
|
+
}
|
|
1791
1894
|
const source = readFileSync8(file.absoluteSourcePath);
|
|
1792
1895
|
const content = file.targetPath === "AGENTS.md" ? renderAgentsTemplate(source.toString("utf8"), basename3(root), profile) : source;
|
|
1793
1896
|
writeFileSync3(targetPath, content);
|
|
@@ -1800,9 +1903,16 @@ function applyStarterFiles(files, profile) {
|
|
|
1800
1903
|
return 0;
|
|
1801
1904
|
}
|
|
1802
1905
|
function renderAgentsTemplate(template, projectName, profile) {
|
|
1803
|
-
const selectedRoute = `docs/governance/agents-routing/${profile}-v1.
|
|
1906
|
+
const selectedRoute = `docs/governance/agents-routing/${profile}-v1.1.md`;
|
|
1804
1907
|
return template.replace("# PROJECT_NAME AI Router", `# ${projectName} AI Router`).replace("`PROFILE_NAME`", `\`${profile}\``).replace("`PROFILE_ROUTE`", `\`${selectedRoute}\``).replace(/\n{3,}/g, "\n\n");
|
|
1805
1908
|
}
|
|
1909
|
+
function safeLstat(path) {
|
|
1910
|
+
try {
|
|
1911
|
+
return lstatSync5(path);
|
|
1912
|
+
} catch {
|
|
1913
|
+
return void 0;
|
|
1914
|
+
}
|
|
1915
|
+
}
|
|
1806
1916
|
function readFlag(args, flag) {
|
|
1807
1917
|
const index = args.indexOf(flag);
|
|
1808
1918
|
if (index === -1) return null;
|
|
@@ -1812,7 +1922,7 @@ function readFlag(args, flag) {
|
|
|
1812
1922
|
}
|
|
1813
1923
|
|
|
1814
1924
|
// src/learning/recall.ts
|
|
1815
|
-
import { existsSync as
|
|
1925
|
+
import { existsSync as existsSync13, readdirSync as readdirSync6, readFileSync as readFileSync9 } from "node:fs";
|
|
1816
1926
|
import { basename as basename4, join as join14, relative as relative5 } from "node:path";
|
|
1817
1927
|
function recallLearnings(root, options) {
|
|
1818
1928
|
const query = options.query.trim();
|
|
@@ -1837,7 +1947,7 @@ function loadLearningRecords(root) {
|
|
|
1837
1947
|
const recordsByTitle = /* @__PURE__ */ new Map();
|
|
1838
1948
|
for (const relativeDir of ["docs/reference/learnings", "docs/solutions"]) {
|
|
1839
1949
|
const learningDir = join14(root, relativeDir);
|
|
1840
|
-
if (!
|
|
1950
|
+
if (!existsSync13(learningDir)) continue;
|
|
1841
1951
|
for (const path of listMarkdownFiles(learningDir)) {
|
|
1842
1952
|
const record = readLearningRecord(root, path);
|
|
1843
1953
|
const key = record.title.trim().toLowerCase();
|
|
@@ -1845,7 +1955,7 @@ function loadLearningRecords(root) {
|
|
|
1845
1955
|
}
|
|
1846
1956
|
}
|
|
1847
1957
|
const conceptsPath = join14(root, "CONCEPTS.md");
|
|
1848
|
-
if (
|
|
1958
|
+
if (existsSync13(conceptsPath)) {
|
|
1849
1959
|
const record = readLearningRecord(root, conceptsPath);
|
|
1850
1960
|
recordsByTitle.set(`concepts:${record.title.toLowerCase()}`, record);
|
|
1851
1961
|
}
|
|
@@ -1947,7 +2057,7 @@ function cleanMarkdownLine(input) {
|
|
|
1947
2057
|
}
|
|
1948
2058
|
|
|
1949
2059
|
// src/learning/capture.ts
|
|
1950
|
-
import { existsSync as
|
|
2060
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync5, writeFileSync as writeFileSync4 } from "node:fs";
|
|
1951
2061
|
import { basename as basename5, join as join15, relative as relative6 } from "node:path";
|
|
1952
2062
|
function captureLearning(root, options) {
|
|
1953
2063
|
const title = options.title.trim();
|
|
@@ -2005,7 +2115,7 @@ function renderLearning(options) {
|
|
|
2005
2115
|
function uniquePath(dir, slug) {
|
|
2006
2116
|
let index = 1;
|
|
2007
2117
|
let candidate = join15(dir, `${slug}.md`);
|
|
2008
|
-
while (
|
|
2118
|
+
while (existsSync14(candidate)) {
|
|
2009
2119
|
index += 1;
|
|
2010
2120
|
candidate = join15(dir, `${slug}-${index}.md`);
|
|
2011
2121
|
}
|
|
@@ -2188,10 +2298,10 @@ function printUsage2() {
|
|
|
2188
2298
|
|
|
2189
2299
|
// src/commands/lens.ts
|
|
2190
2300
|
import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync6 } from "node:fs";
|
|
2191
|
-
import { dirname as
|
|
2301
|
+
import { dirname as dirname10 } from "node:path";
|
|
2192
2302
|
|
|
2193
2303
|
// src/lens/audit.ts
|
|
2194
|
-
import { existsSync as
|
|
2304
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync6, readFileSync as readFileSync10, writeFileSync as writeFileSync5 } from "node:fs";
|
|
2195
2305
|
import { basename as basename6, dirname as dirname8, join as join16 } from "node:path";
|
|
2196
2306
|
var REQUIRED_ARTIFACTS = [
|
|
2197
2307
|
"manifest.md",
|
|
@@ -2223,7 +2333,7 @@ function createProjectLensAuditPackage(targetDir, auditDir) {
|
|
|
2223
2333
|
}
|
|
2224
2334
|
function checkProjectLensAuditPackage(auditDir, options = {}) {
|
|
2225
2335
|
const contractPath = join16(auditDir, "audit.contract.json");
|
|
2226
|
-
if (!
|
|
2336
|
+
if (!existsSync15(contractPath)) {
|
|
2227
2337
|
return {
|
|
2228
2338
|
ok: false,
|
|
2229
2339
|
auditDir,
|
|
@@ -2271,7 +2381,7 @@ function checkProjectLensAuditPackage(auditDir, options = {}) {
|
|
|
2271
2381
|
}
|
|
2272
2382
|
for (const artifactPath of REQUIRED_ARTIFACTS) {
|
|
2273
2383
|
const absolutePath = join16(auditDir, artifactPath);
|
|
2274
|
-
if (!
|
|
2384
|
+
if (!existsSync15(absolutePath)) {
|
|
2275
2385
|
issues.push({ type: "missing-required-artifact", path: artifactPath });
|
|
2276
2386
|
continue;
|
|
2277
2387
|
}
|
|
@@ -2471,6 +2581,9 @@ function formatProjectLensInspection(report) {
|
|
|
2471
2581
|
`excluded-files: ${report.scanScope.excludedFileCount}`,
|
|
2472
2582
|
`ai-entry-files: ${formatList(report.aiEntryFiles)}`,
|
|
2473
2583
|
`ai-config-files: ${formatList(report.aiConfigFiles)}`,
|
|
2584
|
+
`ai-host-ssot: ${report.hostSsot.compliant ? "compliant" : "non-compliant"}`,
|
|
2585
|
+
`claude-entry-link: ${formatLink(report.hostSsot.claudeEntry)}`,
|
|
2586
|
+
`claude-skills-link: ${formatLink(report.hostSsot.claudeSkills)}`,
|
|
2474
2587
|
`package-scripts: ${formatList(report.packageJson?.scripts ?? [])}`,
|
|
2475
2588
|
`dependencies: ${formatList(report.packageJson?.dependencies ?? [])}`,
|
|
2476
2589
|
`dev-dependencies: ${formatList(report.packageJson?.devDependencies ?? [])}`,
|
|
@@ -2503,6 +2616,15 @@ function renderProjectLensMarkdownReport(report) {
|
|
|
2503
2616
|
"",
|
|
2504
2617
|
bulletList(report.aiConfigFiles),
|
|
2505
2618
|
"",
|
|
2619
|
+
"## AI Host SSOT",
|
|
2620
|
+
"",
|
|
2621
|
+
`- Status: ${report.hostSsot.compliant ? "compliant" : "non-compliant"}`,
|
|
2622
|
+
`- Canonical entry: \`${report.hostSsot.agentsEntry.path}\` (${report.hostSsot.agentsEntry.status})`,
|
|
2623
|
+
`- Claude entry: ${formatLink(report.hostSsot.claudeEntry)}`,
|
|
2624
|
+
`- Canonical skills: \`${report.hostSsot.canonicalSkills.path}\` (${report.hostSsot.canonicalSkills.status})`,
|
|
2625
|
+
`- Claude skills: ${formatLink(report.hostSsot.claudeSkills)}`,
|
|
2626
|
+
`- Issues: ${formatList(report.hostSsot.issues)}`,
|
|
2627
|
+
"",
|
|
2506
2628
|
"## Package",
|
|
2507
2629
|
"",
|
|
2508
2630
|
`- Scripts: ${formatList(report.packageJson?.scripts ?? [])}`,
|
|
@@ -2539,11 +2661,141 @@ function bulletList(values) {
|
|
|
2539
2661
|
if (values.length === 0) return "- none";
|
|
2540
2662
|
return values.map((value) => `- \`${value}\``).join("\n");
|
|
2541
2663
|
}
|
|
2664
|
+
function formatLink(link) {
|
|
2665
|
+
const rawTarget = link.rawTarget ? ` -> \`${link.rawTarget}\`` : "";
|
|
2666
|
+
const resolvedTarget = link.resolvedTarget ? `; resolved: \`${link.resolvedTarget}\`` : "";
|
|
2667
|
+
return `\`${link.path}\`${rawTarget} (${link.status}${resolvedTarget})`;
|
|
2668
|
+
}
|
|
2542
2669
|
|
|
2543
2670
|
// src/lens/scan.ts
|
|
2544
2671
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
2545
|
-
import { existsSync as
|
|
2546
|
-
import { join as
|
|
2672
|
+
import { existsSync as existsSync16, readdirSync as readdirSync7, readFileSync as readFileSync11, statSync as statSync3 } from "node:fs";
|
|
2673
|
+
import { join as join18, relative as relative7 } from "node:path";
|
|
2674
|
+
|
|
2675
|
+
// src/host-ssot.ts
|
|
2676
|
+
import {
|
|
2677
|
+
lstatSync as lstatSync6,
|
|
2678
|
+
readlinkSync as readlinkSync3,
|
|
2679
|
+
realpathSync as realpathSync3
|
|
2680
|
+
} from "node:fs";
|
|
2681
|
+
import { dirname as dirname9, isAbsolute as isAbsolute3, join as join17, resolve as resolve3 } from "node:path";
|
|
2682
|
+
function inspectProjectHostSsot(root) {
|
|
2683
|
+
const agentsEntry = inspectCanonicalPath(root, "AGENTS.md");
|
|
2684
|
+
const canonicalSkills = inspectCanonicalPath(root, ".agents/skills");
|
|
2685
|
+
const claudeEntry = inspectCompatibilityLink(root, "CLAUDE.md", "AGENTS.md");
|
|
2686
|
+
const claudeSkills = inspectCompatibilityLink(
|
|
2687
|
+
root,
|
|
2688
|
+
".claude/skills",
|
|
2689
|
+
"../.agents/skills"
|
|
2690
|
+
);
|
|
2691
|
+
const issues = [
|
|
2692
|
+
...agentsEntry.status === "file" ? [] : [`AGENTS.md must be the canonical project entry file; found ${agentsEntry.status}.`],
|
|
2693
|
+
...canonicalSkills.status === "directory" ? [] : [`.agents/skills must be the canonical project skill directory; found ${canonicalSkills.status}.`],
|
|
2694
|
+
...claudeEntry.compliant ? [] : [`CLAUDE.md must be the relative symlink AGENTS.md; found ${claudeEntry.status}.`],
|
|
2695
|
+
...claudeSkills.compliant ? [] : [`.claude/skills must be the relative symlink ../.agents/skills; found ${claudeSkills.status}.`]
|
|
2696
|
+
];
|
|
2697
|
+
return {
|
|
2698
|
+
agentsEntry,
|
|
2699
|
+
canonicalSkills,
|
|
2700
|
+
claudeEntry,
|
|
2701
|
+
claudeSkills,
|
|
2702
|
+
compliant: issues.length === 0,
|
|
2703
|
+
issues
|
|
2704
|
+
};
|
|
2705
|
+
}
|
|
2706
|
+
function inspectUserSkillsSsot(homeDir) {
|
|
2707
|
+
const canonicalSkills = inspectCanonicalPath(homeDir, ".agents/skills");
|
|
2708
|
+
const claudeSkills = inspectCompatibilityLink(
|
|
2709
|
+
homeDir,
|
|
2710
|
+
".claude/skills",
|
|
2711
|
+
"../.agents/skills"
|
|
2712
|
+
);
|
|
2713
|
+
const issues = [
|
|
2714
|
+
...canonicalSkills.status === "directory" ? [] : [`~/.agents/skills must be the canonical user skill directory; found ${canonicalSkills.status}.`],
|
|
2715
|
+
...claudeSkills.compliant ? [] : [`~/.claude/skills must be the relative symlink ../.agents/skills; found ${claudeSkills.status}.`]
|
|
2716
|
+
];
|
|
2717
|
+
return {
|
|
2718
|
+
canonicalSkills,
|
|
2719
|
+
claudeSkills,
|
|
2720
|
+
compliant: issues.length === 0,
|
|
2721
|
+
issues
|
|
2722
|
+
};
|
|
2723
|
+
}
|
|
2724
|
+
function inspectCanonicalPath(root, path) {
|
|
2725
|
+
const absolutePath = join17(root, path);
|
|
2726
|
+
const stat = safeLstat2(absolutePath);
|
|
2727
|
+
if (!stat) return { path, status: "missing" };
|
|
2728
|
+
if (stat.isSymbolicLink()) {
|
|
2729
|
+
try {
|
|
2730
|
+
realpathSync3(absolutePath);
|
|
2731
|
+
return { path, status: "symlink" };
|
|
2732
|
+
} catch {
|
|
2733
|
+
return { path, status: "dangling-symlink" };
|
|
2734
|
+
}
|
|
2735
|
+
}
|
|
2736
|
+
if (stat.isFile()) return { path, status: "file" };
|
|
2737
|
+
if (stat.isDirectory()) return { path, status: "directory" };
|
|
2738
|
+
return { path, status: "other" };
|
|
2739
|
+
}
|
|
2740
|
+
function inspectCompatibilityLink(root, path, expectedRawTarget) {
|
|
2741
|
+
const absolutePath = join17(root, path);
|
|
2742
|
+
const stat = safeLstat2(absolutePath);
|
|
2743
|
+
const base = { path, expectedRawTarget, compliant: false };
|
|
2744
|
+
if (!stat) return { ...base, status: "missing" };
|
|
2745
|
+
if (!stat.isSymbolicLink()) {
|
|
2746
|
+
if (stat.isFile()) return { ...base, status: "file" };
|
|
2747
|
+
if (stat.isDirectory()) return { ...base, status: "directory" };
|
|
2748
|
+
return { ...base, status: "other" };
|
|
2749
|
+
}
|
|
2750
|
+
const rawTarget = readlinkSync3(absolutePath);
|
|
2751
|
+
const resolvedTarget = resolve3(dirname9(absolutePath), rawTarget);
|
|
2752
|
+
const expectedPath = resolve3(dirname9(absolutePath), expectedRawTarget);
|
|
2753
|
+
const targetStat = safeLstat2(resolvedTarget);
|
|
2754
|
+
if (!targetStat) {
|
|
2755
|
+
return {
|
|
2756
|
+
...base,
|
|
2757
|
+
rawTarget,
|
|
2758
|
+
resolvedTarget,
|
|
2759
|
+
status: rawTarget === expectedRawTarget ? "target-missing" : "dangling-symlink"
|
|
2760
|
+
};
|
|
2761
|
+
}
|
|
2762
|
+
let targetMatches = false;
|
|
2763
|
+
try {
|
|
2764
|
+
targetMatches = realpathSync3(absolutePath) === realpathSync3(expectedPath);
|
|
2765
|
+
} catch {
|
|
2766
|
+
return { ...base, rawTarget, resolvedTarget, status: "dangling-symlink" };
|
|
2767
|
+
}
|
|
2768
|
+
if (!targetMatches) {
|
|
2769
|
+
return { ...base, rawTarget, resolvedTarget, status: "wrong-target" };
|
|
2770
|
+
}
|
|
2771
|
+
if (isAbsolute3(rawTarget)) {
|
|
2772
|
+
return { ...base, rawTarget, resolvedTarget, status: "absolute-symlink" };
|
|
2773
|
+
}
|
|
2774
|
+
if (rawTarget !== expectedRawTarget) {
|
|
2775
|
+
return {
|
|
2776
|
+
...base,
|
|
2777
|
+
rawTarget,
|
|
2778
|
+
resolvedTarget,
|
|
2779
|
+
status: "noncanonical-relative-symlink"
|
|
2780
|
+
};
|
|
2781
|
+
}
|
|
2782
|
+
return {
|
|
2783
|
+
...base,
|
|
2784
|
+
rawTarget,
|
|
2785
|
+
resolvedTarget,
|
|
2786
|
+
status: "compliant-relative-symlink",
|
|
2787
|
+
compliant: true
|
|
2788
|
+
};
|
|
2789
|
+
}
|
|
2790
|
+
function safeLstat2(path) {
|
|
2791
|
+
try {
|
|
2792
|
+
return lstatSync6(path);
|
|
2793
|
+
} catch {
|
|
2794
|
+
return void 0;
|
|
2795
|
+
}
|
|
2796
|
+
}
|
|
2797
|
+
|
|
2798
|
+
// src/lens/scan.ts
|
|
2547
2799
|
var ignoredDirectories = /* @__PURE__ */ new Set([
|
|
2548
2800
|
".git",
|
|
2549
2801
|
".next",
|
|
@@ -2569,22 +2821,23 @@ function scanProjectLensTarget(targetDir, options = {}) {
|
|
|
2569
2821
|
excludedFileCount: candidateFiles.length - files.length
|
|
2570
2822
|
},
|
|
2571
2823
|
aiEntryFiles: ["AGENTS.md", "CLAUDE.md"].filter(
|
|
2572
|
-
(file) =>
|
|
2824
|
+
(file) => existsSync16(join18(targetDir, file))
|
|
2573
2825
|
),
|
|
2574
2826
|
aiConfigFiles: [],
|
|
2827
|
+
hostSsot: inspectProjectHostSsot(targetDir),
|
|
2575
2828
|
packageJson,
|
|
2576
2829
|
docs: {
|
|
2577
|
-
hasDocsDirectory:
|
|
2830
|
+
hasDocsDirectory: existsSync16(join18(targetDir, "docs")),
|
|
2578
2831
|
markdownFileCount: markdownFiles.length,
|
|
2579
2832
|
governanceFiles: markdownFiles.filter((file) => file.startsWith("docs/governance/") || file.startsWith("docs/policy/")).sort()
|
|
2580
2833
|
},
|
|
2581
2834
|
git: readGitState(targetDir),
|
|
2582
|
-
largeFiles: files.map((file) => ({ path: file, bytes: statSync3(
|
|
2835
|
+
largeFiles: files.map((file) => ({ path: file, bytes: statSync3(join18(targetDir, file)).size })).filter((file) => file.bytes >= largeFileBytes).sort((a, b) => b.bytes - a.bytes || a.path.localeCompare(b.path)).slice(0, 25)
|
|
2583
2836
|
};
|
|
2584
2837
|
}
|
|
2585
2838
|
function readPackageJson(targetDir) {
|
|
2586
|
-
const packageJsonPath =
|
|
2587
|
-
if (!
|
|
2839
|
+
const packageJsonPath = join18(targetDir, "package.json");
|
|
2840
|
+
if (!existsSync16(packageJsonPath)) return void 0;
|
|
2588
2841
|
try {
|
|
2589
2842
|
const packageJson = JSON.parse(readFileSync11(packageJsonPath, "utf8"));
|
|
2590
2843
|
return {
|
|
@@ -2624,7 +2877,7 @@ function listProjectFiles(targetDir) {
|
|
|
2624
2877
|
"-z"
|
|
2625
2878
|
]);
|
|
2626
2879
|
if (gitFiles.ok) {
|
|
2627
|
-
return gitFiles.stdout.split("\0").filter(Boolean).map(toUnixPath4).filter((file) =>
|
|
2880
|
+
return gitFiles.stdout.split("\0").filter(Boolean).map(toUnixPath4).filter((file) => existsSync16(join18(targetDir, file))).sort();
|
|
2628
2881
|
}
|
|
2629
2882
|
const files = [];
|
|
2630
2883
|
collectFiles2(targetDir, targetDir, files);
|
|
@@ -2642,13 +2895,13 @@ function isFirstPartyEvidenceFile(file) {
|
|
|
2642
2895
|
return !excludedEvidencePrefixes.some((prefix) => file.startsWith(prefix));
|
|
2643
2896
|
}
|
|
2644
2897
|
function collectFiles2(rootDir, currentDir, files) {
|
|
2645
|
-
if (!
|
|
2898
|
+
if (!existsSync16(currentDir)) return;
|
|
2646
2899
|
for (const entry of readdirSync7(currentDir, { withFileTypes: true })) {
|
|
2647
2900
|
if (entry.isDirectory()) {
|
|
2648
2901
|
if (ignoredDirectories.has(entry.name)) continue;
|
|
2649
|
-
collectFiles2(rootDir,
|
|
2902
|
+
collectFiles2(rootDir, join18(currentDir, entry.name), files);
|
|
2650
2903
|
} else if (entry.isFile()) {
|
|
2651
|
-
files.push(toUnixPath4(relative7(rootDir,
|
|
2904
|
+
files.push(toUnixPath4(relative7(rootDir, join18(currentDir, entry.name))));
|
|
2652
2905
|
}
|
|
2653
2906
|
}
|
|
2654
2907
|
}
|
|
@@ -2700,7 +2953,7 @@ function runLensReport(args) {
|
|
|
2700
2953
|
}
|
|
2701
2954
|
const report = scanProjectLensTarget(options.value.targetDir);
|
|
2702
2955
|
const markdown = renderProjectLensMarkdownReport(report);
|
|
2703
|
-
mkdirSync7(
|
|
2956
|
+
mkdirSync7(dirname10(options.value.outPath), { recursive: true });
|
|
2704
2957
|
writeFileSync6(options.value.outPath, markdown);
|
|
2705
2958
|
console.log(`report: ${options.value.outPath}`);
|
|
2706
2959
|
return 0;
|
|
@@ -2804,12 +3057,12 @@ function printUsage3() {
|
|
|
2804
3057
|
}
|
|
2805
3058
|
|
|
2806
3059
|
// src/commands/portfolio.ts
|
|
2807
|
-
import { existsSync as
|
|
2808
|
-
import { join as
|
|
3060
|
+
import { existsSync as existsSync21 } from "node:fs";
|
|
3061
|
+
import { join as join22 } from "node:path";
|
|
2809
3062
|
|
|
2810
3063
|
// src/portfolio/manifest.ts
|
|
2811
|
-
import { existsSync as
|
|
2812
|
-
import { dirname as
|
|
3064
|
+
import { existsSync as existsSync17, readFileSync as readFileSync12 } from "node:fs";
|
|
3065
|
+
import { dirname as dirname11, isAbsolute as isAbsolute4, resolve as resolve4 } from "node:path";
|
|
2813
3066
|
function loadPortfolioManifest(configPath) {
|
|
2814
3067
|
let parsed;
|
|
2815
3068
|
try {
|
|
@@ -2825,7 +3078,7 @@ function loadPortfolioManifest(configPath) {
|
|
|
2825
3078
|
]
|
|
2826
3079
|
};
|
|
2827
3080
|
}
|
|
2828
|
-
const normalized = resolveManifestPaths(parsed,
|
|
3081
|
+
const normalized = resolveManifestPaths(parsed, dirname11(resolve4(configPath)));
|
|
2829
3082
|
const issues = validatePortfolioManifest(normalized);
|
|
2830
3083
|
return {
|
|
2831
3084
|
configPath,
|
|
@@ -2836,14 +3089,14 @@ function loadPortfolioManifest(configPath) {
|
|
|
2836
3089
|
function resolveManifestPaths(value, configDir) {
|
|
2837
3090
|
if (!isRecord(value)) return value;
|
|
2838
3091
|
const resolveEndpoint = (endpoint) => {
|
|
2839
|
-
if (!isRecord(endpoint) || typeof endpoint.path !== "string" ||
|
|
3092
|
+
if (!isRecord(endpoint) || typeof endpoint.path !== "string" || isAbsolute4(endpoint.path)) {
|
|
2840
3093
|
return endpoint;
|
|
2841
3094
|
}
|
|
2842
|
-
return { ...endpoint, path:
|
|
3095
|
+
return { ...endpoint, path: resolve4(configDir, endpoint.path) };
|
|
2843
3096
|
};
|
|
2844
3097
|
return {
|
|
2845
3098
|
...value,
|
|
2846
|
-
technologyGovernance: isRecord(value.technologyGovernance) && typeof value.technologyGovernance.strategySource === "string" && !
|
|
3099
|
+
technologyGovernance: isRecord(value.technologyGovernance) && typeof value.technologyGovernance.strategySource === "string" && !isAbsolute4(value.technologyGovernance.strategySource) ? { ...value.technologyGovernance, strategySource: resolve4(configDir, value.technologyGovernance.strategySource) } : value.technologyGovernance,
|
|
2847
3100
|
controlPlane: resolveEndpoint(value.controlPlane),
|
|
2848
3101
|
executionEngine: resolveEndpoint(value.executionEngine),
|
|
2849
3102
|
targets: Array.isArray(value.targets) ? value.targets.map(resolveEndpoint) : value.targets
|
|
@@ -2877,13 +3130,14 @@ function validatePortfolioManifest(value) {
|
|
|
2877
3130
|
validateAllowedFields(
|
|
2878
3131
|
value,
|
|
2879
3132
|
"root",
|
|
2880
|
-
["schemaVersion", "portfolioId", "technologyGovernance", "controlPlane", "executionEngine", "hostTooling", "targets"],
|
|
3133
|
+
["schemaVersion", "portfolioId", "technologyGovernance", "controlPlane", "executionEngine", "hostTooling", "specialistChecks", "targets"],
|
|
2881
3134
|
issues
|
|
2882
3135
|
);
|
|
2883
3136
|
const technologyCatalog = validateTechnologyGovernance(value.technologyGovernance, issues);
|
|
2884
3137
|
validateEndpoint(value.controlPlane, "controlPlane", issues, technologyCatalog);
|
|
2885
3138
|
validateEndpoint(value.executionEngine, "executionEngine", issues, technologyCatalog);
|
|
2886
3139
|
validateHostTooling(value.hostTooling, issues);
|
|
3140
|
+
validateSpecialistChecks(value.specialistChecks, issues);
|
|
2887
3141
|
if (!Array.isArray(value.targets)) {
|
|
2888
3142
|
issues.push({
|
|
2889
3143
|
type: "invalid-field",
|
|
@@ -2981,7 +3235,7 @@ function validateEndpoint(value, field, issues, technologyCatalog) {
|
|
|
2981
3235
|
});
|
|
2982
3236
|
return;
|
|
2983
3237
|
}
|
|
2984
|
-
if (!
|
|
3238
|
+
if (!existsSync17(value.path)) {
|
|
2985
3239
|
issues.push({
|
|
2986
3240
|
type: "missing-path",
|
|
2987
3241
|
id: typeof value.id === "string" ? value.id : void 0,
|
|
@@ -3076,13 +3330,13 @@ function validateTechnologyGovernance(value, issues) {
|
|
|
3076
3330
|
return { technologies, projectTypes };
|
|
3077
3331
|
}
|
|
3078
3332
|
function isRepositoryRelativePath(value) {
|
|
3079
|
-
if (value.length === 0 ||
|
|
3333
|
+
if (value.length === 0 || isAbsolute4(value)) return false;
|
|
3080
3334
|
const segments = value.replaceAll("\\", "/").split("/");
|
|
3081
3335
|
return !segments.includes("..");
|
|
3082
3336
|
}
|
|
3083
3337
|
function isExactRepositoryRelativePath(value) {
|
|
3084
3338
|
if (value.length === 0) return false;
|
|
3085
|
-
if (
|
|
3339
|
+
if (isAbsolute4(value)) return false;
|
|
3086
3340
|
if (/^[A-Za-z]:[\\/]/.test(value) || value.startsWith("\\\\") || value.startsWith("//")) return false;
|
|
3087
3341
|
if (value.includes("\\")) return false;
|
|
3088
3342
|
const segments = value.split("/");
|
|
@@ -3214,15 +3468,45 @@ function validateHostTooling(value, issues) {
|
|
|
3214
3468
|
}
|
|
3215
3469
|
}
|
|
3216
3470
|
}
|
|
3471
|
+
function validateSpecialistChecks(value, issues) {
|
|
3472
|
+
if (value === void 0) return;
|
|
3473
|
+
if (!isRecord(value)) {
|
|
3474
|
+
issues.push({
|
|
3475
|
+
type: "invalid-field",
|
|
3476
|
+
field: "specialistChecks",
|
|
3477
|
+
message: "Portfolio specialistChecks must be an object."
|
|
3478
|
+
});
|
|
3479
|
+
return;
|
|
3480
|
+
}
|
|
3481
|
+
validateAllowedFields(value, "specialistChecks", ["devspace"], issues);
|
|
3482
|
+
if (value.devspace === void 0) return;
|
|
3483
|
+
if (!isRecord(value.devspace)) {
|
|
3484
|
+
issues.push({
|
|
3485
|
+
type: "invalid-field",
|
|
3486
|
+
field: "specialistChecks.devspace",
|
|
3487
|
+
message: "Portfolio specialistChecks.devspace must be an object."
|
|
3488
|
+
});
|
|
3489
|
+
return;
|
|
3490
|
+
}
|
|
3491
|
+
validateAllowedFields(value.devspace, "specialistChecks.devspace", ["expectedToolMode"], issues);
|
|
3492
|
+
if (value.devspace.expectedToolMode !== void 0 && !["minimal", "full", "codex"].includes(String(value.devspace.expectedToolMode))) {
|
|
3493
|
+
issues.push({
|
|
3494
|
+
type: "invalid-field",
|
|
3495
|
+
field: "specialistChecks.devspace.expectedToolMode",
|
|
3496
|
+
message: "DevSpace expectedToolMode must be minimal, full, or codex."
|
|
3497
|
+
});
|
|
3498
|
+
}
|
|
3499
|
+
}
|
|
3217
3500
|
function isRecord(value) {
|
|
3218
3501
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3219
3502
|
}
|
|
3220
3503
|
|
|
3221
3504
|
// src/portfolio/doctor.ts
|
|
3222
3505
|
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
3223
|
-
import { existsSync as
|
|
3506
|
+
import { existsSync as existsSync19, readFileSync as readFileSync14 } from "node:fs";
|
|
3224
3507
|
import { createRequire as createRequire2 } from "node:module";
|
|
3225
|
-
import {
|
|
3508
|
+
import { homedir } from "node:os";
|
|
3509
|
+
import { dirname as dirname12, join as join20 } from "node:path";
|
|
3226
3510
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
3227
3511
|
|
|
3228
3512
|
// src/host-tooling/inventory.ts
|
|
@@ -3313,13 +3597,13 @@ function isRecord2(value) {
|
|
|
3313
3597
|
}
|
|
3314
3598
|
|
|
3315
3599
|
// src/portfolio/asset-state.ts
|
|
3316
|
-
import { existsSync as
|
|
3317
|
-
import { join as
|
|
3600
|
+
import { existsSync as existsSync18, lstatSync as lstatSync7, readFileSync as readFileSync13 } from "node:fs";
|
|
3601
|
+
import { join as join19 } from "node:path";
|
|
3318
3602
|
function comparePortfolioAssetState(options) {
|
|
3319
3603
|
const expectedManifest = readPlanDocument(options.expectedPlan, ".pro-gov/assets.json");
|
|
3320
3604
|
const expectedLock = readPlanDocument(options.expectedPlan, ".pro-gov/assets.lock.json");
|
|
3321
|
-
const currentManifest = readJsonFile(
|
|
3322
|
-
const currentLock = readJsonFile(
|
|
3605
|
+
const currentManifest = readJsonFile(join19(options.targetDir, ".pro-gov/assets.json"));
|
|
3606
|
+
const currentLock = readJsonFile(join19(options.targetDir, ".pro-gov/assets.lock.json"));
|
|
3323
3607
|
const issues = [];
|
|
3324
3608
|
if (!sameStrings(currentManifest?.bundleIds, expectedManifest?.bundleIds)) {
|
|
3325
3609
|
issues.push({
|
|
@@ -3342,7 +3626,8 @@ function comparePortfolioAssetState(options) {
|
|
|
3342
3626
|
const expectedTargets = new Set((expectedLock?.assets ?? []).map((entry) => entry.targetPath));
|
|
3343
3627
|
for (const entry of currentLock?.assets ?? []) {
|
|
3344
3628
|
if (expectedTargets.has(entry.targetPath)) continue;
|
|
3345
|
-
|
|
3629
|
+
if (options.expectedPlan.actions.some((action) => action.type === "adopt-symlink" && action.assetId === entry.id && action.legacyTargetPath === entry.targetPath)) continue;
|
|
3630
|
+
const targetAbsolutePath = join19(options.targetDir, entry.targetPath);
|
|
3346
3631
|
if (!pathIsSymlink(targetAbsolutePath)) continue;
|
|
3347
3632
|
issues.push({
|
|
3348
3633
|
type: "orphaned-managed-symlink",
|
|
@@ -3364,7 +3649,7 @@ function readPlanDocument(plan, targetPath) {
|
|
|
3364
3649
|
}
|
|
3365
3650
|
}
|
|
3366
3651
|
function readJsonFile(path) {
|
|
3367
|
-
if (!
|
|
3652
|
+
if (!existsSync18(path)) return void 0;
|
|
3368
3653
|
try {
|
|
3369
3654
|
return JSON.parse(readFileSync13(path, "utf8"));
|
|
3370
3655
|
} catch {
|
|
@@ -3387,7 +3672,7 @@ function normalizeLock(lock) {
|
|
|
3387
3672
|
}
|
|
3388
3673
|
function pathIsSymlink(path) {
|
|
3389
3674
|
try {
|
|
3390
|
-
return
|
|
3675
|
+
return lstatSync7(path).isSymbolicLink();
|
|
3391
3676
|
} catch {
|
|
3392
3677
|
return false;
|
|
3393
3678
|
}
|
|
@@ -3409,17 +3694,19 @@ function inspectPortfolio(options) {
|
|
|
3409
3694
|
portfolioId: options.manifest.portfolioId,
|
|
3410
3695
|
expectedPackageVersions,
|
|
3411
3696
|
hostTooling,
|
|
3697
|
+
hostSsot: { userSkills: inspectUserSkillsSsot(options.homeDir ?? homedir()) },
|
|
3412
3698
|
targets
|
|
3413
3699
|
};
|
|
3414
3700
|
}
|
|
3415
3701
|
function inspectTarget(options) {
|
|
3416
3702
|
const { target } = options;
|
|
3703
|
+
const hostSsot = inspectProjectHostSsot(target.path);
|
|
3417
3704
|
const issues = [];
|
|
3418
|
-
const packageJson = readJson2(
|
|
3705
|
+
const packageJson = readJson2(join20(target.path, "package.json"));
|
|
3419
3706
|
const packages = {};
|
|
3420
3707
|
for (const packageName of ["@pieai/pro-gov", "@pieai/doc-gov"]) {
|
|
3421
3708
|
const declared = packageJson?.devDependencies?.[packageName] ?? packageJson?.dependencies?.[packageName];
|
|
3422
|
-
const installedPackage = readJson2(
|
|
3709
|
+
const installedPackage = readJson2(join20(target.path, "node_modules", packageName, "package.json"));
|
|
3423
3710
|
const installed = installedPackage?.version;
|
|
3424
3711
|
const expected = options.expectedPackageVersions[packageName];
|
|
3425
3712
|
packages[packageName] = { declared, installed, expected };
|
|
@@ -3473,7 +3760,7 @@ function inspectTarget(options) {
|
|
|
3473
3760
|
type: "asset-lock-drift",
|
|
3474
3761
|
message: error instanceof Error ? error.message : String(error)
|
|
3475
3762
|
});
|
|
3476
|
-
if (!
|
|
3763
|
+
if (!existsSync19(join20(target.path, ".pro-gov/assets.json"))) {
|
|
3477
3764
|
issues.push({ type: "bundle-drift", message: "Target asset manifest is missing." });
|
|
3478
3765
|
}
|
|
3479
3766
|
}
|
|
@@ -3484,19 +3771,20 @@ function inspectTarget(options) {
|
|
|
3484
3771
|
packages,
|
|
3485
3772
|
git: inspectGit(target.path),
|
|
3486
3773
|
checks,
|
|
3774
|
+
hostSsot,
|
|
3487
3775
|
issues: deduplicateIssues(issues)
|
|
3488
3776
|
};
|
|
3489
3777
|
}
|
|
3490
3778
|
function readTargetAssetHost(targetDir) {
|
|
3491
|
-
const lockfile = readJson2(
|
|
3779
|
+
const lockfile = readJson2(join20(targetDir, ".pro-gov/assets.lock.json"));
|
|
3492
3780
|
return isAssetRegistryHost(lockfile?.host) ? lockfile.host : void 0;
|
|
3493
3781
|
}
|
|
3494
3782
|
function isAssetRegistryHost(value) {
|
|
3495
3783
|
return value === "codex" || value === "claude-code" || value === "gemini-cli" || value === "antigravity";
|
|
3496
3784
|
}
|
|
3497
3785
|
function runTargetChecks(target) {
|
|
3498
|
-
const proGovCli =
|
|
3499
|
-
const docGovCli =
|
|
3786
|
+
const proGovCli = join20(target.path, "node_modules/@pieai/pro-gov/dist/cli.js");
|
|
3787
|
+
const docGovCli = join20(target.path, "node_modules/@pieai/doc-gov/dist/cli.js");
|
|
3500
3788
|
const commands = [
|
|
3501
3789
|
{
|
|
3502
3790
|
name: "pro-gov doctor",
|
|
@@ -3507,7 +3795,7 @@ function runTargetChecks(target) {
|
|
|
3507
3795
|
{ name: "doc-gov scan --check", cli: docGovCli, args: ["scan", "--check"] }
|
|
3508
3796
|
];
|
|
3509
3797
|
return commands.map((command2) => {
|
|
3510
|
-
if (!
|
|
3798
|
+
if (!existsSync19(command2.cli)) return { name: command2.name, status: null };
|
|
3511
3799
|
const result = spawnSync5(process.execPath, [command2.cli, ...command2.args], {
|
|
3512
3800
|
cwd: target.path,
|
|
3513
3801
|
encoding: "utf8",
|
|
@@ -3546,16 +3834,16 @@ function getExpectedPackageVersions() {
|
|
|
3546
3834
|
};
|
|
3547
3835
|
}
|
|
3548
3836
|
function findOwnPackageJson() {
|
|
3549
|
-
let current =
|
|
3837
|
+
let current = dirname12(fileURLToPath3(import.meta.url));
|
|
3550
3838
|
for (let depth = 0; depth < 5; depth += 1) {
|
|
3551
|
-
const candidate =
|
|
3552
|
-
if (
|
|
3553
|
-
current =
|
|
3839
|
+
const candidate = join20(current, "package.json");
|
|
3840
|
+
if (existsSync19(candidate)) return candidate;
|
|
3841
|
+
current = dirname12(current);
|
|
3554
3842
|
}
|
|
3555
3843
|
return "";
|
|
3556
3844
|
}
|
|
3557
3845
|
function readJson2(path) {
|
|
3558
|
-
if (!path || !
|
|
3846
|
+
if (!path || !existsSync19(path)) return void 0;
|
|
3559
3847
|
try {
|
|
3560
3848
|
return JSON.parse(readFileSync14(path, "utf8"));
|
|
3561
3849
|
} catch {
|
|
@@ -3576,26 +3864,27 @@ function deduplicateIssues(issues) {
|
|
|
3576
3864
|
import { execFileSync } from "node:child_process";
|
|
3577
3865
|
import {
|
|
3578
3866
|
cpSync as cpSync2,
|
|
3579
|
-
existsSync as
|
|
3580
|
-
lstatSync as
|
|
3867
|
+
existsSync as existsSync20,
|
|
3868
|
+
lstatSync as lstatSync8,
|
|
3581
3869
|
mkdirSync as mkdirSync8,
|
|
3582
3870
|
readFileSync as readFileSync15,
|
|
3583
3871
|
readdirSync as readdirSync8,
|
|
3584
|
-
realpathSync as
|
|
3872
|
+
realpathSync as realpathSync4,
|
|
3585
3873
|
statSync as statSync4,
|
|
3586
3874
|
writeFileSync as writeFileSync7
|
|
3587
3875
|
} from "node:fs";
|
|
3588
|
-
import { homedir } from "node:os";
|
|
3589
|
-
import { dirname as
|
|
3876
|
+
import { homedir as homedir2 } from "node:os";
|
|
3877
|
+
import { dirname as dirname13, join as join21, relative as relative8, resolve as resolve5, sep } from "node:path";
|
|
3590
3878
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
3879
|
+
var CURRENT_ROUTER_VERSION = "1.1";
|
|
3591
3880
|
function inspectPortfolioAiHealth(options) {
|
|
3592
3881
|
const endpoints = collectEndpoints(options.manifest);
|
|
3593
|
-
const secretsRoot = options.secretsRoot ??
|
|
3594
|
-
const homeDir = options.homeDir ?? process.env.HOME ??
|
|
3882
|
+
const secretsRoot = options.secretsRoot ?? join21(dirname13(options.manifest.controlPlane?.path ?? endpoints[0]?.endpoint.path ?? process.cwd()), ".secrets");
|
|
3883
|
+
const homeDir = options.homeDir ?? process.env.HOME ?? homedir2();
|
|
3595
3884
|
const grokVersion = commandVersion("grok");
|
|
3596
3885
|
const executionEngineRoot = options.manifest.executionEngine?.path;
|
|
3597
3886
|
const skillRegistry = inspectSkillRegistry(executionEngineRoot);
|
|
3598
|
-
const expectedPackageVersion = packageVersion(
|
|
3887
|
+
const expectedPackageVersion = packageVersion(join21(executionEngineRoot ?? "", "packages/pro-gov/package.json"));
|
|
3599
3888
|
const repositories = endpoints.map(({ endpoint, role }) => inspectRepository(
|
|
3600
3889
|
endpoint,
|
|
3601
3890
|
role,
|
|
@@ -3608,12 +3897,17 @@ function inspectPortfolioAiHealth(options) {
|
|
|
3608
3897
|
const summary = { healthy: 0, attention: 0, unhealthy: 0 };
|
|
3609
3898
|
for (const repository of repositories) summary[repository.status] += 1;
|
|
3610
3899
|
return {
|
|
3611
|
-
schemaVersion:
|
|
3900
|
+
schemaVersion: 4,
|
|
3612
3901
|
portfolioId: options.manifest.portfolioId,
|
|
3613
3902
|
generatedAt: options.generatedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
3614
|
-
privacy: "Names, paths, counts, scopes, and configuration origins only. Secret values, environment values, MCP commands, arguments, URLs, headers,
|
|
3903
|
+
privacy: "Names, paths, counts, scopes, safe health states, and configuration origins only. Secret values, environment values, MCP commands, arguments, URLs, headers, MCP environment maps, and raw process environments are never retained or rendered.",
|
|
3615
3904
|
secretsRoot: inspectSecretsRoot(secretsRoot),
|
|
3616
|
-
hostEnvironment: inspectHostEnvironment(
|
|
3905
|
+
hostEnvironment: inspectHostEnvironment(
|
|
3906
|
+
homeDir,
|
|
3907
|
+
grokVersion,
|
|
3908
|
+
endpoints.map(({ endpoint }) => endpoint.path),
|
|
3909
|
+
options.manifest.specialistChecks?.devspace
|
|
3910
|
+
),
|
|
3617
3911
|
skillRegistry,
|
|
3618
3912
|
technologyGovernance: {
|
|
3619
3913
|
strategySource: options.manifest.technologyGovernance?.strategySource,
|
|
@@ -3628,15 +3922,15 @@ function writePortfolioAiHealthReport(report, outDir) {
|
|
|
3628
3922
|
mkdirSync8(outDir, { recursive: true });
|
|
3629
3923
|
const dashboardAssets = findDashboardAssets();
|
|
3630
3924
|
for (const file of ["index.html", "app.js", "app.css"]) {
|
|
3631
|
-
const source =
|
|
3632
|
-
if (!
|
|
3633
|
-
cpSync2(source,
|
|
3925
|
+
const source = join21(dashboardAssets, file);
|
|
3926
|
+
if (!existsSync20(source)) throw new Error(`Portfolio dashboard asset is missing: ${source}`);
|
|
3927
|
+
cpSync2(source, join21(outDir, file));
|
|
3634
3928
|
}
|
|
3635
|
-
const jsonPath =
|
|
3636
|
-
const htmlPath =
|
|
3929
|
+
const jsonPath = join21(outDir, "portfolio-ai-health.json");
|
|
3930
|
+
const htmlPath = join21(outDir, "index.html");
|
|
3637
3931
|
writeFileSync7(jsonPath, `${JSON.stringify(report, null, 2)}
|
|
3638
3932
|
`);
|
|
3639
|
-
writeFileSync7(
|
|
3933
|
+
writeFileSync7(join21(outDir, "data.js"), `window.__PORTFOLIO_AI_HEALTH__ = ${safeJavaScriptJson(report)};
|
|
3640
3934
|
`);
|
|
3641
3935
|
return { jsonPath, htmlPath };
|
|
3642
3936
|
}
|
|
@@ -3647,7 +3941,7 @@ function collectEndpoints(manifest) {
|
|
|
3647
3941
|
for (const target of manifest.targets) result.push({ endpoint: target, role: "target" });
|
|
3648
3942
|
const seen = /* @__PURE__ */ new Set();
|
|
3649
3943
|
return result.filter(({ endpoint }) => {
|
|
3650
|
-
const key =
|
|
3944
|
+
const key = resolve5(endpoint.path);
|
|
3651
3945
|
if (seen.has(key)) return false;
|
|
3652
3946
|
seen.add(key);
|
|
3653
3947
|
return true;
|
|
@@ -3659,13 +3953,14 @@ function inspectRepository(endpoint, role, secretsRoot, homeDir, expectedPackage
|
|
|
3659
3953
|
const entries = inspectEntries(root);
|
|
3660
3954
|
const grokInspection = inspectGrokProject(root, homeDir, grokVersion);
|
|
3661
3955
|
const skills = inspectSkills(root, grokInspection);
|
|
3956
|
+
const hostSsot = inspectProjectHostSsot(root);
|
|
3662
3957
|
const hooks = inspectHooks(root);
|
|
3663
3958
|
const docs = inspectDocs(root, role === "execution-engine" ? void 0 : expectedPackageVersion);
|
|
3664
3959
|
const mcp = {
|
|
3665
|
-
codexProject: tomlMcpNames(
|
|
3666
|
-
claudeCodeProjectShared: jsonObjectKeys(
|
|
3960
|
+
codexProject: tomlMcpNames(join21(root, ".codex/config.toml")),
|
|
3961
|
+
claudeCodeProjectShared: jsonObjectKeys(join21(root, ".mcp.json"), "mcpServers"),
|
|
3667
3962
|
claudeCodeProjectLocal: claudeProjectLocalMcpNames(homeDir, root),
|
|
3668
|
-
grokProject: tomlMcpNames(
|
|
3963
|
+
grokProject: tomlMcpNames(join21(root, ".grok/config.toml")),
|
|
3669
3964
|
grokEffective: grokInspection.effectiveMcp,
|
|
3670
3965
|
grokInspection: grokInspection.inspection
|
|
3671
3966
|
};
|
|
@@ -3689,6 +3984,9 @@ function inspectRepository(endpoint, role, secretsRoot, homeDir, expectedPackage
|
|
|
3689
3984
|
if (skills.canonical.some((skill) => skill.kind === "dangling-symlink")) recommendations.push("`.agents/skills` \u4E2D\u5B58\u5728\u65AD\u5F00\u7684\u6280\u80FD\u94FE\u63A5\u3002");
|
|
3690
3985
|
if (skills.claudeCompatibility === "duplicate-directory") recommendations.push("`.claude/skills` \u662F\u72EC\u7ACB\u526F\u672C\uFF1B\u5EFA\u8BAE\u94FE\u63A5\u5230 `.agents/skills`\uFF0C\u907F\u514D\u53CC\u4EFD\u6280\u80FD\u6F02\u79FB\u3002");
|
|
3691
3986
|
if (skills.claudeCompatibility === "dangling-symlink") recommendations.push("`.claude/skills` \u662F\u65AD\u5F00\u7684\u94FE\u63A5\u3002");
|
|
3987
|
+
if (!hostSsot.claudeEntry.compliant) recommendations.push(`CLAUDE.md \u4E0D\u662F\u89C4\u8303\u7684\u76F8\u5BF9\u94FE\u63A5 AGENTS.md\uFF08${hostSsot.claudeEntry.status}\uFF09\u3002`);
|
|
3988
|
+
if (!hostSsot.claudeSkills.compliant) recommendations.push(`.claude/skills \u4E0D\u662F\u89C4\u8303\u7684\u76F8\u5BF9\u94FE\u63A5 ../.agents/skills\uFF08${hostSsot.claudeSkills.status}\uFF09\u3002`);
|
|
3989
|
+
if (hostSsot.canonicalSkills.status !== "directory") recommendations.push(`\u7F3A\u5C11\u89C4\u8303\u7684 .agents/skills \u6280\u80FD\u76EE\u5F55\uFF08${hostSsot.canonicalSkills.status}\uFF09\u3002`);
|
|
3692
3990
|
if (hasWorkflowReminderHooks(hooks)) recommendations.push("\u53D1\u73B0 Stop/SubagentStop hook\uFF1B\u786E\u8BA4\u5B83\u662F\u5426\u4ECD\u6709\u9879\u76EE\u4E13\u5C5E\u7528\u9014\uFF0C\u5E76\u79FB\u9664\u9000\u4F11\u7684 PGS \u5DE5\u4F5C\u6D41\u63D0\u9192\u3002");
|
|
3693
3991
|
const liveEnv = secrets.repositoryEnvFiles.filter((file) => !file.template && !file.fixture);
|
|
3694
3992
|
const envNeedsCentralReview = liveEnv.filter((file) => !file.localOnly);
|
|
@@ -3697,6 +3995,7 @@ function inspectRepository(endpoint, role, secretsRoot, homeDir, expectedPackage
|
|
|
3697
3995
|
else if (envNeedsCentralReview.some((file) => !file.centralized)) recommendations.push("\u53D1\u73B0\u771F\u5B9E\u73AF\u5883\u6587\u4EF6\u5C1A\u672A\u8FDE\u63A5\u5230\u672C\u9879\u76EE\u7684\u4E2D\u592E `.secrets` \u76EE\u5F55\uFF1B\u533A\u5206\u53EF\u4E22\u5F03\u751F\u6210\u7269\u4E0E\u672C\u5730\u4E3B\u6765\u6E90\uFF0C\u9700\u4FDD\u7559\u7684\u6765\u6E90\u5E94\u96C6\u4E2D\u540E\u518D\u63A5\u56DE\u9879\u76EE\u3002");
|
|
3698
3996
|
if (hasUnsafeCentralSecretPermissions(secrets)) recommendations.push("\u4E2D\u592E\u5BC6\u94A5\u76EE\u5F55\u6216\u6587\u4EF6\u6743\u9650\u8FC7\u5BBD\uFF1B\u76EE\u5F55\u5E94\u4E3A 0700\uFF0C\u914D\u7F6E\u6587\u4EF6\u5E94\u4E3A 0600\u3002");
|
|
3699
3997
|
if (!docs.packages.aligned) recommendations.push(`PGS \u5305\u7248\u672C\u672A\u4E0E\u6267\u884C\u5F15\u64CE ${docs.packages.expected ?? "\u672A\u77E5\u7248\u672C"} \u5BF9\u9F50\uFF1B\u53D1\u5E03\u4E0A\u6E38\u540E\u518D\u540C\u6B65\u76EE\u6807\u4ED3\u5E93\u3002`);
|
|
3998
|
+
if (!docs.routerAligned) recommendations.push(`PGS Router \u7248\u672C\u4E3A ${docs.routerVersion ? `v${docs.routerVersion}` : "\u672A\u8BC6\u522B"}\uFF0C\u5E94\u540C\u6B65\u5230 v${docs.expectedRouterVersion}\u3002`);
|
|
3700
3999
|
if (role === "target" && !docs.manifest) recommendations.push("\u7F3A\u5C11 docs/governance/MANIFEST.yml\uFF1B\u6587\u6863\u6E05\u5355\u65E0\u6CD5\u8BC1\u660E\u5DF2\u540C\u6B65\u3002");
|
|
3701
4000
|
if (technologyGovernance && !projectModel.projectType) recommendations.push("\u672A\u58F0\u660E\u9879\u76EE\u7C7B\u578B\uFF1B\u65E0\u6CD5\u628A\u5B9E\u9645\u6280\u672F\u4E0E\u4EA7\u54C1\u7EBF\u57FA\u7EBF\u8FDB\u884C\u6BD4\u8F83\u3002");
|
|
3702
4001
|
const missingBaseline = projectModel.baseline.filter((technology) => !technology.detected && !hasBaselineException(projectModel, technology.id));
|
|
@@ -3708,27 +4007,29 @@ function inspectRepository(endpoint, role, secretsRoot, homeDir, expectedPackage
|
|
|
3708
4007
|
role,
|
|
3709
4008
|
path: root,
|
|
3710
4009
|
profile: "profile" in endpoint ? endpoint.profile : void 0,
|
|
3711
|
-
status: deriveStatus(role, entries, git, hooks, skills, secrets, docs, projectModel, Boolean(technologyGovernance)),
|
|
4010
|
+
status: deriveStatus(role, entries, git, hooks, skills, hostSsot, secrets, docs, projectModel, Boolean(technologyGovernance)),
|
|
3712
4011
|
recommendations,
|
|
3713
4012
|
git,
|
|
3714
4013
|
entries,
|
|
3715
4014
|
hooks,
|
|
3716
4015
|
mcp,
|
|
3717
4016
|
skills,
|
|
4017
|
+
hostSsot,
|
|
3718
4018
|
secrets,
|
|
3719
4019
|
docs,
|
|
3720
4020
|
projectModel
|
|
3721
4021
|
};
|
|
3722
4022
|
}
|
|
3723
|
-
function deriveStatus(role, entries, git, hooks, skills, secrets, docs, projectModel, technologyGovernanceConfigured) {
|
|
4023
|
+
function deriveStatus(role, entries, git, hooks, skills, hostSsot, secrets, docs, projectModel, technologyGovernanceConfigured) {
|
|
3724
4024
|
if (!git.isRepository || entries.agents === "missing" || entries.claude === "dangling-symlink" || entries.gemini === "dangling-symlink" || skills.canonical.some((item) => item.kind === "dangling-symlink") || secrets.repositoryEnvFiles.some((file) => file.tracked && !file.template && !file.fixture) || hasUnsafeCentralSecretPermissions(secrets)) return "unhealthy";
|
|
3725
4025
|
const missingBaseline = projectModel.baseline.some((technology) => !technology.detected && !hasBaselineException(projectModel, technology.id));
|
|
3726
4026
|
const missingSelected = projectModel.optionalCapabilities.some((technology) => technology.selected && !technology.detected);
|
|
3727
4027
|
const liveEnv = secrets.repositoryEnvFiles.filter((file) => !file.template && !file.fixture);
|
|
3728
4028
|
const secretMaterializationNeedsReview = liveEnv.some((file) => !file.centralized && !file.localOnly);
|
|
3729
4029
|
const packageVersionNeedsReview = docs.packages.expected !== void 0 && !docs.packages.aligned;
|
|
4030
|
+
const routerVersionNeedsReview = !docs.routerAligned;
|
|
3730
4031
|
const targetManifestMissing = role === "target" && !docs.manifest;
|
|
3731
|
-
if (entries.agents !== "pgs-router" || entries.claude !== "agents-symlink" || hasWorkflowReminderHooks(hooks) || skills.claudeCompatibility === "duplicate-directory" || skills.claudeCompatibility === "dangling-symlink" || git.branches.length > 1 || git.worktrees.length > 1 || git.dirtyPaths.length > 0 || (git.ahead ?? 0) > 0 || secretMaterializationNeedsReview || technologyGovernanceConfigured && !projectModel.projectType || missingBaseline || missingSelected || packageVersionNeedsReview || targetManifestMissing) return "attention";
|
|
4032
|
+
if (entries.agents !== "pgs-router" || entries.claude !== "agents-symlink" || hasWorkflowReminderHooks(hooks) || skills.claudeCompatibility === "duplicate-directory" || skills.claudeCompatibility === "dangling-symlink" || !hostSsot.compliant || git.branches.length > 1 || git.worktrees.length > 1 || git.dirtyPaths.length > 0 || (git.ahead ?? 0) > 0 || secretMaterializationNeedsReview || technologyGovernanceConfigured && !projectModel.projectType || missingBaseline || missingSelected || packageVersionNeedsReview || routerVersionNeedsReview || targetManifestMissing) return "attention";
|
|
3732
4033
|
return "healthy";
|
|
3733
4034
|
}
|
|
3734
4035
|
function inspectProjectModel(root, endpoint, governance) {
|
|
@@ -3738,7 +4039,7 @@ function inspectProjectModel(root, endpoint, governance) {
|
|
|
3738
4039
|
const detection = (id) => {
|
|
3739
4040
|
const technology = technologyById.get(id);
|
|
3740
4041
|
const packageMatch = technology?.packages?.some((name) => packages.has(name)) ?? false;
|
|
3741
|
-
const fileMatch = technology?.files?.some((path) =>
|
|
4042
|
+
const fileMatch = technology?.files?.some((path) => existsSync20(join21(root, path))) ?? false;
|
|
3742
4043
|
return { id, label: technology?.label ?? id, detected: packageMatch || fileMatch };
|
|
3743
4044
|
};
|
|
3744
4045
|
const selected = new Set(endpoint.capabilities ?? []);
|
|
@@ -3761,10 +4062,10 @@ function collectPackageNames(root) {
|
|
|
3761
4062
|
try {
|
|
3762
4063
|
files = splitLines(execFileSync("git", ["ls-files", "*package.json"], { cwd: root, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }));
|
|
3763
4064
|
} catch {
|
|
3764
|
-
if (
|
|
4065
|
+
if (existsSync20(join21(root, "package.json"))) files = ["package.json"];
|
|
3765
4066
|
}
|
|
3766
4067
|
for (const file of files) {
|
|
3767
|
-
const packageJson = readJson3(
|
|
4068
|
+
const packageJson = readJson3(join21(root, file));
|
|
3768
4069
|
if (!isRecord3(packageJson)) continue;
|
|
3769
4070
|
for (const section of ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]) {
|
|
3770
4071
|
const dependencies = packageJson[section];
|
|
@@ -3802,15 +4103,15 @@ function inspectGit2(root) {
|
|
|
3802
4103
|
};
|
|
3803
4104
|
}
|
|
3804
4105
|
function inspectEntries(root) {
|
|
3805
|
-
const agentsPath =
|
|
3806
|
-
const agents = !
|
|
3807
|
-
const claudePath =
|
|
4106
|
+
const agentsPath = join21(root, "AGENTS.md");
|
|
4107
|
+
const agents = !existsSync20(agentsPath) ? "missing" : safeRead(agentsPath).includes("PGS-ROUTER:BEGIN") ? "pgs-router" : "custom";
|
|
4108
|
+
const claudePath = join21(root, "CLAUDE.md");
|
|
3808
4109
|
let claude = "missing";
|
|
3809
4110
|
if (pathLexists(claudePath)) {
|
|
3810
|
-
const info =
|
|
4111
|
+
const info = lstatSync8(claudePath);
|
|
3811
4112
|
if (info.isSymbolicLink()) {
|
|
3812
4113
|
try {
|
|
3813
|
-
claude =
|
|
4114
|
+
claude = realpathSync4(claudePath) === realpathSync4(agentsPath) ? "agents-symlink" : "custom";
|
|
3814
4115
|
} catch {
|
|
3815
4116
|
claude = "dangling-symlink";
|
|
3816
4117
|
}
|
|
@@ -3822,12 +4123,12 @@ function inspectEntries(root) {
|
|
|
3822
4123
|
return { agents, claude, gemini: inspectOptionalEntry(root, "GEMINI.md", agentsPath) };
|
|
3823
4124
|
}
|
|
3824
4125
|
function inspectOptionalEntry(root, filename, agentsPath) {
|
|
3825
|
-
const path =
|
|
4126
|
+
const path = join21(root, filename);
|
|
3826
4127
|
if (!pathLexists(path)) return "missing";
|
|
3827
|
-
const info =
|
|
4128
|
+
const info = lstatSync8(path);
|
|
3828
4129
|
if (info.isSymbolicLink()) {
|
|
3829
4130
|
try {
|
|
3830
|
-
return
|
|
4131
|
+
return realpathSync4(path) === realpathSync4(agentsPath) ? "agents-symlink" : "custom";
|
|
3831
4132
|
} catch {
|
|
3832
4133
|
return "dangling-symlink";
|
|
3833
4134
|
}
|
|
@@ -3836,7 +4137,7 @@ function inspectOptionalEntry(root, filename, agentsPath) {
|
|
|
3836
4137
|
return /AGENTS\.md/.test(content) && content.length < 2e3 ? "thin-adapter" : "custom";
|
|
3837
4138
|
}
|
|
3838
4139
|
function inspectSkills(root, grokInspection) {
|
|
3839
|
-
const lock = readJson3(
|
|
4140
|
+
const lock = readJson3(join21(root, ".pro-gov/assets.lock.json"));
|
|
3840
4141
|
const managed = /* @__PURE__ */ new Set();
|
|
3841
4142
|
const bundleIds = stringArray(isRecord3(lock) ? lock.bundleIds : void 0);
|
|
3842
4143
|
if (isRecord3(lock) && Array.isArray(lock.assets)) {
|
|
@@ -3846,14 +4147,14 @@ function inspectSkills(root, grokInspection) {
|
|
|
3846
4147
|
if (match) managed.add(match[1]);
|
|
3847
4148
|
}
|
|
3848
4149
|
}
|
|
3849
|
-
const skillRoot =
|
|
4150
|
+
const skillRoot = join21(root, ".agents/skills");
|
|
3850
4151
|
const canonical = pathLexists(skillRoot) && safeIsDirectory(skillRoot) ? safeReadDir(skillRoot).filter((name) => !name.startsWith(".")).map((name) => {
|
|
3851
|
-
const path =
|
|
3852
|
-
const stat =
|
|
4152
|
+
const path = join21(skillRoot, name);
|
|
4153
|
+
const stat = lstatSync8(path);
|
|
3853
4154
|
let kind = stat.isSymbolicLink() ? "symlink" : stat.isDirectory() ? "directory" : "file";
|
|
3854
4155
|
if (stat.isSymbolicLink()) {
|
|
3855
4156
|
try {
|
|
3856
|
-
|
|
4157
|
+
realpathSync4(path);
|
|
3857
4158
|
} catch {
|
|
3858
4159
|
kind = "dangling-symlink";
|
|
3859
4160
|
}
|
|
@@ -3874,20 +4175,20 @@ function inspectSkills(root, grokInspection) {
|
|
|
3874
4175
|
},
|
|
3875
4176
|
hosts: {
|
|
3876
4177
|
codexProject: canonical.filter((item) => item.kind !== "dangling-symlink").length,
|
|
3877
|
-
claudeCodeProject: inspectSkillRoot(
|
|
3878
|
-
grokNativeProject: inspectSkillRoot(
|
|
4178
|
+
claudeCodeProject: inspectSkillRoot(join21(root, ".claude/skills")).names.length,
|
|
4179
|
+
grokNativeProject: inspectSkillRoot(join21(root, ".grok/skills")).names.length,
|
|
3879
4180
|
grokEffective: grokInspection.skills
|
|
3880
4181
|
}
|
|
3881
4182
|
};
|
|
3882
4183
|
}
|
|
3883
4184
|
function inspectClaudeSkillRoot(root) {
|
|
3884
|
-
const path =
|
|
4185
|
+
const path = join21(root, ".claude/skills");
|
|
3885
4186
|
if (!pathLexists(path)) return "missing";
|
|
3886
|
-
const stat =
|
|
4187
|
+
const stat = lstatSync8(path);
|
|
3887
4188
|
if (stat.isSymbolicLink()) {
|
|
3888
4189
|
try {
|
|
3889
|
-
const target =
|
|
3890
|
-
return target ===
|
|
4190
|
+
const target = realpathSync4(path);
|
|
4191
|
+
return target === realpathSync4(join21(root, ".agents/skills")) ? "shared-root" : "other";
|
|
3891
4192
|
} catch {
|
|
3892
4193
|
return "dangling-symlink";
|
|
3893
4194
|
}
|
|
@@ -3895,7 +4196,7 @@ function inspectClaudeSkillRoot(root) {
|
|
|
3895
4196
|
return stat.isDirectory() ? "duplicate-directory" : "other";
|
|
3896
4197
|
}
|
|
3897
4198
|
function inspectRepositorySecrets(root, id, secretsRoot, isRepository, environmentPolicy) {
|
|
3898
|
-
const centralPath =
|
|
4199
|
+
const centralPath = join21(secretsRoot, id);
|
|
3899
4200
|
const centralRealPath = safeRealpath(centralPath);
|
|
3900
4201
|
const localOnlyReasons = new Map(
|
|
3901
4202
|
(environmentPolicy?.localOnly ?? []).map((entry) => [entry.path, entry.reason])
|
|
@@ -3907,16 +4208,16 @@ function inspectRepositorySecrets(root, id, secretsRoot, isRepository, environme
|
|
|
3907
4208
|
tracked: isRepository ? gitTracks(root, path) : false,
|
|
3908
4209
|
template: isEnvironmentTemplate(path),
|
|
3909
4210
|
fixture: isEnvironmentFixture(path),
|
|
3910
|
-
symlink:
|
|
3911
|
-
centralized: pointsInside(
|
|
4211
|
+
symlink: lstatSync8(join21(root, path)).isSymbolicLink(),
|
|
4212
|
+
centralized: pointsInside(join21(root, path), centralRealPath),
|
|
3912
4213
|
localOnly: localOnlyReason !== void 0,
|
|
3913
4214
|
...localOnlyReason !== void 0 ? { localOnlyReason } : {}
|
|
3914
4215
|
};
|
|
3915
4216
|
});
|
|
3916
4217
|
return {
|
|
3917
|
-
centralDirectory:
|
|
3918
|
-
centralMode:
|
|
3919
|
-
centralFiles:
|
|
4218
|
+
centralDirectory: existsSync20(centralPath) ? "present" : "absent",
|
|
4219
|
+
centralMode: existsSync20(centralPath) ? modeString(statSync4(centralPath).mode) : void 0,
|
|
4220
|
+
centralFiles: existsSync20(centralPath) ? collectCentralSecretFiles(centralPath) : [],
|
|
3920
4221
|
repositoryEnvFiles: envFiles
|
|
3921
4222
|
};
|
|
3922
4223
|
}
|
|
@@ -3927,9 +4228,9 @@ function collectEnvironmentFiles(root, current = root, depth = 0) {
|
|
|
3927
4228
|
try {
|
|
3928
4229
|
for (const entry of readdirSync8(current, { withFileTypes: true })) {
|
|
3929
4230
|
if (entry.isDirectory()) {
|
|
3930
|
-
if (!SKIP_ENV_DIRECTORIES.has(entry.name)) found.push(...collectEnvironmentFiles(root,
|
|
4231
|
+
if (!SKIP_ENV_DIRECTORIES.has(entry.name)) found.push(...collectEnvironmentFiles(root, join21(current, entry.name), depth + 1));
|
|
3931
4232
|
} else if (isEnvironmentFilename(entry.name) && !isProviderGeneratedEnvironmentFile(entry.name)) {
|
|
3932
|
-
found.push(relative8(root,
|
|
4233
|
+
found.push(relative8(root, join21(current, entry.name)));
|
|
3933
4234
|
}
|
|
3934
4235
|
}
|
|
3935
4236
|
} catch {
|
|
@@ -3942,9 +4243,9 @@ function collectCentralSecretFiles(root, current = root, depth = 0) {
|
|
|
3942
4243
|
const found = [];
|
|
3943
4244
|
try {
|
|
3944
4245
|
for (const entry of readdirSync8(current, { withFileTypes: true })) {
|
|
3945
|
-
const path =
|
|
4246
|
+
const path = join21(current, entry.name);
|
|
3946
4247
|
if (entry.isDirectory()) found.push(...collectCentralSecretFiles(root, path, depth + 1));
|
|
3947
|
-
else found.push({ path: relative8(root, path), mode: modeString(
|
|
4248
|
+
else found.push({ path: relative8(root, path), mode: modeString(lstatSync8(path).mode) });
|
|
3948
4249
|
}
|
|
3949
4250
|
} catch {
|
|
3950
4251
|
return found;
|
|
@@ -3969,13 +4270,13 @@ function isEnvironmentFixture(path) {
|
|
|
3969
4270
|
}
|
|
3970
4271
|
function safeRealpath(path) {
|
|
3971
4272
|
try {
|
|
3972
|
-
return
|
|
4273
|
+
return realpathSync4(path);
|
|
3973
4274
|
} catch {
|
|
3974
4275
|
return void 0;
|
|
3975
4276
|
}
|
|
3976
4277
|
}
|
|
3977
4278
|
function pointsInside(path, expectedRoot) {
|
|
3978
|
-
if (!expectedRoot || !
|
|
4279
|
+
if (!expectedRoot || !lstatSync8(path).isSymbolicLink()) return false;
|
|
3979
4280
|
const target = safeRealpath(path);
|
|
3980
4281
|
if (!target) return false;
|
|
3981
4282
|
const fromRoot = relative8(expectedRoot, target);
|
|
@@ -3985,24 +4286,25 @@ function hasUnsafeCentralSecretPermissions(secrets) {
|
|
|
3985
4286
|
return secrets.centralDirectory === "present" && (secrets.centralMode !== "700" || secrets.centralFiles.some((file) => file.mode !== "600"));
|
|
3986
4287
|
}
|
|
3987
4288
|
function inspectSecretsRoot(path) {
|
|
3988
|
-
return
|
|
4289
|
+
return existsSync20(path) ? { path, exists: true, mode: modeString(statSync4(path).mode) } : { path, exists: false };
|
|
3989
4290
|
}
|
|
3990
|
-
function inspectHostEnvironment(homeDir, grokVersion) {
|
|
3991
|
-
const codexConfig =
|
|
3992
|
-
const claudeConfig =
|
|
3993
|
-
const grokConfig =
|
|
3994
|
-
|
|
4291
|
+
function inspectHostEnvironment(homeDir, grokVersion, repositoryPaths, devspaceSettings) {
|
|
4292
|
+
const codexConfig = join21(homeDir, ".codex/config.toml");
|
|
4293
|
+
const claudeConfig = join21(homeDir, ".claude.json");
|
|
4294
|
+
const grokConfig = join21(homeDir, ".grok/config.toml");
|
|
4295
|
+
const hostEnvironment = {
|
|
3995
4296
|
mcp: {
|
|
3996
4297
|
codexUser: { path: codexConfig, names: tomlMcpNames(codexConfig) },
|
|
3997
4298
|
claudeCodeUser: { path: claudeConfig, names: jsonObjectKeys(claudeConfig, "mcpServers") },
|
|
3998
4299
|
grokUser: { path: grokConfig, names: tomlMcpNames(grokConfig) }
|
|
3999
4300
|
},
|
|
4000
4301
|
skills: {
|
|
4001
|
-
codexUser: inspectSkillRoot(
|
|
4002
|
-
claudeCodeUser: inspectSkillRoot(
|
|
4003
|
-
grokUser: inspectSkillRoot(
|
|
4004
|
-
grokAgentsCompatibility: inspectSkillRoot(
|
|
4005
|
-
grokClaudeCompatibility: inspectSkillRoot(
|
|
4302
|
+
codexUser: inspectSkillRoot(join21(homeDir, ".agents/skills")),
|
|
4303
|
+
claudeCodeUser: inspectSkillRoot(join21(homeDir, ".claude/skills")),
|
|
4304
|
+
grokUser: inspectSkillRoot(join21(homeDir, ".grok/skills")),
|
|
4305
|
+
grokAgentsCompatibility: inspectSkillRoot(join21(homeDir, ".agents/skills")),
|
|
4306
|
+
grokClaudeCompatibility: inspectSkillRoot(join21(homeDir, ".claude/skills")),
|
|
4307
|
+
ssot: inspectUserSkillsSsot(homeDir)
|
|
4006
4308
|
},
|
|
4007
4309
|
grok: {
|
|
4008
4310
|
available: grokVersion !== void 0,
|
|
@@ -4010,20 +4312,121 @@ function inspectHostEnvironment(homeDir, grokVersion) {
|
|
|
4010
4312
|
inspectionCommand: "grok inspect --json"
|
|
4011
4313
|
}
|
|
4012
4314
|
};
|
|
4315
|
+
if (devspaceSettings) {
|
|
4316
|
+
hostEnvironment.devspace = inspectDevSpaceHealth({
|
|
4317
|
+
homeDir,
|
|
4318
|
+
repositoryPaths,
|
|
4319
|
+
expectedToolMode: devspaceSettings.expectedToolMode
|
|
4320
|
+
});
|
|
4321
|
+
}
|
|
4322
|
+
return hostEnvironment;
|
|
4323
|
+
}
|
|
4324
|
+
function inspectDevSpaceHealth(options) {
|
|
4325
|
+
const run = options.run ?? runDevSpaceCommand;
|
|
4326
|
+
const configDirectory = join21(options.homeDir, ".devspace");
|
|
4327
|
+
const configPath = join21(configDirectory, "config.json");
|
|
4328
|
+
const authPath = join21(configDirectory, "auth.json");
|
|
4329
|
+
const installedResult = run("devspace", ["--version"], 3e3);
|
|
4330
|
+
const installedVersion = installedResult.ok ? installedResult.stdout.match(/\b\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?\b/)?.[0] : void 0;
|
|
4331
|
+
const latestResult = run("npm", ["view", "@waishnav/devspace", "version", "--registry=https://registry.npmjs.org/"], 5e3);
|
|
4332
|
+
const latestVersion = latestResult.ok ? latestResult.stdout.match(/\b\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?\b/)?.[0] : void 0;
|
|
4333
|
+
const processResult = run("pgrep", ["-f", "devspace serve"], 3e3);
|
|
4334
|
+
const pid = processResult.ok ? processResult.stdout.match(/\b\d+\b/)?.[0] : void 0;
|
|
4335
|
+
const processEnvironment = pid ? run("ps", ["eww", "-p", pid, "-o", "command="], 3e3) : void 0;
|
|
4336
|
+
const processToolMode = processEnvironment?.ok ? processEnvironment.stdout.match(/(?:^|\s)DEVSPACE_TOOL_MODE=(minimal|full|codex)(?:\s|$)/)?.[1] : void 0;
|
|
4337
|
+
const doctor = !installedResult.ok ? "unavailable" : run("devspace", ["doctor"], 1e4).ok ? "ok" : "failed";
|
|
4338
|
+
const configValue = readJson3(configPath);
|
|
4339
|
+
const configExists = isRecord3(configValue);
|
|
4340
|
+
const allowedRoots = configExists && Array.isArray(configValue.allowedRoots) ? configValue.allowedRoots.filter((value) => typeof value === "string") : [];
|
|
4341
|
+
const portfolioCoverage = !configExists || allowedRoots.length === 0 ? "unknown" : options.repositoryPaths.every((repositoryPath) => allowedRoots.some((root) => isPathInside(repositoryPath, root))) ? "complete" : "partial";
|
|
4342
|
+
const bind = configExists && typeof configValue.host === "string" ? isLoopbackHost(configValue.host) ? "loopback" : "non-loopback" : "unknown";
|
|
4343
|
+
const directoryMode = existsSync20(configDirectory) ? modeString(statSync4(configDirectory).mode) : void 0;
|
|
4344
|
+
const fileMode = existsSync20(configPath) ? modeString(statSync4(configPath).mode) : void 0;
|
|
4345
|
+
const authMode = existsSync20(authPath) ? modeString(statSync4(authPath).mode) : void 0;
|
|
4346
|
+
const update = installedVersion && latestVersion ? installedVersion === latestVersion ? "current" : "available" : "unknown";
|
|
4347
|
+
const recommendations = [];
|
|
4348
|
+
let status = "healthy";
|
|
4349
|
+
const unhealthy = (message) => {
|
|
4350
|
+
status = "unhealthy";
|
|
4351
|
+
recommendations.push(message);
|
|
4352
|
+
};
|
|
4353
|
+
const attention = (message) => {
|
|
4354
|
+
if (status === "healthy") status = "attention";
|
|
4355
|
+
recommendations.push(message);
|
|
4356
|
+
};
|
|
4357
|
+
if (!installedResult.ok) unhealthy("\u672C\u673A\u672A\u53D1\u73B0 DevSpace\uFF1B\u65E0\u6CD5\u4F7F\u7528\u5BBF\u4E3B\u5DE5\u4F5C\u533A\u670D\u52A1\u3002");
|
|
4358
|
+
if (!configExists) unhealthy("\u7F3A\u5C11 ~/.devspace/config.json\u3002");
|
|
4359
|
+
if (!existsSync20(authPath)) unhealthy("\u7F3A\u5C11 ~/.devspace/auth.json\u3002");
|
|
4360
|
+
if (directoryMode && directoryMode !== "700") unhealthy(`~/.devspace \u76EE\u5F55\u6743\u9650\u4E3A ${directoryMode}\uFF0C\u5E94\u6536\u7D27\u4E3A 700\u3002`);
|
|
4361
|
+
if (fileMode && fileMode !== "600") unhealthy(`DevSpace \u914D\u7F6E\u6587\u4EF6\u6743\u9650\u4E3A ${fileMode}\uFF0C\u5E94\u4E3A 600\u3002`);
|
|
4362
|
+
if (authMode && authMode !== "600") unhealthy(`DevSpace \u8BA4\u8BC1\u6587\u4EF6\u6743\u9650\u4E3A ${authMode}\uFF0C\u5E94\u4E3A 600\u3002`);
|
|
4363
|
+
if (bind === "non-loopback") unhealthy("DevSpace \u76F4\u63A5\u7ED1\u5B9A\u5230\u975E\u672C\u673A\u5730\u5740\uFF1B\u5E94\u4F7F\u7528 loopback \u5E76\u7531\u53D7\u63A7\u4EE3\u7406\u66B4\u9732\u3002");
|
|
4364
|
+
if (doctor === "failed") unhealthy("devspace doctor \u672A\u901A\u8FC7\u3002");
|
|
4365
|
+
if (portfolioCoverage === "partial") attention("DevSpace allowedRoots \u6CA1\u6709\u8986\u76D6\u5168\u90E8\u5DF2\u767B\u8BB0\u4ED3\u5E93\u3002");
|
|
4366
|
+
if (installedResult.ok && !pid) attention("DevSpace \u5DF2\u5B89\u88C5\u4F46\u5F53\u524D\u6CA1\u6709\u8FD0\u884C\u3002");
|
|
4367
|
+
if (options.expectedToolMode && pid && processToolMode !== options.expectedToolMode) {
|
|
4368
|
+
attention(`\u8FD0\u884C\u4E2D\u7684 DevSpace \u5DE5\u5177\u6A21\u5F0F\u4E3A ${processToolMode ?? "unknown"}\uFF0C\u671F\u671B ${options.expectedToolMode}\u3002`);
|
|
4369
|
+
}
|
|
4370
|
+
if (update === "available") attention(`DevSpace \u6709\u65B0\u7248\u672C\uFF1A${installedVersion} \u2192 ${latestVersion}\uFF1B\u5347\u7EA7\u524D\u4ECD\u5E94\u68C0\u67E5\u53D8\u66F4\u4E0E\u517C\u5BB9\u6027\u3002`);
|
|
4371
|
+
return {
|
|
4372
|
+
status,
|
|
4373
|
+
installed: installedResult.ok,
|
|
4374
|
+
...installedVersion ? { installedVersion } : {},
|
|
4375
|
+
...latestVersion ? { latestVersion } : {},
|
|
4376
|
+
update,
|
|
4377
|
+
doctor,
|
|
4378
|
+
running: Boolean(pid),
|
|
4379
|
+
...options.expectedToolMode ? { expectedToolMode: options.expectedToolMode } : {},
|
|
4380
|
+
processToolMode: processToolMode ?? "unknown",
|
|
4381
|
+
config: {
|
|
4382
|
+
exists: configExists,
|
|
4383
|
+
...directoryMode ? { directoryMode } : {},
|
|
4384
|
+
...fileMode ? { fileMode } : {},
|
|
4385
|
+
authExists: existsSync20(authPath),
|
|
4386
|
+
...authMode ? { authMode } : {},
|
|
4387
|
+
bind,
|
|
4388
|
+
portValid: configExists && typeof configValue.port === "number" && Number.isInteger(configValue.port) && configValue.port > 0 && configValue.port <= 65535,
|
|
4389
|
+
publicHttpsConfigured: configExists && typeof configValue.publicBaseUrl === "string" && configValue.publicBaseUrl.startsWith("https://"),
|
|
4390
|
+
allowedRootCount: allowedRoots.length,
|
|
4391
|
+
portfolioCoverage
|
|
4392
|
+
},
|
|
4393
|
+
recommendations
|
|
4394
|
+
};
|
|
4395
|
+
}
|
|
4396
|
+
function runDevSpaceCommand(command2, args, timeout) {
|
|
4397
|
+
try {
|
|
4398
|
+
return {
|
|
4399
|
+
ok: true,
|
|
4400
|
+
stdout: execFileSync(command2, args, {
|
|
4401
|
+
encoding: "utf8",
|
|
4402
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
4403
|
+
timeout
|
|
4404
|
+
})
|
|
4405
|
+
};
|
|
4406
|
+
} catch {
|
|
4407
|
+
return { ok: false, stdout: "" };
|
|
4408
|
+
}
|
|
4409
|
+
}
|
|
4410
|
+
function isLoopbackHost(host) {
|
|
4411
|
+
return ["127.0.0.1", "localhost", "::1"].includes(host.trim().toLowerCase());
|
|
4412
|
+
}
|
|
4413
|
+
function isPathInside(path, root) {
|
|
4414
|
+
const fromRoot = relative8(resolve5(root), resolve5(path));
|
|
4415
|
+
return fromRoot === "" || fromRoot !== ".." && !fromRoot.startsWith(`..${sep}`);
|
|
4013
4416
|
}
|
|
4014
4417
|
function inspectSkillRoot(path) {
|
|
4015
4418
|
const exists = pathLexists(path) && safeIsDirectory(path);
|
|
4016
|
-
const names = exists ? safeReadDir(path).filter((name) => !name.startsWith(".") &&
|
|
4419
|
+
const names = exists ? safeReadDir(path).filter((name) => !name.startsWith(".") && existsSync20(join21(path, name, "SKILL.md"))) : [];
|
|
4017
4420
|
return { path, exists, names };
|
|
4018
4421
|
}
|
|
4019
4422
|
function claudeProjectLocalMcpNames(homeDir, root) {
|
|
4020
4423
|
if (!homeDir) return [];
|
|
4021
|
-
const value = readJson3(
|
|
4424
|
+
const value = readJson3(join21(homeDir, ".claude.json"));
|
|
4022
4425
|
if (!isRecord3(value) || !isRecord3(value.projects)) return [];
|
|
4023
|
-
const candidates = new Set([
|
|
4426
|
+
const candidates = new Set([resolve5(root), safeRealpath(root)].filter((path) => Boolean(path)));
|
|
4024
4427
|
const names = /* @__PURE__ */ new Set();
|
|
4025
4428
|
for (const [path, project] of Object.entries(value.projects)) {
|
|
4026
|
-
const projectPaths = [
|
|
4429
|
+
const projectPaths = [resolve5(path), safeRealpath(path)].filter((candidate) => Boolean(candidate));
|
|
4027
4430
|
if (!projectPaths.some((candidate) => candidates.has(candidate)) || !isRecord3(project) || !isRecord3(project.mcpServers)) continue;
|
|
4028
4431
|
for (const name of Object.keys(project.mcpServers)) names.add(name);
|
|
4029
4432
|
}
|
|
@@ -4054,13 +4457,13 @@ function inspectGrokProject(root, homeDir, grokVersion) {
|
|
|
4054
4457
|
const value = JSON.parse(execFileSync("grok", ["inspect", "--json"], {
|
|
4055
4458
|
cwd: root,
|
|
4056
4459
|
encoding: "utf8",
|
|
4057
|
-
env: { ...process.env, HOME: homeDir, GROK_HOME:
|
|
4460
|
+
env: { ...process.env, HOME: homeDir, GROK_HOME: join21(homeDir, ".grok") },
|
|
4058
4461
|
maxBuffer: 10 * 1024 * 1024,
|
|
4059
4462
|
stdio: ["ignore", "pipe", "ignore"],
|
|
4060
4463
|
timeout: 8e3
|
|
4061
4464
|
}));
|
|
4062
4465
|
if (!isRecord3(value)) return empty("failed");
|
|
4063
|
-
const userClaudeNames = new Set(jsonObjectKeys(
|
|
4466
|
+
const userClaudeNames = new Set(jsonObjectKeys(join21(homeDir, ".claude.json"), "mcpServers"));
|
|
4064
4467
|
const localClaudeNames = new Set(claudeProjectLocalMcpNames(homeDir, root));
|
|
4065
4468
|
const effectiveMcp = Array.isArray(value.mcpServers) ? value.mcpServers.flatMap((item) => {
|
|
4066
4469
|
if (!isRecord3(item) || typeof item.name !== "string") return [];
|
|
@@ -4116,15 +4519,15 @@ function inferGrokMcpScope(name, sourceType, sourcePath, root, homeDir, userClau
|
|
|
4116
4519
|
if (userClaudeNames.has(name)) return "user";
|
|
4117
4520
|
return "unknown";
|
|
4118
4521
|
}
|
|
4119
|
-
const resolvedSource = safeRealpath(sourcePath) ??
|
|
4120
|
-
const resolvedRoot = safeRealpath(root) ??
|
|
4121
|
-
if (resolvedSource ===
|
|
4522
|
+
const resolvedSource = safeRealpath(sourcePath) ?? resolve5(sourcePath);
|
|
4523
|
+
const resolvedRoot = safeRealpath(root) ?? resolve5(root);
|
|
4524
|
+
if (resolvedSource === join21(resolvedRoot, ".mcp.json")) return "project-shared";
|
|
4122
4525
|
if (resolvedSource.startsWith(resolvedRoot + sep)) return "project";
|
|
4123
|
-
if (homeDir && resolvedSource ===
|
|
4526
|
+
if (homeDir && resolvedSource === join21(resolve5(homeDir), ".claude.json")) {
|
|
4124
4527
|
if (localClaudeNames.has(name)) return "project-local";
|
|
4125
4528
|
if (userClaudeNames.has(name)) return "user";
|
|
4126
4529
|
}
|
|
4127
|
-
if (homeDir && resolvedSource.startsWith(
|
|
4530
|
+
if (homeDir && resolvedSource.startsWith(resolve5(homeDir) + sep)) return "user";
|
|
4128
4531
|
if (sourceType === "project") return "project";
|
|
4129
4532
|
return "unknown";
|
|
4130
4533
|
}
|
|
@@ -4155,7 +4558,7 @@ function inspectHooks(root) {
|
|
|
4155
4558
|
{ host: "codex", path: ".codex/hooks.json" }
|
|
4156
4559
|
];
|
|
4157
4560
|
return configs.map((config) => {
|
|
4158
|
-
const value = readJson3(
|
|
4561
|
+
const value = readJson3(join21(root, config.path));
|
|
4159
4562
|
const counts = /* @__PURE__ */ new Map();
|
|
4160
4563
|
collectHookEvents(value, counts);
|
|
4161
4564
|
return {
|
|
@@ -4176,16 +4579,18 @@ function collectHookEvents(value, counts) {
|
|
|
4176
4579
|
}
|
|
4177
4580
|
}
|
|
4178
4581
|
function inspectDocs(root, expected) {
|
|
4179
|
-
const packageJson = readJson3(
|
|
4582
|
+
const packageJson = readJson3(join21(root, "package.json"));
|
|
4180
4583
|
const dependencies = isRecord3(packageJson) ? { ...recordOrEmpty(packageJson.dependencies), ...recordOrEmpty(packageJson.devDependencies) } : {};
|
|
4181
4584
|
const docGov = dependencyVersion(dependencies["@pieai/doc-gov"]);
|
|
4182
4585
|
const proGov = dependencyVersion(dependencies["@pieai/pro-gov"]);
|
|
4183
|
-
const routerMatch = safeRead(
|
|
4586
|
+
const routerMatch = safeRead(join21(root, "AGENTS.md")).match(/PGS-ROUTER:BEGIN\s+v([0-9.]+)/);
|
|
4184
4587
|
const declared = [docGov, proGov].filter((value) => Boolean(value));
|
|
4185
4588
|
return {
|
|
4186
4589
|
routerVersion: routerMatch?.[1],
|
|
4187
|
-
|
|
4188
|
-
|
|
4590
|
+
expectedRouterVersion: CURRENT_ROUTER_VERSION,
|
|
4591
|
+
routerAligned: routerMatch?.[1] === CURRENT_ROUTER_VERSION,
|
|
4592
|
+
manifest: existsSync20(join21(root, "docs/governance/MANIFEST.yml")),
|
|
4593
|
+
currentWork: existsSync20(join21(root, "docs/reference/execution/current-work.md")),
|
|
4189
4594
|
packages: {
|
|
4190
4595
|
expected,
|
|
4191
4596
|
docGov,
|
|
@@ -4208,20 +4613,20 @@ function recordOrEmpty(value) {
|
|
|
4208
4613
|
}
|
|
4209
4614
|
function inspectSkillRegistry(executionEngineRoot) {
|
|
4210
4615
|
if (!executionEngineRoot) return { source: 0, registered: 0, bundled: 0, bundles: 0 };
|
|
4211
|
-
const agentAssetsRoot =
|
|
4212
|
-
const registry = readJson3(
|
|
4616
|
+
const agentAssetsRoot = join21(executionEngineRoot, "agent-assets");
|
|
4617
|
+
const registry = readJson3(join21(agentAssetsRoot, "registry.json"));
|
|
4213
4618
|
const assets = isRecord3(registry) && Array.isArray(registry.assets) ? registry.assets : [];
|
|
4214
4619
|
const registeredSkills = assets.filter((asset) => isRecord3(asset) && asset.kind === "skill");
|
|
4215
|
-
const bundleRoot =
|
|
4620
|
+
const bundleRoot = join21(agentAssetsRoot, "bundles");
|
|
4216
4621
|
const bundleFiles = safeReadDir(bundleRoot).filter((file) => file.endsWith(".json"));
|
|
4217
4622
|
const bundledIds = /* @__PURE__ */ new Set();
|
|
4218
4623
|
for (const file of bundleFiles) {
|
|
4219
|
-
const bundle = readJson3(
|
|
4624
|
+
const bundle = readJson3(join21(bundleRoot, file));
|
|
4220
4625
|
if (!isRecord3(bundle) || !Array.isArray(bundle.assets)) continue;
|
|
4221
4626
|
for (const id of bundle.assets) if (typeof id === "string") bundledIds.add(id);
|
|
4222
4627
|
}
|
|
4223
|
-
const sourceRoots = [
|
|
4224
|
-
const source = sourceRoots.reduce((count, root) => count + safeReadDir(root).filter((name) =>
|
|
4628
|
+
const sourceRoots = [join21(agentAssetsRoot, "skills/pie-skills"), join21(agentAssetsRoot, "skills/npx-skills/.agents/skills")];
|
|
4629
|
+
const source = sourceRoots.reduce((count, root) => count + safeReadDir(root).filter((name) => existsSync20(join21(root, name, "SKILL.md"))).length, 0);
|
|
4225
4630
|
return {
|
|
4226
4631
|
source,
|
|
4227
4632
|
registered: registeredSkills.length,
|
|
@@ -4235,7 +4640,7 @@ function jsonObjectKeys(path, key) {
|
|
|
4235
4640
|
return Object.keys(value[key]).sort();
|
|
4236
4641
|
}
|
|
4237
4642
|
function tomlMcpNames(path) {
|
|
4238
|
-
if (!
|
|
4643
|
+
if (!existsSync20(path)) return [];
|
|
4239
4644
|
const names = /* @__PURE__ */ new Set();
|
|
4240
4645
|
for (const line of safeRead(path).split(/\r?\n/)) {
|
|
4241
4646
|
const match = line.match(/^\s*\[mcp_servers\.(?:"([^"]+)"|([^\.\]]+))\]\s*$/);
|
|
@@ -4274,7 +4679,7 @@ function safeIsDirectory(path) {
|
|
|
4274
4679
|
}
|
|
4275
4680
|
function pathLexists(path) {
|
|
4276
4681
|
try {
|
|
4277
|
-
|
|
4682
|
+
lstatSync8(path);
|
|
4278
4683
|
return true;
|
|
4279
4684
|
} catch {
|
|
4280
4685
|
return false;
|
|
@@ -4301,14 +4706,17 @@ function isRecord3(value) {
|
|
|
4301
4706
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4302
4707
|
}
|
|
4303
4708
|
function findDashboardAssets() {
|
|
4304
|
-
const packageRoot2 =
|
|
4709
|
+
const packageRoot2 = dirname13(dirname13(fileURLToPath4(import.meta.url)));
|
|
4305
4710
|
const candidates = [
|
|
4306
4711
|
process.env.PGS_DASHBOARD_ASSETS_DIR,
|
|
4307
|
-
|
|
4308
|
-
|
|
4309
|
-
|
|
4712
|
+
join21(packageRoot2, ".dashboard-build"),
|
|
4713
|
+
join21(packageRoot2, "assets/portfolio-dashboard"),
|
|
4714
|
+
join21(process.cwd(), ".dashboard-build"),
|
|
4715
|
+
join21(process.cwd(), "assets/portfolio-dashboard"),
|
|
4716
|
+
join21(process.cwd(), "packages/pro-gov/.dashboard-build"),
|
|
4717
|
+
join21(process.cwd(), "packages/pro-gov/assets/portfolio-dashboard")
|
|
4310
4718
|
].filter((value) => Boolean(value));
|
|
4311
|
-
const match = candidates.find((path) =>
|
|
4719
|
+
const match = candidates.find((path) => existsSync20(join21(path, "index.html")));
|
|
4312
4720
|
if (!match) throw new Error("Portfolio dashboard assets were not built. Run pnpm --filter @pieai/pro-gov build.");
|
|
4313
4721
|
return match;
|
|
4314
4722
|
}
|
|
@@ -4398,12 +4806,22 @@ function runPortfolioDoctor(args) {
|
|
|
4398
4806
|
const output = { configPath: loaded.configPath, ...result };
|
|
4399
4807
|
if (options.value.json) {
|
|
4400
4808
|
console.log(JSON.stringify(output, null, 2));
|
|
4401
|
-
} else if (result.ok) {
|
|
4402
|
-
console.log(`portfolio doctor passed (${targets.length} targets)`);
|
|
4403
4809
|
} else {
|
|
4404
|
-
for (const
|
|
4810
|
+
for (const warning of result.hostSsot.userSkills.issues) {
|
|
4811
|
+
console.log(`user host-ssot-warning: ${warning}`);
|
|
4812
|
+
}
|
|
4405
4813
|
for (const target of result.targets) {
|
|
4406
|
-
for (const
|
|
4814
|
+
for (const warning of target.hostSsot.issues) {
|
|
4815
|
+
console.log(`${target.id} host-ssot-warning: ${warning}`);
|
|
4816
|
+
}
|
|
4817
|
+
}
|
|
4818
|
+
if (result.ok) {
|
|
4819
|
+
console.log(`portfolio doctor passed (${targets.length} targets)`);
|
|
4820
|
+
} else {
|
|
4821
|
+
for (const issue of result.hostTooling.issues) console.log(`${issue.host} ${issue.type}: ${issue.message}`);
|
|
4822
|
+
for (const target of result.targets) {
|
|
4823
|
+
for (const issue of target.issues) console.log(`${target.id} ${issue.type}: ${issue.message}`);
|
|
4824
|
+
}
|
|
4407
4825
|
}
|
|
4408
4826
|
}
|
|
4409
4827
|
return result.ok ? 0 : 1;
|
|
@@ -4660,8 +5078,8 @@ function isHost2(value) {
|
|
|
4660
5078
|
return value === "codex" || value === "claude-code" || value === "gemini-cli" || value === "antigravity";
|
|
4661
5079
|
}
|
|
4662
5080
|
function findPortfolioAgentAssetsDir(manifest) {
|
|
4663
|
-
const agentAssetsDir = manifest?.executionEngine?.path ?
|
|
4664
|
-
return agentAssetsDir &&
|
|
5081
|
+
const agentAssetsDir = manifest?.executionEngine?.path ? join22(manifest.executionEngine.path, "agent-assets") : void 0;
|
|
5082
|
+
return agentAssetsDir && existsSync21(join22(agentAssetsDir, "registry.json")) ? agentAssetsDir : void 0;
|
|
4665
5083
|
}
|
|
4666
5084
|
function printUsage4() {
|
|
4667
5085
|
console.error("Usage:");
|
|
@@ -4673,8 +5091,8 @@ function printUsage4() {
|
|
|
4673
5091
|
}
|
|
4674
5092
|
|
|
4675
5093
|
// src/commands/sync.ts
|
|
4676
|
-
import { existsSync as
|
|
4677
|
-
import { join as
|
|
5094
|
+
import { existsSync as existsSync22, lstatSync as lstatSync9, readFileSync as readFileSync16, readlinkSync as readlinkSync4 } from "node:fs";
|
|
5095
|
+
import { join as join23 } from "node:path";
|
|
4678
5096
|
function runSync(args) {
|
|
4679
5097
|
const check = args.includes("--check");
|
|
4680
5098
|
if (!check) {
|
|
@@ -4702,8 +5120,9 @@ function runSync(args) {
|
|
|
4702
5120
|
console.log("pro-gov sync check");
|
|
4703
5121
|
console.log(`profile: ${profile}`);
|
|
4704
5122
|
for (const file of planStarterFiles(profile)) {
|
|
4705
|
-
const targetPath =
|
|
4706
|
-
|
|
5123
|
+
const targetPath = join23(process.cwd(), file.targetPath);
|
|
5124
|
+
const stat = safeLstat3(targetPath);
|
|
5125
|
+
if (!stat) {
|
|
4707
5126
|
if (file.ownership === "optional-guardrail") continue;
|
|
4708
5127
|
console.log(`missing: ${file.targetPath}`);
|
|
4709
5128
|
differences += 1;
|
|
@@ -4711,6 +5130,20 @@ function runSync(args) {
|
|
|
4711
5130
|
}
|
|
4712
5131
|
if (file.ownership === "optional-guardrail") continue;
|
|
4713
5132
|
if (file.ownership === "project-local-seed") continue;
|
|
5133
|
+
if (file.kind === "directory") {
|
|
5134
|
+
if (!stat.isDirectory()) {
|
|
5135
|
+
console.log(`different: ${file.targetPath}`);
|
|
5136
|
+
differences += 1;
|
|
5137
|
+
}
|
|
5138
|
+
continue;
|
|
5139
|
+
}
|
|
5140
|
+
if (file.kind === "symlink") {
|
|
5141
|
+
if (!stat.isSymbolicLink() || readlinkSync4(targetPath) !== file.linkTarget) {
|
|
5142
|
+
console.log(`different: ${file.targetPath}`);
|
|
5143
|
+
differences += 1;
|
|
5144
|
+
}
|
|
5145
|
+
continue;
|
|
5146
|
+
}
|
|
4714
5147
|
const source = readFileSync16(file.absoluteSourcePath, "utf8");
|
|
4715
5148
|
const target = readFileSync16(targetPath, "utf8");
|
|
4716
5149
|
if (!matchesExpectedContent(file.targetPath, source, target)) {
|
|
@@ -4746,10 +5179,17 @@ function normalizeMarkdownTableCell(cell) {
|
|
|
4746
5179
|
}
|
|
4747
5180
|
function inferInstalledProfile(root) {
|
|
4748
5181
|
const installed = ["engineering-runtime", "doc-only"].filter(
|
|
4749
|
-
(profile) =>
|
|
5182
|
+
(profile) => existsSync22(join23(root, `docs/governance/agents-routing/${profile}-v1.1.md`))
|
|
4750
5183
|
);
|
|
4751
5184
|
return installed.length === 1 ? installed[0] : void 0;
|
|
4752
5185
|
}
|
|
5186
|
+
function safeLstat3(path) {
|
|
5187
|
+
try {
|
|
5188
|
+
return lstatSync9(path);
|
|
5189
|
+
} catch {
|
|
5190
|
+
return void 0;
|
|
5191
|
+
}
|
|
5192
|
+
}
|
|
4753
5193
|
function readFlag2(args, flag) {
|
|
4754
5194
|
const index = args.indexOf(flag);
|
|
4755
5195
|
const value = index >= 0 ? args[index + 1] : void 0;
|