@pieai/pro-gov 0.3.9 → 0.3.10
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 +5 -2
- package/assets/docs/reference/adoption/adoption-playbook.md +30 -1
- package/dist/cli.js +99 -17
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -149,8 +149,11 @@ Compound Gate: skipped -> <reason>
|
|
|
149
149
|
```
|
|
150
150
|
|
|
151
151
|
Use `pro-gov doctor --strict-hooks` after syncing an engineering project to
|
|
152
|
-
verify that the host configs are wired.
|
|
153
|
-
|
|
152
|
+
verify that the host configs are wired. This is a wiring check: it proves the
|
|
153
|
+
project calls `pro-gov host-hook` for Codex, Claude Code, and Antigravity. The
|
|
154
|
+
host behavior contract is covered by PGS package tests against recorded
|
|
155
|
+
per-host Stop/SubagentStop fixtures. Open a fresh AI session after installing or
|
|
156
|
+
changing hooks; old sessions may not reload host configuration.
|
|
154
157
|
|
|
155
158
|
Ponytail can be installed as an optional complexity adviser. Keep its global mode
|
|
156
159
|
`off`; test `lite` in one isolated task before considering a stronger mode.
|
|
@@ -6,7 +6,7 @@ status: stable
|
|
|
6
6
|
canonical: true
|
|
7
7
|
owner: human
|
|
8
8
|
created: 2026-05-06
|
|
9
|
-
last_reviewed: 2026-
|
|
9
|
+
last_reviewed: 2026-07-02
|
|
10
10
|
domain: adoption
|
|
11
11
|
tags:
|
|
12
12
|
- adoption
|
|
@@ -92,8 +92,17 @@ Current package-based method:
|
|
|
92
92
|
- copy `starter/lefthook.template.yml` to `lefthook.yml` and
|
|
93
93
|
`starter/.github/workflows/docs-check.yml` to `.github/workflows/docs-check.yml`
|
|
94
94
|
when the target project is ready for standard guardrails
|
|
95
|
+
- for an `engineering-runtime` target, install the three host-hook starter files
|
|
96
|
+
or merge their Stop/SubagentStop entries into the target's existing host
|
|
97
|
+
config:
|
|
98
|
+
- `.codex/hooks.json`
|
|
99
|
+
- `.claude/settings.json`
|
|
100
|
+
- `.agents/hooks.json`
|
|
95
101
|
- run `doc-gov doctor` after wiring guardrails to verify they are actually
|
|
96
102
|
connected
|
|
103
|
+
- run `pro-gov doctor --strict-hooks` after wiring host hooks to prove the
|
|
104
|
+
target has the expected host-hook configuration for Codex, Claude Code, and
|
|
105
|
+
Antigravity
|
|
97
106
|
- treat the npm package as the CLI source and the local `docs/governance/`
|
|
98
107
|
files as the project's checked-in governance contract
|
|
99
108
|
|
|
@@ -198,6 +207,16 @@ Use Compound Engineering by default only as the post-work Compound Gate:
|
|
|
198
207
|
`ce-compound` captures reusable lessons when they exist; otherwise the agent
|
|
199
208
|
reports a skip reason. Full CE workflows require an explicit user request.
|
|
200
209
|
|
|
210
|
+
The host-hook gate is deliberately small. It does not make Compound Engineering
|
|
211
|
+
the main workflow. It only blocks a final completion report that looks like
|
|
212
|
+
finished engineering work but does not say whether the Compound Gate ran or was
|
|
213
|
+
skipped:
|
|
214
|
+
|
|
215
|
+
```text
|
|
216
|
+
Compound Gate: ran ce-compound -> <path>
|
|
217
|
+
Compound Gate: skipped -> <reason>
|
|
218
|
+
```
|
|
219
|
+
|
|
201
220
|
### Doc-Only
|
|
202
221
|
|
|
203
222
|
Add:
|
|
@@ -257,6 +276,7 @@ For the v0.9 structural migration, use
|
|
|
257
276
|
Minimum:
|
|
258
277
|
|
|
259
278
|
```bash
|
|
279
|
+
pnpm pro-gov doctor --strict-hooks
|
|
260
280
|
pnpm doc-gov check
|
|
261
281
|
pnpm doc-gov router-check
|
|
262
282
|
pnpm doc-gov scan --check
|
|
@@ -266,6 +286,15 @@ pnpm doc-gov doctor
|
|
|
266
286
|
git diff --check
|
|
267
287
|
```
|
|
268
288
|
|
|
289
|
+
Use `pro-gov doctor --strict-hooks` for engineering-runtime projects. Doc-only
|
|
290
|
+
projects may omit strict host hooks unless they intentionally adopt the
|
|
291
|
+
engineering runtime profile.
|
|
292
|
+
|
|
293
|
+
`pro-gov doctor --strict-hooks` is a wiring check, not a live host simulation.
|
|
294
|
+
It proves the project has hook files that call `pro-gov host-hook`; PGS package
|
|
295
|
+
tests prove the expected Stop/SubagentStop behavior for the supported host
|
|
296
|
+
schemas. After installing or changing hooks, validate from a fresh AI session.
|
|
297
|
+
|
|
269
298
|
Engineering projects should also run their local verification ladder.
|
|
270
299
|
|
|
271
300
|
## Example: Migrating A Runtime Product
|
package/dist/cli.js
CHANGED
|
@@ -1636,9 +1636,10 @@ function isEngineeringRuntimeProject(root) {
|
|
|
1636
1636
|
}
|
|
1637
1637
|
|
|
1638
1638
|
// src/commands/host-hook.ts
|
|
1639
|
-
import { readFileSync as
|
|
1639
|
+
import { readFileSync as readFileSync10 } from "node:fs";
|
|
1640
1640
|
|
|
1641
1641
|
// src/host-hooks/host-hook-runner.ts
|
|
1642
|
+
import { closeSync, openSync, readFileSync as readFileSync9, readSync, statSync as statSync3 } from "node:fs";
|
|
1642
1643
|
var gateMarkerPattern = /Compound Gate:\s*(ran ce-compound|skipped)\s*->/i;
|
|
1643
1644
|
var completionSignalPatterns = [
|
|
1644
1645
|
/\b(done|completed|implemented|fixed|verified|validated|shipped|pushed|committed)\b/i,
|
|
@@ -1653,6 +1654,7 @@ var compoundGateInstruction = [
|
|
|
1653
1654
|
"If there is no reusable learning, report:",
|
|
1654
1655
|
"Compound Gate: skipped -> <reason>"
|
|
1655
1656
|
].join("\n");
|
|
1657
|
+
var maxTranscriptBytes = 2 * 1024 * 1024;
|
|
1656
1658
|
function evaluateHostHook(request) {
|
|
1657
1659
|
if (request.event !== "Stop" && request.event !== "SubagentStop") {
|
|
1658
1660
|
return { action: "allow" };
|
|
@@ -1694,7 +1696,7 @@ function formatHostHookOutput(host, event, decision) {
|
|
|
1694
1696
|
}
|
|
1695
1697
|
if (host === "claude" || host === "claude-code") {
|
|
1696
1698
|
if (event === "Stop" || event === "SubagentStop") {
|
|
1697
|
-
return {
|
|
1699
|
+
return { decision: "block", reason: decision.reason };
|
|
1698
1700
|
}
|
|
1699
1701
|
if (decision.action === "block") {
|
|
1700
1702
|
return {
|
|
@@ -1723,7 +1725,7 @@ function normalizeStopInput(input) {
|
|
|
1723
1725
|
return { lastAssistantMessage: void 0, stopHookActive: false };
|
|
1724
1726
|
}
|
|
1725
1727
|
return {
|
|
1726
|
-
lastAssistantMessage: findString(input, ["last_assistant_message", "lastAssistantMessage", "message", "text"]),
|
|
1728
|
+
lastAssistantMessage: findString(input, ["last_assistant_message", "lastAssistantMessage", "message", "text"]) ?? findLastAssistantMessageFromTranscript(findString(input, ["agent_transcript_path", "agentTranscriptPath"])) ?? findLastAssistantMessageFromTranscript(findString(input, ["transcript_path", "transcriptPath"])),
|
|
1727
1729
|
stopHookActive: findBoolean(input, ["stop_hook_active", "stopHookActive"]) ?? false
|
|
1728
1730
|
};
|
|
1729
1731
|
}
|
|
@@ -1748,6 +1750,86 @@ function findBoolean(input, keys) {
|
|
|
1748
1750
|
}
|
|
1749
1751
|
return void 0;
|
|
1750
1752
|
}
|
|
1753
|
+
function findLastAssistantMessageFromTranscript(path) {
|
|
1754
|
+
if (!path || path.startsWith("~/")) {
|
|
1755
|
+
return void 0;
|
|
1756
|
+
}
|
|
1757
|
+
let content;
|
|
1758
|
+
try {
|
|
1759
|
+
content = readTranscriptTail(path);
|
|
1760
|
+
} catch {
|
|
1761
|
+
return void 0;
|
|
1762
|
+
}
|
|
1763
|
+
const lines = content.split(/\r?\n/);
|
|
1764
|
+
for (let index = lines.length - 1; index >= 0; index -= 1) {
|
|
1765
|
+
const line = lines[index]?.trim();
|
|
1766
|
+
if (!line) {
|
|
1767
|
+
continue;
|
|
1768
|
+
}
|
|
1769
|
+
let record;
|
|
1770
|
+
try {
|
|
1771
|
+
record = JSON.parse(line);
|
|
1772
|
+
} catch {
|
|
1773
|
+
continue;
|
|
1774
|
+
}
|
|
1775
|
+
const message = extractAssistantText(record);
|
|
1776
|
+
if (message) {
|
|
1777
|
+
return message;
|
|
1778
|
+
}
|
|
1779
|
+
}
|
|
1780
|
+
return void 0;
|
|
1781
|
+
}
|
|
1782
|
+
function readTranscriptTail(path) {
|
|
1783
|
+
const size = statSync3(path).size;
|
|
1784
|
+
if (size <= maxTranscriptBytes) {
|
|
1785
|
+
return readFileSync9(path, "utf8");
|
|
1786
|
+
}
|
|
1787
|
+
const fd = openSync(path, "r");
|
|
1788
|
+
try {
|
|
1789
|
+
const buffer = Buffer.allocUnsafe(maxTranscriptBytes);
|
|
1790
|
+
readSync(fd, buffer, 0, maxTranscriptBytes, size - maxTranscriptBytes);
|
|
1791
|
+
return buffer.toString("utf8");
|
|
1792
|
+
} finally {
|
|
1793
|
+
closeSync(fd);
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
function extractAssistantText(value) {
|
|
1797
|
+
if (!isRecord(value)) {
|
|
1798
|
+
return void 0;
|
|
1799
|
+
}
|
|
1800
|
+
const nested = value.message;
|
|
1801
|
+
const role = findString(value, ["role", "type"]);
|
|
1802
|
+
if (isRecord(nested)) {
|
|
1803
|
+
const nestedRole = findString(nested, ["role", "type"]);
|
|
1804
|
+
if (nestedRole === "assistant") {
|
|
1805
|
+
return extractTextContent(nested.content);
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1808
|
+
if (role === "assistant") {
|
|
1809
|
+
return extractTextContent(value.content) ?? extractTextContent(value.text);
|
|
1810
|
+
}
|
|
1811
|
+
return void 0;
|
|
1812
|
+
}
|
|
1813
|
+
function extractTextContent(value) {
|
|
1814
|
+
if (typeof value === "string" && value.trim().length > 0) {
|
|
1815
|
+
return value;
|
|
1816
|
+
}
|
|
1817
|
+
if (!Array.isArray(value)) {
|
|
1818
|
+
return void 0;
|
|
1819
|
+
}
|
|
1820
|
+
const parts = [];
|
|
1821
|
+
for (const item of value) {
|
|
1822
|
+
if (typeof item === "string") {
|
|
1823
|
+
parts.push(item);
|
|
1824
|
+
continue;
|
|
1825
|
+
}
|
|
1826
|
+
if (isRecord(item) && typeof item.text === "string") {
|
|
1827
|
+
parts.push(item.text);
|
|
1828
|
+
}
|
|
1829
|
+
}
|
|
1830
|
+
const text = parts.join("\n").trim();
|
|
1831
|
+
return text.length > 0 ? text : void 0;
|
|
1832
|
+
}
|
|
1751
1833
|
function isRecord(value) {
|
|
1752
1834
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1753
1835
|
}
|
|
@@ -1785,7 +1867,7 @@ function readOption(args, name) {
|
|
|
1785
1867
|
return args[index + 1];
|
|
1786
1868
|
}
|
|
1787
1869
|
function readStdinJson() {
|
|
1788
|
-
const raw =
|
|
1870
|
+
const raw = readFileSync10(0, "utf8").trim();
|
|
1789
1871
|
if (!raw) return {};
|
|
1790
1872
|
try {
|
|
1791
1873
|
return JSON.parse(raw);
|
|
@@ -1795,7 +1877,7 @@ function readStdinJson() {
|
|
|
1795
1877
|
}
|
|
1796
1878
|
|
|
1797
1879
|
// src/commands/init.ts
|
|
1798
|
-
import { existsSync as existsSync13, mkdirSync as mkdirSync4, readFileSync as
|
|
1880
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync4, readFileSync as readFileSync11, writeFileSync as writeFileSync3 } from "node:fs";
|
|
1799
1881
|
import { basename as basename3, dirname as dirname7, join as join13 } from "node:path";
|
|
1800
1882
|
|
|
1801
1883
|
// src/commands/shared.ts
|
|
@@ -1878,7 +1960,7 @@ function applyStarterFiles(files, profile) {
|
|
|
1878
1960
|
for (const file of files) {
|
|
1879
1961
|
const targetPath = join13(root, file.targetPath);
|
|
1880
1962
|
mkdirSync4(dirname7(targetPath), { recursive: true });
|
|
1881
|
-
const source =
|
|
1963
|
+
const source = readFileSync11(file.absoluteSourcePath);
|
|
1882
1964
|
const content = file.targetPath === "AGENTS.md" ? renderAgentsTemplate(source.toString("utf8"), basename3(root), profile) : source;
|
|
1883
1965
|
writeFileSync3(targetPath, content);
|
|
1884
1966
|
}
|
|
@@ -1912,7 +1994,7 @@ import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync5 } from "node:f
|
|
|
1912
1994
|
import { dirname as dirname9 } from "node:path";
|
|
1913
1995
|
|
|
1914
1996
|
// src/lens/audit.ts
|
|
1915
|
-
import { existsSync as existsSync14, mkdirSync as mkdirSync5, readFileSync as
|
|
1997
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync5, readFileSync as readFileSync12, writeFileSync as writeFileSync4 } from "node:fs";
|
|
1916
1998
|
import { basename as basename4, dirname as dirname8, join as join14 } from "node:path";
|
|
1917
1999
|
var REQUIRED_ARTIFACTS = [
|
|
1918
2000
|
"manifest.md",
|
|
@@ -1960,7 +2042,7 @@ function checkProjectLensAuditPackage(auditDir, options = {}) {
|
|
|
1960
2042
|
}
|
|
1961
2043
|
let contract;
|
|
1962
2044
|
try {
|
|
1963
|
-
contract = JSON.parse(
|
|
2045
|
+
contract = JSON.parse(readFileSync12(contractPath, "utf8"));
|
|
1964
2046
|
} catch (error) {
|
|
1965
2047
|
return {
|
|
1966
2048
|
ok: false,
|
|
@@ -1998,7 +2080,7 @@ function checkProjectLensAuditPackage(auditDir, options = {}) {
|
|
|
1998
2080
|
issues.push({ type: "missing-required-artifact", path: artifactPath });
|
|
1999
2081
|
continue;
|
|
2000
2082
|
}
|
|
2001
|
-
const content =
|
|
2083
|
+
const content = readFileSync12(absolutePath, "utf8");
|
|
2002
2084
|
if (isPendingArtifact(content)) {
|
|
2003
2085
|
issues.push({ type: "artifact-not-complete", path: artifactPath });
|
|
2004
2086
|
} else if (hasTemplateBody(content)) {
|
|
@@ -2258,7 +2340,7 @@ function bulletList(values) {
|
|
|
2258
2340
|
|
|
2259
2341
|
// src/lens/scan.ts
|
|
2260
2342
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
2261
|
-
import { existsSync as existsSync15, readdirSync as readdirSync6, readFileSync as
|
|
2343
|
+
import { existsSync as existsSync15, readdirSync as readdirSync6, readFileSync as readFileSync13, statSync as statSync4 } from "node:fs";
|
|
2262
2344
|
import { join as join15, relative as relative5 } from "node:path";
|
|
2263
2345
|
var ignoredDirectories = /* @__PURE__ */ new Set([
|
|
2264
2346
|
".git",
|
|
@@ -2286,14 +2368,14 @@ function scanProjectLensTarget(targetDir, options = {}) {
|
|
|
2286
2368
|
governanceFiles: markdownFiles.filter((file) => file.startsWith("docs/governance/") || file.startsWith("docs/policy/")).sort()
|
|
2287
2369
|
},
|
|
2288
2370
|
git: readGitState(targetDir),
|
|
2289
|
-
largeFiles: files.map((file) => ({ path: file, bytes:
|
|
2371
|
+
largeFiles: files.map((file) => ({ path: file, bytes: statSync4(join15(targetDir, file)).size })).filter((file) => file.bytes >= largeFileBytes).sort((a, b) => b.bytes - a.bytes || a.path.localeCompare(b.path)).slice(0, 25)
|
|
2290
2372
|
};
|
|
2291
2373
|
}
|
|
2292
2374
|
function readPackageJson(targetDir) {
|
|
2293
2375
|
const packageJsonPath = join15(targetDir, "package.json");
|
|
2294
2376
|
if (!existsSync15(packageJsonPath)) return void 0;
|
|
2295
2377
|
try {
|
|
2296
|
-
const packageJson = JSON.parse(
|
|
2378
|
+
const packageJson = JSON.parse(readFileSync13(packageJsonPath, "utf8"));
|
|
2297
2379
|
return {
|
|
2298
2380
|
scripts: Object.keys(packageJson.scripts ?? {}).sort(),
|
|
2299
2381
|
dependencies: Object.keys(packageJson.dependencies ?? {}).sort(),
|
|
@@ -2494,12 +2576,12 @@ import { existsSync as existsSync17 } from "node:fs";
|
|
|
2494
2576
|
import { join as join16 } from "node:path";
|
|
2495
2577
|
|
|
2496
2578
|
// src/portfolio/manifest.ts
|
|
2497
|
-
import { existsSync as existsSync16, readFileSync as
|
|
2579
|
+
import { existsSync as existsSync16, readFileSync as readFileSync14 } from "node:fs";
|
|
2498
2580
|
import { dirname as dirname10, isAbsolute as isAbsolute3, resolve as resolve2 } from "node:path";
|
|
2499
2581
|
function loadPortfolioManifest(configPath) {
|
|
2500
2582
|
let parsed;
|
|
2501
2583
|
try {
|
|
2502
|
-
parsed = JSON.parse(
|
|
2584
|
+
parsed = JSON.parse(readFileSync14(configPath, "utf8"));
|
|
2503
2585
|
} catch (error) {
|
|
2504
2586
|
return {
|
|
2505
2587
|
configPath,
|
|
@@ -2939,7 +3021,7 @@ function printUsage3() {
|
|
|
2939
3021
|
}
|
|
2940
3022
|
|
|
2941
3023
|
// src/commands/sync.ts
|
|
2942
|
-
import { existsSync as existsSync18, readFileSync as
|
|
3024
|
+
import { existsSync as existsSync18, readFileSync as readFileSync15 } from "node:fs";
|
|
2943
3025
|
import { join as join17 } from "node:path";
|
|
2944
3026
|
function runSync(args) {
|
|
2945
3027
|
if (!args.includes("--check")) {
|
|
@@ -2975,8 +3057,8 @@ function runSync(args) {
|
|
|
2975
3057
|
continue;
|
|
2976
3058
|
}
|
|
2977
3059
|
if (file.ownership === "project-local-seed") continue;
|
|
2978
|
-
const source =
|
|
2979
|
-
const target =
|
|
3060
|
+
const source = readFileSync15(file.absoluteSourcePath, "utf8");
|
|
3061
|
+
const target = readFileSync15(targetPath, "utf8");
|
|
2980
3062
|
if (source !== target) {
|
|
2981
3063
|
console.log(`different: ${file.targetPath}`);
|
|
2982
3064
|
differences += 1;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pieai/pro-gov",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.10",
|
|
4
4
|
"description": "Project-level distribution kit for Project Governance System.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai-agents",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"access": "public"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@pieai/doc-gov": "^0.3.
|
|
38
|
+
"@pieai/doc-gov": "^0.3.10"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@types/node": "24.13.2",
|