@pieai/pro-gov 0.3.4 → 0.3.6
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 +14 -3
- package/assets/docs/reference/adoption/project-relationship.md +7 -5
- package/assets/docs/reference/adoption/public-release-checklist.md +27 -2
- 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 +670 -79
- package/package.json +12 -13
- 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) {
|
|
@@ -413,9 +435,105 @@ function toUnixPath3(path) {
|
|
|
413
435
|
return path.replaceAll("\\", "/");
|
|
414
436
|
}
|
|
415
437
|
|
|
438
|
+
// src/asset-registry/public-promotion.ts
|
|
439
|
+
import { existsSync as existsSync6 } from "node:fs";
|
|
440
|
+
import { isAbsolute as isAbsolute2, join as join6, posix as posix2 } from "node:path";
|
|
441
|
+
function checkPublicAssetPromotions(options) {
|
|
442
|
+
const issues = [];
|
|
443
|
+
let checked = 0;
|
|
444
|
+
for (const asset of options.registry.assets) {
|
|
445
|
+
if (!needsPromotionCheck(asset)) continue;
|
|
446
|
+
checked += 1;
|
|
447
|
+
if (!asset.promotion) {
|
|
448
|
+
issues.push({
|
|
449
|
+
type: "missing-promotion",
|
|
450
|
+
id: asset.id,
|
|
451
|
+
message: `Public asset is missing promotion metadata: ${asset.id}`
|
|
452
|
+
});
|
|
453
|
+
continue;
|
|
454
|
+
}
|
|
455
|
+
const publicPathResult = resolveSafePath(options.publicAgentAssetsDir, asset.sourcePath);
|
|
456
|
+
if (!publicPathResult.ok) {
|
|
457
|
+
issues.push({
|
|
458
|
+
type: "unsafe-public-source-path",
|
|
459
|
+
id: asset.id,
|
|
460
|
+
path: asset.sourcePath,
|
|
461
|
+
message: `Public asset source path is unsafe: ${asset.sourcePath}`
|
|
462
|
+
});
|
|
463
|
+
continue;
|
|
464
|
+
}
|
|
465
|
+
const privatePathResult = resolveSafePath(
|
|
466
|
+
options.privateAgentAssetsDir,
|
|
467
|
+
asset.promotion.privateSourcePath
|
|
468
|
+
);
|
|
469
|
+
if (!privatePathResult.ok) {
|
|
470
|
+
issues.push({
|
|
471
|
+
type: "unsafe-private-source-path",
|
|
472
|
+
id: asset.id,
|
|
473
|
+
path: asset.promotion.privateSourcePath,
|
|
474
|
+
message: `Private promotion source path is unsafe: ${asset.promotion.privateSourcePath}`
|
|
475
|
+
});
|
|
476
|
+
continue;
|
|
477
|
+
}
|
|
478
|
+
if (!existsSync6(privatePathResult.path)) {
|
|
479
|
+
issues.push({
|
|
480
|
+
type: "missing-private-source",
|
|
481
|
+
id: asset.id,
|
|
482
|
+
path: asset.promotion.privateSourcePath,
|
|
483
|
+
message: `Private promotion source does not exist: ${asset.promotion.privateSourcePath}`
|
|
484
|
+
});
|
|
485
|
+
} else {
|
|
486
|
+
const actualPrivateHash = hashAssetPathContent(privatePathResult.path);
|
|
487
|
+
if (actualPrivateHash !== asset.promotion.privateSourceHash) {
|
|
488
|
+
issues.push({
|
|
489
|
+
type: "source-drift",
|
|
490
|
+
id: asset.id,
|
|
491
|
+
path: asset.promotion.privateSourcePath,
|
|
492
|
+
expected: asset.promotion.privateSourceHash,
|
|
493
|
+
actual: actualPrivateHash,
|
|
494
|
+
message: `Private source changed after public promotion: ${asset.id}`
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
if (!existsSync6(publicPathResult.path)) {
|
|
499
|
+
issues.push({
|
|
500
|
+
type: "missing-public-source",
|
|
501
|
+
id: asset.id,
|
|
502
|
+
path: asset.sourcePath,
|
|
503
|
+
message: `Public promoted asset does not exist: ${asset.sourcePath}`
|
|
504
|
+
});
|
|
505
|
+
} else {
|
|
506
|
+
const actualPublicHash = hashAssetPathContent(publicPathResult.path);
|
|
507
|
+
if (actualPublicHash !== asset.promotion.publicHash) {
|
|
508
|
+
issues.push({
|
|
509
|
+
type: "public-drift",
|
|
510
|
+
id: asset.id,
|
|
511
|
+
path: asset.sourcePath,
|
|
512
|
+
expected: asset.promotion.publicHash,
|
|
513
|
+
actual: actualPublicHash,
|
|
514
|
+
message: `Public promoted asset changed after review: ${asset.id}`
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
return { checked, issues };
|
|
520
|
+
}
|
|
521
|
+
function needsPromotionCheck(asset) {
|
|
522
|
+
return asset.visibility === "public" && asset.publishable;
|
|
523
|
+
}
|
|
524
|
+
function resolveSafePath(root, sourcePath) {
|
|
525
|
+
if (!sourcePath || isAbsolute2(sourcePath) || sourcePath.startsWith("/")) return { ok: false };
|
|
526
|
+
const normalized = posix2.normalize(sourcePath.replaceAll("\\", "/"));
|
|
527
|
+
if (normalized === "." || normalized === ".." || normalized.startsWith("../")) {
|
|
528
|
+
return { ok: false };
|
|
529
|
+
}
|
|
530
|
+
if (normalized.split("/").includes("..")) return { ok: false };
|
|
531
|
+
return { ok: true, path: join6(root, normalized) };
|
|
532
|
+
}
|
|
533
|
+
|
|
416
534
|
// src/asset-targets/apply.ts
|
|
417
|
-
import { existsSync as
|
|
418
|
-
import { dirname as dirname3, join as
|
|
535
|
+
import { existsSync as existsSync7, lstatSync as lstatSync2, mkdirSync as mkdirSync2, symlinkSync, unlinkSync, writeFileSync } from "node:fs";
|
|
536
|
+
import { dirname as dirname3, join as join7, resolve } from "node:path";
|
|
419
537
|
function applyAssetInstallPlan(plan) {
|
|
420
538
|
const appliedActions = [];
|
|
421
539
|
for (const action of plan.actions) {
|
|
@@ -425,7 +543,7 @@ function applyAssetInstallPlan(plan) {
|
|
|
425
543
|
return { appliedActions };
|
|
426
544
|
}
|
|
427
545
|
function applyAction(targetDir, action) {
|
|
428
|
-
const targetAbsolutePath =
|
|
546
|
+
const targetAbsolutePath = join7(targetDir, action.targetPath);
|
|
429
547
|
if (action.type === "create-dir") {
|
|
430
548
|
mkdirSync2(targetAbsolutePath, { recursive: true });
|
|
431
549
|
return;
|
|
@@ -460,16 +578,16 @@ function pathExistsEvenIfDanglingSymlink2(path) {
|
|
|
460
578
|
lstatSync2(path);
|
|
461
579
|
return true;
|
|
462
580
|
} catch {
|
|
463
|
-
return
|
|
581
|
+
return existsSync7(path);
|
|
464
582
|
}
|
|
465
583
|
}
|
|
466
584
|
|
|
467
585
|
// src/asset-targets/check.ts
|
|
468
|
-
import { existsSync as
|
|
469
|
-
import { join as
|
|
586
|
+
import { existsSync as existsSync8, lstatSync as lstatSync3, readFileSync as readFileSync4 } from "node:fs";
|
|
587
|
+
import { basename, join as join8 } from "node:path";
|
|
470
588
|
function checkInstalledAssets(options) {
|
|
471
|
-
const lockfilePath =
|
|
472
|
-
if (!
|
|
589
|
+
const lockfilePath = join8(options.targetDir, ".pro-gov/assets.lock.json");
|
|
590
|
+
if (!existsSync8(lockfilePath)) {
|
|
473
591
|
return {
|
|
474
592
|
targetDir: options.targetDir,
|
|
475
593
|
issues: [
|
|
@@ -485,8 +603,8 @@ function checkInstalledAssets(options) {
|
|
|
485
603
|
const issues = [];
|
|
486
604
|
for (const entry of lockfile.assets ?? []) {
|
|
487
605
|
const asset = registryById.get(entry.id);
|
|
488
|
-
const targetAbsolutePath =
|
|
489
|
-
const sourceAbsolutePath =
|
|
606
|
+
const targetAbsolutePath = join8(options.targetDir, entry.targetPath);
|
|
607
|
+
const sourceAbsolutePath = join8(options.agentAssetsDir, entry.sourcePath);
|
|
490
608
|
if (!asset) {
|
|
491
609
|
issues.push({
|
|
492
610
|
type: "unknown-asset",
|
|
@@ -496,10 +614,22 @@ function checkInstalledAssets(options) {
|
|
|
496
614
|
});
|
|
497
615
|
continue;
|
|
498
616
|
}
|
|
617
|
+
if (asset.kind === "skill" && asset.defaultScope === "user") {
|
|
618
|
+
issues.push({
|
|
619
|
+
type: "user-scoped-asset-in-project-lock",
|
|
620
|
+
id: entry.id,
|
|
621
|
+
targetPath: entry.targetPath,
|
|
622
|
+
message: `User-scoped skill is still locked into this project; move it to the user skill roots: ${entry.id}`
|
|
623
|
+
});
|
|
624
|
+
}
|
|
499
625
|
const hostFolderIssue = checkHostFolder(lockfile.host, asset.kind, entry.targetPath, entry.id);
|
|
500
626
|
if (hostFolderIssue) {
|
|
501
627
|
issues.push(hostFolderIssue);
|
|
502
628
|
}
|
|
629
|
+
const placementDriftIssue = checkRegistryPlacement(lockfile, asset, entry.targetPath);
|
|
630
|
+
if (placementDriftIssue) {
|
|
631
|
+
issues.push(placementDriftIssue);
|
|
632
|
+
}
|
|
503
633
|
if (!pathExistsEvenIfDanglingSymlink3(targetAbsolutePath)) {
|
|
504
634
|
issues.push({
|
|
505
635
|
type: "missing-target",
|
|
@@ -519,7 +649,7 @@ function checkInstalledAssets(options) {
|
|
|
519
649
|
});
|
|
520
650
|
continue;
|
|
521
651
|
}
|
|
522
|
-
if (!
|
|
652
|
+
if (!existsSync8(targetAbsolutePath)) {
|
|
523
653
|
issues.push({
|
|
524
654
|
type: "dangling-symlink",
|
|
525
655
|
id: entry.id,
|
|
@@ -528,7 +658,7 @@ function checkInstalledAssets(options) {
|
|
|
528
658
|
});
|
|
529
659
|
continue;
|
|
530
660
|
}
|
|
531
|
-
if (!
|
|
661
|
+
if (!existsSync8(sourceAbsolutePath)) {
|
|
532
662
|
issues.push({
|
|
533
663
|
type: "missing-source",
|
|
534
664
|
id: entry.id,
|
|
@@ -547,12 +677,43 @@ function checkInstalledAssets(options) {
|
|
|
547
677
|
});
|
|
548
678
|
}
|
|
549
679
|
}
|
|
680
|
+
issues.push(...checkDuplicateSkillPlacements(options.targetDir, options.registry));
|
|
550
681
|
return { targetDir: options.targetDir, issues };
|
|
551
682
|
}
|
|
683
|
+
function checkRegistryPlacement(lockfile, asset, targetPath) {
|
|
684
|
+
if (lockfile.placement !== "registry") return void 0;
|
|
685
|
+
if (asset.kind !== "skill") return void 0;
|
|
686
|
+
const expectedPath = expectedRegistrySkillTargetPath(lockfile.host, asset.sourcePath, asset.defaultPlacement);
|
|
687
|
+
if (!expectedPath || targetPath === expectedPath) return void 0;
|
|
688
|
+
return {
|
|
689
|
+
type: "skill-placement-drift",
|
|
690
|
+
id: asset.id,
|
|
691
|
+
targetPath,
|
|
692
|
+
message: `Managed skill target ${targetPath} does not match registry placement; expected ${expectedPath}`
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
function checkDuplicateSkillPlacements(targetDir, registry) {
|
|
696
|
+
const issues = [];
|
|
697
|
+
for (const asset of registry.assets) {
|
|
698
|
+
if (asset.kind !== "skill") continue;
|
|
699
|
+
const skillName = basename(asset.sourcePath);
|
|
700
|
+
const autoPath = `.agents/skills/${skillName}`;
|
|
701
|
+
const manualPath = `.agents/manual-skills/${skillName}`;
|
|
702
|
+
if (pathExistsEvenIfDanglingSymlink3(join8(targetDir, autoPath)) && pathExistsEvenIfDanglingSymlink3(join8(targetDir, manualPath))) {
|
|
703
|
+
issues.push({
|
|
704
|
+
type: "duplicate-skill-placement",
|
|
705
|
+
id: asset.id,
|
|
706
|
+
targetPath: `${autoPath} + ${manualPath}`,
|
|
707
|
+
message: `Skill is linked in both auto and manual locations: ${skillName}`
|
|
708
|
+
});
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
return issues;
|
|
712
|
+
}
|
|
552
713
|
function checkHostFolder(host, kind, targetPath, id) {
|
|
553
714
|
if (kind !== "skill") return void 0;
|
|
554
|
-
const
|
|
555
|
-
if (!
|
|
715
|
+
const expectedPrefixes = expectedSkillTargetPrefixes(host);
|
|
716
|
+
if (!expectedPrefixes) {
|
|
556
717
|
return {
|
|
557
718
|
type: "unsupported-host-folder",
|
|
558
719
|
id,
|
|
@@ -560,20 +721,30 @@ function checkHostFolder(host, kind, targetPath, id) {
|
|
|
560
721
|
message: `Lockfile host is unsupported for managed skill target: ${host ?? "missing"}`
|
|
561
722
|
};
|
|
562
723
|
}
|
|
563
|
-
if (!targetPath.startsWith(
|
|
724
|
+
if (!expectedPrefixes.some((prefix) => targetPath.startsWith(prefix))) {
|
|
564
725
|
return {
|
|
565
726
|
type: "unsupported-host-folder",
|
|
566
727
|
id,
|
|
567
728
|
targetPath,
|
|
568
|
-
message: `Managed skill target ${targetPath} does not match host ${host}; expected ${
|
|
729
|
+
message: `Managed skill target ${targetPath} does not match host ${host}; expected ${expectedPrefixes.join(" or ")}`
|
|
569
730
|
};
|
|
570
731
|
}
|
|
571
732
|
return void 0;
|
|
572
733
|
}
|
|
573
|
-
function
|
|
574
|
-
if (host === "claude-code") return ".claude/skills/";
|
|
734
|
+
function expectedSkillTargetPrefixes(host) {
|
|
735
|
+
if (host === "claude-code") return [".claude/skills/"];
|
|
575
736
|
if (host === "codex" || host === "gemini-cli" || host === "antigravity") {
|
|
576
|
-
return ".agents/skills/";
|
|
737
|
+
return [".agents/skills/", ".agents/manual-skills/"];
|
|
738
|
+
}
|
|
739
|
+
return void 0;
|
|
740
|
+
}
|
|
741
|
+
function expectedRegistrySkillTargetPath(host, sourcePath, placement) {
|
|
742
|
+
const skillName = basename(sourcePath);
|
|
743
|
+
if (host === "claude-code") {
|
|
744
|
+
return placement === "manual" ? void 0 : `.claude/skills/${skillName}`;
|
|
745
|
+
}
|
|
746
|
+
if (host === "codex" || host === "gemini-cli" || host === "antigravity") {
|
|
747
|
+
return placement === "manual" ? `.agents/manual-skills/${skillName}` : `.agents/skills/${skillName}`;
|
|
577
748
|
}
|
|
578
749
|
return void 0;
|
|
579
750
|
}
|
|
@@ -587,9 +758,10 @@ function pathExistsEvenIfDanglingSymlink3(path) {
|
|
|
587
758
|
}
|
|
588
759
|
|
|
589
760
|
// src/asset-targets/install-plan.ts
|
|
590
|
-
import { existsSync as
|
|
591
|
-
import { basename, dirname as dirname4, join as
|
|
761
|
+
import { existsSync as existsSync9, lstatSync as lstatSync4, readFileSync as readFileSync5 } from "node:fs";
|
|
762
|
+
import { basename as basename2, dirname as dirname4, join as join9 } from "node:path";
|
|
592
763
|
function createAssetInstallPlan(options) {
|
|
764
|
+
const placement = options.placement ?? "registry";
|
|
593
765
|
const assetsById = new Map(options.registry.assets.map((asset) => [asset.id, asset]));
|
|
594
766
|
const bundlesById = new Map(options.bundles.map((bundle) => [bundle.id, bundle]));
|
|
595
767
|
const assetIds = resolveBundleAssetIds(options.bundleIds, bundlesById);
|
|
@@ -604,17 +776,19 @@ function createAssetInstallPlan(options) {
|
|
|
604
776
|
const lockEntries = createAgentAssetLockEntries(options.registry, options.agentAssetsDir, assetIds);
|
|
605
777
|
const managedTargets = readManagedTargets(options.targetDir);
|
|
606
778
|
const assetActions = assets.map(
|
|
607
|
-
(asset) => createAssetAction(asset, options.agentAssetsDir, options.targetDir, options.host, managedTargets)
|
|
779
|
+
(asset) => createAssetAction(asset, options.agentAssetsDir, options.targetDir, options.host, placement, managedTargets)
|
|
608
780
|
);
|
|
609
781
|
const manifest = {
|
|
610
782
|
schemaVersion: 1,
|
|
611
783
|
host: options.host,
|
|
784
|
+
placement,
|
|
612
785
|
bundleIds: [...options.bundleIds],
|
|
613
786
|
assetIds
|
|
614
787
|
};
|
|
615
788
|
const lockfile = {
|
|
616
789
|
schemaVersion: 1,
|
|
617
790
|
host: options.host,
|
|
791
|
+
placement,
|
|
618
792
|
bundleIds: [...options.bundleIds],
|
|
619
793
|
assets: lockEntries.map((entry) => {
|
|
620
794
|
const action = assetActions.find((candidate) => "assetId" in candidate && candidate.assetId === entry.id);
|
|
@@ -643,6 +817,7 @@ function createAssetInstallPlan(options) {
|
|
|
643
817
|
dryRun: true,
|
|
644
818
|
targetDir: options.targetDir,
|
|
645
819
|
host: options.host,
|
|
820
|
+
placement,
|
|
646
821
|
bundleIds: [...options.bundleIds],
|
|
647
822
|
assetIds,
|
|
648
823
|
actions: [...createDirectoryActions([...assetActions, ...writeActions]), ...assetActions, ...writeActions]
|
|
@@ -659,10 +834,15 @@ function resolveBundleAssetIds(bundleIds, bundlesById) {
|
|
|
659
834
|
}
|
|
660
835
|
return [...ids].sort();
|
|
661
836
|
}
|
|
662
|
-
function createAssetAction(asset, agentAssetsDir, targetDir, host, managedTargets) {
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
837
|
+
function createAssetAction(asset, agentAssetsDir, targetDir, host, placement, managedTargets) {
|
|
838
|
+
if (asset.kind === "skill" && asset.defaultScope === "user") {
|
|
839
|
+
throw new Error(
|
|
840
|
+
`User-scoped asset ${asset.id} must be linked at the user level, not installed into a project target.`
|
|
841
|
+
);
|
|
842
|
+
}
|
|
843
|
+
const sourcePath = join9(agentAssetsDir, asset.sourcePath);
|
|
844
|
+
const targetPath = resolveHostTargetPath(asset, host, placement);
|
|
845
|
+
const targetAbsolutePath = join9(targetDir, targetPath);
|
|
666
846
|
const targetExists = pathExistsEvenIfDanglingSymlink4(targetAbsolutePath);
|
|
667
847
|
if (targetExists) {
|
|
668
848
|
const stats = lstatSync4(targetAbsolutePath);
|
|
@@ -683,17 +863,28 @@ function createAssetAction(asset, agentAssetsDir, targetDir, host, managedTarget
|
|
|
683
863
|
targetPath
|
|
684
864
|
};
|
|
685
865
|
}
|
|
686
|
-
function resolveHostTargetPath(asset, host) {
|
|
866
|
+
function resolveHostTargetPath(asset, host, placement) {
|
|
687
867
|
if (asset.kind === "skill") {
|
|
868
|
+
const effectivePlacement = resolveSkillPlacement(asset, placement);
|
|
688
869
|
if (host === "claude-code") {
|
|
689
|
-
|
|
870
|
+
if (placement === "manual") {
|
|
871
|
+
throw new Error("Manual skill placement is only supported for .agents hosts");
|
|
872
|
+
}
|
|
873
|
+
return `.claude/skills/${basename2(asset.sourcePath)}`;
|
|
690
874
|
}
|
|
691
|
-
|
|
875
|
+
if (effectivePlacement === "manual") {
|
|
876
|
+
return `.agents/manual-skills/${basename2(asset.sourcePath)}`;
|
|
877
|
+
}
|
|
878
|
+
return `.agents/skills/${basename2(asset.sourcePath)}`;
|
|
692
879
|
}
|
|
693
880
|
if (asset.kind === "rule") {
|
|
694
|
-
return `.pro-gov/agent-assets/rules/${
|
|
881
|
+
return `.pro-gov/agent-assets/rules/${basename2(asset.sourcePath)}`;
|
|
695
882
|
}
|
|
696
|
-
return `.pro-gov/agent-assets/commands/${
|
|
883
|
+
return `.pro-gov/agent-assets/commands/${basename2(asset.sourcePath)}`;
|
|
884
|
+
}
|
|
885
|
+
function resolveSkillPlacement(asset, placement) {
|
|
886
|
+
if (placement !== "registry") return placement;
|
|
887
|
+
return asset.defaultPlacement ?? "auto";
|
|
697
888
|
}
|
|
698
889
|
function createDirectoryActions(actions) {
|
|
699
890
|
const directories = /* @__PURE__ */ new Set();
|
|
@@ -705,8 +896,8 @@ function createDirectoryActions(actions) {
|
|
|
705
896
|
return [...directories].sort().map((targetPath) => ({ type: "create-dir", targetPath }));
|
|
706
897
|
}
|
|
707
898
|
function readManagedTargets(targetDir) {
|
|
708
|
-
const lockfilePath =
|
|
709
|
-
if (!
|
|
899
|
+
const lockfilePath = join9(targetDir, ".pro-gov/assets.lock.json");
|
|
900
|
+
if (!existsSync9(lockfilePath)) return /* @__PURE__ */ new Set();
|
|
710
901
|
try {
|
|
711
902
|
const lockfile = JSON.parse(readFileSync5(lockfilePath, "utf8"));
|
|
712
903
|
return new Set((lockfile.assets ?? []).map((asset) => asset.targetPath).filter(Boolean));
|
|
@@ -724,8 +915,8 @@ function pathExistsEvenIfDanglingSymlink4(path) {
|
|
|
724
915
|
}
|
|
725
916
|
|
|
726
917
|
// src/asset-targets/recommend.ts
|
|
727
|
-
import { existsSync as
|
|
728
|
-
import { join as
|
|
918
|
+
import { existsSync as existsSync10, readdirSync as readdirSync5, readFileSync as readFileSync6 } from "node:fs";
|
|
919
|
+
import { join as join10 } from "node:path";
|
|
729
920
|
var frontendPackages = /* @__PURE__ */ new Set([
|
|
730
921
|
"@vitejs/plugin-react",
|
|
731
922
|
"astro",
|
|
@@ -737,23 +928,23 @@ var frontendPackages = /* @__PURE__ */ new Set([
|
|
|
737
928
|
"vite",
|
|
738
929
|
"vue"
|
|
739
930
|
]);
|
|
740
|
-
var agentEntryCandidates = ["AGENTS.md", "CLAUDE.md"
|
|
931
|
+
var agentEntryCandidates = ["AGENTS.md", "CLAUDE.md"];
|
|
741
932
|
function discoverTargetSignals(targetDir) {
|
|
742
|
-
const packageJson = readJson(
|
|
933
|
+
const packageJson = readJson(join10(targetDir, "package.json"));
|
|
743
934
|
const dependencyNames = packageJson ? Object.keys({ ...packageJson.dependencies, ...packageJson.devDependencies }) : [];
|
|
744
935
|
const frontendSignals = dependencyNames.filter((name) => frontendPackages.has(name)).sort();
|
|
745
|
-
const hasAgentEntry = agentEntryCandidates.some((file) =>
|
|
936
|
+
const hasAgentEntry = agentEntryCandidates.some((file) => existsSync10(join10(targetDir, file)));
|
|
746
937
|
const researchSignals = [
|
|
747
|
-
|
|
748
|
-
|
|
938
|
+
existsSync10(join10(targetDir, "docs/research")) ? "docs/research" : "",
|
|
939
|
+
existsSync10(join10(targetDir, "research")) ? "research" : "",
|
|
749
940
|
hasBookChildDirectory(targetDir, "research") ? "books/*/research" : "",
|
|
750
|
-
textFileIncludes(
|
|
941
|
+
textFileIncludes(join10(targetDir, "README.md"), ["research", "\u8C03\u7814"]) ? "README research" : ""
|
|
751
942
|
].filter(Boolean);
|
|
752
943
|
const writingSignals = [
|
|
753
|
-
|
|
754
|
-
|
|
944
|
+
existsSync10(join10(targetDir, "chapters")) ? "chapters" : "",
|
|
945
|
+
existsSync10(join10(targetDir, "src/chapters")) ? "src/chapters" : "",
|
|
755
946
|
hasBookChildDirectory(targetDir, "chapters") ? "books/*/chapters" : "",
|
|
756
|
-
textFileIncludes(
|
|
947
|
+
textFileIncludes(join10(targetDir, "AGENTS.md"), ["writing mode", "novel chapter", "book content"]) ? "AGENTS writing" : ""
|
|
757
948
|
].filter(Boolean);
|
|
758
949
|
return {
|
|
759
950
|
targetDir,
|
|
@@ -801,7 +992,7 @@ function recommendBundlesForTarget(targetDir) {
|
|
|
801
992
|
return recommendations;
|
|
802
993
|
}
|
|
803
994
|
function readJson(path) {
|
|
804
|
-
if (!
|
|
995
|
+
if (!existsSync10(path)) return void 0;
|
|
805
996
|
try {
|
|
806
997
|
return JSON.parse(readFileSync6(path, "utf8"));
|
|
807
998
|
} catch {
|
|
@@ -809,16 +1000,16 @@ function readJson(path) {
|
|
|
809
1000
|
}
|
|
810
1001
|
}
|
|
811
1002
|
function textFileIncludes(path, needles) {
|
|
812
|
-
if (!
|
|
1003
|
+
if (!existsSync10(path)) return false;
|
|
813
1004
|
const contents = readFileSync6(path, "utf8").toLowerCase();
|
|
814
1005
|
return needles.some((needle) => contents.includes(needle.toLowerCase()));
|
|
815
1006
|
}
|
|
816
1007
|
function hasBookChildDirectory(targetDir, childName) {
|
|
817
|
-
const booksDir =
|
|
818
|
-
if (!
|
|
1008
|
+
const booksDir = join10(targetDir, "books");
|
|
1009
|
+
if (!existsSync10(booksDir)) return false;
|
|
819
1010
|
try {
|
|
820
1011
|
return readdirSync5(booksDir, { withFileTypes: true }).some(
|
|
821
|
-
(entry) => entry.isDirectory() &&
|
|
1012
|
+
(entry) => entry.isDirectory() && existsSync10(join10(booksDir, entry.name, childName))
|
|
822
1013
|
);
|
|
823
1014
|
} catch {
|
|
824
1015
|
return false;
|
|
@@ -846,12 +1037,55 @@ function runAssets(args) {
|
|
|
846
1037
|
if (subcommand2 === "check") {
|
|
847
1038
|
return runAssetsCheck(rest);
|
|
848
1039
|
}
|
|
1040
|
+
if (subcommand2 === "public-check") {
|
|
1041
|
+
return runAssetsPublicCheck(rest);
|
|
1042
|
+
}
|
|
849
1043
|
if (subcommand2 === "npx") {
|
|
850
1044
|
return runAssetsNpx(rest);
|
|
851
1045
|
}
|
|
852
1046
|
printUsage();
|
|
853
1047
|
return 1;
|
|
854
1048
|
}
|
|
1049
|
+
function runAssetsPublicCheck(args) {
|
|
1050
|
+
const options = parsePublicCheckOptions(args);
|
|
1051
|
+
if (!options.ok) {
|
|
1052
|
+
console.error(options.error);
|
|
1053
|
+
printUsage();
|
|
1054
|
+
return 1;
|
|
1055
|
+
}
|
|
1056
|
+
const registryLoad = loadAgentAssetRegistry({ agentAssetsDir: options.value.publicRoot });
|
|
1057
|
+
if (registryLoad.issues.length > 0) {
|
|
1058
|
+
for (const issue of registryLoad.issues) {
|
|
1059
|
+
console.error(`${issue.type}: ${issue.message}`);
|
|
1060
|
+
}
|
|
1061
|
+
return 1;
|
|
1062
|
+
}
|
|
1063
|
+
const result = checkPublicAssetPromotions({
|
|
1064
|
+
publicAgentAssetsDir: options.value.publicRoot,
|
|
1065
|
+
privateAgentAssetsDir: options.value.privateRoot,
|
|
1066
|
+
registry: registryLoad.registry
|
|
1067
|
+
});
|
|
1068
|
+
if (options.value.json) {
|
|
1069
|
+
console.log(
|
|
1070
|
+
JSON.stringify(
|
|
1071
|
+
{
|
|
1072
|
+
publicRoot: options.value.publicRoot,
|
|
1073
|
+
privateRoot: options.value.privateRoot,
|
|
1074
|
+
...result
|
|
1075
|
+
},
|
|
1076
|
+
null,
|
|
1077
|
+
2
|
|
1078
|
+
)
|
|
1079
|
+
);
|
|
1080
|
+
} else if (result.issues.length === 0) {
|
|
1081
|
+
console.log(`public assets check passed (${result.checked} checked)`);
|
|
1082
|
+
} else {
|
|
1083
|
+
for (const issue of result.issues) {
|
|
1084
|
+
console.log(`${issue.type}: ${issue.message}`);
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
return result.issues.length === 0 ? 0 : 1;
|
|
1088
|
+
}
|
|
855
1089
|
function runAssetsNpx(args) {
|
|
856
1090
|
const [operation, ...rest] = args;
|
|
857
1091
|
if (!operation || operation === "--help" || operation === "-h" || rest.includes("--help")) {
|
|
@@ -999,13 +1233,15 @@ function runAssetsPlan(args) {
|
|
|
999
1233
|
registry: loaded.registry,
|
|
1000
1234
|
bundles: loadAgentAssetBundles(loaded.agentAssetsDir),
|
|
1001
1235
|
bundleIds: options.value.bundleIds,
|
|
1002
|
-
host: options.value.host
|
|
1236
|
+
host: options.value.host,
|
|
1237
|
+
placement: options.value.placement
|
|
1003
1238
|
});
|
|
1004
1239
|
if (options.value.json) {
|
|
1005
1240
|
console.log(JSON.stringify(plan, null, 2));
|
|
1006
1241
|
} else {
|
|
1007
1242
|
console.log(`target: ${plan.targetDir}`);
|
|
1008
1243
|
console.log(`host: ${plan.host}`);
|
|
1244
|
+
console.log(`placement: ${plan.placement}`);
|
|
1009
1245
|
console.log(`bundles: ${plan.bundleIds.join(", ")}`);
|
|
1010
1246
|
console.log(`assets: ${plan.assetIds.length}`);
|
|
1011
1247
|
console.log(`actions: ${plan.actions.length}`);
|
|
@@ -1101,6 +1337,16 @@ function parsePlanOptions(args) {
|
|
|
1101
1337
|
}
|
|
1102
1338
|
options.host = host;
|
|
1103
1339
|
index += 1;
|
|
1340
|
+
} else if (arg === "--placement") {
|
|
1341
|
+
const placement = args[index + 1];
|
|
1342
|
+
if (!isSkillPlacement(placement)) {
|
|
1343
|
+
return {
|
|
1344
|
+
ok: false,
|
|
1345
|
+
error: "Expected --placement auto|manual"
|
|
1346
|
+
};
|
|
1347
|
+
}
|
|
1348
|
+
options.placement = placement;
|
|
1349
|
+
index += 1;
|
|
1104
1350
|
} else if (arg === "--out") {
|
|
1105
1351
|
const outPath = args[index + 1];
|
|
1106
1352
|
if (!outPath) return { ok: false, error: "Expected --out <path>" };
|
|
@@ -1167,6 +1413,52 @@ function parseNpxOptions(operation, args) {
|
|
|
1167
1413
|
}
|
|
1168
1414
|
return { ok: true, value: options };
|
|
1169
1415
|
}
|
|
1416
|
+
function parsePublicCheckOptions(args) {
|
|
1417
|
+
const defaults = getDefaultPublicCheckRoots();
|
|
1418
|
+
const options = {
|
|
1419
|
+
publicRoot: defaults.publicRoot,
|
|
1420
|
+
privateRoot: defaults.privateRoot,
|
|
1421
|
+
json: false
|
|
1422
|
+
};
|
|
1423
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
1424
|
+
const arg = args[index];
|
|
1425
|
+
if (arg === "--public-root") {
|
|
1426
|
+
const publicRoot = args[index + 1];
|
|
1427
|
+
if (!publicRoot) return { ok: false, error: "Expected --public-root <path>" };
|
|
1428
|
+
options.publicRoot = publicRoot;
|
|
1429
|
+
index += 1;
|
|
1430
|
+
} else if (arg === "--private-root") {
|
|
1431
|
+
const privateRoot = args[index + 1];
|
|
1432
|
+
if (!privateRoot) return { ok: false, error: "Expected --private-root <path>" };
|
|
1433
|
+
options.privateRoot = privateRoot;
|
|
1434
|
+
index += 1;
|
|
1435
|
+
} else if (arg === "--json") {
|
|
1436
|
+
options.json = true;
|
|
1437
|
+
} else {
|
|
1438
|
+
return { ok: false, error: `Unknown assets public-check option: ${arg}` };
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
if (!existsSync11(options.publicRoot)) {
|
|
1442
|
+
return { ok: false, error: `Public agent assets root does not exist: ${options.publicRoot}` };
|
|
1443
|
+
}
|
|
1444
|
+
if (!existsSync11(options.privateRoot)) {
|
|
1445
|
+
return { ok: false, error: `Private agent assets root does not exist: ${options.privateRoot}` };
|
|
1446
|
+
}
|
|
1447
|
+
return { ok: true, value: options };
|
|
1448
|
+
}
|
|
1449
|
+
function getDefaultPublicCheckRoots() {
|
|
1450
|
+
const defaultRegistryRoot = loadAgentAssetRegistry().agentAssetsDir;
|
|
1451
|
+
if (defaultRegistryRoot.endsWith("public-agent-assets")) {
|
|
1452
|
+
return {
|
|
1453
|
+
privateRoot: join11(dirname5(defaultRegistryRoot), "agent-assets"),
|
|
1454
|
+
publicRoot: defaultRegistryRoot
|
|
1455
|
+
};
|
|
1456
|
+
}
|
|
1457
|
+
return {
|
|
1458
|
+
privateRoot: defaultRegistryRoot,
|
|
1459
|
+
publicRoot: join11(dirname5(defaultRegistryRoot), "public-agent-assets")
|
|
1460
|
+
};
|
|
1461
|
+
}
|
|
1170
1462
|
function listRegistryAssets(options) {
|
|
1171
1463
|
const loaded = loadAgentAssetRegistry();
|
|
1172
1464
|
if (loaded.issues.length > 0) {
|
|
@@ -1205,14 +1497,18 @@ function isVisibilityFilter(value) {
|
|
|
1205
1497
|
function isHost(value) {
|
|
1206
1498
|
return value === "codex" || value === "claude-code" || value === "gemini-cli" || value === "antigravity";
|
|
1207
1499
|
}
|
|
1500
|
+
function isSkillPlacement(value) {
|
|
1501
|
+
return value === "auto" || value === "manual";
|
|
1502
|
+
}
|
|
1208
1503
|
function printUsage() {
|
|
1209
1504
|
console.error("Usage:");
|
|
1210
1505
|
console.error(" pro-gov assets list [--registry] [--json] [--visibility public|private|third-party|all]");
|
|
1211
1506
|
console.error(" pro-gov assets discover [--target <path>] [--json]");
|
|
1212
1507
|
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]");
|
|
1508
|
+
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
1509
|
console.error(" pro-gov assets apply --plan <path>");
|
|
1215
1510
|
console.error(" pro-gov assets check [--target <path>] [--json]");
|
|
1511
|
+
console.error(" pro-gov assets public-check [--public-root <path>] [--private-root <path>] [--json]");
|
|
1216
1512
|
console.error(" pro-gov assets npx add <source> [--skill <name>] --plan [--root <path>]");
|
|
1217
1513
|
console.error(" pro-gov assets npx update [--skill <name>] --plan [--root <path>]");
|
|
1218
1514
|
}
|
|
@@ -1225,12 +1521,11 @@ function printNpxUsage() {
|
|
|
1225
1521
|
|
|
1226
1522
|
// src/commands/doctor.ts
|
|
1227
1523
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
1228
|
-
import { existsSync as
|
|
1524
|
+
import { existsSync as existsSync12 } from "node:fs";
|
|
1229
1525
|
import { createRequire } from "node:module";
|
|
1230
|
-
import { dirname as dirname6, join as
|
|
1526
|
+
import { dirname as dirname6, join as join12 } from "node:path";
|
|
1231
1527
|
var REQUIRED_ASSETS = [
|
|
1232
1528
|
"starter/AGENTS.template.md",
|
|
1233
|
-
"starter/.gemini/settings.json",
|
|
1234
1529
|
"starter/docs/governance/ssot-v0.9.md",
|
|
1235
1530
|
"starter/docs/governance/agents-routing/engineering-runtime-v0.9.md",
|
|
1236
1531
|
"starter/docs/governance/agents-routing/doc-only-v0.9.md",
|
|
@@ -1278,8 +1573,8 @@ function resolveDocGovDependencyCli() {
|
|
|
1278
1573
|
try {
|
|
1279
1574
|
const require2 = createRequire(import.meta.url);
|
|
1280
1575
|
const packageJsonPath = require2.resolve("@pieai/doc-gov/package.json");
|
|
1281
|
-
const cliPath =
|
|
1282
|
-
return
|
|
1576
|
+
const cliPath = join12(dirname6(packageJsonPath), "dist/cli.js");
|
|
1577
|
+
return existsSync12(cliPath) ? cliPath : null;
|
|
1283
1578
|
} catch {
|
|
1284
1579
|
return null;
|
|
1285
1580
|
}
|
|
@@ -1421,8 +1716,8 @@ function bulletList(values) {
|
|
|
1421
1716
|
|
|
1422
1717
|
// src/lens/scan.ts
|
|
1423
1718
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
1424
|
-
import { existsSync as
|
|
1425
|
-
import { join as
|
|
1719
|
+
import { existsSync as existsSync13, readdirSync as readdirSync6, readFileSync as readFileSync8, statSync as statSync3 } from "node:fs";
|
|
1720
|
+
import { join as join13, relative as relative4 } from "node:path";
|
|
1426
1721
|
var ignoredDirectories = /* @__PURE__ */ new Set([
|
|
1427
1722
|
".git",
|
|
1428
1723
|
".next",
|
|
@@ -1438,23 +1733,23 @@ function scanProjectLensTarget(targetDir, options = {}) {
|
|
|
1438
1733
|
const packageJson = readPackageJson(targetDir);
|
|
1439
1734
|
return {
|
|
1440
1735
|
targetDir,
|
|
1441
|
-
aiEntryFiles: ["AGENTS.md", "CLAUDE.md"
|
|
1442
|
-
(file) =>
|
|
1736
|
+
aiEntryFiles: ["AGENTS.md", "CLAUDE.md"].filter(
|
|
1737
|
+
(file) => existsSync13(join13(targetDir, file))
|
|
1443
1738
|
),
|
|
1444
|
-
aiConfigFiles: [
|
|
1739
|
+
aiConfigFiles: [],
|
|
1445
1740
|
packageJson,
|
|
1446
1741
|
docs: {
|
|
1447
|
-
hasDocsDirectory:
|
|
1742
|
+
hasDocsDirectory: existsSync13(join13(targetDir, "docs")),
|
|
1448
1743
|
markdownFileCount: markdownFiles.length,
|
|
1449
1744
|
governanceFiles: markdownFiles.filter((file) => file.startsWith("docs/governance/") || file.startsWith("docs/policy/")).sort()
|
|
1450
1745
|
},
|
|
1451
1746
|
git: readGitState(targetDir),
|
|
1452
|
-
largeFiles: files.map((file) => ({ path: file, bytes: statSync3(
|
|
1747
|
+
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
1748
|
};
|
|
1454
1749
|
}
|
|
1455
1750
|
function readPackageJson(targetDir) {
|
|
1456
|
-
const packageJsonPath =
|
|
1457
|
-
if (!
|
|
1751
|
+
const packageJsonPath = join13(targetDir, "package.json");
|
|
1752
|
+
if (!existsSync13(packageJsonPath)) return void 0;
|
|
1458
1753
|
try {
|
|
1459
1754
|
const packageJson = JSON.parse(readFileSync8(packageJsonPath, "utf8"));
|
|
1460
1755
|
return {
|
|
@@ -1491,13 +1786,13 @@ function listProjectFiles(targetDir) {
|
|
|
1491
1786
|
return files.sort();
|
|
1492
1787
|
}
|
|
1493
1788
|
function collectFiles2(rootDir, currentDir, files) {
|
|
1494
|
-
if (!
|
|
1789
|
+
if (!existsSync13(currentDir)) return;
|
|
1495
1790
|
for (const entry of readdirSync6(currentDir, { withFileTypes: true })) {
|
|
1496
1791
|
if (entry.isDirectory()) {
|
|
1497
1792
|
if (ignoredDirectories.has(entry.name)) continue;
|
|
1498
|
-
collectFiles2(rootDir,
|
|
1793
|
+
collectFiles2(rootDir, join13(currentDir, entry.name), files);
|
|
1499
1794
|
} else if (entry.isFile()) {
|
|
1500
|
-
files.push(toUnixPath4(relative4(rootDir,
|
|
1795
|
+
files.push(toUnixPath4(relative4(rootDir, join13(currentDir, entry.name))));
|
|
1501
1796
|
}
|
|
1502
1797
|
}
|
|
1503
1798
|
}
|
|
@@ -1591,9 +1886,300 @@ function printUsage2() {
|
|
|
1591
1886
|
console.error("Usage: pro-gov lens report --target <path> --out <path>");
|
|
1592
1887
|
}
|
|
1593
1888
|
|
|
1889
|
+
// src/portfolio/manifest.ts
|
|
1890
|
+
import { existsSync as existsSync14, readFileSync as readFileSync9 } from "node:fs";
|
|
1891
|
+
function loadPortfolioManifest(configPath) {
|
|
1892
|
+
let parsed;
|
|
1893
|
+
try {
|
|
1894
|
+
parsed = JSON.parse(readFileSync9(configPath, "utf8"));
|
|
1895
|
+
} catch (error) {
|
|
1896
|
+
return {
|
|
1897
|
+
configPath,
|
|
1898
|
+
issues: [
|
|
1899
|
+
{
|
|
1900
|
+
type: "invalid-json",
|
|
1901
|
+
message: error instanceof Error ? error.message : String(error)
|
|
1902
|
+
}
|
|
1903
|
+
]
|
|
1904
|
+
};
|
|
1905
|
+
}
|
|
1906
|
+
const issues = validatePortfolioManifest(parsed);
|
|
1907
|
+
return {
|
|
1908
|
+
configPath,
|
|
1909
|
+
manifest: issues.length === 0 ? parsed : void 0,
|
|
1910
|
+
issues
|
|
1911
|
+
};
|
|
1912
|
+
}
|
|
1913
|
+
function validatePortfolioManifest(value) {
|
|
1914
|
+
const issues = [];
|
|
1915
|
+
if (!isRecord(value)) {
|
|
1916
|
+
return [
|
|
1917
|
+
{
|
|
1918
|
+
type: "invalid-field",
|
|
1919
|
+
field: "root",
|
|
1920
|
+
message: "Portfolio manifest must be a JSON object."
|
|
1921
|
+
}
|
|
1922
|
+
];
|
|
1923
|
+
}
|
|
1924
|
+
if (value.schemaVersion !== 1) {
|
|
1925
|
+
issues.push({
|
|
1926
|
+
type: "invalid-field",
|
|
1927
|
+
field: "schemaVersion",
|
|
1928
|
+
message: "Portfolio manifest schemaVersion must be 1."
|
|
1929
|
+
});
|
|
1930
|
+
}
|
|
1931
|
+
if (typeof value.portfolioId !== "string" || value.portfolioId.length === 0) {
|
|
1932
|
+
issues.push({
|
|
1933
|
+
type: "invalid-field",
|
|
1934
|
+
field: "portfolioId",
|
|
1935
|
+
message: "Portfolio manifest portfolioId must be a non-empty string."
|
|
1936
|
+
});
|
|
1937
|
+
}
|
|
1938
|
+
validateEndpoint(value.controlPlane, "controlPlane", issues);
|
|
1939
|
+
validateEndpoint(value.executionEngine, "executionEngine", issues);
|
|
1940
|
+
if (!Array.isArray(value.targets)) {
|
|
1941
|
+
issues.push({
|
|
1942
|
+
type: "invalid-field",
|
|
1943
|
+
field: "targets",
|
|
1944
|
+
message: "Portfolio manifest targets must be an array."
|
|
1945
|
+
});
|
|
1946
|
+
return issues;
|
|
1947
|
+
}
|
|
1948
|
+
const seenTargetIds = /* @__PURE__ */ new Set();
|
|
1949
|
+
for (const target of value.targets) {
|
|
1950
|
+
if (!isRecord(target)) {
|
|
1951
|
+
issues.push({
|
|
1952
|
+
type: "invalid-field",
|
|
1953
|
+
field: "targets",
|
|
1954
|
+
message: "Portfolio target must be an object."
|
|
1955
|
+
});
|
|
1956
|
+
continue;
|
|
1957
|
+
}
|
|
1958
|
+
validateEndpoint(target, "targets", issues);
|
|
1959
|
+
if (typeof target.id === "string") {
|
|
1960
|
+
if (seenTargetIds.has(target.id)) {
|
|
1961
|
+
issues.push({
|
|
1962
|
+
type: "duplicate-target-id",
|
|
1963
|
+
id: target.id,
|
|
1964
|
+
message: `Duplicate portfolio target id: ${target.id}`
|
|
1965
|
+
});
|
|
1966
|
+
}
|
|
1967
|
+
seenTargetIds.add(target.id);
|
|
1968
|
+
}
|
|
1969
|
+
validateOptionalStringArray(target.assetBundles, target.id, "assetBundles", issues);
|
|
1970
|
+
validateOptionalStringArray(target.sharedRules, target.id, "sharedRules", issues);
|
|
1971
|
+
}
|
|
1972
|
+
return issues;
|
|
1973
|
+
}
|
|
1974
|
+
function getDefaultPortfolioTargets(manifest) {
|
|
1975
|
+
return manifest?.targets ?? [];
|
|
1976
|
+
}
|
|
1977
|
+
function validateEndpoint(value, field, issues) {
|
|
1978
|
+
if (value === void 0) return;
|
|
1979
|
+
if (!isRecord(value)) {
|
|
1980
|
+
issues.push({
|
|
1981
|
+
type: "invalid-field",
|
|
1982
|
+
field,
|
|
1983
|
+
message: `Portfolio ${field} must be an object.`
|
|
1984
|
+
});
|
|
1985
|
+
return;
|
|
1986
|
+
}
|
|
1987
|
+
if (typeof value.id !== "string" || value.id.length === 0) {
|
|
1988
|
+
issues.push({
|
|
1989
|
+
type: "invalid-field",
|
|
1990
|
+
field: `${field}.id`,
|
|
1991
|
+
message: `Portfolio ${field} id must be a non-empty string.`
|
|
1992
|
+
});
|
|
1993
|
+
}
|
|
1994
|
+
if (typeof value.path !== "string" || value.path.length === 0) {
|
|
1995
|
+
issues.push({
|
|
1996
|
+
type: "invalid-field",
|
|
1997
|
+
id: typeof value.id === "string" ? value.id : void 0,
|
|
1998
|
+
field: `${field}.path`,
|
|
1999
|
+
message: `Portfolio ${field} path must be a non-empty string.`
|
|
2000
|
+
});
|
|
2001
|
+
return;
|
|
2002
|
+
}
|
|
2003
|
+
if (!existsSync14(value.path)) {
|
|
2004
|
+
issues.push({
|
|
2005
|
+
type: "missing-path",
|
|
2006
|
+
id: typeof value.id === "string" ? value.id : void 0,
|
|
2007
|
+
field: `${field}.path`,
|
|
2008
|
+
message: `Portfolio ${field} path does not exist: ${value.path}`
|
|
2009
|
+
});
|
|
2010
|
+
}
|
|
2011
|
+
}
|
|
2012
|
+
function validateOptionalStringArray(value, id, field, issues) {
|
|
2013
|
+
if (value === void 0) return;
|
|
2014
|
+
if (!Array.isArray(value) || !value.every((entry) => typeof entry === "string")) {
|
|
2015
|
+
issues.push({
|
|
2016
|
+
type: "invalid-field",
|
|
2017
|
+
id: typeof id === "string" ? id : void 0,
|
|
2018
|
+
field,
|
|
2019
|
+
message: `Portfolio target ${field} must be an array of strings.`
|
|
2020
|
+
});
|
|
2021
|
+
}
|
|
2022
|
+
}
|
|
2023
|
+
function isRecord(value) {
|
|
2024
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2025
|
+
}
|
|
2026
|
+
|
|
2027
|
+
// src/commands/portfolio.ts
|
|
2028
|
+
function runPortfolio(args) {
|
|
2029
|
+
const [subcommand2, ...rest] = args;
|
|
2030
|
+
if (subcommand2 === "check") return runPortfolioCheck(rest);
|
|
2031
|
+
if (subcommand2 === "plan") return runPortfolioPlan(rest);
|
|
2032
|
+
printUsage3();
|
|
2033
|
+
return 1;
|
|
2034
|
+
}
|
|
2035
|
+
function runPortfolioCheck(args) {
|
|
2036
|
+
const options = parsePortfolioOptions(args);
|
|
2037
|
+
if (!options.ok) {
|
|
2038
|
+
console.error(options.error);
|
|
2039
|
+
printUsage3();
|
|
2040
|
+
return 1;
|
|
2041
|
+
}
|
|
2042
|
+
const loaded = loadPortfolioManifest(options.value.configPath);
|
|
2043
|
+
const targetIds = getDefaultPortfolioTargets(loaded.manifest).map((target) => target.id);
|
|
2044
|
+
if (options.value.json) {
|
|
2045
|
+
console.log(
|
|
2046
|
+
JSON.stringify(
|
|
2047
|
+
{
|
|
2048
|
+
ok: loaded.issues.length === 0,
|
|
2049
|
+
configPath: loaded.configPath,
|
|
2050
|
+
portfolioId: loaded.manifest?.portfolioId,
|
|
2051
|
+
targetIds,
|
|
2052
|
+
issues: loaded.issues
|
|
2053
|
+
},
|
|
2054
|
+
null,
|
|
2055
|
+
2
|
|
2056
|
+
)
|
|
2057
|
+
);
|
|
2058
|
+
} else if (loaded.issues.length === 0) {
|
|
2059
|
+
console.log(`portfolio check passed (${targetIds.length} targets)`);
|
|
2060
|
+
} else {
|
|
2061
|
+
for (const issue of loaded.issues) {
|
|
2062
|
+
console.log(`${issue.type}: ${issue.message}`);
|
|
2063
|
+
}
|
|
2064
|
+
}
|
|
2065
|
+
return loaded.issues.length === 0 ? 0 : 1;
|
|
2066
|
+
}
|
|
2067
|
+
function runPortfolioPlan(args) {
|
|
2068
|
+
const options = parsePortfolioOptions(args);
|
|
2069
|
+
if (!options.ok) {
|
|
2070
|
+
console.error(options.error);
|
|
2071
|
+
printUsage3();
|
|
2072
|
+
return 1;
|
|
2073
|
+
}
|
|
2074
|
+
const loaded = loadPortfolioManifest(options.value.configPath);
|
|
2075
|
+
if (loaded.issues.length > 0 || !loaded.manifest) {
|
|
2076
|
+
for (const issue of loaded.issues) {
|
|
2077
|
+
console.error(`${issue.type}: ${issue.message}`);
|
|
2078
|
+
}
|
|
2079
|
+
return 1;
|
|
2080
|
+
}
|
|
2081
|
+
const targets = getDefaultPortfolioTargets(loaded.manifest).filter(
|
|
2082
|
+
(target) => !options.value.targetId || options.value.targetId === "all" || target.id === options.value.targetId
|
|
2083
|
+
);
|
|
2084
|
+
if (targets.length === 0) {
|
|
2085
|
+
console.error(`Unknown portfolio target: ${options.value.targetId}`);
|
|
2086
|
+
return 1;
|
|
2087
|
+
}
|
|
2088
|
+
const loadedAssets = loadAgentAssetRegistry();
|
|
2089
|
+
if (loadedAssets.issues.length > 0) {
|
|
2090
|
+
for (const issue of loadedAssets.issues) {
|
|
2091
|
+
console.error(`${issue.type}: ${issue.message}`);
|
|
2092
|
+
}
|
|
2093
|
+
return 1;
|
|
2094
|
+
}
|
|
2095
|
+
const bundles = loadAgentAssetBundles(loadedAssets.agentAssetsDir);
|
|
2096
|
+
try {
|
|
2097
|
+
const plans = targets.map((target) => {
|
|
2098
|
+
const bundleIds = target.assetBundles ?? [];
|
|
2099
|
+
if (bundleIds.length === 0) {
|
|
2100
|
+
throw new Error(`Portfolio target ${target.id} has no assetBundles`);
|
|
2101
|
+
}
|
|
2102
|
+
return {
|
|
2103
|
+
id: target.id,
|
|
2104
|
+
path: target.path,
|
|
2105
|
+
plan: createAssetInstallPlan({
|
|
2106
|
+
targetDir: target.path,
|
|
2107
|
+
agentAssetsDir: loadedAssets.agentAssetsDir,
|
|
2108
|
+
registry: loadedAssets.registry,
|
|
2109
|
+
bundles,
|
|
2110
|
+
bundleIds,
|
|
2111
|
+
host: options.value.host
|
|
2112
|
+
})
|
|
2113
|
+
};
|
|
2114
|
+
});
|
|
2115
|
+
if (options.value.json) {
|
|
2116
|
+
console.log(
|
|
2117
|
+
JSON.stringify(
|
|
2118
|
+
{
|
|
2119
|
+
configPath: loaded.configPath,
|
|
2120
|
+
portfolioId: loaded.manifest.portfolioId,
|
|
2121
|
+
targets: plans
|
|
2122
|
+
},
|
|
2123
|
+
null,
|
|
2124
|
+
2
|
|
2125
|
+
)
|
|
2126
|
+
);
|
|
2127
|
+
} else {
|
|
2128
|
+
console.log(`portfolio plan targets: ${plans.length}`);
|
|
2129
|
+
for (const target of plans) {
|
|
2130
|
+
console.log(`${target.id} ${target.plan.actions.length} actions dry-run`);
|
|
2131
|
+
}
|
|
2132
|
+
}
|
|
2133
|
+
return 0;
|
|
2134
|
+
} catch (error) {
|
|
2135
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
2136
|
+
return 1;
|
|
2137
|
+
}
|
|
2138
|
+
}
|
|
2139
|
+
function parsePortfolioOptions(args) {
|
|
2140
|
+
const options = {
|
|
2141
|
+
configPath: "",
|
|
2142
|
+
host: "codex",
|
|
2143
|
+
json: false
|
|
2144
|
+
};
|
|
2145
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
2146
|
+
const arg = args[index];
|
|
2147
|
+
if (arg === "--config") {
|
|
2148
|
+
const configPath = args[index + 1];
|
|
2149
|
+
if (!configPath) return { ok: false, error: "Expected --config <path>" };
|
|
2150
|
+
options.configPath = configPath;
|
|
2151
|
+
index += 1;
|
|
2152
|
+
} else if (arg === "--target") {
|
|
2153
|
+
const targetId = args[index + 1];
|
|
2154
|
+
if (!targetId) return { ok: false, error: "Expected --target <id|all>" };
|
|
2155
|
+
options.targetId = targetId;
|
|
2156
|
+
index += 1;
|
|
2157
|
+
} else if (arg === "--host") {
|
|
2158
|
+
const host = args[index + 1];
|
|
2159
|
+
if (!isHost2(host)) return { ok: false, error: "Expected --host codex|claude-code" };
|
|
2160
|
+
options.host = host;
|
|
2161
|
+
index += 1;
|
|
2162
|
+
} else if (arg === "--json") {
|
|
2163
|
+
options.json = true;
|
|
2164
|
+
} else {
|
|
2165
|
+
return { ok: false, error: `Unknown portfolio option: ${arg}` };
|
|
2166
|
+
}
|
|
2167
|
+
}
|
|
2168
|
+
if (!options.configPath) return { ok: false, error: "Expected --config <path>" };
|
|
2169
|
+
return { ok: true, value: options };
|
|
2170
|
+
}
|
|
2171
|
+
function isHost2(value) {
|
|
2172
|
+
return value === "codex" || value === "claude-code" || value === "gemini-cli" || value === "antigravity";
|
|
2173
|
+
}
|
|
2174
|
+
function printUsage3() {
|
|
2175
|
+
console.error("Usage:");
|
|
2176
|
+
console.error(" pro-gov portfolio check --config <path> [--json]");
|
|
2177
|
+
console.error(" pro-gov portfolio plan --config <path> [--target <id|all>] [--host codex|claude-code|gemini-cli|antigravity] [--json]");
|
|
2178
|
+
}
|
|
2179
|
+
|
|
1594
2180
|
// src/commands/sync.ts
|
|
1595
|
-
import { existsSync as
|
|
1596
|
-
import { join as
|
|
2181
|
+
import { existsSync as existsSync15, readFileSync as readFileSync10 } from "node:fs";
|
|
2182
|
+
import { join as join14 } from "node:path";
|
|
1597
2183
|
function runSync(args) {
|
|
1598
2184
|
if (!args.includes("--check")) {
|
|
1599
2185
|
console.error("pro-gov sync requires --check in this first read-only release.");
|
|
@@ -1602,14 +2188,14 @@ function runSync(args) {
|
|
|
1602
2188
|
let differences = 0;
|
|
1603
2189
|
console.log("pro-gov sync check");
|
|
1604
2190
|
for (const file of planStarterFiles()) {
|
|
1605
|
-
const targetPath =
|
|
1606
|
-
if (!
|
|
2191
|
+
const targetPath = join14(process.cwd(), file.targetPath);
|
|
2192
|
+
if (!existsSync15(targetPath)) {
|
|
1607
2193
|
console.log(`missing: ${file.targetPath}`);
|
|
1608
2194
|
differences += 1;
|
|
1609
2195
|
continue;
|
|
1610
2196
|
}
|
|
1611
|
-
const source =
|
|
1612
|
-
const target =
|
|
2197
|
+
const source = readFileSync10(file.absoluteSourcePath, "utf8");
|
|
2198
|
+
const target = readFileSync10(targetPath, "utf8");
|
|
1613
2199
|
if (source !== target) {
|
|
1614
2200
|
console.log(`different: ${file.targetPath}`);
|
|
1615
2201
|
differences += 1;
|
|
@@ -1631,7 +2217,10 @@ var COMMANDS = [
|
|
|
1631
2217
|
"assets plan --bundle <bundle-id> [--target <path>] [--json]",
|
|
1632
2218
|
"assets apply --plan <path>",
|
|
1633
2219
|
"assets check [--target <path>] [--json]",
|
|
2220
|
+
"assets public-check [--public-root <path>] [--private-root <path>] [--json]",
|
|
1634
2221
|
"assets npx add|update ... --plan",
|
|
2222
|
+
"portfolio check --config <path> [--json]",
|
|
2223
|
+
"portfolio plan --config <path> [--target <id|all>] [--json]",
|
|
1635
2224
|
"lens scan [--target <path>] [--json]",
|
|
1636
2225
|
"lens inspect [--target <path>] [--format text|json]",
|
|
1637
2226
|
"lens report --target <path> --out <path>",
|
|
@@ -1647,6 +2236,8 @@ if (!command || command === "--help" || command === "-h") {
|
|
|
1647
2236
|
process.exitCode = runAssets(process.argv.slice(3));
|
|
1648
2237
|
} else if (command === "lens") {
|
|
1649
2238
|
process.exitCode = runLens(process.argv.slice(3));
|
|
2239
|
+
} else if (command === "portfolio") {
|
|
2240
|
+
process.exitCode = runPortfolio(process.argv.slice(3));
|
|
1650
2241
|
} else if (command === "init") {
|
|
1651
2242
|
process.exitCode = runInit(process.argv.slice(3));
|
|
1652
2243
|
} else if (command === "sync") {
|