@allocator-one/rcl 4.4.19 → 4.5.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.
Files changed (2) hide show
  1. package/README.md +674 -1240
  2. package/package.json +4 -4
package/README.md CHANGED
@@ -1,150 +1,299 @@
1
- # review-council
1
+ # rcl — Review Council
2
2
 
3
- > Multi-model AI code review in your terminal — many models, many roles, one consensus.
3
+ Multi-model AI code review in your terminal. Several models review the same
4
+ change in different roles; `rcl` merges their findings into one consensus
5
+ report and marks which findings block.
4
6
 
5
- ![npm](https://img.shields.io/npm/v/review-council) ![license](https://img.shields.io/npm/l/review-council) ![node](https://img.shields.io/node/v/review-council)
7
+ ![npm](https://img.shields.io/npm/v/@allocator-one/rcl) ![license](https://img.shields.io/npm/l/@allocator-one/rcl) ![node](https://img.shields.io/node/v/@allocator-one/rcl)
8
+
9
+ - [Install](#install)
10
+ - [Prerequisites](#prerequisites)
11
+ - [Quick start](#quick-start)
12
+ - [Commands](#commands)
13
+ - [Configuration](#configuration)
14
+ - [Environment variables](#environment-variables)
15
+ - [How consensus and gating work](#how-consensus-and-gating-work)
16
+ - [Harness evidence and gate](#harness-evidence-and-gate)
17
+ - [Agent skills: `/rcl` and `/rcl-converge`](#agent-skills-rcl-and-rcl-converge)
18
+ - [Changelog](#changelog)
6
19
 
7
20
  ---
8
21
 
9
22
  ## Install
10
23
 
11
24
  ```bash
12
- npm install -g review-council
25
+ npm install -g @allocator-one/rcl
13
26
  ```
14
27
 
15
- Requires Node.js >= 18.
28
+ Requires Node.js 20 or later. The command is `rcl`.
16
29
 
17
- ---
30
+ ### Migrating from `review-council`
18
31
 
19
- ## Quick Start
32
+ Earlier releases were published as `review-council`. Both packages install the
33
+ same `rcl` command, and npm refuses to overwrite a command that belongs to
34
+ another package (`EEXIST`), so remove the old package first:
20
35
 
21
36
  ```bash
22
- # Review a GitHub PR with default models and roles
23
- rcl review owner/repo#42
37
+ npm uninstall -g review-council
38
+ npm install -g @allocator-one/rcl
39
+ ```
24
40
 
25
- # Review with specific roles and post findings as a PR comment
26
- rcl review owner/repo#42 --roles security-auditor,bug-hunter --post
41
+ Nothing else changes. Config files keep their names (`.review-council.yml`,
42
+ `.review-council.yaml`, `.review-council.json`) and keep working, and the
43
+ per-machine state in `~/.rcl` and the convergence state under `.git/` are read
44
+ as before.
27
45
 
28
- # Review a local patch file; fail CI if critical/important findings exist
29
- rcl review changes.patch --ci --markdown report.md
30
- ```
46
+ ---
47
+
48
+ ## Prerequisites
49
+
50
+ ### Provider API keys
51
+
52
+ The default council calls three providers directly. Set their keys before your
53
+ first review:
54
+
55
+ | Seat | Default model | Key |
56
+ | --- | --- | --- |
57
+ | General and specialist reviewers (blocking lane) | `anthropic/claude-opus-5-5`, `openai/gpt-6-sol` | `ANTHROPIC_API_KEY`, `OPENAI_API_KEY` |
58
+ | Specialist reviewer (secondary lane) | `google/gemini-3.8-flash` | `GOOGLE_API_KEY` or `GEMINI_API_KEY` |
59
+ | Verifier for single-model findings | `openai/gpt-6-astra` | `OPENAI_API_KEY` |
60
+ | Async bonus reviewer (optional) | `openrouter/moonshotai/kimi-k3` | `OPENROUTER_API_KEY` |
61
+
62
+ A seat whose key is missing fails; failed blocking-lane seats lower the
63
+ report's reviewer health. If `OPENROUTER_API_KEY` is not set, the default async
64
+ reviewer is dropped with a warning; models you configure explicitly fail loudly
65
+ instead. Guarded convergence launches keep the default roster intact and refuse
66
+ before claiming an attempt when any roster provider's key is missing.
67
+
68
+ When the key is set, default reviews also send diff and context content to
69
+ OpenRouter, an aggregator and an additional data processor beyond the direct
70
+ model providers. Configure `models` and `asyncModels` explicitly if that
71
+ matters for your repository. An explicit `models` list (in the config or via
72
+ `--models`) also clears the default secondary and async lists unless you set
73
+ those too.
74
+
75
+ Reviewing a GitHub pull request reads it through the GitHub API. For PR
76
+ fetches and `--post`, rcl uses a nonempty `githubToken` config value, then
77
+ `GITHUB_TOKEN`, then your existing `gh auth token --hostname github.com` login.
78
+ The fallback is noninteractive and bounded; without any credential, public
79
+ repositories still work anonymously, and a PR 404 explains how to check
80
+ private-repository access without exposing credentials. Local patch and
81
+ git-mode reviews read no GitHub credentials.
82
+
83
+ ### Providers and model names
84
+
85
+ Name models with a provider prefix:
86
+
87
+ | Prefix | Provider | Credentials |
88
+ | --- | --- | --- |
89
+ | `anthropic/` | Anthropic API | `ANTHROPIC_API_KEY` |
90
+ | `openai/` | OpenAI API | `OPENAI_API_KEY` |
91
+ | `google/` | Google Gemini API | `GOOGLE_API_KEY`, else `GEMINI_API_KEY` (blank values fall through) |
92
+ | `openrouter/` | [OpenRouter](https://openrouter.ai); keep the vendor segment, e.g. `openrouter/moonshotai/kimi-k3` | `OPENROUTER_API_KEY` (required; never falls back to `OPENAI_API_KEY`) |
93
+ | `openai-compat/` | Any OpenAI-compatible chat completions endpoint (Ollama, LM Studio, a self-hosted gateway) | `OPENAI_COMPAT_BASE_URL` (default `http://localhost:11434/v1`), `OPENAI_COMPAT_API_KEY` (default: the placeholder `local`) |
94
+
95
+ Unprefixed names are routed by how they start: `claude…` to Anthropic;
96
+ `gpt…`, `o1…`, `o3…` and `o4…` to OpenAI; `gemini…` to Google. **Every other
97
+ unprefixed name is routed to `openai-compat`** without a warning, which means
98
+ `http://localhost:11434/v1` unless `OPENAI_COMPAT_BASE_URL` is set. A typo such
99
+ as `opus-5-5` therefore targets a local endpoint, and with no local server that
100
+ seat fails with a connection error. Use provider prefixes.
101
+
102
+ ### Keys from Harness (optional)
103
+
104
+ Repositories that carry a committed `.harness-cli/config.json` (discovered
105
+ git-style, walking up from the working directory) can get their provider keys
106
+ from a [Harness](https://harness.infra.one) backend instead of every teammate
107
+ managing them by hand: run `harness login` once, and any provider key
108
+ **missing from the environment** is fetched from `GET /api/v1/model-keys` on
109
+ the host that minted the stored login token, and injected for the run.
110
+
111
+ - Environment variables always win; only missing keys are injected.
112
+ - The stored credential is only ever sent to the host it was minted for, never
113
+ to a URL named by the repository's own config (untrusted input in a cloned
114
+ repository).
115
+ - If the fetch is not possible — not logged in, offline, an older backend
116
+ without the endpoint — rcl prints a one-line note and continues with the
117
+ environment as it is. The fetch runs under a 3-second timeout, and keys are
118
+ never written to disk or logs.
119
+ - Which providers the backend serves is server configuration;
120
+ `RCL_NO_HARNESS_KEYS` disables the mechanism client-side.
31
121
 
32
122
  ---
33
123
 
34
- ## Built-in Roles
35
-
36
- | Role | Description |
37
- |------|-------------|
38
- | 🔍 `general` | Comprehensive review covering all dimensions |
39
- | 🔒 `security-auditor` | Auth, injection, XSS, CSRF, IDOR, and sensitive data exposure |
40
- | ⚡ `performance-engineer` | N+1 queries, caching, algorithmic complexity, and memory efficiency |
41
- | 📐 `api-design` | API contracts, breaking changes, REST/gRPC conventions |
42
- | 🧪 `test-coverage` | Missing tests, edge cases, flawed test logic |
43
- | ✏️ `dx-critic` | Readability, naming, documentation, and developer ergonomics |
44
- | 🏗️ `architecture` | Module boundaries, coupling, and architectural patterns |
45
- | 🐛 `bug-hunter` | Logic errors, null paths, race conditions, off-by-one |
46
- | ♿ `accessibility-auditor` | WCAG compliance, ARIA roles, keyboard navigation |
47
- | 📄 `spec-compliance` | Checks implementation against a spec or plan file |
48
- | `regression-hunter` | Changed defaults, weakened guards, and lost behavior |
49
- | `dependency-hygiene` | Unnecessary dependencies, external requests, and privacy leaks |
50
- | `edge-case-hunter` | Boundary values, unusual inputs, and failure paths |
124
+ ## Quick start
51
125
 
52
- `project-rules` and `dead-code` are no longer built-in reviewer roles. Repository
53
- rules discovered in `AGENTS.md`, `CLAUDE.md`, or the other supported rules files
54
- are supplied as shared context to the remaining reviewers. Remove the retired
55
- names from explicit role lists; they follow the usual unknown-role warning and
56
- skip behavior unless you define a custom role with that name.
126
+ ```bash
127
+ export ANTHROPIC_API_KEY=… OPENAI_API_KEY=… GEMINI_API_KEY=…
57
128
 
58
- The default Opus 5.5 and Sol general reviewers plus specialist assignments schedule
59
- 13 blocking seats, or 14 when a specification enables `spec-compliance`. Gemini
60
- is eligible for specialist assignments only. The async general reviewer
61
- and verifier are separate; quorum is calculated from the blocking roster.
129
+ # Review your staged changes before committing
130
+ rcl review --staged
62
131
 
63
- List roles in the terminal:
132
+ # Review a GitHub pull request and keep the full report
133
+ rcl review owner/repo#42 --json-file report.json --markdown report.md
64
134
 
65
- ```bash
66
- rcl roles list
67
- rcl roles show security-auditor
135
+ # Fail a CI job when the council reports a blocking finding
136
+ rcl review owner/repo#42 --ci
68
137
  ```
69
138
 
139
+ Without `--json-file` or `--markdown`, rcl writes no report files; the terminal
140
+ summary is the only output.
141
+
70
142
  ---
71
143
 
72
- ## CLI Reference
144
+ ## Commands
145
+
146
+ | Command | Purpose |
147
+ | --- | --- |
148
+ | [`rcl review [target]`](#rcl-review-target) | Review a pull request, a patch file, or uncommitted work |
149
+ | [`rcl review-plan <file>`](#rcl-review-plan-file) | Review an implementation plan before code exists |
150
+ | [`rcl discuss <question>`](#rcl-discuss-question) | Ask the models that raised a finding a follow-up question |
151
+ | [`rcl roles`](#rcl-roles) | List and inspect reviewer roles |
152
+ | [`rcl models`](#rcl-models) | Per-model precision, volume, latency and consensus weight |
153
+ | [`rcl evidence …`, `rcl telemetry …`](#rcl-evidence-and-rcl-telemetry) | Read and deliver review evidence on Harness |
154
+ | [`rcl converge-…`](#convergence-commands) | Convergence-loop accounting and recovery |
155
+
156
+ `rcl <command> --help` lists every option.
73
157
 
74
158
  ### `rcl review [target]`
75
159
 
76
- Review a PR, a local diff, or uncommitted work.
160
+ Review a PR, a local diff, or uncommitted work. Pick exactly one source:
77
161
 
78
- **Target formats:**
79
- - `owner/repo#N` — GitHub PR number
80
- - GitHub PR URL
81
- - Path to a `.patch` or `.diff` file
82
- - No target with `--staged` or `--working-tree` — review uncommitted changes in the current repository
162
+ - `owner/repo#N` or a GitHub PR URL — a pull request
163
+ - a path to a `.patch` or `.diff` file — a local patch
164
+ - `--staged` — staged changes (`git diff --cached`)
165
+ - `--working-tree` — all uncommitted changes (`git diff HEAD`, staged and
166
+ unstaged)
83
167
 
84
- **Options:**
168
+ Untracked files are invisible to `git diff` and therefore not reviewed.
85
169
 
86
170
  | Flag | Description |
87
- |------|-------------|
88
- | `--staged` | Review staged changes (`git diff --cached`) |
89
- | `--working-tree` | Review all uncommitted changes (`git diff HEAD`, staged + unstaged) |
171
+ | --- | --- |
172
+ | `--staged` / `--working-tree` | Review uncommitted changes instead of a target |
90
173
  | `--role <name>` | Use a single named role |
91
- | `--roles <names>` | Comma-separated list of roles |
92
- | `--reviewer <model:role>` | Explicit model:role pair (repeatable) |
93
- | `--models <models>` | Comma-separated list of models to use |
174
+ | `--roles <names>` | Comma-separated list of roles (`all` runs every role) |
175
+ | `--reviewer <model:role>` | Explicit model:role pair (repeatable); runs exactly these pairs, with no async seats |
176
+ | `--models <models>` | Comma-separated primary (blocking) models; clears the default secondary and async lists unless those flags are also given |
177
+ | `--secondary-models <models>` | Comma-separated secondary models, used for specialist roles only |
178
+ | `--async-models <models>` | Comma-separated async bonus reviewers, fired with the round and never awaited |
94
179
  | `--context <path>` | Context file or directory (repeatable) |
95
- | `--spec <path>` | Specification file for `spec-compliance` role |
96
- | `--focus <areas>` | Comma-separated focus areas |
97
- | `--post` | Post review as a GitHub PR comment |
98
- | `--json` | Print JSON output to stdout |
99
- | `--json-file <path>` | Write JSON output to a file |
100
- | `--markdown <path>` | Write Markdown report to a file |
101
- | `--ci` | Exit non-zero if critical/important findings exist |
102
- | `--head-sha <sha>` | Exact head commit a patch file was taken from (patch files only) |
103
- | `--base-sha <sha>` | Exact base commit a patch file was taken from (patch files only) |
180
+ | `--spec <path>` | Specification file; enables the `spec-compliance` role |
181
+ | `--spec-source <source>` | Where `--spec` came from: `flag`, `repo_file`, or `harness_issue:<ID>` (recorded in the report) |
182
+ | `--post` | Post the review to the pull request; findings that map onto the diff become inline comments |
183
+ | `--json` | Print the JSON report to stdout |
184
+ | `--json-file <path>` | Write the JSON report to a file |
185
+ | `--markdown <path>` | Write the Markdown report to a file |
186
+ | `--ci` | Exit 1 when the review is a CI failure (see [`--ci`](#--ci)) |
187
+ | `--head-sha <sha>` / `--base-sha <sha>` | Exact head/base commit a patch file was taken from (patch files only) |
104
188
  | `--expect-head-sha <sha>` | Fail fast unless the resolved head commit equals this SHA |
105
- | `--spec-source <source>` | Where `--spec` came from: `flag`, `repo_file`, or `harness_issue:<ID>` |
106
- | `--converge-target <key>` / `--round <n>` / `--attempt <n>` | Converge context recorded in the report (or `RCL_CONVERGE_TARGET` / `_ROUND` / `_ATTEMPT`) |
107
- | `--start-over` | Explicitly start a fresh PR review with a new normal budget; preserve all prior spending and evidence |
108
- | `--guarded-converge` | Validate and claim inside this process; derive the next round from native admitted state |
109
- | `--bound-fix-recovery <run-id>` | Allow one additional review of unchanged inputs after verifying the selected native dismissal-only run against live Harness evidence; requires guarded convergence and required PR evidence |
110
- | `--launch-intent <intent>` | Guarded intent: `review` (default), `stop-upstream`, `stop-review`, or `retry-delivery` |
111
- | `--retry-reason <reason>` | Explicit bounded recovery decision for a failed/unknown launch; preserves spent attempts |
112
- | `--retry-report <path>` | Bind an original legacy report to an inconclusive retry; new inputs may differ; requires `--retry-reason` |
113
- | `--resume-pending` / `--resume-async-sha256 <hashes>` | Recover one proven dead-owner pending launch while retaining exact async artifacts and treating unknown blocking work as failed |
114
- | `--ordinary-pending-package <path>` / `--preview-pending` | Authenticate a sealed ordinary pending recovery package without mutation before choosing a recovery operation |
115
- | `--finalize-pending-only` / `--pending-native-sha256 <digest>` / `--pending-attempt-sha256 <digest>` | Finalize the previewed ordinary pending attempt as failed/unknown without claiming or dispatching its successor |
116
- | `--max-attempts <n>` / `--max-rounds <n>` | Guarded launch only: explicitly authorized caps; omission preserves native caps |
117
- | `--attest` | GitHub Actions gate workflow only: exchange the job's OIDC token for a run-bound Harness credential and record the review as attested (see below) |
189
+ | `--for-pr <owner/repo#N>` | Bind a patch-file review to the pull request it was taken from (needs `--head-sha`) |
190
+ | `--no-telemetry` | Do not deliver this review as evidence to Harness |
191
+ | `--evidence-required` | Exit 4 unless Harness acknowledged the evidence |
192
+ | `--attest` | GitHub Actions gate workflow only: record the review as attested (see [The gate workflow](#the-gate-workflow)) |
118
193
  | `--config <path>` | Path to a config file |
119
194
 
120
- `--role`, `--roles`, and `--reviewer` are mutually exclusive. So are a positional target, `--staged`, and `--working-tree` — pick exactly one review source. Untracked files are invisible to `git diff` and therefore not reviewed.
195
+ `--role`, `--roles`, and `--reviewer` are mutually exclusive.
121
196
 
122
- **Self-describing reports (3.0).** Every report carries a `run` header: a client run id (UUIDv7), the rcl version, the target with its exact `head_sha`/`base_sha` (from GitHub for PRs, from `git rev-parse HEAD` and the merge-base with the remote default branch for `--staged`/`--working-tree`, from `--head-sha`/`--base-sha` for patch files) and a `diff_sha256`, the roster with each seat's lane (`blocking`, `secondary`, `async`, `verification`), a config digest with thresholds and gating inline, spec and context-file digests, a best-effort `runner` claim (`agent` / `ci` / `human`), timing, the CI verdict (computed even without `--ci`), and the converge context when run under rcl-converge. Every finding carries an `identity`, allocated uniquely across the report's consensus findings, including the below-threshold appendix. Keys use `report:<run-id>:<16-hex-key>` so report allocation cannot alias unrelated native ledger identities or reuse another run's classification. Colliding location anchors are disambiguated before thresholding; native cross-round location matching still determines the unchanged canonical ledger identity. Every reviewer call records token `usage` where the provider reports it. Reports without a `run` header (pre-3.0) remain readable; ambiguous classifications are refused as described below.
197
+ `--focus <areas>` is accepted by `rcl review` but has no effect: it is not
198
+ passed to reviewers. (`rcl review-plan --focus` does work.)
123
199
 
124
- **Examples:**
200
+ **Convergence and recovery flags.** These serve the convergence loop and its
201
+ recovery operations, documented in
202
+ [Convergence and recovery](https://github.com/allocator-one/rcl/blob/main/docs/convergence.md):
203
+
204
+ | Flag | Purpose |
205
+ | --- | --- |
206
+ | `--guarded-converge` | Validate and claim one attempt inside this process; derive the round from native state |
207
+ | `--converge-target <key>` | Convergence target this round belongs to (or `RCL_CONVERGE_TARGET`) |
208
+ | `--round <n>` / `--attempt <n>` | Converge context recorded in the report (or `RCL_CONVERGE_ROUND` / `RCL_CONVERGE_ATTEMPT`); guarded launches derive these themselves |
209
+ | `--start-over` | Start an explicitly requested fresh review cycle with a new normal budget; preserves prior spending and evidence |
210
+ | `--max-attempts <n>` / `--max-rounds <n>` | Guarded launch only: explicitly authorized caps; omission preserves native caps |
211
+ | `--retry-reason <reason>` | Explicit bounded recovery decision for a failed, unknown or inconclusive launch; preserves spent attempts |
212
+ | `--retry-report <path>` | Bind an original legacy (4.1.10–4.1.12) report to an inconclusive retry; requires `--retry-reason` |
213
+ | `--launch-intent <intent>` | `review` (default), `stop-upstream`, `stop-review`, or `retry-delivery` |
214
+ | `--bound-fix-recovery <run-id>` | Allow one more review of unchanged inputs after verifying a native dismissal-only run against live Harness evidence |
215
+ | `--export-pending-package <path>` | Export the authenticated inputs of an ordinary pending launch whose coordinator died to an exclusive private file, without provider calls or native writes |
216
+ | `--expect-base-sha <sha>` | With `--export-pending-package` only: require the resolved current base to equal this SHA |
217
+ | `--preview-pending` | Authenticate a pending recovery or preview a package export without writes or provider calls |
218
+ | `--ordinary-pending-package <path>` | Immutable pending-launch package for `--resume-pending` or `--finalize-pending-only` |
219
+ | `--resume-pending` / `--resume-async-sha256 <hashes>` | Finalize a dead pending launch and claim one checkpointed retry, retaining the exact async results |
220
+ | `--finalize-pending-only` / `--pending-native-sha256 <digest>` / `--pending-attempt-sha256 <digest>` | Finalize the previewed pending attempt as failed/unknown without claiming a successor |
221
+
222
+ **Reports.** Every report carries a `run` header: a client run id (UUIDv7),
223
+ the rcl version, the target with its exact `head_sha`/`base_sha` (from GitHub
224
+ for PRs, from `git rev-parse HEAD` and the merge-base with the remote default
225
+ branch for `--staged`/`--working-tree`, from `--head-sha`/`--base-sha` for
226
+ patch files) and a `diff_sha256`, the roster with each seat's lane
227
+ (`blocking`, `secondary`, `async`, `verification`), a config digest with
228
+ thresholds and gating inline, spec and context-file digests, a best-effort
229
+ `runner` claim (`agent` / `ci` / `human`), timing, the CI verdict (computed
230
+ even without `--ci`), and the converge context when run in a convergence loop.
231
+ Every finding carries an `identity`, allocated uniquely across the report's
232
+ consensus findings, including the below-threshold appendix. Keys use
233
+ `report:<run-id>:<16-hex-key>` so report allocation cannot alias unrelated
234
+ native ledger identities or reuse another run's classification. Colliding
235
+ location anchors are disambiguated before thresholding; native cross-round
236
+ location matching still determines the unchanged canonical ledger identity.
237
+ Every reviewer call records token `usage` where the provider reports it.
238
+ Reports without a `run` header (before 3.0) remain readable; ambiguous
239
+ classifications are refused by `rcl converge-report`.
240
+
241
+ Before dispatch, rcl prints the expanded reviewer × chunk call count,
242
+ concurrency, wave count, timeout, and timeout-bound queue estimate. Interactive
243
+ runs update the spinner; redirected runs emit periodic heartbeat and bounded
244
+ completion lines, so a long queue is distinguishable from a hung process.
245
+
246
+ #### Exit codes
247
+
248
+ | Exit | Meaning |
249
+ | --- | --- |
250
+ | 0 | The review completed (or there was nothing to review); with `--ci`, nothing failed the gate; with `--evidence-required`, Harness acknowledged the evidence |
251
+ | 1 | An error (invalid flags or target, refused launch, configuration or provider setup failure), a `--ci` gate failure, or a requested report file that could not be written |
252
+ | 2 | Guarded convergence only: the attempt or round cap is exhausted; continuing needs an explicitly approved higher cap |
253
+ | 3 | Guarded convergence only: native attempt or round state could not be read or written |
254
+ | 4 | `--evidence-required` (implied by `--attest`): the review completed but Harness did not acknowledge the evidence |
255
+
256
+ When `--ci` fails and evidence delivery also failed, the exit code is 1 and the
257
+ evidence failure is printed beside the gate verdict. A run that starts or
258
+ continues a fresh review cycle (`--start-over`, or a later ordinary review of
259
+ that pull request from the same repository) is a guarded convergence launch
260
+ with evidence required.
261
+
262
+ #### `--ci`
263
+
264
+ `--ci` exits 1 when no reviewer succeeded (an empty finding list then means
265
+ "nobody looked", not "clean") or when the report contains at least one
266
+ **gating** finding. In the default `verified-consensus` gating mode, a
267
+ critical or important finding gates when at least two distinct models raised it
268
+ (`gating.minModels`), when it is critical, or when the verifier confirmed it
269
+ with source evidence; its `gating.reason` is then `consensus`, `critical` or
270
+ `verified`. A refuted or unverifiable single-model claim (`gating.reason:
271
+ none`) is reported but does not fail CI. In `all-findings` mode every critical
272
+ or important finding fails CI. Findings in the below-threshold appendix never
273
+ count. See [How consensus and gating work](#how-consensus-and-gating-work).
274
+
275
+ Examples:
125
276
 
126
277
  ```bash
127
- # Use explicit model:role pairs
278
+ # Explicit model:role pairs
128
279
  rcl review owner/repo#7 \
129
- --reviewer claude-opus-5-5:security-auditor \
130
- --reviewer gpt-6-sol:bug-hunter
280
+ --reviewer anthropic/claude-opus-5-5:security-auditor \
281
+ --reviewer openai/gpt-6-sol:bug-hunter
131
282
 
132
283
  # Spec compliance review with context
133
284
  rcl review ./feature.patch --role spec-compliance --spec SPEC.md --context src/
134
285
 
135
- # Output JSON for downstream processing
286
+ # JSON for downstream processing
136
287
  rcl review owner/repo#99 --json > findings.json
137
288
 
138
- # Review your uncommitted work before committing
139
- rcl review --staged
289
+ # Review uncommitted work with two roles
140
290
  rcl review --working-tree --roles security-auditor,bug-hunter
141
291
  ```
142
292
 
143
- ---
144
-
145
293
  ### `rcl review-plan <file>`
146
294
 
147
- Council-review an implementation plan document (PRD, BUILD_PLAN.md, design doc) **before any code exists** — the cheapest bugs to fix are the ones caught in the plan.
295
+ Council-review an implementation plan document (PRD, build plan, design doc)
296
+ before any code exists.
148
297
 
149
298
  ```bash
150
299
  rcl review-plan docs/plan.md
@@ -152,15 +301,25 @@ rcl review-plan docs/plan.md --focus risks # feasibility | completeness |
152
301
  rcl review-plan docs/plan.md --spec PRD.md # also check the plan against a spec
153
302
  ```
154
303
 
155
- The plan flows through the normal pipeline — multi-model dispatch, dedup, consensus, agreement-tier report — with plan-adapted prompts. Finding line numbers refer to the plan document's own lines. Categories are reinterpreted for plans (`correctness` = infeasible/contradictory steps, `tests` = missing validation strategy, `best-practices` = process gaps like rollback/migration, …).
156
-
157
- Default roles are a plan-suited subset (`general`, `architecture`, `edge-case-hunter`, plus `spec-compliance` when a spec is given); `--role`/`--roles`/`--reviewer` and config `roles` override as usual. Shares `--context`, `--models`, `--json`, `--json-file`, `--markdown`, and `--config` with `rcl review`. `--post` and `--ci` are not offered (no PR to post to; plan findings are judgment calls, not gates).
304
+ The plan flows through the normal pipeline — multi-model dispatch, dedup,
305
+ consensus, agreement-tier report — with plan-adapted prompts. Finding line
306
+ numbers refer to the plan document's own lines. Categories are reinterpreted
307
+ for plans (`correctness` = infeasible or contradictory steps, `tests` = missing
308
+ validation strategy, `best-practices` = process gaps like rollback or
309
+ migration, …).
158
310
 
159
- ---
311
+ Default roles are a plan-suited subset (`general`, `architecture`,
312
+ `edge-case-hunter`, plus `spec-compliance` when a spec is given);
313
+ `--role`/`--roles`/`--reviewer` and config `roles` override as usual. It shares
314
+ the model, context, output, telemetry and config flags of `rcl review`. `--post`
315
+ and `--ci` are not offered: there is no PR to post to, and plan findings are
316
+ judgment calls, not gates.
160
317
 
161
- ### `rcl discuss`
318
+ ### `rcl discuss <question>`
162
319
 
163
- Ask the models that flagged a finding a follow-up question — one round, reconstructed from a saved report. Useful when triaging: "is this actually exploitable given the sanitizer at line 40?" goes to the reviewers who raised it (especially valuable for **disputed** findings, where the report shows each model's position).
320
+ Ask the models that flagged a finding a follow-up question — one round,
321
+ reconstructed from a saved report. Useful when triaging, especially for
322
+ **disputed** findings, where the report shows each model's position.
164
323
 
165
324
  ```bash
166
325
  rcl review --staged --json-file report.json
@@ -171,966 +330,134 @@ rcl discuss --report report.json --finding f003 --context src/auth.ts "Does the
171
330
  rcl discuss --report report.json --finding f003 --models anthropic/claude-opus-5-5 "Summarize the strongest counterargument."
172
331
  ```
173
332
 
174
- Model-generated finding ids can collide; when `--finding <id>` is ambiguous the error lists `<id>:<n>` disambiguators. Findings in the below-threshold appendix are addressable too. Answers come back in parallel, respecting the configured `timeout`, `maxRetries`, and `reasoningEffort`. There is no session state: each `discuss` is one independent round built from the report file.
175
-
176
- ---
333
+ Model-generated finding ids can collide; when `--finding <id>` is ambiguous the
334
+ error lists `<id>:<n>` disambiguators. Findings in the below-threshold appendix
335
+ are addressable too. Answers come back in parallel, respecting the configured
336
+ `timeout`, `maxRetries`, and `reasoningEffort`. There is no session state: each
337
+ `discuss` is one independent round built from the report file.
177
338
 
178
339
  ### `rcl roles`
179
340
 
180
341
  ```bash
181
- rcl roles list # List all built-in roles
182
- rcl roles show <name> # Show system prompt and details for a role
183
- ```
184
-
185
- ---
186
-
187
- ### Start a fresh review
188
-
189
- ```bash
190
- rcl review owner/repo#123 --start-over
191
- ```
192
-
193
- This reviews the full current inputs again, even on an unchanged head or after an exhausted previous cycle. RCL archives the original native state and spending, creates a Harness review cycle, and starts at attempt 1 / round 1 with the normal 20-attempt / 15-round budget. Previous approvals, dismissals and reviewer responses stay historical. Explicit `--max-attempts` / `--max-rounds` select different limits for the new cycle; old overrides are not inherited.
194
-
195
- The command enables guarded launch and chooses private JSON/Markdown paths under the Git common directory when omitted. It prints the cycle and cumulative prior spending. Keep the files in place. A captured patch can use the same flag with `--for-pr owner/repo#123 --head-sha <captured-head>`. Full Harness evidence and a user/API actor credential are required; unbound local and attested starts are refused.
196
-
197
- An unfinished start resumes with the same command and operation, keeping spent claims. A durable terminal dispatch record marks completion, including a recorded failure. If the command output or acknowledgement is lost or uncertain, inspect the native current operation and retained launch/report before retrying; reuse a completed result instead of blindly repeating `--start-over`. An invocation after terminal completion represents a new request; identical command text cannot distinguish an acknowledgement retry from a later deliberate fresh request. No user-supplied operation ID or additional confirmation is required. Ordinary `rcl review owner/repo#123` discovers the local cycle and continues its existing budget; it does not replenish it. A concurrent fresh request cannot silently create another cycle after waiting for the first.
198
-
199
- Stop an active review through its recorded host task before replacing it. Late reports and verdicts remain bound to their original cycle. After checking reviewer health and exact-head freshness, admit the report with `converge-report`; use `converge-verdict --run-id <report-run-id>` for triage in a fresh cycle. All native, enforced and CI merge gates still apply. To complete missing reviewers while preserving successful work, use the supported recovery workflow instead of starting over.
200
-
201
- ### Guarded convergence launches
202
-
203
- ```bash
204
- rcl review change.patch --guarded-converge --converge-target repo-123 \
205
- --head-sha <captured-head> --base-sha <captured-base> --json-file fresh-report.json
206
- ```
207
-
208
- Keep this command foreground inside a persistent host task/session. It validates
209
- inputs, credentials and fresh output paths before claiming; native target
210
- ownership spans claim through completion. Do not call `converge-attempt` first
211
- or supply `--attempt`. RCL derives the next round from admitted state and rejects
212
- a conflicting `--round`. Existing caps and all spent attempts are retained.
213
- Guarded assignment order is stable and missing credentials never shrink the roster.
214
-
215
- Process and triage the original report before another launch. Unchanged reviewed
216
- inputs, including mere upstream base-tip movement, do not need another council.
217
- A real fix needs a fresh resulting head; unresolved native blockers refuse another
218
- launch. Unknown/failed dispatch requires an explicit `--retry-reason` after
219
- recovery, even if the head changed. This does not refund attempts or promise
220
- exactly-once provider billing. Credential presence cannot prove provider availability.
221
-
222
- For a 4.1.10, 4.1.11 or 4.1.12 aggregate-only completion, retain the original report and
223
- config and add `--retry-report original-report.json` with an explicit bounded
224
- `--retry-reason` and a fresh `--json-file` destination. RCL binds the exact original
225
- report, config, target, head, input, round, attempt, cycle and roster before deriving
226
- blocking-only health with the shared quorum policy. The new launch may review changed
227
- current inputs, but its original proof stays bound to those original identities. When
228
- roles or verifier defaults have since changed, RCL reconstructs the producer version's
229
- deterministic roster from that bound config; unknown, substituted or ambiguous identities
230
- still refuse before a claim. Healthy or ambiguous evidence refuses.
231
- The 4.1.10 producer predates review cycles and is accepted only when the retained
232
- report, native state and attempt state all remain cycle-free.
233
- An unadmitted source retries its pending round; an exact latest admitted source
234
- whose blocking health was inconclusive continues at the next native round. Its
235
- original admission, findings, verdicts and spent claims remain unchanged. The
236
- new claim retains the source bytes and binding; no history or budget is reset.
237
-
238
- For an ordinary pending launch whose coordinator is provably dead, supply an independently reconstructed immutable package that binds the original target, head, base, guarded input, attempt, round, PID and retained async artifact descriptors. RCL recomputes its production guarded-input digest and checks every binding under the target lock before it mutates native state.
239
-
240
- Use `--preview-pending` with either `--resume-pending` or `--finalize-pending-only` and `--ordinary-pending-package <path>` to authenticate that package with zero state, output or provider writes. Combined `--resume-pending` repeats the checks, marks the spent attempt failed with blocking outcome unknown, archives the exact retained async artifacts, and claims exactly the next checkpointed attempt under its explicitly bounded cap.
241
-
242
- Use `--finalize-pending-only` when recovery must stop at that failed/unknown finalization. Pass the unchanged cap plus the exact `nativeStateSha256` and `attemptStateSha256` returned by its preview as `--pending-native-sha256` and `--pending-attempt-sha256`. Apply archives the retained async artifacts idempotently and returns a source-bound receipt while leaving the attempt counter, cap, round history and next-free ordinal unchanged. Repeating the command reads back the same receipt; it never creates a successor claim, checkpoint, reviewer callback or provider call. Receipt readback validates either the exact finalized state or a monotonic successor against retained source snapshots. An exact legacy receipt without those snapshots is upgraded idempotently before a successor proceeds, without changing native state or accounting. A later guarded convergence process may claim the next attempt independently.
243
-
244
- When native triage reports `converged-dismissal-only` but Harness still reports
245
- `fixes_pending` with no actionable findings, an explicit recovery can authorize
246
- one additional review of the same inputs:
247
-
248
- ```bash
249
- rcl review change.patch --guarded-converge --converge-target repo-123 \
250
- --for-pr owner/repo#123 --head-sha <captured-head> --base-sha <captured-base> \
251
- --evidence-required --bound-fix-recovery <latest-admitted-run-id> \
252
- --json-file fresh-recovery-report.json
253
- ```
254
-
255
- Reuse the original review inputs and configuration, including the specification
256
- and reviewer roster; both the head and effective input digest must match the
257
- latest completed, healthy, delivered and admitted native launch. Its resolution
258
- must be `converged-dismissal-only` with `fixedThisRound: 0`. Use a PR target or
259
- an explicit `--for-pr` binding, and explicitly supply `--guarded-converge` and
260
- `--evidence-required`. This mode rejects `--start-over`, `--attest`,
261
- `--retry-report`, `--retry-reason`, git working-tree/staged reviews, and launch
262
- intents other than `review`.
263
-
264
- Before claiming or dispatching reviewers, RCL reads authenticated live Harness
265
- status and the selected run. The PR must be unmerged at the exact reviewed head;
266
- its advisory projection must be conclusive, `fixes_pending`, have zero actionable
267
- findings, and name that same run. The recorded run must match the repository, PR,
268
- head, convergence target and native round. Exposed classification and legacy
269
- pending fields must be clear, and an exposed bound classification protocol must
270
- be version 1. Unanswered, malformed or mismatched evidence refuses recovery.
271
-
272
- Harness currently exposes `fixes_pending` for the whole PR and does not provide
273
- proof identifying which convergence target owns a retained fix obligation.
274
- These checks establish the native/server mismatch; they cannot establish that
275
- another round on the selected target will clear it. Recovery therefore allows
276
- only one claimed attempt per target, PR and head in the current native attempt
277
- ledger, even if that attempt fails or a later run remains `fixes_pending`.
278
- The claim durably retains its source run, binding, server status and response
279
- hashes. Existing attempt and round caps still apply. Process and deliver the new
280
- report normally; matching enforced evidence and CI remain required for merging.
281
-
282
- `--launch-intent stop-upstream` never cancels review. `stop-review` and
283
- `retry-delivery` refuse new reviewer dispatch; cancel an existing review only
284
- through its retained host handle. Retry evidence with `rcl telemetry flush --run
285
- <run-id>`, not another council. Intent interpretation and finding adjudication
286
- remain human/agent decisions; native/enforced evidence and CI still gate merging.
287
-
288
- ### `rcl converge-attempt`
289
-
290
- Low-level accounting command retained for legacy callers. The generated
291
- `rcl-converge` skill instead uses `review --guarded-converge`; do not preclaim
292
- an attempt for that path.
293
- Each call atomically and durably consumes one per-target attempt under the
294
- repository's common Git directory, so the budget survives sessions, linked
295
- worktrees, and abrupt system restarts.
296
- New targets default to twenty attempts, but an explicit invocation can set any
297
- positive cap with `--max-attempts`. Omitting the flag on resume preserves the
298
- persisted cap. At the boundary, RCL refuses before provider calls and directs
299
- the workflow to ask the user; an approved continuation explicitly supplies a
300
- higher cap.
301
-
302
- At the skill level, `--max-attempts N` controls this machine launch budget.
303
- The separate `--max-rounds N` flag caps evidence rounds and is machine-enforced
304
- by `rcl converge-report` (default 15, valid range 2–99; rounds past 99 are
305
- impossible under any flag). The default is a consent boundary, not a stop: at
306
- 15 rounds the workflow asks the user, and an approved continuation supplies a
307
- higher `--max-rounds`.
308
-
309
- Full-fleet reviewer completion is not required. Reviewer health counts only
310
- the report roster's blocking seats, and a seat counts only when every one of
311
- its chunks succeeded. A round is conclusive when at least
312
- `max(2, ceil(2 × blocking seats / 3))` blocking seats completed, or more under
313
- a stricter configured `quorumFraction`. Each report records this as
314
- `stats.blockingHealth` (`seats`, `successful`, `required`, `conclusive`).
315
- Secondary, async and verification results keep their findings but never count
316
- toward the quorum, so the aggregate `stats.successfulReviews` /
317
- `stats.totalReviews` are informational only: 11 of 17 blocking seats plus one
318
- async success is 12/18 in aggregate yet inconclusive, because 12 blocking
319
- seats are required. This is the rule Harness applies to delivered evidence.
320
- Round closure uses the same seats and policy: bonus successes never cancel
321
- unfinished blocking reviewers. Every timeout or error must be disclosed, and a
322
- result below the requirement is inconclusive.
323
-
324
- Exit code 2 means the configured cap was exhausted and explicit continuation
325
- approval is required. Exit code 3 means attempt accounting itself failed
326
- (state, lock, Git, filesystem, or another infrastructure error); increasing
327
- the cap is not the remedy. With `--json`, failures are emitted as structured
328
- JSON on stderr. If the attempt is durably recorded but final lock release
329
- fails, the claim still succeeds with a warning so retrying cannot spend a
330
- second slot for the same intended launch.
331
-
332
- The short accounting mutex is fully written as a private owner file and then
333
- published with an exclusive hard link, which cannot replace an existing file
334
- or legacy directory. State contents and, where supported, their directory
335
- entry are synced before a claim succeeds. A dead owner is isolated through a
336
- token-scoped hard-link tombstone before another claimant can proceed; inode
337
- checks make that tombstone safe to remove after reclamation. Invalid or legacy
338
- ownerless locks fail closed, and timeout errors include the manual recovery
339
- path. When upgrading,
340
- an evidence ledger seeds only its highest recorded round: historical failed or
341
- missing-report launches cannot be reconstructed, while every claim after the
342
- machine state is created is counted exactly. The state remains a same-user
343
- local safety mechanism, not a tamper-proof store: deliberately deleting
344
- `.git/rcl-converge-attempts` is an explicit policy bypass.
345
-
346
- ```bash
347
- rcl converge-attempt --target owner-repo-123 # default/persisted cap
348
- rcl converge-attempt --target owner-repo-123 --max-attempts 10 # explicit override
349
- ```
350
-
351
- ---
352
-
353
- ### `rcl converge-report` and `rcl converge-verdict`
354
-
355
- The cross-round memory of a converge run, persisted in
356
- `.git/rcl-converge-runs/<target>.json`.
357
-
358
- `converge-report` dedupes one round's report JSON against every prior round of
359
- the run using a location-anchored finding identity (hash of file + category +
360
- line bucket, plus a line-overlap matcher — titles are deliberately ignored:
361
- models rephrase ~98% of them between rounds). Each finding is classified
362
- `new`, `repeat`, `suppressed` (previously dismissed — a dismissal is terminal
363
- on its evidence and fresh corroboration alone never reopens it), or `regating`
364
- (previously dismissed at non-critical severity, now sighted as critical —
365
- genuinely new evidence). The same call enforces the evidence-round cap:
366
- default 15, `--max-rounds` accepts 2–99, and rounds past 99 are impossible. Exit
367
- code 2 is the cap consent boundary; exit 3 is a state failure.
368
-
369
- Before reading or writing native state, `converge-report` derives blocking
370
- reviewer health from the report's roster and rows. An inconclusive report exits
371
- 4 (`report_health_inconclusive`) and is not admitted: its findings are not
372
- classified or triaged. The refusal names the completed blocking seats, the
373
- required count, every missing, failed or canceled seat, and the excluded
374
- secondary/async successes (with `--json`, as `error.reviewerHealth`). A report
375
- whose recorded `stats.blockingHealth` disagrees with its rows exits 4 with
376
- `report_health_unverifiable`. The report, its attempt and all earlier rounds
377
- stay unchanged. Continue with the same guarded review command plus
378
- `--retry-reason`: the guard records blocking health for every new launch,
379
- requires that explicit reason for an inconclusive one, and spends one more
380
- attempt at the same round without resetting caps or cycle history. Retained
381
- missing-reviewer recovery remains a separate workflow. Conclusive health does
382
- not replace triage, and merging still requires matching enforced review and CI.
383
-
384
- A report key must identify one canonical identity, status and suppression reason.
385
- `converge-report` refuses conflicting mappings with exit 3 before writing the
386
- round state, even when telemetry is off. Reports without finding keys use the
387
- canonical identity as a fallback and are subject to the same check. This leaves
388
- ambiguous older reports readable but not classifiable by this command. Preserve
389
- the original report and ledger for separately supported finding-ref recovery;
390
- rewriting published evidence or rerunning an unchanged council is not recovery.
391
- Identical mappings still deduplicate, and native ledger keys and verdicts do not
392
- change when new run-scoped report keys appear. Until the current run's
393
- classification is delivered, older runs' aliases cannot resolve its new report
394
- keys. A report without a current classification does not inherit prior native
395
- verdicts, even when its findings look unchanged.
396
-
397
- `converge-verdict` records triage outcomes per finding identity —
398
- `--fixed <key>` and `--dismissed '<key>=<reason>'` (both repeatable) — which
399
- drives later-round suppression and accrues the per-model precision history.
400
- Add `--fixed-reason '<key>=<reason>'` to attach the current fix explanation to
401
- an identity also passed to `--fixed`. A fixed verdict without this option clears
402
- any prior explanation; it never reuses an earlier dismissal reason. Each identity
403
- may appear once per command, and each fixed reason must be nonempty and unique.
404
- Once every gating identity of the current round is triaged, it also reports
405
- the round's resolution: `converged-dismissal-only` (everything dismissed,
406
- nothing fixed — the round converges on the spot, no confirmation round),
407
- `fixes-pending-fresh-round`, or `unresolved` with the identities still open.
408
-
409
- ```bash
410
- rcl converge-report --target rcl-30 --report report-r2.json --round 2 --json
411
- rcl converge-verdict --target rcl-30 --round 2 \
412
- --fixed 9787c6ea72ae778c \
413
- --fixed-reason '9787c6ea72ae778c=callback failures now have a distinct outcome' \
414
- --dismissed 'd2baf9675eb450f0=guard already exists'
415
- ```
416
-
417
- ### `rcl converge-stale`
418
-
419
- A healthy report that became materially stale before admission must be retained without
420
- assigning its findings or verdicts. The guarded launch refuses with `report_not_admitted`
421
- and prints the current head and effective input digest. Inspect the actual changes and
422
- use those exact values to preview a disposition:
423
-
424
- ```bash
425
- rcl converge-stale --preview --manifest stale.json --target repo-123 \
426
- --head <current-head> --input-sha256 <current-effective-input-sha256> \
427
- --report original.json --report-sha256 <original-sha256> \
428
- --reason "Committed changes and the current specification supersede this report"
429
- rcl converge-stale --apply --manifest stale.json --manifest-sha256 <reviewed-manifest-sha256>
430
- # After an interrupted apply, use the same immutable manifest:
431
- rcl converge-stale --resume --manifest stale.json --manifest-sha256 <reviewed-manifest-sha256>
432
- ```
433
-
434
- Preview writes only its exclusive manifest. Apply retains the original report and exact
435
- native snapshots, then atomically adds a digest-bound audit entry under native target ownership.
436
- Immutable shared objects and reconstructible snapshot templates avoid copying the full
437
- report and growing history for every correction. Incremental prefix hashing verifies
438
- the growing audit without repeatedly serializing all earlier entries.
439
- It does not admit findings, claim attempts, flush evidence, raise caps, or approve a PR.
440
- Resume is idempotent. Continue with the original `review --guarded-converge` invocation;
441
- it recomputes the real head/input digest and checks every retained receipt before claiming
442
- one normal attempt at the next native ordinal. Admission of the fresh report rechecks
443
- that retained history. The stale attempt stays spent.
444
-
445
- Before any stale disposition, unchanged inputs require normal admission; upstream tip movement alone is insufficient.
446
- After disposal, the original report stays historical even if its inputs return. Inspect and
447
- apply those inputs as another replacement to obtain a fresh review without reviving that report.
448
- Unknown outcomes, unhealthy reports, pending delivery, missing original evidence, changed
449
- state and unresolved earlier findings refuse safely. A disposition is bound to one exact
450
- replacement input. If inputs change again before continuation, inspect the new inputs and
451
- create a new preview and apply operation; it appends another inspected replacement for the
452
- same preserved report. Every earlier inspected replacement remains usable: returning to
453
- one needs only the guarded review command, not another disposition. Preview refuses a
454
- duplicate replacement plan. Missing or changed retained evidence, forged manifests and
455
- accidentally truncated audit history are refused before an attempt is claimed. Preview/apply
456
- bind the then-current native state; later legitimate native transitions remain authoritative.
457
- This local audit does not authenticate arbitrary edits to the entire native state file.
458
- Keep the audit directory with its original repository location: repository relocation and
459
- reconstruction of lost evidence are not supported by this command. Every receipt remains
460
- required, including earlier inspected alternatives. Native convergence, enforced review and CI remain required.
461
-
462
- ### `rcl converge-gap`
463
-
464
- A paid attempt and an admitted report round are separate counters. If an original
465
- later report already carries round 3 while native history ends at round 1, preserve
466
- that original. Do not relabel it, create an empty round 2, reset budgets, or launch
467
- reviewers again for bookkeeping.
468
-
469
- For one explicitly evidenced missing terminal report, preview a local audit:
470
-
471
- ```bash
472
- rcl converge-gap --preview --manifest gap.json --target rcl-81 \
473
- --gap-round 2 --admitting-round 3 --attempt 2 --run <original-run-uuid> \
474
- --report original-r3.json --report-sha256 <original-json-sha256> \
475
- --incomplete terminal-incomplete.md --incomplete-sha256 <original-evidence-sha256>
476
- rcl converge-gap --apply --manifest gap.json --manifest-sha256 <preview-manifest-sha256>
477
- # After interruption, reuse exactly the reviewed manifest and original sources:
478
- rcl converge-gap --resume --manifest gap.json --manifest-sha256 <preview-manifest-sha256>
479
- rcl converge-report --target rcl-81 --report original-r3.json --round 3 --json
480
- ```
481
-
482
- Preview reads bounded original files and the native/attempt ledgers; its only write
483
- is the requested exclusive manifest. An optional `--evidence <json-path>` supplies
484
- an array of additional `{ "path": "...", "sha256": "..." }` selections. Apply and
485
- resume accept only that manifest and its exact byte digest. They share target
486
- ownership with ordinary writers, retain exact original native, attempt and source
487
- bytes, and append audit checkpoints before admission becomes available. They leave
488
- rounds, findings, verdicts, severities, attempts used and caps unchanged. The
489
- controller exit stays `unknown`; supplied files do not prove global absence.
490
- Neither audit mode flushes the outbox or sends server events.
491
-
492
- This version supports one missing ordinal immediately before the selected original
493
- report, with an explicit spent record for both ordinals and every earlier ordinary
494
- round present from round 1. Histories with earlier gaps, including audited gaps,
495
- are unsupported. Migrated totals without those records, multiple gaps, altered
496
- sources and unsupported storage refuse.
497
- The later report keeps its original round, run and contents. Later discovery of
498
- the missing report needs separate explicit evidence recovery; an ordinary empty
499
- report cannot fill the reserved gap. Audit is local history, never reviewer health,
500
- convergence or gate approval. Existing v1 clients preserve its additive metadata
501
- and still refuse an unadmitted jump, but do not validate the new receipt protocol.
502
- Use this version for gap admission and recovery; no new backend capability is claimed.
503
-
504
- ---
505
-
506
- Structured findings include the recorded verifier model and explanation, when present,
507
- at both `findings` and `full` telemetry levels. The JSON report and wire envelope
508
- share normalization: blank values are absent, model identifiers are capped at 500
509
- Unicode code points, and explanations at 2,000. Missing legacy explanations are
510
- never generated. Unicode normalization forms and gate outcomes are unchanged.
511
-
512
- Quoted credential assignments are redacted through their closing quote or end of
513
- input, including multiline and unfinished values. Preliminary text truncation
514
- keeps only through the last whitespace in its bounded prefix (or only an ellipsis
515
- if there is none), preventing partial secrets from surviving redaction. The shared
516
- fixture `test/fixtures/verification-normalization.json` comes from allocator-one
517
- PR #8974 and pins the receiver contract. This corrects the quoted-value and
518
- preliminary-truncation behavior of RCL 3.6.0. Existing source reports are not
519
- rewritten to add explanations; legacy backfill retains its declared artifact
520
- scrubbing and deterministic source identity rules.
521
-
522
- ### `rcl telemetry status`, `flush` and `rejected`
523
-
524
- Evidence delivery to Harness (epic IO-12475). In a repository that carries
525
- `.harness-cli/config.json` and with a `harness login` (or `HARNESS_API_TOKEN` +
526
- `HARNESS_API_URL` in CI), every `rcl review` / `rcl review-plan` records the
527
- run on Harness after the report is written: the self-describing `run` header,
528
- one row per consensus finding (with its stable identity), one row per reviewer
529
- call (status, latency, token usage), the report's `stats`, and — at the default
530
- `full` level — the JSON and Markdown reports exactly as written, digest-checked
531
- by the server. The converge commands report their events (attempt claims, cap
532
- changes, processed rounds, verdicts, resolutions) the same way. Never sent:
533
- provider API keys, `GITHUB_TOKEN`, the Harness credential, environment
534
- variables, prompts or raw model answers; every free-text field is truncated
535
- and scrubbed for key-shaped strings before it leaves the process.
536
-
537
- The review never blocks on the network. A retryable delivery outage is
538
- spooled to `~/.rcl/outbox/<run id>/` and retried, with its original run id,
539
- at the start of every rcl command (bounded to five seconds) or by
540
- `rcl telemetry flush`. One dim status line says what happened:
541
- `Evidence recorded: <url>`, `Evidence spooled (Harness unreachable); run rcl
542
- telemetry flush`, or `Evidence not sent: <host> has not enabled review
543
- evidence for this organization`. `--evidence-required` exits 4 when the
544
- evidence is incomplete: the envelope was spooled or refused, the organization
545
- has evidence off, or a declared artifact did not land (a patch file then needs
546
- `--head-sha`, and the flag contradicts `--no-telemetry` / `RCL_TELEMETRY=off`).
547
- Only a spooled delivery is worth `rcl telemetry flush --run <id>`; the status
548
- line says which. Under `--ci` the gate verdict keeps its exit code and the
549
- evidence failure is printed beside it. The first delivery from a machine
550
- prints a one-time notice naming the host and what is sent
551
- (`~/.rcl/telemetry-notice` records it).
552
-
553
- Completed envelopes are validated locally before transmission. Two valid
554
- reversed line numbers are ordered before finding identity is allocated, with
555
- the original numeric pair retained as parser provenance. Missing, blank,
556
- boolean, negative, fractional or non-finite coordinates remain parser errors;
557
- valid sibling findings are preserved. Provenance delivery requires the server
558
- to advertise evidence protocol version 2.
559
-
560
- Local validation failures and terminal server refusals retain the exact JSON
561
- and Markdown bytes in `~/.rcl/quarantine/<run id>/` (or under `RCL_DATA_DIR`).
562
- The immutable manifest records original digests, delivery mode and diagnostics;
563
- distinct later delivery observations are appended separately. Interrupted or
564
- corrupted entries are reported as incomplete. Retention failure, including a
565
- read-only filesystem or the 1 GiB storage cap, is reported explicitly.
566
- `rcl telemetry rejected` inspects these files and verifies their digests without
567
- contacting Harness, flushing the outbox, changing native review accounting or
568
- applying recovery. Retained evidence is not server acknowledgment. Attested
569
- evidence is never queued for replay with ordinary credentials. Recovery of an
570
- already-completed historical run remains a separate operation.
571
-
572
- If an envelope is slow to acknowledge, use `telemetry flush --envelope-timeout-ms`
573
- with an integer from 1 to 120000 milliseconds. It changes only the envelope POST
574
- timeout (default: 10000 ms); artifact transfers keep their 120000 ms ceiling,
575
- and ordinary reads and events keep their existing timeouts. Shorter caller
576
- deadlines and known attested credential lifetimes still apply. A timeout leaves
577
- the evidence queued for a later flush; this option does not rerun reviewers.
578
-
579
- ```bash
580
- rcl telemetry status # level, credential source, what waits in the outbox
581
- rcl telemetry flush # deliver everything spooled, to completion
582
- rcl telemetry flush --run <run id> # one run only
583
- rcl telemetry flush --run <run id> --envelope-timeout-ms 120000
584
- rcl telemetry rejected --run <run id> --json # inspect one retained original
585
- rcl review owner/repo#7 --no-telemetry # keep this review on the machine
586
- ```
587
-
588
- ```yaml
589
- # .review-council.yml
590
- harness:
591
- telemetry: full # off | envelope | findings | full (default)
592
- parseFailures: false # send a parse-failed call's raw answer (scrubbed, 32 KB cap)
593
- ```
594
-
595
- ---
596
-
597
- ### `--attest`: attested reviews from the gate workflow
598
-
599
- Only evidence recorded from the organization's own gate workflow on GitHub
600
- Actions counts for the enforced gate (epic IO-12475, section 4.1). Inside
601
- such a job — one that grants `id-token: write` — `rcl review owner/repo#N
602
- --attest` asks the runner for the job's OIDC token with the Harness origin as
603
- audience, exchanges it at `POST /api/v1/reviews/attest` for a **run-bound
604
- credential** (`rbc_…`, thirty minutes, one rcl run id, valid while the
605
- Actions run is in progress), and records the review under it: the envelope,
606
- its artifacts, the model keys and the model stats all travel with that
607
- credential and nothing else. Harness verifies the token, requires the
608
- workflow file to be on the organization's gate allow-list at its default
609
- branch, re-reads the pull request through its GitHub App and stores the run
610
- only if the reviewed head is the pull request's current head and the PR is
611
- not from a fork — the run is then `credential_kind: attested`.
612
-
613
- `--attest` fails loudly, before any token is requested or any reviewer is
614
- paid: outside Actions (no `ACTIONS_ID_TOKEN_REQUEST_URL` / `_TOKEN`), without
615
- `HARNESS_API_URL`, off a pull request target, with a telemetry level other
616
- than `full` (from `RCL_TELEMETRY` or the project config — an attested run
617
- carries its full report), or when Harness refuses the exchange (the refusal
618
- names the reason: `workflow_not_allowed`, `reviews_disabled`,
619
- `run_not_in_progress`, …). It never falls back to `HARNESS_API_TOKEN` or the
620
- stored login, it implies `--evidence-required`, and nothing recorded under the
621
- run-bound credential is ever spooled — the credential does not outlive the
622
- workflow run. A review that outlasts most of the credential's thirty minutes
623
- mints it again for the same run id before delivery. Pair it with
624
- `--expect-head-sha` so a moved pull request fails fast instead of being
625
- refused at ingest.
626
-
627
- If the completed envelope's POST becomes unavailable, delivery first reads a
628
- restricted receipt for that same run with its still-live attested credential.
629
- A matching receipt resumes artifact delivery without another POST; only an
630
- explicit 404 permits replay of the exact serialized envelope. Recovery allows
631
- at most three POSTs including the original, with an additional 20-second recovery
632
- deadline bounded by credential expiry. Conflicts, rejected or unanswered receipts,
633
- expiry and exhausted retries stop recovery. No reviewer is called again and no
634
- ordinary credential is substituted. Failed delivery retains bounded, redacted
635
- transport diagnostics, including the initial error cause, with the original
636
- recovery evidence.
637
-
638
- Operators recovering the gate's encrypted GitHub artifact must use the
639
- [review evidence recovery runbook](https://github.com/allocator-one/rcl/blob/main/docs/review-evidence-recovery.md).
640
- Recovery requires the separately held, version-mapped private key and does not
641
- confer review or merge approval.
642
-
643
- ```yaml
644
- # .github/workflows/review_gate.yml (dispatched by Harness for one pull request)
645
- permissions:
646
- id-token: write
647
- contents: read
648
- jobs:
649
- review:
650
- runs-on: ubuntu-latest
651
- env:
652
- HARNESS_API_URL: https://harness.infra.one
653
- steps:
654
- - run: npm i -g review-council
655
- # Inputs reach the shell through the environment, never by expression
656
- # interpolation into the command line.
657
- - env:
658
- REPO: ${{ inputs.repo }}
659
- PR: ${{ inputs.pr }}
660
- HEAD_SHA: ${{ inputs.head_sha }}
661
- run: rcl review "$REPO#$PR" --attest --expect-head-sha "$HEAD_SHA" --ci
342
+ rcl roles list # all built-in roles
343
+ rcl roles show <name> # system prompt and details for a role
662
344
  ```
663
345
 
664
- ---
665
-
666
- ### `rcl evidence status` and `rcl evidence show`
667
-
668
- What Harness holds — never the client's own claim. `rcl evidence status`
669
- prints the gate status Harness computed for a pull request: its known head,
670
- the advisory and enforced projections (status, the rounds behind them, the
671
- actionable findings still open) and the merge decision once it merged. The
672
- exit code is the contract the skills gate on:
673
-
674
- | exit | meaning |
346
+ | Role | Focus |
675
347
  | --- | --- |
676
- | 0 | the judged projection (advisory by default, `--enforced` on request) is `converged` |
677
- | 1 | any other status: `none`, `stale`, `unverified`, `inconclusive`, `fixes_pending`, `unresolved` |
678
- | 2 | the pull request could not be named |
679
- | 3 | the read could not be answered: no credential, evidence off for the organization, unknown pull request, refused credential, unreachable host — never reported as "not converged" |
680
-
681
- `rcl evidence show <run id>` prints one recorded run: header and verification,
682
- credential tier and runner, reviewer health, artifact state, and every finding
683
- with its identity and gating reason. Each **Verification** block shows the
684
- recorded result, actual model and complete stored explanation. Recovered notes
685
- identify the original report digest and recovery time. Legacy results without
686
- a note say `Explanation not recorded`; `unavailable` remains distinct from
687
- `refuted`. A separate **Triage** block shows the recorded judgment, reason,
688
- actor, round and recording time when available. Missing attribution stays
689
- unknown, and a displayed judgment does not assert current gate resolution.
690
-
691
- Text preserves multiline explanations while scrubbing credential-shaped text
692
- and terminal controls. `--json` preserves the API object's semantic values with
693
- safe control-character escaping. Older servers may omit the optional evidence
694
- and attribution fields. Local Markdown reports also show verifier explanations
695
- for kept findings and the rendered below-threshold appendix; the appendix still
696
- shows at most 20 findings and points to JSON for omitted entries.
697
-
698
- The reads use the same credential rules as delivery — the stored `harness
699
- login`, or `HARNESS_API_TOKEN` + `HARNESS_API_URL` in CI, the token sent to
700
- its own host only — and need `reviews:read`. They do not depend on the
701
- telemetry level: switching delivery off does not blind them.
702
- Both commands send only their normal GET requests. They neither fetch an
703
- artifact per finding nor flush pending retry deliveries, run reviewers or
704
- record verdicts. Use `rcl telemetry flush` explicitly to retry queued delivery.
705
-
706
- ```bash
707
- rcl evidence status # error: name the pull request
708
- rcl evidence status 8524 # against the current checkout's origin remote
709
- rcl evidence status '#8524' --enforced
710
- rcl evidence status allocator-one/rcl#42 --json
711
- rcl evidence status https://github.com/allocator-one/rcl/pull/42
712
- rcl evidence show 01a08032-0838-76db-ade3-1990f6e54072
713
- ```
714
-
715
- ### `rcl evidence recover-run`
716
-
717
- Recover one original completed asserted run without rerunning review, changing its
718
- UUID or rewriting its JSON/Markdown artifacts. This is delivery only: it does not
719
- admit a native round, record verdicts, change attempts/precision, flush unrelated
720
- outbox entries or confer gate approval. Semantic claim splits are not part of this
721
- command.
722
-
723
- First prepare an exclusive manifest using explicit original source pins:
724
-
725
- ```sh
726
- rcl evidence recover-run --preview --manifest original-run.json \
727
- --run "$ORIGINAL_RUN_ID" --for-pr owner/repo#123 --head "$ORIGINAL_HEAD_SHA" \
728
- --report-json /absolute/original/report.json --report-sha256 "$JSON_SHA256" \
729
- --report-md /absolute/original/report.md --markdown-sha256 "$MARKDOWN_SHA256" \
730
- --original-mode asserted --json
731
- ```
732
-
733
- Markdown is optional; its path and digest must be supplied together. The report
734
- must retain its complete modern header, explicit finding identities, and an exact
735
- PR or PR-bound patch target. Original run UUID bytes are preserved; only generated operation IDs are canonical lowercase. Recovery
736
- refuses other spellings instead of rewriting identity. Headerless imports, CI/attested/backfill originals,
737
- unknown mode fields, missing sources and ambiguous bindings refuse. The explicit
738
- asserted mode is an **operator assertion**, checked against the retained non-CI
739
- runner metadata; it is not cryptographic proof of the original invocation. A
740
- run-bound credential is never converted to a normal login.
741
-
742
- Preview validates all local inputs before HTTP and writes only the explicitly
743
- named manifest. Its formatted UTF-8 representation, including the final newline,
744
- must fit within 8 MiB so apply and resume can read it. Oversized prepared evidence
745
- refuses before HTTP; the complete manifest is checked again before publication.
746
- It makes scoped authenticated GET requests, requires
747
- `meta.original_report_recovery_version: 1` and evidence protocol 2, and binds the
748
- host and server organization. An older or disabled server remains unsupported.
749
- Inspect the manifest's exact envelope, source digests, transformations and retained
750
- content limitations, then use the digest printed by preview:
751
-
752
- ```sh
753
- rcl evidence recover-run --apply --manifest original-run.json \
754
- --manifest-sha256 "$MANIFEST_SHA256" --json
755
-
756
- # After interruption or an uncertain acknowledgment, reuse that same operation.
757
- rcl evidence recover-run --resume --manifest original-run.json \
758
- --manifest-sha256 "$MANIFEST_SHA256" --json
759
- ```
760
-
761
- Apply starts an adjacent `original-run.json.journal` directory with append-only,
762
- fsynced checkpoints before every remote write. Each checkpoint has an
763
- 8 MiB + 1 KiB read/write bound, retaining the manifest's full prose audit and
764
- reserving space for the checkpoint wrapper.
765
- Oversized checkpoints refuse before publication. Apply and resume require the
766
- journal to be effective-user-owned mode 0700, on the supported local storage
767
- listed below, with protected ancestors and no harmful or unknown ACL grants.
768
- Apply checks the selected parent before creating the journal exclusively; resume
769
- never creates missing state or repairs permissions. Each append checks the
770
- selected journal's device/inode identity before writing. This detects replacement
771
- between checkpoints; it does not claim protection against concurrent privileged
772
- or same-user path manipulation. Resume requires that directory;
773
- it never generates a replacement operation. A dedicated
774
- `RCL_DATA_DIR/original-run-recovery-locks` directory serializes applies for the same
775
- host/organization/run, including different manifest paths. Native accounting
776
- locks and stores are not used. Locks with incomplete or unverifiable ownership
777
- fail closed and require inspection; never delete a live lock.
778
-
779
- The lock uses a unique registration per acquisition and automatically removes a
780
- dead participant only when its PID is absent in the same kernel boot and PID
781
- namespace. A reboot, foreign scope, PID reuse or uncertain liveness requires
782
- inspection or a bounded retry; age alone never permits deletion. Legacy private
783
- `.lock`/`.reclaim` state is refused, not migrated or bypassed. Empty `.bakery`
784
- registries remain in place, and an interrupted unpublished `.tmp` is harmless.
785
- Concurrent older private recovery clients are unsupported.
786
-
787
- This protocol requires coherent ordinary local storage: local APFS/HFS with
788
- ownership enabled on macOS, or ext2/3/4, tmpfs, XFS or Btrfs on Linux. Network,
789
- FUSE, overlay and unknown filesystems are unsupported. macOS refuses an ambiguous
790
- system mount listing, including an ambiguous entry for an unrelated mount. It
791
- uses the existing directory's filesystem name and mountpoint from bounded
792
- `df --libxo json` output, matched to one exact mount-table entry; firmlink or case
793
- aliases never select an ancestor's flags. Unavailable structured inspection or
794
- unmatched/ambiguous attribution refuses without a fallback. It
795
- rejects ACL allow grants or unrecognized ACL output; restrictive deny-only ACLs
796
- are allowed. The root must already be private (effective-user-owned
797
- mode 0700), and ancestors must be protected against other users' writes, apart
798
- from root-owned sticky temporary directories. Missing private directories are
799
- created; existing permissions are never silently repaired. These checks do not
800
- certify arbitrary filesystem implementations or protect against hostile code
801
- running as the same user or a privileged administrator.
802
-
803
- Every invocation rechecks source bytes and the reviewed manifest. Before retrying,
804
- it reads and compares all immutable run header/settings/findings/calls/artifact
805
- declarations, then fetches exact raw artifact bytes and verifies their digest and
806
- size. A POST duplicate receipt or a stored-artifact flag alone is insufficient.
807
- Lost POST/PUT acknowledgment can finish through exact reads; otherwise the same
808
- journal remains incomplete. Changed bytes, organization, header, findings or calls
809
- refuse. A torn last checkpoint is retained and bound into the next append; it is
810
- never treated as acknowledgment. Preserve the manifest, journal and sources until
811
- independent reconciliation is complete.
812
-
813
- Only unpaired UTF-16 units in actual finding `title`, `description` or
814
- `suggestedFix` prose receive a derived wire spelling: visible ASCII `\uD800`,
815
- using uppercase hex. Valid surrogate pairs and existing literal backslash-u text
816
- remain unchanged. Each transformation records the original JSON path, code-unit
817
- index and byte offset. Keys, identifiers, descriptors and structural fields cannot
818
- receive that transformation. Existing producer `[redacted]` literals in finding
819
- prose stay unchanged and are listed as retained-content limitations; markers in
820
- protected bindings, or any newly required secret redaction, refuse. Markdown
821
- requiring redaction is unsupported. No fresh-report normalizer or backfill UUID
822
- is applied to the original.
823
-
824
- An original that contains an escaped C0 control or literal DEL in that same
825
- finding prose is unsupported by default. When the retained report must be
826
- represented, select the explicit, versioned mode during preview:
827
-
828
- ```sh
829
- rcl evidence recover-run --preview --manifest original-run.json \
830
- --run "$ORIGINAL_RUN_ID" --for-pr owner/repo#123 --head "$ORIGINAL_HEAD_SHA" \
831
- --report-json /absolute/original/report.json --report-sha256 "$JSON_SHA256" \
832
- --original-mode asserted --original-prose control-code-units-v1 --json
833
- ```
834
-
835
- This selection never rewrites the retained artifact or its digest. It projects
836
- only allowed finding-prose controls to visible uppercase `\uXXXX` transport text
837
- and records a version-1 `control_code_unit` transformation with the source path,
838
- code-unit offset and original UTF-8 byte offset. Short JSON escapes
839
- (`\b`, `\f`), `\uXXXX` escapes and literal DEL are covered. Literal tab, LF
840
- and CR are valid JSON prose and remain literal; they are not transformed. Raw
841
- unescaped C0 is invalid JSON; controls in keys, descriptors, identifiers,
842
- locations or other structural fields refuse. Existing surrogate records keep
843
- their prior shape.
844
-
845
- The mode is intentionally unavailable against older servers. Preview requires
846
- the usual recovery/evidence metadata **and**
847
- `meta.original_prose_representation_version: 1`; without it, it makes the scoped
848
- capability GET but writes no manifest and sends no delivery request. Apply and
849
- resume use the selected, manifest-pinned representation and repeat that check.
850
- Inspect the visible projection and transformation records before applying. A
851
- successful delivery still does not repair native accounting or establish a fresh
852
- review gate.
853
-
854
- Existing transport scrubbing, limits, call summaries and duration rounding remain
855
- explicitly recorded derivations. Two valid reversed integer coordinates may use
856
- `report_projection` provenance bound to the original coordinates and JSON digest;
857
- original finding identities are never recomputed from the normalized interval.
858
- Unsupported coordinate types refuse rather than being guessed.
859
-
860
- Exit codes: `0` means preview prepared or delivery independently verified; `2`
861
- means invalid/unavailable local selection; `3` means remote capability/read/delivery
862
- unanswered; `4` means conflicting destination, evidence or journal bindings; `5` means local durable
863
- journal/lock persistence failed. JSON diagnostics include the stage and next step.
864
- Even successful delivery does not establish current-head review freshness,
865
- convergence, attestation or historical accounting repair.
866
-
867
- ### `rcl evidence recover-finding`
868
-
869
- Recover one recorded finding whose report identity collided, using its retained
870
- native convergence identity. Preview is the default; `--submit` explicitly
871
- posts one attributed `finding_identity_corrected` event. This unpaid command
872
- never calls reviewers, claims an attempt, changes a round or verdict, updates
873
- precision accounting, flushes/spools an outbox, or rewrites native history.
874
-
875
- ```bash
876
- rcl evidence recover-finding --target "$TARGET" --run "$RUN_ID" \
877
- --report-sha256 "$ORIGINAL_REPORT_SHA256" --finding-ref f002 \
878
- --identity "$NATIVE_IDENTITY" --for-pr example/project#42
879
- # Inspect the preview, then repeat with --submit if authorized.
880
- ```
881
-
882
- Run it in the checkout holding the retained `.git/rcl-converge-runs` state.
883
- All selectors are mandatory. The command reads the server run and checks its
884
- ID, original report digest, repository/PR, convergence target and round. The
885
- explicit native identity must match the selected ref's exact file, category
886
- and line span. Its latest sighting, verdict and native round-to-run binding
887
- must belong to that same recorded round. Missing or ambiguous evidence is an
888
- error, never a reason to reconstruct state, reset counters or rerun review.
889
-
890
- The event includes the digest of the exact retained state bytes and the minimal
891
- native identity/verdict assertion, not private verdict reasons. Harness must
892
- already hold the corresponding canonical verdict on that same run and round;
893
- the command does not create one. Harness validates the bindings, but trusts the
894
- authenticated actor's native mapping assertion. Neither the command nor the
895
- server claims to have retrieved or verified the original report bytes. Normal
896
- transport scrubbing applies; if redaction or truncation would change an exact
897
- binding, both preview and submission refuse it rather than print the raw value
898
- or silently rebind the evidence.
899
-
900
- Uses the normal Harness credential rules, requiring `reviews:read` for preview
901
- and also `reviews:write` for submission. Requires backend support for the new
902
- event; an older backend rejects it without changing history. Conflicts and
903
- network errors fail visibly, without automatic retries or spooling. An
904
- acknowledgment (exit 0) is not a convergence verdict; separately inspect
905
- `rcl evidence status` when authorized. Originals and sibling sightings remain
906
- unchanged, and the correction is not inherited by another run. A correction can
907
- reopen a previously suppressed critical finding if its canonical verdict is not
908
- critical. A `fixed` correction is audit-only for that run and does not clear
909
- `fixes_pending`.
910
-
911
- ### `rcl evidence retriage-finding`
912
-
913
- Record a **new explicit dismissal** for one existing finding at its actual
914
- recorded severity. This repairs a historical grouped-severity dismissal without
915
- replaying the report or guessing a native identity after spans have drifted.
916
- It is not an automatic upgrade of the old verdict or a gate waiver.
917
-
918
- ```bash
919
- rcl evidence retriage-finding --target "$TARGET" --run "$RUN_ID" \
920
- --report-sha256 "$ORIGINAL_REPORT_SHA256" --finding-ref f002 \
921
- --for-pr example/project#42 --reason-file ./retriage-reason.txt
922
- # Inspect the preview and source-backed reason; repeat with --submit if authorized.
923
- ```
924
-
925
- All selectors and the UTF-8 reason file are required. The nonblank reason is
926
- limited to 2000 characters; malformed UTF-8 is refused. The command reads the
927
- run, checks its PR, head and stored report metadata, and requires one exact
928
- finding ref with a unique `report:<run-id>:<key>` identity (RCL 3.3+). A native
929
- convergence run must match the selected target and carry a positive round. A
930
- standalone gate run without convergence metadata is accepted only when Harness
931
- records it as an attested, current-head, same-repository CI review. Its PR, run,
932
- report digest and finding ref provide the server binding; the selected target
933
- remains the event's informational label. The required event round is `1` as a
934
- wire-protocol value only, not a claim that the attested review participated in
935
- native convergence. Legacy unqualified or
936
- colliding keys are refused, because a verdict on those keys could affect an
937
- unrelated sighting. The digest is compared with the stored artifact metadata;
938
- the command does not retrieve or claim to verify the original report bytes.
939
- It does not need or read native convergence state. `recover-finding` retains
940
- its separate exact-span/native-verdict checks unchanged.
941
-
942
- Preview performs only the run read and labels the standalone-attested transport
943
- round when applicable. `--submit` appends one fresh, authenticated
944
- `verdicts_recorded` event under the original report key, on the selected run,
945
- with the reason and recorded severity. No existing report, verdict,
946
- mapping, attempt count, model statistics or native file is rewritten. No
947
- reviewers run, and no outbox is flushed or spooled. Scrubbing that would alter
948
- the selected evidence or reason causes refusal before submission. The existing
949
- Harness API and its critical-dismissal check remain unchanged.
950
-
951
- Uses the normal Harness credential rules (`reviews:read`, plus `reviews:write`
952
- to submit). A refusal or uncertain response fails visibly, without automatic
953
- retry. Each submission is a fresh attributed judgment, not an idempotent replay
954
- of an old event; inspect server evidence before retrying an uncertain write.
955
- Exit 0 means preview succeeded or exactly one new insertion was acknowledged, **not** that the
956
- gate converged. Independently run `rcl evidence status` for the exact PR.
957
-
958
- ### `--for-pr` on a patch-file review
959
-
960
- A patch-file review (`rcl review changes.patch`) carries no repository or pull
961
- request, so Harness records it as a `patch` run it cannot verify or count for
962
- any gate. `--for-pr owner/repo#N` (or a pull request URL) names the pull
963
- request the patch was taken from (`RCL_FOR_PR` in the environment does the
964
- same for patch files): the run is bound to that pull request and its
965
- `--head-sha` — required with the flag — is verified against the pull
966
- request's head. Owner and repository are lower-cased, as GitHub reads them. Counting the round for that pull request's
967
- gate is the server half (IO-12585); until it lands, `rcl evidence status`
968
- still reads `stale`/`none` for patch-file loops. The flag is refused on PR and
969
- git-mode targets, which name their own pull request or checkout. `rcl-converge` passes `--converge-target`, `--round`
970
- and `--attempt` on every round and adds `--for-pr` when a pull request loop
971
- reviews a patch file taken from the pull request (a pull request target names
972
- its own). A converge
973
- target of the `owner/repo#N` form attributes the run the same way; a slug such
974
- as `rcl-7` does not.
348
+ | `general` | Comprehensive review covering all dimensions |
349
+ | `security-auditor` | Auth, injection, XSS, CSRF, IDOR, and sensitive data exposure |
350
+ | `performance-engineer` | N+1 queries, caching, algorithmic complexity, and memory efficiency |
351
+ | `api-design` | API contracts, breaking changes, REST/gRPC conventions |
352
+ | `test-coverage` | Missing tests, edge cases, flawed test logic |
353
+ | `dx-critic` | Readability, naming, documentation, and developer ergonomics |
354
+ | `architecture` | Module boundaries, coupling, and architectural patterns |
355
+ | `bug-hunter` | Logic errors, null paths, race conditions, off-by-one |
356
+ | `accessibility-auditor` | WCAG compliance, ARIA roles, keyboard navigation |
357
+ | `spec-compliance` | Checks the implementation against a spec or plan file |
358
+ | `regression-hunter` | Changed defaults, weakened guards, and lost behavior |
359
+ | `dependency-hygiene` | Unnecessary dependencies, external requests, and privacy leaks |
360
+ | `edge-case-hunter` | Boundary values, unusual inputs, and failure paths |
975
361
 
976
- ```bash
977
- rcl review round-3.patch --head-sha "$HEAD" --base-sha "$BASE" --for-pr allocator-one/rcl#42 \
978
- --converge-target allocator-one/rcl#42 --round 3 --attempt 3 --evidence-required
979
- ```
362
+ By default every role runs except `spec-compliance`, which runs only when a
363
+ spec is supplied. `general` runs on each primary model; specialist roles are
364
+ spread round-robin across primary and secondary models. The default council
365
+ therefore schedules 13 reviewer seats, or 14 when a spec enables
366
+ `spec-compliance`. Seats on the primary models (Opus 5.5 and Sol) form the
367
+ blocking lane that reviewer health and quorum count; Gemini receives specialist
368
+ seats only, in the secondary lane, whose results keep their findings but do not
369
+ count toward quorum. The async reviewer and the verifier are separate lanes.
370
+
371
+ The first repository rules file found in the working directory — `AGENTS.md`,
372
+ `CLAUDE.md`, `CONTRIBUTING.md`, `.github/CONTRIBUTING.md`, `DEVELOPMENT.md` or
373
+ `docs/CONTRIBUTING.md`, in that order — is supplied to every reviewer as shared
374
+ context.
375
+ `project-rules` and `dead-code` are no longer built-in roles; remove them from
376
+ explicit role lists (unknown roles are skipped with a warning) unless you define
377
+ a custom role with that name.
980
378
 
981
379
  ### `rcl models`
982
380
 
983
381
  The tool's own memory of which reviewers earn their seat. Every reviewer call
984
382
  and every `converge-verdict` outcome accrues in a cross-run store at `~/.rcl`
985
- (`RCL_DATA_DIR` overrides; deliberately not under /tmp, so history survives
986
- converge-state cleanup). `rcl models` prints, per model over a trailing 90-day
987
- window: triage precision (share of its supported findings the converge loop
988
- verified and fixed rather than dismissed), triage volume, call volume,
989
- dead-call rate, p50 latency — and the consensus **weight** the model earns:
990
- `0.5 + precision`, clamped to [0.5, 1.5], neutral (1) below 20 triaged
991
- outcomes. Weights scale each model's consensus vote in report confidence and
992
- in consensus gating, so persistently noisy models lose gating power
383
+ (`RCL_DATA_DIR` overrides). `rcl models` prints, per model over a trailing
384
+ 90-day window: triage precision (the share of its supported findings the
385
+ convergence loop verified and fixed rather than dismissed), triage volume, call
386
+ volume, dead-call rate, p50 latency — and the consensus **weight** the model
387
+ earns: `0.5 + precision`, clamped to [0.5, 1.5], neutral (1) below 20 triaged
388
+ outcomes. Weights scale each model's consensus vote in report confidence and in
389
+ consensus gating, so persistently noisy models lose gating power
993
390
  automatically; the applied weights are visible per finding
994
391
  (`consensus.weightedScore` / `consensus.modelWeights`) and per run
995
392
  (`stats.modelWeights`) in the report JSON.
996
393
 
997
- ```bash
998
- rcl models # table over the trailing 90 days
999
- rcl models show --window 30 --json
1000
- rcl models seed --from ~/recovered-rcl-artifacts # backfill from reports + converge ledgers
1001
- ```
1002
-
1003
- ---
1004
-
1005
- Since 3.1 the table merges the organization's window from Harness
1006
- (`GET /api/v1/reviews/model-stats`, the server-side `rcl models` over every run
1007
- the org recorded, backfilled history included) with this machine's store: for a
1008
- model the server holds at least 20 outcomes for, the server's weight is used
1009
- (`source: server`); below that the local store decides (`local`); a model
1010
- neither knows enough about keeps the neutral weight (`neutral`). Reviews weight
1011
- consensus the same way, asking the server with a three-second bound and falling
1012
- back to the local store when it cannot answer. `--local` shows this machine's
1013
- view alone; `--json` carries `server` (host, window, rows) and `weights` with
1014
- their `source`.
1015
-
1016
- ```bash
1017
- rcl models # org-wide where Harness has enough history, local otherwise
1018
- rcl models show --local # this machine's store only
1019
- rcl models show --window 30 --json
1020
- ```
1021
-
1022
- ### `rcl telemetry backfill`
1023
-
1024
- Recovered history becomes day-one evidence on Harness. `rcl telemetry backfill
1025
- --from <dir> --repo <owner/repo>` reads the pre-3.0 `rcl-report-*.json`
1026
- reports and `rcl-converge-*-ledger.md` ledgers in a directory (the same layout
1027
- `rcl models seed` reads) and posts each report as a run with `provenance:
1028
- backfill`: a synthesized header bound to the named repository (target `patch`,
1029
- the report bytes as the digest, a runner claim naming this command), the
1030
- report's findings with their stable identities, its reviewer calls, and the
1031
- report files as artifacts. Ledger bullets matched to a round's findings become
1032
- `verdicts_recorded` events. Run ids are UUIDv5 of `(host, repo, sha256 of the
1033
- report)` and event ids derive from them, so running the backfill twice reports
1034
- the second run as `0 new` — nothing is duplicated. Backfilled runs count for
1035
- model stats and analytics and never enter a gate decision.
394
+ With Harness evidence configured, the table merges the organization's window
395
+ from Harness (`GET /api/v1/reviews/model-stats`, computed over every run the
396
+ organization recorded) with this machine's store: for a model the server holds
397
+ at least 20 outcomes for, the server's weight is used (`source: server`); below
398
+ that the local store decides (`local`); a model neither knows enough about
399
+ keeps the neutral weight (`neutral`). Reviews weight consensus the same way,
400
+ asking the server with a three-second bound and falling back to the local store
401
+ when it cannot answer.
1036
402
 
1037
403
  ```bash
1038
- rcl telemetry backfill --from ~/recovered-rcl-artifacts --repo allocator-one/allocator-one --dry-run
1039
- rcl telemetry backfill --from ~/recovered-rcl-artifacts --repo allocator-one/allocator-one
404
+ rcl models # org-wide where Harness has enough history, local otherwise
405
+ rcl models show --window 30 --json # `server` (host, window, rows) and `weights` with their `source`
406
+ rcl models show --local # this machine's store only
407
+ rcl models seed --from ~/recovered-rcl-artifacts # backfill the local store from reports and converge ledgers
1040
408
  ```
1041
409
 
1042
- ### `rcl telemetry recover-refutations`
410
+ ### `rcl evidence` and `rcl telemetry`
1043
411
 
1044
- Recover the original verifier model and explanation from retained modern and
1045
- pre-header reports. The default command only reads files and makes authenticated
1046
- GET requests. It writes a private manifest for review; `--apply` is a separate,
1047
- explicit step. No reviewer is rerun, no triage events are invented, and retry
1048
- queues and source reports remain untouched.
412
+ These commands read and deliver review evidence on Harness. Details, exit codes
413
+ and the recovery runbooks are in
414
+ [Telemetry and evidence](https://github.com/allocator-one/rcl/blob/main/docs/telemetry-and-evidence.md).
1049
415
 
1050
- ```bash
1051
- # Offline discovery, also usable before the compatible Harness backend is deployed.
1052
- rcl telemetry recover-refutations --inventory-only --manifest inventory.json
416
+ | Command | Purpose |
417
+ | --- | --- |
418
+ | `rcl evidence status <pr> [--enforced]` | Gate status Harness computed for a pull request; exit 0 only when the judged projection is `converged` |
419
+ | `rcl evidence show <run-id>` | One recorded run: header, reviewer health, artifacts, findings with identity, gating reason and verdict |
420
+ | `rcl evidence recover-run` | Preview, apply or resume delivery of one original asserted run |
421
+ | `rcl evidence recover-finding` | Preview or submit a correction of one recorded finding identity |
422
+ | `rcl evidence retriage-finding` | Preview or submit a fresh dismissal of one finding at its recorded severity |
423
+ | `rcl telemetry status` | Telemetry level, credential source and spooled deliveries |
424
+ | `rcl telemetry flush [--run <id>]` | Deliver spooled evidence |
425
+ | `rcl telemetry rejected` | Inspect retained rejected evidence without delivering it |
426
+ | `rcl telemetry recover-reviewer --target <t> --run <id>` | Redeliver one exact retained terminal reviewer report without restarting reviewers or verification |
427
+ | `rcl telemetry backfill` | Post recovered pre-3.0 reports and ledgers as backfill evidence |
428
+ | `rcl telemetry recover-refutations` | Discover original verifier explanations and write a reviewed recovery manifest |
429
+
430
+ ### Convergence commands
431
+
432
+ Convergence-loop state lives under the repository's common Git directory. The
433
+ commands are documented in
434
+ [Convergence and recovery](https://github.com/allocator-one/rcl/blob/main/docs/convergence.md).
435
+
436
+ | Command | Purpose |
437
+ | --- | --- |
438
+ | `rcl converge-report` | Dedupe a round report against earlier rounds, enforce the round cap, classify findings (`new` / `repeat` / `suppressed` / `regating`); refuses inconclusive reviewer health |
439
+ | `rcl converge-verdict` | Record fixed/dismissed verdicts and report the round's resolution |
440
+ | `rcl converge-stale` | Audited disposition of a healthy report that became stale before admission |
441
+ | `rcl converge-gap` | Audited record of one evidenced missing terminal report |
442
+ | `rcl converge-rejected` | Audited disposition of a report rejected locally before delivery |
443
+ | `rcl converge-attempt` | Legacy attempt accounting; guarded launches claim their own attempts |
1053
444
 
1054
- # Plan against the authenticated organization. Repeated roots replace defaults.
1055
- rcl telemetry recover-refutations --root ~/Development --root /tmp --manifest recovery.json
445
+ ---
1056
446
 
1057
- # Inspect coverage, source hashes, every refutation and each proposed action first.
1058
- rcl telemetry recover-refutations --manifest recovery.json --apply --output outcome.json
1059
- ```
447
+ ## Configuration
1060
448
 
1061
- The destination is the complete authenticated Harness base URL plus its
1062
- server-reported organization. Applying with a different host, URL path or
1063
- organization fails before any write. An older receiver without `meta.org_id`
1064
- cannot produce an applyable manifest; use `--inventory-only` until the compatible
1065
- backend is released. Inventory-only artifacts are never accepted for apply.
1066
- Telemetry opt-outs and the existing Harness login/CI credential rules still apply.
1067
-
1068
- Default discovery covers `~/Development`, `/tmp`, `/private/tmp`, the configured
1069
- OS temporary directory and `RCL_DATA_DIR` (otherwise `~/.rcl`). It also inspects
1070
- registered Git worktrees/common directories, RCL output and outbox directories,
1071
- and explicit JSON report references in retained ledgers and task metadata,
1072
- including references beyond the initial roots. Add `--root` for other retained
1073
- cache/task locations. Only bounded candidate files are read; symlinks, changing
1074
- files, invalid UTF-8 and files larger than 25 MiB are rejected. Incomplete,
1075
- missing and inaccessible sources stay in the coverage report. Re-inventory after
1076
- active reviews finish and record a final discovery cutoff.
1077
-
1078
- Identical report bytes collapse to one SHA-256 entry with all discovered file
1079
- locations. The manifest retains positional finding refs, original identities,
1080
- normalized model/notes and source bindings. Missing original notes remain explicit.
1081
- Legacy repository ownership must be proven by a registered source worktree or a
1082
- retained ledger in that worktree; ambiguous ownership is unresolved. A directory
1083
- marked `SYNTHETIC_TEST_ONLY`, or a repeated `--exclude-sha256 <digest>`, explicitly
1084
- excludes synthetic evidence and all copies with that digest. Missing reference
1085
- paths are counted separately from missing reports; a basename match is not proof.
1086
-
1087
- | Planned action | Application |
1088
- | --- | --- |
1089
- | `upload_and_recover` | Upload only the absent original artifact matching the recorded run's declaration, then select that run for server recovery. |
1090
- | `recover` | Select an existing run for the server's validated, append-only recovery operation. RCL does not patch findings. |
1091
- | `import_history` | Import missing evidence once under a deterministic historical ID; preserve original timing/target and explicit original-run/digest binding for modern reports. |
1092
- | `already_present` | Read and confirm the recorded evidence; no write. |
1093
- | `skip`, `conflict`, `unavailable` | Preserve the disposition for resolution; no write. |
1094
-
1095
- Apply rechecks source bytes and server bindings. It can use a retained,
1096
- digest-verified copy if another location disappeared. It never replaces an
1097
- existing run or escalates an existing-run selection into a new historical import.
1098
- For legacy reports, the established UUID derives from lowercase host (including
1099
- port), repository and **original** report digest. Its scrubbed upload may have a
1100
- different digest, recorded in the artifact declaration. Modern originals needing
1101
- redaction are never uploaded under their original digest. When that exact
1102
- artifact is already stored in Harness, it can still supply server recovery without
1103
- another upload. Unsafe, unsupported or conflicting sources require resolution.
1104
-
1105
- An apply outcome includes `server_recovery_run_ids`. An authorized operator passes
1106
- those IDs to the released, bounded Harness recovery operation documented in
1107
- [`harness_review_verification.md`](https://github.com/allocator-one/allocator-one/blob/main/docs/ops/harness_review_verification.md),
1108
- previews the selection, applies it with explicit operator/operation attribution,
1109
- and retains its results. Rerun the reviewed manifest to verify the common API
1110
- projection afterwards. Repeat application is resumable and idempotent, including
1111
- when a delivery receipt is lost; the source, queues and manifest are never deleted.
1112
- Outcome `writes` counts acknowledged creations, so a lost receipt can leave a
1113
- confirmed stored result without a creation count. The report dispositions and
1114
- readback determine completion.
1115
-
1116
- Manifest/output paths must be new and are published atomically with mode `0600`.
1117
- Without `--output`, apply uses a unique outcome filename beside the manifest.
1118
- Exit `0` means a plan/inventory was written, or apply reconciled its selected
1119
- reports; `1` means apply still needs server recovery or source/conflict resolution;
1120
- `2` means the command could not validate or perform the operation. Discovery
1121
- issues and absent original notes still need explicit reconciliation even with
1122
- exit `0`. Stopping and keeping the manifest is the rollback for an interrupted
1123
- operation: do not delete historical records, rewrite original evidence, or flush
1124
- queued live reviews as a recovery shortcut.
1125
-
1126
- ## Config File
1127
-
1128
- Place `.review-council.yml` in your project root and run `rcl` from there. rcl looks only in the current working directory, not in parent directories. Use `--config <path>` for a file elsewhere. All fields are optional.
449
+ Place `.review-council.yml` (or `.review-council.yaml` / `.review-council.json`)
450
+ in the directory you run `rcl` from. rcl looks only in the current working
451
+ directory, not in parent directories; use `--config <path>` for a file
452
+ elsewhere. Executable JavaScript config is never discovered: rcl often runs in
453
+ untrusted checkouts with provider keys in the environment. All fields are
454
+ optional, and a config file that fails validation stops the review instead of
455
+ falling back to defaults.
1129
456
 
1130
457
  ```yaml
1131
- # Blocking council (provider-prefixed names) — every round waits for these.
1132
- # Shown here: the actual defaults. Keep slow/aggregator-routed models out of
1133
- # this list; give them an async seat instead.
458
+ # Blocking council (provider-prefixed names); every round waits for these.
459
+ # Shown here: the defaults. Keep slow or aggregator-routed models out of this
460
+ # list; give them an async seat instead.
1134
461
  models:
1135
462
  - anthropic/claude-opus-5-5
1136
463
  - openai/gpt-6-sol
@@ -1139,30 +466,24 @@ models:
1139
466
  secondaryModels:
1140
467
  - google/gemini-3.8-flash
1141
468
 
1142
- # Async bonus reviewers — fired with each round, never awaited. Results that
469
+ # Async bonus reviewers, fired with each round and never awaited. Results that
1143
470
  # have arrived by the next round of the same target are merged into that
1144
471
  # round's dedup and marked `async` in the report JSON.
1145
- # Any model on https://openrouter.ai works — keep the vendor segment after the prefix.
1146
472
  asyncModels:
1147
473
  - openrouter/moonshotai/kimi-k3
1148
474
 
1149
- # Default roles to run
475
+ # Roles to run (default: all built-in and custom roles; spec-compliance only with a spec)
1150
476
  roles:
477
+ - general
1151
478
  - security-auditor
1152
479
  - bug-hunter
1153
- - test-coverage
1154
-
1155
- # Or pin explicit model:role pairs
1156
- reviewers:
1157
- - model: anthropic/claude-opus-5-5
1158
- role: security-auditor
1159
- - model: openai/gpt-6-sol
1160
- role: bug-hunter
1161
480
 
1162
- # Custom role overrides (extends a built-in or creates new)
481
+ # Custom roles: a new role, or an override of a built-in with the same name
1163
482
  customRoles:
1164
483
  - name: my-style-guide
1165
- focus: [best-practices]
484
+ focus: [best-practices] # categories this role specializes in
485
+ severityBias:
486
+ best-practices: 1.2 # >1: lean more severe; <1: lean less severe
1166
487
  systemPrompt: |
1167
488
  Enforce our team style guide. Flag any deviation from snake_case
1168
489
  variable names and require docstrings on all public functions.
@@ -1174,242 +495,355 @@ thresholds:
1174
495
  dedupeLineWindow: 5 # lines within which findings are merged
1175
496
  jaccardThreshold: 0.3 # weighted title+description similarity threshold for dedup
1176
497
 
1177
- # Convergence gating: which findings block convergence / CI (RCL-23).
1178
- # A finding gates when multi-model, critical, or confirmed with source evidence by the
1179
- # verifier; refuted or insufficient-evidence single-model claims stay visible but
1180
- # stop blocking, and so does a claim the pass could not check (verdict
1181
- # unavailable): verification promotes nothing it did not check. Report
1182
- # JSON marks every finding with gating.reason
1183
- # (consensus | critical | verified | none).
498
+ # Which findings block convergence and CI
1184
499
  gating:
1185
- mode: verified-consensus # or all-findings (legacy: severity alone decides)
500
+ mode: verified-consensus # or all-findings (severity alone decides)
1186
501
  minModels: 2 # distinct models for consensus gating
1187
- verificationModel: openai/gpt-6-astra # direct-API only
1188
- verificationReasoningEffort: high # OpenAI verifier only; separate from reviewer effort
1189
- verificationPassTimeout: 600000 # ms for the complete verification queue
502
+ verificationModel: openai/gpt-6-astra # direct-API models only
503
+ verificationReasoningEffort: high # OpenAI verifier only; separate from reviewer effort
504
+ verificationPassTimeout: 600000 # ms for the complete verification queue
1190
505
 
1191
- # Output defaults
1192
506
  output:
1193
- markdown: true
1194
- markdownPath: review-report.md
1195
507
  belowThresholdAppendix: true # false drops below-threshold findings outright
1196
508
 
1197
509
  # Concurrency and reliability
1198
510
  concurrency: 9 # maximum simultaneous blocking reviewer calls per process
1199
- # set to 6 to retain the previous limit
1200
511
  providerConcurrency: # provider admission caps, applied in addition to concurrency
1201
- anthropic: 2 # default: bound high-effort Anthropic bursts
1202
- timeout: 540000 # ms per blocking model call (matches the current default)
1203
- asyncTimeout: 900000 # ms per async-lane call (slow reasoning models get headroom; nothing waits on them)
1204
- # quorumFraction: 0.75 # round closes once this share of blocking seats succeeds
1205
- # on every chunk; all stragglers can be canceled, including core models.
1206
- # Secondary successes never count; secondary calls still
1207
- # running at closure are canceled. Also raises the report's
1208
- # blocking-health requirement.
1209
- # Default: exactly 2/3 — leave unset for that; 1 disables
1210
- # closure and waits for every call.
512
+ anthropic: 2 # default
513
+ timeout: 540000 # ms per blocking model call
514
+ asyncTimeout: 900000 # ms per async-lane call
515
+ # quorumFraction: 0.75 # see below; default exactly 2/3
1211
516
  maxRetries: 3
1212
517
 
1213
- # Reasoning budget for providers that support it (currently OpenRouter).
1214
- # low | medium | high — default low (supported by the default Kimi K3).
1215
- # Check the selected model's supported levels before overriding. Unbounded reasoning makes these
1216
- # models spend the whole completion budget thinking before they answer;
1217
- # select a supported higher level when evaluation justifies deeper review.
518
+ # Reasoning effort for OpenRouter reviewers: low | medium | high (default low)
1218
519
  reasoningEffort: low
1219
520
 
1220
- # Context files to attach to every review
521
+ # Context files attached to every review, and the spec for spec-compliance
1221
522
  context:
1222
523
  - ARCHITECTURE.md
1223
524
  - docs/api.md
1224
-
1225
- # Spec file for spec-compliance role
1226
525
  spec: SPEC.md
1227
526
 
1228
- # GitHub token (prefer GITHUB_TOKEN env var instead)
527
+ # Harness evidence delivery
528
+ harness:
529
+ telemetry: full # off | envelope | findings | full (default)
530
+ parseFailures: false # send a parse-failed call's raw answer (scrubbed, 32 KB cap)
531
+
532
+ # GitHub token (prefer the GITHUB_TOKEN environment variable)
1229
533
  # githubToken: ghp_...
1230
534
  ```
1231
535
 
1232
- `concurrency` limits all blocking reviewer calls within one RCL process.
1233
- `providerConcurrency` adds stricter provider admission caps; increasing the global
1234
- limit never bypasses them. The scheduler scans past a saturated provider so calls
1235
- for other providers continue without changing the original result order. Anthropic
1236
- defaults to two concurrent calls to prevent the observed five-seat high-effort burst;
1237
- the existing 540-second call deadline is unchanged. Providers with no default or
1238
- explicit entry use only the global limit. Separate RCL processes do not share these
1239
- limits; async reviewers and verification use their own scheduling.
1240
-
1241
- `maxRetries` limits additional adapter SDK invocations after the first attempt;
1242
- all attempts share the call's `timeout` and parent cancellation signal. Supported
1243
- transient connection failures and HTTP status errors may retry; cancellation,
1244
- expired deadlines, permanent TLS/configuration errors and unusable output do not.
1245
- Report reviews and `ask` results expose `adapterAttempts` when observed. Chunk
1246
- reports sum it only when every part has a known count. It is neither a wire-request
1247
- count nor a billing total: lower-level activity and charges after ambiguous
1248
- transport failures can be unknown. Token usage remains what the SDK response
1249
- exposes, not proof of total charges across retries.
1250
-
1251
- Supported config file names: `.review-council.yml`, `.review-council.yaml`, `.review-council.json`. Executable JS config is never discovered: rcl often runs in untrusted checkouts with provider keys in the environment.
1252
-
1253
- The verifier uses three verdicts: `confirmed`, `refuted`, and
1254
- `insufficient_evidence`. Confirmation requires a reachable failure mechanism and
1255
- exact code excerpts from the supplied change; citations are checked against that
1256
- source before a claim can be promoted to `verified`. Missing context or inability
1257
- to refute a claim is not confirmation. This checks citation provenance, not semantic
1258
- truth: the verifier still has to reason correctly. Infrastructure failures remain
1259
- `unavailable`. Historical `unrefuted` verdicts retain their original meaning.
1260
-
1261
- Astra defaults to `high` effort. Set `gating.verificationReasoningEffort` to `low`,
1262
- `medium`, `high`, `xhigh`, or `max` for an OpenAI verifier. The setting is included
1263
- in the report header and retained verification plan. Other provider overrides
1264
- retain their provider effort defaults and reject this OpenAI-only setting.
1265
-
1266
- The top-level `reasoningEffort` applies only to OpenRouter reviewers. Direct
1267
- Sol and Gemini reviewers use their provider defaults (currently `medium`).
1268
- Opus 5.5 reviews explicitly use `high`, streaming, and a 65,536-token output
1269
- ceiling so thinking and findings share adequate headroom. Opus 5.5's API default
1270
- is `medium`; RCL sets `high` because a review gate is intelligence-sensitive work
536
+ **Accepted but ignored.** These keys pass validation but have no effect, so
537
+ older config files keep loading: `reviewers` (use `--reviewer` for explicit
538
+ model:role pairs; the key is read only when reconstructing a legacy 4.1.x
539
+ retry roster), top-level `focus`, and `output.terminal`, `output.json`,
540
+ `output.jsonPath`, `output.markdown`, `output.markdownPath` and
541
+ `output.github`. rcl writes report files only when `--json-file` or
542
+ `--markdown` is given. `output.belowThresholdAppendix` is the only `output`
543
+ key in use.
544
+
545
+ **Custom roles.** A custom role whose name matches a built-in (case-insensitive)
546
+ overrides it and inherits the fields you omit; any other name creates a new
547
+ specialist role. `focus` lists the categories the role specializes in
548
+ (`security`, `correctness`, `best-practices`, `tests`, `api-design`); it feeds
549
+ the role-relevance part of the consensus score and defaults to
550
+ `[best-practices]` for new roles. `severityBias` maps a category to a factor:
551
+ above 1 tells the reviewer to pick the more severe of two adjacent severity
552
+ levels for that category, below 1 the less severe one, and 1 has no effect.
553
+ The bias becomes calibration guidance in the reviewer's prompt; consensus
554
+ scoring itself is bias-free. Built-in examples: `security-auditor` uses
555
+ `{ security: 1.2 }`, `bug-hunter` uses `{ correctness: 1.3 }`.
556
+
557
+ **Quorum.** `quorumFraction` closes a round once that share of blocking seats
558
+ has succeeded on every chunk; stragglers, including core models, can be
559
+ canceled. Secondary successes never count, and secondary calls still running at
560
+ closure are canceled. It also raises the report's blocking-health requirement.
561
+ The default is exactly 2/3 (leave it unset for that); the minimum is 2/3, and
562
+ 1 disables early closure and waits for every call.
563
+
564
+ **Concurrency.** `concurrency` limits all blocking reviewer calls within one
565
+ rcl process. `providerConcurrency` adds stricter per-provider admission caps;
566
+ raising the global limit never bypasses them. The scheduler scans past a
567
+ saturated provider so calls for other providers continue without changing the
568
+ original result order. Anthropic defaults to two concurrent calls; providers
569
+ with no default or explicit entry use only the global limit. Separate rcl
570
+ processes do not share these limits; async reviewers and verification use their
571
+ own scheduling.
572
+
573
+ **Retries.** `maxRetries` limits additional adapter SDK invocations after the
574
+ first attempt; all attempts share the call's `timeout` and parent cancellation
575
+ signal. Supported transient connection failures and HTTP status errors may
576
+ retry; cancellation, expired deadlines, permanent TLS/configuration errors and
577
+ unusable output do not. Report reviews and `discuss` answers expose
578
+ `adapterAttempts` when observed; chunked reviews sum it only when every part
579
+ has a known count. It is neither a wire-request count nor a billing total:
580
+ lower-level activity and charges after ambiguous transport failures can be
581
+ unknown, and token usage is what the SDK response exposes, not proof of total
582
+ charges across retries.
583
+
584
+ **Reasoning effort.** The top-level `reasoningEffort` applies only to OpenRouter
585
+ reviewers (default `low`, a supported level for the default Kimi K3, which
586
+ advertises `low`, `high` and `max` — avoid `medium` for it). Check the
587
+ selected model's supported levels before overriding: unbounded reasoning makes
588
+ these models spend the whole completion budget thinking before they answer.
589
+ Direct Sol and Gemini reviewers use their provider defaults. Opus 5.5 reviews
590
+ explicitly use `high` effort, streaming, and a 65,536-token output ceiling so
591
+ thinking and findings share adequate headroom. Opus 5.5's API default is
592
+ `medium`; rcl sets `high` because a review gate is intelligence-sensitive work
1271
593
  ([Anthropic's effort guidance](https://platform.claude.com/docs/en/build-with-claude/effort)).
1272
- The same profile applies when Fable 5.1 is configured explicitly. RCL's whole-call
1273
- timeout and rejection of incomplete output are unchanged.
1274
- OpenRouter reviewers default to `low`, a supported level for the default Kimi K3;
1275
- explicit `reasoningEffort` overrides are preserved. Kimi K3 advertises `low`,
1276
- `high`, and `max`, so avoid overriding it to `medium`. Effort labels are
1277
- provider-specific and do not imply equal compute or quality across models.
1278
-
1279
- The default whole-pass deadline is 10 minutes across all queued verifier
1280
- batches. Verifier calls default to the remaining whole-pass budget. Set the optional
1281
- `gating.verificationTimeout` in milliseconds to impose a shorter per-call limit;
1282
- every call is still capped by the remaining `verificationPassTimeout` deadline.
1283
-
1284
- For converging patch reviews, async collection uses `--converge-target` (or
1285
- `RCL_CONVERGE_TARGET`), not the patch pathname. Each round can keep a distinct,
1286
- immutable capture while sharing results across linked worktrees of the same
1287
- repository and target. Other review modes retain their existing keys; previously
1288
- spooled path-keyed results are not migrated. Async findings can come from an
1289
- earlier capture and still need checking against the current code. This does not
1290
- make detached-worker completion part of the blocking round. `run.roster` records
1291
- this round's planned seats; collected async reviews retain their model and role
1292
- in `reviews` and can come from seats absent from the current roster.
1293
-
1294
- Before dispatch, RCL prints the expanded reviewer × chunk call count,
1295
- concurrency, wave count, timeout, and timeout-bound queue estimate. Interactive
1296
- runs update the spinner; redirected runs emit periodic heartbeat and bounded
1297
- completion lines with status counters, so a long queue is distinguishable from
1298
- a hung process.
594
+ The same profile applies when Fable 5.1 is configured explicitly. Effort labels
595
+ are provider-specific and do not imply equal compute or quality across models.
596
+
597
+ **Verifier.** The verifier uses three verdicts: `confirmed`, `refuted`, and
598
+ `insufficient_evidence`. Confirmation requires a reachable failure mechanism
599
+ and exact code excerpts from the supplied change; citations are checked against
600
+ that source before a claim can be promoted to `verified`. Missing context or
601
+ inability to refute a claim is not confirmation. This checks citation
602
+ provenance, not semantic truth: the verifier still has to reason correctly.
603
+ Infrastructure failures are recorded as `unavailable`, and an unavailable
604
+ verdict never promotes a finding. Historical `unrefuted` verdicts retain their
605
+ original meaning. The default
606
+ verifier `openai/gpt-6-astra` is used only when OpenAI is already in the
607
+ roster; otherwise rcl picks the first direct-API roster model, so the default
608
+ never sends the diff to a provider you configured away from. OpenRouter models
609
+ cannot verify. Astra defaults to `high` effort;
610
+ `gating.verificationReasoningEffort` accepts `low`, `medium`, `high`, `xhigh`
611
+ or `max` for an OpenAI verifier only (other verifiers keep their provider
612
+ defaults and reject the setting), and is recorded in the report header and
613
+ retained verification plan. The whole verification pass has a 10-minute
614
+ default deadline (`gating.verificationPassTimeout`) across all queued verifier
615
+ batches; verifier calls default to the remaining pass budget, and the optional
616
+ `gating.verificationTimeout` (milliseconds) imposes a shorter per-call limit,
617
+ still capped by the remaining pass deadline.
1299
618
 
1300
619
  ---
1301
620
 
1302
- ## How Consensus Works
621
+ ## Environment variables
1303
622
 
1304
- When multiple models and roles review the same diff, their findings are:
623
+ | Variable | Description |
624
+ | --- | --- |
625
+ | `ANTHROPIC_API_KEY` | Anthropic models |
626
+ | `OPENAI_API_KEY` | OpenAI models and the default verifier |
627
+ | `GOOGLE_API_KEY` | Google Gemini; empty or whitespace-only values fall through |
628
+ | `GEMINI_API_KEY` | Google Gemini when `GOOGLE_API_KEY` is absent or blank; also used for Harness-injected keys |
629
+ | `OPENROUTER_API_KEY` | `openrouter/…` models |
630
+ | `OPENAI_COMPAT_BASE_URL` | Base URL for `openai-compat` models and unrecognized unprefixed names (default `http://localhost:11434/v1`) |
631
+ | `OPENAI_COMPAT_API_KEY` | API key for that endpoint (default: the placeholder `local`) |
632
+ | `GITHUB_TOKEN` | GitHub token for PR fetch and `--post` |
633
+ | `XDG_CONFIG_HOME` | Where the `harness login` credential is read from (`$XDG_CONFIG_HOME/harness/credentials.json`; default `~/.config/harness/credentials.json`) |
634
+ | `RCL_DATA_DIR` | Per-machine state directory (model stats, evidence outbox and quarantine); default `~/.rcl` |
635
+ | `RCL_TELEMETRY` | `off` keeps every review on the machine |
636
+ | `RCL_NO_HARNESS_KEYS` | Any value disables provider-key distribution via Harness |
637
+ | `RCL_FOR_PR` | Same as `--for-pr`, for patch-file reviews |
638
+ | `RCL_CONVERGE_TARGET` / `RCL_CONVERGE_ROUND` / `RCL_CONVERGE_ATTEMPT` | Same as `--converge-target` / `--round` / `--attempt` |
639
+ | `RCL_DEBUG` | Any value prints full error stack traces |
640
+ | `HARNESS_API_TOKEN` | CI credential for evidence delivery; requires `HARNESS_API_URL` and never pairs with the stored login host |
641
+ | `HARNESS_API_URL` | The Harness host `HARNESS_API_TOKEN` was minted by; under `--attest`, the host attested to (no token needed) |
642
+ | `ACTIONS_ID_TOKEN_REQUEST_URL` / `ACTIONS_ID_TOKEN_REQUEST_TOKEN` | Set by the GitHub Actions runner for jobs with `id-token: write`; `--attest` requires them |
1305
643
 
1306
- 1. **Deduplicated** — findings on the same file and overlapping line range are grouped by weighted title+description token similarity; findings in different categories can still merge, but need stronger similarity (models disagree on category boundaries constantly). Findings whose line ranges strictly overlap and that name the same issue concept (sql injection, IDOR, hardcoded secret, …) merge regardless of wording — models phrase the same issue too differently for token overlap alone. Repeats within a single review are collapsed first. Findings that clearly reach opposite conclusions are kept as separate, disputed findings; subtler contradictions merge but are flagged as disputed.
1307
- 2. **Scored** — each group receives a consensus score based on three dimensions: reviewer diversity (how many distinct models and roles flagged it, saturating at half the fleet so large configurations aren't penalized), role relevance (whether a role specialised in that finding type confirmed it), and isolation (what fraction of relevant reviewers flagged it).
1308
- 3. **Classified** — groups are assigned a confidence band (Very High → Minimal) and a final severity. Severity is the most common rating across reviewers; when reviewers disagree, high-confidence agreement elevates it, but only to a severity at least two reviewers independently assigned — a lone outlier rating is surfaced as a dispute instead. Each group also gets an **agreement tier** measured over distinct models — `unanimous` (every successful model), `majority` (at least half), `minority` (2+, under half), `single` (one model) — because roles share a model's blind spots, so model count is the evidence axis.
1309
- 4. **Filtered** — groups below `minConsensusScore` or `minConfidence` are demoted (blocking severities are never dropped). Demoted findings land in a collapsed "worth checking" appendix at the bottom of the report and in the JSON `belowThresholdFindings` field — never in severity totals or CI gating. Set `output.belowThresholdAppendix: false` to drop them outright instead.
644
+ ---
645
+
646
+ ## How consensus and gating work
1310
647
 
1311
- The report is organized by agreement tier — unanimous first, then majority, minority, **disputed** (reviewers reached materially different conclusions; rendered as per-model positions so you can judge), and single-model last. Within each tier, findings sort by severity. The tier structure is the point of a multi-model council: it tells you which findings are independently confirmed and where to spend your own judgment.
648
+ When multiple models and roles review the same diff, their findings are:
649
+
650
+ 1. **Deduplicated** — findings on the same file and overlapping line range are
651
+ grouped by weighted title+description token similarity; findings in
652
+ different categories can still merge, but need stronger similarity. Findings
653
+ whose line ranges strictly overlap and that name the same issue concept (SQL
654
+ injection, IDOR, hardcoded secret, …) merge regardless of wording. Repeats
655
+ within a single review are collapsed first. Findings that clearly reach
656
+ opposite conclusions are kept as separate, disputed findings; subtler
657
+ contradictions merge but are flagged as disputed.
658
+ 2. **Scored** — each group receives a consensus score from three dimensions:
659
+ reviewer diversity (how many distinct models and roles flagged it, saturating
660
+ at half the fleet), role relevance (whether a role specialized in that
661
+ finding type confirmed it), and isolation (what fraction of relevant
662
+ reviewers flagged it).
663
+ 3. **Classified** — groups get a confidence band (Very High → Minimal) and a
664
+ final severity. Severity is the most common rating across reviewers; when
665
+ reviewers disagree, high-confidence agreement elevates it, but only to a
666
+ severity at least two reviewers independently assigned — a lone outlier
667
+ rating is surfaced as a dispute instead. Each group also gets an
668
+ **agreement tier** measured over distinct models — `unanimous` (every
669
+ successful model), `majority` (at least half), `minority` (2+, under half),
670
+ `single` (one model) — because roles share a model's blind spots.
671
+ 4. **Filtered** — groups below `minConsensusScore` or `minConfidence` are
672
+ demoted (blocking severities are never dropped). Demoted findings land in a
673
+ collapsed "worth checking" appendix at the bottom of the report and in the
674
+ JSON `belowThresholdFindings` field — never in severity totals or CI gating.
675
+ Set `output.belowThresholdAppendix: false` to drop them outright.
676
+
677
+ The report is organized by agreement tier — unanimous first, then majority,
678
+ minority, **disputed** (rendered as per-model positions so you can judge), and
679
+ single-model last. Within each tier, findings sort by severity. The tiers tell
680
+ you which findings are independently confirmed and where to spend your own
681
+ judgment.
682
+
683
+ **Gating** decides which findings block convergence and CI. In the default
684
+ `verified-consensus` mode only critical and important findings can gate, and
685
+ each gets a `gating.reason`:
686
+
687
+ | `gating.reason` | When |
688
+ | --- | --- |
689
+ | `consensus` | Raised by at least `gating.minModels` (default 2) distinct models; model weights can demote consensus but never replace distinct models |
690
+ | `critical` | Critical severity |
691
+ | `verified` | A single-model important finding the verifier confirmed with source evidence |
692
+ | `none` | Everything else, including refuted, insufficient-evidence and unverified (`unavailable`) claims; still reported, never blocking |
1312
693
 
1313
- For the full algorithm, see [CONSENSUS_V2_SPEC.md](./CONSENSUS_V2_SPEC.md).
694
+ `gating.mode: all-findings` restores the legacy rule where every critical or
695
+ important finding gates. For the full scoring algorithm, see
696
+ [CONSENSUS_V2_SPEC.md](https://github.com/allocator-one/rcl/blob/main/CONSENSUS_V2_SPEC.md).
1314
697
 
1315
698
  ---
1316
699
 
1317
- ## Environment Variables
700
+ ## Harness evidence and gate
701
+
702
+ In a repository that carries `.harness-cli/config.json`, with a
703
+ `harness login` (or `HARNESS_API_TOKEN` + `HARNESS_API_URL` in CI), every
704
+ review is recorded on [Harness](https://harness.infra.one) after the report is
705
+ written: the run header, findings, reviewer calls, stats and — at the default
706
+ `full` level — both report files, scrubbed for key-shaped strings. Provider
707
+ keys, tokens, environment variables and prompts are never sent, and raw model
708
+ answers only when `harness.parseFailures: true` opts in for parse-failed calls.
709
+ The review never blocks on the
710
+ network: an outage spools the evidence to `~/.rcl/outbox/` for
711
+ `rcl telemetry flush`. One status line reports the outcome, and
712
+ `--evidence-required` turns a missing acknowledgment into exit 4. Opt out with
713
+ `--no-telemetry`, `RCL_TELEMETRY=off` or `harness.telemetry: off`.
714
+
715
+ Harness computes two projections per pull request head from that evidence:
716
+
717
+ - **advisory** — counts rounds recorded with any credential, such as a
718
+ developer's `harness login` or a CI token;
719
+ - **enforced** — counts only **attested** rounds, recorded by the
720
+ organization's gate workflow; attested rounds also drive the
721
+ `Review Council` check run.
722
+
723
+ A round counts only when it names the pull request, comes from the same
724
+ repository and reviewed the pull request's current head.
725
+
726
+ `rcl evidence status owner/repo#N` prints both and exits 0 only when the judged
727
+ projection (advisory, or enforced with `--enforced`) is `converged`.
728
+
729
+ - [Telemetry and evidence](https://github.com/allocator-one/rcl/blob/main/docs/telemetry-and-evidence.md)
730
+ — delivery, attestation, evidence reads and the evidence recovery commands
731
+ - [Convergence and recovery](https://github.com/allocator-one/rcl/blob/main/docs/convergence.md)
732
+ — the convergence loop, budgets, reviewer health and recovery operations
733
+ - [Review evidence recovery](https://github.com/allocator-one/rcl/blob/main/docs/review-evidence-recovery.md)
734
+ — operator runbook for the gate's encrypted evidence artifact
735
+
736
+ ### The gate workflow
737
+
738
+ Once a pull request head's advisory status has converged, Harness dispatches
739
+ the organization's gate workflow for that head. Inside the job,
740
+ `rcl review owner/repo#N --attest` exchanges the job's OIDC token for a
741
+ run-bound Harness credential, so the round it records is attested. Harness
742
+ accepts it only if the workflow file is on the organization's gate allow-list
743
+ at its default branch, the reviewed head is the pull request's current head,
744
+ and the pull request is not from a fork. `--attest` never falls back to another
745
+ credential and implies `--evidence-required`.
746
+
747
+ The workflow contract, as in this repository's
748
+ [`review_gate.yml`](https://github.com/allocator-one/rcl/blob/main/.github/workflows/review_gate.yml):
749
+
750
+ - `workflow_dispatch` inputs `pr_number` and `head_sha` (required) and
751
+ `attempt_id` (optional: the Harness dispatch attempt UUID, omitted only for
752
+ an unregistered manual run);
753
+ - a `# harness-review-lifecycle: 1` comment, which promises the optional
754
+ `attempt_id` input and the exact run-name
755
+ `Review Council gate · ${{ inputs.attempt_id || 'unregistered' }}`;
756
+ - `id-token: write` for the review job, no provider secrets, and the pull
757
+ request never checked out;
758
+ - the latest release installed by the verified installer, which checks the
759
+ package against the registry's SHA-512 integrity before installing.
760
+
761
+ Abridged:
1318
762
 
1319
- Explicit GitHub PR fetches and review posting use a nonempty `githubToken`
1320
- configuration value first, then `GITHUB_TOKEN`, then the existing
1321
- `gh auth token --hostname github.com` login. The fallback is noninteractive
1322
- and bounded; if unavailable, public anonymous reads still work. A PR 404
1323
- explains how to check private-repository access without exposing credentials.
1324
- Local patch reviews do not read GitHub credentials.
763
+ ```yaml
764
+ # harness-review-lifecycle: 1
765
+ name: Review Council gate
766
+ run-name: Review Council gate · ${{ inputs.attempt_id || 'unregistered' }}
1325
767
 
1326
- | Variable | Description |
1327
- |----------|-------------|
1328
- | `ANTHROPIC_API_KEY` | API key for Claude models |
1329
- | `OPENAI_API_KEY` | API key for OpenAI models |
1330
- | `GOOGLE_API_KEY` | Preferred Google Gemini API key; empty or whitespace-only values fall through |
1331
- | `GEMINI_API_KEY` | Google Gemini API key when `GOOGLE_API_KEY` is absent or blank; also used for Harness-injected keys |
1332
- | `OPENROUTER_API_KEY` | API key for [OpenRouter](https://openrouter.ai) models (`openrouter/…` prefix) |
1333
- | `GITHUB_TOKEN` | GitHub personal access token (PR fetch and post) |
1334
- | `RCL_DEBUG` | Set to any value to print full error stack traces |
1335
- | `RCL_NO_HARNESS_KEYS` | Set to any value to disable Harness key distribution (below) |
1336
- | `RCL_TELEMETRY` | `off` keeps every review on the machine (see `rcl telemetry`) |
1337
- | `HARNESS_API_TOKEN` | CI credential for evidence delivery; requires `HARNESS_API_URL` — never pairs with the stored login host |
1338
- | `HARNESS_API_URL` | The Harness host `HARNESS_API_TOKEN` was minted by; under `--attest` the host attested to (no token needed) |
1339
- | `ACTIONS_ID_TOKEN_REQUEST_URL` / `ACTIONS_ID_TOKEN_REQUEST_TOKEN` | Set by the GitHub Actions runner for jobs with `id-token: write`; `--attest` reads them and refuses to run without them |
1340
-
1341
- The default blocking council is direct-API only (Anthropic, OpenAI, Google) —
1342
- no default review round ever waits on an OpenRouter-routed call. The default
1343
- async lane holds one OpenRouter-hosted bonus reviewer (`kimi-k3`); if
1344
- `OPENROUTER_API_KEY` is not set, it is dropped from the defaults with a warning
1345
- (models you configure explicitly still fail loudly instead). Note that when the
1346
- key is set, default reviews send diff and context content to OpenRouter — an
1347
- aggregator and an additional data processor beyond the direct model providers —
1348
- as well as to Anthropic, OpenAI, and Google. Configure `models:` and
1349
- `asyncModels:` explicitly if that matters for your repository.
1350
-
1351
- ### Key distribution via Harness
1352
-
1353
- Repos that carry a committed `.harness-cli/config.json` (discovered git-style,
1354
- walking up from the working directory) can get their provider keys from a
1355
- [Harness](https://harness.infra.one) backend instead of every teammate managing
1356
- them by hand: run `harness login` once, and any provider key **missing from the
1357
- environment** is fetched from `GET /api/v1/model-keys` on the host that minted
1358
- the stored login token, and injected for the run.
1359
-
1360
- - Environment variables always win — only missing keys are injected.
1361
- - The stored credential is only ever sent to the host it was minted for, never
1362
- to a URL named by the repo's own config (untrusted input in a cloned repo).
1363
- - Any failure — not logged in, offline, older backend without the endpoint —
1364
- falls back silently to the plain-environment behavior above. The fetch runs
1365
- under a 3-second timeout and keys are never written to disk or logs.
1366
- - Which providers the backend serves is server configuration
1367
- (`HARNESS_MODEL_KEYS` on the backend); `RCL_NO_HARNESS_KEYS` disables the
1368
- whole mechanism client-side.
768
+ on:
769
+ workflow_dispatch:
770
+ inputs:
771
+ pr_number: { required: true, type: string }
772
+ head_sha: { required: true, type: string }
773
+ attempt_id: { required: false, type: string }
1369
774
 
1370
- ---
775
+ permissions:
776
+ contents: read
1371
777
 
1372
- ## License
778
+ jobs:
779
+ attested-review:
780
+ runs-on: ubuntu-24.04
781
+ permissions:
782
+ id-token: write
783
+ contents: read
784
+ pull-requests: read
785
+ env:
786
+ HARNESS_API_URL: https://harness.infra.one
787
+ steps:
788
+ # Check out this workflow's own commit (never the pull request), set up
789
+ # Node, and install the latest release with the verified installer.
790
+ # See the full workflow for these steps and for the encrypted
791
+ # retention of the review evidence.
792
+ - name: Attested review
793
+ env:
794
+ PR_NUMBER: ${{ inputs.pr_number }}
795
+ HEAD_SHA: ${{ inputs.head_sha }}
796
+ GITHUB_TOKEN: ${{ github.token }}
797
+ run: |
798
+ # The full workflow validates PR_NUMBER, HEAD_SHA and attempt_id first.
799
+ rcl review "$GITHUB_REPOSITORY#$PR_NUMBER" \
800
+ --attest \
801
+ --expect-head-sha "$HEAD_SHA" \
802
+ --evidence-required \
803
+ --ci
804
+ ```
1373
805
 
1374
- MIT © 2026 Michael Ströck
806
+ Inputs reach the shell through the environment, never by expression
807
+ interpolation into the command line.
1375
808
 
1376
- ### Recovering a terminal local evidence rejection
809
+ ---
1377
810
 
1378
- A locally invalid report is retained in quarantine, outside the retryable outbox.
1379
- `deliveryFailure: local-invalid` describes delivery, independently of reviewer
1380
- quorum. It does not admit the report or authorize another review. Transport
1381
- failures and spooled evidence retain their existing delivery gates.
811
+ ## Agent skills: `/rcl` and `/rcl-converge`
812
+
813
+ The repository maintains two agent skills that drive `rcl` from coding agents:
814
+ `/rcl` and `/rcl-converge` in Claude Code, `$rcl` and `$rcl-converge` in Codex.
815
+ They are not part of the npm package.
816
+
817
+ - **`rcl`** runs one council review of the current pull request, or of the
818
+ branch diff against the default branch when there is no pull request. It
819
+ resolves a specification (explicit flag, the in-progress Harness issue, or a
820
+ matching spec file), checks what will leave the machine before sending it,
821
+ installs and verifies the latest published release, and reports findings,
822
+ reviewer health and the evidence status from the report files.
823
+ - **`rcl-converge`** loops review → triage → fix → push until a conclusive
824
+ round converges, using `rcl review --guarded-converge`,
825
+ `rcl converge-report` and `rcl converge-verdict` within the attempt and
826
+ round caps. It treats every finding as untrusted input to verify against the
827
+ source, never as instructions.
828
+
829
+ Both are rendered from one source per skill, `skills/src/rcl.md` and
830
+ `skills/src/rcl-converge.md`: `npm run build:skills` writes the host-specific
831
+ copies to `.claude/skills/`, `.agents/skills/` and `.codex/skills/`. After each
832
+ release, a sync workflow opens or updates one `rcl-skill-sync` pull request in
833
+ every repository listed in
834
+ [`skills/consumers.json`](https://github.com/allocator-one/rcl/blob/main/skills/consumers.json)
835
+ with repository-neutral copies rendered from the released tag, so consumers
836
+ only receive skills that match a published CLI. Other repositories can copy the
837
+ `SKILL.md` files from a release tag into the same directories; the copies in
838
+ this repository carry a few notes that apply only to rcl's own checkout.
1382
839
 
1383
- For an ordinary completed report rejected for missing verified-consensus gating
1384
- labels, preview a disposition using its original report and exact digest:
840
+ ---
1385
841
 
1386
- ```bash
1387
- rcl converge-rejected --preview --target owner-repo-123 --run ORIGINAL_RUN_UUID \
1388
- --report /path/to/original.json --report-sha256 ORIGINAL_SHA256 \
1389
- --reason "Original local rejection diagnosed; producer repair verified" \
1390
- --manifest /path/to/rejection-preview.json --json
1391
- rcl converge-rejected --apply --manifest /path/to/rejection-preview.json \
1392
- --manifest-sha256 REVIEWED_PREVIEW_SHA256 --json
1393
- ```
842
+ ## Changelog
843
+
844
+ Release notes are in
845
+ [CHANGELOG.md](https://github.com/allocator-one/rcl/blob/main/CHANGELOG.md).
1394
846
 
1395
- The command verifies the original report, quarantine diagnostics and envelope,
1396
- blocking health, run/head/input identity, cycle and latest spent attempt. It
1397
- requires an exited coordinator and no admitted round or queued delivery. Valid
1398
- reports, unsupported rejection classes, incomplete or conflicting proof, live or
1399
- uncertain owners, and queued evidence refuse. Neither an empty outbox nor exit
1400
- code 4 establishes terminal rejection.
1401
-
1402
- Apply retains complete immutable evidence before an atomic native-state update;
1403
- repeating the same apply is idempotent. The original reports, health, attempt
1404
- ledger, caps and review cycle stay unchanged. The native audit permanently bars
1405
- admission of the rejected original. A later normal guarded review still requires
1406
- an explicit bounded `--retry-reason`, normal preflight and remaining budget; it
1407
- claims the next attempt in the same cycle and native round. Recovery itself
1408
- uploads nothing, starts no provider calls and provides no convergence or approval.
1409
-
1410
- Queue checks are fail-closed observations, not a new global outbox lock. This
1411
- narrow recovery proves rejection before network delivery and requires the
1412
- original producer to have exited. It rechecks for queued evidence at apply and
1413
- before the later guarded claim. It never treats a server/transport refusal as
1414
- that proof. Historical audits use their retained copies, so ordinary temporary
1415
- source cleanup does not break subsequent review and admission.
847
+ ## License
848
+
849
+ MIT © 2026 Michael Ströck