@codedology/code-forge 0.14.1 → 0.15.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.
@@ -1,5 +1,7 @@
1
1
  # Code — brief, dispatch, re-measure, gate
2
2
 
3
+ Small changes may use `block open <id> --kind light`. Close allows at most `light.max_files` owned changed files (default 5) and `light.max_lines` added plus removed lines (default 300), including new files. Auto ticket lines are unchanged. `gates run --block` runs the block's tests through `{test_files}`, ignoring the full-suite threshold; supply block tests and a test command containing that item. A close refusal `light-limits <files> files, <lines> lines` means the block outgrew this kind: the orchestrator stops the open attempt, then opens the same id with `block open <id> --kind code --attempt <n+1>` and the original base, ownership and acceptance.
4
+
3
5
  ## §1 The coder's first deliverable is a facts diff
4
6
 
5
7
  Before any forecast or code, the coder reads the code it will call and reports **every contradiction between the plan and the code** in one message — a signature, a flag, a key, a version. One stop for facts, never one per discovery. Contract corrections (a key, a rule, a signature) are the architect's alone and are recorded, not asked; a ruling reaches the human only when it changes user-visible behaviour, money semantics or an approved decision. A coder that stops three times for three true facts did nothing wrong; the protocol did.
@@ -10,6 +12,8 @@ The coder then forecasts **cases** (tests) and **lines**. Compute the floor your
10
12
 
11
13
  ## §3 Dispatch — what the brief contains
12
14
 
15
+ Acceptance (`block open --acceptance`) is a YAML/JSON list. Code clauses need `tests: [<test ids>]`. A device check instead uses `- clause: "Check the layout on a phone"` with `kind: human_check` on the next indented line and no `tests` key (even an empty list is refused). These texts are recorded as the block's `human_checks` and skipped by test-coverage checks; every other clause still needs tests.
16
+
13
17
  Run a Codex coder with `codex exec --json` in its worktree, save stdout to a log, and pass that
14
18
  log to `block close --transcript <file>`. A Codex JSON log (every non-empty line is an object
15
19
  with a string `type`, first event `thread.started`) is checked by its commands: command items'
@@ -17,6 +21,10 @@ with a string `type`, first event `thread.started`) is checked by its commands:
17
21
  Agent messages, reasoning, command output (`aggregated_output`) and non-item events are skipped. Any other log
18
22
  is checked line by line, where a mention of a forbidden command or path is a hit.
19
23
 
24
+ `block open` refuses a missing `--owned` or `--tests` entry containing whitespace or a comma outside a `{...}` brace group (exit 2: use spaces, not commas). A glob character does not exempt an outside comma: `src/*,lib` is refused when it is not an existing path. Brace globs like `src/{a,b}`, bracket paths like `app/[id]/page.tsx`, and new plain paths stay allowed under this check. Literal existing file and directory names remain valid.
25
+
26
+ If `run start` cannot launch because another run still has a live worker, the error names that run, its pid and `code-forge run end --run <other-run>`. Process start-time probes use `/bin/ps` when present, falling back to `ps` on PATH.
27
+
20
28
  `forge block open <id> --run <r> [--level L<n>] --owned <paths…> --acceptance <file> --brief <file> [--attempt <n>] [--base <sha>] [--kind code|docs|contract] [--tests <paths…>] --lines <n>` records the base sha, the owned files (overlap with another active block ⇒ refused; an owned entry with no glob character — `*`, `?`, `{` — owns that path and every path below it, so `src/feature` owns `src/feature/a.swift` but not `src/featureX/a.swift`, and `src/feature` overlaps `src/feature/x.swift` or `src` in another block — unless the entry is a regular file in the workspace at `block open` (or `block claim`): then it owns only itself, recorded as `owned_kinds`, and every later check reads that answer; a glob owns what it matches, and a brace entry `src/{a,b}` is a glob, so `src/a` owns nothing below it; no trailing `/`), the block kind (declared, else detected — `decisions.md` §5), the level (a docs/contract block below `levels.coder_floor_docs`, default L1, is raised to it and the CLI prints the floor), the clauses with their test ids and the forecast, writes the ledger `dispatch` row and prints the pointer `BRIEF <path> lines=<n> sha=<sha8> <<<EOM>>>` (≤ 200 B). Call it before every spawn, in every engine. `--level` may be left out only in a brief-mode run (`plan.md` §6: the rules pick the lane from the owned entries, `--lines` and `proof.tiers.high.paths`, and a signed `decision` row with `source: rules` records it) or when autopilot recorded a level; otherwise its absence is a usage error. A docs block that owns a glob (`docs/**`) must declare `--kind docs`: a glob is judged by its literal ending, so undeclared it counts as `code`. `--tests <paths…>` (same path rules as `--owned`, else `bad-tests`; exact paths only — a file or a directory; `*` and `?` are refused, and so is a path starting with `-`, both a usage error, exit 2; `[` and `{` are allowed, as in `app/[id]/page.test.tsx`) names the block's test files, stored as `test_files` on the block and the `dispatch` row; a `{test_files}` gate runs them (`proof.md` §3). Without it the owned entries that are concrete files (no glob) and look like tests are used; `--tests` is the precise way.
21
29
 
22
30
  Under `proof.isolation: worktree` (B62a, `proof.md` §3), `block open` first makes the block's own worktree. The output then has a second line, `worktree <path> · branch <branch>` (with `· reused` when a second `block open` took the earlier attempt's tree), before the pointer. Give the coder that path as its working directory. Two worktree blocks may own the same path. A refused worktree refuses the open, and nothing is recorded. A second `block open` keeps the tree's base: do not pass a different `--base`. The coder works in that tree path: it edits there, commits on the block's branch, and calls `review-file` there. Run `gates run --run <r> --block <id>` without `--cwd` (another `--cwd` is refused, exit 2). Gates, reviews and the close all measure the tree (B62b, `proof.md` §3): a change made only in the main checkout is not the block's work, and a block whose tree was deleted cannot close until the tree is restored.
@@ -64,6 +72,18 @@ Each `review-file` ticket is served by the worker `forge run start` launched: to
64
72
 
65
73
  ## §5 The block gate is a re-measurement — `forge block close <id> --run <r>`
66
74
 
75
+ The human can use `block close <id> --run <r> --decide --reason <text>` to see all waivable findings, unacknowledged agent waivers and acceptable transcript hits in one numbered list, then confirm once. Yes writes the same signed human rows as the individual verbs, with the shared reason for findings and hits, then runs normal close. No writes nothing. Unreviewed or stopped content needing a new review, failed gates, unowned changes and missing proof print `cannot decide here:` and write nothing. Both terminal streams are required; no `--yes`; an active autopilot grant refuses decision close. Coders must leave this step to the human.
76
+
77
+ `close.require_human: true` in the run snapshot also makes plain close require terminal confirmation. It cannot change through `run reload`. An active `close:routine` grant permits unattended close only with signed approvals for every changed file, a signed green gate row, no waiver rows of any kind and no transcript or accepted hits; otherwise `close-not-routine: <reason>` refuses it. A routine close also signs `autopilot.close {block, grant}`. With the key absent or false, ordinary close retains its existing behavior.
78
+
79
+ For a block with `human_checks`, a successful close runs all code gates and writes the normal close rows plus `block.awaiting_human_check {block, checks}`, then sets `awaiting_human_check`. It prints each check and the next command: `code-forge block human-pass <id> --run <r> --note "<device check note>" [--proof <dir-or-file>]`. Awaiting blocks are unfinished: no done file or end hook yet, no worktree removal, and `run end` refuses and names them. Both status commands and the report list `awaiting_human_check: [ids]`. The human checks the device, then types that command in their own terminal (both stdin and stdout must be TTYs); there is no `--yes`, and an active autopilot grant refuses it. The note is required. Proof is optional: when supplied, it must name an existing path and is recorded as a string; when omitted, the row records `proof: null`. One signed `block.human_pass {block, note, proof, checks, by: human}` closes the block and emits its normal end signal; `block stop` is the alternative when a check fails. Coders must never run `human-pass`. A block with no human checks closes as before.
80
+
81
+ With `gates.repeat_new_tests: N` (integer 2..10; absent means off), a passing `gates run --run <r> --block <id>` test gate runs N additional times for only the block's added or changed test files against its base, using a whole `{test_files}` argv item; without that item it prints `repeat_new_tests: skipped (gates.test has no {test_files})`, and without changed tests it does no repeats. A failed repeat makes the gate red with `reason: flaky-test`, the file list and failing output; JSON and signed gate rows carry `repeats: {n, files, failed}`, and the report's forecast section shows the count.
82
+
83
+ With `gates.test` configured, `block close` refuses (exit 1) without a green `gate.done` row for this run and block: `no green gates run; run: code-forge gates run --run <r> --block <id>`. When timestamps allow comparison, the green row must be newer than the last commit touching owned paths after the block base; otherwise any green row suffices. The history lookup must succeed; only a successful empty log means no later commit. Waiving reviews does not lift the test gate; the human confirmation and other bypass diagnostics still apply.
84
+
85
+ `block accept-hit <id> --run <r> --line <n> --transcript <path> --reason <text>` asks the human once for every rule hit on that line and its paired Codex `item.completed` line (same item id), then signs one acceptance per rule and line. Add `--rule <rule-id>` to keep the single-hit behavior.
86
+
67
87
  The coder's report is a claim sheet. The gate reproduces each number in the measurement export (`proof.md` §3), and so do you when you read the report:
68
88
 
69
89
  | claim | re-measured by |
@@ -71,7 +91,7 @@ The coder's report is a claim sheet. The gate reproduces each number in the meas
71
91
  | only owned paths touched | file set = tracked changes since the base ∪ untracked (not ignored), listed from the repository top level, outside the repository top-level `.code-forge/` (keep transcripts and reports there; a subdirectory workspace's own `.code-forge/` is source tree except this run's record mirror and the block's default transcript: a `--transcript` or `--report` path in the source tree is a change like any other); each file must be in `owned_files`, unchanged since `block open` recorded the tree (`tree_at_open`; a path unreadable at open or at close never counts as unchanged), owned by another block of the run that is still **open**, or a closed block's file still exactly at the content its gate approved — else `unowned_change <file>` until `forge block claim <id> <path> --run <r>` or the human puts it back (not lifted by `--no-require-reviews`). The gate cannot tell which coder wrote a file an open sibling owns: it passes it to that sibling's gate and prints `WARN block <id>: <file> changed and is owned by open block <other>` for each one — read those lines |
72
92
  | every file reviewed | file set = owned files changed since the base ∪ owned files with any review row of the block (`review.result`/`round`/`approved`/`triage`/`cap`/`late_finding`), an owned entry with no glob covering the paths below it; every path is repo-root-relative (what the worker signs), and an owned entry also matches relative to a workspace in a subdirectory; an empty set refuses `no_changes` (only the owner's `--no-require-reviews`, confirmed at a terminal, lifts it), and an empty diff with review rows WARNs (work committed before `block open`, or in another worktree — open blocks on a base without the work, in the workspace the coder edits). The close prints a `reviewed <path> ticket <id>` line per file the review gate accepts (the `--report` the orchestrator always passes needs none). The gate accepts a changed or new file with a **signed** `review.approved` row for its current content hash that no later review of the same content overrode (a `review.result` with `approved: false`, a `review.round` with findings open, a `review.cap`), or with every finding its review left open waived or answered (`reviewed_by_waiver`): else `not_approved <file> <finding>` per open finding, cleared only by a fix and a fresh approval or a waiver — a critical only by the human's `block waive` — or an architect-ruled disposition to a named successor block |
73
93
  | tests green | every gate command run once in the export, each exit status read |
74
- | clauses covered | every clause names ≥ 1 test id; each test exists and passed in this run; S1 `scope` on the hunks outside the named tests says `false` |
94
+ | clauses covered | every code clause names ≥ 1 test id; each test exists and passed in this run; `kind: human_check` clauses name no tests and wait for a human after code gates; S1 `scope` on the hunks outside the named tests says `false` |
75
95
  | test count | the filtered run's delta equals the new tests' own count |
76
96
  | line and case forecast | `git diff --stat` net lines against `--lines`: > 1.25 × ⇒ stop (`===BLOCK <id> FAILED: forecast exceeded, split plan <id>a/<id>b===`); cases > 1.5 × forecast ⇒ a `forecast.cases_exceeded` WARN row, never a stop |
77
97
  | no rule break | the transcript grep for forbidden verbs and paths (`--transcript <file>`, else the run record's log); a missing transcript ⇒ a signed `gate.transcript_missing` row and a WARN, never silence |
@@ -85,6 +105,8 @@ The coder's report is a claim sheet. The gate reproduces each number in the meas
85
105
 
86
106
  Without the flag the tree stays. On a block without a worktree the flag is a usage error (exit 2). The flag is forbidden to coders: only you remove a tree.
87
107
 
108
+ After close, if the base moved, run `code-forge block rebase <id> --run <r>` before the fast-forward merge.
109
+
88
110
  **When a file stops** (`review_cap`, `l3_patch_exhausted`, `split_required`, …), the block cannot close. The stop's `exits` name the ways out. Take them in this order; `review.md` §5 gives each one's rules and refusals:
89
111
 
90
112
  1. **Fix, then `block reopen`.** When the fix belongs in the stopped file, have it fixed. Then print the exact command, `forge block reopen <id> --file <path> --run <r> --reason <text>` with the human's reason, and the human runs it in their own terminal, exactly like `block waive`. For this comparison, `block waive` means its human path. Never run it yourself, and never for a coder (it is on the coder forbidden list). After it, send the coder's `review-file` again: the next round is a full round 1 when the content differs from what the stop reviewed; on the stop's own content the stop repeats. For known stop content, `reopen-same-content` means the content is still what the stop reviewed: offer the waive instead. A legacy stop with unknown content accepts a current hash without a verified `review.round` of this run/block/file and records `from_hash: null`. If that hash already has such a round, change the file and reopen, or the owner can use `block close --no-require-reviews`; waivers at an old hash cannot clear the current one.
@@ -110,6 +132,8 @@ Both `autonomy` keys default to false. Whatever they say, a block's branch is ha
110
132
 
111
133
  **An outside review of the PR (B66).** When a reviewer outside code-forge (the `review.external` reviewer, a person, a CI review bot) has finished with a PR, record its rounds and findings: `forge ledger outside-review --run <r> --pr <n> --rounds <n> --findings <n> [--link <url>] [--block <id>]...` — name every block the PR carries (`ledger.md` §4). A later call for the same PR replaces the earlier one in `forge report`, whose `outside_review` section sets the rounds next to code-forge's cost for those blocks.
112
134
 
135
+ `report --slug <s>`, `ledger tail --slug <s>` and `ledger calibration --slug <s>` exit 1 when the ledger file is absent, naming the slug and sorted known slugs (rotations counted once, or `none`). An existing empty ledger succeeds with its empty output.
136
+
113
137
  **A finding dispositioned out of the block:** only the architect may rule it, and only when severity is warning or lower, every fix site lies outside the owned paths, no shipped UI or API reaches the failure path after merge, and a **named successor block** carries the failure path as its opening clause. The verdict stays recorded; a disposition without a successor clause is a dismissal, and dismissals do not exist.
114
138
 
115
139
  ## §8 Definition of Done (per block)
@@ -1,12 +1,16 @@
1
1
  # Review — per file, fresh L2 session, bounded rounds
2
2
 
3
+ A light block reviews all owned changed files in one packet, with each path heading and its full diff, using `review.budgets.full_in` and one full reviewer session. Findings name their file; each file receives signed `review.round` and, when clean, `review.approved` rows for its own content hash. `review-file --wait-block` joins those file tickets as usual. Open critical/warning findings end the round: fix them, then join again for one new batch of all changed files. At most two batches run per block, including across worker restarts; subsequent changes use the normal per-file loop. Keep large-file routing configured for the long-context reviewer.
4
+
3
5
  ## §1 The trigger is a CLI call or commit watch, and the file set is complete
4
6
 
5
- `forge review-file <path> --block <id> --run <r>` enqueues a ticket and returns `{ticket, status: queued}` in under a second; `forge review-file --wait <ticket> [--max 90s]` polls it. The same content answers the cached result; `review-file <path> --block <id> --fresh` (B75) drops that cached result and queues the ticket again only when it never counted as a review (`stale`, `unavailable`, round-1 `split_required`, or `refused` with `other-run`, `context-unreadable` or `worktree-*`). Use it after such a transient answer to get a real review; a coder may run it, and with no cached result it is a plain `review-file`. A cached `review-state-tampered` is refused `fresh-tampered` (only the owner's `block close --no-require-reviews` gets past it); any other cached result is refused `fresh-needs-reopen` (the human's exits are `block reopen` and `block waive`). A refused marker is read in place and never moved; only a re-runnable one is claimed (an atomic rename, then a byte compare) and dropped. The worker computes the diff itself against the base sha `forge block open` recorded: tracked changes plus untracked files, filtered by `owned_files` for the review and unfiltered for the scope check. Without commit watch, a coder that never calls `review-file` gains nothing: the block gate refuses any changed or new file without a signed `review.approved` row for its final content hash. The reviewer is always a new OS process (R1, R10) — identical under Solo, a harness subagent, a detached CLI process, or a human typing.
7
+ Each file's `outcome` is `approved`, `findings`, `stopped`, `split_required` or `unavailable` from its completion marker, and `null` while `pending` or `not_queued`; a live join queues an owned changed file even if it has no earlier ticket. A `split_required` outcome returns `done: false`, `reason: split_required` and `next` naming the file: the owner raises `review.budgets`, runs `run reload`, then runs `review-file <path> --fresh`.
8
+
9
+ `forge review-file <path> --block <id> --run <r>` enqueues a ticket and returns `{ticket, status: queued}` in under a second; `forge review-file --wait <ticket> [--max 90s]` polls it. The same content answers the cached result; `review-file <path> --block <id> --fresh` (B75) drops that cached result and queues the ticket again only when it never counted as a review (`stale`, `unavailable`, round-1 `split_required`, or `refused` with `other-run`, `context-unreadable` or `worktree-*`), or when it is a `split_required` stop whose measured lens budget changed since it was measured (see below). With an unchanged budget, a counted `split_required` stop is still refused `fresh-needs-reopen`. Use it after such a transient answer or budget change to get a real review; a coder may run it, and with no cached result it is a plain `review-file`. A cached `review-state-tampered` is refused `fresh-tampered` (only the owner's `block close --no-require-reviews` gets past it); any other cached result is refused `fresh-needs-reopen` (the human's exits are `block reopen` and `block waive`). A refused marker is read in place and never moved; only a re-runnable one is claimed (an atomic rename, then a byte compare) and dropped. The worker computes the diff itself against the base sha `forge block open` recorded: tracked changes plus untracked files, filtered by `owned_files` for the review and unfiltered for the scope check. Without commit watch, a coder that never calls `review-file` gains nothing: the block gate refuses any changed or new file without a signed `review.approved` row for its final content hash. The reviewer is always a new OS process (R1, R10) — identical under Solo, a harness subagent, a detached CLI process, or a human typing.
6
10
 
7
11
  With `review.file_loop: watch` (B104), commit each owned file after its tests are green and continue coding. Watch replaces per-file enqueueing. Before the completion sentinel, including fix rounds, run `forge review-file --wait-block <id> --run <r> [--max 90s]`, inspect finished tickets with `--wait <ticket>`, and fix findings. The worker watches the block worktree HEAD (otherwise repository HEAD) and reads owned content from git objects. A file's first commit is queued at once with the same ticket id as a manual request. Later commits of a file that already has a ticket of this block are held until `--wait-block`. If the last review found problems and the file changed blind, the join does not recheck it. It answers `status: findings` with that ticket and records delivery once. Read the findings with `review-file --wait <ticket>`, fix them, and the next join rechecks the latest content once. A file whose last review approved still queues its latest content at the join. A stopped file is not requeued. Each held file writes one `watch.held {block, file, content_hash}` row per new head. Older pending tickets are still marked `refused`, `reason: superseded`. A running ticket finishes for its old hash, which cannot approve newer bytes; `--wait-block` joins the current ticket per file. The join reruns a stale, unavailable or round-1 `split_required` review of the current content once. The file stays `pending` until that rerun finishes. No manual `--fresh` is needed. The retry is recorded per ticket across joins. If the rerun is stale again, later joins report `done`; `--wait <ticket>` shows that stale status. `--fresh` refuses a superseded marker; enqueueing that content again removes only its superseded marker so it can run, while a real result stays cached. `queue/watch/<block>.json` stores the last seen commit; missing or unreadable state restarts at `base_sha` with idempotent tickets. A rewound tick queues nothing and drops the last seen commit so the next tick restarts at `base_sha`. A `watch.skipped {block, reason}` row is written once per reason until a healthy head clears the skip reasons: `unreadable_head` (head, tree or objects cannot be read), `not_descendant` (head does not descend from the block base), `rewound` (head no longer descends from the last seen commit), or `unowned_path` (unowned paths are ignored; owned paths still queue). A skip never fails the block. After a `watch.skipped` row, use `forge review-file <path> --block <id> --run <r>` when the file needs a manual review; use `forge review-file <fix file> --block <id> --run <r> --answers <ids> --of <source>` for a fix in another owned file that answers findings. Set watch before starting the worker. Without the `review.file_loop: watch` setting, the default `wait` loop performs no watch work or git calls.
8
12
 
9
- A cached round-1 `split_required` ran no session and counted no round. Plain `review-file` drops its marker and re-queues the same ticket with `retry: true`; `--fresh` also re-queues it (B110). After the human raises the budget and runs `run reload`, the worker re-plans with the live config; the ticket id stays the same. If it still does not fit, it returns `split_required` again with no session or cost. A recheck stop (`status: stopped`, `stopped: split_required`) has a `review.cap` row and still needs `block reopen` or `block waive`; plain review keeps it cached and `--fresh` refuses `fresh-needs-reopen`.
13
+ A cached round-1 `split_required` ran no session and counted no round. Plain `review-file` drops its marker and re-queues the same ticket with `retry: true`; `--fresh` also re-queues it (B110). After the human raises the budget and runs `run reload`, the worker re-plans with the live config; the ticket id stays the same. If it still does not fit, it returns `split_required` again with no session or cost. For a recheck `split_required` stop, raising its lens budget and running `run reload` is enough for plain `review-file` or `--fresh` to review the same content again, printing the old and new budgets; with an unchanged budget it stays cached and `--fresh` still refuses `fresh-needs-reopen`.
10
14
 
11
15
  A ticket whose session breaks is not a review. A broken result is a `schema` miss or a timed-out attempt (B91): a timeout counts as a broken result, like a schema miss. Misses and timeouts of the same packet, level and role are counted together, so one miss plus one timeout is two. A packet's second broken result is tried once on the ticket's second level, in the same ticket, only when a second level exists (`review.second_levels.<level>`, or for a routed ticket the rule's `second`) and never in consensus mode (§4).
12
16
 
@@ -14,6 +18,8 @@ Under `proof.isolation: worktree` (`proof.md` §3) the coder calls `review-file`
14
18
 
15
19
  **The owner's two ways out of the per-file review.** (1) `forge block close <id> --no-require-reviews`, typed by the owner at a terminal (`code.md` §5). (2) `review.external: {required: true, name: <reviewer>}` in `.code-forge.yml`, set before `run start`, when an outside reviewer reviews each block (B58). With `required: true`, `run start` refuses a missing or blank `name`, and writes a signed `review.external` row with the reviewer's name. `run reload` refuses to add, remove or change the key. With the key, `block close` does not check the review rows (a `not_approved` file included) or the report's ticket ids, needs no terminal and works under an autopilot grant. When the block closes, it writes a signed `gate.reviews_waived {by: config, reviewer}` row just before `block.close`; a refused close writes no such row. Unowned changes, `unproven`, `no_changes`, the transcript grep and the registry's orphans still refuse. No block may own `.code-forge.yml` while the key is on: `block open` and `block claim` refuse it. `block open`, `block claim` and `block close` read the run's config snapshot to find the key, so a snapshot that does not match its hash refuses them with `config-snapshot`, with or without the key. Never set or remove the key yourself: it is the owner's decision.
16
20
 
21
+ The orchestrator can add a review worker to an active run with `run start --add-worker --run <id>`, up to `review.workers` (1–4, default 1); coders never run this verb.
22
+
17
23
  ## §2 Depth by risk (adaptive mode)
18
24
 
19
25
  The topology is decided in one place (`planSessions`). Consensus (§6) runs only when `review.multimodel` is on **and** the file's risk is above `review.single_reviewer_max_risk` (default 1) **and** the block is not a `docs`/`contract` block (`decisions.md` §5) unless `review.multimodel_for_docs` is true (default false). Everything else is adaptive, below. Why: a consensus review of a four-file docs block cost $16.70 — review cost dominates, and a low-risk file gains nothing from a second provider.
@@ -99,6 +105,8 @@ Only real rechecks count toward the round cap, per-level ladder and stall rules
99
105
 
100
106
  Rechecks are never degraded by the budget: a round-2+ packet is already the minimum.
101
107
 
108
+ Recheck packets include each open finding's evidence; reviewers keeping one open must supply `current_lines` quoting current file lines (≤ 300 characters): a nonempty quote absent from the current file after whitespace normalisation closes it as stale with a signed `review.stale_finding` row, while missing or empty quotes keep the finding open and count as before. A stale finding does not make the round heavy, and a round whose open findings all close as stale passes. Only new ids in a recheck repeating an earlier round's trimmed claim and evidence for the same file are listed in `review.round.repeated`; rounds containing only repeated or stale findings add no per-level or warning-round weight and cannot trigger escalation on their own.
109
+
102
110
  ## §6 Consensus mode (`review.multimodel` on, risk above `review.single_reviewer_max_risk`, not a docs/contract block unless `review.multimodel_for_docs`)
103
111
 
104
112
  Two L2 reviewers, one per effective provider (`resolve(L2)` and `review.second_levels`), each a fresh blind session with the `full` lens; then a fresh L3 judge **from the third provider** reads the two reports and returns the findings plus `agreement: both|A-only|B-only`. The judge's `fix_now` list is final. Second provider unavailable at run start ⇒ `consensus unavailable: <reason>`; ask the human once per run whether to continue adaptive, stop when non-interactive. Rechecks run on the provider that raised the finding; §5 applies unchanged.
@@ -10,7 +10,7 @@
10
10
  # copied install with no package next to it); `test/skill/shim.test.mjs` asserts the pin
11
11
  # equals package.json's version.
12
12
  # The shim holds no logic of its own: every verb, flag and message is the CLI's.
13
- PINNED_VERSION="0.14.1"
13
+ PINNED_VERSION="0.15.0"
14
14
  set -eu
15
15
 
16
16
  self="$0"
@@ -13,7 +13,7 @@
13
13
  */
14
14
 
15
15
  /** Scopes an owner may delegate. */
16
- export const ALLOWABLE_SCOPES = Object.freeze(['waive:warning', 'waive:nit', 'round:extra', 'model:choose']);
16
+ export const ALLOWABLE_SCOPES = Object.freeze(['waive:warning', 'waive:nit', 'round:extra', 'model:choose', 'close:routine']);
17
17
 
18
18
  /** What each allowable scope lets the delegate do (status output and docs). */
19
19
  export const ALLOWABLE_WHAT = Object.freeze({
@@ -21,6 +21,7 @@ export const ALLOWABLE_WHAT = Object.freeze({
21
21
  'waive:nit': 'waive a nit finding',
22
22
  'round:extra': 'one fix round past the cap for a file',
23
23
  'model:choose': 'choose the coder level for a block',
24
+ 'close:routine': 'close a routine block',
24
25
  });
25
26
 
26
27
  /**