@codedology/code-forge 0.8.0 → 0.10.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 (64) hide show
  1. package/CHANGELOG.md +114 -0
  2. package/README.md +46 -7
  3. package/docs/reference/blocks.json +618 -1
  4. package/docs/reference/config.md +36 -0
  5. package/package.json +1 -1
  6. package/schema/code-forge.schema.json +89 -0
  7. package/skill/references/adapters/claude-code.md +1 -1
  8. package/skill/references/adapters/solo.md +2 -2
  9. package/skill/references/adapters/subprocess.md +12 -2
  10. package/skill/references/code.md +21 -7
  11. package/skill/references/ledger.md +2 -2
  12. package/skill/references/proof.md +77 -2
  13. package/skill/references/review.md +29 -4
  14. package/skill/references/security.md +5 -5
  15. package/skill/scripts/forge +1 -1
  16. package/skill/templates/coder-brief.md +4 -2
  17. package/src/autopilot/binnacle.mjs +9 -3
  18. package/src/cli/block.mjs +300 -33
  19. package/src/cli/jev.mjs +22 -3
  20. package/src/cli/keys.mjs +156 -21
  21. package/src/cli/proof.mjs +35 -7
  22. package/src/cli/review-file.mjs +264 -13
  23. package/src/cli/spawn.mjs +44 -3
  24. package/src/config/validate.mjs +320 -5
  25. package/src/doctor/index.mjs +7 -5
  26. package/src/doctor/local.mjs +116 -8
  27. package/src/doctor/probes.mjs +38 -0
  28. package/src/engines/builders/codex.mjs +44 -4
  29. package/src/engines/sandbox-dirs.mjs +257 -0
  30. package/src/keys/store.mjs +128 -21
  31. package/src/ledger/write.mjs +7 -1
  32. package/src/proof/junit.mjs +530 -0
  33. package/src/proof/red-green.mjs +57 -9
  34. package/src/proof/red-parse.mjs +2 -0
  35. package/src/proof/runners.mjs +472 -0
  36. package/src/proof/xcresult.mjs +285 -0
  37. package/src/review/answers.mjs +445 -0
  38. package/src/review/deletion.mjs +106 -0
  39. package/src/review/engine.mjs +117 -31
  40. package/src/review/fixloop.mjs +70 -28
  41. package/src/review/gate-check.mjs +378 -80
  42. package/src/review/lenses/A.md +1 -0
  43. package/src/review/lenses/B.md +1 -0
  44. package/src/review/lenses/full.md +1 -0
  45. package/src/review/lenses/quick.md +1 -0
  46. package/src/review/lenses/recheck.md +1 -0
  47. package/src/review/reopen.mjs +256 -0
  48. package/src/review/route.mjs +232 -0
  49. package/src/review/schema-fallback.mjs +77 -22
  50. package/src/review/session-retry.mjs +44 -9
  51. package/src/review/stop-exits.mjs +15 -0
  52. package/src/review/test-quality.mjs +201 -0
  53. package/src/review/verb-forms.mjs +39 -0
  54. package/src/session/spawn.mjs +39 -2
  55. package/src/state/block.mjs +13 -3
  56. package/src/state/config-snapshot.mjs +39 -0
  57. package/src/state/run.mjs +4 -0
  58. package/src/util/forbidden.mjs +11 -1
  59. package/src/util/tmp.mjs +16 -4
  60. package/src/worker/engine.mjs +75 -9
  61. package/src/worker/loop.mjs +82 -15
  62. package/src/worker/queue.mjs +265 -10
  63. package/src/worker/review-state.mjs +346 -51
  64. package/src/worker/ticket.mjs +9 -1
package/CHANGELOG.md CHANGED
@@ -6,6 +6,120 @@ waits on a second real consumer (plan §10.4, Q17).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.10.0] — 2026-10-13
10
+
11
+ ### Added
12
+
13
+ - `keys.cache_hours` (1–24 h, default 8) and `keys set --op … --cache-hours <n>` set how long a key read from 1Password
14
+ stays cached; a lower value shortens an existing cached key at the next read, `keys set --op` refuses an invalid value
15
+ and other readers warn and use 8 h (B84, #14).
16
+ - `keys list` and `doctor` warn when a cached 1Password key has less than 1 hour left, and `jev ask` prints a re-link
17
+ command (`code-forge keys set jev --op <ref>`, the ref shell-quoted when needed) when the cached key expired and
18
+ 1Password did not answer; the key index keeps the `op://` reference (B85, #14).
19
+ - `src/proof/junit.mjs`: a fail-closed JUnit XML reader and RED/GREEN classifier for `proof red-green` (no DTD or
20
+ entities, 8 MiB cap, only failed assertions count as RED; a SwiftPM bare failure needs a matching XCTest console
21
+ line); used by `proof.runners` from B87 (B86, #7).
22
+ - `proof.runners`: `proof red-green` proves a non-`node:test` file through the matching runner's argv and its JUnit
23
+ report; the list is frozen in the run snapshot, validated by rule 17 `proof-runners-invalid`, and the row carries
24
+ `runner`, `env_keys` and `failed_tests` (B87, #7).
25
+ - `swift` adapter for `proof.runners`: anchored `{test_filter}` (file base name or `Suite/test` case), reads the XCTest
26
+ report and its Swift Testing sibling, `swiftc` compile errors give `RED_INVALID compile`; opt-in live check
27
+ `CODE_FORGE_LIVE_SWIFT=1` verified on Swift 6.3.3 (B88, #7).
28
+ - PHP red→green through the `junit` adapter, verified live on Laravel 13 with PHPUnit 12 and Pest 4
29
+ (`CODE_FORGE_LIVE_PHP=1`); captured reports replace the hand-written PHP fixtures (B89, #7).
30
+ - The `xcodebuild` adapter for `proof.runners`: `xcode` keys, `{only_testing}` and `{result_bundle}` tokens, a precheck
31
+ for full Xcode and an available simulator (`xcode-missing`, `destination-unavailable`), a fail-closed `xcresulttool`
32
+ reader, and `RED_INVALID compile` for a build failure; fixtures captured from Xcode 26.6 (B102, #7).
33
+ - A live `xcodebuild` red→green check on an iOS simulator (`CODE_FORGE_LIVE_XCODE=1`), verified with Xcode 26.6 on an
34
+ iPhone 17 simulator; it skips with a reason without full Xcode or an available simulator (B103, #7).
35
+ - `review.routes`: size-based review routing. A file with at least `min_lines` lines is reviewed (L2) or judged (L3) by
36
+ the rule's model, with its own session timeout and second level; one signed `review.route` row per routed level;
37
+ config rule 18 `review-routes-invalid` (B90, #10).
38
+ - A `## test-quality` section in review packets lists the lines of a test file that read a source file and assert on its
39
+ text; every reviewer lens gets one rule to judge those lines (a hint, not a finding); recheck packets get the rule but
40
+ no section (B92, #10).
41
+ - `coder.sandbox` with `profile: workspace|toolchain|full` and `add_dirs`: `toolchain` gives the Codex coder one
42
+ `--add-dir` per entry, `full` runs it with `-s danger-full-access` and `doctor` warns; entries that are, hold or sit
43
+ inside a protected dir (home, `~/.ssh`, `~/.codex`, `~/.code-forge`, the run temp root, …) are refused
44
+ `sandbox-dir-refused` after symlinks are followed; the key is fixed for the run and `forge spawn --role coder --run`
45
+ refuses a changed live value `sandbox-changed` (B93, #10).
46
+
47
+ ### Changed
48
+
49
+ - A timed-out review session is a broken result like a schema miss: at a packet's second broken result the second level
50
+ (`review.second_levels.<level>`, or a routed rule's `second`) is tried once in the same ticket;
51
+ `review.session_timeout` notes carry `packet_hash` and are signed, and `review.schema_fallback` gains `trigger:
52
+ schema|timeout` (B91, #10).
53
+ - The skill sends ONE fix message per block: after the coder's first COMPLETE it waits for every `review-file` ticket
54
+ and the block's tests, then sends every failing test and every open finding together; a later finding waits for the
55
+ next round, and the per-file round cap still counts per file (B94, #10).
56
+ - `block waive` and `block reopen` are human-only: they ask for a yes at a terminal, have no `--yes`, exit 2 and record
57
+ nothing with stdin or stdout not a terminal, and refuse a bad id, path, control character, unowned file or unknown
58
+ finding before asking; an autopilot grant does not change this; the prompt does not prove a human, since a
59
+ pseudo-terminal passes it (B80, #10).
60
+
61
+ ### Fixed
62
+
63
+ - The JUnit reader accepts the real SwiftPM 6.3 bare XCTest failure (`message="failure"`), not only `failed`; captured
64
+ Swift reports replace the hand-written fixtures (B88, #7).
65
+ - A missing class in Laravel 13's JSON-escaped AI-agent console summary is now read as `import`, not `error` (B89, #7).
66
+
67
+ ## [0.9.0] — 2026-10-12
68
+
69
+ ### Added
70
+
71
+ - review-file --answers <ids> --of <source>: a fresh L2 session judges a fix made in another owned file; resolved ids
72
+ get signed review.answered rows bound to both content hashes, and the close gate counts a still-current answer like a
73
+ waiver (answered list on gate.review_waived_all).
74
+ - block reopen <id> --run <r> --file <path> --reason <text>: a human-only verb that restarts a stopped file on changed
75
+ content (or a lost one on its current content) with a full round 1; signed review.reopened row, one per anchor seq;
76
+ refusal codes reopen-same-content, reopen-tampered, reopen-not-stopped, reopen-block-not-open, reopen-not-owned,
77
+ reopen-config, reopen-already-recorded, reopen-write-failed.
78
+ - A stopped `review-file` answer carries `exits`, and the gate's `review_cap` and stopped-file `unreviewed` detail end
79
+ with the same exits (B73, issue #12).
80
+ - The review packet of a deleted owned file carries a `## deleted file` note: up to 20 tracked or untracked paths that
81
+ still name its basename (one bounded `git grep`, secret-like and control-character paths left out), `no references
82
+ found`, or `references not checked` (B74, issue #8).
83
+ - `review-file --fresh` drops a cached result that never counted and queues the ticket again; counted results refuse
84
+ `fresh-needs-reopen`, tampered ones `fresh-tampered`, and the cached marker is never moved (B75, issue #10 item 4).
85
+
86
+ ### Changed
87
+
88
+ - Review state is saved under ~/.code-forge/runs/<run>/review-state/ instead of the temp root; older state files are
89
+ moved once after an anchor check; doctor warns about missing state files of active runs (#15)
90
+ - Review state: the reviewed text is stored as a git blob (reviewed_content_blob in the signed review.state body), so a
91
+ restored state keeps reviewed_content after the file changed; a missing, malformed or mismatching blob falls back to
92
+ the full-file review.
93
+ - A `patch_check` that closes at least one finding open before the patch (by id) and is under
94
+ `review.max_rounds_per_file` continues the fix loop at L2; a patch that closes nothing still stops
95
+ `l3_patch_exhausted` (B73, issue #12).
96
+ - The skill and docs name the review exits in order, say where review state lives and how the ledger rebuilds it; the
97
+ coder brief names `review-file --answers`, and the security reference lists `block reopen` as forbidden to a coder
98
+ (B76).
99
+
100
+ ### Fixed
101
+
102
+ - A lost review state file is rebuilt from the signed ledger and recorded as review.state_restored; rounds, findings and
103
+ stops stay; with no ledger copy the ticket is refused as review-state-lost, not tampered (#15)
104
+ - A file whose every finding is cleared by accepted waivers counts as reviewed (reviewed_by_waiver) and block close
105
+ writes a signed gate.review_waived_all row; unknown severities are human-only (#9)
106
+ - Test isolation guard: new files of a run that existed before the test process (its lock, key or run directory) no
107
+ longer count as leaks; a new run id or any other new path still fails the process.
108
+ - `review-file --answers` reads the severity of every `review.cap` id from the signed refusal rows, so an id the close
109
+ cannot count is refused before a reviewer session (B79).
110
+ - The answers packet checks the source file with the secret-path rule, redacts each part before it is cut, keeps every
111
+ part and the whole packet inside the budget, and refuses `packet-too-large` when the fixed text alone is over it
112
+ (B79).
113
+ - `block reopen` refuses a non-string `tmp.root` as `reopen-config`, and `reopen-write-failed` names the error code,
114
+ never a path (B79).
115
+ - The ledger rotation race test forces the order of the two racers with an `afterLink` hook instead of scheduling them,
116
+ so it no longer fails at random under load (B81).
117
+ - The worker restart test waits up to 5 s for killed sessions to exit instead of checking once, so it no longer fails at
118
+ random under load (B82).
119
+ - `review-file --fresh`: a failed restore of the done marker no longer leaves the ticket looking pending; it writes a
120
+ refused `fresh-restore-failed` marker or keeps the claim, `block reopen` clears leftover claims, and the messages say
121
+ which state was left (B83).
122
+
9
123
  ## [0.8.0] — 2026-10-12
10
124
 
11
125
  ### Added
package/README.md CHANGED
@@ -8,6 +8,43 @@ for cross-model, parallel, evidence-gated feature delivery — on Claude Code, C
8
8
  > `examples/` have all landed (`docs/reference/blocks.json` lists every build block). What changed
9
9
  > in each version is in [CHANGELOG.md](CHANGELOG.md).
10
10
 
11
+ ## What's new in 0.9
12
+
13
+ In the next release:
14
+
15
+ - **Choose how long a 1Password key stays cached (B84).** Set `keys.cache_hours` (1 to 24, default 8) in `.code-forge.yml`, or `keys set <name> --op <ref> --cache-hours <n>` for one key. A lower value shortens keys that are already cached at the next read.
16
+ - **Warnings before a cached key runs out (B85).** `keys list` and `doctor` warn when a 1Password key has less than 1 hour left. When the cached key expired and 1Password does not answer, `jev ask` prints the exact command to re-link it: `code-forge keys set jev --op <ref>`.
17
+ - **A test report reader for red-green proof (B86).** code-forge can now read a JUnit XML test report (XCTest, Swift Testing, PHPUnit, Pest, pytest). A missing, broken or empty report, or an error that is not a failed assertion, never counts as red. B87 connects it to `proof red-green`.
18
+ - **Red-green proof for any test runner (B87).** A new `proof.runners` list in `.code-forge.yml` names a runner per test-file glob: an adapter (`junit` or `swift`), an argv with `{test_file}`, `{test_id}`, `{test_filter}` and `{report}`, optional `env` and `timeout_s`. `proof red-green` runs that argv and reads its JUnit report, so PHPUnit, Pest, XCTest or pytest tests can be proven. The list is fixed at `run start`; a bad entry refuses the proof and never falls back to `node:test`.
19
+ - **Red-green proof for Swift (B88).** A `proof.runners` entry with `adapter: swift` runs `swift test --parallel --filter {test_filter} --xunit-output {report}` and reads both the XCTest and the Swift Testing reports. The test filter is anchored, so a similar suite or test name is never picked up. A test that does not compile at base is `RED_INVALID compile` (a test of a new API needs the human waiver). Checked live with Swift 6.3.3 and Xcode 26.6.
20
+ - **Red-green proof for PHP (B89).** A `proof.runners` entry with `adapter: junit` and `[php, artisan, test, "{test_file}", --filter, "{test_filter}", --log-junit, "{report}"]` proves PHPUnit and Pest tests in a Laravel app. A test of a class the block added is `RED_INVALID import`, also when Laravel prints its AI-agent JSON summary. Checked live with PHP 8.4, Laravel 13, PHPUnit 12 and Pest 4.
21
+ - **Red-green proof for Xcode projects (B102).** A `proof.runners` entry with `adapter: xcodebuild` and an `xcode` object (`scheme`, `destination`, `test_target`) proves XCTest and Swift Testing tests through `xcodebuild test` on a simulator or on macOS. code-forge checks first that full Xcode and the simulator are there, runs only the named suite with `-only-testing`, and reads the result bundle. A build failure is `RED_INVALID compile`. code-forge never boots, creates or deletes a simulator.
22
+ - **Xcode red-green checked live (B103).** An opt-in live check (`CODE_FORGE_LIVE_XCODE=1`) proves XCTest and Swift Testing tests red→green through `xcodebuild test` on an iPhone simulator. It skips with a reason when full Xcode or a simulator is missing. Verified with Xcode 26.6.
23
+ - **Big files go to a big-context reviewer (B90).** Set `review.routes` once, for example `{min_lines: 400, model: <id>, session_timeout_s: 900}`. Files with that many lines get that model and timeout. No more editing `.code-forge.yml` per file. Each routed file gets a signed `review.route` row.
24
+ - **Timeouts reach the second level (B91).** A timeout now counts as a broken result, like an answer that fails the schema. At the second broken result on one file, the second-level model is tried at once, in the same ticket. You no longer wait for a second ticket.
25
+ - **Test-quality hint (B92).** A test that reads a source file and checks its text is a weak test. The review packet now lists those lines for the reviewer. The reviewer decides if it is a problem. The list is a hint, not a finding.
26
+ - **More room for the Codex coder (B93).** New setting `coder.sandbox`. With `profile: toolchain`, the Codex coder may also write to the folders you list in `add_dirs`, such as a build cache. With `profile: full`, the Codex sandbox is off and `doctor` warns you. Secret folders like `~/.ssh` are always refused. The setting is fixed for the whole run.
27
+ - **One fix round per block (B94).** The skill now waits for every review and test of a block. Then it sends the coder ONE fix message with every failing test and every open finding. It never sends findings one at a time.
28
+ - **Waive and reopen ask you at a terminal (B80).** `block waive` and `block reopen` now ask the human for a yes in their own terminal. There is no `--yes`. With no terminal they record nothing. The prompt is a speed bump, not proof of a human: a pseudo-terminal passes it.
29
+
30
+ In 0.9.0:
31
+
32
+ - **A lost review state is rebuilt, not reset.** When the review state file of a file is gone (for example, macOS cleaned its temp folder), the worker rebuilds it from the signed ledger and writes a `review.state_restored` row. Rounds, open findings and stops stay as they were. If the ledger cannot rebuild it, the ticket is refused as `review-state-lost`, not `tampered` (#15).
33
+ - **Review state no longer lives in the temp folder.** It is saved under `~/.code-forge/runs/<run>/review-state/`, so a temp cleanup or a worker restart does not touch it. A state file from an older run is moved there once, after a check against its signed anchor. `code-forge doctor` warns when a state file of an active run is missing and says whether the ledger can rebuild it (#15).
34
+ - **Waiving every finding is a real way out.** A file whose signed review on its current content left findings, all cleared by accepted waivers, now counts as reviewed (`reviewed_by_waiver`, never approved). `block close` writes one signed `gate.review_waived_all` row for it. A critical still needs a human waiver, and a finding with an unknown severity does too (#9).
35
+ - **Tests no longer fail because of your own live run.** The test guard used to fail when a code-forge run you had open wrote its lock file during `npm test`. Now new files of a run that existed before the tests are not counted as leaks. A new run or any other new file still fails the tests.
36
+ - **A restore brings back the exact reviewed text.** The reviewed text is now also kept as a git object, and the signed ledger points to it. After a lost state file is rebuilt, the next round reviews only what changed since the last review, even when the file changed. If git has removed the object, the round reviews the full file, as before (#15).
37
+ - **A fix in another file can answer a finding.** `review-file <file> --block <id> --answers <ids> --of <source>` asks a fresh reviewer whether a change in another owned file of the block fixes findings of the source file. Each finding it judges `resolved` gets a signed `review.answered` row, and the source file can then close as `reviewed_by_waiver` with an `answered` list. If either file changes later, the answer no longer counts. The answering file still needs its own approval (#9).
38
+ - **`block reopen` restarts a stopped file.** After you change a file whose review stopped, `code-forge block reopen <id> --run <r> --file <path> --reason <text>` starts a fresh round 1 for it. A file whose review state is lost can be reopened on its current content. Only a human may run it: a coder's command line is blocked, like `block waive`. The block's one L3 step stays used. The same content is refused, so you cannot simply roll again (#12, #15).
39
+ - **Safer answers packets and clearer reopen errors (B79).** An id named only by a `review.cap` row with no recorded severity is refused before any reviewer session. The answers packet checks the source file for secrets, stays inside the packet budget and refuses `packet-too-large` instead of going over it. `block reopen` refuses a bad `tmp.root` and names the cause of a failed write.
40
+ - **A last-chance patch that fixes something is progress (B73).** When the L3 patch closes at least one old finding and the file is under the round cap, the fix loop goes on at L2 instead of stopping `l3_patch_exhausted`. A stopped answer from `review-file` and the gate's `review_cap` detail now list the exits: `block reopen`, `block waive`, `review-file --answers`, re-decompose.
41
+ - **A steadier test suite (B81).** The ledger rotation race test forces the race order through a test hook, so it no longer fails at random under load.
42
+ - **Deleted files get a useful review (B74).** When a block deletes an owned file, the review packet says the file is deleted and lists up to 20 files that still name it, so the reviewer can look for dangling references. A deletion still needs an approval.
43
+ - **`review-file --fresh` (B75).** Runs a file's review again only when the cached result never counted (`stale`, `unavailable`, or a non-verdict refusal such as `other-run`). A counted result is refused `fresh-needs-reopen` and names `block reopen` and `block waive`; a tampered one is refused `fresh-tampered`. A coder may use it.
44
+ - **Another steadier test (B82).** The worker restart test waits for killed sessions to exit instead of checking once, so it no longer fails at random under load.
45
+ - **The review exits, in one place (B76).** `skill/references/review.md` §5, `code.md` and `docs/how-it-works.md` name the ways out of a stopped file in order: fix and the human's `block reopen`, `review-file --answers`, `block waive`, re-decompose. They also say `block attempt` never resets review state and that a lost state file is rebuilt from the ledger.
46
+ - **`review-file --fresh` fails closed (B83).** When a cached result cannot be put back, the ticket gets a refused `fresh-restore-failed` marker (or keeps its claim), so the worker does not run it by itself. `block reopen` clears it. Messages name the error code only.
47
+
11
48
  ## What's new in 0.8
12
49
 
13
50
  In 0.8.0:
@@ -239,7 +276,7 @@ code-forge validate # loads and validates .code-forge.yml
239
276
  code-forge resolve L1 # {provider, model, effort, fallback, cli} for a level
240
277
  code-forge doctor --quick # config + links + key resolution only
241
278
  code-forge list # what is installed where, and whether it still resolves
242
- code-forge keys list # NAME / SOURCE / BACKEND / EXPIRES — never a value
279
+ code-forge keys list # NAME / SOURCE / BACKEND / EXPIRES — never a value; WARN under 1 h left
243
280
  code-forge models # the model catalog per provider
244
281
  code-forge --help # every verb, one per line
245
282
  code-forge version
@@ -273,7 +310,7 @@ usage line.
273
310
  | `validate` | `code-forge validate [--file <path>]` |
274
311
  | `resolve` | `code-forge resolve <L0\|L1\|L2\|L3>` |
275
312
  | `run` | `code-forge run start [--cwd <dir>] [--run <id>] [--engine <e>] [--worker-pid <pid>]` · `run start --reattach --run <id> [--worker-pid <pid>]` · `run status --run <id>` (with spent and budget USD) · `run reload --run <id>` (re-read `.code-forge.yml` mid-run) · `run end --run <id>` |
276
- | `block` | `code-forge block open <id> --run <r> [--level L<n>] --owned <paths…> --acceptance <file> [--brief <file>] [--attempt <n>] [--base <sha>] [--lines <n>] [--kind code\|docs\|contract]` · `block attempt\|rebase <id> --run <r>` · `block close <id> --run <r> [--transcript <file>] [--report <file>] [--no-require-reviews]` · `block claim <id> <path> --run <r>` · `block stop <id> --run <r> --reason <text>` · `block waive <id> <finding-id> --run <r> --file <path> --reason <text>` (human-only) |
313
+ | `block` | `code-forge block open <id> --run <r> [--level L<n>] --owned <paths…> --acceptance <file> [--brief <file>] [--attempt <n>] [--base <sha>] [--lines <n>] [--kind code\|docs\|contract]` · `block attempt\|rebase <id> --run <r>` · `block close <id> --run <r> [--transcript <file>] [--report <file>] [--no-require-reviews]` · `block claim <id> <path> --run <r>` · `block stop <id> --run <r> --reason <text>` · `block waive <id> <finding-id> --run <r> --file <path> --reason <text>` · `block reopen <id> --run <r> --file <path> --reason <text>` (both human-only: you run them in your own terminal, they ask for a yes, and there is no `--yes`) |
277
314
  | `worker` | `code-forge worker --run <id> [--cwd <dir>] [--poll-ms <n>] [--once]` — started detached by `run start`, never by a coder |
278
315
  | `review` | `code-forge review [--base <ref>] [--files <path…>] [--acceptance <file> \| --intent "<text>"] [--run <id>] [--max <seconds>] [--json] [--keep-run]` — review only: no coder, no proof, never closes a block |
279
316
  | `review-file` | `code-forge review-file <path> --block <id> [--run <id>]` (enqueue) · `review-file --wait <ticket> [--max <seconds>s]` (poll, default 90s; still running at `--max` prints `status: pending, reason: wait_timeout`) |
@@ -282,10 +319,10 @@ usage line.
282
319
  | `author` | `code-forge author --job plan\|harden --brief <file> [--facts <file>] [--draft <file>] [--answers <file>] [--out <file>] [--run <id>] [--timeout <seconds>]` |
283
320
  | `facts` | `code-forge facts --brief <file> [--sources <path>…] [--out <file>] [--run <id>] [--timeout <seconds>]` |
284
321
  | `plan` | `code-forge plan check <plan-file> [--facts <sheet>] [--slug <slug>]` — every block's level must be the lane recorded by `jev ask lane --block <id>`; prints `lanes: B1 L1 (rules) · …` |
285
- | `proof` | `code-forge proof tier --file <path> --risk <0-3> [--security] [--cwd <dir>]` · `proof export <block> --run <r> [--remove]` · `proof lock\|unlock\|restore <block> --run <r>` · `proof red-green <block> --run <r> --test <file[::case]> [--mechanism revert\|assertion-deletion]` |
322
+ | `proof` | `code-forge proof tier --file <path> --risk <0-3> [--security] [--cwd <dir>]` · `proof export <block> --run <r> [--remove]` · `proof lock\|unlock\|restore <block> --run <r>` · `proof red-green <block> --run <r> --test <file[::case]> [--mechanism revert\|assertion-deletion]` (a non-`node:test` file runs through its `proof.runners` entry) |
286
323
  | `gates` | `code-forge gates detect\|run\|secret-scan\|safe-edit\|scope\|acceptance\|transcript-grep --cwd <dir> …` |
287
324
  | `jev` | `code-forge jev ask <question-id> --state <file> [--cwd <dir>] [--slug <slug>] [--key-ref <ref>] [--block <id>] [--plan <file>] [--rules]` — `--rules` answers `lane`, `risk` or `security_sensitive` without Jev; writes to the project's ledger slug |
288
- | `keys` | `code-forge keys list \| set <name> [--op <item-id\|link\|op://ref>] \| test <name> [--ref <ref>] \| remove <name>` |
325
+ | `keys` | `code-forge keys list \| set <name> [--op <item-id\|link\|op://ref> [--cache-hours <1-24>]] \| test <name> [--ref <ref>] \| remove <name>` |
289
326
  | `ledger` | `code-forge ledger tail --slug <slug> [--n\|-n <count>]` · `ledger calibration --slug <slug> [--question <id>]` · `ledger outcome --slug <slug> --pr <n> --ci red\|green\|reverted` · `ledger outcome --slug <slug> --scan-git [--cwd <dir>] [--days <n>]` · `ledger add coder --run <r> --block <b> --usd <n> [--note "..."]` |
290
327
  | `report` | `code-forge report --slug <slug> [--json] [--export <dir>]` — 13 sections, then the spend per run (open blocks included) and the total |
291
328
  | `models` | `code-forge models [--refresh --from-cli-caches]` |
@@ -308,8 +345,8 @@ CLI verbs like any other and every one of them is exercised by this package's ow
308
345
  `doctor` always prints this line — the signature is not a defense against an attacker who
309
346
  already runs code as you, and no part of code-forge claims otherwise.
310
347
  - **Keys never reach a coder process.** Provider and Jev keys resolve only inside the worker or the
311
- orchestrator's own CLI calls (`env:<NAME>` → OS keychain → 1Password, cached briefly → ask at
312
- setup only). `keys list`/`keys test` never print a value; every stdout/stderr path is redacted.
348
+ orchestrator's own CLI calls (`env:<NAME>` → OS keychain → 1Password, cached for
349
+ `keys.cache_hours`: default 8 h, at most 24 h → ask at setup only). `keys list`/`keys test` never print a value; every stdout/stderr path is redacted.
313
350
  - **A forbidden-command list** stops a coder from starting its own worker, waiving a review, or
314
351
  running other orchestrator-only actions — rendered into each provider's own deny-list mechanism
315
352
  (`--disallowedTools` for Claude, an execpolicy rules file plus prose for Codex).
@@ -327,7 +364,8 @@ CLI verbs like any other and every one of them is exercised by this package's ow
327
364
  the warnings and the command list off. Every field is listed in [docs/privacy.md](docs/privacy.md).
328
365
  - **No mutation testing.** Neither this package's own tests nor the product it ships run mutation
329
366
  testing (Ricardo's ruling R14/Q16, 2026-09-25). Proof is plain unit and feature tests with exact
330
- assertions, plus a red→green runner that proves a new test fails before the fix and passes after.
367
+ assertions, plus a red→green runner that proves a new test fails before the fix and passes after (`node:test`
368
+ built in; any other runner through a `proof.runners` entry that writes a JUnit report).
331
369
 
332
370
  ## Configuration reference
333
371
 
@@ -346,6 +384,7 @@ new in 0.5, `autopilot.min_confidence` in 0.6):
346
384
  | `review.multimodel_for_docs` | `false` | let docs and contract blocks use multimodel review too |
347
385
  | `review.allow_open_book_codex` | `false` | let Codex run reviewer, judge, System 2 and plan author anyway |
348
386
  | `review.session_timeout_s` | `300` | a review session that runs longer is killed and retried once |
387
+ | `review.routes` | none | big files go to another model: a file with at least `min_lines` lines is reviewed by the rule's `model`, with its own `session_timeout_s` and `second` level |
349
388
  | `review.parallel_tickets` | `3` | review tickets the worker runs at once (1 to 16); `1` reviews one file at a time |
350
389
  | `review.provider_concurrency` | anthropic `4`, openai `2`, xai `2` | review sessions at once per provider |
351
390
  | `autopilot.min_confidence` | `0.7` | the lowest delegate confidence that is acted on; below it the question goes to you |