@bookedsolid/rea 0.10.3 → 0.12.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.
Files changed (77) hide show
  1. package/.husky/pre-push +48 -162
  2. package/README.md +834 -552
  3. package/agents/codex-adversarial.md +5 -3
  4. package/commands/codex-review.md +3 -5
  5. package/dist/audit/append.d.ts +7 -32
  6. package/dist/audit/append.js +7 -35
  7. package/dist/cli/audit.d.ts +0 -31
  8. package/dist/cli/audit.js +5 -74
  9. package/dist/cli/doctor.d.ts +12 -0
  10. package/dist/cli/doctor.js +96 -17
  11. package/dist/cli/hook.d.ts +55 -0
  12. package/dist/cli/hook.js +138 -0
  13. package/dist/cli/index.js +5 -80
  14. package/dist/cli/init.js +1 -1
  15. package/dist/cli/install/gitignore.d.ts +2 -2
  16. package/dist/cli/install/gitignore.js +3 -3
  17. package/dist/cli/install/pre-push.d.ts +158 -272
  18. package/dist/cli/install/pre-push.js +491 -2633
  19. package/dist/cli/install/settings-merge.d.ts +17 -0
  20. package/dist/cli/install/settings-merge.js +48 -1
  21. package/dist/cli/upgrade.js +131 -3
  22. package/dist/config/tier-map.js +18 -25
  23. package/dist/hooks/push-gate/base.d.ts +104 -0
  24. package/dist/hooks/push-gate/base.js +198 -0
  25. package/dist/hooks/push-gate/codex-runner.d.ts +126 -0
  26. package/dist/hooks/push-gate/codex-runner.js +223 -0
  27. package/dist/hooks/push-gate/findings.d.ts +68 -0
  28. package/dist/hooks/push-gate/findings.js +142 -0
  29. package/dist/hooks/push-gate/halt.d.ts +28 -0
  30. package/dist/hooks/push-gate/halt.js +49 -0
  31. package/dist/hooks/push-gate/index.d.ts +98 -0
  32. package/dist/hooks/push-gate/index.js +416 -0
  33. package/dist/hooks/push-gate/policy.d.ts +55 -0
  34. package/dist/hooks/push-gate/policy.js +64 -0
  35. package/dist/hooks/push-gate/report.d.ts +89 -0
  36. package/dist/hooks/push-gate/report.js +140 -0
  37. package/dist/policy/loader.d.ts +15 -10
  38. package/dist/policy/loader.js +8 -6
  39. package/dist/policy/types.d.ts +73 -22
  40. package/package.json +1 -1
  41. package/scripts/tarball-smoke.sh +7 -2
  42. package/dist/cache/review-cache.d.ts +0 -115
  43. package/dist/cache/review-cache.js +0 -200
  44. package/dist/cli/cache.d.ts +0 -84
  45. package/dist/cli/cache.js +0 -150
  46. package/dist/hooks/review-gate/args.d.ts +0 -126
  47. package/dist/hooks/review-gate/args.js +0 -315
  48. package/dist/hooks/review-gate/audit.d.ts +0 -131
  49. package/dist/hooks/review-gate/audit.js +0 -181
  50. package/dist/hooks/review-gate/banner.d.ts +0 -97
  51. package/dist/hooks/review-gate/banner.js +0 -172
  52. package/dist/hooks/review-gate/base-resolve.d.ts +0 -155
  53. package/dist/hooks/review-gate/base-resolve.js +0 -247
  54. package/dist/hooks/review-gate/cache-key.d.ts +0 -55
  55. package/dist/hooks/review-gate/cache-key.js +0 -41
  56. package/dist/hooks/review-gate/cache.d.ts +0 -108
  57. package/dist/hooks/review-gate/cache.js +0 -120
  58. package/dist/hooks/review-gate/constants.d.ts +0 -26
  59. package/dist/hooks/review-gate/constants.js +0 -34
  60. package/dist/hooks/review-gate/diff.d.ts +0 -181
  61. package/dist/hooks/review-gate/diff.js +0 -232
  62. package/dist/hooks/review-gate/errors.d.ts +0 -72
  63. package/dist/hooks/review-gate/errors.js +0 -100
  64. package/dist/hooks/review-gate/hash.d.ts +0 -43
  65. package/dist/hooks/review-gate/hash.js +0 -46
  66. package/dist/hooks/review-gate/index.d.ts +0 -31
  67. package/dist/hooks/review-gate/index.js +0 -35
  68. package/dist/hooks/review-gate/metadata.d.ts +0 -98
  69. package/dist/hooks/review-gate/metadata.js +0 -158
  70. package/dist/hooks/review-gate/policy.d.ts +0 -55
  71. package/dist/hooks/review-gate/policy.js +0 -71
  72. package/dist/hooks/review-gate/protected-paths.d.ts +0 -46
  73. package/dist/hooks/review-gate/protected-paths.js +0 -76
  74. package/hooks/_lib/push-review-core.sh +0 -1250
  75. package/hooks/commit-review-gate.sh +0 -330
  76. package/hooks/push-review-gate-git.sh +0 -94
  77. package/hooks/push-review-gate.sh +0 -92
package/.husky/pre-push CHANGED
@@ -1,180 +1,66 @@
1
1
  #!/bin/sh
2
- # rea:husky-pre-push-gate v1
3
- # rea:gate-body-v1
4
- # .husky/pre-push — rea governance gate for terminal-initiated pushes.
2
+ # rea:husky-pre-push-gate v3
3
+ # rea:gate-body-v3
5
4
  #
6
- # Mirrors the logic of `.claude/hooks/push-review-gate.sh` but consumes the
7
- # git pre-push stdin contract directly (one line per refspec:
8
- # <local_ref> <local_sha> <remote_ref> <remote_sha>).
5
+ # Husky pre-push hook installed by `rea init` / `rea upgrade`. Do NOT
6
+ # edit by hand the file is refreshed on every rea upgrade.
9
7
  #
10
- # Minimum viable check NOT a full replacement for the Claude Code gate:
11
- # 1. If `.rea/HALT` exists, block.
12
- # 2. If the push touches a protected path AND policy.review.codex_required
13
- # is not explicitly false, require a `codex.review` audit entry for the
14
- # HEAD SHA (or REA_SKIP_CODEX_REVIEW env var for a one-off bypass).
15
- #
16
- # Escape hatch: REA_SKIP_CODEX_REVIEW=<reason> bypasses the protected-path
17
- # check. The skip record is appended by `push-review-gate.sh` in the Claude
18
- # Code path; for terminal pushes, export the variable AND append a skip
19
- # record manually if you want it in the audit trail.
20
- #
21
- # Subshell-safety note: earlier versions piped `echo "$INPUT" | while read`,
22
- # which ran the loop in a subshell — `exit 1` inside the loop aborted the
23
- # subshell only, and the script then ran `exit 0` and allowed the push. We
24
- # now feed the loop with a here-doc so it runs in the main shell, and we
25
- # abort immediately (`exit 1`) on the first blocking refspec. The accumulator
26
- # pattern (`block_push=1; continue`) was dropped so the text-level detector
27
- # in `src/cli/install/pre-push.ts` can verify the miss-path is truly blocking
28
- # without modeling loop-carried flags and post-loop exit blocks.
8
+ # Governance contract: HALT kill-switch check, then delegate to
9
+ # `rea hook push-gate`. See src/hooks/push-gate/index.ts.
29
10
 
30
11
  set -eu
31
12
 
32
- # git passes the remote name as $1 to pre-push. Fall back to `origin` for
33
- # direct invocation (tests, manual runs). The shared core uses the same
34
- # argv_remote convention parity required so a push to `upstream` probes
35
- # `upstream/main` rather than stale `origin/main`.
36
- REMOTE="${1:-origin}"
13
+ # REA push-gate (0.11.0+). The heavy lifting git diff resolution, Codex
14
+ # invocation, verdict inference, audit write lives in
15
+ # `src/hooks/push-gate/` and is invoked via `rea hook push-gate`.
16
+ # This stub only short-circuits on the kill-switch and resolves the rea
17
+ # binary (in priority: project node_modules/.bin/rea → dogfood dist →
18
+ # PATH → npx).
19
+ #
20
+ # The 0.10.x hooks assumed rea was on PATH. Consumers who bootstrap via
21
+ # `npx @bookedsolid/rea init` have no persistent global rea install, so
22
+ # the bare `exec rea` pattern fails with "rea: not found" on push. We
23
+ # resolve against the project-local node_modules/.bin first, then PATH,
24
+ # then fall back to npx so the gate runs in every documented setup.
37
25
 
38
26
  REA_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd)
39
-
40
- # Well-known empty-tree SHA: `git hash-object -t tree /dev/null`. Every git
41
- # installation carries this object implicitly — using it as a merge-base
42
- # baseline for initial pushes lets `git diff $EMPTY_TREE $local_sha` emit
43
- # the complete change set against a truly-empty tree. The protected-path
44
- # check then sees every file in the initial push, so a first push of
45
- # protected-path changes to a fresh remote is still gated.
46
- EMPTY_TREE='4b825dc642cb6eb9a060e54bf8d69288fbee4904'
47
-
48
27
  if [ -f "${REA_ROOT}/.rea/HALT" ]; then
49
- # POSIX `head` does not specify `-c`; use awk for the first line. HALT is
50
- # a short reason string, so the first line is enough for display.
51
28
  reason=$(awk 'NR==1 { print; exit }' "${REA_ROOT}/.rea/HALT" 2>/dev/null || printf 'unknown')
52
29
  [ -z "${reason:-}" ] && reason='unknown'
53
30
  printf 'REA HALT: %s\nAll push operations suspended. Run: rea unfreeze\n' "$reason" >&2
54
31
  exit 1
55
32
  fi
56
33
 
57
- # Read refspec lines from stdin. Each line: <local_ref> <local_sha> <remote_ref> <remote_sha>
58
- INPUT=$(cat)
59
- [ -z "$INPUT" ] && exit 0
60
-
61
- # Anchor every alternative so a legitimate file like `docs/hooks-guide.md` or
62
- # `src/thirdparty/src/policy/loader.c` is not mistaken for a protected path.
63
- # `^\.claude/hooks/` is included so someone editing the consumer install
64
- # (which ships alongside rea itself) cannot sneak past the gate.
65
- PROTECTED_RE='^src/gateway/middleware/|^hooks/|^\.claude/hooks/|^src/policy/|^\.github/workflows/'
66
- AUDIT_LOG="${REA_ROOT}/.rea/audit.jsonl"
67
-
68
- # G11.4 — honor review.codex_required. When explicitly false, skip the
69
- # protected-path Codex audit requirement entirely (first-class no-Codex
70
- # mode). Mirrors the logic in `.claude/hooks/push-review-gate.sh`.
34
+ # Dispatch the rea CLI as a positional-args list rather than a string
35
+ # (the 0.11.x `exec $REA_BIN ...` pattern broke when REA_ROOT contained
36
+ # whitespace because the unquoted $REA_BIN expansion underwent word
37
+ # splitting; `/Users/jane/My Projects/repo` produced four argv tokens
38
+ # instead of two). The `set --` form below preserves spaces verbatim.
71
39
  #
72
- # Fail-closed: if the helper is missing or errors, treat as true. A missing
73
- # helper means rea is unbuilt the operator can run `pnpm build` or set
74
- # REA_SKIP_CODEX_REVIEW for a one-off bypass.
75
- CODEX_REQUIRED=true
76
- READ_FIELD_JS="${REA_ROOT}/dist/scripts/read-policy-field.js"
77
- if [ -f "$READ_FIELD_JS" ]; then
78
- field_value=$(REA_ROOT="$REA_ROOT" node "$READ_FIELD_JS" review.codex_required 2>/dev/null || printf '')
79
- if [ "$field_value" = "false" ]; then
80
- CODEX_REQUIRED=false
81
- fi
82
- fi
83
-
84
- # Here-doc feeds the loop without creating a subshell, so an `exit 1`
85
- # inside the loop terminates the hook and blocks the push. A pipeline
86
- # would run the loop in a subshell and `exit 1` inside it would only
87
- # abort that subshell — NOT the push — which was a real governance
88
- # defect in the pre-review version of this file.
89
- while IFS=' ' read -r local_ref local_sha remote_ref remote_sha; do
90
- [ -z "${local_sha:-}" ] && continue
91
- # Branch deletion: local_sha is 40 zeros. Skip protected-path check.
92
- case "$local_sha" in
93
- 0000000000000000000000000000000000000000) continue ;;
94
- esac
40
+ # The pre-push stdin carries one line per refspec; `exec` inherits stdin
41
+ # unchanged. $@ on entry carries git's <remote-name> <remote-url>; we
42
+ # preserve those by appending "$@" inside each `set --` arm.
95
43
 
96
- # Determine merge base. If remote is new (remote_sha is zeros), diff against
97
- # the default branch; else against remote_sha.
98
- #
99
- # Anchor on a REMOTE-TRACKING ref (refs/remotes/<remote>/<name>), NOT a bare
100
- # branch name. A bare `main` resolves to refs/heads/main, which the pusher
101
- # controls locally a local main fast-forwarded to the feature tip would
102
- # give merge-base main <local_sha> == local_sha and silently collapse the
103
- # diff to empty. Remote-tracking refs are server-authoritative from the
104
- # last fetch and cannot be tampered with locally.
105
- #
106
- # Fallback order when $REMOTE/HEAD is not set (common on shallow or mirror
107
- # clones): probe $REMOTE/main then $REMOTE/master via rev-parse. If neither
108
- # exists initial push to a fresh remote with no tracking refs yet — use
109
- # the well-known EMPTY_TREE as the baseline so the diff covers the FULL
110
- # change set. This keeps the protected-path check honest on first push
111
- # (prior versions of this patch `continue`d here, which was a fail-open
112
- # flagged as HIGH by adversarial review).
113
- if [ "$remote_sha" = "0000000000000000000000000000000000000000" ]; then
114
- default_ref=$(git symbolic-ref "refs/remotes/${REMOTE}/HEAD" 2>/dev/null || printf '')
115
- if [ -z "${default_ref:-}" ]; then
116
- if git rev-parse --verify --quiet "refs/remotes/${REMOTE}/main" >/dev/null 2>&1; then
117
- default_ref="refs/remotes/${REMOTE}/main"
118
- elif git rev-parse --verify --quiet "refs/remotes/${REMOTE}/master" >/dev/null 2>&1; then
119
- default_ref="refs/remotes/${REMOTE}/master"
120
- else
121
- default_ref=""
122
- fi
123
- fi
124
- if [ -n "${default_ref:-}" ]; then
125
- base=$(git merge-base "$default_ref" "$local_sha" 2>/dev/null || printf '')
126
- else
127
- # Bootstrap: no remote-tracking ref exists at all. Use the empty-tree
128
- # baseline so the diff covers every file in the push. git diff accepts
129
- # a tree SHA as the left-hand side.
130
- base="$EMPTY_TREE"
131
- fi
132
- else
133
- base=$(git merge-base "$remote_sha" "$local_sha" 2>/dev/null || printf '')
134
- fi
135
- # Fail CLOSED on empty merge-base when a remote ref DID resolve. The
136
- # 0.4.0..0.6.2 behavior here was to `continue` — a silent bypass. A push
137
- # whose history is unrelated to origin (or any transient git failure at
138
- # merge-base resolution) would pass through without the protected-path
139
- # check ever running. Refuse instead and force the operator to resolve it.
140
- if [ -z "${base:-}" ]; then
141
- printf 'PUSH BLOCKED: could not resolve merge-base between %s and %s (local_ref=%s remote_ref=%s).\n' \
142
- "${remote_sha:-<new>}" "${local_sha:-<missing>}" "${local_ref:-<unknown>}" "${remote_ref:-<unknown>}" >&2
143
- printf ' Run `git fetch %s` and retry. If the history is genuinely unrelated\n' "$REMOTE" >&2
144
- printf ' to %s (e.g. grafted branch), resolve manually before pushing.\n' "$REMOTE" >&2
145
- exit 1
146
- fi
147
-
148
- # Check if the diff touches protected paths.
149
- if git diff --name-only "$base" "$local_sha" 2>/dev/null | grep -qE "$PROTECTED_RE"; then
150
- if [ "$CODEX_REQUIRED" = "false" ]; then
151
- # Policy opts out of the Codex gate. The downstream `.claude/hooks/`
152
- # path already records telemetry; terminal pushes skip silently.
153
- continue
154
- fi
155
- if [ -n "${REA_SKIP_CODEX_REVIEW:-}" ]; then
156
- printf 'rea: REA_SKIP_CODEX_REVIEW set (%s) — skipping Codex review requirement for %s\n' \
157
- "$REA_SKIP_CODEX_REVIEW" "$local_sha" >&2
158
- continue
159
- fi
160
- if [ ! -f "$AUDIT_LOG" ]; then
161
- printf 'PUSH BLOCKED: protected paths changed but no audit log found at %s\n' "$AUDIT_LOG" >&2
162
- printf ' Run /codex-review on HEAD %s before pushing.\n' "$local_sha" >&2
163
- exit 1
164
- fi
165
- # Require both (a) a `codex.review` tool_name and (b) the exact head_sha
166
- # on the same JSONL line. The `codex.review` pattern ends with a closing
167
- # quote, so `codex.review.skipped` never satisfies the gate. The first
168
- # refspec that fails this check aborts the hook — no accumulator needed.
169
- if ! grep -E '"tool_name":"codex\.review"' "$AUDIT_LOG" 2>/dev/null | \
170
- grep -qF "\"head_sha\":\"$local_sha\""; then
171
- printf 'PUSH BLOCKED: protected paths changed — /codex-review required for HEAD %s\n' "$local_sha" >&2
172
- printf ' Run /codex-review, or set REA_SKIP_CODEX_REVIEW=<reason> to bypass.\n' >&2
173
- exit 1
174
- fi
175
- fi
176
- done <<HOOK_INPUT_EOF
177
- $INPUT
178
- HOOK_INPUT_EOF
44
+ if [ -x "${REA_ROOT}/node_modules/.bin/rea" ]; then
45
+ set -- "${REA_ROOT}/node_modules/.bin/rea" hook push-gate "$@"
46
+ elif [ -f "${REA_ROOT}/dist/cli/index.js" ] && [ -f "${REA_ROOT}/package.json" ] && grep -q '"name": *"@bookedsolid/rea"' "${REA_ROOT}/package.json" 2>/dev/null; then
47
+ # rea's own repo (dogfood) the package is not installed under
48
+ # node_modules here because we ARE the package. The built CLI
49
+ # entry point lives at dist/cli/index.js; node runs it directly.
50
+ # Gate this branch on `package.json` declaring `@bookedsolid/rea` so a
51
+ # consumer repo that happens to ship its own `dist/cli/index.js` does
52
+ # not get this hook executing the consumer's unrelated build.
53
+ set -- node "${REA_ROOT}/dist/cli/index.js" hook push-gate "$@"
54
+ elif command -v rea >/dev/null 2>&1; then
55
+ set -- rea hook push-gate "$@"
56
+ elif command -v npx >/dev/null 2>&1; then
57
+ # Last resort: npx will resolve the package from npm or the cache.
58
+ # Pass `--no-install` so a rare cache-cold machine surfaces a clear
59
+ # error instead of silently downloading at push time.
60
+ set -- npx --no-install @bookedsolid/rea hook push-gate "$@"
61
+ else
62
+ printf 'rea: cannot locate the rea CLI. Install locally (`pnpm add -D @bookedsolid/rea`) or globally (`npm i -g @bookedsolid/rea`).\n' >&2
63
+ exit 2
64
+ fi
179
65
 
180
- exit 0
66
+ exec "$@"