@bookedsolid/rea 0.40.0 → 0.42.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/MIGRATING.md +139 -0
- package/README.md +153 -36
- package/dist/cli/audit-summary.d.ts +160 -0
- package/dist/cli/audit-summary.js +535 -0
- package/dist/cli/doctor.d.ts +44 -4
- package/dist/cli/doctor.js +141 -37
- package/dist/cli/index.js +33 -0
- package/dist/cli/install/gitignore.d.ts +23 -1
- package/dist/cli/install/gitignore.js +33 -6
- package/dist/cli/install/unified-diff.d.ts +78 -0
- package/dist/cli/install/unified-diff.js +270 -0
- package/dist/cli/upgrade-check.d.ts +187 -0
- package/dist/cli/upgrade-check.js +685 -0
- package/dist/cli/upgrade.js +42 -0
- package/package.json +1 -1
package/MIGRATING.md
CHANGED
|
@@ -389,6 +389,145 @@ per finding and produces more consistent verdicts — fewer
|
|
|
389
389
|
same-code-different-verdict round-trips. Trade-off is push-gate
|
|
390
390
|
latency.
|
|
391
391
|
|
|
392
|
+
## Node-binary hook scanner (added in 0.32.0)
|
|
393
|
+
|
|
394
|
+
Pre-0.32.0 every `.claude/hooks/*.sh` carried the full gate body in
|
|
395
|
+
bash. Adversarial review consistently caught bash-only edge cases that
|
|
396
|
+
were structurally unfixable in shell — multi-line awk encodings,
|
|
397
|
+
ANSI-C escapes, deep nested-shell decoding. 0.32.0 pivoted the entire
|
|
398
|
+
hook surface to a Node-binary scanner: hooks became thin shims (~20-80
|
|
399
|
+
LOC each) that delegate the actual gate work to `rea hook <name>` —
|
|
400
|
+
which runs the canonical scanner inside `dist/cli/index.js`.
|
|
401
|
+
|
|
402
|
+
**Consumer impact:**
|
|
403
|
+
|
|
404
|
+
- Run `pnpm install` (or `npm install`) after upgrading to 0.32.0+ so
|
|
405
|
+
`dist/cli/index.js` is built and the shims have something to call.
|
|
406
|
+
- `.claude/hooks/*.sh` files on disk are noticeably smaller after
|
|
407
|
+
`rea upgrade`; this is the canonical post-0.32.0 shape, not a
|
|
408
|
+
truncation. `rea doctor` will tell you if a shim is the wrong
|
|
409
|
+
vintage.
|
|
410
|
+
- The audit trail is unchanged: hooks still emit `rea.bash_scan`-class
|
|
411
|
+
records to `.rea/audit.jsonl` with the same field shape.
|
|
412
|
+
- Performance is materially better — single Node startup per scan
|
|
413
|
+
instead of an awk/sed pipeline per pattern.
|
|
414
|
+
|
|
415
|
+
If `rea doctor` reports `policy-reader Tier 1 (rea CLI)` as `warn:
|
|
416
|
+
dist not found`, you skipped the build step. Run `pnpm install`.
|
|
417
|
+
|
|
418
|
+
## Graceful-degradation policy reader (added in 0.37.0)
|
|
419
|
+
|
|
420
|
+
The shimmed hooks need to read `.rea/policy.yaml` from a bash context
|
|
421
|
+
that may or may not have python3, jq, or rea's CLI on PATH. 0.37.0
|
|
422
|
+
formalized a 4-tier reader ladder:
|
|
423
|
+
|
|
424
|
+
1. **Tier 1** — `rea hook policy-get` (requires `dist/cli/index.js`)
|
|
425
|
+
2. **Tier 2** — `python3 + stdlib yaml` (PyYAML) — handles flow-form
|
|
426
|
+
3. **Tier 3** — POSIX `awk` block-form parser (the always-available floor)
|
|
427
|
+
4. **Fail-closed** — every tier unreachable: shim refuses the action
|
|
428
|
+
|
|
429
|
+
Tier 1 → 2 → 3 fallthrough is silent at hook-runtime; that's
|
|
430
|
+
intentional (graceful degradation), but means an unreachable Tier 1 +
|
|
431
|
+
unreachable Tier 2 can silently downgrade flow-form policy lookups to
|
|
432
|
+
block-form-only. `rea doctor` (0.39.0+) surfaces all three tier
|
|
433
|
+
reachabilities so you can spot the gap.
|
|
434
|
+
|
|
435
|
+
**Consumer impact:**
|
|
436
|
+
|
|
437
|
+
- If you use FLOW-form YAML for any policy block (e.g.
|
|
438
|
+
`blocked_paths: [.env, ".env.*"]`), make sure either the rea CLI
|
|
439
|
+
dist is present OR `python3 + PyYAML` is installed. With ONLY awk
|
|
440
|
+
reachable, flow-form lookups silently no-op on every shim
|
|
441
|
+
fallthrough path and your declared policy isn't enforced.
|
|
442
|
+
- Install PyYAML on CI runners: `pip3 install pyyaml`. On consumer
|
|
443
|
+
developer machines, it's almost always already present (macOS ships
|
|
444
|
+
it; major Linux distros bundle it with python3).
|
|
445
|
+
- For list-valued policy keys (`blocked_paths`, `protected_writes`),
|
|
446
|
+
the loader iterates the resulting JSON via jq OR python3. Have at
|
|
447
|
+
least one on PATH or `rea doctor` (0.42.0+) will report `fail` on
|
|
448
|
+
the `policy-reader Tier 3 (awk)` row with a list-walker-specific
|
|
449
|
+
remediation message.
|
|
450
|
+
|
|
451
|
+
## Shim runtime extraction (added in 0.38.0)
|
|
452
|
+
|
|
453
|
+
Cosmetic-only refactor: every `.claude/hooks/*.sh` shim now sources
|
|
454
|
+
`hooks/_lib/shim-runtime.sh` for shared boilerplate (env loading,
|
|
455
|
+
tier classification, audit-event emission). **No consumer action
|
|
456
|
+
required** — the change is byte-equivalent at the gate surface. New
|
|
457
|
+
shims you author can adopt the same runtime by sourcing the shared
|
|
458
|
+
helper; documented in the shim authoring guide.
|
|
459
|
+
|
|
460
|
+
## Doctor health surfaces for the policy reader (added in 0.39.0)
|
|
461
|
+
|
|
462
|
+
`rea doctor` gained explicit reachability checks for the 4-tier
|
|
463
|
+
ladder, the dist invokability probe, and a sandbox-containment check
|
|
464
|
+
on the resolved `dist/cli/index.js` path. Output lines you'll see:
|
|
465
|
+
|
|
466
|
+
- `policy-reader Tier 1 (rea CLI)` — pass/warn based on dist
|
|
467
|
+
presence + actual invocation
|
|
468
|
+
- `policy-reader Tier 2 (python3 + PyYAML)` — pass/warn based on
|
|
469
|
+
python3 + import yaml succeeding
|
|
470
|
+
- `policy-reader Tier 3 (awk)` — pass when awk present; warn or
|
|
471
|
+
fail conditional on whether other tiers cover the gap (0.40.0
|
|
472
|
+
refined the verdict logic; 0.42.0 hardened the list-walker
|
|
473
|
+
predicate)
|
|
474
|
+
- `policy-reader effective floor` — summary verdict across all three
|
|
475
|
+
- `policy-reader jq (JSON accelerator)` — info-level, calls out
|
|
476
|
+
Tier 1/2 perf when jq is absent
|
|
477
|
+
|
|
478
|
+
**Consumer action:** run `rea doctor` after each upgrade. The lines
|
|
479
|
+
above accurately reflect what your shims will do at runtime — a
|
|
480
|
+
`warn` is not a hard failure but signals a posture worth knowing
|
|
481
|
+
about (e.g. flow-form policy silently no-ops). A `fail` on any tier
|
|
482
|
+
row IS a hard failure that the doctor exits non-zero on.
|
|
483
|
+
|
|
484
|
+
## Upgrade preview + audit summary (added in 0.41.0)
|
|
485
|
+
|
|
486
|
+
Two new consumer-facing commands rolled out:
|
|
487
|
+
|
|
488
|
+
### `rea upgrade --check`
|
|
489
|
+
|
|
490
|
+
Dry-run preview of what `rea upgrade` would write, file-by-file, with
|
|
491
|
+
unified diffs. JSON output via `--json`. Always exits 0 — this is a
|
|
492
|
+
preview, not a gate. Use it before any non-trivial rea upgrade to
|
|
493
|
+
sanity-check the diff:
|
|
494
|
+
|
|
495
|
+
```bash
|
|
496
|
+
rea upgrade --check # human-readable table + diffs
|
|
497
|
+
rea upgrade --check --json # machine-readable for CI
|
|
498
|
+
rea upgrade --check --no-diff # counts + paths only
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
0.42.0 added the same settings-schema validation that `rea upgrade`
|
|
502
|
+
itself runs — if the merged settings would fail schema parse (typo'd
|
|
503
|
+
hook event, malformed hook command, …), the preview surfaces the
|
|
504
|
+
`WOULD REFUSE` message rather than promising a write the real
|
|
505
|
+
upgrade would refuse. The `settings_validation` field in the JSON
|
|
506
|
+
output carries the structured outcome.
|
|
507
|
+
|
|
508
|
+
### `rea audit summary`
|
|
509
|
+
|
|
510
|
+
High-level rollup of the audit log: counts by `tool_name`, `tier`,
|
|
511
|
+
`status`, `session`, the time window covered, and a sample-verified
|
|
512
|
+
chain-integrity check. `--since <duration>` (e.g. `24h`, `7d`, `2w`)
|
|
513
|
+
narrows to a recent window:
|
|
514
|
+
|
|
515
|
+
```bash
|
|
516
|
+
rea audit summary # all time
|
|
517
|
+
rea audit summary --since 24h # last 24 hours
|
|
518
|
+
rea audit summary --since 7d --json # last week, JSON
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
0.42.0 hardened the rotated-file walk: pre-0.42.0 `--since` pruned
|
|
522
|
+
rotated audit segments by filename stamp, which is wall-clock at the
|
|
523
|
+
rotation INSTANT — not the earliest record contained. A rotated file
|
|
524
|
+
from N days ago can contain records from N+M days ago when the
|
|
525
|
+
rotation cycle was long, so pruning by filename silently dropped
|
|
526
|
+
in-window records. Post-0.42.0 the walker reads every rotated file
|
|
527
|
+
under `--since` and lets the per-record timestamp filter drop the
|
|
528
|
+
out-of-window entries. Correctness over micro-optimization;
|
|
529
|
+
`rea audit summary` performance is unchanged in practice.
|
|
530
|
+
|
|
392
531
|
## Policy knobs worth setting
|
|
393
532
|
|
|
394
533
|
For consumers with a long-running migration branch (>30 commits since
|
package/README.md
CHANGED
|
@@ -9,8 +9,13 @@
|
|
|
9
9
|
[](https://developercertificate.org/)
|
|
10
10
|
[](https://nodejs.org/)
|
|
11
11
|
|
|
12
|
-
> Status: `0.
|
|
13
|
-
> [CHANGELOG.md](./CHANGELOG.md) for the per-release history.
|
|
12
|
+
> Status: `0.41.0` — published to npm with SLSA v1 provenance. See
|
|
13
|
+
> [CHANGELOG.md](./CHANGELOG.md) for the per-release history. The
|
|
14
|
+
> hook-port marathon (0.32→0.35) replaced every shell hook body with
|
|
15
|
+
> a Node-binary shim; the bash files in `.claude/hooks/` are now
|
|
16
|
+
> ~30-line stubs that fork `rea hook scan-bash` / `scan-write` for
|
|
17
|
+
> the real work. See [Architecture](#architecture) for the runtime
|
|
18
|
+
> picture.
|
|
14
19
|
|
|
15
20
|
REA is a single npm package that gates and audits agentic tool calls made by
|
|
16
21
|
Claude Code — shell commands, filesystem writes, and MCP tool invocations —
|
|
@@ -38,6 +43,7 @@ the [migration section](#migration-from-010x) below.
|
|
|
38
43
|
- [Quickstart](#quickstart)
|
|
39
44
|
- [What REA is](#what-rea-is)
|
|
40
45
|
- [What REA is NOT](#what-rea-is-not)
|
|
46
|
+
- [Architecture](#architecture)
|
|
41
47
|
- [The pre-push Codex gate](#the-pre-push-codex-gate)
|
|
42
48
|
- [MCP gateway](#mcp-gateway)
|
|
43
49
|
- [Policy file](#policy-file)
|
|
@@ -141,31 +147,39 @@ does not prevent the kill-switch from firing.
|
|
|
141
147
|
|
|
142
148
|
### 3. A hook layer
|
|
143
149
|
|
|
144
|
-
|
|
145
|
-
by `rea init`. All
|
|
146
|
-
and fire on Claude Code's `PreToolUse` /
|
|
147
|
-
scanning, dangerous-command interception,
|
|
148
|
-
settings protection, attribution rejection,
|
|
149
|
-
disclosure-policy routing, dependency audit,
|
|
150
|
-
PR-issue-link advisory, architecture advisory
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
the full inventory.
|
|
154
|
-
|
|
155
|
-
**
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
re-verifies the verdict JSON shape on return so a tampered
|
|
165
|
-
`REA_NODE_CLI` env var cannot bypass. See
|
|
150
|
+
Fourteen hook scripts ship in `hooks/` and are copied into
|
|
151
|
+
`.claude/hooks/` by `rea init`. All fourteen are wired into the default
|
|
152
|
+
`.claude/settings.json` and fire on Claude Code's `PreToolUse` /
|
|
153
|
+
`PostToolUse` events (secret scanning, dangerous-command interception,
|
|
154
|
+
blocked-path enforcement, settings protection, attribution rejection,
|
|
155
|
+
env-file protection, disclosure-policy routing, dependency audit,
|
|
156
|
+
changeset security, PR-issue-link advisory, architecture advisory,
|
|
157
|
+
local-review enforcement, protected-paths + blocked-paths bash-tier
|
|
158
|
+
parity). Each hook performs a HALT check near the top. See
|
|
159
|
+
[Hooks shipped](#hooks-shipped) for the full inventory.
|
|
160
|
+
|
|
161
|
+
**Node-binary scanners (since 0.32.0).** The hook-port marathon
|
|
162
|
+
(0.32.0 → 0.35.0) replaced every shell hook body with a Node-binary
|
|
163
|
+
shim. The bash files in `.claude/hooks/` are now ~30-line stubs that
|
|
164
|
+
fork `rea hook scan-bash` / `rea hook scan-write` (the AST-walker and
|
|
165
|
+
write-tier scanner respectively) and re-verify the verdict JSON shape
|
|
166
|
+
on return — a tampered `REA_NODE_CLI` env var cannot bypass. The
|
|
167
|
+
parser-tier walker (`mvdan-sh@0.10.1`) handles Bash AST grammar
|
|
168
|
+
exhaustively; the write-tier scanner handles `Write`/`Edit`/
|
|
169
|
+
`MultiEdit`/`NotebookEdit` payloads against the same policy. See
|
|
166
170
|
[`docs/architecture/bash-scanner.md`](docs/architecture/bash-scanner.md)
|
|
167
|
-
|
|
168
|
-
|
|
171
|
+
and [`docs/migration/0.23.0.md`](docs/migration/0.23.0.md) for the
|
|
172
|
+
walker design and consumer migration notes.
|
|
173
|
+
|
|
174
|
+
**Four-tier policy reader.** The shim infrastructure honors a
|
|
175
|
+
four-tier ladder when loading policy (`hooks/_lib/policy-reader.sh`):
|
|
176
|
+
Tier 1 is `rea hook policy-get` (the dist CLI), Tier 2 is
|
|
177
|
+
`python3 + PyYAML`, Tier 3 is `awk` (block-form only), and an
|
|
178
|
+
optional `jq` accelerator for JSON walks. `rea doctor` probes every
|
|
179
|
+
tier and surfaces which one(s) are reachable in the operator's
|
|
180
|
+
environment — pre-0.39.0 a stale dist + missing PyYAML would
|
|
181
|
+
silently no-op flow-form policy when `awk` was the only working
|
|
182
|
+
tier. See `Self-validation` below.
|
|
169
183
|
|
|
170
184
|
The hook layer runs independently of the MCP gateway — bypassing one does
|
|
171
185
|
not disable the other. That redundancy is intentional.
|
|
@@ -220,6 +234,71 @@ The non-goals are the product.
|
|
|
220
234
|
|
|
221
235
|
---
|
|
222
236
|
|
|
237
|
+
## Architecture
|
|
238
|
+
|
|
239
|
+
REA ships as a single npm package that delivers five runtime surfaces:
|
|
240
|
+
|
|
241
|
+
1. **Node-binary CLI** (`dist/cli/index.js`) — the `rea` command, with
|
|
242
|
+
subcommands for install (`init`/`upgrade`), runtime
|
|
243
|
+
(`serve`/`check`/`status`/`doctor`), kill-switch (`freeze`/
|
|
244
|
+
`unfreeze`), audit (`rotate`/`verify`/`summary`/`specialists`),
|
|
245
|
+
review (`review`/`preflight`/`hook push-gate`), and hook
|
|
246
|
+
evaluation (`hook scan-bash`/`scan-write`/`policy-get`).
|
|
247
|
+
2. **Shell hook shims** (`hooks/*.sh` → `.claude/hooks/*.sh`) — ~30
|
|
248
|
+
lines apiece. Each shim performs a HALT check, then forks the
|
|
249
|
+
corresponding `rea hook scan-*` Node CLI for the real verdict.
|
|
250
|
+
Pre-0.32.0 these were 500+ line bash regex pipelines; the rewrite
|
|
251
|
+
closed 24 known bypass classes and removed the bash hot-path
|
|
252
|
+
entirely. The shims still ship as bash so Claude Code's hook
|
|
253
|
+
matcher (which spawns sh) works without a Node prerequisite at
|
|
254
|
+
hook-fire time — the Node CLI is invoked from inside.
|
|
255
|
+
3. **Husky hooks** (`.husky/commit-msg`, `.husky/pre-push`,
|
|
256
|
+
`.husky/prepare-commit-msg`) — written by `rea init`. Pre-push
|
|
257
|
+
runs `rea hook push-gate` (stateless Codex review). Commit-msg
|
|
258
|
+
blocks AI attribution and DCO violations. Prepare-commit-msg
|
|
259
|
+
optionally appends a `Co-Authored-By:` trailer when configured.
|
|
260
|
+
Extension surfaces in `.husky/{commit-msg,pre-push}.d/*` are
|
|
261
|
+
sourced after rea's body for layering commitlint, lint-staged,
|
|
262
|
+
etc.
|
|
263
|
+
4. **MCP gateway** (`rea serve`) — a stdio MCP server started by
|
|
264
|
+
Claude Code via `.mcp.json`. Proxies downstream MCPs declared in
|
|
265
|
+
`.rea/registry.yaml` through a fixed middleware chain (audit,
|
|
266
|
+
kill-switch, tier, policy, blocked-paths, rate-limit, breaker,
|
|
267
|
+
injection, redact, size-cap). See [MCP gateway](#mcp-gateway).
|
|
268
|
+
5. **Four-tier policy reader** (`hooks/_lib/policy-reader.sh`) — the
|
|
269
|
+
shared helper that bash shims use to read `.rea/policy.yaml`.
|
|
270
|
+
Tier 1 calls `rea hook policy-get` (full YAML semantics). Tier 2
|
|
271
|
+
falls back to `python3 + PyYAML` when the CLI is unreachable.
|
|
272
|
+
Tier 3 falls back to `awk` for the block-form subset. `jq` is an
|
|
273
|
+
optional JSON accelerator. Each tier downgrades silently to the
|
|
274
|
+
next; `rea doctor` probes the ladder explicitly so operators see
|
|
275
|
+
exactly which tier(s) work in their environment.
|
|
276
|
+
|
|
277
|
+
### Self-validation
|
|
278
|
+
|
|
279
|
+
`rea doctor` validates every install surface in one shot:
|
|
280
|
+
|
|
281
|
+
- `.rea/policy.yaml` parses against the strict zod schema
|
|
282
|
+
- `.rea/` directory layout (HALT, registry, fingerprints, audit log)
|
|
283
|
+
- `.claude/settings.json` schema + every shipped hook registered
|
|
284
|
+
- `.husky/commit-msg`, `.husky/pre-push`, `.husky/prepare-commit-msg`
|
|
285
|
+
exist + have the expected marker, with husky-9 stub indirection
|
|
286
|
+
followed transparently
|
|
287
|
+
- `codex` binary on `PATH` when `policy.review.codex_required: true`
|
|
288
|
+
- Per-tier policy reader probe (`rea hook policy-get` → `python3 +
|
|
289
|
+
PyYAML` → `awk` → optional `jq`) so silent flow-form no-ops are
|
|
290
|
+
caught at install time, not at first hook fire
|
|
291
|
+
- Optional `--smoke` drives the real delegation-capture hook
|
|
292
|
+
end-to-end (writes a probe audit record + verifies chain integrity)
|
|
293
|
+
- Optional `--drift` reports per-file SHA drift vs. the install
|
|
294
|
+
manifest without mutating
|
|
295
|
+
- `--strict` promotes settings-schema warnings to hard fail (for CI)
|
|
296
|
+
|
|
297
|
+
This repo dogfoods every check — see `.rea/` and `.claude/` in the
|
|
298
|
+
checkout for the canonical `bst-internal` profile layout.
|
|
299
|
+
|
|
300
|
+
---
|
|
301
|
+
|
|
223
302
|
## The pre-push Codex gate
|
|
224
303
|
|
|
225
304
|
The 0.11.0 gate is stateless. Every `git push` runs Codex on the diff, and
|
|
@@ -864,12 +943,27 @@ Sync `.claude/`, `.husky/`, and managed fragments with this rea version.
|
|
|
864
943
|
Prompts on drift; silently refreshes unmodified files.
|
|
865
944
|
|
|
866
945
|
```bash
|
|
867
|
-
rea upgrade --dry-run
|
|
868
|
-
rea upgrade
|
|
869
|
-
rea upgrade
|
|
870
|
-
rea upgrade --
|
|
946
|
+
rea upgrade --dry-run # rehearse the interactive flow; write nothing
|
|
947
|
+
rea upgrade --check # structured preview + unified diffs (0.41.0)
|
|
948
|
+
rea upgrade --check --json # machine-readable preview document
|
|
949
|
+
rea upgrade --check --no-diff # paths + counts only (large repos)
|
|
950
|
+
rea upgrade # interactive
|
|
951
|
+
rea upgrade -y # non-interactive, keep drifted files
|
|
952
|
+
rea upgrade --force # non-interactive, overwrite drift
|
|
871
953
|
```
|
|
872
954
|
|
|
955
|
+
`--check` and `--dry-run` are distinct:
|
|
956
|
+
|
|
957
|
+
- `--dry-run` rehearses the full interactive flow with writes
|
|
958
|
+
suppressed (prompts still fire, output streams in classification
|
|
959
|
+
order). Useful locally to walk through the same prompts you'd see
|
|
960
|
+
during a real upgrade.
|
|
961
|
+
- `--check` is the structured, non-interactive preview: emits a
|
|
962
|
+
summary table + unified diffs per modified file, exits 0
|
|
963
|
+
regardless of what would change. The shape mirrors
|
|
964
|
+
`terraform plan` / `npm install --dry-run` — wire it into CI to
|
|
965
|
+
surface the changes an upgrade PR would produce.
|
|
966
|
+
|
|
873
967
|
### `rea serve`
|
|
874
968
|
|
|
875
969
|
Start the MCP gateway. Invoked by Claude Code via `.mcp.json`; not a
|
|
@@ -908,27 +1002,50 @@ rea status --json # pipe to jq
|
|
|
908
1002
|
|
|
909
1003
|
### `rea doctor`
|
|
910
1004
|
|
|
911
|
-
Validate the install — policy parses, `.rea/` layout, hooks, Codex
|
|
912
|
-
presence, TOFU fingerprint store
|
|
1005
|
+
Validate the install — policy parses, `.rea/` layout, hooks, Codex
|
|
1006
|
+
plugin presence, TOFU fingerprint store, husky stub indirection,
|
|
1007
|
+
and the four-tier policy reader ladder (`rea hook policy-get` →
|
|
1008
|
+
`python3 + PyYAML` → `awk` → optional `jq`).
|
|
913
1009
|
|
|
914
1010
|
```bash
|
|
915
1011
|
rea doctor
|
|
916
1012
|
rea doctor --metrics # also print 7-day Codex telemetry summary
|
|
917
1013
|
rea doctor --drift # report drift vs. install manifest (read-only)
|
|
1014
|
+
rea doctor --smoke # exercise delegation-capture hook end-to-end
|
|
1015
|
+
rea doctor --strict # 0.30.0 — promote settings-schema warnings to fail
|
|
918
1016
|
```
|
|
919
1017
|
|
|
920
|
-
In non-git directories the commit-msg and pre-push checks are
|
|
921
|
-
cleanly. Audit hash-chain integrity is verified by `rea
|
|
922
|
-
not by `rea doctor`.
|
|
1018
|
+
In non-git directories the commit-msg and pre-push checks are
|
|
1019
|
+
skipped cleanly. Audit hash-chain integrity is verified by `rea
|
|
1020
|
+
audit verify`, not by `rea doctor`. Each policy-reader tier is
|
|
1021
|
+
probed independently so silent flow-form no-ops (e.g. stale dist +
|
|
1022
|
+
missing PyYAML, with awk handling block-form only) surface at
|
|
1023
|
+
install time instead of at first hook fire.
|
|
923
1024
|
|
|
924
|
-
### `rea audit rotate` / `rea audit verify`
|
|
1025
|
+
### `rea audit rotate` / `rea audit verify` / `rea audit summary` / `rea audit specialists`
|
|
925
1026
|
|
|
926
1027
|
```bash
|
|
927
1028
|
rea audit rotate # force rotation now
|
|
928
1029
|
rea audit verify # re-hash the chain; exit 1 on first tamper
|
|
929
1030
|
rea audit verify --since <file> # walk forward from a rotated file
|
|
1031
|
+
|
|
1032
|
+
rea audit summary # 0.41.0 — counts by tool/tier/session/status
|
|
1033
|
+
rea audit summary --since 24h # filter to last 24h (units: s/m/h/d/w)
|
|
1034
|
+
rea audit summary --since 7d --json # machine-readable rollup for jq
|
|
1035
|
+
|
|
1036
|
+
rea audit specialists # delegation-telemetry roll-up
|
|
1037
|
+
rea audit specialists --session all # show every session (default: $CLAUDE_SESSION_ID)
|
|
1038
|
+
rea audit specialists --since <file> # extend the walk through rotated files
|
|
930
1039
|
```
|
|
931
1040
|
|
|
1041
|
+
`rea audit summary` is the high-level overview reader: total events,
|
|
1042
|
+
counts grouped by `tool_name` / tier / status / session, the time
|
|
1043
|
+
window covered, and a sample-verified chain-integrity check. Note
|
|
1044
|
+
that `--since` for `summary` is a duration (`24h`, `7d`) — distinct
|
|
1045
|
+
from `--since <rotated-file>` on `verify` / `specialists` which
|
|
1046
|
+
anchors on a rotated-audit basename. Use `rea audit verify` for the
|
|
1047
|
+
rigorous per-record re-hash; `summary` only samples.
|
|
1048
|
+
|
|
932
1049
|
### `rea tofu list` / `rea tofu accept`
|
|
933
1050
|
|
|
934
1051
|
```bash
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `rea audit summary` — high-level audit-log overview (0.41.0).
|
|
3
|
+
*
|
|
4
|
+
* The audit log is rich and `rea audit specialists` already exists for
|
|
5
|
+
* one narrow event-class. `rea audit summary` complements it with a
|
|
6
|
+
* broad rollup: total events, counts by `tool_name`, by tier, by
|
|
7
|
+
* session, by status, the time window covered, and a sample-verified
|
|
8
|
+
* chain-integrity check.
|
|
9
|
+
*
|
|
10
|
+
* # Filtering
|
|
11
|
+
*
|
|
12
|
+
* `--since <duration>` accepts a compact duration string
|
|
13
|
+
* (`s`/`m`/`h`/`d`/`w`) and filters records by `timestamp >= now -
|
|
14
|
+
* duration`. Examples: `24h`, `7d`, `90m`, `2w`. This is DIFFERENT
|
|
15
|
+
* from `rea audit verify --since <file>` / `rea audit specialists
|
|
16
|
+
* --since <file>` which take a rotated-file ANCHOR, not a duration —
|
|
17
|
+
* the two `--since` semantics serve different needs (anchor for chain
|
|
18
|
+
* walks, duration for summarization) and we accept the surface area
|
|
19
|
+
* cost. The duration form is what consumers reach for when asking
|
|
20
|
+
* "what happened in the last day?".
|
|
21
|
+
*
|
|
22
|
+
* # Chain integrity
|
|
23
|
+
*
|
|
24
|
+
* `rea audit verify` does the rigorous per-record re-hash. `summary`
|
|
25
|
+
* samples up to `CHAIN_SAMPLE_SIZE` records, evenly spaced through
|
|
26
|
+
* the filtered window, and reports `ok` / `tampered` / `unsampled`
|
|
27
|
+
* (window empty). Operators who suspect tampering should still run
|
|
28
|
+
* `rea audit verify` for an authoritative answer.
|
|
29
|
+
*
|
|
30
|
+
* # JSON output
|
|
31
|
+
*
|
|
32
|
+
* {
|
|
33
|
+
* "schema_version": 1,
|
|
34
|
+
* "window_seconds": 86400,
|
|
35
|
+
* "window_start": "2026-05-15T13:42:00Z",
|
|
36
|
+
* "window_end": "2026-05-16T13:42:00Z",
|
|
37
|
+
* "files_scanned": ["/abs/path/.rea/audit.jsonl"],
|
|
38
|
+
* "total_events": 1247,
|
|
39
|
+
* "by_tool_name": { "Bash": 612, "Edit": 289, … },
|
|
40
|
+
* "by_tier": { "read": 683, "write": 416, "destructive": 148 },
|
|
41
|
+
* "by_status": { "allowed": 1242, "denied": 5, "error": 0 },
|
|
42
|
+
* "by_session": { "session-abc…": 312, "session-def…": 935 },
|
|
43
|
+
* "session_count": 8,
|
|
44
|
+
* "earliest_timestamp": "2026-05-15T13:43:01.103Z",
|
|
45
|
+
* "latest_timestamp": "2026-05-16T13:41:57.842Z",
|
|
46
|
+
* "chain_integrity": "ok",
|
|
47
|
+
* "chain_samples_verified": 12
|
|
48
|
+
* }
|
|
49
|
+
*
|
|
50
|
+
* # Walk scope
|
|
51
|
+
*
|
|
52
|
+
* v1 walks the current `.rea/audit.jsonl` plus EVERY rotated file
|
|
53
|
+
* whose latest record falls within the window. Older rotated files
|
|
54
|
+
* are skipped — they cannot contain in-window records. When `--since`
|
|
55
|
+
* is omitted, no time filter is applied and the walk covers the
|
|
56
|
+
* current `audit.jsonl` only (operators wanting historical depth
|
|
57
|
+
* should pass `--since <DUR>`).
|
|
58
|
+
*/
|
|
59
|
+
import type { Command } from 'commander';
|
|
60
|
+
export declare const AUDIT_SUMMARY_SCHEMA_VERSION = 1;
|
|
61
|
+
/** Hard cap on chain-integrity samples. Keeps `rea audit summary`
|
|
62
|
+
* fast even on large logs while still surfacing obvious tampering. */
|
|
63
|
+
export declare const CHAIN_SAMPLE_SIZE = 12;
|
|
64
|
+
/**
|
|
65
|
+
* Thrown by `computeAuditSummary` when `--since` cannot be parsed.
|
|
66
|
+
* The commander wrapper exits 1.
|
|
67
|
+
*/
|
|
68
|
+
export declare class AuditSummarySinceError extends Error {
|
|
69
|
+
constructor(message: string);
|
|
70
|
+
}
|
|
71
|
+
/** Three-state classification for chain integrity. */
|
|
72
|
+
export type ChainIntegrity = 'ok' | 'tampered' | 'unsampled';
|
|
73
|
+
export interface AuditSummaryResult {
|
|
74
|
+
schema_version: typeof AUDIT_SUMMARY_SCHEMA_VERSION;
|
|
75
|
+
/** Window length in seconds. `null` when no `--since` filter was set. */
|
|
76
|
+
window_seconds: number | null;
|
|
77
|
+
/** Inclusive window start. `null` when no filter; otherwise the
|
|
78
|
+
* computed cutoff (now - duration). */
|
|
79
|
+
window_start: string | null;
|
|
80
|
+
/** Window end — always `now` when filter is set; `null` otherwise. */
|
|
81
|
+
window_end: string | null;
|
|
82
|
+
/** Absolute paths of audit files walked. */
|
|
83
|
+
files_scanned: string[];
|
|
84
|
+
/**
|
|
85
|
+
* 0.42.0 codex round 4 P2 + round 6 P2 (2026-05-16) — reserved for
|
|
86
|
+
* future use; ALWAYS EMPTY in 0.42.0. The original intent (round 4)
|
|
87
|
+
* was to soft-skip rotated segments that the operator could not
|
|
88
|
+
* read (e.g. EACCES/EPERM after a backup restore). Round 6 showed
|
|
89
|
+
* the soft-skip was unsound: without per-segment time-range
|
|
90
|
+
* metadata we cannot prove a skipped file is out-of-scope for the
|
|
91
|
+
* `--since` window, so a silent skip risks an undercount + a
|
|
92
|
+
* misleading `chain_integrity: ok`. The current implementation
|
|
93
|
+
* therefore throws on any non-ENOENT read error; this field is
|
|
94
|
+
* kept in the public schema so a future release that ships
|
|
95
|
+
* per-segment time-range metadata can populate it without breaking
|
|
96
|
+
* JSON consumers.
|
|
97
|
+
*/
|
|
98
|
+
unreadable_segments: string[];
|
|
99
|
+
total_events: number;
|
|
100
|
+
by_tool_name: Record<string, number>;
|
|
101
|
+
by_tier: Record<string, number>;
|
|
102
|
+
by_status: Record<string, number>;
|
|
103
|
+
by_session: Record<string, number>;
|
|
104
|
+
session_count: number;
|
|
105
|
+
/** Earliest in-window `timestamp` seen. `null` when no records. */
|
|
106
|
+
earliest_timestamp: string | null;
|
|
107
|
+
/** Latest in-window `timestamp` seen. `null` when no records. */
|
|
108
|
+
latest_timestamp: string | null;
|
|
109
|
+
chain_integrity: ChainIntegrity;
|
|
110
|
+
/** Number of samples actually verified. Always `<= CHAIN_SAMPLE_SIZE`. */
|
|
111
|
+
chain_samples_verified: number;
|
|
112
|
+
}
|
|
113
|
+
export interface ComputeAuditSummaryOptions {
|
|
114
|
+
/** Override CWD. Tests set this; production uses `process.cwd()`. */
|
|
115
|
+
baseDir?: string;
|
|
116
|
+
/** Raw `--since` value (e.g. `24h`, `7d`). Parsed via parseDuration. */
|
|
117
|
+
since?: string;
|
|
118
|
+
/** Test seam — pin "now" for deterministic window calculations. */
|
|
119
|
+
now?: Date;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Parse a compact duration string into seconds. Accepts:
|
|
123
|
+
*
|
|
124
|
+
* - `<N>s` — seconds
|
|
125
|
+
* - `<N>m` — minutes
|
|
126
|
+
* - `<N>h` — hours
|
|
127
|
+
* - `<N>d` — days
|
|
128
|
+
* - `<N>w` — weeks (7 days)
|
|
129
|
+
*
|
|
130
|
+
* `N` must be a positive integer with no whitespace. Returns the
|
|
131
|
+
* number of seconds; throws `AuditSummarySinceError` on parse failure.
|
|
132
|
+
*
|
|
133
|
+
* We deliberately do not accept bare numbers (would be ambiguous) or
|
|
134
|
+
* fractional units (no real use case; complicates rendering).
|
|
135
|
+
*/
|
|
136
|
+
export declare function parseDurationSeconds(raw: string): number;
|
|
137
|
+
/**
|
|
138
|
+
* Compute the summary. Pure (read-only). Throws
|
|
139
|
+
* `AuditSummarySinceError` on bad `--since`; everything else is
|
|
140
|
+
* surfaced via the result.
|
|
141
|
+
*/
|
|
142
|
+
export declare function computeAuditSummary(options?: ComputeAuditSummaryOptions): Promise<AuditSummaryResult>;
|
|
143
|
+
/**
|
|
144
|
+
* Render the result as a human-readable terminal block. Designed for
|
|
145
|
+
* the default `rea audit summary` invocation; `--json` callers bypass
|
|
146
|
+
* this entirely.
|
|
147
|
+
*/
|
|
148
|
+
export declare function renderAuditSummary(result: AuditSummaryResult): string;
|
|
149
|
+
export interface RunAuditSummaryOptions {
|
|
150
|
+
since?: string;
|
|
151
|
+
json?: boolean;
|
|
152
|
+
/** Test seam — pin "now". */
|
|
153
|
+
now?: Date;
|
|
154
|
+
}
|
|
155
|
+
/** Commander entrypoint. */
|
|
156
|
+
export declare function runAuditSummary(options: RunAuditSummaryOptions): Promise<void>;
|
|
157
|
+
/**
|
|
158
|
+
* Register `rea audit summary` under the `audit` command group.
|
|
159
|
+
*/
|
|
160
|
+
export declare function registerAuditSummaryCommand(auditCommand: Command): void;
|