@pieai/pro-gov 0.5.3 → 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 +788 -194
- 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,34 +3864,50 @@ 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 {
|
|
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";
|
|
3589
3878
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
3879
|
+
var CURRENT_ROUTER_VERSION = "1.1";
|
|
3590
3880
|
function inspectPortfolioAiHealth(options) {
|
|
3591
3881
|
const endpoints = collectEndpoints(options.manifest);
|
|
3592
|
-
const secretsRoot = options.secretsRoot ??
|
|
3593
|
-
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();
|
|
3884
|
+
const grokVersion = commandVersion("grok");
|
|
3594
3885
|
const executionEngineRoot = options.manifest.executionEngine?.path;
|
|
3595
3886
|
const skillRegistry = inspectSkillRegistry(executionEngineRoot);
|
|
3596
|
-
const expectedPackageVersion = packageVersion(
|
|
3597
|
-
const repositories = endpoints.map(({ endpoint, role }) => inspectRepository(
|
|
3887
|
+
const expectedPackageVersion = packageVersion(join21(executionEngineRoot ?? "", "packages/pro-gov/package.json"));
|
|
3888
|
+
const repositories = endpoints.map(({ endpoint, role }) => inspectRepository(
|
|
3889
|
+
endpoint,
|
|
3890
|
+
role,
|
|
3891
|
+
secretsRoot,
|
|
3892
|
+
homeDir,
|
|
3893
|
+
expectedPackageVersion,
|
|
3894
|
+
options.manifest.technologyGovernance,
|
|
3895
|
+
grokVersion
|
|
3896
|
+
));
|
|
3598
3897
|
const summary = { healthy: 0, attention: 0, unhealthy: 0 };
|
|
3599
3898
|
for (const repository of repositories) summary[repository.status] += 1;
|
|
3600
3899
|
return {
|
|
3601
|
-
schemaVersion:
|
|
3900
|
+
schemaVersion: 4,
|
|
3602
3901
|
portfolioId: options.manifest.portfolioId,
|
|
3603
3902
|
generatedAt: options.generatedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
3604
|
-
privacy: "Names, paths, counts, and configuration
|
|
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.",
|
|
3605
3904
|
secretsRoot: inspectSecretsRoot(secretsRoot),
|
|
3606
|
-
|
|
3905
|
+
hostEnvironment: inspectHostEnvironment(
|
|
3906
|
+
homeDir,
|
|
3907
|
+
grokVersion,
|
|
3908
|
+
endpoints.map(({ endpoint }) => endpoint.path),
|
|
3909
|
+
options.manifest.specialistChecks?.devspace
|
|
3910
|
+
),
|
|
3607
3911
|
skillRegistry,
|
|
3608
3912
|
technologyGovernance: {
|
|
3609
3913
|
strategySource: options.manifest.technologyGovernance?.strategySource,
|
|
@@ -3618,15 +3922,15 @@ function writePortfolioAiHealthReport(report, outDir) {
|
|
|
3618
3922
|
mkdirSync8(outDir, { recursive: true });
|
|
3619
3923
|
const dashboardAssets = findDashboardAssets();
|
|
3620
3924
|
for (const file of ["index.html", "app.js", "app.css"]) {
|
|
3621
|
-
const source =
|
|
3622
|
-
if (!
|
|
3623
|
-
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));
|
|
3624
3928
|
}
|
|
3625
|
-
const jsonPath =
|
|
3626
|
-
const htmlPath =
|
|
3929
|
+
const jsonPath = join21(outDir, "portfolio-ai-health.json");
|
|
3930
|
+
const htmlPath = join21(outDir, "index.html");
|
|
3627
3931
|
writeFileSync7(jsonPath, `${JSON.stringify(report, null, 2)}
|
|
3628
3932
|
`);
|
|
3629
|
-
writeFileSync7(
|
|
3933
|
+
writeFileSync7(join21(outDir, "data.js"), `window.__PORTFOLIO_AI_HEALTH__ = ${safeJavaScriptJson(report)};
|
|
3630
3934
|
`);
|
|
3631
3935
|
return { jsonPath, htmlPath };
|
|
3632
3936
|
}
|
|
@@ -3637,23 +3941,28 @@ function collectEndpoints(manifest) {
|
|
|
3637
3941
|
for (const target of manifest.targets) result.push({ endpoint: target, role: "target" });
|
|
3638
3942
|
const seen = /* @__PURE__ */ new Set();
|
|
3639
3943
|
return result.filter(({ endpoint }) => {
|
|
3640
|
-
const key =
|
|
3944
|
+
const key = resolve5(endpoint.path);
|
|
3641
3945
|
if (seen.has(key)) return false;
|
|
3642
3946
|
seen.add(key);
|
|
3643
3947
|
return true;
|
|
3644
3948
|
});
|
|
3645
3949
|
}
|
|
3646
|
-
function inspectRepository(endpoint, role, secretsRoot, expectedPackageVersion, technologyGovernance) {
|
|
3950
|
+
function inspectRepository(endpoint, role, secretsRoot, homeDir, expectedPackageVersion, technologyGovernance, grokVersion) {
|
|
3647
3951
|
const root = endpoint.path;
|
|
3648
3952
|
const git = inspectGit2(root);
|
|
3649
3953
|
const entries = inspectEntries(root);
|
|
3650
|
-
const
|
|
3954
|
+
const grokInspection = inspectGrokProject(root, homeDir, grokVersion);
|
|
3955
|
+
const skills = inspectSkills(root, grokInspection);
|
|
3956
|
+
const hostSsot = inspectProjectHostSsot(root);
|
|
3651
3957
|
const hooks = inspectHooks(root);
|
|
3652
3958
|
const docs = inspectDocs(root, role === "execution-engine" ? void 0 : expectedPackageVersion);
|
|
3653
3959
|
const mcp = {
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3960
|
+
codexProject: tomlMcpNames(join21(root, ".codex/config.toml")),
|
|
3961
|
+
claudeCodeProjectShared: jsonObjectKeys(join21(root, ".mcp.json"), "mcpServers"),
|
|
3962
|
+
claudeCodeProjectLocal: claudeProjectLocalMcpNames(homeDir, root),
|
|
3963
|
+
grokProject: tomlMcpNames(join21(root, ".grok/config.toml")),
|
|
3964
|
+
grokEffective: grokInspection.effectiveMcp,
|
|
3965
|
+
grokInspection: grokInspection.inspection
|
|
3657
3966
|
};
|
|
3658
3967
|
const secrets = inspectRepositorySecrets(root, endpoint.id, secretsRoot, git.isRepository, endpoint.environmentPolicy);
|
|
3659
3968
|
const projectModel = inspectProjectModel(root, endpoint, technologyGovernance);
|
|
@@ -3675,6 +3984,9 @@ function inspectRepository(endpoint, role, secretsRoot, expectedPackageVersion,
|
|
|
3675
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");
|
|
3676
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");
|
|
3677
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`);
|
|
3678
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");
|
|
3679
3991
|
const liveEnv = secrets.repositoryEnvFiles.filter((file) => !file.template && !file.fixture);
|
|
3680
3992
|
const envNeedsCentralReview = liveEnv.filter((file) => !file.localOnly);
|
|
@@ -3683,6 +3995,7 @@ function inspectRepository(endpoint, role, secretsRoot, expectedPackageVersion,
|
|
|
3683
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");
|
|
3684
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");
|
|
3685
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`);
|
|
3686
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");
|
|
3687
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");
|
|
3688
4001
|
const missingBaseline = projectModel.baseline.filter((technology) => !technology.detected && !hasBaselineException(projectModel, technology.id));
|
|
@@ -3694,27 +4007,29 @@ function inspectRepository(endpoint, role, secretsRoot, expectedPackageVersion,
|
|
|
3694
4007
|
role,
|
|
3695
4008
|
path: root,
|
|
3696
4009
|
profile: "profile" in endpoint ? endpoint.profile : void 0,
|
|
3697
|
-
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)),
|
|
3698
4011
|
recommendations,
|
|
3699
4012
|
git,
|
|
3700
4013
|
entries,
|
|
3701
4014
|
hooks,
|
|
3702
4015
|
mcp,
|
|
3703
4016
|
skills,
|
|
4017
|
+
hostSsot,
|
|
3704
4018
|
secrets,
|
|
3705
4019
|
docs,
|
|
3706
4020
|
projectModel
|
|
3707
4021
|
};
|
|
3708
4022
|
}
|
|
3709
|
-
function deriveStatus(role, entries, git, hooks, skills, secrets, docs, projectModel, technologyGovernanceConfigured) {
|
|
4023
|
+
function deriveStatus(role, entries, git, hooks, skills, hostSsot, secrets, docs, projectModel, technologyGovernanceConfigured) {
|
|
3710
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";
|
|
3711
4025
|
const missingBaseline = projectModel.baseline.some((technology) => !technology.detected && !hasBaselineException(projectModel, technology.id));
|
|
3712
4026
|
const missingSelected = projectModel.optionalCapabilities.some((technology) => technology.selected && !technology.detected);
|
|
3713
4027
|
const liveEnv = secrets.repositoryEnvFiles.filter((file) => !file.template && !file.fixture);
|
|
3714
4028
|
const secretMaterializationNeedsReview = liveEnv.some((file) => !file.centralized && !file.localOnly);
|
|
3715
4029
|
const packageVersionNeedsReview = docs.packages.expected !== void 0 && !docs.packages.aligned;
|
|
4030
|
+
const routerVersionNeedsReview = !docs.routerAligned;
|
|
3716
4031
|
const targetManifestMissing = role === "target" && !docs.manifest;
|
|
3717
|
-
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";
|
|
3718
4033
|
return "healthy";
|
|
3719
4034
|
}
|
|
3720
4035
|
function inspectProjectModel(root, endpoint, governance) {
|
|
@@ -3724,7 +4039,7 @@ function inspectProjectModel(root, endpoint, governance) {
|
|
|
3724
4039
|
const detection = (id) => {
|
|
3725
4040
|
const technology = technologyById.get(id);
|
|
3726
4041
|
const packageMatch = technology?.packages?.some((name) => packages.has(name)) ?? false;
|
|
3727
|
-
const fileMatch = technology?.files?.some((path) =>
|
|
4042
|
+
const fileMatch = technology?.files?.some((path) => existsSync20(join21(root, path))) ?? false;
|
|
3728
4043
|
return { id, label: technology?.label ?? id, detected: packageMatch || fileMatch };
|
|
3729
4044
|
};
|
|
3730
4045
|
const selected = new Set(endpoint.capabilities ?? []);
|
|
@@ -3747,10 +4062,10 @@ function collectPackageNames(root) {
|
|
|
3747
4062
|
try {
|
|
3748
4063
|
files = splitLines(execFileSync("git", ["ls-files", "*package.json"], { cwd: root, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }));
|
|
3749
4064
|
} catch {
|
|
3750
|
-
if (
|
|
4065
|
+
if (existsSync20(join21(root, "package.json"))) files = ["package.json"];
|
|
3751
4066
|
}
|
|
3752
4067
|
for (const file of files) {
|
|
3753
|
-
const packageJson = readJson3(
|
|
4068
|
+
const packageJson = readJson3(join21(root, file));
|
|
3754
4069
|
if (!isRecord3(packageJson)) continue;
|
|
3755
4070
|
for (const section of ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]) {
|
|
3756
4071
|
const dependencies = packageJson[section];
|
|
@@ -3788,15 +4103,15 @@ function inspectGit2(root) {
|
|
|
3788
4103
|
};
|
|
3789
4104
|
}
|
|
3790
4105
|
function inspectEntries(root) {
|
|
3791
|
-
const agentsPath =
|
|
3792
|
-
const agents = !
|
|
3793
|
-
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");
|
|
3794
4109
|
let claude = "missing";
|
|
3795
4110
|
if (pathLexists(claudePath)) {
|
|
3796
|
-
const info =
|
|
4111
|
+
const info = lstatSync8(claudePath);
|
|
3797
4112
|
if (info.isSymbolicLink()) {
|
|
3798
4113
|
try {
|
|
3799
|
-
claude =
|
|
4114
|
+
claude = realpathSync4(claudePath) === realpathSync4(agentsPath) ? "agents-symlink" : "custom";
|
|
3800
4115
|
} catch {
|
|
3801
4116
|
claude = "dangling-symlink";
|
|
3802
4117
|
}
|
|
@@ -3808,12 +4123,12 @@ function inspectEntries(root) {
|
|
|
3808
4123
|
return { agents, claude, gemini: inspectOptionalEntry(root, "GEMINI.md", agentsPath) };
|
|
3809
4124
|
}
|
|
3810
4125
|
function inspectOptionalEntry(root, filename, agentsPath) {
|
|
3811
|
-
const path =
|
|
4126
|
+
const path = join21(root, filename);
|
|
3812
4127
|
if (!pathLexists(path)) return "missing";
|
|
3813
|
-
const info =
|
|
4128
|
+
const info = lstatSync8(path);
|
|
3814
4129
|
if (info.isSymbolicLink()) {
|
|
3815
4130
|
try {
|
|
3816
|
-
return
|
|
4131
|
+
return realpathSync4(path) === realpathSync4(agentsPath) ? "agents-symlink" : "custom";
|
|
3817
4132
|
} catch {
|
|
3818
4133
|
return "dangling-symlink";
|
|
3819
4134
|
}
|
|
@@ -3821,8 +4136,8 @@ function inspectOptionalEntry(root, filename, agentsPath) {
|
|
|
3821
4136
|
const content = safeRead(path);
|
|
3822
4137
|
return /AGENTS\.md/.test(content) && content.length < 2e3 ? "thin-adapter" : "custom";
|
|
3823
4138
|
}
|
|
3824
|
-
function inspectSkills(root) {
|
|
3825
|
-
const lock = readJson3(
|
|
4139
|
+
function inspectSkills(root, grokInspection) {
|
|
4140
|
+
const lock = readJson3(join21(root, ".pro-gov/assets.lock.json"));
|
|
3826
4141
|
const managed = /* @__PURE__ */ new Set();
|
|
3827
4142
|
const bundleIds = stringArray(isRecord3(lock) ? lock.bundleIds : void 0);
|
|
3828
4143
|
if (isRecord3(lock) && Array.isArray(lock.assets)) {
|
|
@@ -3832,14 +4147,14 @@ function inspectSkills(root) {
|
|
|
3832
4147
|
if (match) managed.add(match[1]);
|
|
3833
4148
|
}
|
|
3834
4149
|
}
|
|
3835
|
-
const skillRoot =
|
|
4150
|
+
const skillRoot = join21(root, ".agents/skills");
|
|
3836
4151
|
const canonical = pathLexists(skillRoot) && safeIsDirectory(skillRoot) ? safeReadDir(skillRoot).filter((name) => !name.startsWith(".")).map((name) => {
|
|
3837
|
-
const path =
|
|
3838
|
-
const stat =
|
|
4152
|
+
const path = join21(skillRoot, name);
|
|
4153
|
+
const stat = lstatSync8(path);
|
|
3839
4154
|
let kind = stat.isSymbolicLink() ? "symlink" : stat.isDirectory() ? "directory" : "file";
|
|
3840
4155
|
if (stat.isSymbolicLink()) {
|
|
3841
4156
|
try {
|
|
3842
|
-
|
|
4157
|
+
realpathSync4(path);
|
|
3843
4158
|
} catch {
|
|
3844
4159
|
kind = "dangling-symlink";
|
|
3845
4160
|
}
|
|
@@ -3857,17 +4172,23 @@ function inspectSkills(root) {
|
|
|
3857
4172
|
installed: canonical.filter((item) => item.managed && item.kind !== "dangling-symlink").length,
|
|
3858
4173
|
discoverable: canonical.filter((item) => item.kind !== "dangling-symlink").length,
|
|
3859
4174
|
runtime: "unobservable"
|
|
4175
|
+
},
|
|
4176
|
+
hosts: {
|
|
4177
|
+
codexProject: canonical.filter((item) => item.kind !== "dangling-symlink").length,
|
|
4178
|
+
claudeCodeProject: inspectSkillRoot(join21(root, ".claude/skills")).names.length,
|
|
4179
|
+
grokNativeProject: inspectSkillRoot(join21(root, ".grok/skills")).names.length,
|
|
4180
|
+
grokEffective: grokInspection.skills
|
|
3860
4181
|
}
|
|
3861
4182
|
};
|
|
3862
4183
|
}
|
|
3863
4184
|
function inspectClaudeSkillRoot(root) {
|
|
3864
|
-
const path =
|
|
4185
|
+
const path = join21(root, ".claude/skills");
|
|
3865
4186
|
if (!pathLexists(path)) return "missing";
|
|
3866
|
-
const stat =
|
|
4187
|
+
const stat = lstatSync8(path);
|
|
3867
4188
|
if (stat.isSymbolicLink()) {
|
|
3868
4189
|
try {
|
|
3869
|
-
const target =
|
|
3870
|
-
return target ===
|
|
4190
|
+
const target = realpathSync4(path);
|
|
4191
|
+
return target === realpathSync4(join21(root, ".agents/skills")) ? "shared-root" : "other";
|
|
3871
4192
|
} catch {
|
|
3872
4193
|
return "dangling-symlink";
|
|
3873
4194
|
}
|
|
@@ -3875,7 +4196,7 @@ function inspectClaudeSkillRoot(root) {
|
|
|
3875
4196
|
return stat.isDirectory() ? "duplicate-directory" : "other";
|
|
3876
4197
|
}
|
|
3877
4198
|
function inspectRepositorySecrets(root, id, secretsRoot, isRepository, environmentPolicy) {
|
|
3878
|
-
const centralPath =
|
|
4199
|
+
const centralPath = join21(secretsRoot, id);
|
|
3879
4200
|
const centralRealPath = safeRealpath(centralPath);
|
|
3880
4201
|
const localOnlyReasons = new Map(
|
|
3881
4202
|
(environmentPolicy?.localOnly ?? []).map((entry) => [entry.path, entry.reason])
|
|
@@ -3887,16 +4208,16 @@ function inspectRepositorySecrets(root, id, secretsRoot, isRepository, environme
|
|
|
3887
4208
|
tracked: isRepository ? gitTracks(root, path) : false,
|
|
3888
4209
|
template: isEnvironmentTemplate(path),
|
|
3889
4210
|
fixture: isEnvironmentFixture(path),
|
|
3890
|
-
symlink:
|
|
3891
|
-
centralized: pointsInside(
|
|
4211
|
+
symlink: lstatSync8(join21(root, path)).isSymbolicLink(),
|
|
4212
|
+
centralized: pointsInside(join21(root, path), centralRealPath),
|
|
3892
4213
|
localOnly: localOnlyReason !== void 0,
|
|
3893
4214
|
...localOnlyReason !== void 0 ? { localOnlyReason } : {}
|
|
3894
4215
|
};
|
|
3895
4216
|
});
|
|
3896
4217
|
return {
|
|
3897
|
-
centralDirectory:
|
|
3898
|
-
centralMode:
|
|
3899
|
-
centralFiles:
|
|
4218
|
+
centralDirectory: existsSync20(centralPath) ? "present" : "absent",
|
|
4219
|
+
centralMode: existsSync20(centralPath) ? modeString(statSync4(centralPath).mode) : void 0,
|
|
4220
|
+
centralFiles: existsSync20(centralPath) ? collectCentralSecretFiles(centralPath) : [],
|
|
3900
4221
|
repositoryEnvFiles: envFiles
|
|
3901
4222
|
};
|
|
3902
4223
|
}
|
|
@@ -3907,9 +4228,9 @@ function collectEnvironmentFiles(root, current = root, depth = 0) {
|
|
|
3907
4228
|
try {
|
|
3908
4229
|
for (const entry of readdirSync8(current, { withFileTypes: true })) {
|
|
3909
4230
|
if (entry.isDirectory()) {
|
|
3910
|
-
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));
|
|
3911
4232
|
} else if (isEnvironmentFilename(entry.name) && !isProviderGeneratedEnvironmentFile(entry.name)) {
|
|
3912
|
-
found.push(relative8(root,
|
|
4233
|
+
found.push(relative8(root, join21(current, entry.name)));
|
|
3913
4234
|
}
|
|
3914
4235
|
}
|
|
3915
4236
|
} catch {
|
|
@@ -3922,9 +4243,9 @@ function collectCentralSecretFiles(root, current = root, depth = 0) {
|
|
|
3922
4243
|
const found = [];
|
|
3923
4244
|
try {
|
|
3924
4245
|
for (const entry of readdirSync8(current, { withFileTypes: true })) {
|
|
3925
|
-
const path =
|
|
4246
|
+
const path = join21(current, entry.name);
|
|
3926
4247
|
if (entry.isDirectory()) found.push(...collectCentralSecretFiles(root, path, depth + 1));
|
|
3927
|
-
else found.push({ path: relative8(root, path), mode: modeString(
|
|
4248
|
+
else found.push({ path: relative8(root, path), mode: modeString(lstatSync8(path).mode) });
|
|
3928
4249
|
}
|
|
3929
4250
|
} catch {
|
|
3930
4251
|
return found;
|
|
@@ -3949,13 +4270,13 @@ function isEnvironmentFixture(path) {
|
|
|
3949
4270
|
}
|
|
3950
4271
|
function safeRealpath(path) {
|
|
3951
4272
|
try {
|
|
3952
|
-
return
|
|
4273
|
+
return realpathSync4(path);
|
|
3953
4274
|
} catch {
|
|
3954
4275
|
return void 0;
|
|
3955
4276
|
}
|
|
3956
4277
|
}
|
|
3957
4278
|
function pointsInside(path, expectedRoot) {
|
|
3958
|
-
if (!expectedRoot || !
|
|
4279
|
+
if (!expectedRoot || !lstatSync8(path).isSymbolicLink()) return false;
|
|
3959
4280
|
const target = safeRealpath(path);
|
|
3960
4281
|
if (!target) return false;
|
|
3961
4282
|
const fromRoot = relative8(expectedRoot, target);
|
|
@@ -3965,15 +4286,251 @@ function hasUnsafeCentralSecretPermissions(secrets) {
|
|
|
3965
4286
|
return secrets.centralDirectory === "present" && (secrets.centralMode !== "700" || secrets.centralFiles.some((file) => file.mode !== "600"));
|
|
3966
4287
|
}
|
|
3967
4288
|
function inspectSecretsRoot(path) {
|
|
3968
|
-
return
|
|
3969
|
-
}
|
|
3970
|
-
function
|
|
3971
|
-
|
|
4289
|
+
return existsSync20(path) ? { path, exists: true, mode: modeString(statSync4(path).mode) } : { path, exists: false };
|
|
4290
|
+
}
|
|
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 = {
|
|
4296
|
+
mcp: {
|
|
4297
|
+
codexUser: { path: codexConfig, names: tomlMcpNames(codexConfig) },
|
|
4298
|
+
claudeCodeUser: { path: claudeConfig, names: jsonObjectKeys(claudeConfig, "mcpServers") },
|
|
4299
|
+
grokUser: { path: grokConfig, names: tomlMcpNames(grokConfig) }
|
|
4300
|
+
},
|
|
4301
|
+
skills: {
|
|
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)
|
|
4308
|
+
},
|
|
4309
|
+
grok: {
|
|
4310
|
+
available: grokVersion !== void 0,
|
|
4311
|
+
...grokVersion ? { version: grokVersion } : {},
|
|
4312
|
+
inspectionCommand: "grok inspect --json"
|
|
4313
|
+
}
|
|
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`);
|
|
3972
4371
|
return {
|
|
3973
|
-
|
|
3974
|
-
|
|
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
|
|
3975
4394
|
};
|
|
3976
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}`);
|
|
4416
|
+
}
|
|
4417
|
+
function inspectSkillRoot(path) {
|
|
4418
|
+
const exists = pathLexists(path) && safeIsDirectory(path);
|
|
4419
|
+
const names = exists ? safeReadDir(path).filter((name) => !name.startsWith(".") && existsSync20(join21(path, name, "SKILL.md"))) : [];
|
|
4420
|
+
return { path, exists, names };
|
|
4421
|
+
}
|
|
4422
|
+
function claudeProjectLocalMcpNames(homeDir, root) {
|
|
4423
|
+
if (!homeDir) return [];
|
|
4424
|
+
const value = readJson3(join21(homeDir, ".claude.json"));
|
|
4425
|
+
if (!isRecord3(value) || !isRecord3(value.projects)) return [];
|
|
4426
|
+
const candidates = new Set([resolve5(root), safeRealpath(root)].filter((path) => Boolean(path)));
|
|
4427
|
+
const names = /* @__PURE__ */ new Set();
|
|
4428
|
+
for (const [path, project] of Object.entries(value.projects)) {
|
|
4429
|
+
const projectPaths = [resolve5(path), safeRealpath(path)].filter((candidate) => Boolean(candidate));
|
|
4430
|
+
if (!projectPaths.some((candidate) => candidates.has(candidate)) || !isRecord3(project) || !isRecord3(project.mcpServers)) continue;
|
|
4431
|
+
for (const name of Object.keys(project.mcpServers)) names.add(name);
|
|
4432
|
+
}
|
|
4433
|
+
return [...names].sort();
|
|
4434
|
+
}
|
|
4435
|
+
function commandVersion(command2) {
|
|
4436
|
+
try {
|
|
4437
|
+
const output = execFileSync(command2, ["version"], {
|
|
4438
|
+
encoding: "utf8",
|
|
4439
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
4440
|
+
timeout: 3e3
|
|
4441
|
+
});
|
|
4442
|
+
return output.match(/\b\d+\.\d+\.\d+\b/)?.[0];
|
|
4443
|
+
} catch {
|
|
4444
|
+
return void 0;
|
|
4445
|
+
}
|
|
4446
|
+
}
|
|
4447
|
+
function inspectGrokProject(root, homeDir, grokVersion) {
|
|
4448
|
+
const empty = (inspection) => ({
|
|
4449
|
+
inspection,
|
|
4450
|
+
effectiveMcp: [],
|
|
4451
|
+
skills: { inspection, total: 0, project: 0, userOrCompatible: 0, plugin: 0, bundled: 0 },
|
|
4452
|
+
claudeCompatibility: "unknown",
|
|
4453
|
+
cursorCompatibility: "unknown"
|
|
4454
|
+
});
|
|
4455
|
+
if (!grokVersion) return empty("unavailable");
|
|
4456
|
+
try {
|
|
4457
|
+
const value = JSON.parse(execFileSync("grok", ["inspect", "--json"], {
|
|
4458
|
+
cwd: root,
|
|
4459
|
+
encoding: "utf8",
|
|
4460
|
+
env: { ...process.env, HOME: homeDir, GROK_HOME: join21(homeDir, ".grok") },
|
|
4461
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
4462
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
4463
|
+
timeout: 8e3
|
|
4464
|
+
}));
|
|
4465
|
+
if (!isRecord3(value)) return empty("failed");
|
|
4466
|
+
const userClaudeNames = new Set(jsonObjectKeys(join21(homeDir, ".claude.json"), "mcpServers"));
|
|
4467
|
+
const localClaudeNames = new Set(claudeProjectLocalMcpNames(homeDir, root));
|
|
4468
|
+
const effectiveMcp = Array.isArray(value.mcpServers) ? value.mcpServers.flatMap((item) => {
|
|
4469
|
+
if (!isRecord3(item) || typeof item.name !== "string") return [];
|
|
4470
|
+
const source = isRecord3(item.source) ? item.source : {};
|
|
4471
|
+
const sourceType = typeof source.type === "string" ? source.type : "unknown";
|
|
4472
|
+
const sourcePath = typeof source.path === "string" ? source.path : void 0;
|
|
4473
|
+
const vendor = typeof item.vendor === "string" ? item.vendor : sourceType;
|
|
4474
|
+
return [{
|
|
4475
|
+
name: item.name,
|
|
4476
|
+
vendor,
|
|
4477
|
+
scope: inferGrokMcpScope(item.name, sourceType, sourcePath, root, homeDir, userClaudeNames, localClaudeNames),
|
|
4478
|
+
sourceType,
|
|
4479
|
+
...sourcePath ? { sourcePath } : {}
|
|
4480
|
+
}];
|
|
4481
|
+
}) : [];
|
|
4482
|
+
const skillCounts = { project: 0, userOrCompatible: 0, plugin: 0, bundled: 0 };
|
|
4483
|
+
if (Array.isArray(value.skills)) {
|
|
4484
|
+
for (const item of value.skills) {
|
|
4485
|
+
if (!isRecord3(item) || !isRecord3(item.source)) continue;
|
|
4486
|
+
const type = item.source.type;
|
|
4487
|
+
const path = item.source.path;
|
|
4488
|
+
if (typeof path === "string" && path.includes(sep + ".grok" + sep + "bundled" + sep)) skillCounts.bundled += 1;
|
|
4489
|
+
else if (type === "project") skillCounts.project += 1;
|
|
4490
|
+
else if (type === "plugin") skillCounts.plugin += 1;
|
|
4491
|
+
else skillCounts.userOrCompatible += 1;
|
|
4492
|
+
}
|
|
4493
|
+
}
|
|
4494
|
+
const cells = isRecord3(value.externalCompat) && Array.isArray(value.externalCompat.cells) ? value.externalCompat.cells : [];
|
|
4495
|
+
return {
|
|
4496
|
+
inspection: "ok",
|
|
4497
|
+
effectiveMcp,
|
|
4498
|
+
skills: {
|
|
4499
|
+
inspection: "ok",
|
|
4500
|
+
total: skillCounts.project + skillCounts.userOrCompatible + skillCounts.plugin + skillCounts.bundled,
|
|
4501
|
+
...skillCounts
|
|
4502
|
+
},
|
|
4503
|
+
claudeCompatibility: compatibilityEnabled(cells, "claude"),
|
|
4504
|
+
cursorCompatibility: compatibilityEnabled(cells, "cursor")
|
|
4505
|
+
};
|
|
4506
|
+
} catch {
|
|
4507
|
+
return empty("failed");
|
|
4508
|
+
}
|
|
4509
|
+
}
|
|
4510
|
+
function compatibilityEnabled(cells, vendor) {
|
|
4511
|
+
const matching = cells.filter((cell) => isRecord3(cell) && cell.vendor === vendor);
|
|
4512
|
+
if (matching.length === 0) return "unknown";
|
|
4513
|
+
return matching.some((cell) => isRecord3(cell) && cell.enabled === true);
|
|
4514
|
+
}
|
|
4515
|
+
function inferGrokMcpScope(name, sourceType, sourcePath, root, homeDir, userClaudeNames, localClaudeNames) {
|
|
4516
|
+
if (!sourcePath) {
|
|
4517
|
+
if (sourceType === "project") return "project";
|
|
4518
|
+
if (localClaudeNames.has(name)) return "project-local";
|
|
4519
|
+
if (userClaudeNames.has(name)) return "user";
|
|
4520
|
+
return "unknown";
|
|
4521
|
+
}
|
|
4522
|
+
const resolvedSource = safeRealpath(sourcePath) ?? resolve5(sourcePath);
|
|
4523
|
+
const resolvedRoot = safeRealpath(root) ?? resolve5(root);
|
|
4524
|
+
if (resolvedSource === join21(resolvedRoot, ".mcp.json")) return "project-shared";
|
|
4525
|
+
if (resolvedSource.startsWith(resolvedRoot + sep)) return "project";
|
|
4526
|
+
if (homeDir && resolvedSource === join21(resolve5(homeDir), ".claude.json")) {
|
|
4527
|
+
if (localClaudeNames.has(name)) return "project-local";
|
|
4528
|
+
if (userClaudeNames.has(name)) return "user";
|
|
4529
|
+
}
|
|
4530
|
+
if (homeDir && resolvedSource.startsWith(resolve5(homeDir) + sep)) return "user";
|
|
4531
|
+
if (sourceType === "project") return "project";
|
|
4532
|
+
return "unknown";
|
|
4533
|
+
}
|
|
3977
4534
|
var HOOK_EVENT_NAMES = /* @__PURE__ */ new Set([
|
|
3978
4535
|
"PreToolUse",
|
|
3979
4536
|
"PostToolUse",
|
|
@@ -4001,7 +4558,7 @@ function inspectHooks(root) {
|
|
|
4001
4558
|
{ host: "codex", path: ".codex/hooks.json" }
|
|
4002
4559
|
];
|
|
4003
4560
|
return configs.map((config) => {
|
|
4004
|
-
const value = readJson3(
|
|
4561
|
+
const value = readJson3(join21(root, config.path));
|
|
4005
4562
|
const counts = /* @__PURE__ */ new Map();
|
|
4006
4563
|
collectHookEvents(value, counts);
|
|
4007
4564
|
return {
|
|
@@ -4022,16 +4579,18 @@ function collectHookEvents(value, counts) {
|
|
|
4022
4579
|
}
|
|
4023
4580
|
}
|
|
4024
4581
|
function inspectDocs(root, expected) {
|
|
4025
|
-
const packageJson = readJson3(
|
|
4582
|
+
const packageJson = readJson3(join21(root, "package.json"));
|
|
4026
4583
|
const dependencies = isRecord3(packageJson) ? { ...recordOrEmpty(packageJson.dependencies), ...recordOrEmpty(packageJson.devDependencies) } : {};
|
|
4027
4584
|
const docGov = dependencyVersion(dependencies["@pieai/doc-gov"]);
|
|
4028
4585
|
const proGov = dependencyVersion(dependencies["@pieai/pro-gov"]);
|
|
4029
|
-
const routerMatch = safeRead(
|
|
4586
|
+
const routerMatch = safeRead(join21(root, "AGENTS.md")).match(/PGS-ROUTER:BEGIN\s+v([0-9.]+)/);
|
|
4030
4587
|
const declared = [docGov, proGov].filter((value) => Boolean(value));
|
|
4031
4588
|
return {
|
|
4032
4589
|
routerVersion: routerMatch?.[1],
|
|
4033
|
-
|
|
4034
|
-
|
|
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")),
|
|
4035
4594
|
packages: {
|
|
4036
4595
|
expected,
|
|
4037
4596
|
docGov,
|
|
@@ -4054,20 +4613,20 @@ function recordOrEmpty(value) {
|
|
|
4054
4613
|
}
|
|
4055
4614
|
function inspectSkillRegistry(executionEngineRoot) {
|
|
4056
4615
|
if (!executionEngineRoot) return { source: 0, registered: 0, bundled: 0, bundles: 0 };
|
|
4057
|
-
const agentAssetsRoot =
|
|
4058
|
-
const registry = readJson3(
|
|
4616
|
+
const agentAssetsRoot = join21(executionEngineRoot, "agent-assets");
|
|
4617
|
+
const registry = readJson3(join21(agentAssetsRoot, "registry.json"));
|
|
4059
4618
|
const assets = isRecord3(registry) && Array.isArray(registry.assets) ? registry.assets : [];
|
|
4060
4619
|
const registeredSkills = assets.filter((asset) => isRecord3(asset) && asset.kind === "skill");
|
|
4061
|
-
const bundleRoot =
|
|
4620
|
+
const bundleRoot = join21(agentAssetsRoot, "bundles");
|
|
4062
4621
|
const bundleFiles = safeReadDir(bundleRoot).filter((file) => file.endsWith(".json"));
|
|
4063
4622
|
const bundledIds = /* @__PURE__ */ new Set();
|
|
4064
4623
|
for (const file of bundleFiles) {
|
|
4065
|
-
const bundle = readJson3(
|
|
4624
|
+
const bundle = readJson3(join21(bundleRoot, file));
|
|
4066
4625
|
if (!isRecord3(bundle) || !Array.isArray(bundle.assets)) continue;
|
|
4067
4626
|
for (const id of bundle.assets) if (typeof id === "string") bundledIds.add(id);
|
|
4068
4627
|
}
|
|
4069
|
-
const sourceRoots = [
|
|
4070
|
-
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);
|
|
4071
4630
|
return {
|
|
4072
4631
|
source,
|
|
4073
4632
|
registered: registeredSkills.length,
|
|
@@ -4081,7 +4640,7 @@ function jsonObjectKeys(path, key) {
|
|
|
4081
4640
|
return Object.keys(value[key]).sort();
|
|
4082
4641
|
}
|
|
4083
4642
|
function tomlMcpNames(path) {
|
|
4084
|
-
if (!
|
|
4643
|
+
if (!existsSync20(path)) return [];
|
|
4085
4644
|
const names = /* @__PURE__ */ new Set();
|
|
4086
4645
|
for (const line of safeRead(path).split(/\r?\n/)) {
|
|
4087
4646
|
const match = line.match(/^\s*\[mcp_servers\.(?:"([^"]+)"|([^\.\]]+))\]\s*$/);
|
|
@@ -4120,7 +4679,7 @@ function safeIsDirectory(path) {
|
|
|
4120
4679
|
}
|
|
4121
4680
|
function pathLexists(path) {
|
|
4122
4681
|
try {
|
|
4123
|
-
|
|
4682
|
+
lstatSync8(path);
|
|
4124
4683
|
return true;
|
|
4125
4684
|
} catch {
|
|
4126
4685
|
return false;
|
|
@@ -4147,14 +4706,17 @@ function isRecord3(value) {
|
|
|
4147
4706
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4148
4707
|
}
|
|
4149
4708
|
function findDashboardAssets() {
|
|
4150
|
-
const packageRoot2 =
|
|
4709
|
+
const packageRoot2 = dirname13(dirname13(fileURLToPath4(import.meta.url)));
|
|
4151
4710
|
const candidates = [
|
|
4152
4711
|
process.env.PGS_DASHBOARD_ASSETS_DIR,
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
|
|
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")
|
|
4156
4718
|
].filter((value) => Boolean(value));
|
|
4157
|
-
const match = candidates.find((path) =>
|
|
4719
|
+
const match = candidates.find((path) => existsSync20(join21(path, "index.html")));
|
|
4158
4720
|
if (!match) throw new Error("Portfolio dashboard assets were not built. Run pnpm --filter @pieai/pro-gov build.");
|
|
4159
4721
|
return match;
|
|
4160
4722
|
}
|
|
@@ -4244,12 +4806,22 @@ function runPortfolioDoctor(args) {
|
|
|
4244
4806
|
const output = { configPath: loaded.configPath, ...result };
|
|
4245
4807
|
if (options.value.json) {
|
|
4246
4808
|
console.log(JSON.stringify(output, null, 2));
|
|
4247
|
-
} else if (result.ok) {
|
|
4248
|
-
console.log(`portfolio doctor passed (${targets.length} targets)`);
|
|
4249
4809
|
} else {
|
|
4250
|
-
for (const
|
|
4810
|
+
for (const warning of result.hostSsot.userSkills.issues) {
|
|
4811
|
+
console.log(`user host-ssot-warning: ${warning}`);
|
|
4812
|
+
}
|
|
4251
4813
|
for (const target of result.targets) {
|
|
4252
|
-
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
|
+
}
|
|
4253
4825
|
}
|
|
4254
4826
|
}
|
|
4255
4827
|
return result.ok ? 0 : 1;
|
|
@@ -4506,8 +5078,8 @@ function isHost2(value) {
|
|
|
4506
5078
|
return value === "codex" || value === "claude-code" || value === "gemini-cli" || value === "antigravity";
|
|
4507
5079
|
}
|
|
4508
5080
|
function findPortfolioAgentAssetsDir(manifest) {
|
|
4509
|
-
const agentAssetsDir = manifest?.executionEngine?.path ?
|
|
4510
|
-
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;
|
|
4511
5083
|
}
|
|
4512
5084
|
function printUsage4() {
|
|
4513
5085
|
console.error("Usage:");
|
|
@@ -4519,8 +5091,8 @@ function printUsage4() {
|
|
|
4519
5091
|
}
|
|
4520
5092
|
|
|
4521
5093
|
// src/commands/sync.ts
|
|
4522
|
-
import { existsSync as
|
|
4523
|
-
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";
|
|
4524
5096
|
function runSync(args) {
|
|
4525
5097
|
const check = args.includes("--check");
|
|
4526
5098
|
if (!check) {
|
|
@@ -4548,8 +5120,9 @@ function runSync(args) {
|
|
|
4548
5120
|
console.log("pro-gov sync check");
|
|
4549
5121
|
console.log(`profile: ${profile}`);
|
|
4550
5122
|
for (const file of planStarterFiles(profile)) {
|
|
4551
|
-
const targetPath =
|
|
4552
|
-
|
|
5123
|
+
const targetPath = join23(process.cwd(), file.targetPath);
|
|
5124
|
+
const stat = safeLstat3(targetPath);
|
|
5125
|
+
if (!stat) {
|
|
4553
5126
|
if (file.ownership === "optional-guardrail") continue;
|
|
4554
5127
|
console.log(`missing: ${file.targetPath}`);
|
|
4555
5128
|
differences += 1;
|
|
@@ -4557,6 +5130,20 @@ function runSync(args) {
|
|
|
4557
5130
|
}
|
|
4558
5131
|
if (file.ownership === "optional-guardrail") continue;
|
|
4559
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
|
+
}
|
|
4560
5147
|
const source = readFileSync16(file.absoluteSourcePath, "utf8");
|
|
4561
5148
|
const target = readFileSync16(targetPath, "utf8");
|
|
4562
5149
|
if (!matchesExpectedContent(file.targetPath, source, target)) {
|
|
@@ -4592,10 +5179,17 @@ function normalizeMarkdownTableCell(cell) {
|
|
|
4592
5179
|
}
|
|
4593
5180
|
function inferInstalledProfile(root) {
|
|
4594
5181
|
const installed = ["engineering-runtime", "doc-only"].filter(
|
|
4595
|
-
(profile) =>
|
|
5182
|
+
(profile) => existsSync22(join23(root, `docs/governance/agents-routing/${profile}-v1.1.md`))
|
|
4596
5183
|
);
|
|
4597
5184
|
return installed.length === 1 ? installed[0] : void 0;
|
|
4598
5185
|
}
|
|
5186
|
+
function safeLstat3(path) {
|
|
5187
|
+
try {
|
|
5188
|
+
return lstatSync9(path);
|
|
5189
|
+
} catch {
|
|
5190
|
+
return void 0;
|
|
5191
|
+
}
|
|
5192
|
+
}
|
|
4599
5193
|
function readFlag2(args, flag) {
|
|
4600
5194
|
const index = args.indexOf(flag);
|
|
4601
5195
|
const value = index >= 0 ? args[index + 1] : void 0;
|