@pieai/pro-gov 0.3.8 → 0.3.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -0
- package/assets/profiles/engineering-runtime/profile.md +6 -0
- package/assets/starter/.agents/hooks.json +26 -0
- package/assets/starter/.claude/settings.json +26 -0
- package/assets/starter/.codex/hooks.json +28 -0
- package/assets/starter/AGENTS.template.md +1 -0
- package/dist/cli.js +221 -21
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -139,6 +139,19 @@ 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. Open a fresh AI session after installing
|
|
153
|
+
or changing hooks; old sessions may not reload host configuration.
|
|
154
|
+
|
|
142
155
|
Ponytail can be installed as an optional complexity adviser. Keep its global mode
|
|
143
156
|
`off`; test `lite` in one isolated task before considering a stronger mode.
|
|
144
157
|
Ponytail must not remove requested scope, tests, safety, accessibility, or proof.
|
|
@@ -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(
|
|
17
|
+
const root = existsSync(join(sourceRoot, "starter")) ? sourceRoot : packagedAssetsRoot;
|
|
18
18
|
return assetRoots.flatMap((assetRoot) => {
|
|
19
19
|
const absoluteRoot = join(root, assetRoot);
|
|
20
20
|
if (!existsSync(absoluteRoot)) return [];
|
|
@@ -1542,7 +1542,7 @@ function printNpxUsage() {
|
|
|
1542
1542
|
|
|
1543
1543
|
// src/commands/doctor.ts
|
|
1544
1544
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
1545
|
-
import { existsSync as existsSync12 } from "node:fs";
|
|
1545
|
+
import { existsSync as existsSync12, readFileSync as readFileSync8 } from "node:fs";
|
|
1546
1546
|
import { createRequire } from "node:module";
|
|
1547
1547
|
import { dirname as dirname6, join as join12 } from "node:path";
|
|
1548
1548
|
var REQUIRED_ASSETS = [
|
|
@@ -1554,9 +1554,11 @@ var REQUIRED_ASSETS = [
|
|
|
1554
1554
|
"profiles/doc-only/profile.md"
|
|
1555
1555
|
];
|
|
1556
1556
|
function runDoctor(_args) {
|
|
1557
|
+
const strictHooks = _args.includes("--strict-hooks");
|
|
1557
1558
|
const assets = listAssets();
|
|
1558
1559
|
const assetPaths = new Set(assets.map((asset) => asset.path));
|
|
1559
1560
|
const missing = REQUIRED_ASSETS.filter((assetPath) => !assetPaths.has(assetPath));
|
|
1561
|
+
const hookIssues = strictHooks ? checkStrictHostHooks(process.cwd()) : [];
|
|
1560
1562
|
console.log("pro-gov doctor");
|
|
1561
1563
|
console.log(`assets: ${assets.length}`);
|
|
1562
1564
|
if (missing.length > 0) {
|
|
@@ -1567,7 +1569,13 @@ function runDoctor(_args) {
|
|
|
1567
1569
|
console.log("assets: required project-governance assets found");
|
|
1568
1570
|
}
|
|
1569
1571
|
console.log(checkDocGov());
|
|
1570
|
-
|
|
1572
|
+
for (const issue of hookIssues) {
|
|
1573
|
+
console.error(issue);
|
|
1574
|
+
}
|
|
1575
|
+
if (strictHooks && hookIssues.length === 0) {
|
|
1576
|
+
console.log("host-hooks: PGS Compound Gate hooks wired");
|
|
1577
|
+
}
|
|
1578
|
+
return missing.length > 0 || hookIssues.length > 0 ? 1 : 0;
|
|
1571
1579
|
}
|
|
1572
1580
|
function checkDocGov() {
|
|
1573
1581
|
const fromPath = spawnSync2("doc-gov", ["--help"], {
|
|
@@ -1600,9 +1608,194 @@ function resolveDocGovDependencyCli() {
|
|
|
1600
1608
|
return null;
|
|
1601
1609
|
}
|
|
1602
1610
|
}
|
|
1611
|
+
function checkStrictHostHooks(root) {
|
|
1612
|
+
if (!isEngineeringRuntimeProject(root)) {
|
|
1613
|
+
return [];
|
|
1614
|
+
}
|
|
1615
|
+
const expected = [
|
|
1616
|
+
{ path: ".codex/hooks.json", host: "codex" },
|
|
1617
|
+
{ path: ".claude/settings.json", host: "claude-code" },
|
|
1618
|
+
{ path: ".agents/hooks.json", host: "antigravity" }
|
|
1619
|
+
];
|
|
1620
|
+
const issues = [];
|
|
1621
|
+
for (const entry of expected) {
|
|
1622
|
+
const absolutePath = join12(root, entry.path);
|
|
1623
|
+
if (!existsSync12(absolutePath)) {
|
|
1624
|
+
issues.push(`host-hooks: missing ${entry.path}`);
|
|
1625
|
+
continue;
|
|
1626
|
+
}
|
|
1627
|
+
const content = readFileSync8(absolutePath, "utf8");
|
|
1628
|
+
if (!content.includes("pro-gov host-hook") || !content.includes(`--host ${entry.host}`)) {
|
|
1629
|
+
issues.push(`host-hooks: ${entry.path} does not call pro-gov host-hook for ${entry.host}`);
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
return issues;
|
|
1633
|
+
}
|
|
1634
|
+
function isEngineeringRuntimeProject(root) {
|
|
1635
|
+
return existsSync12(join12(root, "docs/governance/agents-routing/engineering-runtime-v0.9.md"));
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
// src/commands/host-hook.ts
|
|
1639
|
+
import { readFileSync as readFileSync9 } from "node:fs";
|
|
1640
|
+
|
|
1641
|
+
// src/host-hooks/host-hook-runner.ts
|
|
1642
|
+
var gateMarkerPattern = /Compound Gate:\s*(ran ce-compound|skipped)\s*->/i;
|
|
1643
|
+
var completionSignalPatterns = [
|
|
1644
|
+
/\b(done|completed|implemented|fixed|verified|validated|shipped|pushed|committed)\b/i,
|
|
1645
|
+
/\b(tests?|typecheck|build|lint|doctor|pack)\b.*\b(pass|passed|green|succeed|succeeded|ok)\b/i,
|
|
1646
|
+
/\b(changed|updated|modified|created|deleted|refactored)\b.*\b(files?|docs?|tests?|hooks?|configs?)\b/i,
|
|
1647
|
+
/完成|已完成|修好了|实现了|验证通过|测试通过|已经提交|已经推送|提交并推送/
|
|
1648
|
+
];
|
|
1649
|
+
var compoundGateInstruction = [
|
|
1650
|
+
"Before final reporting, pass the PGS Compound Gate.",
|
|
1651
|
+
"If this completed work produced reusable learning, run compound-engineering:ce-compound and report:",
|
|
1652
|
+
"Compound Gate: ran ce-compound -> <path>",
|
|
1653
|
+
"If there is no reusable learning, report:",
|
|
1654
|
+
"Compound Gate: skipped -> <reason>"
|
|
1655
|
+
].join("\n");
|
|
1656
|
+
function evaluateHostHook(request) {
|
|
1657
|
+
if (request.event !== "Stop" && request.event !== "SubagentStop") {
|
|
1658
|
+
return { action: "allow" };
|
|
1659
|
+
}
|
|
1660
|
+
const input = normalizeStopInput(request.input);
|
|
1661
|
+
if (input.stopHookActive) {
|
|
1662
|
+
return { action: "allow" };
|
|
1663
|
+
}
|
|
1664
|
+
if (!input.lastAssistantMessage) {
|
|
1665
|
+
return { action: "allow" };
|
|
1666
|
+
}
|
|
1667
|
+
if (gateMarkerPattern.test(input.lastAssistantMessage)) {
|
|
1668
|
+
return { action: "allow" };
|
|
1669
|
+
}
|
|
1670
|
+
if (!looksLikeCompletedEngineeringWork(input.lastAssistantMessage)) {
|
|
1671
|
+
return { action: "allow" };
|
|
1672
|
+
}
|
|
1673
|
+
return { action: "continue", reason: compoundGateInstruction };
|
|
1674
|
+
}
|
|
1675
|
+
function formatHostHookOutput(host, event, decision) {
|
|
1676
|
+
if (decision.action === "allow") {
|
|
1677
|
+
return {};
|
|
1678
|
+
}
|
|
1679
|
+
if (host === "codex") {
|
|
1680
|
+
if (event === "Stop" || event === "SubagentStop") {
|
|
1681
|
+
return { decision: "block", reason: decision.reason };
|
|
1682
|
+
}
|
|
1683
|
+
if (decision.action === "block") {
|
|
1684
|
+
return {
|
|
1685
|
+
hookSpecificOutput: {
|
|
1686
|
+
hookEventName: event,
|
|
1687
|
+
permissionDecision: "deny",
|
|
1688
|
+
permissionDecisionReason: decision.reason
|
|
1689
|
+
},
|
|
1690
|
+
systemMessage: decision.reason
|
|
1691
|
+
};
|
|
1692
|
+
}
|
|
1693
|
+
return { systemMessage: decision.reason };
|
|
1694
|
+
}
|
|
1695
|
+
if (host === "claude" || host === "claude-code") {
|
|
1696
|
+
if (event === "Stop" || event === "SubagentStop") {
|
|
1697
|
+
return { ok: false, reason: decision.reason };
|
|
1698
|
+
}
|
|
1699
|
+
if (decision.action === "block") {
|
|
1700
|
+
return {
|
|
1701
|
+
hookSpecificOutput: {
|
|
1702
|
+
hookEventName: event,
|
|
1703
|
+
permissionDecision: "deny",
|
|
1704
|
+
permissionDecisionReason: decision.reason
|
|
1705
|
+
}
|
|
1706
|
+
};
|
|
1707
|
+
}
|
|
1708
|
+
return { systemMessage: decision.reason };
|
|
1709
|
+
}
|
|
1710
|
+
if (host === "antigravity") {
|
|
1711
|
+
if (event === "Stop" || event === "SubagentStop") {
|
|
1712
|
+
return { decision: "continue", reason: decision.reason };
|
|
1713
|
+
}
|
|
1714
|
+
if (decision.action === "block") {
|
|
1715
|
+
return { decision: "deny", reason: decision.reason };
|
|
1716
|
+
}
|
|
1717
|
+
return { decision: "allow", reason: decision.reason };
|
|
1718
|
+
}
|
|
1719
|
+
return {};
|
|
1720
|
+
}
|
|
1721
|
+
function normalizeStopInput(input) {
|
|
1722
|
+
if (!isRecord(input)) {
|
|
1723
|
+
return { lastAssistantMessage: void 0, stopHookActive: false };
|
|
1724
|
+
}
|
|
1725
|
+
return {
|
|
1726
|
+
lastAssistantMessage: findString(input, ["last_assistant_message", "lastAssistantMessage", "message", "text"]),
|
|
1727
|
+
stopHookActive: findBoolean(input, ["stop_hook_active", "stopHookActive"]) ?? false
|
|
1728
|
+
};
|
|
1729
|
+
}
|
|
1730
|
+
function looksLikeCompletedEngineeringWork(message) {
|
|
1731
|
+
return completionSignalPatterns.some((pattern) => pattern.test(message));
|
|
1732
|
+
}
|
|
1733
|
+
function findString(input, keys) {
|
|
1734
|
+
for (const key of keys) {
|
|
1735
|
+
const value = input[key];
|
|
1736
|
+
if (typeof value === "string" && value.trim().length > 0) {
|
|
1737
|
+
return value;
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1740
|
+
return void 0;
|
|
1741
|
+
}
|
|
1742
|
+
function findBoolean(input, keys) {
|
|
1743
|
+
for (const key of keys) {
|
|
1744
|
+
const value = input[key];
|
|
1745
|
+
if (typeof value === "boolean") {
|
|
1746
|
+
return value;
|
|
1747
|
+
}
|
|
1748
|
+
}
|
|
1749
|
+
return void 0;
|
|
1750
|
+
}
|
|
1751
|
+
function isRecord(value) {
|
|
1752
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1753
|
+
}
|
|
1754
|
+
|
|
1755
|
+
// src/host-hooks/types.ts
|
|
1756
|
+
function isHostHookHost(value) {
|
|
1757
|
+
return value === "antigravity" || value === "claude" || value === "claude-code" || value === "codex";
|
|
1758
|
+
}
|
|
1759
|
+
function isHostHookEvent(value) {
|
|
1760
|
+
return value === "PostToolUse" || value === "PreToolUse" || value === "Stop" || value === "SubagentStop" || value === "UserPromptSubmit";
|
|
1761
|
+
}
|
|
1762
|
+
|
|
1763
|
+
// src/commands/host-hook.ts
|
|
1764
|
+
function runHostHook(args) {
|
|
1765
|
+
const host = readOption(args, "--host");
|
|
1766
|
+
const event = readOption(args, "--event");
|
|
1767
|
+
if (!isHostHookHost(host)) {
|
|
1768
|
+
console.error("Expected --host <codex|claude-code|antigravity>");
|
|
1769
|
+
return 1;
|
|
1770
|
+
}
|
|
1771
|
+
if (!isHostHookEvent(event)) {
|
|
1772
|
+
console.error("Expected --event <Stop|SubagentStop|PreToolUse|PostToolUse|UserPromptSubmit>");
|
|
1773
|
+
return 1;
|
|
1774
|
+
}
|
|
1775
|
+
const input = readStdinJson();
|
|
1776
|
+
const decision = evaluateHostHook({ host, event, input });
|
|
1777
|
+
const output = formatHostHookOutput(host, event, decision);
|
|
1778
|
+
console.log(`${JSON.stringify(output)}
|
|
1779
|
+
`);
|
|
1780
|
+
return 0;
|
|
1781
|
+
}
|
|
1782
|
+
function readOption(args, name) {
|
|
1783
|
+
const index = args.indexOf(name);
|
|
1784
|
+
if (index < 0) return void 0;
|
|
1785
|
+
return args[index + 1];
|
|
1786
|
+
}
|
|
1787
|
+
function readStdinJson() {
|
|
1788
|
+
const raw = readFileSync9(0, "utf8").trim();
|
|
1789
|
+
if (!raw) return {};
|
|
1790
|
+
try {
|
|
1791
|
+
return JSON.parse(raw);
|
|
1792
|
+
} catch {
|
|
1793
|
+
return {};
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1603
1796
|
|
|
1604
1797
|
// src/commands/init.ts
|
|
1605
|
-
import { existsSync as existsSync13, mkdirSync as mkdirSync4, readFileSync as
|
|
1798
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync4, readFileSync as readFileSync10, writeFileSync as writeFileSync3 } from "node:fs";
|
|
1606
1799
|
import { basename as basename3, dirname as dirname7, join as join13 } from "node:path";
|
|
1607
1800
|
|
|
1608
1801
|
// src/commands/shared.ts
|
|
@@ -1611,6 +1804,7 @@ function planStarterFiles(profile) {
|
|
|
1611
1804
|
const targetPath = starterTargetPath(asset.path);
|
|
1612
1805
|
if (!targetPath) return [];
|
|
1613
1806
|
if (profile && isOtherProfileRouting(targetPath, profile)) return [];
|
|
1807
|
+
if (profile && isEngineeringOnlyGuardrail(targetPath) && profile !== "engineering-runtime") return [];
|
|
1614
1808
|
return [
|
|
1615
1809
|
{
|
|
1616
1810
|
sourcePath: asset.path,
|
|
@@ -1633,6 +1827,9 @@ function classifyOwnership(targetPath) {
|
|
|
1633
1827
|
function isOtherProfileRouting(targetPath, profile) {
|
|
1634
1828
|
return targetPath.startsWith("docs/governance/agents-routing/") && targetPath !== `docs/governance/agents-routing/${profile}-v0.9.md`;
|
|
1635
1829
|
}
|
|
1830
|
+
function isEngineeringOnlyGuardrail(targetPath) {
|
|
1831
|
+
return targetPath === ".codex/hooks.json" || targetPath === ".claude/settings.json" || targetPath === ".agents/hooks.json";
|
|
1832
|
+
}
|
|
1636
1833
|
function starterTargetPath(sourcePath) {
|
|
1637
1834
|
if (sourcePath === "starter/AGENTS.template.md") return "AGENTS.md";
|
|
1638
1835
|
if (sourcePath === "starter/CLAUDE.template.md") return "CLAUDE.md";
|
|
@@ -1681,7 +1878,7 @@ function applyStarterFiles(files, profile) {
|
|
|
1681
1878
|
for (const file of files) {
|
|
1682
1879
|
const targetPath = join13(root, file.targetPath);
|
|
1683
1880
|
mkdirSync4(dirname7(targetPath), { recursive: true });
|
|
1684
|
-
const source =
|
|
1881
|
+
const source = readFileSync10(file.absoluteSourcePath);
|
|
1685
1882
|
const content = file.targetPath === "AGENTS.md" ? renderAgentsTemplate(source.toString("utf8"), basename3(root), profile) : source;
|
|
1686
1883
|
writeFileSync3(targetPath, content);
|
|
1687
1884
|
}
|
|
@@ -1715,7 +1912,7 @@ import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync5 } from "node:f
|
|
|
1715
1912
|
import { dirname as dirname9 } from "node:path";
|
|
1716
1913
|
|
|
1717
1914
|
// src/lens/audit.ts
|
|
1718
|
-
import { existsSync as existsSync14, mkdirSync as mkdirSync5, readFileSync as
|
|
1915
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync5, readFileSync as readFileSync11, writeFileSync as writeFileSync4 } from "node:fs";
|
|
1719
1916
|
import { basename as basename4, dirname as dirname8, join as join14 } from "node:path";
|
|
1720
1917
|
var REQUIRED_ARTIFACTS = [
|
|
1721
1918
|
"manifest.md",
|
|
@@ -1763,7 +1960,7 @@ function checkProjectLensAuditPackage(auditDir, options = {}) {
|
|
|
1763
1960
|
}
|
|
1764
1961
|
let contract;
|
|
1765
1962
|
try {
|
|
1766
|
-
contract = JSON.parse(
|
|
1963
|
+
contract = JSON.parse(readFileSync11(contractPath, "utf8"));
|
|
1767
1964
|
} catch (error) {
|
|
1768
1965
|
return {
|
|
1769
1966
|
ok: false,
|
|
@@ -1801,7 +1998,7 @@ function checkProjectLensAuditPackage(auditDir, options = {}) {
|
|
|
1801
1998
|
issues.push({ type: "missing-required-artifact", path: artifactPath });
|
|
1802
1999
|
continue;
|
|
1803
2000
|
}
|
|
1804
|
-
const content =
|
|
2001
|
+
const content = readFileSync11(absolutePath, "utf8");
|
|
1805
2002
|
if (isPendingArtifact(content)) {
|
|
1806
2003
|
issues.push({ type: "artifact-not-complete", path: artifactPath });
|
|
1807
2004
|
} else if (hasTemplateBody(content)) {
|
|
@@ -2061,7 +2258,7 @@ function bulletList(values) {
|
|
|
2061
2258
|
|
|
2062
2259
|
// src/lens/scan.ts
|
|
2063
2260
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
2064
|
-
import { existsSync as existsSync15, readdirSync as readdirSync6, readFileSync as
|
|
2261
|
+
import { existsSync as existsSync15, readdirSync as readdirSync6, readFileSync as readFileSync12, statSync as statSync3 } from "node:fs";
|
|
2065
2262
|
import { join as join15, relative as relative5 } from "node:path";
|
|
2066
2263
|
var ignoredDirectories = /* @__PURE__ */ new Set([
|
|
2067
2264
|
".git",
|
|
@@ -2096,7 +2293,7 @@ function readPackageJson(targetDir) {
|
|
|
2096
2293
|
const packageJsonPath = join15(targetDir, "package.json");
|
|
2097
2294
|
if (!existsSync15(packageJsonPath)) return void 0;
|
|
2098
2295
|
try {
|
|
2099
|
-
const packageJson = JSON.parse(
|
|
2296
|
+
const packageJson = JSON.parse(readFileSync12(packageJsonPath, "utf8"));
|
|
2100
2297
|
return {
|
|
2101
2298
|
scripts: Object.keys(packageJson.scripts ?? {}).sort(),
|
|
2102
2299
|
dependencies: Object.keys(packageJson.dependencies ?? {}).sort(),
|
|
@@ -2297,12 +2494,12 @@ import { existsSync as existsSync17 } from "node:fs";
|
|
|
2297
2494
|
import { join as join16 } from "node:path";
|
|
2298
2495
|
|
|
2299
2496
|
// src/portfolio/manifest.ts
|
|
2300
|
-
import { existsSync as existsSync16, readFileSync as
|
|
2497
|
+
import { existsSync as existsSync16, readFileSync as readFileSync13 } from "node:fs";
|
|
2301
2498
|
import { dirname as dirname10, isAbsolute as isAbsolute3, resolve as resolve2 } from "node:path";
|
|
2302
2499
|
function loadPortfolioManifest(configPath) {
|
|
2303
2500
|
let parsed;
|
|
2304
2501
|
try {
|
|
2305
|
-
parsed = JSON.parse(
|
|
2502
|
+
parsed = JSON.parse(readFileSync13(configPath, "utf8"));
|
|
2306
2503
|
} catch (error) {
|
|
2307
2504
|
return {
|
|
2308
2505
|
configPath,
|
|
@@ -2323,9 +2520,9 @@ function loadPortfolioManifest(configPath) {
|
|
|
2323
2520
|
};
|
|
2324
2521
|
}
|
|
2325
2522
|
function resolveManifestPaths(value, configDir) {
|
|
2326
|
-
if (!
|
|
2523
|
+
if (!isRecord2(value)) return value;
|
|
2327
2524
|
const resolveEndpoint = (endpoint) => {
|
|
2328
|
-
if (!
|
|
2525
|
+
if (!isRecord2(endpoint) || typeof endpoint.path !== "string" || isAbsolute3(endpoint.path)) {
|
|
2329
2526
|
return endpoint;
|
|
2330
2527
|
}
|
|
2331
2528
|
return { ...endpoint, path: resolve2(configDir, endpoint.path) };
|
|
@@ -2339,7 +2536,7 @@ function resolveManifestPaths(value, configDir) {
|
|
|
2339
2536
|
}
|
|
2340
2537
|
function validatePortfolioManifest(value) {
|
|
2341
2538
|
const issues = [];
|
|
2342
|
-
if (!
|
|
2539
|
+
if (!isRecord2(value)) {
|
|
2343
2540
|
return [
|
|
2344
2541
|
{
|
|
2345
2542
|
type: "invalid-field",
|
|
@@ -2375,7 +2572,7 @@ function validatePortfolioManifest(value) {
|
|
|
2375
2572
|
}
|
|
2376
2573
|
const seenTargetIds = /* @__PURE__ */ new Set();
|
|
2377
2574
|
for (const target of value.targets) {
|
|
2378
|
-
if (!
|
|
2575
|
+
if (!isRecord2(target)) {
|
|
2379
2576
|
issues.push({
|
|
2380
2577
|
type: "invalid-field",
|
|
2381
2578
|
field: "targets",
|
|
@@ -2432,7 +2629,7 @@ function validateAllowedFields(value, location, allowedFields, issues) {
|
|
|
2432
2629
|
}
|
|
2433
2630
|
function validateEndpoint(value, field, issues) {
|
|
2434
2631
|
if (value === void 0) return;
|
|
2435
|
-
if (!
|
|
2632
|
+
if (!isRecord2(value)) {
|
|
2436
2633
|
issues.push({
|
|
2437
2634
|
type: "invalid-field",
|
|
2438
2635
|
field,
|
|
@@ -2476,7 +2673,7 @@ function validateOptionalStringArray(value, id, field, issues) {
|
|
|
2476
2673
|
});
|
|
2477
2674
|
}
|
|
2478
2675
|
}
|
|
2479
|
-
function
|
|
2676
|
+
function isRecord2(value) {
|
|
2480
2677
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2481
2678
|
}
|
|
2482
2679
|
|
|
@@ -2742,7 +2939,7 @@ function printUsage3() {
|
|
|
2742
2939
|
}
|
|
2743
2940
|
|
|
2744
2941
|
// src/commands/sync.ts
|
|
2745
|
-
import { existsSync as existsSync18, readFileSync as
|
|
2942
|
+
import { existsSync as existsSync18, readFileSync as readFileSync14 } from "node:fs";
|
|
2746
2943
|
import { join as join17 } from "node:path";
|
|
2747
2944
|
function runSync(args) {
|
|
2748
2945
|
if (!args.includes("--check")) {
|
|
@@ -2778,8 +2975,8 @@ function runSync(args) {
|
|
|
2778
2975
|
continue;
|
|
2779
2976
|
}
|
|
2780
2977
|
if (file.ownership === "project-local-seed") continue;
|
|
2781
|
-
const source =
|
|
2782
|
-
const target =
|
|
2978
|
+
const source = readFileSync14(file.absoluteSourcePath, "utf8");
|
|
2979
|
+
const target = readFileSync14(targetPath, "utf8");
|
|
2783
2980
|
if (source !== target) {
|
|
2784
2981
|
console.log(`different: ${file.targetPath}`);
|
|
2785
2982
|
differences += 1;
|
|
@@ -2822,6 +3019,7 @@ var COMMANDS = [
|
|
|
2822
3019
|
"lens report --target <path> --out <path>",
|
|
2823
3020
|
"lens audit init --target <path> --out <path>",
|
|
2824
3021
|
"lens audit check --dir <path> [--json]",
|
|
3022
|
+
"host-hook --host <codex|claude-code|antigravity> --event <Stop|SubagentStop|...>",
|
|
2825
3023
|
"init --profile <engineering-runtime|doc-only> <--dry-run|--apply>",
|
|
2826
3024
|
"sync --check [--profile <engineering-runtime|doc-only>]",
|
|
2827
3025
|
"doctor"
|
|
@@ -2836,6 +3034,8 @@ if (!command || command === "--help" || command === "-h") {
|
|
|
2836
3034
|
process.exitCode = runLens(process.argv.slice(3));
|
|
2837
3035
|
} else if (command === "portfolio") {
|
|
2838
3036
|
process.exitCode = runPortfolio(process.argv.slice(3));
|
|
3037
|
+
} else if (command === "host-hook") {
|
|
3038
|
+
process.exitCode = runHostHook(process.argv.slice(3));
|
|
2839
3039
|
} else if (command === "init") {
|
|
2840
3040
|
process.exitCode = runInit(process.argv.slice(3));
|
|
2841
3041
|
} else if (command === "sync") {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pieai/pro-gov",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.9",
|
|
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.9"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@types/node": "24.13.2",
|