forge-orkes 0.44.0 → 0.49.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.
@@ -243,6 +243,33 @@ ID = `DI-{N}` where N = next integer after existing max (or 001 if new file).
243
243
  ### Quick Tier
244
244
  Run all non-advisory verification commands once after commit. 1 retry max.
245
245
 
246
+ ### Declared Checks (plan completion)
247
+
248
+ After the plan's FINAL task commit, extract the `check:` commands from the plan's `must_haves.truths` and run each once. Truths with a `manual:` route have no check to run — skip them (never execute the route text as shell). No declared checks → silent no-op (legacy plans unchanged).
249
+
250
+ - **Failure** → enters the auto-fix loop above with the check's failure output as fix context; retries count toward the existing 3-strike/deferred-issues rules.
251
+ - **This run is an EARLY SIGNAL only** — it never records a verification verdict. `verifying` re-executes every check fresh and applies the executed-evidence rule.
252
+ - WHY here: the check's failure output is cheapest to act on before context handoff — same reasoning as a retry-with-failure-context loop; waiting for verifying costs a full phase round-trip.
253
+
254
+ ### Model-Outcome Log (mandatory, per task — M24)
255
+
256
+ Once a task's verification bookkeeping settles (3-Strike result known, declared-check exit code known), call `.claude/hooks/forge-model-outcome.sh record` **once for that task** — this is a MANDATORY step in the bookkeeping, not an optional "consider logging" note (an optional note is exactly the zero-writer failure the M11 desire-path signal caught). Source every field from state the gate already computed — no new instrumentation:
257
+
258
+ ```
259
+ .claude/hooks/forge-model-outcome.sh record \
260
+ --repo "$(basename "$(git rev-parse --show-toplevel)")" \
261
+ --milestone "m{id}" --skill executing \
262
+ --task-type "{task.task_type}" --complexity "{task.complexity}" \
263
+ --model "{model the task's executor ran on}" \
264
+ --first-try-pass {true|false — passed attempt 1 vs needed a retry} \
265
+ --check-exit {declared check's exit code, or omit if none}
266
+ ```
267
+
268
+ - `model` is the **per-task resolved model** (phase 36 — `by_complexity` → `skills.executing` → `default` → parent), not a skill-wide constant.
269
+ - `task_type` / `complexity` come from the task's own fields (planning Step 6.2); omit either if the task left them unmarked.
270
+ - **One append per task, every task** — this is what closes the loop phase 38's `recommend` reads from.
271
+ - **Advisory** — the helper is itself dependency-free and always exits 0 (unwritable `~/.forge`, etc. are swallowed); a logging failure NEVER blocks or fails the task.
272
+
246
273
  ## Context Engineering
247
274
 
248
275
  | Trigger | Action |
@@ -253,8 +280,11 @@ Run all non-advisory verification commands once after commit. 1 retry max.
253
280
  | Context 40-60% | Consider fresh agent |
254
281
  | Context under 40% | Continue normally |
255
282
  | Switching unrelated subsystems | Spawn fresh agent |
283
+ | Task carries ANY `complexity` tag (COST, M24) | Spawn a fresh executor on its resolved model — even under 40% context. Only an UNTAGGED glue task runs inline on the parent model. |
284
+
285
+ **Spawn via Agent tool** with: relevant plan details, specific files, locked decisions from context.md, clear success criteria. **Resolve the model PER TASK**, not once per skill run: `models.by_complexity.{task.complexity}` → `models.skills.executing` → `models.default` → parent (complexity overrides skill — FORGE.md → Model Routing). Display per spawn: *"Spawning executor with model: {model} (from {source})"*
256
286
 
257
- **Spawn via Agent tool** with: relevant plan details, specific files, locked decisions from context.md, clear success criteria. **Pass `model` param** from `project.yml` model routing (`models.skills.executing` `models.default` parent). Display: *"Spawning executor with model: {model} (from {source})"*
287
+ **Why the tag gates the spawn (enforced subagent spawning).** An inline task inherits the parent session's model and cannot be re-routed — routing only has teeth if every tagged task actually spawns. Spawning carries fixed briefing overhead, which is why the rule keys on the *tag* (a deliberate plan-time signal the work is worth routing) rather than "spawn everything": the cheapest untagged glue work stays inline. A full parent-as-orchestrator model (spawn everything, parent never implements) is a deferred follow-up, to be justified by M24 outcome-log evidence — not implemented here.
258
288
 
259
289
  ## Execution Summary
260
290
 
@@ -122,6 +122,8 @@ Uses only signals that stay current (`current.phase`, `current.status`, the road
122
122
 
123
123
  Beads enabled (`forge.beads_integration: true`) → `bd prime`. Optional.
124
124
 
125
+ Notion adapter (opt-in): `notion.enabled: true` in `project.yml` **and** a Notion MCP server present → log *"Notion adapter active"* and (Phase 2) run `pull_inbox()` (`notion-integration` skill); config without MCP → log *"Notion adapter inactive (MCP absent)"*; neither → silent. When active, `project()` also fires at each phase **handoff** (the State Commit Protocol checkpoint) to push agent-owned state to the board. **Advisory — never blocks boot, handoff, or commit.**
126
+
125
127
  Read `.forge/context.md`. **Needs Resolution** unchecked → warn: *"{N} unresolved discrepancies."* Quick proceeds; Standard/Full blocked at planning.
126
128
 
127
129
  ### 1.1a Live-Worktree Ownership Gate (enforced)
@@ -198,6 +200,15 @@ Downstream skills (researching, discussing, planning, executing, verifying, revi
198
200
 
199
201
  **Surface at 3+.** For each remaining group with 3+ files (declined groups already skipped), **load `desire-paths-review.md` in this skill dir and follow it** — it carries the concrete fix-suggestion map (per `type`), the Apply/decline mechanics (archive to `resolved/` on apply; write a `declined/` file on decline — no re-nag), and the upstream transport for `scope: framework` signals (portable block + optional `gh issue` against `forge.upstream_repo`). Groups under 3 (the common boot) → nothing to surface; skip the load. (Occurrence count is derived from file count — no counter to reset.)
200
202
 
203
+ ### 1.2a Prototype hygiene (parked-rot guard)
204
+
205
+ The Prototyping tier writes an always-present state file (`.forge/prototypes/{slug}.md`), so a `parked` prototype can accumulate silently — the rot risk of a durable artifact. Cheap boot check, advisory, never blocks:
206
+
207
+ - No `.forge/prototypes/` dir → skip (nothing to surface). This is the common boot.
208
+ - Glob `.forge/prototypes/*.md` (NOT `archive/` — graduated/discarded are already terminal and archived). For each, read `status`.
209
+ - Any `status: parked` with `parked` (or `created`) older than **30 days** → one line each: *"Prototype `{slug}` ({title}) parked {N}d — resume (`/forge prototype {slug}`), graduate, or discard? Parked mockups rot."* Surface only; the operator decides. `exploring` prototypes are live work in progress — do not nag them.
210
+ - Nothing stale → silent.
211
+
201
212
  ### 1.3 Interface Check
202
213
 
203
214
  Check `interface` in `project.yml`:
@@ -220,6 +231,7 @@ Before tier detection, check if user request matches stream or lifecycle pattern
220
231
  - "resume milestone {id}" / "undefer {id}" / "reactivate milestone {id}" → **Resume**
221
232
  - "delete milestone {id}" / "archive milestone {id}" / "remove milestone {id}" → **Delete** (see below)
222
233
  - "deferred" / "show deferred" / "deferred items" / "what's deferred" / "deferred work" → **Show Deferred**
234
+ - "prototype {slug}" / "resume prototype {slug}" / "mockup {slug}" / "show prototypes" / "list prototypes" → **Prototype Ops**
223
235
 
224
236
  No match → fall through to Step 2B (tier detection).
225
237
 
@@ -244,6 +256,12 @@ Examples:
244
256
  1. Invoke `Skill(deferred)` directly.
245
257
  2. Do not enter tier detection. Do not prompt for milestone. The deferred skill is read-only and self-contained.
246
258
 
259
+ ### Prototype Ops
260
+
261
+ Prototypes are the disposable in-app mockup tier (`.forge/prototypes/{slug}.md`; see `Skill(prototyping)`).
262
+ - **"prototype {slug}" / "resume prototype {slug}" / "mockup {slug}"** → invoke `Skill(prototyping)` with the slug. It reads the existing state file and continues the iteration loop (or starts a new prototype if the slug is unknown). Do not enter tier detection.
263
+ - **"show prototypes" / "list prototypes"** → read-only: glob `.forge/prototypes/*.md`, list each `{slug} — {title} — {status}` (skip `.forge/prototypes/archive/`). No state writes, no tier detection.
264
+
247
265
  ### Defer Operation
248
266
 
249
267
  1. Validate: milestone in `index.yml`, status not `deferred`
@@ -299,6 +317,12 @@ No `project.yml` → `Skill(initializing)`. Brownfield/greenfield, absorption, s
299
317
 
300
318
  ## Step 2B: Detect Tier
301
319
 
320
+ ### Prototyping
321
+ ANY: "mock up"/"mockup"/"sketch"/"prototype"/"draw up"/"iterate on the UI"/"explore how X could look"/"design iteration", **disposable** in-app visual exploration behind an alpha/feature flag, no requirements locked yet
322
+ → `prototyping`
323
+
324
+ Distinguishing signal: the deliverable is *a thing to look at and react to*, not shippable code. If the operator wants to **see how something might look before deciding to build it**, it's Prototyping — even if it touches many files. Real UI work with known requirements is Standard + `designing` (which gates on quality/verification); Prototyping deliberately skips that gate. When genuinely ambiguous, ask: *"Is this a throwaway mockup to react to, or the real build?"*
325
+
302
326
  ### Quick
303
327
  ANY: single file, typo/formatting, config/env, dep bump, <50 lines, "quick"/"small"/"minor"
304
328
  → `quick-tasking`
@@ -380,6 +404,7 @@ Where `{source}` = `skills.{name}` | `models.default` | `parent session`. Suppre
380
404
  | testing | sonnet | Code gen (author) + audit judgment (analyst) — matches executing/reviewing. M9: author-mode refuses e2e without `e2e:true` + `validated:true`. |
381
405
  | deferred | haiku | Read + format only |
382
406
  | chief-of-staff | sonnet | Cross-stream coordination and conflict judgment |
407
+ | prototyping | sonnet | UI code gen for disposable mockups |
383
408
 
384
409
  | `current.status` | Route To |
385
410
  |-------------------|----------|
@@ -396,6 +421,7 @@ Where `{source}` = `skills.{name}` | `models.default` | `parent session`. Suppre
396
421
  | `quick-tasking` | `Skill(quick-tasking)` |
397
422
  | (keyword: `deferred` / "show deferred") | `Skill(deferred)` — read-only aggregator |
398
423
  | (keyword: `show streams` / `start stream` / `merge safe` / `Chief of Staff`) | `Skill(chief-of-staff)` — project stream coordination |
424
+ | (keyword: `prototype {slug}` / `mock up` / `sketch` — disposable UI exploration) | `Skill(prototyping)` — no verify/review gate |
399
425
 
400
426
  ### Status Advancement Check
401
427
 
@@ -454,9 +480,10 @@ After clear, skills load from disk via "Read Context"/"Pre-Execution Checklist".
454
480
  ## State Transitions
455
481
 
456
482
  ```
457
- Standard: not_started → [init?] → researching → discussing → planning → executing → verifying → reviewing → complete
458
- Full: not_started → [init?] → researching → discussing → architecting → planning → executing → verifying → reviewing → complete
459
- Advisory: [init?] → discussing → [tier gate] → planning → executing → verifying → reviewing → complete
483
+ Standard: not_started → [init?] → researching → discussing → planning → executing → verifying → reviewing → complete
484
+ Full: not_started → [init?] → researching → discussing → architecting → planning → executing → verifying → reviewing → complete
485
+ Advisory: [init?] → discussing → [tier gate] → planning → executing → verifying → reviewing → complete
486
+ Prototyping: [flag] → iterate ⟲ (render → react → adjust) → resolve: graduate→milestone | park | discard (no verify/review gate)
460
487
 
461
488
  Branches (any tier, on-demand): debugging (stuck) · designing (UI) · securing (auth/data/API) · testing (e2e/integration/flake)
462
489
  Phase boundaries: `[clear]` recommended between phases to reset context.
@@ -76,11 +76,11 @@ Resolve current milestone ID from `.forge/state/index.yml` (active milestone) or
76
76
 
77
77
  If missing, create from `.forge/templates/requirements.yml`:
78
78
  1. Extract from user description + research
79
- 2. IDs: FR-001, FR-002... **Globally unique across milestones.** Allocate via the **ID Reservation Protocol** (FORGE.md). **This is a blocking gate, not advisory: do not write an `FR-`/`NFR-`/`DEF-` ID into any artifact until its reservation is appended and committed.** Per ID: next = `max(highest `kind: fr` in `.forge/reservations.yml`, highest FR in `.forge/requirements/*.yml`) + 1; append `{kind: fr, id, milestone, reserved_at, summary}` to `.forge/reservations.yml`, **commit + push**, *then* write the FR. Skipping the reserve step is the exact failure ADR-016 exists to prevent bare scan-and-increment sees only committed state, so two worktrees branched from one baseline claim the same number and collide silently until merge (a multi-file renumber). No `reservations.yml` yet scan as before; the first reservation creates it (lazy migration), but once it exists the gate is mandatory.
79
+ 2. IDs: FR-001, FR-002... **Globally unique across milestones.** Allocate with the `forge-reserve` helper (ADR-021, supersedes ADR-016). **This is a blocking gate, not advisory: do not write an `FR-`/`NFR-`/`DEF-` ID into any artifact until `forge-reserve` has printed it.** Per ID: run `id=$(.claude/hooks/forge-reserve.sh fr --milestone <id> --summary "<one-line>")` and use its stdout, *then* write the FR; `.forge/reservations.yml` commits with the plan (the helper writes it uncommitted — the caller commits). WHY: the id is allocated against a machine-local ledger (`.git/forge/id-reservations`) every sibling worktree shares the moment it is written, so two worktrees branched from one baseline can no longer claim the same number and collide silently until merge (a multi-file renumber) the exact failure ADR-016's "commit + push" could not prevent, because reservations sat on unmerged branches nothing else reads. The helper self-heals a missing ledger (lazy migration).
80
80
  3. Acceptance: Given/When/Then
81
81
  4. Uncertain: `[NEEDS CLARIFICATION]`
82
82
  5. P1 (must) / P2 (should) / P3 (nice)
83
- 6. Deferred: DEF-001..., and NFR-001... — **also globally unique; reserve the same way** (`kind: def` / `kind: nfr`) before writing.
83
+ 6. Deferred: DEF-001..., and NFR-001... — **also globally unique; reserve the same way** via `forge-reserve def` / `forge-reserve nfr` before writing.
84
84
 
85
85
  **E2E gate (M9):** For each functional requirement being added or refined:
86
86
  1. Decide `e2e: true|false` -- does this story need a post-validation e2e test?
@@ -193,6 +193,8 @@ Plan naming reflects the slice: `plan-01-user-signs-up.md`, not `plan-01-models.
193
193
  2. Frontmatter: phase, plan#, wave, deps, `slice_exception:` (optional, see Core Principle), `integration_checkpoint:` (optional, see below)
194
194
  3. must_haves:
195
195
  - **Truths:** User-observable outcomes (3-7). MUST be phrased as something the user can see, click, or receive -- not "model X exists" or "table Y created".
196
+ - Each truth SHOULD carry a `check:` command authored NOW, goal-backward, before any code exists -- a check written before the code cannot be retro-fitted to what got built (semantics in `.forge/templates/plan.md`).
197
+ - A truth that cannot be a command (visual, real-device, external service) carries a `manual:` route instead (human gate / M9 e2e) -- never a fake check.
196
198
  - **Artifacts:** Must exist, substantive not stubs. Slice plans typically span 2-4 layers (e.g., component + handler + repo).
197
199
  - **Key Links:** Connections between artifacts -- these prove the slice is wired, not stubbed.
198
200
  4. XML tasks (2-3/plan, 15-60 min):
@@ -207,6 +209,18 @@ Plan naming reflects the slice: `plan-01-user-signs-up.md`, not `plan-01-models.
207
209
  </task>
208
210
  ```
209
211
 
212
+ #### Complexity + Task Type Assignment (cost-aware routing, M24)
213
+
214
+ At the same goal-backward moment `check:` commands are authored — plan time, before any code exists — assign each task an **optional** `complexity` (`trivial`|`standard`|`complex`) and `task_type` (`code-feature`|`code-fix`|`docs`|`research`|`config`|`test`). Executing resolves these into the spawn model via `models.by_complexity` (FORGE.md → Model Routing); an unmarked task falls back to skill-level routing (FR-131) — zero behavior change if you skip it.
215
+
216
+ Light heuristic, do not over-prescribe:
217
+ - **complexity** — config/doc/mechanical single-file edit -> `trivial`; ordinary feature/fix -> `standard`; new cross-layer behavior, tricky logic, or high blast radius -> `complex`.
218
+ - **task_type** — pick the dominant kind of work the task does.
219
+
220
+ Both fields are **advisory, not a gate**: a genuinely ambiguous task MAY be left unmarked rather than guessed. Do not add a Step 8 lint dimension for this — unlike `check:` truths, routing hints are optional annotations, not verification obligations.
221
+
222
+ **Consulting the promotion ladder (advisory).** While assigning, the planner MAY run `.claude/hooks/forge-model-outcome.sh recommend` and fold any `proven`-bucket line into the assignment as advisory input — e.g. "ladder: haiku proven on docs → a docs task here is a safe `trivial`". This is advisory only: planning NEVER blocks on it, and on the common early case (`recommend` reports insufficient data) assignment proceeds normally on the heuristic above. Do not turn this into a required lookup step.
223
+
210
224
  #### Overlay / Popover must_haves (when a plan adds a floating surface)
211
225
 
212
226
  When a plan introduces an **overlay, popover, sheet, anchored panel, or any floating surface** (especially one anchored to a moving element like a canvas block), specifying its *content* (rows, keys, payload) is **not enough** — the *behavior* questions below only surface when a human touches the running UI, each costing a full build → deploy → UAT round. Add a `must_haves.truths` entry for every item that applies, phrased as a user-observable behavior:
@@ -384,7 +398,7 @@ Decision captured once, pre-code. Does not block planning.
384
398
 
385
399
  ## Step 8: Verify Plans
386
400
 
387
- 9 dimensions:
401
+ 10 dimensions:
388
402
  1. **Requirement Coverage** -- every req has task(s)
389
403
  2. **Task Completeness** -- files + action + verify + done
390
404
  3. **Deps** -- valid DAG, no cycles
@@ -394,6 +408,7 @@ Decision captured once, pre-code. Does not block planning.
394
408
  7. **Context** -- honors locked, excludes deferred
395
409
  8. **Spec Validity** -- valid syntax, correct paths
396
410
  9. **Slice Integrity (HARD GATE)** -- every plan delivers a vertical slice OR declares `slice_exception:`
411
+ 10. **Check Quality** -- truth checks are real, diagnostic, and routed
397
412
 
398
413
  ### Slice Integrity Check
399
414
 
@@ -408,6 +423,17 @@ Roadmap-level check: FAIL if phase 1 has no user-observable goal.
408
423
 
409
424
  On fail: restructure plan(s) into slices, or declare `slice_exception:` with one of `infra_bootstrap | shared_library | data_migration` and a one-line rationale. Re-verify.
410
425
 
426
+ ### Check Quality Check
427
+
428
+ A check that cannot fail manufactures fake evidence -- worse than no check, because verifying will execute it and record a hollow PASS. Lint every truth `check:` before handoff (heuristics ported from Ringer's manifest lint, applied as prose -- no script):
429
+
430
+ - **(a) Cannot-fail check** -- `true`, `:`, `exit 0`, or an echo-only pipeline → **always a failure, no justify path.** Rewrite it so it can catch the truth being false.
431
+ - **(b) Silent-fail check** -- every conjunct is an existence test / `grep -q` / `diff -q` and no `||` branch prints diagnostics → fix or justify in one line. The failure output is what feeds executing's auto-fix loop and the verification report; a silent exit 1 starves both.
432
+ - **(c) Existence-only check on a behavioral truth** -- `test -f` (or equivalent) where the truth claims behavior → fix (run/validate the content) or justify.
433
+ - **(d) Unrouted truth** -- a truth with neither a `check:` nor a named manual route (human gate / M9 e2e) → flag; assign a route. Checks stay optional per truth -- the route does not.
434
+
435
+ On fail: same mechanics as the other dimensions -- fix, re-verify, max 3 cycles. Rule (a) findings must be fixed, never justified.
436
+
411
437
  Issues -> fix, re-verify. Max 3 cycles.
412
438
 
413
439
  ## Step 9: Present
@@ -0,0 +1,88 @@
1
+ ---
2
+ name: prototyping
3
+ description: "Rapid, disposable, in-app design mockups. Iterate UI behind an alpha/feature flag to conceptualize how something might look BEFORE committing to a real build. Trigger: 'mock up', 'sketch a UI', 'quick design iteration', 'prototype this screen', 'explore how X could look', 'draw up options'. Prototyping tier — no verify/review gate."
4
+ model: sonnet
5
+ ---
6
+
7
+ # Prototyping
8
+
9
+ Fast visual exploration inside the real app. You are drawing iterations of how something *might* look, gated behind an alpha/feature flag, so the operator can react to a concrete rendering before any real build is scoped. This is **not** production work.
10
+
11
+ **What makes this its own tier.** Standard/Full ceremony (research → discuss → plan → verify → review, goal-backward verification, a milestone cursor) is the wrong shape — a mockup has no requirements yet (discovering them is the *point*) and no observable truth to verify beyond "does it look like something worth discussing?". Quick tier's <50-line / 1-2-file caps and its commit-and-done finish don't fit a multi-file, multi-iteration loop whose output is deliberately disposable. Prototyping fills that gap.
12
+
13
+ **Three rules that define the tier:**
14
+ 1. **Disposable by default.** Everything you write lives behind a flag and is expected to be thrown away. Do not refactor for production, do not add tests, do not handle every edge case. Make it *look* right, fast.
15
+ 2. **No verify/review gate.** There is no `verifying`, no `reviewing`, no health gate. The gate is the operator's eyes.
16
+ 3. **Iterate, don't finish.** The loop is: render → operator reacts → adjust → render. It ends when the operator graduates, parks, or discards — never at "100% of tasks done".
17
+
18
+ ## Step 1: Establish the prototype
19
+
20
+ Give the exploration a short kebab `{slug}` (e.g. `dashboard-redesign`, `onboarding-v2`).
21
+
22
+ **Check for an existing session.** Glob `.forge/prototypes/{slug}.md`. Exists → this is a **resume**: read it, summarize the iterations so far and the current open question, and continue the loop at Step 4. Absent → new prototype, continue.
23
+
24
+ **Load the design system loosely.** Read `.forge/project.yml → design_system` and `.forge/design-system.md` if present — use real components and tokens so the mockup reads as native to the app. But this is exploration: **it is fine to hand-roll a component the design system doesn't have yet** (that gap may be the very thing you're exploring). The `designing` skill's *strict* "never build custom for what the system provides" rule is relaxed here — prefer the system, don't be blocked by it.
25
+
26
+ ## Step 2: Establish the visibility flag (alpha-toggle pattern)
27
+
28
+ **A prototype must never be reachable in production.** The default mechanism is to render it behind an alpha/feature flag in the real app, so it is visible in-context to whoever is exploring but off for everyone else.
29
+
30
+ Resolve the flag in this order:
31
+ 1. `.forge/project.yml → prototype_flag` — a project convention naming how alpha features gate (e.g. `alpha`, a feature-flag key, an env check, a `?alpha=1` query param, a settings toggle). Use it.
32
+ 2. No convention recorded → **look** at how the codebase already gates alpha/experimental UI (grep for existing flags, `alpha`, `feature`, `experimental`, env-gated routes). Found a pattern → follow it and offer to record it: *"Gate this behind `{pattern}`? I'll note `prototype_flag: {pattern}` in project.yml so future prototypes reuse it."*
33
+ 3. No pattern anywhere → **ask once**: *"How should alpha/mockup UI be gated so it's visible to us but never in prod? (feature flag / env check / query param / settings toggle)"* — then record the answer as `prototype_flag`.
34
+
35
+ Never ship prototype code on an always-on path. If you can't find or agree a gate, STOP and ask — do not guess.
36
+
37
+ ## Step 3: Write the state file
38
+
39
+ Copy `.forge/templates/prototypes/prototype.md` → `.forge/prototypes/{slug}.md` and populate the frontmatter (`slug`, `title`, `created`, `status: exploring`, `flag`, `goal`). This is a **lightweight, resumable** record — the loop's memory across `/clear`, not a plan. Keep it under the 8 KB size gate; it holds the current question and a terse iteration log, not a design doc.
40
+
41
+ ## Step 4: Iterate (the loop)
42
+
43
+ This is where the time goes. Each pass:
44
+
45
+ 1. **Render an iteration.** Build the mockup UI behind the flag, using the design system where it fits. Aim for *fast and legible*, not complete or robust. Static/stub data is fine and expected.
46
+ 2. **Make it visible.** Confirm it renders behind the flag and tell the operator exactly how to see it (route, toggle, `?alpha=1`, whichever the flag is).
47
+ 3. **Log the iteration.** Append one terse line to the state file's `## Iterations` (date, what this pass tried, what to react to). This is what makes a resume cheap.
48
+ 4. **Operator reacts.** They eyeball it and say what to change, what to try instead, or what they've decided. Adjust and loop.
49
+
50
+ **Deviation posture (looser than executing).** Auto-fix anything blocking the render (Rules 1-3) without ceremony — the point is speed. But a **Rule 4 stop still holds**: a new DB table, a real service layer, a schema migration, a dependency install, or anything that outlives the flag is no longer a mockup — STOP and tell the operator this has outgrown prototyping (likely a graduate). Mockups fake their data; they don't build backends.
51
+
52
+ **Commits are optional and disposable.** Commit iterations only if the operator wants checkpoints to compare or revert. Use `chore(proto): {slug} — {iteration note}`. Never present prototype commits as durable work; they ride the flag and leave with it.
53
+
54
+ ## Step 5: Resolve (one of three terminal outcomes)
55
+
56
+ The loop ends when the operator picks an outcome. Each has a clean terminal path so prototypes never rot.
57
+
58
+ ### Graduate → promote to a real milestone
59
+ The mockup proved the idea; now build it for real. This mirrors the refactor-backlog promotion procedure.
60
+
61
+ 1. Synthesize milestone id `m-proto-{slug}` (or a numeric id — scan `.forge/state/milestone-*.yml` for max + 1).
62
+ 2. Copy `.forge/templates/state/milestone.yml` → `.forge/state/milestone-{id}.yml`. Populate: `milestone.id`, `milestone.name: "Graduated from prototype: {title}"`, `milestone.origin: prototype/{slug}`, `current.tier: standard` (or `full` if the operator says so), `current.status: researching`, `current.last_updated: {ISO}`.
63
+ 3. Append to `.forge/roadmap.yml`: one milestone entry + a first phase (`dependencies: []`). Note the prototype as the design reference so `researching`/`designing` can look at the alpha code.
64
+ 4. Run the **State Rollup (forge 1.0)** so `index.yml` picks up the new milestone (do **not** hand-edit `index.yml`).
65
+ 5. In the state file: set `status: graduated`, add `graduated_to: m-{id}`, `resolved: {ISO}`. **Archive** it: move `.forge/prototypes/{slug}.md` → `.forge/prototypes/archive/{slug}.md`.
66
+ 6. **Leave the alpha flag in place** — the graduated milestone builds the real thing and removes/keeps the flag as its own work decides. Say so.
67
+ 7. Confirm: *"Graduated prototype {slug} → milestone m-{id}. State + roadmap created, prototype archived. The alpha mockup stays as the design reference. Routing to researching."* Then fall through to normal Standard/Full routing via `forge`.
68
+
69
+ ### Discard → the idea's out, clean up
70
+ 1. **Remove the alpha-flagged code** you added for this prototype (the render, the flag branch if nothing else uses it, stub data). Grep first; only remove what this prototype introduced — never touch code the flag gated before you arrived.
71
+ 2. Commit the removal: `chore(proto): discard {slug} mockup`.
72
+ 3. In the state file: set `status: discarded`, add `resolved: {ISO}`, a one-line `discard_reason`. **Archive** it: move → `.forge/prototypes/archive/{slug}.md` (keeps the audit trail — what we tried and why we dropped it).
73
+ 4. Confirm what was removed and that the file is archived.
74
+
75
+ ### Park → sit on it for later
76
+ Keep the mockup live behind the flag; you're not ready to decide.
77
+ 1. In the state file: set `status: parked`, add `parked: {ISO}` and a one-line note on *what would unpark it* (a decision, a dependency, a conversation).
78
+ 2. Leave the file in `.forge/prototypes/` (not archived — it's still live). Leave the alpha code in place behind the flag.
79
+ 3. Confirm: *"Parked prototype {slug} behind `{flag}`. Resume anytime with `/forge prototype {slug}`."*
80
+
81
+ **Parked prototypes are the rot risk your always-a-state-file choice creates.** `forge` boot surfaces parked prototypes older than a threshold so they don't accumulate silently (see forge Step 1.5 → Prototype hygiene). A park is a deferral, not a hiding place.
82
+
83
+ ## What this skill does NOT do
84
+
85
+ - **No goal-backward verification.** There's nothing to prove works. If you find yourself wanting `verifying`, the work has graduated — promote it.
86
+ - **No production quality bar.** No test coverage, no a11y gate, no security/architecture review. (Basic keyboard/focus sanity is fine if cheap, but it is not gated.)
87
+ - **No unit tests, no real data layer, no migrations.** Mockups stub their data. Any of these = a Rule 4 stop → graduate.
88
+ - **No `git add .`** — stage individually, as everywhere in Forge.
@@ -126,17 +126,22 @@ Milestones with zero `e2e:true` stories never see this gate. Verifying logs noth
126
126
 
127
127
  ### Level 1: Observable Truths
128
128
 
129
- Read the plan's `must_haves.truths`. For each truth:
129
+ Read the plan's `must_haves.truths`. Truths may be legacy bare strings or `{truth, check}` / `{truth, manual}` objects (see `.forge/templates/plan.md`). For each truth, one of three branches — **this is the canonical statement of the executed-evidence rule** (verifier.md and FORGE.md cite it):
130
130
 
131
- 1. Design a test that proves/disproves it
132
- 2. Run the test
133
- 3. Record: **VERIFIED** | **FAILED** | **UNCERTAIN**
131
+ 1. **Declared `check:`** (and no `manual:` route) → EXECUTE it. Exit 0 → **VERIFIED**; non-zero → **FAILED**. Record the command, exit code, and an output excerpt either way.
132
+ 2. **No declared check** (legacy plan, or a truth planning left judgment-shaped) → run your OWN command that proves/disproves it and record the same evidence triple — executed evidence qualifies regardless of schema vintage.
133
+ 3. **No executed evidence** the verdict is **UNCERTAIN** or **HUMAN VERIFICATION NEEDED**. **Never VERIFIED.**
134
+
135
+ WHY the hard line: a verdict without executed evidence is the hallucination surface this rule closes — an agent designing and grading its own test can report VERIFIED without running anything. The exit code, not judgment, is the verdict.
136
+
137
+ Manual-route truths (`manual:` field, or inherently uncommandable — visual, real-device, external service) are branch 3 by design: never execute their route text as shell, never invent a proxy command — list them under HUMAN VERIFICATION NEEDED; they flow to the existing Human Verification Gate / M9 e2e gate unchanged.
134
138
 
135
139
  ```markdown
136
- | Truth | Test | Result |
137
- |-------|------|--------|
138
- | User can edit their bio | Click edit, type, save, check persistence | VERIFIED |
139
- | Profile photo uploads correctly | Upload image, check display | FAILED — 413 on large files |
140
+ | Truth | Evidence (command → exit) | Result |
141
+ |-------|---------------------------|--------|
142
+ | User can edit their bio | `npx playwright test bio-edit.spec` 0 | VERIFIED |
143
+ | Profile photo uploads correctly | `npm test -- upload.spec` → 1; "413 Payload Too Large at 6MB" | FAILED — 413 on large files |
144
+ | Layout matches design on mobile | (manual: human gate — not executed) | HUMAN VERIFICATION NEEDED |
140
145
  ```
141
146
 
142
147
  ### Level 2: Artifacts (Exists → Substantive → Wired)
@@ -69,6 +69,11 @@ Worktrees are a **base concern** — Chief/Streams creates and references them w
69
69
 
70
70
  Auto-detects complexity. Override: "Use Quick/Standard/Full tier."
71
71
 
72
+ ### Prototyping (minutes–hours, disposable)
73
+ **Triggers:** "mock up"/"sketch"/"prototype"/"draw up"/"iterate on the UI"/"explore how X could look" — disposable in-app visual exploration behind an alpha/feature flag, no requirements locked yet
74
+ **Flow:** `prototyping` — establish flag → iterate ⟲ (render → react → adjust) → resolve (graduate→milestone | park | discard)
75
+ **No verify/review gate by design.** The deliverable is a thing to look at and react to, not shippable code. Fills the gap where Standard/Full ceremony (goal-backward verification, a milestone cursor) is the wrong shape and Quick's <50-line/1-2-file caps + commit-and-done don't fit an iteration loop. Real UI work with known requirements is Standard + `designing` (which *does* gate on quality). See [ADR-022](../docs/decisions/ADR-022-prototyping-tier.md).
76
+
72
77
  ### Quick (minutes)
73
78
  **Triggers:** bug fix, typo, config, dep bump, <50 lines
74
79
  **Flow:** `quick-tasking` → commit → done
@@ -100,12 +105,14 @@ Auto-detects complexity. Override: "Use Quick/Standard/Full tier."
100
105
  | Prove work delivers on goals (+ M9 e2e validation gate when `e2e:true` stories present; captures the Human Verification Gate sign-off) | `verifying` | Standard, Full |
101
106
  | Audit health + catalog refactoring (+ M9 e2e soft-cap, orphan-test, flake-rate audits; hard-blocks completion on the Human Verification Gate) | `reviewing` | Standard, Full |
102
107
  | Small scoped fix | `quick-tasking` | Quick |
108
+ | Rapid disposable UI mockup behind an alpha flag (no verify/review gate) | `prototyping` | Prototyping |
103
109
  | UI with design system | `designing` | When UI |
104
110
  | Security review | `securing` | When auth/data/API |
105
111
  | E2E/integration tests + suite audit (+ M9 author-mode gate refuses e2e without `e2e:true` + `validated:true`) | `testing` | When UI/flows or flaky suite |
106
112
  | Systematic debugging | `debugging` | When stuck |
107
113
  | Upgrade Forge files | `upgrading` | On-demand |
108
114
  | Cross-session memory | `beads-integration` | When Beads installed |
115
+ | Project/read Forge work in Notion | `notion-integration` | When Notion configured |
109
116
  | Multi-agent backend (experimental) | `orchestrating` | Optional, usually selected by Chief/Streams |
110
117
 
111
118
  > Experimental skills require opt-in install — see `packages/create-forge/experimental/m10/README.md`.
@@ -126,6 +133,7 @@ Auto-detects complexity. Override: "Use Quick/Standard/Full tier."
126
133
  | `streams/{stream}/brief.md` | 8 KB | Stream context packet must fit into active session context |
127
134
  | `streams/{stream}/packages/{id}.yml` | 10 KB | Delegated work contract stays bounded |
128
135
  | `reservations.yml` | 50 KB | Append-only ID-reservation registry; one short line per ADR/DEF/FR/NFR reserved |
136
+ | `prototypes/{slug}.md` | 8 KB | Disposable-mockup state — current question + terse iteration log, not a design doc; terminal items archive to `prototypes/archive/` |
129
137
 
130
138
  ### Accumulating Artifacts
131
139
 
@@ -136,8 +144,13 @@ Every new accumulating artifact needs: owner, size gate, archive/compaction rule
136
144
  source-of-truth; load scoped slices, not whole history.
137
145
  - `phases/`, `audits/`, `research/`, `archive/`, and `context-archive.md` are
138
146
  historical; load only by milestone/stream/package/version.
139
- - Runtime/generated files (`.mcp-server/*.db*`, logs, spike DBs) are excluded
140
- from framework context and packages unless debugging them.
147
+ - `prototypes/{slug}.md` is single-owner mutable while `status: exploring | parked`
148
+ (the driving session writes it); terminal statuses (`graduated`, `discarded`)
149
+ archive to `prototypes/archive/`. `forge` boot surfaces stale `parked` prototypes
150
+ (>30d). Not derived, not shared — a prototype has exactly one driver.
151
+ - Runtime/generated files (`.mcp-server/*.db*`, logs, spike DBs, and the
152
+ `.git/forge/*` machine-local files — the id-reservation ledger + integration
153
+ flag) are excluded from framework context and packages unless debugging them.
141
154
  - Template copies sync mechanically; do not treat them as separate concepts.
142
155
 
143
156
  ### Fresh Agent Pattern
@@ -167,6 +180,10 @@ Configure in `project.yml`:
167
180
  models:
168
181
  default: sonnet # fallback
169
182
  parent_session: sonnet # advisory — warns on mismatch
183
+ by_complexity: # per-task complexity → model (M24). Overrides skills — see Precedence.
184
+ trivial: haiku
185
+ standard: sonnet
186
+ complex: opus
170
187
  skills: # per-skill overrides
171
188
  architecting: opus
172
189
  planning: opus
@@ -174,10 +191,11 @@ models:
174
191
  quick-tasking: haiku
175
192
  ```
176
193
 
177
- **Precedence:** `skills.X` → `default` → parent model.
194
+ **Precedence:** `by_complexity.{task.complexity}` → `skills.X` → `default` → parent model.
195
+ **Complexity overrides skill** (LOCKED, context.md § M24): when a task declares a `<complexity>`, its `by_complexity` resolution wins over `models.skills.{skill}`. WHY — the per-task signal is the granular one and the whole point of M24; letting the coarser skill-level setting win would make per-task routing inert on any project that already sets `models.skills.executing`. A task with **no** complexity resolves via the unchanged `skills → default → parent` chain — zero behavior change for untagged tasks.
178
196
  **All advisory** — forge displays expected model at each transition, suggests `/model` on mismatch. Review gate logs model used. Cannot auto-switch.
179
197
 
180
- **Subagent inheritance.** When a skill spawns subagents via the Agent tool (parallel research, fresh-context reviewers, executor fan-out, tester author/analyst, etc.), the skill MUST pass the resolved `model` param explicitly. Subagents do NOT inherit skill-level routing automatically — without the param they run on the parent session's model, defeating the routing intent. Resolve using the same precedence chain (`models.skills.{skill}` → `models.default` → parent) and surface it: *"Spawning {role} with model: {model} (from {source})"*.
198
+ **Subagent inheritance.** When a skill spawns subagents via the Agent tool (parallel research, fresh-context reviewers, executor fan-out, tester author/analyst, etc.), the skill MUST pass the resolved `model` param explicitly. Subagents do NOT inherit skill-level routing automatically — without the param they run on the parent session's model, defeating the routing intent. Resolution is **per-task** where the task being spawned declares a `complexity` (`by_complexity` wins per the Precedence above), falling back to the skill-level chain (`models.skills.{skill}` → `models.default` → parent) otherwise surface it: *"Spawning {role} with model: {model} (from {source})"*. **Inline-task limitation:** a task that runs inline (no spawn) inherits the parent session's model and cannot be re-routed — per-task routing is inherently a subagent-spawn feature, not something that reaches into an already-running skill session.
181
199
 
182
200
  ## Agents
183
201
 
@@ -211,7 +229,7 @@ State lives in `.forge/`:
211
229
  - `project.yml` — Vision, stack, design system, verification, constraints (<5KB)
212
230
  - `constitution.md` — Active architectural gates
213
231
  - `design-system.md` — Component mapping table
214
- - `requirements/m{N}.yml` — Per-milestone structured requirements with `[NEEDS CLARIFICATION]` markers. **FR-IDs, DEF-IDs, and NFR-IDs are globally unique across all milestone files** — `FR-001` may exist in exactly one `m{N}.yml`. Before adding a new ID, **reserve it via the ID Reservation Protocol** (append to `.forge/reservations.yml`, commit+push, then write) the next number is `max(highest reserved, highest in `.forge/requirements/*.yml`) + 1`. That reservation is what makes the shared ID space safe across **concurrent worktrees**: bare scan-and-increment sees only committed state, so two worktrees branched from one baseline claim the same number and collide silently until merge (see ADR-016). On a residual collision (legacy/un-reserved, e.g. during a migration), keep the older milestone's ID and renumber the newer. Concurrent milestones each own their file — no cross-stream contention on file writes, but ID space is shared. Functional requirements may carry M9 e2e gate fields (`e2e`, `observable_outcome`, `observable_outcome_hash`, `validated`) — lazy migration, absent fields default to `e2e:false`/`validated:false`.
232
+ - `requirements/m{N}.yml` — Per-milestone structured requirements with `[NEEDS CLARIFICATION]` markers. **FR-IDs, DEF-IDs, and NFR-IDs are globally unique across all milestone files** — `FR-001` may exist in exactly one `m{N}.yml`. Before adding a new ID, **reserve it via the ID Reservation Protocol** — run `forge-reserve fr|nfr|def --milestone <id>` and use the id it prints (the helper dual-writes the machine-local ledger + `.forge/reservations.yml`; you commit `reservations.yml` with the work). The next number is `max(ledger in-tree ∪ main:reservations.yml) + 1`. The machine-local ledger is what makes the shared ID space safe across **concurrent worktrees**: bare scan-and-increment (or a branch-local `reservations.yml`) sees only committed/merged state, so two worktrees branched from one baseline claim the same number and collide silently until merge (see ADR-021, supersedes ADR-016). On a residual collision (legacy/un-reserved, e.g. during a migration), keep the older milestone's ID and renumber the newer. Concurrent milestones each own their file — no cross-stream contention on file writes, but ID space is shared. Functional requirements may carry M9 e2e gate fields (`e2e`, `observable_outcome`, `observable_outcome_hash`, `validated`) — lazy migration, absent fields default to `e2e:false`/`validated:false`.
215
233
  - `roadmap.yml` — Phases, milestones, dependencies
216
234
  - `state/index.yml` — DERIVED registry rolled up from milestone files (id, name, status, last_updated). Never hand-edited; never written by worktree agents.
217
235
  - `state/milestone-{id}.yml` — Per-milestone cursor (single source of truth): position, progress, decisions, blockers. One owner at a time.
@@ -227,8 +245,9 @@ State lives in `.forge/`:
227
245
  - `refactor-backlog.yml` — Refactoring catalog (actionable items only), worked via quick-tasking. Canonical status vocab: `pending | in_progress | done | dismissed | deferred`. Terminal items auto-archive on the next reviewing/quick-tasking write (compaction-on-write).
228
246
  - `refactor-backlog-archive.yml` — Append-only terminal-item archive (`done`/`dismissed`); keeps the audit trail out of the working backlog.
229
247
  - `releases.yml` — Append-only version-reservation registry (cross-session coordination point; see Version Reservation Protocol)
230
- - `reservations.yml` — Append-only ID-reservation registry for ADR/DEF/FR/NFR numbers (cross-worktree coordination point; see ID Reservation Protocol)
248
+ - `reservations.yml` — Append-only ID-reservation audit trail + cross-machine floor for ADR/DEF/FR/NFR numbers, written by `forge-reserve` (ADR-021). The same-machine cross-worktree coordination point is the machine-local ledger `.git/forge/id-reservations`; see ID Reservation Protocol.
231
249
  - `archive/milestone-{id}/` — Archived milestone artifacts (archive-delete)
250
+ - `prototypes/{slug}.md` — Lightweight resumable state for a disposable in-app design mockup (Prototyping tier). Status vocab: `exploring | parked | graduated | discarded`. Terminal (`graduated`/`discarded`) → archive to `prototypes/archive/{slug}.md`. Single-owner mutable; not derived, not shared. See `Skill(prototyping)`.
232
251
 
233
252
  **Milestones** group phases into concurrent streams. Own state file — no conflicts across sessions. Can be deferred (frozen in place) or archive-deleted.
234
253
  `index.yml status` gates routing: `not_started | active | deferred | complete`.
@@ -265,7 +284,8 @@ with the work it describes. Every artifact has a sharing class:
265
284
  | **Single-owner mutable** | `state/milestone-{id}.yml`, `phases/milestone-{id}/`, `research/milestone-{id}.md` | Exactly one writer at a time — the worktree (or main session) currently driving that milestone. **Other worktrees never touch it**, not even to read-then-write — they pull from main if they need it. |
266
285
  | **Single-owner mutable (per stream)** | `streams/{stream}.yml`, `streams/{stream}/brief.md`, `streams/{stream}/packages/*.yml` | The stream's **current driver** writes them — one writer at a time, like the milestone file. Distinct streams own distinct files (no cross-stream contention). `active.yml` is **derived** from them: a worktree driving a stream may write its own stream file but **never** `active.yml` or a peer stream's file. |
267
286
  | **Derived** | `state/index.yml`, `streams/active.yml` | Never hand-edited. Regenerated by rollup from their sources (`index.yml` ← `milestone-*.yml` via Rollup 1.0; `active.yml` ← per-stream files + active milestones via the Stream Rollup). Only the main/orchestrator session (and `chief-of-staff` for `active.yml`) runs rollup. |
268
- | **Append-only shared** | `releases.yml` (version reservations), `reservations.yml` (ADR/DEF/FR/NFR id reservations), `state/desire-paths/*` (one file per observation) | Many writers OK; structure prevents collision. `releases.yml` + `reservations.yml` use the reservation protocols (append + commit + push before depending on the number); desire-paths use distinct filenames so two writers never touch the same file. |
287
+ | **Append-only shared** | `releases.yml` (version reservations), `reservations.yml` (ADR/DEF/FR/NFR id **audit trail + cross-machine floor**), `state/desire-paths/*` (one file per observation) | Many writers OK; structure prevents collision. `releases.yml` uses the Version Reservation Protocol (append + commit + push). `reservations.yml` is written by `forge-reserve` (ADR-021) and committed with the caller's work it is no longer the id *coordination* point (the machine-local ledger is), just the durable committed shadow. Desire-paths use distinct filenames so two writers never touch the same file. |
288
+ | **Machine-local runtime** | `.git/forge/id-reservations` (id-reservation ledger), `.git/forge/integration-pending` (integration flag) | In the git common dir, shared across a repo's worktrees on one machine, **not** git-tracked. `forge-reserve` writes the ledger under a `mkdir` lock; excluded from framework context. Machine-local by design (cross-machine falls back to the `main:reservations.yml` floor). |
269
289
  | **Shared mutable** | `context.md`, `refactor-backlog.yml`, `requirements/m{N}.yml` (ID space `FR-`/`DEF-`/`NFR-` is globally shared even though each milestone owns its file) | Write only to your milestone/stream block. Within a block, append-only; strike through instead of rewriting. |
270
290
  | **Stable shared** | `project.yml`, `constitution.md`, `design-system.md`, `roadmap.yml`, `FORGE.md`, `.claude/skills/`, `.claude/agents/` | Rarely changes except via `/upgrading`. Canonical on main; worktrees lag until they rebase. An upgrade run touches **only the checkout it runs in**, so after upgrading in main, live worktrees keep their branch-point framework files — `upgrading` **Step 8** enumerates live `forge/m-*` worktrees and offers to rebase each (never auto), and the opt-in `forge.worktree_rebase_check` watches these files as a boot-time safety net. Gitignored carve-outs (`.mcp.json`, experimental hooks) don't rebase — re-run the experimental installer if the upgrade changed them. |
271
291
 
@@ -300,15 +320,16 @@ The project version + CHANGELOG slot are **shared resources** — when two miles
300
320
 
301
321
  ### ID Reservation Protocol
302
322
 
303
- Sequential IDs — `ADR-NNN` (decision records), `DEF-NNN` (deferred issues), `FR-NNN`/`NFR-NNN` (requirements) — are **shared cross-worktree resources** the same way the version number is. "Scan the tree for the highest and increment" only sees *committed* state, so two streams branched from the same baseline each claim the same next number and collide **silently until merge** — a costly multi-file renumber pass (see [ADR-016](../docs/decisions/ADR-016-id-reservation-protocol.md)). `.forge/reservations.yml` removes the number from per-stream scope, exactly as `releases.yml` does for versions.
323
+ Sequential IDs — `ADR-NNN` (decision records), `DEF-NNN` (deferred issues), `FR-NNN`/`NFR-NNN` (requirements) — are **shared cross-worktree resources** the same way the version number is. "Scan the tree for the highest and increment" only sees *committed* state, so two worktrees branched from the same baseline each claim the same next number and collide **silently until merge** — a costly multi-file renumber pass. [ADR-016](../docs/decisions/ADR-016-id-reservation-protocol.md) tried to fix this by reserving in the branch-tracked `reservations.yml` and *committing + pushing before allocating*, but a reservation on an unmerged branch is invisible to every sibling worktree (they share `.git`, not branches) — so collisions continued. [ADR-021](../docs/decisions/ADR-021-id-reservation-ledger-git-common-dir.md) (**supersedes ADR-016**) moves the coordination point to a **machine-local ledger in the git common dir**, the same primitive as the integration flag: every sibling worktree of the repo on a machine sees it the moment it is written — no push, no pull, no merge.
304
324
 
305
- - **Reserve before allocating.** Before authoring a new ADR or writing a new DEF/FR/NFR ID, append ONE entry to `.forge/reservations.yml` `{kind, id, milestone, reserved_at, summary}`, `kind {adr, def, fr, nfr}`then **commit + push it BEFORE creating the artifact**. A parallel worktree pulls and sees the number taken.
306
- - **Next number = `max(reserved, in-tree) + 1` per kind.** The higher of (a) the highest reservation for that kind in `reservations.yml` and (b) the highest ID of that kind actually landed in the tree. Taking both keeps it backward-compatible landed IDs that were never reserved are still respected.
307
- - **Append-only never edit prior entries.** Distinct lines = no contention. Race resolution: the second of two near-simultaneous reservations rebases onto the first (append-only → clean), re-reads `max()`, takes the next number.
308
- - **Reserve points.** `architecting` reserves an `adr` before authoring it; `planning` reserves `fr`/`nfr`/`def` before writing them into requirements. (Out of scope: `executing`'s `DI-NNN` deferred-*issue* ids in `deferred-issues.md` are a separate, more local namespace — not reserved here.)
309
- - **Lazy migration.** No `reservations.yml` allocate as before (scan in-tree max); the first reservation creates the file. Existing projects are unaffected until they next allocate an ID.
325
+ - **Reserve with the helper.** `forge-reserve <kind> --milestone <id> [--summary <text>]` (`.claude/hooks/forge-reserve.sh`, `kind {adr, def, fr, nfr}`) allocates the next number under a portable `mkdir` lock and prints it on stdout. It **dual-writes**: one TSV line to `.git/forge/id-reservations` (the machine-local coordination ledger) and one `{kind, id, milestone, reserved_at, summary}` block to the tracked `.forge/reservations.yml`. Neither is committed by the helper **the caller commits `reservations.yml` with its work** (unchanged handoff). Reserve *before* creating the artifact; the printed id is the one to use.
326
+ - **Next number = `max(ledger in-tree ∪ main:reservations.yml) + 1` per kind.** Three inputs, unioned: the **ledger** (same-machine siblings, *including uncommitted* the input ADR-016 could not see), the **in-tree** landed ids (`docs/decisions/ADR-NNN*`; `.forge/requirements/*.yml` for fr/nfr/def), and **`main:reservations.yml`** (the cross-machine / cold-start floor). Taking all three keeps it backward-compatible (landed & pre-ledger IDs respected) while giving the ledger authority for the same-machine hot path.
327
+ - **`reservations.yml` is now the durable audit trail + cross-machine floor**, not the coordination mechanism. Append-only, distinct lines it unions cleanly at merge (it stops being a merge-conflict source). An optional `forge-reserve --verify` can diff ledger vs. `reservations.yml`.
328
+ - **Reserve points.** `architecting` reserves an `adr` before authoring it; `planning` reserves `fr`/`nfr`/`def` before writing them into requirements — both via `forge-reserve`. (Out of scope: `executing`'s `DI-NNN` deferred-*issue* ids in `deferred-issues.md` are a separate, more local namespace — not reserved here.)
329
+ - **Cross-machine gap (accepted).** The ledger is machine-local (like `integration-pending`). Two *concurrent* worktrees on *different* laptops, both reserving before either merges, still fall back to merge-time renumber bounded by the `main:reservations.yml` floor, so no worse than ADR-016.
330
+ - **Lazy migration.** No ledger ⇒ the helper creates it on first reserve, seeded by the `max()` (which reads in-tree + `main:reservations.yml`). Existing `reservations.yml` is untouched and still respected. Projects that never run concurrent worktrees see no behavior change.
310
331
 
311
- This is the cross-worktree mechanism the FR/DEF/NFR "globally unique" rule (State Management, above) previously lacked, and the first reservation rule ADR numbers have ever had. Versions stay in `releases.yml` (separate semver/CHANGELOG semantics); everything else reserves here.
332
+ This is the cross-worktree mechanism the FR/DEF/NFR "globally unique" rule (State Management, above) previously lacked, now made collision-safe for same-machine worktrees. Versions stay in `releases.yml` (separate semver/CHANGELOG semantics); everything else reserves through `forge-reserve`.
312
333
 
313
334
  ## Deviation Rules
314
335
 
@@ -345,6 +366,10 @@ verification:
345
366
  - Empty commands = no gate (opt-out)
346
367
  - `verification.e2e_soft_cap` (default 10) — advisory cap on `e2e:true` stories per milestone surfaced by the `reviewing` skill. Soft — never blocks.
347
368
 
369
+ ### Truth checks (executable must_haves)
370
+
371
+ Plans MAY declare a per-truth `check:` shell command in `must_haves.truths` (exit 0 = pass; authored goal-backward at planning, before code exists; linted by planning Step 8 dimension 10). `executing` runs declared checks once at plan completion (early fix-loop signal, never a verdict); `verifying` re-executes them and applies the **executed-evidence rule**: VERIFIED only with a recorded command + exit code + output excerpt — no executed evidence → UNCERTAIN or human-route, never VERIFIED. Legacy bare-string truths stay valid; details live in the planning/executing/verifying skills + plan template.
372
+
348
373
  ### Human Verification Gate (close precondition)
349
374
 
350
375
  **No milestone reaches `complete` and no orchestration worktree is torn down until a human has explicitly signed off** — recorded as `current.human_verified` in `milestone-{id}.yml`. Code-level verification (tests, lint, type-check, the 3-level goal-backward pass) is necessary but **never sufficient** to close a milestone. A human must confirm the work in whatever form fits the milestone — device session, visual check, e2e walk, smoke test ("of some sorts").
@@ -0,0 +1,44 @@
1
+ # Migration Guide: ID Reservation Ledger in the Git Common Dir (Forge 0.46.0)
2
+
3
+ Forge 0.46.0 moves the ID-reservation **coordination point** from the branch-tracked `.forge/reservations.yml` to a **machine-local ledger in the git common dir** — `.git/forge/id-reservations` — written by a new `forge-reserve` POSIX-sh helper. This fixes the cross-worktree collisions ADR-016 could not (a reservation on an unmerged branch is invisible to sibling worktrees, which share `.git`, not branches). See [ADR-021](../../docs/decisions/ADR-021-id-reservation-ledger-git-common-dir.md) (supersedes ADR-016) and [issue #19](https://github.com/Attuned-Media/forge/issues/19).
4
+
5
+ **It is backward-compatible and zero-touch.** The helper is installed + `chmod +x`'d by the upgrade's additive hooks sync. The ledger self-creates on the **first** `forge-reserve` run, seeded implicitly by the allocation `max()` (which reads in-tree IDs + `main:.forge/reservations.yml`). Your existing `reservations.yml` is untouched and still respected as a `max()` input. A project that never runs concurrent worktrees sees no behavior change and needs no action.
6
+
7
+ ## What changed (no action needed)
8
+
9
+ - **`forge-reserve` helper** allocates the next `ADR/DEF/FR/NFR` number under a portable `mkdir` lock and prints it on stdout; `planning` (fr/nfr/def) and `architecting` (adr) now call it instead of hand-scanning.
10
+ - **Allocation is `max(ledger ∪ in-tree ∪ main:reservations.yml) + 1`** per kind — the ledger gives same-machine worktrees authority (including *uncommitted* siblings), while `main:reservations.yml` remains the durable cross-machine floor.
11
+ - **`reservations.yml` is reclassified** from the coordination mechanism to a **durable audit trail + cross-machine floor**, still committed with your work by the helper.
12
+
13
+ These take effect the moment the framework files are synced. No per-project state changes.
14
+
15
+ ## Detection
16
+
17
+ Prints `MIGRATE` only if a project predates the helper (has `.forge/` but no `forge-reserve.sh`). Silent + exit 0 once the helper is present.
18
+
19
+ ```bash
20
+ [ -d .forge ] || exit 0 # not a Forge project
21
+ [ -f .claude/hooks/forge-reserve.sh ] && exit 0 # helper present → already migrated, silent
22
+ echo "MIGRATE — ID reservation now uses .git/forge/id-reservations via forge-reserve; run 'npx forge-orkes upgrade' to install the helper (ADR-021, issue #19)"
23
+ exit 0
24
+ ```
25
+
26
+ ## Migration steps (normally none)
27
+
28
+ The upgrade installs the helper; the ledger self-creates on first reserve. Two optional cleanups:
29
+
30
+ - **Backfill un-reserved IDs (recommended for concurrent-worktree projects).** `forge` boot's ID-reservation preflight lists landed `ADR-`/`FR-`/`NFR-`/`DEF-` IDs that have no `reservations.yml` entry. Appending them keeps `main:reservations.yml` a complete floor.
31
+ - **ADR location caveat.** `forge-reserve`'s in-tree ADR scan reads `docs/decisions/ADR-NNN*` (per ADR-021). Projects that keep ADRs under **`.forge/decisions/`** should ensure those ADRs are represented in `reservations.yml` (via the backfill above) so the `main:reservations.yml` floor covers them — the ledger + floor still prevent collisions, but the in-tree ADR *input* won't see a `.forge/decisions/` file directly.
32
+
33
+ ## Cross-machine caveat (accepted)
34
+
35
+ The ledger is machine-local (like the integration flag). Two *concurrent* worktrees on *different* laptops, both reserving before either merges, still fall back to merge-time renumber — bounded by the `main:reservations.yml` floor, so no worse than ADR-016. Tracked as DEF-046 (land-time detector deferred).
36
+
37
+ ## Validation
38
+
39
+ ```bash
40
+ [ -x .claude/hooks/forge-reserve.sh ] && echo "OK: forge-reserve present + executable" || echo "FAIL: helper missing/not executable"
41
+ id=$(.claude/hooks/forge-reserve.sh fr --milestone m-smoke --summary "migration smoke") && echo "OK: allocated $id" || echo "FAIL: forge-reserve did not run"
42
+ # revert the smoke reservation: drop the last reservations.yml block + the ledger line for $id
43
+ echo "validation complete"
44
+ ```