@codedology/code-forge 0.15.2 → 0.15.4

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 (79) hide show
  1. package/CHANGELOG.md +81 -0
  2. package/README.md +5 -1
  3. package/docs/README.md +2 -0
  4. package/docs/reference/blocks.json +162 -1
  5. package/docs/reference/config.md +38 -15
  6. package/native/README.md +10 -0
  7. package/native/bin/darwin-arm64/code-forge-native +0 -0
  8. package/native/bin/darwin-x64/code-forge-native +0 -0
  9. package/native/bin/linux-arm64/code-forge-native +0 -0
  10. package/native/bin/linux-x64/code-forge-native +0 -0
  11. package/package.json +4 -1
  12. package/schema/code-forge.schema.json +83 -3
  13. package/schema/outcome.schema.json +45 -0
  14. package/schema/pipeline.schema.json +30 -0
  15. package/scripts/first-use.mjs +8 -0
  16. package/scripts/gen-config-doc.mjs +1 -1
  17. package/skill/references/autopilot.md +6 -1
  18. package/skill/references/code.md +52 -0
  19. package/skill/references/ledger.md +13 -1
  20. package/skill/references/plan.md +5 -1
  21. package/skill/references/review.md +46 -8
  22. package/skill/scripts/forge +1 -1
  23. package/src/cli/block.mjs +427 -38
  24. package/src/cli/context.mjs +17 -7
  25. package/src/cli/doctor.mjs +32 -1
  26. package/src/cli/gates.mjs +76 -10
  27. package/src/cli/help.mjs +5 -0
  28. package/src/cli/native-status.mjs +13 -0
  29. package/src/cli/outcome.mjs +87 -0
  30. package/src/cli/pipeline.mjs +61 -0
  31. package/src/cli/proof.mjs +10 -7
  32. package/src/cli/report.mjs +53 -6
  33. package/src/cli/review-file.mjs +278 -41
  34. package/src/cli/run.mjs +3 -2
  35. package/src/cli/status.mjs +43 -9
  36. package/src/cli/sweep.mjs +28 -0
  37. package/src/cli/validate.mjs +6 -0
  38. package/src/config/validate.mjs +83 -1
  39. package/src/context/pack.mjs +53 -3
  40. package/src/doctor/index.mjs +11 -0
  41. package/src/doctor/local.mjs +22 -4
  42. package/src/doctor/worker-probe.mjs +1 -1
  43. package/src/engines/builders/codex.mjs +27 -7
  44. package/src/engines/builders/grok.mjs +11 -5
  45. package/src/engines/codex-home.mjs +18 -15
  46. package/src/engines/sentinel.mjs +11 -0
  47. package/src/gates/detect.mjs +6 -3
  48. package/src/gates/run.mjs +295 -27
  49. package/src/ledger/export.mjs +1 -1
  50. package/src/ledger/report.mjs +143 -5
  51. package/src/outcome/outcome.mjs +108 -0
  52. package/src/pipeline/pipeline.mjs +112 -0
  53. package/src/proof/red-green.mjs +41 -13
  54. package/src/proof/runners.mjs +28 -4
  55. package/src/proof/worktree.mjs +25 -15
  56. package/src/review/agent-waiver.mjs +113 -12
  57. package/src/review/engine.mjs +25 -5
  58. package/src/review/gate-check.mjs +52 -10
  59. package/src/review/lenses/full.md +1 -0
  60. package/src/review/lenses/quick.md +1 -0
  61. package/src/review/model-stats.mjs +125 -0
  62. package/src/review/packet.mjs +88 -9
  63. package/src/review/related.mjs +112 -0
  64. package/src/review/reopen.mjs +42 -16
  65. package/src/review/route.mjs +58 -4
  66. package/src/review/session-retry.mjs +59 -0
  67. package/src/session/facts.mjs +19 -7
  68. package/src/session/plan-check.mjs +4 -1
  69. package/src/session/spawn.mjs +54 -3
  70. package/src/state/block.mjs +139 -49
  71. package/src/state/config-snapshot.mjs +13 -3
  72. package/src/state/run.mjs +3 -2
  73. package/src/util/exec.mjs +48 -31
  74. package/src/util/native.mjs +131 -0
  75. package/src/util/reaper.mjs +238 -8
  76. package/src/worker/engine.mjs +7 -3
  77. package/src/worker/loop.mjs +211 -30
  78. package/src/worker/queue.mjs +107 -24
  79. package/src/worker/watch.mjs +18 -7
package/CHANGELOG.md CHANGED
@@ -6,6 +6,87 @@ waits on a second real consumer (plan §10.4, Q17).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.15.4] — 2026-10-31
10
+
11
+ ### Added
12
+
13
+ - Go core, step 1: `native/` Go library (canonical JSON, row signing, ticket ids, ledger read) and a golden compat kit made from the Node functions; Go tests prove byte-equal output (B180)
14
+ - `block land <id>` runs review join, changed-file gates and close in one step, stopping at the first failure with the next command; `--merge` also merges the block branch into its base (B185)
15
+ - The npm package ships a prebuilt Go helper (`code-forge-native`, darwin/linux, arm64/x64; built at release) with `version` and `verify-ledger`; `doctor` shows it, and Node is used when it is missing or `CODE_FORGE_NATIVE=0` (B181)
16
+ - `block land` and `block close --auto-claim` claim and review a file changed outside the owned list (unless another open block owns it), and claims are kept when a block is reopened with `--attempt N` (B186)
17
+ - Review packets always keep the file's import header, the judge packet shows the cited `path:line` windows (ranges like `a-b` too), and `block reopen --yes` works on an open file with findings when the judge agrees (B192)
18
+ - `block land --max <dur>` (default 30m) waits for reviews, retries the join up to 3 times while a findings round is open, and on timeout stops with `wait_timeout` and a next line that names a larger `--max` (B190)
19
+ - Merge gate: `block land --merge` runs the full suite on the merged result in a temporary worktree first; a red suite or a conflict leaves main unchanged. Skip with `--no-merge-gate` or `gates.merge_gate: false` (B189)
20
+ - A file whose review stopped with findings is reviewed again automatically when a fix changes its content; unchanged or waived files are not re-queued (B194)
21
+ - Every Go command the tests run sets `CGO_ENABLED=0`, so `go test` passes on macOS without extra setup; `native/README.md` says how to test the Go code (B195)
22
+ - `block waive --yes` now says the evidence may cite `:line or :start-end`, the same as `block reopen --yes`; a range citation is accepted (B193)
23
+ - `forge facts` cuts an excerpt over 200 characters (ending with `…`) instead of refusing the whole sheet; repo-relative paths with an extension or a top-level folder, and absolute paths, are now path claims; `§ 12.1` is no longer a version claim; a long brief with almost no claims prints a warning (#29, B197)
24
+ - Critical findings still need a human waiver by default; under an active autopilot grant, `block waive --yes` may waive a critical when the reason cites a passing acceptance test and the finding's lines and the judge agrees; the row records `by: agent` and `autopilot: true` (B196)
25
+ - Go core, step 3: `code-forge-native daemon` serves `wait-block`, `block-tickets` and `status` over a Unix socket with a ledger tail index, a queue index and a cached worker start time; answers match the Node commands (compat tests). Not used by the CLI yet (B182)
26
+ - The full test suite is green again: reopen output keeps its old text when no claims are kept, the config doc is current, and a plan clause that only cites a planned test file is no longer refused. (B204)
27
+ - The native daemon status helper now lives with the other command files, so the import-order rule passes again. (B218)
28
+ - Commit watch waits until a burst of commits ends (15 s quiet, 120 s at most) and reviews only the last commit, so a coder making several quick commits no longer starts extra reviews. (B211)
29
+ - The worker and `status` use the native Go daemon when it runs and fall back to Node when it does not; status output is the same in every mode, and `doctor` shows the daemon state. (B183)
30
+ - Two block tests now give the commit watch its second quiet tick, so they pass with the B211 debounce. (B219)
31
+ - The worker runs up to 6 review tickets at once (was 3), with at most 4 sessions per provider (`review.provider_parallel`); tickets waiting for a provider still count toward the limit. (B208)
32
+ - code-forge picks the primary reviewer from this project's measured speed, availability and accuracy (`review.adaptive`, on by default). A matching route still competes on performance unless it has `pin: true`. (B207)
33
+
34
+ ## [0.15.3] — 2026-10-30
35
+
36
+ ### Added
37
+
38
+ - Gate sets per block kind (`gates.kinds`), and unknown config keys now say to upgrade with the running version (B160)
39
+
40
+ ### Changed
41
+
42
+ - The test leak check ignores a live code-forge run of another project (its run record names a workspace outside the temp dir, raw or resolved path) and that project's ledger; test-made runs stay leaks (B158)
43
+ - Two worker tests no longer fail on a busy machine: the orphan test waits on observed worker events, and the enqueue speed test subtracts a cold node start measured in the same test; both keep their checks (B157)
44
+ - Scaffolding lock files skip code review through `review.exclude`; close signs each exclusion and checks excluded npm locks for HTTPS registry URLs and integrity, while other lock formats receive no safety check (B155)
45
+ - Named custom gates: `gates.custom` runs named gates after the fixed ones and records evidence files (path, bytes, sha256) on the gate rows (B146)
46
+ - Time in the report: `cost_per_block` shows wall time and human wait per block; a new `run_time` section shows each run's wall time, cost and blocks, plus a total (B147b)
47
+ - Design before code: `block open --after <ids>` refuses until every named block is closed; a block that waits for a human check is not done (B148)
48
+ - Report: spend with no run id is shown as `outside runs`, not as a "(none)" run; stopped blocks show `stopped` and the time to the stop (B149).
49
+ - Report: spend per run and cost per block print `outside runs` instead of a "(none)" run (B150).
50
+ - Specialist outcome file: `code-forge outcome seal|check` and `schema/outcome.schema.json`; a caller uses a specialist result only when it is approved and every file matches its sealed sha256 (B151)
51
+ - Specialist pipelines: validate `code-forge.pipeline.yml` and use `code-forge pipeline status|next` to see step states and ready work (B152)
52
+ - Specialist pipelines: `block open --after-outcome <step ids>` waits until each named step's outcome is approved and passes `outcome check`, and records the accepted outcome hashes; `block close --report` no longer asks for a review ticket for a `review.exclude` file (B153)
53
+ - `code-forge outcome record` writes a signed `outcome.recorded` row for an approved specialist outcome; `report` shows them in a `specialist_outcomes` section (B154)
54
+ - Gates: `gates.timeout_s` (30..7200 s), CI=1 and npm_config_yes=false for every gate, a gate that ends on an
55
+ interactive prompt fails with reason `prompt`, and a warning when the checkout has no installed dependencies (B156)
56
+ - Context pack: a `## code-forge` section names the running node, the absolute code-forge bin and its version, and tells
57
+ the coder not to use another code-forge from PATH (B162)
58
+ - `block rebase` works for a block with its own worktree: it rebases the block branch onto main, moves the block base,
59
+ and earlier green gates stop counting (B159)
60
+ - Reviews see the sibling test and source files of a changed file, so findings about missing coverage are checked
61
+ against real related tests (B161)
62
+
63
+ ### Fixed
64
+
65
+ - Grok runs no longer start the user's Cursor or Claude MCP servers, so no orphan MCP helpers stay behind and use memory
66
+ (B164)
67
+ - An invalid L3 judge answer is now a broken result, not a disagreement: it retries once, then tries the next L3 model;
68
+ a schema-invalid review answer uses every level fallback even when `second_levels` is empty (#24, #26 item 5, B168)
69
+ - Codex `spawn --background` keeps its CODEX_HOME after the parent exits, and a Codex coder in a linked worktree can commit:
70
+ it gets write access only to the worktree git dir and the shared objects, refs and logs, never to config or hooks (#26 items 3-4, B167)
71
+ - `block open --base <sha>` uses the given commit (also for the proof worktree), and reuses an existing block branch that
72
+ descends from that base instead of refusing it (#26 item 1, B166)
73
+ - `block close` accepts review results from a worker that was pinned for the same run before `run start --reattach`; a worker never pinned for the run still stops the block (B176)
74
+ - Gates: a gate that ends with no exit code (signal or timeout) now says why and shows the output tail; the detected Pint and
75
+ Prettier format gates check only git-tracked files, in batches; gate rows store `duration_ms` and `report` shows gate time per block (#26 items 11, 14, B173)
76
+ - Proof: Pest/Laravel projects get a default junit runner, `run reload` may add new proof runners, a `proof.worktree.setup`
77
+ hook runs before RED (a failing setup fails with reason `setup`), and assertion deletion removes a multi-line chained
78
+ Pest `expect()` (#26 items 6-8, B171)
79
+ - Coder context: `context pack` includes the block brief and acceptance and prints the pack itself; `spawn` for a coder
80
+ appends them; the sentinel and TESTS line are found in a JSON-escaped coder answer (#26 items 9-10, B172)
81
+ - `block reopen` now restarts the review: it clears the cached done result, queues a fresh ticket, and `review-file --wait-block` waits for the new result instead of returning the old one (#25, B169)
82
+ - Process cleanup: `sweep` never stops a worker of an open run or a run it cannot read; zombie parents are cleaned up and an unreadable registry status is reported, not guessed (B163)
83
+ - Idle CPU: `review-file --wait-block` and the worker back off from 200/250 ms up to 5 s while nothing changes, and a block file is re-hashed only when its mtime or size changes (B177)
84
+ - Process sweep (`sweep`, `run start`, `doctor`) never lists or stops a worker of another code-forge home, or one whose environment cannot be read; before, tests with an isolated HOME stopped the live review worker (B178)
85
+ - `--help` and `-h` work on every `block` verb and write nothing; docs explain `review.routes` for files over 400 lines, the L3 judge fallback, the pnpm 11 symlink note and `codex exec </dev/null` (#26 items 12, 15-16, #24, B175)
86
+ - Review timing: `review-file --cwd <dir>` runs from any folder; each ticket records queued, started and finished times and `duration_ms`, and `report` shows review time per block (#26 items 13-14, B174)
87
+ - A `block close` refused because main moved now prints its exits: `review-file --rebase-check` and the human-only `block rebase --accept-changed <file> --reason`; each rebase writes a signed `block.rebase` row (#23, B170)
88
+ - The suite is green again after the B163–B178 merges; the review worker now starts a waiting ticket at once instead of after its idle back-off, so parallel reviews keep their speedup (B179)
89
+
9
90
  ## [0.15.2] — 2026-10-30
10
91
 
11
92
  ### Fixed
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  A CLI and Agent Skill for cross-model, parallel, evidence-gated feature delivery.
4
4
 
5
- Latest: 0.15.2 - see [CHANGELOG.md](CHANGELOG.md)
5
+ Latest: 0.15.4 - see [CHANGELOG.md](CHANGELOG.md)
6
6
 
7
7
  ## The problem
8
8
 
@@ -113,6 +113,8 @@ default. See [the workflow](docs/how-it-works.md) and [concepts](docs/concepts.m
113
113
  | `code-forge gates detect\|run` | Discover and run project checks |
114
114
  | `code-forge keys list\|set\|test\|remove` | Manage key references without printing secrets |
115
115
  | `code-forge report` | Inspect recorded outcomes and costs |
116
+ | `code-forge outcome seal\|check` | Seal a specialist result and check its files before a caller uses it |
117
+ | `code-forge pipeline status\|next` | See specialist step states and the next ready step |
116
118
  | `code-forge logs` | Inspect local errors; `logs report` previews a public issue |
117
119
  | `code-forge upgrade` | Refresh installed skill copies after upgrading the package |
118
120
  | `code-forge --help` | List every verb; each verb prints its own usage on bad arguments |
@@ -127,6 +129,8 @@ To upgrade, run `npm install -g @codedology/code-forge@latest`, then `code-forge
127
129
  The reference lists every key, type, and default, including budget, escalation, coder floors,
128
130
  review risk, multimodel, concurrency, and timeout settings.
129
131
  `review.routes` sends big files to another model with its own timeout and second level.
132
+ See the [routing and fallback examples](docs/how-it-works.md) for a long-context reviewer above
133
+ 400 lines and fallback lists of `{provider, model}` objects (optional `effort`, never strings).
130
134
  `block open --kind code|docs|contract` overrides the kind inferred from filenames.
131
135
  Regenerate the reference with `node scripts/gen-config-doc.mjs`; add `--check` to check it.
132
136
 
package/docs/README.md CHANGED
@@ -28,6 +28,8 @@ then 5a. Going away while a run goes on? Read 5b. One small task, no plan? Read
28
28
  |---|---|
29
29
  | [../CHANGELOG.md](../CHANGELOG.md) | what changed in each version; `## [Unreleased]` is the next one |
30
30
  | [reference/config.md](reference/config.md) | every `.code-forge.yml` key, its type and default. Generated from `schema/code-forge.schema.json` |
31
+ | [outcomes.md](outcomes.md) | specialist result fields, approval statuses, and the outcome seal and check commands |
32
+ | [pipelines.md](pipelines.md) | specialist steps, dependencies, and the pipeline status and next commands |
31
33
  | [reference/blocks.json](reference/blocks.json) | the build blocks of this package and what each one owns |
32
34
  | [marketing/code-forge-story.md](marketing/code-forge-story.md) | the marketing content bank (copy, lessons, what not to claim) |
33
35
  | [privacy.md](privacy.md) | what the local error log holds (errors, warnings, the last command names), every cleaning rule, the AI check, the setup check (`--with-doctor`), known fixes, and how to turn the log off or delete it |
@@ -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": 156,
7
+ "block_count": 165,
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": {
@@ -2883,6 +2883,167 @@
2883
2883
  "B141"
2884
2884
  ],
2885
2885
  "note": "F-17. Took the B143 commit, updated the pinned coder-only counts. Reviews clean on the first join; gates passed on the first run."
2886
+ },
2887
+ "B146": {
2888
+ "title": "Named custom gates with evidence: gates.custom [{name, argv, env?, evidence?}] run after the 4 fixed gates, each on its own; evidence files recorded as path, bytes and sha256",
2889
+ "wave": 15,
2890
+ "level": "L2",
2891
+ "status": "landed",
2892
+ "owned": [
2893
+ "src/gates/run.mjs",
2894
+ "src/gates/custom.mjs",
2895
+ "src/cli/gates.mjs",
2896
+ "src/config/validate.mjs",
2897
+ "schema/code-forge.schema.json",
2898
+ "test/gates/custom.test.mjs",
2899
+ "test/gates/run.test.mjs",
2900
+ "test/gates/cli.test.mjs",
2901
+ "test/config/cli-validate.test.mjs",
2902
+ "test/config/schema-coverage.test.mjs",
2903
+ "docs/reference/config.md",
2904
+ "skill/references/code.md"
2905
+ ],
2906
+ "depends_on": [],
2907
+ "note": "F006 phase 1 (proof gates for the website skill). One fix round (drive-letter segments in evidence paths, unreadable evidence no longer stops later gates, a stronger no-gate-ran test). Gates passed on the first run."
2908
+ },
2909
+ "B147": {
2910
+ "title": "Time per block and per run in the report: cost_per_block gets wallMs and humanWaitMs; a new run_time section lists each run (wall time, USD, blocks closed and stopped) and a total",
2911
+ "wave": 15,
2912
+ "level": "L2",
2913
+ "status": "landed",
2914
+ "owned": [
2915
+ "src/ledger/report.mjs",
2916
+ "src/cli/report.mjs",
2917
+ "test/ledger/report-time.test.mjs",
2918
+ "test/ledger/report.test.mjs",
2919
+ "test/ledger/export.test.mjs",
2920
+ "test/ledger/cli.test.mjs",
2921
+ "skill/references/ledger.md",
2922
+ "docs/how-it-works.md"
2923
+ ],
2924
+ "depends_on": [],
2925
+ "note": "F006 phase 1 (cost and time per site). Stopped for scope only: test/first-use.test.mjs and test/state/brief-mode.test.mjs pin 15 report sections and were not owned. Its changes landed through B147b."
2926
+ },
2927
+ "B147b": {
2928
+ "title": "Time per block and per run in the report: cost_per_block gets wallMs and humanWaitMs; a new run_time section lists each run (wall time, USD, blocks closed and stopped) and a total",
2929
+ "wave": 15,
2930
+ "level": "L2",
2931
+ "status": "landed",
2932
+ "owned": [
2933
+ "src/ledger/report.mjs",
2934
+ "src/cli/report.mjs",
2935
+ "test/ledger/report-time.test.mjs",
2936
+ "test/ledger/report.test.mjs",
2937
+ "test/ledger/export.test.mjs",
2938
+ "test/ledger/cli.test.mjs",
2939
+ "skill/references/ledger.md",
2940
+ "docs/how-it-works.md",
2941
+ "test/first-use.test.mjs",
2942
+ "test/state/brief-mode.test.mjs"
2943
+ ],
2944
+ "depends_on": [
2945
+ "B147"
2946
+ ],
2947
+ "note": "Took the B147 commits and updated the two pinned section lists to 16. Reviews clean on the first join; gates passed on the first run (F-27 workaround for close)."
2948
+ },
2949
+ "B148": {
2950
+ "title": "block open --after <ids…>: refuse with after-not-done until every named block is closed (a block awaiting a human check is not done); after is recorded on the entry and the dispatch row",
2951
+ "wave": 15,
2952
+ "level": "L2",
2953
+ "status": "landed",
2954
+ "owned": [
2955
+ "src/state/block.mjs",
2956
+ "src/cli/block.mjs",
2957
+ "test/cli/block-after.test.mjs",
2958
+ "test/state/block.test.mjs",
2959
+ "test/cli/block-open-owned.test.mjs",
2960
+ "docs/how-it-works.md",
2961
+ "docs/brief-mode.md",
2962
+ "skill/references/code.md"
2963
+ ],
2964
+ "depends_on": [
2965
+ "B146",
2966
+ "B147b"
2967
+ ],
2968
+ "note": "F006 phase 1 (design before code). One fix round (doc example flags). Gates passed on the first run."
2969
+ },
2970
+ "B149": {
2971
+ "title": "Report run_time: rows with no run id go to outside_runs {usd, rows} (total.usd still counts them; total.runs and wallMs count real runs only); stopped blocks show stopped: true and the time to the stop",
2972
+ "wave": 15,
2973
+ "level": "L2",
2974
+ "status": "landed",
2975
+ "owned": [
2976
+ "src/ledger/report.mjs",
2977
+ "src/cli/report.mjs",
2978
+ "test/ledger/report-time.test.mjs",
2979
+ "test/ledger/report.test.mjs",
2980
+ "test/ledger/export.test.mjs",
2981
+ "test/ledger/cli.test.mjs",
2982
+ "skill/references/ledger.md",
2983
+ "docs/how-it-works.md"
2984
+ ],
2985
+ "depends_on": [],
2986
+ "note": "Fixes F-30 and F-31 (dogfood findings). No fix round. Gates passed on the first run."
2987
+ },
2988
+ "B150": {
2989
+ "title": "Report text: spend per run and the cost_per_block run column print `outside runs` instead of `(none)` for rows with no run id",
2990
+ "wave": 15,
2991
+ "level": "L1",
2992
+ "status": "landed",
2993
+ "owned": [
2994
+ "src/cli/report.mjs",
2995
+ "test/ledger/cli.test.mjs",
2996
+ "test/ledger/report-time.test.mjs",
2997
+ "skill/references/ledger.md"
2998
+ ],
2999
+ "depends_on": [],
3000
+ "note": "Leftover from B149 (F-30). No fix round. Gates passed on the first run."
3001
+ },
3002
+ "B151": {
3003
+ "title": "Specialist outcome file: schema/outcome.schema.json and `code-forge outcome seal|check` (F007 step 1)",
3004
+ "wave": 15,
3005
+ "level": "L2",
3006
+ "status": "landed",
3007
+ "owned": [
3008
+ "schema/outcome.schema.json",
3009
+ "src/outcome/outcome.mjs",
3010
+ "src/cli/outcome.mjs",
3011
+ "test/outcome/schema.test.mjs",
3012
+ "test/outcome/cli.test.mjs",
3013
+ "docs/outcomes.md",
3014
+ "docs/README.md"
3015
+ ],
3016
+ "depends_on": [],
3017
+ "note": "F007 step 1. One fix round (test coverage for a missing in-root file and schema failures). blocks.json entry added before the gates: the new src/outcome/ dir needs an owner for the import-order test."
3018
+ },
3019
+ "B152": {
3020
+ "title": "Pipeline file and read-only pipeline status|next commands (F007 step 2)",
3021
+ "wave": 15,
3022
+ "level": "L2",
3023
+ "status": "landed",
3024
+ "owned": [
3025
+ "schema/pipeline.schema.json",
3026
+ "src/pipeline/pipeline.mjs",
3027
+ "src/cli/pipeline.mjs",
3028
+ "test/pipeline/config.test.mjs",
3029
+ "test/pipeline/cli.test.mjs",
3030
+ "docs/pipelines.md",
3031
+ "docs/outcomes.md",
3032
+ "docs/README.md",
3033
+ "CHANGELOG.md",
3034
+ "docs/reference/blocks.json",
3035
+ "test/docs-blocks.test.mjs"
3036
+ ],
3037
+ "depends_on": ["B151"],
3038
+ "note": "F007 step 2; also amends the baseline-owned src/cli/validate.mjs to check the pipeline after config validation. Its pipeline import is recorded as an exact file/directory exemption in test/imports-order.test.mjs, following the existing later-block CLI amendments."
3039
+ },
3040
+ "B182": {
3041
+ "title": "native daemon status companion",
3042
+ "wave": 15,
3043
+ "level": "L2",
3044
+ "status": "landed",
3045
+ "owned": ["src/cli/native-status.mjs"],
3046
+ "depends_on": ["B64a"]
2886
3047
  }
2887
3048
  }
2888
3049
  }
@@ -11,6 +11,8 @@ Every other key defaults as shown, or is entirely absent from a minimal file. `a
11
11
  | Key | Type | Required | Default | Notes |
12
12
  |---|---|---|---|---|
13
13
  | `version` | const `1` | yes | — | |
14
+ | `native` | object | | — | |
15
+ | `native.daemon` | boolean | | `true` | Use the optional native daemon for run status and block waits. False uses Node only; CODE_FORGE_NATIVE=0 also disables it. |
14
16
  | `project` | object | | — | |
15
17
  | `project.name` | string | | — | |
16
18
  | `project.slug` | string | | — | B50: the ledger name. Default: the project root's folder name (the nearest .code-forge.yml up to the git top level, else the git top level), so every verb run from a subfolder writes to the same ledger. |
@@ -92,6 +94,8 @@ Every other key defaults as shown, or is entirely absent from a minimal file. `a
92
94
  | `escalation.after_rounds_with_warnings` | integer | | `1` | min 0 |
93
95
  | `escalation.warning_threshold` | integer | | `2` | min 1 |
94
96
  | `review` | object | | — | |
97
+ | `review.exclude` | array<string> | | `["**/package-lock.json","**/npm-shrinkwrap.json","**/yarn.lock","**/pnpm-lock.yaml","**/bun.lock","**/bun.lockb","**/composer.lock","**/Cargo.lock","**/Gemfile.lock","**/poetry.lock","**/uv.lock","**/go.sum","**/Package.resolved","**/Podfile.lock","**/pubspec.lock","**/mix.lock","**/flake.lock"]` | Repo-relative globs excluded from model code review, including scaffolding archives. Uses the ownership glob syntax (*, ?, ** and braces). A project list replaces the defaults; [] disables exclusion. Ownership and all other close checks still apply. A passing close signs gate.review_excluded per file. |
98
+ | `review.lockfile_registries` | array<string> | | `["registry.npmjs.org"]` | Allowed URL hosts for excluded package-lock.json and npm-shrinkwrap.json packages.*.resolved entries. Close requires HTTPS and integrity for each resolved package; link:true and entries without resolved are allowed. Other lock formats receive no safety check. |
95
99
  | `review.multimodel` | boolean | | `false` | |
96
100
  | `review.multimodel_for_docs` | boolean | | `false` | |
97
101
  | `review.single_reviewer_max_risk` | number | | `1` | min 0; max 3 |
@@ -113,9 +117,15 @@ Every other key defaults as shown, or is entirely absent from a minimal file. `a
113
117
  | `review.min_tokens_out` | integer | | `120` | min 1; The stub guard: a review answer with fewer output tokens is unavailable (too_short). A clean pass (passed: true, no findings, a non-blank summary) needs only min(this, 40), whatever the diff size (B53). |
114
118
  | `review.batch_small_files` | boolean | | `true` | |
115
119
  | `review.session_timeout_s` | integer | | `300` | min 1 |
120
+ | `review.adaptive` | object | | — | Choose the primary reviewer by availability × (1 − waived share) ÷ median session seconds from this project's ledger. No history keeps configured order; a matching explicit route has configured priority but competes on performance unless pinned. Disabling restores legacy routing exactly. |
121
+ | `review.adaptive.enabled` | boolean | | `true` | |
122
+ | `review.adaptive.window_days` | integer | | `14` | min 1 |
123
+ | `review.adaptive.min_sessions` | integer | | `10` | min 1 |
124
+ | `review.adaptive.explore` | number | | `0.1` | min 0; max 1; Share of tickets given to a less-measured candidate, deterministically hashed from the ticket id. |
116
125
  | `review.routes` | array<object> | | — | B90: size-based review routing. min_lines and model are required. A rule's level decides which sessions it changes: an L2 rule (level: L2, the default) changes only the reviewers' model, and in consensus mode only slot A (slot B stays review.second_levels.L2); an L3 rule changes only the judge's model. The count is the file's lines at the reviewed state (the bytes in the review packet): newline bytes, plus one for a last line without one; a deleted file is never routed. Rules compete only within their level: among the rules of one level, the largest min_lines at or under the count wins; the same level and min_lines twice is refused. Left out, an optional key gets: provider the top-level provider (the rule replaces the level entry, so the level's own provider is not inherited; validation checks the rule with that same provider); effort none; fallback none; session_timeout_s nothing (the rule does not count in the timeout); second review.second_levels for the rule's level. The timeout is one value for every session of the ticket: the largest session_timeout_s among the matched rules that set one (a matched rule without one is left out of that max), else review.session_timeout_s. The second-level try uses the rule's second, except for an L2 rule in consensus mode. The worker writes one signed review.route row per routed level before the first session, never twice for one ticket; its second_model is null when the rule's second is not used, which includes an L2 rule in consensus mode, because slot B is not the rule's. run reload accepts a change. |
117
126
  | `review.routes[].min_lines` | integer | yes | — | min 0; B90: the rule applies to a file with at least this many lines (0: every file that is not deleted). |
118
127
  | `review.routes[].level` | enum: `L2`, `L3` | | `"L2"` | B90: the level the rule replaces, L2 (the reviewers) or L3 (the judge). |
128
+ | `review.routes[].pin` | boolean | | `false` | Force this matching route's model instead of adaptive primary-reviewer selection. |
119
129
  | `review.routes[].model` | string | yes | — | minLength 1 |
120
130
  | `review.routes[].effort` | string | | — | |
121
131
  | `review.routes[].provider` | enum: `anthropic`, `openai`, `xai` | | — | |
@@ -137,16 +147,17 @@ Every other key defaults as shown, or is entirely absent from a minimal file. `a
137
147
  | `review.recheck_scope` | enum: `fix_hunks`, `file` | | `"fix_hunks"` | |
138
148
  | `review.late_findings` | enum: `sweep`, `block` | | `"sweep"` | |
139
149
  | `review.allow_open_book_codex` | boolean | | `false` | B32: let openai (Codex) run reviewer/judge/S2/author with its read-only sandbox, which can read files on this machine. Off: those sessions are refused. |
140
- | `review.provider_concurrency` | object | | — | |
150
+ | `review.provider_concurrency` | object | | — | B40: how many sessions of each provider may run at once in this process; a rate-limited session waits 2 s, then 6 s (±30 %) before it retries. |
141
151
  | `review.provider_concurrency.anthropic` | integer | | `4` | min 1 |
142
152
  | `review.provider_concurrency.openai` | integer | | `2` | min 1 |
143
153
  | `review.provider_concurrency.xai` | integer | | `2` | min 1 |
144
- | `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. |
154
+ | `review.parallel_tickets` | integer | | `6` | 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. |
155
+ | `review.provider_parallel` | integer | | `4` | min 1; max 16; Maximum simultaneous review sessions per provider in the worker. Tickets waiting for this cap still count toward parallel_tickets; stricter provider_concurrency limits still apply. |
145
156
  | `review.fair_share` | boolean | | `false` | Give each free ticket slot to the block with the fewest running tickets; ties stay oldest first. |
146
157
  | `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. A file's first commit is queued at once. Later commits of a file that already has a ticket of this block are held (a watch.held row) until --wait-block, which queues the latest content once before the sentinel. Older pending tickets are still superseded. |
147
- | `review.agent_waivers` | object | | — | |
158
+ | `review.agent_waivers` | object | | — | Human acknowledgement of counted agent waivers. Set before run start; run reload refuses to change require_ack. |
148
159
  | `review.agent_waivers.require_ack` | boolean | | `false` | Require a verified human acknowledgement of the current counted agent waiver set before block close. Read only from the run config snapshot. New or removed waivers need a new acknowledgement. |
149
- | `review.external` | object | | — | |
160
+ | `review.external` | object | | — | B58: an outside reviewer reviews each block. Set it before run start; run reload refuses to add, remove or change it. |
150
161
  | `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. |
151
162
  | `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. |
152
163
  | `engine` | enum: `auto`, `solo`, `harness`, `subprocess` | | `"auto"` | |
@@ -158,16 +169,27 @@ Every other key defaults as shown, or is entirely absent from a minimal file. `a
158
169
  | `harness.solo.interactive_harden` | boolean | | `false` | |
159
170
  | `caps` | object | | — | |
160
171
  | `caps.coders` | integer | | `2` | min 1 |
161
- | `gates` | object | | — | |
172
+ | `gates` | object | | — | Every fixed gate, custom gate and repeat inherits CI=1 and npm_config_yes=false; a gate's explicit env wins. These injected defaults are omitted from env_keys. A gate exiting 0 fails with reason prompt if the last non-empty line of either stdout or stderr ends with ?, (y), (y/n), [y/N], [Y/n] or Yes/No (case-insensitive; trailing whitespace ignored). Earlier prompt-like lines followed by normal output do not count. The failure says: gate ended on an interactive prompt; install the tool or make the command non-interactive. |
162
173
  | `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. |
163
174
  | `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. |
164
175
  | `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. |
165
176
  | `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. |
177
+ | `gates.merge_gate` | boolean | | `true` | With block land --merge, run the configured full suite on a trial merge in a temporary detached worktree under the run worktree root before merging into main. Failure or conflict leaves main unchanged and removes the temporary tree. False or --no-merge-gate skips this check with a warning. Without --merge the gate does not run. |
178
+ | `gates.kinds` | object | | — | Optional gate sets for code, docs and contract blocks. Each value is an array of fixed gate names (test, lint, types, format) or gates.custom[].name values. Unknown names and explicitly null fixed gates are refused with the kind and name. With gates run --run R --block B, the declared block kind wins; otherwise the kind is inferred from owned files using block open's rules. An entry runs only its listed gates; every other configured gate is reported skipped with reason not in gates.kinds.<kind>. A missing entry runs all gates as usual. An empty array runs no commands, reports 0 gates for kind <kind>, and writes gate.done with all_ok: true so block close can proceed to its remaining checks. Light blocks keep their light rules and ignore kinds. A gates run without --block ignores kinds. Example: gates: {types: [npm, run, typecheck], kinds: {docs: [types], contract: []}}. |
179
+ | `gates.kinds.code` | array<string> | | — | |
180
+ | `gates.kinds.docs` | array<string> | | — | |
181
+ | `gates.kinds.contract` | array<string> | | — | |
182
+ | `gates.custom` | array<object> | | — | Named gates run after test, lint, types and format in list order, independently even after a failure, with the same timeout and env rules. At most 20; names are unique and cannot be fixed gate names (gates-not-argv-array error rule). Custom argv refuses {test_files} and {changed_files}; --changed and light-block narrowing do not apply. Evidence uses safe relative paths or simple globs: * within a segment, ** across segments; absolute paths, backslashes, empty strings and .. segments are refused. After pass or fail, regular files under cwd are hashed (no symlinks followed), sorted by path, deduplicated and capped at 200 per gate as {path, bytes, sha256}; unmatched patterns appear in missing and do not fail the gate. Files are never copied or stored as content. gate.red includes gate and custom: true; gate.done includes custom summaries {name, ok, evidence_count, missing?}, and with --run/--block an evidence map by name. Website example: gates: {custom: [{name: build, argv: [npm, run, build]}, {name: lighthouse, argv: [npm, run, lighthouse], evidence: ['reports/**/*.json']}, {name: links, argv: [npm, run, links]}]}. |
183
+ | `gates.custom[].name` | string | yes | — | Matches ^[a-z][a-z0-9_-]{0,31}$, unique in the list, and not test, lint, types or format. |
184
+ | `gates.custom[].argv` | array<string> | yes | — | minItems 1 |
185
+ | `gates.custom[].env` | object | | — | |
186
+ | `gates.custom[].evidence` | array<string> | | — | |
166
187
  | `gates.extra` | object | | — | |
167
188
  | `gates.extra.secret_scan` | boolean | | `true` | |
168
189
  | `gates.extra.safe_edit` | boolean | | `true` | |
169
190
  | `gates.extra.invariants` | string | | — | |
170
191
  | `gates.repeat_new_tests` | integer | | — | min 2; max 10; After a passing test gate with --run and --block, run its command this many additional times for only tests added or changed against the block base; absent means off. Requires a whole {test_files} argv item; otherwise prints repeat_new_tests: skipped (gates.test has no {test_files}). A failed repeat makes the gate red with reason flaky-test; output and signed gate rows carry repeats {n, files, failed}, and report shows the count. |
192
+ | `gates.timeout_s` | integer | | `300` | min 30; max 7200; Whole seconds, 30..7200, for each fixed gate, custom gate and test repeat. gates run --timeout-ms overrides this value. A timed-out gate fails with reason timeout and stderr naming the timeout in seconds and gates.timeout_s to raise. |
171
193
  | `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. |
172
194
  | `proof` | object | | — | |
173
195
  | `proof.tiers` | object | | — | |
@@ -176,24 +198,25 @@ Every other key defaults as shown, or is entirely absent from a minimal file. `a
176
198
  | `proof.tiers.light` | object | | — | |
177
199
  | `proof.extra` | array<string> | | — | |
178
200
  | `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. |
179
- | `proof.worktree` | object | | — | |
201
+ | `proof.worktree` | object | | — | 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. |
180
202
  | `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. |
181
203
  | `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. |
182
204
  | `proof.worktree.base` | string | | — | minLength 1; The commit-ish the tree starts from; default the block's base (block open --base, else HEAD). |
205
+ | `proof.worktree.setup` | array<array<string>> | | — | Argv commands run sequentially in the proof export after export and before RED. A command failure stops proof with reason setup and its output tail. Never run during block worktree creation or under proof lock. |
183
206
  | `proof.export` | object | | — | |
184
207
  | `proof.export.link_dirs` | array<string> | | `["vendor","node_modules"]` | |
185
208
  | `proof.export.copy_untracked` | array<string> | | `[".env",".env.testing"]` | |
186
- | `proof.runners` | array<object> | | — | minItems 1; B87: how proof red-green runs a test file that is not a node:test file. proof red-green uses the first entry whose match covers the --test file (list order). With no match, a .mjs/.cjs/.js file uses the built-in node:test command and any other file is refused. Fixed for the run: run start copies the list into the run snapshot, run reload refuses to change it, and with it set no block may own .code-forge.yml. An empty list is refused: leave the key out for no runner. |
209
+ | `proof.runners` | array<object> | | — | minItems 1; How proof red-green runs tests. The first matching entry wins. With no match, Node files use node:test and PHP tests in Pest/Laravel projects use a default JUnit runner. run reload may append runners but cannot change, reorder or remove existing entries. With runners set no block may own .code-forge.yml. An empty list is refused: leave the key out for defaults. |
187
210
  | `proof.runners[].match` | array<string> | yes | — | minItems 1; Globs of the test files this runner runs, relative to the repository (the same glob rules as owned entries). |
188
211
  | `proof.runners[].adapter` | enum: `junit`, `swift`, `xcodebuild` | yes | — | How the result is read. junit: the runner writes a JUnit XML report to {report} (PHPUnit, Pest, pytest and other JUnit runners). swift: swift test with --xunit-output {report}. xcodebuild: xcodebuild test with -resultBundlePath {result_bundle}; the result is read with xcrun xcresulttool, and a precheck needs full Xcode and the destination's simulator first. |
189
212
  | `proof.runners[].argv` | array<string> | yes | — | minItems 1; The command, an argv array (never a shell string). These items are replaced when an item is exactly the token. junit and swift: {test_file} (the test path), {test_id} (<file>::<case>, or <file> with no case), {test_filter} (the case, regex-escaped), {report} (a fresh report path outside the work dir); {report} and one test token are required. xcodebuild: {workspace}, {project}, {scheme}, {destination} (from xcode), {only_testing} (one item, -only-testing:<test_target>/<Suite>[/<case>]), {result_bundle} (a fresh result bundle path outside the work dir); {scheme}, {destination}, {only_testing} and {result_bundle} are required. A token cannot be the command (argv[0]) or part of a longer item. |
190
- | `proof.runners[].xcode` | object | | — | |
213
+ | `proof.runners[].xcode` | object | | — | B102: the xcodebuild adapter's values; required for it, refused on any other adapter. Each value is a non-empty string that does not start with -. At most one of workspace and project. |
191
214
  | `proof.runners[].xcode.workspace` | string | | — | minLength 1; The .xcworkspace for {workspace}. At most one of workspace and project; neither = a Swift package in the work dir. |
192
215
  | `proof.runners[].xcode.project` | string | | — | minLength 1; The .xcodeproj for {project}. At most one of workspace and project. |
193
216
  | `proof.runners[].xcode.scheme` | string | yes | — | minLength 1; The scheme for {scheme}. |
194
217
  | `proof.runners[].xcode.destination` | string | yes | — | minLength 1; The destination for {destination}, e.g. platform=iOS Simulator,name=iPhone 17. A simulator destination must name a device xcrun simctl lists as available (by name or id, and OS when given); platform=macOS needs none. code-forge never boots, creates or deletes a simulator. |
195
218
  | `proof.runners[].xcode.test_target` | string | yes | — | minLength 1; The test target (one name, no /) that {only_testing} starts with; every test in the result must sit under <test_target>/<Suite>. |
196
- | `proof.runners[].env` | object | | — | |
219
+ | `proof.runners[].env` | object | | — | Variables added to the runner's environment, NAME: value. code-forge never prints or records an env value; only the names are shown (env_keys). Child output is masked only for values of 8 or more characters. Not a secret store. |
197
220
  | `proof.runners[].timeout_s` | integer | | `600` | min 60; max 7200; Seconds each step (RED, GREEN) may run before it is stopped. |
198
221
  | `budget` | object | | — | |
199
222
  | `budget.block_lines` | integer | | `600` | min 1 |
@@ -221,24 +244,24 @@ Every other key defaults as shown, or is entirely absent from a minimal file. `a
221
244
  | `keys.<key>` | string | | — | user-defined key |
222
245
  | `tmp` | object | | — | |
223
246
  | `tmp.root` | string \| null | | `null` | |
224
- | `context` | object | | — | |
247
+ | `context` | object | | — | 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. |
225
248
  | `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. |
226
249
  | `context.lessons` | string | | — | minLength 1; One lessons-learned file (path relative to the repository root). Cut first when the pack is over max_lines. |
227
- | `context.commands` | object | | — | |
250
+ | `context.commands` | object | | — | The project's commands as argv lists, shown to the coder in the pack. code-forge never runs them. |
228
251
  | `context.commands.build` | array<string> | | — | minItems 1; The build command (argv), shown POSIX shell-quoted in the pack. |
229
252
  | `context.commands.test` | array<string> | | — | minItems 1; The test command (argv), shown POSIX shell-quoted in the pack. |
230
253
  | `context.commands.lint` | array<string> | | — | minItems 1; The lint command (argv), shown POSIX shell-quoted in the pack. |
231
254
  | `context.commands.format` | array<string> | | — | minItems 1; The format command (argv), shown POSIX shell-quoted in the pack. |
232
255
  | `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. |
233
256
  | `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. |
234
- | `hooks` | object | | — | |
235
- | `hooks.on_block_end` | object | | — | |
257
+ | `hooks` | object | | — | 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. |
258
+ | `hooks.on_block_end` | object | | — | 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. |
236
259
  | `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. |
237
260
  | `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). |
238
261
  | `autopilot` | object | | — | |
239
262
  | `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. |
240
- | `coder` | object | | — | |
241
- | `coder.sandbox` | object | | — | |
263
+ | `coder` | object | | — | B93: what the coder session may write beyond the project tree. Read by the Codex (openai) coder only; it has no effect on the Claude (anthropic) and Grok (xai) coders, whose argv does not change. No key means profile workspace and today's coder argv. Authority: fixed at run start; `run reload` refuses a change; `forge spawn --role coder --run <id>` refuses a live value that differs from the run-start snapshot (sandbox-changed). Without --run, spawn reads the live config. |
264
+ | `coder.sandbox` | object | | — | The Codex coder sandbox profile and its extra writable directories. |
242
265
  | `coder.sandbox.profile` | enum: `workspace`, `toolchain`, `full` | | `"workspace"` | workspace (the default; no key gives today's argv): `-s workspace-write`, the project tree is the only writable root. toolchain: the same plus one `--add-dir <abs>` per add_dirs entry (a build cache, a package store). full: `-s danger-full-access`, the coder writes anywhere the user can; like the Claude coder, the forbidden list, the transcript grep and the gates are the only limits, and doctor prints a WARN row. Every profile keeps the $TMPDIR and /tmp exclusions in the argv; Codex ignores them under danger-full-access. |
243
266
  | `coder.sandbox.add_dirs` | array<string> | | `[]` | Extra writable directories for profile toolchain; under workspace or full they are unused and validate warns (rule sandbox-dirs-unused), and a refused entry is still refused. Each entry is absolute or starts with ~/ (expanded against the process HOME) and becomes an absolute realpath: symlinks are followed, even a dangling one, so a symlink is judged by its target. Refused (rule sandbox-dir-refused, by validate and again by the builder; the message names the key path, never the value): /, the home directory itself, and any directory that is, holds or sits inside ~/.code-forge, ~/.codex or $CODEX_HOME, ~/.ssh, ~/.gnupg, ~/.aws, ~/.config, ~/Library/Keychains, the run temp root (so $TMPDIR or /tmp when it holds it), or the session CODEX_HOME; for example ~/Library is refused because it holds ~/Library/Keychains. |
244
267
  | `telemetry` | const `"off"` | | `"off"` | |
@@ -0,0 +1,10 @@
1
+ # Native accelerator
2
+
3
+ `native/` contains the optional Go accelerator and compatibility packages for code-forge.
4
+ The build is pure Go, uses only the standard library, and keeps CGO off (`CGO_ENABLED=0`).
5
+ This also avoids the macOS 26 LC_UUID linker error during local tests.
6
+ Test from the repository root:
7
+
8
+ ```sh
9
+ cd native && CGO_ENABLED=0 go vet ./... && CGO_ENABLED=0 go test ./...
10
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codedology/code-forge",
3
- "version": "0.15.2",
3
+ "version": "0.15.4",
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",
@@ -23,6 +23,7 @@
23
23
  },
24
24
  "files": [
25
25
  "bin",
26
+ "native/bin",
26
27
  "src",
27
28
  "schema",
28
29
  "skill",
@@ -33,6 +34,8 @@
33
34
  "!scripts/changelog.mjs",
34
35
  "!scripts/known-fix.mjs",
35
36
  "!scripts/bench-review.mjs",
37
+ "!scripts/build-native.mjs",
38
+ "!scripts/compat-golden.mjs",
36
39
  "README.md",
37
40
  "CHANGELOG.md",
38
41
  "LICENSE"