@bookedsolid/rea 0.27.0 → 0.28.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/data/claims/helix-022.json +51 -0
- package/data/claims/helix-023.json +44 -0
- package/data/claims/helix-024.json +72 -0
- package/data/claims/helix-028.json +23 -0
- package/data/claims/helix-031.json +27 -0
- package/dist/cli/index.js +6 -0
- package/dist/cli/preflight.d.ts +12 -0
- package/dist/cli/preflight.js +65 -4
- package/dist/cli/review.d.ts +55 -1
- package/dist/cli/review.js +167 -5
- package/dist/cli/status.d.ts +6 -0
- package/dist/cli/status.js +7 -0
- package/dist/cli/verify-claim.d.ts +149 -0
- package/dist/cli/verify-claim.js +386 -0
- package/dist/gateway/downstream-pool.d.ts +17 -0
- package/dist/gateway/downstream-pool.js +1 -0
- package/dist/gateway/downstream.d.ts +25 -0
- package/dist/gateway/downstream.js +40 -0
- package/dist/gateway/live-state.d.ts +12 -0
- package/dist/gateway/live-state.js +1 -0
- package/dist/hooks/bash-scanner/walker.js +196 -0
- package/dist/hooks/push-gate/findings.d.ts +27 -0
- package/dist/hooks/push-gate/findings.js +87 -0
- package/dist/hooks/push-gate/index.js +58 -4
- package/dist/hooks/push-gate/policy.d.ts +15 -0
- package/dist/hooks/push-gate/policy.js +82 -0
- package/dist/policy/loader.d.ts +20 -0
- package/dist/policy/loader.js +12 -0
- package/dist/policy/types.d.ts +31 -0
- package/hooks/blocked-paths-bash-gate.sh +12 -0
- package/hooks/protected-paths-bash-gate.sh +21 -0
- package/package.json +2 -1
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "helix-022",
|
|
3
|
+
"title": "Bash-tier bypass classes (F1 parent-doesn't-exist symlink walk-up; F2 interpreter writes; F3 nested-shell unwrap depth; F4 cp/mv argv walker; F5 fail-closed unresolved expansion in redirect targets)",
|
|
4
|
+
"introduced_in": "<=0.21.0",
|
|
5
|
+
"closed_in": "0.22.0",
|
|
6
|
+
"summary": "Five adjacent Bash-tier bypass classes against 0.21.0 closed by structural fixes: nearest-existing-ancestor resolution, shared interpreter-scanner.sh, fixed-point recursion (depth bound 8) for nested shells, explicit cp/mv destination walker, and __rea_unresolved_expansion__ sentinel for $-substitution / backticks in redirect targets.",
|
|
7
|
+
"pocs": [
|
|
8
|
+
{
|
|
9
|
+
"id": "F1.symlink-walkup-parent-missing",
|
|
10
|
+
"type": "scan-bash",
|
|
11
|
+
"input": "ln -s ../../etc/passwd .rea/no-such-dir/x; echo y > .rea/no-such-dir/x",
|
|
12
|
+
"mode": "protected",
|
|
13
|
+
"expected_verdict": "block"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"id": "F2.interpreter-write-node",
|
|
17
|
+
"type": "scan-bash",
|
|
18
|
+
"input": "node -e 'require(\"fs\").writeFileSync(\".rea/HALT\", \"x\")'",
|
|
19
|
+
"mode": "protected",
|
|
20
|
+
"expected_verdict": "block"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"id": "F2.interpreter-write-python",
|
|
24
|
+
"type": "scan-bash",
|
|
25
|
+
"input": "python3 -c 'open(\".rea/HALT\", \"w\").write(\"x\")'",
|
|
26
|
+
"mode": "protected",
|
|
27
|
+
"expected_verdict": "block"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"id": "F3.nested-shell-depth",
|
|
31
|
+
"type": "scan-bash",
|
|
32
|
+
"input": "bash -c 'bash -c \"bash -c \\\"echo x > .rea/HALT\\\"\"'",
|
|
33
|
+
"mode": "protected",
|
|
34
|
+
"expected_verdict": "block"
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"id": "F4.cp-explicit-destination",
|
|
38
|
+
"type": "scan-bash",
|
|
39
|
+
"input": "cp /tmp/source -t .rea -- HALT",
|
|
40
|
+
"mode": "protected",
|
|
41
|
+
"expected_verdict": "block"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"id": "F5.unresolved-expansion-redirect",
|
|
45
|
+
"type": "scan-bash",
|
|
46
|
+
"input": "echo x > .rea/$(printf HALT)",
|
|
47
|
+
"mode": "protected",
|
|
48
|
+
"expected_verdict": "block"
|
|
49
|
+
}
|
|
50
|
+
]
|
|
51
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "helix-023",
|
|
3
|
+
"title": "0.23.0 structural rewrite — bash hooks → Node-binary parser-backed scanner via mvdan-sh AST",
|
|
4
|
+
"introduced_in": "<=0.22.0",
|
|
5
|
+
"closed_in": "0.23.0",
|
|
6
|
+
"summary": "13 codex adversarial rounds + 12,875-fixture corpus across 23 classes A-W. Closure ladder 34 → 14 → 9 → 8 → 5 → 2 → 1 → 1 → 3 → 2 → 7 → 9 → 9 (round-13 deferred to 0.24.0 as denylist-asymptotic per codex). PoCs sample one fixture from each closure-ladder round to verify the parser-backed scanner still rejects them.",
|
|
7
|
+
"pocs": [
|
|
8
|
+
{
|
|
9
|
+
"id": "round1.alias-then-invoke",
|
|
10
|
+
"type": "scan-bash",
|
|
11
|
+
"input": "alias evil='echo x > .rea/HALT'; evil",
|
|
12
|
+
"mode": "protected",
|
|
13
|
+
"expected_verdict": "block"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"id": "round1.heredoc-into-shell",
|
|
17
|
+
"type": "scan-bash",
|
|
18
|
+
"input": "bash <<'EOF'\necho x > .rea/HALT\nEOF",
|
|
19
|
+
"mode": "protected",
|
|
20
|
+
"expected_verdict": "block"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"id": "round1.process-substitution",
|
|
24
|
+
"type": "scan-bash",
|
|
25
|
+
"input": "diff <(echo a) <(rm -rf .rea)",
|
|
26
|
+
"mode": "protected",
|
|
27
|
+
"expected_verdict": "block"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"id": "round2.tee-redirect",
|
|
31
|
+
"type": "scan-bash",
|
|
32
|
+
"input": "echo x | tee .rea/HALT",
|
|
33
|
+
"mode": "protected",
|
|
34
|
+
"expected_verdict": "block"
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"id": "round3.pipe-into-bash",
|
|
38
|
+
"type": "scan-bash",
|
|
39
|
+
"input": "echo 'echo x > .rea/HALT' | bash",
|
|
40
|
+
"mode": "protected",
|
|
41
|
+
"expected_verdict": "block"
|
|
42
|
+
}
|
|
43
|
+
]
|
|
44
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "helix-024",
|
|
3
|
+
"title": "Round-24 kill-switch bypass classes (cd-cwd, double-eval, ln-symlink) — 9 PoCs",
|
|
4
|
+
"introduced_in": "0.23.0",
|
|
5
|
+
"closed_in": "0.23.1",
|
|
6
|
+
"summary": "Three P1 bypass classes against 0.23.0 closed via cd-target tracking + bash-eval payload re-parse + ln-symlink target sandbox. 5 codex local pre-push rounds caught every refinement-introduced over-block. PR #131 landed green-first-try.",
|
|
7
|
+
"pocs": [
|
|
8
|
+
{
|
|
9
|
+
"id": "F1.1.cd-and-write-literal",
|
|
10
|
+
"type": "scan-bash",
|
|
11
|
+
"input": "cd .rea && echo x > HALT",
|
|
12
|
+
"mode": "protected",
|
|
13
|
+
"expected_verdict": "block"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"id": "F1.2.cd-with-quoted-target",
|
|
17
|
+
"type": "scan-bash",
|
|
18
|
+
"input": "cd '.rea' && echo x > HALT",
|
|
19
|
+
"mode": "protected",
|
|
20
|
+
"expected_verdict": "block"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"id": "F1.3.cd-then-write-via-redirect",
|
|
24
|
+
"type": "scan-bash",
|
|
25
|
+
"input": "cd .husky; echo x > pre-push",
|
|
26
|
+
"mode": "protected",
|
|
27
|
+
"expected_verdict": "block"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"id": "F1.4.pushd-then-write",
|
|
31
|
+
"type": "scan-bash",
|
|
32
|
+
"input": "pushd .rea && cat /tmp/payload > HALT",
|
|
33
|
+
"mode": "protected",
|
|
34
|
+
"expected_verdict": "block"
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"id": "F1.5.cd-with-binary-and-write",
|
|
38
|
+
"type": "scan-bash",
|
|
39
|
+
"input": "cd .claude && echo x > settings.json",
|
|
40
|
+
"mode": "protected",
|
|
41
|
+
"expected_verdict": "block"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"id": "F2.1.double-eval-string",
|
|
45
|
+
"type": "scan-bash",
|
|
46
|
+
"input": "eval 'echo x > .rea/HALT'",
|
|
47
|
+
"mode": "protected",
|
|
48
|
+
"expected_verdict": "block"
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"id": "F2.2.eval-bash-c-string",
|
|
52
|
+
"type": "scan-bash",
|
|
53
|
+
"input": "eval \"bash -c 'echo x > .rea/HALT'\"",
|
|
54
|
+
"mode": "protected",
|
|
55
|
+
"expected_verdict": "block"
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"id": "F3.1.ln-symlink-into-protected",
|
|
59
|
+
"type": "scan-bash",
|
|
60
|
+
"input": "ln -s /tmp/x .rea/HALT",
|
|
61
|
+
"mode": "protected",
|
|
62
|
+
"expected_verdict": "block"
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"id": "F3.2.ln-symlink-into-husky",
|
|
66
|
+
"type": "scan-bash",
|
|
67
|
+
"input": "ln -sf /tmp/payload .husky/pre-push",
|
|
68
|
+
"mode": "protected",
|
|
69
|
+
"expected_verdict": "block"
|
|
70
|
+
}
|
|
71
|
+
]
|
|
72
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "helix-028",
|
|
3
|
+
"title": "0.23.0 cmd-segments multiline awk + ANSI-C bypass classes",
|
|
4
|
+
"introduced_in": "0.23.0",
|
|
5
|
+
"closed_in": "0.26.1",
|
|
6
|
+
"summary": "Multiline awk inside command-substitutions and ANSI-C $'...' string literals were silently dropped from segment scanning. Two P1 bypass classes closed in 0.26.1: awk multiline body now flagged when destination is protected; ANSI-C strings normalize to their decoded form before scanning.",
|
|
7
|
+
"pocs": [
|
|
8
|
+
{
|
|
9
|
+
"id": "multiline-awk-redirect",
|
|
10
|
+
"type": "scan-bash",
|
|
11
|
+
"input": "awk 'BEGIN { print \"x\" > \".rea/HALT\" }'",
|
|
12
|
+
"mode": "protected",
|
|
13
|
+
"expected_verdict": "block"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"id": "ansi-c-string-redirect",
|
|
17
|
+
"type": "scan-bash",
|
|
18
|
+
"input": "echo $'\\x78' > .rea/HALT",
|
|
19
|
+
"mode": "protected",
|
|
20
|
+
"expected_verdict": "block"
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "helix-031",
|
|
3
|
+
"title": "shellcheck SC1078 false positive on cmd-segments.sh closed by removing legacy bash module (0.27.0)",
|
|
4
|
+
"introduced_in": "0.26.1",
|
|
5
|
+
"closed_in": "0.27.0",
|
|
6
|
+
"summary": "0.26.1 added an ANSI-C handling path inside hooks/_lib/cmd-segments.sh that confused shellcheck SC1078 (mismatched single-quote in unclosed-string). 0.27.0 removed the legacy bash cmd-segments path entirely (the parser-backed Node scanner has owned the segmentation work since 0.23.0). This claim verifies that shellcheck runs clean on every shipped hook.",
|
|
7
|
+
"pocs": [
|
|
8
|
+
{
|
|
9
|
+
"id": "SC1078-clean-protected-paths-bash-gate",
|
|
10
|
+
"type": "shellcheck",
|
|
11
|
+
"target": "hooks/protected-paths-bash-gate.sh",
|
|
12
|
+
"expected_verdict": "clean"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"id": "SC1078-clean-blocked-paths-bash-gate",
|
|
16
|
+
"type": "shellcheck",
|
|
17
|
+
"target": "hooks/blocked-paths-bash-gate.sh",
|
|
18
|
+
"expected_verdict": "clean"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"id": "SC1078-clean-local-review-gate",
|
|
22
|
+
"type": "shellcheck",
|
|
23
|
+
"target": "hooks/local-review-gate.sh",
|
|
24
|
+
"expected_verdict": "clean"
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
}
|
package/dist/cli/index.js
CHANGED
|
@@ -12,6 +12,7 @@ import { runServe } from './serve.js';
|
|
|
12
12
|
import { runStatus } from './status.js';
|
|
13
13
|
import { runTofuAccept, runTofuList } from './tofu.js';
|
|
14
14
|
import { runUpgrade } from './upgrade.js';
|
|
15
|
+
import { registerVerifyClaimCommand } from './verify-claim.js';
|
|
15
16
|
import { err, getPkgVersion } from './utils.js';
|
|
16
17
|
async function main() {
|
|
17
18
|
const program = new Command();
|
|
@@ -115,6 +116,11 @@ async function main() {
|
|
|
115
116
|
// `local-review-gate.sh` hook both delegate to `rea preflight --strict`.
|
|
116
117
|
registerReviewCommand(program);
|
|
117
118
|
registerPreflightCommand(program);
|
|
119
|
+
// 0.28.0 — `rea verify-claim <claim-id>` replays recorded
|
|
120
|
+
// security-claim PoC batteries against the CLI under test. The
|
|
121
|
+
// centerpiece of 0.28.0 (4th structural pivot — claims as
|
|
122
|
+
// machine-verifiable artifacts).
|
|
123
|
+
registerVerifyClaimCommand(program);
|
|
118
124
|
const tofu = program
|
|
119
125
|
.command('tofu')
|
|
120
126
|
.description('TOFU fingerprint operations (G7) — inspect and rebase `.rea/fingerprints.json` when a legitimate registry edit has triggered drift fail-close. Emits audit records.');
|
package/dist/cli/preflight.d.ts
CHANGED
|
@@ -111,6 +111,18 @@ export interface LocalReviewLookupResult {
|
|
|
111
111
|
* (back-compat / fallback).
|
|
112
112
|
*/
|
|
113
113
|
match_kind?: 'content_token' | 'head_sha';
|
|
114
|
+
/**
|
|
115
|
+
* 0.28.0 round-29 P3 — set when the most recent path-matching audit
|
|
116
|
+
* entry for this HEAD had verdict `blocking` (or `error`) and was
|
|
117
|
+
* therefore skipped as "not coverage". Surfacing this lets the
|
|
118
|
+
* preflight caller render a clearer message than "no recent local-
|
|
119
|
+
* review audit entry covers HEAD" — the operator hasn't forgotten
|
|
120
|
+
* to review, they've already done one and it told them to fix
|
|
121
|
+
* findings.
|
|
122
|
+
*/
|
|
123
|
+
last_blocking_verdict?: 'blocking' | 'error';
|
|
124
|
+
/** ISO timestamp of the last blocking entry, when present. */
|
|
125
|
+
last_blocking_timestamp?: string;
|
|
114
126
|
}
|
|
115
127
|
export declare function findRecentLocalReview(baseDir: string, headSha: string, maxAgeSeconds: number, now?: Date, contentToken?: string): LocalReviewLookupResult;
|
|
116
128
|
/**
|
package/dist/cli/preflight.js
CHANGED
|
@@ -124,10 +124,20 @@ export async function computePreflight(baseDir, options, env = process.env) {
|
|
|
124
124
|
if (!reviewCheckSkipped) {
|
|
125
125
|
const lookup = findRecentLocalReview(baseDir, headSha, maxAgeSeconds, new Date(), contentToken);
|
|
126
126
|
if (!lookup.found) {
|
|
127
|
+
// 0.28.0 round-29 P3: when the most recent path-matching audit
|
|
128
|
+
// entry was blocking/error, the operator HAS reviewed — they
|
|
129
|
+
// just need to address the findings. The original message ("no
|
|
130
|
+
// recent local-review audit entry covers HEAD") makes them
|
|
131
|
+
// think they forgot to review. Distinguish the two cases.
|
|
132
|
+
const reason = lookup.last_blocking_verdict === 'blocking'
|
|
133
|
+
? 'your last local review was blocking — address findings or override'
|
|
134
|
+
: lookup.last_blocking_verdict === 'error'
|
|
135
|
+
? 'your last local review errored — re-run `rea review` and address findings'
|
|
136
|
+
: 'no recent local-review audit entry covers HEAD';
|
|
127
137
|
return {
|
|
128
138
|
outcome: {
|
|
129
139
|
status: 'refuse',
|
|
130
|
-
reason
|
|
140
|
+
reason,
|
|
131
141
|
exitCode: 2,
|
|
132
142
|
details: {
|
|
133
143
|
head_sha: headSha,
|
|
@@ -135,6 +145,12 @@ export async function computePreflight(baseDir, options, env = process.env) {
|
|
|
135
145
|
max_age_seconds: maxAgeSeconds,
|
|
136
146
|
bypass_env_var: bypassEnvVar,
|
|
137
147
|
policy_off_switch: 'policy.review.local_review.mode: off',
|
|
148
|
+
...(lookup.last_blocking_verdict !== undefined
|
|
149
|
+
? {
|
|
150
|
+
last_blocking_verdict: lookup.last_blocking_verdict,
|
|
151
|
+
last_blocking_timestamp: lookup.last_blocking_timestamp,
|
|
152
|
+
}
|
|
153
|
+
: {}),
|
|
138
154
|
},
|
|
139
155
|
},
|
|
140
156
|
policy,
|
|
@@ -292,6 +308,21 @@ function resolveCommitCountBase(baseDir) {
|
|
|
292
308
|
if (resolveRef(baseDir, ref).length > 0)
|
|
293
309
|
return ref;
|
|
294
310
|
}
|
|
311
|
+
// 0.28.0 round-29 P3: develop-branch repos sometimes omit
|
|
312
|
+
// `origin/HEAD` entirely (the symbolic ref is unset until a fresh
|
|
313
|
+
// `git remote set-head origin -a`), and `origin/main` /
|
|
314
|
+
// `origin/master` may not exist when the trunk is `origin/develop`.
|
|
315
|
+
// Pre-fix the resolver silently fell through to `null`, disabling
|
|
316
|
+
// the auto-narrow check without telling the operator. Emit a single
|
|
317
|
+
// advisory line on stderr so the failure mode is visible — but do
|
|
318
|
+
// not fail; this path is best-effort and a missing trunk is a
|
|
319
|
+
// recoverable misconfiguration.
|
|
320
|
+
if (resolveRef(baseDir, 'origin/develop').length > 0) {
|
|
321
|
+
process.stderr.write(`rea: preflight commit-count base falling through to origin/develop ` +
|
|
322
|
+
`(origin/HEAD/main/master not resolvable). ` +
|
|
323
|
+
`Consider: \`git remote set-head origin -a\` to seed origin/HEAD.\n`);
|
|
324
|
+
return 'origin/develop';
|
|
325
|
+
}
|
|
295
326
|
// `@{upstream}` LAST. We additionally probe what it resolves to —
|
|
296
327
|
// if it's a remote feature-branch ref under `refs/remotes/origin/`
|
|
297
328
|
// (not a primary trunk ref we already tried), the candidate is
|
|
@@ -365,6 +396,13 @@ export function findRecentLocalReview(baseDir, headSha, maxAgeSeconds, now = new
|
|
|
365
396
|
}
|
|
366
397
|
const lines = raw.split(/\r?\n/);
|
|
367
398
|
const cutoffMs = now.getTime() - maxAgeSeconds * 1000;
|
|
399
|
+
// 0.28.0 round-29 P3: track the most recent blocking/error entry that
|
|
400
|
+
// path-matched HEAD even though it didn't qualify as coverage. The
|
|
401
|
+
// not-found return path consumes this so the operator-facing message
|
|
402
|
+
// distinguishes "you haven't reviewed" from "your review found
|
|
403
|
+
// problems".
|
|
404
|
+
let lastBlockingVerdict;
|
|
405
|
+
let lastBlockingTimestamp;
|
|
368
406
|
// Walk in reverse — most recent first.
|
|
369
407
|
for (let i = lines.length - 1; i >= 0; i--) {
|
|
370
408
|
const line = lines[i];
|
|
@@ -421,8 +459,19 @@ export function findRecentLocalReview(baseDir, headSha, maxAgeSeconds, now = new
|
|
|
421
459
|
if (matchKind === null)
|
|
422
460
|
continue;
|
|
423
461
|
const verdict = typeof metadata.verdict === 'string' ? metadata.verdict : '';
|
|
424
|
-
if (verdict === 'error' || verdict === 'blocking')
|
|
462
|
+
if (verdict === 'error' || verdict === 'blocking') {
|
|
463
|
+
// 0.28.0 round-29 P3 — capture the FIRST (i.e., most-recent in
|
|
464
|
+
// reverse walk) blocking/error entry that path-matched HEAD so
|
|
465
|
+
// the not-found path can render a better message. Don't
|
|
466
|
+
// overwrite a later catch — we want the most-recent one.
|
|
467
|
+
if (lastBlockingVerdict === undefined) {
|
|
468
|
+
lastBlockingVerdict = verdict;
|
|
469
|
+
const ts = typeof record.timestamp === 'string' ? record.timestamp : '';
|
|
470
|
+
if (ts.length > 0)
|
|
471
|
+
lastBlockingTimestamp = ts;
|
|
472
|
+
}
|
|
425
473
|
continue;
|
|
474
|
+
}
|
|
426
475
|
const timestamp = typeof record.timestamp === 'string' ? record.timestamp : '';
|
|
427
476
|
if (timestamp.length > 0) {
|
|
428
477
|
const ts = Date.parse(timestamp);
|
|
@@ -430,7 +479,13 @@ export function findRecentLocalReview(baseDir, headSha, maxAgeSeconds, now = new
|
|
|
430
479
|
// Older than max_age_seconds — keep walking; a more recent valid
|
|
431
480
|
// record may exist further back? No: we walk newest-to-oldest so
|
|
432
481
|
// anything older from here on is also stale. Stop early.
|
|
433
|
-
return {
|
|
482
|
+
return {
|
|
483
|
+
found: false,
|
|
484
|
+
...(lastBlockingVerdict !== undefined ? { last_blocking_verdict: lastBlockingVerdict } : {}),
|
|
485
|
+
...(lastBlockingTimestamp !== undefined
|
|
486
|
+
? { last_blocking_timestamp: lastBlockingTimestamp }
|
|
487
|
+
: {}),
|
|
488
|
+
};
|
|
434
489
|
}
|
|
435
490
|
}
|
|
436
491
|
return {
|
|
@@ -441,7 +496,13 @@ export function findRecentLocalReview(baseDir, headSha, maxAgeSeconds, now = new
|
|
|
441
496
|
match_kind: matchKind,
|
|
442
497
|
};
|
|
443
498
|
}
|
|
444
|
-
return {
|
|
499
|
+
return {
|
|
500
|
+
found: false,
|
|
501
|
+
...(lastBlockingVerdict !== undefined ? { last_blocking_verdict: lastBlockingVerdict } : {}),
|
|
502
|
+
...(lastBlockingTimestamp !== undefined
|
|
503
|
+
? { last_blocking_timestamp: lastBlockingTimestamp }
|
|
504
|
+
: {}),
|
|
505
|
+
};
|
|
445
506
|
}
|
|
446
507
|
async function safeAudit(baseDir, toolName, status, metadata, policy) {
|
|
447
508
|
try {
|
package/dist/cli/review.d.ts
CHANGED
|
@@ -31,6 +31,8 @@
|
|
|
31
31
|
* the push-gate's review.
|
|
32
32
|
*/
|
|
33
33
|
import type { Command } from 'commander';
|
|
34
|
+
import { type LocalReviewVerdict } from '../audit/local-review-event.js';
|
|
35
|
+
import { type Finding } from '../hooks/push-gate/findings.js';
|
|
34
36
|
export interface RunReviewOptions {
|
|
35
37
|
/** Optional explicit base ref. Defaults to upstream-ladder resolution. */
|
|
36
38
|
base?: string;
|
|
@@ -42,13 +44,65 @@ export interface RunReviewOptions {
|
|
|
42
44
|
strictFailOn?: 'concerns' | 'blocking';
|
|
43
45
|
/** Emit a single JSON line on stdout instead of pretty output. */
|
|
44
46
|
json?: boolean;
|
|
47
|
+
/**
|
|
48
|
+
* 0.28.1 defect-V: when true, after the human-readable summary line
|
|
49
|
+
* (or alongside the JSON payload), emit the finding bodies grouped by
|
|
50
|
+
* severity. Default off — preserves backward-compatible single-line
|
|
51
|
+
* stdout for existing CI consumers.
|
|
52
|
+
*/
|
|
53
|
+
withFindings?: boolean;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Exported so tests can construct fake outcomes for the seam in
|
|
57
|
+
* `runReview`. Production callers don't reference this directly.
|
|
58
|
+
*/
|
|
59
|
+
export interface ReviewOutcome {
|
|
60
|
+
verdict: LocalReviewVerdict;
|
|
61
|
+
findingCount: number;
|
|
62
|
+
baseRef: string;
|
|
63
|
+
headSha: string;
|
|
64
|
+
/**
|
|
65
|
+
* 0.26.0 helix-026 finding-1: tree SHA of HEAD at review time. The
|
|
66
|
+
* deterministic content fingerprint `rea preflight` matches coverage
|
|
67
|
+
* on. Empty string when not resolvable (no HEAD, no git repo) — the
|
|
68
|
+
* audit writer omits `content_token` from metadata in that case.
|
|
69
|
+
*/
|
|
70
|
+
contentToken: string;
|
|
71
|
+
durationSeconds: number;
|
|
72
|
+
model: string;
|
|
73
|
+
reasoningEffort: string;
|
|
74
|
+
/**
|
|
75
|
+
* 0.28.1 defect-V: structured findings produced by the review. Pre-fix
|
|
76
|
+
* the CLI threw these away after counting; agents could not remediate
|
|
77
|
+
* blocking verdicts because the bodies were unreadable through any
|
|
78
|
+
* documented surface.
|
|
79
|
+
*/
|
|
80
|
+
findings: Finding[];
|
|
81
|
+
/**
|
|
82
|
+
* 0.28.1 defect-V: full agent-prose review text. Persisted to
|
|
83
|
+
* `.rea/last-review.json` (post-redaction) so consumers have a
|
|
84
|
+
* machine-readable transcript for parser-miss debugging.
|
|
85
|
+
*/
|
|
86
|
+
reviewText: string;
|
|
87
|
+
/** Count of raw JSONL events from codex — recorded in last-review.json. */
|
|
88
|
+
eventCount: number;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* 0.28.1 defect-V — narrow test seam. Production callers never set this;
|
|
92
|
+
* tests inject a fake to drive `runReview` deterministically without
|
|
93
|
+
* spawning codex. The seam matches `executeCodexReview`'s signature so
|
|
94
|
+
* the production path and the test path go through the same downstream
|
|
95
|
+
* wiring (audit append, last-review.json, exit code, output).
|
|
96
|
+
*/
|
|
97
|
+
export interface RunReviewDeps {
|
|
98
|
+
executeCodexReview?: (baseDir: string, options: RunReviewOptions) => Promise<ReviewOutcome>;
|
|
45
99
|
}
|
|
46
100
|
/**
|
|
47
101
|
* Public runner — exposed so tests can drive the function in-process and
|
|
48
102
|
* the commander binding can stay thin. Throws via `process.exit` (CLI
|
|
49
103
|
* convention across `src/cli/`).
|
|
50
104
|
*/
|
|
51
|
-
export declare function runReview(options: RunReviewOptions): Promise<void>;
|
|
105
|
+
export declare function runReview(options: RunReviewOptions, deps?: RunReviewDeps): Promise<void>;
|
|
52
106
|
/**
|
|
53
107
|
* Attach `rea review` to a commander Program.
|
|
54
108
|
*/
|