forge-orkes 0.49.0 → 0.53.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.
@@ -36,9 +36,9 @@ Run **before the first task begins**. Makes failure causality mechanical — no
36
36
  1. Run all non-advisory verification commands
37
37
  2. Record which tests/checks pass and which fail — this is the baseline
38
38
  3. After each commit: failures **not in baseline** = introduced by this task = **must fix under Rule 3**
39
- 4. Failures **present in baseline** = pre-existing → append to `.forge/deferred-issues.md`, mark `status: pending`
39
+ 4. Failures **present in baseline** = pre-existing → log a file under `.forge/deferred-issues/`, `status: pending` (see Deferred Issues Format)
40
40
 
41
- Skip only if re-entering an in-progress execution and `deferred-issues.md` already documents all current failures.
41
+ Skip only if re-entering an in-progress execution and `.forge/deferred-issues/` (or legacy `deferred-issues.md`) already documents all current failures.
42
42
 
43
43
  ## Plan Anchor Re-Derivation
44
44
 
@@ -63,7 +63,7 @@ Execution-phase operational guidance below supplements the rules — it does not
63
63
  3 auto-fix attempts on a single task → STOP. Document remaining issues. Move to next task.
64
64
 
65
65
  ### Scope Boundary
66
- Only fix issues DIRECTLY caused by the current task. Pre-existing warnings, tech debt, unrelated bugs → log to `.forge/deferred-issues.md`.
66
+ Only fix issues DIRECTLY caused by the current task. Pre-existing warnings, tech debt, unrelated bugs → log a file under `.forge/deferred-issues/`.
67
67
 
68
68
  ### Slice Integrity (Execution-Side)
69
69
 
@@ -203,14 +203,14 @@ For each command, in order:
203
203
  Attempt 1:
204
204
  1. Read error output
205
205
  2. In baseline snapshot?
206
- - YES (pre-existing) → mark advisory for this session; append to .forge/deferred-issues.md; continue
206
+ - YES (pre-existing) → mark advisory for this session; log a file under .forge/deferred-issues/; continue
207
207
  - NO (introduced by this task) → fix code, stage fixes, amend commit
208
208
  3. Re-run command
209
209
  4. Pass → next command
210
210
  5. Fail → next attempt (up to max_retries)
211
211
 
212
212
  After max_retries exhausted:
213
- Append to .forge/deferred-issues.md
213
+ Log a file under .forge/deferred-issues/
214
214
  → Log failure in execution summary
215
215
  → Continue to next task
216
216
  ```
@@ -219,26 +219,30 @@ After max_retries exhausted:
219
219
  Verification retries count toward the task's 3-strike limit. 2 strikes used = 1 verification retry max.
220
220
 
221
221
  ### Do NOT Fix
222
- - **Pre-existing failures** present in baseline snapshot → mark advisory; append to `.forge/deferred-issues.md`
222
+ - **Pre-existing failures** present in baseline snapshot → mark advisory; log a file under `.forge/deferred-issues/`
223
223
  - **Flaky tests** passing on re-run without changes → note in summary, no strike
224
224
  - **Unrelated warnings** (deprecation, non-blocking lint) → ignore
225
225
 
226
226
  ### Deferred Issues Format
227
227
 
228
- Append one entry per failure to `.forge/deferred-issues.md` (create if missing):
228
+ **One file per failure** (ADR-023 — no DI-NNN counter). Copy `.forge/templates/deferred-issue.md`
229
+ → `.forge/deferred-issues/{YYYY-MM-DD}-{milestone}-{slug}.md`, where `{slug}` is a short kebab of
230
+ the failure (e.g. `redis-unavailable`). Distinct filenames mean concurrent worktrees never collide —
231
+ the same anti-collision property as `.forge/state/desire-paths/`.
229
232
 
230
233
  ```yaml
231
- issues:
232
- - id: DI-001
233
- type: test_failure | lint_failure | build_failure
234
- command: "npm test"
235
- summary: "HealthEndpointTest — Redis unavailable in test env"
236
- first_seen: "2026-04-14"
237
- milestone: "m3"
238
- status: pending
234
+ # .forge/deferred-issues/2026-04-14-m3-redis-unavailable.md
235
+ type: test_failure # test_failure | lint_failure | build_failure
236
+ command: "npm test"
237
+ summary: "HealthEndpointTest — Redis unavailable in test env"
238
+ first_seen: "2026-04-14"
239
+ milestone: "m3"
240
+ status: pending
239
241
  ```
240
242
 
241
- ID = `DI-{N}` where N = next integer after existing max (or 001 if new file).
243
+ **Legacy read (lazy migration):** if `.forge/deferred-issues/` is absent, the old single-file
244
+ `.forge/deferred-issues.md` (DI-NNN `issues[]` entries) is still read — no data loss before migration.
245
+ Never allocate a DI-NNN number; the filename slug is the id.
242
246
 
243
247
  ### Quick Tier
244
248
  Run all non-advisory verification commands once after commit. 1 retry max.
@@ -307,7 +311,7 @@ After completing all tasks in a plan:
307
311
 
308
312
  ## Notes
309
313
  [Genuine handoff context only: environment requirements, seed data, external services needed (e.g. "Redis must be running for auth tests").
310
- Do NOT list test failures here — pre-existing failures belong in deferred-issues.md; task-introduced failures must be fixed before committing.]
314
+ Do NOT list test failures here — pre-existing failures belong in `.forge/deferred-issues/`; task-introduced failures must be fixed before committing.]
311
315
  ```
312
316
 
313
317
  ## State Updates
@@ -65,13 +65,13 @@ Cross-machine note: the flag file is machine-local, so a worktree (or the primar
65
65
  - **Absent registry, but IDs already exist.** `.forge/reservations.yml` missing *and* the tree already holds allocated IDs (`requirements/*.yml` has `FR-`/`NFR-`/`DEF-`, or `.forge/decisions/` has `ADR-NNN`) → one-time nudge: *"No `reservations.yml` yet, but this project already allocates IDs. Concurrent worktrees will collide on the next number. Create it by reserving your next ID via the protocol (`planning`/`architecting` do this), or backfill existing IDs now."* (No IDs anywhere → silent; the first reservation creates the file lazily.)
66
66
  - **Backfill scan (un-reserved landed IDs).** When `reservations.yml` exists, diff the IDs actually landed in the tree (`FR-`/`NFR-`/`DEF-` across `requirements/*.yml`; `ADR-NNN` across `.forge/decisions/`) against the ids recorded in `reservations.yml`. Any landed ID with **no** reservation entry → list them once: *"{N} ID(s) are in the tree but not in `reservations.yml` ({ids}) — landed before the protocol or via an un-reserved path. They are still respected (next = max(reserved, in-tree)+1), but appending backfill entries keeps the registry the single audit trail."* Advisory: surface, don't auto-write — backfilling is the operator's call.
67
67
 
68
- **Stream Rollup (active.yml is derived).** If `.forge/streams/` has any stream files, regenerate `.forge/streams/active.yml` from them + active milestones per FORGE.md → Stream Rollup, the same way step 1.0 regenerates `index.yml`. **Run the 1.0 State Rollup (below) first** so `index.yml` is fresh — the Stream Rollup reads active milestones from it for implicit-row coverage, so on a boot where a just-merged milestone promotion lands, a stale `index.yml` would silently drop that milestone's coverage row. `active.yml` is derived — never read it as authoritative without rolling up first, and never hand-edit it. Runs in the main checkout (worktrees never write `active.yml`). No `streams/` dir → skip.
68
+ **Stream Rollup (active.yml is derived).** If `.forge/streams/` has any stream files, regenerate `.forge/streams/active.yml` from them + active milestones per FORGE.md → Stream Rollup, the same way step 1.0 regenerates `index.yml`. **Run the 1.0 State Rollup (below) first** so `index.yml` is fresh — the Stream Rollup reads active milestones from it for implicit-row coverage, so on a boot where a just-merged milestone promotion lands, a stale `index.yml` would silently drop that milestone's coverage row. This includes the rollup's **step 0 completed-stream auto-close sweep** (FORGE.md → Stream Rollup): a milestone-backed stream whose milestone is now `complete`, that is not already `closed`, and has no live worktree, is auto-closed (`status: closed`, `merge.readiness: merged`) *before* the join — so a done-and-merged milestone can never leave a zombie `ready` row that re-nags every boot. Announce each: *"auto-closed stale stream {id} — milestone m-{id} complete, already on main."* (A `complete` milestone that still has a live worktree is left for the finalize path below, not swept.) `active.yml` is derived — never read it as authoritative without rolling up first, and never hand-edit it. Since 0.53.0 it is a **git-ignored render-on-read cache** (never committed), so rendering it is a local-cache write that never lands on `main`; the source writes it still performs (the step-0 auto-close sweep touches *stream files*, which remain committed) run in the main checkout as before. No `streams/` dir → skip.
69
69
 
70
- **Ready-to-merge nudge (main checkout / streams without checkpoints).** From the just-regenerated `active.yml`, scan for streams with `coordination: ready` or a non-empty `merge_queue` **that are not auto-publishing via checkpoints**. Any present one line: *"{N} stream(s) ready to merge — run `/chief-of-staff` (merge safe) to integrate."* Pointer only — the cadence logic lives in the Chief's Merge Cadence Check; `forge` never merges. Nothing pending → silent.
70
+ **Ready-to-merge nudge (main checkout / streams without checkpoints).** From the just-regenerated `active.yml` — already swept of completed-milestone zombies by the rollup's step 0 — scan for streams with `coordination: ready` or a non-empty `merge_queue` **that are not auto-publishing via checkpoints**. Because the sweep ran first, anything surfaced here is genuinely mergeable (open work, not yet on main), so the nudge is truthful: *"{N} stream(s) ready to merge — run `/chief-of-staff` (merge safe) to integrate."* Pointer only — the cadence logic lives in the Chief's Merge Cadence Check; `forge` never merges. Nothing pending → silent.
71
71
 
72
72
  ### 1.0 State Rollup (index.yml is derived)
73
73
 
74
- `index.yml` is a **derived registry** — regenerate it from the milestone files before reading, and after any milestone CRUD (promote/defer/resume/delete). **Never hand-edit `index.yml`.**
74
+ `index.yml` is a **derived registry** — regenerate it from the milestone files before reading, and after any milestone CRUD (promote/defer/resume/delete). **Never hand-edit `index.yml`.** Since 0.53.0 it is a **git-ignored render-on-read cache** (never committed): rendering it here is a local-cache write, so it never conflicts and never needs the orchestrator-only guard for *safety* — any session may render its own copy. The rollup below is unchanged; only the storage is (see FORGE.md → State Ownership, Derived class).
75
75
 
76
76
  Rollup procedure (deterministic + idempotent):
77
77
  1. Glob `.forge/state/milestone-*.yml`.
@@ -152,7 +152,10 @@ The gate (below) and this finalize path are mutually exclusive on `current.statu
152
152
 
153
153
  Present the four resolutions and wait for an explicit choice:
154
154
 
155
- 1. **Enter the worktree** *(recommended)* — print the exact command (`cd {worktree_path}` then `/forge`); re-running there anchors to this milestone and writes land in the owner. This session does nothing further to the milestone.
155
+ 1. **Enter the worktree** *(recommended)* — relocate this session into the worktree **in place** (the operator no longer has to `cd` + re-run). This is the canonical **Native Worktree Entry** contract, reused verbatim at the `chief-of-staff` resume and `orchestrating` hand-back touch points:
156
+ - **If the `EnterWorktree` tool is available**, call `EnterWorktree({path: {worktree_path}})` — always `{path}` into the recorded `lifecycle.worktree_path`, never `{name}` (Forge owns worktree creation; `{name}` would spawn a new one on the wrong branch convention). Then log one line — `Relocated into worktree {worktree_path} ({branch})` — **independent of any confirmation prompt**, so the cwd move is always visible in the transcript. Continue routing to the requested skill from inside the worktree: writes now land in the owner, and the ownership gate is satisfied because this session **is** the owner.
157
+ - **If `EnterWorktree` is unavailable** (older Claude Code CLI, or a non-Claude-Code adapter), fall back to today's behavior: print the exact command (`cd {worktree_path}` then `/forge`); re-running there anchors to this milestone and writes land in the owner. This session does nothing further to the milestone.
158
+ - Exit is always `ExitWorktree({action: "keep"})` — Forge never lets the native tool **remove** a worktree; teardown stays option 3 / `git worktree remove`.
156
159
  2. **Sync, then continue in the worktree** — bring main's commits in (`git -C {worktree_path} rebase main`, or merge), then work from the worktree. Use when main has changes the worktree needs first.
157
160
  3. **Retire the worktree** — only if it is dead/superseded. Close the stream, `git worktree remove {worktree_path}`, and clear `lifecycle.worktree_*` on the milestone — which ends its ownership so main may write. **Confirm before removing; never auto-remove a worktree.**
158
161
  4. **Recorded override** — deliberately write from main anyway. Write `lifecycle.ownership_override: {at: {ISO}, from: main, reason: "{operator's reason}"}` into `milestone-{id}.yml`, echo it in the boot line, and carry it into the next `reviewing` report. This is the **only** way past the block and it is **never silent**. (An override is scoped to this milestone; clear it once the worktree is retired or synced.)
@@ -173,16 +176,17 @@ Check `.forge/refactor-backlog.yml`:
173
176
  **Promotion procedure (effort: standard only):**
174
177
 
175
178
  1. Read backlog item `id` (e.g. `R61-002`). Synthesize milestone id `m-{R-id}` (e.g. `m-R61-002`).
176
- 2. Copy `.forge/templates/state/milestone.yml` → `.forge/state/milestone-{m-R-id}.yml`. Populate:
179
+ 2. **Enter the worktree first (R10 — this runs in Step 1, *before* Step 3's routing gate fires, so do not assume a worktree already exists):** if this session is still on the main checkout (`git rev-parse --git-dir` == `--git-common-dir`), `EnterWorktree` now; if R10 already entered one, this is simply the first write in it. Then write the declaration (`.forge/templates/streams/stream.yml` → `.forge/streams/{stream}.yml` — `stream.goal`, `stream.milestone: m-{R-id}`, `stream.status: draft`, `stream.tier: standard`, `runtime.branch`/`runtime.worktree` from the worktree just entered, best-known `ownership.owned_surfaces`/`shared_surfaces`) as the worktree's first commit and fast-forward-push it, per FORGE.md's Stream Integration Checkpoints intake-declaration trigger.
180
+ 3. Copy `.forge/templates/state/milestone.yml` → `.forge/state/milestone-{m-R-id}.yml`. Populate:
177
181
  - `milestone.id: m-{R-id}`
178
182
  - `milestone.name: "Promoted from {R-id}: {backlog item title}"`
179
183
  - `milestone.origin: {R-id}`
180
184
  - `current.tier: standard`, `current.status: researching`
181
185
  - `current.last_updated: {ISO date}`
182
- 3. Run **Rollup (1.0)** — regenerates `index.yml` from the new milestone file (do not hand-append the registry entry). New milestone id is `m-{R-id}` from step 1; for numeric ids, allocate by scanning existing `milestone-*.yml` for the max id + 1.
183
- 4. Append to `.forge/roadmap.yml` — one milestone entry + one phase (`refactor-{R-id}`, `requirements_source: refactor-backlog.yml#{R-id}`, `dependencies: []`).
184
- 5. Update `.forge/refactor-backlog.yml` item: `status: in_progress`, add `promoted_to: m-{R-id}`.
185
- 6. Confirm: *"Promoted {R-id} → milestone m-{R-id}. State + roadmap created. Routing to researching."*
186
+ 4. Run **Rollup (1.0)** — regenerates `index.yml` from the new milestone file (do not hand-append the registry entry). New milestone id is `m-{R-id}` from step 1; for numeric ids, allocate via `id=$(.claude/hooks/forge-reserve.sh milestone --milestone m-new)` (ADR-023) — never scan `milestone-*.yml` for max+1 inline. The three milestone-id call sites (here, `discussing`, `prototyping`) share this ledger-backed allocator so they cannot disagree, and it scans live **+ archived** milestones so a burned id is never reused.
187
+ 5. Append to `.forge/roadmap.yml` — one milestone entry + one phase (`refactor-{R-id}`, `requirements_source: refactor-backlog.yml#{R-id}`, `dependencies: []`).
188
+ 6. Update `.forge/refactor-backlog.yml` item: `status: in_progress`, add `promoted_to: m-{R-id}`.
189
+ 7. Confirm: *"Promoted {R-id} → milestone m-{R-id}. State + roadmap created. Routing to researching."*
186
190
 
187
191
  ```text
188
192
  backlog pickup → effort: standard
@@ -354,6 +358,8 @@ Tier + state → invoke via `Skill` tool. All phases use `Skill()`.
354
358
 
355
359
  **CRITICAL: NEVER `EnterPlanMode`.** "Planning" = `Skill(planning)`. Native plan mode writes wrong format, bypasses gates + state.
356
360
 
361
+ **CRITICAL: New work enters its worktree before anything else (R10).** When routing genuinely NEW work — `current.status: not_started` about to detect tier and start, or any session about to invoke `researching`/`discussing`/`planning`/`quick-tasking`/`prototyping` for a request that has no milestone yet — call `EnterWorktree` (Worktree Convention, FORGE.md) BEFORE invoking that skill. The main checkout is for orchestration and reads. Applies to every tier, including Quick and Prototyping: one `EnterWorktree` call, no heavier ceremony — a Quick fix or a disposable mockup gets a worktree but no milestone/stream declaration (nothing is being registered until, for a prototype, it graduates). A mockup is disposable but not weightless — it writes flag-gated app code + a prototype state file the team shares and may fold into a feature-toggled section — so its initial entry is worktree-first like the rest. Earlier practice held off on worktree isolation until implementation began, on the theory that research/discussion/planning don't touch code — that theory doesn't hold: discussing/planning/researching write `.forge` state too (context.md, plans, research notes), and per P17 those writes left untracked droppings on main that broke other worktrees' landings. No phase may hold off on worktree entry for new work. Milestone/stream intake itself is a deliberate exception with its own mechanic — see FORGE.md → Stream Integration Checkpoints for the declaration/content split (B3'). Pure read-only sessions that start no work need no worktree. This governs the FIRST write of new work; resuming an in-flight milestone already routes through the Live-Worktree Ownership Gate (1.1a) / branch-anchor path (Step 1.1) instead.
362
+
357
363
  **Experimental — M10 orchestration backend.** Chief/Streams is the preferred
358
364
  user-facing orchestration model. M10 remains an optional backend for streams
359
365
  that need worktree isolation, merge-queue discipline, or experimental file
@@ -361,7 +367,7 @@ claims. Route through `Skill(orchestrating)` first when **all** hold:
361
367
  - **Selected:** Project Chief chooses M10 for a stream, user explicitly asks for multi-agent/worktree mode, or legacy `orchestration.auto` is enabled.
362
368
  - **Installed:** `.claude/skills/orchestrating/` present + `forge-orchestrator` in `.mcp.json` + `.claude/hooks/forge-claim-check.sh` present.
363
369
  - **Not opted out:** `orchestration.auto` in `project.yml` ≠ `false` for legacy auto-routing; explicit Project Chief selection can still use M10 when installed.
364
- - **Tier is Standard or Full.** Quick never auto-orchestrates (a worktree per typo is the wrong trade).
370
+ - **Tier is Standard or Full.** Quick never auto-orchestrates — the *orchestration backend* (MCP file-claims, merge queue, bootstrap) is the wrong trade for a typo. (The plain worktree is not the cost: per R10 a Quick fix already runs in one — `EnterWorktree` is ~instant. What Quick skips here is the M10 machinery on top, not the worktree.)
365
371
  - **Not already in a session:** active milestone has no `lifecycle.worktree_mode: active|degraded` (don't re-bootstrap inside a live session).
366
372
 
367
373
  `orchestrating` bootstrap stays the safety net — incompatible repo → it refuses, writes `worktree_mode: refused`, and `forge` continues single-agent. Absent install → standard routing. Manual `Skill(orchestrating)` still works for explicit backend control; set `orchestration.auto: false` in `project.yml` to opt out of legacy auto-routing.
@@ -408,7 +414,7 @@ Where `{source}` = `skills.{name}` | `models.default` | `parent session`. Suppre
408
414
 
409
415
  | `current.status` | Route To |
410
416
  |-------------------|----------|
411
- | `not_started` | Detect tier, start |
417
+ | `not_started` | EnterWorktree (R10), then detect tier, start |
412
418
  | `researching` | `Skill(researching)` → discussing |
413
419
  | `discussing` | `Skill(discussing)` → planning (or architecting if Full) |
414
420
  | `architecting` | `Skill(architecting)` → planning |
@@ -7,6 +7,8 @@ description: "Break work into executable tasks with verification gates. Enforces
7
7
 
8
8
  > **Do NOT use `EnterPlanMode`.** Output -> `.forge/phases/`.
9
9
 
10
+ > Entered via a worktree already, for new work (R10, `forge/SKILL.md` Step 3) — if this skill is somehow invoked directly on the main checkout for new work, enter one now before writing anything.
11
+
10
12
  ## Core Principle: Vertical Slicing
11
13
 
12
14
  **Every phase and every plan MUST deliver a thin vertical slice -- a user-observable behavior reachable end-to-end (UI -> API -> data, or CLI -> core -> output).** Never decompose by horizontal layer (all models, then all APIs, then all UI). Horizontal slicing defers user-testable behavior until the last phase and amplifies integration risk.
@@ -72,7 +74,7 @@ or active context summary; do not copy the historical block back into
72
74
 
73
75
  ## Step 4: Structure Requirements
74
76
 
75
- Resolve current milestone ID from `.forge/state/index.yml` (active milestone) or invocation context. Target file: `.forge/requirements/m{N}.yml`.
77
+ Resolve the current milestone ID from the invocation context, or from the active milestone. Since 0.53.0 `.forge/state/index.yml` is a **git-ignored render-on-read cache** — legitimately absent on a fresh clone until the first `/forge` boot renders it, so do not depend on it being present and never treat its absence as "no project" (`.forge/project.yml` is the project sentinel). If the cache is present and fresh, read the active milestone from it; otherwise derive it from `.forge/state/milestone-*.yml` directly (the rollup's own source), or run a `/forge` boot first to render the cache. Target file: `.forge/requirements/m{N}.yml`.
76
78
 
77
79
  If missing, create from `.forge/templates/requirements.yml`:
78
80
  1. Extract from user description + research
@@ -15,6 +15,8 @@ Fast visual exploration inside the real app. You are drawing iterations of how s
15
15
  2. **No verify/review gate.** There is no `verifying`, no `reviewing`, no health gate. The gate is the operator's eyes.
16
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
17
 
18
+ > **Enter a worktree first (R10, `forge/SKILL.md` Step 3).** A mockup is disposable, but it is not weightless: it writes flag-gated code into the real app, a `.forge/prototypes/{slug}.md` state file, and iteration commits — artifacts the team discusses, shares, and may fold into a feature-toggled section of the app. That is exactly the write-before-worktree hazard R10 closes, so prototyping's **initial entry** enters its worktree before Step 1, the same as researching/discussing/planning. If `forge` boot already entered one, this is simply where you work; if this skill was invoked directly on the main checkout, `EnterWorktree` now before establishing the flag or writing the state file. (Resuming an existing prototype: work in the worktree it already lives in.)
19
+
18
20
  ## Step 1: Establish the prototype
19
21
 
20
22
  Give the exploration a short kebab `{slug}` (e.g. `dashboard-redesign`, `onboarding-v2`).
@@ -58,13 +60,14 @@ The loop ends when the operator picks an outcome. Each has a clean terminal path
58
60
  ### Graduate → promote to a real milestone
59
61
  The mockup proved the idea; now build it for real. This mirrors the refactor-backlog promotion procedure.
60
62
 
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`.
63
+ 1. Synthesize milestone id `m-proto-{slug}` (or a numeric id via `.claude/hooks/forge-reserve.sh milestone --milestone m-new` — ADR-023, collision-safe across worktrees; never scan `milestone-*.yml` for max+1 inline).
64
+ 2. **Write the declaration (R10 already put you in a worktree at initial entry):** the prototype has run in its worktree since Step 1, so you are already there — no re-entry. (Defensive: if this is somehow reached on the main checkout, `git rev-parse --git-dir` == `--git-common-dir`, `EnterWorktree` now first.) Write the declaration (`.forge/templates/streams/stream.yml` → `.forge/streams/{stream}.yml` `stream.goal` = the mockup's title/one-line intent, `stream.milestone: m-{id}`, `stream.status: draft`, `runtime.branch`/`runtime.worktree` from the worktree in use, best-known `ownership.owned_surfaces`/`shared_surfaces`, context noting `milestone.origin: prototype/{slug}`) as the worktree's first *milestone* commit and fast-forward-push it, per FORGE.md's Stream Integration Checkpoints intake-declaration trigger.
65
+ 3. 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}`.
66
+ 4. 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.
67
+ 5. Run the **State Rollup (forge 1.0)** so `index.yml` picks up the new milestone (do **not** hand-edit `index.yml`).
68
+ 6. 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`.
69
+ 7. **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.
70
+ 8. 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
71
 
69
72
  ### Discard → the idea's out, clean up
70
73
  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.
@@ -7,6 +7,8 @@ description: "Small, scoped changes: typo fixes, config updates, minor bugs, dep
7
7
 
8
8
  Small change? Skip ceremony. Do it right, do it fast.
9
9
 
10
+ > Entered via a worktree already, for new work (R10, `forge/SKILL.md` Step 3) — a quick fix still gets a worktree (just no milestone/stream declaration). If this skill is somehow invoked directly on the main checkout for new work, enter one now before writing anything.
11
+
10
12
  ## Qualifies as Quick
11
13
 
12
14
  - Single file change, under 50 lines
@@ -88,5 +90,5 @@ Standalone quick fix (no forge state exists or no milestone context provided):
88
90
 
89
91
  During execution, if you discover:
90
92
  - Fix is bigger than expected → **STOP**. Escalate to Standard.
91
- - Related issues that should be fixed → **LOG** to `.forge/deferred-issues.md`. Don't fix now.
93
+ - Related issues that should be fixed → **LOG** a file under `.forge/deferred-issues/` (copy `.forge/templates/deferred-issue.md`, slug the summary; ADR-023 — legacy `deferred-issues.md` still read if the dir is absent). Don't fix now.
92
94
  - An architectural question → **STOP**. Escalate to Standard with a note on what triggered it.
@@ -7,6 +7,8 @@ description: "Use when you need to investigate before building: understand the c
7
7
 
8
8
  Gather context before planning. Bad research leads to bad plans.
9
9
 
10
+ > Entered via a worktree already, for new work (R10, `forge/SKILL.md` Step 3) — research writes `.forge/research/` state, so if this skill is somehow invoked directly on the main checkout for new work, enter one now before writing anything.
11
+
10
12
  ## Research Types
11
13
 
12
14
  ### 1. Codebase Research
@@ -29,7 +29,7 @@ Read: .forge/project.yml → tech stack, framework, database, dependencies
29
29
  Read: .forge/state/milestone-{id}.yml → milestone ID and name
30
30
  Read: .forge/constitution.md → active architectural gates (if exists)
31
31
  Read: .forge/refactor-backlog.yml → existing backlog items (if any)
32
- Read: .forge/deferred-issues.md → pre-existing failures logged during execution (if exists)
32
+ Read: .forge/deferred-issues/*.md → pre-existing failures logged during execution (glob the dir; else legacy .forge/deferred-issues.md)
33
33
  ```
34
34
 
35
35
  Skip by stack: no DB->SQL/NoSQL N/A, no frontend->XSS N/A, no CI/CD->Pipeline N/A.
@@ -97,7 +97,7 @@ security_audit:
97
97
  |-----------|--------|
98
98
  | **Scalability** | Synchronous blocking, missing pagination, unbounded queries, N+1, missing caching, SPOFs, hardcoded limits |
99
99
  | **Maintainability** | Files >300 lines, nesting >4, god components/classes, circular deps, dup logic |
100
- | **Code Health** | Dead code/unused exports, TODO/FIXME inventory, untested critical paths, stale deps, deferred issues in `.forge/deferred-issues.md` |
100
+ | **Code Health** | Dead code/unused exports, TODO/FIXME inventory, untested critical paths, stale deps, deferred issues in `.forge/deferred-issues/` (else legacy `.forge/deferred-issues.md`) |
101
101
  | **Structural Quality** | Biz logic in UI, inconsistent patterns, missing error boundaries, API contract drift |
102
102
 
103
103
  **Rules:** Actual code, not theory. Specific files + evidence. `critical`=prod issues/blocking, `warning`=quality, `info`=improvement. Respect ADRs + constitution.
@@ -303,12 +303,12 @@ Refactoring triage (max 10): *"{N} opportunities:"*
303
303
 
304
304
  **Accept**->backlog | **Dismiss**->skip | **Accept all** | **Dismiss all**
305
305
 
306
- Deferred issues triage: If `.forge/deferred-issues.md` has `status: pending` items, surface them:
306
+ Deferred issues triage: Glob `.forge/deferred-issues/*.md` (else legacy `.forge/deferred-issues.md`) for `status: pending` items, surface them:
307
307
  *"**Test debt** ({N} pre-existing failures): 1. `{summary}` -- first seen {date}. [Accept/Dismiss/Fix-now]*"*
308
308
 
309
- - **Accept** → add to refactor-backlog.yml as `category: test-debt`, mark `status: triaged` in deferred-issues.md
309
+ - **Accept** → add to refactor-backlog.yml as `category: test-debt`, set `status: triaged` in the issue's file under `.forge/deferred-issues/` (or the legacy file if that is where it lives)
310
310
  - **Fix-now** → route to `planning` fix mode before completing milestone
311
- - **Dismiss** → mark `status: dismissed` in deferred-issues.md with reason
311
+ - **Dismiss** → set `status: dismissed` + `dismissed_reason` in the issue's file (or the legacy file)
312
312
 
313
313
  ## Step 7: Backlog + Route
314
314
 
@@ -320,7 +320,7 @@ Deferred issues triage: If `.forge/deferred-issues.md` has `status: pending` ite
320
320
 
321
321
  **Cross-tree liveness check (informational).** Before writing, run `git worktree list --porcelain | awk '/^branch / && $2 ~ /refs\/heads\/forge\/m-/ {print}'`. Any non-empty output means another milestone is live in a worktree — surface one line: *"{N} other Forge milestone(s) live in worktrees ({list}). Backlog appends here land cleanly on main but won't be visible to those worktrees until they rebase."* Proceed with the write — reviewing on main is the canonical author. (Two reviewing sessions writing in parallel from different worktrees rely on the milestone-scoped append rule above, not on this check.)
322
322
 
323
- Read `.forge/refactor-backlog.yml`. Next ID = max + 1. Append:
323
+ Read `.forge/refactor-backlog.yml`. Reserve the next id via `id=$(.claude/hooks/forge-reserve.sh refactor --milestone m-{ctx})` (ADR-023 — collision-safe across worktrees; scans live + archived backlog, so a compacted R-id is never reused). Append:
324
324
 
325
325
  ```yaml
326
326
  items:
@@ -408,7 +408,7 @@ If the milestone's phases produced `contract.md` files (planning Step 6.1 Tier 1
408
408
  ## Phase Handoff
409
409
 
410
410
  1. Confirm report + backlog
411
- 2. **Human Verification Gate (hard block)** — Read `current.human_verified` from `milestone-{id}.yml`. If **absent/null** → **do not complete the milestone.** Code-clean + review-clean is not enough to close (FORGE.md → Human Verification Gate). The milestone needs an explicit human sign-off, normally captured by `verifying`. Either route the user back to verify (then re-review) or capture the sign-off / recorded override here and write `current.human_verified` before proceeding. A recorded override (`method: override`) satisfies this check but **must** be surfaced in the health report. Never silently complete past an unset gate.
411
+ 2. **Human Verification Gate (hard block)** — Read `current.human_verified` from `milestone-{id}.yml`. If **absent/null** → **do not complete the milestone.** Code-clean + review-clean is not enough to close (FORGE.md → Human Verification Gate). The milestone needs an explicit human sign-off, normally captured by `verifying`. Either route the user back to verify (then re-review) or capture the sign-off / recorded override here and write `current.human_verified` before proceeding. A recorded override (`method: override`) satisfies this check but **must** be surfaced in the health report. Never silently complete past an unset gate. *(Attended lane: the recorded `current.human_verified` — or a recorded override — **is** the gate, exactly as today. The M0 `verify-signoff` **forward-gate is not wired here** — that is autonomous-lane. Where an M0 `signoff/<wu-id>` tag does exist, treat `human_verified` as a **rendered echo** of that signature and surface the tag in the report; where none exists it is the stored attestation. A pre-existing tagless `complete` milestone reads complete — grandfathered, never re-gated.)*
412
412
  3. **Run promoted-milestone completion hook** (above) if `milestone.origin` set
413
413
  4. **Run Contract Landing** (above) for any cross-layer phases — fold ratified contracts into their ADRs
414
414
  5. Set `current.status: complete` and `current.completed_at: "<ISO 8601 timestamp>"` in `milestone-{id}.yml`, then regenerate `index.yml` via the `forge` **Rollup**
@@ -21,17 +21,18 @@ Read: .forge/project.yml → tech stack (for running tests)
21
21
  Read: .forge/phases/milestone-{id}/{phase}-{name}/plan-{NN}.md → must_haves (truths, artifacts, key_links)
22
22
  Read: .forge/context.md → locked decisions
23
23
  Read: .forge/requirements/m{N}.yml → requirement IDs for coverage check (current milestone, resolved from state)
24
- Read: .forge/deferred-issues.md → known pre-existing failures (if exists; treat as advisory)
24
+ Read: .forge/deferred-issues/*.md → known pre-existing failures (glob the dir; else legacy .forge/deferred-issues.md; advisory)
25
25
  ```
26
26
 
27
27
  ## Deferred Issues
28
28
 
29
- If `.forge/deferred-issues.md` exists, load it before running any tests.
29
+ Glob `.forge/deferred-issues/*.md` before running any tests (ADR-023 — one file per issue). If the
30
+ directory is absent, read the legacy single-file `.forge/deferred-issues.md` instead.
30
31
 
31
- When test results come in, cross-reference failures against known deferred issue IDs:
32
+ When test results come in, cross-reference failures against the known deferred issues:
32
33
 
33
- - **Failure matches a deferred ID** → advisory only — note as "Pre-existing (deferred: DI-{N})", do NOT fail verification for this
34
- - **Failure not in deferred-issues.md** → regression introduced after the baseline — **FAIL** and include in gaps
34
+ - **Failure matches a deferred issue** → advisory only — note as "Pre-existing (deferred: {slug})", do NOT fail verification for this
35
+ - **Failure not matched by any file in `.forge/deferred-issues/` (nor the legacy file)** → regression introduced after the baseline — **FAIL** and include in gaps
35
36
 
36
37
  In the Test Results section of the report, split accordingly:
37
38
 
@@ -43,7 +44,7 @@ Run: {n} | Passed: {n} | Failed: {n}
43
44
  - {test name}: {error}
44
45
 
45
46
  ### Pre-existing / Deferred (advisory)
46
- - DI-{N}: {test name} — {summary from deferred-issues.md}
47
+ - {slug} ({file}): {test name} — {summary}
47
48
  ```
48
49
 
49
50
  Verdict: FAIL only triggers for regressions. Known deferred failures do not block a PASSED verdict — they are tracked separately and must be worked off via the refactor backlog or a dedicated fix phase.
@@ -252,7 +253,7 @@ Run on a PASSED verdict (and after any **HUMAN VERIFICATION NEEDED** items are r
252
253
  1. Surface what the human should confirm — the Observable Truths, any "Human Verification Items", and any milestone-specific manual check (device session, visual pass, real-device / e2e walk).
253
254
  2. Prompt explicitly: *"Code verification passed. Have you verified the work yourself (device session / visual / e2e walk)? This is required before the milestone can close. [verified | not yet | close without verifying]"*
254
255
  3. Record the response in `current.human_verified` in `.forge/state/milestone-{id}.yml`:
255
- - **verified** → `{at: "<ISO 8601>", method: "<how they verified>", notes: "<optional>"}`
256
+ - **verified** → `{at: "<ISO 8601>", method: "<how they verified>", notes: "<optional>"}`. *(Where an M0 `signoff/<wu-id>` tag exists for this milestone's wu, `human_verified` is a **rendered echo** of that signature — a human-readable cache of the sign-off — rather than the source of truth; surface the tag. Attended lane: the `verify-signoff` predicate is **not** wired into this gate's hard block — that forward-gate is autonomous-lane. See FORGE.md → Human Verification Gate.)*
256
257
  - **not yet** → leave `human_verified` unset. **Do NOT route to reviewing** — the milestone cannot close. Tell the human what's outstanding and stop. Re-run this gate after they've done it.
257
258
  - **close without verifying** → recorded override: `{at: "<ISO 8601>", method: "override", override: true, reason: "<required free text>"}`. Note the override in the verification report. Satisfies the gate but is flagged downstream.
258
259
 
@@ -45,6 +45,7 @@ Integration is **event-driven, not polled**: a stream publishes its work to main
45
45
 
46
46
  - **Checkpoints (plan-marked).** `planning` marks selected phases as integration checkpoints (`integration_checkpoint: true` in the plan frontmatter) — the points where a verified slice should land on main. Default heuristic: the last phase, plus any phase another stream/phase depends on. Advisory — planning proposes, the operator confirms. **The mark is the opt-in:** a plan with no marked checkpoint never auto-publishes, so existing/unmarked work is unaffected.
47
47
  - **Producer — publish on verified checkpoint (fast-forward only, strand-guarded).** When `verifying` returns PASS for a checkpoint phase *and the session is a worktree*, it integrates that phase to main with a **fast-forward-only** push: `git push origin HEAD:main`. Git rejects a non-ff push (vs `origin/main`) — that surfaces a diverged *origin*, never force-pushed. But ff-vs-origin is not enough on its own: `origin/main` is not the operator's canonical checkout, and the local `main` ref can carry commits origin never saw (another stream's merge, a framework bump, state-sync commits). Pushing past a *behind* origin then leaves **local main diverged, not behind** — and the naive "you're behind, `git pull --ff-only`" advice *fails* on divergence (forge#13). So the producer **strand-guards** first: it publishes only when local `main` is an **ancestor of the worktree HEAD** (no local commits this checkpoint omits). Stranded (local main has such commits) → it does **not** push; it surfaces "integrate through local main first" and leaves the work on the branch. On a strand-safe success it sets the **integration flag**. (No remote → fast-forward local `main` where possible, else record ready-to-ff for the primary checkout.)
48
+ - **Producer — publish at intake (declaration split, second trigger for the same mechanic).** Milestone/stream intake (discussing's Milestone Gate, `forge` Step 1.2 backlog promotion, `prototyping` graduation) reuses this exact strand-guard + fast-forward + integration-flag mechanic, triggered at a different moment: not a `verifying` PASS, but the worktree's first commit. Before any content (the full `milestone-{id}.yml`, research, discussion, plans, code) is written, the worktree commits a **declaration** — milestone ID (+ reservation), one-line intent, stream registration, and coordination/touches claims, i.e. `.forge/streams/{stream}.yml` with `ownership.owned_surfaces`/`shared_surfaces` populated — and fast-forward-pushes it to `origin/main` immediately. Content stays worktree-only until the milestone's own checkpoint (above). This keeps `chief-of-staff`'s cross-stream conflict detection — which globs `.forge/streams/*.yml` from the current checkout only — sighted on in-flight work without teaching it to read across worktrees (rejected: this project spans machines, so cross-worktree reading needs the early-push step *plus* multi-branch readers — strictly more machinery). One-breath rule: main knows THAT work exists; the worktree knows WHAT it is.
48
49
  - **Flag (cheap, set by the producer).** A marker in the shared git common dir — `.git/forge/integration-pending` — records the new main sha, the publishing stream, and the shared surfaces that changed. It is shared across all of a repo's worktrees on a machine, so any boot reads it for free; absent or already-seen → skip. The `main` ref is the source of truth the marker points at. Cross-machine (the marker is machine-local) falls back to the opt-in `forge.worktree_rebase_check` / a periodic fetch — eventual, not instant.
49
50
  - **Consumer — auto fast-forward, else prompt (worktrees *and* the primary main checkout).** `forge` boot checks the flag in **both** checkout kinds. In a **worktree**: flag set (main advanced past this worktree's merge-base) and no divergent local commits → **fast-forward automatically** (`git merge --ff-only main`); diverged → **surface a rebase prompt** (never auto-rebase). In the **primary main checkout**: the strand guard guarantees local main is a clean ff of the new `origin/main`, so boot **fast-forwards local main automatically** (`git merge --ff-only origin/main`) — this is what keeps the operator's canonical checkout in lockstep with origin after every checkpoint, instead of drifting behind until the next local commit diverges it. Flag not set → no fetch, no scan, continue boot.
50
51
 
@@ -231,12 +232,12 @@ State lives in `.forge/`:
231
232
  - `design-system.md` — Component mapping table
232
233
  - `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`.
233
234
  - `roadmap.yml` — Phases, milestones, dependencies
234
- - `state/index.yml` — DERIVED registry rolled up from milestone files (id, name, status, last_updated). Never hand-edited; never written by worktree agents.
235
+ - `state/index.yml` — DERIVED registry rolled up from milestone files (id, name, status, last_updated). Never hand-edited; never written by worktree agents. **Git-ignored render-on-read cache (0.53.0): regenerated at every boot from the milestone files, never committed** — so a committed projection can never drift stale from its source, and any session can render it locally without touching `main`. Absent on a fresh clone until the first boot renders it (the project sentinel is `project.yml`, not this file).
235
236
  - `state/milestone-{id}.yml` — Per-milestone cursor (single source of truth): position, progress, decisions, blockers. One owner at a time.
236
237
  - `state/desire-paths/` — Append-only framework-usage observations, one file per observation. Occurrence counts derived by globbing (no mutable counter). Each carries `scope: project | framework` (set at capture; absent ⇒ project). **`forge` boot is the single review owner**: at 3+ occurrences it surfaces a concrete fix from the co-located suggestion table, persists declines to `declined/` (no re-nag), and for `scope: framework` signals routes them upstream — a portable block to `upstream/` plus a `gh issue` offer against `forge.upstream_repo`. This closes Principle 7's capture→act loop; `verifying` captures but no longer surfaces. See [ADR-012](../docs/decisions/ADR-012-upstream-desire-path-feedback-channel.md).
237
238
  - `context.md` — Active locked decisions + deferred ideas (target <=12KB)
238
239
  - `context-archive.md` — Historical locked decisions; load only when relevant
239
- - `streams/active.yml` — **DERIVED** Project Chief traffic map. Regenerated by the Stream Rollup from the per-stream files + active milestone files (see Stream Rollup below). **Never hand-edited**, like `index.yml`. Each row carries the two orthogonal dimensions side by side: `phase` (from the milestone, for milestone-backed streams) and `coordination` (from the stream file). See [ADR-015](../docs/decisions/ADR-015-derived-stream-registry.md).
240
+ - `streams/active.yml` — **DERIVED** Project Chief traffic map. Regenerated by the Stream Rollup from the per-stream files + active milestone files (see Stream Rollup below). **Never hand-edited**, like `index.yml`, and — since 0.53.0 — a **git-ignored render-on-read cache** regenerated at every boot, never committed (this is the durable kill for the drift where a committed `active.yml` phase went stale against its milestone). Each row carries the two orthogonal dimensions side by side: `phase` (from the milestone, for milestone-backed streams) and `coordination` (from the stream file). See [ADR-015](../docs/decisions/ADR-015-derived-stream-registry.md).
240
241
  - `streams/{stream}.yml` — Per-stream **source of truth**: coordination lifecycle, ownership, shared surfaces, blockers, merge readiness, and an optional `stream.milestone: m-{id}` link. Standalone (quick-fix) streams own their `status` here; milestone-backed streams omit workflow phase (the rollup reads it from the milestone).
241
242
  - `streams/{stream}/brief.md` — Human-readable stream context packet loaded when resuming that stream.
242
243
  - `streams/{stream}/packages/{id}.yml` — Work package contract for delegated workers or manual sessions.
@@ -253,6 +254,7 @@ State lives in `.forge/`:
253
254
  `index.yml status` gates routing: `not_started | active | deferred | complete`.
254
255
 
255
256
  **Stream Rollup (`active.yml` is derived).** Mirrors the milestone Rollup: `active.yml` is a pure projection, regenerated — never hand-edited — by a deterministic, idempotent **join** of two single-sourced inputs (ADR-015). Run by the main/orchestrator session and at `chief-of-staff` Show/Sync; worktrees never write it. Procedure:
257
+ 0. **Completed-stream auto-close sweep (source write, main checkout only — runs *before* the join).** A milestone-backed stream whose work has completed and landed on main should not linger as a `ready`/`merge_queue` row — that stale readiness is a **false** "ready to merge" nudge (the work is already on main; the stream is *closable, not mergeable*). Before the pure join, glob the stream files and for each with `stream.milestone: m-{id}` where the milestone's derived status (from the freshly-rolled `index.yml`) is `complete`, its stream `status` is **not** already `closed`, **and** it has no live worktree (`runtime.worktree` empty, or the path no longer resolves to a live `git worktree`) → **auto-close it**: set the stream file's `status: closed` and `merge.readiness: merged`, append a `merge.notes` line (*"auto-closed: milestone m-{id} complete + on main"*), refresh `updated_at`. Emit one line — *"auto-closed stale stream {id} — milestone m-{id} complete, already on main."* This is a legitimate source write (stream file = single-owner mutable; the milestone is done, so no live driver contends). **Guards:** a `deferred` linked milestone is **not** swept (its stream should pause, not close — Chief offers that interactively); a `complete` milestone **with a live worktree** is **not** swept here either — the forge-boot "Completed milestone + live worktree → finalize" path (SKILL Step 1) owns teardown, and closes the stream as part of it. Idempotent: an already-`closed` stream is skipped, so re-running the sweep is a no-op. No `streams/` dir, or nothing complete-and-open → the sweep is silent and the join runs unchanged.
256
258
  1. Glob `streams/{stream}.yml`. Each → a row carrying its coordination state (`coordination` = the file's `status`), goal, runtime, ownership summary, `merge_readiness` (derived from the stream file's `merge.readiness`), `blocked_by`, `next_action` — all from the stream file.
257
259
  2. For a stream with `stream.milestone: m-{id}`, read `phase`/activity from `milestone-{id}.yml` (`current.status`, `last_updated`) — **not** stored in the stream file. A linked milestone that is `deferred`/`complete`/absent → the row's `phase` reflects that.
258
260
  3. Glob active milestones (from `index.yml`). Any active milestone with **no** stream file → emit a derived `stream: implicit` row (phase from the milestone, no coordination state) so coverage is guaranteed in the artifact — an active milestone can never be silently missing.
@@ -283,7 +285,7 @@ with the work it describes. Every artifact has a sharing class:
283
285
  |---|---|---|
284
286
  | **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. |
285
287
  | **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. |
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. |
288
+ | **Derived (git-ignored render-on-read cache)** | `state/index.yml`, `streams/active.yml` | Never hand-edited, **never committed** — git-ignored local caches (0.53.0), 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) at every boot. Because the value is computed on read and never stored in git, a committed projection cannot drift stale from its source, and **any** session (not just the orchestrator) may render them locally without a `main` write. Absent on a fresh clone until the first boot; readers must render-then-read and must **not** use their absence as a "not a Forge project" sentinel (that is `project.yml`). |
287
289
  | **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
290
  | **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). |
289
291
  | **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. |
@@ -320,12 +322,13 @@ The project version + CHANGELOG slot are **shared resources** — when two miles
320
322
 
321
323
  ### ID Reservation Protocol
322
324
 
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.
325
+ Sequential IDs — `ADR-NNN` (decision records), `DEF-NNN` (deferred requirements), `FR-NNN`/`NFR-NNN` (requirements), plus milestone ids (`M-NN`) and refactor-backlog ids (`R0NN`) — 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.
324
326
 
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
+ - **Reserve with the helper.** `forge-reserve <kind> --milestone <id> [--summary <text>]` (`.claude/hooks/forge-reserve.sh`, `kind ∈ {adr, def, fr, nfr, milestone, refactor}`) allocates the next number under a portable `mkdir` lock and prints it on stdout. Id formats differ per kind (ADR-023): `adr/def/fr/nfr` → `PREFIX-0NN` (hyphen, zero-padded); `milestone` → `M-NN` (hyphen, **unpadded** — ids are used bare, `milestone-26.yml`/`m-26`); `refactor` → `R0NN` (**no hyphen**, zero-padded — matches the backlog convention). 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.
328
+ - **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, and **`main:reservations.yml`** (the cross-machine / cold-start floor). The in-tree scan is per-kind: `adr` → `docs/decisions/ADR-NNN*`; `fr/nfr/def` → `.forge/requirements/*.yml`; `milestone` live `state/milestone-*.yml` **∪ archived** `.forge/archive/milestone-*/`; `refactor` → `refactor-backlog.yml` **∪** `refactor-backlog-archive.yml` (ADR-023 — reading archives means a compacted/archived id is never reallocated). Taking all three inputs keeps it backward-compatible (landed & pre-ledger IDs respected) while giving the ledger authority for the same-machine hot path.
327
329
  - **`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.)
330
+ - **Reserve points.** `architecting` reserves an `adr` before authoring it; `planning` reserves `fr`/`nfr`/`def` before writing them into requirements; the three milestone-id sites (`forge` Rollup, `discussing`, `prototyping`) reserve `milestone`; `reviewing` reserves `refactor` for a new backlog idall via `forge-reserve` (ADR-023).
331
+ - **DI-NNN eliminated, not reserved (ADR-023).** `executing`'s deferred-*issue* ids used to be a scan-and-increment `DI-NNN` counter in a single `deferred-issues.md` — the same cross-worktree collision in kind. Rather than reserve them, deferred issues are now **counter-free per-issue files** under `.forge/deferred-issues/` (`{date}-{milestone}-{slug}.md`, the desire-path pattern): distinct filenames, no shared counter, nothing to collide on. Nothing cross-references a deferred issue by number, so the id density reservation buys is worthless here. Legacy `deferred-issues.md` is still read until migrated.
329
332
  - **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
333
  - **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.
331
334
 
@@ -380,6 +383,10 @@ Plans MAY declare a per-truth `check:` shell command in `must_haves.truths` (exi
380
383
 
381
384
  Lazy migration: milestones with no `human_verified` field (pre-0.27.0) simply hit the prompt on their next close transition; already-`complete` milestones are never re-gated.
382
385
 
386
+ **Grandfather (legacy-complete marker).** A milestone already stored `current.status: complete` with **no** `signoff/<wu-id>` tag (the pre-M0 history — 26 milestones as of 0.53.0) is **grandfathered**: readers honor it as complete and it is **never re-gated** by any later sign-off convention. "Complete with no signoff tag" **is** the legacy-complete marker — no field is stamped onto those files. Only *new* completions going forward can be asked to carry a signature (that forward-gate is autonomous-lane and is **not** wired in the attended lane — see below).
387
+
388
+ **Where the Contract M0 sign-off convention is adopted, `human_verified` is a derived echo, not the source of truth.** The authoritative predicate becomes `verify-signoff <wu-id>` exiting 0 — an SSH-signed `signoff/<wu-id>` tag an agent cannot forge (see `docs/sign-off-convention.md` and `.claude/hooks/{verify-signoff,wu-done}.sh`). `current.human_verified` may still be written for human-readable display, but treat it as a cache of the signature check, never the gate itself. Core does not yet wire this predicate into `verifying`/`reviewing`'s own hard-block logic automatically — that remains a flagged follow-up (Contract M0 §D2 scoped to a minimal fold stub, not the full skill wiring).
389
+
383
390
  ## Beads Integration (Optional)
384
391
 
385
392
  With Beads installed: `bd prime` (session context), `bd ready` (unblocked tasks), `bd complete` (update deps), `bd compact` (summarize old). Without Beads, `.forge/state/` + Session Memory suffice.
@@ -28,3 +28,11 @@ dev-source
28
28
 
29
29
  # Orchestration locks
30
30
  *.lock
31
+
32
+ # Render-on-read derived registries (0.53.0). Regenerated at every `forge` boot
33
+ # from their sources (state/index.yml ← state/milestone-*.yml; streams/active.yml
34
+ # ← streams/*.yml + active milestones). Local cache only — never committed, so a
35
+ # committed projection can never drift stale from its source. Without these
36
+ # entries a fresh install would re-commit them.
37
+ state/index.yml
38
+ streams/active.yml
@@ -0,0 +1,91 @@
1
+ # Migration Guide: Template-Seeded Release Registry Scrub (Forge 0.50.1)
2
+
3
+ Applies to projects whose fresh install ran with forge-orkes 0.32.0–0.50.0. Those
4
+ package versions shipped `template/.forge/releases.yml` containing the forge dev
5
+ repo's own release history (13–19 entries, versions 0.20.0–0.37.0) instead of an
6
+ empty scaffold, so `npx forge-orkes` seeded every new project's Version
7
+ Reservation Registry with foreign entries. The registry's `max()` rule then
8
+ resolves against forge's history — a fresh 0.1.0 project reserving its first
9
+ version gets 0.38.0. From 0.50.1 the template ships an empty scaffold
10
+ (`releases: []`) and the release script refuses to ship a template with live
11
+ registry entries; this guide cleans installs that were already seeded.
12
+
13
+ `reservations.yml` (the ID-reservation sibling) was audited for the same
14
+ pattern: its template copy has been an empty scaffold since it was introduced —
15
+ no migration needed there.
16
+
17
+ ## Prerequisites
18
+
19
+ 1. On the new version's framework files (run `npx forge-orkes upgrade` first).
20
+ 2. Working tree clean or changes committed — the migration rewrites
21
+ `.forge/releases.yml`.
22
+
23
+ ## Detection
24
+
25
+ Prints `MIGRATE` when `.forge/releases.yml` carries the template-seeded foreign
26
+ entries; silent + exit 0 otherwise.
27
+
28
+ ```bash
29
+ # Fingerprint two forge-dev entries every seeded variant (0.32.0–0.50.0)
30
+ # contains; either match → the registry was template-seeded at install.
31
+ # The forge dev repo itself legitimately holds these entries — skip it.
32
+ F=.forge/releases.yml
33
+ [ -f "$F" ] || exit 0
34
+ [ -d packages/create-forge/template ] && exit 0
35
+ if grep -qF 'CLAUDE.md @import extraction (framework prose' "$F" \
36
+ || grep -qF 'Nested phase layout (phases/milestone-{id}/{phase}-{name}/)' "$F"; then
37
+ echo "MIGRATE — .forge/releases.yml was seeded with the forge dev repo's release history at install; scrub the foreign entries"
38
+ fi
39
+ ```
40
+
41
+ ## Migration steps
42
+
43
+ The migration is NEVER auto-applied. In Claude Code it runs via the
44
+ `quick-tasking` skill, which the `upgrading` Step-7 prompt hands this guide to
45
+ on "yes". Step 3 is potentially lossy — confirm with the user before renumbering
46
+ anything.
47
+
48
+ ### 1. Separate foreign entries from the project's own
49
+
50
+ An entry belongs to THIS project iff its `milestone:` matches a milestone that
51
+ exists here — in `.forge/state/milestone-*.yml`, `.forge/roadmap.yml`, or
52
+ `.forge/archive/milestone-*/`. Everything else is foreign: the seeded block is
53
+ contiguous, sits at the top of the `releases:` list, spans versions
54
+ 0.20.0–0.37.0, and its summaries describe forge framework features (streams,
55
+ worktree gates, CLAUDE.md @import extraction, …) rather than anything this
56
+ project built. Entries the project itself appended (if any) follow the seeded
57
+ block.
58
+
59
+ ### 2. Rebuild the registry
60
+
61
+ Keep the header comment block. Delete the foreign entries. Keep the project's
62
+ own entries in their original order. If no own entries remain, end the file
63
+ with `releases: []`.
64
+
65
+ ### 3. Check the project's own entries for version skew (confirm with user)
66
+
67
+ Any entry the project reserved while the foreign block was present took
68
+ `max()` off forge's history — e.g. a project actually at 0.3.0 reserving
69
+ "0.38.0". For each such entry, ask the operator:
70
+
71
+ - **Not yet published or tagged anywhere** → renumber the entry (and the
72
+ project's `package.json` or equivalent version file) to continue the
73
+ project's true version line.
74
+ - **Already published/tagged** → keep the inflated number for continuity and
75
+ note the skew in a comment beside the entry.
76
+
77
+ Never renumber without explicit confirmation.
78
+
79
+ ### 4. Commit
80
+
81
+ One scoped commit, e.g.
82
+ `fix(forge): scrub template-seeded entries from releases.yml (0.50.1 migration)`.
83
+
84
+ ## Validation
85
+
86
+ - `grep -F 'CLAUDE.md @import extraction' .forge/releases.yml` → no matches.
87
+ - Every remaining `milestone:` in `.forge/releases.yml` names a milestone that
88
+ exists in this project's state, roadmap, or archive.
89
+ - The highest `version:` left in the registry (if any) is consistent with the
90
+ project's actual version in `package.json` (or equivalent).
91
+ - Re-running the Detection block prints nothing and exits 0.