@codedology/code-forge 0.7.7 → 0.8.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 (59) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/README.md +50 -2
  3. package/docs/README.md +2 -1
  4. package/docs/reference/blocks.json +157 -1
  5. package/docs/reference/config.md +26 -8
  6. package/package.json +1 -1
  7. package/schema/code-forge.schema.json +81 -4
  8. package/skill/SKILL.md +6 -6
  9. package/skill/references/code.md +33 -4
  10. package/skill/references/ledger.md +8 -4
  11. package/skill/references/proof.md +44 -3
  12. package/skill/references/review.md +4 -2
  13. package/skill/references/security.md +1 -1
  14. package/skill/scripts/forge +1 -1
  15. package/skill/templates/coder-brief.md +18 -1
  16. package/src/autopilot/binnacle.mjs +21 -3
  17. package/src/cli/block.mjs +292 -33
  18. package/src/cli/context.mjs +175 -0
  19. package/src/cli/gates.mjs +125 -15
  20. package/src/cli/ledger.mjs +46 -2
  21. package/src/cli/proof.mjs +62 -16
  22. package/src/cli/report.mjs +7 -4
  23. package/src/cli/review-file.mjs +61 -6
  24. package/src/cli/status.mjs +212 -0
  25. package/src/config/validate.mjs +45 -11
  26. package/src/context/pack.mjs +319 -0
  27. package/src/doctor/local.mjs +2 -1
  28. package/src/engines/sentinel.mjs +27 -6
  29. package/src/gates/detect.mjs +21 -8
  30. package/src/gates/expand.mjs +202 -0
  31. package/src/gates/owned-count.mjs +66 -0
  32. package/src/gates/run.mjs +49 -12
  33. package/src/install/wizard/answers.mjs +34 -5
  34. package/src/install/wizard/flags.mjs +8 -1
  35. package/src/install/wizard/project-file.mjs +2 -0
  36. package/src/install/wizard/run.mjs +6 -0
  37. package/src/install/wizard/steps.mjs +24 -4
  38. package/src/install/wizard/summary.mjs +13 -5
  39. package/src/ledger/export.mjs +1 -1
  40. package/src/ledger/outside-review.mjs +164 -0
  41. package/src/ledger/report.mjs +17 -5
  42. package/src/proof/export.mjs +93 -10
  43. package/src/proof/lock.mjs +28 -5
  44. package/src/proof/worktree.mjs +482 -0
  45. package/src/review/gate-check.mjs +9 -5
  46. package/src/review/open-findings.mjs +59 -0
  47. package/src/state/block-end.mjs +290 -0
  48. package/src/state/block.mjs +103 -39
  49. package/src/state/brief-status.mjs +131 -0
  50. package/src/state/config-snapshot.mjs +51 -0
  51. package/src/state/registry.mjs +35 -4
  52. package/src/state/run.mjs +10 -1
  53. package/src/util/breadcrumbs.mjs +1 -0
  54. package/src/util/forbidden.mjs +36 -1
  55. package/src/util/gate-token.mjs +8 -0
  56. package/src/worker/engine.mjs +157 -17
  57. package/src/worker/loop.mjs +26 -7
  58. package/src/worker/queue.mjs +36 -10
  59. package/src/worker/ticket.mjs +46 -6
package/CHANGELOG.md CHANGED
@@ -6,6 +6,47 @@ waits on a second real consumer (plan §10.4, Q17).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.8.0] — 2026-10-12
10
+
11
+ ### Added
12
+
13
+ - B60: related-test gates (issue #13 item 3). block open --tests <paths> names the test files of a block; a gate argv
14
+ item exactly {test_files} becomes those files, or the full suite runs when the block owns more than
15
+ gates.full_suite_threshold_files files or no test file is known. Gate rows carry scope, reason and test_count. A gate
16
+ can be {argv, env}; env values are never printed or recorded, and child output masks values of 8 or more characters.
17
+ - B61: Laravel under export isolation is verified. A Laravel or plain PHP project on PHPUnit without Pest is detected as
18
+ php-phpunit. Export copies a Composer vendor folder instead of linking it, so PHP autoloads the code of the block and
19
+ not the main tree. An opt-in live check (CODE_FORGE_LIVE_PHP=1) runs a Laravel app on PHPUnit and on Pest through the
20
+ export.
21
+ - proof.isolation: worktree — each block gets its own git worktree and branch (proof.worktree.dir/branch/base); owned
22
+ files need to be disjoint only inside one tree; block close --remove-worktree removes the tree and keeps the branch
23
+ (issue #13)
24
+ - Worktree blocks: gates run --block, review-file, the worker, block close and proof export/red-green all measure the
25
+ block's own tree (against its base), never the main checkout; a missing, malformed, off-branch or foreign tree is
26
+ refused (issue #13)
27
+ - Context pack: a top-level `context` key (rules, lessons, commands, max_lines) and `code-forge context pack --run
28
+ --block [--json]`, which writes the pack, prints its path and token estimate and appends a signed `context.pack` row;
29
+ review packets carry the rules and lessons (issue #13)
30
+ - Coder test proof: the coder report may carry `TESTS <command> exit=<n>`; with `context.require_coder_tests: true`
31
+ (fixed at run start) `block close --report` refuses a report without a green TESTS line and writes a signed
32
+ `coder.tests` row (issue #13)
33
+ - code-forge status: at most 10 lines per run (state, mode, spend, open blocks with level, model, last gate, open
34
+ findings) or one --json line; every block verb takes --json and prints one JSON line (issue #13)
35
+ - Block-end signal: every block end writes <run root>/blocks/<id>.done.json; hooks.on_block_end runs a command once per
36
+ block end (no shell, timeout, signed hook.ran row); a failing hook only warns (issue #13)
37
+ - ledger outside-review records an outside reviewer's rounds and findings per PR (signed outside_review row, link
38
+ redacted); report gets a 14th section, outside_review, with the code-forge cost of the PR's blocks next to the rounds
39
+ (issue #13)
40
+ - docs/brief-mode.md walks a brief-only run with an outside reviewer, related tests, a worktree, the context pack and
41
+ the no-poll flow; a test runs the page's config and steps; the skill says when to use brief mode or plan mode (issue
42
+ #13)
43
+
44
+ ### Changed
45
+
46
+ - BREAKING: autonomy.coder_may_push and autonomy.coder_may_open_draft_pr now default to false, so a coder no longer
47
+ pushes or opens a draft PR unless the config allows it; init asks both (default no) and takes --coder-may-push /
48
+ --coder-may-open-draft-pr yes|no; set both to true to keep the old behaviour (issue #13)
49
+
9
50
  ## [0.7.7] — 2026-10-11
10
51
 
11
52
  ### Added
package/README.md CHANGED
@@ -8,9 +8,57 @@ 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.8
12
+
13
+ In 0.8.0:
14
+
15
+ - **Run only the tests a block touches.** `block open --tests <paths>` names the block's test
16
+ files. A gate argv item exactly `{test_files}` becomes those files. When the block owns more
17
+ files than `gates.full_suite_threshold_files`, or no test file is known, the full suite runs.
18
+ The ledger row says which one ran. A gate can also be `{argv, env}`. Env values are never
19
+ printed. Gate env is not a secret store.
20
+ - **Laravel works under export, checked live.** A Laravel app on PHPUnit (no Pest) is now
21
+ detected. Export copies the Composer `vendor` folder instead of linking it, so the tests load
22
+ the block's code, not the main tree's. A live check (`CODE_FORGE_LIVE_PHP=1`) runs a Laravel
23
+ app on PHPUnit and on Pest through the export.
24
+ - **One git worktree per block.** With `proof.isolation: worktree`, `block open` gives each block
25
+ its own git worktree and branch (`proof.worktree.dir`, `branch`, `base`). Blocks in different
26
+ trees may own the same files. Nothing is pushed. `block close --remove-worktree` removes the
27
+ tree and keeps the branch. Gates, reviews, the close check and the mutation proof of that
28
+ block all run on the block's tree, never on the main checkout.
29
+ - **A context pack for coders.** Set `context: {rules, lessons, commands, max_lines}`.
30
+ `code-forge context pack --run <r> --block <id>` writes one file with the block's header,
31
+ the build and test commands, the project rules and the lessons, cut to `max_lines`. It prints
32
+ the path and a token estimate, and writes a signed `context.pack` row. Review packets now
33
+ carry the same rules and lessons. With no `context` key, nothing changes.
34
+ - **Coders prove a green test run.** A coder prints `TESTS <command> exit=<n>` just before its
35
+ COMPLETE sentinel. With `context.require_coder_tests: true` (fixed at `run start`),
36
+ `block close --report` refuses a report with no such line or a non-zero exit, even when
37
+ reviews are waived, and writes a signed `coder.tests` row when it passes.
38
+ - **Short status.** `code-forge status [--run <id>] [--json]` prints at most 10 lines per run:
39
+ state, mode, spend, and one line per open block with its level, model, last gate and open
40
+ findings. It is read-only. Every `block` verb takes `--json` and prints one JSON line, so an
41
+ orchestrator never has to read logs or poll transcripts.
42
+ - **A signal when a block ends.** Every block end writes `~/.code-forge/runs/<run>/blocks/<id>.done.json`
43
+ (status, commit, spend, open findings). With `hooks.on_block_end` (fixed at `run start`),
44
+ code-forge also runs your command once per block end, with the block id, status and file path
45
+ in its env, under a timeout, and writes a signed `hook.ran` row. A failing hook only warns.
46
+ - **Breaking: coders no longer push or open draft PRs by default.** `autonomy.coder_may_push` and
47
+ `autonomy.coder_may_open_draft_pr` now default to `false`. `init` asks both (default no) and
48
+ writes your answers. To keep the old behaviour, set both to `true` in `.code-forge.yml`.
49
+ - **Outside review rounds next to code-forge cost.** `code-forge ledger outside-review --run <r>
50
+ --pr <n> --rounds <n> --findings <n> [--link <url>] [--block <id>]…` records how many rounds an
51
+ outside reviewer needed on a PR (a later call for the same PR replaces it). `report` gets a
52
+ 14th section, `outside_review`: per PR, the rounds and findings next to the code-forge cost
53
+ of its blocks.
54
+ - **A walk-through for brief-only runs.** [docs/brief-mode.md](docs/brief-mode.md) walks one task
55
+ from a brief to a closed block with an outside reviewer: related tests, a worktree, the context
56
+ pack, the done file and hook, `status`, and the outside review rounds in `report`. A test runs
57
+ the page's own config and steps, so the page and the CLI stay the same.
58
+
11
59
  ## What's new in 0.7
12
60
 
13
- Next release:
61
+ In 0.7.7:
14
62
 
15
63
  - **Work from a brief, with no plan.** `run start --mode brief` marks the run as brief-only.
16
64
  Then `block open` without `--level` picks the lane from the rules: the owned files,
@@ -219,7 +267,7 @@ usage line.
219
267
 
220
268
  | Verb | Usage |
221
269
  |---|---|
222
- | `init` | `code-forge init [--no-interaction] [--tools recommended\|current] [--yes-tool <tool>]… [--harness a,b] [-g\|-p] [--copy] [--provider P] [--level Ln=model[:effort][@provider]]… [--refresh-models] [--multimodel on\|off] [--second-provider P] [--jev-ref <item-id\|link\|op://…> \| --jev-env NAME \| --no-jev] [--engine auto\|solo\|harness] [--solo-project N] [--gate name=cmd]… [--proof isolation=export\|lock \| high=a,b \| link_dirs=a,b \| copy_untracked=a,b]… [--skip-doctor]` |
270
+ | `init` | `code-forge init [--no-interaction] [--tools recommended\|current] [--yes-tool <tool>]… [--harness a,b] [-g\|-p] [--copy] [--provider P] [--level Ln=model[:effort][@provider]]… [--refresh-models] [--multimodel on\|off] [--second-provider P] [--jev-ref <item-id\|link\|op://…> \| --jev-env NAME \| --no-jev] [--engine auto\|solo\|harness] [--solo-project N] [--gate name=cmd]… [--proof isolation=export\|lock \| high=a,b \| link_dirs=a,b \| copy_untracked=a,b]… [--coder-may-push yes\|no] [--coder-may-open-draft-pr yes\|no] [--skip-doctor]` |
223
271
  | `doctor` | `code-forge doctor [--quick] [--json] [--cwd <dir>]` |
224
272
  | `tools` | `code-forge tools [--json]` · `code-forge tools install [<id>…] [--yes] [--dry-run]` — see and install the recommended tools (claude, codex, gemini, grok, op, solo) |
225
273
  | `validate` | `code-forge validate [--file <path>]` |
package/docs/README.md CHANGED
@@ -15,11 +15,12 @@ disagree, the verb is right: each verb prints its own usage line when its argume
15
15
  | 5 | [tutorial-first-test.md](tutorial-first-test.md) | run one real end-to-end test: build the code-forge marketing site in a new repo |
16
16
  | 5a | [review-only.md](review-only.md) | only review code you already wrote: a branch, uncommitted work or some files, with CI exit codes |
17
17
  | 5b | [autopilot.md](autopilot.md) | step away while a run goes on: grant a delegate a few decisions for a set time, and review the live log (Binnacle and Full log) when you return |
18
+ | 5c | [brief-mode.md](brief-mode.md) | code one small task from a brief, with no plan: a brief-mode run with an outside reviewer, related tests, worktrees, the context pack and the done file, step by step |
18
19
  | 6 | [examples/marketing-site/brief.md](examples/marketing-site/brief.md) | copy the brief the tutorial uses |
19
20
  | 7 | [marketing.md](marketing.md) | read the product story: the problem, the promise, the honest limits |
20
21
 
21
22
  New to the tool? Read 1 → 3 → 4, then do 5. Keep 2 open as a glossary. Only want reviews? Read 4,
22
- then 5a. Going away while a run goes on? Read 5b.
23
+ then 5a. Going away while a run goes on? Read 5b. One small task, no plan? Read 5c.
23
24
 
24
25
  ## Reference pages (already in the repo)
25
26
 
@@ -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": 62,
7
+ "block_count": 73,
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": {
@@ -850,6 +850,162 @@
850
850
  "B58"
851
851
  ],
852
852
  "note": "root-added 2026-10-11; GitHub issue #13 adoption plan (plans/adoption-13/plan.md); also amends src/state/{run,block,config-snapshot}.mjs, src/cli/{run,block,report}.mjs, src/ledger/report.mjs, src/review/engine.mjs (shared migration/policy path checks), docs/{how-it-works,concepts}.md, skill/SKILL.md, skill/references/plan.md"
853
+ },
854
+ "B60": {
855
+ "title": "Related-test gates (issue #13 item 3): block open --tests <paths> sets the block's test files (exact paths or directories; * ? and a leading - refused); a gate argv item exactly {test_files} expands to them (else owned entries that look like tests) and is dropped for the full suite when the block owns more than gates.full_suite_threshold_files files (tracked and new, not ignored; globs and directories count the files they cover) or no test file is known; gate.red/gate.done carry scope, reason, test_count, run and block; a gate may be {argv, env}: env values are never printed or recorded, and child output masks values of 8+ characters; plain gates run exactly as before",
856
+ "wave": 13,
857
+ "level": "L1",
858
+ "status": "landed",
859
+ "owned": [
860
+ "src/gates/expand.mjs",
861
+ "src/gates/owned-count.mjs",
862
+ "src/util/gate-token.mjs",
863
+ "test/gates/placeholders.test.mjs"
864
+ ],
865
+ "depends_on": [
866
+ "B59"
867
+ ],
868
+ "note": "root-added 2026-10-11; GitHub issue #13 adoption plan (plans/adoption-13/plan.md); also amends src/gates/{run,detect}.mjs, src/proof/{export,lock}.mjs, src/cli/{gates,block}.mjs, src/state/{block,registry}.mjs, src/config/validate.mjs, src/doctor/local.mjs, src/install/wizard/{answers,steps,summary}.mjs, src/autopilot/binnacle.mjs, schema/code-forge.schema.json, docs/reference/config.md, docs/{how-it-works,concepts}.md, skill/SKILL.md, skill/references/{proof,code,ledger}.md"
869
+ },
870
+ "B61": {
871
+ "title": "Laravel under export, verified (issue #13 item 3): composer.json + vendor/bin/phpunit without Pest is the php-phpunit stack (php artisan test --compact with artisan, else vendor/bin/phpunit; PHP is detected before Node); export copies a Composer vendor (link_dirs entry holding composer/autoload_real.php, resolved through realpath, reflink when possible) instead of linking it, because PHP resolves __DIR__ through symlinks and a linked vendor autoloaded app code from the main tree; absolute links into the main tree are pointed into the export; an opt-in live test (CODE_FORGE_LIVE_PHP=1) runs a Laravel skeleton on PHPUnit and on Pest green through the export while the main tree is red; proof.md marks PHP under export VERIFIED",
872
+ "wave": 13,
873
+ "level": "L2",
874
+ "status": "landed",
875
+ "owned": [
876
+ "test/proof/laravel-live.test.mjs"
877
+ ],
878
+ "depends_on": [
879
+ "B60"
880
+ ],
881
+ "note": "root-added 2026-10-11; GitHub issue #13 adoption plan (plans/adoption-13/plan.md); also amends src/gates/detect.mjs, src/proof/export.mjs, src/install/wizard/answers.mjs (comment), test/gates/detect.test.mjs, test/proof/export.test.mjs, test/init/fix-r3.test.mjs (Laravel+PHPUnit now php-phpunit), docs/{how-it-works,concepts,getting-started}.md, skill/references/proof.md"
882
+ },
883
+ "B62a": {
884
+ "title": "Worktree isolation, open and close (issue #13 item 4): proof.isolation worktree gives each block its own git worktree and branch (proof.worktree {dir, branch, base}, dir resolved from the repository top level, <repo-name> is its basename, a symlinked dir is recorded by its realpath); both keys are read from the run's config snapshot and run reload refuses to change them; block open adds the tree (GIT_* stripped), re-checks the branch just before the add, rolls the tree and its own branch back on a later failure and names any leftover; a re-open reuses or re-adds the tree on the same branch and keeps the tree's base (a different --base is refused, block rebase refuses a worktree block); owned files must be disjoint only inside one tree; block close --remove-worktree removes the tree (never the branch) and writes worktree.removed or a signed worktree.remove_failed; --remove-worktree is on the coder forbidden list",
885
+ "wave": 13,
886
+ "level": "L2",
887
+ "status": "landed",
888
+ "owned": [
889
+ "src/proof/worktree.mjs",
890
+ "test/proof/worktree.test.mjs"
891
+ ],
892
+ "depends_on": [
893
+ "B61"
894
+ ],
895
+ "note": "root-added 2026-10-11; GitHub issue #13 adoption plan (plans/adoption-13/plan.md); also amends src/state/{block,registry,run,config-snapshot}.mjs, src/cli/block.mjs, src/util/forbidden.mjs, schema/code-forge.schema.json, docs/reference/config.md, docs/{how-it-works,concepts}.md, skill/references/{proof,code,ledger,security}.md, test/state/registry.test.mjs, test/util/forbidden.test.mjs, test/config/schema-coverage.test.mjs, test/engines/{codex-home,builders/argv-snapshots,builders/forbidden-rendering}.test.mjs"
896
+ },
897
+ "B62b": {
898
+ "title": "Worktree isolation, gates, reviews and the close check (issue #13 item 4): for a block with a worktree, gates run --block runs in the tree (--cwd must name the workspace's place in the tree, else exit 2; --cwd=<dir> is refused), {test_files} and the threshold count in the tree, gate rows name the tree; review-file queues in the main tree but hashes the tree's content, the ticket id includes the tree, and the worker reads content and the diff against worktree.base_sha from the run record's tree (a mismatch, a missing, malformed, off-branch or foreign tree is unavailable or refused); block close measures changed and unowned files in the tree against its base with no sibling or tree_at_open exemption; proof export and red-green build the export from the tree and never write to it, and proof lock is refused for a worktree block; nothing is pushed, merged or checked out, and the main tree's HEAD and index stay unchanged; blocks without a worktree behave as before",
899
+ "wave": 13,
900
+ "level": "L1",
901
+ "status": "landed",
902
+ "owned": [
903
+ "test/proof/worktree-flow.test.mjs"
904
+ ],
905
+ "depends_on": [
906
+ "B62a"
907
+ ],
908
+ "note": "root-added 2026-10-12; GitHub issue #13 adoption plan (plans/adoption-13/plan.md); also amends src/cli/{block,gates,proof,review-file}.mjs, src/proof/worktree.mjs, src/review/gate-check.mjs, src/worker/{engine,loop,queue,ticket}.mjs, test/proof/worktree.test.mjs, test/review/engine.test.mjs, test/worker/queue.test.mjs, docs/{how-it-works,concepts}.md, skill/references/{proof,review,code,ledger}.md"
909
+ },
910
+ "B63a": {
911
+ "title": "Context pack (issue #13 item 5, issue #10 item 2): a top-level context key (rules, lessons, commands, max_lines default 300); code-forge context pack --run --block [--json] writes <run-root>/context/<block>.md with a header, the redacted commands in POSIX quoting, the rules and the lessons, cut to max_lines (lessons first, then rules from the last file back, each cut marked), prints the path and a token estimate, and appends a signed context.pack row unless the block's last row has the same sha256; paths outside the repository, missing files and control characters are refused with nothing written; review packets (first and recheck) carry the rules and lessons from the main tree in the 60-line digest slot, read from the live config, and an unreadable file makes the ticket unavailable (context-unreadable); with no context key everything behaves as before",
912
+ "wave": 13,
913
+ "level": "L1",
914
+ "status": "landed",
915
+ "owned": [
916
+ "src/context/pack.mjs",
917
+ "src/cli/context.mjs",
918
+ "src/worker/engine.mjs",
919
+ "test/context/pack.test.mjs"
920
+ ],
921
+ "depends_on": [
922
+ "B62b"
923
+ ],
924
+ "note": "root-added 2026-10-12; GitHub issue #13 adoption plan (plans/adoption-13/plan.md); also amends src/util/breadcrumbs.mjs, schema/code-forge.schema.json, test/config/schema-coverage.test.mjs, docs/{how-it-works,concepts}.md, docs/reference/config.md, skill/SKILL.md, skill/references/{code,review,ledger}.md"
925
+ },
926
+ "B63b": {
927
+ "title": "Coders prove a green test run (issue #13 item 5, issue #10 item 1): parseSentinel returns tests {command, exit} from the last whole-line TESTS <command> exit=<n> in the returned sentinel's segment (after the previous sentinel of any block; 0-255, no leading zeros); context.require_coder_tests (run-start snapshot only, run reload refuses a change) makes block close refuse with no --report, an unreadable report, no COMPLETE sentinel, no TESTS line or a non-zero exit, even when reviews are waived or external, and on a pass writes a signed coder.tests row with the close rows; the coder brief template gets a context pack slot and the TESTS line; with the key false or absent close behaves as before",
928
+ "wave": 13,
929
+ "level": "L1",
930
+ "status": "landed",
931
+ "owned": [
932
+ "test/engines/sentinel-tests.test.mjs"
933
+ ],
934
+ "depends_on": [
935
+ "B63a"
936
+ ],
937
+ "note": "root-added 2026-10-12; GitHub issue #13 adoption plan (plans/adoption-13/plan.md); also amends src/engines/sentinel.mjs, src/cli/block.mjs, src/context/pack.mjs, src/state/{config-snapshot,run}.mjs, schema/code-forge.schema.json, test/config/schema-coverage.test.mjs, test/skill/structure.test.mjs, docs/{how-it-works,concepts}.md, docs/reference/config.md, skill/SKILL.md, skill/references/{code,ledger}.md, skill/templates/coder-brief.md"
938
+ },
939
+ "B64a": {
940
+ "title": "Short status (issue #13 item 6, part 1): code-forge status [--run] [--json] is read-only and prints at most 10 lines per run (run id, state, mode, spend; one line per open block with level, coder model, last gate and the open findings the close gate would refuse; an overflow line and a closed/stopped tally), or one JSON line; every block verb takes --json and prints one JSON line, with stdout empty on failure except a gate-refused close; without --json block output is unchanged",
941
+ "wave": 13,
942
+ "level": "L1",
943
+ "status": "landed",
944
+ "owned": [
945
+ "src/cli/status.mjs",
946
+ "src/state/brief-status.mjs",
947
+ "test/cli/status.test.mjs"
948
+ ],
949
+ "depends_on": [
950
+ "B63b"
951
+ ],
952
+ "note": "root-added 2026-10-12; GitHub issue #13 adoption plan (plans/adoption-13/plan.md); also amends src/cli/block.mjs, docs/{how-it-works,concepts}.md, skill/SKILL.md, skill/references/code.md"
953
+ },
954
+ "B64b": {
955
+ "title": "Block-end signal (issue #13 item 6, part 2): every block end (close, stop, a stop from close) writes <run root>/blocks/<id>.done.json atomically (status, event, reason, commit, block spend, open findings, time); hooks.on_block_end {argv, timeout_s}, fixed at run start, runs once per end outside the run lock with no shell and four CODE_FORGE_* env vars, its process group killed at the timeout, and writes one signed hook.ran row; a failing hook only warns; a coder transcript running the hook command is refused at close",
956
+ "wave": 13,
957
+ "level": "L1",
958
+ "status": "landed",
959
+ "owned": [
960
+ "src/state/block-end.mjs",
961
+ "src/review/open-findings.mjs",
962
+ "test/state/block-end.test.mjs"
963
+ ],
964
+ "depends_on": [
965
+ "B64a"
966
+ ],
967
+ "note": "root-added 2026-10-12; GitHub issue #13 adoption plan (plans/adoption-13/plan.md); also amends src/cli/block.mjs, src/cli/status.mjs, src/state/{block,config-snapshot,run}.mjs, src/util/forbidden.mjs, schema, docs/{how-it-works,concepts}.md, docs/reference/config.md, skill/SKILL.md, skill/references/{code,ledger}.md"
968
+ },
969
+ "B65": {
970
+ "title": "Safer push and draft-PR defaults (issue #13 item 7): autonomy.coder_may_push and autonomy.coder_may_open_draft_pr default to false; init asks both (default no, the draft-PR question only after a push yes, pre-filled on a re-run), writes both keys, and takes --coder-may-push / --coder-may-open-draft-pr yes|no; a draft PR without push is refused; the skill gates every hand-back, push or PR on the same checks",
971
+ "wave": 13,
972
+ "level": "L1",
973
+ "status": "landed",
974
+ "owned": [
975
+ "test/init/autonomy.test.mjs"
976
+ ],
977
+ "depends_on": [
978
+ "B64b"
979
+ ],
980
+ "note": "root-added 2026-10-12; GitHub issue #13 adoption plan (plans/adoption-13/plan.md); breaking default change; also amends src/install/wizard/{answers,flags,project-file,run,steps}.mjs, schema, docs/{how-it-works,concepts,getting-started,overview}.md, docs/reference/config.md, skill/references/code.md, test/init/{flags,settings}.test.mjs, test/fixtures/repos/laravel-vue/expected.code-forge.yml"
981
+ },
982
+ "B66": {
983
+ "title": "Outside review rounds per PR next to code-forge cost",
984
+ "wave": 13,
985
+ "level": "L1",
986
+ "status": "landed",
987
+ "owned": [
988
+ "src/ledger/outside-review.mjs",
989
+ "test/ledger/outside-review.test.mjs"
990
+ ],
991
+ "depends_on": [
992
+ "B65"
993
+ ],
994
+ "note": "root-added 2026-10-12; GitHub issue #13 adoption plan (plans/adoption-13/plan.md); also amends src/cli/ledger.mjs, src/cli/report.mjs, src/ledger/report.mjs, src/ledger/export.mjs and the 14-section pins"
995
+ },
996
+ "B67": {
997
+ "title": "Docs and skill for brief-only runs, context packs and the no-poll flow",
998
+ "wave": 13,
999
+ "level": "L1",
1000
+ "status": "landed",
1001
+ "owned": [
1002
+ "docs/brief-mode.md",
1003
+ "test/integration/brief-mode-walkthrough.test.mjs"
1004
+ ],
1005
+ "depends_on": [
1006
+ "B66"
1007
+ ],
1008
+ "note": "root-added 2026-10-12; GitHub issue #13 adoption plan (plans/adoption-13/plan.md); also amends skill/SKILL.md, skill/references/code.md, docs/README.md, docs/how-it-works.md and test/skill/structure.test.mjs"
853
1009
  }
854
1010
  }
855
1011
  }
@@ -130,22 +130,26 @@ Every other key defaults as shown, or is entirely absent from a minimal file. `a
130
130
  | `caps` | object | | — | |
131
131
  | `caps.coders` | integer | | `2` | min 1 |
132
132
  | `gates` | object | | — | |
133
- | `gates.test` | array<string> \| null | | — | |
134
- | `gates.lint` | array<string> \| null | | — | |
135
- | `gates.types` | array<string> \| null | | — | |
136
- | `gates.format` | array<string> \| null | | — | |
133
+ | `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. |
134
+ | `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. |
135
+ | `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. |
136
+ | `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. |
137
137
  | `gates.extra` | object | | — | |
138
138
  | `gates.extra.secret_scan` | boolean | | `true` | |
139
139
  | `gates.extra.safe_edit` | boolean | | `true` | |
140
140
  | `gates.extra.invariants` | string | | — | |
141
- | `gates.full_suite_threshold_files` | integer | | `10` | min 1 |
141
+ | `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. |
142
142
  | `proof` | object | | — | |
143
143
  | `proof.tiers` | object | | — | |
144
144
  | `proof.tiers.high` | object | | — | |
145
145
  | `proof.tiers.high.paths` | array<string> | | — | |
146
146
  | `proof.tiers.light` | object | | — | |
147
147
  | `proof.extra` | array<string> | | — | |
148
- | `proof.isolation` | enum: `export`, `lock` | | `"export"` | |
148
+ | `proof.isolation` | enum: `export`, `lock`, `worktree` | | `"export"` | export: proof runs in a temporary copy; lock: in the shared tree under the proof lock; worktree (B62a): block open gives each block its own git worktree and branch (proof.worktree). Fixed for the run: run reload refuses to change it. |
149
+ | `proof.worktree` | object | | — | |
150
+ | `proof.worktree.dir` | string | | `"../<repo-name>-worktrees"` | minLength 1; Where the trees go, relative to the repository top level (not the workspace). In the default, <repo-name> is replaced with the basename of the repository top level; the default text is never used as is. The default sits beside the repository, so the main tree never lists the trees as untracked. |
151
+ | `proof.worktree.branch` | string | | `"forge/{run}/{block}"` | minLength 1; The branch template: only {run} and {block} are substituted; the result must pass git check-ref-format --branch. |
152
+ | `proof.worktree.base` | string | | — | minLength 1; The commit-ish the tree starts from; default the block's base (block open --base, else HEAD). |
149
153
  | `proof.export` | object | | — | |
150
154
  | `proof.export.link_dirs` | array<string> | | `["vendor","node_modules"]` | |
151
155
  | `proof.export.copy_untracked` | array<string> | | `[".env",".env.testing"]` | |
@@ -157,8 +161,8 @@ Every other key defaults as shown, or is entirely absent from a minimal file. `a
157
161
  | `budget.usd` | number | | — | > 0; Per-run spend stop in USD (R6). Before every session spawn the run's estimated spend is checked: one warning at 80%, new sessions refused at 100%. |
158
162
  | `budget.block_cases` | integer | | `80` | min 10 |
159
163
  | `autonomy` | object | | — | |
160
- | `autonomy.coder_may_open_draft_pr` | boolean | | `true` | |
161
- | `autonomy.coder_may_push` | boolean | | `true` | |
164
+ | `autonomy.coder_may_open_draft_pr` | boolean | | `false` | A coder may open a draft PR on its own, under the skill's five-condition rule. Has effect only when coder_may_push is true. False or absent: it never opens a PR. |
165
+ | `autonomy.coder_may_push` | boolean | | `false` | A coder may push its block branch on its own. False or absent: it hands back the branch and the orchestrator (or the human) pushes. |
162
166
  | `commit` | object | | — | |
163
167
  | `commit.trailers` | enum: `none`, `harness` | | `"none"` | |
164
168
  | `wip` | object | | — | |
@@ -174,6 +178,20 @@ Every other key defaults as shown, or is entirely absent from a minimal file. `a
174
178
  | `keys.<key>` | string | | — | user-defined key |
175
179
  | `tmp` | object | | — | |
176
180
  | `tmp.root` | string \| null | | `null` | |
181
+ | `context` | object | | — | |
182
+ | `context.rules` | array<string> | | `[]` | Rules files (paths relative to the repository root), in order. Read from the main tree; a missing file or a path outside the repository is refused. |
183
+ | `context.lessons` | string | | — | minLength 1; One lessons-learned file (path relative to the repository root). Cut first when the pack is over max_lines. |
184
+ | `context.commands` | object | | — | |
185
+ | `context.commands.build` | array<string> | | — | minItems 1; The build command (argv), shown POSIX shell-quoted in the pack. |
186
+ | `context.commands.test` | array<string> | | — | minItems 1; The test command (argv), shown POSIX shell-quoted in the pack. |
187
+ | `context.commands.lint` | array<string> | | — | minItems 1; The lint command (argv), shown POSIX shell-quoted in the pack. |
188
+ | `context.commands.format` | array<string> | | — | minItems 1; The format command (argv), shown POSIX shell-quoted in the pack. |
189
+ | `context.max_lines` | integer | | `300` | min 20; The pack's line cap. Lessons are cut first, then the rules files from the last one backwards, each cut section ending in [cut: N lines dropped]; the header and the commands are never cut. |
190
+ | `context.require_coder_tests` | boolean | | `false` | block close refuses unless --report carries the block's COMPLETE sentinel with a `TESTS <command> exit=0` line before it, and writes a signed coder.tests row. Authority, unlike the rest of `context`: read from the run-start snapshot only, and `run reload` refuses a change to it. Reviews waived (--no-require-reviews or review.external) do not lift it. |
191
+ | `hooks` | object | | — | |
192
+ | `hooks.on_block_end` | object | | — | |
193
+ | `hooks.on_block_end.argv` | array<string> | yes | — | minItems 1; The command as an argv list (no shell): at least one item, and every item a non-empty string (an empty argument is refused). argv[0] is a program on PATH or a path. |
194
+ | `hooks.on_block_end.timeout_s` | integer | | `60` | min 1; max 600; Seconds before the hook's process group is killed (the hook.ran row then says timed_out). |
177
195
  | `autopilot` | object | | — | |
178
196
  | `autopilot.min_confidence` | number | | `0.7` | min 0; max 1; B46: the lowest confidence at which the autopilot delegate's answer is acted on; below it the question goes to the owner. |
179
197
  | `telemetry` | const `"off"` | | `"off"` | |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codedology/code-forge",
3
- "version": "0.7.7",
3
+ "version": "0.8.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",
@@ -205,7 +205,12 @@
205
205
  "invariants": { "type": "string" }
206
206
  }
207
207
  },
208
- "full_suite_threshold_files": { "type": "integer", "minimum": 1, "default": 10 }
208
+ "full_suite_threshold_files": {
209
+ "type": "integer",
210
+ "minimum": 1,
211
+ "default": 10,
212
+ "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."
213
+ }
209
214
  }
210
215
  },
211
216
  "proof": {
@@ -227,7 +232,17 @@
227
232
  }
228
233
  },
229
234
  "extra": { "type": "array", "items": { "type": "string" } },
230
- "isolation": { "type": "string", "enum": ["export", "lock"], "default": "export" },
235
+ "isolation": { "type": "string", "enum": ["export", "lock", "worktree"], "default": "export", "description": "export: proof runs in a temporary copy; lock: in the shared tree under the proof lock; worktree (B62a): block open gives each block its own git worktree and branch (proof.worktree). Fixed for the run: run reload refuses to change it." },
236
+ "worktree": {
237
+ "type": "object",
238
+ "additionalProperties": false,
239
+ "description": "B62a: used when proof.isolation is worktree. block open runs git worktree add <dir>/<block> -b <branch> <base>; nothing is pushed. Fixed for the run: run reload refuses to change it. Until B62b, gates, reviews and the close check run as under export (from the main checkout) and do not see changes made only in the block worktree.",
240
+ "properties": {
241
+ "dir": { "type": "string", "minLength": 1, "default": "../<repo-name>-worktrees", "description": "Where the trees go, relative to the repository top level (not the workspace). In the default, <repo-name> is replaced with the basename of the repository top level; the default text is never used as is. The default sits beside the repository, so the main tree never lists the trees as untracked." },
242
+ "branch": { "type": "string", "minLength": 1, "default": "forge/{run}/{block}", "description": "The branch template: only {run} and {block} are substituted; the result must pass git check-ref-format --branch." },
243
+ "base": { "type": "string", "minLength": 1, "description": "The commit-ish the tree starts from; default the block's base (block open --base, else HEAD)." }
244
+ }
245
+ },
231
246
  "export": {
232
247
  "type": "object",
233
248
  "additionalProperties": false,
@@ -266,8 +281,16 @@
266
281
  "type": "object",
267
282
  "additionalProperties": false,
268
283
  "properties": {
269
- "coder_may_open_draft_pr": { "type": "boolean", "default": true },
270
- "coder_may_push": { "type": "boolean", "default": true }
284
+ "coder_may_open_draft_pr": {
285
+ "type": "boolean",
286
+ "default": false,
287
+ "description": "A coder may open a draft PR on its own, under the skill's five-condition rule. Has effect only when coder_may_push is true. False or absent: it never opens a PR."
288
+ },
289
+ "coder_may_push": {
290
+ "type": "boolean",
291
+ "default": false,
292
+ "description": "A coder may push its block branch on its own. False or absent: it hands back the branch and the orchestrator (or the human) pushes."
293
+ }
271
294
  }
272
295
  },
273
296
  "commit": {
@@ -315,6 +338,45 @@
315
338
  }
316
339
  }
317
340
  },
341
+ "context": {
342
+ "type": "object",
343
+ "additionalProperties": false,
344
+ "description": "The coder context pack. `code-forge context pack --run <id> --block <id>` builds one pack per block from it (header, commands, rules, lessons; cut to max_lines), and every review packet carries the rules and lessons in its project-rules digest. The whole key is read from the live config, not the run-start snapshot: it is not authority, and `run reload` accepts a change to it — except require_coder_tests, which is authority, read from the snapshot only and fixed for the run. Without this key packets are unchanged.",
345
+ "properties": {
346
+ "rules": { "type": "array", "items": { "type": "string", "minLength": 1 }, "uniqueItems": true, "default": [], "description": "Rules files (paths relative to the repository root), in order. Read from the main tree; a missing file or a path outside the repository is refused." },
347
+ "lessons": { "type": "string", "minLength": 1, "description": "One lessons-learned file (path relative to the repository root). Cut first when the pack is over max_lines." },
348
+ "commands": {
349
+ "type": "object",
350
+ "additionalProperties": false,
351
+ "description": "The project's commands as argv lists, shown to the coder in the pack. code-forge never runs them.",
352
+ "properties": {
353
+ "build": { "type": "array", "items": { "type": "string", "minLength": 1 }, "minItems": 1, "description": "The build command (argv), shown POSIX shell-quoted in the pack." },
354
+ "test": { "type": "array", "items": { "type": "string", "minLength": 1 }, "minItems": 1, "description": "The test command (argv), shown POSIX shell-quoted in the pack." },
355
+ "lint": { "type": "array", "items": { "type": "string", "minLength": 1 }, "minItems": 1, "description": "The lint command (argv), shown POSIX shell-quoted in the pack." },
356
+ "format": { "type": "array", "items": { "type": "string", "minLength": 1 }, "minItems": 1, "description": "The format command (argv), shown POSIX shell-quoted in the pack." }
357
+ }
358
+ },
359
+ "max_lines": { "type": "integer", "minimum": 20, "default": 300, "description": "The pack's line cap. Lessons are cut first, then the rules files from the last one backwards, each cut section ending in [cut: N lines dropped]; the header and the commands are never cut." },
360
+ "require_coder_tests": { "type": "boolean", "default": false, "description": "block close refuses unless --report carries the block's COMPLETE sentinel with a `TESTS <command> exit=0` line before it, and writes a signed coder.tests row. Authority, unlike the rest of `context`: read from the run-start snapshot only, and `run reload` refuses a change to it. Reviews waived (--no-require-reviews or review.external) do not lift it." }
361
+ }
362
+ },
363
+ "hooks": {
364
+ "type": "object",
365
+ "additionalProperties": false,
366
+ "description": "Commands code-forge runs on the owner's machine. Read from the run-start snapshot only: `run reload` refuses a change. With hooks.on_block_end set, .code-forge.yml is non-ownable (the same guard as review.external.required): no block may own it, also when hooks.on_block_end is the only key that guards it.",
367
+ "properties": {
368
+ "on_block_end": {
369
+ "type": "object",
370
+ "additionalProperties": false,
371
+ "required": ["argv"],
372
+ "description": "Run once after every block end (block close closes or stops it, block stop), after the done file `<run root>/blocks/<id>.done.json` is written (its absolute path is in CODE_FORGE_DONE_FILE, the empty string when it was not written, and in done_file of close/stop --json) and outside the run lock. No shell; cwd is the repository top; stdin closed; output discarded; env adds CODE_FORGE_RUN, CODE_FORGE_BLOCK, CODE_FORGE_BLOCK_STATUS, CODE_FORGE_DONE_FILE. One signed hook.ran row (block, hook, argv0, exit, signal, timed_out, duration_ms) per block end, also when the hook fails. A failing, missing or timed-out hook only adds a stderr warning. A coder transcript that runs it refuses the close.",
373
+ "properties": {
374
+ "argv": { "type": "array", "items": { "type": "string", "minLength": 1 }, "minItems": 1, "description": "The command as an argv list (no shell): at least one item, and every item a non-empty string (an empty argument is refused). argv[0] is a program on PATH or a path." },
375
+ "timeout_s": { "type": "integer", "minimum": 1, "maximum": 600, "default": 60, "description": "Seconds before the hook's process group is killed (the hook.ran row then says timed_out)." }
376
+ }
377
+ }
378
+ }
379
+ },
318
380
  "autopilot": {
319
381
  "type": "object",
320
382
  "additionalProperties": false,
@@ -360,8 +422,23 @@
360
422
  }
361
423
  },
362
424
  "argvOrNull": {
425
+ "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.",
363
426
  "anyOf": [
364
427
  { "type": "array", "minItems": 1, "items": { "type": "string", "minLength": 1 } },
428
+ {
429
+ "type": "object",
430
+ "additionalProperties": false,
431
+ "required": ["argv"],
432
+ "properties": {
433
+ "argv": { "type": "array", "minItems": 1, "items": { "type": "string", "minLength": 1 } },
434
+ "env": {
435
+ "type": "object",
436
+ "additionalProperties": false,
437
+ "patternProperties": { "^[A-Za-z_][A-Za-z0-9_]*$": { "type": "string" } },
438
+ "description": "Variables added to the gate's environment, NAME: value. code-forge never prints or records an env value. Gate env is not a secret store. Child output is masked only for values of 8 or more characters. Only the names are shown (env_keys); put secrets in the environment or a secret manager."
439
+ }
440
+ }
441
+ },
365
442
  { "type": "null" }
366
443
  ]
367
444
  }
package/skill/SKILL.md CHANGED
@@ -11,14 +11,14 @@ You are the **orchestrator**: the session that invoked this skill. You dispatch,
11
11
 
12
12
  1. `forge validate` — exit 0, or stop and show the refusal (it names the key, never the value).
13
13
  2. `forge doctor --quick` — config, links, key resolution. On the first run of a day run `forge doctor` in full and read its `engine` row and its `signer: same-user boundary only` row (`references/security.md`).
14
- 3. `forge run start` — prints `run <id> started · engine <e> · worker pid <n>`. The engine is `solo`, `harness` or `subprocess` (`engine: auto` picks; `subprocess` only by explicit config). No engine ⇒ the four-line stop text in `references/degraded.md` §1 is printed and you stop. Keep `<id>`: every later verb takes `--run <id>`. A small change with briefs and no plan starts with `forge run start --mode brief` instead (`references/plan.md` §6); the mode is fixed for the run.
14
+ 3. `forge run start` — prints `run <id> started · engine <e> · worker pid <n>`. The engine is `solo`, `harness` or `subprocess` (`engine: auto` picks; `subprocess` only by explicit config). No engine ⇒ the four-line stop text in `references/degraded.md` §1 is printed and you stop. Keep `<id>`: every later verb takes `--run <id>`. Pick the mode here; it is fixed for the run. **Brief mode** — `forge run start --mode brief` (`references/plan.md` §6) — is for one small task or a short list of tasks (a monorepo pass) whose blocks are clear from their briefs: no plan doc, no `forge plan check`, no plan approval; steps 1–3 of §3 below (facts, author, harden) are skipped and `block open` without `--level` takes the rules lane (a signed `decision` row, `source: rules`). **Plan mode** (the default) is for multi-block work that needs `forge plan check` and the owner's approval.
15
15
  4. When the engine is not `solo`, print the banner of `references/degraded.md` §2 once.
16
16
 
17
17
  ## §1 Invocation and state
18
18
 
19
19
  `/code-forge plan|harden|code|full [brief file | plan file]`. No job given ⇒ infer it: a brief ⇒ `plan`; a plan whose header says `status: planned` ⇒ `harden`; `status: hardened` ⇒ `code`. `full` runs the three in order with one human checkpoint after `harden`.
20
20
 
21
- State lives in the plan file's header — `facts → planned → hardened → coding → done` — and in the run record: `forge run status --run <id>` prints the active blocks, `forge ledger tail --slug <slug>` the last rows. Takeover after a lost session: `references/continuity.md`. A config change mid-run (a model swap, the review topology) is `forge run reload --run <id>` — never `block stop` + `run end` + `run start` (`references/continuity.md` §4).
21
+ State lives in the plan file's header — `facts → planned → hardened → coding → done` — and in the run record. To check progress run `forge status` — never read logs or poll transcripts for it. It is read-only and shows each active run (a run of this repository that is not ended; `no active run` when there is none) in ≤ 10 lines: the spend, then each open or in-attempt block's state, level, model, last gate and open findings, `… +<k> more blocks` when they do not fit, and the `closed <a> · stopped <b>` counts; `--run <id>` for one run, `--json` for one line; `forge run status --run <id>` prints the full record, `forge ledger tail --slug <slug>` the last rows. To know a block is done, wait for its done file `<run root>/blocks/<id>.done.json` — `<run root>` is `~/.code-forge/runs/<run>/`; written at every close or stop — or the owner's `hooks.on_block_end` command instead of polling (`references/code.md`). With `hooks.on_block_end` set, `close`/`stop --json` carry the file's absolute path as `done_file`, and the hook's own env (not yours) carries it as `CODE_FORGE_DONE_FILE`. A done file from an earlier end stays after a reopen: count it only when its `at` is later than your current dispatch. If no fresh done file appears by the time the coder agent reports (or stderr shows `warning: done file not written`), run `forge status` once. Takeover after a lost session: `references/continuity.md`. A config change mid-run (a model swap, the review topology) is `forge run reload --run <id>` — never `block stop` + `run end` + `run start` (`references/continuity.md` §4).
22
22
 
23
23
  ## §2 Roles by level (never by model name)
24
24
 
@@ -38,11 +38,11 @@ State lives in the plan file's header — `facts → planned → hardened → co
38
38
  2. `forge author --job plan --brief <brief> --facts <sheet> --out <plan>` — refuses without `--facts` (exit 2) and refuses a sheet older than the brief. The draft's §0 is the sheet, verbatim.
39
39
  3. Harden: `forge author --job harden --brief <brief> --facts <sheet> --draft <plan> --answers <file>` until `questions[]` is empty. The human answers; you relay, never answer for them (`references/harden.md`). Before the exit gate, record each block's lane: `forge jev ask lane --block <id> --plan <plan> --state <file>` (`--rules` without Jev); the plan's level must equal it. Exit gate: `forge plan check <plan>` green, with a lane for every block on its `lanes:` line (`references/plan.md`).
40
40
  Then show the plan to the owner for approval in the harness's plan mode, never as chat text (Claude Code: EnterPlanMode, write the plan, ExitPlanMode); a harness without a plan mode shows it and waits for an explicit yes. No block is dispatched before that approval (`references/plan.md` §4.1).
41
- 4. Per block, in dispatch order, never more than `caps.coders` alive: `forge block open <id> --run <r> --level L<n> --owned <paths…> --acceptance <file> --brief <file> --lines <n> [--kind code|docs|contract]` prints the pointer `BRIEF <path> lines=<n> sha=<sha8> <<<EOM>>>`. Send the pointer per the engine's adapter — `solo` → `references/adapters/solo.md`, `harness` → `references/adapters/claude-code.md`, `subprocess` → `references/adapters/subprocess.md` — and wait for `ACK <sha8> lines=<n>`.
41
+ 4. Per block, in dispatch order, never more than `caps.coders` alive: `forge block open <id> --run <r> --level L<n> --owned <paths…> --acceptance <file> --brief <file> --lines <n> [--kind code|docs|contract] [--tests <paths…>]` prints the pointer `BRIEF <path> lines=<n> sha=<sha8> <<<EOM>>>`. With a `context` key, build the pack right after `block open`, before the pointer goes out: `forge context pack --run <r> --block <id>`; send the pack's text with the pointer and with every later coder task (`references/code.md` §3). Send the pointer per the engine's adapter — `solo` → `references/adapters/solo.md`, `harness` → `references/adapters/claude-code.md`, `subprocess` → `references/adapters/subprocess.md` — and wait for `ACK <sha8> lines=<n>`.
42
42
  5. The coder's first message is its **facts diff**, then its forecast (cases and lines, `templates/block-record.md`), then code, file by file: after each file `forge review-file <path> --block <id> --run <r>`, then `forge review-file --wait <ticket>` before the next file. Rounds ≥ 2 re-check only the fix hunks (`references/review.md`).
43
- 6. `===BLOCK <id> COMPLETE===` or `===BLOCK <id> FAILED: <reason>===` ends the coder's turn. Its report is a claim sheet: re-measure it (`references/code.md` §5).
44
- 7. `forge block close <id> --run <r> --report <coder report>` is the gate: a report without a `reviewed <path> ticket <id>` line per changed file is FAILED; a signed `review.approved` row per changed file that no later review of the same content overrode (`not_approved`), no `unowned_change`, no `no_changes` (the files are the owned diff plus every owned file reviewed for the block), gates green, every clause covered, forecast vs actual, no `rule_break`. Red ⇒ `references/decisions.md` (S1 `next`, escalation). Green ⇒ one commit for the block in the project's house style; `forge block rebase <id> --run <r>` first when HEAD moved.
45
- 8. `forge run end --run <r>` when every block is `done`; `forge report --slug <slug>` prints cost per block, escalations, review rounds and the spend per run (`budget.usd`, `references/ledger.md` §3.1).
43
+ 6. `===BLOCK <id> COMPLETE===` or `===BLOCK <id> FAILED: <reason>===` ends the coder's turn. Its report is a claim sheet: re-measure it (`references/code.md` §5). Just before the sentinel it carries the `TESTS <command> exit=<n>` line (`references/code.md` §3).
44
+ 7. `forge block close <id> --run <r> --report <coder report>` is the gate: a report without a `reviewed <path> ticket <id>` line per changed file is FAILED; with `context.require_coder_tests` (set before `run start`) `--report` is required, and a report whose LAST `TESTS` line before its COMPLETE sentinel is missing or not `TESTS <command> exit=0` is FAILED too, waived or external reviews or not (`references/code.md` §3); a signed `review.approved` row per changed file that no later review of the same content overrode (`not_approved`), no `unowned_change`, no `no_changes` (the files are the owned diff plus every owned file reviewed for the block), gates green, every clause covered, forecast vs actual, no `rule_break`. Red ⇒ `references/decisions.md` (S1 `next`, escalation). Green ⇒ one commit for the block in the project's house style; `forge block rebase <id> --run <r>` first when HEAD moved.
45
+ 8. `forge run end --run <r>` when every block is `done`; `forge report --slug <slug>` prints cost per block, escalations, review rounds, the outside review rounds per PR next to their code-forge cost, and the spend per run (`budget.usd`, `references/ledger.md` §3.1); when a PR also got an outside review, record it first with `forge ledger outside-review --run <r> --pr <n> --rounds <n> --findings <n> [--link <url>] [--block <id>]…` (`references/ledger.md` §4).
46
46
 
47
47
  ## §4 Hard rules (the headings of `references/rules-core.md`)
48
48