@bookedsolid/rea 0.25.0 → 0.26.0

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
@@ -748,8 +748,8 @@ install so teams without a Codex bench get a first-class opt-out. Flip
748
748
 
749
749
  ## Hooks shipped
750
750
 
751
- Eleven hooks ship in `hooks/` and are copied into `.claude/hooks/` by
752
- `rea init`. All eleven are wired by default in the shipped
751
+ Fourteen hooks ship in `hooks/` and are copied into `.claude/hooks/` by
752
+ `rea init`. All fourteen are wired by default in the shipped
753
753
  `.claude/settings.json`.
754
754
 
755
755
  | Hook | Event | Purpose | Default |
@@ -760,11 +760,14 @@ Eleven hooks ship in `hooks/` and are copied into `.claude/hooks/` by
760
760
  | `security-disclosure-gate.sh` | `PreToolUse: Bash` | Route security-keyword `gh issue create` to private disclosure | Registered |
761
761
  | `pr-issue-link-gate.sh` | `PreToolUse: Bash` | Advisory warn when `gh pr create` has no linked issue | Registered |
762
762
  | `attribution-advisory.sh` | `PreToolUse: Bash` | Block commits/PRs containing AI attribution markers | Registered |
763
- | `secret-scanner.sh` | `PreToolUse: Write\|Edit` | Scan file writes for credential patterns | Registered |
764
- | `settings-protection.sh` | `PreToolUse: Write\|Edit` | Block agent writes to `.claude/settings.json`, hook dirs, policy | Registered |
765
- | `blocked-paths-enforcer.sh` | `PreToolUse: Write\|Edit` | Enforce `blocked_paths` from policy | Registered |
766
- | `changeset-security-gate.sh` | `PreToolUse: Write\|Edit` | Guard changesets against GHSA leaks and malformed frontmatter | Registered |
767
- | `architecture-review-gate.sh` | `PostToolUse: Write\|Edit` | Flag edits crossing architectural boundaries (advisory) | Registered |
763
+ | `protected-paths-bash-gate.sh` | `PreToolUse: Bash` | Bash-tier parity with `settings-protection.sh` — refuses shell writes to `.claude/`/`.husky/`/policy paths (0.21.0+) | Registered |
764
+ | `blocked-paths-bash-gate.sh` | `PreToolUse: Bash` | Bash-tier parity with `blocked-paths-enforcer.sh` — refuses shell writes to `blocked_paths` policy entries (0.22.0+) | Registered |
765
+ | `local-review-gate.sh` | `PreToolUse: Bash` | Refuse `git push` (and optionally `git commit`) until a recent `rea.local_review` audit entry covers HEAD (0.26.0+) | Registered |
766
+ | `secret-scanner.sh` | `PreToolUse: Write\|Edit\|MultiEdit\|NotebookEdit` | Scan file writes for credential patterns | Registered |
767
+ | `settings-protection.sh` | `PreToolUse: Write\|Edit\|MultiEdit\|NotebookEdit` | Block agent writes to `.claude/settings.json`, hook dirs, policy | Registered |
768
+ | `blocked-paths-enforcer.sh` | `PreToolUse: Write\|Edit\|MultiEdit\|NotebookEdit` | Enforce `blocked_paths` from policy | Registered |
769
+ | `changeset-security-gate.sh` | `PreToolUse: Write\|Edit\|MultiEdit\|NotebookEdit` | Guard changesets against GHSA leaks and malformed frontmatter | Registered |
770
+ | `architecture-review-gate.sh` | `PostToolUse: Write\|Edit\|MultiEdit\|NotebookEdit` | Flag edits crossing architectural boundaries (advisory) | Registered |
768
771
 
769
772
  The 0.10.x review-gate scripts (`push-review-gate.sh`,
770
773
  `push-review-gate-git.sh`, `commit-review-gate.sh`) and the 1,250-line
@@ -5,8 +5,12 @@ description: Adversarial code review via the Codex plugin (GPT-5.4). Independent
5
5
 
6
6
  # Codex Adversarial Reviewer
7
7
 
8
+ Run on the working tree before commit. Never let the push-gate be the first time codex sees the diff. Write the audit entry via `rea review` so the preflight gate accepts the push.
9
+
8
10
  You wrap the Codex plugin (`/codex:adversarial-review`) inside REA's governance envelope. Your role is to provide an **independent** adversarial perspective on code that was planned and built by another model — typically Opus. Independence is the value: the authoring model is least likely to catch the mistakes it made.
9
11
 
12
+ As of 0.26.0 (CTO directive 2026-05-05) this review is a forceful step — the Bash-tier `local-review-gate.sh` hook + husky pre-push refuse `git push` when no recent `rea.local_review` audit entry covers HEAD. The cleanest gate-friendly invocation is `rea review`, which runs codex on the working tree and writes the canonical audit entry. The interactive `/codex-review` form is still useful for structured exploratory feedback, but it does NOT write the audit entry the gate looks for.
13
+
10
14
  This is not a bolt-on. Adversarial review is a first-class, non-optional step in the REA engineering process. The default workflow is Plan → Build → Review, and you are the Review leg.
11
15
 
12
16
  ## When You Are Invoked
@@ -14,6 +14,15 @@ You are the REA orchestrator. Your role is to enforce the project's governance c
14
14
  3. Verify the requested task falls within the current autonomy level
15
15
  4. If the task exceeds autonomy, escalate to the user — do not attempt workarounds
16
16
 
17
+ ## Before Dispatching Commit / Push
18
+
19
+ The local-first guardrail (CTO directive 2026-05-05) is forceful as of 0.26.0. Before delegating any commit-or-push step:
20
+
21
+ 1. Ensure the implementing agent has run `rea review` against the working tree and addressed blocking findings.
22
+ 2. The agent's `git push` will be refused at the Bash-tier `local-review-gate.sh` hook unless a recent `rea.local_review` audit entry covers HEAD. Plan for review BEFORE commit, not after.
23
+ 3. If the consumer team has set `policy.review.local_review.mode: off`, the gate is a no-op — proceed normally. Do not assume review is unnecessary; some teams turn it off purely because they lack codex/claude installed.
24
+ 4. The push-gate is a BACKUP layer, not the primary review surface. Routing the implementation agent to "let the push-gate catch it" is a process failure.
25
+
17
26
  ## Autonomy Levels
18
27
 
19
28
  - **L0** — Read-only. Every write requires explicit user approval. Ask before any file change.
@@ -14,6 +14,10 @@ allowed-tools:
14
14
 
15
15
  Invokes the Codex plugin (`/codex:adversarial-review`) on the current branch's diff, captures the result, and records it to the REA audit log. Adversarial review by an independent model (GPT-5.4) is a **first-class, non-optional step** in the REA engineering process — it is the counterweight to Opus-authored code.
16
16
 
17
+ ## When to run
18
+
19
+ **Default: working tree before commit.** As of 0.26.0 (CTO directive 2026-05-05) the local-first guardrail is forceful — the Bash-tier `local-review-gate.sh` + husky pre-push refuse `git push` when no recent `rea.local_review` audit entry covers HEAD. Running `/codex-review` produces structured exploratory feedback but does NOT write the audit entry the gate looks for. For the gate-friendly form, use `rea review` from a Bash invocation — it runs codex on the working tree AND writes the canonical audit entry. `/codex-review` remains the interactive surface; `rea review` is the gate-aligned automation.
20
+
17
21
  ## Why this exists
18
22
 
19
23
  The default workflow in REA is Plan → Build → Review, with the Review leg handed to a different model than the one that wrote the code. Codex adversarial review is free, fast, and independent — it catches the mistakes the authoring model is most likely to miss: security assumptions, correctness under edge cases, and logical gaps in tests. Treat it with the same weight as a human second set of eyes.
@@ -82,3 +82,4 @@ export declare function appendAuditRecord(baseDir: string, input: AppendAuditInp
82
82
  export type { AuditRecord, EmissionSource } from '../gateway/middleware/audit-types.js';
83
83
  export { Tier, InvocationStatus } from '../policy/types.js';
84
84
  export { CODEX_REVIEW_TOOL_NAME, CODEX_REVIEW_SERVER_NAME, type CodexVerdict, type CodexReviewMetadata, } from './codex-event.js';
85
+ export { LOCAL_REVIEW_TOOL_NAME, LOCAL_REVIEW_SKIPPED_OVERRIDE_TOOL_NAME, LOCAL_REVIEW_SKIPPED_UNAVAILABLE_TOOL_NAME, LOCAL_REVIEW_PREFLIGHT_SKIPPED_TOOL_NAME, LOCAL_REVIEW_SERVER_NAME, type LocalReviewVerdict, type LocalReviewMetadata, type LocalReviewSkippedOverrideMetadata, type LocalReviewSkippedUnavailableMetadata, } from './local-review-event.js';
@@ -203,3 +203,4 @@ export async function appendAuditRecord(baseDir, input) {
203
203
  }
204
204
  export { Tier, InvocationStatus } from '../policy/types.js';
205
205
  export { CODEX_REVIEW_TOOL_NAME, CODEX_REVIEW_SERVER_NAME, } from './codex-event.js';
206
+ export { LOCAL_REVIEW_TOOL_NAME, LOCAL_REVIEW_SKIPPED_OVERRIDE_TOOL_NAME, LOCAL_REVIEW_SKIPPED_UNAVAILABLE_TOOL_NAME, LOCAL_REVIEW_PREFLIGHT_SKIPPED_TOOL_NAME, LOCAL_REVIEW_SERVER_NAME, } from './local-review-event.js';
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Content-token computation for the local-first review audit trail.
3
+ *
4
+ * 0.26.0 P1 fix (helix-026, codex finding 1): `rea preflight` originally
5
+ * keyed reviews to `git rev-parse HEAD`. The local-first flow is
6
+ * "working tree → review → fix → commit → push" — by design the COMMIT
7
+ * happens AFTER the review, so HEAD changes between them. Matching by
8
+ * `head_sha` guaranteed a stale lookup and broke the advertised loop.
9
+ *
10
+ * The fix is to record a token tied to the CONTENT codex actually
11
+ * reviewed instead of the commit it sat on top of.
12
+ *
13
+ * 0.26.0 round-25 P1-A fix: codex `exec review` diffs the WORKING TREE
14
+ * against base — meaning the token must reflect the working tree (with
15
+ * any uncommitted edits), NOT `HEAD^{tree}`. Pre-fix the token was
16
+ * computed via `git rev-parse HEAD^{tree}` — the LAST COMMIT's tree,
17
+ * not the working-tree state codex actually reviewed. The documented
18
+ * happy path is `edit → review → fix → commit → push`; after commit
19
+ * HEAD changes, preflight's token doesn't match the audit entry's
20
+ * token, and the gate REFUSES the very flow it documents.
21
+ *
22
+ * Fix: compute the WORKING-TREE token via `git stash create`. That
23
+ * command returns the SHA of a commit object whose tree is the
24
+ * working-tree + index merged. `git rev-parse <stash-sha>^{tree}` then
25
+ * gives a deterministic fingerprint of what codex actually reviewed:
26
+ *
27
+ * - At review time: working tree dirty → token = Tw (working-tree tree).
28
+ * - Operator commits: HEAD becomes B with tree Tb. Tb == Tw because
29
+ * `git commit` just persists the index, not new content.
30
+ * - Push time: working tree CLEAN, `git stash create` returns empty,
31
+ * fall back to `HEAD^{tree}` = Tb == Tw. Match. Allow.
32
+ *
33
+ * Stable behaviors preserved from the 0.26.0 finding-1 fix:
34
+ * - Stable across `git commit --amend` with no content change
35
+ * - Stable across rebases that don't touch content (fixup, reword)
36
+ * - Differs immediately on any real content edit
37
+ *
38
+ * NOTE: untracked AND `.gitignore`'d content is by design NOT part of
39
+ * the token, because:
40
+ * - `git stash create` does not include untracked files (without `-u`,
41
+ * and even `-u` excludes ignored files).
42
+ * - `git push` cannot transmit them either.
43
+ * So the token reflects what would actually be pushed. Codex review
44
+ * follows the same `git diff` semantics.
45
+ *
46
+ * The audit record continues to record `head_sha` for forensics —
47
+ * `content_token` is what `rea preflight` matches on. Legacy
48
+ * `codex.review` audit entries (pre-0.26.0) only have `head_sha`;
49
+ * preflight falls back to head-sha matching for those.
50
+ */
51
+ /**
52
+ * Git's well-known "empty tree" object SHA — the SHA-1 of an empty tree
53
+ * object, identical across every git repository on Earth.
54
+ *
55
+ * Round-27 F2 fix: this constant exists so the unborn-HEAD bootstrap path
56
+ * is symmetric between writer (`rea review` audit-record `head_sha`) and
57
+ * reader (`rea preflight` HEAD probe). Pre-fix:
58
+ *
59
+ * - `rea review` (writer) used `EMPTY_TREE_SHA` as the synthetic head
60
+ * when HEAD couldn't be resolved (round-25 P2-B).
61
+ * - `rea preflight` (reader) returned `''` for headSha AND empty
62
+ * contentToken on unborn HEAD, then refused with a both-empty guard.
63
+ *
64
+ * The asymmetry deadlocked the bootstrap flow `git init` →
65
+ * `rea review` → `rea preflight` under `refuse_at: both`. Both sides
66
+ * now reference THIS constant when HEAD cannot be resolved, so the
67
+ * head_sha-fallback path in `findRecentLocalReview` matches uniformly.
68
+ */
69
+ export declare const EMPTY_TREE_SHA = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
70
+ /**
71
+ * Compute a deterministic content token for the working tree.
72
+ *
73
+ * Returns `''` (empty string) when the tree token cannot be resolved
74
+ * (no git repo, no HEAD, etc.). Callers MUST treat empty token as
75
+ * "no token" — the audit record's `content_token` field should be
76
+ * omitted in that case so `rea preflight` does not match a missing
77
+ * token against a missing token (which would be a trivial bypass).
78
+ *
79
+ * The token is the SHA of the working-tree tree object (or HEAD's
80
+ * tree object if the working tree is clean). Format: 40-char lowercase
81
+ * hex (or 64-char if the repo uses SHA-256). The function does not
82
+ * normalize or truncate — `rea preflight` does an exact-string match.
83
+ *
84
+ * Resolution order (round-25 P1-A):
85
+ * 1. `git stash create` — non-empty stdout means there are tracked
86
+ * changes. The stash SHA is a commit object; its tree is the
87
+ * working-tree + index merged. Use `<sha>^{tree}` as treeSource.
88
+ * `git stash create` does NOT modify the stash list, working tree,
89
+ * or index — it only synthesizes a commit object pointing at the
90
+ * current state.
91
+ * 2. Empty stdout from `git stash create` (or non-zero exit) → tree
92
+ * is clean OR repo is empty. Fall back to `HEAD^{tree}`. A clean
93
+ * working tree's tree object is identical to HEAD's tree by
94
+ * definition.
95
+ * 3. Both fail → return empty string. `head_sha` fallback in
96
+ * preflight takes over.
97
+ */
98
+ export declare function computeTreeToken(cwd: string): string;
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Content-token computation for the local-first review audit trail.
3
+ *
4
+ * 0.26.0 P1 fix (helix-026, codex finding 1): `rea preflight` originally
5
+ * keyed reviews to `git rev-parse HEAD`. The local-first flow is
6
+ * "working tree → review → fix → commit → push" — by design the COMMIT
7
+ * happens AFTER the review, so HEAD changes between them. Matching by
8
+ * `head_sha` guaranteed a stale lookup and broke the advertised loop.
9
+ *
10
+ * The fix is to record a token tied to the CONTENT codex actually
11
+ * reviewed instead of the commit it sat on top of.
12
+ *
13
+ * 0.26.0 round-25 P1-A fix: codex `exec review` diffs the WORKING TREE
14
+ * against base — meaning the token must reflect the working tree (with
15
+ * any uncommitted edits), NOT `HEAD^{tree}`. Pre-fix the token was
16
+ * computed via `git rev-parse HEAD^{tree}` — the LAST COMMIT's tree,
17
+ * not the working-tree state codex actually reviewed. The documented
18
+ * happy path is `edit → review → fix → commit → push`; after commit
19
+ * HEAD changes, preflight's token doesn't match the audit entry's
20
+ * token, and the gate REFUSES the very flow it documents.
21
+ *
22
+ * Fix: compute the WORKING-TREE token via `git stash create`. That
23
+ * command returns the SHA of a commit object whose tree is the
24
+ * working-tree + index merged. `git rev-parse <stash-sha>^{tree}` then
25
+ * gives a deterministic fingerprint of what codex actually reviewed:
26
+ *
27
+ * - At review time: working tree dirty → token = Tw (working-tree tree).
28
+ * - Operator commits: HEAD becomes B with tree Tb. Tb == Tw because
29
+ * `git commit` just persists the index, not new content.
30
+ * - Push time: working tree CLEAN, `git stash create` returns empty,
31
+ * fall back to `HEAD^{tree}` = Tb == Tw. Match. Allow.
32
+ *
33
+ * Stable behaviors preserved from the 0.26.0 finding-1 fix:
34
+ * - Stable across `git commit --amend` with no content change
35
+ * - Stable across rebases that don't touch content (fixup, reword)
36
+ * - Differs immediately on any real content edit
37
+ *
38
+ * NOTE: untracked AND `.gitignore`'d content is by design NOT part of
39
+ * the token, because:
40
+ * - `git stash create` does not include untracked files (without `-u`,
41
+ * and even `-u` excludes ignored files).
42
+ * - `git push` cannot transmit them either.
43
+ * So the token reflects what would actually be pushed. Codex review
44
+ * follows the same `git diff` semantics.
45
+ *
46
+ * The audit record continues to record `head_sha` for forensics —
47
+ * `content_token` is what `rea preflight` matches on. Legacy
48
+ * `codex.review` audit entries (pre-0.26.0) only have `head_sha`;
49
+ * preflight falls back to head-sha matching for those.
50
+ */
51
+ import { spawnSync } from 'node:child_process';
52
+ /**
53
+ * Git's well-known "empty tree" object SHA — the SHA-1 of an empty tree
54
+ * object, identical across every git repository on Earth.
55
+ *
56
+ * Round-27 F2 fix: this constant exists so the unborn-HEAD bootstrap path
57
+ * is symmetric between writer (`rea review` audit-record `head_sha`) and
58
+ * reader (`rea preflight` HEAD probe). Pre-fix:
59
+ *
60
+ * - `rea review` (writer) used `EMPTY_TREE_SHA` as the synthetic head
61
+ * when HEAD couldn't be resolved (round-25 P2-B).
62
+ * - `rea preflight` (reader) returned `''` for headSha AND empty
63
+ * contentToken on unborn HEAD, then refused with a both-empty guard.
64
+ *
65
+ * The asymmetry deadlocked the bootstrap flow `git init` →
66
+ * `rea review` → `rea preflight` under `refuse_at: both`. Both sides
67
+ * now reference THIS constant when HEAD cannot be resolved, so the
68
+ * head_sha-fallback path in `findRecentLocalReview` matches uniformly.
69
+ */
70
+ export const EMPTY_TREE_SHA = '4b825dc642cb6eb9a060e54bf8d69288fbee4904';
71
+ /**
72
+ * Compute a deterministic content token for the working tree.
73
+ *
74
+ * Returns `''` (empty string) when the tree token cannot be resolved
75
+ * (no git repo, no HEAD, etc.). Callers MUST treat empty token as
76
+ * "no token" — the audit record's `content_token` field should be
77
+ * omitted in that case so `rea preflight` does not match a missing
78
+ * token against a missing token (which would be a trivial bypass).
79
+ *
80
+ * The token is the SHA of the working-tree tree object (or HEAD's
81
+ * tree object if the working tree is clean). Format: 40-char lowercase
82
+ * hex (or 64-char if the repo uses SHA-256). The function does not
83
+ * normalize or truncate — `rea preflight` does an exact-string match.
84
+ *
85
+ * Resolution order (round-25 P1-A):
86
+ * 1. `git stash create` — non-empty stdout means there are tracked
87
+ * changes. The stash SHA is a commit object; its tree is the
88
+ * working-tree + index merged. Use `<sha>^{tree}` as treeSource.
89
+ * `git stash create` does NOT modify the stash list, working tree,
90
+ * or index — it only synthesizes a commit object pointing at the
91
+ * current state.
92
+ * 2. Empty stdout from `git stash create` (or non-zero exit) → tree
93
+ * is clean OR repo is empty. Fall back to `HEAD^{tree}`. A clean
94
+ * working tree's tree object is identical to HEAD's tree by
95
+ * definition.
96
+ * 3. Both fail → return empty string. `head_sha` fallback in
97
+ * preflight takes over.
98
+ */
99
+ export function computeTreeToken(cwd) {
100
+ // Step 1: try `git stash create`. Width-preserved by design — never
101
+ // touches the stash list, working tree, or index.
102
+ const stashResult = spawnSync('git', ['stash', 'create'], {
103
+ cwd,
104
+ encoding: 'utf8',
105
+ });
106
+ let treeSource;
107
+ if (stashResult.status === 0 && (stashResult.stdout ?? '').toString().trim().length > 0) {
108
+ const stashSha = (stashResult.stdout ?? '').toString().trim();
109
+ // Defensive: only accept hex SHA shapes — otherwise treat as
110
+ // unresolvable and fall through to HEAD^{tree}.
111
+ if (!/^[0-9a-f]{40,64}$/.test(stashSha)) {
112
+ treeSource = 'HEAD^{tree}';
113
+ }
114
+ else {
115
+ treeSource = `${stashSha}^{tree}`;
116
+ }
117
+ }
118
+ else {
119
+ // Step 2: clean working tree OR empty repo — clean tree's content
120
+ // is identical to HEAD^{tree} by definition; resolve that. Empty
121
+ // repo will fail in step 3 below and return empty string.
122
+ treeSource = 'HEAD^{tree}';
123
+ }
124
+ const treeResult = spawnSync('git', ['rev-parse', treeSource], {
125
+ cwd,
126
+ encoding: 'utf8',
127
+ });
128
+ if (treeResult.status !== 0)
129
+ return '';
130
+ const out = (treeResult.stdout ?? '').toString().trim();
131
+ // Defensive: only accept hex strings — git's tree SHA is always hex.
132
+ // This rejects unexpected output (error messages, ANSI noise, etc.).
133
+ if (!/^[0-9a-f]{40,64}$/.test(out))
134
+ return '';
135
+ return out;
136
+ }
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Single source of truth for the `rea.local_review` audit event shape
3
+ * (0.26.0+).
4
+ *
5
+ * The local-first delegation enforcement (CTO directive 2026-05-05)
6
+ * replaces the soft "memory rule + orchestrator routing convention"
7
+ * with three forceful enforcement layers:
8
+ *
9
+ * 1. Bash-tier PreToolUse hook (hooks/local-review-gate.sh) — refuses
10
+ * `git push`/`git commit` from the agent's Bash tool.
11
+ * 2. Husky pre-push (`exec rea preflight --strict`) — refuses git push
12
+ * at the terminal layer.
13
+ * 3. `rea preflight` CLI — the workhorse all enforcement layers call.
14
+ *
15
+ * `rea review` writes a `rea.local_review` audit entry. `rea preflight`
16
+ * reads the audit log, finds the most recent matching entry for HEAD,
17
+ * and exits 0/1/2 accordingly.
18
+ *
19
+ * # Provider seam
20
+ *
21
+ * The `provider` field is the LIGHT seam for future review providers
22
+ * (Claude-subagent, Pi, Gemma). Today the only writer is `rea review`
23
+ * with `provider: 'codex'`. Tomorrow a different provider writes the
24
+ * SAME shape with its own `provider:` value, and `rea preflight` reads
25
+ * any of them — no registry, no factory, no swap mechanism.
26
+ *
27
+ * # Skipped variants
28
+ *
29
+ * - `rea.local_review.skipped_override`: env-var bypass; reason logged
30
+ * - `rea.local_review.skipped_unavailable`: codex missing + `mode: off`;
31
+ * no review run, gate is a no-op
32
+ * - `rea.preflight.review_skipped`: `--no-review-check` CLI escape hatch
33
+ *
34
+ * Both `rea preflight` and any future audit-log reader treat these
35
+ * sibling tool names as INFORMATIONAL — they do NOT cover HEAD. Only
36
+ * the canonical `rea.local_review` entry (or the back-compat
37
+ * `codex.review` entry from pre-0.26.0 audit data) covers HEAD.
38
+ */
39
+ export declare const LOCAL_REVIEW_TOOL_NAME: "rea.local_review";
40
+ export declare const LOCAL_REVIEW_SKIPPED_OVERRIDE_TOOL_NAME: "rea.local_review.skipped_override";
41
+ export declare const LOCAL_REVIEW_SKIPPED_UNAVAILABLE_TOOL_NAME: "rea.local_review.skipped_unavailable";
42
+ export declare const LOCAL_REVIEW_PREFLIGHT_SKIPPED_TOOL_NAME: "rea.preflight.review_skipped";
43
+ export declare const LOCAL_REVIEW_SERVER_NAME: "rea";
44
+ /**
45
+ * The verdict shape — same alphabet as the push-gate's CodexVerdict.
46
+ * `error` is reserved for future providers that surface a non-execution
47
+ * outcome (timeout, transport failure, etc.) so `rea preflight` can
48
+ * decide whether to honor a stale error-marker.
49
+ */
50
+ export type LocalReviewVerdict = 'pass' | 'concerns' | 'blocking' | 'error';
51
+ /**
52
+ * Canonical metadata payload written under `metadata` on a
53
+ * `rea.local_review` audit record. `rea preflight` reads `head_sha`
54
+ * (must match `git rev-parse HEAD`) and `verdict` (must not be `error`
55
+ * for the entry to count as covering HEAD).
56
+ *
57
+ * `provider`, `provider_version`, `model`, `reasoning_effort` are
58
+ * non-semantic identification — the gate doesn't read them, but
59
+ * they're invaluable for forensic analysis ("which review producer
60
+ * did this concerns verdict come from?").
61
+ */
62
+ export interface LocalReviewMetadata {
63
+ /**
64
+ * git rev-parse HEAD at review time. Recorded for forensics.
65
+ *
66
+ * 0.26.0 helix-026 finding-1: prior to this release `rea preflight`
67
+ * matched coverage by exact `head_sha`. That keyed coverage to a
68
+ * commit that didn't exist yet at review time (the local-first flow
69
+ * is "review → fix → commit → push" — HEAD changes between review
70
+ * and preflight). Coverage is now matched by `content_token` instead;
71
+ * `head_sha` remains in the payload purely for audit forensics.
72
+ */
73
+ head_sha: string;
74
+ /**
75
+ * Tree SHA of HEAD at review time — the deterministic content
76
+ * fingerprint codex reviewed. `rea preflight` matches coverage on
77
+ * this field. Stable across content-equivalent commits (`--amend` with
78
+ * no edits, fixup rebases). Differs on any real content change.
79
+ *
80
+ * Optional for back-compat: legacy `codex.review` entries (pre-0.26.0)
81
+ * and any future provider that can't compute a tree fingerprint should
82
+ * omit this. `rea preflight` falls back to `head_sha` exact match when
83
+ * `content_token` is absent.
84
+ */
85
+ content_token?: string;
86
+ /** Base ref (or SHA) the review diffed against. */
87
+ base_ref: string;
88
+ /** Verdict alphabet shared with the push-gate. */
89
+ verdict: LocalReviewVerdict;
90
+ /** Total findings extracted from the review prose. */
91
+ finding_count: number;
92
+ /**
93
+ * Logical name of the review provider. Today: `'codex'`. Future:
94
+ * `'claude-subagent'`, `'gemini'`, `'pi'`. Free-form lowercase
95
+ * identifier; the gate doesn't consume it.
96
+ */
97
+ provider: string;
98
+ /** Version string of the provider binary, when available. */
99
+ provider_version?: string;
100
+ /** Model name passed to the provider, when applicable. */
101
+ model?: string;
102
+ /** Reasoning effort, when applicable to the model. */
103
+ reasoning_effort?: string;
104
+ /** Wall-time of the review subprocess in seconds. */
105
+ duration_seconds: number;
106
+ /**
107
+ * Identifier the reviewer attached to this run. Today this is the
108
+ * codex session id; future providers use whatever identifies a
109
+ * single review invocation.
110
+ */
111
+ reviewer_session_id?: string;
112
+ }
113
+ /**
114
+ * Metadata for the `rea.local_review.skipped_override` event. Carries
115
+ * the reason string from the bypass env-var so audit-log readers can
116
+ * see WHY the gate was bypassed.
117
+ */
118
+ export interface LocalReviewSkippedOverrideMetadata {
119
+ head_sha: string;
120
+ /** Verbatim env-var value (the operator's reason). */
121
+ reason: string;
122
+ /** Which env-var was set (configurable via policy). */
123
+ bypass_env_var: string;
124
+ }
125
+ /**
126
+ * Metadata for the `rea.local_review.skipped_unavailable` event. This
127
+ * fires when codex is absent AND `policy.review.local_review.mode === 'off'`
128
+ * — `rea review` exits 0 silently with this entry recording the no-op.
129
+ */
130
+ export interface LocalReviewSkippedUnavailableMetadata {
131
+ head_sha?: string;
132
+ /** Why the run was skipped: `'codex-not-installed'`, etc. */
133
+ reason: string;
134
+ /** Provider that was probed, e.g. `'codex'`. */
135
+ provider: string;
136
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Single source of truth for the `rea.local_review` audit event shape
3
+ * (0.26.0+).
4
+ *
5
+ * The local-first delegation enforcement (CTO directive 2026-05-05)
6
+ * replaces the soft "memory rule + orchestrator routing convention"
7
+ * with three forceful enforcement layers:
8
+ *
9
+ * 1. Bash-tier PreToolUse hook (hooks/local-review-gate.sh) — refuses
10
+ * `git push`/`git commit` from the agent's Bash tool.
11
+ * 2. Husky pre-push (`exec rea preflight --strict`) — refuses git push
12
+ * at the terminal layer.
13
+ * 3. `rea preflight` CLI — the workhorse all enforcement layers call.
14
+ *
15
+ * `rea review` writes a `rea.local_review` audit entry. `rea preflight`
16
+ * reads the audit log, finds the most recent matching entry for HEAD,
17
+ * and exits 0/1/2 accordingly.
18
+ *
19
+ * # Provider seam
20
+ *
21
+ * The `provider` field is the LIGHT seam for future review providers
22
+ * (Claude-subagent, Pi, Gemma). Today the only writer is `rea review`
23
+ * with `provider: 'codex'`. Tomorrow a different provider writes the
24
+ * SAME shape with its own `provider:` value, and `rea preflight` reads
25
+ * any of them — no registry, no factory, no swap mechanism.
26
+ *
27
+ * # Skipped variants
28
+ *
29
+ * - `rea.local_review.skipped_override`: env-var bypass; reason logged
30
+ * - `rea.local_review.skipped_unavailable`: codex missing + `mode: off`;
31
+ * no review run, gate is a no-op
32
+ * - `rea.preflight.review_skipped`: `--no-review-check` CLI escape hatch
33
+ *
34
+ * Both `rea preflight` and any future audit-log reader treat these
35
+ * sibling tool names as INFORMATIONAL — they do NOT cover HEAD. Only
36
+ * the canonical `rea.local_review` entry (or the back-compat
37
+ * `codex.review` entry from pre-0.26.0 audit data) covers HEAD.
38
+ */
39
+ export const LOCAL_REVIEW_TOOL_NAME = 'rea.local_review';
40
+ export const LOCAL_REVIEW_SKIPPED_OVERRIDE_TOOL_NAME = 'rea.local_review.skipped_override';
41
+ export const LOCAL_REVIEW_SKIPPED_UNAVAILABLE_TOOL_NAME = 'rea.local_review.skipped_unavailable';
42
+ export const LOCAL_REVIEW_PREFLIGHT_SKIPPED_TOOL_NAME = 'rea.preflight.review_skipped';
43
+ export const LOCAL_REVIEW_SERVER_NAME = 'rea';
@@ -145,12 +145,29 @@ const EXPECTED_AGENTS = [
145
145
  const EXPECTED_HOOKS = [
146
146
  'architecture-review-gate.sh',
147
147
  'attribution-advisory.sh',
148
+ // 0.22.0 — Bash-tier parity with `blocked-paths-enforcer.sh`.
149
+ // Round-27 F8 fix: was silently missing from EXPECTED_HOOKS, so
150
+ // doctor returned pass on consumer installs that lacked this
151
+ // security-load-bearing hook (any consumer who upgraded from
152
+ // 0.21.x → 0.22.x without `rea upgrade` was undetected).
153
+ 'blocked-paths-bash-gate.sh',
148
154
  'blocked-paths-enforcer.sh',
149
155
  'changeset-security-gate.sh',
150
156
  'dangerous-bash-interceptor.sh',
151
157
  'dependency-audit-gate.sh',
152
158
  'env-file-protection.sh',
159
+ // 0.26.0 local-first enforcement (CTO directive 2026-05-05).
160
+ // Round-25 P3 fix: doctor's EXPECTED_HOOKS list missed this entry.
161
+ // Without it, `rea doctor` returned pass on consumer installs that
162
+ // didn't actually have the new gate present after upgrade — silently
163
+ // disabling the local-first guardrail.
164
+ 'local-review-gate.sh',
153
165
  'pr-issue-link-gate.sh',
166
+ // 0.21.0 — Bash-tier parity with `settings-protection.sh`.
167
+ // Round-27 F8 fix: same class as blocked-paths-bash-gate.sh — silently
168
+ // missing since 0.21.0, doctor would pass even when the hook was
169
+ // absent from a consumer install.
170
+ 'protected-paths-bash-gate.sh',
154
171
  'secret-scanner.sh',
155
172
  'security-disclosure-gate.sh',
156
173
  'settings-protection.sh',
@@ -80,6 +80,50 @@ export interface HookScanBashOptions {
80
80
  * writes the verdict JSON, exits with the appropriate code.
81
81
  */
82
82
  export declare function runHookScanBash(options: HookScanBashOptions): Promise<void>;
83
+ /**
84
+ * `rea hook policy-get <dot.path>` — single source of truth for
85
+ * policy-value reads from the bash-tier hooks. Round-30 F2 structural
86
+ * fix.
87
+ *
88
+ * Pre-fix: `hooks/_lib/policy-read.sh::policy_nested_scalar` used a
89
+ * regex/awk parser that ONLY handled block-form mappings. The TS loader
90
+ * (`src/policy/loader.ts`) accepted inline-form mappings — `local_review:
91
+ * { mode: off }` — but the bash reader missed them. Silent split-brain:
92
+ * TS preflight saw `mode=off` (no-op), bash gate saw the field as unset
93
+ * and fell through to the enforced default → refused the push.
94
+ *
95
+ * Fix: have the bash gate shell out HERE for nested reads. The TS
96
+ * `yaml.parse()` call accepts both forms identically — single source of
97
+ * truth, drift impossible by construction.
98
+ *
99
+ * Contract:
100
+ * - `key` is dot-separated: `review.local_review.mode`. Only
101
+ * scalar leaves are supported (objects/arrays print empty).
102
+ * - Output is the raw scalar VALUE on stdout (no trailing newline,
103
+ * no quoting). Booleans render as `true`/`false`. Numbers render
104
+ * as their JS string form.
105
+ * - Unknown / missing path → empty stdout, exit 0. The bash caller
106
+ * treats empty as "default applies".
107
+ * - Unparseable YAML → empty stdout, exit 1. Bash callers swallow
108
+ * the exit and treat as default (matches pre-fix posture: any read
109
+ * error returns empty rather than refusing the gate).
110
+ */
111
+ export interface HookPolicyGetOptions {
112
+ /** Dotted path; e.g. `review.local_review.mode`. */
113
+ key: string;
114
+ /**
115
+ * When true, emit the resolved subtree as JSON instead of a scalar.
116
+ * Object/array leaves print as their JSON form; scalars print as
117
+ * JSON-encoded scalars (`"off"`, `42`, `true`, `null`). Missing
118
+ * paths print `null`. Used by the bash hooks to read an entire
119
+ * sub-object in one node-spawn (e.g. all `review.local_review.*`
120
+ * fields at once) and parse client-side via jq.
121
+ */
122
+ json?: boolean;
123
+ /** Override REA_ROOT. Production callers omit. */
124
+ reaRoot?: string;
125
+ }
126
+ export declare function runHookPolicyGet(options: HookPolicyGetOptions): Promise<void>;
83
127
  /**
84
128
  * Attach the `rea hook` subcommand tree to a commander Program. Two
85
129
  * subcommands today: `push-gate` and `scan-bash`. New hooks should land