@pieai/pro-gov 0.3.7 → 0.3.9
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 +36 -9
- package/assets/docs/reference/adoption/adoption-playbook.md +29 -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 +12 -4
- package/assets/starter/.agents/hooks.json +26 -0
- package/assets/starter/.claude/settings.json +26 -0
- package/assets/starter/.codex/hooks.json +28 -0
- package/assets/starter/AGENTS.template.md +3 -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 +680 -45
- 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
|
@@ -14,7 +14,7 @@ var assetRoots = [
|
|
|
14
14
|
"docs/reference/adoption"
|
|
15
15
|
];
|
|
16
16
|
function listAssets() {
|
|
17
|
-
const root = existsSync(
|
|
17
|
+
const root = existsSync(join(sourceRoot, "starter")) ? sourceRoot : packagedAssetsRoot;
|
|
18
18
|
return assetRoots.flatMap((assetRoot) => {
|
|
19
19
|
const absoluteRoot = join(root, assetRoot);
|
|
20
20
|
if (!existsSync(absoluteRoot)) return [];
|
|
@@ -1542,7 +1542,7 @@ function printNpxUsage() {
|
|
|
1542
1542
|
|
|
1543
1543
|
// src/commands/doctor.ts
|
|
1544
1544
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
1545
|
-
import { existsSync as existsSync12 } from "node:fs";
|
|
1545
|
+
import { existsSync as existsSync12, readFileSync as readFileSync8 } from "node:fs";
|
|
1546
1546
|
import { createRequire } from "node:module";
|
|
1547
1547
|
import { dirname as dirname6, join as join12 } from "node:path";
|
|
1548
1548
|
var REQUIRED_ASSETS = [
|
|
@@ -1554,9 +1554,11 @@ var REQUIRED_ASSETS = [
|
|
|
1554
1554
|
"profiles/doc-only/profile.md"
|
|
1555
1555
|
];
|
|
1556
1556
|
function runDoctor(_args) {
|
|
1557
|
+
const strictHooks = _args.includes("--strict-hooks");
|
|
1557
1558
|
const assets = listAssets();
|
|
1558
1559
|
const assetPaths = new Set(assets.map((asset) => asset.path));
|
|
1559
1560
|
const missing = REQUIRED_ASSETS.filter((assetPath) => !assetPaths.has(assetPath));
|
|
1561
|
+
const hookIssues = strictHooks ? checkStrictHostHooks(process.cwd()) : [];
|
|
1560
1562
|
console.log("pro-gov doctor");
|
|
1561
1563
|
console.log(`assets: ${assets.length}`);
|
|
1562
1564
|
if (missing.length > 0) {
|
|
@@ -1567,7 +1569,13 @@ function runDoctor(_args) {
|
|
|
1567
1569
|
console.log("assets: required project-governance assets found");
|
|
1568
1570
|
}
|
|
1569
1571
|
console.log(checkDocGov());
|
|
1570
|
-
|
|
1572
|
+
for (const issue of hookIssues) {
|
|
1573
|
+
console.error(issue);
|
|
1574
|
+
}
|
|
1575
|
+
if (strictHooks && hookIssues.length === 0) {
|
|
1576
|
+
console.log("host-hooks: PGS Compound Gate hooks wired");
|
|
1577
|
+
}
|
|
1578
|
+
return missing.length > 0 || hookIssues.length > 0 ? 1 : 0;
|
|
1571
1579
|
}
|
|
1572
1580
|
function checkDocGov() {
|
|
1573
1581
|
const fromPath = spawnSync2("doc-gov", ["--help"], {
|
|
@@ -1600,6 +1608,195 @@ function resolveDocGovDependencyCli() {
|
|
|
1600
1608
|
return null;
|
|
1601
1609
|
}
|
|
1602
1610
|
}
|
|
1611
|
+
function checkStrictHostHooks(root) {
|
|
1612
|
+
if (!isEngineeringRuntimeProject(root)) {
|
|
1613
|
+
return [];
|
|
1614
|
+
}
|
|
1615
|
+
const expected = [
|
|
1616
|
+
{ path: ".codex/hooks.json", host: "codex" },
|
|
1617
|
+
{ path: ".claude/settings.json", host: "claude-code" },
|
|
1618
|
+
{ path: ".agents/hooks.json", host: "antigravity" }
|
|
1619
|
+
];
|
|
1620
|
+
const issues = [];
|
|
1621
|
+
for (const entry of expected) {
|
|
1622
|
+
const absolutePath = join12(root, entry.path);
|
|
1623
|
+
if (!existsSync12(absolutePath)) {
|
|
1624
|
+
issues.push(`host-hooks: missing ${entry.path}`);
|
|
1625
|
+
continue;
|
|
1626
|
+
}
|
|
1627
|
+
const content = readFileSync8(absolutePath, "utf8");
|
|
1628
|
+
if (!content.includes("pro-gov host-hook") || !content.includes(`--host ${entry.host}`)) {
|
|
1629
|
+
issues.push(`host-hooks: ${entry.path} does not call pro-gov host-hook for ${entry.host}`);
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
return issues;
|
|
1633
|
+
}
|
|
1634
|
+
function isEngineeringRuntimeProject(root) {
|
|
1635
|
+
return existsSync12(join12(root, "docs/governance/agents-routing/engineering-runtime-v0.9.md"));
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
// src/commands/host-hook.ts
|
|
1639
|
+
import { readFileSync as readFileSync9 } from "node:fs";
|
|
1640
|
+
|
|
1641
|
+
// src/host-hooks/host-hook-runner.ts
|
|
1642
|
+
var gateMarkerPattern = /Compound Gate:\s*(ran ce-compound|skipped)\s*->/i;
|
|
1643
|
+
var completionSignalPatterns = [
|
|
1644
|
+
/\b(done|completed|implemented|fixed|verified|validated|shipped|pushed|committed)\b/i,
|
|
1645
|
+
/\b(tests?|typecheck|build|lint|doctor|pack)\b.*\b(pass|passed|green|succeed|succeeded|ok)\b/i,
|
|
1646
|
+
/\b(changed|updated|modified|created|deleted|refactored)\b.*\b(files?|docs?|tests?|hooks?|configs?)\b/i,
|
|
1647
|
+
/完成|已完成|修好了|实现了|验证通过|测试通过|已经提交|已经推送|提交并推送/
|
|
1648
|
+
];
|
|
1649
|
+
var compoundGateInstruction = [
|
|
1650
|
+
"Before final reporting, pass the PGS Compound Gate.",
|
|
1651
|
+
"If this completed work produced reusable learning, run compound-engineering:ce-compound and report:",
|
|
1652
|
+
"Compound Gate: ran ce-compound -> <path>",
|
|
1653
|
+
"If there is no reusable learning, report:",
|
|
1654
|
+
"Compound Gate: skipped -> <reason>"
|
|
1655
|
+
].join("\n");
|
|
1656
|
+
function evaluateHostHook(request) {
|
|
1657
|
+
if (request.event !== "Stop" && request.event !== "SubagentStop") {
|
|
1658
|
+
return { action: "allow" };
|
|
1659
|
+
}
|
|
1660
|
+
const input = normalizeStopInput(request.input);
|
|
1661
|
+
if (input.stopHookActive) {
|
|
1662
|
+
return { action: "allow" };
|
|
1663
|
+
}
|
|
1664
|
+
if (!input.lastAssistantMessage) {
|
|
1665
|
+
return { action: "allow" };
|
|
1666
|
+
}
|
|
1667
|
+
if (gateMarkerPattern.test(input.lastAssistantMessage)) {
|
|
1668
|
+
return { action: "allow" };
|
|
1669
|
+
}
|
|
1670
|
+
if (!looksLikeCompletedEngineeringWork(input.lastAssistantMessage)) {
|
|
1671
|
+
return { action: "allow" };
|
|
1672
|
+
}
|
|
1673
|
+
return { action: "continue", reason: compoundGateInstruction };
|
|
1674
|
+
}
|
|
1675
|
+
function formatHostHookOutput(host, event, decision) {
|
|
1676
|
+
if (decision.action === "allow") {
|
|
1677
|
+
return {};
|
|
1678
|
+
}
|
|
1679
|
+
if (host === "codex") {
|
|
1680
|
+
if (event === "Stop" || event === "SubagentStop") {
|
|
1681
|
+
return { decision: "block", reason: decision.reason };
|
|
1682
|
+
}
|
|
1683
|
+
if (decision.action === "block") {
|
|
1684
|
+
return {
|
|
1685
|
+
hookSpecificOutput: {
|
|
1686
|
+
hookEventName: event,
|
|
1687
|
+
permissionDecision: "deny",
|
|
1688
|
+
permissionDecisionReason: decision.reason
|
|
1689
|
+
},
|
|
1690
|
+
systemMessage: decision.reason
|
|
1691
|
+
};
|
|
1692
|
+
}
|
|
1693
|
+
return { systemMessage: decision.reason };
|
|
1694
|
+
}
|
|
1695
|
+
if (host === "claude" || host === "claude-code") {
|
|
1696
|
+
if (event === "Stop" || event === "SubagentStop") {
|
|
1697
|
+
return { ok: false, reason: decision.reason };
|
|
1698
|
+
}
|
|
1699
|
+
if (decision.action === "block") {
|
|
1700
|
+
return {
|
|
1701
|
+
hookSpecificOutput: {
|
|
1702
|
+
hookEventName: event,
|
|
1703
|
+
permissionDecision: "deny",
|
|
1704
|
+
permissionDecisionReason: decision.reason
|
|
1705
|
+
}
|
|
1706
|
+
};
|
|
1707
|
+
}
|
|
1708
|
+
return { systemMessage: decision.reason };
|
|
1709
|
+
}
|
|
1710
|
+
if (host === "antigravity") {
|
|
1711
|
+
if (event === "Stop" || event === "SubagentStop") {
|
|
1712
|
+
return { decision: "continue", reason: decision.reason };
|
|
1713
|
+
}
|
|
1714
|
+
if (decision.action === "block") {
|
|
1715
|
+
return { decision: "deny", reason: decision.reason };
|
|
1716
|
+
}
|
|
1717
|
+
return { decision: "allow", reason: decision.reason };
|
|
1718
|
+
}
|
|
1719
|
+
return {};
|
|
1720
|
+
}
|
|
1721
|
+
function normalizeStopInput(input) {
|
|
1722
|
+
if (!isRecord(input)) {
|
|
1723
|
+
return { lastAssistantMessage: void 0, stopHookActive: false };
|
|
1724
|
+
}
|
|
1725
|
+
return {
|
|
1726
|
+
lastAssistantMessage: findString(input, ["last_assistant_message", "lastAssistantMessage", "message", "text"]),
|
|
1727
|
+
stopHookActive: findBoolean(input, ["stop_hook_active", "stopHookActive"]) ?? false
|
|
1728
|
+
};
|
|
1729
|
+
}
|
|
1730
|
+
function looksLikeCompletedEngineeringWork(message) {
|
|
1731
|
+
return completionSignalPatterns.some((pattern) => pattern.test(message));
|
|
1732
|
+
}
|
|
1733
|
+
function findString(input, keys) {
|
|
1734
|
+
for (const key of keys) {
|
|
1735
|
+
const value = input[key];
|
|
1736
|
+
if (typeof value === "string" && value.trim().length > 0) {
|
|
1737
|
+
return value;
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1740
|
+
return void 0;
|
|
1741
|
+
}
|
|
1742
|
+
function findBoolean(input, keys) {
|
|
1743
|
+
for (const key of keys) {
|
|
1744
|
+
const value = input[key];
|
|
1745
|
+
if (typeof value === "boolean") {
|
|
1746
|
+
return value;
|
|
1747
|
+
}
|
|
1748
|
+
}
|
|
1749
|
+
return void 0;
|
|
1750
|
+
}
|
|
1751
|
+
function isRecord(value) {
|
|
1752
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1753
|
+
}
|
|
1754
|
+
|
|
1755
|
+
// src/host-hooks/types.ts
|
|
1756
|
+
function isHostHookHost(value) {
|
|
1757
|
+
return value === "antigravity" || value === "claude" || value === "claude-code" || value === "codex";
|
|
1758
|
+
}
|
|
1759
|
+
function isHostHookEvent(value) {
|
|
1760
|
+
return value === "PostToolUse" || value === "PreToolUse" || value === "Stop" || value === "SubagentStop" || value === "UserPromptSubmit";
|
|
1761
|
+
}
|
|
1762
|
+
|
|
1763
|
+
// src/commands/host-hook.ts
|
|
1764
|
+
function runHostHook(args) {
|
|
1765
|
+
const host = readOption(args, "--host");
|
|
1766
|
+
const event = readOption(args, "--event");
|
|
1767
|
+
if (!isHostHookHost(host)) {
|
|
1768
|
+
console.error("Expected --host <codex|claude-code|antigravity>");
|
|
1769
|
+
return 1;
|
|
1770
|
+
}
|
|
1771
|
+
if (!isHostHookEvent(event)) {
|
|
1772
|
+
console.error("Expected --event <Stop|SubagentStop|PreToolUse|PostToolUse|UserPromptSubmit>");
|
|
1773
|
+
return 1;
|
|
1774
|
+
}
|
|
1775
|
+
const input = readStdinJson();
|
|
1776
|
+
const decision = evaluateHostHook({ host, event, input });
|
|
1777
|
+
const output = formatHostHookOutput(host, event, decision);
|
|
1778
|
+
console.log(`${JSON.stringify(output)}
|
|
1779
|
+
`);
|
|
1780
|
+
return 0;
|
|
1781
|
+
}
|
|
1782
|
+
function readOption(args, name) {
|
|
1783
|
+
const index = args.indexOf(name);
|
|
1784
|
+
if (index < 0) return void 0;
|
|
1785
|
+
return args[index + 1];
|
|
1786
|
+
}
|
|
1787
|
+
function readStdinJson() {
|
|
1788
|
+
const raw = readFileSync9(0, "utf8").trim();
|
|
1789
|
+
if (!raw) return {};
|
|
1790
|
+
try {
|
|
1791
|
+
return JSON.parse(raw);
|
|
1792
|
+
} catch {
|
|
1793
|
+
return {};
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
|
|
1797
|
+
// src/commands/init.ts
|
|
1798
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync4, readFileSync as readFileSync10, writeFileSync as writeFileSync3 } from "node:fs";
|
|
1799
|
+
import { basename as basename3, dirname as dirname7, join as join13 } from "node:path";
|
|
1603
1800
|
|
|
1604
1801
|
// src/commands/shared.ts
|
|
1605
1802
|
function planStarterFiles(profile) {
|
|
@@ -1607,18 +1804,32 @@ function planStarterFiles(profile) {
|
|
|
1607
1804
|
const targetPath = starterTargetPath(asset.path);
|
|
1608
1805
|
if (!targetPath) return [];
|
|
1609
1806
|
if (profile && isOtherProfileRouting(targetPath, profile)) return [];
|
|
1807
|
+
if (profile && isEngineeringOnlyGuardrail(targetPath) && profile !== "engineering-runtime") return [];
|
|
1610
1808
|
return [
|
|
1611
1809
|
{
|
|
1612
1810
|
sourcePath: asset.path,
|
|
1613
1811
|
targetPath,
|
|
1614
|
-
absoluteSourcePath: asset.absolutePath
|
|
1812
|
+
absoluteSourcePath: asset.absolutePath,
|
|
1813
|
+
ownership: classifyOwnership(targetPath)
|
|
1615
1814
|
}
|
|
1616
1815
|
];
|
|
1617
1816
|
}).sort((a, b) => a.targetPath.localeCompare(b.targetPath));
|
|
1618
1817
|
}
|
|
1818
|
+
function classifyOwnership(targetPath) {
|
|
1819
|
+
if (targetPath === "lefthook.yml" || targetPath === ".github/workflows/docs-check.yml") {
|
|
1820
|
+
return "optional-guardrail";
|
|
1821
|
+
}
|
|
1822
|
+
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") {
|
|
1823
|
+
return "project-local-seed";
|
|
1824
|
+
}
|
|
1825
|
+
return "shared";
|
|
1826
|
+
}
|
|
1619
1827
|
function isOtherProfileRouting(targetPath, profile) {
|
|
1620
1828
|
return targetPath.startsWith("docs/governance/agents-routing/") && targetPath !== `docs/governance/agents-routing/${profile}-v0.9.md`;
|
|
1621
1829
|
}
|
|
1830
|
+
function isEngineeringOnlyGuardrail(targetPath) {
|
|
1831
|
+
return targetPath === ".codex/hooks.json" || targetPath === ".claude/settings.json" || targetPath === ".agents/hooks.json";
|
|
1832
|
+
}
|
|
1622
1833
|
function starterTargetPath(sourcePath) {
|
|
1623
1834
|
if (sourcePath === "starter/AGENTS.template.md") return "AGENTS.md";
|
|
1624
1835
|
if (sourcePath === "starter/CLAUDE.template.md") return "CLAUDE.md";
|
|
@@ -1631,8 +1842,9 @@ function starterTargetPath(sourcePath) {
|
|
|
1631
1842
|
function runInit(args) {
|
|
1632
1843
|
const profile = readFlag(args, "--profile");
|
|
1633
1844
|
const dryRun = args.includes("--dry-run");
|
|
1634
|
-
|
|
1635
|
-
|
|
1845
|
+
const apply = args.includes("--apply");
|
|
1846
|
+
if (dryRun === apply) {
|
|
1847
|
+
console.error("pro-gov init requires exactly one of --dry-run or --apply.");
|
|
1636
1848
|
return 1;
|
|
1637
1849
|
}
|
|
1638
1850
|
if (!profile) {
|
|
@@ -1643,15 +1855,50 @@ function runInit(args) {
|
|
|
1643
1855
|
console.error(`Invalid profile: ${profile}`);
|
|
1644
1856
|
return 1;
|
|
1645
1857
|
}
|
|
1858
|
+
const files = planStarterFiles(profile).filter((file) => file.ownership !== "optional-guardrail");
|
|
1859
|
+
if (apply) return applyStarterFiles(files, profile);
|
|
1646
1860
|
console.log("pro-gov init DRY RUN");
|
|
1647
1861
|
console.log(`profile: ${profile}`);
|
|
1648
1862
|
console.log("");
|
|
1649
1863
|
console.log("Planned starter files:");
|
|
1650
|
-
for (const file of
|
|
1864
|
+
for (const file of files) {
|
|
1651
1865
|
console.log(` ${file.targetPath} <- ${file.sourcePath}`);
|
|
1652
1866
|
}
|
|
1653
1867
|
return 0;
|
|
1654
1868
|
}
|
|
1869
|
+
function applyStarterFiles(files, profile) {
|
|
1870
|
+
const root = process.cwd();
|
|
1871
|
+
const conflicts = files.filter((file) => existsSync13(join13(root, file.targetPath)));
|
|
1872
|
+
if (conflicts.length > 0) {
|
|
1873
|
+
console.error("pro-gov init is refusing to overwrite existing project files:");
|
|
1874
|
+
for (const file of conflicts) console.error(` ${file.targetPath}`);
|
|
1875
|
+
console.error("No files were written. Use --dry-run and migrate existing files deliberately.");
|
|
1876
|
+
return 1;
|
|
1877
|
+
}
|
|
1878
|
+
for (const file of files) {
|
|
1879
|
+
const targetPath = join13(root, file.targetPath);
|
|
1880
|
+
mkdirSync4(dirname7(targetPath), { recursive: true });
|
|
1881
|
+
const source = readFileSync10(file.absoluteSourcePath);
|
|
1882
|
+
const content = file.targetPath === "AGENTS.md" ? renderAgentsTemplate(source.toString("utf8"), basename3(root), profile) : source;
|
|
1883
|
+
writeFileSync3(targetPath, content);
|
|
1884
|
+
}
|
|
1885
|
+
console.log("pro-gov init APPLIED");
|
|
1886
|
+
console.log(`profile: ${profile}`);
|
|
1887
|
+
console.log(`created-files: ${files.length}`);
|
|
1888
|
+
console.log("Existing project files were not overwritten.");
|
|
1889
|
+
console.log("Next: customize project-local policy/current-work, run doc-gov scan, then run doc-gov doctor.");
|
|
1890
|
+
return 0;
|
|
1891
|
+
}
|
|
1892
|
+
function renderAgentsTemplate(template, projectName, profile) {
|
|
1893
|
+
const selectedRoute = `docs/governance/agents-routing/${profile}-v0.9.md`;
|
|
1894
|
+
return template.replace("# PROJECT_NAME AI Router", `# ${projectName} AI Router`).replace(
|
|
1895
|
+
/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`/,
|
|
1896
|
+
`6. The selected agents routing file: \`${selectedRoute}\``
|
|
1897
|
+
).replace(
|
|
1898
|
+
"- Name this project's adopted profile: `engineering-runtime` or `doc-only`.",
|
|
1899
|
+
`- This project adopts the \`${profile}\` profile.`
|
|
1900
|
+
);
|
|
1901
|
+
}
|
|
1655
1902
|
function readFlag(args, flag) {
|
|
1656
1903
|
const index = args.indexOf(flag);
|
|
1657
1904
|
if (index === -1) return null;
|
|
@@ -1661,8 +1908,282 @@ function readFlag(args, flag) {
|
|
|
1661
1908
|
}
|
|
1662
1909
|
|
|
1663
1910
|
// src/commands/lens.ts
|
|
1664
|
-
import { mkdirSync as
|
|
1665
|
-
import { dirname as
|
|
1911
|
+
import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync5 } from "node:fs";
|
|
1912
|
+
import { dirname as dirname9 } from "node:path";
|
|
1913
|
+
|
|
1914
|
+
// src/lens/audit.ts
|
|
1915
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync5, readFileSync as readFileSync11, writeFileSync as writeFileSync4 } from "node:fs";
|
|
1916
|
+
import { basename as basename4, dirname as dirname8, join as join14 } from "node:path";
|
|
1917
|
+
var REQUIRED_ARTIFACTS = [
|
|
1918
|
+
"manifest.md",
|
|
1919
|
+
"raw/project-lens/architecture-lens.md",
|
|
1920
|
+
"raw/project-lens/truth-surface-audit.md",
|
|
1921
|
+
"raw/project-lens/technology-strategy.md",
|
|
1922
|
+
"raw/ponytail/ponytail-audit.md",
|
|
1923
|
+
"raw/ponytail/ponytail-debt.md",
|
|
1924
|
+
"raw/ponytail/ponytail-gain.md",
|
|
1925
|
+
"raw/target/target-state.md",
|
|
1926
|
+
"raw/target/commands.md",
|
|
1927
|
+
"raw/target/sources.md",
|
|
1928
|
+
"synthesis/decision-index.md",
|
|
1929
|
+
"synthesis/handoff-for-implementation-ai.md"
|
|
1930
|
+
];
|
|
1931
|
+
function createProjectLensAuditPackage(targetDir, auditDir) {
|
|
1932
|
+
const contract = {
|
|
1933
|
+
version: 1,
|
|
1934
|
+
target: {
|
|
1935
|
+
path: targetDir,
|
|
1936
|
+
name: basename4(targetDir) || "target"
|
|
1937
|
+
},
|
|
1938
|
+
requiredArtifacts: [...REQUIRED_ARTIFACTS]
|
|
1939
|
+
};
|
|
1940
|
+
mkdirSync5(auditDir, { recursive: true });
|
|
1941
|
+
writeJson(join14(auditDir, "audit.contract.json"), contract);
|
|
1942
|
+
for (const artifactPath of REQUIRED_ARTIFACTS) {
|
|
1943
|
+
writeTemplate(join14(auditDir, artifactPath), renderArtifactTemplate(artifactPath, contract));
|
|
1944
|
+
}
|
|
1945
|
+
return contract;
|
|
1946
|
+
}
|
|
1947
|
+
function checkProjectLensAuditPackage(auditDir, options = {}) {
|
|
1948
|
+
const contractPath = join14(auditDir, "audit.contract.json");
|
|
1949
|
+
if (!existsSync14(contractPath)) {
|
|
1950
|
+
return {
|
|
1951
|
+
ok: false,
|
|
1952
|
+
auditDir,
|
|
1953
|
+
issues: [
|
|
1954
|
+
{
|
|
1955
|
+
type: "missing-contract",
|
|
1956
|
+
path: "audit.contract.json"
|
|
1957
|
+
}
|
|
1958
|
+
]
|
|
1959
|
+
};
|
|
1960
|
+
}
|
|
1961
|
+
let contract;
|
|
1962
|
+
try {
|
|
1963
|
+
contract = JSON.parse(readFileSync11(contractPath, "utf8"));
|
|
1964
|
+
} catch (error) {
|
|
1965
|
+
return {
|
|
1966
|
+
ok: false,
|
|
1967
|
+
auditDir,
|
|
1968
|
+
issues: [
|
|
1969
|
+
{
|
|
1970
|
+
type: "invalid-contract",
|
|
1971
|
+
path: "audit.contract.json",
|
|
1972
|
+
message: error instanceof Error ? error.message : "Invalid JSON"
|
|
1973
|
+
}
|
|
1974
|
+
]
|
|
1975
|
+
};
|
|
1976
|
+
}
|
|
1977
|
+
const issues = [];
|
|
1978
|
+
if (contract.version !== 1 || !Array.isArray(contract.requiredArtifacts)) {
|
|
1979
|
+
issues.push({
|
|
1980
|
+
type: "invalid-contract",
|
|
1981
|
+
path: "audit.contract.json",
|
|
1982
|
+
message: "Expected version 1 and requiredArtifacts array"
|
|
1983
|
+
});
|
|
1984
|
+
} else {
|
|
1985
|
+
for (const artifactPath of REQUIRED_ARTIFACTS) {
|
|
1986
|
+
if (!contract.requiredArtifacts.includes(artifactPath)) {
|
|
1987
|
+
issues.push({
|
|
1988
|
+
type: "invalid-contract",
|
|
1989
|
+
path: "audit.contract.json",
|
|
1990
|
+
message: `Missing required artifact in contract: ${artifactPath}`
|
|
1991
|
+
});
|
|
1992
|
+
}
|
|
1993
|
+
}
|
|
1994
|
+
}
|
|
1995
|
+
for (const artifactPath of REQUIRED_ARTIFACTS) {
|
|
1996
|
+
const absolutePath = join14(auditDir, artifactPath);
|
|
1997
|
+
if (!existsSync14(absolutePath)) {
|
|
1998
|
+
issues.push({ type: "missing-required-artifact", path: artifactPath });
|
|
1999
|
+
continue;
|
|
2000
|
+
}
|
|
2001
|
+
const content = readFileSync11(absolutePath, "utf8");
|
|
2002
|
+
if (isPendingArtifact(content)) {
|
|
2003
|
+
issues.push({ type: "artifact-not-complete", path: artifactPath });
|
|
2004
|
+
} else if (hasTemplateBody(content)) {
|
|
2005
|
+
issues.push({ type: "artifact-template-not-replaced", path: artifactPath });
|
|
2006
|
+
} else {
|
|
2007
|
+
issues.push(...checkArtifactGuardrails(artifactPath, content, options.mode));
|
|
2008
|
+
}
|
|
2009
|
+
}
|
|
2010
|
+
return {
|
|
2011
|
+
ok: issues.length === 0,
|
|
2012
|
+
auditDir,
|
|
2013
|
+
issues
|
|
2014
|
+
};
|
|
2015
|
+
}
|
|
2016
|
+
function writeJson(path, value) {
|
|
2017
|
+
mkdirSync5(dirname8(path), { recursive: true });
|
|
2018
|
+
writeFileSync4(path, `${JSON.stringify(value, null, 2)}
|
|
2019
|
+
`);
|
|
2020
|
+
}
|
|
2021
|
+
function writeTemplate(path, content) {
|
|
2022
|
+
mkdirSync5(dirname8(path), { recursive: true });
|
|
2023
|
+
writeFileSync4(path, content);
|
|
2024
|
+
}
|
|
2025
|
+
function renderArtifactTemplate(artifactPath, contract) {
|
|
2026
|
+
const title = artifactPath.replace(/\.md$/, "").split("/").map((part) => part.replaceAll("-", " ")).join(" / ");
|
|
2027
|
+
const producer = artifactProducer(artifactPath);
|
|
2028
|
+
return `${[
|
|
2029
|
+
"---",
|
|
2030
|
+
"status: pending",
|
|
2031
|
+
`producer: ${producer}`,
|
|
2032
|
+
`target: ${contract.target.path}`,
|
|
2033
|
+
`artifact: ${artifactPath}`,
|
|
2034
|
+
"---",
|
|
2035
|
+
"",
|
|
2036
|
+
`# ${title}`,
|
|
2037
|
+
"",
|
|
2038
|
+
"Replace this template with the raw audit output for this producer.",
|
|
2039
|
+
...renderArtifactGuardrailHint(artifactPath),
|
|
2040
|
+
"",
|
|
2041
|
+
"Required completion marker:",
|
|
2042
|
+
"",
|
|
2043
|
+
"```text",
|
|
2044
|
+
"status: complete",
|
|
2045
|
+
"```"
|
|
2046
|
+
].join("\n")}
|
|
2047
|
+
`;
|
|
2048
|
+
}
|
|
2049
|
+
function renderArtifactGuardrailHint(artifactPath) {
|
|
2050
|
+
const rules = REQUIRED_METHOD_RECORDS[artifactPath];
|
|
2051
|
+
if (!rules) return [];
|
|
2052
|
+
const lines = [
|
|
2053
|
+
"",
|
|
2054
|
+
"Required audit method records. Keep these labels at the start of their own lines:",
|
|
2055
|
+
"",
|
|
2056
|
+
...rules.map((rule) => `${rule.label} <replace with evidence>`)
|
|
2057
|
+
];
|
|
2058
|
+
if (artifactPath === "manifest.md") {
|
|
2059
|
+
lines.push(
|
|
2060
|
+
"",
|
|
2061
|
+
"For --mode fresh, also keep these labels at the start of their own lines:",
|
|
2062
|
+
"",
|
|
2063
|
+
"Audit run mode: <replace with fresh>",
|
|
2064
|
+
"Current session id: <replace with current Codex thread id, or unknown plus reason>",
|
|
2065
|
+
"Fresh run evidence: <replace with current-run raw pass and subagent evidence>",
|
|
2066
|
+
"",
|
|
2067
|
+
"For --mode reuse, use these labels instead of the fresh-mode labels:",
|
|
2068
|
+
"",
|
|
2069
|
+
"Audit run mode: <replace with reuse>",
|
|
2070
|
+
"Reuse source audit: <replace with reused audit package path>",
|
|
2071
|
+
"Reuse justification: <replace with same target commit, clean status, and check result>",
|
|
2072
|
+
"No new subagents were run: <replace with true and explanation>"
|
|
2073
|
+
);
|
|
2074
|
+
}
|
|
2075
|
+
return lines;
|
|
2076
|
+
}
|
|
2077
|
+
function artifactProducer(artifactPath) {
|
|
2078
|
+
if (artifactPath.startsWith("raw/project-lens/")) return "project-lens";
|
|
2079
|
+
if (artifactPath.startsWith("raw/ponytail/")) return "ponytail";
|
|
2080
|
+
if (artifactPath.startsWith("raw/target/")) return "target-evidence";
|
|
2081
|
+
if (artifactPath.startsWith("synthesis/")) return "synthesis";
|
|
2082
|
+
return "audit";
|
|
2083
|
+
}
|
|
2084
|
+
function isPendingArtifact(content) {
|
|
2085
|
+
const frontmatter = content.match(/^---\n([\s\S]*?)\n---\n/);
|
|
2086
|
+
if (!frontmatter) return true;
|
|
2087
|
+
return !/^status:\s*complete\s*$/m.test(frontmatter[1]);
|
|
2088
|
+
}
|
|
2089
|
+
function hasTemplateBody(content) {
|
|
2090
|
+
return content.includes("Replace this template with the raw audit output for this producer.");
|
|
2091
|
+
}
|
|
2092
|
+
var REQUIRED_METHOD_RECORDS = {
|
|
2093
|
+
"manifest.md": [
|
|
2094
|
+
{
|
|
2095
|
+
label: "Read-only boundary:",
|
|
2096
|
+
match: /^Read-only boundary:/im
|
|
2097
|
+
},
|
|
2098
|
+
{
|
|
2099
|
+
label: "Agent execution record:",
|
|
2100
|
+
match: /^Agent execution record:/im
|
|
2101
|
+
},
|
|
2102
|
+
{
|
|
2103
|
+
label: "Subagent trace:",
|
|
2104
|
+
match: /^Subagent trace:/im
|
|
2105
|
+
}
|
|
2106
|
+
],
|
|
2107
|
+
"raw/target/commands.md": [
|
|
2108
|
+
{
|
|
2109
|
+
label: "Project Lens method source:",
|
|
2110
|
+
match: /^Project Lens method source:/im
|
|
2111
|
+
},
|
|
2112
|
+
{
|
|
2113
|
+
label: "Ponytail method source:",
|
|
2114
|
+
match: /^Ponytail method source:/im
|
|
2115
|
+
}
|
|
2116
|
+
],
|
|
2117
|
+
"synthesis/decision-index.md": [
|
|
2118
|
+
{
|
|
2119
|
+
label: "Target repository final status:",
|
|
2120
|
+
match: /^Target repository final status:/im
|
|
2121
|
+
},
|
|
2122
|
+
{
|
|
2123
|
+
label: "Audit package final status:",
|
|
2124
|
+
match: /^Audit package final status:/im
|
|
2125
|
+
}
|
|
2126
|
+
]
|
|
2127
|
+
};
|
|
2128
|
+
var RUN_MODE_METHOD_RECORDS = {
|
|
2129
|
+
fresh: [
|
|
2130
|
+
{
|
|
2131
|
+
label: "Audit run mode: fresh",
|
|
2132
|
+
match: /^Audit run mode:\s*fresh\b/im
|
|
2133
|
+
},
|
|
2134
|
+
{
|
|
2135
|
+
label: "Current session id:",
|
|
2136
|
+
match: /^Current session id:/im
|
|
2137
|
+
},
|
|
2138
|
+
{
|
|
2139
|
+
label: "Fresh run evidence:",
|
|
2140
|
+
match: /^Fresh run evidence:/im
|
|
2141
|
+
}
|
|
2142
|
+
],
|
|
2143
|
+
reuse: [
|
|
2144
|
+
{
|
|
2145
|
+
label: "Audit run mode: reuse",
|
|
2146
|
+
match: /^Audit run mode:\s*reuse\b/im
|
|
2147
|
+
},
|
|
2148
|
+
{
|
|
2149
|
+
label: "Reuse source audit:",
|
|
2150
|
+
match: /^Reuse source audit:/im
|
|
2151
|
+
},
|
|
2152
|
+
{
|
|
2153
|
+
label: "Reuse justification:",
|
|
2154
|
+
match: /^Reuse justification:/im
|
|
2155
|
+
},
|
|
2156
|
+
{
|
|
2157
|
+
label: "No new subagents were run:",
|
|
2158
|
+
match: /^No new subagents were run:/im
|
|
2159
|
+
}
|
|
2160
|
+
]
|
|
2161
|
+
};
|
|
2162
|
+
function checkArtifactGuardrails(artifactPath, content, mode) {
|
|
2163
|
+
const rules = [
|
|
2164
|
+
...REQUIRED_METHOD_RECORDS[artifactPath] ?? [],
|
|
2165
|
+
...artifactPath === "manifest.md" && mode ? RUN_MODE_METHOD_RECORDS[mode] : []
|
|
2166
|
+
];
|
|
2167
|
+
if (!rules) return [];
|
|
2168
|
+
return rules.filter((rule) => !hasCompletedMethodRecord(rule, content)).map((rule) => ({
|
|
2169
|
+
type: "audit-method-not-recorded",
|
|
2170
|
+
path: artifactPath,
|
|
2171
|
+
message: `Missing required audit method record: ${rule.label}`
|
|
2172
|
+
}));
|
|
2173
|
+
}
|
|
2174
|
+
function hasCompletedMethodRecord(rule, content) {
|
|
2175
|
+
return content.split(/\r?\n/).some((line) => rule.match.test(line) && !line.includes("<replace with"));
|
|
2176
|
+
}
|
|
2177
|
+
function formatProjectLensAuditCheckText(result) {
|
|
2178
|
+
if (result.ok) return `audit package ok: ${result.auditDir}`;
|
|
2179
|
+
return [
|
|
2180
|
+
`audit package failed: ${result.auditDir}`,
|
|
2181
|
+
...result.issues.map((issue) => {
|
|
2182
|
+
const message = issue.message ? `: ${issue.message}` : "";
|
|
2183
|
+
return `- ${issue.type}: ${issue.path}${message}`;
|
|
2184
|
+
})
|
|
2185
|
+
].join("\n");
|
|
2186
|
+
}
|
|
1666
2187
|
|
|
1667
2188
|
// src/lens/report.ts
|
|
1668
2189
|
function formatProjectLensInspection(report) {
|
|
@@ -1737,8 +2258,8 @@ function bulletList(values) {
|
|
|
1737
2258
|
|
|
1738
2259
|
// src/lens/scan.ts
|
|
1739
2260
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
1740
|
-
import { existsSync as
|
|
1741
|
-
import { join as
|
|
2261
|
+
import { existsSync as existsSync15, readdirSync as readdirSync6, readFileSync as readFileSync12, statSync as statSync3 } from "node:fs";
|
|
2262
|
+
import { join as join15, relative as relative5 } from "node:path";
|
|
1742
2263
|
var ignoredDirectories = /* @__PURE__ */ new Set([
|
|
1743
2264
|
".git",
|
|
1744
2265
|
".next",
|
|
@@ -1755,24 +2276,24 @@ function scanProjectLensTarget(targetDir, options = {}) {
|
|
|
1755
2276
|
return {
|
|
1756
2277
|
targetDir,
|
|
1757
2278
|
aiEntryFiles: ["AGENTS.md", "CLAUDE.md"].filter(
|
|
1758
|
-
(file) =>
|
|
2279
|
+
(file) => existsSync15(join15(targetDir, file))
|
|
1759
2280
|
),
|
|
1760
2281
|
aiConfigFiles: [],
|
|
1761
2282
|
packageJson,
|
|
1762
2283
|
docs: {
|
|
1763
|
-
hasDocsDirectory:
|
|
2284
|
+
hasDocsDirectory: existsSync15(join15(targetDir, "docs")),
|
|
1764
2285
|
markdownFileCount: markdownFiles.length,
|
|
1765
2286
|
governanceFiles: markdownFiles.filter((file) => file.startsWith("docs/governance/") || file.startsWith("docs/policy/")).sort()
|
|
1766
2287
|
},
|
|
1767
2288
|
git: readGitState(targetDir),
|
|
1768
|
-
largeFiles: files.map((file) => ({ path: file, bytes: statSync3(
|
|
2289
|
+
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)
|
|
1769
2290
|
};
|
|
1770
2291
|
}
|
|
1771
2292
|
function readPackageJson(targetDir) {
|
|
1772
|
-
const packageJsonPath =
|
|
1773
|
-
if (!
|
|
2293
|
+
const packageJsonPath = join15(targetDir, "package.json");
|
|
2294
|
+
if (!existsSync15(packageJsonPath)) return void 0;
|
|
1774
2295
|
try {
|
|
1775
|
-
const packageJson = JSON.parse(
|
|
2296
|
+
const packageJson = JSON.parse(readFileSync12(packageJsonPath, "utf8"));
|
|
1776
2297
|
return {
|
|
1777
2298
|
scripts: Object.keys(packageJson.scripts ?? {}).sort(),
|
|
1778
2299
|
dependencies: Object.keys(packageJson.dependencies ?? {}).sort(),
|
|
@@ -1807,13 +2328,13 @@ function listProjectFiles(targetDir) {
|
|
|
1807
2328
|
return files.sort();
|
|
1808
2329
|
}
|
|
1809
2330
|
function collectFiles2(rootDir, currentDir, files) {
|
|
1810
|
-
if (!
|
|
2331
|
+
if (!existsSync15(currentDir)) return;
|
|
1811
2332
|
for (const entry of readdirSync6(currentDir, { withFileTypes: true })) {
|
|
1812
2333
|
if (entry.isDirectory()) {
|
|
1813
2334
|
if (ignoredDirectories.has(entry.name)) continue;
|
|
1814
|
-
collectFiles2(rootDir,
|
|
2335
|
+
collectFiles2(rootDir, join15(currentDir, entry.name), files);
|
|
1815
2336
|
} else if (entry.isFile()) {
|
|
1816
|
-
files.push(toUnixPath4(relative5(rootDir,
|
|
2337
|
+
files.push(toUnixPath4(relative5(rootDir, join15(currentDir, entry.name))));
|
|
1817
2338
|
}
|
|
1818
2339
|
}
|
|
1819
2340
|
}
|
|
@@ -1830,6 +2351,9 @@ function runLens(args) {
|
|
|
1830
2351
|
if (subcommand2 === "report") {
|
|
1831
2352
|
return runLensReport(rest);
|
|
1832
2353
|
}
|
|
2354
|
+
if (subcommand2 === "audit") {
|
|
2355
|
+
return runLensAudit(rest);
|
|
2356
|
+
}
|
|
1833
2357
|
printUsage2();
|
|
1834
2358
|
return 1;
|
|
1835
2359
|
}
|
|
@@ -1862,11 +2386,52 @@ function runLensReport(args) {
|
|
|
1862
2386
|
}
|
|
1863
2387
|
const report = scanProjectLensTarget(options.value.targetDir);
|
|
1864
2388
|
const markdown = renderProjectLensMarkdownReport(report);
|
|
1865
|
-
|
|
1866
|
-
|
|
2389
|
+
mkdirSync6(dirname9(options.value.outPath), { recursive: true });
|
|
2390
|
+
writeFileSync5(options.value.outPath, markdown);
|
|
1867
2391
|
console.log(`report: ${options.value.outPath}`);
|
|
1868
2392
|
return 0;
|
|
1869
2393
|
}
|
|
2394
|
+
function runLensAudit(args) {
|
|
2395
|
+
const [auditSubcommand, ...rest] = args;
|
|
2396
|
+
if (auditSubcommand === "init") {
|
|
2397
|
+
const options = parseLensOptions(rest, "audit init");
|
|
2398
|
+
if (!options.ok) {
|
|
2399
|
+
console.error(options.error);
|
|
2400
|
+
printUsage2();
|
|
2401
|
+
return 1;
|
|
2402
|
+
}
|
|
2403
|
+
if (!options.value.outPath) {
|
|
2404
|
+
console.error("Expected --out <path>");
|
|
2405
|
+
printUsage2();
|
|
2406
|
+
return 1;
|
|
2407
|
+
}
|
|
2408
|
+
createProjectLensAuditPackage(options.value.targetDir, options.value.outPath);
|
|
2409
|
+
console.log(`audit: ${options.value.outPath}`);
|
|
2410
|
+
return 0;
|
|
2411
|
+
}
|
|
2412
|
+
if (auditSubcommand === "check") {
|
|
2413
|
+
const options = parseLensOptions(rest, "audit check");
|
|
2414
|
+
if (!options.ok) {
|
|
2415
|
+
console.error(options.error);
|
|
2416
|
+
printUsage2();
|
|
2417
|
+
return 1;
|
|
2418
|
+
}
|
|
2419
|
+
if (!options.value.auditDir) {
|
|
2420
|
+
console.error("Expected --dir <path>");
|
|
2421
|
+
printUsage2();
|
|
2422
|
+
return 1;
|
|
2423
|
+
}
|
|
2424
|
+
const result = checkProjectLensAuditPackage(options.value.auditDir, { mode: options.value.auditMode });
|
|
2425
|
+
if (options.value.json || options.value.format === "json") {
|
|
2426
|
+
console.log(JSON.stringify(result, null, 2));
|
|
2427
|
+
} else {
|
|
2428
|
+
console.log(formatProjectLensAuditCheckText(result));
|
|
2429
|
+
}
|
|
2430
|
+
return result.ok ? 0 : 1;
|
|
2431
|
+
}
|
|
2432
|
+
printUsage2();
|
|
2433
|
+
return 1;
|
|
2434
|
+
}
|
|
1870
2435
|
function parseLensOptions(args, subcommand2) {
|
|
1871
2436
|
const options = {
|
|
1872
2437
|
targetDir: process.cwd(),
|
|
@@ -1895,6 +2460,21 @@ function parseLensOptions(args, subcommand2) {
|
|
|
1895
2460
|
if (!outPath) return { ok: false, error: "Expected --out <path>" };
|
|
1896
2461
|
options.outPath = outPath;
|
|
1897
2462
|
index += 1;
|
|
2463
|
+
} else if (arg === "--dir") {
|
|
2464
|
+
const auditDir = args[index + 1];
|
|
2465
|
+
if (!auditDir) return { ok: false, error: "Expected --dir <path>" };
|
|
2466
|
+
options.auditDir = auditDir;
|
|
2467
|
+
index += 1;
|
|
2468
|
+
} else if (arg === "--mode") {
|
|
2469
|
+
if (subcommand2 !== "audit check") {
|
|
2470
|
+
return { ok: false, error: `Unknown lens ${subcommand2} option: ${arg}` };
|
|
2471
|
+
}
|
|
2472
|
+
const auditMode = args[index + 1];
|
|
2473
|
+
if (auditMode !== "fresh" && auditMode !== "reuse") {
|
|
2474
|
+
return { ok: false, error: "Expected --mode fresh|reuse" };
|
|
2475
|
+
}
|
|
2476
|
+
options.auditMode = auditMode;
|
|
2477
|
+
index += 1;
|
|
1898
2478
|
} else {
|
|
1899
2479
|
return { ok: false, error: `Unknown lens ${subcommand2} option: ${arg}` };
|
|
1900
2480
|
}
|
|
@@ -1905,18 +2485,21 @@ function printUsage2() {
|
|
|
1905
2485
|
console.error("Usage: pro-gov lens scan [--target <path>] [--json]");
|
|
1906
2486
|
console.error("Usage: pro-gov lens inspect [--target <path>] [--format text|json]");
|
|
1907
2487
|
console.error("Usage: pro-gov lens report --target <path> --out <path>");
|
|
2488
|
+
console.error("Usage: pro-gov lens audit init --target <path> --out <path>");
|
|
2489
|
+
console.error("Usage: pro-gov lens audit check --dir <path> [--mode fresh|reuse] [--json]");
|
|
1908
2490
|
}
|
|
1909
2491
|
|
|
1910
2492
|
// src/commands/portfolio.ts
|
|
1911
|
-
import { existsSync as
|
|
1912
|
-
import { join as
|
|
2493
|
+
import { existsSync as existsSync17 } from "node:fs";
|
|
2494
|
+
import { join as join16 } from "node:path";
|
|
1913
2495
|
|
|
1914
2496
|
// src/portfolio/manifest.ts
|
|
1915
|
-
import { existsSync as
|
|
2497
|
+
import { existsSync as existsSync16, readFileSync as readFileSync13 } from "node:fs";
|
|
2498
|
+
import { dirname as dirname10, isAbsolute as isAbsolute3, resolve as resolve2 } from "node:path";
|
|
1916
2499
|
function loadPortfolioManifest(configPath) {
|
|
1917
2500
|
let parsed;
|
|
1918
2501
|
try {
|
|
1919
|
-
parsed = JSON.parse(
|
|
2502
|
+
parsed = JSON.parse(readFileSync13(configPath, "utf8"));
|
|
1920
2503
|
} catch (error) {
|
|
1921
2504
|
return {
|
|
1922
2505
|
configPath,
|
|
@@ -1928,16 +2511,32 @@ function loadPortfolioManifest(configPath) {
|
|
|
1928
2511
|
]
|
|
1929
2512
|
};
|
|
1930
2513
|
}
|
|
1931
|
-
const
|
|
2514
|
+
const normalized = resolveManifestPaths(parsed, dirname10(resolve2(configPath)));
|
|
2515
|
+
const issues = validatePortfolioManifest(normalized);
|
|
1932
2516
|
return {
|
|
1933
2517
|
configPath,
|
|
1934
|
-
manifest: issues.length === 0 ?
|
|
2518
|
+
manifest: issues.length === 0 ? normalized : void 0,
|
|
1935
2519
|
issues
|
|
1936
2520
|
};
|
|
1937
2521
|
}
|
|
2522
|
+
function resolveManifestPaths(value, configDir) {
|
|
2523
|
+
if (!isRecord2(value)) return value;
|
|
2524
|
+
const resolveEndpoint = (endpoint) => {
|
|
2525
|
+
if (!isRecord2(endpoint) || typeof endpoint.path !== "string" || isAbsolute3(endpoint.path)) {
|
|
2526
|
+
return endpoint;
|
|
2527
|
+
}
|
|
2528
|
+
return { ...endpoint, path: resolve2(configDir, endpoint.path) };
|
|
2529
|
+
};
|
|
2530
|
+
return {
|
|
2531
|
+
...value,
|
|
2532
|
+
controlPlane: resolveEndpoint(value.controlPlane),
|
|
2533
|
+
executionEngine: resolveEndpoint(value.executionEngine),
|
|
2534
|
+
targets: Array.isArray(value.targets) ? value.targets.map(resolveEndpoint) : value.targets
|
|
2535
|
+
};
|
|
2536
|
+
}
|
|
1938
2537
|
function validatePortfolioManifest(value) {
|
|
1939
2538
|
const issues = [];
|
|
1940
|
-
if (!
|
|
2539
|
+
if (!isRecord2(value)) {
|
|
1941
2540
|
return [
|
|
1942
2541
|
{
|
|
1943
2542
|
type: "invalid-field",
|
|
@@ -1973,7 +2572,7 @@ function validatePortfolioManifest(value) {
|
|
|
1973
2572
|
}
|
|
1974
2573
|
const seenTargetIds = /* @__PURE__ */ new Set();
|
|
1975
2574
|
for (const target of value.targets) {
|
|
1976
|
-
if (!
|
|
2575
|
+
if (!isRecord2(target)) {
|
|
1977
2576
|
issues.push({
|
|
1978
2577
|
type: "invalid-field",
|
|
1979
2578
|
field: "targets",
|
|
@@ -2030,7 +2629,7 @@ function validateAllowedFields(value, location, allowedFields, issues) {
|
|
|
2030
2629
|
}
|
|
2031
2630
|
function validateEndpoint(value, field, issues) {
|
|
2032
2631
|
if (value === void 0) return;
|
|
2033
|
-
if (!
|
|
2632
|
+
if (!isRecord2(value)) {
|
|
2034
2633
|
issues.push({
|
|
2035
2634
|
type: "invalid-field",
|
|
2036
2635
|
field,
|
|
@@ -2054,7 +2653,7 @@ function validateEndpoint(value, field, issues) {
|
|
|
2054
2653
|
});
|
|
2055
2654
|
return;
|
|
2056
2655
|
}
|
|
2057
|
-
if (!
|
|
2656
|
+
if (!existsSync16(value.path)) {
|
|
2058
2657
|
issues.push({
|
|
2059
2658
|
type: "missing-path",
|
|
2060
2659
|
id: typeof value.id === "string" ? value.id : void 0,
|
|
@@ -2074,7 +2673,7 @@ function validateOptionalStringArray(value, id, field, issues) {
|
|
|
2074
2673
|
});
|
|
2075
2674
|
}
|
|
2076
2675
|
}
|
|
2077
|
-
function
|
|
2676
|
+
function isRecord2(value) {
|
|
2078
2677
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2079
2678
|
}
|
|
2080
2679
|
|
|
@@ -2329,8 +2928,8 @@ function isHost2(value) {
|
|
|
2329
2928
|
return value === "codex" || value === "claude-code" || value === "gemini-cli" || value === "antigravity";
|
|
2330
2929
|
}
|
|
2331
2930
|
function findPortfolioAgentAssetsDir(manifest) {
|
|
2332
|
-
const agentAssetsDir = manifest?.executionEngine?.path ?
|
|
2333
|
-
return agentAssetsDir &&
|
|
2931
|
+
const agentAssetsDir = manifest?.executionEngine?.path ? join16(manifest.executionEngine.path, "agent-assets") : void 0;
|
|
2932
|
+
return agentAssetsDir && existsSync17(join16(agentAssetsDir, "registry.json")) ? agentAssetsDir : void 0;
|
|
2334
2933
|
}
|
|
2335
2934
|
function printUsage3() {
|
|
2336
2935
|
console.error("Usage:");
|
|
@@ -2340,24 +2939,44 @@ function printUsage3() {
|
|
|
2340
2939
|
}
|
|
2341
2940
|
|
|
2342
2941
|
// src/commands/sync.ts
|
|
2343
|
-
import { existsSync as
|
|
2344
|
-
import { join as
|
|
2942
|
+
import { existsSync as existsSync18, readFileSync as readFileSync14 } from "node:fs";
|
|
2943
|
+
import { join as join17 } from "node:path";
|
|
2345
2944
|
function runSync(args) {
|
|
2346
2945
|
if (!args.includes("--check")) {
|
|
2347
|
-
console.error("pro-gov sync
|
|
2946
|
+
console.error("pro-gov sync is read-only and requires --check.");
|
|
2947
|
+
return 1;
|
|
2948
|
+
}
|
|
2949
|
+
const requestedProfile = readFlag2(args, "--profile");
|
|
2950
|
+
let profile;
|
|
2951
|
+
if (requestedProfile) {
|
|
2952
|
+
if (!isValidProfile(requestedProfile)) {
|
|
2953
|
+
console.error(`Invalid profile: ${requestedProfile}`);
|
|
2954
|
+
return 1;
|
|
2955
|
+
}
|
|
2956
|
+
profile = requestedProfile;
|
|
2957
|
+
} else {
|
|
2958
|
+
profile = inferInstalledProfile(process.cwd());
|
|
2959
|
+
}
|
|
2960
|
+
if (!profile) {
|
|
2961
|
+
console.error(
|
|
2962
|
+
"Cannot infer one installed profile. Pass --profile <engineering-runtime|doc-only>."
|
|
2963
|
+
);
|
|
2348
2964
|
return 1;
|
|
2349
2965
|
}
|
|
2350
2966
|
let differences = 0;
|
|
2351
2967
|
console.log("pro-gov sync check");
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2968
|
+
console.log(`profile: ${profile}`);
|
|
2969
|
+
for (const file of planStarterFiles(profile)) {
|
|
2970
|
+
const targetPath = join17(process.cwd(), file.targetPath);
|
|
2971
|
+
if (!existsSync18(targetPath)) {
|
|
2972
|
+
if (file.ownership === "optional-guardrail") continue;
|
|
2355
2973
|
console.log(`missing: ${file.targetPath}`);
|
|
2356
2974
|
differences += 1;
|
|
2357
2975
|
continue;
|
|
2358
2976
|
}
|
|
2359
|
-
|
|
2360
|
-
const
|
|
2977
|
+
if (file.ownership === "project-local-seed") continue;
|
|
2978
|
+
const source = readFileSync14(file.absoluteSourcePath, "utf8");
|
|
2979
|
+
const target = readFileSync14(targetPath, "utf8");
|
|
2361
2980
|
if (source !== target) {
|
|
2362
2981
|
console.log(`different: ${file.targetPath}`);
|
|
2363
2982
|
differences += 1;
|
|
@@ -2370,6 +2989,17 @@ function runSync(args) {
|
|
|
2370
2989
|
console.log("sync check passed: starter files match packaged assets.");
|
|
2371
2990
|
return 0;
|
|
2372
2991
|
}
|
|
2992
|
+
function inferInstalledProfile(root) {
|
|
2993
|
+
const installed = ["engineering-runtime", "doc-only"].filter(
|
|
2994
|
+
(profile) => existsSync18(join17(root, `docs/governance/agents-routing/${profile}-v0.9.md`))
|
|
2995
|
+
);
|
|
2996
|
+
return installed.length === 1 ? installed[0] : void 0;
|
|
2997
|
+
}
|
|
2998
|
+
function readFlag2(args, flag) {
|
|
2999
|
+
const index = args.indexOf(flag);
|
|
3000
|
+
const value = index >= 0 ? args[index + 1] : void 0;
|
|
3001
|
+
return value && !value.startsWith("--") ? value : void 0;
|
|
3002
|
+
}
|
|
2373
3003
|
|
|
2374
3004
|
// src/cli.ts
|
|
2375
3005
|
var COMMANDS = [
|
|
@@ -2387,8 +3017,11 @@ var COMMANDS = [
|
|
|
2387
3017
|
"lens scan [--target <path>] [--json]",
|
|
2388
3018
|
"lens inspect [--target <path>] [--format text|json]",
|
|
2389
3019
|
"lens report --target <path> --out <path>",
|
|
2390
|
-
"init --
|
|
2391
|
-
"
|
|
3020
|
+
"lens audit init --target <path> --out <path>",
|
|
3021
|
+
"lens audit check --dir <path> [--json]",
|
|
3022
|
+
"host-hook --host <codex|claude-code|antigravity> --event <Stop|SubagentStop|...>",
|
|
3023
|
+
"init --profile <engineering-runtime|doc-only> <--dry-run|--apply>",
|
|
3024
|
+
"sync --check [--profile <engineering-runtime|doc-only>]",
|
|
2392
3025
|
"doctor"
|
|
2393
3026
|
];
|
|
2394
3027
|
var [command, subcommand] = process.argv.slice(2);
|
|
@@ -2401,6 +3034,8 @@ if (!command || command === "--help" || command === "-h") {
|
|
|
2401
3034
|
process.exitCode = runLens(process.argv.slice(3));
|
|
2402
3035
|
} else if (command === "portfolio") {
|
|
2403
3036
|
process.exitCode = runPortfolio(process.argv.slice(3));
|
|
3037
|
+
} else if (command === "host-hook") {
|
|
3038
|
+
process.exitCode = runHostHook(process.argv.slice(3));
|
|
2404
3039
|
} else if (command === "init") {
|
|
2405
3040
|
process.exitCode = runInit(process.argv.slice(3));
|
|
2406
3041
|
} else if (command === "sync") {
|