@pieai/pro-gov 0.8.0 → 0.9.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/README.md +2 -1
- package/assets/docs/reference/adoption/adoption-playbook.md +1 -5
- package/assets/host-dashboard/app.css +1 -1
- package/assets/host-dashboard/app.js +6 -6
- package/assets/portfolio-dashboard/app.css +1 -1
- package/assets/portfolio-dashboard/app.js +7 -7
- package/assets/profiles/engineering-runtime/profile.md +1 -1
- package/assets/public-agent-assets/registry.json +3 -3
- package/assets/starter/AGENTS.template.md +17 -3
- package/assets/starter/docs/governance/agents-routing/engineering-runtime-v1.1.md +15 -6
- package/cli-guide.md +4 -1
- package/dist/cli.js +741 -271
- package/package.json +3 -3
- package/assets/starter/docs/governance/templates/donor-map.md +0 -82
package/dist/cli.js
CHANGED
|
@@ -8,8 +8,8 @@ var packageRoot = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
|
8
8
|
var sourceRoot = join(packageRoot, "..", "..");
|
|
9
9
|
var packagedAssetsRoot = join(packageRoot, "assets");
|
|
10
10
|
var assetRoots = ["starter", "profiles", "integrations", "docs/reference/adoption"];
|
|
11
|
-
function listAssets() {
|
|
12
|
-
const root = existsSync(join(sourceRoot, "starter")) ? sourceRoot : packagedAssetsRoot;
|
|
11
|
+
function listAssets(source = "auto") {
|
|
12
|
+
const root = source === "packaged" ? packagedAssetsRoot : source === "source" ? sourceRoot : existsSync(join(sourceRoot, "starter")) ? sourceRoot : packagedAssetsRoot;
|
|
13
13
|
return assetRoots.flatMap((assetRoot) => {
|
|
14
14
|
const absoluteRoot = join(root, assetRoot);
|
|
15
15
|
if (!existsSync(absoluteRoot)) return [];
|
|
@@ -247,6 +247,7 @@ var supportedVisibilities = /* @__PURE__ */ new Set([
|
|
|
247
247
|
var supportedSourceKinds = /* @__PURE__ */ new Set(["local", "local-pack", "npx"]);
|
|
248
248
|
var supportedSkillPlacements = /* @__PURE__ */ new Set(["auto", "manual"]);
|
|
249
249
|
var supportedSkillScopes = /* @__PURE__ */ new Set(["project", "user"]);
|
|
250
|
+
var supportedDeliveries = /* @__PURE__ */ new Set(["symlink", "snapshot"]);
|
|
250
251
|
var supportedHosts = /* @__PURE__ */ new Set([
|
|
251
252
|
"codex",
|
|
252
253
|
"claude-code",
|
|
@@ -254,12 +255,35 @@ var supportedHosts = /* @__PURE__ */ new Set([
|
|
|
254
255
|
"antigravity"
|
|
255
256
|
]);
|
|
256
257
|
var skillInstallNamePattern = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
258
|
+
var safeProjectTargetFilenamePattern = /^[^./\\][^/\\]*$/;
|
|
257
259
|
function assetSkillInstallName(asset) {
|
|
258
260
|
return asset.installName ?? posix.basename(asset.sourcePath);
|
|
259
261
|
}
|
|
262
|
+
function isValidAssetProjectTargetPath(kind, projectTargetPath) {
|
|
263
|
+
if (typeof projectTargetPath !== "string" || projectTargetPath.length === 0 || isAbsolute(projectTargetPath) || projectTargetPath.includes("\\")) {
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
const segments = projectTargetPath.split("/");
|
|
267
|
+
if (segments.some((segment) => segment.length === 0 || segment === "." || segment === "..")) {
|
|
268
|
+
return false;
|
|
269
|
+
}
|
|
270
|
+
const filename = segments.at(-1);
|
|
271
|
+
if (!filename || !safeProjectTargetFilenamePattern.test(filename)) return false;
|
|
272
|
+
if (kind === "rule") {
|
|
273
|
+
return segments.length === 4 && segments[0] === "docs" && segments[1] === "policy" && segments[2] === "shared-rules" && filename.endsWith(".md") || segments.length === 4 && segments[0] === ".pro-gov" && segments[1] === "agent-assets" && segments[2] === "rules";
|
|
274
|
+
}
|
|
275
|
+
if (kind === "command") {
|
|
276
|
+
return segments.length === 4 && segments[0] === ".pro-gov" && segments[1] === "agent-assets" && segments[2] === "commands";
|
|
277
|
+
}
|
|
278
|
+
return false;
|
|
279
|
+
}
|
|
280
|
+
function isValidSnapshotProjectTargetPath(projectTargetPath) {
|
|
281
|
+
return typeof projectTargetPath === "string" && projectTargetPath.startsWith("docs/policy/shared-rules/") && isValidAssetProjectTargetPath("rule", projectTargetPath);
|
|
282
|
+
}
|
|
260
283
|
function validateAssetRegistry(registry, options = {}) {
|
|
261
284
|
const issues = [];
|
|
262
285
|
const seenIds = /* @__PURE__ */ new Set();
|
|
286
|
+
const projectTargetOwners = /* @__PURE__ */ new Map();
|
|
263
287
|
for (const asset of registry.assets) {
|
|
264
288
|
if (seenIds.has(asset.id)) {
|
|
265
289
|
issues.push({
|
|
@@ -311,6 +335,21 @@ function validateAssetRegistry(registry, options = {}) {
|
|
|
311
335
|
message: `Unsupported asset source kind: ${asset.sourceKind}`
|
|
312
336
|
});
|
|
313
337
|
}
|
|
338
|
+
if (asset.delivery !== void 0 && !supportedDeliveries.has(asset.delivery)) {
|
|
339
|
+
issues.push({
|
|
340
|
+
type: "unsupported-enum",
|
|
341
|
+
id: asset.id,
|
|
342
|
+
message: `Unsupported asset delivery: ${asset.delivery}`
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
if (asset.delivery === "snapshot" && (asset.kind !== "rule" || !isValidSnapshotProjectTargetPath(asset.projectTargetPath))) {
|
|
346
|
+
issues.push({
|
|
347
|
+
type: "invalid-snapshot-delivery",
|
|
348
|
+
id: asset.id,
|
|
349
|
+
path: asset.projectTargetPath,
|
|
350
|
+
message: `Snapshot delivery is only allowed for rule assets targeting docs/policy/shared-rules/<name>.md: ${asset.id}`
|
|
351
|
+
});
|
|
352
|
+
}
|
|
314
353
|
for (const host of asset.hosts) {
|
|
315
354
|
if (!supportedHosts.has(host)) {
|
|
316
355
|
issues.push({
|
|
@@ -349,6 +388,26 @@ function validateAssetRegistry(registry, options = {}) {
|
|
|
349
388
|
message: `Asset source path escapes agent-assets: ${asset.sourcePath}`
|
|
350
389
|
});
|
|
351
390
|
}
|
|
391
|
+
if (asset.projectTargetPath !== void 0 && !isValidAssetProjectTargetPath(asset.kind, asset.projectTargetPath)) {
|
|
392
|
+
issues.push({
|
|
393
|
+
type: "invalid-project-target-path",
|
|
394
|
+
id: asset.id,
|
|
395
|
+
path: asset.projectTargetPath,
|
|
396
|
+
message: `Asset project target path is not allowed for ${asset.kind}: ${asset.projectTargetPath}`
|
|
397
|
+
});
|
|
398
|
+
} else if (asset.projectTargetPath !== void 0) {
|
|
399
|
+
const existingOwner = projectTargetOwners.get(asset.projectTargetPath);
|
|
400
|
+
if (existingOwner !== void 0 && existingOwner !== asset.id) {
|
|
401
|
+
issues.push({
|
|
402
|
+
type: "duplicate-project-target-path",
|
|
403
|
+
id: asset.id,
|
|
404
|
+
path: asset.projectTargetPath,
|
|
405
|
+
message: `Project target path is already owned by ${existingOwner}: ${asset.projectTargetPath}`
|
|
406
|
+
});
|
|
407
|
+
} else {
|
|
408
|
+
projectTargetOwners.set(asset.projectTargetPath, asset.id);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
352
411
|
if (asset.visibility !== "public" && asset.publishable) {
|
|
353
412
|
issues.push({
|
|
354
413
|
type: "non-public-publishable",
|
|
@@ -434,6 +493,23 @@ function pathExistsEvenIfDanglingSymlink(path) {
|
|
|
434
493
|
}
|
|
435
494
|
|
|
436
495
|
// src/asset-registry/loader.ts
|
|
496
|
+
function createAgentAssetRegistryProvenance(registry, selectedAssetIds) {
|
|
497
|
+
const selectedIds = new Set(selectedAssetIds);
|
|
498
|
+
const selectedAssets = registry.assets.filter((asset) => selectedIds.has(asset.id));
|
|
499
|
+
const canonicalRegistry = canonicalizeValue({
|
|
500
|
+
schemaVersion: registry.schemaVersion,
|
|
501
|
+
assets: [...selectedAssets].sort(
|
|
502
|
+
(left, right) => left.id < right.id ? -1 : left.id > right.id ? 1 : 0
|
|
503
|
+
)
|
|
504
|
+
});
|
|
505
|
+
const hash = createHash2("sha256").update(JSON.stringify(canonicalRegistry)).digest("hex");
|
|
506
|
+
return {
|
|
507
|
+
schema: "agent-assets-registry",
|
|
508
|
+
version: registry.schemaVersion,
|
|
509
|
+
hash: `sha256:${hash}`,
|
|
510
|
+
assetCount: selectedAssets.length
|
|
511
|
+
};
|
|
512
|
+
}
|
|
437
513
|
function loadAgentAssetRegistry(options = {}) {
|
|
438
514
|
const agentAssetsDir = options.agentAssetsDir ?? findDefaultAgentAssetsDir();
|
|
439
515
|
const registryPath = join5(agentAssetsDir, "registry.json");
|
|
@@ -458,6 +534,7 @@ function createAgentAssetLockEntries(registry, agentAssetsDir, assetIds) {
|
|
|
458
534
|
return registry.assets.filter((asset) => !wantedIds || wantedIds.has(asset.id)).map((asset) => ({
|
|
459
535
|
id: asset.id,
|
|
460
536
|
sourcePath: asset.sourcePath,
|
|
537
|
+
delivery: asset.delivery ?? "symlink",
|
|
461
538
|
contentHash: hashAgentAssetContent(asset, agentAssetsDir)
|
|
462
539
|
})).sort((a, b) => a.id.localeCompare(b.id));
|
|
463
540
|
}
|
|
@@ -475,6 +552,15 @@ function hashAssetPathContent(sourceAbsolutePath) {
|
|
|
475
552
|
}
|
|
476
553
|
return `sha256:${hash.digest("hex")}`;
|
|
477
554
|
}
|
|
555
|
+
function canonicalizeValue(value) {
|
|
556
|
+
if (Array.isArray(value)) return value.map((item) => canonicalizeValue(item));
|
|
557
|
+
if (value !== null && typeof value === "object") {
|
|
558
|
+
return Object.fromEntries(
|
|
559
|
+
Object.entries(value).filter(([, item]) => item !== void 0).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0).map(([key, item]) => [key, canonicalizeValue(item)])
|
|
560
|
+
);
|
|
561
|
+
}
|
|
562
|
+
return value;
|
|
563
|
+
}
|
|
478
564
|
function findDefaultAgentAssetsDir() {
|
|
479
565
|
const packageRoot2 = findPackageRoot(dirname2(fileURLToPath2(import.meta.url)));
|
|
480
566
|
const repoRoot = join5(packageRoot2, "..", "..");
|
|
@@ -620,6 +706,7 @@ function resolveSafePath(root, sourcePath) {
|
|
|
620
706
|
}
|
|
621
707
|
|
|
622
708
|
// src/asset-targets/apply.ts
|
|
709
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
623
710
|
import {
|
|
624
711
|
existsSync as existsSync8,
|
|
625
712
|
lstatSync as lstatSync3,
|
|
@@ -633,7 +720,7 @@ import {
|
|
|
633
720
|
import { dirname as dirname4, join as join8, relative as relative4, resolve as resolve2 } from "node:path";
|
|
634
721
|
|
|
635
722
|
// src/asset-targets/install-plan.ts
|
|
636
|
-
import { existsSync as existsSync7, lstatSync as lstatSync2, readFileSync as readFileSync4, readlinkSync, realpathSync } from "node:fs";
|
|
723
|
+
import { existsSync as existsSync7, lstatSync as lstatSync2, readFileSync as readFileSync4, readlinkSync, realpathSync, statSync as statSync3 } from "node:fs";
|
|
637
724
|
import { basename, dirname as dirname3, join as join7, resolve } from "node:path";
|
|
638
725
|
function createAssetInstallPlan(options) {
|
|
639
726
|
const placement = options.placement ?? "registry";
|
|
@@ -653,9 +740,9 @@ function createAssetInstallPlan(options) {
|
|
|
653
740
|
options.agentAssetsDir,
|
|
654
741
|
assetIds
|
|
655
742
|
);
|
|
743
|
+
const registryProvenance = createAgentAssetRegistryProvenance(options.registry, assetIds);
|
|
656
744
|
const managedLock = readManagedLock(options.targetDir);
|
|
657
745
|
const managedEntries = managedLock.entries;
|
|
658
|
-
const managedTargets = new Set(managedEntries.map((entry) => entry.targetPath));
|
|
659
746
|
const legacyAdoptions = createLegacyClaudeAdoptions({
|
|
660
747
|
targetDir: options.targetDir,
|
|
661
748
|
agentAssetsDir: options.agentAssetsDir,
|
|
@@ -671,7 +758,7 @@ function createAssetInstallPlan(options) {
|
|
|
671
758
|
options.targetDir,
|
|
672
759
|
options.host,
|
|
673
760
|
placement,
|
|
674
|
-
|
|
761
|
+
managedEntries
|
|
675
762
|
)
|
|
676
763
|
);
|
|
677
764
|
const manifest = {
|
|
@@ -686,6 +773,7 @@ function createAssetInstallPlan(options) {
|
|
|
686
773
|
host: options.host,
|
|
687
774
|
placement,
|
|
688
775
|
bundleIds: [...options.bundleIds],
|
|
776
|
+
registryProvenance,
|
|
689
777
|
assets: lockEntries.map((entry) => {
|
|
690
778
|
const action = assetActions.find(
|
|
691
779
|
(candidate) => "assetId" in candidate && candidate.assetId === entry.id
|
|
@@ -747,7 +835,7 @@ function resolveAssetIds(bundleIds, explicitAssetIds, bundlesById) {
|
|
|
747
835
|
}
|
|
748
836
|
return [...ids].sort();
|
|
749
837
|
}
|
|
750
|
-
function createAssetAction(asset, agentAssetsDir, targetDir, host, placement,
|
|
838
|
+
function createAssetAction(asset, agentAssetsDir, targetDir, host, placement, managedEntries) {
|
|
751
839
|
if (asset.kind === "skill" && asset.defaultScope === "user") {
|
|
752
840
|
throw new Error(
|
|
753
841
|
`User-scoped asset ${asset.id} must be linked at the user level, not installed into a project target.`
|
|
@@ -757,9 +845,23 @@ function createAssetAction(asset, agentAssetsDir, targetDir, host, placement, ma
|
|
|
757
845
|
const targetPath = resolveHostTargetPath(asset, host, placement);
|
|
758
846
|
const targetAbsolutePath = join7(targetDir, targetPath);
|
|
759
847
|
const targetExists = pathExistsEvenIfDanglingSymlink2(targetAbsolutePath);
|
|
848
|
+
const managedEntry = managedEntries.find(
|
|
849
|
+
(entry) => entry.id === asset.id && entry.targetPath === targetPath
|
|
850
|
+
);
|
|
851
|
+
if ((asset.delivery ?? "symlink") === "snapshot") {
|
|
852
|
+
return createSnapshotAction({
|
|
853
|
+
asset,
|
|
854
|
+
agentAssetsDir,
|
|
855
|
+
sourcePath,
|
|
856
|
+
targetPath,
|
|
857
|
+
targetAbsolutePath,
|
|
858
|
+
targetExists,
|
|
859
|
+
managedEntry
|
|
860
|
+
});
|
|
861
|
+
}
|
|
760
862
|
if (targetExists) {
|
|
761
863
|
const stats = lstatSync2(targetAbsolutePath);
|
|
762
|
-
if (stats.isSymbolicLink() &&
|
|
864
|
+
if (stats.isSymbolicLink() && managedEntry && (managedEntry.delivery ?? "symlink") === "symlink") {
|
|
763
865
|
return {
|
|
764
866
|
type: "update-symlink",
|
|
765
867
|
assetId: asset.id,
|
|
@@ -767,6 +869,9 @@ function createAssetAction(asset, agentAssetsDir, targetDir, host, placement, ma
|
|
|
767
869
|
targetPath
|
|
768
870
|
};
|
|
769
871
|
}
|
|
872
|
+
if (managedEntry && managedEntry.delivery === "snapshot") {
|
|
873
|
+
throw new Error(`Refusing to replace managed snapshot with a symlink: ${targetPath}`);
|
|
874
|
+
}
|
|
770
875
|
if (stats.isSymbolicLink() && existsSync7(targetAbsolutePath) && realpathSync(targetAbsolutePath) === realpathSync(sourcePath)) {
|
|
771
876
|
return {
|
|
772
877
|
type: "adopt-existing-symlink",
|
|
@@ -784,7 +889,78 @@ function createAssetAction(asset, agentAssetsDir, targetDir, host, placement, ma
|
|
|
784
889
|
targetPath
|
|
785
890
|
};
|
|
786
891
|
}
|
|
892
|
+
function createSnapshotAction(options) {
|
|
893
|
+
if (options.asset.kind !== "rule" || !isValidSnapshotProjectTargetPath(options.asset.projectTargetPath)) {
|
|
894
|
+
throw new Error(
|
|
895
|
+
`Snapshot delivery requires a rule target under docs/policy/shared-rules/: ${options.asset.id}`
|
|
896
|
+
);
|
|
897
|
+
}
|
|
898
|
+
if (!statSync3(options.sourcePath).isFile()) {
|
|
899
|
+
throw new Error(`Snapshot source must be a regular file: ${options.asset.sourcePath}`);
|
|
900
|
+
}
|
|
901
|
+
const content = readFileSync4(options.sourcePath);
|
|
902
|
+
const contentBase64 = content.toString("base64");
|
|
903
|
+
const contentHash = hashAgentAssetContent(options.asset, options.agentAssetsDir);
|
|
904
|
+
const managedDelivery = options.managedEntry?.delivery ?? "symlink";
|
|
905
|
+
if (!options.targetExists) {
|
|
906
|
+
return {
|
|
907
|
+
type: "snapshot",
|
|
908
|
+
assetId: options.asset.id,
|
|
909
|
+
targetPath: options.targetPath,
|
|
910
|
+
contentBase64,
|
|
911
|
+
contentHash
|
|
912
|
+
};
|
|
913
|
+
}
|
|
914
|
+
const stats = lstatSync2(options.targetAbsolutePath);
|
|
915
|
+
if (stats.isSymbolicLink()) {
|
|
916
|
+
if (existsSync7(options.targetAbsolutePath) && realpathSync(options.targetAbsolutePath) === realpathSync(options.sourcePath) && hashAssetPathContent(options.targetAbsolutePath) === contentHash) {
|
|
917
|
+
return {
|
|
918
|
+
type: "migrate-symlink-to-snapshot",
|
|
919
|
+
assetId: options.asset.id,
|
|
920
|
+
sourcePath: options.sourcePath,
|
|
921
|
+
targetPath: options.targetPath,
|
|
922
|
+
contentBase64,
|
|
923
|
+
contentHash
|
|
924
|
+
};
|
|
925
|
+
}
|
|
926
|
+
throw new Error(`Refusing to overwrite unmanaged target: ${options.targetPath}`);
|
|
927
|
+
}
|
|
928
|
+
if (!stats.isFile()) {
|
|
929
|
+
throw new Error(`Refusing to overwrite unmanaged target: ${options.targetPath}`);
|
|
930
|
+
}
|
|
931
|
+
const currentTargetHash = hashAssetPathContent(options.targetAbsolutePath);
|
|
932
|
+
if (currentTargetHash === contentHash) {
|
|
933
|
+
return {
|
|
934
|
+
type: "adopt-snapshot",
|
|
935
|
+
assetId: options.asset.id,
|
|
936
|
+
targetPath: options.targetPath,
|
|
937
|
+
contentHash
|
|
938
|
+
};
|
|
939
|
+
}
|
|
940
|
+
if (managedDelivery === "snapshot" && options.managedEntry?.contentHash) {
|
|
941
|
+
if (currentTargetHash !== options.managedEntry.contentHash) {
|
|
942
|
+
throw new Error(`Refusing to overwrite locally drifted snapshot: ${options.targetPath}`);
|
|
943
|
+
}
|
|
944
|
+
return {
|
|
945
|
+
type: "update-snapshot",
|
|
946
|
+
assetId: options.asset.id,
|
|
947
|
+
targetPath: options.targetPath,
|
|
948
|
+
contentBase64,
|
|
949
|
+
contentHash,
|
|
950
|
+
expectedContentHash: options.managedEntry.contentHash
|
|
951
|
+
};
|
|
952
|
+
}
|
|
953
|
+
throw new Error(`Refusing to overwrite unmanaged target: ${options.targetPath}`);
|
|
954
|
+
}
|
|
787
955
|
function resolveHostTargetPath(asset, _host, placement) {
|
|
956
|
+
if (asset.projectTargetPath !== void 0) {
|
|
957
|
+
if (!isValidAssetProjectTargetPath(asset.kind, asset.projectTargetPath)) {
|
|
958
|
+
throw new Error(
|
|
959
|
+
`Invalid project target path for asset ${asset.id}: ${asset.projectTargetPath}`
|
|
960
|
+
);
|
|
961
|
+
}
|
|
962
|
+
return asset.projectTargetPath;
|
|
963
|
+
}
|
|
788
964
|
if (asset.kind === "skill") {
|
|
789
965
|
const effectivePlacement = resolveSkillPlacement(asset, placement);
|
|
790
966
|
if (effectivePlacement === "manual") {
|
|
@@ -818,7 +994,7 @@ function readManagedLock(targetDir) {
|
|
|
818
994
|
return {
|
|
819
995
|
host: typeof lockfile.host === "string" ? lockfile.host : void 0,
|
|
820
996
|
entries: (lockfile.assets ?? []).filter(
|
|
821
|
-
(entry) => typeof entry.id === "string" && typeof entry.sourcePath === "string" && typeof entry.targetPath === "string"
|
|
997
|
+
(entry) => typeof entry.id === "string" && typeof entry.sourcePath === "string" && typeof entry.targetPath === "string" && (entry.delivery === void 0 || entry.delivery === "symlink" || entry.delivery === "snapshot") && (entry.contentHash === void 0 || typeof entry.contentHash === "string")
|
|
822
998
|
)
|
|
823
999
|
};
|
|
824
1000
|
} catch {
|
|
@@ -900,6 +1076,29 @@ function createRemovalActions(targetDir, agentAssetsDir, managedEntries, expecte
|
|
|
900
1076
|
const targetAbsolutePath = join7(targetDir, entry.targetPath);
|
|
901
1077
|
if (!pathExistsEvenIfDanglingSymlink2(targetAbsolutePath)) continue;
|
|
902
1078
|
const stats = lstatSync2(targetAbsolutePath);
|
|
1079
|
+
if ((entry.delivery ?? "symlink") === "snapshot") {
|
|
1080
|
+
if (!isValidSnapshotProjectTargetPath(entry.targetPath)) {
|
|
1081
|
+
throw new Error(
|
|
1082
|
+
`Refusing to remove snapshot outside live shared rules: ${entry.targetPath}`
|
|
1083
|
+
);
|
|
1084
|
+
}
|
|
1085
|
+
if (!stats.isFile() || !entry.contentHash) {
|
|
1086
|
+
throw new Error(
|
|
1087
|
+
`Refusing to remove managed snapshot without its locked file hash: ${entry.targetPath}`
|
|
1088
|
+
);
|
|
1089
|
+
}
|
|
1090
|
+
const currentTargetHash = hashAssetPathContent(targetAbsolutePath);
|
|
1091
|
+
if (currentTargetHash !== entry.contentHash) {
|
|
1092
|
+
throw new Error(`Refusing to remove locally drifted managed snapshot: ${entry.targetPath}`);
|
|
1093
|
+
}
|
|
1094
|
+
actions.push({
|
|
1095
|
+
type: "remove-snapshot",
|
|
1096
|
+
assetId: entry.id,
|
|
1097
|
+
targetPath: entry.targetPath,
|
|
1098
|
+
expectedContentHash: entry.contentHash
|
|
1099
|
+
});
|
|
1100
|
+
continue;
|
|
1101
|
+
}
|
|
903
1102
|
if (!stats.isSymbolicLink()) {
|
|
904
1103
|
throw new Error(
|
|
905
1104
|
`Refusing to remove path that is no longer a managed symlink: ${entry.targetPath}`
|
|
@@ -922,12 +1121,12 @@ function createRemovalActions(targetDir, agentAssetsDir, managedEntries, expecte
|
|
|
922
1121
|
return actions.sort((a, b) => a.targetPath.localeCompare(b.targetPath));
|
|
923
1122
|
}
|
|
924
1123
|
function isManagedAssetTargetPath(path) {
|
|
925
|
-
return /^(?:\.agents\/(?:skills|manual-skills)|\.pro-gov\/agent-assets\/(?:rules|commands))\/[
|
|
1124
|
+
return /^(?:(?:\.agents\/(?:skills|manual-skills)|\.pro-gov\/agent-assets\/(?:rules|commands))\/[^./\\][^/\\]*|docs\/policy\/shared-rules\/[^./\\][^/\\]*\.md)$/.test(
|
|
926
1125
|
path
|
|
927
1126
|
);
|
|
928
1127
|
}
|
|
929
1128
|
function isLegacyClaudeSkillTargetPath(path) {
|
|
930
|
-
return /^\.claude\/skills\/[
|
|
1129
|
+
return /^\.claude\/skills\/[^./\\][^/\\]*$/.test(path);
|
|
931
1130
|
}
|
|
932
1131
|
function pathExistsEvenIfDanglingSymlink2(path) {
|
|
933
1132
|
try {
|
|
@@ -944,6 +1143,9 @@ function applyAssetInstallPlan(plan) {
|
|
|
944
1143
|
for (const action of plan.actions) {
|
|
945
1144
|
if (action.type === "adopt-symlink") validateAdoptedSymlink(plan.targetDir, action);
|
|
946
1145
|
if (action.type === "adopt-existing-symlink") validateExistingSymlink(plan.targetDir, action);
|
|
1146
|
+
if (action.type === "snapshot" || action.type === "update-snapshot" || action.type === "adopt-snapshot" || action.type === "migrate-symlink-to-snapshot" || action.type === "remove-snapshot") {
|
|
1147
|
+
validateSnapshotAction(plan.targetDir, action);
|
|
1148
|
+
}
|
|
947
1149
|
}
|
|
948
1150
|
for (const action of plan.actions) {
|
|
949
1151
|
applyAction(plan.targetDir, action);
|
|
@@ -958,6 +1160,10 @@ function applyAction(targetDir, action) {
|
|
|
958
1160
|
return;
|
|
959
1161
|
}
|
|
960
1162
|
if (action.type === "adopt-symlink" || action.type === "adopt-existing-symlink") return;
|
|
1163
|
+
if (action.type === "snapshot" || action.type === "update-snapshot" || action.type === "adopt-snapshot" || action.type === "migrate-symlink-to-snapshot" || action.type === "remove-snapshot") {
|
|
1164
|
+
applySnapshotAction(targetDir, action);
|
|
1165
|
+
return;
|
|
1166
|
+
}
|
|
961
1167
|
if (action.type === "create-dir") {
|
|
962
1168
|
mkdirSync2(targetAbsolutePath, { recursive: true });
|
|
963
1169
|
return;
|
|
@@ -997,6 +1203,67 @@ function validateExistingSymlink(targetDir, action) {
|
|
|
997
1203
|
throw new Error(`Existing skill symlink changed before apply: ${action.targetPath}`);
|
|
998
1204
|
}
|
|
999
1205
|
}
|
|
1206
|
+
function validateSnapshotAction(targetDir, action) {
|
|
1207
|
+
if (!isValidSnapshotProjectTargetPath(action.targetPath)) {
|
|
1208
|
+
throw new Error(`Refusing unsafe snapshot target: ${action.targetPath}`);
|
|
1209
|
+
}
|
|
1210
|
+
if ("contentBase64" in action && hashSnapshotBytes(Buffer.from(action.contentBase64, "base64")) !== action.contentHash) {
|
|
1211
|
+
throw new Error(`Snapshot content hash is invalid: ${action.targetPath}`);
|
|
1212
|
+
}
|
|
1213
|
+
const targetAbsolutePath = join8(targetDir, action.targetPath);
|
|
1214
|
+
if (action.type === "snapshot") {
|
|
1215
|
+
if (pathExistsEvenIfDanglingSymlink3(targetAbsolutePath)) {
|
|
1216
|
+
throw new Error(`Snapshot target changed before apply: ${action.targetPath}`);
|
|
1217
|
+
}
|
|
1218
|
+
return;
|
|
1219
|
+
}
|
|
1220
|
+
if (action.type === "update-snapshot") {
|
|
1221
|
+
assertRegularSnapshotHash(targetAbsolutePath, action.expectedContentHash, action.targetPath);
|
|
1222
|
+
return;
|
|
1223
|
+
}
|
|
1224
|
+
if (action.type === "adopt-snapshot") {
|
|
1225
|
+
assertRegularSnapshotHash(targetAbsolutePath, action.contentHash, action.targetPath);
|
|
1226
|
+
return;
|
|
1227
|
+
}
|
|
1228
|
+
if (action.type === "migrate-symlink-to-snapshot") {
|
|
1229
|
+
if (!pathExistsEvenIfDanglingSymlink3(targetAbsolutePath) || !lstatSync3(targetAbsolutePath).isSymbolicLink() || !existsSync8(targetAbsolutePath) || realpathSync2(targetAbsolutePath) !== realpathSync2(action.sourcePath) || hashAssetPathContent(targetAbsolutePath) !== action.contentHash) {
|
|
1230
|
+
throw new Error(`Snapshot symlink changed before apply: ${action.targetPath}`);
|
|
1231
|
+
}
|
|
1232
|
+
return;
|
|
1233
|
+
}
|
|
1234
|
+
assertRegularSnapshotHash(targetAbsolutePath, action.expectedContentHash, action.targetPath);
|
|
1235
|
+
}
|
|
1236
|
+
function applySnapshotAction(targetDir, action) {
|
|
1237
|
+
const targetAbsolutePath = join8(targetDir, action.targetPath);
|
|
1238
|
+
if (action.type === "adopt-snapshot") return;
|
|
1239
|
+
if (action.type === "remove-snapshot") {
|
|
1240
|
+
unlinkSync(targetAbsolutePath);
|
|
1241
|
+
return;
|
|
1242
|
+
}
|
|
1243
|
+
if (action.type === "migrate-symlink-to-snapshot") unlinkSync(targetAbsolutePath);
|
|
1244
|
+
mkdirSync2(dirname4(targetAbsolutePath), { recursive: true });
|
|
1245
|
+
if (action.type === "snapshot" && pathExistsEvenIfDanglingSymlink3(targetAbsolutePath)) {
|
|
1246
|
+
throw new Error(`Refusing to overwrite unmanaged target: ${action.targetPath}`);
|
|
1247
|
+
}
|
|
1248
|
+
writeFileSync(targetAbsolutePath, Buffer.from(action.contentBase64, "base64"));
|
|
1249
|
+
}
|
|
1250
|
+
function assertRegularSnapshotHash(targetAbsolutePath, expectedHash, targetPath) {
|
|
1251
|
+
if (!pathExistsEvenIfDanglingSymlink3(targetAbsolutePath)) {
|
|
1252
|
+
throw new Error(`Snapshot target changed before apply: ${targetPath}`);
|
|
1253
|
+
}
|
|
1254
|
+
const stats = lstatSync3(targetAbsolutePath);
|
|
1255
|
+
if (!stats.isFile() || hashAssetPathContent(targetAbsolutePath) !== expectedHash) {
|
|
1256
|
+
throw new Error(`Snapshot target hash changed before apply: ${targetPath}`);
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
function hashSnapshotBytes(content) {
|
|
1260
|
+
const hash = createHash3("sha256");
|
|
1261
|
+
hash.update("");
|
|
1262
|
+
hash.update("\0");
|
|
1263
|
+
hash.update(content);
|
|
1264
|
+
hash.update("\0");
|
|
1265
|
+
return `sha256:${hash.digest("hex")}`;
|
|
1266
|
+
}
|
|
1000
1267
|
function validateAdoptedSymlink(targetDir, action) {
|
|
1001
1268
|
if (!isManagedAssetTargetPath(action.targetPath) || !isLegacyClaudeSkillTargetPath(action.legacyTargetPath) || action.compatibilityRootPath !== ".claude/skills" || action.expectedCompatibilityRawTarget !== "../.agents/skills") {
|
|
1002
1269
|
throw new Error(`Refusing unsafe legacy Claude adoption: ${action.legacyTargetPath}`);
|
|
@@ -1064,10 +1331,54 @@ function checkInstalledAssets(options) {
|
|
|
1064
1331
|
const lockfile = JSON.parse(readFileSync5(lockfilePath, "utf8"));
|
|
1065
1332
|
const issues = [];
|
|
1066
1333
|
const strictRegistry = options.strictRegistry ?? false;
|
|
1334
|
+
const selectedAssetIds = (lockfile.assets ?? []).map((entry) => entry.id);
|
|
1335
|
+
const hasRegistryProvenance2 = Object.prototype.hasOwnProperty.call(
|
|
1336
|
+
lockfile,
|
|
1337
|
+
"registryProvenance"
|
|
1338
|
+
);
|
|
1339
|
+
const registryProvenanceMismatch = strictRegistry && hasRegistryProvenance2 && !matchesRegistryProvenance(
|
|
1340
|
+
lockfile.registryProvenance,
|
|
1341
|
+
createAgentAssetRegistryProvenance(options.registry, selectedAssetIds)
|
|
1342
|
+
);
|
|
1343
|
+
if (registryProvenanceMismatch) {
|
|
1344
|
+
issues.push({
|
|
1345
|
+
type: "registry-provenance-mismatch",
|
|
1346
|
+
message: "Asset lock registry provenance does not match the registry available to this checker; registry-dependent checks were skipped."
|
|
1347
|
+
});
|
|
1348
|
+
}
|
|
1067
1349
|
for (const entry of lockfile.assets ?? []) {
|
|
1350
|
+
if (!isManagedAssetTargetPath(entry.targetPath) && !isLegacyClaudeSkillTargetPath(entry.targetPath)) {
|
|
1351
|
+
issues.push({
|
|
1352
|
+
type: "unsafe-target-path",
|
|
1353
|
+
id: entry.id,
|
|
1354
|
+
targetPath: entry.targetPath,
|
|
1355
|
+
message: `Managed asset target is outside supported roots: ${entry.targetPath}`
|
|
1356
|
+
});
|
|
1357
|
+
continue;
|
|
1358
|
+
}
|
|
1068
1359
|
const asset = registryById.get(entry.id);
|
|
1069
1360
|
const targetAbsolutePath = join9(options.targetDir, entry.targetPath);
|
|
1070
|
-
|
|
1361
|
+
const delivery = entry.delivery ?? "symlink";
|
|
1362
|
+
const portableDeferredSkill = delivery === "symlink" && !strictRegistry && isProjectSkillTarget(entry.targetPath);
|
|
1363
|
+
if (delivery !== "symlink" && delivery !== "snapshot") {
|
|
1364
|
+
issues.push({
|
|
1365
|
+
type: "unsupported-delivery",
|
|
1366
|
+
id: entry.id,
|
|
1367
|
+
targetPath: entry.targetPath,
|
|
1368
|
+
message: `Managed asset delivery is unsupported: ${delivery}`
|
|
1369
|
+
});
|
|
1370
|
+
continue;
|
|
1371
|
+
}
|
|
1372
|
+
if (delivery === "snapshot" && !isValidSnapshotProjectTargetPath(entry.targetPath)) {
|
|
1373
|
+
issues.push({
|
|
1374
|
+
type: "unsafe-target-path",
|
|
1375
|
+
id: entry.id,
|
|
1376
|
+
targetPath: entry.targetPath,
|
|
1377
|
+
message: `Managed snapshot target is outside live shared rules: ${entry.targetPath}`
|
|
1378
|
+
});
|
|
1379
|
+
continue;
|
|
1380
|
+
}
|
|
1381
|
+
if (!asset && strictRegistry && !registryProvenanceMismatch) {
|
|
1071
1382
|
issues.push({
|
|
1072
1383
|
type: "unknown-asset",
|
|
1073
1384
|
id: entry.id,
|
|
@@ -1075,7 +1386,7 @@ function checkInstalledAssets(options) {
|
|
|
1075
1386
|
message: `Lockfile references unknown asset: ${entry.id}`
|
|
1076
1387
|
});
|
|
1077
1388
|
}
|
|
1078
|
-
if (asset?.kind === "skill" && asset.defaultScope === "user") {
|
|
1389
|
+
if (!registryProvenanceMismatch && asset?.kind === "skill" && asset.defaultScope === "user") {
|
|
1079
1390
|
issues.push({
|
|
1080
1391
|
type: "user-scoped-asset-in-project-lock",
|
|
1081
1392
|
id: entry.id,
|
|
@@ -1083,7 +1394,7 @@ function checkInstalledAssets(options) {
|
|
|
1083
1394
|
message: `User-scoped skill is still locked into this project; move it to the user skill roots: ${entry.id}`
|
|
1084
1395
|
});
|
|
1085
1396
|
}
|
|
1086
|
-
if (asset) {
|
|
1397
|
+
if (asset && !registryProvenanceMismatch) {
|
|
1087
1398
|
const hostFolderIssue = checkHostFolder(
|
|
1088
1399
|
lockfile.host,
|
|
1089
1400
|
asset.kind,
|
|
@@ -1099,6 +1410,7 @@ function checkInstalledAssets(options) {
|
|
|
1099
1410
|
}
|
|
1100
1411
|
}
|
|
1101
1412
|
if (!pathExistsEvenIfDanglingSymlink4(targetAbsolutePath)) {
|
|
1413
|
+
if (portableDeferredSkill) continue;
|
|
1102
1414
|
issues.push({
|
|
1103
1415
|
type: "missing-target",
|
|
1104
1416
|
id: entry.id,
|
|
@@ -1108,7 +1420,16 @@ function checkInstalledAssets(options) {
|
|
|
1108
1420
|
continue;
|
|
1109
1421
|
}
|
|
1110
1422
|
const targetStats = lstatSync4(targetAbsolutePath);
|
|
1111
|
-
if (!targetStats.
|
|
1423
|
+
if (delivery === "snapshot" && !targetStats.isFile()) {
|
|
1424
|
+
issues.push({
|
|
1425
|
+
type: "snapshot-not-regular-file",
|
|
1426
|
+
id: entry.id,
|
|
1427
|
+
targetPath: entry.targetPath,
|
|
1428
|
+
message: `Managed snapshot is not a regular file: ${entry.targetPath}`
|
|
1429
|
+
});
|
|
1430
|
+
continue;
|
|
1431
|
+
}
|
|
1432
|
+
if (delivery === "symlink" && !targetStats.isSymbolicLink()) {
|
|
1112
1433
|
issues.push({
|
|
1113
1434
|
type: "unmanaged-conflict",
|
|
1114
1435
|
id: entry.id,
|
|
@@ -1117,7 +1438,8 @@ function checkInstalledAssets(options) {
|
|
|
1117
1438
|
});
|
|
1118
1439
|
continue;
|
|
1119
1440
|
}
|
|
1120
|
-
if (!existsSync9(targetAbsolutePath)) {
|
|
1441
|
+
if (delivery === "symlink" && !existsSync9(targetAbsolutePath)) {
|
|
1442
|
+
if (portableDeferredSkill) continue;
|
|
1121
1443
|
issues.push({
|
|
1122
1444
|
type: "dangling-symlink",
|
|
1123
1445
|
id: entry.id,
|
|
@@ -1136,7 +1458,7 @@ function checkInstalledAssets(options) {
|
|
|
1136
1458
|
message: `Managed asset hash drifted: ${entry.id}`
|
|
1137
1459
|
});
|
|
1138
1460
|
}
|
|
1139
|
-
if (!asset || !strictRegistry) continue;
|
|
1461
|
+
if (!asset || !strictRegistry || registryProvenanceMismatch) continue;
|
|
1140
1462
|
const sourceAbsolutePath = join9(options.agentAssetsDir, asset.sourcePath);
|
|
1141
1463
|
if (!existsSync9(sourceAbsolutePath)) {
|
|
1142
1464
|
issues.push({
|
|
@@ -1148,7 +1470,7 @@ function checkInstalledAssets(options) {
|
|
|
1148
1470
|
continue;
|
|
1149
1471
|
}
|
|
1150
1472
|
const currentSourceHash = hashAgentAssetContent(asset, options.agentAssetsDir);
|
|
1151
|
-
if (targetHashMatchesLock && currentSourceHash !== entry.contentHash) {
|
|
1473
|
+
if ((delivery === "snapshot" || targetHashMatchesLock) && currentSourceHash !== entry.contentHash) {
|
|
1152
1474
|
issues.push({
|
|
1153
1475
|
type: "hash-drift",
|
|
1154
1476
|
id: entry.id,
|
|
@@ -1157,9 +1479,16 @@ function checkInstalledAssets(options) {
|
|
|
1157
1479
|
});
|
|
1158
1480
|
}
|
|
1159
1481
|
}
|
|
1160
|
-
|
|
1482
|
+
if (!registryProvenanceMismatch) {
|
|
1483
|
+
issues.push(...checkDuplicateSkillPlacements(options.targetDir, options.registry));
|
|
1484
|
+
}
|
|
1161
1485
|
return { targetDir: options.targetDir, issues };
|
|
1162
1486
|
}
|
|
1487
|
+
function matchesRegistryProvenance(value, expected) {
|
|
1488
|
+
if (value === null || typeof value !== "object") return false;
|
|
1489
|
+
const provenance = value;
|
|
1490
|
+
return provenance.schema === expected.schema && provenance.version === expected.version && provenance.hash === expected.hash && provenance.assetCount === expected.assetCount;
|
|
1491
|
+
}
|
|
1163
1492
|
function checkRegistryPlacement(lockfile, asset, targetPath) {
|
|
1164
1493
|
if (lockfile.placement !== "registry") return void 0;
|
|
1165
1494
|
if (asset.kind !== "skill") return void 0;
|
|
@@ -1235,6 +1564,9 @@ function pathExistsEvenIfDanglingSymlink4(path) {
|
|
|
1235
1564
|
return false;
|
|
1236
1565
|
}
|
|
1237
1566
|
}
|
|
1567
|
+
function isProjectSkillTarget(targetPath) {
|
|
1568
|
+
return targetPath.startsWith(".agents/skills/") || targetPath.startsWith(".agents/manual-skills/");
|
|
1569
|
+
}
|
|
1238
1570
|
|
|
1239
1571
|
// src/asset-targets/recommend.ts
|
|
1240
1572
|
import { existsSync as existsSync10, readdirSync as readdirSync6, readFileSync as readFileSync6 } from "node:fs";
|
|
@@ -1301,13 +1633,6 @@ function recommendBundlesForTarget(targetDir) {
|
|
|
1301
1633
|
reasons: signals.frontendSignals.map((signal) => `frontend dependency: ${signal}`)
|
|
1302
1634
|
});
|
|
1303
1635
|
}
|
|
1304
|
-
if (signals.researchSignals.length > 0) {
|
|
1305
|
-
recommendations.push({
|
|
1306
|
-
bundleId: "research-docs",
|
|
1307
|
-
confidence: "high",
|
|
1308
|
-
reasons: signals.researchSignals
|
|
1309
|
-
});
|
|
1310
|
-
}
|
|
1311
1636
|
if (signals.writingSignals.length > 0) {
|
|
1312
1637
|
recommendations.push({
|
|
1313
1638
|
bundleId: "novel-writing",
|
|
@@ -1877,11 +2202,12 @@ var REQUIRED_ASSETS = [
|
|
|
1877
2202
|
"profiles/doc-only/profile.md",
|
|
1878
2203
|
"docs/reference/adoption/migration-v1.1.md"
|
|
1879
2204
|
];
|
|
1880
|
-
function
|
|
1881
|
-
const assets = listAssets();
|
|
2205
|
+
function runPackageDoctor(_args, dependencies = {}) {
|
|
2206
|
+
const assets = dependencies.assets ?? listAssets("packaged");
|
|
1882
2207
|
const assetPaths = new Set(assets.map((asset) => asset.path));
|
|
1883
2208
|
const missing = REQUIRED_ASSETS.filter((assetPath) => !assetPaths.has(assetPath));
|
|
1884
|
-
|
|
2209
|
+
const docGov = dependencies.docGov ?? checkDocGov();
|
|
2210
|
+
console.log("pro-gov package-doctor");
|
|
1885
2211
|
console.log(`assets: ${assets.length}`);
|
|
1886
2212
|
if (missing.length > 0) {
|
|
1887
2213
|
for (const assetPath of missing) {
|
|
@@ -1890,8 +2216,11 @@ function runDoctor(_args) {
|
|
|
1890
2216
|
} else {
|
|
1891
2217
|
console.log("assets: required project-governance assets found");
|
|
1892
2218
|
}
|
|
1893
|
-
console.log(
|
|
1894
|
-
return missing.length > 0 ? 1 : 0;
|
|
2219
|
+
console.log(docGov.message);
|
|
2220
|
+
return missing.length > 0 || !docGov.ok ? 1 : 0;
|
|
2221
|
+
}
|
|
2222
|
+
function runDoctor(args) {
|
|
2223
|
+
return runPackageDoctor(args);
|
|
1895
2224
|
}
|
|
1896
2225
|
function checkDocGov() {
|
|
1897
2226
|
const fromPath = spawnSync2("doc-gov", ["--help"], {
|
|
@@ -1899,20 +2228,26 @@ function checkDocGov() {
|
|
|
1899
2228
|
stdio: "ignore"
|
|
1900
2229
|
});
|
|
1901
2230
|
if (!fromPath.error && fromPath.status === 0) {
|
|
1902
|
-
return "doc-gov: available on PATH";
|
|
2231
|
+
return { ok: true, message: "doc-gov: available on PATH" };
|
|
1903
2232
|
}
|
|
1904
2233
|
const dependencyCli = resolveDocGovDependencyCli();
|
|
1905
2234
|
if (!dependencyCli) {
|
|
1906
|
-
return
|
|
2235
|
+
return {
|
|
2236
|
+
ok: false,
|
|
2237
|
+
message: "doc-gov: not found; install @pieai/doc-gov beside @pieai/pro-gov for validation."
|
|
2238
|
+
};
|
|
1907
2239
|
}
|
|
1908
2240
|
const fromDependency = spawnSync2(process.execPath, [dependencyCli, "--help"], {
|
|
1909
2241
|
encoding: "utf8",
|
|
1910
2242
|
stdio: "ignore"
|
|
1911
2243
|
});
|
|
1912
2244
|
if (!fromDependency.error && fromDependency.status === 0) {
|
|
1913
|
-
return "doc-gov: available via package dependency";
|
|
2245
|
+
return { ok: true, message: "doc-gov: available via package dependency" };
|
|
1914
2246
|
}
|
|
1915
|
-
return
|
|
2247
|
+
return {
|
|
2248
|
+
ok: false,
|
|
2249
|
+
message: `doc-gov: dependency found but returned status ${fromDependency.status ?? "unknown"}`
|
|
2250
|
+
};
|
|
1916
2251
|
}
|
|
1917
2252
|
function resolveDocGovDependencyCli() {
|
|
1918
2253
|
try {
|
|
@@ -2077,7 +2412,7 @@ import {
|
|
|
2077
2412
|
lstatSync as lstatSync6,
|
|
2078
2413
|
mkdirSync as mkdirSync5,
|
|
2079
2414
|
readdirSync as readdirSync7,
|
|
2080
|
-
statSync as
|
|
2415
|
+
statSync as statSync4,
|
|
2081
2416
|
writeFileSync as writeFileSync4
|
|
2082
2417
|
} from "node:fs";
|
|
2083
2418
|
import { homedir } from "node:os";
|
|
@@ -2504,7 +2839,7 @@ function fallbackMeasure(root) {
|
|
|
2504
2839
|
if (entry.isDirectory()) pending.push(path);
|
|
2505
2840
|
else if (entry.isFile()) {
|
|
2506
2841
|
try {
|
|
2507
|
-
bytes +=
|
|
2842
|
+
bytes += statSync4(path).size;
|
|
2508
2843
|
} catch {
|
|
2509
2844
|
}
|
|
2510
2845
|
}
|
|
@@ -4417,10 +4752,10 @@ function formatLink(link) {
|
|
|
4417
4752
|
}
|
|
4418
4753
|
|
|
4419
4754
|
// src/lens/scan.ts
|
|
4420
|
-
import { spawnSync as
|
|
4421
|
-
import { existsSync as
|
|
4755
|
+
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
4756
|
+
import { existsSync as existsSync21, readFileSync as readFileSync13, statSync as statSync6 } from "node:fs";
|
|
4422
4757
|
import { homedir as homedir3 } from "node:os";
|
|
4423
|
-
import { join as
|
|
4758
|
+
import { join as join22 } from "node:path";
|
|
4424
4759
|
|
|
4425
4760
|
// src/host-ssot.ts
|
|
4426
4761
|
import { lstatSync as lstatSync7, readlinkSync as readlinkSync3, realpathSync as realpathSync3 } from "node:fs";
|
|
@@ -4542,7 +4877,7 @@ function safeLstat2(path) {
|
|
|
4542
4877
|
}
|
|
4543
4878
|
|
|
4544
4879
|
// src/portfolio/redundancy.ts
|
|
4545
|
-
import { existsSync as existsSync18, readdirSync as readdirSync9, statSync as
|
|
4880
|
+
import { existsSync as existsSync18, readdirSync as readdirSync9, statSync as statSync5 } from "node:fs";
|
|
4546
4881
|
import { homedir as homedir2 } from "node:os";
|
|
4547
4882
|
import { join as join19 } from "node:path";
|
|
4548
4883
|
var DEFAULT_CACHE_THRESHOLD_BYTES = 1e9;
|
|
@@ -4657,7 +4992,7 @@ function collectDirectoryStats(root) {
|
|
|
4657
4992
|
} else if (entry.isFile()) {
|
|
4658
4993
|
fileCount += 1;
|
|
4659
4994
|
try {
|
|
4660
|
-
bytes +=
|
|
4995
|
+
bytes += statSync5(path).size;
|
|
4661
4996
|
} catch {
|
|
4662
4997
|
}
|
|
4663
4998
|
}
|
|
@@ -4698,6 +5033,111 @@ function readPackageJson(path) {
|
|
|
4698
5033
|
}
|
|
4699
5034
|
}
|
|
4700
5035
|
|
|
5036
|
+
// src/repository-files.ts
|
|
5037
|
+
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
5038
|
+
import { existsSync as existsSync20, readdirSync as readdirSync10 } from "node:fs";
|
|
5039
|
+
import { isAbsolute as isAbsolute5, join as join21, posix as posix3, relative as relative8 } from "node:path";
|
|
5040
|
+
var gitMaxBufferBytes = 64 * 1024 * 1024;
|
|
5041
|
+
var RepositoryFileDiscoveryError = class extends Error {
|
|
5042
|
+
constructor(message) {
|
|
5043
|
+
super(message);
|
|
5044
|
+
this.name = "RepositoryFileDiscoveryError";
|
|
5045
|
+
}
|
|
5046
|
+
};
|
|
5047
|
+
function discoverRepositoryFiles(root, options = {}) {
|
|
5048
|
+
const probe = runGit(root, ["rev-parse", "--is-inside-work-tree"]);
|
|
5049
|
+
if (!probe.ok) {
|
|
5050
|
+
if (!probe.notRepository || existsSync20(join21(root, ".git"))) {
|
|
5051
|
+
throw new RepositoryFileDiscoveryError(probe.message);
|
|
5052
|
+
}
|
|
5053
|
+
return {
|
|
5054
|
+
source: "filesystem",
|
|
5055
|
+
files: discoverFilesystemFiles(root, options)
|
|
5056
|
+
};
|
|
5057
|
+
}
|
|
5058
|
+
if (probe.stdout.trim() !== "true") {
|
|
5059
|
+
throw new RepositoryFileDiscoveryError(
|
|
5060
|
+
"Git repository discovery failed: target is a Git repository without a worktree."
|
|
5061
|
+
);
|
|
5062
|
+
}
|
|
5063
|
+
const gitFiles = runGit(root, [
|
|
5064
|
+
"ls-files",
|
|
5065
|
+
"--cached",
|
|
5066
|
+
"--others",
|
|
5067
|
+
"--exclude-standard",
|
|
5068
|
+
"-z",
|
|
5069
|
+
...options.gitPathspecs?.length ? ["--", ...options.gitPathspecs] : []
|
|
5070
|
+
]);
|
|
5071
|
+
if (!gitFiles.ok) throw new RepositoryFileDiscoveryError(gitFiles.message);
|
|
5072
|
+
const files = /* @__PURE__ */ new Set();
|
|
5073
|
+
for (const path of gitFiles.stdout.split("\0")) {
|
|
5074
|
+
if (!path) continue;
|
|
5075
|
+
const normalized = normalizeRepositoryRelativePath(path);
|
|
5076
|
+
if (!isSafeRepositoryRelativePath(normalized)) {
|
|
5077
|
+
throw new RepositoryFileDiscoveryError(
|
|
5078
|
+
`Git returned a path outside the repository boundary: ${path}`
|
|
5079
|
+
);
|
|
5080
|
+
}
|
|
5081
|
+
if (existsSync20(join21(root, normalized))) files.add(normalized);
|
|
5082
|
+
}
|
|
5083
|
+
return { source: "git", files: [...files].sort() };
|
|
5084
|
+
}
|
|
5085
|
+
function normalizeRepositoryRelativePath(path) {
|
|
5086
|
+
return posix3.normalize(path.replaceAll("\\", "/").replace(/^\.\/+/, ""));
|
|
5087
|
+
}
|
|
5088
|
+
function discoverFilesystemFiles(root, options) {
|
|
5089
|
+
const files = /* @__PURE__ */ new Set();
|
|
5090
|
+
const maxDepth = options.fallbackMaxDepth ?? Number.POSITIVE_INFINITY;
|
|
5091
|
+
const ignoredDirectories2 = options.fallbackIgnoredDirectories ?? /* @__PURE__ */ new Set();
|
|
5092
|
+
const visit = (directory, depth) => {
|
|
5093
|
+
if (depth > maxDepth || !existsSync20(directory)) return;
|
|
5094
|
+
let entries;
|
|
5095
|
+
try {
|
|
5096
|
+
entries = readdirSync10(directory, { withFileTypes: true });
|
|
5097
|
+
} catch {
|
|
5098
|
+
return;
|
|
5099
|
+
}
|
|
5100
|
+
for (const entry of entries) {
|
|
5101
|
+
const absolutePath = join21(directory, entry.name);
|
|
5102
|
+
if (entry.isDirectory()) {
|
|
5103
|
+
if (!ignoredDirectories2.has(entry.name)) visit(absolutePath, depth + 1);
|
|
5104
|
+
continue;
|
|
5105
|
+
}
|
|
5106
|
+
if (!entry.isFile()) continue;
|
|
5107
|
+
const relativePath = normalizeRepositoryRelativePath(relative8(root, absolutePath));
|
|
5108
|
+
if (isSafeRepositoryRelativePath(relativePath) && (options.fallbackIncludeFile?.(relativePath) ?? true)) {
|
|
5109
|
+
files.add(relativePath);
|
|
5110
|
+
}
|
|
5111
|
+
}
|
|
5112
|
+
};
|
|
5113
|
+
visit(root, 0);
|
|
5114
|
+
return [...files].sort();
|
|
5115
|
+
}
|
|
5116
|
+
function isSafeRepositoryRelativePath(path) {
|
|
5117
|
+
return path !== "" && path !== "." && !isAbsolute5(path) && !/^[a-zA-Z]:\//.test(path) && path !== ".." && !path.startsWith("../");
|
|
5118
|
+
}
|
|
5119
|
+
function runGit(root, args) {
|
|
5120
|
+
const result = spawnSync3("git", ["-C", root, ...args], {
|
|
5121
|
+
encoding: "utf8",
|
|
5122
|
+
maxBuffer: gitMaxBufferBytes,
|
|
5123
|
+
env: { ...process.env, LANG: "C", LC_ALL: "C" }
|
|
5124
|
+
});
|
|
5125
|
+
if (result.error) {
|
|
5126
|
+
return {
|
|
5127
|
+
ok: false,
|
|
5128
|
+
notRepository: false,
|
|
5129
|
+
message: `Git repository discovery failed: ${result.error.message}`
|
|
5130
|
+
};
|
|
5131
|
+
}
|
|
5132
|
+
if (result.status === 0) return { ok: true, stdout: result.stdout };
|
|
5133
|
+
const stderr = result.stderr.trim();
|
|
5134
|
+
return {
|
|
5135
|
+
ok: false,
|
|
5136
|
+
notRepository: /not a git repository/i.test(stderr),
|
|
5137
|
+
message: `Git repository discovery failed (${result.status ?? "unknown status"}): ${stderr || "no diagnostic output"}`
|
|
5138
|
+
};
|
|
5139
|
+
}
|
|
5140
|
+
|
|
4701
5141
|
// src/lens/scan.ts
|
|
4702
5142
|
var ignoredDirectories = /* @__PURE__ */ new Set([".git", ".next", ".turbo", "dist", "node_modules", "coverage"]);
|
|
4703
5143
|
function scanProjectLensTarget(targetDir, options = {}) {
|
|
@@ -4714,7 +5154,7 @@ function scanProjectLensTarget(targetDir, options = {}) {
|
|
|
4714
5154
|
includedFileCount: files.length,
|
|
4715
5155
|
excludedFileCount: candidateFiles.length - files.length
|
|
4716
5156
|
},
|
|
4717
|
-
aiEntryFiles: ["AGENTS.md", "CLAUDE.md"].filter((file) =>
|
|
5157
|
+
aiEntryFiles: ["AGENTS.md", "CLAUDE.md"].filter((file) => existsSync21(join22(targetDir, file))),
|
|
4718
5158
|
aiConfigFiles: [],
|
|
4719
5159
|
hostSsot: inspectProjectHostSsot(targetDir),
|
|
4720
5160
|
userHostSsot: inspectUserSkillsSsot(options.homeDir ?? process.env.HOME ?? homedir3()),
|
|
@@ -4725,17 +5165,17 @@ function scanProjectLensTarget(targetDir, options = {}) {
|
|
|
4725
5165
|
}),
|
|
4726
5166
|
packageJson,
|
|
4727
5167
|
docs: {
|
|
4728
|
-
hasDocsDirectory:
|
|
5168
|
+
hasDocsDirectory: existsSync21(join22(targetDir, "docs")),
|
|
4729
5169
|
markdownFileCount: markdownFiles.length,
|
|
4730
5170
|
governanceFiles: markdownFiles.filter((file) => file.startsWith("docs/governance/") || file.startsWith("docs/policy/")).sort()
|
|
4731
5171
|
},
|
|
4732
5172
|
git: readGitState(targetDir),
|
|
4733
|
-
largeFiles: files.map((file) => ({ path: file, bytes:
|
|
5173
|
+
largeFiles: files.map((file) => ({ path: file, bytes: statSync6(join22(targetDir, file)).size })).filter((file) => file.bytes >= largeFileBytes).sort((a, b) => b.bytes - a.bytes || a.path.localeCompare(b.path)).slice(0, 25)
|
|
4734
5174
|
};
|
|
4735
5175
|
}
|
|
4736
5176
|
function readPackageJson2(targetDir) {
|
|
4737
|
-
const packageJsonPath =
|
|
4738
|
-
if (!
|
|
5177
|
+
const packageJsonPath = join22(targetDir, "package.json");
|
|
5178
|
+
if (!existsSync21(packageJsonPath)) return void 0;
|
|
4739
5179
|
try {
|
|
4740
5180
|
const packageJson = JSON.parse(readFileSync13(packageJsonPath, "utf8"));
|
|
4741
5181
|
return {
|
|
@@ -4748,10 +5188,10 @@ function readPackageJson2(targetDir) {
|
|
|
4748
5188
|
}
|
|
4749
5189
|
}
|
|
4750
5190
|
function readGitState(targetDir) {
|
|
4751
|
-
const branch =
|
|
5191
|
+
const branch = runGit2(targetDir, ["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
4752
5192
|
if (!branch.ok) return { available: false };
|
|
4753
|
-
const head =
|
|
4754
|
-
const status =
|
|
5193
|
+
const head = runGit2(targetDir, ["log", "-1", "--format=%H %s"]);
|
|
5194
|
+
const status = runGit2(targetDir, ["status", "-sb"]);
|
|
4755
5195
|
return {
|
|
4756
5196
|
available: true,
|
|
4757
5197
|
branch: branch.stdout,
|
|
@@ -4759,27 +5199,18 @@ function readGitState(targetDir) {
|
|
|
4759
5199
|
statusShort: status.ok ? status.stdout : void 0
|
|
4760
5200
|
};
|
|
4761
5201
|
}
|
|
4762
|
-
function
|
|
4763
|
-
const result =
|
|
4764
|
-
encoding: "utf8"
|
|
5202
|
+
function runGit2(targetDir, args) {
|
|
5203
|
+
const result = spawnSync4("git", ["-C", targetDir, ...args], {
|
|
5204
|
+
encoding: "utf8",
|
|
5205
|
+
maxBuffer: 64 * 1024 * 1024
|
|
4765
5206
|
});
|
|
4766
5207
|
if (result.status !== 0) return { ok: false };
|
|
4767
5208
|
return { ok: true, stdout: result.stdout.trim() };
|
|
4768
5209
|
}
|
|
4769
5210
|
function listProjectFiles(targetDir) {
|
|
4770
|
-
|
|
4771
|
-
|
|
4772
|
-
|
|
4773
|
-
"--others",
|
|
4774
|
-
"--exclude-standard",
|
|
4775
|
-
"-z"
|
|
4776
|
-
]);
|
|
4777
|
-
if (gitFiles.ok) {
|
|
4778
|
-
return gitFiles.stdout.split("\0").filter(Boolean).map(toUnixPath4).filter((file) => existsSync20(join21(targetDir, file))).sort();
|
|
4779
|
-
}
|
|
4780
|
-
const files = [];
|
|
4781
|
-
collectFiles2(targetDir, targetDir, files);
|
|
4782
|
-
return files.sort();
|
|
5211
|
+
return discoverRepositoryFiles(targetDir, {
|
|
5212
|
+
fallbackIgnoredDirectories: ignoredDirectories
|
|
5213
|
+
}).files;
|
|
4783
5214
|
}
|
|
4784
5215
|
var excludedEvidencePrefixes = [
|
|
4785
5216
|
".agents/manual-skills/",
|
|
@@ -4792,20 +5223,6 @@ var excludedEvidencePrefixes = [
|
|
|
4792
5223
|
function isFirstPartyEvidenceFile(file) {
|
|
4793
5224
|
return !excludedEvidencePrefixes.some((prefix) => file.startsWith(prefix));
|
|
4794
5225
|
}
|
|
4795
|
-
function collectFiles2(rootDir, currentDir, files) {
|
|
4796
|
-
if (!existsSync20(currentDir)) return;
|
|
4797
|
-
for (const entry of readdirSync10(currentDir, { withFileTypes: true })) {
|
|
4798
|
-
if (entry.isDirectory()) {
|
|
4799
|
-
if (ignoredDirectories.has(entry.name)) continue;
|
|
4800
|
-
collectFiles2(rootDir, join21(currentDir, entry.name), files);
|
|
4801
|
-
} else if (entry.isFile()) {
|
|
4802
|
-
files.push(toUnixPath4(relative8(rootDir, join21(currentDir, entry.name))));
|
|
4803
|
-
}
|
|
4804
|
-
}
|
|
4805
|
-
}
|
|
4806
|
-
function toUnixPath4(path) {
|
|
4807
|
-
return path.replaceAll("\\", "/");
|
|
4808
|
-
}
|
|
4809
5226
|
|
|
4810
5227
|
// src/commands/lens.ts
|
|
4811
5228
|
function runLens(args) {
|
|
@@ -4957,19 +5374,19 @@ function printUsage4() {
|
|
|
4957
5374
|
}
|
|
4958
5375
|
|
|
4959
5376
|
// src/commands/portfolio.ts
|
|
4960
|
-
import { existsSync as
|
|
4961
|
-
import { join as
|
|
5377
|
+
import { existsSync as existsSync33, readFileSync as readFileSync18 } from "node:fs";
|
|
5378
|
+
import { join as join34 } from "node:path";
|
|
4962
5379
|
|
|
4963
5380
|
// src/portfolio/doctor.ts
|
|
4964
|
-
import { spawnSync as
|
|
4965
|
-
import { existsSync as
|
|
5381
|
+
import { spawnSync as spawnSync7 } from "node:child_process";
|
|
5382
|
+
import { existsSync as existsSync24, readFileSync as readFileSync16 } from "node:fs";
|
|
4966
5383
|
import { createRequire as createRequire2 } from "node:module";
|
|
4967
5384
|
import { homedir as homedir4 } from "node:os";
|
|
4968
|
-
import { dirname as dirname15, join as
|
|
5385
|
+
import { dirname as dirname15, join as join25 } from "node:path";
|
|
4969
5386
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
4970
5387
|
|
|
4971
5388
|
// src/host-tooling/inventory.ts
|
|
4972
|
-
import { spawnSync as
|
|
5389
|
+
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
4973
5390
|
function inspectHostTooling(requirements, runner = defaultRunner2) {
|
|
4974
5391
|
const hosts = [];
|
|
4975
5392
|
const issues = [];
|
|
@@ -5043,7 +5460,7 @@ function parseHostPlugins(host, value) {
|
|
|
5043
5460
|
});
|
|
5044
5461
|
}
|
|
5045
5462
|
function defaultRunner2({ command: command2 }) {
|
|
5046
|
-
const result =
|
|
5463
|
+
const result = spawnSync5(command2[0] ?? "", command2.slice(1), {
|
|
5047
5464
|
encoding: "utf8",
|
|
5048
5465
|
timeout: 1e4
|
|
5049
5466
|
});
|
|
@@ -5058,8 +5475,8 @@ function isRecord2(value) {
|
|
|
5058
5475
|
}
|
|
5059
5476
|
|
|
5060
5477
|
// src/portfolio/asset-state.ts
|
|
5061
|
-
import { existsSync as
|
|
5062
|
-
import { join as
|
|
5478
|
+
import { existsSync as existsSync22, lstatSync as lstatSync8, readFileSync as readFileSync14 } from "node:fs";
|
|
5479
|
+
import { join as join23 } from "node:path";
|
|
5063
5480
|
function comparePortfolioAssetState(options) {
|
|
5064
5481
|
const expectedManifest = readPlanDocument(
|
|
5065
5482
|
options.expectedPlan,
|
|
@@ -5070,10 +5487,10 @@ function comparePortfolioAssetState(options) {
|
|
|
5070
5487
|
".pro-gov/assets.lock.json"
|
|
5071
5488
|
);
|
|
5072
5489
|
const currentManifest = readJsonFile(
|
|
5073
|
-
|
|
5490
|
+
join23(options.targetDir, ".pro-gov/assets.json")
|
|
5074
5491
|
);
|
|
5075
5492
|
const currentLock = readJsonFile(
|
|
5076
|
-
|
|
5493
|
+
join23(options.targetDir, ".pro-gov/assets.lock.json")
|
|
5077
5494
|
);
|
|
5078
5495
|
const issues = [];
|
|
5079
5496
|
if (!sameStrings(currentManifest?.bundleIds, expectedManifest?.bundleIds)) {
|
|
@@ -5101,7 +5518,7 @@ function comparePortfolioAssetState(options) {
|
|
|
5101
5518
|
(action) => action.type === "adopt-symlink" && action.assetId === entry.id && action.legacyTargetPath === entry.targetPath
|
|
5102
5519
|
))
|
|
5103
5520
|
continue;
|
|
5104
|
-
const targetAbsolutePath =
|
|
5521
|
+
const targetAbsolutePath = join23(options.targetDir, entry.targetPath);
|
|
5105
5522
|
if (!pathIsSymlink(targetAbsolutePath)) continue;
|
|
5106
5523
|
issues.push({
|
|
5107
5524
|
type: "orphaned-managed-symlink",
|
|
@@ -5123,7 +5540,7 @@ function readPlanDocument(plan, targetPath) {
|
|
|
5123
5540
|
}
|
|
5124
5541
|
}
|
|
5125
5542
|
function readJsonFile(path) {
|
|
5126
|
-
if (!
|
|
5543
|
+
if (!existsSync22(path)) return void 0;
|
|
5127
5544
|
try {
|
|
5128
5545
|
return JSON.parse(readFileSync14(path, "utf8"));
|
|
5129
5546
|
} catch {
|
|
@@ -5134,7 +5551,12 @@ function sameStrings(left, right) {
|
|
|
5134
5551
|
return JSON.stringify([...left ?? []].sort()) === JSON.stringify([...right ?? []].sort());
|
|
5135
5552
|
}
|
|
5136
5553
|
function sameLock(left, right) {
|
|
5137
|
-
|
|
5554
|
+
if (JSON.stringify(normalizeLock(left)) !== JSON.stringify(normalizeLock(right))) return false;
|
|
5555
|
+
if (!hasRegistryProvenance(left) || !hasRegistryProvenance(right)) return true;
|
|
5556
|
+
return JSON.stringify(left.registryProvenance) === JSON.stringify(right.registryProvenance);
|
|
5557
|
+
}
|
|
5558
|
+
function hasRegistryProvenance(lock) {
|
|
5559
|
+
return lock !== void 0 && Object.prototype.hasOwnProperty.call(lock, "registryProvenance");
|
|
5138
5560
|
}
|
|
5139
5561
|
function normalizeLock(lock) {
|
|
5140
5562
|
return {
|
|
@@ -5153,9 +5575,9 @@ function pathIsSymlink(path) {
|
|
|
5153
5575
|
}
|
|
5154
5576
|
|
|
5155
5577
|
// src/portfolio/version-policy.ts
|
|
5156
|
-
import { spawnSync as
|
|
5157
|
-
import { existsSync as
|
|
5158
|
-
import { dirname as dirname14, join as
|
|
5578
|
+
import { spawnSync as spawnSync6 } from "node:child_process";
|
|
5579
|
+
import { existsSync as existsSync23, lstatSync as lstatSync9, readFileSync as readFileSync15 } from "node:fs";
|
|
5580
|
+
import { dirname as dirname14, join as join24 } from "node:path";
|
|
5159
5581
|
function inspectVersionPolicy(root, policy, projectType) {
|
|
5160
5582
|
if (!policy) return { status: "compliant", packages: [], runtimes: [], attentionCount: 0 };
|
|
5161
5583
|
const packageManifests = collectPackageManifests(root);
|
|
@@ -5245,7 +5667,7 @@ function inspectRuntime(expectedName, expectedVersion) {
|
|
|
5245
5667
|
function readRuntimeVersion(name) {
|
|
5246
5668
|
if (name === "node") return process.versions.node;
|
|
5247
5669
|
if (name !== "deno") return void 0;
|
|
5248
|
-
const result =
|
|
5670
|
+
const result = spawnSync6("deno", ["--version"], { encoding: "utf8" });
|
|
5249
5671
|
if (result.status !== 0) return void 0;
|
|
5250
5672
|
return /^deno\s+(\d+\.\d+\.\d+)/m.exec(result.stdout)?.[1];
|
|
5251
5673
|
}
|
|
@@ -5264,7 +5686,7 @@ function findDeclaredVersion(packageJson, name) {
|
|
|
5264
5686
|
function readInstalledVersion(root, name, fromDirectory = root) {
|
|
5265
5687
|
let current = fromDirectory;
|
|
5266
5688
|
while (true) {
|
|
5267
|
-
const packageJson = readJson2(
|
|
5689
|
+
const packageJson = readJson2(join24(current, "node_modules", name, "package.json"));
|
|
5268
5690
|
if (typeof packageJson?.version === "string") return packageJson.version;
|
|
5269
5691
|
if (current === root) return void 0;
|
|
5270
5692
|
const parent = dirname14(current);
|
|
@@ -5273,7 +5695,6 @@ function readInstalledVersion(root, name, fromDirectory = root) {
|
|
|
5273
5695
|
}
|
|
5274
5696
|
}
|
|
5275
5697
|
function collectPackageManifests(root) {
|
|
5276
|
-
const manifests = [];
|
|
5277
5698
|
const ignored = /* @__PURE__ */ new Set([
|
|
5278
5699
|
".git",
|
|
5279
5700
|
".next",
|
|
@@ -5297,37 +5718,36 @@ function collectPackageManifests(root) {
|
|
|
5297
5718
|
".pnpm-store",
|
|
5298
5719
|
".tmp-repos"
|
|
5299
5720
|
]);
|
|
5300
|
-
const
|
|
5301
|
-
|
|
5302
|
-
|
|
5721
|
+
const manifests = /* @__PURE__ */ new Map();
|
|
5722
|
+
const addManifest = (relativePath) => {
|
|
5723
|
+
const directorySegments = relativePath.split("/").slice(0, -1);
|
|
5724
|
+
if (relativePath.split("/").at(-1) !== "package.json" || directorySegments.length > 6 || directorySegments.some((segment) => ignored.has(segment))) {
|
|
5725
|
+
return;
|
|
5726
|
+
}
|
|
5727
|
+
const path = join24(root, relativePath);
|
|
5303
5728
|
try {
|
|
5304
|
-
|
|
5729
|
+
if (!lstatSync9(path).isFile()) return;
|
|
5305
5730
|
} catch {
|
|
5306
5731
|
return;
|
|
5307
5732
|
}
|
|
5308
|
-
|
|
5309
|
-
|
|
5310
|
-
|
|
5311
|
-
const packageJson = readJson2(path);
|
|
5312
|
-
if (packageJson)
|
|
5313
|
-
manifests.push({
|
|
5314
|
-
path: path.slice(root.length + 1) || "package.json",
|
|
5315
|
-
directory,
|
|
5316
|
-
packageJson
|
|
5317
|
-
});
|
|
5318
|
-
} else if (entry.isDirectory() && !ignored.has(entry.name)) {
|
|
5319
|
-
visit(path, depth + 1);
|
|
5320
|
-
}
|
|
5321
|
-
}
|
|
5733
|
+
const packageJson = readJson2(path);
|
|
5734
|
+
if (!packageJson) return;
|
|
5735
|
+
manifests.set(relativePath, { path: relativePath, directory: dirname14(path), packageJson });
|
|
5322
5736
|
};
|
|
5323
|
-
|
|
5324
|
-
|
|
5737
|
+
const discovery = discoverRepositoryFiles(root, {
|
|
5738
|
+
gitPathspecs: ["package.json", ":(glob)**/package.json"],
|
|
5739
|
+
fallbackIgnoredDirectories: ignored,
|
|
5740
|
+
fallbackMaxDepth: 6,
|
|
5741
|
+
fallbackIncludeFile: (path) => path.split("/").at(-1) === "package.json"
|
|
5742
|
+
});
|
|
5743
|
+
for (const relativePath of discovery.files) addManifest(relativePath);
|
|
5744
|
+
return [...manifests.values()].sort((a, b) => a.path.localeCompare(b.path));
|
|
5325
5745
|
}
|
|
5326
5746
|
function unique(values) {
|
|
5327
5747
|
return [...new Set(values)];
|
|
5328
5748
|
}
|
|
5329
5749
|
function readJson2(path) {
|
|
5330
|
-
if (!
|
|
5750
|
+
if (!existsSync23(path)) return void 0;
|
|
5331
5751
|
try {
|
|
5332
5752
|
return JSON.parse(readFileSync15(path, "utf8"));
|
|
5333
5753
|
} catch {
|
|
@@ -5362,12 +5782,12 @@ function inspectTarget(options) {
|
|
|
5362
5782
|
const { target } = options;
|
|
5363
5783
|
const hostSsot = inspectProjectHostSsot(target.path);
|
|
5364
5784
|
const issues = [];
|
|
5365
|
-
const packageJson = readJson3(
|
|
5785
|
+
const packageJson = readJson3(join25(target.path, "package.json"));
|
|
5366
5786
|
const packages = {};
|
|
5367
5787
|
for (const packageName of ["@pieai/pro-gov", "@pieai/doc-gov"]) {
|
|
5368
5788
|
const declared = packageJson?.devDependencies?.[packageName] ?? packageJson?.dependencies?.[packageName];
|
|
5369
5789
|
const installedPackage = readJson3(
|
|
5370
|
-
|
|
5790
|
+
join25(target.path, "node_modules", packageName, "package.json")
|
|
5371
5791
|
);
|
|
5372
5792
|
const installed = installedPackage?.version;
|
|
5373
5793
|
const expected = options.expectedPackageVersions[packageName];
|
|
@@ -5427,7 +5847,7 @@ function inspectTarget(options) {
|
|
|
5427
5847
|
type: "asset-lock-drift",
|
|
5428
5848
|
message: error instanceof Error ? error.message : String(error)
|
|
5429
5849
|
});
|
|
5430
|
-
if (!
|
|
5850
|
+
if (!existsSync24(join25(target.path, ".pro-gov/assets.json"))) {
|
|
5431
5851
|
issues.push({ type: "bundle-drift", message: "Target asset manifest is missing." });
|
|
5432
5852
|
}
|
|
5433
5853
|
}
|
|
@@ -5445,15 +5865,15 @@ function inspectTarget(options) {
|
|
|
5445
5865
|
};
|
|
5446
5866
|
}
|
|
5447
5867
|
function readTargetAssetHost(targetDir) {
|
|
5448
|
-
const lockfile = readJson3(
|
|
5868
|
+
const lockfile = readJson3(join25(targetDir, ".pro-gov/assets.lock.json"));
|
|
5449
5869
|
return isAssetRegistryHost(lockfile?.host) ? lockfile.host : void 0;
|
|
5450
5870
|
}
|
|
5451
5871
|
function isAssetRegistryHost(value) {
|
|
5452
5872
|
return value === "codex" || value === "claude-code" || value === "gemini-cli" || value === "antigravity";
|
|
5453
5873
|
}
|
|
5454
5874
|
function runTargetChecks(target) {
|
|
5455
|
-
const proGovCli =
|
|
5456
|
-
const docGovCli =
|
|
5875
|
+
const proGovCli = join25(target.path, "node_modules/@pieai/pro-gov/dist/cli.js");
|
|
5876
|
+
const docGovCli = join25(target.path, "node_modules/@pieai/doc-gov/dist/cli.js");
|
|
5457
5877
|
const commands = [
|
|
5458
5878
|
{
|
|
5459
5879
|
name: "pro-gov doctor",
|
|
@@ -5464,8 +5884,8 @@ function runTargetChecks(target) {
|
|
|
5464
5884
|
{ name: "doc-gov scan --check", cli: docGovCli, args: ["scan", "--check"] }
|
|
5465
5885
|
];
|
|
5466
5886
|
return commands.map((command2) => {
|
|
5467
|
-
if (!
|
|
5468
|
-
const result =
|
|
5887
|
+
if (!existsSync24(command2.cli)) return { name: command2.name, status: null };
|
|
5888
|
+
const result = spawnSync7(process.execPath, [command2.cli, ...command2.args], {
|
|
5469
5889
|
cwd: target.path,
|
|
5470
5890
|
encoding: "utf8",
|
|
5471
5891
|
timeout: 3e4
|
|
@@ -5474,13 +5894,13 @@ function runTargetChecks(target) {
|
|
|
5474
5894
|
});
|
|
5475
5895
|
}
|
|
5476
5896
|
function inspectGit(path) {
|
|
5477
|
-
const inside =
|
|
5897
|
+
const inside = spawnSync7("git", ["rev-parse", "--is-inside-work-tree"], {
|
|
5478
5898
|
cwd: path,
|
|
5479
5899
|
encoding: "utf8"
|
|
5480
5900
|
});
|
|
5481
5901
|
if (inside.status !== 0) return { isRepository: false, dirty: false };
|
|
5482
|
-
const status =
|
|
5483
|
-
const branch =
|
|
5902
|
+
const status = spawnSync7("git", ["status", "--porcelain"], { cwd: path, encoding: "utf8" });
|
|
5903
|
+
const branch = spawnSync7("git", ["branch", "--show-current"], { cwd: path, encoding: "utf8" });
|
|
5484
5904
|
return {
|
|
5485
5905
|
isRepository: true,
|
|
5486
5906
|
dirty: status.stdout.trim().length > 0,
|
|
@@ -5505,14 +5925,14 @@ function getExpectedPackageVersions() {
|
|
|
5505
5925
|
function findOwnPackageJson() {
|
|
5506
5926
|
let current = dirname15(fileURLToPath4(import.meta.url));
|
|
5507
5927
|
for (let depth = 0; depth < 5; depth += 1) {
|
|
5508
|
-
const candidate =
|
|
5509
|
-
if (
|
|
5928
|
+
const candidate = join25(current, "package.json");
|
|
5929
|
+
if (existsSync24(candidate)) return candidate;
|
|
5510
5930
|
current = dirname15(current);
|
|
5511
5931
|
}
|
|
5512
5932
|
return "";
|
|
5513
5933
|
}
|
|
5514
5934
|
function readJson3(path) {
|
|
5515
|
-
if (!path || !
|
|
5935
|
+
if (!path || !existsSync24(path)) return void 0;
|
|
5516
5936
|
try {
|
|
5517
5937
|
return JSON.parse(readFileSync16(path, "utf8"));
|
|
5518
5938
|
} catch {
|
|
@@ -5531,15 +5951,15 @@ function deduplicateIssues(issues) {
|
|
|
5531
5951
|
|
|
5532
5952
|
// src/portfolio/ai-health/index.ts
|
|
5533
5953
|
import { homedir as homedir5 } from "node:os";
|
|
5534
|
-
import { dirname as dirname17, join as
|
|
5954
|
+
import { dirname as dirname17, join as join33, resolve as resolve9 } from "node:path";
|
|
5535
5955
|
|
|
5536
5956
|
// src/portfolio/ai-health/entries.ts
|
|
5537
|
-
import { existsSync as
|
|
5538
|
-
import { join as
|
|
5957
|
+
import { existsSync as existsSync26, lstatSync as lstatSync11, realpathSync as realpathSync5 } from "node:fs";
|
|
5958
|
+
import { join as join26 } from "node:path";
|
|
5539
5959
|
|
|
5540
5960
|
// src/portfolio/ai-health/shared.ts
|
|
5541
5961
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
5542
|
-
import { existsSync as
|
|
5962
|
+
import { existsSync as existsSync25, lstatSync as lstatSync10, readFileSync as readFileSync17, readdirSync as readdirSync11, realpathSync as realpathSync4, statSync as statSync7 } from "node:fs";
|
|
5543
5963
|
function safeRealpath(path) {
|
|
5544
5964
|
try {
|
|
5545
5965
|
return realpathSync4(path);
|
|
@@ -5569,7 +5989,7 @@ function jsonObjectKeys(path, key) {
|
|
|
5569
5989
|
return Object.keys(value[key]).sort();
|
|
5570
5990
|
}
|
|
5571
5991
|
function tomlMcpNames(path) {
|
|
5572
|
-
if (!
|
|
5992
|
+
if (!existsSync25(path)) return [];
|
|
5573
5993
|
const names = /* @__PURE__ */ new Set();
|
|
5574
5994
|
for (const line of safeRead(path).split(/\r?\n/)) {
|
|
5575
5995
|
const match = line.match(/^\s*\[mcp_servers\.(?:"([^"]+)"|([^.\]]+))\]\s*$/);
|
|
@@ -5594,21 +6014,21 @@ function safeRead(path) {
|
|
|
5594
6014
|
}
|
|
5595
6015
|
function safeReadDir(path) {
|
|
5596
6016
|
try {
|
|
5597
|
-
return
|
|
6017
|
+
return readdirSync11(path).sort();
|
|
5598
6018
|
} catch {
|
|
5599
6019
|
return [];
|
|
5600
6020
|
}
|
|
5601
6021
|
}
|
|
5602
6022
|
function safeIsDirectory(path) {
|
|
5603
6023
|
try {
|
|
5604
|
-
return
|
|
6024
|
+
return statSync7(path).isDirectory();
|
|
5605
6025
|
} catch {
|
|
5606
6026
|
return false;
|
|
5607
6027
|
}
|
|
5608
6028
|
}
|
|
5609
6029
|
function pathLexists(path) {
|
|
5610
6030
|
try {
|
|
5611
|
-
|
|
6031
|
+
lstatSync10(path);
|
|
5612
6032
|
return true;
|
|
5613
6033
|
} catch {
|
|
5614
6034
|
return false;
|
|
@@ -5646,12 +6066,12 @@ function hasWorkflowReminderHooks(hooks) {
|
|
|
5646
6066
|
);
|
|
5647
6067
|
}
|
|
5648
6068
|
function inspectEntries(root) {
|
|
5649
|
-
const agentsPath =
|
|
5650
|
-
const agents = !
|
|
5651
|
-
const claudePath =
|
|
6069
|
+
const agentsPath = join26(root, "AGENTS.md");
|
|
6070
|
+
const agents = !existsSync26(agentsPath) ? "missing" : safeRead(agentsPath).includes("PGS-ROUTER:BEGIN") ? "pgs-router" : "custom";
|
|
6071
|
+
const claudePath = join26(root, "CLAUDE.md");
|
|
5652
6072
|
let claude = "missing";
|
|
5653
6073
|
if (pathLexists(claudePath)) {
|
|
5654
|
-
const info =
|
|
6074
|
+
const info = lstatSync11(claudePath);
|
|
5655
6075
|
if (info.isSymbolicLink()) {
|
|
5656
6076
|
try {
|
|
5657
6077
|
claude = realpathSync5(claudePath) === realpathSync5(agentsPath) ? "agents-symlink" : "custom";
|
|
@@ -5665,10 +6085,45 @@ function inspectEntries(root) {
|
|
|
5665
6085
|
}
|
|
5666
6086
|
return { agents, claude, gemini: inspectOptionalEntry(root, "GEMINI.md", agentsPath) };
|
|
5667
6087
|
}
|
|
6088
|
+
var AGENT_LINK_ROOTS = [".agents/workflows", ".agents/commands", ".claude/commands"];
|
|
6089
|
+
function inspectAgentLinks(root) {
|
|
6090
|
+
const entries = AGENT_LINK_ROOTS.flatMap((directory) => {
|
|
6091
|
+
const directoryPath = join26(root, directory);
|
|
6092
|
+
if (!pathLexists(directoryPath)) return [];
|
|
6093
|
+
try {
|
|
6094
|
+
if (!lstatSync11(directoryPath).isDirectory()) return [];
|
|
6095
|
+
} catch {
|
|
6096
|
+
return [];
|
|
6097
|
+
}
|
|
6098
|
+
return safeReadDir(directoryPath).filter((name) => !name.startsWith(".")).flatMap((name) => {
|
|
6099
|
+
const relativePath = `${directory}/${name}`;
|
|
6100
|
+
const path = join26(root, relativePath);
|
|
6101
|
+
let stat;
|
|
6102
|
+
try {
|
|
6103
|
+
stat = lstatSync11(path);
|
|
6104
|
+
} catch {
|
|
6105
|
+
return [];
|
|
6106
|
+
}
|
|
6107
|
+
let kind = stat.isSymbolicLink() ? "symlink" : stat.isDirectory() ? "directory" : "file";
|
|
6108
|
+
if (stat.isSymbolicLink()) {
|
|
6109
|
+
try {
|
|
6110
|
+
realpathSync5(path);
|
|
6111
|
+
} catch {
|
|
6112
|
+
kind = "dangling-symlink";
|
|
6113
|
+
}
|
|
6114
|
+
}
|
|
6115
|
+
return [{ path: relativePath, kind, tracked: gitTracks(root, relativePath) }];
|
|
6116
|
+
});
|
|
6117
|
+
});
|
|
6118
|
+
return {
|
|
6119
|
+
entries,
|
|
6120
|
+
trackedDangling: entries.filter((entry) => entry.tracked && entry.kind === "dangling-symlink").map((entry) => entry.path)
|
|
6121
|
+
};
|
|
6122
|
+
}
|
|
5668
6123
|
function inspectOptionalEntry(root, filename, agentsPath) {
|
|
5669
|
-
const path =
|
|
6124
|
+
const path = join26(root, filename);
|
|
5670
6125
|
if (!pathLexists(path)) return "missing";
|
|
5671
|
-
const info =
|
|
6126
|
+
const info = lstatSync11(path);
|
|
5672
6127
|
if (info.isSymbolicLink()) {
|
|
5673
6128
|
try {
|
|
5674
6129
|
return realpathSync5(path) === realpathSync5(agentsPath) ? "agents-symlink" : "custom";
|
|
@@ -5706,7 +6161,7 @@ function inspectHooks(root) {
|
|
|
5706
6161
|
{ host: "codex", path: ".codex/hooks.json" }
|
|
5707
6162
|
];
|
|
5708
6163
|
return configs.map((config) => {
|
|
5709
|
-
const value = readJson4(
|
|
6164
|
+
const value = readJson4(join26(root, config.path));
|
|
5710
6165
|
const counts = /* @__PURE__ */ new Map();
|
|
5711
6166
|
collectHookEvents(value, counts);
|
|
5712
6167
|
return {
|
|
@@ -5727,18 +6182,18 @@ function collectHookEvents(value, counts) {
|
|
|
5727
6182
|
}
|
|
5728
6183
|
}
|
|
5729
6184
|
function inspectDocs(root, expected) {
|
|
5730
|
-
const packageJson = readJson4(
|
|
6185
|
+
const packageJson = readJson4(join26(root, "package.json"));
|
|
5731
6186
|
const dependencies = isRecord3(packageJson) ? { ...recordOrEmpty(packageJson.dependencies), ...recordOrEmpty(packageJson.devDependencies) } : {};
|
|
5732
6187
|
const docGov = dependencyVersion(dependencies["@pieai/doc-gov"]);
|
|
5733
6188
|
const proGov = dependencyVersion(dependencies["@pieai/pro-gov"]);
|
|
5734
|
-
const routerMatch = safeRead(
|
|
6189
|
+
const routerMatch = safeRead(join26(root, "AGENTS.md")).match(/PGS-ROUTER:BEGIN\s+v([0-9.]+)/);
|
|
5735
6190
|
const declared = [docGov, proGov].filter((value) => Boolean(value));
|
|
5736
6191
|
return {
|
|
5737
6192
|
routerVersion: routerMatch?.[1],
|
|
5738
6193
|
expectedRouterVersion: CURRENT_ROUTER_VERSION,
|
|
5739
6194
|
routerAligned: routerMatch?.[1] === CURRENT_ROUTER_VERSION,
|
|
5740
|
-
manifest:
|
|
5741
|
-
currentWork:
|
|
6195
|
+
manifest: existsSync26(join26(root, "docs/governance/MANIFEST.yml")),
|
|
6196
|
+
currentWork: existsSync26(join26(root, "docs/reference/execution/current-work.md")),
|
|
5742
6197
|
packages: {
|
|
5743
6198
|
expected,
|
|
5744
6199
|
docGov,
|
|
@@ -5801,18 +6256,18 @@ function inspectGit2(root) {
|
|
|
5801
6256
|
|
|
5802
6257
|
// src/portfolio/ai-health/hosts.ts
|
|
5803
6258
|
import { execFileSync as execFileSync5 } from "node:child_process";
|
|
5804
|
-
import { existsSync as
|
|
5805
|
-
import { join as
|
|
6259
|
+
import { existsSync as existsSync28 } from "node:fs";
|
|
6260
|
+
import { join as join28, resolve as resolve8, sep as sep2 } from "node:path";
|
|
5806
6261
|
|
|
5807
6262
|
// src/portfolio/ai-health/devspace.ts
|
|
5808
6263
|
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
5809
|
-
import { existsSync as
|
|
5810
|
-
import { join as
|
|
6264
|
+
import { existsSync as existsSync27, statSync as statSync8 } from "node:fs";
|
|
6265
|
+
import { join as join27, relative as relative9, resolve as resolve7, sep } from "node:path";
|
|
5811
6266
|
function inspectDevSpaceHealth(options) {
|
|
5812
6267
|
const run = options.run ?? runDevSpaceCommand;
|
|
5813
|
-
const configDirectory =
|
|
5814
|
-
const configPath =
|
|
5815
|
-
const authPath =
|
|
6268
|
+
const configDirectory = join27(options.homeDir, ".devspace");
|
|
6269
|
+
const configPath = join27(configDirectory, "config.json");
|
|
6270
|
+
const authPath = join27(configDirectory, "auth.json");
|
|
5816
6271
|
const installedResult = run("devspace", ["--version"], 3e3);
|
|
5817
6272
|
const installedVersion = installedResult.ok ? installedResult.stdout.match(/\b\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?\b/)?.[0] : void 0;
|
|
5818
6273
|
const latestResult = run(
|
|
@@ -5835,9 +6290,9 @@ function inspectDevSpaceHealth(options) {
|
|
|
5835
6290
|
(repositoryPath) => allowedRoots.some((root) => isPathInside(repositoryPath, root))
|
|
5836
6291
|
) ? "complete" : "partial";
|
|
5837
6292
|
const bind = configExists && typeof configValue.host === "string" ? isLoopbackHost(configValue.host) ? "loopback" : "non-loopback" : "unknown";
|
|
5838
|
-
const directoryMode =
|
|
5839
|
-
const fileMode =
|
|
5840
|
-
const authMode =
|
|
6293
|
+
const directoryMode = existsSync27(configDirectory) ? modeString(statSync8(configDirectory).mode) : void 0;
|
|
6294
|
+
const fileMode = existsSync27(configPath) ? modeString(statSync8(configPath).mode) : void 0;
|
|
6295
|
+
const authMode = existsSync27(authPath) ? modeString(statSync8(authPath).mode) : void 0;
|
|
5841
6296
|
const update = installedVersion && latestVersion ? installedVersion === latestVersion ? "current" : "available" : "unknown";
|
|
5842
6297
|
const recommendations = [];
|
|
5843
6298
|
let status = "healthy";
|
|
@@ -5851,7 +6306,7 @@ function inspectDevSpaceHealth(options) {
|
|
|
5851
6306
|
};
|
|
5852
6307
|
if (!installedResult.ok) unhealthy("\u672C\u673A\u672A\u53D1\u73B0 DevSpace\uFF1B\u65E0\u6CD5\u4F7F\u7528\u5BBF\u4E3B\u5DE5\u4F5C\u533A\u670D\u52A1\u3002");
|
|
5853
6308
|
if (!configExists) unhealthy("\u7F3A\u5C11 ~/.devspace/config.json\u3002");
|
|
5854
|
-
if (!
|
|
6309
|
+
if (!existsSync27(authPath)) unhealthy("\u7F3A\u5C11 ~/.devspace/auth.json\u3002");
|
|
5855
6310
|
if (directoryMode && directoryMode !== "700")
|
|
5856
6311
|
unhealthy(`~/.devspace \u76EE\u5F55\u6743\u9650\u4E3A ${directoryMode}\uFF0C\u5E94\u6536\u7D27\u4E3A 700\u3002`);
|
|
5857
6312
|
if (fileMode && fileMode !== "600") unhealthy(`DevSpace \u914D\u7F6E\u6587\u4EF6\u6743\u9650\u4E3A ${fileMode}\uFF0C\u5E94\u4E3A 600\u3002`);
|
|
@@ -5884,7 +6339,7 @@ function inspectDevSpaceHealth(options) {
|
|
|
5884
6339
|
exists: configExists,
|
|
5885
6340
|
...directoryMode ? { directoryMode } : {},
|
|
5886
6341
|
...fileMode ? { fileMode } : {},
|
|
5887
|
-
authExists:
|
|
6342
|
+
authExists: existsSync27(authPath),
|
|
5888
6343
|
...authMode ? { authMode } : {},
|
|
5889
6344
|
bind,
|
|
5890
6345
|
portValid: configExists && typeof configValue.port === "number" && Number.isInteger(configValue.port) && configValue.port > 0 && configValue.port <= 65535,
|
|
@@ -5931,9 +6386,9 @@ var MCP_DISCOVERY_PATHS = {
|
|
|
5931
6386
|
}
|
|
5932
6387
|
};
|
|
5933
6388
|
function inspectHostEnvironment(homeDir, grokVersion, repositoryPaths, devspaceSettings) {
|
|
5934
|
-
const codexConfig =
|
|
5935
|
-
const claudeConfig =
|
|
5936
|
-
const grokConfig =
|
|
6389
|
+
const codexConfig = join28(homeDir, MCP_DISCOVERY_PATHS.user.codex);
|
|
6390
|
+
const claudeConfig = join28(homeDir, MCP_DISCOVERY_PATHS.user.claudeCode);
|
|
6391
|
+
const grokConfig = join28(homeDir, MCP_DISCOVERY_PATHS.user.grok);
|
|
5937
6392
|
const hostEnvironment = {
|
|
5938
6393
|
mcp: {
|
|
5939
6394
|
codexUser: { path: codexConfig, names: tomlMcpNames(codexConfig) },
|
|
@@ -5941,11 +6396,11 @@ function inspectHostEnvironment(homeDir, grokVersion, repositoryPaths, devspaceS
|
|
|
5941
6396
|
grokUser: { path: grokConfig, names: tomlMcpNames(grokConfig) }
|
|
5942
6397
|
},
|
|
5943
6398
|
skills: {
|
|
5944
|
-
codexUser: inspectSkillRoot(
|
|
5945
|
-
claudeCodeUser: inspectSkillRoot(
|
|
5946
|
-
grokUser: inspectSkillRoot(
|
|
5947
|
-
grokAgentsCompatibility: inspectSkillRoot(
|
|
5948
|
-
grokClaudeCompatibility: inspectSkillRoot(
|
|
6399
|
+
codexUser: inspectSkillRoot(join28(homeDir, ".agents/skills")),
|
|
6400
|
+
claudeCodeUser: inspectSkillRoot(join28(homeDir, ".claude/skills")),
|
|
6401
|
+
grokUser: inspectSkillRoot(join28(homeDir, ".grok/skills")),
|
|
6402
|
+
grokAgentsCompatibility: inspectSkillRoot(join28(homeDir, ".agents/skills")),
|
|
6403
|
+
grokClaudeCompatibility: inspectSkillRoot(join28(homeDir, ".claude/skills")),
|
|
5949
6404
|
ssot: inspectUserSkillsSsot(homeDir)
|
|
5950
6405
|
},
|
|
5951
6406
|
grok: {
|
|
@@ -5966,13 +6421,13 @@ function inspectHostEnvironment(homeDir, grokVersion, repositoryPaths, devspaceS
|
|
|
5966
6421
|
function inspectSkillRoot(path) {
|
|
5967
6422
|
const exists = pathLexists(path) && safeIsDirectory(path);
|
|
5968
6423
|
const names = exists ? safeReadDir(path).filter(
|
|
5969
|
-
(name) => !name.startsWith(".") &&
|
|
6424
|
+
(name) => !name.startsWith(".") && existsSync28(join28(path, name, "SKILL.md"))
|
|
5970
6425
|
) : [];
|
|
5971
6426
|
return { path, exists, names };
|
|
5972
6427
|
}
|
|
5973
6428
|
function claudeProjectLocalMcpNames(homeDir, root) {
|
|
5974
6429
|
if (!homeDir) return [];
|
|
5975
|
-
const value = readJson4(
|
|
6430
|
+
const value = readJson4(join28(homeDir, MCP_DISCOVERY_PATHS.user.claudeCode));
|
|
5976
6431
|
if (!isRecord3(value) || !isRecord3(value.projects)) return [];
|
|
5977
6432
|
const candidates = new Set(
|
|
5978
6433
|
[resolve8(root), safeRealpath(root)].filter((path) => Boolean(path))
|
|
@@ -6002,7 +6457,7 @@ function inspectGrokProject(root, homeDir, grokVersion) {
|
|
|
6002
6457
|
execFileSync5("grok", ["inspect", "--json"], {
|
|
6003
6458
|
cwd: root,
|
|
6004
6459
|
encoding: "utf8",
|
|
6005
|
-
env: { ...process.env, HOME: homeDir, GROK_HOME:
|
|
6460
|
+
env: { ...process.env, HOME: homeDir, GROK_HOME: join28(homeDir, ".grok") },
|
|
6006
6461
|
maxBuffer: 10 * 1024 * 1024,
|
|
6007
6462
|
stdio: ["ignore", "pipe", "ignore"],
|
|
6008
6463
|
timeout: 8e3
|
|
@@ -6010,7 +6465,7 @@ function inspectGrokProject(root, homeDir, grokVersion) {
|
|
|
6010
6465
|
);
|
|
6011
6466
|
if (!isRecord3(value)) return empty("failed");
|
|
6012
6467
|
const userClaudeNames = new Set(
|
|
6013
|
-
jsonObjectKeys(
|
|
6468
|
+
jsonObjectKeys(join28(homeDir, MCP_DISCOVERY_PATHS.user.claudeCode), "mcpServers")
|
|
6014
6469
|
);
|
|
6015
6470
|
const localClaudeNames = new Set(claudeProjectLocalMcpNames(homeDir, root));
|
|
6016
6471
|
const effectiveMcp = Array.isArray(value.mcpServers) ? value.mcpServers.flatMap((item) => {
|
|
@@ -6080,10 +6535,10 @@ function inferGrokMcpScope(name, sourceType, sourcePath, root, homeDir, userClau
|
|
|
6080
6535
|
}
|
|
6081
6536
|
const resolvedSource = safeRealpath(sourcePath) ?? resolve8(sourcePath);
|
|
6082
6537
|
const resolvedRoot = safeRealpath(root) ?? resolve8(root);
|
|
6083
|
-
if (resolvedSource ===
|
|
6538
|
+
if (resolvedSource === join28(resolvedRoot, MCP_DISCOVERY_PATHS.project.claudeCodeShared))
|
|
6084
6539
|
return "project-shared";
|
|
6085
6540
|
if (resolvedSource.startsWith(resolvedRoot + sep2)) return "project";
|
|
6086
|
-
if (homeDir && resolvedSource ===
|
|
6541
|
+
if (homeDir && resolvedSource === join28(resolve8(homeDir), MCP_DISCOVERY_PATHS.user.claudeCode)) {
|
|
6087
6542
|
if (localClaudeNames.has(name)) return "project-local";
|
|
6088
6543
|
if (userClaudeNames.has(name)) return "user";
|
|
6089
6544
|
}
|
|
@@ -6093,10 +6548,10 @@ function inferGrokMcpScope(name, sourceType, sourcePath, root, homeDir, userClau
|
|
|
6093
6548
|
}
|
|
6094
6549
|
|
|
6095
6550
|
// src/portfolio/ai-health/secrets.ts
|
|
6096
|
-
import { existsSync as
|
|
6097
|
-
import { join as
|
|
6551
|
+
import { existsSync as existsSync29, lstatSync as lstatSync12, readdirSync as readdirSync12, statSync as statSync9 } from "node:fs";
|
|
6552
|
+
import { join as join29, relative as relative10, sep as sep3 } from "node:path";
|
|
6098
6553
|
function inspectRepositorySecrets(root, id, secretsRoot, isRepository, environmentPolicy) {
|
|
6099
|
-
const centralPath =
|
|
6554
|
+
const centralPath = join29(secretsRoot, id);
|
|
6100
6555
|
const centralRealPath = safeRealpath(centralPath);
|
|
6101
6556
|
const localOnlyReasons = new Map(
|
|
6102
6557
|
(environmentPolicy?.localOnly ?? []).map((entry) => [entry.path, entry.reason])
|
|
@@ -6108,16 +6563,16 @@ function inspectRepositorySecrets(root, id, secretsRoot, isRepository, environme
|
|
|
6108
6563
|
tracked: isRepository ? gitTracks(root, path) : false,
|
|
6109
6564
|
template: isEnvironmentTemplate(path),
|
|
6110
6565
|
fixture: isEnvironmentFixture(path),
|
|
6111
|
-
symlink:
|
|
6112
|
-
centralized: pointsInside(
|
|
6566
|
+
symlink: lstatSync12(join29(root, path)).isSymbolicLink(),
|
|
6567
|
+
centralized: pointsInside(join29(root, path), centralRealPath),
|
|
6113
6568
|
localOnly: localOnlyReason !== void 0,
|
|
6114
6569
|
...localOnlyReason !== void 0 ? { localOnlyReason } : {}
|
|
6115
6570
|
};
|
|
6116
6571
|
});
|
|
6117
6572
|
return {
|
|
6118
|
-
centralDirectory:
|
|
6119
|
-
centralMode:
|
|
6120
|
-
centralFiles:
|
|
6573
|
+
centralDirectory: existsSync29(centralPath) ? "present" : "absent",
|
|
6574
|
+
centralMode: existsSync29(centralPath) ? modeString(statSync9(centralPath).mode) : void 0,
|
|
6575
|
+
centralFiles: existsSync29(centralPath) ? collectCentralSecretFiles(centralPath) : [],
|
|
6121
6576
|
repositoryEnvFiles: envFiles
|
|
6122
6577
|
};
|
|
6123
6578
|
}
|
|
@@ -6140,12 +6595,12 @@ function collectEnvironmentFiles(root, current = root, depth = 0) {
|
|
|
6140
6595
|
if (depth > 5) return [];
|
|
6141
6596
|
const found = [];
|
|
6142
6597
|
try {
|
|
6143
|
-
for (const entry of
|
|
6598
|
+
for (const entry of readdirSync12(current, { withFileTypes: true })) {
|
|
6144
6599
|
if (entry.isDirectory()) {
|
|
6145
6600
|
if (!SKIP_ENV_DIRECTORIES.has(entry.name))
|
|
6146
|
-
found.push(...collectEnvironmentFiles(root,
|
|
6601
|
+
found.push(...collectEnvironmentFiles(root, join29(current, entry.name), depth + 1));
|
|
6147
6602
|
} else if (isEnvironmentFilename(entry.name) && !isProviderGeneratedEnvironmentFile(entry.name)) {
|
|
6148
|
-
found.push(relative10(root,
|
|
6603
|
+
found.push(relative10(root, join29(current, entry.name)));
|
|
6149
6604
|
}
|
|
6150
6605
|
}
|
|
6151
6606
|
} catch {
|
|
@@ -6157,10 +6612,10 @@ function collectCentralSecretFiles(root, current = root, depth = 0) {
|
|
|
6157
6612
|
if (depth > 3) return [];
|
|
6158
6613
|
const found = [];
|
|
6159
6614
|
try {
|
|
6160
|
-
for (const entry of
|
|
6161
|
-
const path =
|
|
6615
|
+
for (const entry of readdirSync12(current, { withFileTypes: true })) {
|
|
6616
|
+
const path = join29(current, entry.name);
|
|
6162
6617
|
if (entry.isDirectory()) found.push(...collectCentralSecretFiles(root, path, depth + 1));
|
|
6163
|
-
else found.push({ path: relative10(root, path), mode: modeString(
|
|
6618
|
+
else found.push({ path: relative10(root, path), mode: modeString(lstatSync12(path).mode) });
|
|
6164
6619
|
}
|
|
6165
6620
|
} catch {
|
|
6166
6621
|
return found;
|
|
@@ -6184,7 +6639,7 @@ function isEnvironmentFixture(path) {
|
|
|
6184
6639
|
return /(^|[\\/])(?:tests?|__tests__)[\\/]fixtures?[\\/]/i.test(path) || /(^|[\\/])__fixtures__[\\/]/i.test(path);
|
|
6185
6640
|
}
|
|
6186
6641
|
function pointsInside(path, expectedRoot) {
|
|
6187
|
-
if (!expectedRoot || !
|
|
6642
|
+
if (!expectedRoot || !lstatSync12(path).isSymbolicLink()) return false;
|
|
6188
6643
|
const target = safeRealpath(path);
|
|
6189
6644
|
if (!target) return false;
|
|
6190
6645
|
const fromRoot = relative10(expectedRoot, target);
|
|
@@ -6194,20 +6649,20 @@ function hasUnsafeCentralSecretPermissions(secrets) {
|
|
|
6194
6649
|
return secrets.centralDirectory === "present" && (secrets.centralMode !== "700" || secrets.centralFiles.some((file) => file.mode !== "600"));
|
|
6195
6650
|
}
|
|
6196
6651
|
function inspectSecretsRoot(path) {
|
|
6197
|
-
return
|
|
6652
|
+
return existsSync29(path) ? { path, exists: true, mode: modeString(statSync9(path).mode) } : { path, exists: false };
|
|
6198
6653
|
}
|
|
6199
6654
|
|
|
6200
6655
|
// src/portfolio/ai-health/skills.ts
|
|
6201
|
-
import { existsSync as
|
|
6202
|
-
import { join as
|
|
6656
|
+
import { existsSync as existsSync30, lstatSync as lstatSync13, realpathSync as realpathSync6 } from "node:fs";
|
|
6657
|
+
import { join as join30 } from "node:path";
|
|
6203
6658
|
function countAutomaticSkillsNeedingReview(skills) {
|
|
6204
6659
|
return skills.automatic.filter(
|
|
6205
6660
|
(item) => !item.managed || !item.registryId || item.expectedPlacement !== "auto" || item.expectedScope === "user"
|
|
6206
6661
|
).length;
|
|
6207
6662
|
}
|
|
6208
6663
|
function inspectSkills(root, grokInspection, registeredSkills, userSkills) {
|
|
6209
|
-
const lock = readJson4(
|
|
6210
|
-
const assetManifest = readJson4(
|
|
6664
|
+
const lock = readJson4(join30(root, ".pro-gov/assets.lock.json"));
|
|
6665
|
+
const assetManifest = readJson4(join30(root, ".pro-gov/assets.json"));
|
|
6211
6666
|
const managed = /* @__PURE__ */ new Set();
|
|
6212
6667
|
const bundleIds = stringArray(isRecord3(lock) ? lock.bundleIds : void 0);
|
|
6213
6668
|
if (isRecord3(lock) && Array.isArray(lock.assets)) {
|
|
@@ -6219,7 +6674,7 @@ function inspectSkills(root, grokInspection, registeredSkills, userSkills) {
|
|
|
6219
6674
|
}
|
|
6220
6675
|
const inspectPlacement = (placement) => {
|
|
6221
6676
|
const directory = placement === "auto" ? "skills" : "manual-skills";
|
|
6222
|
-
const skillRoot =
|
|
6677
|
+
const skillRoot = join30(root, ".agents", directory);
|
|
6223
6678
|
if (!pathLexists(skillRoot) || !safeIsDirectory(skillRoot)) return [];
|
|
6224
6679
|
return safeReadDir(skillRoot).filter((name) => !name.startsWith(".")).map((name) => inspectSkillItem(skillRoot, directory, name, managed, registeredSkills));
|
|
6225
6680
|
};
|
|
@@ -6274,15 +6729,15 @@ function inspectSkills(root, grokInspection, registeredSkills, userSkills) {
|
|
|
6274
6729
|
},
|
|
6275
6730
|
hosts: {
|
|
6276
6731
|
codexProject: automatic.filter((item) => item.kind !== "dangling-symlink").length,
|
|
6277
|
-
claudeCodeProject: inspectSkillRoot(
|
|
6278
|
-
grokNativeProject: inspectSkillRoot(
|
|
6732
|
+
claudeCodeProject: inspectSkillRoot(join30(root, ".claude/skills")).names.length,
|
|
6733
|
+
grokNativeProject: inspectSkillRoot(join30(root, ".grok/skills")).names.length,
|
|
6279
6734
|
grokEffective: grokInspection.skills
|
|
6280
6735
|
}
|
|
6281
6736
|
};
|
|
6282
6737
|
}
|
|
6283
6738
|
function inspectSkillItem(skillRoot, directory, name, managed, registeredSkills) {
|
|
6284
|
-
const path =
|
|
6285
|
-
const stat =
|
|
6739
|
+
const path = join30(skillRoot, name);
|
|
6740
|
+
const stat = lstatSync13(path);
|
|
6286
6741
|
let kind = stat.isSymbolicLink() ? "symlink" : stat.isDirectory() ? "directory" : "file";
|
|
6287
6742
|
let realPath;
|
|
6288
6743
|
try {
|
|
@@ -6291,7 +6746,7 @@ function inspectSkillItem(skillRoot, directory, name, managed, registeredSkills)
|
|
|
6291
6746
|
if (stat.isSymbolicLink()) kind = "dangling-symlink";
|
|
6292
6747
|
}
|
|
6293
6748
|
const registered = realPath ? registeredSkills.find((skill) => skill.sourceRealPath === realPath) : void 0;
|
|
6294
|
-
const classification = registered ? void 0 : realPath && isPluginPack(realPath) ? "plugin-pack" : kind === "directory" &&
|
|
6749
|
+
const classification = registered ? void 0 : realPath && isPluginPack(realPath) ? "plugin-pack" : kind === "directory" && existsSync30(join30(path, "SKILL.md")) ? "project-local" : void 0;
|
|
6295
6750
|
return {
|
|
6296
6751
|
name,
|
|
6297
6752
|
kind,
|
|
@@ -6303,11 +6758,11 @@ function inspectSkillItem(skillRoot, directory, name, managed, registeredSkills)
|
|
|
6303
6758
|
};
|
|
6304
6759
|
}
|
|
6305
6760
|
function isPluginPack(path) {
|
|
6306
|
-
const skillsRoot =
|
|
6307
|
-
return
|
|
6761
|
+
const skillsRoot = join30(path, "skills");
|
|
6762
|
+
return existsSync30(join30(path, ".codex-plugin/plugin.json")) && safeIsDirectory(skillsRoot) && safeReadDir(skillsRoot).some((name) => existsSync30(join30(skillsRoot, name, "SKILL.md")));
|
|
6308
6763
|
}
|
|
6309
6764
|
function inspectInvalidSkillEntries(root, directory) {
|
|
6310
|
-
const skillRoot =
|
|
6765
|
+
const skillRoot = join30(root, ".agents", directory);
|
|
6311
6766
|
if (!pathLexists(skillRoot) || !safeIsDirectory(skillRoot)) return [];
|
|
6312
6767
|
return safeReadDir(skillRoot).flatMap((name) => {
|
|
6313
6768
|
if (name === ".gitkeep") return [];
|
|
@@ -6315,14 +6770,14 @@ function inspectInvalidSkillEntries(root, directory) {
|
|
|
6315
6770
|
return [{ path: `.agents/${directory}/${name}`, reason: "metadata-junk" }];
|
|
6316
6771
|
if (name.startsWith("."))
|
|
6317
6772
|
return [{ path: `.agents/${directory}/${name}`, reason: "unexpected-file" }];
|
|
6318
|
-
const path =
|
|
6319
|
-
return !
|
|
6773
|
+
const path = join30(skillRoot, name);
|
|
6774
|
+
return !lstatSync13(path).isDirectory() && !lstatSync13(path).isSymbolicLink() ? [{ path: `.agents/${directory}/${name}`, reason: "unexpected-file" }] : [];
|
|
6320
6775
|
});
|
|
6321
6776
|
}
|
|
6322
6777
|
function skillDuplicatesUser(item, root, userSkills) {
|
|
6323
6778
|
if (userSkills.names.has(item.name)) return true;
|
|
6324
|
-
const automatic =
|
|
6325
|
-
const manual =
|
|
6779
|
+
const automatic = join30(root, ".agents/skills", item.name);
|
|
6780
|
+
const manual = join30(root, ".agents/manual-skills", item.name);
|
|
6326
6781
|
const realPath = safeRealpath(pathLexists(automatic) ? automatic : manual);
|
|
6327
6782
|
return realPath ? userSkills.realPaths.has(realPath) : false;
|
|
6328
6783
|
}
|
|
@@ -6342,13 +6797,13 @@ function skillPlacementDrift(item, actualPlacement) {
|
|
|
6342
6797
|
return [];
|
|
6343
6798
|
}
|
|
6344
6799
|
function inspectClaudeSkillRoot(root) {
|
|
6345
|
-
const path =
|
|
6800
|
+
const path = join30(root, ".claude/skills");
|
|
6346
6801
|
if (!pathLexists(path)) return "missing";
|
|
6347
|
-
const stat =
|
|
6802
|
+
const stat = lstatSync13(path);
|
|
6348
6803
|
if (stat.isSymbolicLink()) {
|
|
6349
6804
|
try {
|
|
6350
6805
|
const target = realpathSync6(path);
|
|
6351
|
-
return target === realpathSync6(
|
|
6806
|
+
return target === realpathSync6(join30(root, ".agents/skills")) ? "shared-root" : "other";
|
|
6352
6807
|
} catch {
|
|
6353
6808
|
return "dangling-symlink";
|
|
6354
6809
|
}
|
|
@@ -6361,27 +6816,27 @@ function inspectSkillRegistry(executionEngineRoot) {
|
|
|
6361
6816
|
health: { source: 0, registered: 0, bundled: 0, bundles: 0 },
|
|
6362
6817
|
skills: []
|
|
6363
6818
|
};
|
|
6364
|
-
const agentAssetsRoot =
|
|
6365
|
-
const registry = readJson4(
|
|
6819
|
+
const agentAssetsRoot = join30(executionEngineRoot, "agent-assets");
|
|
6820
|
+
const registry = readJson4(join30(agentAssetsRoot, "registry.json"));
|
|
6366
6821
|
const assets = isRecord3(registry) && Array.isArray(registry.assets) ? registry.assets : [];
|
|
6367
6822
|
const registeredSkills = assets.filter((asset) => isRecord3(asset) && asset.kind === "skill");
|
|
6368
|
-
const bundleRoot =
|
|
6823
|
+
const bundleRoot = join30(agentAssetsRoot, "bundles");
|
|
6369
6824
|
const bundleFiles = safeReadDir(bundleRoot).filter((file) => file.endsWith(".json"));
|
|
6370
6825
|
const bundledIds = /* @__PURE__ */ new Set();
|
|
6371
6826
|
for (const file of bundleFiles) {
|
|
6372
|
-
const bundle = readJson4(
|
|
6827
|
+
const bundle = readJson4(join30(bundleRoot, file));
|
|
6373
6828
|
if (!isRecord3(bundle) || !Array.isArray(bundle.assets)) continue;
|
|
6374
6829
|
for (const id of bundle.assets) if (typeof id === "string") bundledIds.add(id);
|
|
6375
6830
|
}
|
|
6376
6831
|
const sourceRoots = [
|
|
6377
|
-
|
|
6378
|
-
|
|
6832
|
+
join30(agentAssetsRoot, "skills/pie-skills"),
|
|
6833
|
+
join30(agentAssetsRoot, "skills/npx-skills/.agents/skills")
|
|
6379
6834
|
];
|
|
6380
6835
|
const source = sourceRoots.reduce(
|
|
6381
|
-
(count, root) => count + safeReadDir(root).filter((name) =>
|
|
6836
|
+
(count, root) => count + safeReadDir(root).filter((name) => existsSync30(join30(root, name, "SKILL.md"))).length,
|
|
6382
6837
|
0
|
|
6383
6838
|
) + registeredSkills.filter(
|
|
6384
|
-
(asset) => isRecord3(asset) && asset.sourceKind === "local-pack" && typeof asset.sourcePath === "string" && isPluginPack(
|
|
6839
|
+
(asset) => isRecord3(asset) && asset.sourceKind === "local-pack" && typeof asset.sourcePath === "string" && isPluginPack(join30(agentAssetsRoot, asset.sourcePath))
|
|
6385
6840
|
).length;
|
|
6386
6841
|
return {
|
|
6387
6842
|
health: {
|
|
@@ -6398,7 +6853,7 @@ function inspectSkillRegistry(executionEngineRoot) {
|
|
|
6398
6853
|
return [
|
|
6399
6854
|
{
|
|
6400
6855
|
id: asset.id,
|
|
6401
|
-
sourceRealPath: safeRealpath(
|
|
6856
|
+
sourceRealPath: safeRealpath(join30(agentAssetsRoot, asset.sourcePath)),
|
|
6402
6857
|
defaultPlacement: asset.defaultPlacement,
|
|
6403
6858
|
defaultScope: asset.defaultScope === "user" ? "user" : "project"
|
|
6404
6859
|
}
|
|
@@ -6413,21 +6868,27 @@ function inspectUserSkillEvidence(root) {
|
|
|
6413
6868
|
for (const name of safeReadDir(root)) {
|
|
6414
6869
|
if (name.startsWith(".")) continue;
|
|
6415
6870
|
names.add(name);
|
|
6416
|
-
const realPath = safeRealpath(
|
|
6871
|
+
const realPath = safeRealpath(join30(root, name));
|
|
6417
6872
|
if (realPath) realPaths.add(realPath);
|
|
6418
6873
|
}
|
|
6419
6874
|
return { names, realPaths };
|
|
6420
6875
|
}
|
|
6421
6876
|
|
|
6422
6877
|
// src/portfolio/ai-health/technology.ts
|
|
6423
|
-
import { existsSync as
|
|
6424
|
-
import { join as
|
|
6878
|
+
import { existsSync as existsSync31, readdirSync as readdirSync13, statSync as statSync10 } from "node:fs";
|
|
6879
|
+
import { join as join31 } from "node:path";
|
|
6425
6880
|
function buildTechnologyMatrix(governance, repositories) {
|
|
6426
|
-
if (!governance) return [];
|
|
6881
|
+
if (!governance || governance.technologies.length === 0) return [];
|
|
6427
6882
|
const policy = governance.versionPolicy;
|
|
6883
|
+
const packageManifestsByRepository = /* @__PURE__ */ new Map();
|
|
6884
|
+
for (const repository of repositories) {
|
|
6885
|
+
if (!packageManifestsByRepository.has(repository.path)) {
|
|
6886
|
+
packageManifestsByRepository.set(repository.path, collectPackageManifests(repository.path));
|
|
6887
|
+
}
|
|
6888
|
+
}
|
|
6428
6889
|
return governance.technologies.map((technology) => {
|
|
6429
6890
|
const projects = repositories.flatMap((repository) => {
|
|
6430
|
-
const packageManifests =
|
|
6891
|
+
const packageManifests = packageManifestsByRepository.get(repository.path) ?? [];
|
|
6431
6892
|
const packageSignals = (technology.packages ?? []).map((name) => {
|
|
6432
6893
|
const requirement = policy?.packages.find((item) => item.name === name);
|
|
6433
6894
|
return packageManifests.map((manifest) => {
|
|
@@ -6442,8 +6903,8 @@ function buildTechnologyMatrix(governance, repositories) {
|
|
|
6442
6903
|
}).filter((item) => item !== void 0);
|
|
6443
6904
|
}).flat();
|
|
6444
6905
|
const fileSignal = (technology.files ?? []).some(
|
|
6445
|
-
(path) => hasUsableTechnologyFile(
|
|
6446
|
-
(manifest) => hasUsableTechnologyFile(
|
|
6906
|
+
(path) => hasUsableTechnologyFile(join31(repository.path, path)) || packageManifests.some(
|
|
6907
|
+
(manifest) => hasUsableTechnologyFile(join31(manifest.directory, path))
|
|
6447
6908
|
)
|
|
6448
6909
|
);
|
|
6449
6910
|
const modelSignal = [
|
|
@@ -6534,14 +6995,14 @@ function buildTechnologyMatrix(governance, repositories) {
|
|
|
6534
6995
|
}).filter((technology) => technology.projectCount > 0);
|
|
6535
6996
|
}
|
|
6536
6997
|
function hasUsableTechnologyFile(path) {
|
|
6537
|
-
if (!
|
|
6998
|
+
if (!existsSync31(path)) return false;
|
|
6538
6999
|
try {
|
|
6539
|
-
const info =
|
|
7000
|
+
const info = statSync10(path);
|
|
6540
7001
|
if (info.isFile()) return true;
|
|
6541
7002
|
if (!info.isDirectory()) return false;
|
|
6542
|
-
return
|
|
7003
|
+
return readdirSync13(path, { withFileTypes: true }).some((entry) => {
|
|
6543
7004
|
if (entry.name.startsWith(".")) return false;
|
|
6544
|
-
const child =
|
|
7005
|
+
const child = join31(path, entry.name);
|
|
6545
7006
|
if (entry.isDirectory()) return hasUsableTechnologyFile(child);
|
|
6546
7007
|
return entry.name.toLowerCase() !== "readme.md";
|
|
6547
7008
|
});
|
|
@@ -6553,7 +7014,7 @@ function inspectExclusiveOwnership(root, endpoint, governance) {
|
|
|
6553
7014
|
const projectType = endpoint.projectType;
|
|
6554
7015
|
return (governance?.exclusiveOwnership ?? []).flatMap((rule) => {
|
|
6555
7016
|
if (projectType && rule.allowedProjectTypes.includes(projectType)) return [];
|
|
6556
|
-
const paths = rule.paths.filter((path) =>
|
|
7017
|
+
const paths = rule.paths.filter((path) => existsSync31(join31(root, path)));
|
|
6557
7018
|
return paths.length > 0 ? [{ rule, paths }] : [];
|
|
6558
7019
|
});
|
|
6559
7020
|
}
|
|
@@ -6568,7 +7029,7 @@ function inspectProjectModel(root, endpoint, governance) {
|
|
|
6568
7029
|
const detection = (id) => {
|
|
6569
7030
|
const technology = technologyById.get(id);
|
|
6570
7031
|
const packageMatch = technology?.packages?.some((name) => packages.has(name)) ?? false;
|
|
6571
|
-
const fileMatch = technology?.files?.some((path) =>
|
|
7032
|
+
const fileMatch = technology?.files?.some((path) => existsSync31(join31(root, path))) ?? false;
|
|
6572
7033
|
return { id, label: technology?.label ?? id, detected: packageMatch || fileMatch };
|
|
6573
7034
|
};
|
|
6574
7035
|
const selected = new Set(endpoint.capabilities ?? []);
|
|
@@ -6607,8 +7068,8 @@ function collectPackageNames(root) {
|
|
|
6607
7068
|
}
|
|
6608
7069
|
|
|
6609
7070
|
// src/portfolio/ai-health/report.ts
|
|
6610
|
-
import { cpSync as cpSync3, existsSync as
|
|
6611
|
-
import { dirname as dirname16, join as
|
|
7071
|
+
import { cpSync as cpSync3, existsSync as existsSync32, mkdirSync as mkdirSync10, writeFileSync as writeFileSync9 } from "node:fs";
|
|
7072
|
+
import { dirname as dirname16, join as join32 } from "node:path";
|
|
6612
7073
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
6613
7074
|
function mergePortfolioAiHealthReport(existing, latest, allRepositoryIds) {
|
|
6614
7075
|
const repositoriesById = /* @__PURE__ */ new Map();
|
|
@@ -6652,16 +7113,16 @@ function writePortfolioAiHealthReport(report, outDir) {
|
|
|
6652
7113
|
mkdirSync10(outDir, { recursive: true });
|
|
6653
7114
|
const dashboardAssets = findDashboardAssets();
|
|
6654
7115
|
for (const file of ["index.html", "app.js", "app.css"]) {
|
|
6655
|
-
const source =
|
|
6656
|
-
if (!
|
|
6657
|
-
cpSync3(source,
|
|
7116
|
+
const source = join32(dashboardAssets, file);
|
|
7117
|
+
if (!existsSync32(source)) throw new Error(`Portfolio dashboard asset is missing: ${source}`);
|
|
7118
|
+
cpSync3(source, join32(outDir, file));
|
|
6658
7119
|
}
|
|
6659
|
-
const jsonPath =
|
|
6660
|
-
const htmlPath =
|
|
7120
|
+
const jsonPath = join32(outDir, "portfolio-ai-health.json");
|
|
7121
|
+
const htmlPath = join32(outDir, "index.html");
|
|
6661
7122
|
writeFileSync9(jsonPath, `${JSON.stringify(report, null, 2)}
|
|
6662
7123
|
`);
|
|
6663
7124
|
writeFileSync9(
|
|
6664
|
-
|
|
7125
|
+
join32(outDir, "data.js"),
|
|
6665
7126
|
`window.__PORTFOLIO_AI_HEALTH__ = ${safeJavaScriptJson2(report)};
|
|
6666
7127
|
`
|
|
6667
7128
|
);
|
|
@@ -6671,14 +7132,14 @@ function findDashboardAssets() {
|
|
|
6671
7132
|
const packageRoot2 = dirname16(dirname16(fileURLToPath5(import.meta.url)));
|
|
6672
7133
|
const candidates = [
|
|
6673
7134
|
process.env.PGS_DASHBOARD_ASSETS_DIR,
|
|
6674
|
-
|
|
6675
|
-
|
|
6676
|
-
|
|
6677
|
-
|
|
6678
|
-
|
|
6679
|
-
|
|
7135
|
+
join32(packageRoot2, ".dashboard-build"),
|
|
7136
|
+
join32(packageRoot2, "assets/portfolio-dashboard"),
|
|
7137
|
+
join32(process.cwd(), ".dashboard-build"),
|
|
7138
|
+
join32(process.cwd(), "assets/portfolio-dashboard"),
|
|
7139
|
+
join32(process.cwd(), "packages/pro-gov/.dashboard-build"),
|
|
7140
|
+
join32(process.cwd(), "packages/pro-gov/assets/portfolio-dashboard")
|
|
6680
7141
|
].filter((value) => Boolean(value));
|
|
6681
|
-
const match = candidates.find((path) =>
|
|
7142
|
+
const match = candidates.find((path) => existsSync32(join32(path, "index.html")));
|
|
6682
7143
|
if (!match)
|
|
6683
7144
|
throw new Error(
|
|
6684
7145
|
"Portfolio dashboard assets were not built. Run pnpm --filter @pieai/pro-gov build."
|
|
@@ -6696,7 +7157,7 @@ function inspectPortfolioAiHealth(options) {
|
|
|
6696
7157
|
if (options.targetId && options.targetId !== "all" && endpoints.length === 0) {
|
|
6697
7158
|
throw new Error(`Unknown portfolio target: ${options.targetId}`);
|
|
6698
7159
|
}
|
|
6699
|
-
const secretsRoot = options.secretsRoot ??
|
|
7160
|
+
const secretsRoot = options.secretsRoot ?? join33(
|
|
6700
7161
|
dirname17(
|
|
6701
7162
|
options.manifest.controlPlane?.path ?? allEndpoints[0]?.endpoint.path ?? process.cwd()
|
|
6702
7163
|
),
|
|
@@ -6706,9 +7167,9 @@ function inspectPortfolioAiHealth(options) {
|
|
|
6706
7167
|
const grokVersion = commandVersion("grok");
|
|
6707
7168
|
const executionEngineRoot = options.manifest.executionEngine?.path;
|
|
6708
7169
|
const skillRegistry = inspectSkillRegistry(executionEngineRoot);
|
|
6709
|
-
const userSkills = inspectUserSkillEvidence(
|
|
7170
|
+
const userSkills = inspectUserSkillEvidence(join33(homeDir, ".agents/skills"));
|
|
6710
7171
|
const expectedPackageVersion = packageVersion(
|
|
6711
|
-
|
|
7172
|
+
join33(executionEngineRoot ?? "", "packages/pro-gov/package.json")
|
|
6712
7173
|
);
|
|
6713
7174
|
const repositories = endpoints.map(
|
|
6714
7175
|
({ endpoint, role }) => inspectRepository(
|
|
@@ -6726,7 +7187,7 @@ function inspectPortfolioAiHealth(options) {
|
|
|
6726
7187
|
const summary = { healthy: 0, attention: 0, unhealthy: 0 };
|
|
6727
7188
|
for (const repository of repositories) summary[repository.status] += 1;
|
|
6728
7189
|
return {
|
|
6729
|
-
schemaVersion:
|
|
7190
|
+
schemaVersion: 8,
|
|
6730
7191
|
portfolioId: options.manifest.portfolioId,
|
|
6731
7192
|
generatedAt: options.generatedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
6732
7193
|
coverage: {
|
|
@@ -6775,19 +7236,20 @@ function inspectRepository(endpoint, role, secretsRoot, homeDir, expectedPackage
|
|
|
6775
7236
|
const root = endpoint.path;
|
|
6776
7237
|
const git = inspectGit2(root);
|
|
6777
7238
|
const entries = inspectEntries(root);
|
|
7239
|
+
const agentLinks = inspectAgentLinks(root);
|
|
6778
7240
|
const grokInspection = inspectGrokProject(root, homeDir, grokVersion);
|
|
6779
7241
|
const skills = inspectSkills(root, grokInspection, registeredSkills, userSkills);
|
|
6780
7242
|
const hostSsot = inspectProjectHostSsot(root);
|
|
6781
7243
|
const hooks = inspectHooks(root);
|
|
6782
7244
|
const docs = inspectDocs(root, role === "execution-engine" ? void 0 : expectedPackageVersion);
|
|
6783
7245
|
const mcp = {
|
|
6784
|
-
codexProject: tomlMcpNames(
|
|
7246
|
+
codexProject: tomlMcpNames(join33(root, MCP_DISCOVERY_PATHS.project.codex)),
|
|
6785
7247
|
claudeCodeProjectShared: jsonObjectKeys(
|
|
6786
|
-
|
|
7248
|
+
join33(root, MCP_DISCOVERY_PATHS.project.claudeCodeShared),
|
|
6787
7249
|
"mcpServers"
|
|
6788
7250
|
),
|
|
6789
7251
|
claudeCodeProjectLocal: claudeProjectLocalMcpNames(homeDir, root),
|
|
6790
|
-
grokProject: tomlMcpNames(
|
|
7252
|
+
grokProject: tomlMcpNames(join33(root, MCP_DISCOVERY_PATHS.project.grok)),
|
|
6791
7253
|
grokEffective: grokInspection.effectiveMcp,
|
|
6792
7254
|
grokInspection: grokInspection.inspection
|
|
6793
7255
|
};
|
|
@@ -6855,6 +7317,10 @@ function inspectRepository(endpoint, role, secretsRoot, homeDir, expectedPackage
|
|
|
6855
7317
|
recommendations.push("GEMINI.md \u662F\u65AD\u5F00\u7684\u94FE\u63A5\uFF1B\u9700\u8981\u91CD\u65B0\u6307\u5411 AGENTS.md\u3002");
|
|
6856
7318
|
if ([...skills.automatic, ...skills.manual].some((skill) => skill.kind === "dangling-symlink"))
|
|
6857
7319
|
recommendations.push("`.agents/skills` \u6216 `.agents/manual-skills` \u4E2D\u5B58\u5728\u65AD\u5F00\u7684\u6280\u80FD\u94FE\u63A5\u3002");
|
|
7320
|
+
if (agentLinks.trackedDangling.length > 0)
|
|
7321
|
+
recommendations.push(
|
|
7322
|
+
`\u53D1\u73B0 ${agentLinks.trackedDangling.length} \u4E2A\u53D7 Git \u8DDF\u8E2A\u7684 agent workflow/command \u65AD\u5F00\u94FE\u63A5\uFF1A${agentLinks.trackedDangling.join("\u3001")}\uFF1B\u786E\u8BA4\u662F\u5426\u5E94\u5220\u9664\u65E7\u5165\u53E3\u6216\u6062\u590D canonical \u76EE\u6807\u3002`
|
|
7323
|
+
);
|
|
6858
7324
|
if (skills.invalidEntries.length > 0)
|
|
6859
7325
|
recommendations.push(
|
|
6860
7326
|
`\u6280\u80FD\u76EE\u5F55\u4E2D\u6709 ${skills.invalidEntries.length} \u4E2A\u5783\u573E\u6216\u975E\u6280\u80FD\u6587\u4EF6\uFF1A${skills.invalidEntries.map((entry) => entry.path).join("\u3001")}\u3002`
|
|
@@ -6967,6 +7433,7 @@ function inspectRepository(endpoint, role, secretsRoot, homeDir, expectedPackage
|
|
|
6967
7433
|
status: deriveStatus(
|
|
6968
7434
|
role,
|
|
6969
7435
|
entries,
|
|
7436
|
+
agentLinks,
|
|
6970
7437
|
git,
|
|
6971
7438
|
hooks,
|
|
6972
7439
|
skills,
|
|
@@ -6983,6 +7450,7 @@ function inspectRepository(endpoint, role, secretsRoot, homeDir, expectedPackage
|
|
|
6983
7450
|
recommendations,
|
|
6984
7451
|
git,
|
|
6985
7452
|
entries,
|
|
7453
|
+
agentLinks,
|
|
6986
7454
|
hooks,
|
|
6987
7455
|
mcp,
|
|
6988
7456
|
skills,
|
|
@@ -6995,8 +7463,8 @@ function inspectRepository(endpoint, role, secretsRoot, homeDir, expectedPackage
|
|
|
6995
7463
|
redundancy
|
|
6996
7464
|
};
|
|
6997
7465
|
}
|
|
6998
|
-
function deriveStatus(role, entries, git, hooks, skills, hostSsot, secrets, docs, projectModel, exclusiveOwnershipViolations, technologyGovernanceConfigured, versions, verification, redundancy) {
|
|
6999
|
-
if (!git.isRepository || entries.agents === "missing" || entries.claude === "dangling-symlink" || entries.gemini === "dangling-symlink" || [...skills.automatic, ...skills.manual].some((item) => item.kind === "dangling-symlink") || exclusiveOwnershipViolations.length > 0 || secrets.repositoryEnvFiles.some((file) => file.tracked && !file.template && !file.fixture) || hasUnsafeCentralSecretPermissions(secrets))
|
|
7466
|
+
function deriveStatus(role, entries, agentLinks, git, hooks, skills, hostSsot, secrets, docs, projectModel, exclusiveOwnershipViolations, technologyGovernanceConfigured, versions, verification, redundancy) {
|
|
7467
|
+
if (!git.isRepository || entries.agents === "missing" || entries.claude === "dangling-symlink" || entries.gemini === "dangling-symlink" || agentLinks.trackedDangling.length > 0 || [...skills.automatic, ...skills.manual].some((item) => item.kind === "dangling-symlink") || exclusiveOwnershipViolations.length > 0 || secrets.repositoryEnvFiles.some((file) => file.tracked && !file.template && !file.fixture) || hasUnsafeCentralSecretPermissions(secrets))
|
|
7000
7468
|
return "unhealthy";
|
|
7001
7469
|
const missingBaseline = projectModel.baseline.some(
|
|
7002
7470
|
(technology) => !technology.detected && !hasBaselineException(projectModel, technology.id)
|
|
@@ -7403,8 +7871,8 @@ function isHost2(value) {
|
|
|
7403
7871
|
return value === "codex" || value === "claude-code" || value === "gemini-cli" || value === "antigravity";
|
|
7404
7872
|
}
|
|
7405
7873
|
function findPortfolioAgentAssetsDir(manifest) {
|
|
7406
|
-
const agentAssetsDir = manifest?.executionEngine?.path ?
|
|
7407
|
-
return agentAssetsDir &&
|
|
7874
|
+
const agentAssetsDir = manifest?.executionEngine?.path ? join34(manifest.executionEngine.path, "agent-assets") : void 0;
|
|
7875
|
+
return agentAssetsDir && existsSync33(join34(agentAssetsDir, "registry.json")) ? agentAssetsDir : void 0;
|
|
7408
7876
|
}
|
|
7409
7877
|
function printUsage5() {
|
|
7410
7878
|
console.error("Usage:");
|
|
@@ -7420,8 +7888,8 @@ function printUsage5() {
|
|
|
7420
7888
|
}
|
|
7421
7889
|
function readExistingAiHealthReport(outDir, portfolioId) {
|
|
7422
7890
|
if (!outDir) return void 0;
|
|
7423
|
-
const path =
|
|
7424
|
-
if (!
|
|
7891
|
+
const path = join34(outDir, "portfolio-ai-health.json");
|
|
7892
|
+
if (!existsSync33(path)) return void 0;
|
|
7425
7893
|
try {
|
|
7426
7894
|
const value = JSON.parse(readFileSync18(path, "utf8"));
|
|
7427
7895
|
if (!value || typeof value !== "object" || value.portfolioId !== portfolioId || !Array.isArray(value.repositories))
|
|
@@ -7433,8 +7901,8 @@ function readExistingAiHealthReport(outDir, portfolioId) {
|
|
|
7433
7901
|
}
|
|
7434
7902
|
|
|
7435
7903
|
// src/commands/sync.ts
|
|
7436
|
-
import { existsSync as
|
|
7437
|
-
import { join as
|
|
7904
|
+
import { existsSync as existsSync34, lstatSync as lstatSync14, readFileSync as readFileSync19, readlinkSync as readlinkSync4 } from "node:fs";
|
|
7905
|
+
import { join as join35 } from "node:path";
|
|
7438
7906
|
function runSync(args) {
|
|
7439
7907
|
const check = args.includes("--check");
|
|
7440
7908
|
if (!check) {
|
|
@@ -7462,7 +7930,7 @@ function runSync(args) {
|
|
|
7462
7930
|
console.log("pro-gov sync check");
|
|
7463
7931
|
console.log(`profile: ${profile}`);
|
|
7464
7932
|
for (const file of planStarterFiles(profile)) {
|
|
7465
|
-
const targetPath =
|
|
7933
|
+
const targetPath = join35(process.cwd(), file.targetPath);
|
|
7466
7934
|
const stat = safeLstat3(targetPath);
|
|
7467
7935
|
if (!stat) {
|
|
7468
7936
|
if (file.ownership === "optional-guardrail") continue;
|
|
@@ -7521,13 +7989,13 @@ function normalizeMarkdownTableCell(cell) {
|
|
|
7521
7989
|
}
|
|
7522
7990
|
function inferInstalledProfile(root) {
|
|
7523
7991
|
const installed = ["engineering-runtime", "doc-only"].filter(
|
|
7524
|
-
(profile) =>
|
|
7992
|
+
(profile) => existsSync34(join35(root, `docs/governance/agents-routing/${profile}-v1.1.md`))
|
|
7525
7993
|
);
|
|
7526
7994
|
return installed.length === 1 ? installed[0] : void 0;
|
|
7527
7995
|
}
|
|
7528
7996
|
function safeLstat3(path) {
|
|
7529
7997
|
try {
|
|
7530
|
-
return
|
|
7998
|
+
return lstatSync14(path);
|
|
7531
7999
|
} catch {
|
|
7532
8000
|
return void 0;
|
|
7533
8001
|
}
|
|
@@ -7565,7 +8033,8 @@ var COMMANDS = [
|
|
|
7565
8033
|
"lens audit check --dir <path> [--json]",
|
|
7566
8034
|
"init --profile <engineering-runtime|doc-only> <--dry-run|--apply>",
|
|
7567
8035
|
"sync --check [--profile <engineering-runtime|doc-only>]",
|
|
7568
|
-
"doctor"
|
|
8036
|
+
"package-doctor",
|
|
8037
|
+
"doctor (legacy alias for package-doctor)"
|
|
7569
8038
|
];
|
|
7570
8039
|
var [command, subcommand] = process.argv.slice(2);
|
|
7571
8040
|
process.exitCode = await main();
|
|
@@ -7581,6 +8050,7 @@ async function main() {
|
|
|
7581
8050
|
if (command === "host-lens") return runHostLens(process.argv.slice(3));
|
|
7582
8051
|
if (command === "init") return runInit(process.argv.slice(3));
|
|
7583
8052
|
if (command === "sync") return runSync(process.argv.slice(3));
|
|
8053
|
+
if (command === "package-doctor") return runPackageDoctor(process.argv.slice(3));
|
|
7584
8054
|
if (command === "doctor") return runDoctor(process.argv.slice(3));
|
|
7585
8055
|
console.error(`Unknown command: ${[command, subcommand].filter(Boolean).join(" ")}`);
|
|
7586
8056
|
printHelp();
|