@pieai/pro-gov 0.3.8 → 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 CHANGED
@@ -139,6 +139,22 @@ engineering work. The default PGS pattern is a Compound Gate: run
139
139
  skipped. Full CE workflows such as `ce-plan`, `ce-work`, and `lfg` remain
140
140
  explicit user choices.
141
141
 
142
+ Engineering-runtime starters include cross-host Stop hooks for Codex,
143
+ Claude Code, and Antigravity. Those hooks call `pro-gov host-hook` and require
144
+ the final report to include either:
145
+
146
+ ```text
147
+ Compound Gate: ran ce-compound -> <path>
148
+ Compound Gate: skipped -> <reason>
149
+ ```
150
+
151
+ Use `pro-gov doctor --strict-hooks` after syncing an engineering project to
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.
157
+
142
158
  Ponytail can be installed as an optional complexity adviser. Keep its global mode
143
159
  `off`; test `lite` in one isolated task before considering a stronger mode.
144
160
  Ponytail must not remove requested scope, tests, safety, accessibility, or proof.
@@ -6,7 +6,7 @@ status: stable
6
6
  canonical: true
7
7
  owner: human
8
8
  created: 2026-05-06
9
- last_reviewed: 2026-06-15
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
@@ -39,3 +39,9 @@ project shape. `pro-gov init --apply` can install it into a fresh target, but
39
39
  refuses all writes when any destination already exists. `sync --check` compares
40
40
  shared core files while leaving project-local router, policy, and current-work
41
41
  content under the target project's ownership.
42
+
43
+ Engineering-runtime starters also include Codex, Claude Code, and Antigravity
44
+ Stop hooks that call `pro-gov host-hook`. Use `pro-gov doctor --strict-hooks`
45
+ after sync to confirm the exit gate is wired. The hook enforces the Compound
46
+ Gate final-report marker; it does not replace verification, tests, or
47
+ `ce-compound` itself.
@@ -0,0 +1,26 @@
1
+ {
2
+ "pgs-compound-gate": {
3
+ "Stop": [
4
+ {
5
+ "hooks": [
6
+ {
7
+ "type": "command",
8
+ "command": "cd \"$(git rev-parse --show-toplevel)\" && if [ -x ./node_modules/.bin/pro-gov ]; then ./node_modules/.bin/pro-gov host-hook --host antigravity --event Stop; elif [ -f packages/pro-gov/src/cli.ts ] && command -v pnpm >/dev/null 2>&1; then pnpm --silent --filter @pieai/pro-gov dev host-hook --host antigravity --event Stop; else npx --no-install @pieai/pro-gov host-hook --host antigravity --event Stop; fi",
9
+ "timeout": 30
10
+ }
11
+ ]
12
+ }
13
+ ],
14
+ "SubagentStop": [
15
+ {
16
+ "hooks": [
17
+ {
18
+ "type": "command",
19
+ "command": "cd \"$(git rev-parse --show-toplevel)\" && if [ -x ./node_modules/.bin/pro-gov ]; then ./node_modules/.bin/pro-gov host-hook --host antigravity --event SubagentStop; elif [ -f packages/pro-gov/src/cli.ts ] && command -v pnpm >/dev/null 2>&1; then pnpm --silent --filter @pieai/pro-gov dev host-hook --host antigravity --event SubagentStop; else npx --no-install @pieai/pro-gov host-hook --host antigravity --event SubagentStop; fi",
20
+ "timeout": 30
21
+ }
22
+ ]
23
+ }
24
+ ]
25
+ }
26
+ }
@@ -0,0 +1,26 @@
1
+ {
2
+ "hooks": {
3
+ "Stop": [
4
+ {
5
+ "hooks": [
6
+ {
7
+ "type": "command",
8
+ "command": "cd \"${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel)}\" && if [ -x ./node_modules/.bin/pro-gov ]; then ./node_modules/.bin/pro-gov host-hook --host claude-code --event Stop; elif [ -f packages/pro-gov/src/cli.ts ] && command -v pnpm >/dev/null 2>&1; then pnpm --silent --filter @pieai/pro-gov dev host-hook --host claude-code --event Stop; else npx --no-install @pieai/pro-gov host-hook --host claude-code --event Stop; fi",
9
+ "timeout": 30
10
+ }
11
+ ]
12
+ }
13
+ ],
14
+ "SubagentStop": [
15
+ {
16
+ "hooks": [
17
+ {
18
+ "type": "command",
19
+ "command": "cd \"${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel)}\" && if [ -x ./node_modules/.bin/pro-gov ]; then ./node_modules/.bin/pro-gov host-hook --host claude-code --event SubagentStop; elif [ -f packages/pro-gov/src/cli.ts ] && command -v pnpm >/dev/null 2>&1; then pnpm --silent --filter @pieai/pro-gov dev host-hook --host claude-code --event SubagentStop; else npx --no-install @pieai/pro-gov host-hook --host claude-code --event SubagentStop; fi",
20
+ "timeout": 30
21
+ }
22
+ ]
23
+ }
24
+ ]
25
+ }
26
+ }
@@ -0,0 +1,28 @@
1
+ {
2
+ "hooks": {
3
+ "Stop": [
4
+ {
5
+ "hooks": [
6
+ {
7
+ "type": "command",
8
+ "command": "cd \"$(git rev-parse --show-toplevel)\" && if [ -x ./node_modules/.bin/pro-gov ]; then ./node_modules/.bin/pro-gov host-hook --host codex --event Stop; elif [ -f packages/pro-gov/src/cli.ts ] && command -v pnpm >/dev/null 2>&1; then pnpm --silent --filter @pieai/pro-gov dev host-hook --host codex --event Stop; else npx --no-install @pieai/pro-gov host-hook --host codex --event Stop; fi",
9
+ "timeout": 30,
10
+ "statusMessage": "Checking PGS Compound Gate"
11
+ }
12
+ ]
13
+ }
14
+ ],
15
+ "SubagentStop": [
16
+ {
17
+ "hooks": [
18
+ {
19
+ "type": "command",
20
+ "command": "cd \"$(git rev-parse --show-toplevel)\" && if [ -x ./node_modules/.bin/pro-gov ]; then ./node_modules/.bin/pro-gov host-hook --host codex --event SubagentStop; elif [ -f packages/pro-gov/src/cli.ts ] && command -v pnpm >/dev/null 2>&1; then pnpm --silent --filter @pieai/pro-gov dev host-hook --host codex --event SubagentStop; else npx --no-install @pieai/pro-gov host-hook --host codex --event SubagentStop; fi",
21
+ "timeout": 30,
22
+ "statusMessage": "Checking PGS subagent Compound Gate"
23
+ }
24
+ ]
25
+ }
26
+ ]
27
+ }
28
+ }
@@ -39,6 +39,7 @@
39
39
  - Point to the chosen agents-routing file from `docs/governance/agents-routing/`.
40
40
  - Engineering projects may use Superpowers, Directed Development, GStack, or other external workflows only inside the selected lane; do not copy upstream integration guides into the target project by default.
41
41
  - For engineering projects, Superpowers is the default engineering workflow. Compound Engineering is used by default only as the post-work Compound Gate through `ce-compound`; full CE workflows require an explicit user request.
42
+ - Engineering projects should have PGS Stop hooks wired for Codex, Claude Code, and Antigravity. Before final reporting after completed engineering work, report `Compound Gate: ran ce-compound -> <path>` or `Compound Gate: skipped -> <reason>`.
42
43
  - Doc-only projects should say that Superpowers TDD and Directed Development are not enabled by default.
43
44
  - External workflow systems such as Superpowers or GStack run inside the lane selected by this router. They must not replace this project router.
44
45
 
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(packagedAssetsRoot) ? packagedAssetsRoot : sourceRoot;
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
- return missing.length > 0 ? 1 : 0;
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,9 +1608,276 @@ 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 readFileSync10 } from "node:fs";
1640
+
1641
+ // src/host-hooks/host-hook-runner.ts
1642
+ import { closeSync, openSync, readFileSync as readFileSync9, readSync, statSync as statSync3 } from "node:fs";
1643
+ var gateMarkerPattern = /Compound Gate:\s*(ran ce-compound|skipped)\s*->/i;
1644
+ var completionSignalPatterns = [
1645
+ /\b(done|completed|implemented|fixed|verified|validated|shipped|pushed|committed)\b/i,
1646
+ /\b(tests?|typecheck|build|lint|doctor|pack)\b.*\b(pass|passed|green|succeed|succeeded|ok)\b/i,
1647
+ /\b(changed|updated|modified|created|deleted|refactored)\b.*\b(files?|docs?|tests?|hooks?|configs?)\b/i,
1648
+ /完成|已完成|修好了|实现了|验证通过|测试通过|已经提交|已经推送|提交并推送/
1649
+ ];
1650
+ var compoundGateInstruction = [
1651
+ "Before final reporting, pass the PGS Compound Gate.",
1652
+ "If this completed work produced reusable learning, run compound-engineering:ce-compound and report:",
1653
+ "Compound Gate: ran ce-compound -> <path>",
1654
+ "If there is no reusable learning, report:",
1655
+ "Compound Gate: skipped -> <reason>"
1656
+ ].join("\n");
1657
+ var maxTranscriptBytes = 2 * 1024 * 1024;
1658
+ function evaluateHostHook(request) {
1659
+ if (request.event !== "Stop" && request.event !== "SubagentStop") {
1660
+ return { action: "allow" };
1661
+ }
1662
+ const input = normalizeStopInput(request.input);
1663
+ if (input.stopHookActive) {
1664
+ return { action: "allow" };
1665
+ }
1666
+ if (!input.lastAssistantMessage) {
1667
+ return { action: "allow" };
1668
+ }
1669
+ if (gateMarkerPattern.test(input.lastAssistantMessage)) {
1670
+ return { action: "allow" };
1671
+ }
1672
+ if (!looksLikeCompletedEngineeringWork(input.lastAssistantMessage)) {
1673
+ return { action: "allow" };
1674
+ }
1675
+ return { action: "continue", reason: compoundGateInstruction };
1676
+ }
1677
+ function formatHostHookOutput(host, event, decision) {
1678
+ if (decision.action === "allow") {
1679
+ return {};
1680
+ }
1681
+ if (host === "codex") {
1682
+ if (event === "Stop" || event === "SubagentStop") {
1683
+ return { decision: "block", reason: decision.reason };
1684
+ }
1685
+ if (decision.action === "block") {
1686
+ return {
1687
+ hookSpecificOutput: {
1688
+ hookEventName: event,
1689
+ permissionDecision: "deny",
1690
+ permissionDecisionReason: decision.reason
1691
+ },
1692
+ systemMessage: decision.reason
1693
+ };
1694
+ }
1695
+ return { systemMessage: decision.reason };
1696
+ }
1697
+ if (host === "claude" || host === "claude-code") {
1698
+ if (event === "Stop" || event === "SubagentStop") {
1699
+ return { decision: "block", reason: decision.reason };
1700
+ }
1701
+ if (decision.action === "block") {
1702
+ return {
1703
+ hookSpecificOutput: {
1704
+ hookEventName: event,
1705
+ permissionDecision: "deny",
1706
+ permissionDecisionReason: decision.reason
1707
+ }
1708
+ };
1709
+ }
1710
+ return { systemMessage: decision.reason };
1711
+ }
1712
+ if (host === "antigravity") {
1713
+ if (event === "Stop" || event === "SubagentStop") {
1714
+ return { decision: "continue", reason: decision.reason };
1715
+ }
1716
+ if (decision.action === "block") {
1717
+ return { decision: "deny", reason: decision.reason };
1718
+ }
1719
+ return { decision: "allow", reason: decision.reason };
1720
+ }
1721
+ return {};
1722
+ }
1723
+ function normalizeStopInput(input) {
1724
+ if (!isRecord(input)) {
1725
+ return { lastAssistantMessage: void 0, stopHookActive: false };
1726
+ }
1727
+ return {
1728
+ lastAssistantMessage: findString(input, ["last_assistant_message", "lastAssistantMessage", "message", "text"]) ?? findLastAssistantMessageFromTranscript(findString(input, ["agent_transcript_path", "agentTranscriptPath"])) ?? findLastAssistantMessageFromTranscript(findString(input, ["transcript_path", "transcriptPath"])),
1729
+ stopHookActive: findBoolean(input, ["stop_hook_active", "stopHookActive"]) ?? false
1730
+ };
1731
+ }
1732
+ function looksLikeCompletedEngineeringWork(message) {
1733
+ return completionSignalPatterns.some((pattern) => pattern.test(message));
1734
+ }
1735
+ function findString(input, keys) {
1736
+ for (const key of keys) {
1737
+ const value = input[key];
1738
+ if (typeof value === "string" && value.trim().length > 0) {
1739
+ return value;
1740
+ }
1741
+ }
1742
+ return void 0;
1743
+ }
1744
+ function findBoolean(input, keys) {
1745
+ for (const key of keys) {
1746
+ const value = input[key];
1747
+ if (typeof value === "boolean") {
1748
+ return value;
1749
+ }
1750
+ }
1751
+ return void 0;
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
+ }
1833
+ function isRecord(value) {
1834
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1835
+ }
1836
+
1837
+ // src/host-hooks/types.ts
1838
+ function isHostHookHost(value) {
1839
+ return value === "antigravity" || value === "claude" || value === "claude-code" || value === "codex";
1840
+ }
1841
+ function isHostHookEvent(value) {
1842
+ return value === "PostToolUse" || value === "PreToolUse" || value === "Stop" || value === "SubagentStop" || value === "UserPromptSubmit";
1843
+ }
1844
+
1845
+ // src/commands/host-hook.ts
1846
+ function runHostHook(args) {
1847
+ const host = readOption(args, "--host");
1848
+ const event = readOption(args, "--event");
1849
+ if (!isHostHookHost(host)) {
1850
+ console.error("Expected --host <codex|claude-code|antigravity>");
1851
+ return 1;
1852
+ }
1853
+ if (!isHostHookEvent(event)) {
1854
+ console.error("Expected --event <Stop|SubagentStop|PreToolUse|PostToolUse|UserPromptSubmit>");
1855
+ return 1;
1856
+ }
1857
+ const input = readStdinJson();
1858
+ const decision = evaluateHostHook({ host, event, input });
1859
+ const output = formatHostHookOutput(host, event, decision);
1860
+ console.log(`${JSON.stringify(output)}
1861
+ `);
1862
+ return 0;
1863
+ }
1864
+ function readOption(args, name) {
1865
+ const index = args.indexOf(name);
1866
+ if (index < 0) return void 0;
1867
+ return args[index + 1];
1868
+ }
1869
+ function readStdinJson() {
1870
+ const raw = readFileSync10(0, "utf8").trim();
1871
+ if (!raw) return {};
1872
+ try {
1873
+ return JSON.parse(raw);
1874
+ } catch {
1875
+ return {};
1876
+ }
1877
+ }
1603
1878
 
1604
1879
  // src/commands/init.ts
1605
- import { existsSync as existsSync13, mkdirSync as mkdirSync4, readFileSync as readFileSync8, writeFileSync as writeFileSync3 } from "node:fs";
1880
+ import { existsSync as existsSync13, mkdirSync as mkdirSync4, readFileSync as readFileSync11, writeFileSync as writeFileSync3 } from "node:fs";
1606
1881
  import { basename as basename3, dirname as dirname7, join as join13 } from "node:path";
1607
1882
 
1608
1883
  // src/commands/shared.ts
@@ -1611,6 +1886,7 @@ function planStarterFiles(profile) {
1611
1886
  const targetPath = starterTargetPath(asset.path);
1612
1887
  if (!targetPath) return [];
1613
1888
  if (profile && isOtherProfileRouting(targetPath, profile)) return [];
1889
+ if (profile && isEngineeringOnlyGuardrail(targetPath) && profile !== "engineering-runtime") return [];
1614
1890
  return [
1615
1891
  {
1616
1892
  sourcePath: asset.path,
@@ -1633,6 +1909,9 @@ function classifyOwnership(targetPath) {
1633
1909
  function isOtherProfileRouting(targetPath, profile) {
1634
1910
  return targetPath.startsWith("docs/governance/agents-routing/") && targetPath !== `docs/governance/agents-routing/${profile}-v0.9.md`;
1635
1911
  }
1912
+ function isEngineeringOnlyGuardrail(targetPath) {
1913
+ return targetPath === ".codex/hooks.json" || targetPath === ".claude/settings.json" || targetPath === ".agents/hooks.json";
1914
+ }
1636
1915
  function starterTargetPath(sourcePath) {
1637
1916
  if (sourcePath === "starter/AGENTS.template.md") return "AGENTS.md";
1638
1917
  if (sourcePath === "starter/CLAUDE.template.md") return "CLAUDE.md";
@@ -1681,7 +1960,7 @@ function applyStarterFiles(files, profile) {
1681
1960
  for (const file of files) {
1682
1961
  const targetPath = join13(root, file.targetPath);
1683
1962
  mkdirSync4(dirname7(targetPath), { recursive: true });
1684
- const source = readFileSync8(file.absoluteSourcePath);
1963
+ const source = readFileSync11(file.absoluteSourcePath);
1685
1964
  const content = file.targetPath === "AGENTS.md" ? renderAgentsTemplate(source.toString("utf8"), basename3(root), profile) : source;
1686
1965
  writeFileSync3(targetPath, content);
1687
1966
  }
@@ -1715,7 +1994,7 @@ import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync5 } from "node:f
1715
1994
  import { dirname as dirname9 } from "node:path";
1716
1995
 
1717
1996
  // src/lens/audit.ts
1718
- import { existsSync as existsSync14, mkdirSync as mkdirSync5, readFileSync as readFileSync9, writeFileSync as writeFileSync4 } from "node:fs";
1997
+ import { existsSync as existsSync14, mkdirSync as mkdirSync5, readFileSync as readFileSync12, writeFileSync as writeFileSync4 } from "node:fs";
1719
1998
  import { basename as basename4, dirname as dirname8, join as join14 } from "node:path";
1720
1999
  var REQUIRED_ARTIFACTS = [
1721
2000
  "manifest.md",
@@ -1763,7 +2042,7 @@ function checkProjectLensAuditPackage(auditDir, options = {}) {
1763
2042
  }
1764
2043
  let contract;
1765
2044
  try {
1766
- contract = JSON.parse(readFileSync9(contractPath, "utf8"));
2045
+ contract = JSON.parse(readFileSync12(contractPath, "utf8"));
1767
2046
  } catch (error) {
1768
2047
  return {
1769
2048
  ok: false,
@@ -1801,7 +2080,7 @@ function checkProjectLensAuditPackage(auditDir, options = {}) {
1801
2080
  issues.push({ type: "missing-required-artifact", path: artifactPath });
1802
2081
  continue;
1803
2082
  }
1804
- const content = readFileSync9(absolutePath, "utf8");
2083
+ const content = readFileSync12(absolutePath, "utf8");
1805
2084
  if (isPendingArtifact(content)) {
1806
2085
  issues.push({ type: "artifact-not-complete", path: artifactPath });
1807
2086
  } else if (hasTemplateBody(content)) {
@@ -2061,7 +2340,7 @@ function bulletList(values) {
2061
2340
 
2062
2341
  // src/lens/scan.ts
2063
2342
  import { spawnSync as spawnSync3 } from "node:child_process";
2064
- import { existsSync as existsSync15, readdirSync as readdirSync6, readFileSync as readFileSync10, statSync as statSync3 } from "node:fs";
2343
+ import { existsSync as existsSync15, readdirSync as readdirSync6, readFileSync as readFileSync13, statSync as statSync4 } from "node:fs";
2065
2344
  import { join as join15, relative as relative5 } from "node:path";
2066
2345
  var ignoredDirectories = /* @__PURE__ */ new Set([
2067
2346
  ".git",
@@ -2089,14 +2368,14 @@ function scanProjectLensTarget(targetDir, options = {}) {
2089
2368
  governanceFiles: markdownFiles.filter((file) => file.startsWith("docs/governance/") || file.startsWith("docs/policy/")).sort()
2090
2369
  },
2091
2370
  git: readGitState(targetDir),
2092
- largeFiles: files.map((file) => ({ path: file, bytes: statSync3(join15(targetDir, file)).size })).filter((file) => file.bytes >= largeFileBytes).sort((a, b) => b.bytes - a.bytes || a.path.localeCompare(b.path)).slice(0, 25)
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)
2093
2372
  };
2094
2373
  }
2095
2374
  function readPackageJson(targetDir) {
2096
2375
  const packageJsonPath = join15(targetDir, "package.json");
2097
2376
  if (!existsSync15(packageJsonPath)) return void 0;
2098
2377
  try {
2099
- const packageJson = JSON.parse(readFileSync10(packageJsonPath, "utf8"));
2378
+ const packageJson = JSON.parse(readFileSync13(packageJsonPath, "utf8"));
2100
2379
  return {
2101
2380
  scripts: Object.keys(packageJson.scripts ?? {}).sort(),
2102
2381
  dependencies: Object.keys(packageJson.dependencies ?? {}).sort(),
@@ -2297,12 +2576,12 @@ import { existsSync as existsSync17 } from "node:fs";
2297
2576
  import { join as join16 } from "node:path";
2298
2577
 
2299
2578
  // src/portfolio/manifest.ts
2300
- import { existsSync as existsSync16, readFileSync as readFileSync11 } from "node:fs";
2579
+ import { existsSync as existsSync16, readFileSync as readFileSync14 } from "node:fs";
2301
2580
  import { dirname as dirname10, isAbsolute as isAbsolute3, resolve as resolve2 } from "node:path";
2302
2581
  function loadPortfolioManifest(configPath) {
2303
2582
  let parsed;
2304
2583
  try {
2305
- parsed = JSON.parse(readFileSync11(configPath, "utf8"));
2584
+ parsed = JSON.parse(readFileSync14(configPath, "utf8"));
2306
2585
  } catch (error) {
2307
2586
  return {
2308
2587
  configPath,
@@ -2323,9 +2602,9 @@ function loadPortfolioManifest(configPath) {
2323
2602
  };
2324
2603
  }
2325
2604
  function resolveManifestPaths(value, configDir) {
2326
- if (!isRecord(value)) return value;
2605
+ if (!isRecord2(value)) return value;
2327
2606
  const resolveEndpoint = (endpoint) => {
2328
- if (!isRecord(endpoint) || typeof endpoint.path !== "string" || isAbsolute3(endpoint.path)) {
2607
+ if (!isRecord2(endpoint) || typeof endpoint.path !== "string" || isAbsolute3(endpoint.path)) {
2329
2608
  return endpoint;
2330
2609
  }
2331
2610
  return { ...endpoint, path: resolve2(configDir, endpoint.path) };
@@ -2339,7 +2618,7 @@ function resolveManifestPaths(value, configDir) {
2339
2618
  }
2340
2619
  function validatePortfolioManifest(value) {
2341
2620
  const issues = [];
2342
- if (!isRecord(value)) {
2621
+ if (!isRecord2(value)) {
2343
2622
  return [
2344
2623
  {
2345
2624
  type: "invalid-field",
@@ -2375,7 +2654,7 @@ function validatePortfolioManifest(value) {
2375
2654
  }
2376
2655
  const seenTargetIds = /* @__PURE__ */ new Set();
2377
2656
  for (const target of value.targets) {
2378
- if (!isRecord(target)) {
2657
+ if (!isRecord2(target)) {
2379
2658
  issues.push({
2380
2659
  type: "invalid-field",
2381
2660
  field: "targets",
@@ -2432,7 +2711,7 @@ function validateAllowedFields(value, location, allowedFields, issues) {
2432
2711
  }
2433
2712
  function validateEndpoint(value, field, issues) {
2434
2713
  if (value === void 0) return;
2435
- if (!isRecord(value)) {
2714
+ if (!isRecord2(value)) {
2436
2715
  issues.push({
2437
2716
  type: "invalid-field",
2438
2717
  field,
@@ -2476,7 +2755,7 @@ function validateOptionalStringArray(value, id, field, issues) {
2476
2755
  });
2477
2756
  }
2478
2757
  }
2479
- function isRecord(value) {
2758
+ function isRecord2(value) {
2480
2759
  return typeof value === "object" && value !== null && !Array.isArray(value);
2481
2760
  }
2482
2761
 
@@ -2742,7 +3021,7 @@ function printUsage3() {
2742
3021
  }
2743
3022
 
2744
3023
  // src/commands/sync.ts
2745
- import { existsSync as existsSync18, readFileSync as readFileSync12 } from "node:fs";
3024
+ import { existsSync as existsSync18, readFileSync as readFileSync15 } from "node:fs";
2746
3025
  import { join as join17 } from "node:path";
2747
3026
  function runSync(args) {
2748
3027
  if (!args.includes("--check")) {
@@ -2778,8 +3057,8 @@ function runSync(args) {
2778
3057
  continue;
2779
3058
  }
2780
3059
  if (file.ownership === "project-local-seed") continue;
2781
- const source = readFileSync12(file.absoluteSourcePath, "utf8");
2782
- const target = readFileSync12(targetPath, "utf8");
3060
+ const source = readFileSync15(file.absoluteSourcePath, "utf8");
3061
+ const target = readFileSync15(targetPath, "utf8");
2783
3062
  if (source !== target) {
2784
3063
  console.log(`different: ${file.targetPath}`);
2785
3064
  differences += 1;
@@ -2822,6 +3101,7 @@ var COMMANDS = [
2822
3101
  "lens report --target <path> --out <path>",
2823
3102
  "lens audit init --target <path> --out <path>",
2824
3103
  "lens audit check --dir <path> [--json]",
3104
+ "host-hook --host <codex|claude-code|antigravity> --event <Stop|SubagentStop|...>",
2825
3105
  "init --profile <engineering-runtime|doc-only> <--dry-run|--apply>",
2826
3106
  "sync --check [--profile <engineering-runtime|doc-only>]",
2827
3107
  "doctor"
@@ -2836,6 +3116,8 @@ if (!command || command === "--help" || command === "-h") {
2836
3116
  process.exitCode = runLens(process.argv.slice(3));
2837
3117
  } else if (command === "portfolio") {
2838
3118
  process.exitCode = runPortfolio(process.argv.slice(3));
3119
+ } else if (command === "host-hook") {
3120
+ process.exitCode = runHostHook(process.argv.slice(3));
2839
3121
  } else if (command === "init") {
2840
3122
  process.exitCode = runInit(process.argv.slice(3));
2841
3123
  } else if (command === "sync") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pieai/pro-gov",
3
- "version": "0.3.8",
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.8"
38
+ "@pieai/doc-gov": "^0.3.10"
39
39
  },
40
40
  "devDependencies": {
41
41
  "@types/node": "24.13.2",