@pieai/pro-gov 0.4.3 → 0.4.5
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 +16 -58
- package/assets/docs/reference/adoption/adoption-playbook.md +16 -68
- package/assets/docs/reference/adoption/recommended-agent-tooling.md +21 -118
- package/assets/integrations/mattpocock-skills.md +27 -130
- package/assets/profiles/doc-only/manifest.yml +1 -6
- package/assets/profiles/doc-only/profile.md +1 -2
- package/assets/profiles/engineering-runtime/manifest.yml +1 -5
- package/assets/profiles/engineering-runtime/profile.md +3 -14
- package/assets/starter/AGENTS.template.md +3 -7
- package/assets/starter/docs/governance/agents-routing/doc-only-v0.9.md +2 -4
- package/assets/starter/docs/governance/agents-routing/engineering-runtime-v0.9.md +3 -21
- package/assets/starter/docs/governance/boundary.md +4 -7
- package/assets/starter/docs/governance/ssot-v0.9.md +3 -4
- package/assets/starter/docs/reference/documentation-map.md +4 -4
- package/cli-guide.md +16 -40
- package/dist/cli.js +185 -709
- package/package.json +2 -2
- package/assets/docs/reference/adoption/agent-workflow-methodology-comparison.md +0 -331
- package/assets/integrations/compound-engineering.md +0 -164
- package/assets/integrations/directed-development.md +0 -46
- package/assets/integrations/ponytail.md +0 -161
- package/assets/integrations/superpowers.md +0 -107
- package/assets/starter/.agents/hooks.json +0 -26
- package/assets/starter/.claude/settings.json +0 -26
- package/assets/starter/.codex/hooks.json +0 -28
package/dist/cli.js
CHANGED
|
@@ -93,6 +93,7 @@ function createNpxSkillsMaintenancePlan(options) {
|
|
|
93
93
|
if (options.operation === "update") {
|
|
94
94
|
assertNoReportedPartialUpdateFailure(result.stdout, result.stderr);
|
|
95
95
|
}
|
|
96
|
+
assertNoDeprecatedMattSkills(tempRoot);
|
|
96
97
|
const after = snapshotFiles(tempRoot);
|
|
97
98
|
const changes = diffSnapshots(before, after);
|
|
98
99
|
return {
|
|
@@ -117,6 +118,16 @@ ${stderr}`.replace(/\u001B\[[0-?]*[ -/]*[@-~]/g, "");
|
|
|
117
118
|
throw new Error(`npx skills update reported a partial failure: ${failure[0]}`);
|
|
118
119
|
}
|
|
119
120
|
}
|
|
121
|
+
function assertNoDeprecatedMattSkills(npxRoot) {
|
|
122
|
+
const lockPath = join3(npxRoot, "skills-lock.json");
|
|
123
|
+
const lock = JSON.parse(readFileSync2(lockPath, "utf8"));
|
|
124
|
+
const deprecated = Object.entries(lock.skills ?? {}).filter(
|
|
125
|
+
([, entry]) => entry.source === "mattpocock/skills" && entry.skillPath?.startsWith("skills/deprecated/")
|
|
126
|
+
).map(([name]) => name).sort();
|
|
127
|
+
if (deprecated.length > 0) {
|
|
128
|
+
throw new Error(`npx skills plan contains deprecated mattpocock skills: ${deprecated.join(", ")}`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
120
131
|
function assertNativeNpxRoot(npxRoot) {
|
|
121
132
|
if (!existsSync3(join3(npxRoot, "skills-lock.json"))) {
|
|
122
133
|
throw new Error(`npx skills root is missing skills-lock.json: ${npxRoot}`);
|
|
@@ -1638,7 +1649,7 @@ function printNpxUsage() {
|
|
|
1638
1649
|
|
|
1639
1650
|
// src/commands/doctor.ts
|
|
1640
1651
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
1641
|
-
import { existsSync as existsSync12
|
|
1652
|
+
import { existsSync as existsSync12 } from "node:fs";
|
|
1642
1653
|
import { createRequire } from "node:module";
|
|
1643
1654
|
import { dirname as dirname6, join as join12 } from "node:path";
|
|
1644
1655
|
var REQUIRED_ASSETS = [
|
|
@@ -1650,11 +1661,9 @@ var REQUIRED_ASSETS = [
|
|
|
1650
1661
|
"profiles/doc-only/profile.md"
|
|
1651
1662
|
];
|
|
1652
1663
|
function runDoctor(_args) {
|
|
1653
|
-
const strictHooks = _args.includes("--strict-hooks");
|
|
1654
1664
|
const assets = listAssets();
|
|
1655
1665
|
const assetPaths = new Set(assets.map((asset) => asset.path));
|
|
1656
1666
|
const missing = REQUIRED_ASSETS.filter((assetPath) => !assetPaths.has(assetPath));
|
|
1657
|
-
const hookIssues = strictHooks ? checkStrictHostHooks(process.cwd()) : [];
|
|
1658
1667
|
console.log("pro-gov doctor");
|
|
1659
1668
|
console.log(`assets: ${assets.length}`);
|
|
1660
1669
|
if (missing.length > 0) {
|
|
@@ -1665,13 +1674,7 @@ function runDoctor(_args) {
|
|
|
1665
1674
|
console.log("assets: required project-governance assets found");
|
|
1666
1675
|
}
|
|
1667
1676
|
console.log(checkDocGov());
|
|
1668
|
-
|
|
1669
|
-
console.error(issue);
|
|
1670
|
-
}
|
|
1671
|
-
if (strictHooks && hookIssues.length === 0) {
|
|
1672
|
-
console.log("host-hooks: PGS Compound Gate hooks wired");
|
|
1673
|
-
}
|
|
1674
|
-
return missing.length > 0 || hookIssues.length > 0 ? 1 : 0;
|
|
1677
|
+
return missing.length > 0 ? 1 : 0;
|
|
1675
1678
|
}
|
|
1676
1679
|
function checkDocGov() {
|
|
1677
1680
|
const fromPath = spawnSync2("doc-gov", ["--help"], {
|
|
@@ -1704,424 +1707,10 @@ function resolveDocGovDependencyCli() {
|
|
|
1704
1707
|
return null;
|
|
1705
1708
|
}
|
|
1706
1709
|
}
|
|
1707
|
-
function checkStrictHostHooks(root) {
|
|
1708
|
-
if (!isEngineeringRuntimeProject(root)) {
|
|
1709
|
-
return [];
|
|
1710
|
-
}
|
|
1711
|
-
const expected = [
|
|
1712
|
-
{ path: ".codex/hooks.json", host: "codex" },
|
|
1713
|
-
{ path: ".claude/settings.json", host: "claude-code" },
|
|
1714
|
-
{ path: ".agents/hooks.json", host: "antigravity" }
|
|
1715
|
-
];
|
|
1716
|
-
const issues = [];
|
|
1717
|
-
for (const entry of expected) {
|
|
1718
|
-
const absolutePath = join12(root, entry.path);
|
|
1719
|
-
if (!existsSync12(absolutePath)) {
|
|
1720
|
-
issues.push(`host-hooks: missing ${entry.path}`);
|
|
1721
|
-
continue;
|
|
1722
|
-
}
|
|
1723
|
-
const content = readFileSync8(absolutePath, "utf8");
|
|
1724
|
-
if (!content.includes("pro-gov host-hook") || !content.includes(`--host ${entry.host}`)) {
|
|
1725
|
-
issues.push(`host-hooks: ${entry.path} does not call pro-gov host-hook for ${entry.host}`);
|
|
1726
|
-
}
|
|
1727
|
-
if (entry.host === "antigravity" && !content.includes("PGS_HOST_HOOK_DEBUG=1")) {
|
|
1728
|
-
issues.push(`host-hooks: ${entry.path} does not enable PGS_HOST_HOOK_DEBUG=1 for Antigravity diagnostics`);
|
|
1729
|
-
}
|
|
1730
|
-
}
|
|
1731
|
-
return issues;
|
|
1732
|
-
}
|
|
1733
|
-
function isEngineeringRuntimeProject(root) {
|
|
1734
|
-
return existsSync12(join12(root, "docs/governance/agents-routing/engineering-runtime-v0.9.md"));
|
|
1735
|
-
}
|
|
1736
|
-
|
|
1737
|
-
// src/commands/host-hook.ts
|
|
1738
|
-
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
1739
|
-
import { existsSync as existsSync13, mkdirSync as mkdirSync4, readFileSync as readFileSync10, writeFileSync as writeFileSync3 } from "node:fs";
|
|
1740
|
-
import { tmpdir as tmpdir2 } from "node:os";
|
|
1741
|
-
import { basename as basename3, join as join13, resolve as resolve3 } from "node:path";
|
|
1742
|
-
|
|
1743
|
-
// src/host-hooks/host-hook-runner.ts
|
|
1744
|
-
import { closeSync, openSync, readFileSync as readFileSync9, readSync, statSync as statSync3 } from "node:fs";
|
|
1745
|
-
var gateMarkerPattern = /Compound Gate:\s*(ran ce-compound|ran fallback capture|skipped)\s*->/i;
|
|
1746
|
-
var completionSignalPatterns = [
|
|
1747
|
-
/\b(done|completed|implemented|fixed|verified|validated|shipped|pushed|committed)\b/i,
|
|
1748
|
-
/\b(tests?|typecheck|build|lint|doctor|pack)\b.*\b(pass|passed|green|succeed|succeeded|ok)\b/i,
|
|
1749
|
-
/\b(changed|updated|modified|created|deleted|refactored)\b.*\b(files?|docs?|tests?|hooks?|configs?)\b/i,
|
|
1750
|
-
/已完成|完成了|修好了|实现了|验证通过|测试通过|已经提交|已经推送|提交并推送|已提交|已推送/
|
|
1751
|
-
];
|
|
1752
|
-
var negativeCompletionSignalPatterns = [
|
|
1753
|
-
/\b(not done|not completed|not implemented|not fixed|did not complete|didn't complete|have not completed|haven't completed)\b/i,
|
|
1754
|
-
/没有完成|未完成|还没完成|没有改动|未改动|没有修改|未修改/
|
|
1755
|
-
];
|
|
1756
|
-
var compoundGateInstruction = [
|
|
1757
|
-
"Before final reporting, pass the PGS Compound Gate.",
|
|
1758
|
-
"If this completed work produced reusable learning, run compound-engineering:ce-compound and report:",
|
|
1759
|
-
"Compound Gate: ran ce-compound -> <path>",
|
|
1760
|
-
"If the Compound Engineering plugin is unavailable in this host, use PGS fallback capture:",
|
|
1761
|
-
'pro-gov learn capture --title "<learning title>" --summary "<reusable lesson>"',
|
|
1762
|
-
"Compound Gate: ran fallback capture -> <path>",
|
|
1763
|
-
"If there is no reusable learning, report:",
|
|
1764
|
-
"Compound Gate: skipped -> <reason>"
|
|
1765
|
-
].join("\n");
|
|
1766
|
-
var liteCompoundGateInstruction = [
|
|
1767
|
-
"Pass the PGS Compound Gate before final reporting.",
|
|
1768
|
-
"Report one line:",
|
|
1769
|
-
"Compound Gate: ran ce-compound -> <path>",
|
|
1770
|
-
"Compound Gate: ran fallback capture -> <path>",
|
|
1771
|
-
"Compound Gate: skipped -> <reason>"
|
|
1772
|
-
].join("\n");
|
|
1773
|
-
var maxTranscriptBytes = 2 * 1024 * 1024;
|
|
1774
|
-
function evaluateHostHook(request) {
|
|
1775
|
-
const mode = request.compoundGateMode ?? "off";
|
|
1776
|
-
if (mode === "off") {
|
|
1777
|
-
return { action: "allow" };
|
|
1778
|
-
}
|
|
1779
|
-
if (request.event !== "Stop" && request.event !== "SubagentStop") {
|
|
1780
|
-
return { action: "allow" };
|
|
1781
|
-
}
|
|
1782
|
-
const input = normalizeStopInput(request.input);
|
|
1783
|
-
if (input.stopHookActive) {
|
|
1784
|
-
return { action: "allow" };
|
|
1785
|
-
}
|
|
1786
|
-
if (!input.lastAssistantMessage) {
|
|
1787
|
-
return { action: "allow" };
|
|
1788
|
-
}
|
|
1789
|
-
if (gateMarkerPattern.test(input.lastAssistantMessage)) {
|
|
1790
|
-
return { action: "allow" };
|
|
1791
|
-
}
|
|
1792
|
-
if (!looksLikeCompletedEngineeringWork(input.lastAssistantMessage)) {
|
|
1793
|
-
return { action: "allow" };
|
|
1794
|
-
}
|
|
1795
|
-
return { action: "continue", reason: compoundGateInstructionForMode(mode) };
|
|
1796
|
-
}
|
|
1797
|
-
function compoundGateInstructionForMode(mode) {
|
|
1798
|
-
return mode === "lite" ? liteCompoundGateInstruction : compoundGateInstruction;
|
|
1799
|
-
}
|
|
1800
|
-
function formatHostHookOutput(host, event, decision) {
|
|
1801
|
-
if (decision.action === "allow") {
|
|
1802
|
-
return {};
|
|
1803
|
-
}
|
|
1804
|
-
if (host === "codex") {
|
|
1805
|
-
if (event === "Stop" || event === "SubagentStop") {
|
|
1806
|
-
return { decision: "block", reason: decision.reason };
|
|
1807
|
-
}
|
|
1808
|
-
if (decision.action === "block") {
|
|
1809
|
-
return {
|
|
1810
|
-
hookSpecificOutput: {
|
|
1811
|
-
hookEventName: event,
|
|
1812
|
-
permissionDecision: "deny",
|
|
1813
|
-
permissionDecisionReason: decision.reason
|
|
1814
|
-
},
|
|
1815
|
-
systemMessage: decision.reason
|
|
1816
|
-
};
|
|
1817
|
-
}
|
|
1818
|
-
return { systemMessage: decision.reason };
|
|
1819
|
-
}
|
|
1820
|
-
if (host === "claude" || host === "claude-code") {
|
|
1821
|
-
if (event === "Stop" || event === "SubagentStop") {
|
|
1822
|
-
return { decision: "block", reason: decision.reason };
|
|
1823
|
-
}
|
|
1824
|
-
if (decision.action === "block") {
|
|
1825
|
-
return {
|
|
1826
|
-
hookSpecificOutput: {
|
|
1827
|
-
hookEventName: event,
|
|
1828
|
-
permissionDecision: "deny",
|
|
1829
|
-
permissionDecisionReason: decision.reason
|
|
1830
|
-
}
|
|
1831
|
-
};
|
|
1832
|
-
}
|
|
1833
|
-
return { systemMessage: decision.reason };
|
|
1834
|
-
}
|
|
1835
|
-
if (host === "antigravity") {
|
|
1836
|
-
if (event === "Stop" || event === "SubagentStop") {
|
|
1837
|
-
return { decision: "continue", reason: decision.reason };
|
|
1838
|
-
}
|
|
1839
|
-
if (decision.action === "block") {
|
|
1840
|
-
return { decision: "deny", reason: decision.reason };
|
|
1841
|
-
}
|
|
1842
|
-
return { decision: "allow", reason: decision.reason };
|
|
1843
|
-
}
|
|
1844
|
-
return {};
|
|
1845
|
-
}
|
|
1846
|
-
function normalizeStopInput(input) {
|
|
1847
|
-
if (!isRecord(input)) {
|
|
1848
|
-
return { lastAssistantMessage: void 0, stopHookActive: false };
|
|
1849
|
-
}
|
|
1850
|
-
return {
|
|
1851
|
-
lastAssistantMessage: findString(input, ["last_assistant_message", "lastAssistantMessage", "message", "text"]) ?? findLastAssistantMessageFromTranscript(findString(input, ["agent_transcript_path", "agentTranscriptPath"])) ?? findLastAssistantMessageFromTranscript(findString(input, ["transcript_path", "transcriptPath"])),
|
|
1852
|
-
stopHookActive: findBoolean(input, ["stop_hook_active", "stopHookActive"]) ?? false
|
|
1853
|
-
};
|
|
1854
|
-
}
|
|
1855
|
-
function looksLikeCompletedEngineeringWork(message) {
|
|
1856
|
-
if (negativeCompletionSignalPatterns.some((pattern) => pattern.test(message))) {
|
|
1857
|
-
return false;
|
|
1858
|
-
}
|
|
1859
|
-
return completionSignalPatterns.some((pattern) => pattern.test(message));
|
|
1860
|
-
}
|
|
1861
|
-
function findString(input, keys) {
|
|
1862
|
-
for (const key of keys) {
|
|
1863
|
-
const value = input[key];
|
|
1864
|
-
if (typeof value === "string" && value.trim().length > 0) {
|
|
1865
|
-
return value;
|
|
1866
|
-
}
|
|
1867
|
-
}
|
|
1868
|
-
return void 0;
|
|
1869
|
-
}
|
|
1870
|
-
function findBoolean(input, keys) {
|
|
1871
|
-
for (const key of keys) {
|
|
1872
|
-
const value = input[key];
|
|
1873
|
-
if (typeof value === "boolean") {
|
|
1874
|
-
return value;
|
|
1875
|
-
}
|
|
1876
|
-
}
|
|
1877
|
-
return void 0;
|
|
1878
|
-
}
|
|
1879
|
-
function findLastAssistantMessageFromTranscript(path) {
|
|
1880
|
-
if (!path || path.startsWith("~/")) {
|
|
1881
|
-
return void 0;
|
|
1882
|
-
}
|
|
1883
|
-
let content;
|
|
1884
|
-
try {
|
|
1885
|
-
content = readTranscriptTail(path);
|
|
1886
|
-
} catch {
|
|
1887
|
-
return void 0;
|
|
1888
|
-
}
|
|
1889
|
-
const lines = content.split(/\r?\n/);
|
|
1890
|
-
for (let index = lines.length - 1; index >= 0; index -= 1) {
|
|
1891
|
-
const line = lines[index]?.trim();
|
|
1892
|
-
if (!line) {
|
|
1893
|
-
continue;
|
|
1894
|
-
}
|
|
1895
|
-
let record;
|
|
1896
|
-
try {
|
|
1897
|
-
record = JSON.parse(line);
|
|
1898
|
-
} catch {
|
|
1899
|
-
continue;
|
|
1900
|
-
}
|
|
1901
|
-
const message = extractAssistantText(record);
|
|
1902
|
-
if (message) {
|
|
1903
|
-
return message;
|
|
1904
|
-
}
|
|
1905
|
-
}
|
|
1906
|
-
return void 0;
|
|
1907
|
-
}
|
|
1908
|
-
function readTranscriptTail(path) {
|
|
1909
|
-
const size = statSync3(path).size;
|
|
1910
|
-
if (size <= maxTranscriptBytes) {
|
|
1911
|
-
return readFileSync9(path, "utf8");
|
|
1912
|
-
}
|
|
1913
|
-
const fd = openSync(path, "r");
|
|
1914
|
-
try {
|
|
1915
|
-
const buffer = Buffer.allocUnsafe(maxTranscriptBytes);
|
|
1916
|
-
readSync(fd, buffer, 0, maxTranscriptBytes, size - maxTranscriptBytes);
|
|
1917
|
-
return buffer.toString("utf8");
|
|
1918
|
-
} finally {
|
|
1919
|
-
closeSync(fd);
|
|
1920
|
-
}
|
|
1921
|
-
}
|
|
1922
|
-
function extractAssistantText(value) {
|
|
1923
|
-
if (!isRecord(value)) {
|
|
1924
|
-
return void 0;
|
|
1925
|
-
}
|
|
1926
|
-
const nested = value.message;
|
|
1927
|
-
const role = findString(value, ["role", "type"]);
|
|
1928
|
-
if (isRecord(nested)) {
|
|
1929
|
-
const nestedRole = findString(nested, ["role", "type"]);
|
|
1930
|
-
if (nestedRole === "assistant") {
|
|
1931
|
-
return extractTextContent(nested.content);
|
|
1932
|
-
}
|
|
1933
|
-
}
|
|
1934
|
-
if (role === "assistant") {
|
|
1935
|
-
return extractTextContent(value.content) ?? extractTextContent(value.text);
|
|
1936
|
-
}
|
|
1937
|
-
return void 0;
|
|
1938
|
-
}
|
|
1939
|
-
function extractTextContent(value) {
|
|
1940
|
-
if (typeof value === "string" && value.trim().length > 0) {
|
|
1941
|
-
return value;
|
|
1942
|
-
}
|
|
1943
|
-
if (!Array.isArray(value)) {
|
|
1944
|
-
return void 0;
|
|
1945
|
-
}
|
|
1946
|
-
const parts = [];
|
|
1947
|
-
for (const item of value) {
|
|
1948
|
-
if (typeof item === "string") {
|
|
1949
|
-
parts.push(item);
|
|
1950
|
-
continue;
|
|
1951
|
-
}
|
|
1952
|
-
if (isRecord(item) && typeof item.text === "string") {
|
|
1953
|
-
parts.push(item.text);
|
|
1954
|
-
}
|
|
1955
|
-
}
|
|
1956
|
-
const text = parts.join("\n").trim();
|
|
1957
|
-
return text.length > 0 ? text : void 0;
|
|
1958
|
-
}
|
|
1959
|
-
function isRecord(value) {
|
|
1960
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1961
|
-
}
|
|
1962
|
-
|
|
1963
|
-
// src/host-hooks/types.ts
|
|
1964
|
-
function isHostHookHost(value) {
|
|
1965
|
-
return value === "antigravity" || value === "claude" || value === "claude-code" || value === "codex";
|
|
1966
|
-
}
|
|
1967
|
-
function isHostHookEvent(value) {
|
|
1968
|
-
return value === "PostToolUse" || value === "PreToolUse" || value === "Stop" || value === "SubagentStop" || value === "UserPromptSubmit";
|
|
1969
|
-
}
|
|
1970
|
-
function isCompoundGateMode(value) {
|
|
1971
|
-
return value === "off" || value === "lite" || value === "strict";
|
|
1972
|
-
}
|
|
1973
|
-
|
|
1974
|
-
// src/commands/host-hook.ts
|
|
1975
|
-
var defaultStdinTimeoutMs = 750;
|
|
1976
|
-
var maxDebugRawInputBytes = 256 * 1024;
|
|
1977
|
-
async function runHostHook(args) {
|
|
1978
|
-
const host = readOption(args, "--host");
|
|
1979
|
-
const event = readOption(args, "--event");
|
|
1980
|
-
const compoundGateMode = readCompoundGateMode(args);
|
|
1981
|
-
if (!isHostHookHost(host)) {
|
|
1982
|
-
console.error("Expected --host <codex|claude-code|antigravity>");
|
|
1983
|
-
return 1;
|
|
1984
|
-
}
|
|
1985
|
-
if (!isHostHookEvent(event)) {
|
|
1986
|
-
console.error("Expected --event <Stop|SubagentStop|PreToolUse|PostToolUse|UserPromptSubmit>");
|
|
1987
|
-
return 1;
|
|
1988
|
-
}
|
|
1989
|
-
const rawInput = await readStdinText(defaultStdinTimeoutMs);
|
|
1990
|
-
const input = parseStdinJson(rawInput);
|
|
1991
|
-
const decision = evaluateHostHook({ compoundGateMode, host, event, input });
|
|
1992
|
-
const output = formatHostHookOutput(host, event, decision);
|
|
1993
|
-
writeDebugLogIfRequested(args, {
|
|
1994
|
-
compoundGateMode,
|
|
1995
|
-
decision,
|
|
1996
|
-
event,
|
|
1997
|
-
host,
|
|
1998
|
-
input,
|
|
1999
|
-
output,
|
|
2000
|
-
rawInput
|
|
2001
|
-
});
|
|
2002
|
-
console.log(`${JSON.stringify(output)}
|
|
2003
|
-
`);
|
|
2004
|
-
return 0;
|
|
2005
|
-
}
|
|
2006
|
-
function readCompoundGateMode(args) {
|
|
2007
|
-
const value = readOption(args, "--compound-gate-mode") ?? process.env.PGS_COMPOUND_GATE_MODE ?? "off";
|
|
2008
|
-
if (isCompoundGateMode(value)) return value;
|
|
2009
|
-
return "off";
|
|
2010
|
-
}
|
|
2011
|
-
function readOption(args, name) {
|
|
2012
|
-
const index = args.indexOf(name);
|
|
2013
|
-
if (index < 0) return void 0;
|
|
2014
|
-
return args[index + 1];
|
|
2015
|
-
}
|
|
2016
|
-
function parseStdinJson(raw) {
|
|
2017
|
-
if (!raw) return {};
|
|
2018
|
-
try {
|
|
2019
|
-
return JSON.parse(raw);
|
|
2020
|
-
} catch {
|
|
2021
|
-
return {};
|
|
2022
|
-
}
|
|
2023
|
-
}
|
|
2024
|
-
function readStdinText(timeoutMs) {
|
|
2025
|
-
if (process.stdin.isTTY) {
|
|
2026
|
-
return Promise.resolve("");
|
|
2027
|
-
}
|
|
2028
|
-
process.stdin.setEncoding("utf8");
|
|
2029
|
-
return new Promise((resolveText) => {
|
|
2030
|
-
let settled = false;
|
|
2031
|
-
let content = "";
|
|
2032
|
-
const settle = () => {
|
|
2033
|
-
if (settled) return;
|
|
2034
|
-
settled = true;
|
|
2035
|
-
clearTimeout(timer);
|
|
2036
|
-
process.stdin.off("data", onData);
|
|
2037
|
-
process.stdin.off("end", settle);
|
|
2038
|
-
process.stdin.off("error", settle);
|
|
2039
|
-
process.stdin.pause();
|
|
2040
|
-
resolveText(content.trim());
|
|
2041
|
-
};
|
|
2042
|
-
const onData = (chunk) => {
|
|
2043
|
-
content += chunk.toString();
|
|
2044
|
-
};
|
|
2045
|
-
const timer = setTimeout(settle, timeoutMs);
|
|
2046
|
-
timer.unref();
|
|
2047
|
-
process.stdin.on("data", onData);
|
|
2048
|
-
process.stdin.on("end", settle);
|
|
2049
|
-
process.stdin.on("error", settle);
|
|
2050
|
-
process.stdin.resume();
|
|
2051
|
-
});
|
|
2052
|
-
}
|
|
2053
|
-
function writeDebugLogIfRequested(args, record) {
|
|
2054
|
-
const explicitPath = readOption(args, "--debug-log");
|
|
2055
|
-
const enabled = explicitPath !== void 0 || process.env.PGS_HOST_HOOK_DEBUG === "1";
|
|
2056
|
-
if (!enabled) return;
|
|
2057
|
-
const debugDir = explicitPath && explicitPath.trim().length > 0 ? explicitPath : defaultDebugDir();
|
|
2058
|
-
try {
|
|
2059
|
-
mkdirSync4(debugDir, { recursive: true });
|
|
2060
|
-
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
2061
|
-
const fileName = `${timestamp}-${process.pid}-${record.host}-${record.event}.json`;
|
|
2062
|
-
writeFileSync3(
|
|
2063
|
-
join13(debugDir, fileName),
|
|
2064
|
-
`${JSON.stringify(
|
|
2065
|
-
{
|
|
2066
|
-
schemaVersion: 1,
|
|
2067
|
-
cwd: process.cwd(),
|
|
2068
|
-
compoundGateMode: record.compoundGateMode,
|
|
2069
|
-
event: record.event,
|
|
2070
|
-
host: record.host,
|
|
2071
|
-
nodeVersion: process.version,
|
|
2072
|
-
packageVersion: readPackageVersion(),
|
|
2073
|
-
rawInput: truncateDebugRawInput(record.rawInput),
|
|
2074
|
-
input: record.input,
|
|
2075
|
-
decision: record.decision,
|
|
2076
|
-
output: record.output
|
|
2077
|
-
},
|
|
2078
|
-
null,
|
|
2079
|
-
2
|
|
2080
|
-
)}
|
|
2081
|
-
`,
|
|
2082
|
-
"utf8"
|
|
2083
|
-
);
|
|
2084
|
-
} catch {
|
|
2085
|
-
}
|
|
2086
|
-
}
|
|
2087
|
-
function defaultDebugDir() {
|
|
2088
|
-
const gitPath = spawnSync3("git", ["rev-parse", "--git-path", "pro-gov-hook-debug"], {
|
|
2089
|
-
encoding: "utf8",
|
|
2090
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
2091
|
-
});
|
|
2092
|
-
const value = gitPath.status === 0 ? gitPath.stdout.trim() : "";
|
|
2093
|
-
if (value) {
|
|
2094
|
-
return resolve3(process.cwd(), value);
|
|
2095
|
-
}
|
|
2096
|
-
return join13(tmpdir2(), "pro-gov-hook-debug", basename3(process.cwd()));
|
|
2097
|
-
}
|
|
2098
|
-
function readPackageVersion() {
|
|
2099
|
-
const packageJsonPath = resolvePackageJsonPath();
|
|
2100
|
-
if (!packageJsonPath) return void 0;
|
|
2101
|
-
try {
|
|
2102
|
-
const parsed = JSON.parse(readFileSync10(packageJsonPath, "utf8"));
|
|
2103
|
-
return typeof parsed.version === "string" ? parsed.version : void 0;
|
|
2104
|
-
} catch {
|
|
2105
|
-
return void 0;
|
|
2106
|
-
}
|
|
2107
|
-
}
|
|
2108
|
-
function resolvePackageJsonPath() {
|
|
2109
|
-
const candidates = [
|
|
2110
|
-
resolve3(process.cwd(), "packages/pro-gov/package.json"),
|
|
2111
|
-
resolve3(process.cwd(), "node_modules/@pieai/pro-gov/package.json")
|
|
2112
|
-
];
|
|
2113
|
-
return candidates.find((candidate) => existsSync13(candidate));
|
|
2114
|
-
}
|
|
2115
|
-
function truncateDebugRawInput(value) {
|
|
2116
|
-
const buffer = Buffer.from(value);
|
|
2117
|
-
if (buffer.byteLength <= maxDebugRawInputBytes) return value;
|
|
2118
|
-
return `${buffer.subarray(0, maxDebugRawInputBytes).toString("utf8")}
|
|
2119
|
-
[truncated]`;
|
|
2120
|
-
}
|
|
2121
1710
|
|
|
2122
1711
|
// src/commands/init.ts
|
|
2123
|
-
import { existsSync as
|
|
2124
|
-
import { basename as
|
|
1712
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync4, readFileSync as readFileSync8, writeFileSync as writeFileSync3 } from "node:fs";
|
|
1713
|
+
import { basename as basename3, dirname as dirname7, join as join13 } from "node:path";
|
|
2125
1714
|
|
|
2126
1715
|
// src/commands/shared.ts
|
|
2127
1716
|
function planStarterFiles(profile) {
|
|
@@ -2129,7 +1718,6 @@ function planStarterFiles(profile) {
|
|
|
2129
1718
|
const targetPath = starterTargetPath(asset.path);
|
|
2130
1719
|
if (!targetPath) return [];
|
|
2131
1720
|
if (profile && isOtherProfileRouting(targetPath, profile)) return [];
|
|
2132
|
-
if (profile && isEngineeringOnlyGuardrail(targetPath) && profile !== "engineering-runtime") return [];
|
|
2133
1721
|
return [
|
|
2134
1722
|
{
|
|
2135
1723
|
sourcePath: asset.path,
|
|
@@ -2152,12 +1740,6 @@ function classifyOwnership(targetPath) {
|
|
|
2152
1740
|
function isOtherProfileRouting(targetPath, profile) {
|
|
2153
1741
|
return targetPath.startsWith("docs/governance/agents-routing/") && targetPath !== `docs/governance/agents-routing/${profile}-v0.9.md`;
|
|
2154
1742
|
}
|
|
2155
|
-
function isHostHookTargetPath(targetPath) {
|
|
2156
|
-
return targetPath === ".codex/hooks.json" || targetPath === ".claude/settings.json" || targetPath === ".agents/hooks.json";
|
|
2157
|
-
}
|
|
2158
|
-
function isEngineeringOnlyGuardrail(targetPath) {
|
|
2159
|
-
return isHostHookTargetPath(targetPath);
|
|
2160
|
-
}
|
|
2161
1743
|
function starterTargetPath(sourcePath) {
|
|
2162
1744
|
if (sourcePath === "starter/AGENTS.template.md") return "AGENTS.md";
|
|
2163
1745
|
if (sourcePath === "starter/CLAUDE.template.md") return "CLAUDE.md";
|
|
@@ -2196,7 +1778,7 @@ function runInit(args) {
|
|
|
2196
1778
|
}
|
|
2197
1779
|
function applyStarterFiles(files, profile) {
|
|
2198
1780
|
const root = process.cwd();
|
|
2199
|
-
const conflicts = files.filter((file) =>
|
|
1781
|
+
const conflicts = files.filter((file) => existsSync13(join13(root, file.targetPath)));
|
|
2200
1782
|
if (conflicts.length > 0) {
|
|
2201
1783
|
console.error("pro-gov init is refusing to overwrite existing project files:");
|
|
2202
1784
|
for (const file of conflicts) console.error(` ${file.targetPath}`);
|
|
@@ -2204,11 +1786,11 @@ function applyStarterFiles(files, profile) {
|
|
|
2204
1786
|
return 1;
|
|
2205
1787
|
}
|
|
2206
1788
|
for (const file of files) {
|
|
2207
|
-
const targetPath =
|
|
2208
|
-
|
|
2209
|
-
const source =
|
|
2210
|
-
const content = file.targetPath === "AGENTS.md" ? renderAgentsTemplate(source.toString("utf8"),
|
|
2211
|
-
|
|
1789
|
+
const targetPath = join13(root, file.targetPath);
|
|
1790
|
+
mkdirSync4(dirname7(targetPath), { recursive: true });
|
|
1791
|
+
const source = readFileSync8(file.absoluteSourcePath);
|
|
1792
|
+
const content = file.targetPath === "AGENTS.md" ? renderAgentsTemplate(source.toString("utf8"), basename3(root), profile) : source;
|
|
1793
|
+
writeFileSync3(targetPath, content);
|
|
2212
1794
|
}
|
|
2213
1795
|
console.log("pro-gov init APPLIED");
|
|
2214
1796
|
console.log(`profile: ${profile}`);
|
|
@@ -2236,8 +1818,8 @@ function readFlag(args, flag) {
|
|
|
2236
1818
|
}
|
|
2237
1819
|
|
|
2238
1820
|
// src/learning/recall.ts
|
|
2239
|
-
import { existsSync as
|
|
2240
|
-
import { basename as
|
|
1821
|
+
import { existsSync as existsSync14, readdirSync as readdirSync6, readFileSync as readFileSync9 } from "node:fs";
|
|
1822
|
+
import { basename as basename4, join as join14, relative as relative5 } from "node:path";
|
|
2241
1823
|
function recallLearnings(root, options) {
|
|
2242
1824
|
const query = options.query.trim();
|
|
2243
1825
|
const terms = tokenize(query);
|
|
@@ -2259,14 +1841,14 @@ function recallLearnings(root, options) {
|
|
|
2259
1841
|
}
|
|
2260
1842
|
function loadLearningRecords(root) {
|
|
2261
1843
|
const records = [];
|
|
2262
|
-
const solutionsDir =
|
|
2263
|
-
if (
|
|
1844
|
+
const solutionsDir = join14(root, "docs/solutions");
|
|
1845
|
+
if (existsSync14(solutionsDir)) {
|
|
2264
1846
|
for (const path of listMarkdownFiles(solutionsDir)) {
|
|
2265
1847
|
records.push(readLearningRecord(root, path));
|
|
2266
1848
|
}
|
|
2267
1849
|
}
|
|
2268
|
-
const conceptsPath =
|
|
2269
|
-
if (
|
|
1850
|
+
const conceptsPath = join14(root, "CONCEPTS.md");
|
|
1851
|
+
if (existsSync14(conceptsPath)) {
|
|
2270
1852
|
records.push(readLearningRecord(root, conceptsPath));
|
|
2271
1853
|
}
|
|
2272
1854
|
return records;
|
|
@@ -2274,7 +1856,7 @@ function loadLearningRecords(root) {
|
|
|
2274
1856
|
function listMarkdownFiles(dir) {
|
|
2275
1857
|
const files = [];
|
|
2276
1858
|
for (const entry of readdirSync6(dir, { withFileTypes: true })) {
|
|
2277
|
-
const absolutePath =
|
|
1859
|
+
const absolutePath = join14(dir, entry.name);
|
|
2278
1860
|
if (entry.isDirectory()) {
|
|
2279
1861
|
files.push(...listMarkdownFiles(absolutePath));
|
|
2280
1862
|
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
@@ -2284,7 +1866,7 @@ function listMarkdownFiles(dir) {
|
|
|
2284
1866
|
return files.sort();
|
|
2285
1867
|
}
|
|
2286
1868
|
function readLearningRecord(root, absolutePath) {
|
|
2287
|
-
const content =
|
|
1869
|
+
const content = readFileSync9(absolutePath, "utf8");
|
|
2288
1870
|
const parsed = splitFrontmatter(content);
|
|
2289
1871
|
const body = parsed.body;
|
|
2290
1872
|
return {
|
|
@@ -2316,7 +1898,7 @@ function findTitle(frontmatter, body) {
|
|
|
2316
1898
|
return heading ? heading.slice(2).trim() : void 0;
|
|
2317
1899
|
}
|
|
2318
1900
|
function titleFromPath(path) {
|
|
2319
|
-
return
|
|
1901
|
+
return basename4(path, ".md").split(/[-_]/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
|
|
2320
1902
|
}
|
|
2321
1903
|
function scoreRecord(record, terms) {
|
|
2322
1904
|
const title = record.title.toLowerCase();
|
|
@@ -2367,92 +1949,55 @@ function cleanMarkdownLine(input) {
|
|
|
2367
1949
|
}
|
|
2368
1950
|
|
|
2369
1951
|
// src/learning/capture.ts
|
|
2370
|
-
import { existsSync as
|
|
2371
|
-
import { join as
|
|
2372
|
-
function
|
|
1952
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync5, writeFileSync as writeFileSync4 } from "node:fs";
|
|
1953
|
+
import { join as join15, relative as relative6 } from "node:path";
|
|
1954
|
+
function captureLearning(root, options) {
|
|
2373
1955
|
const title = options.title.trim();
|
|
2374
1956
|
const summary = options.summary.trim();
|
|
2375
1957
|
if (!title) throw new Error("title is required");
|
|
2376
1958
|
if (!summary) throw new Error("summary is required");
|
|
2377
1959
|
const category = slugify(options.category ?? "workflow-issues") || "workflow-issues";
|
|
2378
|
-
const moduleName = options.module?.trim() || "PGS
|
|
2379
|
-
const dir =
|
|
2380
|
-
|
|
2381
|
-
const
|
|
2382
|
-
|
|
2383
|
-
const content = renderFallbackLearning({
|
|
2384
|
-
title,
|
|
2385
|
-
summary,
|
|
2386
|
-
category,
|
|
2387
|
-
moduleName
|
|
2388
|
-
});
|
|
2389
|
-
writeFileSync5(path, content);
|
|
1960
|
+
const moduleName = options.module?.trim() || "PGS learning capture";
|
|
1961
|
+
const dir = join15(root, "docs/solutions", category);
|
|
1962
|
+
mkdirSync5(dir, { recursive: true });
|
|
1963
|
+
const path = uniquePath(dir, slugify(title) || "learning");
|
|
1964
|
+
writeFileSync4(path, renderLearning({ title, summary, category, moduleName }));
|
|
2390
1965
|
return {
|
|
2391
1966
|
relativePath: normalizePath2(relative6(root, path)),
|
|
2392
1967
|
title,
|
|
2393
|
-
captureMode: "pgs-
|
|
1968
|
+
captureMode: "pgs-native"
|
|
2394
1969
|
};
|
|
2395
1970
|
}
|
|
2396
|
-
function
|
|
2397
|
-
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
2398
|
-
const problemType = options.category === "workflow-issues" ? "workflow_issue" : "knowledge";
|
|
1971
|
+
function renderLearning(options) {
|
|
2399
1972
|
return [
|
|
2400
1973
|
"---",
|
|
2401
1974
|
`title: ${yamlString(options.title)}`,
|
|
2402
|
-
`date: ${
|
|
1975
|
+
`date: ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}`,
|
|
2403
1976
|
`category: ${options.category}`,
|
|
2404
1977
|
`module: ${yamlString(options.moduleName)}`,
|
|
2405
|
-
|
|
2406
|
-
"
|
|
2407
|
-
"severity: medium",
|
|
2408
|
-
"capture_mode: pgs-fallback",
|
|
2409
|
-
"applies_when:",
|
|
2410
|
-
' - "The Compound Engineering plugin or ce-compound skill is unavailable in the current host"',
|
|
2411
|
-
' - "Completed work produced reusable learning that should be recallable later"',
|
|
2412
|
-
"tags: [compound-gate, fallback-capture, learning-recall]",
|
|
1978
|
+
"capture_mode: pgs-native",
|
|
1979
|
+
"tags: [learning-recall]",
|
|
2413
1980
|
"---",
|
|
2414
1981
|
"",
|
|
2415
1982
|
`# ${options.title}`,
|
|
2416
1983
|
"",
|
|
2417
|
-
"## Context",
|
|
2418
|
-
"",
|
|
2419
|
-
"This record was captured with the PGS fallback capture path because the full",
|
|
2420
|
-
"Compound Engineering `ce-compound` workflow was unavailable in the current",
|
|
2421
|
-
"host. Prefer `ce-compound` when the plugin is installed; use this fallback",
|
|
2422
|
-
"only to avoid losing reusable learning.",
|
|
2423
|
-
"",
|
|
2424
1984
|
"## Guidance",
|
|
2425
1985
|
"",
|
|
2426
1986
|
options.summary,
|
|
2427
1987
|
"",
|
|
2428
|
-
"##
|
|
2429
|
-
"",
|
|
2430
|
-
"A Compound Gate that cannot write a learning record turns reusable experience",
|
|
2431
|
-
"into a final-report sentence that future agents cannot reliably find. The",
|
|
2432
|
-
"fallback keeps the learning searchable by `pro-gov learn recall` without",
|
|
2433
|
-
"copying or replacing the Compound Engineering workflow.",
|
|
2434
|
-
"",
|
|
2435
|
-
"## When to Apply",
|
|
1988
|
+
"## Applies When",
|
|
2436
1989
|
"",
|
|
2437
1990
|
"- The work is complete and verified.",
|
|
2438
|
-
"- The lesson is reusable
|
|
2439
|
-
"- `compound-engineering:ce-compound` is not available in the current host.",
|
|
2440
|
-
"- The learning is not already covered by an existing `docs/solutions/**` record.",
|
|
2441
|
-
"",
|
|
2442
|
-
"## Examples",
|
|
2443
|
-
"",
|
|
2444
|
-
"```text",
|
|
2445
|
-
"Compound Gate: ran fallback capture -> docs/solutions/<category>/<file>.md",
|
|
2446
|
-
"```",
|
|
1991
|
+
"- The lesson is non-obvious, reusable, and not already documented.",
|
|
2447
1992
|
""
|
|
2448
1993
|
].join("\n");
|
|
2449
1994
|
}
|
|
2450
1995
|
function uniquePath(dir, slug) {
|
|
2451
1996
|
let index = 1;
|
|
2452
|
-
let candidate =
|
|
2453
|
-
while (
|
|
1997
|
+
let candidate = join15(dir, `${slug}.md`);
|
|
1998
|
+
while (existsSync15(candidate)) {
|
|
2454
1999
|
index += 1;
|
|
2455
|
-
candidate =
|
|
2000
|
+
candidate = join15(dir, `${slug}-${index}.md`);
|
|
2456
2001
|
}
|
|
2457
2002
|
return candidate;
|
|
2458
2003
|
}
|
|
@@ -2508,7 +2053,7 @@ function runLearnCapture(args) {
|
|
|
2508
2053
|
}
|
|
2509
2054
|
let result;
|
|
2510
2055
|
try {
|
|
2511
|
-
result =
|
|
2056
|
+
result = captureLearning(options.value.targetDir, {
|
|
2512
2057
|
title: options.value.title,
|
|
2513
2058
|
summary: options.value.summary,
|
|
2514
2059
|
category: options.value.category,
|
|
@@ -2629,20 +2174,18 @@ function printUsage2() {
|
|
|
2629
2174
|
}
|
|
2630
2175
|
|
|
2631
2176
|
// src/commands/lens.ts
|
|
2632
|
-
import { mkdirSync as
|
|
2177
|
+
import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync6 } from "node:fs";
|
|
2633
2178
|
import { dirname as dirname9 } from "node:path";
|
|
2634
2179
|
|
|
2635
2180
|
// src/lens/audit.ts
|
|
2636
|
-
import { existsSync as
|
|
2637
|
-
import { basename as
|
|
2181
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync6, readFileSync as readFileSync10, writeFileSync as writeFileSync5 } from "node:fs";
|
|
2182
|
+
import { basename as basename5, dirname as dirname8, join as join16 } from "node:path";
|
|
2638
2183
|
var REQUIRED_ARTIFACTS = [
|
|
2639
2184
|
"manifest.md",
|
|
2640
2185
|
"raw/project-lens/architecture-lens.md",
|
|
2641
2186
|
"raw/project-lens/truth-surface-audit.md",
|
|
2642
2187
|
"raw/project-lens/technology-strategy.md",
|
|
2643
|
-
"raw/
|
|
2644
|
-
"raw/ponytail/ponytail-debt.md",
|
|
2645
|
-
"raw/ponytail/ponytail-gain.md",
|
|
2188
|
+
"raw/project-lens/simplicity-review.md",
|
|
2646
2189
|
"raw/target/target-state.md",
|
|
2647
2190
|
"raw/target/commands.md",
|
|
2648
2191
|
"raw/target/sources.md",
|
|
@@ -2654,20 +2197,20 @@ function createProjectLensAuditPackage(targetDir, auditDir) {
|
|
|
2654
2197
|
version: 1,
|
|
2655
2198
|
target: {
|
|
2656
2199
|
path: targetDir,
|
|
2657
|
-
name:
|
|
2200
|
+
name: basename5(targetDir) || "target"
|
|
2658
2201
|
},
|
|
2659
2202
|
requiredArtifacts: [...REQUIRED_ARTIFACTS]
|
|
2660
2203
|
};
|
|
2661
|
-
|
|
2662
|
-
writeJson(
|
|
2204
|
+
mkdirSync6(auditDir, { recursive: true });
|
|
2205
|
+
writeJson(join16(auditDir, "audit.contract.json"), contract);
|
|
2663
2206
|
for (const artifactPath of REQUIRED_ARTIFACTS) {
|
|
2664
|
-
writeTemplate(
|
|
2207
|
+
writeTemplate(join16(auditDir, artifactPath), renderArtifactTemplate(artifactPath, contract));
|
|
2665
2208
|
}
|
|
2666
2209
|
return contract;
|
|
2667
2210
|
}
|
|
2668
2211
|
function checkProjectLensAuditPackage(auditDir, options = {}) {
|
|
2669
|
-
const contractPath =
|
|
2670
|
-
if (!
|
|
2212
|
+
const contractPath = join16(auditDir, "audit.contract.json");
|
|
2213
|
+
if (!existsSync16(contractPath)) {
|
|
2671
2214
|
return {
|
|
2672
2215
|
ok: false,
|
|
2673
2216
|
auditDir,
|
|
@@ -2681,7 +2224,7 @@ function checkProjectLensAuditPackage(auditDir, options = {}) {
|
|
|
2681
2224
|
}
|
|
2682
2225
|
let contract;
|
|
2683
2226
|
try {
|
|
2684
|
-
contract = JSON.parse(
|
|
2227
|
+
contract = JSON.parse(readFileSync10(contractPath, "utf8"));
|
|
2685
2228
|
} catch (error) {
|
|
2686
2229
|
return {
|
|
2687
2230
|
ok: false,
|
|
@@ -2714,12 +2257,12 @@ function checkProjectLensAuditPackage(auditDir, options = {}) {
|
|
|
2714
2257
|
}
|
|
2715
2258
|
}
|
|
2716
2259
|
for (const artifactPath of REQUIRED_ARTIFACTS) {
|
|
2717
|
-
const absolutePath =
|
|
2718
|
-
if (!
|
|
2260
|
+
const absolutePath = join16(auditDir, artifactPath);
|
|
2261
|
+
if (!existsSync16(absolutePath)) {
|
|
2719
2262
|
issues.push({ type: "missing-required-artifact", path: artifactPath });
|
|
2720
2263
|
continue;
|
|
2721
2264
|
}
|
|
2722
|
-
const content =
|
|
2265
|
+
const content = readFileSync10(absolutePath, "utf8");
|
|
2723
2266
|
if (isPendingArtifact(content)) {
|
|
2724
2267
|
issues.push({ type: "artifact-not-complete", path: artifactPath });
|
|
2725
2268
|
} else if (hasTemplateBody(content)) {
|
|
@@ -2735,13 +2278,13 @@ function checkProjectLensAuditPackage(auditDir, options = {}) {
|
|
|
2735
2278
|
};
|
|
2736
2279
|
}
|
|
2737
2280
|
function writeJson(path, value) {
|
|
2738
|
-
|
|
2739
|
-
|
|
2281
|
+
mkdirSync6(dirname8(path), { recursive: true });
|
|
2282
|
+
writeFileSync5(path, `${JSON.stringify(value, null, 2)}
|
|
2740
2283
|
`);
|
|
2741
2284
|
}
|
|
2742
2285
|
function writeTemplate(path, content) {
|
|
2743
|
-
|
|
2744
|
-
|
|
2286
|
+
mkdirSync6(dirname8(path), { recursive: true });
|
|
2287
|
+
writeFileSync5(path, content);
|
|
2745
2288
|
}
|
|
2746
2289
|
function renderArtifactTemplate(artifactPath, contract) {
|
|
2747
2290
|
const title = artifactPath.replace(/\.md$/, "").split("/").map((part) => part.replaceAll("-", " ")).join(" / ");
|
|
@@ -2797,7 +2340,6 @@ function renderArtifactGuardrailHint(artifactPath) {
|
|
|
2797
2340
|
}
|
|
2798
2341
|
function artifactProducer(artifactPath) {
|
|
2799
2342
|
if (artifactPath.startsWith("raw/project-lens/")) return "project-lens";
|
|
2800
|
-
if (artifactPath.startsWith("raw/ponytail/")) return "ponytail";
|
|
2801
2343
|
if (artifactPath.startsWith("raw/target/")) return "target-evidence";
|
|
2802
2344
|
if (artifactPath.startsWith("synthesis/")) return "synthesis";
|
|
2803
2345
|
return "audit";
|
|
@@ -2831,8 +2373,8 @@ var REQUIRED_METHOD_RECORDS = {
|
|
|
2831
2373
|
match: /^Project Lens method source:/im
|
|
2832
2374
|
},
|
|
2833
2375
|
{
|
|
2834
|
-
label: "
|
|
2835
|
-
match: /^
|
|
2376
|
+
label: "Simplicity review method source:",
|
|
2377
|
+
match: /^Simplicity review method source:/im
|
|
2836
2378
|
}
|
|
2837
2379
|
],
|
|
2838
2380
|
"synthesis/decision-index.md": [
|
|
@@ -2910,6 +2452,10 @@ function formatProjectLensAuditCheckText(result) {
|
|
|
2910
2452
|
function formatProjectLensInspection(report) {
|
|
2911
2453
|
return [
|
|
2912
2454
|
`target: ${report.targetDir}`,
|
|
2455
|
+
`scan-scope: ${report.scanScope.mode}`,
|
|
2456
|
+
`candidate-files: ${report.scanScope.candidateFileCount}`,
|
|
2457
|
+
`included-files: ${report.scanScope.includedFileCount}`,
|
|
2458
|
+
`excluded-files: ${report.scanScope.excludedFileCount}`,
|
|
2913
2459
|
`ai-entry-files: ${formatList(report.aiEntryFiles)}`,
|
|
2914
2460
|
`ai-config-files: ${formatList(report.aiConfigFiles)}`,
|
|
2915
2461
|
`package-scripts: ${formatList(report.packageJson?.scripts ?? [])}`,
|
|
@@ -2931,6 +2477,10 @@ function renderProjectLensMarkdownReport(report) {
|
|
|
2931
2477
|
`- Target: \`${report.targetDir}\``,
|
|
2932
2478
|
`- Generated: ${(/* @__PURE__ */ new Date()).toISOString()}`,
|
|
2933
2479
|
"- Scope: local read-only evidence for AI-assisted project review",
|
|
2480
|
+
`- Evidence mode: ${report.scanScope.mode}`,
|
|
2481
|
+
`- Candidate files: ${report.scanScope.candidateFileCount}`,
|
|
2482
|
+
`- Included files: ${report.scanScope.includedFileCount}`,
|
|
2483
|
+
`- Excluded vendor/cache files: ${report.scanScope.excludedFileCount}`,
|
|
2934
2484
|
"",
|
|
2935
2485
|
"## AI Entry Files",
|
|
2936
2486
|
"",
|
|
@@ -2978,9 +2528,9 @@ function bulletList(values) {
|
|
|
2978
2528
|
}
|
|
2979
2529
|
|
|
2980
2530
|
// src/lens/scan.ts
|
|
2981
|
-
import { spawnSync as
|
|
2982
|
-
import { existsSync as
|
|
2983
|
-
import { join as
|
|
2531
|
+
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
2532
|
+
import { existsSync as existsSync17, readdirSync as readdirSync7, readFileSync as readFileSync11, statSync as statSync3 } from "node:fs";
|
|
2533
|
+
import { join as join17, relative as relative7 } from "node:path";
|
|
2984
2534
|
var ignoredDirectories = /* @__PURE__ */ new Set([
|
|
2985
2535
|
".git",
|
|
2986
2536
|
".next",
|
|
@@ -2991,30 +2541,39 @@ var ignoredDirectories = /* @__PURE__ */ new Set([
|
|
|
2991
2541
|
]);
|
|
2992
2542
|
function scanProjectLensTarget(targetDir, options = {}) {
|
|
2993
2543
|
const largeFileBytes = options.largeFileBytes ?? 5e4;
|
|
2994
|
-
const
|
|
2995
|
-
const
|
|
2544
|
+
const candidateFiles = listProjectFiles(targetDir);
|
|
2545
|
+
const files = candidateFiles.filter(isFirstPartyEvidenceFile);
|
|
2546
|
+
const markdownFiles = files.filter(
|
|
2547
|
+
(file) => file.startsWith("docs/") && file.endsWith(".md")
|
|
2548
|
+
);
|
|
2996
2549
|
const packageJson = readPackageJson(targetDir);
|
|
2997
2550
|
return {
|
|
2998
2551
|
targetDir,
|
|
2552
|
+
scanScope: {
|
|
2553
|
+
mode: "first-party",
|
|
2554
|
+
candidateFileCount: candidateFiles.length,
|
|
2555
|
+
includedFileCount: files.length,
|
|
2556
|
+
excludedFileCount: candidateFiles.length - files.length
|
|
2557
|
+
},
|
|
2999
2558
|
aiEntryFiles: ["AGENTS.md", "CLAUDE.md"].filter(
|
|
3000
|
-
(file) =>
|
|
2559
|
+
(file) => existsSync17(join17(targetDir, file))
|
|
3001
2560
|
),
|
|
3002
2561
|
aiConfigFiles: [],
|
|
3003
2562
|
packageJson,
|
|
3004
2563
|
docs: {
|
|
3005
|
-
hasDocsDirectory:
|
|
2564
|
+
hasDocsDirectory: existsSync17(join17(targetDir, "docs")),
|
|
3006
2565
|
markdownFileCount: markdownFiles.length,
|
|
3007
2566
|
governanceFiles: markdownFiles.filter((file) => file.startsWith("docs/governance/") || file.startsWith("docs/policy/")).sort()
|
|
3008
2567
|
},
|
|
3009
2568
|
git: readGitState(targetDir),
|
|
3010
|
-
largeFiles: files.map((file) => ({ path: file, bytes:
|
|
2569
|
+
largeFiles: files.map((file) => ({ path: file, bytes: statSync3(join17(targetDir, file)).size })).filter((file) => file.bytes >= largeFileBytes).sort((a, b) => b.bytes - a.bytes || a.path.localeCompare(b.path)).slice(0, 25)
|
|
3011
2570
|
};
|
|
3012
2571
|
}
|
|
3013
2572
|
function readPackageJson(targetDir) {
|
|
3014
|
-
const packageJsonPath =
|
|
3015
|
-
if (!
|
|
2573
|
+
const packageJsonPath = join17(targetDir, "package.json");
|
|
2574
|
+
if (!existsSync17(packageJsonPath)) return void 0;
|
|
3016
2575
|
try {
|
|
3017
|
-
const packageJson = JSON.parse(
|
|
2576
|
+
const packageJson = JSON.parse(readFileSync11(packageJsonPath, "utf8"));
|
|
3018
2577
|
return {
|
|
3019
2578
|
scripts: Object.keys(packageJson.scripts ?? {}).sort(),
|
|
3020
2579
|
dependencies: Object.keys(packageJson.dependencies ?? {}).sort(),
|
|
@@ -3037,25 +2596,46 @@ function readGitState(targetDir) {
|
|
|
3037
2596
|
};
|
|
3038
2597
|
}
|
|
3039
2598
|
function runGit(targetDir, args) {
|
|
3040
|
-
const result =
|
|
2599
|
+
const result = spawnSync3("git", ["-C", targetDir, ...args], {
|
|
3041
2600
|
encoding: "utf8"
|
|
3042
2601
|
});
|
|
3043
2602
|
if (result.status !== 0) return { ok: false };
|
|
3044
2603
|
return { ok: true, stdout: result.stdout.trim() };
|
|
3045
2604
|
}
|
|
3046
2605
|
function listProjectFiles(targetDir) {
|
|
2606
|
+
const gitFiles = runGit(targetDir, [
|
|
2607
|
+
"ls-files",
|
|
2608
|
+
"--cached",
|
|
2609
|
+
"--others",
|
|
2610
|
+
"--exclude-standard",
|
|
2611
|
+
"-z"
|
|
2612
|
+
]);
|
|
2613
|
+
if (gitFiles.ok) {
|
|
2614
|
+
return gitFiles.stdout.split("\0").filter(Boolean).map(toUnixPath4).filter((file) => existsSync17(join17(targetDir, file))).sort();
|
|
2615
|
+
}
|
|
3047
2616
|
const files = [];
|
|
3048
2617
|
collectFiles2(targetDir, targetDir, files);
|
|
3049
2618
|
return files.sort();
|
|
3050
2619
|
}
|
|
2620
|
+
var excludedEvidencePrefixes = [
|
|
2621
|
+
".agents/manual-skills/",
|
|
2622
|
+
".agents/skills/",
|
|
2623
|
+
"agent-assets/skills/npx-skills/",
|
|
2624
|
+
"third-party/",
|
|
2625
|
+
"third_party/",
|
|
2626
|
+
"vendor/"
|
|
2627
|
+
];
|
|
2628
|
+
function isFirstPartyEvidenceFile(file) {
|
|
2629
|
+
return !excludedEvidencePrefixes.some((prefix) => file.startsWith(prefix));
|
|
2630
|
+
}
|
|
3051
2631
|
function collectFiles2(rootDir, currentDir, files) {
|
|
3052
|
-
if (!
|
|
2632
|
+
if (!existsSync17(currentDir)) return;
|
|
3053
2633
|
for (const entry of readdirSync7(currentDir, { withFileTypes: true })) {
|
|
3054
2634
|
if (entry.isDirectory()) {
|
|
3055
2635
|
if (ignoredDirectories.has(entry.name)) continue;
|
|
3056
|
-
collectFiles2(rootDir,
|
|
2636
|
+
collectFiles2(rootDir, join17(currentDir, entry.name), files);
|
|
3057
2637
|
} else if (entry.isFile()) {
|
|
3058
|
-
files.push(toUnixPath4(relative7(rootDir,
|
|
2638
|
+
files.push(toUnixPath4(relative7(rootDir, join17(currentDir, entry.name))));
|
|
3059
2639
|
}
|
|
3060
2640
|
}
|
|
3061
2641
|
}
|
|
@@ -3107,8 +2687,8 @@ function runLensReport(args) {
|
|
|
3107
2687
|
}
|
|
3108
2688
|
const report = scanProjectLensTarget(options.value.targetDir);
|
|
3109
2689
|
const markdown = renderProjectLensMarkdownReport(report);
|
|
3110
|
-
|
|
3111
|
-
|
|
2690
|
+
mkdirSync7(dirname9(options.value.outPath), { recursive: true });
|
|
2691
|
+
writeFileSync6(options.value.outPath, markdown);
|
|
3112
2692
|
console.log(`report: ${options.value.outPath}`);
|
|
3113
2693
|
return 0;
|
|
3114
2694
|
}
|
|
@@ -3211,16 +2791,16 @@ function printUsage3() {
|
|
|
3211
2791
|
}
|
|
3212
2792
|
|
|
3213
2793
|
// src/commands/portfolio.ts
|
|
3214
|
-
import { existsSync as
|
|
3215
|
-
import { join as
|
|
2794
|
+
import { existsSync as existsSync21 } from "node:fs";
|
|
2795
|
+
import { join as join20 } from "node:path";
|
|
3216
2796
|
|
|
3217
2797
|
// src/portfolio/manifest.ts
|
|
3218
|
-
import { existsSync as
|
|
3219
|
-
import { dirname as dirname10, isAbsolute as isAbsolute3, resolve as
|
|
2798
|
+
import { existsSync as existsSync18, readFileSync as readFileSync12 } from "node:fs";
|
|
2799
|
+
import { dirname as dirname10, isAbsolute as isAbsolute3, resolve as resolve3 } from "node:path";
|
|
3220
2800
|
function loadPortfolioManifest(configPath) {
|
|
3221
2801
|
let parsed;
|
|
3222
2802
|
try {
|
|
3223
|
-
parsed = JSON.parse(
|
|
2803
|
+
parsed = JSON.parse(readFileSync12(configPath, "utf8"));
|
|
3224
2804
|
} catch (error) {
|
|
3225
2805
|
return {
|
|
3226
2806
|
configPath,
|
|
@@ -3232,7 +2812,7 @@ function loadPortfolioManifest(configPath) {
|
|
|
3232
2812
|
]
|
|
3233
2813
|
};
|
|
3234
2814
|
}
|
|
3235
|
-
const normalized = resolveManifestPaths(parsed, dirname10(
|
|
2815
|
+
const normalized = resolveManifestPaths(parsed, dirname10(resolve3(configPath)));
|
|
3236
2816
|
const issues = validatePortfolioManifest(normalized);
|
|
3237
2817
|
return {
|
|
3238
2818
|
configPath,
|
|
@@ -3241,12 +2821,12 @@ function loadPortfolioManifest(configPath) {
|
|
|
3241
2821
|
};
|
|
3242
2822
|
}
|
|
3243
2823
|
function resolveManifestPaths(value, configDir) {
|
|
3244
|
-
if (!
|
|
2824
|
+
if (!isRecord(value)) return value;
|
|
3245
2825
|
const resolveEndpoint = (endpoint) => {
|
|
3246
|
-
if (!
|
|
2826
|
+
if (!isRecord(endpoint) || typeof endpoint.path !== "string" || isAbsolute3(endpoint.path)) {
|
|
3247
2827
|
return endpoint;
|
|
3248
2828
|
}
|
|
3249
|
-
return { ...endpoint, path:
|
|
2829
|
+
return { ...endpoint, path: resolve3(configDir, endpoint.path) };
|
|
3250
2830
|
};
|
|
3251
2831
|
return {
|
|
3252
2832
|
...value,
|
|
@@ -3257,7 +2837,7 @@ function resolveManifestPaths(value, configDir) {
|
|
|
3257
2837
|
}
|
|
3258
2838
|
function validatePortfolioManifest(value) {
|
|
3259
2839
|
const issues = [];
|
|
3260
|
-
if (!
|
|
2840
|
+
if (!isRecord(value)) {
|
|
3261
2841
|
return [
|
|
3262
2842
|
{
|
|
3263
2843
|
type: "invalid-field",
|
|
@@ -3299,7 +2879,7 @@ function validatePortfolioManifest(value) {
|
|
|
3299
2879
|
}
|
|
3300
2880
|
const seenTargetIds = /* @__PURE__ */ new Set();
|
|
3301
2881
|
for (const target of value.targets) {
|
|
3302
|
-
if (!
|
|
2882
|
+
if (!isRecord(target)) {
|
|
3303
2883
|
issues.push({
|
|
3304
2884
|
type: "invalid-field",
|
|
3305
2885
|
field: "targets",
|
|
@@ -3356,7 +2936,7 @@ function validateAllowedFields(value, location, allowedFields, issues) {
|
|
|
3356
2936
|
}
|
|
3357
2937
|
function validateEndpoint(value, field, issues) {
|
|
3358
2938
|
if (value === void 0) return;
|
|
3359
|
-
if (!
|
|
2939
|
+
if (!isRecord(value)) {
|
|
3360
2940
|
issues.push({
|
|
3361
2941
|
type: "invalid-field",
|
|
3362
2942
|
field,
|
|
@@ -3380,7 +2960,7 @@ function validateEndpoint(value, field, issues) {
|
|
|
3380
2960
|
});
|
|
3381
2961
|
return;
|
|
3382
2962
|
}
|
|
3383
|
-
if (!
|
|
2963
|
+
if (!existsSync18(value.path)) {
|
|
3384
2964
|
issues.push({
|
|
3385
2965
|
type: "missing-path",
|
|
3386
2966
|
id: typeof value.id === "string" ? value.id : void 0,
|
|
@@ -3412,7 +2992,7 @@ function validateHostTooling(value, issues) {
|
|
|
3412
2992
|
}
|
|
3413
2993
|
const seenHosts = /* @__PURE__ */ new Set();
|
|
3414
2994
|
for (const entry of value) {
|
|
3415
|
-
if (!
|
|
2995
|
+
if (!isRecord(entry)) {
|
|
3416
2996
|
issues.push({
|
|
3417
2997
|
type: "invalid-field",
|
|
3418
2998
|
field: "hostTooling",
|
|
@@ -3445,19 +3025,19 @@ function validateHostTooling(value, issues) {
|
|
|
3445
3025
|
}
|
|
3446
3026
|
}
|
|
3447
3027
|
}
|
|
3448
|
-
function
|
|
3028
|
+
function isRecord(value) {
|
|
3449
3029
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3450
3030
|
}
|
|
3451
3031
|
|
|
3452
3032
|
// src/portfolio/doctor.ts
|
|
3453
|
-
import { spawnSync as
|
|
3454
|
-
import { existsSync as
|
|
3033
|
+
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
3034
|
+
import { existsSync as existsSync20, readFileSync as readFileSync14 } from "node:fs";
|
|
3455
3035
|
import { createRequire as createRequire2 } from "node:module";
|
|
3456
|
-
import { dirname as dirname11, join as
|
|
3036
|
+
import { dirname as dirname11, join as join19 } from "node:path";
|
|
3457
3037
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
3458
3038
|
|
|
3459
3039
|
// src/host-tooling/inventory.ts
|
|
3460
|
-
import { spawnSync as
|
|
3040
|
+
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
3461
3041
|
function inspectHostTooling(requirements, runner = defaultRunner2) {
|
|
3462
3042
|
const hosts = [];
|
|
3463
3043
|
const issues = [];
|
|
@@ -3515,10 +3095,10 @@ function inspectHostTooling(requirements, runner = defaultRunner2) {
|
|
|
3515
3095
|
return { hosts, issues };
|
|
3516
3096
|
}
|
|
3517
3097
|
function parseHostPlugins(host, value) {
|
|
3518
|
-
const entries = host === "codex" ?
|
|
3098
|
+
const entries = host === "codex" ? isRecord2(value) && Array.isArray(value.installed) ? value.installed : void 0 : Array.isArray(value) ? value : void 0;
|
|
3519
3099
|
if (!entries) throw new Error("expected a plugin array");
|
|
3520
3100
|
return entries.flatMap((entry) => {
|
|
3521
|
-
if (!
|
|
3101
|
+
if (!isRecord2(entry)) return [];
|
|
3522
3102
|
const id = host === "codex" ? entry.pluginId : entry.id;
|
|
3523
3103
|
if (typeof id !== "string") return [];
|
|
3524
3104
|
return [{
|
|
@@ -3529,7 +3109,7 @@ function parseHostPlugins(host, value) {
|
|
|
3529
3109
|
});
|
|
3530
3110
|
}
|
|
3531
3111
|
function defaultRunner2({ command: command2 }) {
|
|
3532
|
-
const result =
|
|
3112
|
+
const result = spawnSync4(command2[0] ?? "", command2.slice(1), {
|
|
3533
3113
|
encoding: "utf8",
|
|
3534
3114
|
timeout: 1e4
|
|
3535
3115
|
});
|
|
@@ -3539,18 +3119,18 @@ function defaultRunner2({ command: command2 }) {
|
|
|
3539
3119
|
stderr: result.stderr || result.error?.message || ""
|
|
3540
3120
|
};
|
|
3541
3121
|
}
|
|
3542
|
-
function
|
|
3122
|
+
function isRecord2(value) {
|
|
3543
3123
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3544
3124
|
}
|
|
3545
3125
|
|
|
3546
3126
|
// src/portfolio/asset-state.ts
|
|
3547
|
-
import { existsSync as
|
|
3548
|
-
import { join as
|
|
3127
|
+
import { existsSync as existsSync19, lstatSync as lstatSync5, readFileSync as readFileSync13 } from "node:fs";
|
|
3128
|
+
import { join as join18 } from "node:path";
|
|
3549
3129
|
function comparePortfolioAssetState(options) {
|
|
3550
3130
|
const expectedManifest = readPlanDocument(options.expectedPlan, ".pro-gov/assets.json");
|
|
3551
3131
|
const expectedLock = readPlanDocument(options.expectedPlan, ".pro-gov/assets.lock.json");
|
|
3552
|
-
const currentManifest = readJsonFile(
|
|
3553
|
-
const currentLock = readJsonFile(
|
|
3132
|
+
const currentManifest = readJsonFile(join18(options.targetDir, ".pro-gov/assets.json"));
|
|
3133
|
+
const currentLock = readJsonFile(join18(options.targetDir, ".pro-gov/assets.lock.json"));
|
|
3554
3134
|
const issues = [];
|
|
3555
3135
|
if (!sameStrings(currentManifest?.bundleIds, expectedManifest?.bundleIds)) {
|
|
3556
3136
|
issues.push({
|
|
@@ -3573,7 +3153,7 @@ function comparePortfolioAssetState(options) {
|
|
|
3573
3153
|
const expectedTargets = new Set((expectedLock?.assets ?? []).map((entry) => entry.targetPath));
|
|
3574
3154
|
for (const entry of currentLock?.assets ?? []) {
|
|
3575
3155
|
if (expectedTargets.has(entry.targetPath)) continue;
|
|
3576
|
-
const targetAbsolutePath =
|
|
3156
|
+
const targetAbsolutePath = join18(options.targetDir, entry.targetPath);
|
|
3577
3157
|
if (!pathIsSymlink(targetAbsolutePath)) continue;
|
|
3578
3158
|
issues.push({
|
|
3579
3159
|
type: "orphaned-managed-symlink",
|
|
@@ -3595,9 +3175,9 @@ function readPlanDocument(plan, targetPath) {
|
|
|
3595
3175
|
}
|
|
3596
3176
|
}
|
|
3597
3177
|
function readJsonFile(path) {
|
|
3598
|
-
if (!
|
|
3178
|
+
if (!existsSync19(path)) return void 0;
|
|
3599
3179
|
try {
|
|
3600
|
-
return JSON.parse(
|
|
3180
|
+
return JSON.parse(readFileSync13(path, "utf8"));
|
|
3601
3181
|
} catch {
|
|
3602
3182
|
return void 0;
|
|
3603
3183
|
}
|
|
@@ -3646,11 +3226,11 @@ function inspectPortfolio(options) {
|
|
|
3646
3226
|
function inspectTarget(options) {
|
|
3647
3227
|
const { target } = options;
|
|
3648
3228
|
const issues = [];
|
|
3649
|
-
const packageJson = readJson2(
|
|
3229
|
+
const packageJson = readJson2(join19(target.path, "package.json"));
|
|
3650
3230
|
const packages = {};
|
|
3651
3231
|
for (const packageName of ["@pieai/pro-gov", "@pieai/doc-gov"]) {
|
|
3652
3232
|
const declared = packageJson?.devDependencies?.[packageName] ?? packageJson?.dependencies?.[packageName];
|
|
3653
|
-
const installedPackage = readJson2(
|
|
3233
|
+
const installedPackage = readJson2(join19(target.path, "node_modules", packageName, "package.json"));
|
|
3654
3234
|
const installed = installedPackage?.version;
|
|
3655
3235
|
const expected = options.expectedPackageVersions[packageName];
|
|
3656
3236
|
packages[packageName] = { declared, installed, expected };
|
|
@@ -3704,7 +3284,7 @@ function inspectTarget(options) {
|
|
|
3704
3284
|
type: "asset-lock-drift",
|
|
3705
3285
|
message: error instanceof Error ? error.message : String(error)
|
|
3706
3286
|
});
|
|
3707
|
-
if (!
|
|
3287
|
+
if (!existsSync20(join19(target.path, ".pro-gov/assets.json"))) {
|
|
3708
3288
|
issues.push({ type: "bundle-drift", message: "Target asset manifest is missing." });
|
|
3709
3289
|
}
|
|
3710
3290
|
}
|
|
@@ -3719,27 +3299,27 @@ function inspectTarget(options) {
|
|
|
3719
3299
|
};
|
|
3720
3300
|
}
|
|
3721
3301
|
function readTargetAssetHost(targetDir) {
|
|
3722
|
-
const lockfile = readJson2(
|
|
3302
|
+
const lockfile = readJson2(join19(targetDir, ".pro-gov/assets.lock.json"));
|
|
3723
3303
|
return isAssetRegistryHost(lockfile?.host) ? lockfile.host : void 0;
|
|
3724
3304
|
}
|
|
3725
3305
|
function isAssetRegistryHost(value) {
|
|
3726
3306
|
return value === "codex" || value === "claude-code" || value === "gemini-cli" || value === "antigravity";
|
|
3727
3307
|
}
|
|
3728
3308
|
function runTargetChecks(target) {
|
|
3729
|
-
const proGovCli =
|
|
3730
|
-
const docGovCli =
|
|
3309
|
+
const proGovCli = join19(target.path, "node_modules/@pieai/pro-gov/dist/cli.js");
|
|
3310
|
+
const docGovCli = join19(target.path, "node_modules/@pieai/doc-gov/dist/cli.js");
|
|
3731
3311
|
const commands = [
|
|
3732
3312
|
{
|
|
3733
3313
|
name: "pro-gov doctor",
|
|
3734
3314
|
cli: proGovCli,
|
|
3735
|
-
args:
|
|
3315
|
+
args: ["doctor"]
|
|
3736
3316
|
},
|
|
3737
3317
|
{ name: "doc-gov router-check", cli: docGovCli, args: ["router-check"] },
|
|
3738
3318
|
{ name: "doc-gov scan --check", cli: docGovCli, args: ["scan", "--check"] }
|
|
3739
3319
|
];
|
|
3740
3320
|
return commands.map((command2) => {
|
|
3741
|
-
if (!
|
|
3742
|
-
const result =
|
|
3321
|
+
if (!existsSync20(command2.cli)) return { name: command2.name, status: null };
|
|
3322
|
+
const result = spawnSync5(process.execPath, [command2.cli, ...command2.args], {
|
|
3743
3323
|
cwd: target.path,
|
|
3744
3324
|
encoding: "utf8",
|
|
3745
3325
|
timeout: 3e4
|
|
@@ -3748,13 +3328,13 @@ function runTargetChecks(target) {
|
|
|
3748
3328
|
});
|
|
3749
3329
|
}
|
|
3750
3330
|
function inspectGit(path) {
|
|
3751
|
-
const inside =
|
|
3331
|
+
const inside = spawnSync5("git", ["rev-parse", "--is-inside-work-tree"], {
|
|
3752
3332
|
cwd: path,
|
|
3753
3333
|
encoding: "utf8"
|
|
3754
3334
|
});
|
|
3755
3335
|
if (inside.status !== 0) return { isRepository: false, dirty: false };
|
|
3756
|
-
const status =
|
|
3757
|
-
const branch =
|
|
3336
|
+
const status = spawnSync5("git", ["status", "--porcelain"], { cwd: path, encoding: "utf8" });
|
|
3337
|
+
const branch = spawnSync5("git", ["branch", "--show-current"], { cwd: path, encoding: "utf8" });
|
|
3758
3338
|
return {
|
|
3759
3339
|
isRepository: true,
|
|
3760
3340
|
dirty: status.stdout.trim().length > 0,
|
|
@@ -3779,16 +3359,16 @@ function getExpectedPackageVersions() {
|
|
|
3779
3359
|
function findOwnPackageJson() {
|
|
3780
3360
|
let current = dirname11(fileURLToPath3(import.meta.url));
|
|
3781
3361
|
for (let depth = 0; depth < 5; depth += 1) {
|
|
3782
|
-
const candidate =
|
|
3783
|
-
if (
|
|
3362
|
+
const candidate = join19(current, "package.json");
|
|
3363
|
+
if (existsSync20(candidate)) return candidate;
|
|
3784
3364
|
current = dirname11(current);
|
|
3785
3365
|
}
|
|
3786
3366
|
return "";
|
|
3787
3367
|
}
|
|
3788
3368
|
function readJson2(path) {
|
|
3789
|
-
if (!path || !
|
|
3369
|
+
if (!path || !existsSync20(path)) return void 0;
|
|
3790
3370
|
try {
|
|
3791
|
-
return JSON.parse(
|
|
3371
|
+
return JSON.parse(readFileSync14(path, "utf8"));
|
|
3792
3372
|
} catch {
|
|
3793
3373
|
return void 0;
|
|
3794
3374
|
}
|
|
@@ -4105,8 +3685,8 @@ function isHost2(value) {
|
|
|
4105
3685
|
return value === "codex" || value === "claude-code" || value === "gemini-cli" || value === "antigravity";
|
|
4106
3686
|
}
|
|
4107
3687
|
function findPortfolioAgentAssetsDir(manifest) {
|
|
4108
|
-
const agentAssetsDir = manifest?.executionEngine?.path ?
|
|
4109
|
-
return agentAssetsDir &&
|
|
3688
|
+
const agentAssetsDir = manifest?.executionEngine?.path ? join20(manifest.executionEngine.path, "agent-assets") : void 0;
|
|
3689
|
+
return agentAssetsDir && existsSync21(join20(agentAssetsDir, "registry.json")) ? agentAssetsDir : void 0;
|
|
4110
3690
|
}
|
|
4111
3691
|
function printUsage4() {
|
|
4112
3692
|
console.error("Usage:");
|
|
@@ -4117,69 +3697,12 @@ function printUsage4() {
|
|
|
4117
3697
|
}
|
|
4118
3698
|
|
|
4119
3699
|
// src/commands/sync.ts
|
|
4120
|
-
import { existsSync as
|
|
4121
|
-
import {
|
|
4122
|
-
import { isDeepStrictEqual } from "node:util";
|
|
4123
|
-
|
|
4124
|
-
// src/host-hooks/config-merge.ts
|
|
4125
|
-
var MANAGED_EVENTS = ["Stop", "SubagentStop"];
|
|
4126
|
-
function mergeHostHookConfig(targetPath, current, template) {
|
|
4127
|
-
const currentRecord = asRecord(current);
|
|
4128
|
-
const templateRecord = asRecord(template);
|
|
4129
|
-
if (targetPath === ".agents/hooks.json") {
|
|
4130
|
-
return {
|
|
4131
|
-
...currentRecord,
|
|
4132
|
-
"pgs-compound-gate": templateRecord["pgs-compound-gate"]
|
|
4133
|
-
};
|
|
4134
|
-
}
|
|
4135
|
-
const host = targetPath === ".codex/hooks.json" ? "codex" : "claude-code";
|
|
4136
|
-
const currentHooks = asRecord(currentRecord.hooks);
|
|
4137
|
-
const templateHooks = asRecord(templateRecord.hooks);
|
|
4138
|
-
const mergedHooks = { ...currentHooks };
|
|
4139
|
-
for (const event of MANAGED_EVENTS) {
|
|
4140
|
-
const currentEntries = asArray(currentHooks[event]);
|
|
4141
|
-
const templateEntries = asArray(templateHooks[event]);
|
|
4142
|
-
mergedHooks[event] = replaceManagedEntries(currentEntries, templateEntries, host, event);
|
|
4143
|
-
}
|
|
4144
|
-
return {
|
|
4145
|
-
...currentRecord,
|
|
4146
|
-
hooks: mergedHooks
|
|
4147
|
-
};
|
|
4148
|
-
}
|
|
4149
|
-
function replaceManagedEntries(currentEntries, templateEntries, host, event) {
|
|
4150
|
-
const result = [];
|
|
4151
|
-
let insertedTemplate = false;
|
|
4152
|
-
for (const entry of currentEntries) {
|
|
4153
|
-
if (isManagedEntry(entry, host, event)) {
|
|
4154
|
-
if (!insertedTemplate) {
|
|
4155
|
-
result.push(...templateEntries);
|
|
4156
|
-
insertedTemplate = true;
|
|
4157
|
-
}
|
|
4158
|
-
continue;
|
|
4159
|
-
}
|
|
4160
|
-
result.push(entry);
|
|
4161
|
-
}
|
|
4162
|
-
if (!insertedTemplate) result.push(...templateEntries);
|
|
4163
|
-
return result;
|
|
4164
|
-
}
|
|
4165
|
-
function isManagedEntry(entry, host, event) {
|
|
4166
|
-
const serialized = JSON.stringify(entry);
|
|
4167
|
-
return serialized.includes("pro-gov host-hook") && serialized.includes(`--host ${host}`) && serialized.includes(`--event ${event}`);
|
|
4168
|
-
}
|
|
4169
|
-
function asRecord(value) {
|
|
4170
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
|
|
4171
|
-
return value;
|
|
4172
|
-
}
|
|
4173
|
-
function asArray(value) {
|
|
4174
|
-
return Array.isArray(value) ? value : [];
|
|
4175
|
-
}
|
|
4176
|
-
|
|
4177
|
-
// src/commands/sync.ts
|
|
3700
|
+
import { existsSync as existsSync22, readFileSync as readFileSync15 } from "node:fs";
|
|
3701
|
+
import { join as join21 } from "node:path";
|
|
4178
3702
|
function runSync(args) {
|
|
4179
3703
|
const check = args.includes("--check");
|
|
4180
|
-
|
|
4181
|
-
|
|
4182
|
-
console.error("pro-gov sync requires exactly one of --check or --apply-host-hooks.");
|
|
3704
|
+
if (!check) {
|
|
3705
|
+
console.error("pro-gov sync requires --check.");
|
|
4183
3706
|
return 1;
|
|
4184
3707
|
}
|
|
4185
3708
|
const requestedProfile = readFlag2(args, "--profile");
|
|
@@ -4199,13 +3722,12 @@ function runSync(args) {
|
|
|
4199
3722
|
);
|
|
4200
3723
|
return 1;
|
|
4201
3724
|
}
|
|
4202
|
-
if (applyHostHooks) return applyHostHookConfigs(profile);
|
|
4203
3725
|
let differences = 0;
|
|
4204
3726
|
console.log("pro-gov sync check");
|
|
4205
3727
|
console.log(`profile: ${profile}`);
|
|
4206
3728
|
for (const file of planStarterFiles(profile)) {
|
|
4207
|
-
const targetPath =
|
|
4208
|
-
if (!
|
|
3729
|
+
const targetPath = join21(process.cwd(), file.targetPath);
|
|
3730
|
+
if (!existsSync22(targetPath)) {
|
|
4209
3731
|
if (file.ownership === "optional-guardrail") continue;
|
|
4210
3732
|
console.log(`missing: ${file.targetPath}`);
|
|
4211
3733
|
differences += 1;
|
|
@@ -4213,8 +3735,8 @@ function runSync(args) {
|
|
|
4213
3735
|
}
|
|
4214
3736
|
if (file.ownership === "optional-guardrail") continue;
|
|
4215
3737
|
if (file.ownership === "project-local-seed") continue;
|
|
4216
|
-
const source =
|
|
4217
|
-
const target =
|
|
3738
|
+
const source = readFileSync15(file.absoluteSourcePath, "utf8");
|
|
3739
|
+
const target = readFileSync15(targetPath, "utf8");
|
|
4218
3740
|
if (!matchesExpectedContent(file.targetPath, source, target)) {
|
|
4219
3741
|
console.log(`different: ${file.targetPath}`);
|
|
4220
3742
|
differences += 1;
|
|
@@ -4227,52 +3749,11 @@ function runSync(args) {
|
|
|
4227
3749
|
console.log("sync check passed: starter files match packaged assets.");
|
|
4228
3750
|
return 0;
|
|
4229
3751
|
}
|
|
4230
|
-
function applyHostHookConfigs(profile) {
|
|
4231
|
-
if (profile !== "engineering-runtime") {
|
|
4232
|
-
console.error("Host hooks are installed only for the engineering-runtime profile.");
|
|
4233
|
-
return 1;
|
|
4234
|
-
}
|
|
4235
|
-
const root = process.cwd();
|
|
4236
|
-
const updates = [];
|
|
4237
|
-
try {
|
|
4238
|
-
for (const file of planStarterFiles(profile)) {
|
|
4239
|
-
if (!isHostHookTargetPath(file.targetPath)) continue;
|
|
4240
|
-
const path = join22(root, file.targetPath);
|
|
4241
|
-
const template = JSON.parse(readFileSync18(file.absoluteSourcePath, "utf8"));
|
|
4242
|
-
const targetExists = existsSync23(path);
|
|
4243
|
-
const current = targetExists ? JSON.parse(readFileSync18(path, "utf8")) : {};
|
|
4244
|
-
const merged = mergeHostHookConfig(file.targetPath, current, template);
|
|
4245
|
-
if (targetExists && isDeepStrictEqual(current, merged)) continue;
|
|
4246
|
-
updates.push({ path, content: `${JSON.stringify(merged, null, 2)}
|
|
4247
|
-
` });
|
|
4248
|
-
}
|
|
4249
|
-
} catch (error) {
|
|
4250
|
-
console.error(`Cannot merge host hook configuration: ${errorMessage(error)}`);
|
|
4251
|
-
console.error("No host hook files were written.");
|
|
4252
|
-
return 1;
|
|
4253
|
-
}
|
|
4254
|
-
for (const update of updates) {
|
|
4255
|
-
mkdirSync9(dirname12(update.path), { recursive: true });
|
|
4256
|
-
writeFileSync8(update.path, update.content);
|
|
4257
|
-
}
|
|
4258
|
-
console.log(`host hooks applied: ${updates.length}`);
|
|
4259
|
-
console.log("Project-owned host config keys and non-PGS hook entries were preserved.");
|
|
4260
|
-
return 0;
|
|
4261
|
-
}
|
|
4262
3752
|
function matchesExpectedContent(targetPath, source, target) {
|
|
4263
|
-
if (
|
|
4264
|
-
|
|
4265
|
-
return normalizeMarkdownTablePadding(source) === normalizeMarkdownTablePadding(target);
|
|
4266
|
-
}
|
|
4267
|
-
return source === target;
|
|
4268
|
-
}
|
|
4269
|
-
try {
|
|
4270
|
-
const current = JSON.parse(target);
|
|
4271
|
-
const template = JSON.parse(source);
|
|
4272
|
-
return isDeepStrictEqual(current, mergeHostHookConfig(targetPath, current, template));
|
|
4273
|
-
} catch {
|
|
4274
|
-
return false;
|
|
3753
|
+
if (targetPath.endsWith(".md")) {
|
|
3754
|
+
return normalizeMarkdownTablePadding(source) === normalizeMarkdownTablePadding(target);
|
|
4275
3755
|
}
|
|
3756
|
+
return source === target;
|
|
4276
3757
|
}
|
|
4277
3758
|
function normalizeMarkdownTablePadding(content) {
|
|
4278
3759
|
return content.split("\n").map((line) => {
|
|
@@ -4289,7 +3770,7 @@ function normalizeMarkdownTableCell(cell) {
|
|
|
4289
3770
|
}
|
|
4290
3771
|
function inferInstalledProfile(root) {
|
|
4291
3772
|
const installed = ["engineering-runtime", "doc-only"].filter(
|
|
4292
|
-
(profile) =>
|
|
3773
|
+
(profile) => existsSync22(join21(root, `docs/governance/agents-routing/${profile}-v0.9.md`))
|
|
4293
3774
|
);
|
|
4294
3775
|
return installed.length === 1 ? installed[0] : void 0;
|
|
4295
3776
|
}
|
|
@@ -4298,9 +3779,6 @@ function readFlag2(args, flag) {
|
|
|
4298
3779
|
const value = index >= 0 ? args[index + 1] : void 0;
|
|
4299
3780
|
return value && !value.startsWith("--") ? value : void 0;
|
|
4300
3781
|
}
|
|
4301
|
-
function errorMessage(error) {
|
|
4302
|
-
return error instanceof Error ? error.message : String(error);
|
|
4303
|
-
}
|
|
4304
3782
|
|
|
4305
3783
|
// src/cli.ts
|
|
4306
3784
|
var COMMANDS = [
|
|
@@ -4323,9 +3801,8 @@ var COMMANDS = [
|
|
|
4323
3801
|
"lens report --target <path> --out <path>",
|
|
4324
3802
|
"lens audit init --target <path> --out <path>",
|
|
4325
3803
|
"lens audit check --dir <path> [--json]",
|
|
4326
|
-
"host-hook --host <codex|claude-code|antigravity> --event <Stop|SubagentStop|...> [--compound-gate-mode off|lite|strict]",
|
|
4327
3804
|
"init --profile <engineering-runtime|doc-only> <--dry-run|--apply>",
|
|
4328
|
-
"sync
|
|
3805
|
+
"sync --check [--profile <engineering-runtime|doc-only>]",
|
|
4329
3806
|
"doctor"
|
|
4330
3807
|
];
|
|
4331
3808
|
var [command, subcommand] = process.argv.slice(2);
|
|
@@ -4339,7 +3816,6 @@ async function main() {
|
|
|
4339
3816
|
if (command === "learn") return runLearn(process.argv.slice(3));
|
|
4340
3817
|
if (command === "lens") return runLens(process.argv.slice(3));
|
|
4341
3818
|
if (command === "portfolio") return runPortfolio(process.argv.slice(3));
|
|
4342
|
-
if (command === "host-hook") return runHostHook(process.argv.slice(3));
|
|
4343
3819
|
if (command === "init") return runInit(process.argv.slice(3));
|
|
4344
3820
|
if (command === "sync") return runSync(process.argv.slice(3));
|
|
4345
3821
|
if (command === "doctor") return runDoctor(process.argv.slice(3));
|