@bookedsolid/rea 0.11.0 → 0.12.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/.husky/pre-push +44 -13
- package/README.md +834 -552
- package/dist/cli/doctor.d.ts +12 -0
- package/dist/cli/doctor.js +90 -1
- package/dist/cli/hook.d.ts +7 -0
- package/dist/cli/hook.js +12 -1
- package/dist/cli/install/pre-push.d.ts +21 -10
- package/dist/cli/install/pre-push.js +47 -27
- package/dist/hooks/push-gate/base.d.ts +48 -1
- package/dist/hooks/push-gate/base.js +121 -0
- package/dist/hooks/push-gate/index.d.ts +8 -0
- package/dist/hooks/push-gate/index.js +86 -21
- package/dist/hooks/push-gate/policy.d.ts +18 -4
- package/dist/hooks/push-gate/policy.js +13 -4
- package/dist/policy/loader.d.ts +5 -0
- package/dist/policy/loader.js +1 -0
- package/dist/policy/types.d.ts +44 -2
- package/package.json +1 -1
- package/scripts/tarball-smoke.sh +7 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# REA
|
|
2
2
|
|
|
3
|
-
**Agentic governance layer for Claude Code — policy enforcement, hook-based safety gates, audit logging, and
|
|
3
|
+
**Agentic governance layer for Claude Code — policy enforcement, hook-based safety gates, audit logging, and a stateless pre-push Codex review gate.**
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@bookedsolid/rea)
|
|
6
6
|
[](https://github.com/bookedsolidtech/rea/actions/workflows/ci.yml)
|
|
@@ -9,68 +9,169 @@
|
|
|
9
9
|
[](https://developercertificate.org/)
|
|
10
10
|
[](https://nodejs.org/)
|
|
11
11
|
|
|
12
|
-
> Status: `0.
|
|
12
|
+
> Status: `0.11.0` — published to npm with SLSA v1 provenance. See
|
|
13
13
|
> [CHANGELOG.md](./CHANGELOG.md) for the per-release history.
|
|
14
14
|
|
|
15
|
+
REA is a single npm package that gates and audits agentic tool calls made by
|
|
16
|
+
Claude Code — shell commands, filesystem writes, and MCP tool invocations —
|
|
17
|
+
against an operator-defined policy file. It ships an MCP middleware gateway,
|
|
18
|
+
a set of hook scripts, a pre-push Codex review gate, a hash-chained audit
|
|
19
|
+
log, and a hard kill-switch. Every layer fails closed.
|
|
20
|
+
|
|
21
|
+
**What changed in 0.11.0.** Through 0.10.x the push-review gate asked "has a
|
|
22
|
+
qualifying Codex receipt been recorded for this HEAD SHA?" and consulted a
|
|
23
|
+
`.rea/review-cache.jsonl` + audit-record lookup to decide. Agents spent a
|
|
24
|
+
meaningful fraction of every push cycle fabricating attestations with `rea
|
|
25
|
+
cache set` and `rea audit record codex-review --also-set-cache`, and the
|
|
26
|
+
bash core around the gate grew to ~1,250 lines. 0.11.0 deletes that stack
|
|
27
|
+
and replaces it with a single subcommand — `rea hook push-gate` — that
|
|
28
|
+
**runs Codex on every push**, parses the verdict from the streamed review
|
|
29
|
+
output, writes the findings to `.rea/last-review.json`, and blocks exit
|
|
30
|
+
code `2` on `[P1]` or (by default) `[P2]` findings. Readers landing on
|
|
31
|
+
0.9/0.10-era docs should treat the "record-and-cache" flow as gone — see
|
|
32
|
+
the [migration section](#migration-from-010x) below.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Table of contents
|
|
37
|
+
|
|
38
|
+
- [Quickstart](#quickstart)
|
|
39
|
+
- [What REA is](#what-rea-is)
|
|
40
|
+
- [What REA is NOT](#what-rea-is-not)
|
|
41
|
+
- [The pre-push Codex gate](#the-pre-push-codex-gate)
|
|
42
|
+
- [MCP gateway](#mcp-gateway)
|
|
43
|
+
- [Policy file](#policy-file)
|
|
44
|
+
- [Hooks shipped](#hooks-shipped)
|
|
45
|
+
- [Slash commands](#slash-commands)
|
|
46
|
+
- [Curated agents](#curated-agents)
|
|
47
|
+
- [CLI reference](#cli-reference)
|
|
48
|
+
- [Migration from 0.10.x](#migration-from-010x)
|
|
49
|
+
- [Contributor quality gates](#contributor-quality-gates)
|
|
50
|
+
- [Threat model and security](#threat-model-and-security)
|
|
51
|
+
- [Non-goals](#non-goals)
|
|
52
|
+
- [License and contributing](#license-and-contributing)
|
|
53
|
+
|
|
15
54
|
---
|
|
16
55
|
|
|
17
|
-
##
|
|
56
|
+
## Quickstart
|
|
18
57
|
|
|
19
58
|
```bash
|
|
20
59
|
npx @bookedsolid/rea init
|
|
21
60
|
```
|
|
22
61
|
|
|
23
|
-
The `init`
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
`.husky/
|
|
62
|
+
The `init` wizard detects your project, writes `.rea/policy.yaml`, copies
|
|
63
|
+
curated hooks and slash commands into `.claude/`, wires `.mcp.json` to run
|
|
64
|
+
`rea serve` as a governance gateway, installs `.husky/commit-msg` and
|
|
65
|
+
`.husky/pre-push` hooks, and appends a managed fragment to `CLAUDE.md`. Run
|
|
66
|
+
it non-interactively with `-y`:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
npx @bookedsolid/rea init -y --profile bst-internal
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Node 22+ and pnpm 9.12+ are required. The package is published with npm
|
|
73
|
+
provenance (SLSA v1) from the `main` branch via GitHub Actions OIDC.
|
|
74
|
+
|
|
75
|
+
**Your first push.** After a feature commit, `git push` will:
|
|
76
|
+
|
|
77
|
+
1. `.husky/pre-push` checks `.rea/HALT` and delegates to `rea hook push-gate`.
|
|
78
|
+
2. `rea hook push-gate` loads `.rea/policy.yaml`, resolves a base ref, and
|
|
79
|
+
shells out to `codex exec review --base <ref> --json --ephemeral`.
|
|
80
|
+
3. Codex streams JSONL events back; the gate parses `[P1]`/`[P2]`/`[P3]`
|
|
81
|
+
findings out of the `agent_message` text.
|
|
82
|
+
4. A severity-sorted summary is printed to stderr; full findings with file
|
|
83
|
+
and line detail are written atomically to `.rea/last-review.json`.
|
|
84
|
+
5. An audit record (`rea.push_gate.reviewed`) is appended to
|
|
85
|
+
`.rea/audit.jsonl`.
|
|
86
|
+
6. Exit code is `0` (pass), `1` (HALT), or `2` (blocked by verdict or
|
|
87
|
+
Codex error).
|
|
88
|
+
|
|
89
|
+
On a blocking verdict the push fails; the in-session Claude agent reads the
|
|
90
|
+
stderr summary and the `.rea/last-review.json` payload, fixes the issues,
|
|
91
|
+
commits, and pushes again. No cache, no receipt to fabricate.
|
|
27
92
|
|
|
28
|
-
|
|
93
|
+
Verify the install:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
rea doctor
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Freeze everything if something unexpected happens:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
rea freeze --reason "incident triage; investigate unexpected .env write"
|
|
103
|
+
# later
|
|
104
|
+
rea unfreeze
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
---
|
|
29
108
|
|
|
30
109
|
## What REA is
|
|
31
110
|
|
|
32
|
-
REA is a governance layer for Claude Code. It
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
111
|
+
REA is a governance layer for Claude Code. It ships four things.
|
|
112
|
+
|
|
113
|
+
### 1. A policy runtime
|
|
114
|
+
|
|
115
|
+
`.rea/policy.yaml`, validated by a strict zod schema — unknown fields are
|
|
116
|
+
**rejected**, not ignored. The policy defines:
|
|
117
|
+
|
|
118
|
+
- `autonomy_level` (L0–L3) and a hard `max_autonomy_level` ceiling
|
|
119
|
+
- `blocked_paths` (globs; `.rea/` is always blocked regardless)
|
|
120
|
+
- `block_ai_attribution` — enforced by the commit-msg hook
|
|
121
|
+
- `review.codex_required` — whether the pre-push gate runs Codex at all
|
|
122
|
+
- `review.concerns_blocks` — whether `[P2]` verdicts halt the push
|
|
123
|
+
- `review.timeout_ms` — hard cap on the Codex subprocess
|
|
124
|
+
- Redaction patterns, injection tuning, audit rotation, and MCP gateway knobs
|
|
125
|
+
|
|
126
|
+
Policy is re-read on every middleware invocation and every hook run. Editing
|
|
127
|
+
`.rea/policy.yaml` takes effect on the next tool call — no restart, no
|
|
128
|
+
cache invalidation.
|
|
129
|
+
|
|
130
|
+
### 2. A kill switch
|
|
131
|
+
|
|
132
|
+
`.rea/HALT` is a single file. If it exists, every governed tool call is
|
|
133
|
+
denied — the MCP gateway middleware returns an error, the bash hooks `exit
|
|
134
|
+
1`, and the pre-push gate returns exit `1` with the reason printed to
|
|
135
|
+
stderr. Use `rea freeze --reason "..."` to create it and `rea unfreeze` to
|
|
136
|
+
remove it. Both operations write audit records. The middleware never
|
|
137
|
+
clears HALT on its own.
|
|
138
|
+
|
|
139
|
+
HALT is checked before policy in every flow. A corrupted `.rea/policy.yaml`
|
|
140
|
+
does not prevent the kill-switch from firing.
|
|
141
|
+
|
|
142
|
+
### 3. A hook layer
|
|
143
|
+
|
|
144
|
+
Eleven shell scripts ship in `hooks/` and are copied into `.claude/hooks/`
|
|
145
|
+
by `rea init`. All eleven are wired into the default `.claude/settings.json`
|
|
146
|
+
and fire on Claude Code's `PreToolUse` / `PostToolUse` events (secret
|
|
147
|
+
scanning, dangerous-command interception, blocked-path enforcement,
|
|
148
|
+
settings protection, attribution rejection, env-file protection,
|
|
149
|
+
disclosure-policy routing, dependency audit, changeset security,
|
|
150
|
+
PR-issue-link advisory, architecture advisory). Each hook uses
|
|
151
|
+
`set -euo pipefail` (or `set -uo pipefail` for stdin-JSON consumers) and
|
|
152
|
+
runs a HALT check near the top. See [Hooks shipped](#hooks-shipped) for
|
|
153
|
+
the full inventory.
|
|
154
|
+
|
|
155
|
+
The hook layer runs independently of the MCP gateway — bypassing one does
|
|
156
|
+
not disable the other. That redundancy is intentional.
|
|
157
|
+
|
|
158
|
+
### 4. An MCP gateway
|
|
159
|
+
|
|
160
|
+
`rea serve` is an MCP stdio server that proxies downstream MCP servers
|
|
161
|
+
declared in `.rea/registry.yaml` through a middleware chain. Every tool
|
|
162
|
+
call — native rea tools or proxied downstream tools — is classified,
|
|
163
|
+
policy-checked, redacted, audited, and size-capped before it executes. See
|
|
164
|
+
[MCP gateway](#mcp-gateway) for the chain ordering and supervisor
|
|
165
|
+
behavior.
|
|
166
|
+
|
|
167
|
+
**Plus** a stateless pre-push Codex review gate (new in 0.11.0) that fires
|
|
168
|
+
via `.husky/pre-push` on every `git push` and is covered in the next
|
|
169
|
+
section.
|
|
170
|
+
|
|
171
|
+
REA does one thing: gate and audit agentic tool calls against
|
|
172
|
+
operator-defined policy. That is the whole product.
|
|
173
|
+
|
|
174
|
+
---
|
|
74
175
|
|
|
75
176
|
## What REA is NOT
|
|
76
177
|
|
|
@@ -78,187 +179,258 @@ These are non-goals. PRs adding any of these will be closed with a pointer
|
|
|
78
179
|
to build a separate package that composes with REA.
|
|
79
180
|
|
|
80
181
|
- **Not a project manager.** No task CRUD, no GitHub issue sync, no board
|
|
81
|
-
scaffolding.
|
|
182
|
+
scaffolding.
|
|
82
183
|
- **Not an Obsidian integration.** No vault journaling, no note creation,
|
|
83
|
-
no
|
|
84
|
-
- **Not an account manager.** No `rea account
|
|
85
|
-
|
|
86
|
-
- **Not a Discord bot.**
|
|
87
|
-
|
|
184
|
+
no pre/post-compact hooks.
|
|
185
|
+
- **Not an account manager.** No `rea account` tree, no Keychain, no OAuth,
|
|
186
|
+
no multi-tenant token vault. Env vars only.
|
|
187
|
+
- **Not a Discord bot.** A Discord webhook URL in `policy.yaml` is the
|
|
188
|
+
entire surface area — one outbound POST, opt-in, no MCP tools.
|
|
88
189
|
- **Not a daemon supervisor.** `rea serve` is started by Claude Code via
|
|
89
190
|
`.mcp.json`. Claude Code owns the lifecycle. There is no `rea start`,
|
|
90
|
-
no `rea stop`, no systemd unit.
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
- **Not a
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
191
|
+
no `rea stop`, no systemd unit.
|
|
192
|
+
- **Not a hosted service.** No REA Cloud, no SaaS tier, no multi-tenant
|
|
193
|
+
workload isolation.
|
|
194
|
+
- **Not a 70-agent roster.** Ten curated agents ship in the package.
|
|
195
|
+
Profiles layer additional specialists.
|
|
196
|
+
- **Not a full policy engine.** No OPA/Rego, no CEL, no attribute-based
|
|
197
|
+
access control. A YAML file with a small, fixed schema is the entire
|
|
198
|
+
policy language.
|
|
199
|
+
- **Not a CI replacement.** REA gates agent behavior at author time. CI
|
|
200
|
+
still runs lint, typecheck, tests, and build on every PR.
|
|
201
|
+
- **Not a secret manager.** REA detects secrets in writes and redacts them
|
|
202
|
+
in audit records; it does not store, rotate, or provision them.
|
|
203
|
+
|
|
204
|
+
The non-goals are the product.
|
|
99
205
|
|
|
100
|
-
|
|
101
|
-
in a separate package.
|
|
206
|
+
---
|
|
102
207
|
|
|
103
|
-
##
|
|
208
|
+
## The pre-push Codex gate
|
|
104
209
|
|
|
105
|
-
|
|
210
|
+
The 0.11.0 gate is stateless. Every `git push` runs Codex on the diff, and
|
|
211
|
+
the gate's decision is a function of the review output — not of a cached
|
|
212
|
+
receipt or an audit record from a prior run.
|
|
106
213
|
|
|
107
|
-
|
|
214
|
+
### Flow
|
|
108
215
|
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
216
|
+
```
|
|
217
|
+
$ git push
|
|
218
|
+
│
|
|
219
|
+
▼
|
|
220
|
+
┌─────────────────────────────────────────────────────────┐
|
|
221
|
+
│ .husky/pre-push │
|
|
222
|
+
│ 1. Check .rea/HALT — exit 1 if present │
|
|
223
|
+
│ 2. Locate rea binary (node_modules, dist, PATH, npx) │
|
|
224
|
+
│ 3. exec rea hook push-gate "$@" │
|
|
225
|
+
└─────────────────────────────────────────────────────────┘
|
|
226
|
+
│
|
|
227
|
+
▼
|
|
228
|
+
┌─────────────────────────────────────────────────────────┐
|
|
229
|
+
│ rea hook push-gate │
|
|
230
|
+
│ 1. HALT check (again, defense-in-depth) │
|
|
231
|
+
│ 2. Load .rea/policy.yaml (zod-validated) │
|
|
232
|
+
│ 3. codex_required=false → status:disabled, exit 0 │
|
|
233
|
+
│ 4. REA_SKIP_PUSH_GATE / REA_SKIP_CODEX_REVIEW=<reason>│
|
|
234
|
+
│ → skipped, exit 0 (audit records skip_var) │
|
|
235
|
+
│ 5. Parse pre-push stdin refspecs │
|
|
236
|
+
│ 6. Resolve base ref (--base → --last-n-commits N │
|
|
237
|
+
│ → policy.review.last_n_commits → refspec → │
|
|
238
|
+
│ upstream → origin/HEAD → main/master → empty-tree) │
|
|
239
|
+
│ 7. Empty-diff check → status:empty-diff, exit 0 │
|
|
240
|
+
│ 8. codex exec review --base <ref> --json --ephemeral │
|
|
241
|
+
│ 9. Parse [P1]/[P2]/[P3] findings │
|
|
242
|
+
│ 10. Infer verdict: P1 → blocking; else P2 → concerns; │
|
|
243
|
+
│ else pass │
|
|
244
|
+
│ 11. Atomic write .rea/last-review.json (redacted) │
|
|
245
|
+
│ 12. Render stderr banner │
|
|
246
|
+
│ 13. Append audit record rea.push_gate.reviewed │
|
|
247
|
+
│ 14. Exit 0 (pass/disabled/skipped/empty-diff) | │
|
|
248
|
+
│ 1 (HALT) | 2 (blocked, timeout, or error) │
|
|
249
|
+
└─────────────────────────────────────────────────────────┘
|
|
127
250
|
```
|
|
128
251
|
|
|
129
|
-
|
|
130
|
-
The loader rejects the file at parse time if `autonomy_level` exceeds the
|
|
131
|
-
ceiling. The ceiling is set by the human operator and never by an agent.
|
|
132
|
-
|
|
133
|
-
### 2. Freeze when you need to stop everything
|
|
252
|
+
### Verdicts and exit codes
|
|
134
253
|
|
|
135
|
-
|
|
136
|
-
|
|
254
|
+
| Verdict | Default behavior | Exit code | Notes |
|
|
255
|
+
| --- | --- | --- | --- |
|
|
256
|
+
| `pass` | Push proceeds | `0` | No `[P1]` or `[P2]` findings in the review |
|
|
257
|
+
| `concerns` | Push blocks (default) | `2` | `[P2]` findings present; override with `REA_ALLOW_CONCERNS=1` or `review.concerns_blocks: false` |
|
|
258
|
+
| `blocking` | Push blocks always | `2` | `[P1]` findings present; no override |
|
|
259
|
+
| HALT | Push blocks | `1` | `.rea/HALT` is active; run `rea unfreeze` |
|
|
260
|
+
| `disabled` | Push proceeds | `0` | `review.codex_required: false` in policy |
|
|
261
|
+
| `skipped` | Push proceeds | `0` | `REA_SKIP_PUSH_GATE=<reason>` or `REA_SKIP_CODEX_REVIEW=<reason>` set; audited |
|
|
262
|
+
| `empty-diff` | Push proceeds | `0` | No file changes between base and head |
|
|
263
|
+
| `error` | Push blocks | `2` | Codex not installed, timeout, subprocess error, malformed policy, head-sha resolution failure |
|
|
264
|
+
|
|
265
|
+
### The auto-fix loop
|
|
266
|
+
|
|
267
|
+
Previous gate: pre-run Codex manually, record an attestation with `rea
|
|
268
|
+
audit record codex-review --also-set-cache`, push, and hope the
|
|
269
|
+
attestation's SHA matches the tip. Agents working at speed ended up
|
|
270
|
+
fabricating attestations or running Codex out-of-band and recording the
|
|
271
|
+
verdict without anyone actually reading the output. Friction was paid on
|
|
272
|
+
every push.
|
|
273
|
+
|
|
274
|
+
New gate: the gate **is** the review. Codex runs on the same diff the push
|
|
275
|
+
is about to send, so the verdict is causally tied to the push. When the
|
|
276
|
+
gate blocks:
|
|
277
|
+
|
|
278
|
+
1. The stderr banner prints the verdict, base ref, head SHA, finding
|
|
279
|
+
count, duration, and up to 20 severity-sorted findings with file:line
|
|
280
|
+
pointers. Because the pre-push hook's stderr reaches Claude as the
|
|
281
|
+
tool output of `Bash(git push)`, the banner is the primary fast-path
|
|
282
|
+
signal for the in-session agent.
|
|
283
|
+
2. `.rea/last-review.json` is written atomically with the full findings,
|
|
284
|
+
each carrying `severity`, `title`, `body`, and optional `file`/`line`.
|
|
285
|
+
This file is the source of truth for the auto-fix loop — the stderr
|
|
286
|
+
banner is capped at 20 findings; the JSON is not.
|
|
287
|
+
3. Claude reads both, applies fixes, commits (with `-s` and no AI
|
|
288
|
+
attribution), and pushes again. The gate runs Codex fresh on the new
|
|
289
|
+
diff. Repeat until `pass`.
|
|
290
|
+
|
|
291
|
+
See [CHANGELOG 0.11.0](./CHANGELOG.md) for the longer rationale on why
|
|
292
|
+
the cache-attestation design was removed.
|
|
293
|
+
|
|
294
|
+
### `.rea/last-review.json` schema
|
|
295
|
+
|
|
296
|
+
```jsonc
|
|
297
|
+
{
|
|
298
|
+
"schema_version": 1,
|
|
299
|
+
"generated_at": "2026-04-22T18:04:01.123Z",
|
|
300
|
+
"verdict": "blocking", // "pass" | "concerns" | "blocking"
|
|
301
|
+
"base_ref": "origin/main",
|
|
302
|
+
"head_sha": "c5ec101…",
|
|
303
|
+
"finding_count": 3,
|
|
304
|
+
"findings": [
|
|
305
|
+
{
|
|
306
|
+
"severity": "P1", // "P1" | "P2" | "P3"
|
|
307
|
+
"title": "missing input validation on /auth/callback",
|
|
308
|
+
"file": "src/routes/auth.ts", // optional
|
|
309
|
+
"line": 42, // optional
|
|
310
|
+
"body": "The callback accepts raw state without…"
|
|
311
|
+
}
|
|
312
|
+
// …
|
|
313
|
+
],
|
|
314
|
+
"review_text": "[P1] missing input validation…\n[P2] …",
|
|
315
|
+
"event_count": 37,
|
|
316
|
+
"duration_seconds": 12.4
|
|
317
|
+
}
|
|
137
318
|
```
|
|
138
319
|
|
|
139
|
-
|
|
140
|
-
until an operator runs:
|
|
320
|
+
The file is:
|
|
141
321
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
322
|
+
- **Atomic.** Written to `.rea/last-review.json.tmp.<pid>-<rand>`, fsynced,
|
|
323
|
+
then `rename(2)`d. Partial writes never surface to readers.
|
|
324
|
+
- **Redacted.** Both `findings[].title`/`body` and `review_text` are run
|
|
325
|
+
through the same `SECRET_PATTERNS` list the redact middleware uses. If
|
|
326
|
+
Codex quotes a credential out of the diff it never hits disk in cleartext.
|
|
327
|
+
- **Overwritten every push.** There is no rolling history on disk; the
|
|
328
|
+
audit log is the rolling history.
|
|
329
|
+
- **Gitignored.** The default `rea init` install adds `/.rea/last-review.json`
|
|
330
|
+
to `.gitignore`.
|
|
145
331
|
|
|
146
|
-
|
|
147
|
-
its own.
|
|
332
|
+
### Environment variables
|
|
148
333
|
|
|
149
|
-
|
|
334
|
+
| Variable | Purpose |
|
|
335
|
+
| --- | --- |
|
|
336
|
+
| `REA_SKIP_PUSH_GATE=<reason>` | Value-carrying waiver. When set to a non-empty string, the gate short-circuits to `status:skipped` (exit 0) and appends `rea.push_gate.skipped` with the reason and `skip_var: REA_SKIP_PUSH_GATE` as metadata. HALT **always** wins over this — a frozen install still blocks. Use sparingly; every use is audited. |
|
|
337
|
+
| `REA_SKIP_CODEX_REVIEW=<reason>` | Equivalent alias for `REA_SKIP_PUSH_GATE`, added in 0.12.0. Same exit behavior; audit metadata records `skip_var: REA_SKIP_CODEX_REVIEW` so operators can grep their audit log to see which variant agents used. When both env vars are set, `REA_SKIP_PUSH_GATE` wins. |
|
|
338
|
+
| `REA_ALLOW_CONCERNS=1` | One-push override for `concerns` verdict. Accepts `1`, `true`, or `yes` (case-insensitive). Does **not** override `blocking`. The audit record is stamped `concerns_override: true` so reviewers can see the override was used. |
|
|
150
339
|
|
|
151
|
-
|
|
152
|
-
|
|
340
|
+
### Policy knobs
|
|
341
|
+
|
|
342
|
+
```yaml
|
|
343
|
+
# .rea/policy.yaml
|
|
344
|
+
review:
|
|
345
|
+
codex_required: true # default true — run Codex on every push
|
|
346
|
+
concerns_blocks: true # default true — [P2] halts the push
|
|
347
|
+
timeout_ms: 1800000 # default 1800000 (30 minutes; raised from
|
|
348
|
+
# 600000 in 0.12.0 — see CHANGELOG)
|
|
349
|
+
last_n_commits: 10 # OPTIONAL — narrow review to the last N commits
|
|
350
|
+
# (diff vs HEAD~N). Defaults unset.
|
|
153
351
|
```
|
|
154
352
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
returns a
|
|
160
|
-
|
|
161
|
-
pre-push checks are skipped cleanly — REA governs policy and injection
|
|
162
|
-
detection there, not pushes. Audit hash-chain integrity is verified by
|
|
163
|
-
a separate command — `rea check` (on-disk tail) or the full replay
|
|
164
|
-
verifier — not by `rea doctor`.
|
|
353
|
+
| Key | Type | Default | Purpose |
|
|
354
|
+
| --- | --- | --- | --- |
|
|
355
|
+
| `review.codex_required` | boolean | `true` | Master on/off. `false` short-circuits the gate to `status:disabled`, still audited. `-no-codex` profiles set this to `false`. |
|
|
356
|
+
| `review.concerns_blocks` | boolean | `true` | When `true`, `[P2]` verdicts return exit 2. Flip to `false` for a looser posture where only `[P1]` halts the push. |
|
|
357
|
+
| `review.timeout_ms` | number | `1800000` | Hard cap on the `codex exec review` subprocess in milliseconds. Exceeding it kills the subprocess and returns exit 2 with a `timeout` kind. Positive integer; zero/negative is rejected at load. **Raised from 600000 (10 min) to 1800000 (30 min) in 0.12.0** after operator data showed realistic feature-branch reviews routinely exceeded 10 minutes; pin `timeout_ms: 600000` explicitly to retain the old default. |
|
|
358
|
+
| `review.last_n_commits` | number | unset | When set, the gate diffs against `HEAD~N` instead of running the upstream → origin/HEAD → main/master ladder. Useful when a feature branch has accumulated many commits and the full base diff overwhelms the reviewer. Positive integer. CLI `--last-n-commits N` overrides this; `--base <ref>` overrides both. When `HEAD~N` is unreachable the resolver clamps based on whether the repo is a shallow clone: **(full clone, branch shorter than N)** clamps to the empty-tree sentinel so the root commit's changes are included (reviewing all `K+1` commits on the branch); **(shallow clone)** clamps to `HEAD~K` SHA — the deepest locally resolvable ancestor — so the review does not balloon to every tracked file (older history exists on the remote but isn't fetched). A stderr warning surfaces the requested-vs-clamped numbers in both cases. Audit metadata records `base_source: 'last-n-commits'`, `last_n_commits: <count actually reviewed>`, and `last_n_commits_requested: N` (only present when clamped). |
|
|
165
359
|
|
|
166
|
-
###
|
|
360
|
+
### Codex CLI dependency
|
|
167
361
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
362
|
+
The gate shells out to `codex`. **`codex` is a hard prerequisite** when
|
|
363
|
+
`policy.review.codex_required: true` (the default for every profile other
|
|
364
|
+
than the `-no-codex` variants). As of 0.12.0 `rea doctor` runs a
|
|
365
|
+
`codex CLI on PATH` check that **fails** when codex is required by policy
|
|
366
|
+
but the binary is not on `PATH` — surfacing the prereq during install
|
|
367
|
+
rather than at first push:
|
|
368
|
+
|
|
369
|
+
```
|
|
370
|
+
[fail] codex CLI on PATH
|
|
371
|
+
codex not found on PATH. policy.review.codex_required: true requires
|
|
372
|
+
the codex binary. Install: https://github.com/openai/codex
|
|
373
|
+
(e.g. `npm i -g @openai/codex`). To disable the push-gate instead,
|
|
374
|
+
set policy.review.codex_required: false in .rea/policy.yaml.
|
|
171
375
|
```
|
|
172
376
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
policy summary (profile, autonomy, HALT state), audit stats (lines,
|
|
176
|
-
last timestamp, whether the tail record's hash looks well-formed), and
|
|
177
|
-
— as of 0.9.0 — a **per-downstream live block** sourced from
|
|
178
|
-
`.rea/serve.state.json`. Each downstream entry includes:
|
|
179
|
-
|
|
180
|
-
| Field | Type | Meaning |
|
|
181
|
-
| --------------------------- | ------------------------------------ | --------------------------------------------------------------- |
|
|
182
|
-
| `name` | string | Registry server name |
|
|
183
|
-
| `connected` | boolean | MCP client currently holds an open stdio transport |
|
|
184
|
-
| `healthy` | boolean | Gateway considers the server safe to route calls to |
|
|
185
|
-
| `circuit_state` | `closed` \| `open` \| `half-open` | Current breaker position |
|
|
186
|
-
| `retry_at` | ISO timestamp \| `null` | Next allowed half-open probe, when `open` |
|
|
187
|
-
| `last_error` | string \| `null` | Bounded, redacted diagnostic from the most recent failure |
|
|
188
|
-
| `tools_count` | integer \| `null` | Tool count from the last successful `tools/list` |
|
|
189
|
-
| `open_transitions` | integer | Cumulative circuit-open events in this session |
|
|
190
|
-
| `session_blocker_emitted` | boolean | Whether `SESSION_BLOCKER` has fired for this server yet |
|
|
191
|
-
|
|
192
|
-
`.rea/serve.state.json` is the authoritative live source — it is written
|
|
193
|
-
atomically (temp+rename) on every circuit transition and supervisor
|
|
194
|
-
event, debounced through a 250 ms trailing timer so a flap storm can't
|
|
195
|
-
spam disk. State files written by a pre-0.9.0 gateway degrade gracefully:
|
|
196
|
-
`downstreams` surfaces as `null` with a hint to upgrade.
|
|
197
|
-
|
|
198
|
-
Use `rea check` when you want the pure on-disk view (policy + HALT +
|
|
199
|
-
tail audit) without probing for a live process.
|
|
200
|
-
|
|
201
|
-
### 5. Optional Prometheus `/metrics` endpoint
|
|
202
|
-
|
|
203
|
-
`rea serve` can expose a loopback-only Prometheus endpoint when the
|
|
204
|
-
`REA_METRICS_PORT` environment variable is set:
|
|
377
|
+
If a push is attempted without codex on PATH, the gate also returns exit 2
|
|
378
|
+
with the same install hint:
|
|
205
379
|
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
380
|
+
```
|
|
381
|
+
codex CLI not found on PATH. Install with `npm i -g @openai/codex`,
|
|
382
|
+
or set `review.codex_required: false` in .rea/policy.yaml to disable
|
|
383
|
+
the push-gate.
|
|
210
384
|
```
|
|
211
385
|
|
|
212
|
-
|
|
213
|
-
gauge, audit-lines-appended counter, circuit-breaker state gauge, and a
|
|
214
|
-
seconds-since-last-HALT-check gauge. The listener binds to `127.0.0.1`
|
|
215
|
-
only, serves only `GET /metrics` (everything else is a fixed-body 404),
|
|
216
|
-
and never binds by default — "no silent listeners" is a design rule.
|
|
217
|
-
There is no TLS; scrape through SSH/a reverse proxy if you need
|
|
218
|
-
cross-host access.
|
|
386
|
+
Operators who do not have Codex available can either:
|
|
219
387
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
388
|
+
- Run `rea init --profile bst-internal-no-codex` (or `open-source-no-codex`),
|
|
389
|
+
which sets `review.codex_required: false` on install.
|
|
390
|
+
- Flip `review.codex_required: false` in an existing policy file.
|
|
223
391
|
|
|
224
|
-
###
|
|
392
|
+
### Standalone usage
|
|
225
393
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
returns a snapshot of gateway version, uptime, HALT state, policy
|
|
229
|
-
summary, and per-downstream health. The handler **short-circuits the
|
|
230
|
-
middleware chain** — it is callable under HALT and at any autonomy
|
|
231
|
-
level — because it is the tool an operator reaches for when everything
|
|
232
|
-
else is frozen. Every invocation still writes an audit record.
|
|
394
|
+
`rea hook push-gate` is invoked by `.husky/pre-push`, but it is also a
|
|
395
|
+
first-class CLI. Run it manually to test a review without pushing:
|
|
233
396
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
LLM-reachable — which is the right sink for trusted-operator text.
|
|
397
|
+
```bash
|
|
398
|
+
# Review the working tree against the resolved base (@{upstream}, else
|
|
399
|
+
# origin/HEAD, else main/master, else empty-tree).
|
|
400
|
+
rea hook push-gate
|
|
239
401
|
|
|
240
|
-
|
|
402
|
+
# Review against an explicit base.
|
|
403
|
+
rea hook push-gate --base origin/main
|
|
404
|
+
rea hook push-gate --base refs/remotes/upstream/main
|
|
241
405
|
|
|
242
|
-
|
|
243
|
-
#
|
|
244
|
-
|
|
245
|
-
health:
|
|
246
|
-
expose_diagnostics: true
|
|
406
|
+
# Narrow the review to the last N commits (diff vs HEAD~N). Loses to
|
|
407
|
+
# --base when both are set; mirrors policy.review.last_n_commits.
|
|
408
|
+
rea hook push-gate --last-n-commits 10
|
|
247
409
|
```
|
|
248
410
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
any non-`clean` diagnostic string (verdicts `suspicious` or
|
|
252
|
-
`likely_injection`) with the exported `INJECTION_REDACTED_PLACEHOLDER`
|
|
253
|
-
token — the literal string `<redacted: suspected injection>` — and
|
|
254
|
-
oversize values are bounded before scanning so an adversarial downstream
|
|
255
|
-
can't DoS the tool with a multi-megabyte error.
|
|
411
|
+
Exit codes match the pre-push contract. The JSON payload is written to
|
|
412
|
+
`.rea/last-review.json` regardless of invocation context.
|
|
256
413
|
|
|
257
|
-
|
|
414
|
+
### What happens to a protected ref?
|
|
415
|
+
|
|
416
|
+
The gate has no concept of protected vs. unprotected branches; it reviews
|
|
417
|
+
whatever diff git is about to push. Protect-main is enforced by GitHub
|
|
418
|
+
branch protection (required status checks, required reviews, no direct
|
|
419
|
+
pushes to `main`), not by the gate. The gate's job is to surface blocking
|
|
420
|
+
issues before the push reaches the remote.
|
|
421
|
+
|
|
422
|
+
---
|
|
423
|
+
|
|
424
|
+
## MCP gateway
|
|
425
|
+
|
|
426
|
+
`rea serve` is an MCP stdio server. Claude Code starts it via `.mcp.json`
|
|
427
|
+
at the start of a session; it runs for the life of that session. The
|
|
428
|
+
server proxies downstream MCP servers declared in `.rea/registry.yaml`
|
|
429
|
+
through a fixed middleware chain.
|
|
258
430
|
|
|
259
431
|
### Middleware chain
|
|
260
432
|
|
|
261
|
-
Every native
|
|
433
|
+
Every native rea tool call AND every proxied downstream call flows through
|
|
262
434
|
one chain. The order matters — each layer fails closed.
|
|
263
435
|
|
|
264
436
|
```
|
|
@@ -273,13 +445,14 @@ tool call
|
|
|
273
445
|
│ blocked-paths — .rea/ + operator paths │
|
|
274
446
|
│ rate-limit — token bucket per server │
|
|
275
447
|
│ circuit-breaker — trip on downstream failure │
|
|
448
|
+
│ injection (args) — prompt-injection in args │
|
|
276
449
|
│ redact (args) — secrets in arguments │
|
|
277
450
|
│ │
|
|
278
451
|
│ ==== EXECUTE ==== │
|
|
279
452
|
│ │
|
|
280
453
|
│ result-size-cap — bounded response │
|
|
281
454
|
│ redact (result) — secrets in result │
|
|
282
|
-
│ injection
|
|
455
|
+
│ injection (result) — prompt-injection in result │
|
|
283
456
|
│ audit.exit — hash-chained record close │
|
|
284
457
|
└───────────────────────────────────────────────────┘
|
|
285
458
|
│
|
|
@@ -287,415 +460,524 @@ tool call
|
|
|
287
460
|
result
|
|
288
461
|
```
|
|
289
462
|
|
|
290
|
-
`.rea/` is hardcoded as an always-blocked path. It cannot be unblocked
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
`
|
|
316
|
-
`
|
|
317
|
-
`
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
463
|
+
`.rea/` is hardcoded as an always-blocked path. It cannot be unblocked from
|
|
464
|
+
policy. Policy is re-read on every invocation — any edit to `policy.yaml`
|
|
465
|
+
takes effect on the next tool call.
|
|
466
|
+
|
|
467
|
+
### `__rea__health` meta-tool
|
|
468
|
+
|
|
469
|
+
The gateway advertises a single built-in tool, `__rea__health`, in every
|
|
470
|
+
`listTools` response. Calling it returns gateway version, uptime, HALT
|
|
471
|
+
state, policy summary, and per-downstream health. The handler
|
|
472
|
+
**short-circuits the middleware chain** — it is callable under HALT and at
|
|
473
|
+
any autonomy level — because it is the tool an operator reaches for when
|
|
474
|
+
everything else is frozen. Every invocation still writes an audit record.
|
|
475
|
+
|
|
476
|
+
The wire response is sanitized by default: `halt_reason` and
|
|
477
|
+
`downstreams[].last_error` surface as `null`. Full detail lives in the
|
|
478
|
+
audit record. Operators who genuinely need error strings on the MCP wire
|
|
479
|
+
can opt in via `policy.gateway.health.expose_diagnostics: true`; the
|
|
480
|
+
short-circuit still runs the full redact + injection-classify sanitizer
|
|
481
|
+
pass before emitting.
|
|
482
|
+
|
|
483
|
+
### Audit log
|
|
484
|
+
|
|
485
|
+
`.rea/audit.jsonl` is a hash-chained, append-only JSONL file. Each record
|
|
486
|
+
is a single line with:
|
|
487
|
+
|
|
488
|
+
- `seq` — monotonic integer
|
|
489
|
+
- `ts` — ISO-8601 UTC
|
|
490
|
+
- `session_id` — generated at `rea serve` boot
|
|
491
|
+
- `server_name`, `tool_name`, `tier` (`read` | `write` | `destructive`)
|
|
492
|
+
- `status` (`allowed` | `denied` | `error`)
|
|
493
|
+
- `metadata` — tool-specific structured fields (argument digest, target,
|
|
494
|
+
deny reason, verdict)
|
|
495
|
+
- `prev_hash` — SHA-256 of the previous record; tampering is detectable by
|
|
496
|
+
`rea audit verify`
|
|
497
|
+
|
|
498
|
+
Records are redacted on write (secrets swapped for `[REDACTED:*]`,
|
|
499
|
+
injection payloads swapped for `INJECTION_REDACTED_PLACEHOLDER`), never
|
|
500
|
+
LLM-reachable. Rotation is policy-driven (`policy.audit.rotation.max_bytes`
|
|
501
|
+
and/or `max_age_days`); rotation preserves the hash chain by seeding the
|
|
502
|
+
new file with a rotation marker record.
|
|
503
|
+
|
|
504
|
+
### TOFU drift detection
|
|
505
|
+
|
|
506
|
+
Downstream MCP servers are fingerprinted on first sight and stored in
|
|
507
|
+
`.rea/fingerprints.json`. On every `rea serve` boot, each server's canonical
|
|
508
|
+
shape (command path, env-key set, vault list) is hashed and compared
|
|
509
|
+
against the stored fingerprint:
|
|
510
|
+
|
|
511
|
+
- `first-seen` — new server; fingerprint recorded.
|
|
512
|
+
- `unchanged` — match; proceed.
|
|
513
|
+
- `drifted` — mismatch; fail-close. The operator uses `rea tofu list` to
|
|
514
|
+
inspect and `rea tofu accept <name> --reason "..."` to rebase.
|
|
515
|
+
|
|
516
|
+
The drift-accept operation appends a `tofu.drift_accepted_by_cli` audit
|
|
517
|
+
record with the reason. The next boot classifies the server as
|
|
518
|
+
`unchanged`.
|
|
333
519
|
|
|
334
520
|
### Downstream environment safety
|
|
335
521
|
|
|
336
|
-
`rea serve` does **not** forward `process.env` wholesale
|
|
337
|
-
|
|
522
|
+
`rea serve` does **not** forward `process.env` wholesale. Each downstream
|
|
523
|
+
child gets:
|
|
338
524
|
|
|
339
525
|
1. A fixed allowlist of neutral OS vars (`PATH`, `HOME`, `TZ`,
|
|
340
526
|
`NODE_OPTIONS`, …).
|
|
341
|
-
2.
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
3. Values from the registry's `env:` mapping, which may contain
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
downstream is marked unhealthy rather than handed an unresolved
|
|
349
|
-
placeholder.
|
|
350
|
-
|
|
351
|
-
### Hook layer
|
|
352
|
-
|
|
353
|
-
Hooks are shell scripts. 14 ship in the package; 12 are wired into
|
|
354
|
-
the default `.claude/settings.json` and run at Claude Code
|
|
355
|
-
tool-invocation time, independently of the gateway. The remaining
|
|
356
|
-
two (`commit-review-gate.sh` and `push-review-gate-git.sh`) ship
|
|
357
|
-
ready-to-wire but are not registered by default — see "What REA is"
|
|
358
|
-
above and the inventory table at the end of this section for the full
|
|
359
|
-
picture. Both layers (hooks and the gateway middleware) fail closed.
|
|
360
|
-
Bypassing one does not disable the other.
|
|
361
|
-
|
|
362
|
-
Every hook uses `set -euo pipefail` (or `set -uo pipefail` for the
|
|
363
|
-
ones that process stdin JSON) and performs a HALT check near the top.
|
|
364
|
-
The review-gate hooks (`push-review-gate.sh`, `push-review-gate-git.sh`,
|
|
365
|
-
`commit-review-gate.sh`) additionally anchor `REA_ROOT` to their own
|
|
366
|
-
on-disk location (BUG-012 fix, 0.6.2) — for those hooks,
|
|
367
|
-
`CLAUDE_PROJECT_DIR` is accepted only as an advisory signal because it
|
|
368
|
-
is caller-controlled. The remaining hooks (e.g. `secret-scanner.sh`,
|
|
369
|
-
`settings-protection.sh`, `blocked-paths-enforcer.sh`,
|
|
370
|
-
`dangerous-bash-interceptor.sh`) still derive `REA_ROOT` from
|
|
371
|
-
`${CLAUDE_PROJECT_DIR:-$(pwd)}`; extending the script-anchor idiom to
|
|
372
|
-
those hooks is tracked as an open hardening item. Cross-repo
|
|
373
|
-
invocations (running a review-gate hook from a consumer project that
|
|
374
|
-
is not the rea install) short-circuit cleanly using
|
|
375
|
-
`git --git-common-dir` comparison (0.6.1).
|
|
376
|
-
|
|
377
|
-
The two push-review adapters that ship in `hooks/` share a single
|
|
378
|
-
implementation core at `hooks/_lib/push-review-core.sh` (0.7.0 BUG-008
|
|
379
|
-
cleanup) so a fix lands in one place: `push-review-gate.sh` consumes
|
|
380
|
-
Claude-Code PreToolUse JSON and is what `rea init` copies to
|
|
381
|
-
`.claude/hooks/`; `push-review-gate-git.sh` consumes git's native
|
|
382
|
-
`.husky/pre-push` refspec lines and is shipped for consumers who wire
|
|
383
|
-
a wrapper-based `.husky/pre-push` that execs it directly. The default
|
|
384
|
-
`rea init` installer does NOT currently emit that wrapper — it writes
|
|
385
|
-
a standalone inline gate body as `.husky/pre-push` (source of truth:
|
|
386
|
-
`src/cli/install/pre-push.ts`). The native-git adapter and the
|
|
387
|
-
inline installer currently implement the same protected-path logic
|
|
388
|
-
separately; unifying the husky installer on the adapter is tracked as
|
|
389
|
-
follow-up hardening. `commit-review-gate.sh` is a standalone Claude
|
|
390
|
-
`PreToolUse: Bash` hook that matches `git commit`; it does not source
|
|
391
|
-
the push-review core.
|
|
392
|
-
|
|
393
|
-
### Slash commands
|
|
394
|
-
|
|
395
|
-
Five commands ship in the package and are copied into `.claude/commands/`
|
|
396
|
-
during `rea init`.
|
|
397
|
-
|
|
398
|
-
### Agent roster
|
|
399
|
-
|
|
400
|
-
Ten curated agents ship in the package: `rea-orchestrator`, `code-reviewer`,
|
|
401
|
-
`codex-adversarial`, `security-engineer`, `accessibility-engineer`,
|
|
402
|
-
`typescript-specialist`, `frontend-specialist`, `backend-engineer`,
|
|
403
|
-
`qa-engineer`, `technical-writer`. Profiles
|
|
404
|
-
(`client-engagement`, `bst-internal`, `bst-internal-no-codex`,
|
|
405
|
-
`lit-wc`, `open-source`, `open-source-no-codex`, `minimal`) layer
|
|
406
|
-
additional specialists on top. The `-no-codex` variants match their
|
|
407
|
-
parents but default `review.codex_required: false` so teams without a
|
|
408
|
-
Codex CLI on the bench get a first-class opt-out rather than relying on
|
|
409
|
-
`REA_SKIP_CODEX_REVIEW`.
|
|
410
|
-
|
|
411
|
-
The orchestrator is the single entry point for non-trivial tasks. The
|
|
412
|
-
CLAUDE.md template installed by `rea init` instructs the host agent:
|
|
413
|
-
_"For any non-trivial task, delegate to the `rea-orchestrator` agent
|
|
414
|
-
FIRST."_
|
|
415
|
-
|
|
416
|
-
## The Plan / Build / Review loop
|
|
417
|
-
|
|
418
|
-
Codex is a first-class part of REA. It is not a bolt-on. The BST
|
|
419
|
-
engineering process bakes adversarial review into the default flow, and
|
|
420
|
-
REA ships it out of the box.
|
|
421
|
-
|
|
422
|
-
| Phase | Primary model | Codex role | Governance |
|
|
423
|
-
| --- | --- | --- | --- |
|
|
424
|
-
| Plan | Claude Opus | — | Full middleware chain |
|
|
425
|
-
| Pre-implementation review | — | `/codex:review` — review the PLAN before code | Audited |
|
|
426
|
-
| Build | Claude Opus | — | Full middleware chain |
|
|
427
|
-
| Adversarial review | — | `/codex:adversarial-review` on the diff (independent perspective) | Audited, redacted, kill-switched |
|
|
428
|
-
| Pre-merge gate | — | `/codex:adversarial-review` re-run; recorded in audit.jsonl | Required status check (recommended) |
|
|
429
|
-
|
|
430
|
-
Three things make this work:
|
|
431
|
-
|
|
432
|
-
1. The **`codex-adversarial` agent** in the curated roster wraps
|
|
433
|
-
`/codex:adversarial-review`. The orchestrator delegates to it after
|
|
434
|
-
any non-trivial change.
|
|
435
|
-
2. The **`/codex-review` slash command** is one of the five shipped
|
|
436
|
-
commands. It produces an audit entry including the request summary,
|
|
437
|
-
response summary, and pass/fail signal.
|
|
438
|
-
3. The **`push-review-gate.sh` hook** blocks (exit 2) every protected-path
|
|
439
|
-
push that does not carry a matching `codex.review` audit entry for the
|
|
440
|
-
pushed `head_sha` with a `verdict` of `pass` or `concerns`. The only
|
|
441
|
-
other way through the protected-path branch is an active Codex-only
|
|
442
|
-
waiver (`REA_SKIP_CODEX_REVIEW=<reason>`, 0.8.0 narrowing). For
|
|
443
|
-
**non-protected-path** pushes the gate runs a separate review-cache
|
|
444
|
-
lookup — this is where the cache predicate and pushed-ref key
|
|
445
|
-
hardening live. The cache-hit predicate requires
|
|
446
|
-
`.hit == true and .result == "pass"` (0.8.0 hardening — a cached
|
|
447
|
-
`fail` verdict no longer satisfies the gate), and the cache key is
|
|
448
|
-
derived from the **pushed source ref** (from pre-push stdin) rather
|
|
449
|
-
than the checkout branch, so `git push origin hotfix:main` from a
|
|
450
|
-
`feature` checkout correctly looks up the `hotfix` cache entry.
|
|
451
|
-
|
|
452
|
-
### Codex-only waiver semantics (0.8.0)
|
|
453
|
-
|
|
454
|
-
Through 0.7.0, `REA_SKIP_CODEX_REVIEW=<reason>` short-circuited the
|
|
455
|
-
**entire** push-review gate — operators reached for it to silence a
|
|
456
|
-
transient Codex outage and accidentally bypassed HALT, the cross-repo
|
|
457
|
-
guard, and the general push-review gate. 0.8.0 narrows it to what the
|
|
458
|
-
name implies: the waiver satisfies **only** the protected-path Codex
|
|
459
|
-
audit requirement. HALT, cross-repo guard, ref-resolution failures, and
|
|
460
|
-
push-review-cache misses still block. The skip audit record is still
|
|
461
|
-
named `codex.review.skipped` and still fails the `codex.review` jq
|
|
462
|
-
predicate — skipping a review is not a review.
|
|
463
|
-
|
|
464
|
-
For the previous whole-gate bypass, use `REA_SKIP_PUSH_REVIEW=<reason>`
|
|
465
|
-
(unchanged, 0.5.0). It writes `push.review.skipped` with an
|
|
466
|
-
`os_identity` sub-object (uid, whoami, hostname, pid, ppid, tty, ci)
|
|
467
|
-
so auditors can distinguish a real operator from a forged git-config
|
|
468
|
-
actor, and refuses on CI runners unless the policy opts in via
|
|
469
|
-
`review.allow_skip_in_ci: true`.
|
|
470
|
-
|
|
471
|
-
Codex responses are treated as untrusted input. They flow through the
|
|
472
|
-
`redact` and `injection` middleware on return — same treatment as any
|
|
473
|
-
other downstream tool result. Codex never receives `.rea/policy.yaml`
|
|
474
|
-
content in its prompts; Codex reviews diffs, not policy.
|
|
475
|
-
|
|
476
|
-
If Codex is not installed, `rea doctor` warns with a one-line install
|
|
477
|
-
hint. REA does not require Codex to function — the `bst-internal-no-codex`
|
|
478
|
-
and `open-source-no-codex` profiles disable the requirement entirely,
|
|
479
|
-
and `ClaudeSelfReviewer` is the in-process fallback (tagged
|
|
480
|
-
`degraded: true` in the audit record so self-review is visible and
|
|
481
|
-
countable).
|
|
482
|
-
|
|
483
|
-
## Agent push workflow — satisfying the push-review gate
|
|
484
|
-
|
|
485
|
-
When `git push` is blocked by `push-review-gate.sh` the gate prints
|
|
486
|
-
remediation steps. This section is the canonical one-command flow the
|
|
487
|
-
steps reduce to. Agents should copy-paste this verbatim; humans should
|
|
488
|
-
expect agents to.
|
|
489
|
-
|
|
490
|
-
### 1. Run the adversarial review
|
|
527
|
+
2. Names opted into via `registry.yaml#servers[].env_passthrough` — the
|
|
528
|
+
schema refuses secret-looking names (`*_TOKEN`, `*_KEY`, `*_SECRET`, …),
|
|
529
|
+
so secrets must be named explicitly.
|
|
530
|
+
3. Values from the registry's `env:` mapping, which may contain `${VAR}`
|
|
531
|
+
placeholders resolved against the host environment. A `${VAR}` whose
|
|
532
|
+
host variable is unset is treated as fatal — the downstream is marked
|
|
533
|
+
unhealthy rather than handed an unresolved placeholder.
|
|
491
534
|
|
|
492
|
-
|
|
493
|
-
# From an interactive Claude Code session:
|
|
494
|
-
/codex-review
|
|
495
|
-
```
|
|
535
|
+
### Live state
|
|
496
536
|
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
537
|
+
`.rea/serve.state.json` is the on-disk live snapshot. It is written once
|
|
538
|
+
at boot and again on every circuit transition or supervisor event,
|
|
539
|
+
debounced through a 250 ms trailing timer and flushed atomically via
|
|
540
|
+
temp-file + rename. `rea status` reads this file to render a
|
|
541
|
+
per-downstream table; a new `rea serve` whose predecessor crashed can
|
|
542
|
+
detect the abandoned file and take over ownership rather than stalling.
|
|
501
543
|
|
|
502
|
-
###
|
|
544
|
+
### Optional Prometheus metrics
|
|
503
545
|
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
update the push-review cache with a single command:
|
|
546
|
+
`rea serve` can expose a loopback-only Prometheus endpoint when
|
|
547
|
+
`REA_METRICS_PORT` is set:
|
|
507
548
|
|
|
508
549
|
```bash
|
|
509
|
-
rea
|
|
510
|
-
|
|
511
|
-
--branch "$(git rev-parse --abbrev-ref HEAD)" \
|
|
512
|
-
--target main \
|
|
513
|
-
--verdict pass \
|
|
514
|
-
--finding-count 0 \
|
|
515
|
-
--summary "no findings" \
|
|
516
|
-
--also-set-cache
|
|
550
|
+
REA_METRICS_PORT=9464 rea serve
|
|
551
|
+
curl http://127.0.0.1:9464/metrics
|
|
517
552
|
```
|
|
518
553
|
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
554
|
+
Metrics: per-downstream call and error counters, in-flight gauge,
|
|
555
|
+
audit-lines-appended counter, circuit-breaker state gauge, and a
|
|
556
|
+
seconds-since-last-HALT-check gauge. The listener binds to `127.0.0.1`
|
|
557
|
+
only, serves only `GET /metrics`, and never binds by default. No TLS;
|
|
558
|
+
scrape through SSH or a reverse proxy for cross-host access.
|
|
559
|
+
|
|
560
|
+
---
|
|
561
|
+
|
|
562
|
+
## Policy file
|
|
528
563
|
|
|
529
|
-
|
|
564
|
+
`.rea/policy.yaml` fields. The schema is zod-strict — unknown fields are
|
|
565
|
+
rejected at parse time, not ignored.
|
|
530
566
|
|
|
531
|
-
|
|
532
|
-
| ------------ | -------------- | -------------- |
|
|
533
|
-
| `pass` | `pass` | — (omitted) |
|
|
534
|
-
| `concerns` | `pass` | `codex:concerns` |
|
|
535
|
-
| `blocking` | `fail` | `codex:blocking` |
|
|
536
|
-
| `error` | `fail` | `codex:error` |
|
|
567
|
+
### Required fields
|
|
537
568
|
|
|
538
|
-
|
|
569
|
+
| Field | Type | Purpose |
|
|
570
|
+
| --- | --- | --- |
|
|
571
|
+
| `version` | `"1"` | Schema version; only `"1"` accepted in the current major |
|
|
572
|
+
| `profile` | string | Profile name (see below) |
|
|
573
|
+
| `installed_by` | string | Stamped by `rea init` — identifies the installing version |
|
|
574
|
+
| `installed_at` | ISO-8601 | Stamped by `rea init` — install timestamp |
|
|
575
|
+
| `autonomy_level` | `L0` \| `L1` \| `L2` \| `L3` | Current autonomy. `L0` = read-only; `L3` = full tool access |
|
|
576
|
+
| `max_autonomy_level` | `L0` \| `L1` \| `L2` \| `L3` | Hard ceiling. `autonomy_level` cannot exceed this |
|
|
577
|
+
| `promotion_requires_human_approval` | boolean | Require operator confirmation to raise autonomy |
|
|
578
|
+
| `block_ai_attribution` | boolean | Enforce no-AI-attribution in commits and PR bodies |
|
|
579
|
+
| `blocked_paths` | string[] | Glob patterns. `.rea/` is always blocked regardless |
|
|
580
|
+
| `notification_channel` | string | Optional Discord webhook URL; empty string = disabled |
|
|
539
581
|
|
|
540
|
-
|
|
541
|
-
git push
|
|
542
|
-
```
|
|
582
|
+
### Optional blocks
|
|
543
583
|
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
verdict: 'pass',
|
|
572
|
-
},
|
|
573
|
-
});
|
|
584
|
+
```yaml
|
|
585
|
+
injection_detection: block # "block" | "warn" — legacy 0.2.x knob
|
|
586
|
+
injection:
|
|
587
|
+
suspicious_blocks_writes: true # suspicious verdict on write/destructive tier denies
|
|
588
|
+
context_protection:
|
|
589
|
+
delegate_to_subagent:
|
|
590
|
+
- pnpm run build
|
|
591
|
+
- pnpm run test
|
|
592
|
+
max_bash_output_lines: 100
|
|
593
|
+
review:
|
|
594
|
+
codex_required: true
|
|
595
|
+
concerns_blocks: true
|
|
596
|
+
timeout_ms: 1800000
|
|
597
|
+
# last_n_commits: 10 # optional — narrow review to HEAD~N
|
|
598
|
+
redact:
|
|
599
|
+
match_timeout_ms: 100
|
|
600
|
+
patterns:
|
|
601
|
+
- name: custom-api-key
|
|
602
|
+
regex: 'acme_[A-Za-z0-9]{32}'
|
|
603
|
+
flags: 'g'
|
|
604
|
+
audit:
|
|
605
|
+
rotation:
|
|
606
|
+
max_bytes: 52428800 # 50 MiB
|
|
607
|
+
max_age_days: 30
|
|
608
|
+
gateway:
|
|
609
|
+
health:
|
|
610
|
+
expose_diagnostics: false
|
|
574
611
|
```
|
|
575
612
|
|
|
576
|
-
|
|
577
|
-
|
|
613
|
+
User-supplied `redact.patterns[]` are validated via `safe-regex` at load —
|
|
614
|
+
any pattern flagged unsafe fails the load with a specific error naming
|
|
615
|
+
the offender.
|
|
578
616
|
|
|
579
|
-
###
|
|
617
|
+
### Autonomy levels
|
|
580
618
|
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
`
|
|
619
|
+
| Level | Effect |
|
|
620
|
+
| --- | --- |
|
|
621
|
+
| `L0` | Read-only — every write/destructive tier call denies |
|
|
622
|
+
| `L1` | Default. Reads allowed; writes allowed; destructive tier denied |
|
|
623
|
+
| `L2` | Writes and destructive tier allowed |
|
|
624
|
+
| `L3` | No autonomy gate — only hook-layer and policy-layer checks remain |
|
|
625
|
+
|
|
626
|
+
`autonomy_level > max_autonomy_level` is rejected at parse time.
|
|
627
|
+
`promotion_requires_human_approval: false` requires the CLI flag
|
|
628
|
+
`--i-understand-the-risks` on any operation that raises autonomy.
|
|
587
629
|
|
|
588
|
-
|
|
630
|
+
### Profiles
|
|
589
631
|
|
|
590
|
-
|
|
632
|
+
Seven profiles ship in `profiles/`. The profile name is recorded in
|
|
633
|
+
`policy.yaml#profile` and governs which agents `rea init` copies and what
|
|
634
|
+
defaults apply.
|
|
591
635
|
|
|
592
|
-
|
|
|
636
|
+
| Profile | Intended use | Codex default |
|
|
593
637
|
| --- | --- | --- |
|
|
594
|
-
| `
|
|
595
|
-
| `
|
|
596
|
-
| `
|
|
597
|
-
| `
|
|
598
|
-
| `
|
|
599
|
-
| `
|
|
600
|
-
| `
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
638
|
+
| `minimal` | Smallest possible install — curated 10 + opinionated minimal hooks | `true` |
|
|
639
|
+
| `client-engagement` | Consulting engagement where the repo is client-owned | `true` |
|
|
640
|
+
| `bst-internal` | Booked Solid internal projects; conservative posture | `true` |
|
|
641
|
+
| `bst-internal-no-codex` | Same as above; no Codex CLI available | `false` |
|
|
642
|
+
| `lit-wc` | Lit web-component library projects | `true` |
|
|
643
|
+
| `open-source` | OSS library / CLI projects; liberal but audited | `true` |
|
|
644
|
+
| `open-source-no-codex` | Same; no Codex CLI available | `false` |
|
|
645
|
+
|
|
646
|
+
The `-no-codex` variants default `review.codex_required: false` on
|
|
647
|
+
install so teams without a Codex bench get a first-class opt-out. Flip
|
|
648
|
+
`--codex` / `--no-codex` on the `rea init` command line to override.
|
|
649
|
+
|
|
650
|
+
---
|
|
651
|
+
|
|
652
|
+
## Hooks shipped
|
|
653
|
+
|
|
654
|
+
Eleven hooks ship in `hooks/` and are copied into `.claude/hooks/` by
|
|
655
|
+
`rea init`. All eleven are wired by default in the shipped
|
|
656
|
+
`.claude/settings.json`.
|
|
657
|
+
|
|
658
|
+
| Hook | Event | Purpose | Default |
|
|
659
|
+
| --- | --- | --- | --- |
|
|
660
|
+
| `dangerous-bash-interceptor.sh` | `PreToolUse: Bash` | Block categories of destructive shell commands (`rm -rf`, `git reset --hard`, `--no-verify`, …) | Registered |
|
|
661
|
+
| `env-file-protection.sh` | `PreToolUse: Bash` | Block reads of `.env*` files | Registered |
|
|
662
|
+
| `dependency-audit-gate.sh` | `PreToolUse: Bash` | Verify packages exist on the registry before install | Registered |
|
|
663
|
+
| `security-disclosure-gate.sh` | `PreToolUse: Bash` | Route security-keyword `gh issue create` to private disclosure | Registered |
|
|
664
|
+
| `pr-issue-link-gate.sh` | `PreToolUse: Bash` | Advisory warn when `gh pr create` has no linked issue | Registered |
|
|
665
|
+
| `attribution-advisory.sh` | `PreToolUse: Bash` | Block commits/PRs containing AI attribution markers | Registered |
|
|
666
|
+
| `secret-scanner.sh` | `PreToolUse: Write\|Edit` | Scan file writes for credential patterns | Registered |
|
|
667
|
+
| `settings-protection.sh` | `PreToolUse: Write\|Edit` | Block agent writes to `.claude/settings.json`, hook dirs, policy | Registered |
|
|
668
|
+
| `blocked-paths-enforcer.sh` | `PreToolUse: Write\|Edit` | Enforce `blocked_paths` from policy | Registered |
|
|
669
|
+
| `changeset-security-gate.sh` | `PreToolUse: Write\|Edit` | Guard changesets against GHSA leaks and malformed frontmatter | Registered |
|
|
670
|
+
| `architecture-review-gate.sh` | `PostToolUse: Write\|Edit` | Flag edits crossing architectural boundaries (advisory) | Registered |
|
|
671
|
+
|
|
672
|
+
The 0.10.x review-gate scripts (`push-review-gate.sh`,
|
|
673
|
+
`push-review-gate-git.sh`, `commit-review-gate.sh`) and the 1,250-line
|
|
674
|
+
shared bash core (`hooks/_lib/push-review-core.sh`) were removed in
|
|
675
|
+
0.11.0. The `hooks/_lib/` directory now contains only the three shared
|
|
676
|
+
helpers — `common.sh`, `halt-check.sh`, `policy-read.sh` — used by the
|
|
677
|
+
remaining hooks.
|
|
678
|
+
|
|
679
|
+
Every hook uses `set -euo pipefail` (or `set -uo pipefail` for stdin-JSON
|
|
680
|
+
consumers) and performs a HALT check near the top. Both the hook layer
|
|
681
|
+
and the MCP gateway middleware fail closed; bypassing one does not
|
|
682
|
+
disable the other.
|
|
683
|
+
|
|
684
|
+
---
|
|
608
685
|
|
|
609
686
|
## Slash commands
|
|
610
687
|
|
|
688
|
+
Five commands ship in `commands/` and are copied into `.claude/commands/`
|
|
689
|
+
by `rea init`.
|
|
690
|
+
|
|
611
691
|
| Command | Purpose |
|
|
612
692
|
| --- | --- |
|
|
613
|
-
| `/rea` | Session status — autonomy level, HALT state,
|
|
693
|
+
| `/rea` | Session status — autonomy level, HALT state, recent audit entries, next action |
|
|
614
694
|
| `/review` | Invoke the `code-reviewer` agent on current changes |
|
|
615
|
-
| `/codex-review` | Invoke the `codex-adversarial` agent
|
|
695
|
+
| `/codex-review` | Invoke the `codex-adversarial` agent via the Codex plugin |
|
|
616
696
|
| `/freeze` | Prompt for a reason and write `.rea/HALT` |
|
|
617
|
-
| `/halt-check` |
|
|
697
|
+
| `/halt-check` | Smoke test — verify every hook and middleware respects HALT |
|
|
618
698
|
|
|
619
|
-
|
|
699
|
+
---
|
|
620
700
|
|
|
621
|
-
|
|
622
|
-
rejected, not ignored.
|
|
701
|
+
## Curated agents
|
|
623
702
|
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
|
629
|
-
|
|
|
630
|
-
| `
|
|
631
|
-
| `
|
|
632
|
-
| `
|
|
633
|
-
| `
|
|
634
|
-
| `
|
|
635
|
-
| `
|
|
636
|
-
| `
|
|
637
|
-
| `
|
|
638
|
-
| `
|
|
639
|
-
| `
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
703
|
+
Ten specialist agents ship in `agents/` and are copied into `.claude/agents/`
|
|
704
|
+
by `rea init`. Profiles layer additional specialists on top for specific
|
|
705
|
+
project shapes.
|
|
706
|
+
|
|
707
|
+
| Agent | When to use |
|
|
708
|
+
| --- | --- |
|
|
709
|
+
| `rea-orchestrator` | **First stop for any non-trivial task.** Reads policy, checks HALT, routes to the right specialist(s), coordinates multi-step work, enforces the plan/build/review loop. |
|
|
710
|
+
| `code-reviewer` | Structured review of a working-tree diff; surfaces correctness, clarity, and consistency issues without adversarial framing. |
|
|
711
|
+
| `codex-adversarial` | Adversarial review via the Codex plugin (`/codex:adversarial-review`). Independent model perspective; produces an audit entry with verdict. |
|
|
712
|
+
| `security-engineer` | Security-sensitive implementation and review — auth flows, secret handling, injection surfaces. |
|
|
713
|
+
| `accessibility-engineer` | WCAG review, ARIA semantics, keyboard navigation, screen-reader fact-checking. |
|
|
714
|
+
| `typescript-specialist` | Strict-mode TypeScript correctness, generics, narrowing, inference edge cases. |
|
|
715
|
+
| `frontend-specialist` | UI component work, framework idioms (React, Lit, Astro), CSS architecture. |
|
|
716
|
+
| `backend-engineer` | API design, database schema, background jobs, MCP server implementation. |
|
|
717
|
+
| `qa-engineer` | Test strategy, fixture design, regression reproducers, flake triage. |
|
|
718
|
+
| `technical-writer` | User-facing documentation, API references, migration guides, changelog narratives. |
|
|
719
|
+
|
|
720
|
+
The `rea-orchestrator` is the single entry point for non-trivial tasks.
|
|
721
|
+
The CLAUDE.md fragment installed by `rea init` instructs the host agent
|
|
722
|
+
to route there first; delegation contracts are defined in each agent's
|
|
723
|
+
markdown file.
|
|
724
|
+
|
|
725
|
+
---
|
|
726
|
+
|
|
727
|
+
## CLI reference
|
|
728
|
+
|
|
729
|
+
```
|
|
730
|
+
rea <command> [options]
|
|
731
|
+
```
|
|
732
|
+
|
|
733
|
+
Run `rea <command> --help` for full per-command options.
|
|
734
|
+
|
|
735
|
+
### `rea init`
|
|
736
|
+
|
|
737
|
+
Interactive wizard — write `.rea/policy.yaml`, install `.claude/`, the
|
|
738
|
+
commit-msg hook, and a CLAUDE.md fragment.
|
|
739
|
+
|
|
740
|
+
```bash
|
|
741
|
+
rea init
|
|
742
|
+
rea init -y --profile bst-internal # non-interactive
|
|
743
|
+
rea init --from-reagent # migrate from .reagent/
|
|
744
|
+
rea init --profile open-source-no-codex # disable Codex by default
|
|
745
|
+
rea init --force # overwrite existing artifacts
|
|
746
|
+
```
|
|
747
|
+
|
|
748
|
+
### `rea upgrade`
|
|
749
|
+
|
|
750
|
+
Sync `.claude/`, `.husky/`, and managed fragments with this rea version.
|
|
751
|
+
Prompts on drift; silently refreshes unmodified files.
|
|
752
|
+
|
|
753
|
+
```bash
|
|
754
|
+
rea upgrade --dry-run # show what would change; write nothing
|
|
755
|
+
rea upgrade # interactive
|
|
756
|
+
rea upgrade -y # non-interactive, keep drifted files
|
|
757
|
+
rea upgrade --force # non-interactive, overwrite drift
|
|
758
|
+
```
|
|
759
|
+
|
|
760
|
+
### `rea serve`
|
|
761
|
+
|
|
762
|
+
Start the MCP gateway. Invoked by Claude Code via `.mcp.json`; not a
|
|
763
|
+
daemon. Stdio transport only.
|
|
764
|
+
|
|
765
|
+
```bash
|
|
766
|
+
rea serve
|
|
767
|
+
REA_METRICS_PORT=9464 rea serve
|
|
768
|
+
REA_LOG_LEVEL=debug rea serve
|
|
769
|
+
```
|
|
770
|
+
|
|
771
|
+
### `rea freeze` / `rea unfreeze`
|
|
772
|
+
|
|
773
|
+
Write or remove `.rea/HALT`. Every call writes an audit record.
|
|
774
|
+
|
|
775
|
+
```bash
|
|
776
|
+
rea freeze --reason "incident triage"
|
|
777
|
+
rea unfreeze
|
|
778
|
+
rea unfreeze -y # skip confirmation
|
|
779
|
+
```
|
|
780
|
+
|
|
781
|
+
### `rea check`
|
|
782
|
+
|
|
783
|
+
On-disk status — autonomy, HALT, profile, recent audit entries. No live
|
|
784
|
+
process probe.
|
|
785
|
+
|
|
786
|
+
### `rea status`
|
|
787
|
+
|
|
788
|
+
Running-process view — reads `.rea/serve.pid` + `.rea/serve.state.json`
|
|
789
|
+
to render per-downstream health.
|
|
790
|
+
|
|
791
|
+
```bash
|
|
792
|
+
rea status
|
|
793
|
+
rea status --json # pipe to jq
|
|
794
|
+
```
|
|
795
|
+
|
|
796
|
+
### `rea doctor`
|
|
797
|
+
|
|
798
|
+
Validate the install — policy parses, `.rea/` layout, hooks, Codex plugin
|
|
799
|
+
presence, TOFU fingerprint store.
|
|
800
|
+
|
|
801
|
+
```bash
|
|
802
|
+
rea doctor
|
|
803
|
+
rea doctor --metrics # also print 7-day Codex telemetry summary
|
|
804
|
+
rea doctor --drift # report drift vs. install manifest (read-only)
|
|
805
|
+
```
|
|
806
|
+
|
|
807
|
+
In non-git directories the commit-msg and pre-push checks are skipped
|
|
808
|
+
cleanly. Audit hash-chain integrity is verified by `rea audit verify`,
|
|
809
|
+
not by `rea doctor`.
|
|
647
810
|
|
|
648
|
-
|
|
811
|
+
### `rea audit rotate` / `rea audit verify`
|
|
649
812
|
|
|
650
813
|
```bash
|
|
651
|
-
|
|
814
|
+
rea audit rotate # force rotation now
|
|
815
|
+
rea audit verify # re-hash the chain; exit 1 on first tamper
|
|
816
|
+
rea audit verify --since <file> # walk forward from a rotated file
|
|
652
817
|
```
|
|
653
818
|
|
|
654
|
-
|
|
819
|
+
### `rea tofu list` / `rea tofu accept`
|
|
820
|
+
|
|
821
|
+
```bash
|
|
822
|
+
rea tofu list
|
|
823
|
+
rea tofu list --json
|
|
824
|
+
rea tofu accept my-mcp-server --reason "added vault path /Volumes/Work"
|
|
825
|
+
```
|
|
826
|
+
|
|
827
|
+
### `rea hook push-gate`
|
|
828
|
+
|
|
829
|
+
Pre-push Codex review gate. Normally invoked by `.husky/pre-push`; run
|
|
830
|
+
manually to test.
|
|
831
|
+
|
|
832
|
+
```bash
|
|
833
|
+
rea hook push-gate
|
|
834
|
+
rea hook push-gate --base origin/main
|
|
835
|
+
```
|
|
836
|
+
|
|
837
|
+
---
|
|
838
|
+
|
|
839
|
+
## Migration from 0.10.x
|
|
840
|
+
|
|
841
|
+
`rea upgrade` handles the policy and on-disk pieces. You run it once:
|
|
842
|
+
|
|
843
|
+
```bash
|
|
844
|
+
rea upgrade
|
|
845
|
+
```
|
|
846
|
+
|
|
847
|
+
It performs:
|
|
848
|
+
|
|
849
|
+
1. **Backup** — writes `.rea/policy.yaml.bak-<timestamp>` before any edit.
|
|
850
|
+
2. **Strip removed fields** — removes `review.cache_max_age_seconds` and
|
|
851
|
+
`review.allow_skip_in_ci` from the policy file. Both were cache-gate
|
|
852
|
+
concepts with no meaning under the stateless gate.
|
|
853
|
+
3. **Add defaults** — inserts `review.concerns_blocks: true` if absent.
|
|
854
|
+
4. **Prune settings.json** — removes hook registrations for the removed
|
|
855
|
+
scripts (`push-review-gate`, `commit-review-gate`) from
|
|
856
|
+
`.claude/settings.json`, leaving the other registrations intact.
|
|
857
|
+
5. **Refresh `.claude/hooks/`** — deletes the removed scripts with a
|
|
858
|
+
change-log entry.
|
|
859
|
+
6. **Rewrite `.husky/pre-push`** iff it carries a rea marker. A
|
|
860
|
+
foreign `.husky/pre-push` (no marker) is left untouched and a loud
|
|
861
|
+
warning is printed.
|
|
862
|
+
7. **Refresh `.git/hooks/pre-push`** fallback with the new 15-line stub
|
|
863
|
+
(when `core.hooksPath` is unset and git is using the default hooks
|
|
864
|
+
directory).
|
|
865
|
+
|
|
866
|
+
### What the new `.husky/pre-push` looks like
|
|
655
867
|
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
- Moves `.reagent/audit.jsonl` to `.rea/audit.jsonl` and verifies the
|
|
660
|
-
hash chain.
|
|
661
|
-
- Un-wires dropped hooks (Obsidian, PM-layer, account) from
|
|
662
|
-
`.claude/settings.json`.
|
|
663
|
-
- Replaces `reagent` slash commands and agents with the REA equivalents.
|
|
664
|
-
- Leaves `.reagent/` in place; you delete it manually after verifying
|
|
665
|
-
`rea doctor` passes and a dogfood run completes.
|
|
868
|
+
Fifteen lines of POSIX `sh`. HALT check, locate the rea binary, `exec rea
|
|
869
|
+
hook push-gate "$@"`. That is the entire body — all real work lives in
|
|
870
|
+
the TypeScript gate composer at `src/hooks/push-gate/index.ts`.
|
|
666
871
|
|
|
667
|
-
|
|
668
|
-
/ BUG-010 coordinated fix window. Between 0.5.0 and 0.9.0, the breaking
|
|
669
|
-
semantic change worth calling out is 0.8.0's narrowing of
|
|
670
|
-
`REA_SKIP_CODEX_REVIEW` to a Codex-only waiver — see the CHANGELOG
|
|
671
|
-
entry for the migration steps.
|
|
872
|
+
### Rollback
|
|
672
873
|
|
|
673
|
-
|
|
874
|
+
If the stateless gate's behavior is blocking a specific workflow you
|
|
875
|
+
cannot yet address:
|
|
876
|
+
|
|
877
|
+
```bash
|
|
878
|
+
npm install -g @bookedsolid/rea@0.10.3
|
|
879
|
+
# restore policy from the backup rea upgrade wrote
|
|
880
|
+
cp .rea/policy.yaml.bak-<ts> .rea/policy.yaml
|
|
881
|
+
# re-run the 0.10.3 install to put the old hooks back
|
|
882
|
+
rea init --force
|
|
883
|
+
```
|
|
884
|
+
|
|
885
|
+
The 0.10.3 cache-attestation gate remains on npm and continues to work.
|
|
886
|
+
Rollback is a supported path — the `rea upgrade` backup is specifically
|
|
887
|
+
there to make it a one-liner.
|
|
888
|
+
|
|
889
|
+
### What you will not need to do anymore
|
|
890
|
+
|
|
891
|
+
- `rea cache check` / `rea cache set` / `rea cache list` / `rea cache clear`
|
|
892
|
+
— all removed. The stateless gate consults no cache.
|
|
893
|
+
- `rea audit record codex-review --also-set-cache` — removed. The gate
|
|
894
|
+
writes its own audit records from the actual Codex run.
|
|
895
|
+
- Setting `REA_SKIP_PUSH_REVIEW` — removed. Use either
|
|
896
|
+
`REA_SKIP_PUSH_GATE=<reason>` or `REA_SKIP_CODEX_REVIEW=<reason>`
|
|
897
|
+
(both value-carrying and always audited; identical effect, distinct
|
|
898
|
+
`skip_var` in audit metadata) or flip `review.codex_required: false`
|
|
899
|
+
in policy. `REA_SKIP_CODEX_REVIEW` was reinstated in 0.12.0 as an
|
|
900
|
+
audited alias for `REA_SKIP_PUSH_GATE` — it had been documented in the
|
|
901
|
+
gateway-tier reviewers but not in the push-gate, leaving agents
|
|
902
|
+
setting the documented variant blocked.
|
|
903
|
+
|
|
904
|
+
---
|
|
905
|
+
|
|
906
|
+
## Contributor quality gates
|
|
907
|
+
|
|
908
|
+
Before push, run the four checks locally. CI runs them as required status
|
|
909
|
+
checks on every PR to `main`:
|
|
910
|
+
|
|
911
|
+
```bash
|
|
912
|
+
pnpm lint # ESLint 10 — zero warnings
|
|
913
|
+
pnpm type-check # tsc --noEmit (strict)
|
|
914
|
+
pnpm test # vitest run
|
|
915
|
+
pnpm build # tsc -p tsconfig.build.json
|
|
916
|
+
```
|
|
917
|
+
|
|
918
|
+
Additionally, every PR needs:
|
|
919
|
+
|
|
920
|
+
- **DCO sign-off** on all commits (`git commit -s`). The DCO bot rejects
|
|
921
|
+
unsigned commits.
|
|
922
|
+
- **Changeset** entry (`pnpm changeset`) unless the change is purely
|
|
923
|
+
non-publishable (CI, docs, meta). CI flags missing changesets.
|
|
924
|
+
- **Secret scan** clean. Gitleaks runs in CI and via the
|
|
925
|
+
`secret-scanner.sh` hook.
|
|
926
|
+
- **No AI attribution** anywhere — commit messages, PR bodies, code
|
|
927
|
+
comments, changeset content. The commit-msg hook and
|
|
928
|
+
`attribution-advisory.sh` reject structural attribution.
|
|
929
|
+
|
|
930
|
+
Security-sensitive paths (`src/gateway/middleware/**`, `src/policy/**`,
|
|
931
|
+
`src/hooks/**`, `hooks/**`, `.github/workflows/**`) require explicit
|
|
932
|
+
maintainer review and a threat-model update in the same PR.
|
|
933
|
+
|
|
934
|
+
Releases flow through Changesets: a "Version Packages" PR is auto-opened
|
|
935
|
+
when a changeset lands on `main`. Merging it triggers `npm publish
|
|
936
|
+
--provenance` via OIDC from `.github/workflows/release.yml`. Do not
|
|
937
|
+
manually `npm publish`.
|
|
938
|
+
|
|
939
|
+
---
|
|
940
|
+
|
|
941
|
+
## Threat model and security
|
|
674
942
|
|
|
675
943
|
- [SECURITY.md](./SECURITY.md) — disclosure policy, supported versions,
|
|
676
|
-
|
|
944
|
+
GHSA coordination. 72-hour acknowledgment target, 90-day window. Do
|
|
945
|
+
not report vulnerabilities via public GitHub issues.
|
|
677
946
|
- [THREAT_MODEL.md](./THREAT_MODEL.md) — attack surface, mitigations,
|
|
678
|
-
residual risks.
|
|
947
|
+
residual risks. The contract rea holds itself to.
|
|
679
948
|
|
|
680
|
-
Short version: gateway and hook
|
|
681
|
-
closed. `.rea/` is always blocked.
|
|
682
|
-
re-read on every invocation. npm publish uses
|
|
683
|
-
long-lived tokens.
|
|
949
|
+
Short version: the MCP gateway and the hook layer run independently.
|
|
950
|
+
Both fail closed. `.rea/` is always blocked. The audit log is
|
|
951
|
+
hash-chained. Policy is re-read on every invocation. npm publish uses
|
|
952
|
+
OIDC provenance, not long-lived tokens. The pre-push gate runs Codex
|
|
953
|
+
on every push and treats Codex responses as untrusted input — findings
|
|
954
|
+
flow through the same redact pattern set used by the middleware before
|
|
955
|
+
anything hits disk.
|
|
684
956
|
|
|
685
|
-
|
|
957
|
+
---
|
|
686
958
|
|
|
687
|
-
|
|
959
|
+
## Non-goals
|
|
688
960
|
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
Prettier, vitest.
|
|
695
|
-
- Security-sensitive paths (`src/gateway/middleware/**`, `src/policy/**`,
|
|
696
|
-
`hooks/**`, `.github/workflows/**`) require explicit maintainer
|
|
697
|
-
review and a threat-model update in the same PR.
|
|
961
|
+
See [What REA is NOT](#what-rea-is-not) above. Every "but what if we
|
|
962
|
+
just added X" belongs in a separate package that composes with REA. The
|
|
963
|
+
non-goals are the product.
|
|
964
|
+
|
|
965
|
+
---
|
|
698
966
|
|
|
699
|
-
## License
|
|
967
|
+
## License and contributing
|
|
700
968
|
|
|
701
|
-
[MIT](./LICENSE)
|
|
969
|
+
[MIT](./LICENSE). See [CONTRIBUTING.md](./CONTRIBUTING.md) for the full
|
|
970
|
+
contributor guide and [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md) for
|
|
971
|
+
the Contributor Covenant.
|
|
972
|
+
|
|
973
|
+
- DCO sign-off required (`git commit -s`) — no CLA.
|
|
974
|
+
- Conventional commits, TypeScript strict, ESLint zero-warnings,
|
|
975
|
+
Prettier, vitest.
|
|
976
|
+
- Changeset on every publishable change; merge the auto-generated
|
|
977
|
+
Version Packages PR to release.
|
|
978
|
+
- Security-sensitive paths gated by CODEOWNERS; human review required.
|
|
979
|
+
|
|
980
|
+
This repo dogfoods itself. rea's governance layer enforces rea's own
|
|
981
|
+
commit, hook, and attribution rules. The install under `.rea/`,
|
|
982
|
+
`.claude/`, and `.husky/` is the reference example of the
|
|
983
|
+
`bst-internal` profile.
|