@codedology/code-forge 0.5.1 → 0.6.1

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 (54) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/README.md +39 -2
  3. package/docs/README.md +5 -4
  4. package/docs/reference/blocks.json +127 -1
  5. package/docs/reference/config.md +2 -0
  6. package/package.json +1 -1
  7. package/schema/code-forge.schema.json +7 -0
  8. package/skill/SKILL.md +2 -0
  9. package/skill/references/adapters/claude-code.md +14 -0
  10. package/skill/references/autopilot.md +48 -0
  11. package/skill/references/code.md +1 -1
  12. package/skill/references/decisions.md +3 -1
  13. package/skill/references/harden.md +1 -1
  14. package/skill/references/ledger.md +2 -0
  15. package/skill/references/plan.md +6 -4
  16. package/skill/references/review.md +2 -2
  17. package/skill/references/rules-core.md +4 -1
  18. package/skill/scripts/forge +1 -1
  19. package/skill/templates/plan.md +4 -3
  20. package/src/autopilot/actions.mjs +491 -0
  21. package/src/autopilot/binnacle.mjs +494 -0
  22. package/src/autopilot/delegate.mjs +422 -0
  23. package/src/autopilot/grant.mjs +354 -0
  24. package/src/autopilot/limits.mjs +848 -0
  25. package/src/autopilot/scopes.mjs +95 -0
  26. package/src/cli/author.mjs +7 -5
  27. package/src/cli/autopilot.mjs +500 -0
  28. package/src/cli/block.mjs +40 -8
  29. package/src/cli/facts.mjs +7 -6
  30. package/src/cli/jev.mjs +10 -6
  31. package/src/cli/logs.mjs +4 -142
  32. package/src/cli/plan.mjs +18 -7
  33. package/src/cli/run.mjs +20 -61
  34. package/src/cli/s2.mjs +5 -4
  35. package/src/cli/spawn.mjs +5 -4
  36. package/src/config/closed-book.mjs +20 -3
  37. package/src/config/load.mjs +113 -1
  38. package/src/doctor/probes.mjs +34 -11
  39. package/src/engines/builders/claude.mjs +3 -3
  40. package/src/engines/builders/codex.mjs +6 -5
  41. package/src/engines/builders/grok.mjs +3 -3
  42. package/src/engines/builders/validate-params.mjs +3 -3
  43. package/src/ledger/report.mjs +2 -0
  44. package/src/review/fixloop.mjs +69 -5
  45. package/src/review/gate-check.mjs +156 -6
  46. package/src/session/facts.mjs +291 -73
  47. package/src/session/plan-check.mjs +75 -6
  48. package/src/session/spawn.mjs +68 -8
  49. package/src/state/run.mjs +8 -1
  50. package/src/util/breadcrumbs.mjs +1 -0
  51. package/src/util/forbidden.mjs +10 -0
  52. package/src/util/gh.mjs +175 -0
  53. package/src/worker/engine.mjs +8 -4
  54. package/src/worker/loop.mjs +42 -0
package/CHANGELOG.md CHANGED
@@ -6,6 +6,65 @@ waits on a second real consumer (plan §10.4, Q17).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.6.1] — 2026-10-07
10
+
11
+ ### Added
12
+
13
+ - Skill rule R17 "Never relax a rule alone": the orchestrator and any delegate never change, relax or remove a limit,
14
+ threshold, budget, forbidden entry, gate or review requirement without the owner; when a limit blocks work they stop,
15
+ show the data, propose options and wait. The skill also asks to supervise cost continuously (budget.usd, spend after
16
+ every block close).
17
+
18
+ ### Fixed
19
+
20
+ - A verb run from a subfolder uses the project root (the git root, or the nearest regular `.code-forge.yml` below it;
21
+ outside git never above HOME) for its config and ledger slug: `jev`, `plan`, `author`, `facts`, `spawn`, `s2` and
22
+ `run start`. A `.git` at HOME (a dotfiles repo) does not make HOME a project.
23
+ - `plan check` reads the list form of the "cannot back" section that the template shows, still reads the old table
24
+ form, and splits clauses on `;` and on ` · (n)`.
25
+ - `facts`: a flag claim is proved only by `grep -rn -- <flag> <source folder>` (hits in source files under that
26
+ folder; docs, plans, tests, fixtures and the brief never count). The delegate never runs `--help`, `-h`, `--version`,
27
+ a pipe, a list or `rg`; grep options come from a fixed allow-list. No Claude allow rules are added.
28
+ - A command claim written without its CLI (`run reload`) gets the project's own bin only when the brief names that bin
29
+ and the first word is not a well-known CLI.
30
+
31
+ ## [0.6.0] — 2026-10-06
32
+
33
+ ### Added
34
+
35
+ - `code-forge autopilot start|status|stop` (issue #5): a time-boxed grant (at most 24 h) of owner decisions — allow
36
+ `waive:warning`, `waive:nit`, `round:extra`, `model:choose` — with a fixed deny list in code (critical or proof
37
+ waivers, skipping reviews, any limit or rule change incl. parent keys, plan/design approval, merges, destructive
38
+ actions, budget raises). Signed grant, stop and expire rows; expiry is checked at every command. Coders may not run
39
+ it.
40
+ - `code-forge autopilot ask`: a closed-book delegate (never Codex) answers one owner question inside the grant; every
41
+ answer is one signed `autopilot.decision` row. It acts only when the question offers at least 2 options, the answer is
42
+ one of them, it is within scope, does not ask to escalate, and its confidence is at least `autopilot.min_confidence`
43
+ (default 0.7); otherwise the question goes to the owner (exit 3). `autopilot.*` counts as a limit key.
44
+ - `code-forge autopilot waive|round|level`: actions that need the delegate's acted decision (`--decision <id>`, same
45
+ grant, scope and block/file/finding, at most 30 minutes old, with the matching word: `waive`, `allow` or the level).
46
+ Waivers cover warnings and nits only (severity read from the signed review rows) and are written `by: autopilot`; each
47
+ opens one tracked GitHub issue in the project's repo. The block gate accepts an autopilot waiver only under a grant
48
+ and decision that covered it. One extra fix round per file per grant; the coder level comes from the Jev lane, capped
49
+ at the plan level + 1 and L2.
50
+ - Autopilot budget stop per category: with `--budget review=<usd>,coding=<usd>`, new sessions in a category are refused
51
+ once its spend since the grant started plus running reservations reaches `--stop-at` x cap ("autopilot paused: ...
52
+ waiting for the owner"), with one signed `autopilot.pause` row; `autopilot status` shows spend per category.
53
+ `code-forge autopilot approve --key --value --until` (owner only, in a terminal, no `--yes`) changes one setting until
54
+ a time and restores it automatically, crash-safe, with signed rows; a value changed by hand meanwhile is left alone.
55
+ While a grant is active, `run reload` refuses any limit key.
56
+ - `code-forge autopilot binnacle` and `autopilot log`: the logbook of an autopilot run, in the shape of an overnight-run
57
+ page (status at a glance, decisions with why and what would reverse them, blocks, open questions, actions only you can
58
+ take, incidents, timeline newest first) plus a full log of every autopilot event. As JSON or as
59
+ `autopilot-binnacle.md` / `autopilot-log.md` in the run dir, rewritten after each delegate decision and scrubbed.
60
+ `--link <https url>` stores the live page's link.
61
+ - Autopilot log page for your return: the skill (`skill/references/autopilot.md`) creates one Claude Docs doc per run
62
+ with a Binnacle tab (the title and byline, then 7 sections) and a Full log tab, stores its link first, updates only
63
+ the changed sections after each decision, block close or incident, and at stop adds the final rows and asks you to
64
+ review the open questions and your actions. `autopilot status` and `stop` print the link (or the Markdown file paths
65
+ without the Docs connector). Every owner stop in the skill says what autopilot may do there or that it is never
66
+ delegated. New `docs/autopilot.md`.
67
+
9
68
  ## [0.5.1] — 2026-10-04
10
69
 
11
70
  ### Changed
package/README.md CHANGED
@@ -8,6 +8,40 @@ for cross-model, parallel, evidence-gated feature delivery — on Claude Code, C
8
8
  > `examples/` have all landed (`docs/reference/blocks.json` lists every build block). What changed
9
9
  > in each version is in [CHANGELOG.md](CHANGELOG.md).
10
10
 
11
+ ## What's new in 0.6
12
+
13
+ Next release (0.6.1):
14
+
15
+ - **Never relax a rule alone.** A new skill rule (R17): the orchestrator and the delegate never
16
+ change, relax or remove a limit, threshold, budget, gate or review requirement. When a limit
17
+ blocks work they stop, show the data, propose options and wait for you.
18
+ - **Fixes from using code-forge on itself.** A verb run from a subfolder now uses the project's
19
+ ledger and config. `plan check` reads the list form the template shows. `facts` checks a flag
20
+ with `grep -rn -- <flag> <source folder>` instead of `--help`, so the check runs in the
21
+ delegate's sandbox.
22
+
23
+ In 0.6.0:
24
+
25
+ - **Autopilot.** Step away for up to 24 hours. `code-forge autopilot start` grants a delegate (a
26
+ fresh closed-book L2 or L3 session, never Codex) a few of your decisions: waive a warning or a
27
+ nit, one extra fix round past the review cap, the coder level. The orchestrator asks the
28
+ delegate first (`autopilot ask`) and acts only on an answer it acted on. A fixed deny list in
29
+ the code keeps the rest yours: critical and proof waivers, skipping reviews, plan and design
30
+ approval, any limit or rule change, budget raises, merges, destructive actions. Every answer is
31
+ a signed ledger row; each waiver opens a GitHub issue. See [docs/autopilot.md](docs/autopilot.md).
32
+ - **Budget per category.** `--budget review=<usd>,coding=<usd>` pauses a category at `--stop-at`
33
+ (default 0.9) of its cap until you return. `autopilot approve` (only you, at a terminal) changes
34
+ one setting until a time and puts it back by itself. While a grant is active, `run reload`
35
+ refuses any limit key.
36
+ - **A live log for your return.** In Claude Code the orchestrator keeps one Claude Docs page per
37
+ run with two tabs: Binnacle (status at a glance, decisions, blocks, open questions, actions only
38
+ you can take, incidents, timeline) and Full log (every event, newest first). `autopilot status`
39
+ and `autopilot stop` print its link; without the connector they print two Markdown files in the
40
+ run dir (`autopilot binnacle`, `autopilot log`).
41
+ - **From 0.5.x, not listed before:** two files of one block can no longer both take the block's one
42
+ L3 patch rung (0.5.0), and a maintainer can install a tagged release before it is on npm
43
+ ([getting-started](docs/getting-started.md#keeping-it-up-to-date), 0.5.1).
44
+
11
45
  ## What's new in 0.5
12
46
 
13
47
  - **Parallel review.** The worker reviews `review.parallel_tickets` files at once (default 3), with
@@ -131,7 +165,7 @@ usage line.
131
165
  | `validate` | `code-forge validate [--file <path>]` |
132
166
  | `resolve` | `code-forge resolve <L0\|L1\|L2\|L3>` |
133
167
  | `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>` |
134
- | `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) |
168
+ | `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) |
135
169
  | `worker` | `code-forge worker --run <id> [--cwd <dir>] [--poll-ms <n>] [--once]` — started detached by `run start`, never by a coder |
136
170
  | `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 |
137
171
  | `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`) |
@@ -152,6 +186,7 @@ usage line.
152
186
  | `upgrade` | `code-forge upgrade [--source <path>]` |
153
187
  | `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 |
154
188
  | `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` |
189
+ | `autopilot` | `code-forge autopilot start --run <id> --until <ISO-8601 with offset> --delegate <L2\|L3> --allow <scope,…> [--deny <scope,…>] [--budget <category>=<usd>,…] [--stop-at <0..1>] [--yes]` · `autopilot status --run <id> [--json]` · `autopilot stop --run <id>` (both print the live log's link, or the two Markdown files) · `autopilot ask --run <id> --scope <scope> --question <text> [--options a,b,…] [--context-file <path>] [--block <id>] [--file <path>] [--finding <id>] [--json]` · `autopilot waive\|round\|level … --decision <id>` · `autopilot approve --run <id> --key <dot.path> --value <json> --until <time>` (owner only, at a terminal) · `autopilot binnacle --run <id> [--json\|--markdown] [--link <https url>]` · `autopilot log --run <id> [--json\|--markdown]` — the owner's time-boxed grant to a delegate; see [docs/autopilot.md](docs/autopilot.md) |
155
190
  | `help` / `version` | `code-forge --help` (or no verb) · `code-forge --version` |
156
191
 
157
192
  `run`, `block`, `worker`, `spawn`, `s2`, `author`, `facts` and `review-file` are what the Agent
@@ -190,7 +225,8 @@ CLI verbs like any other and every one of them is exercised by this package's ow
190
225
 
191
226
  `schema/code-forge.schema.json` is the source of truth for `.code-forge.yml`.
192
227
  [docs/reference/config.md](docs/reference/config.md) lists every key with its type and default.
193
- Keys added in 0.4 and 0.5 (the last two rows are new in 0.5):
228
+ Keys added in 0.4, 0.5 and 0.6 (`review.parallel_tickets` and `review.provider_concurrency` are
229
+ new in 0.5, `autopilot.min_confidence` in 0.6):
194
230
 
195
231
  | Key | Default | What it does |
196
232
  |---|---|---|
@@ -204,6 +240,7 @@ Keys added in 0.4 and 0.5 (the last two rows are new in 0.5):
204
240
  | `review.session_timeout_s` | `300` | a review session that runs longer is killed and retried once |
205
241
  | `review.parallel_tickets` | `3` | review tickets the worker runs at once (1 to 16); `1` reviews one file at a time |
206
242
  | `review.provider_concurrency` | anthropic `4`, openai `2`, xai `2` | review sessions at once per provider |
243
+ | `autopilot.min_confidence` | `0.7` | the lowest delegate confidence that is acted on; below it the question goes to you |
207
244
 
208
245
  `code-forge block open … --kind code|docs|contract` sets a block's kind when its file endings do
209
246
  not say it. The reference page is generated:
package/docs/README.md CHANGED
@@ -9,16 +9,17 @@ 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, budget, block kind, closed book, known fix… |
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`, try the first commands, and read or report errors (`code-forge logs`) |
12
+ | 2 | [concepts.md](concepts.md) | look up a word: block, level, lane, tier, S1/S2, waiver, ledger, budget, block kind, closed book, plan approval, owner rule, autopilot grant, known fix… |
13
+ | 3 | [how-it-works.md](how-it-works.md) | follow the full workflow from a brief to a closed block (plan approval, parallel review, the escalation ladder, the rules the orchestrator keeps), and see who does what and where state lives |
14
+ | 4 | [getting-started.md](getting-started.md) | install it, answer `init`, install the recommended tools (`code-forge tools`), run `doctor`, set a budget, upgrade, 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
+ | 5b | [autopilot.md](autopilot.md) | step away while a run goes on: grant a delegate a few decisions for a set time, and review the live log (Binnacle and Full log) when you return |
17
18
  | 6 | [examples/marketing-site/brief.md](examples/marketing-site/brief.md) | copy the brief the tutorial uses |
18
19
  | 7 | [marketing.md](marketing.md) | read the product story: the problem, the promise, the honest limits |
19
20
 
20
21
  New to the tool? Read 1 → 3 → 4, then do 5. Keep 2 open as a glossary. Only want reviews? Read 4,
21
- then 5a.
22
+ then 5a. Going away while a run goes on? Read 5b.
22
23
 
23
24
  ## Reference pages (already in the repo)
24
25
 
@@ -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": 46,
7
+ "block_count": 54,
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": {
@@ -606,6 +606,132 @@
606
606
  "B43"
607
607
  ],
608
608
  "note": "root-added 2026-10-03; plan plans/parallel-review (approved in plan mode)"
609
+ },
610
+ "B45": {
611
+ "title": "Autopilot grants: autopilot start/status/stop, scopes with a fixed deny list (incl. parents of limit keys), lazy expiry with signed grant/stop/expire rows; coders may not run autopilot",
612
+ "wave": 11,
613
+ "level": "L2",
614
+ "status": "landed",
615
+ "owned": [
616
+ "src/autopilot/scopes.mjs",
617
+ "src/autopilot/grant.mjs",
618
+ "src/cli/autopilot.mjs",
619
+ "test/cli/autopilot.test.mjs",
620
+ "test/autopilot/grant.test.mjs"
621
+ ],
622
+ "depends_on": [
623
+ "B44"
624
+ ],
625
+ "note": "root-added 2026-10-06; plan plans/autopilot (issue #5), approved in plan mode"
626
+ },
627
+ "B46": {
628
+ "title": "Autopilot delegate: closed-book delegate role (never Codex, opt-in or not), askDelegate (grant first, one session, one signed autopilot.decision row; acts only with options, in scope, no escalation, confidence >= autopilot.min_confidence), autopilot ask, doctor delegate pings; autopilot.* is a limit key",
629
+ "wave": 11,
630
+ "level": "L2",
631
+ "status": "landed",
632
+ "owned": [
633
+ "src/autopilot/delegate.mjs",
634
+ "src/doctor/probes.mjs",
635
+ "test/autopilot/delegate.test.mjs"
636
+ ],
637
+ "depends_on": [
638
+ "B45"
639
+ ],
640
+ "note": "root-added 2026-10-06; plan plans/autopilot (issue #5)"
641
+ },
642
+ "B47": {
643
+ "title": "Autopilot delegated actions, each on the delegate's acted decision with the matching word (waive / allow / the level): autopilot waive (warning or nit read from signed review rows, never critical or proof; tracked gh issue in the project repo, exact-marker dedupe; no gh => waiver stands + autopilot.issue_failed), gate accepts by:autopilot waivers only under a covering grant and decision, autopilot round (one extra fix round per file per grant), autopilot level (lane capped at plan + 1, max L2; block open uses it); re-checked under the run lock before writing",
644
+ "wave": 11,
645
+ "level": "L2",
646
+ "status": "landed",
647
+ "owned": [
648
+ "src/autopilot/actions.mjs",
649
+ "src/review/gate-check.mjs",
650
+ "src/util/gh.mjs",
651
+ "test/autopilot/actions.test.mjs"
652
+ ],
653
+ "depends_on": [
654
+ "B46"
655
+ ],
656
+ "note": "root-added 2026-10-06; plan plans/autopilot (issue #5); also amends src/autopilot/delegate.mjs, src/cli/autopilot.mjs, src/cli/block.mjs, src/cli/logs.mjs, src/review/fixloop.mjs, src/worker/engine.mjs"
657
+ },
658
+ "B48": {
659
+ "title": "Autopilot budget stop per category (review/coding caps at --stop-at, refused at the spawn budget gate under the B41 lock, one signed autopilot.pause row per category per grant, grantFor paused, status spend) and expiring owner approvals (autopilot approve: terminal only, no --yes, fixed keys and __proto__/constructor/prototype refused, applied through the run reload path, signed approve/restore/restore_skipped rows, crash-safe lazy restore at autopilot, block and worker drain); run reload refuses limit keys while a grant is active",
660
+ "wave": 11,
661
+ "level": "L2",
662
+ "status": "landed",
663
+ "owned": [
664
+ "src/autopilot/limits.mjs",
665
+ "src/session/spawn.mjs",
666
+ "src/worker/loop.mjs",
667
+ "src/cli/block.mjs",
668
+ "src/cli/run.mjs",
669
+ "test/autopilot/expiry.test.mjs"
670
+ ],
671
+ "depends_on": [
672
+ "B47"
673
+ ],
674
+ "note": "root-added 2026-10-06; plan plans/autopilot (issue #5); also amends src/autopilot/grant.mjs, src/autopilot/scopes.mjs, src/cli/autopilot.mjs, src/state/run.mjs (reloadRun guard), src/config/load.mjs (slugFor moved from cli/run.mjs), src/cli/{author,facts,jev,plan,s2,spawn}.mjs (slugFor import), test/cli/autopilot.test.mjs"
675
+ },
676
+ "B49a": {
677
+ "title": "Autopilot binnacle and full log: autopilot binnacle builds the owner's 8-section logbook (status at a glance, decisions with why and reversal, blocks, open questions, actions only the owner can take, incidents, timeline) and autopilot log lists every autopilot row, as JSON or Markdown files in the run dir (rewritten after each decision, scrubbed with the workspace as <project>); --link stores the live page link with a signed row",
678
+ "wave": 11,
679
+ "level": "L1",
680
+ "status": "landed",
681
+ "owned": [
682
+ "src/autopilot/binnacle.mjs",
683
+ "test/autopilot/binnacle.test.mjs"
684
+ ],
685
+ "depends_on": [
686
+ "B48"
687
+ ],
688
+ "note": "root-added 2026-10-06; plan plans/autopilot (issue #5); also amends src/autopilot/delegate.mjs (refresh hook), src/cli/autopilot.mjs"
689
+ },
690
+ "B49b": {
691
+ "title": "Autopilot live log artifact and skill text: skill/references/autopilot.md (one Claude Docs doc per run with a Binnacle tab - title and byline, then 7 sections - and a Full log tab; link stored first; only changed sections updated; stop adds the final rows and asks the owner to review), Autopilot notes at every owner stop, the never-delegated list, autopilot status/stop print the link or the Markdown paths, docs/autopilot.md, README, concepts",
692
+ "wave": 11,
693
+ "level": "L2",
694
+ "status": "landed",
695
+ "owned": [
696
+ "skill/references/autopilot.md",
697
+ "docs/autopilot.md",
698
+ "test/skill/structure.test.mjs"
699
+ ],
700
+ "depends_on": [
701
+ "B49a"
702
+ ],
703
+ "note": "root-added 2026-10-06; plan plans/autopilot (issue #5); also amends src/cli/autopilot.mjs, skill/SKILL.md, skill/references/{decisions,review,harden,plan,code}.md, skill/references/adapters/claude-code.md, README.md, docs/README.md, docs/concepts.md, docs/getting-started.md"
704
+ },
705
+ "B50": {
706
+ "title": "Dogfood tool fixes: the project root (git root, or the nearest regular .code-forge.yml below it; outside git never above HOME) sets the config and ledger slug for every verb run from a subfolder; plan check reads the list form and the old table form of the cannot-back section and splits clauses on ';' and ' · (n)'; the facts delegate proves a flag only with grep -rn -- <flag> <source folder> (exact option allow-list, no --help, no pipes, no rg, hits only in source files under the path); a command claim without its CLI gets the project bin only when the brief names it",
707
+ "wave": 11,
708
+ "level": "L2",
709
+ "status": "landed",
710
+ "owned": [
711
+ "src/config/load.mjs",
712
+ "src/session/plan-check.mjs",
713
+ "src/session/facts.mjs",
714
+ "test/session/plan-check.test.mjs",
715
+ "test/session/facts.test.mjs"
716
+ ],
717
+ "depends_on": [
718
+ "B49b"
719
+ ],
720
+ "note": "root-added 2026-10-06; dogfood findings from plans/parallel-review §6.1 and plans/autopilot; no Claude allow rules (rejected: an allow-list of commands is unsafe); also amends src/cli/{author,facts,jev,plan,run,s2,spawn}.mjs, src/autopilot/limits.mjs, skill/references/plan.md, skill/templates/plan.md, docs/how-it-works.md, docs/tutorial-first-test.md"
721
+ },
722
+ "B51": {
723
+ "title": "Skill rule R17 'Never relax a rule alone' and cost supervision; owner rules and docs gaps filled",
724
+ "wave": 11,
725
+ "level": "L1",
726
+ "status": "landed",
727
+ "owned": [
728
+ "skill/references/rules-core.md",
729
+ "test/skill/structure.test.mjs"
730
+ ],
731
+ "depends_on": [
732
+ "B49b"
733
+ ],
734
+ "note": "root-added 2026-10-06; also amends skill/SKILL.md, skill/references/{autopilot,decisions,ledger}.md, README.md, docs/*"
609
735
  }
610
736
  }
611
737
  }
@@ -171,4 +171,6 @@ Every other key defaults as shown, or is entirely absent from a minimal file. `a
171
171
  | `keys.<key>` | string | | — | user-defined key |
172
172
  | `tmp` | object | | — | |
173
173
  | `tmp.root` | string \| null | | `null` | |
174
+ | `autopilot` | object | | — | |
175
+ | `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. |
174
176
  | `telemetry` | const `"off"` | | `"off"` | |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codedology/code-forge",
3
- "version": "0.5.1",
3
+ "version": "0.6.1",
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",
@@ -306,6 +306,13 @@
306
306
  }
307
307
  }
308
308
  },
309
+ "autopilot": {
310
+ "type": "object",
311
+ "additionalProperties": false,
312
+ "properties": {
313
+ "min_confidence": { "type": "number", "minimum": 0, "maximum": 1, "default": 0.7, "description": "B46: the lowest confidence at which the autopilot delegate's answer is acted on; below it the question goes to the owner." }
314
+ }
315
+ },
309
316
  "telemetry": { "type": "string", "const": "off", "default": "off" }
310
317
  },
311
318
  "$defs": {
package/skill/SKILL.md CHANGED
@@ -62,6 +62,7 @@ State lives in the plan file's header — `facts → planned → hardened → co
62
62
  - **R14** No design before facts
63
63
  - **R15** Re-checks shrink: fix hunks only, four rounds, then a patch, then a human
64
64
  - **R16** Proof is exact assertions on the main behaviour
65
+ - **R17** Never relax a rule alone
65
66
 
66
67
  ## §5 Where to read more
67
68
 
@@ -77,4 +78,5 @@ State lives in the plan file's header — `facts → planned → hardened → co
77
78
  | takeover, base moves | `references/continuity.md` |
78
79
  | no Solo, no subagent tool, a level unavailable | `references/degraded.md` |
79
80
  | keys, the forbidden list, signed rows and their limit | `references/security.md` |
81
+ | the owner is away: an autopilot grant, the delegate, the live log doc | `references/autopilot.md` |
80
82
  | spawning coders per engine | `references/adapters/solo.md`, `references/adapters/claude-code.md`, `references/adapters/subprocess.md` |
@@ -21,3 +21,17 @@ The plan goes to the owner through plan mode (`plan.md` §4.1): `EnterPlanMode`
21
21
  ## §3 What is lost and what is not
22
22
 
23
23
  Lost: live observation of the coder (only the progress log and the final report), a resumable transcript (a lost subagent is re-dispatched with the fix history in a new brief — `continuity.md` §2). Not lost: session isolation, signed rows, the gate, the ladder, the facts rule, the convergence rule (`degraded.md` §2).
24
+
25
+ ## §4 Autopilot — the live log doc (Claude Docs connector)
26
+
27
+ At `forge autopilot start` (`autopilot.md` §1) the run gets ONE doc through the Claude Docs connector. Follow the connector's own guide (`guide` → `topic.index`, `topic.tabs`). The steps:
28
+
29
+ 0. **One doc per run:** `forge autopilot status --run <r>` first. A link there ⇒ the doc exists (a lost session left it): continue in it from step 5, never birth a second one.
30
+ 1. **Birth, one `batch`:** a new doc named `<project> autopilot run — <date>`. The main tab **Binnacle** follows `forge autopilot binnacle --run <r> --json`: the title and byline, then 7 sections: Status at a glance, Decisions, Blocks, Open questions, Actions only you can take, Incidents, Timeline — one `pending` block per section. A second tab **Full log** is born in the same batch with one `pending` block for its table. The last member places both: Binnacle `order "a0"`, Full log `order "a1"`.
31
+ 2. **Store the link at once,** from the birth ack: `forge autopilot binnacle --run <r> --link <url>`.
32
+ 3. **Open it** with the Artifact tool's `open` action and that link (no `open` action: give the link in your next message, once).
33
+ 4. **Fill one section per `update`**, in reading order: replace each Binnacle pending block with `## <heading>` and its table or list from the JSON; then the Full log tab's pending block with one pipe table from `forge autopilot log --run <r> --json`, newest first.
34
+ 5. **During the window:** after each decision, block close or incident, one `update` per changed section — insert the new Decisions row, insert the new Timeline row at the top, replace only the Status cells that changed — and insert the new rows at the top of the Full log table. Never resend the whole doc. A refused edit (`guard_mismatch`, `find_none`) means the owner edited it: re-read that section, keep their edit, apply only the new row(s); never `force`.
35
+ 6. **At stop:** after `forge autopilot stop --run <r>`, insert the stop event as the newest Timeline row and the newest Full log row, then replace the Status at a glance section with the final values. The doc stays as the run's record. Tell the owner in chat: the link, and "review Open questions and Actions only you can take before work resumes".
36
+
37
+ No Claude Docs connector: skip the doc. The Markdown files in the run dir are the record, and `status` and `stop` print their paths (`autopilot.md` §4).
@@ -0,0 +1,48 @@
1
+ # Autopilot — the owner steps away; a delegate decides inside a grant; one live doc records it all
2
+
3
+ Autopilot lets the owner leave for a set time. A delegate (a fresh L2 or L3 session) answers some owner questions inside a time-boxed grant. Everything it does is a signed ledger row, and one live doc shows it. You start nothing on your own: autopilot exists only when the owner asks for it.
4
+
5
+ ## §1 Start — only after the owner asks
6
+
7
+ 1. The owner asks for autopilot and names the window, the delegate level and the scopes. Run `forge autopilot start --run <r> --until <ISO-8601 with offset> --delegate <L2|L3> --allow <scope,…> [--deny <scope,…>] [--budget <category>=<usd>,…] [--stop-at <0..1>]`. The owner confirms it at the prompt. Never pass `--yes` unless the owner said so in chat.
8
+ 2. **One doc per run.** First run `forge autopilot status --run <r>`: a link there means the doc exists (a lost session left it) — continue in it, never create a second doc for the same run. No link: create ONE Claude Docs doc titled `<project> autopilot run — <date>` with two tabs, born in one call (the exact steps: `adapters/claude-code.md` §4).
9
+ - **Binnacle** (the main tab): its outline is `forge autopilot binnacle --run <r> --json` — the title and byline, then 7 sections: Status at a glance, Decisions, Blocks, Open questions, Actions only you can take, Incidents, Timeline. One pending block per section.
10
+ - **Full log**: one table from `forge autopilot log --run <r> --json` — Time, Event, Block, File, Detail, Link — newest first. One pending block for it.
11
+ 3. **Store the link first**, right after the birth (its ack gives the link): `forge autopilot binnacle --run <r> --link <url>`. From now on `forge autopilot status` and `forge autopilot stop` print it.
12
+ 4. Open the doc for the owner, then fill one section per call, in reading order: the 7 Binnacle sections, then the Full log table. Never write the whole doc in one call.
13
+
14
+ ## §2 During the window
15
+
16
+ - **Every owner-level question goes to the delegate first**: `forge autopilot ask --run <r> --scope <scope> --question <text> --options <words> [--block <id>] [--file <path>] [--finding <id>]`. Use the fixed option words, or the action will refuse the answer: a waiver `waive,fix` · an extra round `allow,deny` · a coder level the candidate levels, e.g. `L1,L2`.
17
+ - **Act only when it acted** (exit 0): run the action with `--decision <id>` from the answer — `forge autopilot waive|round|level … --decision <id>`. Exit 3 means the question goes to the owner: leave it open, it shows under Open questions. Exit 1 means the grant refused it: also the owner's.
18
+ - **A paused category** (`autopilot paused: … waiting for the owner`): wait. Never raise the cap, never route around it.
19
+ - **Keep the doc current — only what changed.** After every decision, block close or incident: re-read `forge autopilot binnacle --run <r> --json` and `forge autopilot log --run <r> --json`, then
20
+ - append the new row to the Binnacle's Decisions table;
21
+ - put the new row at the top of the Timeline (newest first);
22
+ - update the Status at a glance cells that changed (and Blocks, Open questions, Actions or Incidents when they changed);
23
+ - add the new rows to the Full log tab's table, at the top (it is newest first too).
24
+ Never rewrite the whole doc and never replace a section that did not change.
25
+ - **A refused edit** (`guard_mismatch`, `find_none`) means the owner edited that section: re-read it, keep the owner's edit, and apply only the new row(s). Never force. Answer an owner's comment in its own thread.
26
+ - **A lost session:** the next one reads the link from `forge autopilot status --run <r>` and continues in the same doc.
27
+
28
+ ## §3 Stop — at the owner's return or `until`
29
+
30
+ 1. `forge autopilot stop --run <r>` when the owner is back (at `until` the grant expires by itself; `forge autopilot status --run <r>` then says so).
31
+ 2. Add the stop (or expiry) event as the newest Timeline row and the newest Full log row, then write the final Status at a glance (the window's end, the result, the spend). Leave the doc as the record of the run; do not delete or move it.
32
+ 3. Tell the owner in chat: the link, and "review Open questions and Actions only you can take before work resumes".
33
+
34
+ ## §4 Without the Docs connector
35
+
36
+ Another harness, or the connector is missing: the two Markdown files in the run dir are the record. At start, run `forge autopilot binnacle --run <r> --markdown` once: it creates `autopilot-binnacle.md` and `autopilot-log.md`; print both paths for the owner. After that the CLI rewrites both files by itself, after every delegate decision and at every `forge autopilot status` and `forge autopilot stop`, and those two commands print both paths. At stop, tell the owner the two paths and the same review line as §3.
37
+
38
+ ## §5 Never delegated — always the owner
39
+
40
+ These never go to the delegate, whatever the grant says (the CLI's fixed deny list refuses them too):
41
+
42
+ - plan approval (`plan.md` §4.1) and design approval;
43
+ - a budget raise (`code.md` §6, `review.md` §7);
44
+ - a threshold edit or any other limit or rule change — only the owner's own `forge autopilot approve` at a terminal;
45
+ - a critical waiver, a proof waiver, or closing a block without its reviews;
46
+ - merges and destructive actions.
47
+
48
+ When one of these comes up during the window: stop that line of work, add it to Actions only you can take, and wait. The delegate never relaxes a rule or a limit to keep work moving (`rules-core.md` R17).
@@ -45,7 +45,7 @@ The coder's report is a claim sheet. The gate reproduces each number in the meas
45
45
 
46
46
  ## §6 Budget — the stop is the feature (R6)
47
47
 
48
- `budget.block_lines` and `budget.block_cases` bind the plan; the gate's 1.25 × stop binds the block. A coder that overruns and stops before expanding did the right thing: never compress or defer coverage to fit. One raise per run, by the human, logged; a second overrun in the same run re-decomposes at the architect level. A post-acceptance ruling that adds required lines re-baselines the cap at ruling time, priced from the assertions it orders; a STOP defined mid-turn binds from the coder's next reported boundary. A coder never trims to fit; landing exactly on a cap is a tell that is checked. Review tokens count toward `budget.block_tokens_soft` / `budget.block_tokens_hard` (`review.md` §7).
48
+ `budget.block_lines` and `budget.block_cases` bind the plan; the gate's 1.25 × stop binds the block. A coder that overruns and stops before expanding did the right thing: never compress or defer coverage to fit. One raise per run, by the human, logged; a second overrun in the same run re-decomposes at the architect level. **Autopilot:** a budget raise is never delegated, and neither is `--no-require-reviews` or a merge (`autopilot.md` §5). A post-acceptance ruling that adds required lines re-baselines the cap at ruling time, priced from the assertions it orders; a STOP defined mid-turn binds from the coder's next reported boundary. A coder never trims to fit; landing exactly on a cap is a tell that is checked. Review tokens count toward `budget.block_tokens_soft` / `budget.block_tokens_hard` (`review.md` §7).
49
49
 
50
50
  ## §7 The PR contract — a draft PR only after independent review
51
51
 
@@ -50,4 +50,6 @@ A fresh L3 session receives the failure evidence — the last attempt's diff, th
50
50
 
51
51
  ## §7 What reaches the human
52
52
 
53
- `next = stop`, `review.over_budget`, an orphan file, `worker_down`, `review_cap`, `redecompose`, `ask_human`, a security question, consensus unavailable, and one budget raise per run. Everything else is decided by S1, S2 or the rules above and recorded.
53
+ `next = stop`, `review.over_budget`, an orphan file, `worker_down`, `review_cap`, `redecompose`, `ask_human`, a security question, consensus unavailable, and one budget raise per run. Everything else is decided by S1, S2 or the rules above and recorded. A change to a rule, limit or check is never decided here: it always reaches the owner (`rules-core.md` R17).
54
+
55
+ **Autopilot:** while a grant is active, a warning or nit waiver, one extra round at `review_cap` and a coder level go first to the delegate (`forge autopilot ask`, then `forge autopilot waive|round|level --decision <id>` only when it acted, `autopilot.md` §2). Never delegated: `next = stop`, `ask_human`, a security question, a critical finding, a budget raise, `redecompose` — they wait for the owner (`autopilot.md` §5).
@@ -4,7 +4,7 @@
4
4
 
5
5
  `forge author --job harden --brief <brief> --facts <sheet> --draft <plan> [--answers <file>] --out <plan>` runs a fresh L3 closed-book session over the brief, the facts sheet, the current draft and the answers so far. It returns `{draft, questions: [{id, question, options[], why, blocking}], cost}`.
6
6
 
7
- Loop: write the draft to the plan file → print `questions[]` to the human in chat (and to the Solo scratchpad when present) → collect answers into `<answers file>` → re-run with `--draft` and `--answers`. Stop when **no blocking question is left**: `questions[]` is empty, or every remaining item is non-blocking and the human chose to skip it — a skipped question is written into the plan's Q&A as `skipped` with the human's word, never silently dropped. Each round prints its cost. **The human answers; the orchestrator never answers for them.** Under Solo the human may run harden as an interactive L3 session (`harness.solo.interactive_harden`).
7
+ Loop: write the draft to the plan file → print `questions[]` to the human in chat (and to the Solo scratchpad when present) → collect answers into `<answers file>` → re-run with `--draft` and `--answers`. Stop when **no blocking question is left**: `questions[]` is empty, or every remaining item is non-blocking and the human chose to skip it — a skipped question is written into the plan's Q&A as `skipped` with the human's word, never silently dropped. Each round prints its cost. **The human answers; the orchestrator never answers for them.** **Autopilot:** never delegated — harden questions wait for the owner, grant or not (`autopilot.md` §5). Under Solo the human may run harden as an interactive L3 session (`harness.solo.interactive_harden`).
8
8
 
9
9
  ## §2 What the L3 session interrogates (so you can judge its questions)
10
10
 
@@ -18,6 +18,8 @@ Terminal output in this version. Sections: cost per block — completed and stil
18
18
 
19
19
  Every `session` row carries `usd` (tokens × the static price table) or `usd: null, usd_unknown: true` when the provider/level has no price — never a guess; a `session.background` coder's price is always unknown. Spend code-forge never saw (a cloud or hand-run coder) goes in with `forge ledger add coder --run <r> --block <b> --usd <n> [--note "..."]`, a signed `session` row with `manual: true`. With `budget.usd` set, every session spawn first sums the run's spend: at 80 % one `budget.warning` row and one stderr line per run; at 100 % the session is refused (`budget.usd 20.00 reached (spent 20.13); raise budget.usd or end the run`), a `budget.refused` row is written, nothing is spawned, and a review reports `review.unavailable {reason: budget}`. A budget whose spend cannot be read refuses too (fail closed).
20
20
 
21
+ Watch the cost the whole run, not only at the end: ask the owner for a `budget.usd` before the first block when the project has none, read `spent_usd` / `budget_usd` in `forge run status` after every block close, and record cloud or hand-run coder spend with `forge ledger add coder` as soon as it is known. A budget stop is the owner's call (`rules-core.md` R6, R17).
22
+
21
23
  Sessions that run at the same time (B41): each session first takes its provider slot, then — under one in-process lock per run — reads the spend, checks it and reserves its estimated cost (the packet's bytes / 4 in, plus a fixed tokens_out per role and level, priced with the same table; a session waiting for a slot reserves nothing). A session is refused when spent + reserved by running sessions + its own estimate passes the budget: `budget.usd 20.00 would be exceeded: spent 3.10, reserved by running sessions 15.80, this session about 1.92; it starts when running sessions finish or when budget.usd is raised`, or, when its estimate alone is above the budget, `this session's estimated cost (about 64.00) is above budget.usd 20.00; raise budget.usd`; the `budget.refused` row then carries `reserved_usd` and `estimate_usd`. The reservation is released when the session's row is written, or when the spawn fails or throws. The 80 % warning is checked under the same lock, so it is written once per run.
22
24
 
23
25
  Known limits, accepted: unknown-price sessions and `session.background` coders add **0** to the spent total — record their real cost with `forge ledger add coder --usd <n>`; a background coder's reservation is released at its start row, since its cost is never seen; a session refused because of running sessions' reservations is refused, not queued — run it again once they finish; `forge run status` shows `unknown_usd_sessions` and prints `N sessions have no known price; the budget does not count them` when N > 0. The lock and the reservations live in one process only: two separate code-forge processes starting a session of one run at the same moment can still each pass the check or each write a `budget.warning` row.
@@ -4,8 +4,8 @@
4
4
 
5
5
  Run `forge facts --brief <brief> [--sources <path>...] --out <plans_dir>/<slug>.facts.md` before anything else. What it does:
6
6
 
7
- 1. Deterministic: the CLI extracts every *claim token* from the brief and the sources it names — CLI flags, `<cli> <verb>` commands, paths, versions, endpoints, package names, environment variable names — and writes a claim list.
8
- 2. One L0 session whose cwd is a **read-only snapshot of the repository at HEAD plus the `--sources` files**, built under the run's temp root and removed at session end — so `./path` claims are checkable, while **uncommitted files are not visible unless named as sources** (the snapshot is HEAD, not the working tree) — and whose packet is the claim list and the rule "run the cheapest read-only check, quote the command and the output line, tag VERIFIED / NOT-FOUND / UNVERIFIABLE, never infer". Two lines of defence, exactly as built. **First, at spawn:** the session gets the shell tool only, with the forbidden list plus every write verb rendered as deny rules and the provider's restricted mode (file tools confined to the working directory). There is no pre-execution gate on individual commands beyond those deny rules: the delegate runs what it runs. **Second, after the fact:** the CLI checks every `command` the delegate reports against an allow-list, fail closed, and refuses the whole sheet on the first violation. Allowed forms: `ls cat head tail wc grep rg stat file uname sw_vers jq which` with in-cwd arguments (`grep` never recursive), `find` with only these predicates — `-name -iname -path -ipath -type -maxdepth -mindepth -print -print0 -newer -size -empty -mtime -mmin -not ! -a -o -and -or` — every other predicate refused, `git log|show|status|rev-parse|ls-files|cat-file|--version`, `npm view|ls|--version`, `node --version`, `test -e|-f|-d|-n <one operand>`, `curl` as a HEAD request of exactly one http(s) URL with `-s -S -I -L -f --max-time <N>` only, and `<cli> --help|-h|--version` for a bare CLI name that is not a build runner; an env claim is only `printenv NAME >/dev/null` with its excerpt blanked. Refused anywhere: backticks, `$`, redirection, unquoted globs, `~`, `..`, absolute paths outside `/usr`, `/opt/homebrew`, `/bin`, and secret-looking paths. **`npm view` and `curl -I` reach the network** — they are what makes a package version or an endpoint checkable; nothing else the delegate may run does.
7
+ 1. Deterministic: the CLI extracts every *claim token* from the brief and the sources it names — CLI flags, `<cli> <verb>` commands, paths, versions, endpoints, package names, environment variable names — and writes a claim list. A `<cli> <verb>` written without its CLI (`run reload`) whose first word is not one of the project's CLIs, not a well-known CLI (`git gh npm npx node pnpm yarn bun deno ls cat grep which find test make docker curl claude codex python python3 pip go cargo op`), and that the brief never uses as a CLI of its own (`` `gh` `` or `` `gh --repo x` `` in a code span) — decided from the brief and `package.json` alone, never PATH — becomes `<project CLI> <verb>` (`code-forge run reload`) when the brief itself (not a source) names one of the project's CLIs (its `package.json` `bin` names) as a whole word — `.code-forge.yml` or `code-forge.config` do not count; `plan check` then counts a clause citing `run reload` as backed by that VERIFIED fact, and only when the extra word is one of the project's own CLIs.
8
+ 2. One L0 session whose cwd is a **read-only snapshot of the repository at HEAD plus the `--sources` files**, built under the run's temp root and removed at session end — so `./path` claims are checkable, while **uncommitted files are not visible unless named as sources** (the snapshot is HEAD, not the working tree) — and whose packet is the claim list and the rule "run the cheapest read-only check, quote the command and the output line, tag VERIFIED / NOT-FOUND / UNVERIFIABLE, never infer". Two lines of defence, exactly as built. **First, at spawn:** the session gets the shell tool only, with the forbidden list plus every write verb rendered as deny rules and the provider's restricted mode (file tools confined to the working directory). Nothing is pre-approved: the delegate only runs what the provider already treats as read-only. A flag claim is therefore checked in the project's **source text**, never by running a program: `grep -rn -- <flag> <path>` over a top-level source folder of the snapshot such as `src` (never `.` or the root); it is VERIFIED only when the claim is that flag and the excerpt has a `file:line:text` hit from a source file — not `.md`/`.markdown`/`.txt`, not under `docs/ plans/ test/ tests/ fixtures/ node_modules/`, not the brief, and under the grep's path — holding the flag as a whole token (`--force-push` does not prove `--force`); a NOT-FOUND counts only over one whole top-level source folder that exists as a directory in the snapshot (not hidden, not `dist build coverage out` or the folders above). The grep's path must be an existing source directory of the snapshot (a nested one such as `src/cli` is fine for VERIFIED; a single file never is), and no other command — a plain grep included — ever proves or disproves a flag. Every check is ONE command: no pipe and no list (`| ; && || &`). There is no pre-execution gate on individual commands beyond the deny rules: the delegate runs what it runs. **Second, after the fact:** the CLI checks every `command` the delegate reports against an allow-list, fail closed, and refuses the whole sheet on the first violation. Allowed forms (never `rg`, which searches recursively by default): `ls cat head tail wc grep stat file uname sw_vers jq which` with in-cwd arguments (`grep` options only `-n -c -i -w -F` before `--`, recursive only as the whole command `grep -rn -- <--long-flag> <path>` with one relative subfolder or file, never `.` or a secret path), `find` with only these predicates — `-name -iname -path -ipath -type -maxdepth -mindepth -print -print0 -newer -size -empty -mtime -mmin -not ! -a -o -and -or` — every other predicate refused, `git log|show|status|rev-parse|ls-files|cat-file`, `npm view|ls`, `test -e|-f|-d|-n <one operand>`, and `curl` as a HEAD request of exactly one http(s) URL with `-s -S -I -L -f --max-time <N>` only. No program ever runs with `--help`, `-h` or `--version` (BSD `rm foo --help` deletes `foo`): a version claim is checked with `npm view`, `which` or `ls`; an env claim is only `printenv NAME >/dev/null` with its excerpt blanked. Refused anywhere: backticks, `$`, redirection, unquoted globs, `~`, `..`, absolute paths outside `/usr`, `/opt/homebrew`, `/bin`, and secret-looking paths. **`npm view` and `curl -I` reach the network** — they are what makes a package version or an endpoint checkable; nothing else the delegate may run does.
9
9
  **Path claims:** `./path` claims resolve against the HEAD snapshot, so a committed file is VERIFIED or NOT-FOUND by a plain `ls`/`test -e`; a file that exists only uncommitted in the working tree reads NOT-FOUND unless you passed it with `--sources`. `~/…` claims stay UNVERIFIABLE (`~` is refused), and absolute paths are allowed only under `/usr`, `/opt/homebrew` and `/bin`; a home-directory path that matters to a block is checked by you with one read-only command and recorded in the unbackable list with that command as the tolerance.
10
10
  3. Deterministic again: the answer is validated against the facts schema, rendered with `templates/facts-sheet.md`, and refused when a VERIFIED tag has no output excerpt. Every claim, command and excerpt passes through the redactor. The header records the brief's sha and mtime.
11
11
 
@@ -23,7 +23,7 @@ Run `forge facts --brief <brief> [--sources <path>...] --out <plans_dir>/<slug>.
23
23
  | `owned_files` | exact paths or disjoint globs; a glob never contains `[ ] ( ) ! + @` (an exact path may); pairwise disjoint across blocks that can run together |
24
24
  | `depends_on` | acyclic; a block imports only modules of blocks it depends on — a batch-mate's module is a `next = stop` |
25
25
  | `level` | the S1 `lane` answer recorded for the block — `forge jev ask lane --block <id> --plan <plan> --state <file>`, or `--rules` (the deterministic fallback) when Jev is unavailable; never a block category (`L0` trivial · `L1` a plain feature following a pattern · `L2` money, dates, tenancy, migrations, concurrency · `split` too large or two concerns); **L3 is not a lane** |
26
- | `acceptance` | clauses that each name ≥ 1 test id; a clause citing a flag, path, version or API cites a VERIFIED fact id or sits in the unbackable list with a tolerance naming this block |
26
+ | `acceptance` | clauses that each name ≥ 1 test id, numbered `(1) … · (2) …` or separated by `;` (`plan check` splits the cell on both and checks each clause on its own); a clause citing a flag, path, version or API cites a VERIFIED fact id or sits in the unbackable list with a tolerance naming this block |
27
27
  | `cases`, `lines` | forecasts: `cases = 2 × counted clauses`, `lines = 26 × cases` with a floor of 1 200 for a greenfield block and 500 for an amendment; above `budget.block_cases` or `budget.block_lines` the block is split in the plan, never "split-ready" |
28
28
  | `test_command` | a quoted glob or an explicit file list, never a bare directory |
29
29
  | `commit_message` | house style |
@@ -36,7 +36,7 @@ Every clause of the parent's acceptance maps to exactly one block; an unmapped c
36
36
 
37
37
  ## §4 The deterministic exit — `forge plan check <plan> [--facts <sheet>]`
38
38
 
39
- Prints every failing row by name and exits 1 on any of: an acceptance clause with a claim token that resolves to no VERIFIED fact and no tolerance row (`unbackable clause without tolerance: <block> <clause>`); a missing caller map or a block absent from it; a block without `cases` and `lines`; a forecast above `budget.block_cases` / `budget.block_lines` without a named split; overlapping `owned_files` in one wave; a cyclic `depends_on` or an import of a batch-mate; a glob with `[ ] ( ) ! + @`; more than `caps.coders` concurrent blocks in a slot; `questions[]` still open; the facts sheet's sha absent from the draft; a block whose level has no recorded lane, or differs from it (`block <id>: level L2 differs from the recorded lane L1 (jev)`). A required section under a near-miss heading at its template position (§0.x, §3) passes with a `WARN` naming the exact heading. stdout ends with the lane recorded per block (`lanes: B1 L1 (jev) · B2 L0 (rules)`). Green ⇒ set `status: hardened` after the harden job (`harden.md`), never before.
39
+ Prints every failing row by name and exits 1 on any of: an acceptance clause with a claim token that resolves to no VERIFIED fact and no tolerance line (`unbackable clause without tolerance: <block> <clause>`) — a tolerance line is a list item of §0.x naming the block, the token and the word `tolerance`, as `templates/plan.md` shows (`- B3 \`--flag\` (F4, UNVERIFIABLE) — tolerance: …`); a row of the older table form with a filled tolerance column still counts (`—`, `-`, `…`, `n/a` and the like are empty); a missing caller map or a block absent from it; a block without `cases` and `lines`; a forecast above `budget.block_cases` / `budget.block_lines` without a named split; overlapping `owned_files` in one wave; a cyclic `depends_on` or an import of a batch-mate; a glob with `[ ] ( ) ! + @`; more than `caps.coders` concurrent blocks in a slot; `questions[]` still open; the facts sheet's sha absent from the draft; a block whose level has no recorded lane, or differs from it (`block <id>: level L2 differs from the recorded lane L1 (jev)`). A required section under a near-miss heading at its template position (§0.x, §3) passes with a `WARN` naming the exact heading. stdout ends with the lane recorded per block (`lanes: B1 L1 (jev) · B2 L0 (rules)`). `plan check` and `jev ask` may run from any folder of the project: both read the project root's config and ledger (the nearest `.code-forge.yml` file up to the git top level, else the git top level; outside git, never above the home directory, and only the folder itself when it is not under home), never a subfolder's name. `author`, `facts`, `spawn`, `s2` and `run start` read the same root. Green ⇒ set `status: hardened` after the harden job (`harden.md`), never before.
40
40
 
41
41
  ### §4.1 Owner approval in plan mode
42
42
 
@@ -47,6 +47,8 @@ A plan that passed `forge plan check` still needs the owner's yes before any blo
47
47
 
48
48
  Never treat a chat summary of the plan as the approval step, and never start `forge block open` before it.
49
49
 
50
+ **Autopilot:** plan approval is never delegated. Under a grant the plan waits for the owner (`autopilot.md` §5).
51
+
50
52
  ## §5 Cost
51
53
 
52
54
  One L0 session per plan for the facts (typically a claim list of 40–80 items) and one L3 session per author round; `forge report --slug <slug>` shows both as `facts $` and coder/review splits.
@@ -30,7 +30,7 @@ Per file, per block:
30
30
  3. **Strictly shrinking**: the open `fix_now` set after round n is smaller than after round n−1, counting closures minus new in-hunk findings. Equal or larger ⇒ `review_stall` (rule 2b), a trigger at once.
31
31
  3a. **Heavy rounds climb at once** (`review_warnings`, `decisions.md` §5 rule 3a): below L2, a round — round 1 included — that leaves ≥ `escalation.warning_threshold` (2) warnings or any `critical` open, `escalation.after_rounds_with_warnings` (1) times at the current level, climbs one level for the next fix. One round with a stronger model is faster and cheaper than three with a cheap one. The per-file cap (step 5) stays the outer limit.
32
32
  4. **Ladder**: the running ceiling is `min(L2, escalation.stop_at)` — L3 is never a *running* level. A trigger — rule 1 (`retries`), rule 2 (`escalation.review_rounds_per_level` exhausted with an open `fix_now`), rule 2b (`review_stall`) or step 3a (`review_warnings`) — below the ceiling climbs one level, coded by a fresh session with the open findings and the fix history in its brief (an L1 block's rounds 3–4 run at L2). At the ceiling — an L2 block, or an L1 block that has climbed to L2 — it takes the **L3 rung**: a patch ≤ 80 lines, owned files only, reviewed as a `patch_check` round outside the cap. Under `escalation.l3_mode: code` (opt-in) the rung is instead one L3 coding turn. Either way the rung fires **once per block** and the same post-patch order follows it. After it, in order: `patch_check` clean ⇒ complete; open set not smaller than before the patch ⇒ `stopped: l3_patch_exhausted` (`review_stall`); round ≥ `review.max_rounds_per_file` ⇒ `stopped: review_cap`; otherwise the fixes continue at L2 with `rounds_at_level` reset and the patch in the brief. Any later trigger in that block stops with its named reason (`l3_patch_exhausted`, `review_cap`) and never patches again.
33
- 5. **Cap — four rounds per file** (`review.max_rounds_per_file`, default 4, range 2–6). Round 4 still open ⇒ the L3 rung if the block has not used it (same post-patch order as step 4), else `stopped: review_cap`. A stop means the block cannot close, the ledger lists the open findings, and the human decides — fix by hand, `forge block waive <id> <finding-id> --run <r> --file <path> --reason <text>` (a signed `review.waived` row, `by: human`), or re-decompose. The coder never sees a `waive` verb; it is on the forbidden list.
33
+ 5. **Cap — four rounds per file** (`review.max_rounds_per_file`, default 4, range 2–6). Round 4 still open ⇒ the L3 rung if the block has not used it (same post-patch order as step 4), else `stopped: review_cap`. A stop means the block cannot close, the ledger lists the open findings, and the human decides — fix by hand, `forge block waive <id> <finding-id> --run <r> --file <path> --reason <text>` (a signed `review.waived` row, `by: human`), or re-decompose. The coder never sees a `waive` verb; it is on the forbidden list. **Autopilot:** under a grant, a warning or nit finding may be waived by the delegate (`forge autopilot waive … --decision <id>`, written `by: autopilot`) and a file at the cap may get one extra round (`forge autopilot round`); a critical or proof waiver is never delegated (`autopilot.md`).
34
34
  6. **Block-close sweep**: `forge block close` batches the `late_findings` per file to one L3 judge ruling (`fix_now | nit`); a `fix_now` opens a round on that file at the block's current level and counts toward its cap.
35
35
  7. Ledger: one `review.round` row per round with `open_before`, `closed`, `new_in_hunks`, `late`, `open_after`; `forge report` prints rounds per file and rounds that stalled.
36
36
 
@@ -42,7 +42,7 @@ Two L2 reviewers, one per effective provider (`resolve(L2)` and `review.second_l
42
42
 
43
43
  ## §7 The review budget per block
44
44
 
45
- `review.block_budget_tokens` (and optionally `review.block_budget_usd`) bounds every review, recheck, judge and S2 session made for the block. When the forecast exceeds it the worker degrades one step at a time, cheapest first: batch small files of one tier into one packet (`review.batch_small_files`) → one judge per block instead of per file → standard band `full` → `quick` → high tier `A + B + judge` → one `full` L2 + judge, the floor. Tools-only is never a review; a light `quick` stays `quick`. Still over ⇒ `review.over_budget`: raise the budget once per run (a config edit by the human, on record) or split the block — never silently, never below the floors. The ledger keeps `depth_unconstrained` and `depth_chosen` per file so `forge report` can show `missed_after_degrade`.
45
+ `review.block_budget_tokens` (and optionally `review.block_budget_usd`) bounds every review, recheck, judge and S2 session made for the block. When the forecast exceeds it the worker degrades one step at a time, cheapest first: batch small files of one tier into one packet (`review.batch_small_files`) → one judge per block instead of per file → standard band `full` → `quick` → high tier `A + B + judge` → one `full` L2 + judge, the floor. Tools-only is never a review; a light `quick` stays `quick`. Still over ⇒ `review.over_budget`: raise the budget once per run (a config edit by the human, on record) or split the block — never silently, never below the floors. **Autopilot:** a budget raise is never delegated; a paused category waits for the owner (`autopilot.md` §5). The ledger keeps `depth_unconstrained` and `depth_chosen` per file so `forge report` can show `missed_after_degrade`.
46
46
 
47
47
  ## §7a Parallel tickets
48
48
 
@@ -1,4 +1,4 @@
1
- # Core rules — sixteen, each with its why
1
+ # Core rules — seventeen, each with its why
2
2
 
3
3
  The headings below are the list in `SKILL.md` §4. A rule names levels and roles, never a model. Config keys are the ones `forge validate` accepts.
4
4
 
@@ -50,6 +50,9 @@ Round n ≥ 2 reviews the fix hunks and the open findings; the open set shrinks
50
50
  ### R16 — Proof is exact assertions on the main behaviour
51
51
  One exact test per clause plus a negative or boundary where the clause has a failure mode; red→green for every new test; there is no mutation tier (`proof.md` §1). Why: exhaustive suites hid the defects the isolated review found.
52
52
 
53
+ ### R17 — Never relax a rule alone
54
+ The orchestrator and any delegate never change, relax or remove a rule, limit, permission or check without the owner: config limits, thresholds, budgets, the forbidden list, gates, review requirements, or a local patch that loosens a check. When a limit blocks work: stop, show the data, propose options, wait. Only the owner raises a limit (the budget once per run, R6); during autopilot only the owner's own `forge autopilot approve` (`autopilot.md` §5). Why: a limit relaxed to finish the work protects nothing, and the owner learns of it too late.
55
+
53
56
  ## What survives from the previous pipeline (14 rows)
54
57
 
55
58
  | survivor | lands in | kind |
@@ -10,7 +10,7 @@
10
10
  # copied install with no package next to it); `test/skill/shim.test.mjs` asserts the pin
11
11
  # equals package.json's version.
12
12
  # The shim holds no logic of its own: every verb, flag and message is the CLI's.
13
- PINNED_VERSION="0.5.1"
13
+ PINNED_VERSION="0.6.1"
14
14
  set -eu
15
15
 
16
16
  self="$0"