@bookedsolid/rea 0.11.0 → 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.
- package/.husky/pre-push +44 -13
- package/README.md +834 -552
- package/dist/cli/doctor.d.ts +12 -0
- package/dist/cli/doctor.js +90 -1
- package/dist/cli/hook.d.ts +7 -0
- package/dist/cli/hook.js +12 -1
- package/dist/cli/install/pre-push.d.ts +21 -10
- package/dist/cli/install/pre-push.js +47 -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/index.d.ts +8 -0
- package/dist/hooks/push-gate/index.js +86 -21
- package/dist/hooks/push-gate/policy.d.ts +18 -4
- package/dist/hooks/push-gate/policy.js +13 -4
- package/dist/policy/loader.d.ts +5 -0
- package/dist/policy/loader.js +1 -0
- package/dist/policy/types.d.ts +44 -2
- package/package.json +1 -1
- package/scripts/tarball-smoke.sh +7 -2
|
@@ -124,38 +124,102 @@ export async function runPushGate(deps) {
|
|
|
124
124
|
summary: 'review.codex_required is false — push-gate skipped',
|
|
125
125
|
};
|
|
126
126
|
}
|
|
127
|
-
// 3.
|
|
128
|
-
//
|
|
127
|
+
// 3. Value-carrying skip waivers. HALT-wins ordering means these are
|
|
128
|
+
// checked AFTER halt (step 1) and AFTER codex_required=false
|
|
129
129
|
// short-circuit (step 2). Both of those should hold anyway; this is
|
|
130
|
-
// for the case where codex
|
|
130
|
+
// for the case where codex IS required but the operator wants to
|
|
131
131
|
// skip for a narrow, documented reason.
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
132
|
+
//
|
|
133
|
+
// Two equivalent env vars are honored — gate behavior is identical;
|
|
134
|
+
// only the audit metadata's `skip_var` differs so operators can grep
|
|
135
|
+
// their audit log to see which variant agents used:
|
|
136
|
+
//
|
|
137
|
+
// - REA_SKIP_PUSH_GATE — the original 0.11.0 var
|
|
138
|
+
// - REA_SKIP_CODEX_REVIEW — added in 0.12.0 to match the variant
|
|
139
|
+
// documented elsewhere in the codebase
|
|
140
|
+
// (gateway/reviewers, codex-probe). Prior
|
|
141
|
+
// to 0.12.0 this string only worked at
|
|
142
|
+
// the gateway tier; agents who set it on
|
|
143
|
+
// a `git push` got no skip and codex still
|
|
144
|
+
// ran. The mismatch surfaced during the
|
|
145
|
+
// helixir migration session 2026-04-26.
|
|
146
|
+
//
|
|
147
|
+
// Precedence on simultaneous set: REA_SKIP_PUSH_GATE wins (it was the
|
|
148
|
+
// canonical name) and REA_SKIP_CODEX_REVIEW is logged but not used.
|
|
149
|
+
// Either var alone with non-empty reason short-circuits.
|
|
150
|
+
const skipPush = (env.REA_SKIP_PUSH_GATE ?? '').trim();
|
|
151
|
+
const skipCodex = (env.REA_SKIP_CODEX_REVIEW ?? '').trim();
|
|
152
|
+
if (skipPush.length > 0 || skipCodex.length > 0) {
|
|
153
|
+
const skipVar = skipPush.length > 0 ? 'REA_SKIP_PUSH_GATE' : 'REA_SKIP_CODEX_REVIEW';
|
|
154
|
+
const skipReason = skipVar === 'REA_SKIP_PUSH_GATE' ? skipPush : skipCodex;
|
|
155
|
+
stderr(`rea: ${skipVar}=${skipReason} — push-gate skipped (audited).\n`);
|
|
135
156
|
await safeAppend(appendAuditFn, deps.baseDir, EVT_SKIPPED, {
|
|
136
157
|
reason: skipReason,
|
|
158
|
+
skip_var: skipVar,
|
|
137
159
|
});
|
|
138
160
|
return {
|
|
139
161
|
status: 'skipped',
|
|
140
162
|
exitCode: 0,
|
|
141
|
-
summary:
|
|
163
|
+
summary: `${skipVar} waiver: ${skipReason}`,
|
|
142
164
|
};
|
|
143
165
|
}
|
|
144
166
|
// 4. Resolve (base_ref, head_sha) for the actual review.
|
|
145
167
|
//
|
|
146
|
-
//
|
|
147
|
-
//
|
|
148
|
-
//
|
|
149
|
-
//
|
|
150
|
-
//
|
|
151
|
-
//
|
|
168
|
+
// Precedence (highest first):
|
|
169
|
+
// a) `--base <ref>` CLI flag (deps.explicitBase) — explicit ref the
|
|
170
|
+
// operator named; we trust it.
|
|
171
|
+
// b) `--last-n-commits N` CLI flag (deps.lastNCommits) — diff
|
|
172
|
+
// against HEAD~N. Wins over the policy key.
|
|
173
|
+
// c) `policy.review.last_n_commits` — same effect as (b), but
|
|
174
|
+
// configured in `.rea/policy.yaml`. Persistent narrow-window.
|
|
175
|
+
// d) Active refspec from pre-push stdin — what git is about to
|
|
176
|
+
// push. Critical for `git push origin HEAD:release/1.0`.
|
|
177
|
+
// e) Upstream → origin/HEAD → main/master ladder.
|
|
152
178
|
//
|
|
153
|
-
// When
|
|
154
|
-
//
|
|
179
|
+
// When (a) collides with (b) or (c), (a) wins and we warn — explicit
|
|
180
|
+
// ref beats relative count.
|
|
181
|
+
const policyLastN = policy.last_n_commits;
|
|
182
|
+
const explicitBaseSet = deps.explicitBase !== undefined && deps.explicitBase.length > 0;
|
|
183
|
+
const lastNFromFlag = deps.lastNCommits;
|
|
184
|
+
const effectiveLastN = lastNFromFlag !== undefined ? lastNFromFlag : policyLastN;
|
|
185
|
+
if (explicitBaseSet && effectiveLastN !== undefined) {
|
|
186
|
+
const source = lastNFromFlag !== undefined ? '--last-n-commits' : 'policy.review.last_n_commits';
|
|
187
|
+
stderr(`rea: --base ${deps.explicitBase} overrides ${source}=${effectiveLastN}; using explicit ref.\n`);
|
|
188
|
+
}
|
|
155
189
|
const activeRefspec = (deps.refspecs ?? []).find((r) => r.localSha !== NULL_SHA && r.localSha.length > 0);
|
|
156
190
|
let base;
|
|
157
191
|
let headSha;
|
|
158
|
-
if (
|
|
192
|
+
if (explicitBaseSet) {
|
|
193
|
+
// (a) explicit base wins absolutely.
|
|
194
|
+
base = resolveBaseRef(git, { explicit: deps.explicitBase });
|
|
195
|
+
headSha = activeRefspec !== undefined ? activeRefspec.localSha : git.headSha();
|
|
196
|
+
}
|
|
197
|
+
else if (effectiveLastN !== undefined && effectiveLastN > 0) {
|
|
198
|
+
// (b) / (c) last-n-commits. Resolves to a SHA via `git rev-parse
|
|
199
|
+
// <headRef>~N`. Compute headSha FIRST so the resolver walks back N
|
|
200
|
+
// commits from the pushed ref rather than the local HEAD — critical
|
|
201
|
+
// for `git push origin some-other-branch` where the active refspec's
|
|
202
|
+
// localSha is a different branch entirely from the checkout's HEAD.
|
|
203
|
+
headSha = activeRefspec !== undefined ? activeRefspec.localSha : git.headSha();
|
|
204
|
+
base = resolveBaseRef(git, {
|
|
205
|
+
lastNCommits: effectiveLastN,
|
|
206
|
+
headRef: headSha,
|
|
207
|
+
});
|
|
208
|
+
if (base.lastNCommitsRequested !== undefined &&
|
|
209
|
+
base.lastNCommits !== undefined &&
|
|
210
|
+
base.lastNCommits < base.lastNCommitsRequested) {
|
|
211
|
+
// Clamp warning: the resolver couldn't go back N commits, so it
|
|
212
|
+
// clamped to the entire branch history (diff vs empty-tree, K+1
|
|
213
|
+
// commits reviewed) — `base.lastNCommits` carries the actual K+1.
|
|
214
|
+
// This warning fires both when source is 'last-n-commits' (clamped
|
|
215
|
+
// mid-branch, root commit included via empty-tree) and when source
|
|
216
|
+
// is 'empty-tree' (single-commit branch). The user-facing message
|
|
217
|
+
// is identical: we wanted N, got K, here's what we reviewed.
|
|
218
|
+
stderr(`rea: ${headSha.slice(0, 12)}~${base.lastNCommitsRequested} not reachable; reviewing all ${base.lastNCommits} commits on this branch instead.\n`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
else if (activeRefspec !== undefined) {
|
|
222
|
+
// (d) refspec-aware base — use what git is about to push.
|
|
159
223
|
headSha = activeRefspec.localSha;
|
|
160
224
|
if (activeRefspec.remoteSha === NULL_SHA || activeRefspec.remoteSha.length === 0) {
|
|
161
225
|
// New remote ref — no existing commits to diff against. Fall back to
|
|
@@ -168,11 +232,8 @@ export async function runPushGate(deps) {
|
|
|
168
232
|
}
|
|
169
233
|
}
|
|
170
234
|
else {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
? { explicit: deps.explicitBase }
|
|
174
|
-
: {}),
|
|
175
|
-
});
|
|
235
|
+
// (e) upstream ladder.
|
|
236
|
+
base = resolveBaseRef(git);
|
|
176
237
|
headSha = git.headSha();
|
|
177
238
|
}
|
|
178
239
|
if (headSha.length === 0) {
|
|
@@ -190,6 +251,8 @@ export async function runPushGate(deps) {
|
|
|
190
251
|
base_ref: base.ref,
|
|
191
252
|
base_source: base.source,
|
|
192
253
|
head_sha: headSha,
|
|
254
|
+
last_n_commits: base.lastNCommits,
|
|
255
|
+
last_n_commits_requested: base.lastNCommitsRequested,
|
|
193
256
|
});
|
|
194
257
|
return {
|
|
195
258
|
status: 'empty-diff',
|
|
@@ -238,6 +301,8 @@ export async function runPushGate(deps) {
|
|
|
238
301
|
duration_seconds: codexResult.durationSeconds,
|
|
239
302
|
event_count: codexResult.eventCount,
|
|
240
303
|
concerns_override: summary.verdict === 'concerns' && isConcernsOverrideSet(env) ? true : undefined,
|
|
304
|
+
last_n_commits: base.lastNCommits,
|
|
305
|
+
last_n_commits_requested: base.lastNCommitsRequested,
|
|
241
306
|
});
|
|
242
307
|
if (blocked) {
|
|
243
308
|
return {
|
|
@@ -9,9 +9,16 @@
|
|
|
9
9
|
* skip". This module is pure policy.
|
|
10
10
|
*
|
|
11
11
|
* Defaults (when a field is absent or `review:` is missing entirely):
|
|
12
|
-
* - `codex_required` → `true`
|
|
13
|
-
* - `concerns_blocks` → `true`
|
|
14
|
-
* - `timeout_ms` →
|
|
12
|
+
* - `codex_required` → `true` (safe-by-default: run Codex)
|
|
13
|
+
* - `concerns_blocks` → `true` (safe-by-default: concerns halt the push)
|
|
14
|
+
* - `timeout_ms` → 1_800_000 (30 minutes — raised in 0.12.0 from the
|
|
15
|
+
* previous 10-minute default after the
|
|
16
|
+
* helixir migration session 2026-04-26
|
|
17
|
+
* showed realistic feature-branch
|
|
18
|
+
* reviews routinely exceeded 10 minutes
|
|
19
|
+
* on large diffs. Operators who pin
|
|
20
|
+
* `timeout_ms:` in policy.yaml are
|
|
21
|
+
* unaffected by this change.)
|
|
15
22
|
*
|
|
16
23
|
* A missing `.rea/policy.yaml` is treated as "defaults apply" — the
|
|
17
24
|
* operator may not have run `rea init` yet, and the gate's behavior
|
|
@@ -22,10 +29,17 @@ export interface ResolvedReviewPolicy {
|
|
|
22
29
|
codex_required: boolean;
|
|
23
30
|
concerns_blocks: boolean;
|
|
24
31
|
timeout_ms: number;
|
|
32
|
+
/**
|
|
33
|
+
* When set, the gate resolves the diff base to `HEAD~N` (see Fix D in
|
|
34
|
+
* 0.12.0). The CLI flag `--last-n-commits N` overrides this; the
|
|
35
|
+
* policy key surfaces here as a runtime knob with the same effect.
|
|
36
|
+
* `undefined` when unset (default-untouched behavior).
|
|
37
|
+
*/
|
|
38
|
+
last_n_commits: number | undefined;
|
|
25
39
|
/** `true` when `.rea/policy.yaml` was absent; defaults apply. */
|
|
26
40
|
policyMissing: boolean;
|
|
27
41
|
}
|
|
28
|
-
export declare const PUSH_GATE_DEFAULT_TIMEOUT_MS =
|
|
42
|
+
export declare const PUSH_GATE_DEFAULT_TIMEOUT_MS = 1800000;
|
|
29
43
|
export declare const PUSH_GATE_DEFAULT_CODEX_REQUIRED = true;
|
|
30
44
|
export declare const PUSH_GATE_DEFAULT_CONCERNS_BLOCKS = true;
|
|
31
45
|
/**
|
|
@@ -9,9 +9,16 @@
|
|
|
9
9
|
* skip". This module is pure policy.
|
|
10
10
|
*
|
|
11
11
|
* Defaults (when a field is absent or `review:` is missing entirely):
|
|
12
|
-
* - `codex_required` → `true`
|
|
13
|
-
* - `concerns_blocks` → `true`
|
|
14
|
-
* - `timeout_ms` →
|
|
12
|
+
* - `codex_required` → `true` (safe-by-default: run Codex)
|
|
13
|
+
* - `concerns_blocks` → `true` (safe-by-default: concerns halt the push)
|
|
14
|
+
* - `timeout_ms` → 1_800_000 (30 minutes — raised in 0.12.0 from the
|
|
15
|
+
* previous 10-minute default after the
|
|
16
|
+
* helixir migration session 2026-04-26
|
|
17
|
+
* showed realistic feature-branch
|
|
18
|
+
* reviews routinely exceeded 10 minutes
|
|
19
|
+
* on large diffs. Operators who pin
|
|
20
|
+
* `timeout_ms:` in policy.yaml are
|
|
21
|
+
* unaffected by this change.)
|
|
15
22
|
*
|
|
16
23
|
* A missing `.rea/policy.yaml` is treated as "defaults apply" — the
|
|
17
24
|
* operator may not have run `rea init` yet, and the gate's behavior
|
|
@@ -21,7 +28,7 @@
|
|
|
21
28
|
import fs from 'node:fs';
|
|
22
29
|
import path from 'node:path';
|
|
23
30
|
import { loadPolicyAsync } from '../../policy/loader.js';
|
|
24
|
-
export const PUSH_GATE_DEFAULT_TIMEOUT_MS =
|
|
31
|
+
export const PUSH_GATE_DEFAULT_TIMEOUT_MS = 1_800_000;
|
|
25
32
|
export const PUSH_GATE_DEFAULT_CODEX_REQUIRED = true;
|
|
26
33
|
export const PUSH_GATE_DEFAULT_CONCERNS_BLOCKS = true;
|
|
27
34
|
/**
|
|
@@ -41,6 +48,7 @@ export async function resolvePushGatePolicy(baseDir) {
|
|
|
41
48
|
codex_required: PUSH_GATE_DEFAULT_CODEX_REQUIRED,
|
|
42
49
|
concerns_blocks: PUSH_GATE_DEFAULT_CONCERNS_BLOCKS,
|
|
43
50
|
timeout_ms: PUSH_GATE_DEFAULT_TIMEOUT_MS,
|
|
51
|
+
last_n_commits: undefined,
|
|
44
52
|
policyMissing: true,
|
|
45
53
|
};
|
|
46
54
|
}
|
|
@@ -50,6 +58,7 @@ export async function resolvePushGatePolicy(baseDir) {
|
|
|
50
58
|
codex_required: review.codex_required ?? PUSH_GATE_DEFAULT_CODEX_REQUIRED,
|
|
51
59
|
concerns_blocks: review.concerns_blocks ?? PUSH_GATE_DEFAULT_CONCERNS_BLOCKS,
|
|
52
60
|
timeout_ms: review.timeout_ms ?? PUSH_GATE_DEFAULT_TIMEOUT_MS,
|
|
61
|
+
last_n_commits: review.last_n_commits,
|
|
53
62
|
policyMissing: false,
|
|
54
63
|
};
|
|
55
64
|
}
|
package/dist/policy/loader.d.ts
CHANGED
|
@@ -34,14 +34,17 @@ declare const PolicySchema: z.ZodObject<{
|
|
|
34
34
|
codex_required: z.ZodOptional<z.ZodBoolean>;
|
|
35
35
|
concerns_blocks: z.ZodOptional<z.ZodBoolean>;
|
|
36
36
|
timeout_ms: z.ZodOptional<z.ZodNumber>;
|
|
37
|
+
last_n_commits: z.ZodOptional<z.ZodNumber>;
|
|
37
38
|
}, "strict", z.ZodTypeAny, {
|
|
38
39
|
codex_required?: boolean | undefined;
|
|
39
40
|
concerns_blocks?: boolean | undefined;
|
|
40
41
|
timeout_ms?: number | undefined;
|
|
42
|
+
last_n_commits?: number | undefined;
|
|
41
43
|
}, {
|
|
42
44
|
codex_required?: boolean | undefined;
|
|
43
45
|
concerns_blocks?: boolean | undefined;
|
|
44
46
|
timeout_ms?: number | undefined;
|
|
47
|
+
last_n_commits?: number | undefined;
|
|
45
48
|
}>>;
|
|
46
49
|
redact: z.ZodOptional<z.ZodObject<{
|
|
47
50
|
match_timeout_ms: z.ZodOptional<z.ZodNumber>;
|
|
@@ -135,6 +138,7 @@ declare const PolicySchema: z.ZodObject<{
|
|
|
135
138
|
codex_required?: boolean | undefined;
|
|
136
139
|
concerns_blocks?: boolean | undefined;
|
|
137
140
|
timeout_ms?: number | undefined;
|
|
141
|
+
last_n_commits?: number | undefined;
|
|
138
142
|
} | undefined;
|
|
139
143
|
redact?: {
|
|
140
144
|
match_timeout_ms?: number | undefined;
|
|
@@ -178,6 +182,7 @@ declare const PolicySchema: z.ZodObject<{
|
|
|
178
182
|
codex_required?: boolean | undefined;
|
|
179
183
|
concerns_blocks?: boolean | undefined;
|
|
180
184
|
timeout_ms?: number | undefined;
|
|
185
|
+
last_n_commits?: number | undefined;
|
|
181
186
|
} | undefined;
|
|
182
187
|
redact?: {
|
|
183
188
|
match_timeout_ms?: number | undefined;
|
package/dist/policy/loader.js
CHANGED
package/dist/policy/types.d.ts
CHANGED
|
@@ -54,12 +54,54 @@ export interface ReviewPolicy {
|
|
|
54
54
|
/**
|
|
55
55
|
* Hard cap on the `codex exec review` subprocess in milliseconds. Exceeding
|
|
56
56
|
* this kills the subprocess and the gate returns exit 2 with a timeout
|
|
57
|
-
* error (audited). Default when unset is
|
|
58
|
-
*
|
|
57
|
+
* error (audited). Default when unset is 1_800_000 (30 minutes) as of
|
|
58
|
+
* 0.12.0 — raised from 10 minutes after the helixir migration session
|
|
59
|
+
* 2026-04-26 showed realistic feature-branch diffs routinely exceeded
|
|
60
|
+
* the previous default. Operators with explicit `timeout_ms:` in
|
|
61
|
+
* `.rea/policy.yaml` are unaffected.
|
|
59
62
|
*
|
|
60
63
|
* Positive integer only. The loader rejects zero/negative values.
|
|
61
64
|
*/
|
|
62
65
|
timeout_ms?: number;
|
|
66
|
+
/**
|
|
67
|
+
* When set, `rea hook push-gate` resolves the diff base to `HEAD~N`
|
|
68
|
+
* instead of the upstream → origin/HEAD ladder. Useful when a feature
|
|
69
|
+
* branch accumulates many commits and the full origin/main diff
|
|
70
|
+
* overwhelms the reviewer (the helixir 2026-04-26 case: 50+ commits
|
|
71
|
+
* relative to origin/main produced non-deterministic Codex verdicts and
|
|
72
|
+
* 10-minute timeouts).
|
|
73
|
+
*
|
|
74
|
+
* Precedence: explicit `--base <ref>` flag wins; then `--last-n-commits N`
|
|
75
|
+
* flag; then this policy key; then refspec-aware base resolution; then
|
|
76
|
+
* the upstream-ladder fallback. When `--base` AND
|
|
77
|
+
* `--last-n-commits`/`policy.last_n_commits` are both set, `--base`
|
|
78
|
+
* wins and a stderr warning is emitted.
|
|
79
|
+
*
|
|
80
|
+
* Resolution: `git rev-parse HEAD~N`. When `HEAD~N` is unreachable
|
|
81
|
+
* the resolver consults `git rev-parse --is-shallow-repository` to
|
|
82
|
+
* pick the right clamp:
|
|
83
|
+
*
|
|
84
|
+
* - FULL clone, branch shorter than N: clamps to the empty-tree
|
|
85
|
+
* sentinel so the root commit's changes are included
|
|
86
|
+
* (`git diff base..HEAD` excludes `base`, so diffing against
|
|
87
|
+
* `HEAD~K` would silently drop the root commit). Reports
|
|
88
|
+
* `last_n_commits: K+1` — every commit on the branch reviewed.
|
|
89
|
+
*
|
|
90
|
+
* - SHALLOW clone: clamps to `HEAD~K` (the deepest LOCALLY
|
|
91
|
+
* resolvable ancestor) since older history exists on the remote
|
|
92
|
+
* but isn't fetched. Using empty-tree here would balloon the
|
|
93
|
+
* review to every tracked file in the checkout. Reports
|
|
94
|
+
* `last_n_commits: K`. The K-th commit's content is excluded —
|
|
95
|
+
* accepted as the cost of the shallow clone.
|
|
96
|
+
*
|
|
97
|
+
* A stderr warning surfaces the requested-vs-clamped numbers in
|
|
98
|
+
* both cases. Audit metadata records `base_source: 'last-n-commits'`,
|
|
99
|
+
* `last_n_commits: <count actually reviewed>`, and
|
|
100
|
+
* `last_n_commits_requested: N` (only present when clamped).
|
|
101
|
+
*
|
|
102
|
+
* Positive integer. The loader rejects zero/negative values.
|
|
103
|
+
*/
|
|
104
|
+
last_n_commits?: number;
|
|
63
105
|
}
|
|
64
106
|
/**
|
|
65
107
|
* User-supplied redaction pattern entry. Each pattern has a stable `name` used
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bookedsolid/rea",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "Agentic governance layer for Claude Code — policy enforcement, hook-based safety gates, audit logging, and Codex-integrated adversarial review for AI-assisted projects",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Booked Solid Technology <oss@bookedsolid.tech> (https://bookedsolid.tech)",
|
package/scripts/tarball-smoke.sh
CHANGED
|
@@ -74,8 +74,13 @@ echo "[smoke] → $VERSION_OUT"
|
|
|
74
74
|
echo "[smoke] rea --help"
|
|
75
75
|
./node_modules/.bin/rea --help >/dev/null
|
|
76
76
|
|
|
77
|
-
echo "[smoke] rea init --yes --profile open-source"
|
|
78
|
-
|
|
77
|
+
echo "[smoke] rea init --yes --profile open-source-no-codex"
|
|
78
|
+
# 0.12.0+: doctor hard-fails when policy.review.codex_required: true and codex
|
|
79
|
+
# is not on PATH (fix C of the helixir migration unblocker — see PR #85). CI
|
|
80
|
+
# does not provision the codex CLI, so the smoke uses the -no-codex profile
|
|
81
|
+
# variant which defaults codex_required: false. The new doctor probe is
|
|
82
|
+
# covered by unit tests in src/cli/doctor.test.ts.
|
|
83
|
+
./node_modules/.bin/rea init --yes --profile open-source-no-codex
|
|
79
84
|
|
|
80
85
|
# Verify the installed layout matches what init claims it wrote.
|
|
81
86
|
for expected in .rea/policy.yaml .rea/registry.yaml .claude/settings.json CLAUDE.md .rea/install-manifest.json; do
|