@codedology/code-forge 0.10.0 → 0.12.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 (44) hide show
  1. package/CHANGELOG.md +74 -0
  2. package/README.md +34 -3
  3. package/docs/reference/blocks.json +360 -1
  4. package/docs/reference/config.md +6 -5
  5. package/package.json +2 -2
  6. package/schema/code-forge.schema.json +3 -2
  7. package/skill/SKILL.md +3 -1
  8. package/skill/references/code.md +17 -8
  9. package/skill/references/proof.md +8 -2
  10. package/skill/references/review.md +32 -5
  11. package/skill/references/rules-core.md +1 -1
  12. package/skill/scripts/forge +1 -1
  13. package/skill/templates/coder-brief.md +17 -7
  14. package/src/cli/block.mjs +112 -7
  15. package/src/cli/gates.mjs +15 -6
  16. package/src/cli/review-file.mjs +118 -14
  17. package/src/config/validate.mjs +12 -5
  18. package/src/context/pack.mjs +7 -4
  19. package/src/gates/expand.mjs +102 -9
  20. package/src/gates/run.mjs +6 -4
  21. package/src/install/wizard/flags.mjs +1 -1
  22. package/src/proof/lock.mjs +23 -10
  23. package/src/proof/rule.mjs +9 -0
  24. package/src/proof/tiers.mjs +2 -14
  25. package/src/review/engine.mjs +59 -12
  26. package/src/review/fixloop.mjs +41 -16
  27. package/src/review/gate-check.mjs +2 -2
  28. package/src/review/lenses/A.md +5 -0
  29. package/src/review/lenses/B.md +5 -0
  30. package/src/review/lenses/full.md +5 -0
  31. package/src/review/lenses/judge.md +4 -0
  32. package/src/review/lenses/quick.md +4 -0
  33. package/src/review/lenses/recheck.md +5 -0
  34. package/src/review/packet.mjs +48 -14
  35. package/src/review/paired.mjs +81 -0
  36. package/src/review/reopen.mjs +18 -6
  37. package/src/review/reviewed-lines.mjs +94 -0
  38. package/src/state/block-end.mjs +10 -6
  39. package/src/util/exec.mjs +2 -1
  40. package/src/util/gate-token.mjs +3 -0
  41. package/src/worker/engine.mjs +43 -15
  42. package/src/worker/loop.mjs +35 -2
  43. package/src/worker/queue.mjs +60 -2
  44. package/src/worker/watch.mjs +113 -0
package/CHANGELOG.md CHANGED
@@ -6,6 +6,80 @@ waits on a second real consumer (plan §10.4, Q17).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.12.0] — 2026-10-14
10
+
11
+ ### Added
12
+
13
+ - `gates run --changed` (with `--run` and `--block`) narrows `{test_files}` to changed tests and stem-paired tests of
14
+ changed sources, and a new `{changed_files}` gate token passes the changed paths; it falls back with `reason`
15
+ `no_baseline`, `unpaired`, `threshold` or `no_changes`; a green `gate.done` records the owned file hashes as the next
16
+ baseline (B99, #16)
17
+ - `review.file_loop: overlap` and `review-file --wait-block <id> --run <r> [--max]`: one wait for all owned changed
18
+ files of a block, one JSON line `{block, files: [{file, ticket, status}], done}`; a file changed after its ticket gets
19
+ its current content enqueued; timeout exits 0 with `reason: wait_timeout`, `not_queued` files give `reason:
20
+ not_queued`, no live worker exits 3 with `worker_down` (B100)
21
+ - `review.file_loop: watch`: the worker watches the block's HEAD and queues owned files from new commits, with the same
22
+ ticket ids as `review-file`; an older pending ticket of the same file is marked `superseded`; problems write one
23
+ `watch.skipped` row per reason (B104)
24
+ - every `code-forge context pack` carries a built-in `## proof rule (code-forge)` section that `context.max_lines` never
25
+ cuts: the red→green definition, no hand mutations, one exact test per clause, proof scales with risk, no extras the
26
+ acceptance does not ask for (B117)
27
+
28
+ ### Changed
29
+
30
+ - `block close` reports `coder report FAILED: TESTS line malformed: <line>` for a `TESTS ` line that does not end in
31
+ `exit=<n>`; no line at all still gives `no TESTS line` (B114)
32
+ - every review lens tells the reviewer not to ask for hand mutations or tests beyond the acceptance; a weak assertion is
33
+ fixed by a stronger assertion; over-engineering is at most a `nit` (B117)
34
+
35
+ ### Removed
36
+
37
+ - the unused `proofRequirements` export in src/proof/tiers.mjs (B117)
38
+
39
+ ### Fixed
40
+
41
+ - every fix-loop stop (stall, cap, split, stop-at, L3 exhaustion) records `stop_content_hash`, so `block reopen` works
42
+ after it; a stop with unknown content reopens when no verified `review.round` names the current hash (`from_hash:
43
+ null`), and the refusal no longer points to `block waive` (B114, #17)
44
+ - paired tests: a Python `<stem>_test.py` file pairs with `<stem>.py`, like `test_<stem>.py` (B115)
45
+ - `npm test -- <args>` passes the extra args to the main test phase again; the snapshot and final no-leak steps still
46
+ run (B115)
47
+
48
+ ## [0.11.0] — 2026-10-13
49
+
50
+ ### Added
51
+
52
+ - `npm run release` moves the README "In the next release:" bullets under the new version, refuses an empty [Unreleased]
53
+ or a missing README anchor in preflight, restores README on a failed check, and runs `gen-config-doc --check`;
54
+ `docs/releasing.md` gets a releasing checklist (B108).
55
+ - `pairsFor` and `summarize` (src/review/paired.mjs) and an optional `## paired` packet section after `## context`,
56
+ dropped first over budget; `attachFiles` attaches an untracked file only when it is an owned, changed file of the
57
+ block and not ignored or secret-like (B97).
58
+ - The review engine, the fix-loop recheck and the worker send `## paired` (the block's owned changed peers and
59
+ `test_files`); the needs_file path passes the block's owned and changed files so a new untracked owned test file can
60
+ be attached; the lenses say a test named in `## paired` exists (B98).
61
+
62
+ ### Changed
63
+
64
+ - `block close` prints the `reviewed <path> ticket <id>` lines itself, for every file the review gate accepts; `--json`
65
+ and the done file carry `reviewed: [{file, ticket, via}]`; the coder report no longer needs hand ticket lines, and a
66
+ mismatched hand line under `--report` is a WARN, not a refusal (B96, #16).
67
+ - A quick review packet over `quick_in` steps up to one full L2 session when it fits `full_in`, before `split_required`;
68
+ the `review.plan` row carries `step_up: {from, tokens_in, budget}` (B109, #16).
69
+ - A cached round-1 `split_required` is re-queued by `review-file` (`retry: true`) and by `review-file --fresh`, with the
70
+ same ticket id; a recheck `split_required` stop still needs `block reopen` or `block waive` (B110, #16).
71
+ - A recheck with no fix hunks counts no round and returns `reviewed`, `approved: false`, `reason: 'no-fix-hunks'`; a
72
+ recheck answer that lacks a `resolved` entry for an open finding of its packet is a `schema` miss; the worker's
73
+ recheck summary adds `status`, `tokens_in`, `tokens_out` and `reason` (B111, #16).
74
+ - A review ticket reads the diffs of the block's other changed files only when the review asks for them, once per ticket
75
+ (B112)
76
+
77
+ ### Fixed
78
+
79
+ - A worker stops by itself, without taking a new ticket, when its run record or its HOME is removed (B113)
80
+ - Tests stop every worker they start, also when a test fails or times out; the final no-leak step of `npm test` fails
81
+ when a worker from this suite is still alive (B113)
82
+
9
83
  ## [0.10.0] — 2026-10-13
10
84
 
11
85
  ### Added
package/README.md CHANGED
@@ -8,10 +8,37 @@ 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
11
+ ## What's new in 0.12
12
12
 
13
13
  In the next release:
14
14
 
15
+ In 0.12.0:
16
+
17
+ - **Faster fix rounds (B99).** `code-forge gates run --run <r> --block <id> --changed` runs only the tests of files changed since the last green gate run, and `{changed_files}` passes only the changed files to lint or format gates. It falls back to the normal scope when it cannot be sure.
18
+ - **Write while reviews run (B100).** With `review.file_loop: overlap`, the coder enqueues each green file and keeps working. Before it ends, `forge review-file --wait-block <id> --run <r>` waits once for every file of the block.
19
+ - **Reopen after any stop (B114, #17).** Every review stop now records the content it reviewed. An older stop with unknown content can be reopened when the current content was never reviewed. A malformed `TESTS` line now gets its own clear error.
20
+ - **Python `*_test.py` tests pair with their source (B115).** Fix rounds and review packets now find `widget_test.py` for `widget.py`, as they already found `test_widget.py`.
21
+ - **Commit watch (B104).** Set `review.file_loop: watch` and the coder just commits each green file. The worker sees the new commits, queues each owned file for review and drops older pending reviews of the same file. `review-file --wait-block` waits for them all.
22
+ - **The proof rule travels with every block (B117).** Every context pack and every reviewer now carry one rule. Red→green: each new or changed test must fail on the old code first, then pass on the new code. No hand mutations. Proof scales with risk, and nothing is added that the task did not ask for.
23
+
24
+ ## What's new in 0.11
25
+
26
+ In 0.11.0:
27
+
28
+ - **The release moves the README What's new list (B108).** `npm run release` now moves the "In the next release:" bullets under "In X.Y.Z:". A minor or major release starts a new "What's new in X.Y" heading. The empty anchor stays for the next block. The release refuses an empty CHANGELOG [Unreleased] or a missing anchor before it changes anything, and a failed check puts the README back with the other files. The checks now include `gen-config-doc --check`. `docs/releasing.md` has a releasing checklist.
29
+ - **The close writes the ticket lines (B96).** The coder's final report no longer needs a `reviewed <path> ticket <id>` line per file. `block close` prints one for every file the review gate accepts (approved, waived or answered), with the queue's ticket. `--json` and the block's done file carry them as `reviewed: [{file, ticket, via}]`. With `--report`, a hand line that names another ticket prints `WARN report ticket mismatch <path>` on stderr, and the close goes on.
30
+ - **A big-diff file steps up to a full review (B109).** A low-risk file whose diff is too big for the quick review budget used to stop with `split_required`, and splitting it only made the diff bigger. Now the review plans again as one full L2 session. If the diff fits the full budget, the file is reviewed. The `review.plan` row records the step as `step_up`.
31
+ - **A file that was too big can be reviewed again (B110).** A first-round `split_required` result ran no review. After you raise the budget and run `run reload`, `review-file` (or `review-file --fresh`) tries again with the same ticket. A recheck stop still needs `block reopen` or `block waive`.
32
+ - **A recheck that did not really review never stalls (B111).** A recheck on unchanged content runs no session and counts no round; it returns `reason: 'no-fix-hunks'`. A recheck answer that skips an open finding is a broken answer, not a "still open". So the stall rule sees only real rechecks. The recheck summary now shows each session's status and tokens.
33
+ - **Paired files for the reviewer, part 1 (B97).** code-forge can now find a changed file's test file (or a test's source file) by name, and list its test names or exported symbols in a small `## paired` packet section. The section is dropped first when the packet is too big. A reviewer can also ask for a new, untracked test file when the block owns and changed it. B98 sends these to the reviewers.
34
+ - **Reviewers see the paired test file (B98).** When a block changes `src/a.mjs` and `test/a.test.mjs`, the reviewer of `src/a.mjs` now gets the test names in a `## paired` section, in the first review and in rechecks. Tests named with `block open --tests` count too. The reviewer is told those tests exist, so it no longer reports them as missing. It can still ask for a test file's body, even a new untracked one the block owns.
35
+ - **Faster reviews (B112).** A review ticket reads the diffs of the block's other changed files only when the review asks for them, and only once.
36
+ - **No leftover workers (B113).** A worker stops by itself when its run record or its HOME is removed. `npm test` now fails when a test leaves a worker running.
37
+
38
+ ## What's new in 0.10
39
+
40
+ In 0.10.0:
41
+
15
42
  - **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
43
  - **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
44
  - **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`.
@@ -27,6 +54,8 @@ In the next release:
27
54
  - **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
55
  - **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
56
 
57
+ ## What's new in 0.9
58
+
30
59
  In 0.9.0:
31
60
 
32
61
  - **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).
@@ -363,7 +392,8 @@ CLI verbs like any other and every one of them is exercised by this package's ow
363
392
  you the full issue and sends nothing without your yes. `CODE_FORGE_NO_ERROR_LOG=1` turns the log,
364
393
  the warnings and the command list off. Every field is listed in [docs/privacy.md](docs/privacy.md).
365
394
  - **No mutation testing.** Neither this package's own tests nor the product it ships run mutation
366
- testing (Ricardo's ruling R14/Q16, 2026-09-25). Proof is plain unit and feature tests with exact
395
+ testing (the owner's ruling R14/Q16, 2026-09-25). Coders do not make hand mutations either: each new
396
+ or changed test is proven red→green, and a weak assertion is fixed by a stronger assertion. Proof is plain unit and feature tests with exact
367
397
  assertions, plus a red→green runner that proves a new test fails before the fix and passes after (`node:test`
368
398
  built in; any other runner through a `proof.runners` entry that writes a JUnit report).
369
399
 
@@ -411,5 +441,6 @@ depends on, kept in sync with the maintainer's design plan.
411
441
 
412
442
  Releasing: record changes with `npm run changelog -- <type> "<text>"` and fixed error reports with
413
443
  `npm run known-fix -- add <fp> <version> "<summary>"`, then `npm run release -- <patch|minor|major|x.y.z> [--dry-run]`
414
- bumps, commits and tags (it never pushes or publishes). Publish with `npm publish` from your
444
+ bumps, moves the README's "In the next release:" bullets under the new version, checks the config
445
+ doc, commits and tags (it never pushes or publishes). Publish with `npm publish` from your
415
446
  machine, then push the tag: CI only creates the GitHub release. See [docs/releasing.md](docs/releasing.md).
@@ -4,7 +4,7 @@
4
4
  "plans/code-forge-plan-v1.3.md §10.4",
5
5
  "plans/NIGHT-LOG.md"
6
6
  ],
7
- "block_count": 102,
7
+ "block_count": 117,
8
8
  "baseline_ids": ["B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8"],
9
9
  "baseline_note": "Wave 0-1 blocks, landed before v1.3; depends_on may name them, but they have no entry under blocks.",
10
10
  "blocks": {
@@ -1623,6 +1623,365 @@
1623
1623
  "B94"
1624
1624
  ],
1625
1625
  "note": "root-added 2026-10-13; issue #10 owner item 1 (plans/iteration/plan.md B80, D1 = a); block waive and block reopen ask the human for a yes at a terminal, no --yes, no terminal exits 2 and records nothing; pre-checks run before the question; a pseudo-terminal passes the check (honest limit in security.md §3); D1 (b) is a later option"
1626
+ },
1627
+ "B108": {
1628
+ "title": "Release moves the README What's new bullets and checks the docs",
1629
+ "wave": 14,
1630
+ "level": "L2",
1631
+ "status": "landed",
1632
+ "owned": [
1633
+ "README.md",
1634
+ "docs/releasing.md",
1635
+ "scripts/release.mjs",
1636
+ "test/release-readme.test.mjs",
1637
+ "test/release.test.mjs"
1638
+ ],
1639
+ "depends_on": [
1640
+ "B80"
1641
+ ],
1642
+ "note": "root-added 2026-10-13; process fix after the 0.10.0 README miss (plans/field-bugs/plan.md B108); npm run release moves the 'In the next release:' bullets under 'In X.Y.Z:' (new '## What's new in X.Y' on a minor or major), keeps an empty anchor, refuses an empty [Unreleased] or a missing anchor in preflight, rolls README back with the other files, and runs gen-config-doc --check"
1643
+ },
1644
+ "B96": {
1645
+ "title": "The close writes the reviewed ticket lines",
1646
+ "wave": 14,
1647
+ "level": "L2",
1648
+ "status": "landed",
1649
+ "owned": [
1650
+ "docs/concepts.md",
1651
+ "docs/how-it-works.md",
1652
+ "docs/tutorial-first-test.md",
1653
+ "skill/SKILL.md",
1654
+ "skill/references/code.md",
1655
+ "skill/templates/coder-brief.md",
1656
+ "src/cli/block.mjs",
1657
+ "src/review/reviewed-lines.mjs",
1658
+ "src/state/block-end.mjs",
1659
+ "test/cli/close-report.test.mjs",
1660
+ "test/cli/status.test.mjs",
1661
+ "test/engines/sentinel-tests.test.mjs",
1662
+ "test/integration/review-flow.test.mjs",
1663
+ "test/review/answers.test.mjs",
1664
+ "test/review/close-gate.test.mjs",
1665
+ "test/review/external.test.mjs",
1666
+ "test/review/gate-check.test.mjs",
1667
+ "test/review/reopen.test.mjs",
1668
+ "test/review/waived-all.test.mjs",
1669
+ "test/session/plan-sections.test.mjs",
1670
+ "test/skill/structure.test.mjs",
1671
+ "test/state/block.test.mjs",
1672
+ "test/worker/deletion-review.test.mjs"
1673
+ ],
1674
+ "depends_on": [
1675
+ "B108"
1676
+ ],
1677
+ "note": "root-added 2026-10-13; issue #16 item 6 (Group E); block close prints `reviewed <path> ticket <id>` for every file the review gate accepts, --json and the done file carry reviewed [{file, ticket, via}]; the coder report needs no hand ticket lines; a mismatched hand line under --report prints a held WARN; no-ticket, bad-ticket and lines-unavailable WARNs are held and print only on a successful close"
1678
+ },
1679
+ "B109": {
1680
+ "title": "Step up from quick to one full review before split_required",
1681
+ "wave": 14,
1682
+ "level": "L2",
1683
+ "status": "landed",
1684
+ "owned": [
1685
+ "docs/how-it-works.md",
1686
+ "skill/references/review.md",
1687
+ "src/review/engine.mjs",
1688
+ "test/review/budget-step-up.test.mjs",
1689
+ "test/review/test-quality.test.mjs"
1690
+ ],
1691
+ "depends_on": [
1692
+ "B96"
1693
+ ],
1694
+ "note": "root-added 2026-10-13; issue #16 item 1 (plans/field-bugs/plan.md B109, D1 = a); a non-Markdown quick packet over quick_in is planned again at risk 1; if that plan is one full session and fits full_in it reviews with it (review.plan step_up {from: 'quick', tokens_in, budget}); otherwise split_required with the full packet's size; first block built by Codex at high effort"
1695
+ },
1696
+ "B110": {
1697
+ "title": "Re-run a cached round-1 split_required",
1698
+ "wave": 14,
1699
+ "level": "L2",
1700
+ "status": "landed",
1701
+ "owned": [
1702
+ "docs/how-it-works.md",
1703
+ "skill/references/code.md",
1704
+ "skill/references/review.md",
1705
+ "src/cli/review-file.mjs",
1706
+ "test/skill/structure.test.mjs",
1707
+ "test/worker/fresh-split.test.mjs",
1708
+ "test/worker/fresh.test.mjs"
1709
+ ],
1710
+ "depends_on": [
1711
+ "B109"
1712
+ ],
1713
+ "note": "root-added 2026-10-13; issue #16 item 2 (plans/field-bugs/plan.md B110); a cached round-1 split_required ran no session and counts no round: plain review-file re-queues it with retry: true and --fresh re-queues it, same ticket id; a recheck stop (status stopped, stopped split_required) still needs block reopen or waive; built by Codex at high effort"
1714
+ },
1715
+ "B111": {
1716
+ "title": "An unchanged or incomplete recheck never counts or stalls",
1717
+ "wave": 14,
1718
+ "level": "L2",
1719
+ "status": "landed",
1720
+ "owned": [
1721
+ "docs/how-it-works.md",
1722
+ "skill/references/review.md",
1723
+ "src/review/fixloop.mjs",
1724
+ "src/worker/engine.mjs",
1725
+ "test/review/recheck-real.test.mjs",
1726
+ "test/autopilot/actions.test.mjs",
1727
+ "test/cli/block-human-confirm.test.mjs",
1728
+ "test/review/fixloop.test.mjs",
1729
+ "test/review/moved-wiring.test.mjs",
1730
+ "test/review/patch-progress.test.mjs",
1731
+ "test/review/reopen.test.mjs",
1732
+ "test/worker/fixloop-wiring.test.mjs",
1733
+ "test/worker/pool.test.mjs",
1734
+ "test/worker/review-state-durable.test.mjs",
1735
+ "test/worker/review-state-restore.test.mjs"
1736
+ ],
1737
+ "depends_on": [
1738
+ "B110"
1739
+ ],
1740
+ "note": "root-added 2026-10-13; issue #16 item 3 (plans/field-bugs/plan.md B111, D3 = a); P1: a recheck with no fix hunks runs no session, counts no round and returns reviewed, approved false, reason no-fix-hunks; P2: a recheck answer without a resolved entry for each open finding of its packet is a schema miss (B55/B91 second level at the second broken result, else unavailable); the worker recheck summary gains status, tokens_in, tokens_out and reason; 10 fixtures now answer resolved explicitly; built by Codex at high effort in 2 rounds"
1741
+ },
1742
+ "B97": {
1743
+ "title": "Paired-file names in the packet; untracked owned files can be attached",
1744
+ "wave": 14,
1745
+ "level": "L2",
1746
+ "status": "landed",
1747
+ "owned": [
1748
+ "docs/how-it-works.md",
1749
+ "skill/references/review.md",
1750
+ "src/review/packet.mjs",
1751
+ "src/review/paired.mjs",
1752
+ "test/review/engine.test.mjs",
1753
+ "test/review/packet.test.mjs",
1754
+ "test/review/paired.test.mjs"
1755
+ ],
1756
+ "depends_on": [
1757
+ "B111"
1758
+ ],
1759
+ "note": "root-added 2026-10-13; Group E E5 part 1 (plans/run-speed/plan.md B97, D4 = a); pairsFor by stem for .mjs/.ts/PHP/Go/Python/Swift; summarize lists at most 40 unique test names or exported symbols of at most 120 chars, redacted before truncation; assemblePacket takes paired and renders ## paired after ## context, dropped first over budget, never split_required; attachFiles attaches an untracked file only when owned and changed in the block and not ignored or secret-like; builders wire it in B98; built by Codex at high effort in 2 rounds"
1760
+ },
1761
+ "B98": {
1762
+ "title": "The packet builders send the paired files; the lenses use them",
1763
+ "wave": 14,
1764
+ "level": "L2",
1765
+ "status": "landed",
1766
+ "owned": [
1767
+ "docs/how-it-works.md",
1768
+ "skill/references/review.md",
1769
+ "src/review/engine.mjs",
1770
+ "src/review/fixloop.mjs",
1771
+ "src/review/lenses/A.md",
1772
+ "src/review/lenses/B.md",
1773
+ "src/review/lenses/full.md",
1774
+ "src/review/lenses/recheck.md",
1775
+ "src/worker/engine.mjs",
1776
+ "test/review/fixloop.test.mjs",
1777
+ "test/review/paired-wiring.test.mjs",
1778
+ "test/review/route.test.mjs",
1779
+ "test/review/sections.test.mjs"
1780
+ ],
1781
+ "depends_on": [
1782
+ "B97"
1783
+ ],
1784
+ "note": "root-added 2026-10-13; Group E E5 part 2 (plans/run-speed/plan.md B98); pairedContext builds ## paired from the block's owned changed peers plus its test_files (glob entries dropped) in round 1, full rounds and rechecks; the worker passes ownedFiles/changedFiles so needs_file can attach a new untracked owned test file; a failing peer listing is fail-soft (empty context); the four lenses say a test named in ## paired exists; built by Codex at high effort in 2 rounds"
1785
+ },
1786
+ "B112": {
1787
+ "title": "Suite speed: peer diffs read lazily again (B98 regression); bench test load-proof",
1788
+ "wave": 14,
1789
+ "level": "L2",
1790
+ "status": "landed",
1791
+ "owned": [
1792
+ "src/worker/engine.mjs",
1793
+ "test/worker/peer-diffs-lazy.test.mjs",
1794
+ "test/bench-review.test.mjs"
1795
+ ],
1796
+ "depends_on": [
1797
+ "B98"
1798
+ ],
1799
+ "note": "root-added 2026-10-13; velocity; review.test.mjs 89 s -> 44 s"
1800
+ },
1801
+ "B113": {
1802
+ "title": "No leaked workers: orphan stop, failure-safe test cleanup, no-leak worker check",
1803
+ "wave": 14,
1804
+ "level": "L2",
1805
+ "status": "landed",
1806
+ "owned": [
1807
+ "docs/how-it-works.md",
1808
+ "package.json",
1809
+ "src/worker/loop.mjs",
1810
+ "test/cli/review.test.mjs",
1811
+ "test/docs-marketing.test.mjs",
1812
+ "test/harness-isolation.test.mjs",
1813
+ "test/helpers/isolate.mjs",
1814
+ "test/helpers/worker-processes.mjs",
1815
+ "test/no-leak.test.mjs",
1816
+ "test/router.test.mjs",
1817
+ "test/worker/cli-cleanup.test.mjs",
1818
+ "test/worker/helper-cleanup.test.mjs",
1819
+ "test/worker/helpers.mjs",
1820
+ "test/worker/orphan-guard.test.mjs",
1821
+ "test/worker/orphan.test.mjs",
1822
+ "test/worker/process-list.test.mjs"
1823
+ ],
1824
+ "depends_on": [
1825
+ "B112"
1826
+ ],
1827
+ "note": "root-added 2026-10-13; bug; 226 orphan test workers drove load to 350-450 and broke the release test step"
1828
+ },
1829
+ "B99": {
1830
+ "title": "Fix rounds check less: gates run --changed, {changed_files}",
1831
+ "wave": 14,
1832
+ "level": "L2",
1833
+ "status": "landed",
1834
+ "owned": [
1835
+ "docs/how-it-works.md",
1836
+ "docs/reference/config.md",
1837
+ "schema/code-forge.schema.json",
1838
+ "skill/references/code.md",
1839
+ "skill/references/proof.md",
1840
+ "skill/templates/coder-brief.md",
1841
+ "src/cli/gates.mjs",
1842
+ "src/config/validate.mjs",
1843
+ "src/gates/expand.mjs",
1844
+ "src/gates/run.mjs",
1845
+ "src/proof/lock.mjs",
1846
+ "src/util/gate-token.mjs",
1847
+ "test/gates/changed.test.mjs",
1848
+ "test/gates/placeholders.test.mjs"
1849
+ ],
1850
+ "depends_on": [
1851
+ "B113"
1852
+ ],
1853
+ "note": "root-added 2026-10-13; #16; owner D2=a: fix-round TESTS may use --changed, orchestrator runs full gates before close"
1854
+ },
1855
+ "B100": {
1856
+ "title": "Overlap loop: review-file --wait-block, review.file_loop overlap",
1857
+ "wave": 14,
1858
+ "level": "L2",
1859
+ "status": "landed",
1860
+ "owned": [
1861
+ "docs/how-it-works.md",
1862
+ "docs/reference/config.md",
1863
+ "schema/code-forge.schema.json",
1864
+ "skill/references/code.md",
1865
+ "skill/templates/coder-brief.md",
1866
+ "src/cli/review-file.mjs",
1867
+ "src/config/validate.mjs",
1868
+ "src/worker/queue.mjs",
1869
+ "test/config/schema-coverage.test.mjs",
1870
+ "test/worker/wait-block.test.mjs"
1871
+ ],
1872
+ "depends_on": [
1873
+ "B99"
1874
+ ],
1875
+ "note": "run-speed E3a; round 1 approved, minors noted (wait-block test 2/4 assertions could be stricter)"
1876
+ },
1877
+ "B114": {
1878
+ "title": "Stop with unknown content is no dead end (#17); TESTS line malformed",
1879
+ "wave": 14,
1880
+ "level": "L2",
1881
+ "status": "landed",
1882
+ "owned": [
1883
+ "docs/how-it-works.md",
1884
+ "skill/references/code.md",
1885
+ "skill/references/review.md",
1886
+ "src/cli/block.mjs",
1887
+ "src/review/fixloop.mjs",
1888
+ "src/review/reopen.mjs",
1889
+ "test/cli/block-tests-line.test.mjs",
1890
+ "test/review/reopen-unknown.test.mjs",
1891
+ "test/review/reopen.test.mjs"
1892
+ ],
1893
+ "depends_on": [
1894
+ "B100"
1895
+ ],
1896
+ "note": "#17; round 1 approved; built in parallel with B115; minor noted: reopen row lost if worker crashes between round save and state save"
1897
+ },
1898
+ "B115": {
1899
+ "title": "Small follow-ups: Python *_test.py pairing, npm test -- <args>, test cleanup errors",
1900
+ "wave": 14,
1901
+ "level": "L1",
1902
+ "status": "landed",
1903
+ "owned": [
1904
+ "docs/how-it-works.md",
1905
+ "package.json",
1906
+ "test/helpers/run-suite.sh",
1907
+ "skill/references/review.md",
1908
+ "src/review/paired.mjs",
1909
+ "test/no-leak.test.mjs",
1910
+ "test/no-leak-final.test.mjs",
1911
+ "test/review/paired.test.mjs",
1912
+ "test/router.test.mjs",
1913
+ "test/worker/helper-cleanup.test.mjs",
1914
+ "test/worker/helpers.mjs"
1915
+ ],
1916
+ "depends_on": [
1917
+ "B114"
1918
+ ],
1919
+ "note": "owner 2026-10-13; 2 rounds; noted: 6 old test files call rm(undefined) in cleanup under --test-name-pattern (follow-up); no-leak-final pins report order only at beforeExit"
1920
+ },
1921
+ "B104": {
1922
+ "title": "Commit watch: review.file_loop watch queues owned files from new commits",
1923
+ "wave": 14,
1924
+ "level": "L2",
1925
+ "status": "landed",
1926
+ "owned": [
1927
+ "docs/how-it-works.md",
1928
+ "docs/reference/config.md",
1929
+ "schema/code-forge.schema.json",
1930
+ "skill/references/code.md",
1931
+ "skill/references/review.md",
1932
+ "skill/templates/coder-brief.md",
1933
+ "src/config/validate.mjs",
1934
+ "src/util/exec.mjs",
1935
+ "src/worker/loop.mjs",
1936
+ "src/worker/queue.mjs",
1937
+ "src/worker/watch.mjs",
1938
+ "test/skill/structure.test.mjs",
1939
+ "test/worker/wait-block.test.mjs",
1940
+ "test/worker/watch.test.mjs"
1941
+ ],
1942
+ "depends_on": [
1943
+ "B115"
1944
+ ],
1945
+ "note": "3 rounds (r2 major fixed by the orchestrator with red→green); noted: activeTickets only from the scheduler pass; stop() during a watch tick still dispatches that pass; watch state reset after realpath; a file replaced by a directory stalls the watch after one skip row; enqueue passes the raw file to enqueueHash"
1946
+ },
1947
+ "B117": {
1948
+ "title": "Proof rule in every context pack, review lens, skill and doc; red→green defined",
1949
+ "wave": 14,
1950
+ "level": "L1",
1951
+ "status": "landed",
1952
+ "owned": [
1953
+ "docs/concepts.md",
1954
+ "docs/how-it-works.md",
1955
+ "docs/overview.md",
1956
+ "docs/tutorial-first-test.md",
1957
+ "skill/SKILL.md",
1958
+ "skill/references/code.md",
1959
+ "skill/references/proof.md",
1960
+ "skill/references/rules-core.md",
1961
+ "skill/templates/coder-brief.md",
1962
+ "src/context/pack.mjs",
1963
+ "src/install/wizard/flags.mjs",
1964
+ "src/proof/rule.mjs",
1965
+ "src/proof/tiers.mjs",
1966
+ "src/review/gate-check.mjs",
1967
+ "src/review/lenses/A.md",
1968
+ "src/review/lenses/B.md",
1969
+ "src/review/lenses/full.md",
1970
+ "src/review/lenses/judge.md",
1971
+ "src/review/lenses/quick.md",
1972
+ "src/review/lenses/recheck.md",
1973
+ "test/context/pack.test.mjs",
1974
+ "test/proof/tiers.test.mjs",
1975
+ "test/review/fixloop.test.mjs",
1976
+ "test/review/packet.test.mjs",
1977
+ "test/review/route.test.mjs",
1978
+ "test/review/test-quality.test.mjs",
1979
+ "test/skill/structure.test.mjs"
1980
+ ],
1981
+ "depends_on": [
1982
+ "B104"
1983
+ ],
1984
+ "note": "owner 2026-10-13: pass the proof rule to every project built with code-forge; 1 round, 3 minors not-bug; removed dead proofRequirements (mutants: held)"
1626
1985
  }
1627
1986
  }
1628
1987
  }
@@ -136,6 +136,7 @@ Every other key defaults as shown, or is entirely absent from a minimal file. `a
136
136
  | `review.provider_concurrency.openai` | integer | | `2` | min 1 |
137
137
  | `review.provider_concurrency.xai` | integer | | `2` | min 1 |
138
138
  | `review.parallel_tickets` | integer | | `3` | min 1; max 16; B42: how many review tickets the worker runs at once (oldest first); 1 = one at a time. Tickets for the same file never overlap. |
139
+ | `review.file_loop` | enum: `wait`, `overlap`, `watch` | | `"wait"` | After each green file, enqueue its review. wait (default): wait for its ticket before the next file. overlap: continue coding, then run review-file --wait-block <id> --run <run> before the completion sentinel; inspect findings with --wait <ticket>. watch: commit each green file instead of running review-file; the worker queues committed owned content, supersedes pending older tickets, and --wait-block joins before the sentinel. |
139
140
  | `review.external` | object | | — | |
140
141
  | `review.external.required` | boolean | | `false` | B58: true ⇒ block close skips the local per-file review checks (and the --report ticket check), with no terminal and under autopilot, and writes a signed gate.reviews_waived {by: config} row. Unowned changes, proof, no_changes and the transcript grep still refuse. No block may own .code-forge.yml. |
141
142
  | `review.external.name` | string | | — | minLength 1; B58: the outside reviewer, written in the review.external and gate.reviews_waived rows. Required when required is true: run start refuses a missing or blank name. |
@@ -149,15 +150,15 @@ Every other key defaults as shown, or is entirely absent from a minimal file. `a
149
150
  | `caps` | object | | — | |
150
151
  | `caps.coders` | integer | | `2` | min 1 |
151
152
  | `gates` | object | | — | |
152
- | `gates.test` | array<string> \| object \| null | | — | An argv array (never a shell string), an {argv, env} object, or null. An argv item that is exactly {test_files} is replaced by the block's test files. It must be a whole item. It cannot be the command or the only item. The test files are the block open --tests paths, else the owned entries that are concrete test files. The item is dropped, and the full suite runs, in two cases. The block owns more files than full_suite_threshold_files (reason threshold). Or no test file is known, or the gate runs without --run/--block (reason no_tests). The gate.red and gate.done rows then carry scope related with test_count, or scope full with reason. |
153
- | `gates.lint` | array<string> \| object \| null | | — | An argv array (never a shell string), an {argv, env} object, or null. An argv item that is exactly {test_files} is replaced by the block's test files. It must be a whole item. It cannot be the command or the only item. The test files are the block open --tests paths, else the owned entries that are concrete test files. The item is dropped, and the full suite runs, in two cases. The block owns more files than full_suite_threshold_files (reason threshold). Or no test file is known, or the gate runs without --run/--block (reason no_tests). The gate.red and gate.done rows then carry scope related with test_count, or scope full with reason. |
154
- | `gates.types` | array<string> \| object \| null | | — | An argv array (never a shell string), an {argv, env} object, or null. An argv item that is exactly {test_files} is replaced by the block's test files. It must be a whole item. It cannot be the command or the only item. The test files are the block open --tests paths, else the owned entries that are concrete test files. The item is dropped, and the full suite runs, in two cases. The block owns more files than full_suite_threshold_files (reason threshold). Or no test file is known, or the gate runs without --run/--block (reason no_tests). The gate.red and gate.done rows then carry scope related with test_count, or scope full with reason. |
155
- | `gates.format` | array<string> \| object \| null | | — | An argv array (never a shell string), an {argv, env} object, or null. An argv item that is exactly {test_files} is replaced by the block's test files. It must be a whole item. It cannot be the command or the only item. The test files are the block open --tests paths, else the owned entries that are concrete test files. The item is dropped, and the full suite runs, in two cases. The block owns more files than full_suite_threshold_files (reason threshold). Or no test file is known, or the gate runs without --run/--block (reason no_tests). The gate.red and gate.done rows then carry scope related with test_count, or scope full with reason. |
153
+ | `gates.test` | array<string> \| object \| null | | — | An argv array (never a shell string), an {argv, env} object, or null. An argv item that is exactly {test_files} is replaced by the block's test files. It must be a whole item. It cannot be the command or the only item. The test files are the block open --tests paths, else the owned entries that are concrete test files. The item is dropped, and the full suite runs, in two cases. The block owns more files than full_suite_threshold_files (reason threshold). Or no test file is known, or the gate runs without --run/--block (reason no_tests). The gate.red and gate.done rows then carry scope related with test_count, or scope full with reason. With gates run --run --block --changed, {test_files} expands to changed tests plus stem-paired tests of changed sources, and {changed_files} expands to changed paths for lint or format. Both tokens must be whole argv items and cannot be the command or the only item. Config validation refuses a gate whose command or only item is {changed_files}. Deleted files count for pairing and the threshold, but both tokens receive only files that exist now. Without --changed, {changed_files} is dropped. Changed rows carry scope changed; narrowed rows have no reason. Only fallback rows carry a reason: no_baseline uses today's scope, unpaired uses today's test scope, threshold runs the full suite above the changed-file threshold, and no_changes uses today's scope when nothing is left to run. Gates without either token run unchanged. Any green gates run --run --block records owned file hashes on gate.done, including runs without --changed. When --slug is omitted, these rows go to the run's project ledger. More than 200 owned files means no record, so the next --changed falls back with no_baseline; unreadable inputs also leave no record. A fix-round TESTS line may use --changed; the orchestrator runs full gates before block close. |
154
+ | `gates.lint` | array<string> \| object \| null | | — | An argv array (never a shell string), an {argv, env} object, or null. An argv item that is exactly {test_files} is replaced by the block's test files. It must be a whole item. It cannot be the command or the only item. The test files are the block open --tests paths, else the owned entries that are concrete test files. The item is dropped, and the full suite runs, in two cases. The block owns more files than full_suite_threshold_files (reason threshold). Or no test file is known, or the gate runs without --run/--block (reason no_tests). The gate.red and gate.done rows then carry scope related with test_count, or scope full with reason. With gates run --run --block --changed, {test_files} expands to changed tests plus stem-paired tests of changed sources, and {changed_files} expands to changed paths for lint or format. Both tokens must be whole argv items and cannot be the command or the only item. Config validation refuses a gate whose command or only item is {changed_files}. Deleted files count for pairing and the threshold, but both tokens receive only files that exist now. Without --changed, {changed_files} is dropped. Changed rows carry scope changed; narrowed rows have no reason. Only fallback rows carry a reason: no_baseline uses today's scope, unpaired uses today's test scope, threshold runs the full suite above the changed-file threshold, and no_changes uses today's scope when nothing is left to run. Gates without either token run unchanged. Any green gates run --run --block records owned file hashes on gate.done, including runs without --changed. When --slug is omitted, these rows go to the run's project ledger. More than 200 owned files means no record, so the next --changed falls back with no_baseline; unreadable inputs also leave no record. A fix-round TESTS line may use --changed; the orchestrator runs full gates before block close. |
155
+ | `gates.types` | array<string> \| object \| null | | — | An argv array (never a shell string), an {argv, env} object, or null. An argv item that is exactly {test_files} is replaced by the block's test files. It must be a whole item. It cannot be the command or the only item. The test files are the block open --tests paths, else the owned entries that are concrete test files. The item is dropped, and the full suite runs, in two cases. The block owns more files than full_suite_threshold_files (reason threshold). Or no test file is known, or the gate runs without --run/--block (reason no_tests). The gate.red and gate.done rows then carry scope related with test_count, or scope full with reason. With gates run --run --block --changed, {test_files} expands to changed tests plus stem-paired tests of changed sources, and {changed_files} expands to changed paths for lint or format. Both tokens must be whole argv items and cannot be the command or the only item. Config validation refuses a gate whose command or only item is {changed_files}. Deleted files count for pairing and the threshold, but both tokens receive only files that exist now. Without --changed, {changed_files} is dropped. Changed rows carry scope changed; narrowed rows have no reason. Only fallback rows carry a reason: no_baseline uses today's scope, unpaired uses today's test scope, threshold runs the full suite above the changed-file threshold, and no_changes uses today's scope when nothing is left to run. Gates without either token run unchanged. Any green gates run --run --block records owned file hashes on gate.done, including runs without --changed. When --slug is omitted, these rows go to the run's project ledger. More than 200 owned files means no record, so the next --changed falls back with no_baseline; unreadable inputs also leave no record. A fix-round TESTS line may use --changed; the orchestrator runs full gates before block close. |
156
+ | `gates.format` | array<string> \| object \| null | | — | An argv array (never a shell string), an {argv, env} object, or null. An argv item that is exactly {test_files} is replaced by the block's test files. It must be a whole item. It cannot be the command or the only item. The test files are the block open --tests paths, else the owned entries that are concrete test files. The item is dropped, and the full suite runs, in two cases. The block owns more files than full_suite_threshold_files (reason threshold). Or no test file is known, or the gate runs without --run/--block (reason no_tests). The gate.red and gate.done rows then carry scope related with test_count, or scope full with reason. With gates run --run --block --changed, {test_files} expands to changed tests plus stem-paired tests of changed sources, and {changed_files} expands to changed paths for lint or format. Both tokens must be whole argv items and cannot be the command or the only item. Config validation refuses a gate whose command or only item is {changed_files}. Deleted files count for pairing and the threshold, but both tokens receive only files that exist now. Without --changed, {changed_files} is dropped. Changed rows carry scope changed; narrowed rows have no reason. Only fallback rows carry a reason: no_baseline uses today's scope, unpaired uses today's test scope, threshold runs the full suite above the changed-file threshold, and no_changes uses today's scope when nothing is left to run. Gates without either token run unchanged. Any green gates run --run --block records owned file hashes on gate.done, including runs without --changed. When --slug is omitted, these rows go to the run's project ledger. More than 200 owned files means no record, so the next --changed falls back with no_baseline; unreadable inputs also leave no record. A fix-round TESTS line may use --changed; the orchestrator runs full gates before block close. |
156
157
  | `gates.extra` | object | | — | |
157
158
  | `gates.extra.secret_scan` | boolean | | `true` | |
158
159
  | `gates.extra.safe_edit` | boolean | | `true` | |
159
160
  | `gates.extra.invariants` | string | | — | |
160
- | `gates.full_suite_threshold_files` | integer | | `10` | min 1; A block that owns more files than this runs the full suite for a {test_files} gate (reason threshold), not its related tests. Owned files are counted this way. A glob or a directory counts the tracked and new (not ignored) files it covers. A path not created yet counts as 1. Outside git each entry counts 1. A git failure runs the full suite. block open --tests names the test files precisely. |
161
+ | `gates.full_suite_threshold_files` | integer | | `10` | min 1; A block that owns more files than this runs the full suite for a {test_files} gate (reason threshold), not its related tests. Owned files are counted this way. A glob or a directory counts the tracked and new (not ignored) files it covers. A path not created yet counts as 1. Outside git each entry counts 1. A git failure runs the full suite. block open --tests names the test files precisely. With gates run --changed, the threshold counts changed files since the last green gates run --run --block instead. |
161
162
  | `proof` | object | | — | |
162
163
  | `proof.tiers` | object | | — | |
163
164
  | `proof.tiers.high` | object | | — | |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codedology/code-forge",
3
- "version": "0.10.0",
3
+ "version": "0.12.0",
4
4
  "description": "Plan/harden/code/review orchestration CLI and Agent Skill for cross-model, parallel, evidence-gated feature delivery.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -38,7 +38,7 @@
38
38
  "LICENSE"
39
39
  ],
40
40
  "scripts": {
41
- "test": "CODE_FORGE_NO_LEAK=snapshot node --import ./test/helpers/isolate.mjs --test test/no-leak.test.mjs && node --import ./test/helpers/isolate.mjs --experimental-test-module-mocks --test 'test/**/*.test.mjs' && CODE_FORGE_NO_LEAK=final node --import ./test/helpers/isolate.mjs --test test/no-leak.test.mjs",
41
+ "test": "sh test/helpers/run-suite.sh",
42
42
  "typecheck": "tsc --checkJs --noEmit",
43
43
  "release": "node scripts/release.mjs",
44
44
  "changelog": "node scripts/changelog.mjs",
@@ -185,6 +185,7 @@
185
185
  }
186
186
  },
187
187
  "parallel_tickets": { "type": "integer", "minimum": 1, "maximum": 16, "default": 3, "description": "B42: how many review tickets the worker runs at once (oldest first); 1 = one at a time. Tickets for the same file never overlap." },
188
+ "file_loop": { "type": "string", "enum": ["wait", "overlap", "watch"], "default": "wait", "description": "After each green file, enqueue its review. wait (default): wait for its ticket before the next file. overlap: continue coding, then run review-file --wait-block <id> --run <run> before the completion sentinel; inspect findings with --wait <ticket>. watch: commit each green file instead of running review-file; the worker queues committed owned content, supersedes pending older tickets, and --wait-block joins before the sentinel." },
188
189
  "external": {
189
190
  "type": "object",
190
191
  "additionalProperties": false,
@@ -240,7 +241,7 @@
240
241
  "type": "integer",
241
242
  "minimum": 1,
242
243
  "default": 10,
243
- "description": "A block that owns more files than this runs the full suite for a {test_files} gate (reason threshold), not its related tests. Owned files are counted this way. A glob or a directory counts the tracked and new (not ignored) files it covers. A path not created yet counts as 1. Outside git each entry counts 1. A git failure runs the full suite. block open --tests names the test files precisely."
244
+ "description": "A block that owns more files than this runs the full suite for a {test_files} gate (reason threshold), not its related tests. Owned files are counted this way. A glob or a directory counts the tracked and new (not ignored) files it covers. A path not created yet counts as 1. Outside git each entry counts 1. A git failure runs the full suite. block open --tests names the test files precisely. With gates run --changed, the threshold counts changed files since the last green gates run --run --block instead."
244
245
  }
245
246
  }
246
247
  },
@@ -511,7 +512,7 @@
511
512
  }
512
513
  },
513
514
  "argvOrNull": {
514
- "description": "An argv array (never a shell string), an {argv, env} object, or null. An argv item that is exactly {test_files} is replaced by the block's test files. It must be a whole item. It cannot be the command or the only item. The test files are the block open --tests paths, else the owned entries that are concrete test files. The item is dropped, and the full suite runs, in two cases. The block owns more files than full_suite_threshold_files (reason threshold). Or no test file is known, or the gate runs without --run/--block (reason no_tests). The gate.red and gate.done rows then carry scope related with test_count, or scope full with reason.",
515
+ "description": "An argv array (never a shell string), an {argv, env} object, or null. An argv item that is exactly {test_files} is replaced by the block's test files. It must be a whole item. It cannot be the command or the only item. The test files are the block open --tests paths, else the owned entries that are concrete test files. The item is dropped, and the full suite runs, in two cases. The block owns more files than full_suite_threshold_files (reason threshold). Or no test file is known, or the gate runs without --run/--block (reason no_tests). The gate.red and gate.done rows then carry scope related with test_count, or scope full with reason. With gates run --run --block --changed, {test_files} expands to changed tests plus stem-paired tests of changed sources, and {changed_files} expands to changed paths for lint or format. Both tokens must be whole argv items and cannot be the command or the only item. Config validation refuses a gate whose command or only item is {changed_files}. Deleted files count for pairing and the threshold, but both tokens receive only files that exist now. Without --changed, {changed_files} is dropped. Changed rows carry scope changed; narrowed rows have no reason. Only fallback rows carry a reason: no_baseline uses today's scope, unpaired uses today's test scope, threshold runs the full suite above the changed-file threshold, and no_changes uses today's scope when nothing is left to run. Gates without either token run unchanged. Any green gates run --run --block records owned file hashes on gate.done, including runs without --changed. When --slug is omitted, these rows go to the run's project ledger. More than 200 owned files means no record, so the next --changed falls back with no_baseline; unreadable inputs also leave no record. A fix-round TESTS line may use --changed; the orchestrator runs full gates before block close.",
515
516
  "anyOf": [
516
517
  { "type": "array", "minItems": 1, "items": { "type": "string", "minLength": 1 } },
517
518
  {