@bookedsolid/rea 0.25.0 → 0.26.1
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/README.md +10 -7
- package/agents/codex-adversarial.md +4 -0
- package/agents/rea-orchestrator.md +9 -0
- package/commands/codex-review.md +4 -0
- package/dist/audit/append.d.ts +1 -0
- package/dist/audit/append.js +1 -0
- package/dist/audit/content-token.d.ts +98 -0
- package/dist/audit/content-token.js +136 -0
- package/dist/audit/local-review-event.d.ts +136 -0
- package/dist/audit/local-review-event.js +43 -0
- package/dist/cli/doctor.js +17 -0
- package/dist/cli/hook.d.ts +44 -0
- package/dist/cli/hook.js +77 -0
- package/dist/cli/index.js +9 -0
- package/dist/cli/init.js +197 -46
- package/dist/cli/install/pre-push.d.ts +15 -3
- package/dist/cli/install/pre-push.js +55 -5
- package/dist/cli/install/settings-merge.js +13 -0
- package/dist/cli/preflight.d.ts +120 -0
- package/dist/cli/preflight.js +487 -0
- package/dist/cli/review.d.ts +56 -0
- package/dist/cli/review.js +325 -0
- package/dist/hooks/bash-scanner/walker.js +232 -2
- package/dist/policy/loader.d.ts +65 -0
- package/dist/policy/loader.js +33 -0
- package/dist/policy/types.d.ts +89 -0
- package/hooks/_lib/cmd-segments.sh +383 -14
- package/hooks/_lib/policy-read.sh +255 -0
- package/hooks/local-review-gate.sh +460 -0
- package/package.json +1 -1
- package/templates/CLAUDE.md.local-first.md +87 -0
- package/templates/pre-push.local-first.sh +65 -0
|
@@ -141,6 +141,261 @@ policy_list() {
|
|
|
141
141
|
done < "$policy"
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
+
# Resolve the rea binary the same 4-branch ladder used by
|
|
145
|
+
# `local-review-gate.sh` and `templates/pre-push.local-first.sh`. Echoes
|
|
146
|
+
# the resolved command (one shell-token per line) on stdout when found,
|
|
147
|
+
# nothing when the ladder exhausts. The caller passes the result to
|
|
148
|
+
# `read -ra` to materialize a bash array.
|
|
149
|
+
#
|
|
150
|
+
# Round-30 F2: shared helper used by `policy_nested_scalar` to invoke
|
|
151
|
+
# `rea hook policy-get` for canonical inline+block YAML reads. Falling
|
|
152
|
+
# open to empty when no rea CLI is reachable keeps the bash gates
|
|
153
|
+
# advisory rather than fail-closed on missing tooling — same posture
|
|
154
|
+
# `local-review-gate.sh` itself takes.
|
|
155
|
+
_rea_resolve_bin() {
|
|
156
|
+
local root="${REA_ROOT:-}"
|
|
157
|
+
if [[ -z "$root" ]]; then
|
|
158
|
+
if command -v rea_root >/dev/null 2>&1; then
|
|
159
|
+
root=$(rea_root)
|
|
160
|
+
else
|
|
161
|
+
root="${CLAUDE_PROJECT_DIR:-$(pwd)}"
|
|
162
|
+
fi
|
|
163
|
+
fi
|
|
164
|
+
if [ -x "${root}/node_modules/.bin/rea" ]; then
|
|
165
|
+
printf '%s\n' "${root}/node_modules/.bin/rea"
|
|
166
|
+
return 0
|
|
167
|
+
fi
|
|
168
|
+
if [ -f "${root}/dist/cli/index.js" ] \
|
|
169
|
+
&& [ -f "${root}/package.json" ] \
|
|
170
|
+
&& grep -q '"name": *"@bookedsolid/rea"' "${root}/package.json" 2>/dev/null; then
|
|
171
|
+
printf 'node\n'
|
|
172
|
+
printf '%s\n' "${root}/dist/cli/index.js"
|
|
173
|
+
return 0
|
|
174
|
+
fi
|
|
175
|
+
if command -v rea >/dev/null 2>&1; then
|
|
176
|
+
printf 'rea\n'
|
|
177
|
+
return 0
|
|
178
|
+
fi
|
|
179
|
+
if command -v npx >/dev/null 2>&1; then
|
|
180
|
+
printf 'npx\n'
|
|
181
|
+
printf -- '--no-install\n'
|
|
182
|
+
printf '@bookedsolid/rea\n'
|
|
183
|
+
return 0
|
|
184
|
+
fi
|
|
185
|
+
return 1
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
# Read the value of a nested scalar under a parent key.
|
|
189
|
+
# Usage: policy_nested_scalar "review" "local_review" "mode"
|
|
190
|
+
# Prints empty when any link in the chain is missing.
|
|
191
|
+
#
|
|
192
|
+
# Round-30 F2 (structural): delegates to `rea hook policy-get` so
|
|
193
|
+
# the bash reader and the TS loader use the same parser. Pre-fix the
|
|
194
|
+
# bash function only matched block-form mappings (parent+child+grandchild
|
|
195
|
+
# at increasing indents) and silently missed inline forms like
|
|
196
|
+
# `local_review: { mode: off }`. The TS loader (yaml.parse) accepts both
|
|
197
|
+
# forms — silent split-brain. Routing through the canonical parser
|
|
198
|
+
# closes the divergence by construction.
|
|
199
|
+
#
|
|
200
|
+
# Fallback: when the rea CLI cannot be located AT ALL (no
|
|
201
|
+
# node_modules/.bin/rea, no dogfood dist, no PATH, no npx), the
|
|
202
|
+
# function falls back to the legacy awk parser. This preserves the
|
|
203
|
+
# pre-0.27 behavior on machines that have not installed rea yet —
|
|
204
|
+
# the gate stays advisory, not fail-closed on missing tooling. The
|
|
205
|
+
# awk fallback keeps the block-only limitation; that's acceptable
|
|
206
|
+
# for the no-CLI scenario because consumers without rea installed
|
|
207
|
+
# can't have edited a rea-format policy anyway.
|
|
208
|
+
policy_nested_scalar() {
|
|
209
|
+
local parent="$1"
|
|
210
|
+
local child="$2"
|
|
211
|
+
local grandchild="$3"
|
|
212
|
+
local policy
|
|
213
|
+
policy=$(policy_path)
|
|
214
|
+
[[ -z "$policy" ]] && return 0
|
|
215
|
+
|
|
216
|
+
# Try the canonical TS reader first. `read -ra` materializes the
|
|
217
|
+
# newline-delimited resolver output into a bash array; an empty
|
|
218
|
+
# result means the ladder exhausted (no CLI reachable).
|
|
219
|
+
local resolved
|
|
220
|
+
resolved=$(_rea_resolve_bin 2>/dev/null) || resolved=""
|
|
221
|
+
if [[ -n "$resolved" ]]; then
|
|
222
|
+
local rea_cmd=()
|
|
223
|
+
while IFS= read -r tok; do
|
|
224
|
+
[[ -n "$tok" ]] && rea_cmd+=("$tok")
|
|
225
|
+
done <<< "$resolved"
|
|
226
|
+
if [[ ${#rea_cmd[@]} -gt 0 ]]; then
|
|
227
|
+
local out
|
|
228
|
+
out=$("${rea_cmd[@]}" hook policy-get "${parent}.${child}.${grandchild}" 2>/dev/null) || out=""
|
|
229
|
+
printf '%s' "$out"
|
|
230
|
+
return 0
|
|
231
|
+
fi
|
|
232
|
+
fi
|
|
233
|
+
|
|
234
|
+
# No rea CLI reachable — fall back to the legacy block-form awk
|
|
235
|
+
# parser. Inline forms still miss in this branch, but the consumer
|
|
236
|
+
# has bigger problems (no rea binary at all).
|
|
237
|
+
_rea_awk_nested_scalar "$parent" "$child" "$grandchild"
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
# Round-30 F2 perf optimization: cache the entire `review.local_review`
|
|
241
|
+
# subtree as JSON on first read. Three rea-CLI spawns per Bash hook fire
|
|
242
|
+
# (200ms each = ~0.6s overhead) is unacceptable; ONE spawn for all three
|
|
243
|
+
# fields is acceptable (~200ms once per hook). The JSON is parsed via jq
|
|
244
|
+
# (already a hook dependency) for each individual field.
|
|
245
|
+
#
|
|
246
|
+
# `_REA_LOCAL_REVIEW_JSON_CACHE` is process-scoped (set when the hook
|
|
247
|
+
# sources this file). Empty string before the first read; either `null`
|
|
248
|
+
# or a JSON object after. The `_REA_LOCAL_REVIEW_JSON_LOADED` flag
|
|
249
|
+
# distinguishes "not yet read" from "read and value was null". Both
|
|
250
|
+
# cleared at re-source time so each hook fire starts fresh.
|
|
251
|
+
_REA_LOCAL_REVIEW_JSON_CACHE=""
|
|
252
|
+
_REA_LOCAL_REVIEW_JSON_LOADED=0
|
|
253
|
+
|
|
254
|
+
_rea_load_local_review_json() {
|
|
255
|
+
if [[ $_REA_LOCAL_REVIEW_JSON_LOADED -eq 1 ]]; then
|
|
256
|
+
return 0
|
|
257
|
+
fi
|
|
258
|
+
_REA_LOCAL_REVIEW_JSON_LOADED=1
|
|
259
|
+
local policy
|
|
260
|
+
policy=$(policy_path)
|
|
261
|
+
if [[ -z "$policy" ]]; then
|
|
262
|
+
_REA_LOCAL_REVIEW_JSON_CACHE='null'
|
|
263
|
+
return 0
|
|
264
|
+
fi
|
|
265
|
+
|
|
266
|
+
# Try the canonical TS reader for the entire subtree as JSON. Falls
|
|
267
|
+
# back to awk-fallback emulation when the rea CLI is unreachable.
|
|
268
|
+
local resolved
|
|
269
|
+
resolved=$(_rea_resolve_bin 2>/dev/null) || resolved=""
|
|
270
|
+
if [[ -n "$resolved" ]]; then
|
|
271
|
+
local rea_cmd=()
|
|
272
|
+
while IFS= read -r tok; do
|
|
273
|
+
[[ -n "$tok" ]] && rea_cmd+=("$tok")
|
|
274
|
+
done <<< "$resolved"
|
|
275
|
+
if [[ ${#rea_cmd[@]} -gt 0 ]]; then
|
|
276
|
+
local out
|
|
277
|
+
out=$("${rea_cmd[@]}" hook policy-get review.local_review --json 2>/dev/null) || out=""
|
|
278
|
+
if [[ -n "$out" ]]; then
|
|
279
|
+
_REA_LOCAL_REVIEW_JSON_CACHE="$out"
|
|
280
|
+
return 0
|
|
281
|
+
fi
|
|
282
|
+
fi
|
|
283
|
+
fi
|
|
284
|
+
|
|
285
|
+
# Fallback: synthesize a JSON object from individual awk reads. This
|
|
286
|
+
# is the only path on machines without rea reachable. Inline-form
|
|
287
|
+
# values still miss in the awk fallback (the documented divergence
|
|
288
|
+
# from when no CLI is reachable), but block-form values work.
|
|
289
|
+
local mode_val refuse_val bypass_val
|
|
290
|
+
mode_val=$(_rea_awk_nested_scalar "review" "local_review" "mode")
|
|
291
|
+
refuse_val=$(_rea_awk_nested_scalar "review" "local_review" "refuse_at")
|
|
292
|
+
bypass_val=$(_rea_awk_nested_scalar "review" "local_review" "bypass_env_var")
|
|
293
|
+
if command -v jq >/dev/null 2>&1; then
|
|
294
|
+
_REA_LOCAL_REVIEW_JSON_CACHE=$(jq -n \
|
|
295
|
+
--arg mode "$mode_val" \
|
|
296
|
+
--arg refuse_at "$refuse_val" \
|
|
297
|
+
--arg bypass_env_var "$bypass_val" \
|
|
298
|
+
'def s($x): if $x == "" then null else $x end;
|
|
299
|
+
{mode: s($mode), refuse_at: s($refuse_at), bypass_env_var: s($bypass_env_var)}')
|
|
300
|
+
else
|
|
301
|
+
# No jq either — synthesize minimal JSON via printf. Values are
|
|
302
|
+
# YAML scalars; we re-quote them safely. Empty values become null.
|
|
303
|
+
_REA_LOCAL_REVIEW_JSON_CACHE='null'
|
|
304
|
+
fi
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
# Helper: read from the cached local_review JSON via jq. Echoes the
|
|
308
|
+
# scalar value or empty string when missing/null.
|
|
309
|
+
_rea_local_review_get() {
|
|
310
|
+
local field="$1"
|
|
311
|
+
_rea_load_local_review_json
|
|
312
|
+
if [[ "$_REA_LOCAL_REVIEW_JSON_CACHE" == "null" || -z "$_REA_LOCAL_REVIEW_JSON_CACHE" ]]; then
|
|
313
|
+
return 0
|
|
314
|
+
fi
|
|
315
|
+
if ! command -v jq >/dev/null 2>&1; then
|
|
316
|
+
return 0
|
|
317
|
+
fi
|
|
318
|
+
local v
|
|
319
|
+
v=$(printf '%s' "$_REA_LOCAL_REVIEW_JSON_CACHE" | jq -r --arg f "$field" '.[$f] // empty' 2>/dev/null) || v=""
|
|
320
|
+
printf '%s' "$v"
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
# Read `policy.review.local_review.mode`. Prints "enforced" or "off"
|
|
324
|
+
# (defaults to empty when unset — the caller treats empty as "enforced",
|
|
325
|
+
# the protective default). Added 0.26.0; round-30 F2 routes through the
|
|
326
|
+
# canonical TS YAML parser so inline AND block forms both work.
|
|
327
|
+
policy_get_local_review_mode() {
|
|
328
|
+
_rea_local_review_get "mode"
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
# Read `policy.review.local_review.refuse_at`. Prints "push" / "commit"
|
|
332
|
+
# / "both" or empty when unset (default "push"). Added 0.26.0.
|
|
333
|
+
policy_get_local_review_refuse_at() {
|
|
334
|
+
_rea_local_review_get "refuse_at"
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
# Read `policy.review.local_review.bypass_env_var`. Prints the configured
|
|
338
|
+
# env-var name or empty when unset (default REA_SKIP_LOCAL_REVIEW).
|
|
339
|
+
# Added 0.26.0.
|
|
340
|
+
policy_get_local_review_bypass_env_var() {
|
|
341
|
+
_rea_local_review_get "bypass_env_var"
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
# Internal: pure-awk nested-scalar reader. Block-form only — used as the
|
|
345
|
+
# fallback when no rea CLI is reachable. Same body as the historical
|
|
346
|
+
# `policy_nested_scalar` awk parser, lifted into a private helper so
|
|
347
|
+
# the public function can route through `rea hook policy-get` first.
|
|
348
|
+
_rea_awk_nested_scalar() {
|
|
349
|
+
local parent="$1"
|
|
350
|
+
local child="$2"
|
|
351
|
+
local grandchild="$3"
|
|
352
|
+
local policy
|
|
353
|
+
policy=$(policy_path)
|
|
354
|
+
[[ -z "$policy" ]] && return 0
|
|
355
|
+
awk -v parent="$parent" -v child="$child" -v grandchild="$grandchild" '
|
|
356
|
+
function indent_of(line, n, c) {
|
|
357
|
+
n = 0
|
|
358
|
+
while (n < length(line)) {
|
|
359
|
+
c = substr(line, n + 1, 1)
|
|
360
|
+
if (c == " " || c == "\t") n++
|
|
361
|
+
else break
|
|
362
|
+
}
|
|
363
|
+
return n
|
|
364
|
+
}
|
|
365
|
+
BEGIN { in_parent = 0; parent_indent = -1; in_child = 0; child_indent = -1 }
|
|
366
|
+
{
|
|
367
|
+
ind = indent_of($0)
|
|
368
|
+
stripped = $0
|
|
369
|
+
sub(/^[[:space:]]+/, "", stripped)
|
|
370
|
+
if (!in_parent && stripped ~ ("^" parent ":[[:space:]]*$") && ind == 0) {
|
|
371
|
+
in_parent = 1
|
|
372
|
+
parent_indent = 0
|
|
373
|
+
next
|
|
374
|
+
}
|
|
375
|
+
if (in_parent && ind <= parent_indent && !match(stripped, "^$")) {
|
|
376
|
+
in_parent = 0
|
|
377
|
+
in_child = 0
|
|
378
|
+
}
|
|
379
|
+
if (in_parent && !in_child && stripped ~ ("^" child ":[[:space:]]*$") && ind > parent_indent) {
|
|
380
|
+
in_child = 1
|
|
381
|
+
child_indent = ind
|
|
382
|
+
next
|
|
383
|
+
}
|
|
384
|
+
if (in_child && ind <= child_indent && !match(stripped, "^$")) {
|
|
385
|
+
in_child = 0
|
|
386
|
+
}
|
|
387
|
+
if (in_child && match(stripped, ("^" grandchild ":[[:space:]]+"))) {
|
|
388
|
+
val = stripped
|
|
389
|
+
sub(("^" grandchild ":[[:space:]]+"), "", val)
|
|
390
|
+
sub(/[[:space:]]+#.*$/, "", val)
|
|
391
|
+
gsub(/^["'\'']|["'\'']$/, "", val)
|
|
392
|
+
printf "%s", val
|
|
393
|
+
exit
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
' "$policy"
|
|
397
|
+
}
|
|
398
|
+
|
|
144
399
|
# Emit each entry of an inline-array body (everything between `[` and
|
|
145
400
|
# `]`, possibly across newlines if the caller concatenated lines with
|
|
146
401
|
# spaces). Strips outer brackets, splits on `,`, trims whitespace and
|