@pieai/pro-gov 0.8.0 → 0.9.1
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 +856 -319
- 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 [];
|
|
@@ -211,12 +211,12 @@ function toUnixPath2(path) {
|
|
|
211
211
|
// src/asset-registry/loader.ts
|
|
212
212
|
import { createHash as createHash2 } from "node:crypto";
|
|
213
213
|
import { existsSync as existsSync5, readdirSync as readdirSync5, readFileSync as readFileSync3, statSync as statSync2 } from "node:fs";
|
|
214
|
-
import { dirname as dirname2, join as join5, relative as
|
|
214
|
+
import { dirname as dirname2, join as join5, relative as relative4 } from "node:path";
|
|
215
215
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
216
216
|
|
|
217
217
|
// src/asset-registry/registry.ts
|
|
218
|
-
import { existsSync as existsSync4, lstatSync, readdirSync as readdirSync4 } from "node:fs";
|
|
219
|
-
import { isAbsolute, join as join4, posix } from "node:path";
|
|
218
|
+
import { existsSync as existsSync4, lstatSync, readdirSync as readdirSync4, realpathSync } from "node:fs";
|
|
219
|
+
import { isAbsolute, join as join4, posix, relative as relative3, sep } from "node:path";
|
|
220
220
|
var supportedFamilies = /* @__PURE__ */ new Set([
|
|
221
221
|
"pie-skills",
|
|
222
222
|
"npx-skills",
|
|
@@ -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",
|
|
@@ -368,6 +427,13 @@ function validateAssetRegistry(registry, options = {}) {
|
|
|
368
427
|
path: asset.sourcePath,
|
|
369
428
|
message: `Asset source path does not exist: ${asset.sourcePath}`
|
|
370
429
|
});
|
|
430
|
+
} else if (!isWithinAgentAssetsDir(options.agentAssetsDir, sourceAbsolutePath)) {
|
|
431
|
+
issues.push({
|
|
432
|
+
type: "source-target-escape",
|
|
433
|
+
id: asset.id,
|
|
434
|
+
path: asset.sourcePath,
|
|
435
|
+
message: `Asset source resolves outside agent-assets: ${asset.sourcePath}`
|
|
436
|
+
});
|
|
371
437
|
} else if (asset.kind === "skill") {
|
|
372
438
|
if (asset.sourceKind === "local-pack") {
|
|
373
439
|
if (!isLocalSkillPack(sourceAbsolutePath)) {
|
|
@@ -424,6 +490,16 @@ function isSafeRegistrySourcePath(sourcePath) {
|
|
|
424
490
|
function normalizeRegistrySourcePath(sourcePath) {
|
|
425
491
|
return posix.normalize(sourcePath.replaceAll("\\", "/"));
|
|
426
492
|
}
|
|
493
|
+
function isWithinAgentAssetsDir(agentAssetsDir, sourceAbsolutePath) {
|
|
494
|
+
try {
|
|
495
|
+
const agentAssetsRealPath = realpathSync(agentAssetsDir);
|
|
496
|
+
const sourceRealPath = realpathSync(sourceAbsolutePath);
|
|
497
|
+
const relativePath = relative3(agentAssetsRealPath, sourceRealPath);
|
|
498
|
+
return relativePath === "" || relativePath !== ".." && !relativePath.startsWith(`..${sep}`) && !isAbsolute(relativePath);
|
|
499
|
+
} catch {
|
|
500
|
+
return false;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
427
503
|
function pathExistsEvenIfDanglingSymlink(path) {
|
|
428
504
|
try {
|
|
429
505
|
lstatSync(path);
|
|
@@ -434,6 +510,23 @@ function pathExistsEvenIfDanglingSymlink(path) {
|
|
|
434
510
|
}
|
|
435
511
|
|
|
436
512
|
// src/asset-registry/loader.ts
|
|
513
|
+
function createAgentAssetRegistryProvenance(registry, selectedAssetIds) {
|
|
514
|
+
const selectedIds = new Set(selectedAssetIds);
|
|
515
|
+
const selectedAssets = registry.assets.filter((asset) => selectedIds.has(asset.id));
|
|
516
|
+
const canonicalRegistry = canonicalizeValue({
|
|
517
|
+
schemaVersion: registry.schemaVersion,
|
|
518
|
+
assets: [...selectedAssets].sort(
|
|
519
|
+
(left, right) => left.id < right.id ? -1 : left.id > right.id ? 1 : 0
|
|
520
|
+
)
|
|
521
|
+
});
|
|
522
|
+
const hash = createHash2("sha256").update(JSON.stringify(canonicalRegistry)).digest("hex");
|
|
523
|
+
return {
|
|
524
|
+
schema: "agent-assets-registry",
|
|
525
|
+
version: registry.schemaVersion,
|
|
526
|
+
hash: `sha256:${hash}`,
|
|
527
|
+
assetCount: selectedAssets.length
|
|
528
|
+
};
|
|
529
|
+
}
|
|
437
530
|
function loadAgentAssetRegistry(options = {}) {
|
|
438
531
|
const agentAssetsDir = options.agentAssetsDir ?? findDefaultAgentAssetsDir();
|
|
439
532
|
const registryPath = join5(agentAssetsDir, "registry.json");
|
|
@@ -458,6 +551,7 @@ function createAgentAssetLockEntries(registry, agentAssetsDir, assetIds) {
|
|
|
458
551
|
return registry.assets.filter((asset) => !wantedIds || wantedIds.has(asset.id)).map((asset) => ({
|
|
459
552
|
id: asset.id,
|
|
460
553
|
sourcePath: asset.sourcePath,
|
|
554
|
+
delivery: asset.delivery ?? "symlink",
|
|
461
555
|
contentHash: hashAgentAssetContent(asset, agentAssetsDir)
|
|
462
556
|
})).sort((a, b) => a.id.localeCompare(b.id));
|
|
463
557
|
}
|
|
@@ -467,7 +561,7 @@ function hashAgentAssetContent(asset, agentAssetsDir) {
|
|
|
467
561
|
function hashAssetPathContent(sourceAbsolutePath) {
|
|
468
562
|
const hash = createHash2("sha256");
|
|
469
563
|
for (const filePath of listFiles3(sourceAbsolutePath)) {
|
|
470
|
-
const relativePath = toUnixPath3(
|
|
564
|
+
const relativePath = toUnixPath3(relative4(sourceAbsolutePath, filePath));
|
|
471
565
|
hash.update(relativePath);
|
|
472
566
|
hash.update("\0");
|
|
473
567
|
hash.update(readFileSync3(filePath));
|
|
@@ -475,6 +569,15 @@ function hashAssetPathContent(sourceAbsolutePath) {
|
|
|
475
569
|
}
|
|
476
570
|
return `sha256:${hash.digest("hex")}`;
|
|
477
571
|
}
|
|
572
|
+
function canonicalizeValue(value) {
|
|
573
|
+
if (Array.isArray(value)) return value.map((item) => canonicalizeValue(item));
|
|
574
|
+
if (value !== null && typeof value === "object") {
|
|
575
|
+
return Object.fromEntries(
|
|
576
|
+
Object.entries(value).filter(([, item]) => item !== void 0).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0).map(([key, item]) => [key, canonicalizeValue(item)])
|
|
577
|
+
);
|
|
578
|
+
}
|
|
579
|
+
return value;
|
|
580
|
+
}
|
|
478
581
|
function findDefaultAgentAssetsDir() {
|
|
479
582
|
const packageRoot2 = findPackageRoot(dirname2(fileURLToPath2(import.meta.url)));
|
|
480
583
|
const repoRoot = join5(packageRoot2, "..", "..");
|
|
@@ -620,20 +723,21 @@ function resolveSafePath(root, sourcePath) {
|
|
|
620
723
|
}
|
|
621
724
|
|
|
622
725
|
// src/asset-targets/apply.ts
|
|
726
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
623
727
|
import {
|
|
624
728
|
existsSync as existsSync8,
|
|
625
729
|
lstatSync as lstatSync3,
|
|
626
730
|
mkdirSync as mkdirSync2,
|
|
627
731
|
readlinkSync as readlinkSync2,
|
|
628
|
-
realpathSync as
|
|
732
|
+
realpathSync as realpathSync3,
|
|
629
733
|
symlinkSync,
|
|
630
734
|
unlinkSync,
|
|
631
735
|
writeFileSync
|
|
632
736
|
} from "node:fs";
|
|
633
|
-
import { dirname as dirname4, join as join8, relative as
|
|
737
|
+
import { dirname as dirname4, join as join8, relative as relative5, resolve as resolve2 } from "node:path";
|
|
634
738
|
|
|
635
739
|
// src/asset-targets/install-plan.ts
|
|
636
|
-
import { existsSync as existsSync7, lstatSync as lstatSync2, readFileSync as readFileSync4, readlinkSync, realpathSync } from "node:fs";
|
|
740
|
+
import { existsSync as existsSync7, lstatSync as lstatSync2, readFileSync as readFileSync4, readlinkSync, realpathSync as realpathSync2, statSync as statSync3 } from "node:fs";
|
|
637
741
|
import { basename, dirname as dirname3, join as join7, resolve } from "node:path";
|
|
638
742
|
function createAssetInstallPlan(options) {
|
|
639
743
|
const placement = options.placement ?? "registry";
|
|
@@ -653,9 +757,9 @@ function createAssetInstallPlan(options) {
|
|
|
653
757
|
options.agentAssetsDir,
|
|
654
758
|
assetIds
|
|
655
759
|
);
|
|
760
|
+
const registryProvenance = createAgentAssetRegistryProvenance(options.registry, assetIds);
|
|
656
761
|
const managedLock = readManagedLock(options.targetDir);
|
|
657
762
|
const managedEntries = managedLock.entries;
|
|
658
|
-
const managedTargets = new Set(managedEntries.map((entry) => entry.targetPath));
|
|
659
763
|
const legacyAdoptions = createLegacyClaudeAdoptions({
|
|
660
764
|
targetDir: options.targetDir,
|
|
661
765
|
agentAssetsDir: options.agentAssetsDir,
|
|
@@ -671,7 +775,7 @@ function createAssetInstallPlan(options) {
|
|
|
671
775
|
options.targetDir,
|
|
672
776
|
options.host,
|
|
673
777
|
placement,
|
|
674
|
-
|
|
778
|
+
managedEntries
|
|
675
779
|
)
|
|
676
780
|
);
|
|
677
781
|
const manifest = {
|
|
@@ -686,6 +790,7 @@ function createAssetInstallPlan(options) {
|
|
|
686
790
|
host: options.host,
|
|
687
791
|
placement,
|
|
688
792
|
bundleIds: [...options.bundleIds],
|
|
793
|
+
registryProvenance,
|
|
689
794
|
assets: lockEntries.map((entry) => {
|
|
690
795
|
const action = assetActions.find(
|
|
691
796
|
(candidate) => "assetId" in candidate && candidate.assetId === entry.id
|
|
@@ -747,7 +852,7 @@ function resolveAssetIds(bundleIds, explicitAssetIds, bundlesById) {
|
|
|
747
852
|
}
|
|
748
853
|
return [...ids].sort();
|
|
749
854
|
}
|
|
750
|
-
function createAssetAction(asset, agentAssetsDir, targetDir, host, placement,
|
|
855
|
+
function createAssetAction(asset, agentAssetsDir, targetDir, host, placement, managedEntries) {
|
|
751
856
|
if (asset.kind === "skill" && asset.defaultScope === "user") {
|
|
752
857
|
throw new Error(
|
|
753
858
|
`User-scoped asset ${asset.id} must be linked at the user level, not installed into a project target.`
|
|
@@ -757,9 +862,23 @@ function createAssetAction(asset, agentAssetsDir, targetDir, host, placement, ma
|
|
|
757
862
|
const targetPath = resolveHostTargetPath(asset, host, placement);
|
|
758
863
|
const targetAbsolutePath = join7(targetDir, targetPath);
|
|
759
864
|
const targetExists = pathExistsEvenIfDanglingSymlink2(targetAbsolutePath);
|
|
865
|
+
const managedEntry = managedEntries.find(
|
|
866
|
+
(entry) => entry.id === asset.id && entry.targetPath === targetPath
|
|
867
|
+
);
|
|
868
|
+
if ((asset.delivery ?? "symlink") === "snapshot") {
|
|
869
|
+
return createSnapshotAction({
|
|
870
|
+
asset,
|
|
871
|
+
agentAssetsDir,
|
|
872
|
+
sourcePath,
|
|
873
|
+
targetPath,
|
|
874
|
+
targetAbsolutePath,
|
|
875
|
+
targetExists,
|
|
876
|
+
managedEntry
|
|
877
|
+
});
|
|
878
|
+
}
|
|
760
879
|
if (targetExists) {
|
|
761
880
|
const stats = lstatSync2(targetAbsolutePath);
|
|
762
|
-
if (stats.isSymbolicLink() &&
|
|
881
|
+
if (stats.isSymbolicLink() && managedEntry && (managedEntry.delivery ?? "symlink") === "symlink") {
|
|
763
882
|
return {
|
|
764
883
|
type: "update-symlink",
|
|
765
884
|
assetId: asset.id,
|
|
@@ -767,7 +886,10 @@ function createAssetAction(asset, agentAssetsDir, targetDir, host, placement, ma
|
|
|
767
886
|
targetPath
|
|
768
887
|
};
|
|
769
888
|
}
|
|
770
|
-
if (
|
|
889
|
+
if (managedEntry && managedEntry.delivery === "snapshot") {
|
|
890
|
+
throw new Error(`Refusing to replace managed snapshot with a symlink: ${targetPath}`);
|
|
891
|
+
}
|
|
892
|
+
if (stats.isSymbolicLink() && existsSync7(targetAbsolutePath) && realpathSync2(targetAbsolutePath) === realpathSync2(sourcePath)) {
|
|
771
893
|
return {
|
|
772
894
|
type: "adopt-existing-symlink",
|
|
773
895
|
assetId: asset.id,
|
|
@@ -784,7 +906,78 @@ function createAssetAction(asset, agentAssetsDir, targetDir, host, placement, ma
|
|
|
784
906
|
targetPath
|
|
785
907
|
};
|
|
786
908
|
}
|
|
909
|
+
function createSnapshotAction(options) {
|
|
910
|
+
if (options.asset.kind !== "rule" || !isValidSnapshotProjectTargetPath(options.asset.projectTargetPath)) {
|
|
911
|
+
throw new Error(
|
|
912
|
+
`Snapshot delivery requires a rule target under docs/policy/shared-rules/: ${options.asset.id}`
|
|
913
|
+
);
|
|
914
|
+
}
|
|
915
|
+
if (!statSync3(options.sourcePath).isFile()) {
|
|
916
|
+
throw new Error(`Snapshot source must be a regular file: ${options.asset.sourcePath}`);
|
|
917
|
+
}
|
|
918
|
+
const content = readFileSync4(options.sourcePath);
|
|
919
|
+
const contentBase64 = content.toString("base64");
|
|
920
|
+
const contentHash = hashAgentAssetContent(options.asset, options.agentAssetsDir);
|
|
921
|
+
const managedDelivery = options.managedEntry?.delivery ?? "symlink";
|
|
922
|
+
if (!options.targetExists) {
|
|
923
|
+
return {
|
|
924
|
+
type: "snapshot",
|
|
925
|
+
assetId: options.asset.id,
|
|
926
|
+
targetPath: options.targetPath,
|
|
927
|
+
contentBase64,
|
|
928
|
+
contentHash
|
|
929
|
+
};
|
|
930
|
+
}
|
|
931
|
+
const stats = lstatSync2(options.targetAbsolutePath);
|
|
932
|
+
if (stats.isSymbolicLink()) {
|
|
933
|
+
if (existsSync7(options.targetAbsolutePath) && realpathSync2(options.targetAbsolutePath) === realpathSync2(options.sourcePath) && hashAssetPathContent(options.targetAbsolutePath) === contentHash) {
|
|
934
|
+
return {
|
|
935
|
+
type: "migrate-symlink-to-snapshot",
|
|
936
|
+
assetId: options.asset.id,
|
|
937
|
+
sourcePath: options.sourcePath,
|
|
938
|
+
targetPath: options.targetPath,
|
|
939
|
+
contentBase64,
|
|
940
|
+
contentHash
|
|
941
|
+
};
|
|
942
|
+
}
|
|
943
|
+
throw new Error(`Refusing to overwrite unmanaged target: ${options.targetPath}`);
|
|
944
|
+
}
|
|
945
|
+
if (!stats.isFile()) {
|
|
946
|
+
throw new Error(`Refusing to overwrite unmanaged target: ${options.targetPath}`);
|
|
947
|
+
}
|
|
948
|
+
const currentTargetHash = hashAssetPathContent(options.targetAbsolutePath);
|
|
949
|
+
if (currentTargetHash === contentHash) {
|
|
950
|
+
return {
|
|
951
|
+
type: "adopt-snapshot",
|
|
952
|
+
assetId: options.asset.id,
|
|
953
|
+
targetPath: options.targetPath,
|
|
954
|
+
contentHash
|
|
955
|
+
};
|
|
956
|
+
}
|
|
957
|
+
if (managedDelivery === "snapshot" && options.managedEntry?.contentHash) {
|
|
958
|
+
if (currentTargetHash !== options.managedEntry.contentHash) {
|
|
959
|
+
throw new Error(`Refusing to overwrite locally drifted snapshot: ${options.targetPath}`);
|
|
960
|
+
}
|
|
961
|
+
return {
|
|
962
|
+
type: "update-snapshot",
|
|
963
|
+
assetId: options.asset.id,
|
|
964
|
+
targetPath: options.targetPath,
|
|
965
|
+
contentBase64,
|
|
966
|
+
contentHash,
|
|
967
|
+
expectedContentHash: options.managedEntry.contentHash
|
|
968
|
+
};
|
|
969
|
+
}
|
|
970
|
+
throw new Error(`Refusing to overwrite unmanaged target: ${options.targetPath}`);
|
|
971
|
+
}
|
|
787
972
|
function resolveHostTargetPath(asset, _host, placement) {
|
|
973
|
+
if (asset.projectTargetPath !== void 0) {
|
|
974
|
+
if (!isValidAssetProjectTargetPath(asset.kind, asset.projectTargetPath)) {
|
|
975
|
+
throw new Error(
|
|
976
|
+
`Invalid project target path for asset ${asset.id}: ${asset.projectTargetPath}`
|
|
977
|
+
);
|
|
978
|
+
}
|
|
979
|
+
return asset.projectTargetPath;
|
|
980
|
+
}
|
|
788
981
|
if (asset.kind === "skill") {
|
|
789
982
|
const effectivePlacement = resolveSkillPlacement(asset, placement);
|
|
790
983
|
if (effectivePlacement === "manual") {
|
|
@@ -818,7 +1011,7 @@ function readManagedLock(targetDir) {
|
|
|
818
1011
|
return {
|
|
819
1012
|
host: typeof lockfile.host === "string" ? lockfile.host : void 0,
|
|
820
1013
|
entries: (lockfile.assets ?? []).filter(
|
|
821
|
-
(entry) => typeof entry.id === "string" && typeof entry.sourcePath === "string" && typeof entry.targetPath === "string"
|
|
1014
|
+
(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
1015
|
)
|
|
823
1016
|
};
|
|
824
1017
|
} catch {
|
|
@@ -855,7 +1048,7 @@ function createLegacyClaudeAdoptions(options) {
|
|
|
855
1048
|
const targetAbsolutePath = join7(options.targetDir, targetPath);
|
|
856
1049
|
const compatibilityRootPath = join7(options.targetDir, ".claude/skills");
|
|
857
1050
|
const canonicalRootPath = join7(options.targetDir, ".agents/skills");
|
|
858
|
-
if (!lstatSync2(canonicalRootPath).isDirectory() || !lstatSync2(compatibilityRootPath).isSymbolicLink() || readlinkSync(compatibilityRootPath) !== "../.agents/skills" ||
|
|
1051
|
+
if (!lstatSync2(canonicalRootPath).isDirectory() || !lstatSync2(compatibilityRootPath).isSymbolicLink() || readlinkSync(compatibilityRootPath) !== "../.agents/skills" || realpathSync2(compatibilityRootPath) !== realpathSync2(canonicalRootPath)) {
|
|
859
1052
|
throw new Error(
|
|
860
1053
|
`Legacy Claude compatibility root is not the exact canonical alias for ${entry.id}.`
|
|
861
1054
|
);
|
|
@@ -868,7 +1061,7 @@ function createLegacyClaudeAdoptions(options) {
|
|
|
868
1061
|
const legacyAbsolutePath = join7(options.targetDir, entry.targetPath);
|
|
869
1062
|
const legacyStat = lstatSync2(legacyAbsolutePath);
|
|
870
1063
|
const expectedSourcePath = join7(options.agentAssetsDir, entry.sourcePath);
|
|
871
|
-
if (!targetStat.isSymbolicLink() || !legacyStat.isSymbolicLink() || targetStat.dev !== legacyStat.dev || targetStat.ino !== legacyStat.ino ||
|
|
1064
|
+
if (!targetStat.isSymbolicLink() || !legacyStat.isSymbolicLink() || targetStat.dev !== legacyStat.dev || targetStat.ino !== legacyStat.ino || realpathSync2(targetAbsolutePath) !== realpathSync2(expectedSourcePath)) {
|
|
872
1065
|
throw new Error(`Legacy Claude skill target cannot be safely adopted: ${entry.targetPath}`);
|
|
873
1066
|
}
|
|
874
1067
|
const action = {
|
|
@@ -900,6 +1093,29 @@ function createRemovalActions(targetDir, agentAssetsDir, managedEntries, expecte
|
|
|
900
1093
|
const targetAbsolutePath = join7(targetDir, entry.targetPath);
|
|
901
1094
|
if (!pathExistsEvenIfDanglingSymlink2(targetAbsolutePath)) continue;
|
|
902
1095
|
const stats = lstatSync2(targetAbsolutePath);
|
|
1096
|
+
if ((entry.delivery ?? "symlink") === "snapshot") {
|
|
1097
|
+
if (!isValidSnapshotProjectTargetPath(entry.targetPath)) {
|
|
1098
|
+
throw new Error(
|
|
1099
|
+
`Refusing to remove snapshot outside live shared rules: ${entry.targetPath}`
|
|
1100
|
+
);
|
|
1101
|
+
}
|
|
1102
|
+
if (!stats.isFile() || !entry.contentHash) {
|
|
1103
|
+
throw new Error(
|
|
1104
|
+
`Refusing to remove managed snapshot without its locked file hash: ${entry.targetPath}`
|
|
1105
|
+
);
|
|
1106
|
+
}
|
|
1107
|
+
const currentTargetHash = hashAssetPathContent(targetAbsolutePath);
|
|
1108
|
+
if (currentTargetHash !== entry.contentHash) {
|
|
1109
|
+
throw new Error(`Refusing to remove locally drifted managed snapshot: ${entry.targetPath}`);
|
|
1110
|
+
}
|
|
1111
|
+
actions.push({
|
|
1112
|
+
type: "remove-snapshot",
|
|
1113
|
+
assetId: entry.id,
|
|
1114
|
+
targetPath: entry.targetPath,
|
|
1115
|
+
expectedContentHash: entry.contentHash
|
|
1116
|
+
});
|
|
1117
|
+
continue;
|
|
1118
|
+
}
|
|
903
1119
|
if (!stats.isSymbolicLink()) {
|
|
904
1120
|
throw new Error(
|
|
905
1121
|
`Refusing to remove path that is no longer a managed symlink: ${entry.targetPath}`
|
|
@@ -922,12 +1138,12 @@ function createRemovalActions(targetDir, agentAssetsDir, managedEntries, expecte
|
|
|
922
1138
|
return actions.sort((a, b) => a.targetPath.localeCompare(b.targetPath));
|
|
923
1139
|
}
|
|
924
1140
|
function isManagedAssetTargetPath(path) {
|
|
925
|
-
return /^(?:\.agents\/(?:skills|manual-skills)|\.pro-gov\/agent-assets\/(?:rules|commands))\/[
|
|
1141
|
+
return /^(?:(?:\.agents\/(?:skills|manual-skills)|\.pro-gov\/agent-assets\/(?:rules|commands))\/[^./\\][^/\\]*|docs\/policy\/shared-rules\/[^./\\][^/\\]*\.md)$/.test(
|
|
926
1142
|
path
|
|
927
1143
|
);
|
|
928
1144
|
}
|
|
929
1145
|
function isLegacyClaudeSkillTargetPath(path) {
|
|
930
|
-
return /^\.claude\/skills\/[
|
|
1146
|
+
return /^\.claude\/skills\/[^./\\][^/\\]*$/.test(path);
|
|
931
1147
|
}
|
|
932
1148
|
function pathExistsEvenIfDanglingSymlink2(path) {
|
|
933
1149
|
try {
|
|
@@ -944,6 +1160,9 @@ function applyAssetInstallPlan(plan) {
|
|
|
944
1160
|
for (const action of plan.actions) {
|
|
945
1161
|
if (action.type === "adopt-symlink") validateAdoptedSymlink(plan.targetDir, action);
|
|
946
1162
|
if (action.type === "adopt-existing-symlink") validateExistingSymlink(plan.targetDir, action);
|
|
1163
|
+
if (action.type === "snapshot" || action.type === "update-snapshot" || action.type === "adopt-snapshot" || action.type === "migrate-symlink-to-snapshot" || action.type === "remove-snapshot") {
|
|
1164
|
+
validateSnapshotAction(plan.targetDir, action);
|
|
1165
|
+
}
|
|
947
1166
|
}
|
|
948
1167
|
for (const action of plan.actions) {
|
|
949
1168
|
applyAction(plan.targetDir, action);
|
|
@@ -958,6 +1177,10 @@ function applyAction(targetDir, action) {
|
|
|
958
1177
|
return;
|
|
959
1178
|
}
|
|
960
1179
|
if (action.type === "adopt-symlink" || action.type === "adopt-existing-symlink") return;
|
|
1180
|
+
if (action.type === "snapshot" || action.type === "update-snapshot" || action.type === "adopt-snapshot" || action.type === "migrate-symlink-to-snapshot" || action.type === "remove-snapshot") {
|
|
1181
|
+
applySnapshotAction(targetDir, action);
|
|
1182
|
+
return;
|
|
1183
|
+
}
|
|
961
1184
|
if (action.type === "create-dir") {
|
|
962
1185
|
mkdirSync2(targetAbsolutePath, { recursive: true });
|
|
963
1186
|
return;
|
|
@@ -969,7 +1192,7 @@ function applyAction(targetDir, action) {
|
|
|
969
1192
|
}
|
|
970
1193
|
mkdirSync2(dirname4(targetAbsolutePath), { recursive: true });
|
|
971
1194
|
const sourceAbsolutePath = resolve2(action.sourcePath);
|
|
972
|
-
const symlinkTarget =
|
|
1195
|
+
const symlinkTarget = relative5(realpathSync3(dirname4(targetAbsolutePath)), realpathSync3(sourceAbsolutePath)) || ".";
|
|
973
1196
|
if (action.type === "symlink") {
|
|
974
1197
|
if (pathExistsEvenIfDanglingSymlink3(targetAbsolutePath)) {
|
|
975
1198
|
throw new Error(`Refusing to overwrite unmanaged target: ${action.targetPath}`);
|
|
@@ -993,10 +1216,71 @@ function validateExistingSymlink(targetDir, action) {
|
|
|
993
1216
|
throw new Error(`Refusing unsafe existing symlink adoption: ${action.targetPath}`);
|
|
994
1217
|
}
|
|
995
1218
|
const targetAbsolutePath = join8(targetDir, action.targetPath);
|
|
996
|
-
if (!lstatSync3(targetAbsolutePath).isSymbolicLink() ||
|
|
1219
|
+
if (!lstatSync3(targetAbsolutePath).isSymbolicLink() || realpathSync3(targetAbsolutePath) !== realpathSync3(action.sourcePath)) {
|
|
997
1220
|
throw new Error(`Existing skill symlink changed before apply: ${action.targetPath}`);
|
|
998
1221
|
}
|
|
999
1222
|
}
|
|
1223
|
+
function validateSnapshotAction(targetDir, action) {
|
|
1224
|
+
if (!isValidSnapshotProjectTargetPath(action.targetPath)) {
|
|
1225
|
+
throw new Error(`Refusing unsafe snapshot target: ${action.targetPath}`);
|
|
1226
|
+
}
|
|
1227
|
+
if ("contentBase64" in action && hashSnapshotBytes(Buffer.from(action.contentBase64, "base64")) !== action.contentHash) {
|
|
1228
|
+
throw new Error(`Snapshot content hash is invalid: ${action.targetPath}`);
|
|
1229
|
+
}
|
|
1230
|
+
const targetAbsolutePath = join8(targetDir, action.targetPath);
|
|
1231
|
+
if (action.type === "snapshot") {
|
|
1232
|
+
if (pathExistsEvenIfDanglingSymlink3(targetAbsolutePath)) {
|
|
1233
|
+
throw new Error(`Snapshot target changed before apply: ${action.targetPath}`);
|
|
1234
|
+
}
|
|
1235
|
+
return;
|
|
1236
|
+
}
|
|
1237
|
+
if (action.type === "update-snapshot") {
|
|
1238
|
+
assertRegularSnapshotHash(targetAbsolutePath, action.expectedContentHash, action.targetPath);
|
|
1239
|
+
return;
|
|
1240
|
+
}
|
|
1241
|
+
if (action.type === "adopt-snapshot") {
|
|
1242
|
+
assertRegularSnapshotHash(targetAbsolutePath, action.contentHash, action.targetPath);
|
|
1243
|
+
return;
|
|
1244
|
+
}
|
|
1245
|
+
if (action.type === "migrate-symlink-to-snapshot") {
|
|
1246
|
+
if (!pathExistsEvenIfDanglingSymlink3(targetAbsolutePath) || !lstatSync3(targetAbsolutePath).isSymbolicLink() || !existsSync8(targetAbsolutePath) || realpathSync3(targetAbsolutePath) !== realpathSync3(action.sourcePath) || hashAssetPathContent(targetAbsolutePath) !== action.contentHash) {
|
|
1247
|
+
throw new Error(`Snapshot symlink changed before apply: ${action.targetPath}`);
|
|
1248
|
+
}
|
|
1249
|
+
return;
|
|
1250
|
+
}
|
|
1251
|
+
assertRegularSnapshotHash(targetAbsolutePath, action.expectedContentHash, action.targetPath);
|
|
1252
|
+
}
|
|
1253
|
+
function applySnapshotAction(targetDir, action) {
|
|
1254
|
+
const targetAbsolutePath = join8(targetDir, action.targetPath);
|
|
1255
|
+
if (action.type === "adopt-snapshot") return;
|
|
1256
|
+
if (action.type === "remove-snapshot") {
|
|
1257
|
+
unlinkSync(targetAbsolutePath);
|
|
1258
|
+
return;
|
|
1259
|
+
}
|
|
1260
|
+
if (action.type === "migrate-symlink-to-snapshot") unlinkSync(targetAbsolutePath);
|
|
1261
|
+
mkdirSync2(dirname4(targetAbsolutePath), { recursive: true });
|
|
1262
|
+
if (action.type === "snapshot" && pathExistsEvenIfDanglingSymlink3(targetAbsolutePath)) {
|
|
1263
|
+
throw new Error(`Refusing to overwrite unmanaged target: ${action.targetPath}`);
|
|
1264
|
+
}
|
|
1265
|
+
writeFileSync(targetAbsolutePath, Buffer.from(action.contentBase64, "base64"));
|
|
1266
|
+
}
|
|
1267
|
+
function assertRegularSnapshotHash(targetAbsolutePath, expectedHash, targetPath) {
|
|
1268
|
+
if (!pathExistsEvenIfDanglingSymlink3(targetAbsolutePath)) {
|
|
1269
|
+
throw new Error(`Snapshot target changed before apply: ${targetPath}`);
|
|
1270
|
+
}
|
|
1271
|
+
const stats = lstatSync3(targetAbsolutePath);
|
|
1272
|
+
if (!stats.isFile() || hashAssetPathContent(targetAbsolutePath) !== expectedHash) {
|
|
1273
|
+
throw new Error(`Snapshot target hash changed before apply: ${targetPath}`);
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
function hashSnapshotBytes(content) {
|
|
1277
|
+
const hash = createHash3("sha256");
|
|
1278
|
+
hash.update("");
|
|
1279
|
+
hash.update("\0");
|
|
1280
|
+
hash.update(content);
|
|
1281
|
+
hash.update("\0");
|
|
1282
|
+
return `sha256:${hash.digest("hex")}`;
|
|
1283
|
+
}
|
|
1000
1284
|
function validateAdoptedSymlink(targetDir, action) {
|
|
1001
1285
|
if (!isManagedAssetTargetPath(action.targetPath) || !isLegacyClaudeSkillTargetPath(action.legacyTargetPath) || action.compatibilityRootPath !== ".claude/skills" || action.expectedCompatibilityRawTarget !== "../.agents/skills") {
|
|
1002
1286
|
throw new Error(`Refusing unsafe legacy Claude adoption: ${action.legacyTargetPath}`);
|
|
@@ -1005,14 +1289,14 @@ function validateAdoptedSymlink(targetDir, action) {
|
|
|
1005
1289
|
const compatibilityRootPath = join8(targetDir, action.compatibilityRootPath);
|
|
1006
1290
|
const targetAbsolutePath = join8(targetDir, action.targetPath);
|
|
1007
1291
|
const legacyAbsolutePath = join8(targetDir, action.legacyTargetPath);
|
|
1008
|
-
if (!lstatSync3(canonicalRootPath).isDirectory() || !lstatSync3(compatibilityRootPath).isSymbolicLink() || readlinkSync2(compatibilityRootPath) !== action.expectedCompatibilityRawTarget ||
|
|
1292
|
+
if (!lstatSync3(canonicalRootPath).isDirectory() || !lstatSync3(compatibilityRootPath).isSymbolicLink() || readlinkSync2(compatibilityRootPath) !== action.expectedCompatibilityRawTarget || realpathSync3(compatibilityRootPath) !== realpathSync3(canonicalRootPath)) {
|
|
1009
1293
|
throw new Error(
|
|
1010
1294
|
`Legacy Claude compatibility alias changed before apply: ${action.compatibilityRootPath}`
|
|
1011
1295
|
);
|
|
1012
1296
|
}
|
|
1013
1297
|
const targetStat = lstatSync3(targetAbsolutePath);
|
|
1014
1298
|
const legacyStat = lstatSync3(legacyAbsolutePath);
|
|
1015
|
-
if (!targetStat.isSymbolicLink() || !legacyStat.isSymbolicLink() || targetStat.dev !== legacyStat.dev || targetStat.ino !== legacyStat.ino || targetStat.dev !== action.expectedDevice || targetStat.ino !== action.expectedInode ||
|
|
1299
|
+
if (!targetStat.isSymbolicLink() || !legacyStat.isSymbolicLink() || targetStat.dev !== legacyStat.dev || targetStat.ino !== legacyStat.ino || targetStat.dev !== action.expectedDevice || targetStat.ino !== action.expectedInode || realpathSync3(targetAbsolutePath) !== realpathSync3(action.sourcePath)) {
|
|
1016
1300
|
throw new Error(`Legacy Claude skill target changed before apply: ${action.targetPath}`);
|
|
1017
1301
|
}
|
|
1018
1302
|
}
|
|
@@ -1045,7 +1329,7 @@ function pathExistsEvenIfDanglingSymlink3(path) {
|
|
|
1045
1329
|
}
|
|
1046
1330
|
|
|
1047
1331
|
// src/asset-targets/check.ts
|
|
1048
|
-
import { existsSync as existsSync9, lstatSync as lstatSync4, readFileSync as readFileSync5 } from "node:fs";
|
|
1332
|
+
import { existsSync as existsSync9, lstatSync as lstatSync4, readFileSync as readFileSync5, realpathSync as realpathSync4 } from "node:fs";
|
|
1049
1333
|
import { join as join9 } from "node:path";
|
|
1050
1334
|
function checkInstalledAssets(options) {
|
|
1051
1335
|
const lockfilePath = join9(options.targetDir, ".pro-gov/assets.lock.json");
|
|
@@ -1064,10 +1348,54 @@ function checkInstalledAssets(options) {
|
|
|
1064
1348
|
const lockfile = JSON.parse(readFileSync5(lockfilePath, "utf8"));
|
|
1065
1349
|
const issues = [];
|
|
1066
1350
|
const strictRegistry = options.strictRegistry ?? false;
|
|
1351
|
+
const selectedAssetIds = (lockfile.assets ?? []).map((entry) => entry.id);
|
|
1352
|
+
const hasRegistryProvenance2 = Object.prototype.hasOwnProperty.call(
|
|
1353
|
+
lockfile,
|
|
1354
|
+
"registryProvenance"
|
|
1355
|
+
);
|
|
1356
|
+
const registryProvenanceMismatch = strictRegistry && hasRegistryProvenance2 && !matchesRegistryProvenance(
|
|
1357
|
+
lockfile.registryProvenance,
|
|
1358
|
+
createAgentAssetRegistryProvenance(options.registry, selectedAssetIds)
|
|
1359
|
+
);
|
|
1360
|
+
if (registryProvenanceMismatch) {
|
|
1361
|
+
issues.push({
|
|
1362
|
+
type: "registry-provenance-mismatch",
|
|
1363
|
+
message: "Asset lock registry provenance does not match the registry available to this checker; registry-dependent checks were skipped."
|
|
1364
|
+
});
|
|
1365
|
+
}
|
|
1067
1366
|
for (const entry of lockfile.assets ?? []) {
|
|
1367
|
+
if (!isManagedAssetTargetPath(entry.targetPath) && !isLegacyClaudeSkillTargetPath(entry.targetPath)) {
|
|
1368
|
+
issues.push({
|
|
1369
|
+
type: "unsafe-target-path",
|
|
1370
|
+
id: entry.id,
|
|
1371
|
+
targetPath: entry.targetPath,
|
|
1372
|
+
message: `Managed asset target is outside supported roots: ${entry.targetPath}`
|
|
1373
|
+
});
|
|
1374
|
+
continue;
|
|
1375
|
+
}
|
|
1068
1376
|
const asset = registryById.get(entry.id);
|
|
1069
1377
|
const targetAbsolutePath = join9(options.targetDir, entry.targetPath);
|
|
1070
|
-
|
|
1378
|
+
const delivery = entry.delivery ?? "symlink";
|
|
1379
|
+
const portableDeferredSkill = delivery === "symlink" && !strictRegistry && isProjectSkillTarget(entry.targetPath);
|
|
1380
|
+
if (delivery !== "symlink" && delivery !== "snapshot") {
|
|
1381
|
+
issues.push({
|
|
1382
|
+
type: "unsupported-delivery",
|
|
1383
|
+
id: entry.id,
|
|
1384
|
+
targetPath: entry.targetPath,
|
|
1385
|
+
message: `Managed asset delivery is unsupported: ${delivery}`
|
|
1386
|
+
});
|
|
1387
|
+
continue;
|
|
1388
|
+
}
|
|
1389
|
+
if (delivery === "snapshot" && !isValidSnapshotProjectTargetPath(entry.targetPath)) {
|
|
1390
|
+
issues.push({
|
|
1391
|
+
type: "unsafe-target-path",
|
|
1392
|
+
id: entry.id,
|
|
1393
|
+
targetPath: entry.targetPath,
|
|
1394
|
+
message: `Managed snapshot target is outside live shared rules: ${entry.targetPath}`
|
|
1395
|
+
});
|
|
1396
|
+
continue;
|
|
1397
|
+
}
|
|
1398
|
+
if (!asset && strictRegistry && !registryProvenanceMismatch) {
|
|
1071
1399
|
issues.push({
|
|
1072
1400
|
type: "unknown-asset",
|
|
1073
1401
|
id: entry.id,
|
|
@@ -1075,7 +1403,7 @@ function checkInstalledAssets(options) {
|
|
|
1075
1403
|
message: `Lockfile references unknown asset: ${entry.id}`
|
|
1076
1404
|
});
|
|
1077
1405
|
}
|
|
1078
|
-
if (asset?.kind === "skill" && asset.defaultScope === "user") {
|
|
1406
|
+
if (!registryProvenanceMismatch && asset?.kind === "skill" && asset.defaultScope === "user") {
|
|
1079
1407
|
issues.push({
|
|
1080
1408
|
type: "user-scoped-asset-in-project-lock",
|
|
1081
1409
|
id: entry.id,
|
|
@@ -1083,7 +1411,7 @@ function checkInstalledAssets(options) {
|
|
|
1083
1411
|
message: `User-scoped skill is still locked into this project; move it to the user skill roots: ${entry.id}`
|
|
1084
1412
|
});
|
|
1085
1413
|
}
|
|
1086
|
-
if (asset) {
|
|
1414
|
+
if (asset && !registryProvenanceMismatch) {
|
|
1087
1415
|
const hostFolderIssue = checkHostFolder(
|
|
1088
1416
|
lockfile.host,
|
|
1089
1417
|
asset.kind,
|
|
@@ -1099,6 +1427,7 @@ function checkInstalledAssets(options) {
|
|
|
1099
1427
|
}
|
|
1100
1428
|
}
|
|
1101
1429
|
if (!pathExistsEvenIfDanglingSymlink4(targetAbsolutePath)) {
|
|
1430
|
+
if (portableDeferredSkill) continue;
|
|
1102
1431
|
issues.push({
|
|
1103
1432
|
type: "missing-target",
|
|
1104
1433
|
id: entry.id,
|
|
@@ -1108,7 +1437,16 @@ function checkInstalledAssets(options) {
|
|
|
1108
1437
|
continue;
|
|
1109
1438
|
}
|
|
1110
1439
|
const targetStats = lstatSync4(targetAbsolutePath);
|
|
1111
|
-
if (!targetStats.
|
|
1440
|
+
if (delivery === "snapshot" && !targetStats.isFile()) {
|
|
1441
|
+
issues.push({
|
|
1442
|
+
type: "snapshot-not-regular-file",
|
|
1443
|
+
id: entry.id,
|
|
1444
|
+
targetPath: entry.targetPath,
|
|
1445
|
+
message: `Managed snapshot is not a regular file: ${entry.targetPath}`
|
|
1446
|
+
});
|
|
1447
|
+
continue;
|
|
1448
|
+
}
|
|
1449
|
+
if (delivery === "symlink" && !targetStats.isSymbolicLink()) {
|
|
1112
1450
|
issues.push({
|
|
1113
1451
|
type: "unmanaged-conflict",
|
|
1114
1452
|
id: entry.id,
|
|
@@ -1117,7 +1455,8 @@ function checkInstalledAssets(options) {
|
|
|
1117
1455
|
});
|
|
1118
1456
|
continue;
|
|
1119
1457
|
}
|
|
1120
|
-
if (!existsSync9(targetAbsolutePath)) {
|
|
1458
|
+
if (delivery === "symlink" && !existsSync9(targetAbsolutePath)) {
|
|
1459
|
+
if (portableDeferredSkill) continue;
|
|
1121
1460
|
issues.push({
|
|
1122
1461
|
type: "dangling-symlink",
|
|
1123
1462
|
id: entry.id,
|
|
@@ -1136,7 +1475,7 @@ function checkInstalledAssets(options) {
|
|
|
1136
1475
|
message: `Managed asset hash drifted: ${entry.id}`
|
|
1137
1476
|
});
|
|
1138
1477
|
}
|
|
1139
|
-
if (!asset || !strictRegistry) continue;
|
|
1478
|
+
if (!asset || !strictRegistry || registryProvenanceMismatch) continue;
|
|
1140
1479
|
const sourceAbsolutePath = join9(options.agentAssetsDir, asset.sourcePath);
|
|
1141
1480
|
if (!existsSync9(sourceAbsolutePath)) {
|
|
1142
1481
|
issues.push({
|
|
@@ -1147,8 +1486,20 @@ function checkInstalledAssets(options) {
|
|
|
1147
1486
|
});
|
|
1148
1487
|
continue;
|
|
1149
1488
|
}
|
|
1489
|
+
if (delivery === "symlink") {
|
|
1490
|
+
const expectedSource = realpathSync4(sourceAbsolutePath);
|
|
1491
|
+
const actualSource = realpathSync4(targetAbsolutePath);
|
|
1492
|
+
if (expectedSource !== actualSource) {
|
|
1493
|
+
issues.push({
|
|
1494
|
+
type: "symlink-source-drift",
|
|
1495
|
+
id: entry.id,
|
|
1496
|
+
targetPath: entry.targetPath,
|
|
1497
|
+
message: `Managed symlink does not resolve to its registered source: ${entry.targetPath}`
|
|
1498
|
+
});
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1150
1501
|
const currentSourceHash = hashAgentAssetContent(asset, options.agentAssetsDir);
|
|
1151
|
-
if (targetHashMatchesLock && currentSourceHash !== entry.contentHash) {
|
|
1502
|
+
if ((delivery === "snapshot" || targetHashMatchesLock) && currentSourceHash !== entry.contentHash) {
|
|
1152
1503
|
issues.push({
|
|
1153
1504
|
type: "hash-drift",
|
|
1154
1505
|
id: entry.id,
|
|
@@ -1157,9 +1508,16 @@ function checkInstalledAssets(options) {
|
|
|
1157
1508
|
});
|
|
1158
1509
|
}
|
|
1159
1510
|
}
|
|
1160
|
-
|
|
1511
|
+
if (!registryProvenanceMismatch) {
|
|
1512
|
+
issues.push(...checkDuplicateSkillPlacements(options.targetDir, options.registry));
|
|
1513
|
+
}
|
|
1161
1514
|
return { targetDir: options.targetDir, issues };
|
|
1162
1515
|
}
|
|
1516
|
+
function matchesRegistryProvenance(value, expected) {
|
|
1517
|
+
if (value === null || typeof value !== "object") return false;
|
|
1518
|
+
const provenance = value;
|
|
1519
|
+
return provenance.schema === expected.schema && provenance.version === expected.version && provenance.hash === expected.hash && provenance.assetCount === expected.assetCount;
|
|
1520
|
+
}
|
|
1163
1521
|
function checkRegistryPlacement(lockfile, asset, targetPath) {
|
|
1164
1522
|
if (lockfile.placement !== "registry") return void 0;
|
|
1165
1523
|
if (asset.kind !== "skill") return void 0;
|
|
@@ -1235,6 +1593,9 @@ function pathExistsEvenIfDanglingSymlink4(path) {
|
|
|
1235
1593
|
return false;
|
|
1236
1594
|
}
|
|
1237
1595
|
}
|
|
1596
|
+
function isProjectSkillTarget(targetPath) {
|
|
1597
|
+
return targetPath.startsWith(".agents/skills/") || targetPath.startsWith(".agents/manual-skills/");
|
|
1598
|
+
}
|
|
1238
1599
|
|
|
1239
1600
|
// src/asset-targets/recommend.ts
|
|
1240
1601
|
import { existsSync as existsSync10, readdirSync as readdirSync6, readFileSync as readFileSync6 } from "node:fs";
|
|
@@ -1301,13 +1662,6 @@ function recommendBundlesForTarget(targetDir) {
|
|
|
1301
1662
|
reasons: signals.frontendSignals.map((signal) => `frontend dependency: ${signal}`)
|
|
1302
1663
|
});
|
|
1303
1664
|
}
|
|
1304
|
-
if (signals.researchSignals.length > 0) {
|
|
1305
|
-
recommendations.push({
|
|
1306
|
-
bundleId: "research-docs",
|
|
1307
|
-
confidence: "high",
|
|
1308
|
-
reasons: signals.researchSignals
|
|
1309
|
-
});
|
|
1310
|
-
}
|
|
1311
1665
|
if (signals.writingSignals.length > 0) {
|
|
1312
1666
|
recommendations.push({
|
|
1313
1667
|
bundleId: "novel-writing",
|
|
@@ -1877,11 +2231,12 @@ var REQUIRED_ASSETS = [
|
|
|
1877
2231
|
"profiles/doc-only/profile.md",
|
|
1878
2232
|
"docs/reference/adoption/migration-v1.1.md"
|
|
1879
2233
|
];
|
|
1880
|
-
function
|
|
1881
|
-
const assets = listAssets();
|
|
2234
|
+
function runPackageDoctor(_args, dependencies = {}) {
|
|
2235
|
+
const assets = dependencies.assets ?? listAssets("packaged");
|
|
1882
2236
|
const assetPaths = new Set(assets.map((asset) => asset.path));
|
|
1883
2237
|
const missing = REQUIRED_ASSETS.filter((assetPath) => !assetPaths.has(assetPath));
|
|
1884
|
-
|
|
2238
|
+
const docGov = dependencies.docGov ?? checkDocGov();
|
|
2239
|
+
console.log("pro-gov package-doctor");
|
|
1885
2240
|
console.log(`assets: ${assets.length}`);
|
|
1886
2241
|
if (missing.length > 0) {
|
|
1887
2242
|
for (const assetPath of missing) {
|
|
@@ -1890,29 +2245,39 @@ function runDoctor(_args) {
|
|
|
1890
2245
|
} else {
|
|
1891
2246
|
console.log("assets: required project-governance assets found");
|
|
1892
2247
|
}
|
|
1893
|
-
console.log(
|
|
1894
|
-
return missing.length > 0 ? 1 : 0;
|
|
2248
|
+
console.log(docGov.message);
|
|
2249
|
+
return missing.length > 0 || !docGov.ok ? 1 : 0;
|
|
2250
|
+
}
|
|
2251
|
+
function runDoctor(args) {
|
|
2252
|
+
return runPackageDoctor(args);
|
|
1895
2253
|
}
|
|
1896
|
-
function checkDocGov() {
|
|
1897
|
-
const
|
|
2254
|
+
function checkDocGov(options = {}) {
|
|
2255
|
+
const run = options.run ?? ((command2, args) => spawnSync2(command2, args, {
|
|
1898
2256
|
encoding: "utf8",
|
|
1899
2257
|
stdio: "ignore"
|
|
1900
|
-
});
|
|
1901
|
-
|
|
1902
|
-
return "doc-gov: available on PATH";
|
|
1903
|
-
}
|
|
1904
|
-
const dependencyCli = resolveDocGovDependencyCli();
|
|
2258
|
+
}));
|
|
2259
|
+
const dependencyCli = (options.resolveDependencyCli ?? resolveDocGovDependencyCli)();
|
|
1905
2260
|
if (!dependencyCli) {
|
|
1906
|
-
|
|
2261
|
+
const fromPath = run("doc-gov", ["--help"]);
|
|
2262
|
+
if (!fromPath.error && fromPath.status === 0) {
|
|
2263
|
+
return {
|
|
2264
|
+
ok: false,
|
|
2265
|
+
message: "doc-gov: found only on PATH; the required local @pieai/doc-gov package dependency is unavailable."
|
|
2266
|
+
};
|
|
2267
|
+
}
|
|
2268
|
+
return {
|
|
2269
|
+
ok: false,
|
|
2270
|
+
message: "doc-gov: not found; install the required @pieai/doc-gov package dependency beside @pieai/pro-gov."
|
|
2271
|
+
};
|
|
1907
2272
|
}
|
|
1908
|
-
const fromDependency =
|
|
1909
|
-
encoding: "utf8",
|
|
1910
|
-
stdio: "ignore"
|
|
1911
|
-
});
|
|
2273
|
+
const fromDependency = run(process.execPath, [dependencyCli, "--help"]);
|
|
1912
2274
|
if (!fromDependency.error && fromDependency.status === 0) {
|
|
1913
|
-
return "doc-gov: available via package dependency";
|
|
2275
|
+
return { ok: true, message: "doc-gov: available via package dependency" };
|
|
1914
2276
|
}
|
|
1915
|
-
return
|
|
2277
|
+
return {
|
|
2278
|
+
ok: false,
|
|
2279
|
+
message: `doc-gov: dependency found but returned status ${fromDependency.status ?? "unknown"}`
|
|
2280
|
+
};
|
|
1916
2281
|
}
|
|
1917
2282
|
function resolveDocGovDependencyCli() {
|
|
1918
2283
|
try {
|
|
@@ -2077,11 +2442,11 @@ import {
|
|
|
2077
2442
|
lstatSync as lstatSync6,
|
|
2078
2443
|
mkdirSync as mkdirSync5,
|
|
2079
2444
|
readdirSync as readdirSync7,
|
|
2080
|
-
statSync as
|
|
2445
|
+
statSync as statSync4,
|
|
2081
2446
|
writeFileSync as writeFileSync4
|
|
2082
2447
|
} from "node:fs";
|
|
2083
2448
|
import { homedir } from "node:os";
|
|
2084
|
-
import { dirname as dirname8, join as join14, relative as
|
|
2449
|
+
import { dirname as dirname8, join as join14, relative as relative6, resolve as resolve3 } from "node:path";
|
|
2085
2450
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
2086
2451
|
var ROOT_DEFINITIONS = [
|
|
2087
2452
|
{
|
|
@@ -2504,7 +2869,7 @@ function fallbackMeasure(root) {
|
|
|
2504
2869
|
if (entry.isDirectory()) pending.push(path);
|
|
2505
2870
|
else if (entry.isFile()) {
|
|
2506
2871
|
try {
|
|
2507
|
-
bytes +=
|
|
2872
|
+
bytes += statSync4(path).size;
|
|
2508
2873
|
} catch {
|
|
2509
2874
|
}
|
|
2510
2875
|
}
|
|
@@ -2522,7 +2887,7 @@ function countImmediateEntries(path) {
|
|
|
2522
2887
|
}
|
|
2523
2888
|
function displayPath(path, homePath) {
|
|
2524
2889
|
const absolute = resolve3(path);
|
|
2525
|
-
const withinHome =
|
|
2890
|
+
const withinHome = relative6(homePath, absolute);
|
|
2526
2891
|
if (withinHome === "") return "~";
|
|
2527
2892
|
if (!withinHome.startsWith("..")) return `~/${withinHome}`;
|
|
2528
2893
|
return absolute;
|
|
@@ -3648,7 +4013,7 @@ function printUsage2() {
|
|
|
3648
4013
|
|
|
3649
4014
|
// src/learning/recall.ts
|
|
3650
4015
|
import { existsSync as existsSync15, readdirSync as readdirSync8, readFileSync as readFileSync10 } from "node:fs";
|
|
3651
|
-
import { basename as basename3, join as join15, relative as
|
|
4016
|
+
import { basename as basename3, join as join15, relative as relative7 } from "node:path";
|
|
3652
4017
|
function recallLearnings(root, options) {
|
|
3653
4018
|
const query = options.query.trim();
|
|
3654
4019
|
const terms = tokenize(query);
|
|
@@ -3703,7 +4068,7 @@ function readLearningRecord(root, absolutePath) {
|
|
|
3703
4068
|
const parsed = splitFrontmatter(content);
|
|
3704
4069
|
const body = parsed.body;
|
|
3705
4070
|
return {
|
|
3706
|
-
relativePath: normalizePath(
|
|
4071
|
+
relativePath: normalizePath(relative7(root, absolutePath)),
|
|
3707
4072
|
title: findTitle(parsed.frontmatter, body) ?? titleFromPath(absolutePath),
|
|
3708
4073
|
metadata: parsed.frontmatter,
|
|
3709
4074
|
body
|
|
@@ -3785,7 +4150,7 @@ function cleanMarkdownLine(input) {
|
|
|
3785
4150
|
|
|
3786
4151
|
// src/learning/capture.ts
|
|
3787
4152
|
import { existsSync as existsSync16, mkdirSync as mkdirSync7, writeFileSync as writeFileSync6 } from "node:fs";
|
|
3788
|
-
import { basename as basename4, join as join16, relative as
|
|
4153
|
+
import { basename as basename4, join as join16, relative as relative8 } from "node:path";
|
|
3789
4154
|
function captureLearning(root, options) {
|
|
3790
4155
|
const title = options.title.trim();
|
|
3791
4156
|
const summary = options.summary.trim();
|
|
@@ -3799,7 +4164,7 @@ function captureLearning(root, options) {
|
|
|
3799
4164
|
const idSlug = basename4(path, ".md");
|
|
3800
4165
|
writeFileSync6(path, renderLearning({ title, summary, category, moduleName, idSlug }));
|
|
3801
4166
|
return {
|
|
3802
|
-
relativePath: normalizePath2(
|
|
4167
|
+
relativePath: normalizePath2(relative8(root, path)),
|
|
3803
4168
|
title,
|
|
3804
4169
|
captureMode: "pgs-native"
|
|
3805
4170
|
};
|
|
@@ -4417,13 +4782,13 @@ function formatLink(link) {
|
|
|
4417
4782
|
}
|
|
4418
4783
|
|
|
4419
4784
|
// src/lens/scan.ts
|
|
4420
|
-
import { spawnSync as
|
|
4421
|
-
import { existsSync as
|
|
4785
|
+
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
4786
|
+
import { existsSync as existsSync21, readFileSync as readFileSync13, statSync as statSync6 } from "node:fs";
|
|
4422
4787
|
import { homedir as homedir3 } from "node:os";
|
|
4423
|
-
import { join as
|
|
4788
|
+
import { join as join22 } from "node:path";
|
|
4424
4789
|
|
|
4425
4790
|
// src/host-ssot.ts
|
|
4426
|
-
import { lstatSync as lstatSync7, readlinkSync as readlinkSync3, realpathSync as
|
|
4791
|
+
import { lstatSync as lstatSync7, readlinkSync as readlinkSync3, realpathSync as realpathSync5 } from "node:fs";
|
|
4427
4792
|
import { dirname as dirname12, isAbsolute as isAbsolute4, join as join18, resolve as resolve6 } from "node:path";
|
|
4428
4793
|
function inspectProjectHostSsot(root) {
|
|
4429
4794
|
const agentsEntry = inspectCanonicalPath(root, "AGENTS.md");
|
|
@@ -4473,7 +4838,7 @@ function inspectCanonicalPath(root, path) {
|
|
|
4473
4838
|
if (!stat) return { path, status: "missing" };
|
|
4474
4839
|
if (stat.isSymbolicLink()) {
|
|
4475
4840
|
try {
|
|
4476
|
-
|
|
4841
|
+
realpathSync5(absolutePath);
|
|
4477
4842
|
return { path, status: "symlink" };
|
|
4478
4843
|
} catch {
|
|
4479
4844
|
return { path, status: "dangling-symlink" };
|
|
@@ -4507,7 +4872,7 @@ function inspectCompatibilityLink(root, path, expectedRawTarget) {
|
|
|
4507
4872
|
}
|
|
4508
4873
|
let targetMatches = false;
|
|
4509
4874
|
try {
|
|
4510
|
-
targetMatches =
|
|
4875
|
+
targetMatches = realpathSync5(absolutePath) === realpathSync5(expectedPath);
|
|
4511
4876
|
} catch {
|
|
4512
4877
|
return { ...base, rawTarget, resolvedTarget, status: "dangling-symlink" };
|
|
4513
4878
|
}
|
|
@@ -4542,7 +4907,7 @@ function safeLstat2(path) {
|
|
|
4542
4907
|
}
|
|
4543
4908
|
|
|
4544
4909
|
// src/portfolio/redundancy.ts
|
|
4545
|
-
import { existsSync as existsSync18, readdirSync as readdirSync9, statSync as
|
|
4910
|
+
import { existsSync as existsSync18, readdirSync as readdirSync9, statSync as statSync5 } from "node:fs";
|
|
4546
4911
|
import { homedir as homedir2 } from "node:os";
|
|
4547
4912
|
import { join as join19 } from "node:path";
|
|
4548
4913
|
var DEFAULT_CACHE_THRESHOLD_BYTES = 1e9;
|
|
@@ -4657,7 +5022,7 @@ function collectDirectoryStats(root) {
|
|
|
4657
5022
|
} else if (entry.isFile()) {
|
|
4658
5023
|
fileCount += 1;
|
|
4659
5024
|
try {
|
|
4660
|
-
bytes +=
|
|
5025
|
+
bytes += statSync5(path).size;
|
|
4661
5026
|
} catch {
|
|
4662
5027
|
}
|
|
4663
5028
|
}
|
|
@@ -4698,6 +5063,111 @@ function readPackageJson(path) {
|
|
|
4698
5063
|
}
|
|
4699
5064
|
}
|
|
4700
5065
|
|
|
5066
|
+
// src/repository-files.ts
|
|
5067
|
+
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
5068
|
+
import { existsSync as existsSync20, readdirSync as readdirSync10 } from "node:fs";
|
|
5069
|
+
import { isAbsolute as isAbsolute5, join as join21, posix as posix3, relative as relative9 } from "node:path";
|
|
5070
|
+
var gitMaxBufferBytes = 64 * 1024 * 1024;
|
|
5071
|
+
var RepositoryFileDiscoveryError = class extends Error {
|
|
5072
|
+
constructor(message) {
|
|
5073
|
+
super(message);
|
|
5074
|
+
this.name = "RepositoryFileDiscoveryError";
|
|
5075
|
+
}
|
|
5076
|
+
};
|
|
5077
|
+
function discoverRepositoryFiles(root, options = {}) {
|
|
5078
|
+
const probe = runGit(root, ["rev-parse", "--is-inside-work-tree"]);
|
|
5079
|
+
if (!probe.ok) {
|
|
5080
|
+
if (!probe.notRepository || existsSync20(join21(root, ".git"))) {
|
|
5081
|
+
throw new RepositoryFileDiscoveryError(probe.message);
|
|
5082
|
+
}
|
|
5083
|
+
return {
|
|
5084
|
+
source: "filesystem",
|
|
5085
|
+
files: discoverFilesystemFiles(root, options)
|
|
5086
|
+
};
|
|
5087
|
+
}
|
|
5088
|
+
if (probe.stdout.trim() !== "true") {
|
|
5089
|
+
throw new RepositoryFileDiscoveryError(
|
|
5090
|
+
"Git repository discovery failed: target is a Git repository without a worktree."
|
|
5091
|
+
);
|
|
5092
|
+
}
|
|
5093
|
+
const gitFiles = runGit(root, [
|
|
5094
|
+
"ls-files",
|
|
5095
|
+
"--cached",
|
|
5096
|
+
"--others",
|
|
5097
|
+
"--exclude-standard",
|
|
5098
|
+
"-z",
|
|
5099
|
+
...options.gitPathspecs?.length ? ["--", ...options.gitPathspecs] : []
|
|
5100
|
+
]);
|
|
5101
|
+
if (!gitFiles.ok) throw new RepositoryFileDiscoveryError(gitFiles.message);
|
|
5102
|
+
const files = /* @__PURE__ */ new Set();
|
|
5103
|
+
for (const path of gitFiles.stdout.split("\0")) {
|
|
5104
|
+
if (!path) continue;
|
|
5105
|
+
const normalized = normalizeRepositoryRelativePath(path);
|
|
5106
|
+
if (!isSafeRepositoryRelativePath(normalized)) {
|
|
5107
|
+
throw new RepositoryFileDiscoveryError(
|
|
5108
|
+
`Git returned a path outside the repository boundary: ${path}`
|
|
5109
|
+
);
|
|
5110
|
+
}
|
|
5111
|
+
if (existsSync20(join21(root, normalized))) files.add(normalized);
|
|
5112
|
+
}
|
|
5113
|
+
return { source: "git", files: [...files].sort() };
|
|
5114
|
+
}
|
|
5115
|
+
function normalizeRepositoryRelativePath(path) {
|
|
5116
|
+
return posix3.normalize(path.replaceAll("\\", "/").replace(/^\.\/+/, ""));
|
|
5117
|
+
}
|
|
5118
|
+
function discoverFilesystemFiles(root, options) {
|
|
5119
|
+
const files = /* @__PURE__ */ new Set();
|
|
5120
|
+
const maxDepth = options.fallbackMaxDepth ?? Number.POSITIVE_INFINITY;
|
|
5121
|
+
const ignoredDirectories2 = options.fallbackIgnoredDirectories ?? /* @__PURE__ */ new Set();
|
|
5122
|
+
const visit = (directory, depth) => {
|
|
5123
|
+
if (depth > maxDepth || !existsSync20(directory)) return;
|
|
5124
|
+
let entries;
|
|
5125
|
+
try {
|
|
5126
|
+
entries = readdirSync10(directory, { withFileTypes: true });
|
|
5127
|
+
} catch {
|
|
5128
|
+
return;
|
|
5129
|
+
}
|
|
5130
|
+
for (const entry of entries) {
|
|
5131
|
+
const absolutePath = join21(directory, entry.name);
|
|
5132
|
+
if (entry.isDirectory()) {
|
|
5133
|
+
if (!ignoredDirectories2.has(entry.name)) visit(absolutePath, depth + 1);
|
|
5134
|
+
continue;
|
|
5135
|
+
}
|
|
5136
|
+
if (!entry.isFile()) continue;
|
|
5137
|
+
const relativePath = normalizeRepositoryRelativePath(relative9(root, absolutePath));
|
|
5138
|
+
if (isSafeRepositoryRelativePath(relativePath) && (options.fallbackIncludeFile?.(relativePath) ?? true)) {
|
|
5139
|
+
files.add(relativePath);
|
|
5140
|
+
}
|
|
5141
|
+
}
|
|
5142
|
+
};
|
|
5143
|
+
visit(root, 0);
|
|
5144
|
+
return [...files].sort();
|
|
5145
|
+
}
|
|
5146
|
+
function isSafeRepositoryRelativePath(path) {
|
|
5147
|
+
return path !== "" && path !== "." && !isAbsolute5(path) && !/^[a-zA-Z]:\//.test(path) && path !== ".." && !path.startsWith("../");
|
|
5148
|
+
}
|
|
5149
|
+
function runGit(root, args) {
|
|
5150
|
+
const result = spawnSync3("git", ["-C", root, ...args], {
|
|
5151
|
+
encoding: "utf8",
|
|
5152
|
+
maxBuffer: gitMaxBufferBytes,
|
|
5153
|
+
env: { ...process.env, LANG: "C", LC_ALL: "C" }
|
|
5154
|
+
});
|
|
5155
|
+
if (result.error) {
|
|
5156
|
+
return {
|
|
5157
|
+
ok: false,
|
|
5158
|
+
notRepository: false,
|
|
5159
|
+
message: `Git repository discovery failed: ${result.error.message}`
|
|
5160
|
+
};
|
|
5161
|
+
}
|
|
5162
|
+
if (result.status === 0) return { ok: true, stdout: result.stdout };
|
|
5163
|
+
const stderr = result.stderr.trim();
|
|
5164
|
+
return {
|
|
5165
|
+
ok: false,
|
|
5166
|
+
notRepository: /not a git repository/i.test(stderr),
|
|
5167
|
+
message: `Git repository discovery failed (${result.status ?? "unknown status"}): ${stderr || "no diagnostic output"}`
|
|
5168
|
+
};
|
|
5169
|
+
}
|
|
5170
|
+
|
|
4701
5171
|
// src/lens/scan.ts
|
|
4702
5172
|
var ignoredDirectories = /* @__PURE__ */ new Set([".git", ".next", ".turbo", "dist", "node_modules", "coverage"]);
|
|
4703
5173
|
function scanProjectLensTarget(targetDir, options = {}) {
|
|
@@ -4714,7 +5184,7 @@ function scanProjectLensTarget(targetDir, options = {}) {
|
|
|
4714
5184
|
includedFileCount: files.length,
|
|
4715
5185
|
excludedFileCount: candidateFiles.length - files.length
|
|
4716
5186
|
},
|
|
4717
|
-
aiEntryFiles: ["AGENTS.md", "CLAUDE.md"].filter((file) =>
|
|
5187
|
+
aiEntryFiles: ["AGENTS.md", "CLAUDE.md"].filter((file) => existsSync21(join22(targetDir, file))),
|
|
4718
5188
|
aiConfigFiles: [],
|
|
4719
5189
|
hostSsot: inspectProjectHostSsot(targetDir),
|
|
4720
5190
|
userHostSsot: inspectUserSkillsSsot(options.homeDir ?? process.env.HOME ?? homedir3()),
|
|
@@ -4725,17 +5195,17 @@ function scanProjectLensTarget(targetDir, options = {}) {
|
|
|
4725
5195
|
}),
|
|
4726
5196
|
packageJson,
|
|
4727
5197
|
docs: {
|
|
4728
|
-
hasDocsDirectory:
|
|
5198
|
+
hasDocsDirectory: existsSync21(join22(targetDir, "docs")),
|
|
4729
5199
|
markdownFileCount: markdownFiles.length,
|
|
4730
5200
|
governanceFiles: markdownFiles.filter((file) => file.startsWith("docs/governance/") || file.startsWith("docs/policy/")).sort()
|
|
4731
5201
|
},
|
|
4732
5202
|
git: readGitState(targetDir),
|
|
4733
|
-
largeFiles: files.map((file) => ({ path: file, bytes:
|
|
5203
|
+
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
5204
|
};
|
|
4735
5205
|
}
|
|
4736
5206
|
function readPackageJson2(targetDir) {
|
|
4737
|
-
const packageJsonPath =
|
|
4738
|
-
if (!
|
|
5207
|
+
const packageJsonPath = join22(targetDir, "package.json");
|
|
5208
|
+
if (!existsSync21(packageJsonPath)) return void 0;
|
|
4739
5209
|
try {
|
|
4740
5210
|
const packageJson = JSON.parse(readFileSync13(packageJsonPath, "utf8"));
|
|
4741
5211
|
return {
|
|
@@ -4748,10 +5218,10 @@ function readPackageJson2(targetDir) {
|
|
|
4748
5218
|
}
|
|
4749
5219
|
}
|
|
4750
5220
|
function readGitState(targetDir) {
|
|
4751
|
-
const branch =
|
|
5221
|
+
const branch = runGit2(targetDir, ["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
4752
5222
|
if (!branch.ok) return { available: false };
|
|
4753
|
-
const head =
|
|
4754
|
-
const status =
|
|
5223
|
+
const head = runGit2(targetDir, ["log", "-1", "--format=%H %s"]);
|
|
5224
|
+
const status = runGit2(targetDir, ["status", "-sb"]);
|
|
4755
5225
|
return {
|
|
4756
5226
|
available: true,
|
|
4757
5227
|
branch: branch.stdout,
|
|
@@ -4759,27 +5229,18 @@ function readGitState(targetDir) {
|
|
|
4759
5229
|
statusShort: status.ok ? status.stdout : void 0
|
|
4760
5230
|
};
|
|
4761
5231
|
}
|
|
4762
|
-
function
|
|
4763
|
-
const result =
|
|
4764
|
-
encoding: "utf8"
|
|
5232
|
+
function runGit2(targetDir, args) {
|
|
5233
|
+
const result = spawnSync4("git", ["-C", targetDir, ...args], {
|
|
5234
|
+
encoding: "utf8",
|
|
5235
|
+
maxBuffer: 64 * 1024 * 1024
|
|
4765
5236
|
});
|
|
4766
5237
|
if (result.status !== 0) return { ok: false };
|
|
4767
5238
|
return { ok: true, stdout: result.stdout.trim() };
|
|
4768
5239
|
}
|
|
4769
5240
|
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();
|
|
5241
|
+
return discoverRepositoryFiles(targetDir, {
|
|
5242
|
+
fallbackIgnoredDirectories: ignoredDirectories
|
|
5243
|
+
}).files;
|
|
4783
5244
|
}
|
|
4784
5245
|
var excludedEvidencePrefixes = [
|
|
4785
5246
|
".agents/manual-skills/",
|
|
@@ -4792,20 +5253,6 @@ var excludedEvidencePrefixes = [
|
|
|
4792
5253
|
function isFirstPartyEvidenceFile(file) {
|
|
4793
5254
|
return !excludedEvidencePrefixes.some((prefix) => file.startsWith(prefix));
|
|
4794
5255
|
}
|
|
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
5256
|
|
|
4810
5257
|
// src/commands/lens.ts
|
|
4811
5258
|
function runLens(args) {
|
|
@@ -4957,19 +5404,19 @@ function printUsage4() {
|
|
|
4957
5404
|
}
|
|
4958
5405
|
|
|
4959
5406
|
// src/commands/portfolio.ts
|
|
4960
|
-
import { existsSync as
|
|
4961
|
-
import { join as
|
|
5407
|
+
import { existsSync as existsSync33, readFileSync as readFileSync18 } from "node:fs";
|
|
5408
|
+
import { join as join34 } from "node:path";
|
|
4962
5409
|
|
|
4963
5410
|
// src/portfolio/doctor.ts
|
|
4964
|
-
import { spawnSync as
|
|
4965
|
-
import { existsSync as
|
|
5411
|
+
import { spawnSync as spawnSync7 } from "node:child_process";
|
|
5412
|
+
import { existsSync as existsSync24, readFileSync as readFileSync16 } from "node:fs";
|
|
4966
5413
|
import { createRequire as createRequire2 } from "node:module";
|
|
4967
5414
|
import { homedir as homedir4 } from "node:os";
|
|
4968
|
-
import { dirname as dirname15, join as
|
|
5415
|
+
import { dirname as dirname15, join as join25 } from "node:path";
|
|
4969
5416
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
4970
5417
|
|
|
4971
5418
|
// src/host-tooling/inventory.ts
|
|
4972
|
-
import { spawnSync as
|
|
5419
|
+
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
4973
5420
|
function inspectHostTooling(requirements, runner = defaultRunner2) {
|
|
4974
5421
|
const hosts = [];
|
|
4975
5422
|
const issues = [];
|
|
@@ -5043,7 +5490,7 @@ function parseHostPlugins(host, value) {
|
|
|
5043
5490
|
});
|
|
5044
5491
|
}
|
|
5045
5492
|
function defaultRunner2({ command: command2 }) {
|
|
5046
|
-
const result =
|
|
5493
|
+
const result = spawnSync5(command2[0] ?? "", command2.slice(1), {
|
|
5047
5494
|
encoding: "utf8",
|
|
5048
5495
|
timeout: 1e4
|
|
5049
5496
|
});
|
|
@@ -5058,8 +5505,8 @@ function isRecord2(value) {
|
|
|
5058
5505
|
}
|
|
5059
5506
|
|
|
5060
5507
|
// src/portfolio/asset-state.ts
|
|
5061
|
-
import { existsSync as
|
|
5062
|
-
import { join as
|
|
5508
|
+
import { existsSync as existsSync22, lstatSync as lstatSync8, readFileSync as readFileSync14 } from "node:fs";
|
|
5509
|
+
import { join as join23 } from "node:path";
|
|
5063
5510
|
function comparePortfolioAssetState(options) {
|
|
5064
5511
|
const expectedManifest = readPlanDocument(
|
|
5065
5512
|
options.expectedPlan,
|
|
@@ -5070,10 +5517,10 @@ function comparePortfolioAssetState(options) {
|
|
|
5070
5517
|
".pro-gov/assets.lock.json"
|
|
5071
5518
|
);
|
|
5072
5519
|
const currentManifest = readJsonFile(
|
|
5073
|
-
|
|
5520
|
+
join23(options.targetDir, ".pro-gov/assets.json")
|
|
5074
5521
|
);
|
|
5075
5522
|
const currentLock = readJsonFile(
|
|
5076
|
-
|
|
5523
|
+
join23(options.targetDir, ".pro-gov/assets.lock.json")
|
|
5077
5524
|
);
|
|
5078
5525
|
const issues = [];
|
|
5079
5526
|
if (!sameStrings(currentManifest?.bundleIds, expectedManifest?.bundleIds)) {
|
|
@@ -5101,7 +5548,7 @@ function comparePortfolioAssetState(options) {
|
|
|
5101
5548
|
(action) => action.type === "adopt-symlink" && action.assetId === entry.id && action.legacyTargetPath === entry.targetPath
|
|
5102
5549
|
))
|
|
5103
5550
|
continue;
|
|
5104
|
-
const targetAbsolutePath =
|
|
5551
|
+
const targetAbsolutePath = join23(options.targetDir, entry.targetPath);
|
|
5105
5552
|
if (!pathIsSymlink(targetAbsolutePath)) continue;
|
|
5106
5553
|
issues.push({
|
|
5107
5554
|
type: "orphaned-managed-symlink",
|
|
@@ -5123,7 +5570,7 @@ function readPlanDocument(plan, targetPath) {
|
|
|
5123
5570
|
}
|
|
5124
5571
|
}
|
|
5125
5572
|
function readJsonFile(path) {
|
|
5126
|
-
if (!
|
|
5573
|
+
if (!existsSync22(path)) return void 0;
|
|
5127
5574
|
try {
|
|
5128
5575
|
return JSON.parse(readFileSync14(path, "utf8"));
|
|
5129
5576
|
} catch {
|
|
@@ -5134,7 +5581,12 @@ function sameStrings(left, right) {
|
|
|
5134
5581
|
return JSON.stringify([...left ?? []].sort()) === JSON.stringify([...right ?? []].sort());
|
|
5135
5582
|
}
|
|
5136
5583
|
function sameLock(left, right) {
|
|
5137
|
-
|
|
5584
|
+
if (JSON.stringify(normalizeLock(left)) !== JSON.stringify(normalizeLock(right))) return false;
|
|
5585
|
+
if (!hasRegistryProvenance(left) || !hasRegistryProvenance(right)) return true;
|
|
5586
|
+
return JSON.stringify(left.registryProvenance) === JSON.stringify(right.registryProvenance);
|
|
5587
|
+
}
|
|
5588
|
+
function hasRegistryProvenance(lock) {
|
|
5589
|
+
return lock !== void 0 && Object.prototype.hasOwnProperty.call(lock, "registryProvenance");
|
|
5138
5590
|
}
|
|
5139
5591
|
function normalizeLock(lock) {
|
|
5140
5592
|
return {
|
|
@@ -5153,9 +5605,9 @@ function pathIsSymlink(path) {
|
|
|
5153
5605
|
}
|
|
5154
5606
|
|
|
5155
5607
|
// src/portfolio/version-policy.ts
|
|
5156
|
-
import { spawnSync as
|
|
5157
|
-
import { existsSync as
|
|
5158
|
-
import { dirname as dirname14, join as
|
|
5608
|
+
import { spawnSync as spawnSync6 } from "node:child_process";
|
|
5609
|
+
import { existsSync as existsSync23, lstatSync as lstatSync9, readFileSync as readFileSync15 } from "node:fs";
|
|
5610
|
+
import { dirname as dirname14, join as join24 } from "node:path";
|
|
5159
5611
|
function inspectVersionPolicy(root, policy, projectType) {
|
|
5160
5612
|
if (!policy) return { status: "compliant", packages: [], runtimes: [], attentionCount: 0 };
|
|
5161
5613
|
const packageManifests = collectPackageManifests(root);
|
|
@@ -5245,7 +5697,7 @@ function inspectRuntime(expectedName, expectedVersion) {
|
|
|
5245
5697
|
function readRuntimeVersion(name) {
|
|
5246
5698
|
if (name === "node") return process.versions.node;
|
|
5247
5699
|
if (name !== "deno") return void 0;
|
|
5248
|
-
const result =
|
|
5700
|
+
const result = spawnSync6("deno", ["--version"], { encoding: "utf8" });
|
|
5249
5701
|
if (result.status !== 0) return void 0;
|
|
5250
5702
|
return /^deno\s+(\d+\.\d+\.\d+)/m.exec(result.stdout)?.[1];
|
|
5251
5703
|
}
|
|
@@ -5264,7 +5716,7 @@ function findDeclaredVersion(packageJson, name) {
|
|
|
5264
5716
|
function readInstalledVersion(root, name, fromDirectory = root) {
|
|
5265
5717
|
let current = fromDirectory;
|
|
5266
5718
|
while (true) {
|
|
5267
|
-
const packageJson = readJson2(
|
|
5719
|
+
const packageJson = readJson2(join24(current, "node_modules", name, "package.json"));
|
|
5268
5720
|
if (typeof packageJson?.version === "string") return packageJson.version;
|
|
5269
5721
|
if (current === root) return void 0;
|
|
5270
5722
|
const parent = dirname14(current);
|
|
@@ -5273,7 +5725,6 @@ function readInstalledVersion(root, name, fromDirectory = root) {
|
|
|
5273
5725
|
}
|
|
5274
5726
|
}
|
|
5275
5727
|
function collectPackageManifests(root) {
|
|
5276
|
-
const manifests = [];
|
|
5277
5728
|
const ignored = /* @__PURE__ */ new Set([
|
|
5278
5729
|
".git",
|
|
5279
5730
|
".next",
|
|
@@ -5297,37 +5748,36 @@ function collectPackageManifests(root) {
|
|
|
5297
5748
|
".pnpm-store",
|
|
5298
5749
|
".tmp-repos"
|
|
5299
5750
|
]);
|
|
5300
|
-
const
|
|
5301
|
-
|
|
5302
|
-
|
|
5751
|
+
const manifests = /* @__PURE__ */ new Map();
|
|
5752
|
+
const addManifest = (relativePath) => {
|
|
5753
|
+
const directorySegments = relativePath.split("/").slice(0, -1);
|
|
5754
|
+
if (relativePath.split("/").at(-1) !== "package.json" || directorySegments.length > 6 || directorySegments.some((segment) => ignored.has(segment))) {
|
|
5755
|
+
return;
|
|
5756
|
+
}
|
|
5757
|
+
const path = join24(root, relativePath);
|
|
5303
5758
|
try {
|
|
5304
|
-
|
|
5759
|
+
if (!lstatSync9(path).isFile()) return;
|
|
5305
5760
|
} catch {
|
|
5306
5761
|
return;
|
|
5307
5762
|
}
|
|
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
|
-
}
|
|
5763
|
+
const packageJson = readJson2(path);
|
|
5764
|
+
if (!packageJson) return;
|
|
5765
|
+
manifests.set(relativePath, { path: relativePath, directory: dirname14(path), packageJson });
|
|
5322
5766
|
};
|
|
5323
|
-
|
|
5324
|
-
|
|
5767
|
+
const discovery = discoverRepositoryFiles(root, {
|
|
5768
|
+
gitPathspecs: ["package.json", ":(glob)**/package.json"],
|
|
5769
|
+
fallbackIgnoredDirectories: ignored,
|
|
5770
|
+
fallbackMaxDepth: 6,
|
|
5771
|
+
fallbackIncludeFile: (path) => path.split("/").at(-1) === "package.json"
|
|
5772
|
+
});
|
|
5773
|
+
for (const relativePath of discovery.files) addManifest(relativePath);
|
|
5774
|
+
return [...manifests.values()].sort((a, b) => a.path.localeCompare(b.path));
|
|
5325
5775
|
}
|
|
5326
5776
|
function unique(values) {
|
|
5327
5777
|
return [...new Set(values)];
|
|
5328
5778
|
}
|
|
5329
5779
|
function readJson2(path) {
|
|
5330
|
-
if (!
|
|
5780
|
+
if (!existsSync23(path)) return void 0;
|
|
5331
5781
|
try {
|
|
5332
5782
|
return JSON.parse(readFileSync15(path, "utf8"));
|
|
5333
5783
|
} catch {
|
|
@@ -5362,12 +5812,12 @@ function inspectTarget(options) {
|
|
|
5362
5812
|
const { target } = options;
|
|
5363
5813
|
const hostSsot = inspectProjectHostSsot(target.path);
|
|
5364
5814
|
const issues = [];
|
|
5365
|
-
const packageJson = readJson3(
|
|
5815
|
+
const packageJson = readJson3(join25(target.path, "package.json"));
|
|
5366
5816
|
const packages = {};
|
|
5367
5817
|
for (const packageName of ["@pieai/pro-gov", "@pieai/doc-gov"]) {
|
|
5368
5818
|
const declared = packageJson?.devDependencies?.[packageName] ?? packageJson?.dependencies?.[packageName];
|
|
5369
5819
|
const installedPackage = readJson3(
|
|
5370
|
-
|
|
5820
|
+
join25(target.path, "node_modules", packageName, "package.json")
|
|
5371
5821
|
);
|
|
5372
5822
|
const installed = installedPackage?.version;
|
|
5373
5823
|
const expected = options.expectedPackageVersions[packageName];
|
|
@@ -5427,7 +5877,7 @@ function inspectTarget(options) {
|
|
|
5427
5877
|
type: "asset-lock-drift",
|
|
5428
5878
|
message: error instanceof Error ? error.message : String(error)
|
|
5429
5879
|
});
|
|
5430
|
-
if (!
|
|
5880
|
+
if (!existsSync24(join25(target.path, ".pro-gov/assets.json"))) {
|
|
5431
5881
|
issues.push({ type: "bundle-drift", message: "Target asset manifest is missing." });
|
|
5432
5882
|
}
|
|
5433
5883
|
}
|
|
@@ -5445,15 +5895,15 @@ function inspectTarget(options) {
|
|
|
5445
5895
|
};
|
|
5446
5896
|
}
|
|
5447
5897
|
function readTargetAssetHost(targetDir) {
|
|
5448
|
-
const lockfile = readJson3(
|
|
5898
|
+
const lockfile = readJson3(join25(targetDir, ".pro-gov/assets.lock.json"));
|
|
5449
5899
|
return isAssetRegistryHost(lockfile?.host) ? lockfile.host : void 0;
|
|
5450
5900
|
}
|
|
5451
5901
|
function isAssetRegistryHost(value) {
|
|
5452
5902
|
return value === "codex" || value === "claude-code" || value === "gemini-cli" || value === "antigravity";
|
|
5453
5903
|
}
|
|
5454
5904
|
function runTargetChecks(target) {
|
|
5455
|
-
const proGovCli =
|
|
5456
|
-
const docGovCli =
|
|
5905
|
+
const proGovCli = join25(target.path, "node_modules/@pieai/pro-gov/dist/cli.js");
|
|
5906
|
+
const docGovCli = join25(target.path, "node_modules/@pieai/doc-gov/dist/cli.js");
|
|
5457
5907
|
const commands = [
|
|
5458
5908
|
{
|
|
5459
5909
|
name: "pro-gov doctor",
|
|
@@ -5464,8 +5914,8 @@ function runTargetChecks(target) {
|
|
|
5464
5914
|
{ name: "doc-gov scan --check", cli: docGovCli, args: ["scan", "--check"] }
|
|
5465
5915
|
];
|
|
5466
5916
|
return commands.map((command2) => {
|
|
5467
|
-
if (!
|
|
5468
|
-
const result =
|
|
5917
|
+
if (!existsSync24(command2.cli)) return { name: command2.name, status: null };
|
|
5918
|
+
const result = spawnSync7(process.execPath, [command2.cli, ...command2.args], {
|
|
5469
5919
|
cwd: target.path,
|
|
5470
5920
|
encoding: "utf8",
|
|
5471
5921
|
timeout: 3e4
|
|
@@ -5474,13 +5924,13 @@ function runTargetChecks(target) {
|
|
|
5474
5924
|
});
|
|
5475
5925
|
}
|
|
5476
5926
|
function inspectGit(path) {
|
|
5477
|
-
const inside =
|
|
5927
|
+
const inside = spawnSync7("git", ["rev-parse", "--is-inside-work-tree"], {
|
|
5478
5928
|
cwd: path,
|
|
5479
5929
|
encoding: "utf8"
|
|
5480
5930
|
});
|
|
5481
5931
|
if (inside.status !== 0) return { isRepository: false, dirty: false };
|
|
5482
|
-
const status =
|
|
5483
|
-
const branch =
|
|
5932
|
+
const status = spawnSync7("git", ["status", "--porcelain"], { cwd: path, encoding: "utf8" });
|
|
5933
|
+
const branch = spawnSync7("git", ["branch", "--show-current"], { cwd: path, encoding: "utf8" });
|
|
5484
5934
|
return {
|
|
5485
5935
|
isRepository: true,
|
|
5486
5936
|
dirty: status.stdout.trim().length > 0,
|
|
@@ -5505,14 +5955,14 @@ function getExpectedPackageVersions() {
|
|
|
5505
5955
|
function findOwnPackageJson() {
|
|
5506
5956
|
let current = dirname15(fileURLToPath4(import.meta.url));
|
|
5507
5957
|
for (let depth = 0; depth < 5; depth += 1) {
|
|
5508
|
-
const candidate =
|
|
5509
|
-
if (
|
|
5958
|
+
const candidate = join25(current, "package.json");
|
|
5959
|
+
if (existsSync24(candidate)) return candidate;
|
|
5510
5960
|
current = dirname15(current);
|
|
5511
5961
|
}
|
|
5512
5962
|
return "";
|
|
5513
5963
|
}
|
|
5514
5964
|
function readJson3(path) {
|
|
5515
|
-
if (!path || !
|
|
5965
|
+
if (!path || !existsSync24(path)) return void 0;
|
|
5516
5966
|
try {
|
|
5517
5967
|
return JSON.parse(readFileSync16(path, "utf8"));
|
|
5518
5968
|
} catch {
|
|
@@ -5531,18 +5981,18 @@ function deduplicateIssues(issues) {
|
|
|
5531
5981
|
|
|
5532
5982
|
// src/portfolio/ai-health/index.ts
|
|
5533
5983
|
import { homedir as homedir5 } from "node:os";
|
|
5534
|
-
import { dirname as dirname17, join as
|
|
5984
|
+
import { dirname as dirname17, join as join33, resolve as resolve9 } from "node:path";
|
|
5535
5985
|
|
|
5536
5986
|
// src/portfolio/ai-health/entries.ts
|
|
5537
|
-
import { existsSync as
|
|
5538
|
-
import { join as
|
|
5987
|
+
import { existsSync as existsSync26, lstatSync as lstatSync11, realpathSync as realpathSync7 } from "node:fs";
|
|
5988
|
+
import { join as join26 } from "node:path";
|
|
5539
5989
|
|
|
5540
5990
|
// src/portfolio/ai-health/shared.ts
|
|
5541
5991
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
5542
|
-
import { existsSync as
|
|
5992
|
+
import { existsSync as existsSync25, lstatSync as lstatSync10, readFileSync as readFileSync17, readdirSync as readdirSync11, realpathSync as realpathSync6, statSync as statSync7 } from "node:fs";
|
|
5543
5993
|
function safeRealpath(path) {
|
|
5544
5994
|
try {
|
|
5545
|
-
return
|
|
5995
|
+
return realpathSync6(path);
|
|
5546
5996
|
} catch {
|
|
5547
5997
|
return void 0;
|
|
5548
5998
|
}
|
|
@@ -5569,7 +6019,7 @@ function jsonObjectKeys(path, key) {
|
|
|
5569
6019
|
return Object.keys(value[key]).sort();
|
|
5570
6020
|
}
|
|
5571
6021
|
function tomlMcpNames(path) {
|
|
5572
|
-
if (!
|
|
6022
|
+
if (!existsSync25(path)) return [];
|
|
5573
6023
|
const names = /* @__PURE__ */ new Set();
|
|
5574
6024
|
for (const line of safeRead(path).split(/\r?\n/)) {
|
|
5575
6025
|
const match = line.match(/^\s*\[mcp_servers\.(?:"([^"]+)"|([^.\]]+))\]\s*$/);
|
|
@@ -5594,21 +6044,21 @@ function safeRead(path) {
|
|
|
5594
6044
|
}
|
|
5595
6045
|
function safeReadDir(path) {
|
|
5596
6046
|
try {
|
|
5597
|
-
return
|
|
6047
|
+
return readdirSync11(path).sort();
|
|
5598
6048
|
} catch {
|
|
5599
6049
|
return [];
|
|
5600
6050
|
}
|
|
5601
6051
|
}
|
|
5602
6052
|
function safeIsDirectory(path) {
|
|
5603
6053
|
try {
|
|
5604
|
-
return
|
|
6054
|
+
return statSync7(path).isDirectory();
|
|
5605
6055
|
} catch {
|
|
5606
6056
|
return false;
|
|
5607
6057
|
}
|
|
5608
6058
|
}
|
|
5609
6059
|
function pathLexists(path) {
|
|
5610
6060
|
try {
|
|
5611
|
-
|
|
6061
|
+
lstatSync10(path);
|
|
5612
6062
|
return true;
|
|
5613
6063
|
} catch {
|
|
5614
6064
|
return false;
|
|
@@ -5646,15 +6096,15 @@ function hasWorkflowReminderHooks(hooks) {
|
|
|
5646
6096
|
);
|
|
5647
6097
|
}
|
|
5648
6098
|
function inspectEntries(root) {
|
|
5649
|
-
const agentsPath =
|
|
5650
|
-
const agents = !
|
|
5651
|
-
const claudePath =
|
|
6099
|
+
const agentsPath = join26(root, "AGENTS.md");
|
|
6100
|
+
const agents = !existsSync26(agentsPath) ? "missing" : safeRead(agentsPath).includes("PGS-ROUTER:BEGIN") ? "pgs-router" : "custom";
|
|
6101
|
+
const claudePath = join26(root, "CLAUDE.md");
|
|
5652
6102
|
let claude = "missing";
|
|
5653
6103
|
if (pathLexists(claudePath)) {
|
|
5654
|
-
const info =
|
|
6104
|
+
const info = lstatSync11(claudePath);
|
|
5655
6105
|
if (info.isSymbolicLink()) {
|
|
5656
6106
|
try {
|
|
5657
|
-
claude =
|
|
6107
|
+
claude = realpathSync7(claudePath) === realpathSync7(agentsPath) ? "agents-symlink" : "custom";
|
|
5658
6108
|
} catch {
|
|
5659
6109
|
claude = "dangling-symlink";
|
|
5660
6110
|
}
|
|
@@ -5665,13 +6115,48 @@ function inspectEntries(root) {
|
|
|
5665
6115
|
}
|
|
5666
6116
|
return { agents, claude, gemini: inspectOptionalEntry(root, "GEMINI.md", agentsPath) };
|
|
5667
6117
|
}
|
|
6118
|
+
var AGENT_LINK_ROOTS = [".agents/workflows", ".agents/commands", ".claude/commands"];
|
|
6119
|
+
function inspectAgentLinks(root) {
|
|
6120
|
+
const entries = AGENT_LINK_ROOTS.flatMap((directory) => {
|
|
6121
|
+
const directoryPath = join26(root, directory);
|
|
6122
|
+
if (!pathLexists(directoryPath)) return [];
|
|
6123
|
+
try {
|
|
6124
|
+
if (!lstatSync11(directoryPath).isDirectory()) return [];
|
|
6125
|
+
} catch {
|
|
6126
|
+
return [];
|
|
6127
|
+
}
|
|
6128
|
+
return safeReadDir(directoryPath).filter((name) => !name.startsWith(".")).flatMap((name) => {
|
|
6129
|
+
const relativePath = `${directory}/${name}`;
|
|
6130
|
+
const path = join26(root, relativePath);
|
|
6131
|
+
let stat;
|
|
6132
|
+
try {
|
|
6133
|
+
stat = lstatSync11(path);
|
|
6134
|
+
} catch {
|
|
6135
|
+
return [];
|
|
6136
|
+
}
|
|
6137
|
+
let kind = stat.isSymbolicLink() ? "symlink" : stat.isDirectory() ? "directory" : "file";
|
|
6138
|
+
if (stat.isSymbolicLink()) {
|
|
6139
|
+
try {
|
|
6140
|
+
realpathSync7(path);
|
|
6141
|
+
} catch {
|
|
6142
|
+
kind = "dangling-symlink";
|
|
6143
|
+
}
|
|
6144
|
+
}
|
|
6145
|
+
return [{ path: relativePath, kind, tracked: gitTracks(root, relativePath) }];
|
|
6146
|
+
});
|
|
6147
|
+
});
|
|
6148
|
+
return {
|
|
6149
|
+
entries,
|
|
6150
|
+
trackedDangling: entries.filter((entry) => entry.tracked && entry.kind === "dangling-symlink").map((entry) => entry.path)
|
|
6151
|
+
};
|
|
6152
|
+
}
|
|
5668
6153
|
function inspectOptionalEntry(root, filename, agentsPath) {
|
|
5669
|
-
const path =
|
|
6154
|
+
const path = join26(root, filename);
|
|
5670
6155
|
if (!pathLexists(path)) return "missing";
|
|
5671
|
-
const info =
|
|
6156
|
+
const info = lstatSync11(path);
|
|
5672
6157
|
if (info.isSymbolicLink()) {
|
|
5673
6158
|
try {
|
|
5674
|
-
return
|
|
6159
|
+
return realpathSync7(path) === realpathSync7(agentsPath) ? "agents-symlink" : "custom";
|
|
5675
6160
|
} catch {
|
|
5676
6161
|
return "dangling-symlink";
|
|
5677
6162
|
}
|
|
@@ -5706,7 +6191,7 @@ function inspectHooks(root) {
|
|
|
5706
6191
|
{ host: "codex", path: ".codex/hooks.json" }
|
|
5707
6192
|
];
|
|
5708
6193
|
return configs.map((config) => {
|
|
5709
|
-
const value = readJson4(
|
|
6194
|
+
const value = readJson4(join26(root, config.path));
|
|
5710
6195
|
const counts = /* @__PURE__ */ new Map();
|
|
5711
6196
|
collectHookEvents(value, counts);
|
|
5712
6197
|
return {
|
|
@@ -5727,18 +6212,18 @@ function collectHookEvents(value, counts) {
|
|
|
5727
6212
|
}
|
|
5728
6213
|
}
|
|
5729
6214
|
function inspectDocs(root, expected) {
|
|
5730
|
-
const packageJson = readJson4(
|
|
6215
|
+
const packageJson = readJson4(join26(root, "package.json"));
|
|
5731
6216
|
const dependencies = isRecord3(packageJson) ? { ...recordOrEmpty(packageJson.dependencies), ...recordOrEmpty(packageJson.devDependencies) } : {};
|
|
5732
6217
|
const docGov = dependencyVersion(dependencies["@pieai/doc-gov"]);
|
|
5733
6218
|
const proGov = dependencyVersion(dependencies["@pieai/pro-gov"]);
|
|
5734
|
-
const routerMatch = safeRead(
|
|
6219
|
+
const routerMatch = safeRead(join26(root, "AGENTS.md")).match(/PGS-ROUTER:BEGIN\s+v([0-9.]+)/);
|
|
5735
6220
|
const declared = [docGov, proGov].filter((value) => Boolean(value));
|
|
5736
6221
|
return {
|
|
5737
6222
|
routerVersion: routerMatch?.[1],
|
|
5738
6223
|
expectedRouterVersion: CURRENT_ROUTER_VERSION,
|
|
5739
6224
|
routerAligned: routerMatch?.[1] === CURRENT_ROUTER_VERSION,
|
|
5740
|
-
manifest:
|
|
5741
|
-
currentWork:
|
|
6225
|
+
manifest: existsSync26(join26(root, "docs/governance/MANIFEST.yml")),
|
|
6226
|
+
currentWork: existsSync26(join26(root, "docs/reference/execution/current-work.md")),
|
|
5742
6227
|
packages: {
|
|
5743
6228
|
expected,
|
|
5744
6229
|
docGov,
|
|
@@ -5801,18 +6286,18 @@ function inspectGit2(root) {
|
|
|
5801
6286
|
|
|
5802
6287
|
// src/portfolio/ai-health/hosts.ts
|
|
5803
6288
|
import { execFileSync as execFileSync5 } from "node:child_process";
|
|
5804
|
-
import { existsSync as
|
|
5805
|
-
import { join as
|
|
6289
|
+
import { existsSync as existsSync28 } from "node:fs";
|
|
6290
|
+
import { join as join28, resolve as resolve8, sep as sep3 } from "node:path";
|
|
5806
6291
|
|
|
5807
6292
|
// src/portfolio/ai-health/devspace.ts
|
|
5808
6293
|
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
5809
|
-
import { existsSync as
|
|
5810
|
-
import { join as
|
|
6294
|
+
import { existsSync as existsSync27, statSync as statSync8 } from "node:fs";
|
|
6295
|
+
import { join as join27, relative as relative10, resolve as resolve7, sep as sep2 } from "node:path";
|
|
5811
6296
|
function inspectDevSpaceHealth(options) {
|
|
5812
6297
|
const run = options.run ?? runDevSpaceCommand;
|
|
5813
|
-
const configDirectory =
|
|
5814
|
-
const configPath =
|
|
5815
|
-
const authPath =
|
|
6298
|
+
const configDirectory = join27(options.homeDir, ".devspace");
|
|
6299
|
+
const configPath = join27(configDirectory, "config.json");
|
|
6300
|
+
const authPath = join27(configDirectory, "auth.json");
|
|
5816
6301
|
const installedResult = run("devspace", ["--version"], 3e3);
|
|
5817
6302
|
const installedVersion = installedResult.ok ? installedResult.stdout.match(/\b\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?\b/)?.[0] : void 0;
|
|
5818
6303
|
const latestResult = run(
|
|
@@ -5835,9 +6320,9 @@ function inspectDevSpaceHealth(options) {
|
|
|
5835
6320
|
(repositoryPath) => allowedRoots.some((root) => isPathInside(repositoryPath, root))
|
|
5836
6321
|
) ? "complete" : "partial";
|
|
5837
6322
|
const bind = configExists && typeof configValue.host === "string" ? isLoopbackHost(configValue.host) ? "loopback" : "non-loopback" : "unknown";
|
|
5838
|
-
const directoryMode =
|
|
5839
|
-
const fileMode =
|
|
5840
|
-
const authMode =
|
|
6323
|
+
const directoryMode = existsSync27(configDirectory) ? modeString(statSync8(configDirectory).mode) : void 0;
|
|
6324
|
+
const fileMode = existsSync27(configPath) ? modeString(statSync8(configPath).mode) : void 0;
|
|
6325
|
+
const authMode = existsSync27(authPath) ? modeString(statSync8(authPath).mode) : void 0;
|
|
5841
6326
|
const update = installedVersion && latestVersion ? installedVersion === latestVersion ? "current" : "available" : "unknown";
|
|
5842
6327
|
const recommendations = [];
|
|
5843
6328
|
let status = "healthy";
|
|
@@ -5851,7 +6336,7 @@ function inspectDevSpaceHealth(options) {
|
|
|
5851
6336
|
};
|
|
5852
6337
|
if (!installedResult.ok) unhealthy("\u672C\u673A\u672A\u53D1\u73B0 DevSpace\uFF1B\u65E0\u6CD5\u4F7F\u7528\u5BBF\u4E3B\u5DE5\u4F5C\u533A\u670D\u52A1\u3002");
|
|
5853
6338
|
if (!configExists) unhealthy("\u7F3A\u5C11 ~/.devspace/config.json\u3002");
|
|
5854
|
-
if (!
|
|
6339
|
+
if (!existsSync27(authPath)) unhealthy("\u7F3A\u5C11 ~/.devspace/auth.json\u3002");
|
|
5855
6340
|
if (directoryMode && directoryMode !== "700")
|
|
5856
6341
|
unhealthy(`~/.devspace \u76EE\u5F55\u6743\u9650\u4E3A ${directoryMode}\uFF0C\u5E94\u6536\u7D27\u4E3A 700\u3002`);
|
|
5857
6342
|
if (fileMode && fileMode !== "600") unhealthy(`DevSpace \u914D\u7F6E\u6587\u4EF6\u6743\u9650\u4E3A ${fileMode}\uFF0C\u5E94\u4E3A 600\u3002`);
|
|
@@ -5884,7 +6369,7 @@ function inspectDevSpaceHealth(options) {
|
|
|
5884
6369
|
exists: configExists,
|
|
5885
6370
|
...directoryMode ? { directoryMode } : {},
|
|
5886
6371
|
...fileMode ? { fileMode } : {},
|
|
5887
|
-
authExists:
|
|
6372
|
+
authExists: existsSync27(authPath),
|
|
5888
6373
|
...authMode ? { authMode } : {},
|
|
5889
6374
|
bind,
|
|
5890
6375
|
portValid: configExists && typeof configValue.port === "number" && Number.isInteger(configValue.port) && configValue.port > 0 && configValue.port <= 65535,
|
|
@@ -5913,8 +6398,8 @@ function isLoopbackHost(host) {
|
|
|
5913
6398
|
return ["127.0.0.1", "localhost", "::1"].includes(host.trim().toLowerCase());
|
|
5914
6399
|
}
|
|
5915
6400
|
function isPathInside(path, root) {
|
|
5916
|
-
const fromRoot =
|
|
5917
|
-
return fromRoot === "" || fromRoot !== ".." && !fromRoot.startsWith(`..${
|
|
6401
|
+
const fromRoot = relative10(resolve7(root), resolve7(path));
|
|
6402
|
+
return fromRoot === "" || fromRoot !== ".." && !fromRoot.startsWith(`..${sep2}`);
|
|
5918
6403
|
}
|
|
5919
6404
|
|
|
5920
6405
|
// src/portfolio/ai-health/hosts.ts
|
|
@@ -5931,9 +6416,9 @@ var MCP_DISCOVERY_PATHS = {
|
|
|
5931
6416
|
}
|
|
5932
6417
|
};
|
|
5933
6418
|
function inspectHostEnvironment(homeDir, grokVersion, repositoryPaths, devspaceSettings) {
|
|
5934
|
-
const codexConfig =
|
|
5935
|
-
const claudeConfig =
|
|
5936
|
-
const grokConfig =
|
|
6419
|
+
const codexConfig = join28(homeDir, MCP_DISCOVERY_PATHS.user.codex);
|
|
6420
|
+
const claudeConfig = join28(homeDir, MCP_DISCOVERY_PATHS.user.claudeCode);
|
|
6421
|
+
const grokConfig = join28(homeDir, MCP_DISCOVERY_PATHS.user.grok);
|
|
5937
6422
|
const hostEnvironment = {
|
|
5938
6423
|
mcp: {
|
|
5939
6424
|
codexUser: { path: codexConfig, names: tomlMcpNames(codexConfig) },
|
|
@@ -5941,11 +6426,11 @@ function inspectHostEnvironment(homeDir, grokVersion, repositoryPaths, devspaceS
|
|
|
5941
6426
|
grokUser: { path: grokConfig, names: tomlMcpNames(grokConfig) }
|
|
5942
6427
|
},
|
|
5943
6428
|
skills: {
|
|
5944
|
-
codexUser: inspectSkillRoot(
|
|
5945
|
-
claudeCodeUser: inspectSkillRoot(
|
|
5946
|
-
grokUser: inspectSkillRoot(
|
|
5947
|
-
grokAgentsCompatibility: inspectSkillRoot(
|
|
5948
|
-
grokClaudeCompatibility: inspectSkillRoot(
|
|
6429
|
+
codexUser: inspectSkillRoot(join28(homeDir, ".agents/skills")),
|
|
6430
|
+
claudeCodeUser: inspectSkillRoot(join28(homeDir, ".claude/skills")),
|
|
6431
|
+
grokUser: inspectSkillRoot(join28(homeDir, ".grok/skills")),
|
|
6432
|
+
grokAgentsCompatibility: inspectSkillRoot(join28(homeDir, ".agents/skills")),
|
|
6433
|
+
grokClaudeCompatibility: inspectSkillRoot(join28(homeDir, ".claude/skills")),
|
|
5949
6434
|
ssot: inspectUserSkillsSsot(homeDir)
|
|
5950
6435
|
},
|
|
5951
6436
|
grok: {
|
|
@@ -5966,13 +6451,13 @@ function inspectHostEnvironment(homeDir, grokVersion, repositoryPaths, devspaceS
|
|
|
5966
6451
|
function inspectSkillRoot(path) {
|
|
5967
6452
|
const exists = pathLexists(path) && safeIsDirectory(path);
|
|
5968
6453
|
const names = exists ? safeReadDir(path).filter(
|
|
5969
|
-
(name) => !name.startsWith(".") &&
|
|
6454
|
+
(name) => !name.startsWith(".") && existsSync28(join28(path, name, "SKILL.md"))
|
|
5970
6455
|
) : [];
|
|
5971
6456
|
return { path, exists, names };
|
|
5972
6457
|
}
|
|
5973
6458
|
function claudeProjectLocalMcpNames(homeDir, root) {
|
|
5974
6459
|
if (!homeDir) return [];
|
|
5975
|
-
const value = readJson4(
|
|
6460
|
+
const value = readJson4(join28(homeDir, MCP_DISCOVERY_PATHS.user.claudeCode));
|
|
5976
6461
|
if (!isRecord3(value) || !isRecord3(value.projects)) return [];
|
|
5977
6462
|
const candidates = new Set(
|
|
5978
6463
|
[resolve8(root), safeRealpath(root)].filter((path) => Boolean(path))
|
|
@@ -6002,7 +6487,7 @@ function inspectGrokProject(root, homeDir, grokVersion) {
|
|
|
6002
6487
|
execFileSync5("grok", ["inspect", "--json"], {
|
|
6003
6488
|
cwd: root,
|
|
6004
6489
|
encoding: "utf8",
|
|
6005
|
-
env: { ...process.env, HOME: homeDir, GROK_HOME:
|
|
6490
|
+
env: { ...process.env, HOME: homeDir, GROK_HOME: join28(homeDir, ".grok") },
|
|
6006
6491
|
maxBuffer: 10 * 1024 * 1024,
|
|
6007
6492
|
stdio: ["ignore", "pipe", "ignore"],
|
|
6008
6493
|
timeout: 8e3
|
|
@@ -6010,7 +6495,7 @@ function inspectGrokProject(root, homeDir, grokVersion) {
|
|
|
6010
6495
|
);
|
|
6011
6496
|
if (!isRecord3(value)) return empty("failed");
|
|
6012
6497
|
const userClaudeNames = new Set(
|
|
6013
|
-
jsonObjectKeys(
|
|
6498
|
+
jsonObjectKeys(join28(homeDir, MCP_DISCOVERY_PATHS.user.claudeCode), "mcpServers")
|
|
6014
6499
|
);
|
|
6015
6500
|
const localClaudeNames = new Set(claudeProjectLocalMcpNames(homeDir, root));
|
|
6016
6501
|
const effectiveMcp = Array.isArray(value.mcpServers) ? value.mcpServers.flatMap((item) => {
|
|
@@ -6043,7 +6528,7 @@ function inspectGrokProject(root, homeDir, grokVersion) {
|
|
|
6043
6528
|
if (!isRecord3(item) || !isRecord3(item.source)) continue;
|
|
6044
6529
|
const type = item.source.type;
|
|
6045
6530
|
const path = item.source.path;
|
|
6046
|
-
if (typeof path === "string" && path.includes(
|
|
6531
|
+
if (typeof path === "string" && path.includes(sep3 + ".grok" + sep3 + "bundled" + sep3))
|
|
6047
6532
|
skillCounts.bundled += 1;
|
|
6048
6533
|
else if (type === "project") skillCounts.project += 1;
|
|
6049
6534
|
else if (type === "plugin") skillCounts.plugin += 1;
|
|
@@ -6080,23 +6565,23 @@ function inferGrokMcpScope(name, sourceType, sourcePath, root, homeDir, userClau
|
|
|
6080
6565
|
}
|
|
6081
6566
|
const resolvedSource = safeRealpath(sourcePath) ?? resolve8(sourcePath);
|
|
6082
6567
|
const resolvedRoot = safeRealpath(root) ?? resolve8(root);
|
|
6083
|
-
if (resolvedSource ===
|
|
6568
|
+
if (resolvedSource === join28(resolvedRoot, MCP_DISCOVERY_PATHS.project.claudeCodeShared))
|
|
6084
6569
|
return "project-shared";
|
|
6085
|
-
if (resolvedSource.startsWith(resolvedRoot +
|
|
6086
|
-
if (homeDir && resolvedSource ===
|
|
6570
|
+
if (resolvedSource.startsWith(resolvedRoot + sep3)) return "project";
|
|
6571
|
+
if (homeDir && resolvedSource === join28(resolve8(homeDir), MCP_DISCOVERY_PATHS.user.claudeCode)) {
|
|
6087
6572
|
if (localClaudeNames.has(name)) return "project-local";
|
|
6088
6573
|
if (userClaudeNames.has(name)) return "user";
|
|
6089
6574
|
}
|
|
6090
|
-
if (homeDir && resolvedSource.startsWith(resolve8(homeDir) +
|
|
6575
|
+
if (homeDir && resolvedSource.startsWith(resolve8(homeDir) + sep3)) return "user";
|
|
6091
6576
|
if (sourceType === "project") return "project";
|
|
6092
6577
|
return "unknown";
|
|
6093
6578
|
}
|
|
6094
6579
|
|
|
6095
6580
|
// src/portfolio/ai-health/secrets.ts
|
|
6096
|
-
import { existsSync as
|
|
6097
|
-
import { join as
|
|
6581
|
+
import { existsSync as existsSync29, lstatSync as lstatSync12, readdirSync as readdirSync12, statSync as statSync9 } from "node:fs";
|
|
6582
|
+
import { join as join29, relative as relative11, sep as sep4 } from "node:path";
|
|
6098
6583
|
function inspectRepositorySecrets(root, id, secretsRoot, isRepository, environmentPolicy) {
|
|
6099
|
-
const centralPath =
|
|
6584
|
+
const centralPath = join29(secretsRoot, id);
|
|
6100
6585
|
const centralRealPath = safeRealpath(centralPath);
|
|
6101
6586
|
const localOnlyReasons = new Map(
|
|
6102
6587
|
(environmentPolicy?.localOnly ?? []).map((entry) => [entry.path, entry.reason])
|
|
@@ -6108,16 +6593,16 @@ function inspectRepositorySecrets(root, id, secretsRoot, isRepository, environme
|
|
|
6108
6593
|
tracked: isRepository ? gitTracks(root, path) : false,
|
|
6109
6594
|
template: isEnvironmentTemplate(path),
|
|
6110
6595
|
fixture: isEnvironmentFixture(path),
|
|
6111
|
-
symlink:
|
|
6112
|
-
centralized: pointsInside(
|
|
6596
|
+
symlink: lstatSync12(join29(root, path)).isSymbolicLink(),
|
|
6597
|
+
centralized: pointsInside(join29(root, path), centralRealPath),
|
|
6113
6598
|
localOnly: localOnlyReason !== void 0,
|
|
6114
6599
|
...localOnlyReason !== void 0 ? { localOnlyReason } : {}
|
|
6115
6600
|
};
|
|
6116
6601
|
});
|
|
6117
6602
|
return {
|
|
6118
|
-
centralDirectory:
|
|
6119
|
-
centralMode:
|
|
6120
|
-
centralFiles:
|
|
6603
|
+
centralDirectory: existsSync29(centralPath) ? "present" : "absent",
|
|
6604
|
+
centralMode: existsSync29(centralPath) ? modeString(statSync9(centralPath).mode) : void 0,
|
|
6605
|
+
centralFiles: existsSync29(centralPath) ? collectCentralSecretFiles(centralPath) : [],
|
|
6121
6606
|
repositoryEnvFiles: envFiles
|
|
6122
6607
|
};
|
|
6123
6608
|
}
|
|
@@ -6140,12 +6625,12 @@ function collectEnvironmentFiles(root, current = root, depth = 0) {
|
|
|
6140
6625
|
if (depth > 5) return [];
|
|
6141
6626
|
const found = [];
|
|
6142
6627
|
try {
|
|
6143
|
-
for (const entry of
|
|
6628
|
+
for (const entry of readdirSync12(current, { withFileTypes: true })) {
|
|
6144
6629
|
if (entry.isDirectory()) {
|
|
6145
6630
|
if (!SKIP_ENV_DIRECTORIES.has(entry.name))
|
|
6146
|
-
found.push(...collectEnvironmentFiles(root,
|
|
6631
|
+
found.push(...collectEnvironmentFiles(root, join29(current, entry.name), depth + 1));
|
|
6147
6632
|
} else if (isEnvironmentFilename(entry.name) && !isProviderGeneratedEnvironmentFile(entry.name)) {
|
|
6148
|
-
found.push(
|
|
6633
|
+
found.push(relative11(root, join29(current, entry.name)));
|
|
6149
6634
|
}
|
|
6150
6635
|
}
|
|
6151
6636
|
} catch {
|
|
@@ -6157,10 +6642,10 @@ function collectCentralSecretFiles(root, current = root, depth = 0) {
|
|
|
6157
6642
|
if (depth > 3) return [];
|
|
6158
6643
|
const found = [];
|
|
6159
6644
|
try {
|
|
6160
|
-
for (const entry of
|
|
6161
|
-
const path =
|
|
6645
|
+
for (const entry of readdirSync12(current, { withFileTypes: true })) {
|
|
6646
|
+
const path = join29(current, entry.name);
|
|
6162
6647
|
if (entry.isDirectory()) found.push(...collectCentralSecretFiles(root, path, depth + 1));
|
|
6163
|
-
else found.push({ path:
|
|
6648
|
+
else found.push({ path: relative11(root, path), mode: modeString(lstatSync12(path).mode) });
|
|
6164
6649
|
}
|
|
6165
6650
|
} catch {
|
|
6166
6651
|
return found;
|
|
@@ -6184,30 +6669,30 @@ function isEnvironmentFixture(path) {
|
|
|
6184
6669
|
return /(^|[\\/])(?:tests?|__tests__)[\\/]fixtures?[\\/]/i.test(path) || /(^|[\\/])__fixtures__[\\/]/i.test(path);
|
|
6185
6670
|
}
|
|
6186
6671
|
function pointsInside(path, expectedRoot) {
|
|
6187
|
-
if (!expectedRoot || !
|
|
6672
|
+
if (!expectedRoot || !lstatSync12(path).isSymbolicLink()) return false;
|
|
6188
6673
|
const target = safeRealpath(path);
|
|
6189
6674
|
if (!target) return false;
|
|
6190
|
-
const fromRoot =
|
|
6191
|
-
return fromRoot === "" || fromRoot !== ".." && !fromRoot.startsWith(`..${
|
|
6675
|
+
const fromRoot = relative11(expectedRoot, target);
|
|
6676
|
+
return fromRoot === "" || fromRoot !== ".." && !fromRoot.startsWith(`..${sep4}`);
|
|
6192
6677
|
}
|
|
6193
6678
|
function hasUnsafeCentralSecretPermissions(secrets) {
|
|
6194
6679
|
return secrets.centralDirectory === "present" && (secrets.centralMode !== "700" || secrets.centralFiles.some((file) => file.mode !== "600"));
|
|
6195
6680
|
}
|
|
6196
6681
|
function inspectSecretsRoot(path) {
|
|
6197
|
-
return
|
|
6682
|
+
return existsSync29(path) ? { path, exists: true, mode: modeString(statSync9(path).mode) } : { path, exists: false };
|
|
6198
6683
|
}
|
|
6199
6684
|
|
|
6200
6685
|
// src/portfolio/ai-health/skills.ts
|
|
6201
|
-
import { existsSync as
|
|
6202
|
-
import { join as
|
|
6686
|
+
import { existsSync as existsSync30, lstatSync as lstatSync13, realpathSync as realpathSync8 } from "node:fs";
|
|
6687
|
+
import { join as join30 } from "node:path";
|
|
6203
6688
|
function countAutomaticSkillsNeedingReview(skills) {
|
|
6204
6689
|
return skills.automatic.filter(
|
|
6205
6690
|
(item) => !item.managed || !item.registryId || item.expectedPlacement !== "auto" || item.expectedScope === "user"
|
|
6206
6691
|
).length;
|
|
6207
6692
|
}
|
|
6208
6693
|
function inspectSkills(root, grokInspection, registeredSkills, userSkills) {
|
|
6209
|
-
const lock = readJson4(
|
|
6210
|
-
const assetManifest = readJson4(
|
|
6694
|
+
const lock = readJson4(join30(root, ".pro-gov/assets.lock.json"));
|
|
6695
|
+
const assetManifest = readJson4(join30(root, ".pro-gov/assets.json"));
|
|
6211
6696
|
const managed = /* @__PURE__ */ new Set();
|
|
6212
6697
|
const bundleIds = stringArray(isRecord3(lock) ? lock.bundleIds : void 0);
|
|
6213
6698
|
if (isRecord3(lock) && Array.isArray(lock.assets)) {
|
|
@@ -6219,7 +6704,7 @@ function inspectSkills(root, grokInspection, registeredSkills, userSkills) {
|
|
|
6219
6704
|
}
|
|
6220
6705
|
const inspectPlacement = (placement) => {
|
|
6221
6706
|
const directory = placement === "auto" ? "skills" : "manual-skills";
|
|
6222
|
-
const skillRoot =
|
|
6707
|
+
const skillRoot = join30(root, ".agents", directory);
|
|
6223
6708
|
if (!pathLexists(skillRoot) || !safeIsDirectory(skillRoot)) return [];
|
|
6224
6709
|
return safeReadDir(skillRoot).filter((name) => !name.startsWith(".")).map((name) => inspectSkillItem(skillRoot, directory, name, managed, registeredSkills));
|
|
6225
6710
|
};
|
|
@@ -6274,24 +6759,24 @@ function inspectSkills(root, grokInspection, registeredSkills, userSkills) {
|
|
|
6274
6759
|
},
|
|
6275
6760
|
hosts: {
|
|
6276
6761
|
codexProject: automatic.filter((item) => item.kind !== "dangling-symlink").length,
|
|
6277
|
-
claudeCodeProject: inspectSkillRoot(
|
|
6278
|
-
grokNativeProject: inspectSkillRoot(
|
|
6762
|
+
claudeCodeProject: inspectSkillRoot(join30(root, ".claude/skills")).names.length,
|
|
6763
|
+
grokNativeProject: inspectSkillRoot(join30(root, ".grok/skills")).names.length,
|
|
6279
6764
|
grokEffective: grokInspection.skills
|
|
6280
6765
|
}
|
|
6281
6766
|
};
|
|
6282
6767
|
}
|
|
6283
6768
|
function inspectSkillItem(skillRoot, directory, name, managed, registeredSkills) {
|
|
6284
|
-
const path =
|
|
6285
|
-
const stat =
|
|
6769
|
+
const path = join30(skillRoot, name);
|
|
6770
|
+
const stat = lstatSync13(path);
|
|
6286
6771
|
let kind = stat.isSymbolicLink() ? "symlink" : stat.isDirectory() ? "directory" : "file";
|
|
6287
6772
|
let realPath;
|
|
6288
6773
|
try {
|
|
6289
|
-
realPath =
|
|
6774
|
+
realPath = realpathSync8(path);
|
|
6290
6775
|
} catch {
|
|
6291
6776
|
if (stat.isSymbolicLink()) kind = "dangling-symlink";
|
|
6292
6777
|
}
|
|
6293
6778
|
const registered = realPath ? registeredSkills.find((skill) => skill.sourceRealPath === realPath) : void 0;
|
|
6294
|
-
const classification = registered ? void 0 : realPath && isPluginPack(realPath) ? "plugin-pack" : kind === "directory" &&
|
|
6779
|
+
const classification = registered ? void 0 : realPath && isPluginPack(realPath) ? "plugin-pack" : kind === "directory" && existsSync30(join30(path, "SKILL.md")) ? "project-local" : void 0;
|
|
6295
6780
|
return {
|
|
6296
6781
|
name,
|
|
6297
6782
|
kind,
|
|
@@ -6303,11 +6788,11 @@ function inspectSkillItem(skillRoot, directory, name, managed, registeredSkills)
|
|
|
6303
6788
|
};
|
|
6304
6789
|
}
|
|
6305
6790
|
function isPluginPack(path) {
|
|
6306
|
-
const skillsRoot =
|
|
6307
|
-
return
|
|
6791
|
+
const skillsRoot = join30(path, "skills");
|
|
6792
|
+
return existsSync30(join30(path, ".codex-plugin/plugin.json")) && safeIsDirectory(skillsRoot) && safeReadDir(skillsRoot).some((name) => existsSync30(join30(skillsRoot, name, "SKILL.md")));
|
|
6308
6793
|
}
|
|
6309
6794
|
function inspectInvalidSkillEntries(root, directory) {
|
|
6310
|
-
const skillRoot =
|
|
6795
|
+
const skillRoot = join30(root, ".agents", directory);
|
|
6311
6796
|
if (!pathLexists(skillRoot) || !safeIsDirectory(skillRoot)) return [];
|
|
6312
6797
|
return safeReadDir(skillRoot).flatMap((name) => {
|
|
6313
6798
|
if (name === ".gitkeep") return [];
|
|
@@ -6315,14 +6800,14 @@ function inspectInvalidSkillEntries(root, directory) {
|
|
|
6315
6800
|
return [{ path: `.agents/${directory}/${name}`, reason: "metadata-junk" }];
|
|
6316
6801
|
if (name.startsWith("."))
|
|
6317
6802
|
return [{ path: `.agents/${directory}/${name}`, reason: "unexpected-file" }];
|
|
6318
|
-
const path =
|
|
6319
|
-
return !
|
|
6803
|
+
const path = join30(skillRoot, name);
|
|
6804
|
+
return !lstatSync13(path).isDirectory() && !lstatSync13(path).isSymbolicLink() ? [{ path: `.agents/${directory}/${name}`, reason: "unexpected-file" }] : [];
|
|
6320
6805
|
});
|
|
6321
6806
|
}
|
|
6322
6807
|
function skillDuplicatesUser(item, root, userSkills) {
|
|
6323
6808
|
if (userSkills.names.has(item.name)) return true;
|
|
6324
|
-
const automatic =
|
|
6325
|
-
const manual =
|
|
6809
|
+
const automatic = join30(root, ".agents/skills", item.name);
|
|
6810
|
+
const manual = join30(root, ".agents/manual-skills", item.name);
|
|
6326
6811
|
const realPath = safeRealpath(pathLexists(automatic) ? automatic : manual);
|
|
6327
6812
|
return realPath ? userSkills.realPaths.has(realPath) : false;
|
|
6328
6813
|
}
|
|
@@ -6342,13 +6827,13 @@ function skillPlacementDrift(item, actualPlacement) {
|
|
|
6342
6827
|
return [];
|
|
6343
6828
|
}
|
|
6344
6829
|
function inspectClaudeSkillRoot(root) {
|
|
6345
|
-
const path =
|
|
6830
|
+
const path = join30(root, ".claude/skills");
|
|
6346
6831
|
if (!pathLexists(path)) return "missing";
|
|
6347
|
-
const stat =
|
|
6832
|
+
const stat = lstatSync13(path);
|
|
6348
6833
|
if (stat.isSymbolicLink()) {
|
|
6349
6834
|
try {
|
|
6350
|
-
const target =
|
|
6351
|
-
return target ===
|
|
6835
|
+
const target = realpathSync8(path);
|
|
6836
|
+
return target === realpathSync8(join30(root, ".agents/skills")) ? "shared-root" : "other";
|
|
6352
6837
|
} catch {
|
|
6353
6838
|
return "dangling-symlink";
|
|
6354
6839
|
}
|
|
@@ -6361,27 +6846,27 @@ function inspectSkillRegistry(executionEngineRoot) {
|
|
|
6361
6846
|
health: { source: 0, registered: 0, bundled: 0, bundles: 0 },
|
|
6362
6847
|
skills: []
|
|
6363
6848
|
};
|
|
6364
|
-
const agentAssetsRoot =
|
|
6365
|
-
const registry = readJson4(
|
|
6849
|
+
const agentAssetsRoot = join30(executionEngineRoot, "agent-assets");
|
|
6850
|
+
const registry = readJson4(join30(agentAssetsRoot, "registry.json"));
|
|
6366
6851
|
const assets = isRecord3(registry) && Array.isArray(registry.assets) ? registry.assets : [];
|
|
6367
6852
|
const registeredSkills = assets.filter((asset) => isRecord3(asset) && asset.kind === "skill");
|
|
6368
|
-
const bundleRoot =
|
|
6853
|
+
const bundleRoot = join30(agentAssetsRoot, "bundles");
|
|
6369
6854
|
const bundleFiles = safeReadDir(bundleRoot).filter((file) => file.endsWith(".json"));
|
|
6370
6855
|
const bundledIds = /* @__PURE__ */ new Set();
|
|
6371
6856
|
for (const file of bundleFiles) {
|
|
6372
|
-
const bundle = readJson4(
|
|
6857
|
+
const bundle = readJson4(join30(bundleRoot, file));
|
|
6373
6858
|
if (!isRecord3(bundle) || !Array.isArray(bundle.assets)) continue;
|
|
6374
6859
|
for (const id of bundle.assets) if (typeof id === "string") bundledIds.add(id);
|
|
6375
6860
|
}
|
|
6376
6861
|
const sourceRoots = [
|
|
6377
|
-
|
|
6378
|
-
|
|
6862
|
+
join30(agentAssetsRoot, "skills/pie-skills"),
|
|
6863
|
+
join30(agentAssetsRoot, "skills/npx-skills/.agents/skills")
|
|
6379
6864
|
];
|
|
6380
6865
|
const source = sourceRoots.reduce(
|
|
6381
|
-
(count, root) => count + safeReadDir(root).filter((name) =>
|
|
6866
|
+
(count, root) => count + safeReadDir(root).filter((name) => existsSync30(join30(root, name, "SKILL.md"))).length,
|
|
6382
6867
|
0
|
|
6383
6868
|
) + registeredSkills.filter(
|
|
6384
|
-
(asset) => isRecord3(asset) && asset.sourceKind === "local-pack" && typeof asset.sourcePath === "string" && isPluginPack(
|
|
6869
|
+
(asset) => isRecord3(asset) && asset.sourceKind === "local-pack" && typeof asset.sourcePath === "string" && isPluginPack(join30(agentAssetsRoot, asset.sourcePath))
|
|
6385
6870
|
).length;
|
|
6386
6871
|
return {
|
|
6387
6872
|
health: {
|
|
@@ -6398,7 +6883,7 @@ function inspectSkillRegistry(executionEngineRoot) {
|
|
|
6398
6883
|
return [
|
|
6399
6884
|
{
|
|
6400
6885
|
id: asset.id,
|
|
6401
|
-
sourceRealPath: safeRealpath(
|
|
6886
|
+
sourceRealPath: safeRealpath(join30(agentAssetsRoot, asset.sourcePath)),
|
|
6402
6887
|
defaultPlacement: asset.defaultPlacement,
|
|
6403
6888
|
defaultScope: asset.defaultScope === "user" ? "user" : "project"
|
|
6404
6889
|
}
|
|
@@ -6413,21 +6898,27 @@ function inspectUserSkillEvidence(root) {
|
|
|
6413
6898
|
for (const name of safeReadDir(root)) {
|
|
6414
6899
|
if (name.startsWith(".")) continue;
|
|
6415
6900
|
names.add(name);
|
|
6416
|
-
const realPath = safeRealpath(
|
|
6901
|
+
const realPath = safeRealpath(join30(root, name));
|
|
6417
6902
|
if (realPath) realPaths.add(realPath);
|
|
6418
6903
|
}
|
|
6419
6904
|
return { names, realPaths };
|
|
6420
6905
|
}
|
|
6421
6906
|
|
|
6422
6907
|
// src/portfolio/ai-health/technology.ts
|
|
6423
|
-
import { existsSync as
|
|
6424
|
-
import { join as
|
|
6908
|
+
import { existsSync as existsSync31, readdirSync as readdirSync13, statSync as statSync10 } from "node:fs";
|
|
6909
|
+
import { join as join31 } from "node:path";
|
|
6425
6910
|
function buildTechnologyMatrix(governance, repositories) {
|
|
6426
|
-
if (!governance) return [];
|
|
6911
|
+
if (!governance || governance.technologies.length === 0) return [];
|
|
6427
6912
|
const policy = governance.versionPolicy;
|
|
6913
|
+
const packageManifestsByRepository = /* @__PURE__ */ new Map();
|
|
6914
|
+
for (const repository of repositories) {
|
|
6915
|
+
if (!packageManifestsByRepository.has(repository.path)) {
|
|
6916
|
+
packageManifestsByRepository.set(repository.path, collectPackageManifests(repository.path));
|
|
6917
|
+
}
|
|
6918
|
+
}
|
|
6428
6919
|
return governance.technologies.map((technology) => {
|
|
6429
6920
|
const projects = repositories.flatMap((repository) => {
|
|
6430
|
-
const packageManifests =
|
|
6921
|
+
const packageManifests = packageManifestsByRepository.get(repository.path) ?? [];
|
|
6431
6922
|
const packageSignals = (technology.packages ?? []).map((name) => {
|
|
6432
6923
|
const requirement = policy?.packages.find((item) => item.name === name);
|
|
6433
6924
|
return packageManifests.map((manifest) => {
|
|
@@ -6442,8 +6933,8 @@ function buildTechnologyMatrix(governance, repositories) {
|
|
|
6442
6933
|
}).filter((item) => item !== void 0);
|
|
6443
6934
|
}).flat();
|
|
6444
6935
|
const fileSignal = (technology.files ?? []).some(
|
|
6445
|
-
(path) => hasUsableTechnologyFile(
|
|
6446
|
-
(manifest) => hasUsableTechnologyFile(
|
|
6936
|
+
(path) => hasUsableTechnologyFile(join31(repository.path, path)) || packageManifests.some(
|
|
6937
|
+
(manifest) => hasUsableTechnologyFile(join31(manifest.directory, path))
|
|
6447
6938
|
)
|
|
6448
6939
|
);
|
|
6449
6940
|
const modelSignal = [
|
|
@@ -6534,14 +7025,14 @@ function buildTechnologyMatrix(governance, repositories) {
|
|
|
6534
7025
|
}).filter((technology) => technology.projectCount > 0);
|
|
6535
7026
|
}
|
|
6536
7027
|
function hasUsableTechnologyFile(path) {
|
|
6537
|
-
if (!
|
|
7028
|
+
if (!existsSync31(path)) return false;
|
|
6538
7029
|
try {
|
|
6539
|
-
const info =
|
|
7030
|
+
const info = statSync10(path);
|
|
6540
7031
|
if (info.isFile()) return true;
|
|
6541
7032
|
if (!info.isDirectory()) return false;
|
|
6542
|
-
return
|
|
7033
|
+
return readdirSync13(path, { withFileTypes: true }).some((entry) => {
|
|
6543
7034
|
if (entry.name.startsWith(".")) return false;
|
|
6544
|
-
const child =
|
|
7035
|
+
const child = join31(path, entry.name);
|
|
6545
7036
|
if (entry.isDirectory()) return hasUsableTechnologyFile(child);
|
|
6546
7037
|
return entry.name.toLowerCase() !== "readme.md";
|
|
6547
7038
|
});
|
|
@@ -6553,7 +7044,7 @@ function inspectExclusiveOwnership(root, endpoint, governance) {
|
|
|
6553
7044
|
const projectType = endpoint.projectType;
|
|
6554
7045
|
return (governance?.exclusiveOwnership ?? []).flatMap((rule) => {
|
|
6555
7046
|
if (projectType && rule.allowedProjectTypes.includes(projectType)) return [];
|
|
6556
|
-
const paths = rule.paths.filter((path) =>
|
|
7047
|
+
const paths = rule.paths.filter((path) => existsSync31(join31(root, path)));
|
|
6557
7048
|
return paths.length > 0 ? [{ rule, paths }] : [];
|
|
6558
7049
|
});
|
|
6559
7050
|
}
|
|
@@ -6568,7 +7059,7 @@ function inspectProjectModel(root, endpoint, governance) {
|
|
|
6568
7059
|
const detection = (id) => {
|
|
6569
7060
|
const technology = technologyById.get(id);
|
|
6570
7061
|
const packageMatch = technology?.packages?.some((name) => packages.has(name)) ?? false;
|
|
6571
|
-
const fileMatch = technology?.files?.some((path) =>
|
|
7062
|
+
const fileMatch = technology?.files?.some((path) => existsSync31(join31(root, path))) ?? false;
|
|
6572
7063
|
return { id, label: technology?.label ?? id, detected: packageMatch || fileMatch };
|
|
6573
7064
|
};
|
|
6574
7065
|
const selected = new Set(endpoint.capabilities ?? []);
|
|
@@ -6607,8 +7098,8 @@ function collectPackageNames(root) {
|
|
|
6607
7098
|
}
|
|
6608
7099
|
|
|
6609
7100
|
// src/portfolio/ai-health/report.ts
|
|
6610
|
-
import { cpSync as cpSync3, existsSync as
|
|
6611
|
-
import { dirname as dirname16, join as
|
|
7101
|
+
import { cpSync as cpSync3, existsSync as existsSync32, mkdirSync as mkdirSync10, writeFileSync as writeFileSync9 } from "node:fs";
|
|
7102
|
+
import { dirname as dirname16, join as join32 } from "node:path";
|
|
6612
7103
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
6613
7104
|
function mergePortfolioAiHealthReport(existing, latest, allRepositoryIds) {
|
|
6614
7105
|
const repositoriesById = /* @__PURE__ */ new Map();
|
|
@@ -6652,16 +7143,16 @@ function writePortfolioAiHealthReport(report, outDir) {
|
|
|
6652
7143
|
mkdirSync10(outDir, { recursive: true });
|
|
6653
7144
|
const dashboardAssets = findDashboardAssets();
|
|
6654
7145
|
for (const file of ["index.html", "app.js", "app.css"]) {
|
|
6655
|
-
const source =
|
|
6656
|
-
if (!
|
|
6657
|
-
cpSync3(source,
|
|
7146
|
+
const source = join32(dashboardAssets, file);
|
|
7147
|
+
if (!existsSync32(source)) throw new Error(`Portfolio dashboard asset is missing: ${source}`);
|
|
7148
|
+
cpSync3(source, join32(outDir, file));
|
|
6658
7149
|
}
|
|
6659
|
-
const jsonPath =
|
|
6660
|
-
const htmlPath =
|
|
7150
|
+
const jsonPath = join32(outDir, "portfolio-ai-health.json");
|
|
7151
|
+
const htmlPath = join32(outDir, "index.html");
|
|
6661
7152
|
writeFileSync9(jsonPath, `${JSON.stringify(report, null, 2)}
|
|
6662
7153
|
`);
|
|
6663
7154
|
writeFileSync9(
|
|
6664
|
-
|
|
7155
|
+
join32(outDir, "data.js"),
|
|
6665
7156
|
`window.__PORTFOLIO_AI_HEALTH__ = ${safeJavaScriptJson2(report)};
|
|
6666
7157
|
`
|
|
6667
7158
|
);
|
|
@@ -6671,14 +7162,14 @@ function findDashboardAssets() {
|
|
|
6671
7162
|
const packageRoot2 = dirname16(dirname16(fileURLToPath5(import.meta.url)));
|
|
6672
7163
|
const candidates = [
|
|
6673
7164
|
process.env.PGS_DASHBOARD_ASSETS_DIR,
|
|
6674
|
-
|
|
6675
|
-
|
|
6676
|
-
|
|
6677
|
-
|
|
6678
|
-
|
|
6679
|
-
|
|
7165
|
+
join32(packageRoot2, ".dashboard-build"),
|
|
7166
|
+
join32(packageRoot2, "assets/portfolio-dashboard"),
|
|
7167
|
+
join32(process.cwd(), ".dashboard-build"),
|
|
7168
|
+
join32(process.cwd(), "assets/portfolio-dashboard"),
|
|
7169
|
+
join32(process.cwd(), "packages/pro-gov/.dashboard-build"),
|
|
7170
|
+
join32(process.cwd(), "packages/pro-gov/assets/portfolio-dashboard")
|
|
6680
7171
|
].filter((value) => Boolean(value));
|
|
6681
|
-
const match = candidates.find((path) =>
|
|
7172
|
+
const match = candidates.find((path) => existsSync32(join32(path, "index.html")));
|
|
6682
7173
|
if (!match)
|
|
6683
7174
|
throw new Error(
|
|
6684
7175
|
"Portfolio dashboard assets were not built. Run pnpm --filter @pieai/pro-gov build."
|
|
@@ -6696,7 +7187,7 @@ function inspectPortfolioAiHealth(options) {
|
|
|
6696
7187
|
if (options.targetId && options.targetId !== "all" && endpoints.length === 0) {
|
|
6697
7188
|
throw new Error(`Unknown portfolio target: ${options.targetId}`);
|
|
6698
7189
|
}
|
|
6699
|
-
const secretsRoot = options.secretsRoot ??
|
|
7190
|
+
const secretsRoot = options.secretsRoot ?? join33(
|
|
6700
7191
|
dirname17(
|
|
6701
7192
|
options.manifest.controlPlane?.path ?? allEndpoints[0]?.endpoint.path ?? process.cwd()
|
|
6702
7193
|
),
|
|
@@ -6706,9 +7197,9 @@ function inspectPortfolioAiHealth(options) {
|
|
|
6706
7197
|
const grokVersion = commandVersion("grok");
|
|
6707
7198
|
const executionEngineRoot = options.manifest.executionEngine?.path;
|
|
6708
7199
|
const skillRegistry = inspectSkillRegistry(executionEngineRoot);
|
|
6709
|
-
const userSkills = inspectUserSkillEvidence(
|
|
7200
|
+
const userSkills = inspectUserSkillEvidence(join33(homeDir, ".agents/skills"));
|
|
6710
7201
|
const expectedPackageVersion = packageVersion(
|
|
6711
|
-
|
|
7202
|
+
join33(executionEngineRoot ?? "", "packages/pro-gov/package.json")
|
|
6712
7203
|
);
|
|
6713
7204
|
const repositories = endpoints.map(
|
|
6714
7205
|
({ endpoint, role }) => inspectRepository(
|
|
@@ -6726,7 +7217,7 @@ function inspectPortfolioAiHealth(options) {
|
|
|
6726
7217
|
const summary = { healthy: 0, attention: 0, unhealthy: 0 };
|
|
6727
7218
|
for (const repository of repositories) summary[repository.status] += 1;
|
|
6728
7219
|
return {
|
|
6729
|
-
schemaVersion:
|
|
7220
|
+
schemaVersion: 8,
|
|
6730
7221
|
portfolioId: options.manifest.portfolioId,
|
|
6731
7222
|
generatedAt: options.generatedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
6732
7223
|
coverage: {
|
|
@@ -6775,19 +7266,20 @@ function inspectRepository(endpoint, role, secretsRoot, homeDir, expectedPackage
|
|
|
6775
7266
|
const root = endpoint.path;
|
|
6776
7267
|
const git = inspectGit2(root);
|
|
6777
7268
|
const entries = inspectEntries(root);
|
|
7269
|
+
const agentLinks = inspectAgentLinks(root);
|
|
6778
7270
|
const grokInspection = inspectGrokProject(root, homeDir, grokVersion);
|
|
6779
7271
|
const skills = inspectSkills(root, grokInspection, registeredSkills, userSkills);
|
|
6780
7272
|
const hostSsot = inspectProjectHostSsot(root);
|
|
6781
7273
|
const hooks = inspectHooks(root);
|
|
6782
7274
|
const docs = inspectDocs(root, role === "execution-engine" ? void 0 : expectedPackageVersion);
|
|
6783
7275
|
const mcp = {
|
|
6784
|
-
codexProject: tomlMcpNames(
|
|
7276
|
+
codexProject: tomlMcpNames(join33(root, MCP_DISCOVERY_PATHS.project.codex)),
|
|
6785
7277
|
claudeCodeProjectShared: jsonObjectKeys(
|
|
6786
|
-
|
|
7278
|
+
join33(root, MCP_DISCOVERY_PATHS.project.claudeCodeShared),
|
|
6787
7279
|
"mcpServers"
|
|
6788
7280
|
),
|
|
6789
7281
|
claudeCodeProjectLocal: claudeProjectLocalMcpNames(homeDir, root),
|
|
6790
|
-
grokProject: tomlMcpNames(
|
|
7282
|
+
grokProject: tomlMcpNames(join33(root, MCP_DISCOVERY_PATHS.project.grok)),
|
|
6791
7283
|
grokEffective: grokInspection.effectiveMcp,
|
|
6792
7284
|
grokInspection: grokInspection.inspection
|
|
6793
7285
|
};
|
|
@@ -6855,6 +7347,10 @@ function inspectRepository(endpoint, role, secretsRoot, homeDir, expectedPackage
|
|
|
6855
7347
|
recommendations.push("GEMINI.md \u662F\u65AD\u5F00\u7684\u94FE\u63A5\uFF1B\u9700\u8981\u91CD\u65B0\u6307\u5411 AGENTS.md\u3002");
|
|
6856
7348
|
if ([...skills.automatic, ...skills.manual].some((skill) => skill.kind === "dangling-symlink"))
|
|
6857
7349
|
recommendations.push("`.agents/skills` \u6216 `.agents/manual-skills` \u4E2D\u5B58\u5728\u65AD\u5F00\u7684\u6280\u80FD\u94FE\u63A5\u3002");
|
|
7350
|
+
if (agentLinks.trackedDangling.length > 0)
|
|
7351
|
+
recommendations.push(
|
|
7352
|
+
`\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`
|
|
7353
|
+
);
|
|
6858
7354
|
if (skills.invalidEntries.length > 0)
|
|
6859
7355
|
recommendations.push(
|
|
6860
7356
|
`\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 +7463,7 @@ function inspectRepository(endpoint, role, secretsRoot, homeDir, expectedPackage
|
|
|
6967
7463
|
status: deriveStatus(
|
|
6968
7464
|
role,
|
|
6969
7465
|
entries,
|
|
7466
|
+
agentLinks,
|
|
6970
7467
|
git,
|
|
6971
7468
|
hooks,
|
|
6972
7469
|
skills,
|
|
@@ -6983,6 +7480,7 @@ function inspectRepository(endpoint, role, secretsRoot, homeDir, expectedPackage
|
|
|
6983
7480
|
recommendations,
|
|
6984
7481
|
git,
|
|
6985
7482
|
entries,
|
|
7483
|
+
agentLinks,
|
|
6986
7484
|
hooks,
|
|
6987
7485
|
mcp,
|
|
6988
7486
|
skills,
|
|
@@ -6995,8 +7493,8 @@ function inspectRepository(endpoint, role, secretsRoot, homeDir, expectedPackage
|
|
|
6995
7493
|
redundancy
|
|
6996
7494
|
};
|
|
6997
7495
|
}
|
|
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))
|
|
7496
|
+
function deriveStatus(role, entries, agentLinks, git, hooks, skills, hostSsot, secrets, docs, projectModel, exclusiveOwnershipViolations, technologyGovernanceConfigured, versions, verification, redundancy) {
|
|
7497
|
+
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
7498
|
return "unhealthy";
|
|
7001
7499
|
const missingBaseline = projectModel.baseline.some(
|
|
7002
7500
|
(technology) => !technology.detected && !hasBaselineException(projectModel, technology.id)
|
|
@@ -7100,8 +7598,12 @@ function runPortfolioDoctor(args) {
|
|
|
7100
7598
|
console.error(`Unknown portfolio target: ${options.value.targetId}`);
|
|
7101
7599
|
return 1;
|
|
7102
7600
|
}
|
|
7601
|
+
const agentAssetsDir = findPortfolioAgentAssetsDir(loaded.manifest);
|
|
7602
|
+
if (loaded.manifest.executionEngine?.path && !agentAssetsDir) {
|
|
7603
|
+
return reportMissingPortfolioRegistry(loaded, options.value.json);
|
|
7604
|
+
}
|
|
7103
7605
|
const loadedAssets = loadAgentAssetRegistry({
|
|
7104
|
-
agentAssetsDir
|
|
7606
|
+
agentAssetsDir
|
|
7105
7607
|
});
|
|
7106
7608
|
if (loadedAssets.issues.length > 0) {
|
|
7107
7609
|
for (const issue of loadedAssets.issues) console.error(`${issue.type}: ${issue.message}`);
|
|
@@ -7202,8 +7704,12 @@ function runPortfolioPlan(args) {
|
|
|
7202
7704
|
console.error(`Unknown portfolio target: ${options.value.targetId}`);
|
|
7203
7705
|
return 1;
|
|
7204
7706
|
}
|
|
7707
|
+
const agentAssetsDir = findPortfolioAgentAssetsDir(loaded.manifest);
|
|
7708
|
+
if (loaded.manifest.executionEngine?.path && !agentAssetsDir) {
|
|
7709
|
+
return reportMissingPortfolioRegistry(loaded, options.value.json);
|
|
7710
|
+
}
|
|
7205
7711
|
const loadedAssets = loadAgentAssetRegistry({
|
|
7206
|
-
agentAssetsDir
|
|
7712
|
+
agentAssetsDir
|
|
7207
7713
|
});
|
|
7208
7714
|
if (loadedAssets.issues.length > 0) {
|
|
7209
7715
|
for (const issue of loadedAssets.issues) {
|
|
@@ -7292,8 +7798,12 @@ function runPortfolioAssetsCheck(args) {
|
|
|
7292
7798
|
console.error(`Unknown portfolio target: ${options.value.targetId}`);
|
|
7293
7799
|
return 1;
|
|
7294
7800
|
}
|
|
7801
|
+
const agentAssetsDir = findPortfolioAgentAssetsDir(loaded.manifest);
|
|
7802
|
+
if (loaded.manifest.executionEngine?.path && !agentAssetsDir) {
|
|
7803
|
+
return reportMissingPortfolioRegistry(loaded, options.value.json);
|
|
7804
|
+
}
|
|
7295
7805
|
const loadedAssets = loadAgentAssetRegistry({
|
|
7296
|
-
agentAssetsDir
|
|
7806
|
+
agentAssetsDir
|
|
7297
7807
|
});
|
|
7298
7808
|
if (loadedAssets.issues.length > 0) {
|
|
7299
7809
|
if (options.value.json) {
|
|
@@ -7403,8 +7913,33 @@ function isHost2(value) {
|
|
|
7403
7913
|
return value === "codex" || value === "claude-code" || value === "gemini-cli" || value === "antigravity";
|
|
7404
7914
|
}
|
|
7405
7915
|
function findPortfolioAgentAssetsDir(manifest) {
|
|
7406
|
-
const agentAssetsDir = manifest?.executionEngine?.path ?
|
|
7407
|
-
return agentAssetsDir &&
|
|
7916
|
+
const agentAssetsDir = manifest?.executionEngine?.path ? join34(manifest.executionEngine.path, "agent-assets") : void 0;
|
|
7917
|
+
return agentAssetsDir && existsSync33(join34(agentAssetsDir, "registry.json")) ? agentAssetsDir : void 0;
|
|
7918
|
+
}
|
|
7919
|
+
function reportMissingPortfolioRegistry(loaded, json) {
|
|
7920
|
+
const expectedPath = loaded.manifest?.executionEngine?.path ? join34(loaded.manifest.executionEngine.path, "agent-assets/registry.json") : "executionEngine.path/agent-assets/registry.json";
|
|
7921
|
+
const issue = {
|
|
7922
|
+
type: "missing-control-plane-registry",
|
|
7923
|
+
message: `Portfolio control-plane registry is required at ${expectedPath}; refusing the package fallback registry.`
|
|
7924
|
+
};
|
|
7925
|
+
if (json) {
|
|
7926
|
+
console.log(
|
|
7927
|
+
JSON.stringify(
|
|
7928
|
+
{
|
|
7929
|
+
ok: false,
|
|
7930
|
+
configPath: loaded.configPath,
|
|
7931
|
+
portfolioId: loaded.manifest?.portfolioId,
|
|
7932
|
+
issues: [issue],
|
|
7933
|
+
targets: []
|
|
7934
|
+
},
|
|
7935
|
+
null,
|
|
7936
|
+
2
|
|
7937
|
+
)
|
|
7938
|
+
);
|
|
7939
|
+
} else {
|
|
7940
|
+
console.error(`${issue.type}: ${issue.message}`);
|
|
7941
|
+
}
|
|
7942
|
+
return 1;
|
|
7408
7943
|
}
|
|
7409
7944
|
function printUsage5() {
|
|
7410
7945
|
console.error("Usage:");
|
|
@@ -7420,8 +7955,8 @@ function printUsage5() {
|
|
|
7420
7955
|
}
|
|
7421
7956
|
function readExistingAiHealthReport(outDir, portfolioId) {
|
|
7422
7957
|
if (!outDir) return void 0;
|
|
7423
|
-
const path =
|
|
7424
|
-
if (!
|
|
7958
|
+
const path = join34(outDir, "portfolio-ai-health.json");
|
|
7959
|
+
if (!existsSync33(path)) return void 0;
|
|
7425
7960
|
try {
|
|
7426
7961
|
const value = JSON.parse(readFileSync18(path, "utf8"));
|
|
7427
7962
|
if (!value || typeof value !== "object" || value.portfolioId !== portfolioId || !Array.isArray(value.repositories))
|
|
@@ -7433,8 +7968,8 @@ function readExistingAiHealthReport(outDir, portfolioId) {
|
|
|
7433
7968
|
}
|
|
7434
7969
|
|
|
7435
7970
|
// src/commands/sync.ts
|
|
7436
|
-
import { existsSync as
|
|
7437
|
-
import { join as
|
|
7971
|
+
import { existsSync as existsSync34, lstatSync as lstatSync14, readFileSync as readFileSync19, readlinkSync as readlinkSync4 } from "node:fs";
|
|
7972
|
+
import { join as join35 } from "node:path";
|
|
7438
7973
|
function runSync(args) {
|
|
7439
7974
|
const check = args.includes("--check");
|
|
7440
7975
|
if (!check) {
|
|
@@ -7462,7 +7997,7 @@ function runSync(args) {
|
|
|
7462
7997
|
console.log("pro-gov sync check");
|
|
7463
7998
|
console.log(`profile: ${profile}`);
|
|
7464
7999
|
for (const file of planStarterFiles(profile)) {
|
|
7465
|
-
const targetPath =
|
|
8000
|
+
const targetPath = join35(process.cwd(), file.targetPath);
|
|
7466
8001
|
const stat = safeLstat3(targetPath);
|
|
7467
8002
|
if (!stat) {
|
|
7468
8003
|
if (file.ownership === "optional-guardrail") continue;
|
|
@@ -7521,13 +8056,13 @@ function normalizeMarkdownTableCell(cell) {
|
|
|
7521
8056
|
}
|
|
7522
8057
|
function inferInstalledProfile(root) {
|
|
7523
8058
|
const installed = ["engineering-runtime", "doc-only"].filter(
|
|
7524
|
-
(profile) =>
|
|
8059
|
+
(profile) => existsSync34(join35(root, `docs/governance/agents-routing/${profile}-v1.1.md`))
|
|
7525
8060
|
);
|
|
7526
8061
|
return installed.length === 1 ? installed[0] : void 0;
|
|
7527
8062
|
}
|
|
7528
8063
|
function safeLstat3(path) {
|
|
7529
8064
|
try {
|
|
7530
|
-
return
|
|
8065
|
+
return lstatSync14(path);
|
|
7531
8066
|
} catch {
|
|
7532
8067
|
return void 0;
|
|
7533
8068
|
}
|
|
@@ -7565,7 +8100,8 @@ var COMMANDS = [
|
|
|
7565
8100
|
"lens audit check --dir <path> [--json]",
|
|
7566
8101
|
"init --profile <engineering-runtime|doc-only> <--dry-run|--apply>",
|
|
7567
8102
|
"sync --check [--profile <engineering-runtime|doc-only>]",
|
|
7568
|
-
"doctor"
|
|
8103
|
+
"package-doctor",
|
|
8104
|
+
"doctor (legacy alias for package-doctor)"
|
|
7569
8105
|
];
|
|
7570
8106
|
var [command, subcommand] = process.argv.slice(2);
|
|
7571
8107
|
process.exitCode = await main();
|
|
@@ -7581,6 +8117,7 @@ async function main() {
|
|
|
7581
8117
|
if (command === "host-lens") return runHostLens(process.argv.slice(3));
|
|
7582
8118
|
if (command === "init") return runInit(process.argv.slice(3));
|
|
7583
8119
|
if (command === "sync") return runSync(process.argv.slice(3));
|
|
8120
|
+
if (command === "package-doctor") return runPackageDoctor(process.argv.slice(3));
|
|
7584
8121
|
if (command === "doctor") return runDoctor(process.argv.slice(3));
|
|
7585
8122
|
console.error(`Unknown command: ${[command, subcommand].filter(Boolean).join(" ")}`);
|
|
7586
8123
|
printHelp();
|