@pieai/pro-gov 0.3.5 → 0.3.7
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 +27 -4
- package/assets/docs/reference/adoption/adoption-playbook.md +20 -3
- package/assets/docs/reference/adoption/project-relationship.md +7 -5
- package/assets/docs/reference/adoption/public-release-checklist.md +24 -1
- package/assets/public-agent-assets/README.md +40 -0
- package/assets/public-agent-assets/bundles/.gitkeep +0 -0
- package/assets/public-agent-assets/bundles/base-governance.json +11 -0
- package/assets/public-agent-assets/commands/pie-commands/README.md +3 -0
- package/assets/public-agent-assets/registry.json +132 -0
- package/assets/public-agent-assets/rules/pie-rules/.gitkeep +0 -0
- package/assets/public-agent-assets/rules/pie-rules/ai-in-the-loop.md +66 -0
- package/assets/public-agent-assets/rules/pie-rules/rule-evolution-methodology.md +74 -0
- package/assets/public-agent-assets/skills/dokobot/.gitkeep +0 -0
- package/assets/public-agent-assets/skills/npx-skills/README.md +3 -0
- package/assets/public-agent-assets/skills/pie-skills/.gitkeep +0 -0
- package/assets/public-agent-assets/skills/pie-skills/beginner-friendly-docs/SKILL.md +225 -0
- package/assets/public-agent-assets/skills/pie-skills/doc-cross-validator/SKILL.md +194 -0
- package/assets/starter/.github/workflows/docs-check.yml +2 -4
- package/assets/starter/docs/governance/agents-routing/doc-only-v0.9.md +2 -3
- package/assets/starter/docs/governance/agents-routing/engineering-runtime-v0.9.md +2 -3
- package/assets/starter/docs/governance/boundary.md +2 -2
- package/assets/starter/docs/governance/ssot-v0.9.md +2 -2
- package/assets/starter/lefthook.template.yml +2 -2
- package/cli-guide.md +18 -3
- package/dist/cli.js +845 -91
- package/package.json +5 -5
- package/assets/docs/reference/adoption/downstream-project-registry.md +0 -85
- package/assets/starter/.gemini/settings.json +0 -5
package/dist/cli.js
CHANGED
|
@@ -48,8 +48,8 @@ function isPlatformMetadata(name) {
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
// src/commands/assets.ts
|
|
51
|
-
import { mkdirSync as mkdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync2 } from "node:fs";
|
|
52
|
-
import { dirname as dirname5 } from "node:path";
|
|
51
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync2 } from "node:fs";
|
|
52
|
+
import { dirname as dirname5, join as join11 } from "node:path";
|
|
53
53
|
|
|
54
54
|
// src/asset-bundles/bundles.ts
|
|
55
55
|
import { existsSync as existsSync2, readdirSync as readdirSync2, readFileSync } from "node:fs";
|
|
@@ -202,7 +202,6 @@ import { existsSync as existsSync4, lstatSync } from "node:fs";
|
|
|
202
202
|
import { isAbsolute, join as join4, posix } from "node:path";
|
|
203
203
|
var supportedFamilies = /* @__PURE__ */ new Set([
|
|
204
204
|
"pie-skills",
|
|
205
|
-
"dokobot",
|
|
206
205
|
"npx-skills",
|
|
207
206
|
"pie-rules",
|
|
208
207
|
"pie-commands"
|
|
@@ -214,6 +213,8 @@ var supportedVisibilities = /* @__PURE__ */ new Set([
|
|
|
214
213
|
"third-party"
|
|
215
214
|
]);
|
|
216
215
|
var supportedSourceKinds = /* @__PURE__ */ new Set(["local", "local-pack", "npx"]);
|
|
216
|
+
var supportedSkillPlacements = /* @__PURE__ */ new Set(["auto", "manual"]);
|
|
217
|
+
var supportedSkillScopes = /* @__PURE__ */ new Set(["project", "user"]);
|
|
217
218
|
var supportedHosts = /* @__PURE__ */ new Set([
|
|
218
219
|
"codex",
|
|
219
220
|
"claude-code",
|
|
@@ -269,6 +270,20 @@ function validateAssetRegistry(registry, options = {}) {
|
|
|
269
270
|
});
|
|
270
271
|
}
|
|
271
272
|
}
|
|
273
|
+
if (asset.kind === "skill" && !supportedSkillPlacements.has(asset.defaultPlacement)) {
|
|
274
|
+
issues.push({
|
|
275
|
+
type: "unsupported-skill-placement",
|
|
276
|
+
id: asset.id,
|
|
277
|
+
message: `Skill asset must declare defaultPlacement auto or manual: ${asset.id}`
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
if (asset.defaultScope && asset.kind !== "skill" || asset.kind === "skill" && asset.defaultScope && !supportedSkillScopes.has(asset.defaultScope)) {
|
|
281
|
+
issues.push({
|
|
282
|
+
type: "unsupported-skill-scope",
|
|
283
|
+
id: asset.id,
|
|
284
|
+
message: `Skill asset defaultScope must be project or user: ${asset.id}`
|
|
285
|
+
});
|
|
286
|
+
}
|
|
272
287
|
if (!isSafeRegistrySourcePath(asset.sourcePath)) {
|
|
273
288
|
issues.push({
|
|
274
289
|
type: "unsafe-source-path",
|
|
@@ -363,7 +378,9 @@ function createAgentAssetLockEntries(registry, agentAssetsDir, assetIds) {
|
|
|
363
378
|
})).sort((a, b) => a.id.localeCompare(b.id));
|
|
364
379
|
}
|
|
365
380
|
function hashAgentAssetContent(asset, agentAssetsDir) {
|
|
366
|
-
|
|
381
|
+
return hashAssetPathContent(join5(agentAssetsDir, asset.sourcePath));
|
|
382
|
+
}
|
|
383
|
+
function hashAssetPathContent(sourceAbsolutePath) {
|
|
367
384
|
const hash = createHash2("sha256");
|
|
368
385
|
for (const filePath of listFiles3(sourceAbsolutePath)) {
|
|
369
386
|
const relativePath = toUnixPath3(relative3(sourceAbsolutePath, filePath));
|
|
@@ -377,7 +394,12 @@ function hashAgentAssetContent(asset, agentAssetsDir) {
|
|
|
377
394
|
function findDefaultAgentAssetsDir() {
|
|
378
395
|
const packageRoot2 = findPackageRoot(dirname2(fileURLToPath2(import.meta.url)));
|
|
379
396
|
const repoRoot = join5(packageRoot2, "..", "..");
|
|
380
|
-
const candidates = [
|
|
397
|
+
const candidates = [
|
|
398
|
+
join5(packageRoot2, "assets/agent-assets"),
|
|
399
|
+
join5(repoRoot, "agent-assets"),
|
|
400
|
+
join5(packageRoot2, "assets/public-agent-assets"),
|
|
401
|
+
join5(repoRoot, "public-agent-assets")
|
|
402
|
+
];
|
|
381
403
|
return candidates.find((candidate) => existsSync5(join5(candidate, "registry.json"))) ?? candidates[0];
|
|
382
404
|
}
|
|
383
405
|
function findPackageRoot(startDir) {
|
|
@@ -400,6 +422,7 @@ function listFiles3(absolutePath) {
|
|
|
400
422
|
if (stats.isFile()) return [absolutePath];
|
|
401
423
|
const files = [];
|
|
402
424
|
for (const entry of readdirSync4(absolutePath, { withFileTypes: true })) {
|
|
425
|
+
if (shouldIgnoreAssetHashEntry(entry.name)) continue;
|
|
403
426
|
const entryPath = join5(absolutePath, entry.name);
|
|
404
427
|
if (entry.isDirectory()) {
|
|
405
428
|
files.push(...listFiles3(entryPath));
|
|
@@ -409,13 +432,112 @@ function listFiles3(absolutePath) {
|
|
|
409
432
|
}
|
|
410
433
|
return files.sort();
|
|
411
434
|
}
|
|
435
|
+
function shouldIgnoreAssetHashEntry(name) {
|
|
436
|
+
return name === "__pycache__" || name === ".DS_Store" || name === "Thumbs.db" || name === "node_modules" || name.endsWith(".pyc") || name.endsWith(".pyo");
|
|
437
|
+
}
|
|
412
438
|
function toUnixPath3(path) {
|
|
413
439
|
return path.replaceAll("\\", "/");
|
|
414
440
|
}
|
|
415
441
|
|
|
442
|
+
// src/asset-registry/public-promotion.ts
|
|
443
|
+
import { existsSync as existsSync6 } from "node:fs";
|
|
444
|
+
import { isAbsolute as isAbsolute2, join as join6, posix as posix2 } from "node:path";
|
|
445
|
+
function checkPublicAssetPromotions(options) {
|
|
446
|
+
const issues = [];
|
|
447
|
+
let checked = 0;
|
|
448
|
+
for (const asset of options.registry.assets) {
|
|
449
|
+
if (!needsPromotionCheck(asset)) continue;
|
|
450
|
+
checked += 1;
|
|
451
|
+
if (!asset.promotion) {
|
|
452
|
+
issues.push({
|
|
453
|
+
type: "missing-promotion",
|
|
454
|
+
id: asset.id,
|
|
455
|
+
message: `Public asset is missing promotion metadata: ${asset.id}`
|
|
456
|
+
});
|
|
457
|
+
continue;
|
|
458
|
+
}
|
|
459
|
+
const publicPathResult = resolveSafePath(options.publicAgentAssetsDir, asset.sourcePath);
|
|
460
|
+
if (!publicPathResult.ok) {
|
|
461
|
+
issues.push({
|
|
462
|
+
type: "unsafe-public-source-path",
|
|
463
|
+
id: asset.id,
|
|
464
|
+
path: asset.sourcePath,
|
|
465
|
+
message: `Public asset source path is unsafe: ${asset.sourcePath}`
|
|
466
|
+
});
|
|
467
|
+
continue;
|
|
468
|
+
}
|
|
469
|
+
const privatePathResult = resolveSafePath(
|
|
470
|
+
options.privateAgentAssetsDir,
|
|
471
|
+
asset.promotion.privateSourcePath
|
|
472
|
+
);
|
|
473
|
+
if (!privatePathResult.ok) {
|
|
474
|
+
issues.push({
|
|
475
|
+
type: "unsafe-private-source-path",
|
|
476
|
+
id: asset.id,
|
|
477
|
+
path: asset.promotion.privateSourcePath,
|
|
478
|
+
message: `Private promotion source path is unsafe: ${asset.promotion.privateSourcePath}`
|
|
479
|
+
});
|
|
480
|
+
continue;
|
|
481
|
+
}
|
|
482
|
+
if (!existsSync6(privatePathResult.path)) {
|
|
483
|
+
issues.push({
|
|
484
|
+
type: "missing-private-source",
|
|
485
|
+
id: asset.id,
|
|
486
|
+
path: asset.promotion.privateSourcePath,
|
|
487
|
+
message: `Private promotion source does not exist: ${asset.promotion.privateSourcePath}`
|
|
488
|
+
});
|
|
489
|
+
} else {
|
|
490
|
+
const actualPrivateHash = hashAssetPathContent(privatePathResult.path);
|
|
491
|
+
if (actualPrivateHash !== asset.promotion.privateSourceHash) {
|
|
492
|
+
issues.push({
|
|
493
|
+
type: "source-drift",
|
|
494
|
+
id: asset.id,
|
|
495
|
+
path: asset.promotion.privateSourcePath,
|
|
496
|
+
expected: asset.promotion.privateSourceHash,
|
|
497
|
+
actual: actualPrivateHash,
|
|
498
|
+
message: `Private source changed after public promotion: ${asset.id}`
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
if (!existsSync6(publicPathResult.path)) {
|
|
503
|
+
issues.push({
|
|
504
|
+
type: "missing-public-source",
|
|
505
|
+
id: asset.id,
|
|
506
|
+
path: asset.sourcePath,
|
|
507
|
+
message: `Public promoted asset does not exist: ${asset.sourcePath}`
|
|
508
|
+
});
|
|
509
|
+
} else {
|
|
510
|
+
const actualPublicHash = hashAssetPathContent(publicPathResult.path);
|
|
511
|
+
if (actualPublicHash !== asset.promotion.publicHash) {
|
|
512
|
+
issues.push({
|
|
513
|
+
type: "public-drift",
|
|
514
|
+
id: asset.id,
|
|
515
|
+
path: asset.sourcePath,
|
|
516
|
+
expected: asset.promotion.publicHash,
|
|
517
|
+
actual: actualPublicHash,
|
|
518
|
+
message: `Public promoted asset changed after review: ${asset.id}`
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
return { checked, issues };
|
|
524
|
+
}
|
|
525
|
+
function needsPromotionCheck(asset) {
|
|
526
|
+
return asset.visibility === "public" && asset.publishable;
|
|
527
|
+
}
|
|
528
|
+
function resolveSafePath(root, sourcePath) {
|
|
529
|
+
if (!sourcePath || isAbsolute2(sourcePath) || sourcePath.startsWith("/")) return { ok: false };
|
|
530
|
+
const normalized = posix2.normalize(sourcePath.replaceAll("\\", "/"));
|
|
531
|
+
if (normalized === "." || normalized === ".." || normalized.startsWith("../")) {
|
|
532
|
+
return { ok: false };
|
|
533
|
+
}
|
|
534
|
+
if (normalized.split("/").includes("..")) return { ok: false };
|
|
535
|
+
return { ok: true, path: join6(root, normalized) };
|
|
536
|
+
}
|
|
537
|
+
|
|
416
538
|
// src/asset-targets/apply.ts
|
|
417
|
-
import { existsSync as
|
|
418
|
-
import { dirname as dirname3, join as
|
|
539
|
+
import { existsSync as existsSync7, lstatSync as lstatSync2, mkdirSync as mkdirSync2, realpathSync, symlinkSync, unlinkSync, writeFileSync } from "node:fs";
|
|
540
|
+
import { dirname as dirname3, join as join7, relative as relative4, resolve } from "node:path";
|
|
419
541
|
function applyAssetInstallPlan(plan) {
|
|
420
542
|
const appliedActions = [];
|
|
421
543
|
for (const action of plan.actions) {
|
|
@@ -425,7 +547,7 @@ function applyAssetInstallPlan(plan) {
|
|
|
425
547
|
return { appliedActions };
|
|
426
548
|
}
|
|
427
549
|
function applyAction(targetDir, action) {
|
|
428
|
-
const targetAbsolutePath =
|
|
550
|
+
const targetAbsolutePath = join7(targetDir, action.targetPath);
|
|
429
551
|
if (action.type === "create-dir") {
|
|
430
552
|
mkdirSync2(targetAbsolutePath, { recursive: true });
|
|
431
553
|
return;
|
|
@@ -437,15 +559,16 @@ function applyAction(targetDir, action) {
|
|
|
437
559
|
}
|
|
438
560
|
mkdirSync2(dirname3(targetAbsolutePath), { recursive: true });
|
|
439
561
|
const sourceAbsolutePath = resolve(action.sourcePath);
|
|
562
|
+
const symlinkTarget = relative4(realpathSync(dirname3(targetAbsolutePath)), realpathSync(sourceAbsolutePath)) || ".";
|
|
440
563
|
if (action.type === "symlink") {
|
|
441
564
|
if (pathExistsEvenIfDanglingSymlink2(targetAbsolutePath)) {
|
|
442
565
|
throw new Error(`Refusing to overwrite unmanaged target: ${action.targetPath}`);
|
|
443
566
|
}
|
|
444
|
-
symlinkSync(
|
|
567
|
+
symlinkSync(symlinkTarget, targetAbsolutePath);
|
|
445
568
|
return;
|
|
446
569
|
}
|
|
447
570
|
if (!pathExistsEvenIfDanglingSymlink2(targetAbsolutePath)) {
|
|
448
|
-
symlinkSync(
|
|
571
|
+
symlinkSync(symlinkTarget, targetAbsolutePath);
|
|
449
572
|
return;
|
|
450
573
|
}
|
|
451
574
|
const stats = lstatSync2(targetAbsolutePath);
|
|
@@ -453,23 +576,23 @@ function applyAction(targetDir, action) {
|
|
|
453
576
|
throw new Error(`Refusing to overwrite unmanaged target: ${action.targetPath}`);
|
|
454
577
|
}
|
|
455
578
|
unlinkSync(targetAbsolutePath);
|
|
456
|
-
symlinkSync(
|
|
579
|
+
symlinkSync(symlinkTarget, targetAbsolutePath);
|
|
457
580
|
}
|
|
458
581
|
function pathExistsEvenIfDanglingSymlink2(path) {
|
|
459
582
|
try {
|
|
460
583
|
lstatSync2(path);
|
|
461
584
|
return true;
|
|
462
585
|
} catch {
|
|
463
|
-
return
|
|
586
|
+
return existsSync7(path);
|
|
464
587
|
}
|
|
465
588
|
}
|
|
466
589
|
|
|
467
590
|
// src/asset-targets/check.ts
|
|
468
|
-
import { existsSync as
|
|
469
|
-
import { join as
|
|
591
|
+
import { existsSync as existsSync8, lstatSync as lstatSync3, readFileSync as readFileSync4 } from "node:fs";
|
|
592
|
+
import { basename, join as join8 } from "node:path";
|
|
470
593
|
function checkInstalledAssets(options) {
|
|
471
|
-
const lockfilePath =
|
|
472
|
-
if (!
|
|
594
|
+
const lockfilePath = join8(options.targetDir, ".pro-gov/assets.lock.json");
|
|
595
|
+
if (!existsSync8(lockfilePath)) {
|
|
473
596
|
return {
|
|
474
597
|
targetDir: options.targetDir,
|
|
475
598
|
issues: [
|
|
@@ -483,22 +606,35 @@ function checkInstalledAssets(options) {
|
|
|
483
606
|
const registryById = new Map(options.registry.assets.map((asset) => [asset.id, asset]));
|
|
484
607
|
const lockfile = JSON.parse(readFileSync4(lockfilePath, "utf8"));
|
|
485
608
|
const issues = [];
|
|
609
|
+
const strictRegistry = options.strictRegistry ?? false;
|
|
486
610
|
for (const entry of lockfile.assets ?? []) {
|
|
487
611
|
const asset = registryById.get(entry.id);
|
|
488
|
-
const targetAbsolutePath =
|
|
489
|
-
|
|
490
|
-
if (!asset) {
|
|
612
|
+
const targetAbsolutePath = join8(options.targetDir, entry.targetPath);
|
|
613
|
+
if (!asset && strictRegistry) {
|
|
491
614
|
issues.push({
|
|
492
615
|
type: "unknown-asset",
|
|
493
616
|
id: entry.id,
|
|
494
617
|
targetPath: entry.targetPath,
|
|
495
618
|
message: `Lockfile references unknown asset: ${entry.id}`
|
|
496
619
|
});
|
|
497
|
-
continue;
|
|
498
620
|
}
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
621
|
+
if (asset?.kind === "skill" && asset.defaultScope === "user") {
|
|
622
|
+
issues.push({
|
|
623
|
+
type: "user-scoped-asset-in-project-lock",
|
|
624
|
+
id: entry.id,
|
|
625
|
+
targetPath: entry.targetPath,
|
|
626
|
+
message: `User-scoped skill is still locked into this project; move it to the user skill roots: ${entry.id}`
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
if (asset) {
|
|
630
|
+
const hostFolderIssue = checkHostFolder(lockfile.host, asset.kind, entry.targetPath, entry.id);
|
|
631
|
+
if (hostFolderIssue) {
|
|
632
|
+
issues.push(hostFolderIssue);
|
|
633
|
+
}
|
|
634
|
+
const placementDriftIssue = checkRegistryPlacement(lockfile, asset, entry.targetPath);
|
|
635
|
+
if (placementDriftIssue) {
|
|
636
|
+
issues.push(placementDriftIssue);
|
|
637
|
+
}
|
|
502
638
|
}
|
|
503
639
|
if (!pathExistsEvenIfDanglingSymlink3(targetAbsolutePath)) {
|
|
504
640
|
issues.push({
|
|
@@ -519,7 +655,7 @@ function checkInstalledAssets(options) {
|
|
|
519
655
|
});
|
|
520
656
|
continue;
|
|
521
657
|
}
|
|
522
|
-
if (!
|
|
658
|
+
if (!existsSync8(targetAbsolutePath)) {
|
|
523
659
|
issues.push({
|
|
524
660
|
type: "dangling-symlink",
|
|
525
661
|
id: entry.id,
|
|
@@ -528,7 +664,19 @@ function checkInstalledAssets(options) {
|
|
|
528
664
|
});
|
|
529
665
|
continue;
|
|
530
666
|
}
|
|
531
|
-
|
|
667
|
+
const currentTargetHash = hashAssetPathContent(targetAbsolutePath);
|
|
668
|
+
const targetHashMatchesLock = currentTargetHash === entry.contentHash;
|
|
669
|
+
if (!targetHashMatchesLock) {
|
|
670
|
+
issues.push({
|
|
671
|
+
type: "hash-drift",
|
|
672
|
+
id: entry.id,
|
|
673
|
+
targetPath: entry.targetPath,
|
|
674
|
+
message: `Managed asset hash drifted: ${entry.id}`
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
if (!asset || !strictRegistry) continue;
|
|
678
|
+
const sourceAbsolutePath = join8(options.agentAssetsDir, asset.sourcePath);
|
|
679
|
+
if (!existsSync8(sourceAbsolutePath)) {
|
|
532
680
|
issues.push({
|
|
533
681
|
type: "missing-source",
|
|
534
682
|
id: entry.id,
|
|
@@ -537,8 +685,8 @@ function checkInstalledAssets(options) {
|
|
|
537
685
|
});
|
|
538
686
|
continue;
|
|
539
687
|
}
|
|
540
|
-
const
|
|
541
|
-
if (
|
|
688
|
+
const currentSourceHash = hashAgentAssetContent(asset, options.agentAssetsDir);
|
|
689
|
+
if (targetHashMatchesLock && currentSourceHash !== entry.contentHash) {
|
|
542
690
|
issues.push({
|
|
543
691
|
type: "hash-drift",
|
|
544
692
|
id: entry.id,
|
|
@@ -547,12 +695,43 @@ function checkInstalledAssets(options) {
|
|
|
547
695
|
});
|
|
548
696
|
}
|
|
549
697
|
}
|
|
698
|
+
issues.push(...checkDuplicateSkillPlacements(options.targetDir, options.registry));
|
|
550
699
|
return { targetDir: options.targetDir, issues };
|
|
551
700
|
}
|
|
701
|
+
function checkRegistryPlacement(lockfile, asset, targetPath) {
|
|
702
|
+
if (lockfile.placement !== "registry") return void 0;
|
|
703
|
+
if (asset.kind !== "skill") return void 0;
|
|
704
|
+
const expectedPath = expectedRegistrySkillTargetPath(lockfile.host, asset.sourcePath, asset.defaultPlacement);
|
|
705
|
+
if (!expectedPath || targetPath === expectedPath) return void 0;
|
|
706
|
+
return {
|
|
707
|
+
type: "skill-placement-drift",
|
|
708
|
+
id: asset.id,
|
|
709
|
+
targetPath,
|
|
710
|
+
message: `Managed skill target ${targetPath} does not match registry placement; expected ${expectedPath}`
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
function checkDuplicateSkillPlacements(targetDir, registry) {
|
|
714
|
+
const issues = [];
|
|
715
|
+
for (const asset of registry.assets) {
|
|
716
|
+
if (asset.kind !== "skill") continue;
|
|
717
|
+
const skillName = basename(asset.sourcePath);
|
|
718
|
+
const autoPath = `.agents/skills/${skillName}`;
|
|
719
|
+
const manualPath = `.agents/manual-skills/${skillName}`;
|
|
720
|
+
if (pathExistsEvenIfDanglingSymlink3(join8(targetDir, autoPath)) && pathExistsEvenIfDanglingSymlink3(join8(targetDir, manualPath))) {
|
|
721
|
+
issues.push({
|
|
722
|
+
type: "duplicate-skill-placement",
|
|
723
|
+
id: asset.id,
|
|
724
|
+
targetPath: `${autoPath} + ${manualPath}`,
|
|
725
|
+
message: `Skill is linked in both auto and manual locations: ${skillName}`
|
|
726
|
+
});
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
return issues;
|
|
730
|
+
}
|
|
552
731
|
function checkHostFolder(host, kind, targetPath, id) {
|
|
553
732
|
if (kind !== "skill") return void 0;
|
|
554
|
-
const
|
|
555
|
-
if (!
|
|
733
|
+
const expectedPrefixes = expectedSkillTargetPrefixes(host);
|
|
734
|
+
if (!expectedPrefixes) {
|
|
556
735
|
return {
|
|
557
736
|
type: "unsupported-host-folder",
|
|
558
737
|
id,
|
|
@@ -560,20 +739,30 @@ function checkHostFolder(host, kind, targetPath, id) {
|
|
|
560
739
|
message: `Lockfile host is unsupported for managed skill target: ${host ?? "missing"}`
|
|
561
740
|
};
|
|
562
741
|
}
|
|
563
|
-
if (!targetPath.startsWith(
|
|
742
|
+
if (!expectedPrefixes.some((prefix) => targetPath.startsWith(prefix))) {
|
|
564
743
|
return {
|
|
565
744
|
type: "unsupported-host-folder",
|
|
566
745
|
id,
|
|
567
746
|
targetPath,
|
|
568
|
-
message: `Managed skill target ${targetPath} does not match host ${host}; expected ${
|
|
747
|
+
message: `Managed skill target ${targetPath} does not match host ${host}; expected ${expectedPrefixes.join(" or ")}`
|
|
569
748
|
};
|
|
570
749
|
}
|
|
571
750
|
return void 0;
|
|
572
751
|
}
|
|
573
|
-
function
|
|
574
|
-
if (host === "claude-code") return ".claude/skills/";
|
|
752
|
+
function expectedSkillTargetPrefixes(host) {
|
|
753
|
+
if (host === "claude-code") return [".claude/skills/"];
|
|
575
754
|
if (host === "codex" || host === "gemini-cli" || host === "antigravity") {
|
|
576
|
-
return ".agents/skills/";
|
|
755
|
+
return [".agents/skills/", ".agents/manual-skills/"];
|
|
756
|
+
}
|
|
757
|
+
return void 0;
|
|
758
|
+
}
|
|
759
|
+
function expectedRegistrySkillTargetPath(host, sourcePath, placement) {
|
|
760
|
+
const skillName = basename(sourcePath);
|
|
761
|
+
if (host === "claude-code") {
|
|
762
|
+
return placement === "manual" ? void 0 : `.claude/skills/${skillName}`;
|
|
763
|
+
}
|
|
764
|
+
if (host === "codex" || host === "gemini-cli" || host === "antigravity") {
|
|
765
|
+
return placement === "manual" ? `.agents/manual-skills/${skillName}` : `.agents/skills/${skillName}`;
|
|
577
766
|
}
|
|
578
767
|
return void 0;
|
|
579
768
|
}
|
|
@@ -587,9 +776,10 @@ function pathExistsEvenIfDanglingSymlink3(path) {
|
|
|
587
776
|
}
|
|
588
777
|
|
|
589
778
|
// src/asset-targets/install-plan.ts
|
|
590
|
-
import { existsSync as
|
|
591
|
-
import { basename, dirname as dirname4, join as
|
|
779
|
+
import { existsSync as existsSync9, lstatSync as lstatSync4, readFileSync as readFileSync5 } from "node:fs";
|
|
780
|
+
import { basename as basename2, dirname as dirname4, join as join9 } from "node:path";
|
|
592
781
|
function createAssetInstallPlan(options) {
|
|
782
|
+
const placement = options.placement ?? "registry";
|
|
593
783
|
const assetsById = new Map(options.registry.assets.map((asset) => [asset.id, asset]));
|
|
594
784
|
const bundlesById = new Map(options.bundles.map((bundle) => [bundle.id, bundle]));
|
|
595
785
|
const assetIds = resolveBundleAssetIds(options.bundleIds, bundlesById);
|
|
@@ -604,17 +794,19 @@ function createAssetInstallPlan(options) {
|
|
|
604
794
|
const lockEntries = createAgentAssetLockEntries(options.registry, options.agentAssetsDir, assetIds);
|
|
605
795
|
const managedTargets = readManagedTargets(options.targetDir);
|
|
606
796
|
const assetActions = assets.map(
|
|
607
|
-
(asset) => createAssetAction(asset, options.agentAssetsDir, options.targetDir, options.host, managedTargets)
|
|
797
|
+
(asset) => createAssetAction(asset, options.agentAssetsDir, options.targetDir, options.host, placement, managedTargets)
|
|
608
798
|
);
|
|
609
799
|
const manifest = {
|
|
610
800
|
schemaVersion: 1,
|
|
611
801
|
host: options.host,
|
|
802
|
+
placement,
|
|
612
803
|
bundleIds: [...options.bundleIds],
|
|
613
804
|
assetIds
|
|
614
805
|
};
|
|
615
806
|
const lockfile = {
|
|
616
807
|
schemaVersion: 1,
|
|
617
808
|
host: options.host,
|
|
809
|
+
placement,
|
|
618
810
|
bundleIds: [...options.bundleIds],
|
|
619
811
|
assets: lockEntries.map((entry) => {
|
|
620
812
|
const action = assetActions.find((candidate) => "assetId" in candidate && candidate.assetId === entry.id);
|
|
@@ -643,6 +835,7 @@ function createAssetInstallPlan(options) {
|
|
|
643
835
|
dryRun: true,
|
|
644
836
|
targetDir: options.targetDir,
|
|
645
837
|
host: options.host,
|
|
838
|
+
placement,
|
|
646
839
|
bundleIds: [...options.bundleIds],
|
|
647
840
|
assetIds,
|
|
648
841
|
actions: [...createDirectoryActions([...assetActions, ...writeActions]), ...assetActions, ...writeActions]
|
|
@@ -659,10 +852,15 @@ function resolveBundleAssetIds(bundleIds, bundlesById) {
|
|
|
659
852
|
}
|
|
660
853
|
return [...ids].sort();
|
|
661
854
|
}
|
|
662
|
-
function createAssetAction(asset, agentAssetsDir, targetDir, host, managedTargets) {
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
855
|
+
function createAssetAction(asset, agentAssetsDir, targetDir, host, placement, managedTargets) {
|
|
856
|
+
if (asset.kind === "skill" && asset.defaultScope === "user") {
|
|
857
|
+
throw new Error(
|
|
858
|
+
`User-scoped asset ${asset.id} must be linked at the user level, not installed into a project target.`
|
|
859
|
+
);
|
|
860
|
+
}
|
|
861
|
+
const sourcePath = join9(agentAssetsDir, asset.sourcePath);
|
|
862
|
+
const targetPath = resolveHostTargetPath(asset, host, placement);
|
|
863
|
+
const targetAbsolutePath = join9(targetDir, targetPath);
|
|
666
864
|
const targetExists = pathExistsEvenIfDanglingSymlink4(targetAbsolutePath);
|
|
667
865
|
if (targetExists) {
|
|
668
866
|
const stats = lstatSync4(targetAbsolutePath);
|
|
@@ -683,17 +881,28 @@ function createAssetAction(asset, agentAssetsDir, targetDir, host, managedTarget
|
|
|
683
881
|
targetPath
|
|
684
882
|
};
|
|
685
883
|
}
|
|
686
|
-
function resolveHostTargetPath(asset, host) {
|
|
884
|
+
function resolveHostTargetPath(asset, host, placement) {
|
|
687
885
|
if (asset.kind === "skill") {
|
|
886
|
+
const effectivePlacement = resolveSkillPlacement(asset, placement);
|
|
688
887
|
if (host === "claude-code") {
|
|
689
|
-
|
|
888
|
+
if (placement === "manual") {
|
|
889
|
+
throw new Error("Manual skill placement is only supported for .agents hosts");
|
|
890
|
+
}
|
|
891
|
+
return `.claude/skills/${basename2(asset.sourcePath)}`;
|
|
892
|
+
}
|
|
893
|
+
if (effectivePlacement === "manual") {
|
|
894
|
+
return `.agents/manual-skills/${basename2(asset.sourcePath)}`;
|
|
690
895
|
}
|
|
691
|
-
return `.agents/skills/${
|
|
896
|
+
return `.agents/skills/${basename2(asset.sourcePath)}`;
|
|
692
897
|
}
|
|
693
898
|
if (asset.kind === "rule") {
|
|
694
|
-
return `.pro-gov/agent-assets/rules/${
|
|
899
|
+
return `.pro-gov/agent-assets/rules/${basename2(asset.sourcePath)}`;
|
|
695
900
|
}
|
|
696
|
-
return `.pro-gov/agent-assets/commands/${
|
|
901
|
+
return `.pro-gov/agent-assets/commands/${basename2(asset.sourcePath)}`;
|
|
902
|
+
}
|
|
903
|
+
function resolveSkillPlacement(asset, placement) {
|
|
904
|
+
if (placement !== "registry") return placement;
|
|
905
|
+
return asset.defaultPlacement ?? "auto";
|
|
697
906
|
}
|
|
698
907
|
function createDirectoryActions(actions) {
|
|
699
908
|
const directories = /* @__PURE__ */ new Set();
|
|
@@ -705,8 +914,8 @@ function createDirectoryActions(actions) {
|
|
|
705
914
|
return [...directories].sort().map((targetPath) => ({ type: "create-dir", targetPath }));
|
|
706
915
|
}
|
|
707
916
|
function readManagedTargets(targetDir) {
|
|
708
|
-
const lockfilePath =
|
|
709
|
-
if (!
|
|
917
|
+
const lockfilePath = join9(targetDir, ".pro-gov/assets.lock.json");
|
|
918
|
+
if (!existsSync9(lockfilePath)) return /* @__PURE__ */ new Set();
|
|
710
919
|
try {
|
|
711
920
|
const lockfile = JSON.parse(readFileSync5(lockfilePath, "utf8"));
|
|
712
921
|
return new Set((lockfile.assets ?? []).map((asset) => asset.targetPath).filter(Boolean));
|
|
@@ -724,8 +933,8 @@ function pathExistsEvenIfDanglingSymlink4(path) {
|
|
|
724
933
|
}
|
|
725
934
|
|
|
726
935
|
// src/asset-targets/recommend.ts
|
|
727
|
-
import { existsSync as
|
|
728
|
-
import { join as
|
|
936
|
+
import { existsSync as existsSync10, readdirSync as readdirSync5, readFileSync as readFileSync6 } from "node:fs";
|
|
937
|
+
import { join as join10 } from "node:path";
|
|
729
938
|
var frontendPackages = /* @__PURE__ */ new Set([
|
|
730
939
|
"@vitejs/plugin-react",
|
|
731
940
|
"astro",
|
|
@@ -737,23 +946,23 @@ var frontendPackages = /* @__PURE__ */ new Set([
|
|
|
737
946
|
"vite",
|
|
738
947
|
"vue"
|
|
739
948
|
]);
|
|
740
|
-
var agentEntryCandidates = ["AGENTS.md", "CLAUDE.md"
|
|
949
|
+
var agentEntryCandidates = ["AGENTS.md", "CLAUDE.md"];
|
|
741
950
|
function discoverTargetSignals(targetDir) {
|
|
742
|
-
const packageJson = readJson(
|
|
951
|
+
const packageJson = readJson(join10(targetDir, "package.json"));
|
|
743
952
|
const dependencyNames = packageJson ? Object.keys({ ...packageJson.dependencies, ...packageJson.devDependencies }) : [];
|
|
744
953
|
const frontendSignals = dependencyNames.filter((name) => frontendPackages.has(name)).sort();
|
|
745
|
-
const hasAgentEntry = agentEntryCandidates.some((file) =>
|
|
954
|
+
const hasAgentEntry = agentEntryCandidates.some((file) => existsSync10(join10(targetDir, file)));
|
|
746
955
|
const researchSignals = [
|
|
747
|
-
|
|
748
|
-
|
|
956
|
+
existsSync10(join10(targetDir, "docs/research")) ? "docs/research" : "",
|
|
957
|
+
existsSync10(join10(targetDir, "research")) ? "research" : "",
|
|
749
958
|
hasBookChildDirectory(targetDir, "research") ? "books/*/research" : "",
|
|
750
|
-
textFileIncludes(
|
|
959
|
+
textFileIncludes(join10(targetDir, "README.md"), ["research", "\u8C03\u7814"]) ? "README research" : ""
|
|
751
960
|
].filter(Boolean);
|
|
752
961
|
const writingSignals = [
|
|
753
|
-
|
|
754
|
-
|
|
962
|
+
existsSync10(join10(targetDir, "chapters")) ? "chapters" : "",
|
|
963
|
+
existsSync10(join10(targetDir, "src/chapters")) ? "src/chapters" : "",
|
|
755
964
|
hasBookChildDirectory(targetDir, "chapters") ? "books/*/chapters" : "",
|
|
756
|
-
textFileIncludes(
|
|
965
|
+
textFileIncludes(join10(targetDir, "AGENTS.md"), ["writing mode", "novel chapter", "book content"]) ? "AGENTS writing" : ""
|
|
757
966
|
].filter(Boolean);
|
|
758
967
|
return {
|
|
759
968
|
targetDir,
|
|
@@ -801,7 +1010,7 @@ function recommendBundlesForTarget(targetDir) {
|
|
|
801
1010
|
return recommendations;
|
|
802
1011
|
}
|
|
803
1012
|
function readJson(path) {
|
|
804
|
-
if (!
|
|
1013
|
+
if (!existsSync10(path)) return void 0;
|
|
805
1014
|
try {
|
|
806
1015
|
return JSON.parse(readFileSync6(path, "utf8"));
|
|
807
1016
|
} catch {
|
|
@@ -809,16 +1018,16 @@ function readJson(path) {
|
|
|
809
1018
|
}
|
|
810
1019
|
}
|
|
811
1020
|
function textFileIncludes(path, needles) {
|
|
812
|
-
if (!
|
|
1021
|
+
if (!existsSync10(path)) return false;
|
|
813
1022
|
const contents = readFileSync6(path, "utf8").toLowerCase();
|
|
814
1023
|
return needles.some((needle) => contents.includes(needle.toLowerCase()));
|
|
815
1024
|
}
|
|
816
1025
|
function hasBookChildDirectory(targetDir, childName) {
|
|
817
|
-
const booksDir =
|
|
818
|
-
if (!
|
|
1026
|
+
const booksDir = join10(targetDir, "books");
|
|
1027
|
+
if (!existsSync10(booksDir)) return false;
|
|
819
1028
|
try {
|
|
820
1029
|
return readdirSync5(booksDir, { withFileTypes: true }).some(
|
|
821
|
-
(entry) => entry.isDirectory() &&
|
|
1030
|
+
(entry) => entry.isDirectory() && existsSync10(join10(booksDir, entry.name, childName))
|
|
822
1031
|
);
|
|
823
1032
|
} catch {
|
|
824
1033
|
return false;
|
|
@@ -846,12 +1055,55 @@ function runAssets(args) {
|
|
|
846
1055
|
if (subcommand2 === "check") {
|
|
847
1056
|
return runAssetsCheck(rest);
|
|
848
1057
|
}
|
|
1058
|
+
if (subcommand2 === "public-check") {
|
|
1059
|
+
return runAssetsPublicCheck(rest);
|
|
1060
|
+
}
|
|
849
1061
|
if (subcommand2 === "npx") {
|
|
850
1062
|
return runAssetsNpx(rest);
|
|
851
1063
|
}
|
|
852
1064
|
printUsage();
|
|
853
1065
|
return 1;
|
|
854
1066
|
}
|
|
1067
|
+
function runAssetsPublicCheck(args) {
|
|
1068
|
+
const options = parsePublicCheckOptions(args);
|
|
1069
|
+
if (!options.ok) {
|
|
1070
|
+
console.error(options.error);
|
|
1071
|
+
printUsage();
|
|
1072
|
+
return 1;
|
|
1073
|
+
}
|
|
1074
|
+
const registryLoad = loadAgentAssetRegistry({ agentAssetsDir: options.value.publicRoot });
|
|
1075
|
+
if (registryLoad.issues.length > 0) {
|
|
1076
|
+
for (const issue of registryLoad.issues) {
|
|
1077
|
+
console.error(`${issue.type}: ${issue.message}`);
|
|
1078
|
+
}
|
|
1079
|
+
return 1;
|
|
1080
|
+
}
|
|
1081
|
+
const result = checkPublicAssetPromotions({
|
|
1082
|
+
publicAgentAssetsDir: options.value.publicRoot,
|
|
1083
|
+
privateAgentAssetsDir: options.value.privateRoot,
|
|
1084
|
+
registry: registryLoad.registry
|
|
1085
|
+
});
|
|
1086
|
+
if (options.value.json) {
|
|
1087
|
+
console.log(
|
|
1088
|
+
JSON.stringify(
|
|
1089
|
+
{
|
|
1090
|
+
publicRoot: options.value.publicRoot,
|
|
1091
|
+
privateRoot: options.value.privateRoot,
|
|
1092
|
+
...result
|
|
1093
|
+
},
|
|
1094
|
+
null,
|
|
1095
|
+
2
|
|
1096
|
+
)
|
|
1097
|
+
);
|
|
1098
|
+
} else if (result.issues.length === 0) {
|
|
1099
|
+
console.log(`public assets check passed (${result.checked} checked)`);
|
|
1100
|
+
} else {
|
|
1101
|
+
for (const issue of result.issues) {
|
|
1102
|
+
console.log(`${issue.type}: ${issue.message}`);
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
return result.issues.length === 0 ? 0 : 1;
|
|
1106
|
+
}
|
|
855
1107
|
function runAssetsNpx(args) {
|
|
856
1108
|
const [operation, ...rest] = args;
|
|
857
1109
|
if (!operation || operation === "--help" || operation === "-h" || rest.includes("--help")) {
|
|
@@ -911,7 +1163,8 @@ function runAssetsCheck(args) {
|
|
|
911
1163
|
const result = checkInstalledAssets({
|
|
912
1164
|
targetDir: options.value.targetDir,
|
|
913
1165
|
agentAssetsDir: loaded.agentAssetsDir,
|
|
914
|
-
registry: loaded.registry
|
|
1166
|
+
registry: loaded.registry,
|
|
1167
|
+
strictRegistry: options.value.strictRegistry
|
|
915
1168
|
});
|
|
916
1169
|
if (options.value.json) {
|
|
917
1170
|
console.log(JSON.stringify(result, null, 2));
|
|
@@ -999,13 +1252,15 @@ function runAssetsPlan(args) {
|
|
|
999
1252
|
registry: loaded.registry,
|
|
1000
1253
|
bundles: loadAgentAssetBundles(loaded.agentAssetsDir),
|
|
1001
1254
|
bundleIds: options.value.bundleIds,
|
|
1002
|
-
host: options.value.host
|
|
1255
|
+
host: options.value.host,
|
|
1256
|
+
placement: options.value.placement
|
|
1003
1257
|
});
|
|
1004
1258
|
if (options.value.json) {
|
|
1005
1259
|
console.log(JSON.stringify(plan, null, 2));
|
|
1006
1260
|
} else {
|
|
1007
1261
|
console.log(`target: ${plan.targetDir}`);
|
|
1008
1262
|
console.log(`host: ${plan.host}`);
|
|
1263
|
+
console.log(`placement: ${plan.placement}`);
|
|
1009
1264
|
console.log(`bundles: ${plan.bundleIds.join(", ")}`);
|
|
1010
1265
|
console.log(`assets: ${plan.assetIds.length}`);
|
|
1011
1266
|
console.log(`actions: ${plan.actions.length}`);
|
|
@@ -1064,6 +1319,8 @@ function parseTargetJsonOptions(args) {
|
|
|
1064
1319
|
if (!targetDir) return { ok: false, error: "Expected --target <path>" };
|
|
1065
1320
|
options.targetDir = targetDir;
|
|
1066
1321
|
index += 1;
|
|
1322
|
+
} else if (arg === "--strict-registry") {
|
|
1323
|
+
options.strictRegistry = true;
|
|
1067
1324
|
} else if (arg === "--json") {
|
|
1068
1325
|
options.json = true;
|
|
1069
1326
|
} else {
|
|
@@ -1101,6 +1358,16 @@ function parsePlanOptions(args) {
|
|
|
1101
1358
|
}
|
|
1102
1359
|
options.host = host;
|
|
1103
1360
|
index += 1;
|
|
1361
|
+
} else if (arg === "--placement") {
|
|
1362
|
+
const placement = args[index + 1];
|
|
1363
|
+
if (!isSkillPlacement(placement)) {
|
|
1364
|
+
return {
|
|
1365
|
+
ok: false,
|
|
1366
|
+
error: "Expected --placement auto|manual"
|
|
1367
|
+
};
|
|
1368
|
+
}
|
|
1369
|
+
options.placement = placement;
|
|
1370
|
+
index += 1;
|
|
1104
1371
|
} else if (arg === "--out") {
|
|
1105
1372
|
const outPath = args[index + 1];
|
|
1106
1373
|
if (!outPath) return { ok: false, error: "Expected --out <path>" };
|
|
@@ -1167,6 +1434,52 @@ function parseNpxOptions(operation, args) {
|
|
|
1167
1434
|
}
|
|
1168
1435
|
return { ok: true, value: options };
|
|
1169
1436
|
}
|
|
1437
|
+
function parsePublicCheckOptions(args) {
|
|
1438
|
+
const defaults = getDefaultPublicCheckRoots();
|
|
1439
|
+
const options = {
|
|
1440
|
+
publicRoot: defaults.publicRoot,
|
|
1441
|
+
privateRoot: defaults.privateRoot,
|
|
1442
|
+
json: false
|
|
1443
|
+
};
|
|
1444
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
1445
|
+
const arg = args[index];
|
|
1446
|
+
if (arg === "--public-root") {
|
|
1447
|
+
const publicRoot = args[index + 1];
|
|
1448
|
+
if (!publicRoot) return { ok: false, error: "Expected --public-root <path>" };
|
|
1449
|
+
options.publicRoot = publicRoot;
|
|
1450
|
+
index += 1;
|
|
1451
|
+
} else if (arg === "--private-root") {
|
|
1452
|
+
const privateRoot = args[index + 1];
|
|
1453
|
+
if (!privateRoot) return { ok: false, error: "Expected --private-root <path>" };
|
|
1454
|
+
options.privateRoot = privateRoot;
|
|
1455
|
+
index += 1;
|
|
1456
|
+
} else if (arg === "--json") {
|
|
1457
|
+
options.json = true;
|
|
1458
|
+
} else {
|
|
1459
|
+
return { ok: false, error: `Unknown assets public-check option: ${arg}` };
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
if (!existsSync11(options.publicRoot)) {
|
|
1463
|
+
return { ok: false, error: `Public agent assets root does not exist: ${options.publicRoot}` };
|
|
1464
|
+
}
|
|
1465
|
+
if (!existsSync11(options.privateRoot)) {
|
|
1466
|
+
return { ok: false, error: `Private agent assets root does not exist: ${options.privateRoot}` };
|
|
1467
|
+
}
|
|
1468
|
+
return { ok: true, value: options };
|
|
1469
|
+
}
|
|
1470
|
+
function getDefaultPublicCheckRoots() {
|
|
1471
|
+
const defaultRegistryRoot = loadAgentAssetRegistry().agentAssetsDir;
|
|
1472
|
+
if (defaultRegistryRoot.endsWith("public-agent-assets")) {
|
|
1473
|
+
return {
|
|
1474
|
+
privateRoot: join11(dirname5(defaultRegistryRoot), "agent-assets"),
|
|
1475
|
+
publicRoot: defaultRegistryRoot
|
|
1476
|
+
};
|
|
1477
|
+
}
|
|
1478
|
+
return {
|
|
1479
|
+
privateRoot: defaultRegistryRoot,
|
|
1480
|
+
publicRoot: join11(dirname5(defaultRegistryRoot), "public-agent-assets")
|
|
1481
|
+
};
|
|
1482
|
+
}
|
|
1170
1483
|
function listRegistryAssets(options) {
|
|
1171
1484
|
const loaded = loadAgentAssetRegistry();
|
|
1172
1485
|
if (loaded.issues.length > 0) {
|
|
@@ -1205,14 +1518,18 @@ function isVisibilityFilter(value) {
|
|
|
1205
1518
|
function isHost(value) {
|
|
1206
1519
|
return value === "codex" || value === "claude-code" || value === "gemini-cli" || value === "antigravity";
|
|
1207
1520
|
}
|
|
1521
|
+
function isSkillPlacement(value) {
|
|
1522
|
+
return value === "auto" || value === "manual";
|
|
1523
|
+
}
|
|
1208
1524
|
function printUsage() {
|
|
1209
1525
|
console.error("Usage:");
|
|
1210
1526
|
console.error(" pro-gov assets list [--registry] [--json] [--visibility public|private|third-party|all]");
|
|
1211
1527
|
console.error(" pro-gov assets discover [--target <path>] [--json]");
|
|
1212
1528
|
console.error(" pro-gov assets recommend [--target <path>] [--json]");
|
|
1213
|
-
console.error(" pro-gov assets plan --bundle <bundle-id> [--bundle <bundle-id>] [--target <path>] [--host codex|claude-code|gemini-cli|antigravity] [--out <path>] [--json]");
|
|
1529
|
+
console.error(" pro-gov assets plan --bundle <bundle-id> [--bundle <bundle-id>] [--target <path>] [--host codex|claude-code|gemini-cli|antigravity] [--placement auto|manual] [--out <path>] [--json]");
|
|
1214
1530
|
console.error(" pro-gov assets apply --plan <path>");
|
|
1215
1531
|
console.error(" pro-gov assets check [--target <path>] [--json]");
|
|
1532
|
+
console.error(" pro-gov assets public-check [--public-root <path>] [--private-root <path>] [--json]");
|
|
1216
1533
|
console.error(" pro-gov assets npx add <source> [--skill <name>] --plan [--root <path>]");
|
|
1217
1534
|
console.error(" pro-gov assets npx update [--skill <name>] --plan [--root <path>]");
|
|
1218
1535
|
}
|
|
@@ -1225,12 +1542,11 @@ function printNpxUsage() {
|
|
|
1225
1542
|
|
|
1226
1543
|
// src/commands/doctor.ts
|
|
1227
1544
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
1228
|
-
import { existsSync as
|
|
1545
|
+
import { existsSync as existsSync12 } from "node:fs";
|
|
1229
1546
|
import { createRequire } from "node:module";
|
|
1230
|
-
import { dirname as dirname6, join as
|
|
1547
|
+
import { dirname as dirname6, join as join12 } from "node:path";
|
|
1231
1548
|
var REQUIRED_ASSETS = [
|
|
1232
1549
|
"starter/AGENTS.template.md",
|
|
1233
|
-
"starter/.gemini/settings.json",
|
|
1234
1550
|
"starter/docs/governance/ssot-v0.9.md",
|
|
1235
1551
|
"starter/docs/governance/agents-routing/engineering-runtime-v0.9.md",
|
|
1236
1552
|
"starter/docs/governance/agents-routing/doc-only-v0.9.md",
|
|
@@ -1278,8 +1594,8 @@ function resolveDocGovDependencyCli() {
|
|
|
1278
1594
|
try {
|
|
1279
1595
|
const require2 = createRequire(import.meta.url);
|
|
1280
1596
|
const packageJsonPath = require2.resolve("@pieai/doc-gov/package.json");
|
|
1281
|
-
const cliPath =
|
|
1282
|
-
return
|
|
1597
|
+
const cliPath = join12(dirname6(packageJsonPath), "dist/cli.js");
|
|
1598
|
+
return existsSync12(cliPath) ? cliPath : null;
|
|
1283
1599
|
} catch {
|
|
1284
1600
|
return null;
|
|
1285
1601
|
}
|
|
@@ -1421,8 +1737,8 @@ function bulletList(values) {
|
|
|
1421
1737
|
|
|
1422
1738
|
// src/lens/scan.ts
|
|
1423
1739
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
1424
|
-
import { existsSync as
|
|
1425
|
-
import { join as
|
|
1740
|
+
import { existsSync as existsSync13, readdirSync as readdirSync6, readFileSync as readFileSync8, statSync as statSync3 } from "node:fs";
|
|
1741
|
+
import { join as join13, relative as relative5 } from "node:path";
|
|
1426
1742
|
var ignoredDirectories = /* @__PURE__ */ new Set([
|
|
1427
1743
|
".git",
|
|
1428
1744
|
".next",
|
|
@@ -1438,23 +1754,23 @@ function scanProjectLensTarget(targetDir, options = {}) {
|
|
|
1438
1754
|
const packageJson = readPackageJson(targetDir);
|
|
1439
1755
|
return {
|
|
1440
1756
|
targetDir,
|
|
1441
|
-
aiEntryFiles: ["AGENTS.md", "CLAUDE.md"
|
|
1442
|
-
(file) =>
|
|
1757
|
+
aiEntryFiles: ["AGENTS.md", "CLAUDE.md"].filter(
|
|
1758
|
+
(file) => existsSync13(join13(targetDir, file))
|
|
1443
1759
|
),
|
|
1444
|
-
aiConfigFiles: [
|
|
1760
|
+
aiConfigFiles: [],
|
|
1445
1761
|
packageJson,
|
|
1446
1762
|
docs: {
|
|
1447
|
-
hasDocsDirectory:
|
|
1763
|
+
hasDocsDirectory: existsSync13(join13(targetDir, "docs")),
|
|
1448
1764
|
markdownFileCount: markdownFiles.length,
|
|
1449
1765
|
governanceFiles: markdownFiles.filter((file) => file.startsWith("docs/governance/") || file.startsWith("docs/policy/")).sort()
|
|
1450
1766
|
},
|
|
1451
1767
|
git: readGitState(targetDir),
|
|
1452
|
-
largeFiles: files.map((file) => ({ path: file, bytes: statSync3(
|
|
1768
|
+
largeFiles: files.map((file) => ({ path: file, bytes: statSync3(join13(targetDir, file)).size })).filter((file) => file.bytes >= largeFileBytes).sort((a, b) => b.bytes - a.bytes || a.path.localeCompare(b.path)).slice(0, 25)
|
|
1453
1769
|
};
|
|
1454
1770
|
}
|
|
1455
1771
|
function readPackageJson(targetDir) {
|
|
1456
|
-
const packageJsonPath =
|
|
1457
|
-
if (!
|
|
1772
|
+
const packageJsonPath = join13(targetDir, "package.json");
|
|
1773
|
+
if (!existsSync13(packageJsonPath)) return void 0;
|
|
1458
1774
|
try {
|
|
1459
1775
|
const packageJson = JSON.parse(readFileSync8(packageJsonPath, "utf8"));
|
|
1460
1776
|
return {
|
|
@@ -1491,13 +1807,13 @@ function listProjectFiles(targetDir) {
|
|
|
1491
1807
|
return files.sort();
|
|
1492
1808
|
}
|
|
1493
1809
|
function collectFiles2(rootDir, currentDir, files) {
|
|
1494
|
-
if (!
|
|
1810
|
+
if (!existsSync13(currentDir)) return;
|
|
1495
1811
|
for (const entry of readdirSync6(currentDir, { withFileTypes: true })) {
|
|
1496
1812
|
if (entry.isDirectory()) {
|
|
1497
1813
|
if (ignoredDirectories.has(entry.name)) continue;
|
|
1498
|
-
collectFiles2(rootDir,
|
|
1814
|
+
collectFiles2(rootDir, join13(currentDir, entry.name), files);
|
|
1499
1815
|
} else if (entry.isFile()) {
|
|
1500
|
-
files.push(toUnixPath4(
|
|
1816
|
+
files.push(toUnixPath4(relative5(rootDir, join13(currentDir, entry.name))));
|
|
1501
1817
|
}
|
|
1502
1818
|
}
|
|
1503
1819
|
}
|
|
@@ -1591,9 +1907,441 @@ function printUsage2() {
|
|
|
1591
1907
|
console.error("Usage: pro-gov lens report --target <path> --out <path>");
|
|
1592
1908
|
}
|
|
1593
1909
|
|
|
1910
|
+
// src/commands/portfolio.ts
|
|
1911
|
+
import { existsSync as existsSync15 } from "node:fs";
|
|
1912
|
+
import { join as join14 } from "node:path";
|
|
1913
|
+
|
|
1914
|
+
// src/portfolio/manifest.ts
|
|
1915
|
+
import { existsSync as existsSync14, readFileSync as readFileSync9 } from "node:fs";
|
|
1916
|
+
function loadPortfolioManifest(configPath) {
|
|
1917
|
+
let parsed;
|
|
1918
|
+
try {
|
|
1919
|
+
parsed = JSON.parse(readFileSync9(configPath, "utf8"));
|
|
1920
|
+
} catch (error) {
|
|
1921
|
+
return {
|
|
1922
|
+
configPath,
|
|
1923
|
+
issues: [
|
|
1924
|
+
{
|
|
1925
|
+
type: "invalid-json",
|
|
1926
|
+
message: error instanceof Error ? error.message : String(error)
|
|
1927
|
+
}
|
|
1928
|
+
]
|
|
1929
|
+
};
|
|
1930
|
+
}
|
|
1931
|
+
const issues = validatePortfolioManifest(parsed);
|
|
1932
|
+
return {
|
|
1933
|
+
configPath,
|
|
1934
|
+
manifest: issues.length === 0 ? parsed : void 0,
|
|
1935
|
+
issues
|
|
1936
|
+
};
|
|
1937
|
+
}
|
|
1938
|
+
function validatePortfolioManifest(value) {
|
|
1939
|
+
const issues = [];
|
|
1940
|
+
if (!isRecord(value)) {
|
|
1941
|
+
return [
|
|
1942
|
+
{
|
|
1943
|
+
type: "invalid-field",
|
|
1944
|
+
field: "root",
|
|
1945
|
+
message: "Portfolio manifest must be a JSON object."
|
|
1946
|
+
}
|
|
1947
|
+
];
|
|
1948
|
+
}
|
|
1949
|
+
if (value.schemaVersion !== 1) {
|
|
1950
|
+
issues.push({
|
|
1951
|
+
type: "invalid-field",
|
|
1952
|
+
field: "schemaVersion",
|
|
1953
|
+
message: "Portfolio manifest schemaVersion must be 1."
|
|
1954
|
+
});
|
|
1955
|
+
}
|
|
1956
|
+
if (typeof value.portfolioId !== "string" || value.portfolioId.length === 0) {
|
|
1957
|
+
issues.push({
|
|
1958
|
+
type: "invalid-field",
|
|
1959
|
+
field: "portfolioId",
|
|
1960
|
+
message: "Portfolio manifest portfolioId must be a non-empty string."
|
|
1961
|
+
});
|
|
1962
|
+
}
|
|
1963
|
+
validateAllowedFields(value, "root", ["schemaVersion", "portfolioId", "controlPlane", "executionEngine", "targets"], issues);
|
|
1964
|
+
validateEndpoint(value.controlPlane, "controlPlane", issues);
|
|
1965
|
+
validateEndpoint(value.executionEngine, "executionEngine", issues);
|
|
1966
|
+
if (!Array.isArray(value.targets)) {
|
|
1967
|
+
issues.push({
|
|
1968
|
+
type: "invalid-field",
|
|
1969
|
+
field: "targets",
|
|
1970
|
+
message: "Portfolio manifest targets must be an array."
|
|
1971
|
+
});
|
|
1972
|
+
return issues;
|
|
1973
|
+
}
|
|
1974
|
+
const seenTargetIds = /* @__PURE__ */ new Set();
|
|
1975
|
+
for (const target of value.targets) {
|
|
1976
|
+
if (!isRecord(target)) {
|
|
1977
|
+
issues.push({
|
|
1978
|
+
type: "invalid-field",
|
|
1979
|
+
field: "targets",
|
|
1980
|
+
message: "Portfolio target must be an object."
|
|
1981
|
+
});
|
|
1982
|
+
continue;
|
|
1983
|
+
}
|
|
1984
|
+
validateEndpoint(target, "targets", issues);
|
|
1985
|
+
validateAllowedFields(target, "target", ["id", "path", "profile", "assetBundles"], issues);
|
|
1986
|
+
if (typeof target.id === "string") {
|
|
1987
|
+
if (seenTargetIds.has(target.id)) {
|
|
1988
|
+
issues.push({
|
|
1989
|
+
type: "duplicate-target-id",
|
|
1990
|
+
id: target.id,
|
|
1991
|
+
message: `Duplicate portfolio target id: ${target.id}`
|
|
1992
|
+
});
|
|
1993
|
+
}
|
|
1994
|
+
seenTargetIds.add(target.id);
|
|
1995
|
+
}
|
|
1996
|
+
if (target.profile !== void 0 && (typeof target.profile !== "string" || !isValidProfile(target.profile))) {
|
|
1997
|
+
issues.push({
|
|
1998
|
+
type: "invalid-field",
|
|
1999
|
+
id: typeof target.id === "string" ? target.id : void 0,
|
|
2000
|
+
field: "profile",
|
|
2001
|
+
message: "Portfolio target profile must be engineering-runtime or doc-only."
|
|
2002
|
+
});
|
|
2003
|
+
}
|
|
2004
|
+
validateOptionalStringArray(target.assetBundles, target.id, "assetBundles", issues);
|
|
2005
|
+
if ("sharedRules" in target) {
|
|
2006
|
+
issues.push({
|
|
2007
|
+
type: "invalid-field",
|
|
2008
|
+
id: typeof target.id === "string" ? target.id : void 0,
|
|
2009
|
+
field: "sharedRules",
|
|
2010
|
+
message: "Portfolio target sharedRules is not managed yet; remove it until plan/check supports it."
|
|
2011
|
+
});
|
|
2012
|
+
}
|
|
2013
|
+
}
|
|
2014
|
+
return issues;
|
|
2015
|
+
}
|
|
2016
|
+
function getDefaultPortfolioTargets(manifest) {
|
|
2017
|
+
return manifest?.targets ?? [];
|
|
2018
|
+
}
|
|
2019
|
+
function validateAllowedFields(value, location, allowedFields, issues) {
|
|
2020
|
+
const allowed = new Set(allowedFields);
|
|
2021
|
+
for (const field of Object.keys(value)) {
|
|
2022
|
+
if (allowed.has(field)) continue;
|
|
2023
|
+
issues.push({
|
|
2024
|
+
type: "invalid-field",
|
|
2025
|
+
id: typeof value.id === "string" ? value.id : void 0,
|
|
2026
|
+
field,
|
|
2027
|
+
message: `Unknown portfolio ${location} field: ${field}`
|
|
2028
|
+
});
|
|
2029
|
+
}
|
|
2030
|
+
}
|
|
2031
|
+
function validateEndpoint(value, field, issues) {
|
|
2032
|
+
if (value === void 0) return;
|
|
2033
|
+
if (!isRecord(value)) {
|
|
2034
|
+
issues.push({
|
|
2035
|
+
type: "invalid-field",
|
|
2036
|
+
field,
|
|
2037
|
+
message: `Portfolio ${field} must be an object.`
|
|
2038
|
+
});
|
|
2039
|
+
return;
|
|
2040
|
+
}
|
|
2041
|
+
if (typeof value.id !== "string" || value.id.length === 0) {
|
|
2042
|
+
issues.push({
|
|
2043
|
+
type: "invalid-field",
|
|
2044
|
+
field: `${field}.id`,
|
|
2045
|
+
message: `Portfolio ${field} id must be a non-empty string.`
|
|
2046
|
+
});
|
|
2047
|
+
}
|
|
2048
|
+
if (typeof value.path !== "string" || value.path.length === 0) {
|
|
2049
|
+
issues.push({
|
|
2050
|
+
type: "invalid-field",
|
|
2051
|
+
id: typeof value.id === "string" ? value.id : void 0,
|
|
2052
|
+
field: `${field}.path`,
|
|
2053
|
+
message: `Portfolio ${field} path must be a non-empty string.`
|
|
2054
|
+
});
|
|
2055
|
+
return;
|
|
2056
|
+
}
|
|
2057
|
+
if (!existsSync14(value.path)) {
|
|
2058
|
+
issues.push({
|
|
2059
|
+
type: "missing-path",
|
|
2060
|
+
id: typeof value.id === "string" ? value.id : void 0,
|
|
2061
|
+
field: `${field}.path`,
|
|
2062
|
+
message: `Portfolio ${field} path does not exist: ${value.path}`
|
|
2063
|
+
});
|
|
2064
|
+
}
|
|
2065
|
+
}
|
|
2066
|
+
function validateOptionalStringArray(value, id, field, issues) {
|
|
2067
|
+
if (value === void 0) return;
|
|
2068
|
+
if (!Array.isArray(value) || !value.every((entry) => typeof entry === "string")) {
|
|
2069
|
+
issues.push({
|
|
2070
|
+
type: "invalid-field",
|
|
2071
|
+
id: typeof id === "string" ? id : void 0,
|
|
2072
|
+
field,
|
|
2073
|
+
message: `Portfolio target ${field} must be an array of strings.`
|
|
2074
|
+
});
|
|
2075
|
+
}
|
|
2076
|
+
}
|
|
2077
|
+
function isRecord(value) {
|
|
2078
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2079
|
+
}
|
|
2080
|
+
|
|
2081
|
+
// src/commands/portfolio.ts
|
|
2082
|
+
function runPortfolio(args) {
|
|
2083
|
+
const [subcommand2, ...rest] = args;
|
|
2084
|
+
if (subcommand2 === "check") return runPortfolioCheck(rest);
|
|
2085
|
+
if (subcommand2 === "plan") return runPortfolioPlan(rest);
|
|
2086
|
+
if (subcommand2 === "assets-check") return runPortfolioAssetsCheck(rest);
|
|
2087
|
+
printUsage3();
|
|
2088
|
+
return 1;
|
|
2089
|
+
}
|
|
2090
|
+
function runPortfolioCheck(args) {
|
|
2091
|
+
const options = parsePortfolioOptions(args);
|
|
2092
|
+
if (!options.ok) {
|
|
2093
|
+
console.error(options.error);
|
|
2094
|
+
printUsage3();
|
|
2095
|
+
return 1;
|
|
2096
|
+
}
|
|
2097
|
+
const loaded = loadPortfolioManifest(options.value.configPath);
|
|
2098
|
+
const targetIds = getDefaultPortfolioTargets(loaded.manifest).map((target) => target.id);
|
|
2099
|
+
if (options.value.json) {
|
|
2100
|
+
console.log(
|
|
2101
|
+
JSON.stringify(
|
|
2102
|
+
{
|
|
2103
|
+
ok: loaded.issues.length === 0,
|
|
2104
|
+
configPath: loaded.configPath,
|
|
2105
|
+
portfolioId: loaded.manifest?.portfolioId,
|
|
2106
|
+
targetIds,
|
|
2107
|
+
issues: loaded.issues
|
|
2108
|
+
},
|
|
2109
|
+
null,
|
|
2110
|
+
2
|
|
2111
|
+
)
|
|
2112
|
+
);
|
|
2113
|
+
} else if (loaded.issues.length === 0) {
|
|
2114
|
+
console.log(`portfolio check passed (${targetIds.length} targets)`);
|
|
2115
|
+
} else {
|
|
2116
|
+
for (const issue of loaded.issues) {
|
|
2117
|
+
console.log(`${issue.type}: ${issue.message}`);
|
|
2118
|
+
}
|
|
2119
|
+
}
|
|
2120
|
+
return loaded.issues.length === 0 ? 0 : 1;
|
|
2121
|
+
}
|
|
2122
|
+
function runPortfolioPlan(args) {
|
|
2123
|
+
const options = parsePortfolioOptions(args);
|
|
2124
|
+
if (!options.ok) {
|
|
2125
|
+
console.error(options.error);
|
|
2126
|
+
printUsage3();
|
|
2127
|
+
return 1;
|
|
2128
|
+
}
|
|
2129
|
+
const loaded = loadPortfolioManifest(options.value.configPath);
|
|
2130
|
+
if (loaded.issues.length > 0 || !loaded.manifest) {
|
|
2131
|
+
for (const issue of loaded.issues) {
|
|
2132
|
+
console.error(`${issue.type}: ${issue.message}`);
|
|
2133
|
+
}
|
|
2134
|
+
return 1;
|
|
2135
|
+
}
|
|
2136
|
+
const targets = getDefaultPortfolioTargets(loaded.manifest).filter(
|
|
2137
|
+
(target) => !options.value.targetId || options.value.targetId === "all" || target.id === options.value.targetId
|
|
2138
|
+
);
|
|
2139
|
+
if (targets.length === 0) {
|
|
2140
|
+
console.error(`Unknown portfolio target: ${options.value.targetId}`);
|
|
2141
|
+
return 1;
|
|
2142
|
+
}
|
|
2143
|
+
const loadedAssets = loadAgentAssetRegistry({
|
|
2144
|
+
agentAssetsDir: findPortfolioAgentAssetsDir(loaded.manifest)
|
|
2145
|
+
});
|
|
2146
|
+
if (loadedAssets.issues.length > 0) {
|
|
2147
|
+
for (const issue of loadedAssets.issues) {
|
|
2148
|
+
console.error(`${issue.type}: ${issue.message}`);
|
|
2149
|
+
}
|
|
2150
|
+
return 1;
|
|
2151
|
+
}
|
|
2152
|
+
const bundles = loadAgentAssetBundles(loadedAssets.agentAssetsDir);
|
|
2153
|
+
try {
|
|
2154
|
+
const plans = targets.map((target) => {
|
|
2155
|
+
const bundleIds = target.assetBundles ?? [];
|
|
2156
|
+
if (bundleIds.length === 0) {
|
|
2157
|
+
throw new Error(`Portfolio target ${target.id} has no assetBundles`);
|
|
2158
|
+
}
|
|
2159
|
+
return {
|
|
2160
|
+
id: target.id,
|
|
2161
|
+
path: target.path,
|
|
2162
|
+
plan: createAssetInstallPlan({
|
|
2163
|
+
targetDir: target.path,
|
|
2164
|
+
agentAssetsDir: loadedAssets.agentAssetsDir,
|
|
2165
|
+
registry: loadedAssets.registry,
|
|
2166
|
+
bundles,
|
|
2167
|
+
bundleIds,
|
|
2168
|
+
host: options.value.host
|
|
2169
|
+
})
|
|
2170
|
+
};
|
|
2171
|
+
});
|
|
2172
|
+
if (options.value.json) {
|
|
2173
|
+
console.log(
|
|
2174
|
+
JSON.stringify(
|
|
2175
|
+
{
|
|
2176
|
+
configPath: loaded.configPath,
|
|
2177
|
+
portfolioId: loaded.manifest.portfolioId,
|
|
2178
|
+
targets: plans
|
|
2179
|
+
},
|
|
2180
|
+
null,
|
|
2181
|
+
2
|
|
2182
|
+
)
|
|
2183
|
+
);
|
|
2184
|
+
} else {
|
|
2185
|
+
console.log(`portfolio plan targets: ${plans.length}`);
|
|
2186
|
+
for (const target of plans) {
|
|
2187
|
+
console.log(`${target.id} ${target.plan.actions.length} actions dry-run`);
|
|
2188
|
+
}
|
|
2189
|
+
}
|
|
2190
|
+
return 0;
|
|
2191
|
+
} catch (error) {
|
|
2192
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
2193
|
+
return 1;
|
|
2194
|
+
}
|
|
2195
|
+
}
|
|
2196
|
+
function runPortfolioAssetsCheck(args) {
|
|
2197
|
+
const options = parsePortfolioOptions(args);
|
|
2198
|
+
if (!options.ok) {
|
|
2199
|
+
console.error(options.error);
|
|
2200
|
+
printUsage3();
|
|
2201
|
+
return 1;
|
|
2202
|
+
}
|
|
2203
|
+
const loaded = loadPortfolioManifest(options.value.configPath);
|
|
2204
|
+
if (loaded.issues.length > 0 || !loaded.manifest) {
|
|
2205
|
+
if (options.value.json) {
|
|
2206
|
+
console.log(
|
|
2207
|
+
JSON.stringify(
|
|
2208
|
+
{
|
|
2209
|
+
ok: false,
|
|
2210
|
+
configPath: loaded.configPath,
|
|
2211
|
+
issues: loaded.issues,
|
|
2212
|
+
targets: []
|
|
2213
|
+
},
|
|
2214
|
+
null,
|
|
2215
|
+
2
|
|
2216
|
+
)
|
|
2217
|
+
);
|
|
2218
|
+
} else {
|
|
2219
|
+
for (const issue of loaded.issues) {
|
|
2220
|
+
console.error(`${issue.type}: ${issue.message}`);
|
|
2221
|
+
}
|
|
2222
|
+
}
|
|
2223
|
+
return 1;
|
|
2224
|
+
}
|
|
2225
|
+
const targets = getDefaultPortfolioTargets(loaded.manifest).filter(
|
|
2226
|
+
(target) => !options.value.targetId || options.value.targetId === "all" || target.id === options.value.targetId
|
|
2227
|
+
);
|
|
2228
|
+
if (targets.length === 0) {
|
|
2229
|
+
console.error(`Unknown portfolio target: ${options.value.targetId}`);
|
|
2230
|
+
return 1;
|
|
2231
|
+
}
|
|
2232
|
+
const loadedAssets = loadAgentAssetRegistry({
|
|
2233
|
+
agentAssetsDir: findPortfolioAgentAssetsDir(loaded.manifest)
|
|
2234
|
+
});
|
|
2235
|
+
if (loadedAssets.issues.length > 0) {
|
|
2236
|
+
if (options.value.json) {
|
|
2237
|
+
console.log(
|
|
2238
|
+
JSON.stringify(
|
|
2239
|
+
{
|
|
2240
|
+
ok: false,
|
|
2241
|
+
configPath: loaded.configPath,
|
|
2242
|
+
portfolioId: loaded.manifest.portfolioId,
|
|
2243
|
+
registryIssues: loadedAssets.issues,
|
|
2244
|
+
targets: []
|
|
2245
|
+
},
|
|
2246
|
+
null,
|
|
2247
|
+
2
|
|
2248
|
+
)
|
|
2249
|
+
);
|
|
2250
|
+
} else {
|
|
2251
|
+
for (const issue of loadedAssets.issues) {
|
|
2252
|
+
console.error(`${issue.type}: ${issue.message}`);
|
|
2253
|
+
}
|
|
2254
|
+
}
|
|
2255
|
+
return 1;
|
|
2256
|
+
}
|
|
2257
|
+
const targetResults = targets.map((target) => {
|
|
2258
|
+
const result = checkInstalledAssets({
|
|
2259
|
+
targetDir: target.path,
|
|
2260
|
+
agentAssetsDir: loadedAssets.agentAssetsDir,
|
|
2261
|
+
registry: loadedAssets.registry,
|
|
2262
|
+
strictRegistry: true
|
|
2263
|
+
});
|
|
2264
|
+
return {
|
|
2265
|
+
id: target.id,
|
|
2266
|
+
path: target.path,
|
|
2267
|
+
issues: result.issues
|
|
2268
|
+
};
|
|
2269
|
+
});
|
|
2270
|
+
const ok = targetResults.every((target) => target.issues.length === 0);
|
|
2271
|
+
if (options.value.json) {
|
|
2272
|
+
console.log(
|
|
2273
|
+
JSON.stringify(
|
|
2274
|
+
{
|
|
2275
|
+
ok,
|
|
2276
|
+
configPath: loaded.configPath,
|
|
2277
|
+
portfolioId: loaded.manifest.portfolioId,
|
|
2278
|
+
agentAssetsDir: loadedAssets.agentAssetsDir,
|
|
2279
|
+
targets: targetResults
|
|
2280
|
+
},
|
|
2281
|
+
null,
|
|
2282
|
+
2
|
|
2283
|
+
)
|
|
2284
|
+
);
|
|
2285
|
+
} else if (ok) {
|
|
2286
|
+
console.log(`portfolio assets check passed (${targetResults.length} targets)`);
|
|
2287
|
+
} else {
|
|
2288
|
+
for (const target of targetResults) {
|
|
2289
|
+
for (const issue of target.issues) {
|
|
2290
|
+
console.log(`${target.id} ${issue.type}: ${issue.message}`);
|
|
2291
|
+
}
|
|
2292
|
+
}
|
|
2293
|
+
}
|
|
2294
|
+
return ok ? 0 : 1;
|
|
2295
|
+
}
|
|
2296
|
+
function parsePortfolioOptions(args) {
|
|
2297
|
+
const options = {
|
|
2298
|
+
configPath: "",
|
|
2299
|
+
host: "codex",
|
|
2300
|
+
json: false
|
|
2301
|
+
};
|
|
2302
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
2303
|
+
const arg = args[index];
|
|
2304
|
+
if (arg === "--config") {
|
|
2305
|
+
const configPath = args[index + 1];
|
|
2306
|
+
if (!configPath) return { ok: false, error: "Expected --config <path>" };
|
|
2307
|
+
options.configPath = configPath;
|
|
2308
|
+
index += 1;
|
|
2309
|
+
} else if (arg === "--target") {
|
|
2310
|
+
const targetId = args[index + 1];
|
|
2311
|
+
if (!targetId) return { ok: false, error: "Expected --target <id|all>" };
|
|
2312
|
+
options.targetId = targetId;
|
|
2313
|
+
index += 1;
|
|
2314
|
+
} else if (arg === "--host") {
|
|
2315
|
+
const host = args[index + 1];
|
|
2316
|
+
if (!isHost2(host)) return { ok: false, error: "Expected --host codex|claude-code" };
|
|
2317
|
+
options.host = host;
|
|
2318
|
+
index += 1;
|
|
2319
|
+
} else if (arg === "--json") {
|
|
2320
|
+
options.json = true;
|
|
2321
|
+
} else {
|
|
2322
|
+
return { ok: false, error: `Unknown portfolio option: ${arg}` };
|
|
2323
|
+
}
|
|
2324
|
+
}
|
|
2325
|
+
if (!options.configPath) return { ok: false, error: "Expected --config <path>" };
|
|
2326
|
+
return { ok: true, value: options };
|
|
2327
|
+
}
|
|
2328
|
+
function isHost2(value) {
|
|
2329
|
+
return value === "codex" || value === "claude-code" || value === "gemini-cli" || value === "antigravity";
|
|
2330
|
+
}
|
|
2331
|
+
function findPortfolioAgentAssetsDir(manifest) {
|
|
2332
|
+
const agentAssetsDir = manifest?.executionEngine?.path ? join14(manifest.executionEngine.path, "agent-assets") : void 0;
|
|
2333
|
+
return agentAssetsDir && existsSync15(join14(agentAssetsDir, "registry.json")) ? agentAssetsDir : void 0;
|
|
2334
|
+
}
|
|
2335
|
+
function printUsage3() {
|
|
2336
|
+
console.error("Usage:");
|
|
2337
|
+
console.error(" pro-gov portfolio check --config <path> [--json]");
|
|
2338
|
+
console.error(" pro-gov portfolio plan --config <path> [--target <id|all>] [--host codex|claude-code|gemini-cli|antigravity] [--json]");
|
|
2339
|
+
console.error(" pro-gov portfolio assets-check --config <path> [--target <id|all>] [--json]");
|
|
2340
|
+
}
|
|
2341
|
+
|
|
1594
2342
|
// src/commands/sync.ts
|
|
1595
|
-
import { existsSync as
|
|
1596
|
-
import { join as
|
|
2343
|
+
import { existsSync as existsSync16, readFileSync as readFileSync10 } from "node:fs";
|
|
2344
|
+
import { join as join15 } from "node:path";
|
|
1597
2345
|
function runSync(args) {
|
|
1598
2346
|
if (!args.includes("--check")) {
|
|
1599
2347
|
console.error("pro-gov sync requires --check in this first read-only release.");
|
|
@@ -1602,14 +2350,14 @@ function runSync(args) {
|
|
|
1602
2350
|
let differences = 0;
|
|
1603
2351
|
console.log("pro-gov sync check");
|
|
1604
2352
|
for (const file of planStarterFiles()) {
|
|
1605
|
-
const targetPath =
|
|
1606
|
-
if (!
|
|
2353
|
+
const targetPath = join15(process.cwd(), file.targetPath);
|
|
2354
|
+
if (!existsSync16(targetPath)) {
|
|
1607
2355
|
console.log(`missing: ${file.targetPath}`);
|
|
1608
2356
|
differences += 1;
|
|
1609
2357
|
continue;
|
|
1610
2358
|
}
|
|
1611
|
-
const source =
|
|
1612
|
-
const target =
|
|
2359
|
+
const source = readFileSync10(file.absoluteSourcePath, "utf8");
|
|
2360
|
+
const target = readFileSync10(targetPath, "utf8");
|
|
1613
2361
|
if (source !== target) {
|
|
1614
2362
|
console.log(`different: ${file.targetPath}`);
|
|
1615
2363
|
differences += 1;
|
|
@@ -1630,8 +2378,12 @@ var COMMANDS = [
|
|
|
1630
2378
|
"assets recommend [--target <path>] [--json]",
|
|
1631
2379
|
"assets plan --bundle <bundle-id> [--target <path>] [--json]",
|
|
1632
2380
|
"assets apply --plan <path>",
|
|
1633
|
-
"assets check [--target <path>] [--json]",
|
|
2381
|
+
"assets check [--target <path>] [--strict-registry] [--json]",
|
|
2382
|
+
"assets public-check [--public-root <path>] [--private-root <path>] [--json]",
|
|
1634
2383
|
"assets npx add|update ... --plan",
|
|
2384
|
+
"portfolio check --config <path> [--json]",
|
|
2385
|
+
"portfolio plan --config <path> [--target <id|all>] [--json]",
|
|
2386
|
+
"portfolio assets-check --config <path> [--target <id|all>] [--json]",
|
|
1635
2387
|
"lens scan [--target <path>] [--json]",
|
|
1636
2388
|
"lens inspect [--target <path>] [--format text|json]",
|
|
1637
2389
|
"lens report --target <path> --out <path>",
|
|
@@ -1647,6 +2399,8 @@ if (!command || command === "--help" || command === "-h") {
|
|
|
1647
2399
|
process.exitCode = runAssets(process.argv.slice(3));
|
|
1648
2400
|
} else if (command === "lens") {
|
|
1649
2401
|
process.exitCode = runLens(process.argv.slice(3));
|
|
2402
|
+
} else if (command === "portfolio") {
|
|
2403
|
+
process.exitCode = runPortfolio(process.argv.slice(3));
|
|
1650
2404
|
} else if (command === "init") {
|
|
1651
2405
|
process.exitCode = runInit(process.argv.slice(3));
|
|
1652
2406
|
} else if (command === "sync") {
|