@bookedsolid/rea 0.26.1 → 0.28.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/README.md +16 -3
- package/agents/adversarial-test-specialist.md +113 -0
- package/agents/ast-parser-specialist.md +92 -0
- package/agents/codex-adversarial.md +50 -97
- package/agents/figma-dx-specialist.md +112 -0
- package/agents/mcp-protocol-specialist.md +94 -0
- package/agents/observability-specialist.md +103 -0
- package/agents/rea-orchestrator.md +25 -5
- package/agents/shell-scripting-specialist.md +101 -0
- package/commands/codex-review.md +62 -59
- 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/hook.d.ts +78 -4
- package/dist/cli/hook.js +291 -4
- 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/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/codex-runner.d.ts +9 -0
- package/dist/hooks/push-gate/codex-runner.js +14 -1
- 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/_lib/cmd-segments.sh +10 -0
- package/hooks/blocked-paths-bash-gate.sh +12 -0
- package/hooks/protected-paths-bash-gate.sh +21 -0
- package/package.json +2 -1
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/status.d.ts
CHANGED
|
@@ -75,6 +75,12 @@ export interface LiveDownstreamSnapshot {
|
|
|
75
75
|
circuit_state: 'closed' | 'open' | 'half-open';
|
|
76
76
|
retry_at: string | null;
|
|
77
77
|
last_error: string | null;
|
|
78
|
+
/**
|
|
79
|
+
* 0.28.0 helix-025 F1 — `'never' | 'ok' | 'errored'` tri-state.
|
|
80
|
+
* `null` for snapshots written by pre-0.28.0 gateways that did not
|
|
81
|
+
* include the field (back-compat).
|
|
82
|
+
*/
|
|
83
|
+
connection_state: 'never' | 'ok' | 'errored' | null;
|
|
78
84
|
tools_count: number | null;
|
|
79
85
|
open_transitions: number;
|
|
80
86
|
session_blocker_emitted: boolean;
|
package/dist/cli/status.js
CHANGED
|
@@ -129,6 +129,12 @@ function parseDownstreamEntry(raw) {
|
|
|
129
129
|
const circuit = r.circuit_state === 'open' || r.circuit_state === 'half-open' || r.circuit_state === 'closed'
|
|
130
130
|
? r.circuit_state
|
|
131
131
|
: 'closed';
|
|
132
|
+
// 0.28.0 helix-025 F1: tri-state. `null` when the snapshot was written
|
|
133
|
+
// by a pre-0.28.0 gateway (back-compat) — the pretty-printer renders
|
|
134
|
+
// that as "—" rather than fabricating a value.
|
|
135
|
+
const connectionState = r.connection_state === 'never' || r.connection_state === 'ok' || r.connection_state === 'errored'
|
|
136
|
+
? r.connection_state
|
|
137
|
+
: null;
|
|
132
138
|
return {
|
|
133
139
|
name: r.name,
|
|
134
140
|
connected: typeof r.connected === 'boolean' ? r.connected : false,
|
|
@@ -136,6 +142,7 @@ function parseDownstreamEntry(raw) {
|
|
|
136
142
|
circuit_state: circuit,
|
|
137
143
|
retry_at: typeof r.retry_at === 'string' ? r.retry_at : null,
|
|
138
144
|
last_error: typeof r.last_error === 'string' ? r.last_error : null,
|
|
145
|
+
connection_state: connectionState,
|
|
139
146
|
tools_count: typeof r.tools_count === 'number' && Number.isInteger(r.tools_count) ? r.tools_count : null,
|
|
140
147
|
open_transitions: typeof r.open_transitions === 'number' && Number.isInteger(r.open_transitions)
|
|
141
148
|
? r.open_transitions
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `rea verify-claim <claim-id>` — replay a recorded security-claim PoC
|
|
3
|
+
* battery against the currently-installed (or in-tree dogfood) rea CLI.
|
|
4
|
+
*
|
|
5
|
+
* The centerpiece of 0.28.0 (4th structural pivot — claims as
|
|
6
|
+
* machine-verifiable artifacts rather than prose-only release notes).
|
|
7
|
+
*
|
|
8
|
+
* Each claim lives at `data/claims/<id>.json` and lists 1..N PoCs.
|
|
9
|
+
* Every PoC has a `type` that names the executor:
|
|
10
|
+
*
|
|
11
|
+
* - `scan-bash` (primary): pipes `input` into
|
|
12
|
+
* `dist/cli/index.js hook scan-bash --mode <protected|blocked>` and
|
|
13
|
+
* compares the resulting verdict to `expected_verdict`.
|
|
14
|
+
* - `shellcheck` (helix-031 case): runs shellcheck on `target` and
|
|
15
|
+
* asserts the run is clean (no SC<code> warnings).
|
|
16
|
+
*
|
|
17
|
+
* Resolution order for the rea CLI under test:
|
|
18
|
+
*
|
|
19
|
+
* - `--installed` → resolves to `<cwd>/node_modules/@bookedsolid/rea/dist/cli/index.js`.
|
|
20
|
+
* This is the canonical "verify against MY pinned rea" mode for
|
|
21
|
+
* consumers — tells them whether the version they actually have
|
|
22
|
+
* installed still rejects the PoCs the claim targets.
|
|
23
|
+
* - default → uses the same `dist/cli/index.js` that ships with the
|
|
24
|
+
* CLI itself (i.e. the rea repo's own dogfood). Resolved relative
|
|
25
|
+
* to the running script.
|
|
26
|
+
*
|
|
27
|
+
* Exit codes:
|
|
28
|
+
*
|
|
29
|
+
* - 0 — every PoC matched the recorded `expected_verdict`.
|
|
30
|
+
* - 1 — at least one PoC mismatched (regression — investigate).
|
|
31
|
+
* - 2 — claim id is unknown / no JSON file at `data/claims/<id>.json`.
|
|
32
|
+
*/
|
|
33
|
+
import { type SpawnSyncReturns } from 'node:child_process';
|
|
34
|
+
import type { Command } from 'commander';
|
|
35
|
+
export interface ScanBashPoC {
|
|
36
|
+
id: string;
|
|
37
|
+
type: 'scan-bash';
|
|
38
|
+
input: string;
|
|
39
|
+
mode: 'protected' | 'blocked';
|
|
40
|
+
expected_verdict: 'allow' | 'block';
|
|
41
|
+
}
|
|
42
|
+
export interface ShellcheckPoC {
|
|
43
|
+
id: string;
|
|
44
|
+
type: 'shellcheck';
|
|
45
|
+
target: string;
|
|
46
|
+
expected_verdict: 'clean';
|
|
47
|
+
}
|
|
48
|
+
export type ClaimPoC = ScanBashPoC | ShellcheckPoC;
|
|
49
|
+
export interface Claim {
|
|
50
|
+
id: string;
|
|
51
|
+
title: string;
|
|
52
|
+
introduced_in: string;
|
|
53
|
+
closed_in: string;
|
|
54
|
+
summary?: string;
|
|
55
|
+
pocs: ClaimPoC[];
|
|
56
|
+
}
|
|
57
|
+
export interface VerifyClaimOptions {
|
|
58
|
+
/** Resolve the CLI to `<cwd>/node_modules/@bookedsolid/rea/dist/cli/index.js`. */
|
|
59
|
+
installed?: boolean;
|
|
60
|
+
/** Emit a single JSON document on stdout. */
|
|
61
|
+
json?: boolean;
|
|
62
|
+
/**
|
|
63
|
+
* Override the claim-file root. Production resolves this internally
|
|
64
|
+
* (ships at `data/claims/` next to the package). Tests pass an
|
|
65
|
+
* absolute path so they can stage fixtures.
|
|
66
|
+
*/
|
|
67
|
+
claimsDir?: string;
|
|
68
|
+
/**
|
|
69
|
+
* Override the rea CLI under test. Wins over `installed`. Used by
|
|
70
|
+
* tests to point at a stub binary. Production callers leave this
|
|
71
|
+
* unset.
|
|
72
|
+
*/
|
|
73
|
+
cliOverride?: string;
|
|
74
|
+
/**
|
|
75
|
+
* Override the working directory the `--installed` resolver uses.
|
|
76
|
+
* Defaults to `process.cwd()`; tests pass a tmp dir.
|
|
77
|
+
*/
|
|
78
|
+
cwd?: string;
|
|
79
|
+
}
|
|
80
|
+
export interface PoCResult {
|
|
81
|
+
poc_id: string;
|
|
82
|
+
type: ClaimPoC['type'];
|
|
83
|
+
expected: string;
|
|
84
|
+
actual: string;
|
|
85
|
+
match: boolean;
|
|
86
|
+
/** Empty on match; populated on mismatch with a one-line diagnostic. */
|
|
87
|
+
detail: string;
|
|
88
|
+
}
|
|
89
|
+
export interface VerifyClaimResult {
|
|
90
|
+
claim_id: string;
|
|
91
|
+
cli: string;
|
|
92
|
+
total: number;
|
|
93
|
+
matched: number;
|
|
94
|
+
mismatched: number;
|
|
95
|
+
results: PoCResult[];
|
|
96
|
+
exit_code: 0 | 1 | 2;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Resolve the directory holding the bundled claim JSON files. Walks up
|
|
100
|
+
* from the running script (or from this file at dev time) looking for
|
|
101
|
+
* a `data/claims/` sibling. Returns null when the directory cannot be
|
|
102
|
+
* located — the caller falls back to whatever `claimsDir` override was
|
|
103
|
+
* passed.
|
|
104
|
+
*/
|
|
105
|
+
export declare function resolveDefaultClaimsDir(): string | null;
|
|
106
|
+
/**
|
|
107
|
+
* Load and validate a claim file. Throws on malformed JSON or shape
|
|
108
|
+
* mismatch — `runVerifyClaim` translates the throw into exit-code 2 +
|
|
109
|
+
* a stderr message.
|
|
110
|
+
*/
|
|
111
|
+
export declare function loadClaim(claimsDir: string, claimId: string): Claim;
|
|
112
|
+
/**
|
|
113
|
+
* Resolve the rea CLI to invoke for `scan-bash` PoCs.
|
|
114
|
+
*
|
|
115
|
+
* Precedence: cliOverride > --installed > sibling dogfood dist/cli/index.js.
|
|
116
|
+
*
|
|
117
|
+
* Returns a pair `[command, args]` so the caller can do
|
|
118
|
+
* `spawnSync(cmd, [...args, 'hook', 'scan-bash', ...])`. The shape
|
|
119
|
+
* keeps node-vs-direct-binary differences localized to this resolver.
|
|
120
|
+
*/
|
|
121
|
+
export declare function resolveCli(opts: VerifyClaimOptions): {
|
|
122
|
+
cmd: string;
|
|
123
|
+
args: string[];
|
|
124
|
+
path: string;
|
|
125
|
+
};
|
|
126
|
+
interface SpawnImpl {
|
|
127
|
+
(cmd: string, args: string[], options: {
|
|
128
|
+
input?: string;
|
|
129
|
+
encoding: 'utf8';
|
|
130
|
+
timeout: number;
|
|
131
|
+
}): SpawnSyncReturns<string>;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Run a single PoC against the resolved CLI. Pure function — no global
|
|
135
|
+
* state, all dependencies threaded through `cliCmd` / `cliArgs` / `spawn`.
|
|
136
|
+
* Tests substitute `spawn` with a fake.
|
|
137
|
+
*/
|
|
138
|
+
export declare function runPoC(poc: ClaimPoC, cliCmd: string, cliArgs: string[], spawn?: SpawnImpl, cwd?: string): PoCResult;
|
|
139
|
+
/**
|
|
140
|
+
* Run all PoCs in a claim. Pure — exposed so tests can drive without
|
|
141
|
+
* spawning processes if they substitute `spawn`.
|
|
142
|
+
*/
|
|
143
|
+
export declare function runVerifyClaimSync(claim: Claim, cliCmd: string, cliArgs: string[], cliPath: string, spawn?: SpawnImpl, cwd?: string): VerifyClaimResult;
|
|
144
|
+
export declare function runVerifyClaim(claimId: string, opts: VerifyClaimOptions): Promise<void>;
|
|
145
|
+
/**
|
|
146
|
+
* Attach `rea verify-claim <claim-id>` to the commander program.
|
|
147
|
+
*/
|
|
148
|
+
export declare function registerVerifyClaimCommand(program: Command): void;
|
|
149
|
+
export {};
|