@bookedsolid/rea 0.10.2 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.husky/pre-push +22 -167
- package/agents/codex-adversarial.md +5 -3
- package/commands/codex-review.md +3 -5
- package/dist/audit/append.d.ts +7 -32
- package/dist/audit/append.js +7 -35
- package/dist/cli/audit.d.ts +0 -31
- package/dist/cli/audit.js +5 -74
- package/dist/cli/doctor.js +6 -16
- package/dist/cli/hook.d.ts +48 -0
- package/dist/cli/hook.js +127 -0
- package/dist/cli/index.js +5 -80
- package/dist/cli/init.js +1 -1
- package/dist/cli/install/gitignore.d.ts +2 -2
- package/dist/cli/install/gitignore.js +3 -3
- package/dist/cli/install/pre-push.d.ts +146 -271
- package/dist/cli/install/pre-push.js +471 -2633
- package/dist/cli/install/settings-merge.d.ts +17 -0
- package/dist/cli/install/settings-merge.js +48 -1
- package/dist/cli/upgrade.js +131 -3
- package/dist/config/tier-map.js +18 -25
- package/dist/hooks/push-gate/base.d.ts +57 -0
- package/dist/hooks/push-gate/base.js +77 -0
- package/dist/hooks/push-gate/codex-runner.d.ts +126 -0
- package/dist/hooks/push-gate/codex-runner.js +223 -0
- package/dist/hooks/push-gate/findings.d.ts +68 -0
- package/dist/hooks/push-gate/findings.js +142 -0
- package/dist/hooks/push-gate/halt.d.ts +28 -0
- package/dist/hooks/push-gate/halt.js +49 -0
- package/dist/hooks/push-gate/index.d.ts +90 -0
- package/dist/hooks/push-gate/index.js +351 -0
- package/dist/hooks/push-gate/policy.d.ts +41 -0
- package/dist/hooks/push-gate/policy.js +55 -0
- package/dist/hooks/push-gate/report.d.ts +89 -0
- package/dist/hooks/push-gate/report.js +140 -0
- package/dist/policy/loader.d.ts +10 -10
- package/dist/policy/loader.js +7 -6
- package/dist/policy/types.d.ts +31 -22
- package/package.json +1 -1
- package/dist/cache/review-cache.d.ts +0 -115
- package/dist/cache/review-cache.js +0 -200
- package/dist/cli/cache.d.ts +0 -84
- package/dist/cli/cache.js +0 -150
- package/dist/hooks/review-gate/args.d.ts +0 -126
- package/dist/hooks/review-gate/args.js +0 -315
- package/dist/hooks/review-gate/banner.d.ts +0 -97
- package/dist/hooks/review-gate/banner.js +0 -172
- package/dist/hooks/review-gate/cache-key.d.ts +0 -55
- package/dist/hooks/review-gate/cache-key.js +0 -41
- package/dist/hooks/review-gate/constants.d.ts +0 -26
- package/dist/hooks/review-gate/constants.js +0 -34
- package/dist/hooks/review-gate/errors.d.ts +0 -72
- package/dist/hooks/review-gate/errors.js +0 -100
- package/dist/hooks/review-gate/hash.d.ts +0 -43
- package/dist/hooks/review-gate/hash.js +0 -46
- package/dist/hooks/review-gate/index.d.ts +0 -21
- package/dist/hooks/review-gate/index.js +0 -21
- package/dist/hooks/review-gate/metadata.d.ts +0 -98
- package/dist/hooks/review-gate/metadata.js +0 -158
- package/dist/hooks/review-gate/policy.d.ts +0 -55
- package/dist/hooks/review-gate/policy.js +0 -71
- package/dist/hooks/review-gate/protected-paths.d.ts +0 -46
- package/dist/hooks/review-gate/protected-paths.js +0 -76
- package/hooks/_lib/push-review-core.sh +0 -1250
- package/hooks/commit-review-gate.sh +0 -330
- package/hooks/push-review-gate-git.sh +0 -94
- package/hooks/push-review-gate.sh +0 -92
|
@@ -1,330 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# PreToolUse hook: commit-review-gate.sh
|
|
3
|
-
# Fires BEFORE every Bash tool call that matches "git commit".
|
|
4
|
-
# Implements a triage-based review gate:
|
|
5
|
-
# - trivial (<20 changed lines, non-sensitive paths) → pass immediately
|
|
6
|
-
# - standard (20-200 lines) → check review cache, pass if cached
|
|
7
|
-
# - significant (>200 lines or sensitive paths) → block, request agent review
|
|
8
|
-
#
|
|
9
|
-
# Exit codes:
|
|
10
|
-
# 0 = allow (trivial change, or cached review found)
|
|
11
|
-
# 2 = block (needs review — returns additionalContext for agent)
|
|
12
|
-
|
|
13
|
-
set -uo pipefail
|
|
14
|
-
|
|
15
|
-
# ── 1. Read ALL stdin immediately ─────────────────────────────────────────────
|
|
16
|
-
INPUT=$(cat)
|
|
17
|
-
|
|
18
|
-
# ── 1a. Cross-repo guard (must come FIRST — before any rea-scoped check) ──────
|
|
19
|
-
# BUG-012 (0.6.2) — mirror of push-review-gate.sh §1a. Script-location
|
|
20
|
-
# anchor (not CLAUDE_PROJECT_DIR) owns the trust decision. See the
|
|
21
|
-
# push-gate comment and THREAT_MODEL.md § CLAUDE_PROJECT_DIR for the full
|
|
22
|
-
# rationale. In short: CLAUDE_PROJECT_DIR is caller-controlled, cannot be
|
|
23
|
-
# trusted for authorization, and the hook's own filesystem location is the
|
|
24
|
-
# only forge-resistant anchor available to a bash script.
|
|
25
|
-
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]:-$0}")" && pwd -P 2>/dev/null)"
|
|
26
|
-
# Walk up from SCRIPT_DIR looking for `.rea/policy.yaml`. Matches every
|
|
27
|
-
# reasonable install topology (see push-review-gate.sh §1a for the full
|
|
28
|
-
# rationale). A hard-coded `../..` breaks the source-path invocation
|
|
29
|
-
# (`bash hooks/commit-review-gate.sh`) and silently reads .rea state from
|
|
30
|
-
# the WRONG directory.
|
|
31
|
-
REA_ROOT=""
|
|
32
|
-
_anchor_candidate="$SCRIPT_DIR"
|
|
33
|
-
for _ in 1 2 3 4; do
|
|
34
|
-
_anchor_candidate="$(cd -- "$_anchor_candidate/.." && pwd -P 2>/dev/null || true)"
|
|
35
|
-
if [[ -n "$_anchor_candidate" && -f "$_anchor_candidate/.rea/policy.yaml" ]]; then
|
|
36
|
-
REA_ROOT="$_anchor_candidate"
|
|
37
|
-
break
|
|
38
|
-
fi
|
|
39
|
-
done
|
|
40
|
-
if [[ -z "$REA_ROOT" ]]; then
|
|
41
|
-
printf 'rea-hook: no .rea/policy.yaml found within 4 parents of %s\n' \
|
|
42
|
-
"$SCRIPT_DIR" >&2
|
|
43
|
-
printf 'rea-hook: is this an installed rea hook, or is `.rea/policy.yaml`\n' >&2
|
|
44
|
-
printf 'rea-hook: nested more than 4 directories above the hook script?\n' >&2
|
|
45
|
-
exit 2
|
|
46
|
-
fi
|
|
47
|
-
unset _anchor_candidate
|
|
48
|
-
|
|
49
|
-
if [[ -n "${CLAUDE_PROJECT_DIR:-}" ]]; then
|
|
50
|
-
CPD_REAL=$(cd -- "${CLAUDE_PROJECT_DIR}" 2>/dev/null && pwd -P 2>/dev/null || true)
|
|
51
|
-
if [[ -n "$CPD_REAL" && "$CPD_REAL" != "$REA_ROOT" ]]; then
|
|
52
|
-
printf 'rea-hook: ignoring CLAUDE_PROJECT_DIR=%s — anchoring to script location %s\n' \
|
|
53
|
-
"$CLAUDE_PROJECT_DIR" "$REA_ROOT" >&2
|
|
54
|
-
fi
|
|
55
|
-
fi
|
|
56
|
-
|
|
57
|
-
CWD_REAL=$(pwd -P 2>/dev/null || pwd)
|
|
58
|
-
CWD_COMMON=$(git -C "$CWD_REAL" rev-parse --path-format=absolute --git-common-dir 2>/dev/null || true)
|
|
59
|
-
REA_COMMON=$(git -C "$REA_ROOT" rev-parse --path-format=absolute --git-common-dir 2>/dev/null || true)
|
|
60
|
-
if [[ -n "$CWD_COMMON" && -n "$REA_COMMON" ]]; then
|
|
61
|
-
CWD_COMMON_REAL=$(cd "$CWD_COMMON" 2>/dev/null && pwd -P 2>/dev/null || echo "$CWD_COMMON")
|
|
62
|
-
REA_COMMON_REAL=$(cd "$REA_COMMON" 2>/dev/null && pwd -P 2>/dev/null || echo "$REA_COMMON")
|
|
63
|
-
if [[ "$CWD_COMMON_REAL" != "$REA_COMMON_REAL" ]]; then
|
|
64
|
-
exit 0
|
|
65
|
-
fi
|
|
66
|
-
elif [[ -z "$CWD_COMMON" && -z "$REA_COMMON" ]]; then
|
|
67
|
-
case "$CWD_REAL/" in
|
|
68
|
-
"$REA_ROOT"/*|"$REA_ROOT"/) : ;; # inside rea — run the gate
|
|
69
|
-
*) exit 0 ;; # outside rea — not our gate
|
|
70
|
-
esac
|
|
71
|
-
fi
|
|
72
|
-
# Mixed state or probe error → fail CLOSED: run the gate.
|
|
73
|
-
|
|
74
|
-
# ── 2. Dependency check ──────────────────────────────────────────────────────
|
|
75
|
-
if ! command -v jq >/dev/null 2>&1; then
|
|
76
|
-
printf 'REA ERROR: jq is required but not installed.\n' >&2
|
|
77
|
-
printf 'Install: brew install jq OR apt-get install -y jq\n' >&2
|
|
78
|
-
exit 2
|
|
79
|
-
fi
|
|
80
|
-
|
|
81
|
-
# ── 3. HALT check ────────────────────────────────────────────────────────────
|
|
82
|
-
HALT_FILE="${REA_ROOT}/.rea/HALT"
|
|
83
|
-
if [ -f "$HALT_FILE" ]; then
|
|
84
|
-
printf 'REA HALT: %s\nAll agent operations suspended. Run: rea unfreeze\n' \
|
|
85
|
-
"$(head -c 1024 "$HALT_FILE" 2>/dev/null || echo 'Reason unknown')" >&2
|
|
86
|
-
exit 2
|
|
87
|
-
fi
|
|
88
|
-
|
|
89
|
-
# ── 4. Parse command ──────────────────────────────────────────────────────────
|
|
90
|
-
CMD=$(printf '%s' "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
|
|
91
|
-
|
|
92
|
-
if [[ -z "$CMD" ]]; then
|
|
93
|
-
exit 0
|
|
94
|
-
fi
|
|
95
|
-
|
|
96
|
-
# Only trigger on git commit commands
|
|
97
|
-
if ! printf '%s' "$CMD" | grep -qiE 'git[[:space:]]+commit'; then
|
|
98
|
-
exit 0
|
|
99
|
-
fi
|
|
100
|
-
|
|
101
|
-
# Skip --amend (reviewing amendments is a future feature)
|
|
102
|
-
if printf '%s' "$CMD" | grep -qiE 'git[[:space:]]+commit.*--amend'; then
|
|
103
|
-
exit 0
|
|
104
|
-
fi
|
|
105
|
-
|
|
106
|
-
# ── 5. Compute diff stats ────────────────────────────────────────────────────
|
|
107
|
-
# Get staged diff (what would be committed)
|
|
108
|
-
DIFF_OUTPUT=$(cd "$REA_ROOT" && git diff --cached --stat 2>/dev/null || echo "")
|
|
109
|
-
DIFF_FULL=$(cd "$REA_ROOT" && git diff --cached 2>/dev/null || echo "")
|
|
110
|
-
|
|
111
|
-
if [[ -z "$DIFF_OUTPUT" ]]; then
|
|
112
|
-
# No staged changes — let git commit handle the error
|
|
113
|
-
exit 0
|
|
114
|
-
fi
|
|
115
|
-
|
|
116
|
-
# Count changed lines (additions + deletions)
|
|
117
|
-
# Defect K (rea#62) sibling: `|| echo "0"` captures "0\n0" into LINE_COUNT
|
|
118
|
-
# when grep exits non-zero on a no-match — grep still prints its own `0` and
|
|
119
|
-
# `echo "0"` appends another. At this site the concatenated `"0\n0"` is then
|
|
120
|
-
# evaluated as arithmetic (`-gt $SIGNIFICANT_THRESHOLD`, `-ge $TRIVIAL_THRESHOLD`
|
|
121
|
-
# below) and bash emits a "syntax error in expression" at runtime on any
|
|
122
|
-
# rename-only / mode-only / empty-file-add diff. `|| true` + bash-default
|
|
123
|
-
# expansion fixes both the banner cosmetic and the arithmetic-unsafe control
|
|
124
|
-
# flow in one shot.
|
|
125
|
-
LINE_COUNT=$(printf '%s' "$DIFF_FULL" | grep -cE '^\+[^+]|^-[^-]' 2>/dev/null || true)
|
|
126
|
-
LINE_COUNT="${LINE_COUNT:-0}"
|
|
127
|
-
|
|
128
|
-
# Check for sensitive paths
|
|
129
|
-
SENSITIVE=0
|
|
130
|
-
SENSITIVE_FILES=""
|
|
131
|
-
if printf '%s' "$DIFF_FULL" | grep -qE '^\+\+\+ .*(\.rea/|\.claude/|\.env|auth|security|\.github/workflows)'; then
|
|
132
|
-
SENSITIVE=1
|
|
133
|
-
SENSITIVE_FILES=$(printf '%s' "$DIFF_FULL" | grep -oE '^\+\+\+ .*(\.rea/|\.claude/|\.env|auth|security|\.github/workflows)[^ ]*' | sed 's/^\+\+\+ [ab]\// /' | head -5)
|
|
134
|
-
fi
|
|
135
|
-
|
|
136
|
-
# ── 7. Triage scoring ────────────────────────────────────────────────────────
|
|
137
|
-
TRIVIAL_THRESHOLD=20
|
|
138
|
-
SIGNIFICANT_THRESHOLD=200
|
|
139
|
-
|
|
140
|
-
if [[ $SENSITIVE -eq 1 ]] || [[ $LINE_COUNT -gt $SIGNIFICANT_THRESHOLD ]]; then
|
|
141
|
-
SCORE="significant"
|
|
142
|
-
elif [[ $LINE_COUNT -ge $TRIVIAL_THRESHOLD ]]; then
|
|
143
|
-
SCORE="standard"
|
|
144
|
-
else
|
|
145
|
-
SCORE="trivial"
|
|
146
|
-
fi
|
|
147
|
-
|
|
148
|
-
# ── 8. Trivial → pass immediately ─────────────────────────────────────────────
|
|
149
|
-
if [[ "$SCORE" == "trivial" ]]; then
|
|
150
|
-
exit 0
|
|
151
|
-
fi
|
|
152
|
-
|
|
153
|
-
# ── 9. Resolve rea CLI ────────────────────────────────────────────────────
|
|
154
|
-
# Try local installs first, then dist build, then global PATH install.
|
|
155
|
-
#
|
|
156
|
-
# node_modules/.bin/rea is a launcher (pnpm writes a POSIX shell shim, npm
|
|
157
|
-
# writes a symlink to dist/cli/index.js with its own `#!/usr/bin/env node`
|
|
158
|
-
# shebang). Either way it is NOT a plain JS file, so running `node` on it
|
|
159
|
-
# would parse shell syntax as JavaScript and SyntaxError. Execute the shim
|
|
160
|
-
# directly — it handles `exec node` itself — and only prepend `node` on the
|
|
161
|
-
# dist fallback, which is a real JS module. The `-x` guard picks up both
|
|
162
|
-
# pnpm shims (executable regular file) and npm symlinks (executable target).
|
|
163
|
-
REA_CLI_ARGS=()
|
|
164
|
-
if [[ -x "${REA_ROOT}/node_modules/.bin/rea" ]]; then
|
|
165
|
-
REA_CLI_ARGS=("${REA_ROOT}/node_modules/.bin/rea")
|
|
166
|
-
elif [[ -f "${REA_ROOT}/dist/cli/index.js" ]]; then
|
|
167
|
-
REA_CLI_ARGS=(node "${REA_ROOT}/dist/cli/index.js")
|
|
168
|
-
elif command -v rea >/dev/null 2>&1; then
|
|
169
|
-
REA_CLI_ARGS=(rea)
|
|
170
|
-
fi
|
|
171
|
-
|
|
172
|
-
# ── 10. Check review cache for all non-trivial commits ────────────────────────
|
|
173
|
-
# Compute SHA and branch here so both standard and significant tiers share them.
|
|
174
|
-
#
|
|
175
|
-
# Defect L (rea#63) sibling: `shasum` is not installed on Alpine, distroless,
|
|
176
|
-
# or most minimal Linux CI images — only `sha256sum` is. The prior chain
|
|
177
|
-
# silently produced an empty STAGED_SHA, which the cache block then skipped
|
|
178
|
-
# AND the banner at §11 rendered as `rea cache set pass` — a dead-end the
|
|
179
|
-
# agent cannot execute. Portable chain mirrors push-review-core.sh §8:
|
|
180
|
-
# sha256sum → shasum → openssl. The openssl branch uses `awk '{print $NF}'`
|
|
181
|
-
# WITHOUT `-r` to stay compatible with OpenSSL 1.1.x (Debian 11, Ubuntu
|
|
182
|
-
# 20.04, RHEL 8, Amazon Linux 2, Alpine 3.13–3.14).
|
|
183
|
-
STAGED_SHA=""
|
|
184
|
-
if command -v sha256sum >/dev/null 2>&1; then
|
|
185
|
-
STAGED_SHA=$(printf '%s' "$DIFF_FULL" | sha256sum 2>/dev/null | awk '{print $1}')
|
|
186
|
-
elif command -v shasum >/dev/null 2>&1; then
|
|
187
|
-
STAGED_SHA=$(printf '%s' "$DIFF_FULL" | shasum -a 256 2>/dev/null | awk '{print $1}')
|
|
188
|
-
elif command -v openssl >/dev/null 2>&1; then
|
|
189
|
-
STAGED_SHA=$(printf '%s' "$DIFF_FULL" | openssl dgst -sha256 2>/dev/null | awk '{print $NF}')
|
|
190
|
-
else
|
|
191
|
-
printf 'rea commit-review: WARN no sha256 hasher found (sha256sum/shasum/openssl); cache disabled\n' >&2
|
|
192
|
-
fi
|
|
193
|
-
if [[ -n "$STAGED_SHA" && ! "$STAGED_SHA" =~ ^[0-9a-f]{64}$ ]]; then
|
|
194
|
-
printf 'rea commit-review: WARN hasher returned invalid output; cache disabled\n' >&2
|
|
195
|
-
STAGED_SHA=""
|
|
196
|
-
fi
|
|
197
|
-
BRANCH=$(cd "$REA_ROOT" && git branch --show-current 2>/dev/null || echo "")
|
|
198
|
-
CACHE_FILE="${REA_ROOT}/.rea/review-cache.json"
|
|
199
|
-
|
|
200
|
-
# Codex pass-3 finding #1: `rea cache check` and `rea cache set` both declare
|
|
201
|
-
# `--base` as a `requiredOption` in src/cli/index.ts. Prior versions of this
|
|
202
|
-
# gate omitted `--base`, so (a) the CLI path exited non-zero and the
|
|
203
|
-
# `|| echo '{"hit":false}'` fallback quietly masked the contract error, and
|
|
204
|
-
# (b) the section-11 banner instructed the agent to run `rea cache set <sha>
|
|
205
|
-
# pass` — also missing `--base`, rejected by the CLI on every retry. A
|
|
206
|
-
# successful cache flow was unreachable.
|
|
207
|
-
#
|
|
208
|
-
# Resolve BASE_BRANCH by the same preference order the push-gate uses in
|
|
209
|
-
# push-review-core.sh §7 (lines 778-794): origin/HEAD → origin/main →
|
|
210
|
-
# origin/master → empty. If nothing resolves, disable the cache (the
|
|
211
|
-
# alternative is emitting a cache command the CLI rejects on every call).
|
|
212
|
-
BASE_BRANCH=""
|
|
213
|
-
_origin_head=$(cd "$REA_ROOT" && git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null || true)
|
|
214
|
-
if [[ -n "$_origin_head" ]]; then
|
|
215
|
-
BASE_BRANCH="${_origin_head#refs/remotes/origin/}"
|
|
216
|
-
fi
|
|
217
|
-
if [[ -z "$BASE_BRANCH" ]]; then
|
|
218
|
-
# Use `git -C` so the current-shell cwd is never mutated — matches the
|
|
219
|
-
# cross-repo guard at §1a and keeps the file's dominant idiom. Raw
|
|
220
|
-
# `cd "$REA_ROOT" && git …` would leave the hook process sitting in
|
|
221
|
-
# $REA_ROOT, which is safe today but breaks silently if a future edit
|
|
222
|
-
# adds a relative-path command downstream.
|
|
223
|
-
if git -C "$REA_ROOT" rev-parse --verify --quiet refs/remotes/origin/main >/dev/null 2>&1; then
|
|
224
|
-
BASE_BRANCH="main"
|
|
225
|
-
elif git -C "$REA_ROOT" rev-parse --verify --quiet refs/remotes/origin/master >/dev/null 2>&1; then
|
|
226
|
-
BASE_BRANCH="master"
|
|
227
|
-
fi
|
|
228
|
-
fi
|
|
229
|
-
if [[ -z "$BASE_BRANCH" && -n "$STAGED_SHA" ]]; then
|
|
230
|
-
printf 'rea commit-review: WARN could not resolve base branch (no origin/HEAD, no origin/main, no origin/master); cache disabled\n' >&2
|
|
231
|
-
STAGED_SHA=""
|
|
232
|
-
fi
|
|
233
|
-
unset _origin_head
|
|
234
|
-
|
|
235
|
-
if [[ -n "$STAGED_SHA" ]]; then
|
|
236
|
-
CACHE_HIT=false
|
|
237
|
-
|
|
238
|
-
# Primary: use CLI when available — handles TTL, expiry, and branch-scoped keys.
|
|
239
|
-
# Cache predicate must require BOTH `.hit == true` AND `.result == "pass"` —
|
|
240
|
-
# a cached `fail` verdict would otherwise satisfy `.hit == true` and let the
|
|
241
|
-
# commit proceed despite a recorded negative review. Mirrors the push-gate
|
|
242
|
-
# predicate at push-review-core.sh §8; the §218-226 direct-cache fallback
|
|
243
|
-
# already enforces `result == "pass"`, so the two paths must agree.
|
|
244
|
-
if [[ ${#REA_CLI_ARGS[@]} -gt 0 ]]; then
|
|
245
|
-
# Defect F (rea#75): surface cache-query errors instead of treating them as
|
|
246
|
-
# legitimate misses. See hooks/_lib/push-review-core.sh for the rationale.
|
|
247
|
-
# SECURITY (Codex LOW 4): require mktemp. Predictable /tmp paths are a
|
|
248
|
-
# TOCTOU surface on shared hosts; fall-loud instead of fall-back.
|
|
249
|
-
if ! CACHE_STDERR_FILE=$(mktemp -t rea-commit-cache-err.XXXXXX 2>/dev/null); then
|
|
250
|
-
printf 'rea commit-review: mktemp unavailable; cannot capture cache-check stderr. Aborting.\n' >&2
|
|
251
|
-
exit 2
|
|
252
|
-
fi
|
|
253
|
-
CACHE_EXIT=0
|
|
254
|
-
CACHE_STDOUT=$("${REA_CLI_ARGS[@]}" cache check "$STAGED_SHA" --branch "$BRANCH" --base "$BASE_BRANCH" 2>"$CACHE_STDERR_FILE") || CACHE_EXIT=$?
|
|
255
|
-
CACHE_STDERR=$(cat "$CACHE_STDERR_FILE" 2>/dev/null || true)
|
|
256
|
-
rm -f "$CACHE_STDERR_FILE"
|
|
257
|
-
if [[ "$CACHE_EXIT" -ne 0 ]]; then
|
|
258
|
-
# SECURITY (Codex LOW 5): strip C0/C1 control chars before echoing CLI
|
|
259
|
-
# stderr. Includes 0x80-0x9F because some terminals interpret bare C1
|
|
260
|
-
# bytes (CSI 0x9B, OSC 0x9D) as escape introducers.
|
|
261
|
-
CACHE_STDERR_SAFE=$(printf '%s' "$CACHE_STDERR" | LC_ALL=C tr -d '\000-\037\177\200-\237')
|
|
262
|
-
printf 'rea commit-review: CACHE CHECK FAILED (exit=%d): %s\n' "$CACHE_EXIT" "$CACHE_STDERR_SAFE" >&2
|
|
263
|
-
printf 'rea commit-review: treating as miss; file bookedsolidtech/rea issue if unexpected.\n' >&2
|
|
264
|
-
CACHE_RESULT='{"hit":false,"reason":"query_error"}'
|
|
265
|
-
elif [[ -z "$CACHE_STDOUT" ]]; then
|
|
266
|
-
CACHE_RESULT='{"hit":false,"reason":"cold"}'
|
|
267
|
-
else
|
|
268
|
-
CACHE_RESULT="$CACHE_STDOUT"
|
|
269
|
-
fi
|
|
270
|
-
if printf '%s' "$CACHE_RESULT" | jq -e '.hit == true and .result == "pass"' >/dev/null 2>&1; then
|
|
271
|
-
CACHE_HIT=true
|
|
272
|
-
fi
|
|
273
|
-
fi
|
|
274
|
-
|
|
275
|
-
# Fallback: read cache JSON directly — works when rea is not on PATH.
|
|
276
|
-
# Checks branch-scoped key ("branch:sha") first, then bare SHA (empty-branch case).
|
|
277
|
-
if [[ "$CACHE_HIT" == "false" ]] && [[ -f "$CACHE_FILE" ]]; then
|
|
278
|
-
CACHE_KEY="${BRANCH}:${STAGED_SHA}"
|
|
279
|
-
DIRECT_HIT=$(jq -r --arg k1 "$CACHE_KEY" --arg k2 "$STAGED_SHA" \
|
|
280
|
-
'(.entries[$k1] // .entries[$k2]) | if . == null then "miss" elif .result == "pass" then "hit" else "miss" end' \
|
|
281
|
-
"$CACHE_FILE" 2>/dev/null || echo "miss")
|
|
282
|
-
if [[ "$DIRECT_HIT" == "hit" ]]; then
|
|
283
|
-
CACHE_HIT=true
|
|
284
|
-
fi
|
|
285
|
-
fi
|
|
286
|
-
|
|
287
|
-
if [[ "$CACHE_HIT" == "true" ]]; then
|
|
288
|
-
exit 0
|
|
289
|
-
fi
|
|
290
|
-
fi
|
|
291
|
-
|
|
292
|
-
# ── 11. Block and request review ──────────────────────────────────────────────
|
|
293
|
-
{
|
|
294
|
-
printf 'COMMIT REVIEW GATE: Review required before committing\n'
|
|
295
|
-
printf '\n'
|
|
296
|
-
printf ' Score: %s (%s changed lines)\n' "$SCORE" "$LINE_COUNT"
|
|
297
|
-
if [[ $SENSITIVE -eq 1 ]]; then
|
|
298
|
-
printf ' Sensitive paths detected:\n'
|
|
299
|
-
printf '%s\n' "$SENSITIVE_FILES"
|
|
300
|
-
fi
|
|
301
|
-
printf '\n'
|
|
302
|
-
printf ' YOU (the agent) are the reviewer. Do not ask the user to commit manually.\n'
|
|
303
|
-
printf ' Review the staged diff, make a pass/fail decision, then proceed:\n'
|
|
304
|
-
printf '\n'
|
|
305
|
-
printf ' 1. Inspect: git diff --cached\n'
|
|
306
|
-
printf ' 2. Decide: Is this safe to commit? (initial commits, refactors, and\n'
|
|
307
|
-
printf ' feature work are normal — use judgement, not ceremony)\n'
|
|
308
|
-
# Defect L follow-up: when no sha256 hasher is available STAGED_SHA is empty
|
|
309
|
-
# and `rea cache set pass` is a dead-end the CLI rejects. Branch the banner
|
|
310
|
-
# to surface an actionable path instead. Unlike push-review-core.sh there is
|
|
311
|
-
# no `REA_SKIP_COMMIT_REVIEW` env escape hatch (the commit gate only fires
|
|
312
|
-
# under Claude Code's Bash `PreToolUse` matcher, so a human direct-shell
|
|
313
|
-
# commit bypasses it entirely). The only remediation is to install a sha256
|
|
314
|
-
# hasher or ask the user to commit directly.
|
|
315
|
-
if [[ -n "$STAGED_SHA" ]]; then
|
|
316
|
-
printf ' 3. Approve: rea cache set %s pass --branch %s --base %s\n' \
|
|
317
|
-
"$STAGED_SHA" "$BRANCH" "$BASE_BRANCH"
|
|
318
|
-
printf ' 4. Retry the git commit command\n'
|
|
319
|
-
else
|
|
320
|
-
printf ' 3. Cache is DISABLED on this host (no sha256 hasher or no base\n'
|
|
321
|
-
printf ' branch resolvable). Install one of: sha256sum (Linux coreutils),\n'
|
|
322
|
-
printf ' shasum (perl-core), or openssl; or ensure origin/HEAD is set so\n'
|
|
323
|
-
printf ' the gate can identify the merge target. Without these the cache\n'
|
|
324
|
-
printf ' path cannot complete — escalate to the user if neither can be\n'
|
|
325
|
-
printf ' provided.\n'
|
|
326
|
-
fi
|
|
327
|
-
printf '\n'
|
|
328
|
-
printf ' Only escalate to the user if you find a genuine problem in the diff.\n'
|
|
329
|
-
} >&2
|
|
330
|
-
exit 2
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# Native git `.husky/pre-push` adapter for the REA push-review gate.
|
|
3
|
-
# Fires BEFORE `git push` via husky. Runs a full diff analysis against the
|
|
4
|
-
# target branch and requests security + code review before allowing the push.
|
|
5
|
-
#
|
|
6
|
-
# Exit codes:
|
|
7
|
-
# 0 = allow (no meaningful diff, cached review pass, or escape hatch
|
|
8
|
-
# invoked with successful audit-append)
|
|
9
|
-
# 2 = block (review required — protected-path gate OR general push-review
|
|
10
|
-
# gate — or escape hatch invoked but audit-append failed)
|
|
11
|
-
#
|
|
12
|
-
# ── Install ───────────────────────────────────────────────────────────────────
|
|
13
|
-
# This adapter is the recommended entry point for husky-driven pushes. Point
|
|
14
|
-
# `.husky/pre-push` at this file:
|
|
15
|
-
#
|
|
16
|
-
# #!/bin/sh
|
|
17
|
-
# REA_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd)
|
|
18
|
-
# exec "$REA_ROOT/.claude/hooks/push-review-gate-git.sh" "$@"
|
|
19
|
-
#
|
|
20
|
-
# `REA_ROOT` is resolved inside `.husky/pre-push` itself because neither git
|
|
21
|
-
# nor husky provides that env var — a bare `"$REA_ROOT/..."` would expand to
|
|
22
|
-
# `/.claude/...` and exit 126. See rea's own `.husky/pre-push` for the
|
|
23
|
-
# reference implementation.
|
|
24
|
-
#
|
|
25
|
-
# Git's native pre-push contract is:
|
|
26
|
-
# - stdin: one line per ref being pushed, `<local_ref> <local_sha> <remote_ref> <remote_sha>`
|
|
27
|
-
# - argv: `<remote_name> <remote_url>`
|
|
28
|
-
#
|
|
29
|
-
# ── Architecture ──────────────────────────────────────────────────────────────
|
|
30
|
-
# This file is a thin ADAPTER. All logic lives in
|
|
31
|
-
# `hooks/_lib/push-review-core.sh` (see `pr_core_run`). The core ships a
|
|
32
|
-
# `pr_parse_prepush_stdin` helper that recognises git's native refspec stdin
|
|
33
|
-
# and synthesises an equivalent `git push <remote>` CMD for the downstream
|
|
34
|
-
# protected-path detection.
|
|
35
|
-
#
|
|
36
|
-
# Two adapters share the core:
|
|
37
|
-
# - push-review-gate.sh ← Claude Code PreToolUse stdin (JSON `.tool_input.command`)
|
|
38
|
-
# - push-review-gate-git.sh ← this file, native `.husky/pre-push` stdin
|
|
39
|
-
#
|
|
40
|
-
# The core's BUG-008 stdin sniff makes either shape work from either adapter,
|
|
41
|
-
# so a consumer CAN wire `push-review-gate.sh` into `.husky/pre-push` and it
|
|
42
|
-
# just works. The git-native adapter exists so `.husky/pre-push` expresses
|
|
43
|
-
# its install intent clearly and so future git-only behaviour (e.g. remote-
|
|
44
|
-
# URL-scoped policy overrides) has a natural home that does not bloat the
|
|
45
|
-
# generic Claude Code adapter.
|
|
46
|
-
#
|
|
47
|
-
# ── Escape hatches ────────────────────────────────────────────────────────────
|
|
48
|
-
# REA_SKIP_CODEX_REVIEW=<reason> — Codex-only waiver. Since 0.8.0 (#85)
|
|
49
|
-
# this ONLY satisfies the protected-path
|
|
50
|
-
# Codex-audit requirement. HALT, cross-
|
|
51
|
-
# repo guard, ref-resolution, and the
|
|
52
|
-
# push-review cache still run. See the
|
|
53
|
-
# authoritative docstring in
|
|
54
|
-
# `push-review-gate.sh` for the full
|
|
55
|
-
# scope description. Audit record
|
|
56
|
-
# `tool_name: "codex.review.skipped"`.
|
|
57
|
-
# REA_SKIP_PUSH_REVIEW=<reason> — bypass the WHOLE gate for this push.
|
|
58
|
-
# Audit record
|
|
59
|
-
# `tool_name: "push.review.skipped"`.
|
|
60
|
-
#
|
|
61
|
-
# Both hatches are value-carrying: the env value IS the reason recorded in
|
|
62
|
-
# the audit receipt. An empty value (`REA_SKIP_...=`) is treated as unset.
|
|
63
|
-
# The hatches sit behind `.rea/HALT` — HALT always wins.
|
|
64
|
-
#
|
|
65
|
-
# Fail-closed contract:
|
|
66
|
-
# - `dist/audit/append.js` missing → exit 2 (build rea first)
|
|
67
|
-
# - Node invocation failure → exit 2
|
|
68
|
-
# - Unable to resolve actor from git config → exit 2
|
|
69
|
-
|
|
70
|
-
set -uo pipefail
|
|
71
|
-
|
|
72
|
-
# Read ALL stdin immediately. For husky-driven pushes this is git's refspec
|
|
73
|
-
# list; for any other caller it is whatever they hand us. The core's sniff
|
|
74
|
-
# decides.
|
|
75
|
-
INPUT=$(cat)
|
|
76
|
-
|
|
77
|
-
# Resolve the core library from this adapter's own on-disk location. Using
|
|
78
|
-
# BASH_SOURCE (not argv $0) so invocations from `.husky/pre-push`, from a
|
|
79
|
-
# consumer's `.claude/hooks/`, or from a direct `bash hooks/push-review-gate-git.sh`
|
|
80
|
-
# all find `_lib/` next to the adapter. Consistent with the BUG-012
|
|
81
|
-
# script-anchor rationale in core.
|
|
82
|
-
_adapter_script="${BASH_SOURCE[0]:-$0}"
|
|
83
|
-
_adapter_dir="$(cd -- "$(dirname -- "$_adapter_script")" && pwd -P 2>/dev/null)"
|
|
84
|
-
_core_lib="${_adapter_dir}/_lib/push-review-core.sh"
|
|
85
|
-
if [[ ! -f "$_core_lib" ]]; then
|
|
86
|
-
printf 'rea-hook: push-review-core.sh not found next to %s\n' \
|
|
87
|
-
"$_adapter_script" >&2
|
|
88
|
-
printf 'rea-hook: expected at %s\n' "$_core_lib" >&2
|
|
89
|
-
exit 2
|
|
90
|
-
fi
|
|
91
|
-
# shellcheck source=_lib/push-review-core.sh
|
|
92
|
-
source "$_core_lib"
|
|
93
|
-
|
|
94
|
-
pr_core_run "$_adapter_script" "$INPUT" "$@"
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# PreToolUse hook: push-review-gate.sh
|
|
3
|
-
# Fires BEFORE every Bash tool call that matches "git push".
|
|
4
|
-
# Runs a full diff analysis against the target branch and requests
|
|
5
|
-
# security + code review before allowing the push.
|
|
6
|
-
#
|
|
7
|
-
# Exit codes:
|
|
8
|
-
# 0 = allow (no meaningful diff, or review cached, or escape hatch invoked)
|
|
9
|
-
# 2 = block (needs review, or escape hatch invoked but audit-append failed)
|
|
10
|
-
#
|
|
11
|
-
# ── Architecture (0.7.0 BUG-008 cleanup) ─────────────────────────────────────
|
|
12
|
-
# This file is now a thin ADAPTER. All logic lives in
|
|
13
|
-
# `hooks/_lib/push-review-core.sh` (see `pr_core_run`). The adapter's only
|
|
14
|
-
# job is to (a) capture stdin, and (b) hand its own script path + stdin +
|
|
15
|
-
# argv to the core so the cross-repo anchor walks up from the RIGHT script
|
|
16
|
-
# location.
|
|
17
|
-
#
|
|
18
|
-
# Two adapters share the core:
|
|
19
|
-
# - push-review-gate.sh ← this file, Claude Code PreToolUse stdin (JSON)
|
|
20
|
-
# - push-review-gate-git.sh ← native `.husky/pre-push` stdin (git refspec)
|
|
21
|
-
# The core's BUG-008 sniff makes either stdin shape work from either adapter,
|
|
22
|
-
# so in practice a consumer can wire THIS file into `.husky/pre-push` and it
|
|
23
|
-
# just works. The `-git` adapter exists for clarity of install intent.
|
|
24
|
-
#
|
|
25
|
-
# ── Codex-only waiver: REA_SKIP_CODEX_REVIEW ─────────────────────────────────
|
|
26
|
-
# Env var `REA_SKIP_CODEX_REVIEW=<reason>` waives the Codex adversarial-
|
|
27
|
-
# review requirement (section 7 protected-path check). Set to any non-empty
|
|
28
|
-
# value; the value IS the reason recorded in the audit record (no default
|
|
29
|
-
# reason is supplied — if the operator sets `REA_SKIP_CODEX_REVIEW=1` the
|
|
30
|
-
# reason is literally "1").
|
|
31
|
-
#
|
|
32
|
-
# SCOPE (0.8.0, #85): Codex-only. The waiver only satisfies the
|
|
33
|
-
# protected-path Codex-audit requirement. Every other gate this hook
|
|
34
|
-
# runs still runs:
|
|
35
|
-
# • HALT (.rea/HALT) — still blocks.
|
|
36
|
-
# • Cross-repo guard — still blocks.
|
|
37
|
-
# • Ref-resolution failures — still block.
|
|
38
|
-
# • Push-review cache — a miss still falls through to section 9's general
|
|
39
|
-
# review-required block.
|
|
40
|
-
# (Blocked-paths enforcement is a separate hook on Edit/Write tiers, not
|
|
41
|
-
# this push hook — it was never gated by REA_SKIP_CODEX_REVIEW.)
|
|
42
|
-
#
|
|
43
|
-
# For a full-gate bypass, use `REA_SKIP_PUSH_REVIEW=<reason>` (section 5a).
|
|
44
|
-
# The 0.7.0 semantic (whole-gate bypass via the Codex hatch) was misleading
|
|
45
|
-
# — operators reached for REA_SKIP_CODEX_REVIEW to silence a transient
|
|
46
|
-
# Codex unavailability and accidentally bypassed every other check too.
|
|
47
|
-
# 0.8.0 narrows it to what the name implies.
|
|
48
|
-
#
|
|
49
|
-
# ORDERING: the waiver fires AFTER the HALT check but BEFORE ref-resolution.
|
|
50
|
-
# Prior to 0.7.0 the check ran inside the protected-path branch and only
|
|
51
|
-
# fired when the diff touched a protected path — which meant an operator
|
|
52
|
-
# who wanted to skip Codex review got blocked by a transient ref-resolution
|
|
53
|
-
# failure (missing remote object, unresolvable source ref, etc.) before the
|
|
54
|
-
# skip ever fired. The current ordering preserves the skip audit record
|
|
55
|
-
# even when downstream gates (ref-resolution, cache) block: the operator's
|
|
56
|
-
# commitment to waive is durable, even if the push itself is blocked on
|
|
57
|
-
# another gate.
|
|
58
|
-
#
|
|
59
|
-
# Every invocation appends a `tool_name: "codex.review.skipped"` record to
|
|
60
|
-
# `.rea/audit.jsonl` via the public audit helper. This record is intentionally
|
|
61
|
-
# NOT named `codex.review` so the existing jq predicate on `.tool_name ==
|
|
62
|
-
# "codex.review" and .metadata.verdict in {pass, concerns}` will never match
|
|
63
|
-
# a skip — a skipped review is not a review.
|
|
64
|
-
#
|
|
65
|
-
# Fail-closed contract:
|
|
66
|
-
# - `dist/audit/append.js` missing → exit 2 (build rea first)
|
|
67
|
-
# - Node invocation failure → exit 2
|
|
68
|
-
# - Unable to resolve actor from git config → exit 2
|
|
69
|
-
|
|
70
|
-
set -uo pipefail
|
|
71
|
-
|
|
72
|
-
# Read ALL stdin immediately. The core's BUG-008 sniff decides whether this
|
|
73
|
-
# is Claude Code JSON or git's native pre-push refspec list.
|
|
74
|
-
INPUT=$(cat)
|
|
75
|
-
|
|
76
|
-
# Resolve the core library from this adapter's own on-disk location. Using
|
|
77
|
-
# BASH_SOURCE (not argv $0) so `bash hooks/push-review-gate.sh` and
|
|
78
|
-
# `.../.claude/hooks/push-review-gate.sh` both find `_lib/` next to the
|
|
79
|
-
# adapter. Consistent with the BUG-012 script-anchor rationale in core.
|
|
80
|
-
_adapter_script="${BASH_SOURCE[0]:-$0}"
|
|
81
|
-
_adapter_dir="$(cd -- "$(dirname -- "$_adapter_script")" && pwd -P 2>/dev/null)"
|
|
82
|
-
_core_lib="${_adapter_dir}/_lib/push-review-core.sh"
|
|
83
|
-
if [[ ! -f "$_core_lib" ]]; then
|
|
84
|
-
printf 'rea-hook: push-review-core.sh not found next to %s\n' \
|
|
85
|
-
"$_adapter_script" >&2
|
|
86
|
-
printf 'rea-hook: expected at %s\n' "$_core_lib" >&2
|
|
87
|
-
exit 2
|
|
88
|
-
fi
|
|
89
|
-
# shellcheck source=_lib/push-review-core.sh
|
|
90
|
-
source "$_core_lib"
|
|
91
|
-
|
|
92
|
-
pr_core_run "$_adapter_script" "$INPUT" "$@"
|