@codedology/code-forge 0.3.2 → 0.5.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 (72) hide show
  1. package/CHANGELOG.md +85 -0
  2. package/README.md +88 -21
  3. package/bin/code-forge.mjs +6 -1
  4. package/docs/README.md +5 -4
  5. package/docs/reference/blocks.json +236 -1
  6. package/docs/reference/config.md +13 -1
  7. package/package.json +6 -2
  8. package/schema/code-forge.schema.json +26 -4
  9. package/skill/SKILL.md +8 -7
  10. package/skill/references/adapters/claude-code.md +5 -1
  11. package/skill/references/adapters/solo.md +1 -1
  12. package/skill/references/adapters/subprocess.md +1 -1
  13. package/skill/references/code.md +4 -3
  14. package/skill/references/continuity.md +4 -0
  15. package/skill/references/decisions.md +5 -2
  16. package/skill/references/ledger.md +10 -2
  17. package/skill/references/plan.md +12 -3
  18. package/skill/references/review.md +13 -6
  19. package/skill/references/security.md +3 -1
  20. package/skill/scripts/forge +1 -1
  21. package/skill/templates/coder-brief.md +36 -0
  22. package/skill/templates/plan.md +2 -0
  23. package/src/cli/block.mjs +51 -8
  24. package/src/cli/facts.mjs +16 -1
  25. package/src/cli/jev.mjs +114 -17
  26. package/src/cli/ledger.mjs +99 -8
  27. package/src/cli/logs.mjs +193 -58
  28. package/src/cli/plan.mjs +27 -11
  29. package/src/cli/report.mjs +15 -1
  30. package/src/cli/resolve.mjs +16 -1
  31. package/src/cli/review-file.mjs +10 -5
  32. package/src/cli/review.mjs +40 -9
  33. package/src/cli/run.mjs +109 -8
  34. package/src/config/closed-book.mjs +75 -0
  35. package/src/config/validate.mjs +128 -7
  36. package/src/decide/escalation.mjs +112 -11
  37. package/src/decide/questions.mjs +27 -4
  38. package/src/doctor/index.mjs +2 -1
  39. package/src/doctor/local.mjs +15 -0
  40. package/src/doctor/probes.mjs +48 -6
  41. package/src/engines/builders/claude.mjs +4 -1
  42. package/src/engines/builders/codex.mjs +26 -11
  43. package/src/engines/builders/grok.mjs +3 -3
  44. package/src/engines/efforts.mjs +109 -0
  45. package/src/engines/sentinel.mjs +34 -0
  46. package/src/keys/onepassword.mjs +3 -0
  47. package/src/ledger/report.mjs +66 -29
  48. package/src/ledger/spend.mjs +288 -0
  49. package/src/review/engine.mjs +57 -16
  50. package/src/review/fixloop.mjs +29 -6
  51. package/src/review/session-retry.mjs +69 -0
  52. package/src/review/triage.mjs +26 -1
  53. package/src/review/validate-review.mjs +6 -3
  54. package/src/session/author.mjs +6 -2
  55. package/src/session/facts.mjs +218 -53
  56. package/src/session/plan-check.mjs +71 -6
  57. package/src/session/plan-sections.mjs +98 -0
  58. package/src/session/spawn.mjs +373 -20
  59. package/src/state/block.mjs +13 -4
  60. package/src/state/config-snapshot.mjs +150 -0
  61. package/src/state/run.mjs +86 -0
  62. package/src/util/breadcrumbs.mjs +145 -0
  63. package/src/util/error-hint.mjs +3 -2
  64. package/src/util/error-kind.mjs +2 -2
  65. package/src/util/error-log.mjs +109 -18
  66. package/src/util/forbidden.mjs +3 -1
  67. package/src/util/known-fixes.json +1 -0
  68. package/src/util/known-fixes.mjs +153 -0
  69. package/src/util/locks.mjs +104 -0
  70. package/src/worker/engine.mjs +122 -40
  71. package/src/worker/loop.mjs +326 -78
  72. package/src/worker/queue.mjs +68 -7
package/CHANGELOG.md CHANGED
@@ -6,6 +6,91 @@ waits on a second real consumer (plan §10.4, Q17).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.5.0] — 2026-10-04
10
+
11
+ ### Added
12
+
13
+ - Per-provider session limit `review.provider_concurrency` (defaults anthropic 4, openai 2, xai 2) and rate-limit
14
+ backoff: a rate-limited session waits about 2 s, then about 6 s (±30 %), before retrying the same step, instead of
15
+ retrying at once. New in-process keyed locks and semaphores (`src/util/locks.mjs`) prepare parallel review.
16
+ - Parallel review in the worker: `review.parallel_tickets` (1–16, default 3) review tickets run at once, oldest first.
17
+ The same file is never reviewed twice at once; a block's budget row and its one L3 rung are taken under a block lock;
18
+ a crashed ticket never stops the others; on stop, running tickets finish. `run reload` can change the pool size. `1`
19
+ keeps the serial behaviour.
20
+ - `code-forge doctor` shows a `review concurrency` row (tickets at once and provider slots); maintainers can measure the
21
+ pool with `npm run bench:review` (6 fake 2 s reviews: about 12 s at 1, about 4 s at 3).
22
+
23
+ ### Changed
24
+
25
+ - Skill: a plan that passed `plan check` is shown to the owner for approval in the harness's plan mode (Claude Code:
26
+ EnterPlanMode → plan file → ExitPlanMode); no block is dispatched before that approval.
27
+ - `budget.usd` holds when sessions start together: each session reserves its estimated cost under one lock (after taking
28
+ its provider slot) and releases it when its row is written or it fails. A refusal caused by running sessions says how
29
+ much they hold and this session's estimate; a session whose estimate alone is above the budget says so.
30
+ - `code-forge review` enqueues every file first, then waits on all of them together under ONE deadline for the whole run
31
+ (`--max`, default 900 s, now a run deadline, not per file), prints each result on stderr as it finishes, and keeps the
32
+ final table in file order. At most 8 wait children run at once.
33
+
34
+ ### Fixed
35
+
36
+ - `code-forge facts` verified nothing: delegates echo claims as `<kind>: <token>` and every answer was dropped as "no
37
+ answer". Answers now match by that form or by fact id (one answer per claim); a check that breaks the read-only rules,
38
+ or a silent command (`test`, `grep -q`), marks only that claim unverifiable with the reason instead of refusing the
39
+ whole sheet; the `<cli> <subcommand> --help | grep -c -- <flag>` check is allowed only for CLIs the brief names, never
40
+ interpreters or launchers.
41
+ - Two files of one block could both take the block's single L3 patch rung (the rung was recorded only when the patch
42
+ check saved); a pending `next: patch` on a sibling file now counts as used.
43
+
44
+ ## [0.4.0] — 2026-10-02
45
+
46
+ ### Added
47
+
48
+ - `budget.usd` per run: every session row carries an estimated `usd`; at 80% one warning, at 100% no new session starts
49
+ (fail closed if the spend cannot be read). `report` shows running totals for open blocks and per run; `run status`
50
+ shows spent and budget; `ledger add coder --usd` records cloud or manual coder spend.
51
+ - `code-forge run reload --run <r>`: re-read `.code-forge.yml` mid-run without stopping blocks. Queued reviews keep the
52
+ old config, new ones use the new one; keys fixed for the run are refused by name; a signed `run.reload` row lists the
53
+ changed key paths (never values). Coders may not run it.
54
+ - `block close --report <file>`: a coder report without a `reviewed <path> ticket <id>` line for every changed file
55
+ counts as FAILED (new `skill/templates/coder-brief.md`).
56
+ - Error reports: `logs report --with-doctor` adds a cleaned setup check; each logged error lists the names of the last 5
57
+ commands (no flags or values); recovered problems are logged as warnings (1Password retry, review retry, System 1
58
+ fallback, budget at 80%), shown by `logs` and `logs summary`, and included in a report only with `--include-warnings`.
59
+ `CODE_FORGE_NO_ERROR_LOG=1` turns all of it off.
60
+ - Known fixes: `logs report` checks a shipped table of fixed errors and, on an older version, tells you to upgrade
61
+ instead of filing (`--force` files anyway). An `error-triage` GitHub Action labels error reports by fingerprint,
62
+ comments once when the error is already fixed, and writes a weekly count. Maintainers record fixes with `npm run
63
+ known-fix -- add`.
64
+
65
+ ### Changed
66
+
67
+ - Escalation and review cost rules from the first real run: after one review round that leaves 2+ warnings (or any
68
+ critical), the next fix climbs one level (`escalation.*`); docs and contract blocks code at L1 or higher
69
+ (`levels.coder_floor_docs`); risk 0–1 gets a single reviewer (`review.single_reviewer_max_risk`); docs blocks skip
70
+ multimodel review unless `review.multimodel_for_docs` is set.
71
+ - `plan check` refuses a block whose level does not match its recorded lane (`jev ask lane --block <id> [--plan
72
+ <file>]`, or `--rules` without Jev) and prints the lane per block. The plan author gets the exact required headings
73
+ from one shared list; a close heading at the right position is accepted with a WARN. `jev ask` now writes to the
74
+ project's ledger slug (it used to default to `code-forge`).
75
+
76
+ ### Fixed
77
+
78
+ - `validate` rejects an effort the level's provider cannot take (for example `xhigh` on an openai level), for levels and
79
+ fallback entries, instead of failing when the session starts. The builders share one list of allowed efforts, and
80
+ messages never repeat an unknown value.
81
+ - Stuck review sessions: a session that times out is retried once before the review is reported unavailable;
82
+ `review.session_timeout_s` now defaults to 300; the ledger note keeps a cleaned stderr tail and the stdout event types
83
+ (never stdout text) so a hang can be diagnosed; a live worker with a fresh heartbeat is never reported as
84
+ `worker_down`.
85
+ - `ledger tail -n <count>` limits the output (only `--n` worked). Jev score criteria keys must be exactly 0..n, with a
86
+ clear error otherwise.
87
+
88
+ ### Security
89
+
90
+ - Codex (openai) is refused for closed-book roles — reviewer, judge, S2 and plan author — because it always has a shell
91
+ that can read files. `validate`, `resolve` and `doctor` say so. Set `review.allow_open_book_codex: true` to accept
92
+ that risk and keep using it (with warnings). Codex coders are unchanged.
93
+
9
94
  ## [0.3.2] — 2026-10-01
10
95
 
11
96
  ### Fixed
package/README.md CHANGED
@@ -3,10 +3,45 @@
3
3
  A CLI (`code-forge`) plus one Agent Skill that runs the **plan → harden → code → review** pipeline
4
4
  for cross-model, parallel, evidence-gated feature delivery — on Claude Code, Codex, Grok, and Solo.
5
5
 
6
- > **Status:** built block by block from a maintainer-only design plan. The CLI, decision layer,
7
- > review engine, worker, proof policy and installer are landed; the Agent Skill under `skill/` and
8
- > the shipped example repo under `examples/` land in later blocks (see
9
- > `docs/reference/blocks.json` for what has landed and what is still pending).
6
+ > **Status:** published on npm. The CLI, the decision layer, the review engine, the worker, the
7
+ > proof policy, the installer, the Agent Skill under `skill/` and the example repo under
8
+ > `examples/` have all landed (`docs/reference/blocks.json` lists every build block). What changed
9
+ > in each version is in [CHANGELOG.md](CHANGELOG.md).
10
+
11
+ ## What's new
12
+
13
+ - **Parallel review.** The worker reviews `review.parallel_tickets` files at once (default 3), with
14
+ a session limit per provider (`review.provider_concurrency`), backoff on rate limits, and
15
+ `budget.usd` still holding. `code-forge review` now waits on all files under one `--max`
16
+ deadline and prints each result as it finishes. `code-forge doctor` shows the numbers.
17
+
18
+ ## What's new in 0.4
19
+
20
+ - **Cost control.** Set `budget.usd` per run: one warning at 80%, no new session at 100%. Every
21
+ session row carries an estimated `usd`; `report` and `run status` show the spend, and
22
+ `ledger add coder --usd` records spend code-forge did not see.
23
+ - **Cheaper, faster fixes.** One review round that leaves 2 or more warnings climbs the coder one
24
+ level (`escalation.*`). Docs and contract blocks code at L1 or higher and skip multimodel review;
25
+ a file with risk 0–1 gets one reviewer (`review.single_reviewer_max_risk`).
26
+ - **Change the config mid-run.** `code-forge run reload --run <id>` re-reads `.code-forge.yml`
27
+ without stopping open blocks.
28
+ - **Stricter plans.** `plan check` refuses a block whose level is not the lane recorded with
29
+ `jev ask lane --block <id>` (or `--rules` without Jev), and prints the lane of every block.
30
+ `block close --report <file>` fails a coder report that does not name a review ticket per file.
31
+ - **Codex is refused for closed-book roles** (reviewer, judge, System 2, plan author): it always
32
+ has a shell. See [Security model](#security-model).
33
+ - **Steadier reviews.** A timed-out review session is retried once; `review.session_timeout_s`
34
+ defaults to 300; a live worker with a fresh heartbeat is never reported down.
35
+ - **Better error reports.** `logs report --with-doctor` adds a cleaned setup check; errors list the
36
+ last 5 command names; recovered problems are logged as warnings; an error that a newer version
37
+ fixes tells you to upgrade instead of filing it.
38
+ - **Earlier checks.** `validate` refuses an effort the level's provider cannot take (for example
39
+ `xhigh` on an openai level), so nothing fails when the session starts.
40
+
41
+ Fixed in 0.3.1 and 0.3.2: Claude reviewer sessions no longer fail at once on the `--json-schema`
42
+ flag; Grok answers are read from Grok's own answer envelope; Jev `score` questions (such as `risk`)
43
+ no longer fall back to rules; and a clean review of a tiny diff (20 added lines or fewer) needs only
44
+ 40 output tokens, so it is no longer refused as `too_short`.
10
45
 
11
46
  ## What ships
12
47
 
@@ -88,28 +123,28 @@ usage line.
88
123
  | `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) |
89
124
  | `validate` | `code-forge validate [--file <path>]` |
90
125
  | `resolve` | `code-forge resolve <L0\|L1\|L2\|L3>` |
91
- | `run` | `code-forge run start [--cwd <dir>] [--run <id>] [--engine <e>] [--worker-pid <pid>]` · `run start --reattach --run <id>` · `run status --run <id>` · `run end --run <id>` |
92
- | `block` | `code-forge block open <id> --run <r> --level L<n> --owned <paths…> --acceptance <file>` · `block attempt\|rebase\|claim\|close\|stop\|waive …` (§4.9; `waive` is human-only) |
126
+ | `run` | `code-forge run start [--cwd <dir>] [--run <id>] [--engine <e>] [--worker-pid <pid>]` · `run start --reattach --run <id> [--worker-pid <pid>]` · `run status --run <id>` (with spent and budget USD) · `run reload --run <id>` (re-read `.code-forge.yml` mid-run) · `run end --run <id>` |
127
+ | `block` | `code-forge block open <id> --run <r> --level L<n> --owned <paths…> --acceptance <file> [--brief <file>] [--attempt <n>] [--base <sha>] [--lines <n>] [--kind code\|docs\|contract]` · `block attempt\|rebase <id> --run <r>` · `block close <id> --run <r> [--transcript <file>] [--report <file>] [--no-require-reviews]` · `block claim <id> <path> --run <r>` · `block stop <id> --run <r> --reason <text>` · `block waive <id> <finding-id> --run <r> --file <path> --reason <text>` (human-only) |
93
128
  | `worker` | `code-forge worker --run <id> [--cwd <dir>] [--poll-ms <n>] [--once]` — started detached by `run start`, never by a coder |
94
129
  | `review` | `code-forge review [--base <ref>] [--files <path…>] [--acceptance <file> \| --intent "<text>"] [--run <id>] [--max <seconds>] [--json] [--keep-run]` — review only: no coder, no proof, never closes a block |
95
- | `review-file` | `code-forge review-file <path> --block <id> [--run <id>]` (enqueue) · `review-file --wait <ticket> [--max <seconds>s]` (poll) |
96
- | `spawn` | `code-forge spawn --level L<n> --role <role> --brief <file> [--schema <file>] [--cwd <dir>] [--run <id>] [--block <id>] [--timeout <s>] [--background]` |
97
- | `s2` | `code-forge s2 --packet <file.json> [--run <id>] [--block <id>] [--timeout <s>]` |
98
- | `author` | `code-forge author --job plan\|harden --brief <file> [--facts <file>] [--draft <file>] [--answers <file>] [--out <file>] [--run <id>] [--timeout <s>]` |
99
- | `facts` | `code-forge facts --brief <file> [--sources <path>…] [--out <file>] [--run <id>] [--timeout <s>]` |
100
- | `plan` | `code-forge plan check <plan-file> [--facts <sheet>]` |
101
- | `proof` | `code-forge proof tier --file <path> --risk <0-3> [--security] [--cwd <dir>]` · `proof export\|lock\|unlock\|restore <block> --run <r>` |
130
+ | `review-file` | `code-forge review-file <path> --block <id> [--run <id>]` (enqueue) · `review-file --wait <ticket> [--max <seconds>s]` (poll, default 90s; still running at `--max` prints `status: pending, reason: wait_timeout`) |
131
+ | `spawn` | `code-forge spawn --level L0\|L1\|L2\|L3 --role <role> --brief <file> [--schema <file>] [--cwd <dir>] [--run <id>] [--block <id>] [--timeout <seconds>] [--background]` |
132
+ | `s2` | `code-forge s2 --packet <file.json> [--run <id>] [--block <id>] [--timeout <seconds>]` |
133
+ | `author` | `code-forge author --job plan\|harden --brief <file> [--facts <file>] [--draft <file>] [--answers <file>] [--out <file>] [--run <id>] [--timeout <seconds>]` |
134
+ | `facts` | `code-forge facts --brief <file> [--sources <path>…] [--out <file>] [--run <id>] [--timeout <seconds>]` |
135
+ | `plan` | `code-forge plan check <plan-file> [--facts <sheet>] [--slug <slug>]` — every block's level must be the lane recorded by `jev ask lane --block <id>`; prints `lanes: B1 L1 (rules) · …` |
136
+ | `proof` | `code-forge proof tier --file <path> --risk <0-3> [--security] [--cwd <dir>]` · `proof export <block> --run <r> [--remove]` · `proof lock\|unlock\|restore <block> --run <r>` · `proof red-green <block> --run <r> --test <file[::case]> [--mechanism revert\|assertion-deletion]` |
102
137
  | `gates` | `code-forge gates detect\|run\|secret-scan\|safe-edit\|scope\|acceptance\|transcript-grep --cwd <dir> …` |
103
- | `jev` | `code-forge jev ask <question-id> --state <file.json> [--cwd <dir>] [--slug <slug>] [--key-ref <ref>]` |
104
- | `keys` | `code-forge keys list \| set <name> [--op <ref>] \| test <name> [--ref <ref>] \| remove <name>` |
105
- | `ledger` | `code-forge ledger calibration\|outcome\|tail --slug <slug> …` |
106
- | `report` | `code-forge report --slug <slug> [--json] [--export <dir>]` |
138
+ | `jev` | `code-forge jev ask <question-id> --state <file> [--cwd <dir>] [--slug <slug>] [--key-ref <ref>] [--block <id>] [--plan <file>] [--rules]` — `--rules` answers `lane`, `risk` or `security_sensitive` without Jev; writes to the project's ledger slug |
139
+ | `keys` | `code-forge keys list \| set <name> [--op <item-id\|link\|op://ref>] \| test <name> [--ref <ref>] \| remove <name>` |
140
+ | `ledger` | `code-forge ledger tail --slug <slug> [--n\|-n <count>]` · `ledger calibration --slug <slug> [--question <id>]` · `ledger outcome --slug <slug> --pr <n> --ci red\|green\|reverted` · `ledger outcome --slug <slug> --scan-git [--cwd <dir>] [--days <n>]` · `ledger add coder --run <r> --block <b> --usd <n> [--note "..."]` |
141
+ | `report` | `code-forge report --slug <slug> [--json] [--export <dir>]` — 13 sections, then the spend per run (open blocks included) and the total |
107
142
  | `models` | `code-forge models [--refresh --from-cli-caches]` |
108
143
  | `list` | `code-forge list` |
109
144
  | `remove` | `code-forge remove [<harness>] [--scope project\|global]` |
110
145
  | `upgrade` | `code-forge upgrade [--source <path>]` |
111
- | `logs` | `code-forge logs [--last N] [--json]` · `logs summary [--days N] [--json]` · `logs clear [--yes]` · `logs path` — the local error log (`~/.code-forge/logs/errors.jsonl`) |
112
- | `logs report` | `code-forge logs report [--last N] [--kind K] [--verb V] [--note "text"] [--dry-run] [--yes]` — share selected errors as a public GitHub issue; you see the full text first and say yes once |
146
+ | `logs` | `code-forge logs [--last N] [--json]` · `logs summary [--days N] [--json]` · `logs clear [--yes]` · `logs path` — the local error log (`~/.code-forge/logs/errors.jsonl`): errors, warnings, and `[fixed in X.Y.Z]` marks |
147
+ | `logs report` | `code-forge logs report [--last N] [--kind K] [--verb V] [--note "text"] [--include-warnings] [--with-doctor] [--no-ai] [--allow-old] [--force] [--dry-run] [--yes]` — share selected errors as a public GitHub issue; you see the full text first and say yes once. An error a newer version fixes is not filed unless `--force` |
113
148
  | `help` / `version` | `code-forge --help` (or no verb) · `code-forge --version` |
114
149
 
115
150
  `run`, `block`, `worker`, `spawn`, `s2`, `author`, `facts` and `review-file` are what the Agent
@@ -128,6 +163,18 @@ CLI verbs like any other and every one of them is exercised by this package's ow
128
163
  - **A forbidden-command list** stops a coder from starting its own worker, waiving a review, or
129
164
  running other orchestrator-only actions — rendered into each provider's own deny-list mechanism
130
165
  (`--disallowedTools` for Claude, an execpolicy rules file plus prose for Codex).
166
+ - **Closed-book reviews.** Reviewers, judges, System 2 and the plan author see the packet and
167
+ nothing else: Claude runs them with no tools, Grok with every tool denied. Codex (provider
168
+ `openai`) has no mode without a shell, so code-forge refuses it for those roles: `validate`
169
+ warns, `resolve` marks the level `closed_book: "refused"`, `doctor` fails its isolation row, and
170
+ the session is never started. Codex coders are unchanged. `review.allow_open_book_codex: true`
171
+ accepts the risk and keeps Codex in those roles, with a warning on every surface.
172
+ - **The error log stays on your machine.** A failed verb is logged to
173
+ `~/.code-forge/logs/errors.jsonl` with flag names but never flag values, and with home folder,
174
+ project, emails, `op://` references and 1Password item IDs replaced. `logs report` cleans the
175
+ text twice (built-in rules, then an AI check), stops on anything that looks like a key, shows
176
+ you the full issue and sends nothing without your yes. `CODE_FORGE_NO_ERROR_LOG=1` turns the log,
177
+ the warnings and the command list off. Every field is listed in [docs/privacy.md](docs/privacy.md).
131
178
  - **No mutation testing.** Neither this package's own tests nor the product it ships run mutation
132
179
  testing (Ricardo's ruling R14/Q16, 2026-09-25). Proof is plain unit and feature tests with exact
133
180
  assertions, plus a red→green runner that proves a new test fails before the fix and passes after.
@@ -135,7 +182,24 @@ CLI verbs like any other and every one of them is exercised by this package's ow
135
182
  ## Configuration reference
136
183
 
137
184
  `schema/code-forge.schema.json` is the source of truth for `.code-forge.yml`.
138
- `docs/reference/config.md` is generated from it:
185
+ [docs/reference/config.md](docs/reference/config.md) lists every key with its type and default.
186
+ Keys added for 0.4:
187
+
188
+ | Key | Default | What it does |
189
+ |---|---|---|
190
+ | `budget.usd` | none | per-run spend stop in USD: one warning at 80%, no new session at 100% |
191
+ | `escalation.after_rounds_with_warnings` | `1` | rounds with many warnings at one level before the coder climbs (`0` turns it off) |
192
+ | `escalation.warning_threshold` | `2` | how many open warnings make a round "heavy" (any critical counts too) |
193
+ | `levels.coder_floor_docs` | `L1` | the lowest level that codes a docs or contract block |
194
+ | `review.single_reviewer_max_risk` | `1` | at or below this risk a file gets one reviewer and no judge |
195
+ | `review.multimodel_for_docs` | `false` | let docs and contract blocks use multimodel review too |
196
+ | `review.allow_open_book_codex` | `false` | let Codex run reviewer, judge, System 2 and plan author anyway |
197
+ | `review.session_timeout_s` | `300` | a review session that runs longer is killed and retried once |
198
+ | `review.parallel_tickets` | `3` | review tickets the worker runs at once (1 to 16); `1` reviews one file at a time |
199
+ | `review.provider_concurrency` | anthropic `4`, openai `2`, xai `2` | review sessions at once per provider |
200
+
201
+ `code-forge block open … --kind code|docs|contract` sets a block's kind when its file endings do
202
+ not say it. The reference page is generated:
139
203
 
140
204
  ```bash
141
205
  node scripts/gen-config-doc.mjs # regenerate docs/reference/config.md
@@ -154,4 +218,7 @@ Node >= 22 required (ruling R7). No build step — the package ships plain ESM `
154
218
  by Node. `docs/reference/blocks.json` maps each build block to the files it owns and what it
155
219
  depends on, kept in sync with the maintainer's design plan.
156
220
 
157
- Releasing: record changes with `npm run changelog -- <type> "<text>"`, then `npm run release -- <patch|minor|major|x.y.z> [--dry-run]` bumps, commits and tags (it never pushes or publishes). See [docs/releasing.md](docs/releasing.md).
221
+ Releasing: record changes with `npm run changelog -- <type> "<text>"` and fixed error reports with
222
+ `npm run known-fix -- add <fp> <version> "<summary>"`, then `npm run release -- <patch|minor|major|x.y.z> [--dry-run]`
223
+ bumps, commits and tags (it never pushes or publishes). Publish with `npm publish` from your
224
+ machine, then push the tag: CI only creates the GitHub release. See [docs/releasing.md](docs/releasing.md).
@@ -14,7 +14,7 @@ import path from 'node:path';
14
14
  import { fileURLToPath, pathToFileURL } from 'node:url';
15
15
  import { clearErrorKind, reportErrorKind, takeErrorKind } from '../src/util/error-kind.mjs';
16
16
  import { maybeHint } from '../src/util/error-hint.mjs';
17
- import { captureStderr, writeVerbFailure } from '../src/util/error-log.mjs';
17
+ import { captureStderr, noteVerb, writeVerbFailure } from '../src/util/error-log.mjs';
18
18
  import { redact } from '../src/util/redact.mjs';
19
19
 
20
20
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
@@ -98,6 +98,11 @@ export async function run(argv) {
98
98
 
99
99
  const modulePath = path.join(CLI_DIR, `${verb}.mjs`);
100
100
 
101
+ // B37: the verb and its known subcommand word (never a flag or a value) join the last-5 list in
102
+ // `~/.code-forge/logs/breadcrumbs.json`; an error logged below names the commands before it.
103
+ // Best effort, never throws (CODE_FORGE_NO_ERROR_LOG=1 turns it off).
104
+ await noteVerb(verb, rest).catch(() => false);
105
+
101
106
  // B27: a verb that exits non-zero or throws is logged to `~/.code-forge/logs/errors.jsonl`
102
107
  // (scrubbed; never throws; never changes the exit code). The kind a verb reports is cleared
103
108
  // first, and stderr is recorded only while the verb runs (the message is its last text).
package/docs/README.md CHANGED
@@ -9,9 +9,9 @@ disagree, the verb is right: each verb prints its own usage line when its argume
9
9
  | # | Page | Read it when you want to… |
10
10
  |---|---|---|
11
11
  | 1 | [overview.md](overview.md) | know what code-forge is, the problem it solves, who it is for, and what it is not |
12
- | 2 | [concepts.md](concepts.md) | look up a word: block, level, lane, tier, S1/S2, waiver, ledger… |
12
+ | 2 | [concepts.md](concepts.md) | look up a word: block, level, lane, tier, S1/S2, waiver, ledger, budget, block kind, closed book, known fix… |
13
13
  | 3 | [how-it-works.md](how-it-works.md) | follow the full workflow from a brief to a closed block, and see who does what and where state lives |
14
- | 4 | [getting-started.md](getting-started.md) | install it, answer `init`, run `doctor`, and try the first commands |
14
+ | 4 | [getting-started.md](getting-started.md) | install it, answer `init`, run `doctor`, try the first commands, and read or report errors (`code-forge logs`) |
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
  | 6 | [examples/marketing-site/brief.md](examples/marketing-site/brief.md) | copy the brief the tutorial uses |
@@ -24,11 +24,12 @@ then 5a.
24
24
 
25
25
  | Page | What it is |
26
26
  |---|---|
27
+ | [../CHANGELOG.md](../CHANGELOG.md) | what changed in each version; `## [Unreleased]` is the next one |
27
28
  | [reference/config.md](reference/config.md) | every `.code-forge.yml` key, its type and default. Generated from `schema/code-forge.schema.json` |
28
29
  | [reference/blocks.json](reference/blocks.json) | the build blocks of this package and what each one owns |
29
30
  | [marketing/code-forge-story.md](marketing/code-forge-story.md) | the marketing content bank (copy, lessons, what not to claim) |
30
- | [privacy.md](privacy.md) | what the local error log holds, every cleaning rule, the AI check, and how to turn the log off or delete it |
31
- | [releasing.md](releasing.md) | for maintainers: record changes (`npm run changelog`), cut a release with one command (`npm run release`), GitHub Releases, what stays manual |
31
+ | [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 |
32
+ | [releasing.md](releasing.md) | for maintainers: record changes (`npm run changelog`) and known fixes (`npm run known-fix`), cut a release with one command (`npm run release`), publish by hand, GitHub Releases, the error-triage Action |
32
33
  | [`skill/SKILL.md`](../skill/SKILL.md) and [`skill/references/`](../skill/references/) | the Agent Skill the orchestrator follows. The deepest and most exact description of the rules |
33
34
 
34
35
  ## Conventions in these pages
@@ -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": 30,
7
+ "block_count": 46,
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": {
@@ -371,6 +371,241 @@
371
371
  ],
372
372
  "depends_on": ["B27"],
373
373
  "note": "root-added 2026-10-01; not in the v1.3 §10.4 table"
374
+ },
375
+ "B29": {
376
+ "title": "Effort checked per provider: one shared PROVIDER_EFFORTS list for builders and validate (rule 15); messages never echo an unknown value",
377
+ "wave": 7,
378
+ "level": "L2",
379
+ "status": "landed",
380
+ "owned": [
381
+ "src/engines/efforts.mjs",
382
+ "test/engines/efforts.test.mjs"
383
+ ],
384
+ "depends_on": [
385
+ "B4.1"
386
+ ],
387
+ "note": "root-added 2026-10-02 from issue #2 field notes"
388
+ },
389
+ "B30": {
390
+ "title": "Review session hangs: one automatic retry on timeout, default timeout 300 s, cleaned stderr tail and stdout event types in a ledger note, worker heartbeat so --wait never reports worker_down for a live worker",
391
+ "wave": 7,
392
+ "level": "L2",
393
+ "status": "landed",
394
+ "owned": [
395
+ "src/review/session-retry.mjs",
396
+ "test/review/session-retry.test.mjs",
397
+ "test/worker/heartbeat.test.mjs"
398
+ ],
399
+ "depends_on": [
400
+ "B12c"
401
+ ],
402
+ "note": "root-added 2026-10-02 from issue #2 field notes; review cap reached, rulings in the local plan"
403
+ },
404
+ "B31": {
405
+ "title": "ledger tail -n, Jev score criteria keys must be 0..n, test hardening for PRs #3 and #4",
406
+ "wave": 7,
407
+ "level": "L2",
408
+ "status": "landed",
409
+ "owned": [
410
+ "src/decide/questions.mjs",
411
+ "test/decide/questions.test.mjs",
412
+ "test/review/validate-review.test.mjs"
413
+ ],
414
+ "depends_on": [
415
+ "B12a"
416
+ ],
417
+ "note": "root-added 2026-10-02"
418
+ },
419
+ "B32": {
420
+ "title": "Codex is refused for closed-book roles (reviewer, judge, S2, plan author): no proven no-tools mode; opt-in review.allow_open_book_codex with warnings",
421
+ "wave": 8,
422
+ "level": "L2",
423
+ "status": "landed",
424
+ "owned": [
425
+ "src/config/closed-book.mjs",
426
+ "test/config/closed-book.test.mjs"
427
+ ],
428
+ "depends_on": [
429
+ "B4.1"
430
+ ],
431
+ "note": "root-added 2026-10-02 from issue #2"
432
+ },
433
+ "B33": {
434
+ "title": "Session USD on every row, report running totals, budget.usd stop per run (80% warning, fail closed), ledger add coder --usd, run status spend",
435
+ "wave": 8,
436
+ "level": "L2",
437
+ "status": "landed",
438
+ "owned": [
439
+ "src/ledger/spend.mjs",
440
+ "test/ledger/spend.test.mjs",
441
+ "test/session/budget.test.mjs",
442
+ "test/state/budget.test.mjs"
443
+ ],
444
+ "depends_on": [
445
+ "B6"
446
+ ],
447
+ "note": "root-added 2026-10-02 from issue #2"
448
+ },
449
+ "B34": {
450
+ "title": "Escalation after a heavy review round, docs/contract coder floor L1, single reviewer for low risk, no multimodel for docs",
451
+ "wave": 8,
452
+ "level": "L2",
453
+ "status": "landed",
454
+ "owned": [
455
+ "src/decide/escalation.mjs",
456
+ "test/decide/escalation.test.mjs"
457
+ ],
458
+ "depends_on": [
459
+ "B12c"
460
+ ],
461
+ "note": "root-added 2026-10-02 from the owner rules in issue #2"
462
+ },
463
+ "B35": {
464
+ "title": "run reload: re-read config mid-run, pin queued tickets to the old snapshot, refuse fixed keys, signed run.reload row",
465
+ "wave": 8,
466
+ "level": "L2",
467
+ "status": "landed",
468
+ "owned": [
469
+ "src/state/config-snapshot.mjs",
470
+ "test/state/reload.test.mjs",
471
+ "test/worker/config-reload.test.mjs"
472
+ ],
473
+ "depends_on": [
474
+ "B11"
475
+ ],
476
+ "note": "root-added 2026-10-02 from issue #2"
477
+ },
478
+ "B36": {
479
+ "title": "plan check: levels must match a recorded lane (jev or rules, per block and plan); one shared source for required headings with a strict near-miss WARN; coder reports fail closed without review-file ticket ids (block close --report)",
480
+ "wave": 8,
481
+ "level": "L2",
482
+ "status": "landed",
483
+ "owned": [
484
+ "src/session/plan-sections.mjs",
485
+ "test/session/plan-sections.test.mjs",
486
+ "skill/templates/coder-brief.md"
487
+ ],
488
+ "depends_on": [
489
+ "B12c"
490
+ ],
491
+ "note": "root-added 2026-10-02 from issue #2 notes 12-14"
492
+ },
493
+ "B37": {
494
+ "title": "Error reports: --with-doctor setup check, breadcrumbs of the last 5 commands, warning lines for recovered problems (1Password retry, review retry, S1 fallback, budget 80%)",
495
+ "wave": 9,
496
+ "level": "L2",
497
+ "status": "landed",
498
+ "owned": [
499
+ "src/util/breadcrumbs.mjs",
500
+ "test/util/breadcrumbs.test.mjs",
501
+ "test/util/warnings.test.mjs"
502
+ ],
503
+ "depends_on": [
504
+ "B28"
505
+ ],
506
+ "note": "root-added 2026-10-02"
507
+ },
508
+ "B38": {
509
+ "title": "Known fixes table (logs report says upgrade instead of filing; regression note), known-fix maintainer script, error-triage GitHub Action (fp labels, one known-fix comment, weekly counts)",
510
+ "wave": 9,
511
+ "level": "L2",
512
+ "status": "landed",
513
+ "owned": [
514
+ "src/util/known-fixes.mjs",
515
+ "src/util/known-fixes.json",
516
+ "scripts/known-fix.mjs",
517
+ ".github/workflows/error-triage.yml",
518
+ "test/util/known-fixes.test.mjs",
519
+ "test/known-fix.test.mjs",
520
+ "test/error-triage.test.mjs"
521
+ ],
522
+ "depends_on": [
523
+ "B37"
524
+ ],
525
+ "note": "root-added 2026-10-02"
526
+ },
527
+ "B39": {
528
+ "title": "facts: match answers that echo the packet form or by fact_id, one row per claim; a rule-breaking check marks only its claim UNVERIFIABLE; narrow subcommand-help form (brief-named CLIs, no interpreters or launchers); silent commands never VERIFIED",
529
+ "wave": 10,
530
+ "level": "L2",
531
+ "status": "landed",
532
+ "owned": [
533
+ "src/session/facts.mjs",
534
+ "src/cli/facts.mjs",
535
+ "test/session/facts.test.mjs"
536
+ ],
537
+ "depends_on": [
538
+ "B12c"
539
+ ],
540
+ "note": "root-added 2026-10-03; found by running code-forge facts on its own repo"
541
+ },
542
+ "B40": {
543
+ "title": "Keyed locks, per-provider session limit (review.provider_concurrency), rate-limit backoff",
544
+ "wave": 10,
545
+ "level": "L2",
546
+ "status": "landed",
547
+ "owned": [
548
+ "src/util/locks.mjs",
549
+ "test/util/locks.test.mjs",
550
+ "test/session/provider-limit.test.mjs"
551
+ ],
552
+ "depends_on": [
553
+ "B39"
554
+ ],
555
+ "note": "root-added 2026-10-03; plan plans/parallel-review (approved in plan mode)"
556
+ },
557
+ "B41": {
558
+ "title": "Budget reservation under concurrency: slot first, then check + reserve estimated cost under one lock",
559
+ "wave": 10,
560
+ "level": "L2",
561
+ "status": "landed",
562
+ "owned": [
563
+ "test/ledger/spend-concurrency.test.mjs"
564
+ ],
565
+ "depends_on": [
566
+ "B40"
567
+ ],
568
+ "note": "root-added 2026-10-03; plan plans/parallel-review (approved in plan mode)"
569
+ },
570
+ "B42": {
571
+ "title": "Worker ticket pool: review.parallel_tickets, same-file rule, block lock for budget row and L3 rung, sibling patch counts as rung used",
572
+ "wave": 10,
573
+ "level": "L2",
574
+ "status": "landed",
575
+ "owned": [
576
+ "test/worker/pool.test.mjs"
577
+ ],
578
+ "depends_on": [
579
+ "B41"
580
+ ],
581
+ "note": "root-added 2026-10-03; plan plans/parallel-review (approved in plan mode)"
582
+ },
583
+ "B43": {
584
+ "title": "code-forge review enqueues all files, waits together under one deadline, at most 8 waiters",
585
+ "wave": 10,
586
+ "level": "L1",
587
+ "status": "landed",
588
+ "owned": [
589
+ "test/cli/review-parallel.test.mjs"
590
+ ],
591
+ "depends_on": [
592
+ "B42"
593
+ ],
594
+ "note": "root-added 2026-10-03; plan plans/parallel-review (approved in plan mode)"
595
+ },
596
+ "B44": {
597
+ "title": "Parallel review docs, doctor review-concurrency row, bench:review (N=1 vs N=3)",
598
+ "wave": 10,
599
+ "level": "L1",
600
+ "status": "landed",
601
+ "owned": [
602
+ "scripts/bench-review.mjs",
603
+ "test/bench-review.test.mjs"
604
+ ],
605
+ "depends_on": [
606
+ "B43"
607
+ ],
608
+ "note": "root-added 2026-10-03; plan plans/parallel-review (approved in plan mode)"
374
609
  }
375
610
  }
376
611
  }
@@ -53,6 +53,7 @@ Every other key defaults as shown, or is entirely absent from a minimal file. `a
53
53
  | `levels.L3.fallback[].provider` | enum: `anthropic`, `openai`, `xai` | yes | — | |
54
54
  | `levels.L3.fallback[].model` | string | yes | — | minLength 1 |
55
55
  | `levels.L3.fallback[].effort` | string | | — | |
56
+ | `levels.coder_floor_docs` | enum: `L0`, `L1`, `L2` | | `"L1"` | |
56
57
  | `orchestrator` | enum: `L0`, `L1`, `L2`, `L3` | | — | |
57
58
  | `system1` | object | | — | |
58
59
  | `system1.provider` | enum: `anthropic`, `openai`, `xai` | | — | |
@@ -83,8 +84,12 @@ Every other key defaults as shown, or is entirely absent from a minimal file. `a
83
84
  | `escalation.review_rounds_per_level` | integer | | `2` | min 0 |
84
85
  | `escalation.stop_at` | const `"L3"` | | `"L3"` | |
85
86
  | `escalation.l3_mode` | enum: `patch`, `code` | | `"patch"` | |
87
+ | `escalation.after_rounds_with_warnings` | integer | | `1` | min 0 |
88
+ | `escalation.warning_threshold` | integer | | `2` | min 1 |
86
89
  | `review` | object | | — | |
87
90
  | `review.multimodel` | boolean | | `false` | |
91
+ | `review.multimodel_for_docs` | boolean | | `false` | |
92
+ | `review.single_reviewer_max_risk` | number | | `1` | min 0; max 3 |
88
93
  | `review.second_provider` | enum: `anthropic`, `openai`, `xai` \| null | | `null` | |
89
94
  | `review.second_levels` | null \| object | | `null` | |
90
95
  | `review.closed_book` | const `true` | | `true` | |
@@ -102,10 +107,16 @@ Every other key defaults as shown, or is entirely absent from a minimal file. `a
102
107
  | `review.block_budget_usd` | number \| null | | `null` | |
103
108
  | `review.min_tokens_out` | integer | | `120` | min 1 |
104
109
  | `review.batch_small_files` | boolean | | `true` | |
105
- | `review.session_timeout_s` | integer | | `600` | min 1 |
110
+ | `review.session_timeout_s` | integer | | `300` | min 1 |
106
111
  | `review.max_rounds_per_file` | integer | | `4` | min 2; max 6 |
107
112
  | `review.recheck_scope` | enum: `fix_hunks`, `file` | | `"fix_hunks"` | |
108
113
  | `review.late_findings` | enum: `sweep`, `block` | | `"sweep"` | |
114
+ | `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. |
115
+ | `review.provider_concurrency` | object | | — | |
116
+ | `review.provider_concurrency.anthropic` | integer | | `4` | min 1 |
117
+ | `review.provider_concurrency.openai` | integer | | `2` | min 1 |
118
+ | `review.provider_concurrency.xai` | integer | | `2` | min 1 |
119
+ | `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. |
109
120
  | `engine` | enum: `auto`, `solo`, `harness`, `subprocess` | | `"auto"` | |
110
121
  | `harnesses` | array<string> | | — | |
111
122
  | `harness` | object | | — | |
@@ -140,6 +151,7 @@ Every other key defaults as shown, or is entirely absent from a minimal file. `a
140
151
  | `budget.block_tokens_soft` | integer | | — | min 1 |
141
152
  | `budget.block_tokens_hard` | integer | | — | min 1 |
142
153
  | `budget.run_usd_soft` | number | | — | min 0 |
154
+ | `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%. |
143
155
  | `budget.block_cases` | integer | | `80` | min 10 |
144
156
  | `autonomy` | object | | — | |
145
157
  | `autonomy.coder_may_open_draft_pr` | boolean | | `true` | |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codedology/code-forge",
3
- "version": "0.3.2",
3
+ "version": "0.5.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",
@@ -31,6 +31,8 @@
31
31
  "scripts",
32
32
  "!scripts/release.mjs",
33
33
  "!scripts/changelog.mjs",
34
+ "!scripts/known-fix.mjs",
35
+ "!scripts/bench-review.mjs",
34
36
  "README.md",
35
37
  "CHANGELOG.md",
36
38
  "LICENSE"
@@ -39,7 +41,9 @@
39
41
  "test": "CODE_FORGE_NO_LEAK=snapshot node --import ./test/helpers/isolate.mjs --test test/no-leak.test.mjs && node --import ./test/helpers/isolate.mjs --experimental-test-module-mocks --test 'test/**/*.test.mjs' && CODE_FORGE_NO_LEAK=final node --import ./test/helpers/isolate.mjs --test test/no-leak.test.mjs",
40
42
  "typecheck": "tsc --checkJs --noEmit",
41
43
  "release": "node scripts/release.mjs",
42
- "changelog": "node scripts/changelog.mjs"
44
+ "changelog": "node scripts/changelog.mjs",
45
+ "known-fix": "node scripts/known-fix.mjs",
46
+ "bench:review": "node scripts/bench-review.mjs"
43
47
  },
44
48
  "dependencies": {
45
49
  "@clack/prompts": "1.8.1",