@bookedsolid/rea 0.1.0 → 0.2.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/.husky/commit-msg +130 -0
- package/.husky/pre-push +128 -0
- package/README.md +5 -5
- package/agents/codex-adversarial.md +23 -8
- package/commands/codex-review.md +2 -2
- package/dist/audit/append.d.ts +62 -0
- package/dist/audit/append.js +189 -0
- package/dist/audit/codex-event.d.ts +28 -0
- package/dist/audit/codex-event.js +15 -0
- package/dist/cli/doctor.d.ts +60 -1
- package/dist/cli/doctor.js +459 -20
- package/dist/cli/index.js +35 -5
- package/dist/cli/init.d.ts +13 -0
- package/dist/cli/init.js +278 -67
- package/dist/cli/install/canonical.d.ts +43 -0
- package/dist/cli/install/canonical.js +101 -0
- package/dist/cli/install/claude-md.d.ts +48 -0
- package/dist/cli/install/claude-md.js +93 -0
- package/dist/cli/install/commit-msg.d.ts +30 -0
- package/dist/cli/install/commit-msg.js +102 -0
- package/dist/cli/install/copy.d.ts +169 -0
- package/dist/cli/install/copy.js +455 -0
- package/dist/cli/install/fs-safe.d.ts +91 -0
- package/dist/cli/install/fs-safe.js +347 -0
- package/dist/cli/install/manifest-io.d.ts +12 -0
- package/dist/cli/install/manifest-io.js +44 -0
- package/dist/cli/install/manifest-schema.d.ts +83 -0
- package/dist/cli/install/manifest-schema.js +80 -0
- package/dist/cli/install/reagent.d.ts +59 -0
- package/dist/cli/install/reagent.js +160 -0
- package/dist/cli/install/settings-merge.d.ts +91 -0
- package/dist/cli/install/settings-merge.js +239 -0
- package/dist/cli/install/sha.d.ts +9 -0
- package/dist/cli/install/sha.js +21 -0
- package/dist/cli/serve.d.ts +11 -0
- package/dist/cli/serve.js +72 -6
- package/dist/cli/upgrade.d.ts +67 -0
- package/dist/cli/upgrade.js +509 -0
- package/dist/gateway/downstream-pool.d.ts +39 -0
- package/dist/gateway/downstream-pool.js +93 -0
- package/dist/gateway/downstream.d.ts +80 -0
- package/dist/gateway/downstream.js +196 -0
- package/dist/gateway/middleware/audit-types.d.ts +10 -0
- package/dist/gateway/middleware/audit.js +14 -0
- package/dist/gateway/middleware/injection.d.ts +59 -2
- package/dist/gateway/middleware/injection.js +91 -14
- package/dist/gateway/middleware/kill-switch.d.ts +20 -5
- package/dist/gateway/middleware/kill-switch.js +57 -35
- package/dist/gateway/middleware/redact.d.ts +83 -6
- package/dist/gateway/middleware/redact.js +133 -46
- package/dist/gateway/observability/codex-probe.d.ts +110 -0
- package/dist/gateway/observability/codex-probe.js +234 -0
- package/dist/gateway/observability/codex-telemetry.d.ts +93 -0
- package/dist/gateway/observability/codex-telemetry.js +221 -0
- package/dist/gateway/redact-safe/match-timeout.d.ts +83 -0
- package/dist/gateway/redact-safe/match-timeout.js +179 -0
- package/dist/gateway/reviewers/claude-self.d.ts +99 -0
- package/dist/gateway/reviewers/claude-self.js +316 -0
- package/dist/gateway/reviewers/codex.d.ts +64 -0
- package/dist/gateway/reviewers/codex.js +80 -0
- package/dist/gateway/reviewers/select.d.ts +64 -0
- package/dist/gateway/reviewers/select.js +102 -0
- package/dist/gateway/reviewers/types.d.ts +85 -0
- package/dist/gateway/reviewers/types.js +14 -0
- package/dist/gateway/server.d.ts +51 -0
- package/dist/gateway/server.js +258 -0
- package/dist/gateway/session.d.ts +9 -0
- package/dist/gateway/session.js +17 -0
- package/dist/policy/loader.d.ts +59 -0
- package/dist/policy/loader.js +65 -0
- package/dist/policy/profiles.d.ts +80 -0
- package/dist/policy/profiles.js +94 -0
- package/dist/policy/types.d.ts +38 -0
- package/dist/registry/loader.d.ts +98 -0
- package/dist/registry/loader.js +153 -0
- package/dist/registry/types.d.ts +44 -0
- package/dist/registry/types.js +6 -0
- package/dist/scripts/read-policy-field.d.ts +36 -0
- package/dist/scripts/read-policy-field.js +96 -0
- package/hooks/push-review-gate.sh +627 -17
- package/package.json +13 -2
- package/profiles/bst-internal-no-codex.yaml +40 -0
- package/profiles/bst-internal.yaml +23 -0
- package/profiles/client-engagement.yaml +23 -0
- package/profiles/lit-wc.yaml +17 -0
- package/profiles/minimal.yaml +11 -0
- package/profiles/open-source-no-codex.yaml +33 -0
- package/profiles/open-source.yaml +18 -0
- package/scripts/lint-safe-regex.mjs +78 -0
- package/scripts/postinstall.mjs +131 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# .husky/commit-msg — optionally BLOCKS commits that contain AI attribution
|
|
3
|
+
#
|
|
4
|
+
# OPT-IN: Only enforces when .rea/policy.yaml contains:
|
|
5
|
+
# block_ai_attribution: true
|
|
6
|
+
#
|
|
7
|
+
# When disabled (default), this hook does nothing — commits work normally.
|
|
8
|
+
# When enabled, rejects (exit 1) commit messages with structural AI attribution
|
|
9
|
+
# markers. This teaches agents to stop including attribution by giving clear
|
|
10
|
+
# feedback on what went wrong.
|
|
11
|
+
#
|
|
12
|
+
# IMPORTANT: This does NOT block casual mentions of AI tools.
|
|
13
|
+
# "Fix Claude API integration" or "Update OpenAI SDK" are fine.
|
|
14
|
+
# What gets blocked are STRUCTURAL ATTRIBUTION MARKERS:
|
|
15
|
+
#
|
|
16
|
+
# Co-Authored-By with noreply@ emails (dead giveaway)
|
|
17
|
+
# Co-Authored-By with known AI names (Claude, Copilot, GPT, Gemini, etc.)
|
|
18
|
+
# "Generated with/by [Tool]" footer lines
|
|
19
|
+
# Markdown-linked tool names: [Claude Code](...)
|
|
20
|
+
# Emoji-prefixed attribution: 🤖 Generated...
|
|
21
|
+
#
|
|
22
|
+
# SAFETY: set -e ensures any unexpected error BLOCKS the commit rather than
|
|
23
|
+
# silently passing a message with attribution intact.
|
|
24
|
+
|
|
25
|
+
set -e
|
|
26
|
+
|
|
27
|
+
COMMIT_MSG_FILE="$1"
|
|
28
|
+
|
|
29
|
+
# Validate input
|
|
30
|
+
if [ -z "$COMMIT_MSG_FILE" ]; then
|
|
31
|
+
echo "ERROR: commit-msg hook received no file path" >&2
|
|
32
|
+
exit 1
|
|
33
|
+
fi
|
|
34
|
+
if [ ! -f "$COMMIT_MSG_FILE" ]; then
|
|
35
|
+
echo "ERROR: commit message file not found: $COMMIT_MSG_FILE" >&2
|
|
36
|
+
exit 1
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
# ── Check if attribution blocking is enabled ───────────────────────────────────
|
|
40
|
+
# Look for block_ai_attribution: true in .rea/policy.yaml
|
|
41
|
+
# If not found or not true, exit 0 (normal commit behavior)
|
|
42
|
+
|
|
43
|
+
POLICY_FILE=".rea/policy.yaml"
|
|
44
|
+
if [ ! -f "$POLICY_FILE" ]; then
|
|
45
|
+
exit 0
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
# Simple grep — no YAML parser dependency needed for a boolean flag
|
|
49
|
+
if ! grep -qE '^block_ai_attribution:[[:space:]]*true' "$POLICY_FILE" 2>/dev/null; then
|
|
50
|
+
exit 0
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
# ── Attribution blocking is enabled — check patterns ───────────────────────────
|
|
54
|
+
|
|
55
|
+
BLOCKED=0
|
|
56
|
+
MATCHES=""
|
|
57
|
+
|
|
58
|
+
# Pattern 1: Co-Authored-By with noreply@ email
|
|
59
|
+
if grep -qiE 'Co-Authored-By:.*noreply@' "$COMMIT_MSG_FILE" 2>/dev/null; then
|
|
60
|
+
BLOCKED=1
|
|
61
|
+
MATCHES="${MATCHES}$(grep -niE 'Co-Authored-By:.*noreply@' "$COMMIT_MSG_FILE" 2>/dev/null)
|
|
62
|
+
"
|
|
63
|
+
fi
|
|
64
|
+
|
|
65
|
+
# Pattern 2: Co-Authored-By with known AI assistant names
|
|
66
|
+
if grep -qiE 'Co-Authored-By:.*\b(Claude|Sonnet|Opus|Haiku|Copilot|GPT|ChatGPT|Gemini|Cursor|Codeium|Tabnine|Amazon Q|CodeWhisperer|Devin|Windsurf|Cline|Aider|Anthropic|OpenAI|GitHub Copilot)\b' "$COMMIT_MSG_FILE" 2>/dev/null; then
|
|
67
|
+
BLOCKED=1
|
|
68
|
+
MATCHES="${MATCHES}$(grep -niE 'Co-Authored-By:.*\b(Claude|Sonnet|Opus|Haiku|Copilot|GPT|ChatGPT|Gemini|Cursor|Codeium|Tabnine|CodeWhisperer|Devin|Windsurf|Cline|Aider|Anthropic|OpenAI|GitHub Copilot)\b' "$COMMIT_MSG_FILE" 2>/dev/null)
|
|
69
|
+
"
|
|
70
|
+
fi
|
|
71
|
+
|
|
72
|
+
# Pattern 3: "Generated/Built/Powered with/by [AI Tool]" footer lines
|
|
73
|
+
if grep -qiE '^\s*(Generated|Created|Built|Powered|Authored|Written|Produced)\s+(with|by)\s+(Claude|Copilot|GPT|ChatGPT|Gemini|Cursor|Codeium|Tabnine|CodeWhisperer|Devin|Windsurf|Cline|Aider|AI|an? AI)\b' "$COMMIT_MSG_FILE" 2>/dev/null; then
|
|
74
|
+
BLOCKED=1
|
|
75
|
+
MATCHES="${MATCHES}$(grep -niE '^\s*(Generated|Created|Built|Powered|Authored|Written|Produced)\s+(with|by)\s+(Claude|Copilot|GPT|ChatGPT|Gemini|Cursor|Codeium|Tabnine|CodeWhisperer|Devin|Windsurf|Cline|Aider|AI|an? AI)\b' "$COMMIT_MSG_FILE" 2>/dev/null)
|
|
76
|
+
"
|
|
77
|
+
fi
|
|
78
|
+
|
|
79
|
+
# Pattern 4: Markdown-linked attribution
|
|
80
|
+
if grep -qiE '\[Claude Code\]|\[GitHub Copilot\]|\[ChatGPT\]|\[Gemini\]|\[Cursor\]' "$COMMIT_MSG_FILE" 2>/dev/null; then
|
|
81
|
+
BLOCKED=1
|
|
82
|
+
MATCHES="${MATCHES}$(grep -niE '\[Claude Code\]|\[GitHub Copilot\]|\[ChatGPT\]|\[Gemini\]|\[Cursor\]' "$COMMIT_MSG_FILE" 2>/dev/null)
|
|
83
|
+
"
|
|
84
|
+
fi
|
|
85
|
+
|
|
86
|
+
# Pattern 5: Emoji-prefixed "Generated" lines
|
|
87
|
+
if grep -qE '🤖.*[Gg]enerated' "$COMMIT_MSG_FILE" 2>/dev/null; then
|
|
88
|
+
BLOCKED=1
|
|
89
|
+
MATCHES="${MATCHES}$(grep -nE '🤖.*[Gg]enerated' "$COMMIT_MSG_FILE" 2>/dev/null)
|
|
90
|
+
"
|
|
91
|
+
fi
|
|
92
|
+
|
|
93
|
+
# ── Block or allow ─────────────────────────────────────────────────────────────
|
|
94
|
+
|
|
95
|
+
if [ "$BLOCKED" -eq 1 ]; then
|
|
96
|
+
{
|
|
97
|
+
printf '\n'
|
|
98
|
+
printf '═══════════════════════════════════════════════════════════════════\n'
|
|
99
|
+
printf ' COMMIT BLOCKED: AI attribution detected in commit message\n'
|
|
100
|
+
printf '═══════════════════════════════════════════════════════════════════\n'
|
|
101
|
+
printf '\n'
|
|
102
|
+
printf ' Your commit message contains structural AI attribution markers\n'
|
|
103
|
+
printf ' that must be removed before committing.\n'
|
|
104
|
+
printf '\n'
|
|
105
|
+
printf ' Matched line(s):\n'
|
|
106
|
+
printf '%s' "$MATCHES" | grep -v '^$' | sed 's/^/ /'
|
|
107
|
+
printf '\n'
|
|
108
|
+
printf ' What gets BLOCKED (structural attribution):\n'
|
|
109
|
+
printf ' - Co-Authored-By with AI names or noreply@ emails\n'
|
|
110
|
+
printf ' - "Generated with/by [AI Tool]" footer lines\n'
|
|
111
|
+
printf ' - Markdown-linked tool names: [Claude Code](...)\n'
|
|
112
|
+
printf ' - Emoji attribution: 🤖 Generated...\n'
|
|
113
|
+
printf '\n'
|
|
114
|
+
printf ' What is ALLOWED (legitimate references):\n'
|
|
115
|
+
printf ' - "Fix Claude API integration"\n'
|
|
116
|
+
printf ' - "Update OpenAI SDK version"\n'
|
|
117
|
+
printf ' - "Add Copilot config"\n'
|
|
118
|
+
printf '\n'
|
|
119
|
+
printf ' Remove the attribution markers and retry your commit.\n'
|
|
120
|
+
printf ' To disable: set block_ai_attribution: false in .rea/policy.yaml\n'
|
|
121
|
+
printf '═══════════════════════════════════════════════════════════════════\n'
|
|
122
|
+
printf '\n'
|
|
123
|
+
} >&2
|
|
124
|
+
exit 1
|
|
125
|
+
fi
|
|
126
|
+
|
|
127
|
+
# Normalize trailing newlines (cosmetic, non-fatal)
|
|
128
|
+
perl -i -0777 -pe 's/\n+$/\n/' "$COMMIT_MSG_FILE" 2>/dev/null || true
|
|
129
|
+
|
|
130
|
+
exit 0
|
package/.husky/pre-push
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# .husky/pre-push — rea governance gate for terminal-initiated pushes.
|
|
3
|
+
#
|
|
4
|
+
# Mirrors the logic of `.claude/hooks/push-review-gate.sh` but consumes the
|
|
5
|
+
# git pre-push stdin contract directly (one line per refspec:
|
|
6
|
+
# <local_ref> <local_sha> <remote_ref> <remote_sha>).
|
|
7
|
+
#
|
|
8
|
+
# Minimum viable check — NOT a full replacement for the Claude Code gate:
|
|
9
|
+
# 1. If `.rea/HALT` exists, block.
|
|
10
|
+
# 2. If the push touches a protected path AND policy.review.codex_required
|
|
11
|
+
# is not explicitly false, require a `codex.review` audit entry for the
|
|
12
|
+
# HEAD SHA (or REA_SKIP_CODEX_REVIEW env var for a one-off bypass).
|
|
13
|
+
#
|
|
14
|
+
# Escape hatch: REA_SKIP_CODEX_REVIEW=<reason> bypasses the protected-path
|
|
15
|
+
# check. The skip record is appended by `push-review-gate.sh` in the Claude
|
|
16
|
+
# Code path; for terminal pushes, export the variable AND append a skip
|
|
17
|
+
# record manually if you want it in the audit trail.
|
|
18
|
+
#
|
|
19
|
+
# Subshell-safety note: earlier versions piped `echo "$INPUT" | while read`,
|
|
20
|
+
# which ran the loop in a subshell — `exit 1` inside the loop aborted the
|
|
21
|
+
# subshell only, and the script then ran `exit 0` and allowed the push. We
|
|
22
|
+
# now feed the loop with a here-doc so it runs in the main shell, and we
|
|
23
|
+
# track `block_push` in the enclosing scope. Final `exit 1` is reached only
|
|
24
|
+
# if no refspec is blocked; a single blocking refspec propagates correctly.
|
|
25
|
+
|
|
26
|
+
set -eu
|
|
27
|
+
|
|
28
|
+
REA_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd)
|
|
29
|
+
|
|
30
|
+
if [ -f "${REA_ROOT}/.rea/HALT" ]; then
|
|
31
|
+
# POSIX `head` does not specify `-c`; use awk for the first line. HALT is
|
|
32
|
+
# a short reason string, so the first line is enough for display.
|
|
33
|
+
reason=$(awk 'NR==1 { print; exit }' "${REA_ROOT}/.rea/HALT" 2>/dev/null || printf 'unknown')
|
|
34
|
+
[ -z "${reason:-}" ] && reason='unknown'
|
|
35
|
+
printf 'REA HALT: %s\nAll push operations suspended. Run: rea unfreeze\n' "$reason" >&2
|
|
36
|
+
exit 1
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
# Read refspec lines from stdin. Each line: <local_ref> <local_sha> <remote_ref> <remote_sha>
|
|
40
|
+
INPUT=$(cat)
|
|
41
|
+
[ -z "$INPUT" ] && exit 0
|
|
42
|
+
|
|
43
|
+
# Anchor every alternative so a legitimate file like `docs/hooks-guide.md` or
|
|
44
|
+
# `src/thirdparty/src/policy/loader.c` is not mistaken for a protected path.
|
|
45
|
+
# `^\.claude/hooks/` is included so someone editing the consumer install
|
|
46
|
+
# (which ships alongside rea itself) cannot sneak past the gate.
|
|
47
|
+
PROTECTED_RE='^src/gateway/middleware/|^hooks/|^\.claude/hooks/|^src/policy/|^\.github/workflows/'
|
|
48
|
+
AUDIT_LOG="${REA_ROOT}/.rea/audit.jsonl"
|
|
49
|
+
|
|
50
|
+
# G11.4 — honor review.codex_required. When explicitly false, skip the
|
|
51
|
+
# protected-path Codex audit requirement entirely (first-class no-Codex
|
|
52
|
+
# mode). Mirrors the logic in `.claude/hooks/push-review-gate.sh`.
|
|
53
|
+
#
|
|
54
|
+
# Fail-closed: if the helper is missing or errors, treat as true. A missing
|
|
55
|
+
# helper means rea is unbuilt — the operator can run `pnpm build` or set
|
|
56
|
+
# REA_SKIP_CODEX_REVIEW for a one-off bypass.
|
|
57
|
+
CODEX_REQUIRED=true
|
|
58
|
+
READ_FIELD_JS="${REA_ROOT}/dist/scripts/read-policy-field.js"
|
|
59
|
+
if [ -f "$READ_FIELD_JS" ]; then
|
|
60
|
+
field_value=$(REA_ROOT="$REA_ROOT" node "$READ_FIELD_JS" review.codex_required 2>/dev/null || printf '')
|
|
61
|
+
if [ "$field_value" = "false" ]; then
|
|
62
|
+
CODEX_REQUIRED=false
|
|
63
|
+
fi
|
|
64
|
+
fi
|
|
65
|
+
|
|
66
|
+
block_push=0
|
|
67
|
+
|
|
68
|
+
# Here-doc feeds the loop without creating a subshell, so `block_push=1`
|
|
69
|
+
# assignments below persist in the enclosing scope and the final `exit`
|
|
70
|
+
# reflects them. A pipeline would run the loop in a subshell and `exit 1`
|
|
71
|
+
# inside it would only abort that subshell — NOT the push — which was a
|
|
72
|
+
# real governance defect in the pre-review version of this file.
|
|
73
|
+
while IFS=' ' read -r local_ref local_sha remote_ref remote_sha; do
|
|
74
|
+
[ -z "${local_sha:-}" ] && continue
|
|
75
|
+
# Branch deletion: local_sha is 40 zeros. Skip protected-path check.
|
|
76
|
+
case "$local_sha" in
|
|
77
|
+
0000000000000000000000000000000000000000) continue ;;
|
|
78
|
+
esac
|
|
79
|
+
|
|
80
|
+
# Determine merge base. If remote is new (remote_sha is zeros), diff against
|
|
81
|
+
# the default branch; else against remote_sha.
|
|
82
|
+
if [ "$remote_sha" = "0000000000000000000000000000000000000000" ]; then
|
|
83
|
+
default_branch=$(git symbolic-ref --short refs/remotes/origin/HEAD 2>/dev/null | sed 's|^origin/||')
|
|
84
|
+
[ -z "${default_branch:-}" ] && default_branch="main"
|
|
85
|
+
base=$(git merge-base "$default_branch" "$local_sha" 2>/dev/null || printf '')
|
|
86
|
+
else
|
|
87
|
+
base=$(git merge-base "$remote_sha" "$local_sha" 2>/dev/null || printf '')
|
|
88
|
+
fi
|
|
89
|
+
[ -z "${base:-}" ] && continue
|
|
90
|
+
|
|
91
|
+
# Check if the diff touches protected paths.
|
|
92
|
+
if git diff --name-only "$base" "$local_sha" 2>/dev/null | grep -qE "$PROTECTED_RE"; then
|
|
93
|
+
if [ "$CODEX_REQUIRED" = "false" ]; then
|
|
94
|
+
# Policy opts out of the Codex gate. The downstream `.claude/hooks/`
|
|
95
|
+
# path already records telemetry; terminal pushes skip silently.
|
|
96
|
+
continue
|
|
97
|
+
fi
|
|
98
|
+
if [ -n "${REA_SKIP_CODEX_REVIEW:-}" ]; then
|
|
99
|
+
printf 'rea: REA_SKIP_CODEX_REVIEW set (%s) — skipping Codex review requirement for %s\n' \
|
|
100
|
+
"$REA_SKIP_CODEX_REVIEW" "$local_sha" >&2
|
|
101
|
+
continue
|
|
102
|
+
fi
|
|
103
|
+
if [ ! -f "$AUDIT_LOG" ]; then
|
|
104
|
+
printf 'PUSH BLOCKED: protected paths changed but no audit log found at %s\n' "$AUDIT_LOG" >&2
|
|
105
|
+
printf ' Run /codex-review on HEAD %s before pushing.\n' "$local_sha" >&2
|
|
106
|
+
block_push=1
|
|
107
|
+
continue
|
|
108
|
+
fi
|
|
109
|
+
# Require both (a) a `codex.review` tool_name and (b) the exact head_sha
|
|
110
|
+
# on the same JSONL line. The `codex.review` pattern ends with a closing
|
|
111
|
+
# quote, so `codex.review.skipped` never satisfies the gate.
|
|
112
|
+
if ! grep -E '"tool_name":"codex\.review"' "$AUDIT_LOG" 2>/dev/null | \
|
|
113
|
+
grep -qF "\"head_sha\":\"$local_sha\""; then
|
|
114
|
+
printf 'PUSH BLOCKED: protected paths changed — /codex-review required for HEAD %s\n' "$local_sha" >&2
|
|
115
|
+
printf ' Run /codex-review, or set REA_SKIP_CODEX_REVIEW=<reason> to bypass.\n' >&2
|
|
116
|
+
block_push=1
|
|
117
|
+
continue
|
|
118
|
+
fi
|
|
119
|
+
fi
|
|
120
|
+
done <<HOOK_INPUT_EOF
|
|
121
|
+
$INPUT
|
|
122
|
+
HOOK_INPUT_EOF
|
|
123
|
+
|
|
124
|
+
if [ "$block_push" -ne 0 ]; then
|
|
125
|
+
exit 1
|
|
126
|
+
fi
|
|
127
|
+
|
|
128
|
+
exit 0
|
package/README.md
CHANGED
|
@@ -206,15 +206,15 @@ REA ships it out of the box.
|
|
|
206
206
|
| Phase | Primary model | Codex role | Governance |
|
|
207
207
|
| --- | --- | --- | --- |
|
|
208
208
|
| Plan | Claude Opus | — | Full middleware chain |
|
|
209
|
-
| Pre-implementation review | — | `/codex
|
|
209
|
+
| Pre-implementation review | — | `/codex:review` — review the PLAN before code | Audited |
|
|
210
210
|
| Build | Claude Opus | — | Full middleware chain |
|
|
211
|
-
| Adversarial review | — | `/codex
|
|
212
|
-
| Pre-merge gate | — | `/codex
|
|
211
|
+
| Adversarial review | — | `/codex:adversarial-review` on the diff (independent perspective) | Audited, redacted, kill-switched |
|
|
212
|
+
| Pre-merge gate | — | `/codex:adversarial-review` re-run; recorded in audit.jsonl | Required status check (recommended) |
|
|
213
213
|
|
|
214
214
|
Three things make this work:
|
|
215
215
|
|
|
216
216
|
1. The **`codex-adversarial` agent** in the curated roster wraps
|
|
217
|
-
`/codex
|
|
217
|
+
`/codex:adversarial-review`. The orchestrator delegates to it after
|
|
218
218
|
any non-trivial change.
|
|
219
219
|
2. The **`/codex-review` slash command** is one of the five shipped
|
|
220
220
|
commands. It produces an audit entry including the request summary,
|
|
@@ -260,7 +260,7 @@ disclosure. It is installed as part of the Bash PreToolUse set.
|
|
|
260
260
|
| --- | --- |
|
|
261
261
|
| `/rea` | Session status — autonomy level, HALT state, last audit entries, next action |
|
|
262
262
|
| `/review` | Invoke the `code-reviewer` agent on current changes |
|
|
263
|
-
| `/codex-review` | Invoke the `codex-adversarial` agent → `/codex
|
|
263
|
+
| `/codex-review` | Invoke the `codex-adversarial` agent → `/codex:adversarial-review` |
|
|
264
264
|
| `/freeze` | Prompt for a reason and write `.rea/HALT` |
|
|
265
265
|
| `/halt-check` | Verify every middleware and hook respects HALT |
|
|
266
266
|
|
|
@@ -5,7 +5,7 @@ description: Adversarial code review via the Codex plugin (GPT-5.4). Independent
|
|
|
5
5
|
|
|
6
6
|
# Codex Adversarial Reviewer
|
|
7
7
|
|
|
8
|
-
You wrap the Codex plugin (`/codex
|
|
8
|
+
You wrap the Codex plugin (`/codex:adversarial-review`) inside REA's governance envelope. Your role is to provide an **independent** adversarial perspective on code that was planned and built by another model — typically Opus. Independence is the value: the authoring model is least likely to catch the mistakes it made.
|
|
9
9
|
|
|
10
10
|
This is not a bolt-on. Adversarial review is a first-class, non-optional step in the REA engineering process. The default workflow is Plan → Build → Review, and you are the Review leg.
|
|
11
11
|
|
|
@@ -30,16 +30,31 @@ You may read additional files in the repo if needed for context, but do so read-
|
|
|
30
30
|
1. **Check HALT and policy** — read `.rea/policy.yaml`, check `.rea/HALT`. If frozen, stop immediately.
|
|
31
31
|
2. **Validate Codex availability** — if `/codex` is not installed, report and stop. Do not silently fall back to another reviewer.
|
|
32
32
|
3. **Prepare the Codex invocation** — construct the adversarial-review prompt with the diff, commit log, and any relevant context files.
|
|
33
|
-
4. **Invoke `/codex
|
|
33
|
+
4. **Invoke `/codex:adversarial-review`** — this call flows through the REA middleware chain (audit → kill-switch → tier → policy → redact → injection → execute → result-size-cap).
|
|
34
34
|
5. **Parse the Codex output** — extract structured findings.
|
|
35
35
|
6. **Classify findings** by category: security, correctness, edge cases, test gaps, API design, performance.
|
|
36
36
|
7. **Assign verdict**: `pass` (no material findings), `concerns` (findings worth addressing but not blocking), `blocking` (findings that must be fixed before merge).
|
|
37
|
-
8. **Emit audit entry** —
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
37
|
+
8. **Emit audit entry** — after producing the verdict, append a structured record to `.rea/audit.jsonl` via the public `@bookedsolid/rea/audit` helper. This is what the `push-review-gate.sh` hook greps for on protected-path diffs, so the field names must match exactly:
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
import { appendAuditRecord, CODEX_REVIEW_TOOL_NAME, CODEX_REVIEW_SERVER_NAME, Tier, InvocationStatus } from '@bookedsolid/rea/audit';
|
|
41
|
+
|
|
42
|
+
await appendAuditRecord(process.cwd(), {
|
|
43
|
+
tool_name: CODEX_REVIEW_TOOL_NAME, // "codex.review"
|
|
44
|
+
server_name: CODEX_REVIEW_SERVER_NAME, // "codex"
|
|
45
|
+
status: InvocationStatus.Allowed,
|
|
46
|
+
tier: Tier.Read,
|
|
47
|
+
metadata: {
|
|
48
|
+
head_sha: '<git rev-parse HEAD>',
|
|
49
|
+
target: '<base ref or SHA diffed against>',
|
|
50
|
+
finding_count: <total>,
|
|
51
|
+
verdict: 'pass' | 'concerns' | 'blocking' | 'error',
|
|
52
|
+
summary: '<one sentence>',
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
If the Codex plugin call itself flowed through rea middleware (the proxy case), the middleware also writes an envelope record — that is fine, the two are complementary: the agent-emitted record carries the semantic verdict, the middleware record carries the chain integrity proof for the underlying tool call.
|
|
43
58
|
|
|
44
59
|
## Finding Shape
|
|
45
60
|
|
package/commands/codex-review.md
CHANGED
|
@@ -11,7 +11,7 @@ allowed-tools:
|
|
|
11
11
|
|
|
12
12
|
# /codex-review — Adversarial Review via Codex
|
|
13
13
|
|
|
14
|
-
Invokes the Codex plugin (`/codex
|
|
14
|
+
Invokes the Codex plugin (`/codex:adversarial-review`) on the current branch's diff, captures the result, and records it to the REA audit log. Adversarial review by an independent model (GPT-5.4) is a **first-class, non-optional step** in the REA engineering process — it is the counterweight to Opus-authored code.
|
|
15
15
|
|
|
16
16
|
## Why this exists
|
|
17
17
|
|
|
@@ -52,7 +52,7 @@ Invoke the `codex-adversarial` agent with:
|
|
|
52
52
|
- The commit log summary
|
|
53
53
|
- The full diff text
|
|
54
54
|
|
|
55
|
-
The agent wraps `/codex
|
|
55
|
+
The agent wraps `/codex:adversarial-review` and returns structured findings.
|
|
56
56
|
|
|
57
57
|
## Step 3 — Record to audit log
|
|
58
58
|
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public audit-append helper — exported from `@bookedsolid/rea/audit`.
|
|
3
|
+
*
|
|
4
|
+
* This is the single hash-chain entry point for external consumers (the
|
|
5
|
+
* `codex-adversarial` agent, Helix's `helix.plan` / `helix.apply` events, and
|
|
6
|
+
* any future plugin that needs to emit structured events through rea's audit
|
|
7
|
+
* trail). Consumers own their event semantics; rea owns the contract.
|
|
8
|
+
*
|
|
9
|
+
* ## Guarantees
|
|
10
|
+
*
|
|
11
|
+
* - Reads the last JSONL line of `.rea/audit.jsonl` to seed `prev_hash`.
|
|
12
|
+
* - Computes a SHA-256 hash over the serialized record minus `hash`.
|
|
13
|
+
* - Appends a single `\n`-terminated JSON line, then fsyncs the file.
|
|
14
|
+
* - Creates `.rea/` and `audit.jsonl` on first use.
|
|
15
|
+
* - Never throws on stat/missing-file conditions; only throws on write failure
|
|
16
|
+
* (the caller decides how to react).
|
|
17
|
+
*
|
|
18
|
+
* ## Concurrency
|
|
19
|
+
*
|
|
20
|
+
* The helper serializes writes per-process via a module-scoped queue keyed by
|
|
21
|
+
* the resolved audit-file path. Cross-process concurrency on the same file is
|
|
22
|
+
* NOT handled here — writers in separate processes can interleave and break
|
|
23
|
+
* the chain. The current deployment targets (rea's own governance hooks, the
|
|
24
|
+
* Codex agent, Helix) all funnel through a single process at a time. If that
|
|
25
|
+
* changes, add an exclusive-lock file (`audit.jsonl.lock`) before lifting this
|
|
26
|
+
* restriction. Documented risk; do not silently expand the guarantee.
|
|
27
|
+
*
|
|
28
|
+
* @see {@link file://./codex-event.ts} for the canonical `codex.review` shape.
|
|
29
|
+
*/
|
|
30
|
+
import { Tier, InvocationStatus } from '../policy/types.js';
|
|
31
|
+
import type { AuditRecord } from '../gateway/middleware/audit-types.js';
|
|
32
|
+
/**
|
|
33
|
+
* Input shape for {@link appendAuditRecord}. All fields except `tool_name`
|
|
34
|
+
* and `server_name` are optional; sensible defaults are applied to keep the
|
|
35
|
+
* hash chain uniform across event types.
|
|
36
|
+
*/
|
|
37
|
+
export interface AppendAuditInput {
|
|
38
|
+
tool_name: string;
|
|
39
|
+
server_name: string;
|
|
40
|
+
status?: InvocationStatus;
|
|
41
|
+
tier?: Tier;
|
|
42
|
+
autonomy_level?: string;
|
|
43
|
+
session_id?: string;
|
|
44
|
+
duration_ms?: number;
|
|
45
|
+
error?: string;
|
|
46
|
+
redacted_fields?: string[];
|
|
47
|
+
metadata?: Record<string, unknown>;
|
|
48
|
+
/** ISO-8601 timestamp; defaults to `new Date().toISOString()` */
|
|
49
|
+
timestamp?: string;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Append a structured audit record to `${baseDir}/.rea/audit.jsonl` with a
|
|
53
|
+
* hash chained against the tail of the existing log.
|
|
54
|
+
*
|
|
55
|
+
* @param baseDir - Repo/project root (the directory that contains `.rea/`).
|
|
56
|
+
* @param input - Event data. `tool_name` and `server_name` are required.
|
|
57
|
+
* @returns The full written record, including the computed `hash`.
|
|
58
|
+
*/
|
|
59
|
+
export declare function appendAuditRecord(baseDir: string, input: AppendAuditInput): Promise<AuditRecord>;
|
|
60
|
+
export type { AuditRecord } from '../gateway/middleware/audit-types.js';
|
|
61
|
+
export { Tier, InvocationStatus } from '../policy/types.js';
|
|
62
|
+
export { CODEX_REVIEW_TOOL_NAME, CODEX_REVIEW_SERVER_NAME, type CodexVerdict, type CodexReviewMetadata, } from './codex-event.js';
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public audit-append helper — exported from `@bookedsolid/rea/audit`.
|
|
3
|
+
*
|
|
4
|
+
* This is the single hash-chain entry point for external consumers (the
|
|
5
|
+
* `codex-adversarial` agent, Helix's `helix.plan` / `helix.apply` events, and
|
|
6
|
+
* any future plugin that needs to emit structured events through rea's audit
|
|
7
|
+
* trail). Consumers own their event semantics; rea owns the contract.
|
|
8
|
+
*
|
|
9
|
+
* ## Guarantees
|
|
10
|
+
*
|
|
11
|
+
* - Reads the last JSONL line of `.rea/audit.jsonl` to seed `prev_hash`.
|
|
12
|
+
* - Computes a SHA-256 hash over the serialized record minus `hash`.
|
|
13
|
+
* - Appends a single `\n`-terminated JSON line, then fsyncs the file.
|
|
14
|
+
* - Creates `.rea/` and `audit.jsonl` on first use.
|
|
15
|
+
* - Never throws on stat/missing-file conditions; only throws on write failure
|
|
16
|
+
* (the caller decides how to react).
|
|
17
|
+
*
|
|
18
|
+
* ## Concurrency
|
|
19
|
+
*
|
|
20
|
+
* The helper serializes writes per-process via a module-scoped queue keyed by
|
|
21
|
+
* the resolved audit-file path. Cross-process concurrency on the same file is
|
|
22
|
+
* NOT handled here — writers in separate processes can interleave and break
|
|
23
|
+
* the chain. The current deployment targets (rea's own governance hooks, the
|
|
24
|
+
* Codex agent, Helix) all funnel through a single process at a time. If that
|
|
25
|
+
* changes, add an exclusive-lock file (`audit.jsonl.lock`) before lifting this
|
|
26
|
+
* restriction. Documented risk; do not silently expand the guarantee.
|
|
27
|
+
*
|
|
28
|
+
* @see {@link file://./codex-event.ts} for the canonical `codex.review` shape.
|
|
29
|
+
*/
|
|
30
|
+
import fs from 'node:fs/promises';
|
|
31
|
+
import path from 'node:path';
|
|
32
|
+
import crypto from 'node:crypto';
|
|
33
|
+
import { Tier, InvocationStatus } from '../policy/types.js';
|
|
34
|
+
const GENESIS_HASH = '0'.repeat(64);
|
|
35
|
+
const REA_DIR = '.rea';
|
|
36
|
+
const AUDIT_FILE = 'audit.jsonl';
|
|
37
|
+
/** Per-file write queue to preserve linear hash-chain order within a process. */
|
|
38
|
+
const writeQueues = new Map();
|
|
39
|
+
/**
|
|
40
|
+
* Resolve a baseDir to a stable, process-wide canonical form. Two callers that
|
|
41
|
+
* pass `'.'` and `process.cwd()` for the same project must land on the same
|
|
42
|
+
* queue key — otherwise the per-process serialization promise in this module's
|
|
43
|
+
* header is broken and concurrent appends can interleave, corrupting the hash
|
|
44
|
+
* chain.
|
|
45
|
+
*
|
|
46
|
+
* Strategy:
|
|
47
|
+
* 1. `path.resolve(baseDir)` — makes relative paths absolute against the
|
|
48
|
+
* CURRENT `process.cwd()`. This must run every call; caching by the raw
|
|
49
|
+
* input key would return a stale absolute path after a `process.chdir()`,
|
|
50
|
+
* which is how rea's audit helper gets used across repos in long-lived
|
|
51
|
+
* processes. (See finding R2-3.)
|
|
52
|
+
* 2. Best-effort `fs.realpath(resolvedBase)` — unwraps symlinks (e.g. macOS
|
|
53
|
+
* `/tmp` → `/private/tmp`). If it throws (directory doesn't exist yet on
|
|
54
|
+
* first write, permission error, etc.), fall back to the `path.resolve`
|
|
55
|
+
* result. The directory will be created in `doAppend` via `mkdir`.
|
|
56
|
+
*
|
|
57
|
+
* NOTE: no caching here. `path.resolve` is microseconds and `fs.realpath` is a
|
|
58
|
+
* single `lstat` syscall; audit append is not a hot path. A previous revision
|
|
59
|
+
* keyed a cache by the raw `baseDir` string, which returned stale absolute
|
|
60
|
+
* paths across `chdir` — a brand-new regression worse than the cost it saved.
|
|
61
|
+
* If a future profiler demands caching, key it by `path.resolve(baseDir)` and
|
|
62
|
+
* only cache already-absolute inputs.
|
|
63
|
+
*/
|
|
64
|
+
async function resolveBaseDir(baseDir) {
|
|
65
|
+
const absolute = path.resolve(baseDir);
|
|
66
|
+
try {
|
|
67
|
+
return await fs.realpath(absolute);
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
// Directory doesn't exist yet, or realpath isn't permitted here. Fall back
|
|
71
|
+
// to the path.resolve'd absolute form — still stable per input, still
|
|
72
|
+
// collapses `'.' === cwd` via the absolute path.
|
|
73
|
+
return absolute;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function computeHash(record) {
|
|
77
|
+
return crypto.createHash('sha256').update(JSON.stringify(record)).digest('hex');
|
|
78
|
+
}
|
|
79
|
+
async function readLastHash(auditFile) {
|
|
80
|
+
let data;
|
|
81
|
+
try {
|
|
82
|
+
data = await fs.readFile(auditFile, 'utf8');
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
if (err.code === 'ENOENT')
|
|
86
|
+
return GENESIS_HASH;
|
|
87
|
+
throw err;
|
|
88
|
+
}
|
|
89
|
+
// Walk the file backwards by newline — the last non-empty line is the tail.
|
|
90
|
+
const trimmed = data.replace(/\n+$/, '');
|
|
91
|
+
if (trimmed.length === 0)
|
|
92
|
+
return GENESIS_HASH;
|
|
93
|
+
const lastNewline = trimmed.lastIndexOf('\n');
|
|
94
|
+
const lastLine = lastNewline === -1 ? trimmed : trimmed.slice(lastNewline + 1);
|
|
95
|
+
try {
|
|
96
|
+
const parsed = JSON.parse(lastLine);
|
|
97
|
+
if (typeof parsed.hash === 'string' && parsed.hash.length === 64) {
|
|
98
|
+
return parsed.hash;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
// Corrupt tail line — fall through to genesis. The operator will see this
|
|
103
|
+
// because the chain verify tool (future) will flag the break point. We do
|
|
104
|
+
// not throw: refusing to append would mask every subsequent event.
|
|
105
|
+
}
|
|
106
|
+
return GENESIS_HASH;
|
|
107
|
+
}
|
|
108
|
+
async function fsyncFile(filePath) {
|
|
109
|
+
let fh;
|
|
110
|
+
try {
|
|
111
|
+
fh = await fs.open(filePath, 'r');
|
|
112
|
+
await fh.sync();
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
// fsync failure is not fatal — durability is best-effort here; the write
|
|
116
|
+
// itself already succeeded.
|
|
117
|
+
}
|
|
118
|
+
finally {
|
|
119
|
+
if (fh)
|
|
120
|
+
await fh.close();
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
async function doAppend(resolvedBase, input) {
|
|
124
|
+
const reaDir = path.join(resolvedBase, REA_DIR);
|
|
125
|
+
const auditFile = path.join(reaDir, AUDIT_FILE);
|
|
126
|
+
await fs.mkdir(reaDir, { recursive: true });
|
|
127
|
+
const prevHash = await readLastHash(auditFile);
|
|
128
|
+
const now = input.timestamp ?? new Date().toISOString();
|
|
129
|
+
const recordBase = {
|
|
130
|
+
timestamp: now,
|
|
131
|
+
session_id: input.session_id ?? 'external',
|
|
132
|
+
tool_name: input.tool_name,
|
|
133
|
+
server_name: input.server_name,
|
|
134
|
+
tier: input.tier ?? Tier.Read,
|
|
135
|
+
status: input.status ?? InvocationStatus.Allowed,
|
|
136
|
+
autonomy_level: input.autonomy_level ?? 'unknown',
|
|
137
|
+
duration_ms: input.duration_ms ?? 0,
|
|
138
|
+
prev_hash: prevHash,
|
|
139
|
+
};
|
|
140
|
+
if (input.error)
|
|
141
|
+
recordBase.error = input.error;
|
|
142
|
+
if (input.redacted_fields?.length)
|
|
143
|
+
recordBase.redacted_fields = input.redacted_fields;
|
|
144
|
+
if (input.metadata && Object.keys(input.metadata).length > 0) {
|
|
145
|
+
recordBase.metadata = input.metadata;
|
|
146
|
+
}
|
|
147
|
+
const hash = computeHash(recordBase);
|
|
148
|
+
const record = { ...recordBase, hash };
|
|
149
|
+
const line = JSON.stringify(record) + '\n';
|
|
150
|
+
await fs.appendFile(auditFile, line);
|
|
151
|
+
await fsyncFile(auditFile);
|
|
152
|
+
return record;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Append a structured audit record to `${baseDir}/.rea/audit.jsonl` with a
|
|
156
|
+
* hash chained against the tail of the existing log.
|
|
157
|
+
*
|
|
158
|
+
* @param baseDir - Repo/project root (the directory that contains `.rea/`).
|
|
159
|
+
* @param input - Event data. `tool_name` and `server_name` are required.
|
|
160
|
+
* @returns The full written record, including the computed `hash`.
|
|
161
|
+
*/
|
|
162
|
+
export async function appendAuditRecord(baseDir, input) {
|
|
163
|
+
// Canonicalize the baseDir so every caller targeting the same on-disk
|
|
164
|
+
// directory lands on the same queue key, regardless of whether they passed
|
|
165
|
+
// `'.'`, `process.cwd()`, or a symlinked path. Without this, two callers in
|
|
166
|
+
// the same process can bypass the serialization promise and interleave
|
|
167
|
+
// appends — corrupting the hash chain (finding #6).
|
|
168
|
+
const resolvedBase = await resolveBaseDir(baseDir);
|
|
169
|
+
const key = path.join(resolvedBase, REA_DIR, AUDIT_FILE);
|
|
170
|
+
const prev = writeQueues.get(key) ?? Promise.resolve();
|
|
171
|
+
let record;
|
|
172
|
+
const next = prev
|
|
173
|
+
.catch(() => {
|
|
174
|
+
/* previous write's error is owned by that caller */
|
|
175
|
+
})
|
|
176
|
+
.then(async () => {
|
|
177
|
+
record = await doAppend(resolvedBase, input);
|
|
178
|
+
});
|
|
179
|
+
writeQueues.set(key, next.finally(() => {
|
|
180
|
+
// Keep the queue lean — once this write resolves, drop the reference
|
|
181
|
+
// if nothing newer is chained behind it.
|
|
182
|
+
if (writeQueues.get(key) === next)
|
|
183
|
+
writeQueues.delete(key);
|
|
184
|
+
}));
|
|
185
|
+
await next;
|
|
186
|
+
return record;
|
|
187
|
+
}
|
|
188
|
+
export { Tier, InvocationStatus } from '../policy/types.js';
|
|
189
|
+
export { CODEX_REVIEW_TOOL_NAME, CODEX_REVIEW_SERVER_NAME, } from './codex-event.js';
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single source of truth for the `codex.review` audit event shape.
|
|
3
|
+
*
|
|
4
|
+
* Both the `codex-adversarial` agent (via `@bookedsolid/rea/audit`) and the
|
|
5
|
+
* `push-review-gate.sh` shell hook depend on these constants. If either drifts,
|
|
6
|
+
* the push gate will silently stop detecting Codex reviews. Keep them in lockstep.
|
|
7
|
+
*
|
|
8
|
+
* The shell gate parses audit lines with `jq` and matches on the top-level
|
|
9
|
+
* `tool_name` plus `metadata.{head_sha, verdict}` — substring greps against the
|
|
10
|
+
* raw JSON were retired because they were forgeable via arbitrary `metadata`
|
|
11
|
+
* payloads. If you rename any of those fields, update both this file and the
|
|
12
|
+
* `jq -e` predicate in `hooks/push-review-gate.sh`.
|
|
13
|
+
*/
|
|
14
|
+
export declare const CODEX_REVIEW_TOOL_NAME = "codex.review";
|
|
15
|
+
export declare const CODEX_REVIEW_SERVER_NAME = "codex";
|
|
16
|
+
export type CodexVerdict = 'pass' | 'concerns' | 'blocking' | 'error';
|
|
17
|
+
export interface CodexReviewMetadata {
|
|
18
|
+
/** git rev-parse HEAD at the time of the review */
|
|
19
|
+
head_sha: string;
|
|
20
|
+
/** base ref or SHA the review diffed against (typically `main` or a merge-base) */
|
|
21
|
+
target: string;
|
|
22
|
+
/** total count of findings surfaced by Codex */
|
|
23
|
+
finding_count: number;
|
|
24
|
+
/** verdict classification — see {@link CodexVerdict} */
|
|
25
|
+
verdict: CodexVerdict;
|
|
26
|
+
/** optional one-sentence summary from the reviewer */
|
|
27
|
+
summary?: string;
|
|
28
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single source of truth for the `codex.review` audit event shape.
|
|
3
|
+
*
|
|
4
|
+
* Both the `codex-adversarial` agent (via `@bookedsolid/rea/audit`) and the
|
|
5
|
+
* `push-review-gate.sh` shell hook depend on these constants. If either drifts,
|
|
6
|
+
* the push gate will silently stop detecting Codex reviews. Keep them in lockstep.
|
|
7
|
+
*
|
|
8
|
+
* The shell gate parses audit lines with `jq` and matches on the top-level
|
|
9
|
+
* `tool_name` plus `metadata.{head_sha, verdict}` — substring greps against the
|
|
10
|
+
* raw JSON were retired because they were forgeable via arbitrary `metadata`
|
|
11
|
+
* payloads. If you rename any of those fields, update both this file and the
|
|
12
|
+
* `jq -e` predicate in `hooks/push-review-gate.sh`.
|
|
13
|
+
*/
|
|
14
|
+
export const CODEX_REVIEW_TOOL_NAME = 'codex.review';
|
|
15
|
+
export const CODEX_REVIEW_SERVER_NAME = 'codex';
|