@bookedsolid/rea 0.11.0 → 0.13.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/commit-msg +32 -0
- package/.husky/pre-push +88 -13
- package/README.md +914 -550
- package/dist/cli/doctor.d.ts +12 -0
- package/dist/cli/doctor.js +152 -1
- package/dist/cli/hook.d.ts +7 -0
- package/dist/cli/hook.js +12 -1
- package/dist/cli/install/commit-msg.d.ts +34 -0
- package/dist/cli/install/commit-msg.js +60 -0
- package/dist/cli/install/pre-push.d.ts +32 -10
- package/dist/cli/install/pre-push.js +106 -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/codex-runner.d.ts +8 -0
- package/dist/hooks/push-gate/codex-runner.js +13 -0
- package/dist/hooks/push-gate/index.d.ts +8 -0
- package/dist/hooks/push-gate/index.js +162 -22
- package/dist/hooks/push-gate/policy.d.ts +39 -4
- package/dist/hooks/push-gate/policy.js +29 -4
- package/dist/policy/loader.d.ts +19 -0
- package/dist/policy/loader.js +11 -0
- package/dist/policy/types.d.ts +72 -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.
|
|
92
|
+
|
|
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
|
+
```
|
|
27
106
|
|
|
28
|
-
|
|
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,338 @@ 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
|
-
|
|
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`.
|
|
331
|
+
|
|
332
|
+
### Environment variables
|
|
333
|
+
|
|
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. |
|
|
339
|
+
|
|
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.
|
|
351
|
+
auto_narrow_threshold: 30 # OPTIONAL — auto-narrow when commit count
|
|
352
|
+
# behind base > N (0 disables, default 30)
|
|
144
353
|
```
|
|
145
354
|
|
|
146
|
-
|
|
147
|
-
|
|
355
|
+
| Key | Type | Default | Purpose |
|
|
356
|
+
| --- | --- | --- | --- |
|
|
357
|
+
| `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`. |
|
|
358
|
+
| `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. |
|
|
359
|
+
| `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. |
|
|
360
|
+
| `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). |
|
|
361
|
+
| `review.auto_narrow_threshold` | number | `30` | Added in **0.13.0**. When the resolved diff base is more than N commits behind HEAD AND the base was resolved from the active refspec's `remoteSha` (i.e. previously-pushed remote tip of this branch — commits already Codex-reviewed) AND no explicit narrowing was set, the gate auto-scopes to the last 10 commits and emits a stderr warning. Set to `0` to disable. **Suppressed** when any of `--base`, `--last-n-commits`, `policy.review.last_n_commits`, OR the base was resolved via the upstream / origin-head / origin-main ladder (initial push, no upstream, fallback to trunk). Auto-narrow never fires on initial pushes — earlier commits on the branch may not have been reviewed yet, and skipping past them would silently bypass the gate's coverage contract. Audit metadata records `auto_narrowed: true` and `original_commit_count: N` so operators can grep for narrowed reviews. Background: large feature branches (50+ commits relative to a previously-pushed tip) routinely produced non-deterministic Codex verdicts and 30-minute timeouts; J makes the protective default automatic without compromising first-push coverage. |
|
|
362
|
+
|
|
363
|
+
### Auto-narrow on large divergence (0.13.0)
|
|
148
364
|
|
|
149
|
-
|
|
365
|
+
When pushing a long-running branch that has already been pushed before (so
|
|
366
|
+
the remote tip is the previously-reviewed Codex baseline), follow-up
|
|
367
|
+
pushes that pile up many commits since the last push can timeout the
|
|
368
|
+
reviewer or produce inconsistent verdicts. **Auto-narrow** detects this
|
|
369
|
+
case and scopes the review down to recent commits automatically:
|
|
370
|
+
|
|
371
|
+
```
|
|
372
|
+
$ git push origin feature/big-thing
|
|
373
|
+
rea: auto-narrow — 80 commits behind <previous-remote-tip-sha> (threshold 30);
|
|
374
|
+
reviewing the last 10 commits instead.
|
|
375
|
+
Override: pass `--last-n-commits N` or `--base <ref>`, set
|
|
376
|
+
`review.last_n_commits` in .rea/policy.yaml, or disable with
|
|
377
|
+
`review.auto_narrow_threshold: 0`.
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
The probe runs `git rev-list --count base..HEAD` after base resolution.
|
|
381
|
+
**Auto-narrow only fires when the base was resolved from the active
|
|
382
|
+
refspec's `remoteSha`** — i.e. the previously-pushed tip of this branch,
|
|
383
|
+
where the older commits have already been Codex-reviewed in a prior push.
|
|
384
|
+
Initial pushes (where the resolver falls back to `origin/main` or the
|
|
385
|
+
upstream ladder) are NEVER auto-narrowed: skipping past those earlier
|
|
386
|
+
commits would silently bypass the advertised pre-push review for any
|
|
387
|
+
hook/policy/security change made early in the branch.
|
|
388
|
+
|
|
389
|
+
When eligible and the count exceeds `review.auto_narrow_threshold`
|
|
390
|
+
(default 30) and no narrowing override is in effect, the gate re-resolves
|
|
391
|
+
to `HEAD~10` and proceeds with the smaller diff. Every reviewed event
|
|
392
|
+
includes `auto_narrowed: true` + `original_commit_count: <N>` in audit
|
|
393
|
+
metadata.
|
|
394
|
+
|
|
395
|
+
To opt out for one push: pass `--last-n-commits N` or `--base <ref>`. To
|
|
396
|
+
opt out persistently: set `review.last_n_commits` (any value), or set
|
|
397
|
+
`review.auto_narrow_threshold: 0`.
|
|
398
|
+
|
|
399
|
+
### Extension-hook chaining (0.13.0)
|
|
400
|
+
|
|
401
|
+
Drop executable scripts into `.husky/commit-msg.d/` or `.husky/pre-push.d/`
|
|
402
|
+
and rea will run them after its own governance work, in lexical order, with
|
|
403
|
+
the same positional args. Useful for layering commitlint, conventional-
|
|
404
|
+
commits linters, branch-policy checks, or any other per-commit / per-push
|
|
405
|
+
work without losing rea coverage.
|
|
150
406
|
|
|
151
407
|
```bash
|
|
152
|
-
|
|
408
|
+
mkdir -p .husky/pre-push.d
|
|
409
|
+
cat > .husky/pre-push.d/10-commitlint <<'EOF'
|
|
410
|
+
#!/bin/sh
|
|
411
|
+
# Verify every new commit on the pushed range has a conventional message.
|
|
412
|
+
exec npx --no-install commitlint --from "origin/main" --to "HEAD"
|
|
413
|
+
EOF
|
|
414
|
+
chmod +x .husky/pre-push.d/10-commitlint
|
|
153
415
|
```
|
|
154
416
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
417
|
+
Rules:
|
|
418
|
+
|
|
419
|
+
- **Sourced AFTER rea's body** — HALT, attribution blocking, and Codex
|
|
420
|
+
review run first; fragments only fire when rea succeeds. A non-zero exit
|
|
421
|
+
from rea short-circuits before any fragment runs.
|
|
422
|
+
- **Lexical order** — `10-foo` runs before `20-bar` runs before `90-baz`.
|
|
423
|
+
The standard convention is to prefix with a two-digit ordering number.
|
|
424
|
+
- **Executable bit gates** — only files with `chmod +x` are run. A README
|
|
425
|
+
or `.disabled` file in the directory is silently skipped.
|
|
426
|
+
- **Non-zero exit fails the hook** — the next fragment does not run, the
|
|
427
|
+
push / commit is blocked. This matches husky's normal hook chaining
|
|
428
|
+
semantics.
|
|
429
|
+
- **Missing directory is a no-op** — backward compatible with consumers
|
|
430
|
+
who never opt into fragments.
|
|
431
|
+
- **Fragments cannot replay pre-push stdin** — git delivers refspec data
|
|
432
|
+
on stdin which rea consumes during its own review. Fragments that need
|
|
433
|
+
refspec data should run before rea (use a custom hook in
|
|
434
|
+
`core.hooksPath` instead). Fragments that need ambient repo state can
|
|
435
|
+
call `git rev-parse` themselves.
|
|
436
|
+
|
|
437
|
+
`rea doctor` lists every fragment it sees and warns when a non-executable
|
|
438
|
+
file is sitting in either directory (silently skipped at hook-fire time).
|
|
439
|
+
|
|
440
|
+
### Codex CLI dependency
|
|
441
|
+
|
|
442
|
+
The gate shells out to `codex`. **`codex` is a hard prerequisite** when
|
|
443
|
+
`policy.review.codex_required: true` (the default for every profile other
|
|
444
|
+
than the `-no-codex` variants). As of 0.12.0 `rea doctor` runs a
|
|
445
|
+
`codex CLI on PATH` check that **fails** when codex is required by policy
|
|
446
|
+
but the binary is not on `PATH` — surfacing the prereq during install
|
|
447
|
+
rather than at first push:
|
|
165
448
|
|
|
166
|
-
|
|
449
|
+
```
|
|
450
|
+
[fail] codex CLI on PATH
|
|
451
|
+
codex not found on PATH. policy.review.codex_required: true requires
|
|
452
|
+
the codex binary. Install: https://github.com/openai/codex
|
|
453
|
+
(e.g. `npm i -g @openai/codex`). To disable the push-gate instead,
|
|
454
|
+
set policy.review.codex_required: false in .rea/policy.yaml.
|
|
455
|
+
```
|
|
167
456
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
rea status --json # JSON — pipe to jq
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
`rea status` is the live-process view. It reads the pidfile written by
|
|
174
|
-
`rea serve`, verifies the pid is alive, and surfaces the session id,
|
|
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:
|
|
457
|
+
If a push is attempted without codex on PATH, the gate also returns exit 2
|
|
458
|
+
with the same install hint:
|
|
205
459
|
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
460
|
+
```
|
|
461
|
+
codex CLI not found on PATH. Install with `npm i -g @openai/codex`,
|
|
462
|
+
or set `review.codex_required: false` in .rea/policy.yaml to disable
|
|
463
|
+
the push-gate.
|
|
210
464
|
```
|
|
211
465
|
|
|
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.
|
|
466
|
+
Operators who do not have Codex available can either:
|
|
219
467
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
468
|
+
- Run `rea init --profile bst-internal-no-codex` (or `open-source-no-codex`),
|
|
469
|
+
which sets `review.codex_required: false` on install.
|
|
470
|
+
- Flip `review.codex_required: false` in an existing policy file.
|
|
223
471
|
|
|
224
|
-
###
|
|
472
|
+
### Standalone usage
|
|
225
473
|
|
|
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.
|
|
474
|
+
`rea hook push-gate` is invoked by `.husky/pre-push`, but it is also a
|
|
475
|
+
first-class CLI. Run it manually to test a review without pushing:
|
|
233
476
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
LLM-reachable — which is the right sink for trusted-operator text.
|
|
477
|
+
```bash
|
|
478
|
+
# Review the working tree against the resolved base (@{upstream}, else
|
|
479
|
+
# origin/HEAD, else main/master, else empty-tree).
|
|
480
|
+
rea hook push-gate
|
|
239
481
|
|
|
240
|
-
|
|
482
|
+
# Review against an explicit base.
|
|
483
|
+
rea hook push-gate --base origin/main
|
|
484
|
+
rea hook push-gate --base refs/remotes/upstream/main
|
|
241
485
|
|
|
242
|
-
|
|
243
|
-
#
|
|
244
|
-
|
|
245
|
-
health:
|
|
246
|
-
expose_diagnostics: true
|
|
486
|
+
# Narrow the review to the last N commits (diff vs HEAD~N). Loses to
|
|
487
|
+
# --base when both are set; mirrors policy.review.last_n_commits.
|
|
488
|
+
rea hook push-gate --last-n-commits 10
|
|
247
489
|
```
|
|
248
490
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
491
|
+
Exit codes match the pre-push contract. The JSON payload is written to
|
|
492
|
+
`.rea/last-review.json` regardless of invocation context.
|
|
493
|
+
|
|
494
|
+
### What happens to a protected ref?
|
|
495
|
+
|
|
496
|
+
The gate has no concept of protected vs. unprotected branches; it reviews
|
|
497
|
+
whatever diff git is about to push. Protect-main is enforced by GitHub
|
|
498
|
+
branch protection (required status checks, required reviews, no direct
|
|
499
|
+
pushes to `main`), not by the gate. The gate's job is to surface blocking
|
|
500
|
+
issues before the push reaches the remote.
|
|
256
501
|
|
|
257
|
-
|
|
502
|
+
---
|
|
503
|
+
|
|
504
|
+
## MCP gateway
|
|
505
|
+
|
|
506
|
+
`rea serve` is an MCP stdio server. Claude Code starts it via `.mcp.json`
|
|
507
|
+
at the start of a session; it runs for the life of that session. The
|
|
508
|
+
server proxies downstream MCP servers declared in `.rea/registry.yaml`
|
|
509
|
+
through a fixed middleware chain.
|
|
258
510
|
|
|
259
511
|
### Middleware chain
|
|
260
512
|
|
|
261
|
-
Every native
|
|
513
|
+
Every native rea tool call AND every proxied downstream call flows through
|
|
262
514
|
one chain. The order matters — each layer fails closed.
|
|
263
515
|
|
|
264
516
|
```
|
|
@@ -273,13 +525,14 @@ tool call
|
|
|
273
525
|
│ blocked-paths — .rea/ + operator paths │
|
|
274
526
|
│ rate-limit — token bucket per server │
|
|
275
527
|
│ circuit-breaker — trip on downstream failure │
|
|
528
|
+
│ injection (args) — prompt-injection in args │
|
|
276
529
|
│ redact (args) — secrets in arguments │
|
|
277
530
|
│ │
|
|
278
531
|
│ ==== EXECUTE ==== │
|
|
279
532
|
│ │
|
|
280
533
|
│ result-size-cap — bounded response │
|
|
281
534
|
│ redact (result) — secrets in result │
|
|
282
|
-
│ injection
|
|
535
|
+
│ injection (result) — prompt-injection in result │
|
|
283
536
|
│ audit.exit — hash-chained record close │
|
|
284
537
|
└───────────────────────────────────────────────────┘
|
|
285
538
|
│
|
|
@@ -287,415 +540,526 @@ tool call
|
|
|
287
540
|
result
|
|
288
541
|
```
|
|
289
542
|
|
|
290
|
-
`.rea/` is hardcoded as an always-blocked path. It cannot be unblocked
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
543
|
+
`.rea/` is hardcoded as an always-blocked path. It cannot be unblocked from
|
|
544
|
+
policy. Policy is re-read on every invocation — any edit to `policy.yaml`
|
|
545
|
+
takes effect on the next tool call.
|
|
546
|
+
|
|
547
|
+
### `__rea__health` meta-tool
|
|
548
|
+
|
|
549
|
+
The gateway advertises a single built-in tool, `__rea__health`, in every
|
|
550
|
+
`listTools` response. Calling it returns gateway version, uptime, HALT
|
|
551
|
+
state, policy summary, and per-downstream health. The handler
|
|
552
|
+
**short-circuits the middleware chain** — it is callable under HALT and at
|
|
553
|
+
any autonomy level — because it is the tool an operator reaches for when
|
|
554
|
+
everything else is frozen. Every invocation still writes an audit record.
|
|
555
|
+
|
|
556
|
+
The wire response is sanitized by default: `halt_reason` and
|
|
557
|
+
`downstreams[].last_error` surface as `null`. Full detail lives in the
|
|
558
|
+
audit record. Operators who genuinely need error strings on the MCP wire
|
|
559
|
+
can opt in via `policy.gateway.health.expose_diagnostics: true`; the
|
|
560
|
+
short-circuit still runs the full redact + injection-classify sanitizer
|
|
561
|
+
pass before emitting.
|
|
562
|
+
|
|
563
|
+
### Audit log
|
|
564
|
+
|
|
565
|
+
`.rea/audit.jsonl` is a hash-chained, append-only JSONL file. Each record
|
|
566
|
+
is a single line with:
|
|
567
|
+
|
|
568
|
+
- `seq` — monotonic integer
|
|
569
|
+
- `ts` — ISO-8601 UTC
|
|
570
|
+
- `session_id` — generated at `rea serve` boot
|
|
571
|
+
- `server_name`, `tool_name`, `tier` (`read` | `write` | `destructive`)
|
|
572
|
+
- `status` (`allowed` | `denied` | `error`)
|
|
573
|
+
- `metadata` — tool-specific structured fields (argument digest, target,
|
|
574
|
+
deny reason, verdict)
|
|
575
|
+
- `prev_hash` — SHA-256 of the previous record; tampering is detectable by
|
|
576
|
+
`rea audit verify`
|
|
577
|
+
|
|
578
|
+
Records are redacted on write (secrets swapped for `[REDACTED:*]`,
|
|
579
|
+
injection payloads swapped for `INJECTION_REDACTED_PLACEHOLDER`), never
|
|
580
|
+
LLM-reachable. Rotation is policy-driven (`policy.audit.rotation.max_bytes`
|
|
581
|
+
and/or `max_age_days`); rotation preserves the hash chain by seeding the
|
|
582
|
+
new file with a rotation marker record.
|
|
583
|
+
|
|
584
|
+
### TOFU drift detection
|
|
585
|
+
|
|
586
|
+
Downstream MCP servers are fingerprinted on first sight and stored in
|
|
587
|
+
`.rea/fingerprints.json`. On every `rea serve` boot, each server's canonical
|
|
588
|
+
shape (command path, env-key set, vault list) is hashed and compared
|
|
589
|
+
against the stored fingerprint:
|
|
590
|
+
|
|
591
|
+
- `first-seen` — new server; fingerprint recorded.
|
|
592
|
+
- `unchanged` — match; proceed.
|
|
593
|
+
- `drifted` — mismatch; fail-close. The operator uses `rea tofu list` to
|
|
594
|
+
inspect and `rea tofu accept <name> --reason "..."` to rebase.
|
|
595
|
+
|
|
596
|
+
The drift-accept operation appends a `tofu.drift_accepted_by_cli` audit
|
|
597
|
+
record with the reason. The next boot classifies the server as
|
|
598
|
+
`unchanged`.
|
|
297
599
|
|
|
298
|
-
###
|
|
299
|
-
|
|
300
|
-
Downstream MCP servers run as child processes over stdio. The
|
|
301
|
-
`DownstreamConnection` wrapper wires the SDK `StdioClientTransport`'s
|
|
302
|
-
`onclose` + `onerror` callbacks, so an unexpected child death — OS
|
|
303
|
-
OOM-kill, unhandled exception in the child, stdio pipe error outside a
|
|
304
|
-
caller-initiated close — is detected **eagerly**: the client and
|
|
305
|
-
transport are nulled before the next `callTool` tries to use them. The
|
|
306
|
-
following call forces a genuine reconnect rather than invoking through a
|
|
307
|
-
stale handle.
|
|
600
|
+
### Downstream environment safety
|
|
308
601
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
A 30-second flapping guard refuses a second reconnect that lands too
|
|
312
|
-
quickly after the previous one — the child is clearly unhealthy and the
|
|
313
|
-
circuit breaker is a better place to handle it.
|
|
602
|
+
`rea serve` does **not** forward `process.env` wholesale. Each downstream
|
|
603
|
+
child gets:
|
|
314
604
|
|
|
315
|
-
`
|
|
316
|
-
`
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
605
|
+
1. A fixed allowlist of neutral OS vars (`PATH`, `HOME`, `TZ`,
|
|
606
|
+
`NODE_OPTIONS`, …).
|
|
607
|
+
2. Names opted into via `registry.yaml#servers[].env_passthrough` — the
|
|
608
|
+
schema refuses secret-looking names (`*_TOKEN`, `*_KEY`, `*_SECRET`, …),
|
|
609
|
+
so secrets must be named explicitly.
|
|
610
|
+
3. Values from the registry's `env:` mapping, which may contain `${VAR}`
|
|
611
|
+
placeholders resolved against the host environment. A `${VAR}` whose
|
|
612
|
+
host variable is unset is treated as fatal — the downstream is marked
|
|
613
|
+
unhealthy rather than handed an unresolved placeholder.
|
|
322
614
|
|
|
323
615
|
### Live state
|
|
324
616
|
|
|
325
|
-
`.rea/serve.state.json` is the on-disk live snapshot. It is written
|
|
326
|
-
|
|
617
|
+
`.rea/serve.state.json` is the on-disk live snapshot. It is written once
|
|
618
|
+
at boot and again on every circuit transition or supervisor event,
|
|
327
619
|
debounced through a 250 ms trailing timer and flushed atomically via
|
|
328
|
-
temp-file + rename.
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
take over ownership rather than stalling forever. `rea status` is a
|
|
332
|
-
read-only consumer of this file.
|
|
620
|
+
temp-file + rename. `rea status` reads this file to render a
|
|
621
|
+
per-downstream table; a new `rea serve` whose predecessor crashed can
|
|
622
|
+
detect the abandoned file and take over ownership rather than stalling.
|
|
333
623
|
|
|
334
|
-
###
|
|
624
|
+
### Optional Prometheus metrics
|
|
335
625
|
|
|
336
|
-
`rea serve`
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
1. A fixed allowlist of neutral OS vars (`PATH`, `HOME`, `TZ`,
|
|
340
|
-
`NODE_OPTIONS`, …).
|
|
341
|
-
2. Any names opted into via `registry.yaml#servers[].env_passthrough` —
|
|
342
|
-
the schema refuses secret-looking names (`*_TOKEN`, `*_KEY`,
|
|
343
|
-
`*_SECRET`, …), so secrets must be named explicitly.
|
|
344
|
-
3. Values from the registry's `env:` mapping, which may contain
|
|
345
|
-
`${VAR}` placeholders resolved against the host environment
|
|
346
|
-
(0.3.0). Secret-looking values are redacted in logs by default.
|
|
347
|
-
A `${VAR}` whose host variable is unset is treated as fatal — the
|
|
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
|
|
626
|
+
`rea serve` can expose a loopback-only Prometheus endpoint when
|
|
627
|
+
`REA_METRICS_PORT` is set:
|
|
491
628
|
|
|
492
629
|
```bash
|
|
493
|
-
|
|
494
|
-
/
|
|
630
|
+
REA_METRICS_PORT=9464 rea serve
|
|
631
|
+
curl http://127.0.0.1:9464/metrics
|
|
495
632
|
```
|
|
496
633
|
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
`
|
|
634
|
+
Metrics: per-downstream call and error counters, in-flight gauge,
|
|
635
|
+
audit-lines-appended counter, circuit-breaker state gauge, and a
|
|
636
|
+
seconds-since-last-HALT-check gauge. The listener binds to `127.0.0.1`
|
|
637
|
+
only, serves only `GET /metrics`, and never binds by default. No TLS;
|
|
638
|
+
scrape through SSH or a reverse proxy for cross-host access.
|
|
501
639
|
|
|
502
|
-
|
|
640
|
+
---
|
|
503
641
|
|
|
504
|
-
|
|
505
|
-
manual Codex run, or from an offline review) emit the audit record AND
|
|
506
|
-
update the push-review cache with a single command:
|
|
642
|
+
## Policy file
|
|
507
643
|
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
--head-sha "$(git rev-parse HEAD)" \
|
|
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
|
|
517
|
-
```
|
|
518
|
-
|
|
519
|
-
`--also-set-cache` writes both `.rea/audit.jsonl` and
|
|
520
|
-
`.rea/review-cache.jsonl` in the same invocation (two sequential
|
|
521
|
-
appends, not a two-phase commit — but close enough in practice that the
|
|
522
|
-
push-gate lookup cannot see the audit record without the cache entry
|
|
523
|
-
unless a crash lands between them). Without it, the audit record lands
|
|
524
|
-
but the cache stays cold — and the next `git push` pays for a re-review
|
|
525
|
-
even though the audit trail already shows the review happened.
|
|
526
|
-
`--also-set-cache` is what the gate's remediation text should be reduced
|
|
527
|
-
to.
|
|
528
|
-
|
|
529
|
-
Verdict mapping for the cache leg:
|
|
530
|
-
|
|
531
|
-
| `--verdict` | Cache `result` | Cache `reason` |
|
|
532
|
-
| ------------ | -------------- | -------------- |
|
|
533
|
-
| `pass` | `pass` | — (omitted) |
|
|
534
|
-
| `concerns` | `pass` | `codex:concerns` |
|
|
535
|
-
| `blocking` | `fail` | `codex:blocking` |
|
|
536
|
-
| `error` | `fail` | `codex:error` |
|
|
537
|
-
|
|
538
|
-
### 3. Push
|
|
644
|
+
`.rea/policy.yaml` fields. The schema is zod-strict — unknown fields are
|
|
645
|
+
rejected at parse time, not ignored.
|
|
539
646
|
|
|
540
|
-
|
|
541
|
-
|
|
647
|
+
### Required fields
|
|
648
|
+
|
|
649
|
+
| Field | Type | Purpose |
|
|
650
|
+
| --- | --- | --- |
|
|
651
|
+
| `version` | `"1"` | Schema version; only `"1"` accepted in the current major |
|
|
652
|
+
| `profile` | string | Profile name (see below) |
|
|
653
|
+
| `installed_by` | string | Stamped by `rea init` — identifies the installing version |
|
|
654
|
+
| `installed_at` | ISO-8601 | Stamped by `rea init` — install timestamp |
|
|
655
|
+
| `autonomy_level` | `L0` \| `L1` \| `L2` \| `L3` | Current autonomy. `L0` = read-only; `L3` = full tool access |
|
|
656
|
+
| `max_autonomy_level` | `L0` \| `L1` \| `L2` \| `L3` | Hard ceiling. `autonomy_level` cannot exceed this |
|
|
657
|
+
| `promotion_requires_human_approval` | boolean | Require operator confirmation to raise autonomy |
|
|
658
|
+
| `block_ai_attribution` | boolean | Enforce no-AI-attribution in commits and PR bodies |
|
|
659
|
+
| `blocked_paths` | string[] | Glob patterns. `.rea/` is always blocked regardless |
|
|
660
|
+
| `notification_channel` | string | Optional Discord webhook URL; empty string = disabled |
|
|
661
|
+
|
|
662
|
+
### Optional blocks
|
|
663
|
+
|
|
664
|
+
```yaml
|
|
665
|
+
injection_detection: block # "block" | "warn" — legacy 0.2.x knob
|
|
666
|
+
injection:
|
|
667
|
+
suspicious_blocks_writes: true # suspicious verdict on write/destructive tier denies
|
|
668
|
+
context_protection:
|
|
669
|
+
delegate_to_subagent:
|
|
670
|
+
- pnpm run build
|
|
671
|
+
- pnpm run test
|
|
672
|
+
max_bash_output_lines: 100
|
|
673
|
+
review:
|
|
674
|
+
codex_required: true
|
|
675
|
+
concerns_blocks: true
|
|
676
|
+
timeout_ms: 1800000
|
|
677
|
+
# last_n_commits: 10 # optional — narrow review to HEAD~N
|
|
678
|
+
# auto_narrow_threshold: 30 # optional — auto-narrow when commits
|
|
679
|
+
# behind base > N (0 disables, default 30)
|
|
680
|
+
redact:
|
|
681
|
+
match_timeout_ms: 100
|
|
682
|
+
patterns:
|
|
683
|
+
- name: custom-api-key
|
|
684
|
+
regex: 'acme_[A-Za-z0-9]{32}'
|
|
685
|
+
flags: 'g'
|
|
686
|
+
audit:
|
|
687
|
+
rotation:
|
|
688
|
+
max_bytes: 52428800 # 50 MiB
|
|
689
|
+
max_age_days: 30
|
|
690
|
+
gateway:
|
|
691
|
+
health:
|
|
692
|
+
expose_diagnostics: false
|
|
542
693
|
```
|
|
543
694
|
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
695
|
+
User-supplied `redact.patterns[]` are validated via `safe-regex` at load —
|
|
696
|
+
any pattern flagged unsafe fails the load with a specific error naming
|
|
697
|
+
the offender.
|
|
698
|
+
|
|
699
|
+
### Autonomy levels
|
|
547
700
|
|
|
548
|
-
|
|
701
|
+
| Level | Effect |
|
|
702
|
+
| --- | --- |
|
|
703
|
+
| `L0` | Read-only — every write/destructive tier call denies |
|
|
704
|
+
| `L1` | Default. Reads allowed; writes allowed; destructive tier denied |
|
|
705
|
+
| `L2` | Writes and destructive tier allowed |
|
|
706
|
+
| `L3` | No autonomy gate — only hook-layer and policy-layer checks remain |
|
|
549
707
|
|
|
550
|
-
|
|
551
|
-
|
|
708
|
+
`autonomy_level > max_autonomy_level` is rejected at parse time.
|
|
709
|
+
`promotion_requires_human_approval: false` requires the CLI flag
|
|
710
|
+
`--i-understand-the-risks` on any operation that raises autonomy.
|
|
552
711
|
|
|
553
|
-
|
|
554
|
-
import {
|
|
555
|
-
appendAuditRecord,
|
|
556
|
-
CODEX_REVIEW_SERVER_NAME,
|
|
557
|
-
CODEX_REVIEW_TOOL_NAME,
|
|
558
|
-
InvocationStatus,
|
|
559
|
-
Tier,
|
|
560
|
-
} from '@bookedsolid/rea/audit';
|
|
712
|
+
### Profiles
|
|
561
713
|
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
tier: Tier.Read,
|
|
566
|
-
status: InvocationStatus.Allowed,
|
|
567
|
-
metadata: {
|
|
568
|
-
head_sha: headSha,
|
|
569
|
-
target: 'main',
|
|
570
|
-
finding_count: 0,
|
|
571
|
-
verdict: 'pass',
|
|
572
|
-
},
|
|
573
|
-
});
|
|
574
|
-
```
|
|
714
|
+
Seven profiles ship in `profiles/`. The profile name is recorded in
|
|
715
|
+
`policy.yaml#profile` and governs which agents `rea init` copies and what
|
|
716
|
+
defaults apply.
|
|
575
717
|
|
|
576
|
-
|
|
577
|
-
|
|
718
|
+
| Profile | Intended use | Codex default |
|
|
719
|
+
| --- | --- | --- |
|
|
720
|
+
| `minimal` | Smallest possible install — curated 10 + opinionated minimal hooks | `true` |
|
|
721
|
+
| `client-engagement` | Consulting engagement where the repo is client-owned | `true` |
|
|
722
|
+
| `bst-internal` | Booked Solid internal projects; conservative posture | `true` |
|
|
723
|
+
| `bst-internal-no-codex` | Same as above; no Codex CLI available | `false` |
|
|
724
|
+
| `lit-wc` | Lit web-component library projects | `true` |
|
|
725
|
+
| `open-source` | OSS library / CLI projects; liberal but audited | `true` |
|
|
726
|
+
| `open-source-no-codex` | Same; no Codex CLI available | `false` |
|
|
727
|
+
|
|
728
|
+
The `-no-codex` variants default `review.codex_required: false` on
|
|
729
|
+
install so teams without a Codex bench get a first-class opt-out. Flip
|
|
730
|
+
`--codex` / `--no-codex` on the `rea init` command line to override.
|
|
578
731
|
|
|
579
|
-
|
|
732
|
+
---
|
|
580
733
|
|
|
581
|
-
|
|
582
|
-
`rea doctor`, and `rea status` are classified **Read tier** — they
|
|
583
|
-
cannot be denied by REA's own middleware. `rea cache set` is Write
|
|
584
|
-
tier and is still allowed at L1. `rea freeze` is Destructive tier and
|
|
585
|
-
is denied at L1 (deny-reason includes the subcommand, e.g.
|
|
586
|
-
`Bash (rea freeze)`, not just `Bash`).
|
|
734
|
+
## Hooks shipped
|
|
587
735
|
|
|
588
|
-
|
|
736
|
+
Eleven hooks ship in `hooks/` and are copied into `.claude/hooks/` by
|
|
737
|
+
`rea init`. All eleven are wired by default in the shipped
|
|
738
|
+
`.claude/settings.json`.
|
|
589
739
|
|
|
590
|
-
|
|
740
|
+
| Hook | Event | Purpose | Default |
|
|
741
|
+
| --- | --- | --- | --- |
|
|
742
|
+
| `dangerous-bash-interceptor.sh` | `PreToolUse: Bash` | Block categories of destructive shell commands (`rm -rf`, `git reset --hard`, `--no-verify`, …) | Registered |
|
|
743
|
+
| `env-file-protection.sh` | `PreToolUse: Bash` | Block reads of `.env*` files | Registered |
|
|
744
|
+
| `dependency-audit-gate.sh` | `PreToolUse: Bash` | Verify packages exist on the registry before install | Registered |
|
|
745
|
+
| `security-disclosure-gate.sh` | `PreToolUse: Bash` | Route security-keyword `gh issue create` to private disclosure | Registered |
|
|
746
|
+
| `pr-issue-link-gate.sh` | `PreToolUse: Bash` | Advisory warn when `gh pr create` has no linked issue | Registered |
|
|
747
|
+
| `attribution-advisory.sh` | `PreToolUse: Bash` | Block commits/PRs containing AI attribution markers | Registered |
|
|
748
|
+
| `secret-scanner.sh` | `PreToolUse: Write\|Edit` | Scan file writes for credential patterns | Registered |
|
|
749
|
+
| `settings-protection.sh` | `PreToolUse: Write\|Edit` | Block agent writes to `.claude/settings.json`, hook dirs, policy | Registered |
|
|
750
|
+
| `blocked-paths-enforcer.sh` | `PreToolUse: Write\|Edit` | Enforce `blocked_paths` from policy | Registered |
|
|
751
|
+
| `changeset-security-gate.sh` | `PreToolUse: Write\|Edit` | Guard changesets against GHSA leaks and malformed frontmatter | Registered |
|
|
752
|
+
| `architecture-review-gate.sh` | `PostToolUse: Write\|Edit` | Flag edits crossing architectural boundaries (advisory) | Registered |
|
|
753
|
+
|
|
754
|
+
The 0.10.x review-gate scripts (`push-review-gate.sh`,
|
|
755
|
+
`push-review-gate-git.sh`, `commit-review-gate.sh`) and the 1,250-line
|
|
756
|
+
shared bash core (`hooks/_lib/push-review-core.sh`) were removed in
|
|
757
|
+
0.11.0. The `hooks/_lib/` directory now contains only the three shared
|
|
758
|
+
helpers — `common.sh`, `halt-check.sh`, `policy-read.sh` — used by the
|
|
759
|
+
remaining hooks.
|
|
760
|
+
|
|
761
|
+
Every hook uses `set -euo pipefail` (or `set -uo pipefail` for stdin-JSON
|
|
762
|
+
consumers) and performs a HALT check near the top. Both the hook layer
|
|
763
|
+
and the MCP gateway middleware fail closed; bypassing one does not
|
|
764
|
+
disable the other.
|
|
591
765
|
|
|
592
|
-
|
|
593
|
-
| --- | --- | --- |
|
|
594
|
-
| `dangerous-bash-interceptor` | PreToolUse: Bash | Block categories of destructive shell commands |
|
|
595
|
-
| `env-file-protection` | PreToolUse: Bash | Block reads of `.env*` files |
|
|
596
|
-
| `dependency-audit-gate` | PreToolUse: Bash | Verify packages exist on the registry before install |
|
|
597
|
-
| `commit-review-gate` | PreToolUse: Bash | Intercept `git commit`; require review on non-trivial diffs |
|
|
598
|
-
| `push-review-gate` | PreToolUse: Bash | Intercept `git push` (Claude-Code-JSON adapter); protected-path + Codex audit |
|
|
599
|
-
| `push-review-gate-git` | `.husky/pre-push` | Native git adapter around the same core |
|
|
600
|
-
| `attribution-advisory` | PreToolUse: Bash | Block commits / PRs containing AI attribution markers |
|
|
601
|
-
| `pr-issue-link-gate` | PreToolUse: Bash | Advisory warn when `gh pr create` has no linked issue |
|
|
602
|
-
| `security-disclosure-gate` | PreToolUse: Bash | Route security-keyword `gh issue create` to private disclosure |
|
|
603
|
-
| `secret-scanner` | PreToolUse: Write\|Edit | Scan file writes for credential patterns |
|
|
604
|
-
| `settings-protection` | PreToolUse: Write\|Edit | Block agent writes to `.claude/settings.json`, hook dirs, policy |
|
|
605
|
-
| `blocked-paths-enforcer` | PreToolUse: Write\|Edit | Enforce `blocked_paths` from policy |
|
|
606
|
-
| `changeset-security-gate` | PreToolUse: Write\|Edit | Guard changesets against GHSA leaks and malformed frontmatter |
|
|
607
|
-
| `architecture-review-gate` | PostToolUse: Write\|Edit | Flag edits crossing architectural boundaries (advisory) |
|
|
766
|
+
---
|
|
608
767
|
|
|
609
768
|
## Slash commands
|
|
610
769
|
|
|
770
|
+
Five commands ship in `commands/` and are copied into `.claude/commands/`
|
|
771
|
+
by `rea init`.
|
|
772
|
+
|
|
611
773
|
| Command | Purpose |
|
|
612
774
|
| --- | --- |
|
|
613
|
-
| `/rea` | Session status — autonomy level, HALT state,
|
|
775
|
+
| `/rea` | Session status — autonomy level, HALT state, recent audit entries, next action |
|
|
614
776
|
| `/review` | Invoke the `code-reviewer` agent on current changes |
|
|
615
|
-
| `/codex-review` | Invoke the `codex-adversarial` agent
|
|
777
|
+
| `/codex-review` | Invoke the `codex-adversarial` agent via the Codex plugin |
|
|
616
778
|
| `/freeze` | Prompt for a reason and write `.rea/HALT` |
|
|
617
|
-
| `/halt-check` |
|
|
779
|
+
| `/halt-check` | Smoke test — verify every hook and middleware respects HALT |
|
|
618
780
|
|
|
619
|
-
|
|
781
|
+
---
|
|
620
782
|
|
|
621
|
-
|
|
622
|
-
rejected, not ignored.
|
|
783
|
+
## Curated agents
|
|
623
784
|
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
| `profile` | string | Profile name from `profiles/` (e.g. `bst-internal`) |
|
|
628
|
-
| `autonomy_level` | `L0`\|`L1`\|`L2`\|`L3` | Current autonomy. `L0` = read-only; `L3` = full tool access |
|
|
629
|
-
| `max_autonomy_level` | `L0`\|`L1`\|`L2`\|`L3` | Hard ceiling. `autonomy_level` cannot exceed this |
|
|
630
|
-
| `promotion_requires_human_approval` | boolean | Require operator confirmation to raise autonomy. Default `true` |
|
|
631
|
-
| `blocked_paths` | string[] | Glob patterns. `.rea/` is always blocked regardless of this list |
|
|
632
|
-
| `block_ai_attribution` | boolean | Enforce no-AI-attribution in commits and PR bodies |
|
|
633
|
-
| `context_protection.delegate_to_subagent` | string[] | Commands that must run in a subagent context to preserve the parent's context window |
|
|
634
|
-
| `context_protection.max_bash_output_lines` | number | Truncate long bash output at this line count |
|
|
635
|
-
| `notification_channel` | string | Optional Discord webhook URL. Empty string = no notifications |
|
|
636
|
-
| `review.codex_required` | boolean | When `false`, protected-path pushes don't require a Codex audit (first-class no-Codex mode). Default `true` |
|
|
637
|
-
| `review.cache_max_age_seconds` | number | TTL for entries in `.rea/review-cache.jsonl`. Default 3600 |
|
|
638
|
-
| `review.allow_skip_in_ci` | boolean | When `true`, `REA_SKIP_PUSH_REVIEW` is accepted on CI runners. Default `false` |
|
|
639
|
-
| `injection.suspicious_blocks_writes` | boolean | `bst-internal` posture — `suspicious` verdict on a write/destructive tool denies instead of warning. Default `false` |
|
|
640
|
-
| `redact.patterns[]` | string[] | User-supplied secret patterns; vetted via `safe-regex` at load |
|
|
641
|
-
| `redact.match_timeout_ms` | number | Per-call regex budget. Default 100 |
|
|
642
|
-
| `gateway.health.expose_diagnostics` | boolean | When `true`, `__rea__health` emits redacted+classified diagnostic strings on the wire. Default `false` (null) |
|
|
643
|
-
|
|
644
|
-
`autonomy_level > max_autonomy_level` is rejected at parse time. Setting
|
|
645
|
-
`promotion_requires_human_approval: false` requires the CLI flag
|
|
646
|
-
`--i-understand-the-risks`.
|
|
785
|
+
Ten specialist agents ship in `agents/` and are copied into `.claude/agents/`
|
|
786
|
+
by `rea init`. Profiles layer additional specialists on top for specific
|
|
787
|
+
project shapes.
|
|
647
788
|
|
|
648
|
-
|
|
789
|
+
| Agent | When to use |
|
|
790
|
+
| --- | --- |
|
|
791
|
+
| `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. |
|
|
792
|
+
| `code-reviewer` | Structured review of a working-tree diff; surfaces correctness, clarity, and consistency issues without adversarial framing. |
|
|
793
|
+
| `codex-adversarial` | Adversarial review via the Codex plugin (`/codex:adversarial-review`). Independent model perspective; produces an audit entry with verdict. |
|
|
794
|
+
| `security-engineer` | Security-sensitive implementation and review — auth flows, secret handling, injection surfaces. |
|
|
795
|
+
| `accessibility-engineer` | WCAG review, ARIA semantics, keyboard navigation, screen-reader fact-checking. |
|
|
796
|
+
| `typescript-specialist` | Strict-mode TypeScript correctness, generics, narrowing, inference edge cases. |
|
|
797
|
+
| `frontend-specialist` | UI component work, framework idioms (React, Lit, Astro), CSS architecture. |
|
|
798
|
+
| `backend-engineer` | API design, database schema, background jobs, MCP server implementation. |
|
|
799
|
+
| `qa-engineer` | Test strategy, fixture design, regression reproducers, flake triage. |
|
|
800
|
+
| `technical-writer` | User-facing documentation, API references, migration guides, changelog narratives. |
|
|
801
|
+
|
|
802
|
+
The `rea-orchestrator` is the single entry point for non-trivial tasks.
|
|
803
|
+
The CLAUDE.md fragment installed by `rea init` instructs the host agent
|
|
804
|
+
to route there first; delegation contracts are defined in each agent's
|
|
805
|
+
markdown file.
|
|
806
|
+
|
|
807
|
+
---
|
|
808
|
+
|
|
809
|
+
## CLI reference
|
|
810
|
+
|
|
811
|
+
```
|
|
812
|
+
rea <command> [options]
|
|
813
|
+
```
|
|
814
|
+
|
|
815
|
+
Run `rea <command> --help` for full per-command options.
|
|
816
|
+
|
|
817
|
+
### `rea init`
|
|
818
|
+
|
|
819
|
+
Interactive wizard — write `.rea/policy.yaml`, install `.claude/`, the
|
|
820
|
+
commit-msg hook, and a CLAUDE.md fragment.
|
|
821
|
+
|
|
822
|
+
```bash
|
|
823
|
+
rea init
|
|
824
|
+
rea init -y --profile bst-internal # non-interactive
|
|
825
|
+
rea init --from-reagent # migrate from .reagent/
|
|
826
|
+
rea init --profile open-source-no-codex # disable Codex by default
|
|
827
|
+
rea init --force # overwrite existing artifacts
|
|
828
|
+
```
|
|
829
|
+
|
|
830
|
+
### `rea upgrade`
|
|
831
|
+
|
|
832
|
+
Sync `.claude/`, `.husky/`, and managed fragments with this rea version.
|
|
833
|
+
Prompts on drift; silently refreshes unmodified files.
|
|
649
834
|
|
|
650
835
|
```bash
|
|
651
|
-
|
|
836
|
+
rea upgrade --dry-run # show what would change; write nothing
|
|
837
|
+
rea upgrade # interactive
|
|
838
|
+
rea upgrade -y # non-interactive, keep drifted files
|
|
839
|
+
rea upgrade --force # non-interactive, overwrite drift
|
|
652
840
|
```
|
|
653
841
|
|
|
654
|
-
|
|
842
|
+
### `rea serve`
|
|
655
843
|
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
rename.
|
|
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.
|
|
844
|
+
Start the MCP gateway. Invoked by Claude Code via `.mcp.json`; not a
|
|
845
|
+
daemon. Stdio transport only.
|
|
666
846
|
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
847
|
+
```bash
|
|
848
|
+
rea serve
|
|
849
|
+
REA_METRICS_PORT=9464 rea serve
|
|
850
|
+
REA_LOG_LEVEL=debug rea serve
|
|
851
|
+
```
|
|
672
852
|
|
|
673
|
-
|
|
853
|
+
### `rea freeze` / `rea unfreeze`
|
|
854
|
+
|
|
855
|
+
Write or remove `.rea/HALT`. Every call writes an audit record.
|
|
856
|
+
|
|
857
|
+
```bash
|
|
858
|
+
rea freeze --reason "incident triage"
|
|
859
|
+
rea unfreeze
|
|
860
|
+
rea unfreeze -y # skip confirmation
|
|
861
|
+
```
|
|
862
|
+
|
|
863
|
+
### `rea check`
|
|
864
|
+
|
|
865
|
+
On-disk status — autonomy, HALT, profile, recent audit entries. No live
|
|
866
|
+
process probe.
|
|
867
|
+
|
|
868
|
+
### `rea status`
|
|
869
|
+
|
|
870
|
+
Running-process view — reads `.rea/serve.pid` + `.rea/serve.state.json`
|
|
871
|
+
to render per-downstream health.
|
|
872
|
+
|
|
873
|
+
```bash
|
|
874
|
+
rea status
|
|
875
|
+
rea status --json # pipe to jq
|
|
876
|
+
```
|
|
877
|
+
|
|
878
|
+
### `rea doctor`
|
|
879
|
+
|
|
880
|
+
Validate the install — policy parses, `.rea/` layout, hooks, Codex plugin
|
|
881
|
+
presence, TOFU fingerprint store.
|
|
882
|
+
|
|
883
|
+
```bash
|
|
884
|
+
rea doctor
|
|
885
|
+
rea doctor --metrics # also print 7-day Codex telemetry summary
|
|
886
|
+
rea doctor --drift # report drift vs. install manifest (read-only)
|
|
887
|
+
```
|
|
888
|
+
|
|
889
|
+
In non-git directories the commit-msg and pre-push checks are skipped
|
|
890
|
+
cleanly. Audit hash-chain integrity is verified by `rea audit verify`,
|
|
891
|
+
not by `rea doctor`.
|
|
892
|
+
|
|
893
|
+
### `rea audit rotate` / `rea audit verify`
|
|
894
|
+
|
|
895
|
+
```bash
|
|
896
|
+
rea audit rotate # force rotation now
|
|
897
|
+
rea audit verify # re-hash the chain; exit 1 on first tamper
|
|
898
|
+
rea audit verify --since <file> # walk forward from a rotated file
|
|
899
|
+
```
|
|
900
|
+
|
|
901
|
+
### `rea tofu list` / `rea tofu accept`
|
|
902
|
+
|
|
903
|
+
```bash
|
|
904
|
+
rea tofu list
|
|
905
|
+
rea tofu list --json
|
|
906
|
+
rea tofu accept my-mcp-server --reason "added vault path /Volumes/Work"
|
|
907
|
+
```
|
|
908
|
+
|
|
909
|
+
### `rea hook push-gate`
|
|
910
|
+
|
|
911
|
+
Pre-push Codex review gate. Normally invoked by `.husky/pre-push`; run
|
|
912
|
+
manually to test.
|
|
913
|
+
|
|
914
|
+
```bash
|
|
915
|
+
rea hook push-gate
|
|
916
|
+
rea hook push-gate --base origin/main
|
|
917
|
+
```
|
|
918
|
+
|
|
919
|
+
---
|
|
920
|
+
|
|
921
|
+
## Migration from 0.10.x
|
|
922
|
+
|
|
923
|
+
`rea upgrade` handles the policy and on-disk pieces. You run it once:
|
|
924
|
+
|
|
925
|
+
```bash
|
|
926
|
+
rea upgrade
|
|
927
|
+
```
|
|
928
|
+
|
|
929
|
+
It performs:
|
|
930
|
+
|
|
931
|
+
1. **Backup** — writes `.rea/policy.yaml.bak-<timestamp>` before any edit.
|
|
932
|
+
2. **Strip removed fields** — removes `review.cache_max_age_seconds` and
|
|
933
|
+
`review.allow_skip_in_ci` from the policy file. Both were cache-gate
|
|
934
|
+
concepts with no meaning under the stateless gate.
|
|
935
|
+
3. **Add defaults** — inserts `review.concerns_blocks: true` if absent.
|
|
936
|
+
4. **Prune settings.json** — removes hook registrations for the removed
|
|
937
|
+
scripts (`push-review-gate`, `commit-review-gate`) from
|
|
938
|
+
`.claude/settings.json`, leaving the other registrations intact.
|
|
939
|
+
5. **Refresh `.claude/hooks/`** — deletes the removed scripts with a
|
|
940
|
+
change-log entry.
|
|
941
|
+
6. **Rewrite `.husky/pre-push`** iff it carries a rea marker. A
|
|
942
|
+
foreign `.husky/pre-push` (no marker) is left untouched and a loud
|
|
943
|
+
warning is printed.
|
|
944
|
+
7. **Refresh `.git/hooks/pre-push`** fallback with the new 15-line stub
|
|
945
|
+
(when `core.hooksPath` is unset and git is using the default hooks
|
|
946
|
+
directory).
|
|
947
|
+
|
|
948
|
+
### What the new `.husky/pre-push` looks like
|
|
949
|
+
|
|
950
|
+
Fifteen lines of POSIX `sh`. HALT check, locate the rea binary, `exec rea
|
|
951
|
+
hook push-gate "$@"`. That is the entire body — all real work lives in
|
|
952
|
+
the TypeScript gate composer at `src/hooks/push-gate/index.ts`.
|
|
953
|
+
|
|
954
|
+
### Rollback
|
|
955
|
+
|
|
956
|
+
If the stateless gate's behavior is blocking a specific workflow you
|
|
957
|
+
cannot yet address:
|
|
958
|
+
|
|
959
|
+
```bash
|
|
960
|
+
npm install -g @bookedsolid/rea@0.10.3
|
|
961
|
+
# restore policy from the backup rea upgrade wrote
|
|
962
|
+
cp .rea/policy.yaml.bak-<ts> .rea/policy.yaml
|
|
963
|
+
# re-run the 0.10.3 install to put the old hooks back
|
|
964
|
+
rea init --force
|
|
965
|
+
```
|
|
966
|
+
|
|
967
|
+
The 0.10.3 cache-attestation gate remains on npm and continues to work.
|
|
968
|
+
Rollback is a supported path — the `rea upgrade` backup is specifically
|
|
969
|
+
there to make it a one-liner.
|
|
970
|
+
|
|
971
|
+
### What you will not need to do anymore
|
|
972
|
+
|
|
973
|
+
- `rea cache check` / `rea cache set` / `rea cache list` / `rea cache clear`
|
|
974
|
+
— all removed. The stateless gate consults no cache.
|
|
975
|
+
- `rea audit record codex-review --also-set-cache` — removed. The gate
|
|
976
|
+
writes its own audit records from the actual Codex run.
|
|
977
|
+
- Setting `REA_SKIP_PUSH_REVIEW` — removed. Use either
|
|
978
|
+
`REA_SKIP_PUSH_GATE=<reason>` or `REA_SKIP_CODEX_REVIEW=<reason>`
|
|
979
|
+
(both value-carrying and always audited; identical effect, distinct
|
|
980
|
+
`skip_var` in audit metadata) or flip `review.codex_required: false`
|
|
981
|
+
in policy. `REA_SKIP_CODEX_REVIEW` was reinstated in 0.12.0 as an
|
|
982
|
+
audited alias for `REA_SKIP_PUSH_GATE` — it had been documented in the
|
|
983
|
+
gateway-tier reviewers but not in the push-gate, leaving agents
|
|
984
|
+
setting the documented variant blocked.
|
|
985
|
+
|
|
986
|
+
---
|
|
987
|
+
|
|
988
|
+
## Contributor quality gates
|
|
989
|
+
|
|
990
|
+
Before push, run the four checks locally. CI runs them as required status
|
|
991
|
+
checks on every PR to `main`:
|
|
992
|
+
|
|
993
|
+
```bash
|
|
994
|
+
pnpm lint # ESLint 10 — zero warnings
|
|
995
|
+
pnpm type-check # tsc --noEmit (strict)
|
|
996
|
+
pnpm test # vitest run
|
|
997
|
+
pnpm build # tsc -p tsconfig.build.json
|
|
998
|
+
```
|
|
999
|
+
|
|
1000
|
+
Additionally, every PR needs:
|
|
1001
|
+
|
|
1002
|
+
- **DCO sign-off** on all commits (`git commit -s`). The DCO bot rejects
|
|
1003
|
+
unsigned commits.
|
|
1004
|
+
- **Changeset** entry (`pnpm changeset`) unless the change is purely
|
|
1005
|
+
non-publishable (CI, docs, meta). CI flags missing changesets.
|
|
1006
|
+
- **Secret scan** clean. Gitleaks runs in CI and via the
|
|
1007
|
+
`secret-scanner.sh` hook.
|
|
1008
|
+
- **No AI attribution** anywhere — commit messages, PR bodies, code
|
|
1009
|
+
comments, changeset content. The commit-msg hook and
|
|
1010
|
+
`attribution-advisory.sh` reject structural attribution.
|
|
1011
|
+
|
|
1012
|
+
Security-sensitive paths (`src/gateway/middleware/**`, `src/policy/**`,
|
|
1013
|
+
`src/hooks/**`, `hooks/**`, `.github/workflows/**`) require explicit
|
|
1014
|
+
maintainer review and a threat-model update in the same PR.
|
|
1015
|
+
|
|
1016
|
+
Releases flow through Changesets: a "Version Packages" PR is auto-opened
|
|
1017
|
+
when a changeset lands on `main`. Merging it triggers `npm publish
|
|
1018
|
+
--provenance` via OIDC from `.github/workflows/release.yml`. Do not
|
|
1019
|
+
manually `npm publish`.
|
|
1020
|
+
|
|
1021
|
+
---
|
|
1022
|
+
|
|
1023
|
+
## Threat model and security
|
|
674
1024
|
|
|
675
1025
|
- [SECURITY.md](./SECURITY.md) — disclosure policy, supported versions,
|
|
676
|
-
|
|
1026
|
+
GHSA coordination. 72-hour acknowledgment target, 90-day window. Do
|
|
1027
|
+
not report vulnerabilities via public GitHub issues.
|
|
677
1028
|
- [THREAT_MODEL.md](./THREAT_MODEL.md) — attack surface, mitigations,
|
|
678
|
-
residual risks.
|
|
1029
|
+
residual risks. The contract rea holds itself to.
|
|
679
1030
|
|
|
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.
|
|
1031
|
+
Short version: the MCP gateway and the hook layer run independently.
|
|
1032
|
+
Both fail closed. `.rea/` is always blocked. The audit log is
|
|
1033
|
+
hash-chained. Policy is re-read on every invocation. npm publish uses
|
|
1034
|
+
OIDC provenance, not long-lived tokens. The pre-push gate runs Codex
|
|
1035
|
+
on every push and treats Codex responses as untrusted input — findings
|
|
1036
|
+
flow through the same redact pattern set used by the middleware before
|
|
1037
|
+
anything hits disk.
|
|
684
1038
|
|
|
685
|
-
|
|
1039
|
+
---
|
|
686
1040
|
|
|
687
|
-
|
|
1041
|
+
## Non-goals
|
|
688
1042
|
|
|
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.
|
|
1043
|
+
See [What REA is NOT](#what-rea-is-not) above. Every "but what if we
|
|
1044
|
+
just added X" belongs in a separate package that composes with REA. The
|
|
1045
|
+
non-goals are the product.
|
|
1046
|
+
|
|
1047
|
+
---
|
|
698
1048
|
|
|
699
|
-
## License
|
|
1049
|
+
## License and contributing
|
|
700
1050
|
|
|
701
|
-
[MIT](./LICENSE)
|
|
1051
|
+
[MIT](./LICENSE). See [CONTRIBUTING.md](./CONTRIBUTING.md) for the full
|
|
1052
|
+
contributor guide and [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md) for
|
|
1053
|
+
the Contributor Covenant.
|
|
1054
|
+
|
|
1055
|
+
- DCO sign-off required (`git commit -s`) — no CLA.
|
|
1056
|
+
- Conventional commits, TypeScript strict, ESLint zero-warnings,
|
|
1057
|
+
Prettier, vitest.
|
|
1058
|
+
- Changeset on every publishable change; merge the auto-generated
|
|
1059
|
+
Version Packages PR to release.
|
|
1060
|
+
- Security-sensitive paths gated by CODEOWNERS; human review required.
|
|
1061
|
+
|
|
1062
|
+
This repo dogfoods itself. rea's governance layer enforces rea's own
|
|
1063
|
+
commit, hook, and attribution rules. The install under `.rea/`,
|
|
1064
|
+
`.claude/`, and `.husky/` is the reference example of the
|
|
1065
|
+
`bst-internal` profile.
|