Skip to content

feat: 0.11.0 stateless Codex push-gate#79

Merged
himerus merged 4 commits intomainfrom
feat/0.11.0-push-gate
Apr 23, 2026
Merged

feat: 0.11.0 stateless Codex push-gate#79
himerus merged 4 commits intomainfrom
feat/0.11.0-push-gate

Conversation

@himerus
Copy link
Copy Markdown
Contributor

@himerus himerus commented Apr 23, 2026

Summary

Replace the cache-attestation push-review gate (.claude/hooks/push-review-gate.sh, hooks/_lib/push-review-core.sh, the in-flight TS port at src/hooks/review-gate/, rea cache subcommands, rea audit record codex-review) with a stateless pre-push Codex gate.

Net LOC: +3,549 / −26,692 in production; test suite rebuilt on the smaller surface (919 tests pass).

What changes

  • git push.husky/pre-pushrea hook push-gatecodex exec review --json against the actual push target (refspec-aware: remote_sha..local_sha) → parse [P1]/[P2]/[P3] severity markers → .rea/last-review.json + audit record → exit 0/1/2.
  • When blocked, Claude reads stderr + the structured last-review.json and fixes. The auto-fix loop IS the retry mechanism — no rea fix-then-push wrapper.
  • Codex is the source of truth, fresh every push. No cache. No SHA attestation. No audit-receipt consultation.

Breaking changes (single 0.11.0 changeset)

  • Removed subcommands: rea cache check|set|clear|list, rea audit record codex-review
  • Removed policy fields: review.cache_max_age_seconds, review.allow_skip_in_ci (rea upgrade strips them and writes a .bak-<ts> backup)
  • Removed env vars: REA_SKIP_CODEX_REVIEW, REA_SKIP_PUSH_REVIEW
  • Removed hooks: hooks/push-review-gate.sh, push-review-gate-git.sh, commit-review-gate.sh, hooks/_lib/push-review-core.sh (+ .claude/ mirrors). rea upgrade prunes the stale .claude/settings.json entries that referenced them.

Added

  • rea hook push-gate [--base <ref>] — the single CLI entry point husky calls. Accepts git's positional pre-push argv silently; consumes refspecs from stdin with a 5s timeout.
  • policy.review.concerns_blocks (bool, default true) — when true, [P2] findings block. Per-push override via REA_ALLOW_CONCERNS=1.
  • policy.review.timeout_ms (int, default 600_000) — hard cap on the Codex subprocess.
  • New env var: REA_SKIP_PUSH_GATE=<reason> — value-carrying, audited; HALT still wins.
  • .rea/last-review.json — atomic-write structured dump, redact-scrubbed before disk.

Adversarial review

Ran /codex-review once before push; Codex surfaced four findings (3× P1 + 1× P2):

  1. Husky stub assumed rea on PATHnpx @bookedsolid/rea init bootstrap leaves no persistent install → rea: not found on every push. Fixed: stub resolves via node_modules/.bin/readist/cli/index.js (rea's own repo dogfood case) → PATH → npx --no-install.
  2. Upgrade didn't prune stale settings — 0.10.x installs would keep invoking deleted push-review-gate.sh. Fixed: added pruneHookCommands() in settings-merge.ts + STALE_HOOK_COMMAND_TOKENS list in upgrade.ts; upgrade now prunes first, merges second.
  3. Gate ignored pre-push stdingit push origin HEAD:release/1.0 reviewed against the wrong base (upstream ladder instead of the actual push target). Fixed: runPushGate accepts a refspecs: PrePushRefspec[] dep; CLI parses stdin; gate diffs remote_sha..local_sha per refspec.
  4. Race between exit and stdout draincodex-runner.ts could parse a truncated buffer and misclassify a blocking review as pass. Fixed: switched exitclose (waits for both stdio streams to end).

All four addressed in commit 63322a5. Dogfood push of this branch exercised the new gate itself and passed cleanly.

Test plan

  • pnpm lint — zero warnings, 12 redact patterns + 2 injection patterns cleared
  • pnpm type-check — strict, zero errors
  • pnpm test — 919 pass, 1 skipped (new push-gate modules: halt, policy, base, codex-runner, findings, report, index, pre-push installer rewrite, pruneHookCommands, refspec parsing)
  • pnpm build — clean tsc
  • Dogfood: this branch pushed through the new gate. Gate short-circuited at review.codex_required: false (the repo's policy) per design — the full Codex path is proven by the unit tests.

Follow-ups

  • Consumer migration proof point: run rea upgrade against Helix's 0.10.x checkout and confirm migration is clean (policy backup + settings prune + husky refresh).

Jake Strawn added 4 commits April 22, 2026 21:13
Replace the cache-attestation push/commit review-gate with a stateless
gate that runs `codex exec review --json` on every push and infers a
verdict from the streamed findings. No cache, no SHA matching, no
audit-receipt consultation — Codex is the source of truth, fresh every
push.

Flow:
  git push → .husky/pre-push → rea hook push-gate
           → codex exec review --base <ref> --json
           → parse [P1]/[P2]/[P3] severity markers
           → write .rea/last-review.json + audit record
           → exit 0 (pass/empty/skip/disabled) / 1 (HALT) / 2 (blocked)

When blocked, Claude reads stderr + the structured last-review.json
and fixes — the organic retry loop IS the value.

Deleted (−26,692 LOC):
- hooks/push-review-gate.sh, push-review-gate-git.sh,
  commit-review-gate.sh, _lib/push-review-core.sh
- src/hooks/review-gate/ (30 files: TS port Phase 1 + 2a + 2b)
- src/cli/cache.ts + src/cache/ (cache CLI surface)
- src/cli/audit.ts::runAuditRecordCodexReview
- src/audit/append.ts::appendCodexReviewAuditRecord
- 12 __tests__/hooks/push-review-*.test.ts files
- __tests__/cli/audit-record-codex.test.ts
- __tests__/readme-agent-workflow.test.ts

Added (+3,549 LOC):
- src/hooks/push-gate/ (7 modules: halt, policy, base, codex-runner,
  findings, report, index) + 7 test modules
- src/cli/hook.ts — `rea hook push-gate [--base <ref>]` subcommand
- 0.11.0 policy migration in src/cli/upgrade.ts (strips removed
  review.cache_max_age_seconds / review.allow_skip_in_ci fields,
  backfills review.concerns_blocks: true)
- New markers v2 in src/cli/install/pre-push.ts; legacy v1 markers
  still detected so upgrade migrates old installs cleanly
- .rea/last-review.json — atomic-write structured findings dump
  (redact-pattern-scrubbed before disk hits)

Policy schema changes:
- REMOVED: review.cache_max_age_seconds, review.allow_skip_in_ci
- ADDED:   review.concerns_blocks (bool, default true),
           review.timeout_ms (int, default 600_000)
- KEPT:    review.codex_required (semantics narrowed — "run codex
           on push" not "consult audit log")

Env vars:
- NEW:     REA_SKIP_PUSH_GATE=<reason> (value-carrying, audited)
- NEW:     REA_ALLOW_CONCERNS=1 (per-push override)
- REMOVED: REA_SKIP_CODEX_REVIEW, REA_SKIP_PUSH_REVIEW

Audit events emitted by the gate (none read back):
- rea.push_gate.reviewed / halted / disabled / skipped / empty_diff
  / error

Tests: 907 pass, 1 skipped. Type-check, lint, build all green.
Signed-off-by: Jake Strawn <bandy.strawn@clarityhouse.press>
Four findings from /codex-review against the 0.11.0 push-gate diff:

P1 — Husky stub resolved `rea` via bare name, which fails for the
  documented `npx @bookedsolid/rea init` bootstrap (no persistent global
  install → `rea: not found` on every push). Now resolves in priority:
  `<root>/node_modules/.bin/rea` → PATH → `npx --no-install @bookedsolid/rea`,
  with a clear stderr error and exit 2 when none of those work. Body is
  identical in the installer template and the dogfood `.husky/pre-push`.

P1 — `rea upgrade` was additive-only on `.claude/settings.json`, leaving
  the 0.10.x `push-review-gate.sh` / `commit-review-gate.sh` /
  `push-review-gate-git.sh` Bash-hook entries pointing at files this
  release just deleted. Added `pruneHookCommands()` to `settings-merge.ts`
  and a `STALE_HOOK_COMMAND_TOKENS` list in `upgrade.ts`. Upgrade now
  prunes first, then merges, and reports removed entries in the
  operator-facing warning output.

P1 — The gate always diffed `HEAD` against the resolver ladder and
  ignored git's pre-push stdin, so `git push origin HEAD:release/1.0`
  silently reviewed against the wrong base (and short-circuited to
  empty-diff when the commit already existed on `main`). `runPushGate`
  now accepts a `refspecs: PrePushRefspec[]` dep, `parsePrePushStdin()`
  builds it from the git contract, and the CLI reads stdin with a
  5s timeout. When stdin yields refspecs, the gate diffs
  `remote_sha..local_sha` per the actual push target; deletions and
  null-SHA new-ref cases fall back cleanly.

P2 — `codex-runner.ts` resolved on the child's `exit` event, which can
  fire before the final stdout chunks are drained on large reviews.
  Switched to `close`, which waits for both stdio streams to end.

Tests +12: parsePrePushStdin, refspec-aware runPushGate branches,
pruneHookCommands.

Tests: 919 pass, 1 skipped. Type-check, lint, build all green.
Signed-off-by: Jake Strawn <bandy.strawn@clarityhouse.press>
The 0.11.0 husky pre-push stub tries node_modules/.bin/rea → PATH → npx.
That works in every consumer install, but rea's own repo has neither a
node_modules copy of itself nor a persistent global install — the
package IS the repo. The first push of feat/0.11.0-push-gate hit
`rea: command not found` and aborted.

Added a dist/cli/index.js fallback between the node_modules check and
the PATH check: if dist/ is built (which it must be for the code under
review to exist), invoke the CLI via `node <root>/dist/cli/index.js`.
This makes the hook self-hosting for rea's own dogfood loop.

The same body lives in src/cli/install/pre-push.ts (installer template)
and .husky/pre-push (live dogfood hook); both updated in lockstep.

Tests 919 pass, 1 skipped. Build + type-check + lint green.

Signed-off-by: Jake Strawn <bandy.strawn@clarityhouse.press>
Git passes `<remote-name> <remote-url>` to pre-push as positional argv;
the husky stub forwards them with `"$@"`. Commander rejects any
unexpected positional by default, so every real push hit:

  error: too many arguments for 'push-gate'. Expected 0 arguments but
  got 2.

Declared the positional slot as a variadic `[gitArgs...]` and ignored
it in the action handler. The base ref + refspecs still come from
stdin + the auto-resolver; these positionals are informational only.

Tests 919 pass. Build + type-check green.

Signed-off-by: Jake Strawn <bandy.strawn@clarityhouse.press>
@himerus himerus merged commit a6faf92 into main Apr 23, 2026
9 checks passed
@himerus himerus deleted the feat/0.11.0-push-gate branch April 23, 2026 01:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant