@pieai/pro-gov 0.3.6 → 0.3.8
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 +23 -9
- package/assets/docs/reference/adoption/adoption-playbook.md +35 -9
- package/assets/docs/reference/adoption/project-relationship.md +14 -15
- package/assets/docs/reference/adoption/recommended-agent-tooling.md +23 -1
- package/assets/integrations/compound-engineering.md +124 -0
- package/assets/integrations/ponytail.md +52 -0
- package/assets/integrations/superpowers.md +21 -8
- package/assets/profiles/doc-only/manifest.yml +2 -0
- package/assets/profiles/doc-only/profile.md +4 -4
- package/assets/profiles/engineering-runtime/manifest.yml +2 -0
- package/assets/profiles/engineering-runtime/profile.md +6 -4
- package/assets/starter/AGENTS.template.md +2 -1
- package/assets/starter/docs/governance/boundary.md +7 -0
- package/assets/starter/docs/governance/ssot-v0.9.md +12 -0
- package/assets/starter/docs/reference/documentation-map.md +5 -0
- package/cli-guide.md +34 -6
- package/dist/cli.js +654 -56
- package/package.json +2 -2
- package/assets/docs/reference/adoption/public-release-checklist.md +0 -153
- package/assets/docs/reference/adoption/site-publication-brief.md +0 -94
package/dist/cli.js
CHANGED
|
@@ -422,6 +422,7 @@ function listFiles3(absolutePath) {
|
|
|
422
422
|
if (stats.isFile()) return [absolutePath];
|
|
423
423
|
const files = [];
|
|
424
424
|
for (const entry of readdirSync4(absolutePath, { withFileTypes: true })) {
|
|
425
|
+
if (shouldIgnoreAssetHashEntry(entry.name)) continue;
|
|
425
426
|
const entryPath = join5(absolutePath, entry.name);
|
|
426
427
|
if (entry.isDirectory()) {
|
|
427
428
|
files.push(...listFiles3(entryPath));
|
|
@@ -431,6 +432,9 @@ function listFiles3(absolutePath) {
|
|
|
431
432
|
}
|
|
432
433
|
return files.sort();
|
|
433
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
|
+
}
|
|
434
438
|
function toUnixPath3(path) {
|
|
435
439
|
return path.replaceAll("\\", "/");
|
|
436
440
|
}
|
|
@@ -532,8 +536,8 @@ function resolveSafePath(root, sourcePath) {
|
|
|
532
536
|
}
|
|
533
537
|
|
|
534
538
|
// src/asset-targets/apply.ts
|
|
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";
|
|
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";
|
|
537
541
|
function applyAssetInstallPlan(plan) {
|
|
538
542
|
const appliedActions = [];
|
|
539
543
|
for (const action of plan.actions) {
|
|
@@ -555,15 +559,16 @@ function applyAction(targetDir, action) {
|
|
|
555
559
|
}
|
|
556
560
|
mkdirSync2(dirname3(targetAbsolutePath), { recursive: true });
|
|
557
561
|
const sourceAbsolutePath = resolve(action.sourcePath);
|
|
562
|
+
const symlinkTarget = relative4(realpathSync(dirname3(targetAbsolutePath)), realpathSync(sourceAbsolutePath)) || ".";
|
|
558
563
|
if (action.type === "symlink") {
|
|
559
564
|
if (pathExistsEvenIfDanglingSymlink2(targetAbsolutePath)) {
|
|
560
565
|
throw new Error(`Refusing to overwrite unmanaged target: ${action.targetPath}`);
|
|
561
566
|
}
|
|
562
|
-
symlinkSync(
|
|
567
|
+
symlinkSync(symlinkTarget, targetAbsolutePath);
|
|
563
568
|
return;
|
|
564
569
|
}
|
|
565
570
|
if (!pathExistsEvenIfDanglingSymlink2(targetAbsolutePath)) {
|
|
566
|
-
symlinkSync(
|
|
571
|
+
symlinkSync(symlinkTarget, targetAbsolutePath);
|
|
567
572
|
return;
|
|
568
573
|
}
|
|
569
574
|
const stats = lstatSync2(targetAbsolutePath);
|
|
@@ -571,7 +576,7 @@ function applyAction(targetDir, action) {
|
|
|
571
576
|
throw new Error(`Refusing to overwrite unmanaged target: ${action.targetPath}`);
|
|
572
577
|
}
|
|
573
578
|
unlinkSync(targetAbsolutePath);
|
|
574
|
-
symlinkSync(
|
|
579
|
+
symlinkSync(symlinkTarget, targetAbsolutePath);
|
|
575
580
|
}
|
|
576
581
|
function pathExistsEvenIfDanglingSymlink2(path) {
|
|
577
582
|
try {
|
|
@@ -601,20 +606,19 @@ function checkInstalledAssets(options) {
|
|
|
601
606
|
const registryById = new Map(options.registry.assets.map((asset) => [asset.id, asset]));
|
|
602
607
|
const lockfile = JSON.parse(readFileSync4(lockfilePath, "utf8"));
|
|
603
608
|
const issues = [];
|
|
609
|
+
const strictRegistry = options.strictRegistry ?? false;
|
|
604
610
|
for (const entry of lockfile.assets ?? []) {
|
|
605
611
|
const asset = registryById.get(entry.id);
|
|
606
612
|
const targetAbsolutePath = join8(options.targetDir, entry.targetPath);
|
|
607
|
-
|
|
608
|
-
if (!asset) {
|
|
613
|
+
if (!asset && strictRegistry) {
|
|
609
614
|
issues.push({
|
|
610
615
|
type: "unknown-asset",
|
|
611
616
|
id: entry.id,
|
|
612
617
|
targetPath: entry.targetPath,
|
|
613
618
|
message: `Lockfile references unknown asset: ${entry.id}`
|
|
614
619
|
});
|
|
615
|
-
continue;
|
|
616
620
|
}
|
|
617
|
-
if (asset
|
|
621
|
+
if (asset?.kind === "skill" && asset.defaultScope === "user") {
|
|
618
622
|
issues.push({
|
|
619
623
|
type: "user-scoped-asset-in-project-lock",
|
|
620
624
|
id: entry.id,
|
|
@@ -622,13 +626,15 @@ function checkInstalledAssets(options) {
|
|
|
622
626
|
message: `User-scoped skill is still locked into this project; move it to the user skill roots: ${entry.id}`
|
|
623
627
|
});
|
|
624
628
|
}
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
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
|
+
}
|
|
632
638
|
}
|
|
633
639
|
if (!pathExistsEvenIfDanglingSymlink3(targetAbsolutePath)) {
|
|
634
640
|
issues.push({
|
|
@@ -658,6 +664,18 @@ function checkInstalledAssets(options) {
|
|
|
658
664
|
});
|
|
659
665
|
continue;
|
|
660
666
|
}
|
|
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);
|
|
661
679
|
if (!existsSync8(sourceAbsolutePath)) {
|
|
662
680
|
issues.push({
|
|
663
681
|
type: "missing-source",
|
|
@@ -667,8 +685,8 @@ function checkInstalledAssets(options) {
|
|
|
667
685
|
});
|
|
668
686
|
continue;
|
|
669
687
|
}
|
|
670
|
-
const
|
|
671
|
-
if (
|
|
688
|
+
const currentSourceHash = hashAgentAssetContent(asset, options.agentAssetsDir);
|
|
689
|
+
if (targetHashMatchesLock && currentSourceHash !== entry.contentHash) {
|
|
672
690
|
issues.push({
|
|
673
691
|
type: "hash-drift",
|
|
674
692
|
id: entry.id,
|
|
@@ -1145,7 +1163,8 @@ function runAssetsCheck(args) {
|
|
|
1145
1163
|
const result = checkInstalledAssets({
|
|
1146
1164
|
targetDir: options.value.targetDir,
|
|
1147
1165
|
agentAssetsDir: loaded.agentAssetsDir,
|
|
1148
|
-
registry: loaded.registry
|
|
1166
|
+
registry: loaded.registry,
|
|
1167
|
+
strictRegistry: options.value.strictRegistry
|
|
1149
1168
|
});
|
|
1150
1169
|
if (options.value.json) {
|
|
1151
1170
|
console.log(JSON.stringify(result, null, 2));
|
|
@@ -1300,6 +1319,8 @@ function parseTargetJsonOptions(args) {
|
|
|
1300
1319
|
if (!targetDir) return { ok: false, error: "Expected --target <path>" };
|
|
1301
1320
|
options.targetDir = targetDir;
|
|
1302
1321
|
index += 1;
|
|
1322
|
+
} else if (arg === "--strict-registry") {
|
|
1323
|
+
options.strictRegistry = true;
|
|
1303
1324
|
} else if (arg === "--json") {
|
|
1304
1325
|
options.json = true;
|
|
1305
1326
|
} else {
|
|
@@ -1580,6 +1601,10 @@ function resolveDocGovDependencyCli() {
|
|
|
1580
1601
|
}
|
|
1581
1602
|
}
|
|
1582
1603
|
|
|
1604
|
+
// src/commands/init.ts
|
|
1605
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync4, readFileSync as readFileSync8, writeFileSync as writeFileSync3 } from "node:fs";
|
|
1606
|
+
import { basename as basename3, dirname as dirname7, join as join13 } from "node:path";
|
|
1607
|
+
|
|
1583
1608
|
// src/commands/shared.ts
|
|
1584
1609
|
function planStarterFiles(profile) {
|
|
1585
1610
|
return listAssets().flatMap((asset) => {
|
|
@@ -1590,11 +1615,21 @@ function planStarterFiles(profile) {
|
|
|
1590
1615
|
{
|
|
1591
1616
|
sourcePath: asset.path,
|
|
1592
1617
|
targetPath,
|
|
1593
|
-
absoluteSourcePath: asset.absolutePath
|
|
1618
|
+
absoluteSourcePath: asset.absolutePath,
|
|
1619
|
+
ownership: classifyOwnership(targetPath)
|
|
1594
1620
|
}
|
|
1595
1621
|
];
|
|
1596
1622
|
}).sort((a, b) => a.targetPath.localeCompare(b.targetPath));
|
|
1597
1623
|
}
|
|
1624
|
+
function classifyOwnership(targetPath) {
|
|
1625
|
+
if (targetPath === "lefthook.yml" || targetPath === ".github/workflows/docs-check.yml") {
|
|
1626
|
+
return "optional-guardrail";
|
|
1627
|
+
}
|
|
1628
|
+
if (targetPath === "AGENTS.md" || targetPath === "CLAUDE.md" || targetPath === "docs/policy/best-practice-for-this-project.md" || targetPath === "docs/reference/documentation-map.md" || targetPath === "docs/reference/execution/current-work.md") {
|
|
1629
|
+
return "project-local-seed";
|
|
1630
|
+
}
|
|
1631
|
+
return "shared";
|
|
1632
|
+
}
|
|
1598
1633
|
function isOtherProfileRouting(targetPath, profile) {
|
|
1599
1634
|
return targetPath.startsWith("docs/governance/agents-routing/") && targetPath !== `docs/governance/agents-routing/${profile}-v0.9.md`;
|
|
1600
1635
|
}
|
|
@@ -1610,8 +1645,9 @@ function starterTargetPath(sourcePath) {
|
|
|
1610
1645
|
function runInit(args) {
|
|
1611
1646
|
const profile = readFlag(args, "--profile");
|
|
1612
1647
|
const dryRun = args.includes("--dry-run");
|
|
1613
|
-
|
|
1614
|
-
|
|
1648
|
+
const apply = args.includes("--apply");
|
|
1649
|
+
if (dryRun === apply) {
|
|
1650
|
+
console.error("pro-gov init requires exactly one of --dry-run or --apply.");
|
|
1615
1651
|
return 1;
|
|
1616
1652
|
}
|
|
1617
1653
|
if (!profile) {
|
|
@@ -1622,15 +1658,50 @@ function runInit(args) {
|
|
|
1622
1658
|
console.error(`Invalid profile: ${profile}`);
|
|
1623
1659
|
return 1;
|
|
1624
1660
|
}
|
|
1661
|
+
const files = planStarterFiles(profile).filter((file) => file.ownership !== "optional-guardrail");
|
|
1662
|
+
if (apply) return applyStarterFiles(files, profile);
|
|
1625
1663
|
console.log("pro-gov init DRY RUN");
|
|
1626
1664
|
console.log(`profile: ${profile}`);
|
|
1627
1665
|
console.log("");
|
|
1628
1666
|
console.log("Planned starter files:");
|
|
1629
|
-
for (const file of
|
|
1667
|
+
for (const file of files) {
|
|
1630
1668
|
console.log(` ${file.targetPath} <- ${file.sourcePath}`);
|
|
1631
1669
|
}
|
|
1632
1670
|
return 0;
|
|
1633
1671
|
}
|
|
1672
|
+
function applyStarterFiles(files, profile) {
|
|
1673
|
+
const root = process.cwd();
|
|
1674
|
+
const conflicts = files.filter((file) => existsSync13(join13(root, file.targetPath)));
|
|
1675
|
+
if (conflicts.length > 0) {
|
|
1676
|
+
console.error("pro-gov init is refusing to overwrite existing project files:");
|
|
1677
|
+
for (const file of conflicts) console.error(` ${file.targetPath}`);
|
|
1678
|
+
console.error("No files were written. Use --dry-run and migrate existing files deliberately.");
|
|
1679
|
+
return 1;
|
|
1680
|
+
}
|
|
1681
|
+
for (const file of files) {
|
|
1682
|
+
const targetPath = join13(root, file.targetPath);
|
|
1683
|
+
mkdirSync4(dirname7(targetPath), { recursive: true });
|
|
1684
|
+
const source = readFileSync8(file.absoluteSourcePath);
|
|
1685
|
+
const content = file.targetPath === "AGENTS.md" ? renderAgentsTemplate(source.toString("utf8"), basename3(root), profile) : source;
|
|
1686
|
+
writeFileSync3(targetPath, content);
|
|
1687
|
+
}
|
|
1688
|
+
console.log("pro-gov init APPLIED");
|
|
1689
|
+
console.log(`profile: ${profile}`);
|
|
1690
|
+
console.log(`created-files: ${files.length}`);
|
|
1691
|
+
console.log("Existing project files were not overwritten.");
|
|
1692
|
+
console.log("Next: customize project-local policy/current-work, run doc-gov scan, then run doc-gov doctor.");
|
|
1693
|
+
return 0;
|
|
1694
|
+
}
|
|
1695
|
+
function renderAgentsTemplate(template, projectName, profile) {
|
|
1696
|
+
const selectedRoute = `docs/governance/agents-routing/${profile}-v0.9.md`;
|
|
1697
|
+
return template.replace("# PROJECT_NAME AI Router", `# ${projectName} AI Router`).replace(
|
|
1698
|
+
/6\. The selected agents routing file:\n - `docs\/governance\/agents-routing\/engineering-runtime-v0\.9\.md`, or\n - `docs\/governance\/agents-routing\/doc-only-v0\.9\.md`/,
|
|
1699
|
+
`6. The selected agents routing file: \`${selectedRoute}\``
|
|
1700
|
+
).replace(
|
|
1701
|
+
"- Name this project's adopted profile: `engineering-runtime` or `doc-only`.",
|
|
1702
|
+
`- This project adopts the \`${profile}\` profile.`
|
|
1703
|
+
);
|
|
1704
|
+
}
|
|
1634
1705
|
function readFlag(args, flag) {
|
|
1635
1706
|
const index = args.indexOf(flag);
|
|
1636
1707
|
if (index === -1) return null;
|
|
@@ -1640,8 +1711,282 @@ function readFlag(args, flag) {
|
|
|
1640
1711
|
}
|
|
1641
1712
|
|
|
1642
1713
|
// src/commands/lens.ts
|
|
1643
|
-
import { mkdirSync as
|
|
1644
|
-
import { dirname as
|
|
1714
|
+
import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync5 } from "node:fs";
|
|
1715
|
+
import { dirname as dirname9 } from "node:path";
|
|
1716
|
+
|
|
1717
|
+
// src/lens/audit.ts
|
|
1718
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync5, readFileSync as readFileSync9, writeFileSync as writeFileSync4 } from "node:fs";
|
|
1719
|
+
import { basename as basename4, dirname as dirname8, join as join14 } from "node:path";
|
|
1720
|
+
var REQUIRED_ARTIFACTS = [
|
|
1721
|
+
"manifest.md",
|
|
1722
|
+
"raw/project-lens/architecture-lens.md",
|
|
1723
|
+
"raw/project-lens/truth-surface-audit.md",
|
|
1724
|
+
"raw/project-lens/technology-strategy.md",
|
|
1725
|
+
"raw/ponytail/ponytail-audit.md",
|
|
1726
|
+
"raw/ponytail/ponytail-debt.md",
|
|
1727
|
+
"raw/ponytail/ponytail-gain.md",
|
|
1728
|
+
"raw/target/target-state.md",
|
|
1729
|
+
"raw/target/commands.md",
|
|
1730
|
+
"raw/target/sources.md",
|
|
1731
|
+
"synthesis/decision-index.md",
|
|
1732
|
+
"synthesis/handoff-for-implementation-ai.md"
|
|
1733
|
+
];
|
|
1734
|
+
function createProjectLensAuditPackage(targetDir, auditDir) {
|
|
1735
|
+
const contract = {
|
|
1736
|
+
version: 1,
|
|
1737
|
+
target: {
|
|
1738
|
+
path: targetDir,
|
|
1739
|
+
name: basename4(targetDir) || "target"
|
|
1740
|
+
},
|
|
1741
|
+
requiredArtifacts: [...REQUIRED_ARTIFACTS]
|
|
1742
|
+
};
|
|
1743
|
+
mkdirSync5(auditDir, { recursive: true });
|
|
1744
|
+
writeJson(join14(auditDir, "audit.contract.json"), contract);
|
|
1745
|
+
for (const artifactPath of REQUIRED_ARTIFACTS) {
|
|
1746
|
+
writeTemplate(join14(auditDir, artifactPath), renderArtifactTemplate(artifactPath, contract));
|
|
1747
|
+
}
|
|
1748
|
+
return contract;
|
|
1749
|
+
}
|
|
1750
|
+
function checkProjectLensAuditPackage(auditDir, options = {}) {
|
|
1751
|
+
const contractPath = join14(auditDir, "audit.contract.json");
|
|
1752
|
+
if (!existsSync14(contractPath)) {
|
|
1753
|
+
return {
|
|
1754
|
+
ok: false,
|
|
1755
|
+
auditDir,
|
|
1756
|
+
issues: [
|
|
1757
|
+
{
|
|
1758
|
+
type: "missing-contract",
|
|
1759
|
+
path: "audit.contract.json"
|
|
1760
|
+
}
|
|
1761
|
+
]
|
|
1762
|
+
};
|
|
1763
|
+
}
|
|
1764
|
+
let contract;
|
|
1765
|
+
try {
|
|
1766
|
+
contract = JSON.parse(readFileSync9(contractPath, "utf8"));
|
|
1767
|
+
} catch (error) {
|
|
1768
|
+
return {
|
|
1769
|
+
ok: false,
|
|
1770
|
+
auditDir,
|
|
1771
|
+
issues: [
|
|
1772
|
+
{
|
|
1773
|
+
type: "invalid-contract",
|
|
1774
|
+
path: "audit.contract.json",
|
|
1775
|
+
message: error instanceof Error ? error.message : "Invalid JSON"
|
|
1776
|
+
}
|
|
1777
|
+
]
|
|
1778
|
+
};
|
|
1779
|
+
}
|
|
1780
|
+
const issues = [];
|
|
1781
|
+
if (contract.version !== 1 || !Array.isArray(contract.requiredArtifacts)) {
|
|
1782
|
+
issues.push({
|
|
1783
|
+
type: "invalid-contract",
|
|
1784
|
+
path: "audit.contract.json",
|
|
1785
|
+
message: "Expected version 1 and requiredArtifacts array"
|
|
1786
|
+
});
|
|
1787
|
+
} else {
|
|
1788
|
+
for (const artifactPath of REQUIRED_ARTIFACTS) {
|
|
1789
|
+
if (!contract.requiredArtifacts.includes(artifactPath)) {
|
|
1790
|
+
issues.push({
|
|
1791
|
+
type: "invalid-contract",
|
|
1792
|
+
path: "audit.contract.json",
|
|
1793
|
+
message: `Missing required artifact in contract: ${artifactPath}`
|
|
1794
|
+
});
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
for (const artifactPath of REQUIRED_ARTIFACTS) {
|
|
1799
|
+
const absolutePath = join14(auditDir, artifactPath);
|
|
1800
|
+
if (!existsSync14(absolutePath)) {
|
|
1801
|
+
issues.push({ type: "missing-required-artifact", path: artifactPath });
|
|
1802
|
+
continue;
|
|
1803
|
+
}
|
|
1804
|
+
const content = readFileSync9(absolutePath, "utf8");
|
|
1805
|
+
if (isPendingArtifact(content)) {
|
|
1806
|
+
issues.push({ type: "artifact-not-complete", path: artifactPath });
|
|
1807
|
+
} else if (hasTemplateBody(content)) {
|
|
1808
|
+
issues.push({ type: "artifact-template-not-replaced", path: artifactPath });
|
|
1809
|
+
} else {
|
|
1810
|
+
issues.push(...checkArtifactGuardrails(artifactPath, content, options.mode));
|
|
1811
|
+
}
|
|
1812
|
+
}
|
|
1813
|
+
return {
|
|
1814
|
+
ok: issues.length === 0,
|
|
1815
|
+
auditDir,
|
|
1816
|
+
issues
|
|
1817
|
+
};
|
|
1818
|
+
}
|
|
1819
|
+
function writeJson(path, value) {
|
|
1820
|
+
mkdirSync5(dirname8(path), { recursive: true });
|
|
1821
|
+
writeFileSync4(path, `${JSON.stringify(value, null, 2)}
|
|
1822
|
+
`);
|
|
1823
|
+
}
|
|
1824
|
+
function writeTemplate(path, content) {
|
|
1825
|
+
mkdirSync5(dirname8(path), { recursive: true });
|
|
1826
|
+
writeFileSync4(path, content);
|
|
1827
|
+
}
|
|
1828
|
+
function renderArtifactTemplate(artifactPath, contract) {
|
|
1829
|
+
const title = artifactPath.replace(/\.md$/, "").split("/").map((part) => part.replaceAll("-", " ")).join(" / ");
|
|
1830
|
+
const producer = artifactProducer(artifactPath);
|
|
1831
|
+
return `${[
|
|
1832
|
+
"---",
|
|
1833
|
+
"status: pending",
|
|
1834
|
+
`producer: ${producer}`,
|
|
1835
|
+
`target: ${contract.target.path}`,
|
|
1836
|
+
`artifact: ${artifactPath}`,
|
|
1837
|
+
"---",
|
|
1838
|
+
"",
|
|
1839
|
+
`# ${title}`,
|
|
1840
|
+
"",
|
|
1841
|
+
"Replace this template with the raw audit output for this producer.",
|
|
1842
|
+
...renderArtifactGuardrailHint(artifactPath),
|
|
1843
|
+
"",
|
|
1844
|
+
"Required completion marker:",
|
|
1845
|
+
"",
|
|
1846
|
+
"```text",
|
|
1847
|
+
"status: complete",
|
|
1848
|
+
"```"
|
|
1849
|
+
].join("\n")}
|
|
1850
|
+
`;
|
|
1851
|
+
}
|
|
1852
|
+
function renderArtifactGuardrailHint(artifactPath) {
|
|
1853
|
+
const rules = REQUIRED_METHOD_RECORDS[artifactPath];
|
|
1854
|
+
if (!rules) return [];
|
|
1855
|
+
const lines = [
|
|
1856
|
+
"",
|
|
1857
|
+
"Required audit method records. Keep these labels at the start of their own lines:",
|
|
1858
|
+
"",
|
|
1859
|
+
...rules.map((rule) => `${rule.label} <replace with evidence>`)
|
|
1860
|
+
];
|
|
1861
|
+
if (artifactPath === "manifest.md") {
|
|
1862
|
+
lines.push(
|
|
1863
|
+
"",
|
|
1864
|
+
"For --mode fresh, also keep these labels at the start of their own lines:",
|
|
1865
|
+
"",
|
|
1866
|
+
"Audit run mode: <replace with fresh>",
|
|
1867
|
+
"Current session id: <replace with current Codex thread id, or unknown plus reason>",
|
|
1868
|
+
"Fresh run evidence: <replace with current-run raw pass and subagent evidence>",
|
|
1869
|
+
"",
|
|
1870
|
+
"For --mode reuse, use these labels instead of the fresh-mode labels:",
|
|
1871
|
+
"",
|
|
1872
|
+
"Audit run mode: <replace with reuse>",
|
|
1873
|
+
"Reuse source audit: <replace with reused audit package path>",
|
|
1874
|
+
"Reuse justification: <replace with same target commit, clean status, and check result>",
|
|
1875
|
+
"No new subagents were run: <replace with true and explanation>"
|
|
1876
|
+
);
|
|
1877
|
+
}
|
|
1878
|
+
return lines;
|
|
1879
|
+
}
|
|
1880
|
+
function artifactProducer(artifactPath) {
|
|
1881
|
+
if (artifactPath.startsWith("raw/project-lens/")) return "project-lens";
|
|
1882
|
+
if (artifactPath.startsWith("raw/ponytail/")) return "ponytail";
|
|
1883
|
+
if (artifactPath.startsWith("raw/target/")) return "target-evidence";
|
|
1884
|
+
if (artifactPath.startsWith("synthesis/")) return "synthesis";
|
|
1885
|
+
return "audit";
|
|
1886
|
+
}
|
|
1887
|
+
function isPendingArtifact(content) {
|
|
1888
|
+
const frontmatter = content.match(/^---\n([\s\S]*?)\n---\n/);
|
|
1889
|
+
if (!frontmatter) return true;
|
|
1890
|
+
return !/^status:\s*complete\s*$/m.test(frontmatter[1]);
|
|
1891
|
+
}
|
|
1892
|
+
function hasTemplateBody(content) {
|
|
1893
|
+
return content.includes("Replace this template with the raw audit output for this producer.");
|
|
1894
|
+
}
|
|
1895
|
+
var REQUIRED_METHOD_RECORDS = {
|
|
1896
|
+
"manifest.md": [
|
|
1897
|
+
{
|
|
1898
|
+
label: "Read-only boundary:",
|
|
1899
|
+
match: /^Read-only boundary:/im
|
|
1900
|
+
},
|
|
1901
|
+
{
|
|
1902
|
+
label: "Agent execution record:",
|
|
1903
|
+
match: /^Agent execution record:/im
|
|
1904
|
+
},
|
|
1905
|
+
{
|
|
1906
|
+
label: "Subagent trace:",
|
|
1907
|
+
match: /^Subagent trace:/im
|
|
1908
|
+
}
|
|
1909
|
+
],
|
|
1910
|
+
"raw/target/commands.md": [
|
|
1911
|
+
{
|
|
1912
|
+
label: "Project Lens method source:",
|
|
1913
|
+
match: /^Project Lens method source:/im
|
|
1914
|
+
},
|
|
1915
|
+
{
|
|
1916
|
+
label: "Ponytail method source:",
|
|
1917
|
+
match: /^Ponytail method source:/im
|
|
1918
|
+
}
|
|
1919
|
+
],
|
|
1920
|
+
"synthesis/decision-index.md": [
|
|
1921
|
+
{
|
|
1922
|
+
label: "Target repository final status:",
|
|
1923
|
+
match: /^Target repository final status:/im
|
|
1924
|
+
},
|
|
1925
|
+
{
|
|
1926
|
+
label: "Audit package final status:",
|
|
1927
|
+
match: /^Audit package final status:/im
|
|
1928
|
+
}
|
|
1929
|
+
]
|
|
1930
|
+
};
|
|
1931
|
+
var RUN_MODE_METHOD_RECORDS = {
|
|
1932
|
+
fresh: [
|
|
1933
|
+
{
|
|
1934
|
+
label: "Audit run mode: fresh",
|
|
1935
|
+
match: /^Audit run mode:\s*fresh\b/im
|
|
1936
|
+
},
|
|
1937
|
+
{
|
|
1938
|
+
label: "Current session id:",
|
|
1939
|
+
match: /^Current session id:/im
|
|
1940
|
+
},
|
|
1941
|
+
{
|
|
1942
|
+
label: "Fresh run evidence:",
|
|
1943
|
+
match: /^Fresh run evidence:/im
|
|
1944
|
+
}
|
|
1945
|
+
],
|
|
1946
|
+
reuse: [
|
|
1947
|
+
{
|
|
1948
|
+
label: "Audit run mode: reuse",
|
|
1949
|
+
match: /^Audit run mode:\s*reuse\b/im
|
|
1950
|
+
},
|
|
1951
|
+
{
|
|
1952
|
+
label: "Reuse source audit:",
|
|
1953
|
+
match: /^Reuse source audit:/im
|
|
1954
|
+
},
|
|
1955
|
+
{
|
|
1956
|
+
label: "Reuse justification:",
|
|
1957
|
+
match: /^Reuse justification:/im
|
|
1958
|
+
},
|
|
1959
|
+
{
|
|
1960
|
+
label: "No new subagents were run:",
|
|
1961
|
+
match: /^No new subagents were run:/im
|
|
1962
|
+
}
|
|
1963
|
+
]
|
|
1964
|
+
};
|
|
1965
|
+
function checkArtifactGuardrails(artifactPath, content, mode) {
|
|
1966
|
+
const rules = [
|
|
1967
|
+
...REQUIRED_METHOD_RECORDS[artifactPath] ?? [],
|
|
1968
|
+
...artifactPath === "manifest.md" && mode ? RUN_MODE_METHOD_RECORDS[mode] : []
|
|
1969
|
+
];
|
|
1970
|
+
if (!rules) return [];
|
|
1971
|
+
return rules.filter((rule) => !hasCompletedMethodRecord(rule, content)).map((rule) => ({
|
|
1972
|
+
type: "audit-method-not-recorded",
|
|
1973
|
+
path: artifactPath,
|
|
1974
|
+
message: `Missing required audit method record: ${rule.label}`
|
|
1975
|
+
}));
|
|
1976
|
+
}
|
|
1977
|
+
function hasCompletedMethodRecord(rule, content) {
|
|
1978
|
+
return content.split(/\r?\n/).some((line) => rule.match.test(line) && !line.includes("<replace with"));
|
|
1979
|
+
}
|
|
1980
|
+
function formatProjectLensAuditCheckText(result) {
|
|
1981
|
+
if (result.ok) return `audit package ok: ${result.auditDir}`;
|
|
1982
|
+
return [
|
|
1983
|
+
`audit package failed: ${result.auditDir}`,
|
|
1984
|
+
...result.issues.map((issue) => {
|
|
1985
|
+
const message = issue.message ? `: ${issue.message}` : "";
|
|
1986
|
+
return `- ${issue.type}: ${issue.path}${message}`;
|
|
1987
|
+
})
|
|
1988
|
+
].join("\n");
|
|
1989
|
+
}
|
|
1645
1990
|
|
|
1646
1991
|
// src/lens/report.ts
|
|
1647
1992
|
function formatProjectLensInspection(report) {
|
|
@@ -1716,8 +2061,8 @@ function bulletList(values) {
|
|
|
1716
2061
|
|
|
1717
2062
|
// src/lens/scan.ts
|
|
1718
2063
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
1719
|
-
import { existsSync as
|
|
1720
|
-
import { join as
|
|
2064
|
+
import { existsSync as existsSync15, readdirSync as readdirSync6, readFileSync as readFileSync10, statSync as statSync3 } from "node:fs";
|
|
2065
|
+
import { join as join15, relative as relative5 } from "node:path";
|
|
1721
2066
|
var ignoredDirectories = /* @__PURE__ */ new Set([
|
|
1722
2067
|
".git",
|
|
1723
2068
|
".next",
|
|
@@ -1734,24 +2079,24 @@ function scanProjectLensTarget(targetDir, options = {}) {
|
|
|
1734
2079
|
return {
|
|
1735
2080
|
targetDir,
|
|
1736
2081
|
aiEntryFiles: ["AGENTS.md", "CLAUDE.md"].filter(
|
|
1737
|
-
(file) =>
|
|
2082
|
+
(file) => existsSync15(join15(targetDir, file))
|
|
1738
2083
|
),
|
|
1739
2084
|
aiConfigFiles: [],
|
|
1740
2085
|
packageJson,
|
|
1741
2086
|
docs: {
|
|
1742
|
-
hasDocsDirectory:
|
|
2087
|
+
hasDocsDirectory: existsSync15(join15(targetDir, "docs")),
|
|
1743
2088
|
markdownFileCount: markdownFiles.length,
|
|
1744
2089
|
governanceFiles: markdownFiles.filter((file) => file.startsWith("docs/governance/") || file.startsWith("docs/policy/")).sort()
|
|
1745
2090
|
},
|
|
1746
2091
|
git: readGitState(targetDir),
|
|
1747
|
-
largeFiles: files.map((file) => ({ path: file, bytes: statSync3(
|
|
2092
|
+
largeFiles: files.map((file) => ({ path: file, bytes: statSync3(join15(targetDir, file)).size })).filter((file) => file.bytes >= largeFileBytes).sort((a, b) => b.bytes - a.bytes || a.path.localeCompare(b.path)).slice(0, 25)
|
|
1748
2093
|
};
|
|
1749
2094
|
}
|
|
1750
2095
|
function readPackageJson(targetDir) {
|
|
1751
|
-
const packageJsonPath =
|
|
1752
|
-
if (!
|
|
2096
|
+
const packageJsonPath = join15(targetDir, "package.json");
|
|
2097
|
+
if (!existsSync15(packageJsonPath)) return void 0;
|
|
1753
2098
|
try {
|
|
1754
|
-
const packageJson = JSON.parse(
|
|
2099
|
+
const packageJson = JSON.parse(readFileSync10(packageJsonPath, "utf8"));
|
|
1755
2100
|
return {
|
|
1756
2101
|
scripts: Object.keys(packageJson.scripts ?? {}).sort(),
|
|
1757
2102
|
dependencies: Object.keys(packageJson.dependencies ?? {}).sort(),
|
|
@@ -1786,13 +2131,13 @@ function listProjectFiles(targetDir) {
|
|
|
1786
2131
|
return files.sort();
|
|
1787
2132
|
}
|
|
1788
2133
|
function collectFiles2(rootDir, currentDir, files) {
|
|
1789
|
-
if (!
|
|
2134
|
+
if (!existsSync15(currentDir)) return;
|
|
1790
2135
|
for (const entry of readdirSync6(currentDir, { withFileTypes: true })) {
|
|
1791
2136
|
if (entry.isDirectory()) {
|
|
1792
2137
|
if (ignoredDirectories.has(entry.name)) continue;
|
|
1793
|
-
collectFiles2(rootDir,
|
|
2138
|
+
collectFiles2(rootDir, join15(currentDir, entry.name), files);
|
|
1794
2139
|
} else if (entry.isFile()) {
|
|
1795
|
-
files.push(toUnixPath4(
|
|
2140
|
+
files.push(toUnixPath4(relative5(rootDir, join15(currentDir, entry.name))));
|
|
1796
2141
|
}
|
|
1797
2142
|
}
|
|
1798
2143
|
}
|
|
@@ -1809,6 +2154,9 @@ function runLens(args) {
|
|
|
1809
2154
|
if (subcommand2 === "report") {
|
|
1810
2155
|
return runLensReport(rest);
|
|
1811
2156
|
}
|
|
2157
|
+
if (subcommand2 === "audit") {
|
|
2158
|
+
return runLensAudit(rest);
|
|
2159
|
+
}
|
|
1812
2160
|
printUsage2();
|
|
1813
2161
|
return 1;
|
|
1814
2162
|
}
|
|
@@ -1841,11 +2189,52 @@ function runLensReport(args) {
|
|
|
1841
2189
|
}
|
|
1842
2190
|
const report = scanProjectLensTarget(options.value.targetDir);
|
|
1843
2191
|
const markdown = renderProjectLensMarkdownReport(report);
|
|
1844
|
-
|
|
1845
|
-
|
|
2192
|
+
mkdirSync6(dirname9(options.value.outPath), { recursive: true });
|
|
2193
|
+
writeFileSync5(options.value.outPath, markdown);
|
|
1846
2194
|
console.log(`report: ${options.value.outPath}`);
|
|
1847
2195
|
return 0;
|
|
1848
2196
|
}
|
|
2197
|
+
function runLensAudit(args) {
|
|
2198
|
+
const [auditSubcommand, ...rest] = args;
|
|
2199
|
+
if (auditSubcommand === "init") {
|
|
2200
|
+
const options = parseLensOptions(rest, "audit init");
|
|
2201
|
+
if (!options.ok) {
|
|
2202
|
+
console.error(options.error);
|
|
2203
|
+
printUsage2();
|
|
2204
|
+
return 1;
|
|
2205
|
+
}
|
|
2206
|
+
if (!options.value.outPath) {
|
|
2207
|
+
console.error("Expected --out <path>");
|
|
2208
|
+
printUsage2();
|
|
2209
|
+
return 1;
|
|
2210
|
+
}
|
|
2211
|
+
createProjectLensAuditPackage(options.value.targetDir, options.value.outPath);
|
|
2212
|
+
console.log(`audit: ${options.value.outPath}`);
|
|
2213
|
+
return 0;
|
|
2214
|
+
}
|
|
2215
|
+
if (auditSubcommand === "check") {
|
|
2216
|
+
const options = parseLensOptions(rest, "audit check");
|
|
2217
|
+
if (!options.ok) {
|
|
2218
|
+
console.error(options.error);
|
|
2219
|
+
printUsage2();
|
|
2220
|
+
return 1;
|
|
2221
|
+
}
|
|
2222
|
+
if (!options.value.auditDir) {
|
|
2223
|
+
console.error("Expected --dir <path>");
|
|
2224
|
+
printUsage2();
|
|
2225
|
+
return 1;
|
|
2226
|
+
}
|
|
2227
|
+
const result = checkProjectLensAuditPackage(options.value.auditDir, { mode: options.value.auditMode });
|
|
2228
|
+
if (options.value.json || options.value.format === "json") {
|
|
2229
|
+
console.log(JSON.stringify(result, null, 2));
|
|
2230
|
+
} else {
|
|
2231
|
+
console.log(formatProjectLensAuditCheckText(result));
|
|
2232
|
+
}
|
|
2233
|
+
return result.ok ? 0 : 1;
|
|
2234
|
+
}
|
|
2235
|
+
printUsage2();
|
|
2236
|
+
return 1;
|
|
2237
|
+
}
|
|
1849
2238
|
function parseLensOptions(args, subcommand2) {
|
|
1850
2239
|
const options = {
|
|
1851
2240
|
targetDir: process.cwd(),
|
|
@@ -1874,6 +2263,21 @@ function parseLensOptions(args, subcommand2) {
|
|
|
1874
2263
|
if (!outPath) return { ok: false, error: "Expected --out <path>" };
|
|
1875
2264
|
options.outPath = outPath;
|
|
1876
2265
|
index += 1;
|
|
2266
|
+
} else if (arg === "--dir") {
|
|
2267
|
+
const auditDir = args[index + 1];
|
|
2268
|
+
if (!auditDir) return { ok: false, error: "Expected --dir <path>" };
|
|
2269
|
+
options.auditDir = auditDir;
|
|
2270
|
+
index += 1;
|
|
2271
|
+
} else if (arg === "--mode") {
|
|
2272
|
+
if (subcommand2 !== "audit check") {
|
|
2273
|
+
return { ok: false, error: `Unknown lens ${subcommand2} option: ${arg}` };
|
|
2274
|
+
}
|
|
2275
|
+
const auditMode = args[index + 1];
|
|
2276
|
+
if (auditMode !== "fresh" && auditMode !== "reuse") {
|
|
2277
|
+
return { ok: false, error: "Expected --mode fresh|reuse" };
|
|
2278
|
+
}
|
|
2279
|
+
options.auditMode = auditMode;
|
|
2280
|
+
index += 1;
|
|
1877
2281
|
} else {
|
|
1878
2282
|
return { ok: false, error: `Unknown lens ${subcommand2} option: ${arg}` };
|
|
1879
2283
|
}
|
|
@@ -1884,14 +2288,21 @@ function printUsage2() {
|
|
|
1884
2288
|
console.error("Usage: pro-gov lens scan [--target <path>] [--json]");
|
|
1885
2289
|
console.error("Usage: pro-gov lens inspect [--target <path>] [--format text|json]");
|
|
1886
2290
|
console.error("Usage: pro-gov lens report --target <path> --out <path>");
|
|
2291
|
+
console.error("Usage: pro-gov lens audit init --target <path> --out <path>");
|
|
2292
|
+
console.error("Usage: pro-gov lens audit check --dir <path> [--mode fresh|reuse] [--json]");
|
|
1887
2293
|
}
|
|
1888
2294
|
|
|
2295
|
+
// src/commands/portfolio.ts
|
|
2296
|
+
import { existsSync as existsSync17 } from "node:fs";
|
|
2297
|
+
import { join as join16 } from "node:path";
|
|
2298
|
+
|
|
1889
2299
|
// src/portfolio/manifest.ts
|
|
1890
|
-
import { existsSync as
|
|
2300
|
+
import { existsSync as existsSync16, readFileSync as readFileSync11 } from "node:fs";
|
|
2301
|
+
import { dirname as dirname10, isAbsolute as isAbsolute3, resolve as resolve2 } from "node:path";
|
|
1891
2302
|
function loadPortfolioManifest(configPath) {
|
|
1892
2303
|
let parsed;
|
|
1893
2304
|
try {
|
|
1894
|
-
parsed = JSON.parse(
|
|
2305
|
+
parsed = JSON.parse(readFileSync11(configPath, "utf8"));
|
|
1895
2306
|
} catch (error) {
|
|
1896
2307
|
return {
|
|
1897
2308
|
configPath,
|
|
@@ -1903,13 +2314,29 @@ function loadPortfolioManifest(configPath) {
|
|
|
1903
2314
|
]
|
|
1904
2315
|
};
|
|
1905
2316
|
}
|
|
1906
|
-
const
|
|
2317
|
+
const normalized = resolveManifestPaths(parsed, dirname10(resolve2(configPath)));
|
|
2318
|
+
const issues = validatePortfolioManifest(normalized);
|
|
1907
2319
|
return {
|
|
1908
2320
|
configPath,
|
|
1909
|
-
manifest: issues.length === 0 ?
|
|
2321
|
+
manifest: issues.length === 0 ? normalized : void 0,
|
|
1910
2322
|
issues
|
|
1911
2323
|
};
|
|
1912
2324
|
}
|
|
2325
|
+
function resolveManifestPaths(value, configDir) {
|
|
2326
|
+
if (!isRecord(value)) return value;
|
|
2327
|
+
const resolveEndpoint = (endpoint) => {
|
|
2328
|
+
if (!isRecord(endpoint) || typeof endpoint.path !== "string" || isAbsolute3(endpoint.path)) {
|
|
2329
|
+
return endpoint;
|
|
2330
|
+
}
|
|
2331
|
+
return { ...endpoint, path: resolve2(configDir, endpoint.path) };
|
|
2332
|
+
};
|
|
2333
|
+
return {
|
|
2334
|
+
...value,
|
|
2335
|
+
controlPlane: resolveEndpoint(value.controlPlane),
|
|
2336
|
+
executionEngine: resolveEndpoint(value.executionEngine),
|
|
2337
|
+
targets: Array.isArray(value.targets) ? value.targets.map(resolveEndpoint) : value.targets
|
|
2338
|
+
};
|
|
2339
|
+
}
|
|
1913
2340
|
function validatePortfolioManifest(value) {
|
|
1914
2341
|
const issues = [];
|
|
1915
2342
|
if (!isRecord(value)) {
|
|
@@ -1935,6 +2362,7 @@ function validatePortfolioManifest(value) {
|
|
|
1935
2362
|
message: "Portfolio manifest portfolioId must be a non-empty string."
|
|
1936
2363
|
});
|
|
1937
2364
|
}
|
|
2365
|
+
validateAllowedFields(value, "root", ["schemaVersion", "portfolioId", "controlPlane", "executionEngine", "targets"], issues);
|
|
1938
2366
|
validateEndpoint(value.controlPlane, "controlPlane", issues);
|
|
1939
2367
|
validateEndpoint(value.executionEngine, "executionEngine", issues);
|
|
1940
2368
|
if (!Array.isArray(value.targets)) {
|
|
@@ -1956,6 +2384,7 @@ function validatePortfolioManifest(value) {
|
|
|
1956
2384
|
continue;
|
|
1957
2385
|
}
|
|
1958
2386
|
validateEndpoint(target, "targets", issues);
|
|
2387
|
+
validateAllowedFields(target, "target", ["id", "path", "profile", "assetBundles"], issues);
|
|
1959
2388
|
if (typeof target.id === "string") {
|
|
1960
2389
|
if (seenTargetIds.has(target.id)) {
|
|
1961
2390
|
issues.push({
|
|
@@ -1966,14 +2395,41 @@ function validatePortfolioManifest(value) {
|
|
|
1966
2395
|
}
|
|
1967
2396
|
seenTargetIds.add(target.id);
|
|
1968
2397
|
}
|
|
2398
|
+
if (target.profile !== void 0 && (typeof target.profile !== "string" || !isValidProfile(target.profile))) {
|
|
2399
|
+
issues.push({
|
|
2400
|
+
type: "invalid-field",
|
|
2401
|
+
id: typeof target.id === "string" ? target.id : void 0,
|
|
2402
|
+
field: "profile",
|
|
2403
|
+
message: "Portfolio target profile must be engineering-runtime or doc-only."
|
|
2404
|
+
});
|
|
2405
|
+
}
|
|
1969
2406
|
validateOptionalStringArray(target.assetBundles, target.id, "assetBundles", issues);
|
|
1970
|
-
|
|
2407
|
+
if ("sharedRules" in target) {
|
|
2408
|
+
issues.push({
|
|
2409
|
+
type: "invalid-field",
|
|
2410
|
+
id: typeof target.id === "string" ? target.id : void 0,
|
|
2411
|
+
field: "sharedRules",
|
|
2412
|
+
message: "Portfolio target sharedRules is not managed yet; remove it until plan/check supports it."
|
|
2413
|
+
});
|
|
2414
|
+
}
|
|
1971
2415
|
}
|
|
1972
2416
|
return issues;
|
|
1973
2417
|
}
|
|
1974
2418
|
function getDefaultPortfolioTargets(manifest) {
|
|
1975
2419
|
return manifest?.targets ?? [];
|
|
1976
2420
|
}
|
|
2421
|
+
function validateAllowedFields(value, location, allowedFields, issues) {
|
|
2422
|
+
const allowed = new Set(allowedFields);
|
|
2423
|
+
for (const field of Object.keys(value)) {
|
|
2424
|
+
if (allowed.has(field)) continue;
|
|
2425
|
+
issues.push({
|
|
2426
|
+
type: "invalid-field",
|
|
2427
|
+
id: typeof value.id === "string" ? value.id : void 0,
|
|
2428
|
+
field,
|
|
2429
|
+
message: `Unknown portfolio ${location} field: ${field}`
|
|
2430
|
+
});
|
|
2431
|
+
}
|
|
2432
|
+
}
|
|
1977
2433
|
function validateEndpoint(value, field, issues) {
|
|
1978
2434
|
if (value === void 0) return;
|
|
1979
2435
|
if (!isRecord(value)) {
|
|
@@ -2000,7 +2456,7 @@ function validateEndpoint(value, field, issues) {
|
|
|
2000
2456
|
});
|
|
2001
2457
|
return;
|
|
2002
2458
|
}
|
|
2003
|
-
if (!
|
|
2459
|
+
if (!existsSync16(value.path)) {
|
|
2004
2460
|
issues.push({
|
|
2005
2461
|
type: "missing-path",
|
|
2006
2462
|
id: typeof value.id === "string" ? value.id : void 0,
|
|
@@ -2029,6 +2485,7 @@ function runPortfolio(args) {
|
|
|
2029
2485
|
const [subcommand2, ...rest] = args;
|
|
2030
2486
|
if (subcommand2 === "check") return runPortfolioCheck(rest);
|
|
2031
2487
|
if (subcommand2 === "plan") return runPortfolioPlan(rest);
|
|
2488
|
+
if (subcommand2 === "assets-check") return runPortfolioAssetsCheck(rest);
|
|
2032
2489
|
printUsage3();
|
|
2033
2490
|
return 1;
|
|
2034
2491
|
}
|
|
@@ -2085,7 +2542,9 @@ function runPortfolioPlan(args) {
|
|
|
2085
2542
|
console.error(`Unknown portfolio target: ${options.value.targetId}`);
|
|
2086
2543
|
return 1;
|
|
2087
2544
|
}
|
|
2088
|
-
const loadedAssets = loadAgentAssetRegistry(
|
|
2545
|
+
const loadedAssets = loadAgentAssetRegistry({
|
|
2546
|
+
agentAssetsDir: findPortfolioAgentAssetsDir(loaded.manifest)
|
|
2547
|
+
});
|
|
2089
2548
|
if (loadedAssets.issues.length > 0) {
|
|
2090
2549
|
for (const issue of loadedAssets.issues) {
|
|
2091
2550
|
console.error(`${issue.type}: ${issue.message}`);
|
|
@@ -2136,6 +2595,106 @@ function runPortfolioPlan(args) {
|
|
|
2136
2595
|
return 1;
|
|
2137
2596
|
}
|
|
2138
2597
|
}
|
|
2598
|
+
function runPortfolioAssetsCheck(args) {
|
|
2599
|
+
const options = parsePortfolioOptions(args);
|
|
2600
|
+
if (!options.ok) {
|
|
2601
|
+
console.error(options.error);
|
|
2602
|
+
printUsage3();
|
|
2603
|
+
return 1;
|
|
2604
|
+
}
|
|
2605
|
+
const loaded = loadPortfolioManifest(options.value.configPath);
|
|
2606
|
+
if (loaded.issues.length > 0 || !loaded.manifest) {
|
|
2607
|
+
if (options.value.json) {
|
|
2608
|
+
console.log(
|
|
2609
|
+
JSON.stringify(
|
|
2610
|
+
{
|
|
2611
|
+
ok: false,
|
|
2612
|
+
configPath: loaded.configPath,
|
|
2613
|
+
issues: loaded.issues,
|
|
2614
|
+
targets: []
|
|
2615
|
+
},
|
|
2616
|
+
null,
|
|
2617
|
+
2
|
|
2618
|
+
)
|
|
2619
|
+
);
|
|
2620
|
+
} else {
|
|
2621
|
+
for (const issue of loaded.issues) {
|
|
2622
|
+
console.error(`${issue.type}: ${issue.message}`);
|
|
2623
|
+
}
|
|
2624
|
+
}
|
|
2625
|
+
return 1;
|
|
2626
|
+
}
|
|
2627
|
+
const targets = getDefaultPortfolioTargets(loaded.manifest).filter(
|
|
2628
|
+
(target) => !options.value.targetId || options.value.targetId === "all" || target.id === options.value.targetId
|
|
2629
|
+
);
|
|
2630
|
+
if (targets.length === 0) {
|
|
2631
|
+
console.error(`Unknown portfolio target: ${options.value.targetId}`);
|
|
2632
|
+
return 1;
|
|
2633
|
+
}
|
|
2634
|
+
const loadedAssets = loadAgentAssetRegistry({
|
|
2635
|
+
agentAssetsDir: findPortfolioAgentAssetsDir(loaded.manifest)
|
|
2636
|
+
});
|
|
2637
|
+
if (loadedAssets.issues.length > 0) {
|
|
2638
|
+
if (options.value.json) {
|
|
2639
|
+
console.log(
|
|
2640
|
+
JSON.stringify(
|
|
2641
|
+
{
|
|
2642
|
+
ok: false,
|
|
2643
|
+
configPath: loaded.configPath,
|
|
2644
|
+
portfolioId: loaded.manifest.portfolioId,
|
|
2645
|
+
registryIssues: loadedAssets.issues,
|
|
2646
|
+
targets: []
|
|
2647
|
+
},
|
|
2648
|
+
null,
|
|
2649
|
+
2
|
|
2650
|
+
)
|
|
2651
|
+
);
|
|
2652
|
+
} else {
|
|
2653
|
+
for (const issue of loadedAssets.issues) {
|
|
2654
|
+
console.error(`${issue.type}: ${issue.message}`);
|
|
2655
|
+
}
|
|
2656
|
+
}
|
|
2657
|
+
return 1;
|
|
2658
|
+
}
|
|
2659
|
+
const targetResults = targets.map((target) => {
|
|
2660
|
+
const result = checkInstalledAssets({
|
|
2661
|
+
targetDir: target.path,
|
|
2662
|
+
agentAssetsDir: loadedAssets.agentAssetsDir,
|
|
2663
|
+
registry: loadedAssets.registry,
|
|
2664
|
+
strictRegistry: true
|
|
2665
|
+
});
|
|
2666
|
+
return {
|
|
2667
|
+
id: target.id,
|
|
2668
|
+
path: target.path,
|
|
2669
|
+
issues: result.issues
|
|
2670
|
+
};
|
|
2671
|
+
});
|
|
2672
|
+
const ok = targetResults.every((target) => target.issues.length === 0);
|
|
2673
|
+
if (options.value.json) {
|
|
2674
|
+
console.log(
|
|
2675
|
+
JSON.stringify(
|
|
2676
|
+
{
|
|
2677
|
+
ok,
|
|
2678
|
+
configPath: loaded.configPath,
|
|
2679
|
+
portfolioId: loaded.manifest.portfolioId,
|
|
2680
|
+
agentAssetsDir: loadedAssets.agentAssetsDir,
|
|
2681
|
+
targets: targetResults
|
|
2682
|
+
},
|
|
2683
|
+
null,
|
|
2684
|
+
2
|
|
2685
|
+
)
|
|
2686
|
+
);
|
|
2687
|
+
} else if (ok) {
|
|
2688
|
+
console.log(`portfolio assets check passed (${targetResults.length} targets)`);
|
|
2689
|
+
} else {
|
|
2690
|
+
for (const target of targetResults) {
|
|
2691
|
+
for (const issue of target.issues) {
|
|
2692
|
+
console.log(`${target.id} ${issue.type}: ${issue.message}`);
|
|
2693
|
+
}
|
|
2694
|
+
}
|
|
2695
|
+
}
|
|
2696
|
+
return ok ? 0 : 1;
|
|
2697
|
+
}
|
|
2139
2698
|
function parsePortfolioOptions(args) {
|
|
2140
2699
|
const options = {
|
|
2141
2700
|
configPath: "",
|
|
@@ -2171,31 +2730,56 @@ function parsePortfolioOptions(args) {
|
|
|
2171
2730
|
function isHost2(value) {
|
|
2172
2731
|
return value === "codex" || value === "claude-code" || value === "gemini-cli" || value === "antigravity";
|
|
2173
2732
|
}
|
|
2733
|
+
function findPortfolioAgentAssetsDir(manifest) {
|
|
2734
|
+
const agentAssetsDir = manifest?.executionEngine?.path ? join16(manifest.executionEngine.path, "agent-assets") : void 0;
|
|
2735
|
+
return agentAssetsDir && existsSync17(join16(agentAssetsDir, "registry.json")) ? agentAssetsDir : void 0;
|
|
2736
|
+
}
|
|
2174
2737
|
function printUsage3() {
|
|
2175
2738
|
console.error("Usage:");
|
|
2176
2739
|
console.error(" pro-gov portfolio check --config <path> [--json]");
|
|
2177
2740
|
console.error(" pro-gov portfolio plan --config <path> [--target <id|all>] [--host codex|claude-code|gemini-cli|antigravity] [--json]");
|
|
2741
|
+
console.error(" pro-gov portfolio assets-check --config <path> [--target <id|all>] [--json]");
|
|
2178
2742
|
}
|
|
2179
2743
|
|
|
2180
2744
|
// src/commands/sync.ts
|
|
2181
|
-
import { existsSync as
|
|
2182
|
-
import { join as
|
|
2745
|
+
import { existsSync as existsSync18, readFileSync as readFileSync12 } from "node:fs";
|
|
2746
|
+
import { join as join17 } from "node:path";
|
|
2183
2747
|
function runSync(args) {
|
|
2184
2748
|
if (!args.includes("--check")) {
|
|
2185
|
-
console.error("pro-gov sync
|
|
2749
|
+
console.error("pro-gov sync is read-only and requires --check.");
|
|
2750
|
+
return 1;
|
|
2751
|
+
}
|
|
2752
|
+
const requestedProfile = readFlag2(args, "--profile");
|
|
2753
|
+
let profile;
|
|
2754
|
+
if (requestedProfile) {
|
|
2755
|
+
if (!isValidProfile(requestedProfile)) {
|
|
2756
|
+
console.error(`Invalid profile: ${requestedProfile}`);
|
|
2757
|
+
return 1;
|
|
2758
|
+
}
|
|
2759
|
+
profile = requestedProfile;
|
|
2760
|
+
} else {
|
|
2761
|
+
profile = inferInstalledProfile(process.cwd());
|
|
2762
|
+
}
|
|
2763
|
+
if (!profile) {
|
|
2764
|
+
console.error(
|
|
2765
|
+
"Cannot infer one installed profile. Pass --profile <engineering-runtime|doc-only>."
|
|
2766
|
+
);
|
|
2186
2767
|
return 1;
|
|
2187
2768
|
}
|
|
2188
2769
|
let differences = 0;
|
|
2189
2770
|
console.log("pro-gov sync check");
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2771
|
+
console.log(`profile: ${profile}`);
|
|
2772
|
+
for (const file of planStarterFiles(profile)) {
|
|
2773
|
+
const targetPath = join17(process.cwd(), file.targetPath);
|
|
2774
|
+
if (!existsSync18(targetPath)) {
|
|
2775
|
+
if (file.ownership === "optional-guardrail") continue;
|
|
2193
2776
|
console.log(`missing: ${file.targetPath}`);
|
|
2194
2777
|
differences += 1;
|
|
2195
2778
|
continue;
|
|
2196
2779
|
}
|
|
2197
|
-
|
|
2198
|
-
const
|
|
2780
|
+
if (file.ownership === "project-local-seed") continue;
|
|
2781
|
+
const source = readFileSync12(file.absoluteSourcePath, "utf8");
|
|
2782
|
+
const target = readFileSync12(targetPath, "utf8");
|
|
2199
2783
|
if (source !== target) {
|
|
2200
2784
|
console.log(`different: ${file.targetPath}`);
|
|
2201
2785
|
differences += 1;
|
|
@@ -2208,6 +2792,17 @@ function runSync(args) {
|
|
|
2208
2792
|
console.log("sync check passed: starter files match packaged assets.");
|
|
2209
2793
|
return 0;
|
|
2210
2794
|
}
|
|
2795
|
+
function inferInstalledProfile(root) {
|
|
2796
|
+
const installed = ["engineering-runtime", "doc-only"].filter(
|
|
2797
|
+
(profile) => existsSync18(join17(root, `docs/governance/agents-routing/${profile}-v0.9.md`))
|
|
2798
|
+
);
|
|
2799
|
+
return installed.length === 1 ? installed[0] : void 0;
|
|
2800
|
+
}
|
|
2801
|
+
function readFlag2(args, flag) {
|
|
2802
|
+
const index = args.indexOf(flag);
|
|
2803
|
+
const value = index >= 0 ? args[index + 1] : void 0;
|
|
2804
|
+
return value && !value.startsWith("--") ? value : void 0;
|
|
2805
|
+
}
|
|
2211
2806
|
|
|
2212
2807
|
// src/cli.ts
|
|
2213
2808
|
var COMMANDS = [
|
|
@@ -2216,16 +2811,19 @@ var COMMANDS = [
|
|
|
2216
2811
|
"assets recommend [--target <path>] [--json]",
|
|
2217
2812
|
"assets plan --bundle <bundle-id> [--target <path>] [--json]",
|
|
2218
2813
|
"assets apply --plan <path>",
|
|
2219
|
-
"assets check [--target <path>] [--json]",
|
|
2814
|
+
"assets check [--target <path>] [--strict-registry] [--json]",
|
|
2220
2815
|
"assets public-check [--public-root <path>] [--private-root <path>] [--json]",
|
|
2221
2816
|
"assets npx add|update ... --plan",
|
|
2222
2817
|
"portfolio check --config <path> [--json]",
|
|
2223
2818
|
"portfolio plan --config <path> [--target <id|all>] [--json]",
|
|
2819
|
+
"portfolio assets-check --config <path> [--target <id|all>] [--json]",
|
|
2224
2820
|
"lens scan [--target <path>] [--json]",
|
|
2225
2821
|
"lens inspect [--target <path>] [--format text|json]",
|
|
2226
2822
|
"lens report --target <path> --out <path>",
|
|
2227
|
-
"init --
|
|
2228
|
-
"
|
|
2823
|
+
"lens audit init --target <path> --out <path>",
|
|
2824
|
+
"lens audit check --dir <path> [--json]",
|
|
2825
|
+
"init --profile <engineering-runtime|doc-only> <--dry-run|--apply>",
|
|
2826
|
+
"sync --check [--profile <engineering-runtime|doc-only>]",
|
|
2229
2827
|
"doctor"
|
|
2230
2828
|
];
|
|
2231
2829
|
var [command, subcommand] = process.argv.slice(2);
|