@bookedsolid/rea 0.11.0 → 0.13.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/commit-msg +32 -0
- package/.husky/pre-push +88 -13
- package/README.md +914 -550
- package/dist/cli/doctor.d.ts +12 -0
- package/dist/cli/doctor.js +152 -1
- package/dist/cli/hook.d.ts +7 -0
- package/dist/cli/hook.js +12 -1
- package/dist/cli/install/commit-msg.d.ts +34 -0
- package/dist/cli/install/commit-msg.js +60 -0
- package/dist/cli/install/pre-push.d.ts +32 -10
- package/dist/cli/install/pre-push.js +106 -27
- package/dist/hooks/push-gate/base.d.ts +48 -1
- package/dist/hooks/push-gate/base.js +121 -0
- package/dist/hooks/push-gate/codex-runner.d.ts +8 -0
- package/dist/hooks/push-gate/codex-runner.js +13 -0
- package/dist/hooks/push-gate/index.d.ts +8 -0
- package/dist/hooks/push-gate/index.js +162 -22
- package/dist/hooks/push-gate/policy.d.ts +39 -4
- package/dist/hooks/push-gate/policy.js +29 -4
- package/dist/policy/loader.d.ts +19 -0
- package/dist/policy/loader.js +11 -0
- package/dist/policy/types.d.ts +72 -2
- package/package.json +1 -1
- package/scripts/tarball-smoke.sh +7 -2
package/.husky/commit-msg
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/bin/sh
|
|
2
|
+
# rea:commit-msg v1
|
|
2
3
|
# .husky/commit-msg — optionally BLOCKS commits that contain AI attribution
|
|
3
4
|
#
|
|
4
5
|
# OPT-IN: Only enforces when .rea/policy.yaml contains:
|
|
@@ -40,13 +41,34 @@ fi
|
|
|
40
41
|
# Look for block_ai_attribution: true in .rea/policy.yaml
|
|
41
42
|
# If not found or not true, exit 0 (normal commit behavior)
|
|
42
43
|
|
|
44
|
+
# Helper: chain extension fragments under .husky/commit-msg.d/ in lex
|
|
45
|
+
# order. Defined once and called from every exit path so consumers get
|
|
46
|
+
# consistent behavior regardless of whether attribution blocking ran.
|
|
47
|
+
chain_commit_msg_fragments() {
|
|
48
|
+
REA_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd)
|
|
49
|
+
ext_dir="${REA_ROOT}/.husky/commit-msg.d"
|
|
50
|
+
if [ -d "$ext_dir" ]; then
|
|
51
|
+
for frag in "$ext_dir"/*; do
|
|
52
|
+
[ -e "$frag" ] || continue
|
|
53
|
+
[ -f "$frag" ] || continue
|
|
54
|
+
[ -x "$frag" ] || continue
|
|
55
|
+
"$frag" "$COMMIT_MSG_FILE"
|
|
56
|
+
done
|
|
57
|
+
fi
|
|
58
|
+
}
|
|
59
|
+
|
|
43
60
|
POLICY_FILE=".rea/policy.yaml"
|
|
44
61
|
if [ ! -f "$POLICY_FILE" ]; then
|
|
62
|
+
chain_commit_msg_fragments
|
|
45
63
|
exit 0
|
|
46
64
|
fi
|
|
47
65
|
|
|
48
66
|
# Simple grep — no YAML parser dependency needed for a boolean flag
|
|
49
67
|
if ! grep -qE '^block_ai_attribution:[[:space:]]*true' "$POLICY_FILE" 2>/dev/null; then
|
|
68
|
+
# Attribution blocking is disabled — still chain extension fragments
|
|
69
|
+
# (consumers may want commitlint/conventional-commits checks regardless
|
|
70
|
+
# of rea's attribution stance).
|
|
71
|
+
chain_commit_msg_fragments
|
|
50
72
|
exit 0
|
|
51
73
|
fi
|
|
52
74
|
|
|
@@ -127,4 +149,14 @@ fi
|
|
|
127
149
|
# Normalize trailing newlines (cosmetic, non-fatal)
|
|
128
150
|
perl -i -0777 -pe 's/\n+$/\n/' "$COMMIT_MSG_FILE" 2>/dev/null || true
|
|
129
151
|
|
|
152
|
+
# ── Extension-hook chaining ────────────────────────────────────────────────────
|
|
153
|
+
# Source every executable file under `.husky/commit-msg.d/` in lexical order.
|
|
154
|
+
# Missing directory = no-op (backward compatible). Each fragment receives the
|
|
155
|
+
# commit-msg file path as $1. Non-zero exit fails the commit (set -e above).
|
|
156
|
+
#
|
|
157
|
+
# Fragments run AFTER rea's attribution check so HALT/governance enforcement
|
|
158
|
+
# happens first; consumers can layer on commitlint, conventional-commits
|
|
159
|
+
# linters, or branch-policy checks without losing rea coverage.
|
|
160
|
+
chain_commit_msg_fragments
|
|
161
|
+
|
|
130
162
|
exit 0
|
package/.husky/pre-push
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/bin/sh
|
|
2
|
-
# rea:husky-pre-push-gate
|
|
3
|
-
# rea:gate-body-
|
|
2
|
+
# rea:husky-pre-push-gate v4
|
|
3
|
+
# rea:gate-body-v4
|
|
4
4
|
#
|
|
5
5
|
# Husky pre-push hook installed by `rea init` / `rea upgrade`. Do NOT
|
|
6
6
|
# edit by hand — the file is refreshed on every rea upgrade.
|
|
@@ -10,26 +10,101 @@
|
|
|
10
10
|
|
|
11
11
|
set -eu
|
|
12
12
|
|
|
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.
|
|
25
|
+
|
|
13
26
|
REA_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd)
|
|
14
27
|
if [ -f "${REA_ROOT}/.rea/HALT" ]; then
|
|
15
|
-
reason=$(awk
|
|
16
|
-
[ -z "${reason:-}" ] && reason=
|
|
17
|
-
printf
|
|
28
|
+
reason=$(awk 'NR==1 { print; exit }' "${REA_ROOT}/.rea/HALT" 2>/dev/null || printf 'unknown')
|
|
29
|
+
[ -z "${reason:-}" ] && reason='unknown'
|
|
30
|
+
printf 'REA HALT: %s\nAll push operations suspended. Run: rea unfreeze\n' "$reason" >&2
|
|
18
31
|
exit 1
|
|
19
32
|
fi
|
|
20
33
|
|
|
21
|
-
|
|
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.
|
|
39
|
+
#
|
|
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.
|
|
43
|
+
|
|
22
44
|
if [ -x "${REA_ROOT}/node_modules/.bin/rea" ]; then
|
|
23
|
-
|
|
24
|
-
elif [ -f "${REA_ROOT}/dist/cli/index.js" ]; then
|
|
25
|
-
|
|
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 "$@"
|
|
26
54
|
elif command -v rea >/dev/null 2>&1; then
|
|
27
|
-
|
|
55
|
+
set -- rea hook push-gate "$@"
|
|
28
56
|
elif command -v npx >/dev/null 2>&1; then
|
|
29
|
-
|
|
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 "$@"
|
|
30
61
|
else
|
|
31
|
-
printf
|
|
62
|
+
printf 'rea: cannot locate the rea CLI. Install locally (`pnpm add -D @bookedsolid/rea`) or globally (`npm i -g @bookedsolid/rea`).\n' >&2
|
|
32
63
|
exit 2
|
|
33
64
|
fi
|
|
34
65
|
|
|
35
|
-
|
|
66
|
+
# Run the rea push-gate FIRST. We capture its exit and explicitly propagate
|
|
67
|
+
# instead of `exec`-ing — extension fragments must only run after rea's own
|
|
68
|
+
# governance work succeeds. The fragments are user code; surfacing them
|
|
69
|
+
# AFTER rea's body (HALT check, Codex review, audit write) preserves the
|
|
70
|
+
# governance contract while letting consumers chain their own checks (e.g.
|
|
71
|
+
# commitlint, branch-policy linters) without losing rea coverage.
|
|
72
|
+
"$@"
|
|
73
|
+
rea_status=$?
|
|
74
|
+
if [ "$rea_status" -ne 0 ]; then
|
|
75
|
+
exit "$rea_status"
|
|
76
|
+
fi
|
|
77
|
+
|
|
78
|
+
# Extension-hook chaining: source every executable file under
|
|
79
|
+
# `.husky/pre-push.d/` in lexical order. Missing directory = no-op
|
|
80
|
+
# (backward compatible). Each fragment receives the original positional
|
|
81
|
+
# args from git's `<remote-name> <remote-url>` invocation. Non-zero exit
|
|
82
|
+
# from any fragment fails the push; this matches husky's normal hook
|
|
83
|
+
# chaining semantics.
|
|
84
|
+
#
|
|
85
|
+
# The git pre-push contract delivers refspecs on stdin. Once rea's body has
|
|
86
|
+
# consumed it (`exec rea hook push-gate "$@"` reads stdin during `runPushGate`),
|
|
87
|
+
# subsequent fragments cannot replay it — that's by design: agents that need
|
|
88
|
+
# refspec data should run before rea, not after. Fragments that need ambient
|
|
89
|
+
# repo state can call `git rev-parse` themselves.
|
|
90
|
+
ext_dir="${REA_ROOT}/.husky/pre-push.d"
|
|
91
|
+
if [ -d "$ext_dir" ]; then
|
|
92
|
+
# Collect fragments deterministically. POSIX sort orders the same way on
|
|
93
|
+
# macOS (BSD sort) and Linux (GNU sort) for ASCII filenames; consumers who
|
|
94
|
+
# name their fragments `10-foo` / `20-bar` get predictable ordering.
|
|
95
|
+
for frag in "$ext_dir"/*; do
|
|
96
|
+
# Glob expands to itself when no matches — guard with -e.
|
|
97
|
+
[ -e "$frag" ] || continue
|
|
98
|
+
# Skip non-files (directories, sockets) and non-executables. The
|
|
99
|
+
# executable bit is the consumer's opt-in: dropping a non-executable
|
|
100
|
+
# README into the dir does not become a hook.
|
|
101
|
+
[ -f "$frag" ] || continue
|
|
102
|
+
[ -x "$frag" ] || continue
|
|
103
|
+
# Each fragment runs in its own subprocess with the original git args.
|
|
104
|
+
# Failures propagate via `set -eu` above (loop body inherits, so any
|
|
105
|
+
# non-zero exit blocks the push immediately).
|
|
106
|
+
"$frag" "$@"
|
|
107
|
+
done
|
|
108
|
+
fi
|
|
109
|
+
|
|
110
|
+
exit 0
|