forge-orkes 0.50.1 → 0.55.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. package/package.json +1 -1
  2. package/template/.claude/hooks/forge-reserve.sh +51 -15
  3. package/template/.claude/hooks/forge-slice-notify.sh +66 -0
  4. package/template/.claude/hooks/forge-slice-runner.sh +593 -0
  5. package/template/.claude/hooks/tests/verify-signoff.test.sh +122 -0
  6. package/template/.claude/hooks/tests/wu-done.test.sh +68 -0
  7. package/template/.claude/hooks/verify-signoff.sh +139 -0
  8. package/template/.claude/hooks/wu-done.sh +48 -0
  9. package/template/.claude/skills/chief-of-staff/SKILL.md +11 -3
  10. package/template/.claude/skills/deferred/SKILL.md +8 -4
  11. package/template/.claude/skills/discussing/SKILL.md +4 -2
  12. package/template/.claude/skills/executing/SKILL.md +21 -17
  13. package/template/.claude/skills/forge/SKILL.md +13 -10
  14. package/template/.claude/skills/planning/SKILL.md +3 -1
  15. package/template/.claude/skills/prototyping/SKILL.md +10 -7
  16. package/template/.claude/skills/quick-tasking/SKILL.md +3 -1
  17. package/template/.claude/skills/researching/SKILL.md +2 -0
  18. package/template/.claude/skills/reviewing/SKILL.md +7 -7
  19. package/template/.claude/skills/verifying/SKILL.md +8 -7
  20. package/template/.forge/FORGE.md +14 -7
  21. package/template/.forge/gitignore +8 -0
  22. package/template/.forge/migrations/0.52.0-id-reservation-milestone-refactor.md +45 -0
  23. package/template/.forge/templates/deferred-issue.md +22 -0
  24. package/template/.forge/templates/slice-runner/config.yml +62 -0
  25. package/template/.forge/templates/slice-runner/phase-report.schema.json +51 -0
  26. package/template/.forge/templates/slice-runner/phase-report.yml +58 -0
@@ -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,45 @@
1
+ # Migration Guide: forge-reserve covers milestone + refactor ids; DI-NNN → counter-free files (Forge 0.52.0)
2
+
3
+ Forge 0.52.0 closes the last scan-and-increment ID collisions across concurrent worktrees ([ADR-023](../../docs/decisions/ADR-023-extend-id-reservation-to-milestone-refactor-ids.md), extends [ADR-021](../../docs/decisions/ADR-021-id-reservation-ledger-git-common-dir.md)):
4
+
5
+ - **`forge-reserve` gains two kinds** — `milestone` (emits `M-NN`, unpadded) and `refactor` (emits `R0NN`, no hyphen). Both scan live **and archived** locations as the cold-ledger floor, so a compacted or archived id is never reallocated. The milestone-id sites (`forge` Rollup, `discussing`, `prototyping`) and the refactor-backlog site (`reviewing`) now call the helper instead of hand-scanning `max + 1`.
6
+ - **DI-NNN deferred-issue ids are eliminated.** Deferred issues become **counter-free per-issue files** under `.forge/deferred-issues/` named `{YYYY-MM-DD}-{milestone}-{slug}.md` — the same anti-collision pattern as `.forge/state/desire-paths/`. No shared counter means concurrent worktrees never collide.
7
+
8
+ **The `forge-reserve` change is zero-touch** — it takes effect the moment the framework files sync; the next milestone/refactor allocation just works. **The deferred-issues change is backward-compatible** — the reader skills still read a legacy single-file `.forge/deferred-issues.md` when the new directory is absent, so nothing breaks before you migrate.
9
+
10
+ ## What changed (no action needed for the helper)
11
+
12
+ - `forge-reserve <milestone|refactor> --milestone <id>` allocates through the same machine-local ledger + `reservations.yml` audit trail as the existing kinds. Id formats: `milestone` → `M-26` (hyphen, unpadded — ids are used bare); `refactor` → `R067` (no hyphen, zero-padded — matches the backlog).
13
+ - The in-tree scan for these kinds reads archives too: `milestone` = live `state/milestone-*.yml` ∪ `.forge/archive/milestone-*/`; `refactor` = `refactor-backlog.yml` ∪ `refactor-backlog-archive.yml`.
14
+ - `executing`/`quick-tasking` (writers) log a new per-issue file; `verifying`/`reviewing` (readers) glob `.forge/deferred-issues/*.md`, with the legacy single file as fallback.
15
+
16
+ ## Detection
17
+
18
+ Prints `MIGRATE` only if a legacy single-file `.forge/deferred-issues.md` is present (the DI-NNN layout the new skills supersede). Silent + exit 0 otherwise — including projects that never had deferred issues.
19
+
20
+ ```bash
21
+ [ -d .forge ] || exit 0 # not a Forge project
22
+ [ -f .forge/deferred-issues.md ] || exit 0 # no legacy file → nothing to migrate, silent
23
+ echo "MIGRATE — legacy .forge/deferred-issues.md present; split each issue into a counter-free file under .forge/deferred-issues/ ({date}-{milestone}-{slug}.md) and remove the old file (ADR-023). Safe to defer — the skills still read the legacy file until you do."
24
+ exit 0
25
+ ```
26
+
27
+ ## Migration steps (deferred issues only)
28
+
29
+ The `forge-reserve` kinds need no migration. For deferred issues, if Detection prints `MIGRATE`:
30
+
31
+ 1. For each entry in `.forge/deferred-issues.md` `issues:`, create `.forge/deferred-issues/{first_seen}-{milestone}-{slug}.md` by copying `.forge/templates/deferred-issue.md` and filling the frontmatter (`type`, `command`, `summary`, `first_seen`, `milestone`, `status`). `{slug}` is a short kebab of the summary. **Drop the `id: DI-NNN` field** — the filename is the id now.
32
+ 2. Delete `.forge/deferred-issues.md` once every entry has a file.
33
+ 3. Commit the split with your next state-sync.
34
+
35
+ This is **safe to defer**: until you split the file, the reader skills (`verifying`, `reviewing`) still load the legacy `deferred-issues.md`, so no deferred failure is lost or silently un-tracked.
36
+
37
+ ## Validation
38
+
39
+ ```bash
40
+ [ -x .claude/hooks/forge-reserve.sh ] && echo "OK: forge-reserve present" || echo "FAIL: helper missing"
41
+ m=$(.claude/hooks/forge-reserve.sh milestone --milestone m-smoke) && echo "OK: milestone $m" || echo "FAIL: milestone kind"
42
+ r=$(.claude/hooks/forge-reserve.sh refactor --milestone m-smoke) && echo "OK: refactor $r" || echo "FAIL: refactor kind"
43
+ # revert the smoke reservations: drop the last two reservations.yml blocks + their ledger lines
44
+ echo "validation complete"
45
+ ```
@@ -0,0 +1,22 @@
1
+ <!--
2
+ Deferred-issue file template (ADR-023). Copy to:
3
+ .forge/deferred-issues/{YYYY-MM-DD}-{milestone}-{slug}.md
4
+ ONE file per issue — the filename slug replaces the old DI-NNN counter. Because
5
+ each writer picks a distinct {date}-{milestone}-{slug} name, concurrent worktrees
6
+ never touch the same file, so there is no shared counter to collide on at merge
7
+ (the same anti-collision property as .forge/state/desire-paths/).
8
+
9
+ Legacy .forge/deferred-issues.md (single file, DI-NNN entries) is still READ by the
10
+ reader skills when this directory is absent — lazy migration, no data loss.
11
+
12
+ Frontmatter below is the per-issue record. Keep it terse; this is state, not a doc.
13
+ -->
14
+ type: test_failure # test_failure | lint_failure | build_failure
15
+ command: "" # the command that surfaced it, e.g. "npm test"
16
+ summary: "" # one line — what fails and why (feeds the verify report)
17
+ first_seen: "" # YYYY-MM-DD — also the filename date
18
+ milestone: "" # milestone id that logged it, e.g. m26
19
+ status: pending # pending | triaged | dismissed
20
+ # --- set by reviewing triage (optional; omit until triaged) ---
21
+ # triaged_at: "" # YYYY-MM-DD when accepted into the refactor backlog
22
+ # dismissed_reason: "" # why it was dismissed instead of tracked
@@ -0,0 +1,62 @@
1
+ # config.yml — forge-slice-runner.sh per-slice configuration.
2
+ #
3
+ # Flat key: value only (the runner parses it with grep/sed — no YAML lib, NFR-026).
4
+ # Everything here bounds ONE slice run; the runner holds no state of its own
5
+ # (Fork 3 / NFR-028). Do NOT add scheduler / daemon / queue / resident-state keys —
6
+ # if the runner ever needs authoritative state to work, that is a STOP-and-route-out
7
+ # signal, not a config addition.
8
+
9
+ # --- budget & bounds --------------------------------------------------------
10
+
11
+ # Static per-slice hard cap on spend. A breach parks the slice with a §8 `budget`
12
+ # ping (phase 47). Deliberately generous — the point is a runaway guard, not a
13
+ # tight leash. `claude -p --max-budget-usd` also bounds each phase individually.
14
+ per_slice_budget_usd: 25
15
+
16
+ # Max agent turns per headless phase (passed to `claude -p --max-turns`). Bounds a
17
+ # single phase; cross-platform (unlike a `timeout` wall-clock, which macOS lacks).
18
+ max_turns_per_phase: 60
19
+
20
+ # RESERVED — not yet enforced. Intended as an optional per-phase wall-clock cap in
21
+ # seconds (0 = off); the runner does not read this key yet (macOS has no stock
22
+ # `timeout`, so a portable guard is still to be built). Setting it is currently a
23
+ # no-op — left here so the config shape is stable for when the guard lands.
24
+ max_wall_clock_s_per_phase: 0
25
+
26
+ # --- divergence budget (phase-boundary check, plan-03) ----------------------
27
+
28
+ # The branch the slice integrates at each phase boundary. `origin/<branch>` is
29
+ # preferred when a remote has it (fetched first), else the local branch.
30
+ integration_branch: main
31
+
32
+ # At each phase boundary the runner refreshes the slice worktree from main and
33
+ # checks how far the slice has diverged. Exceeding either threshold parks the
34
+ # slice with a §8 `fork` ping. The merge-boundary half of this budget lives in
35
+ # step 2 (not here).
36
+ divergence_budget_commits: 40
37
+ divergence_budget_lines: 4000
38
+
39
+ # --- notification seam (phase 48) -------------------------------------------
40
+
41
+ # The channel the runner's `notify` seam dispatches §8 pings + the done ping over.
42
+ # `pushnotification` = the PushNotification agent tool via a thin `claude -p`
43
+ # micro-call (the only existing phone channel; smoke-tested 2026-07-15). Kept
44
+ # behind a swappable seam so phone-delivery follow-up can change the channel
45
+ # without touching the loop.
46
+ notify_channel: pushnotification
47
+
48
+ # --- model-by-phase-type map (STEP-5 SEAM — read, not routed) ---------------
49
+
50
+ # Read per phase-type and recorded into phase-report.yml usage.model, but NOT used
51
+ # to route the launch yet — this is the seam step 5 (per-phase model routing)
52
+ # fills. Leaving it here now keeps the report's `model` field populated and gives
53
+ # step 5 a config surface to switch on. The runner MUST NOT branch launch behavior
54
+ # on this map in this milestone.
55
+ model_by_phase_type:
56
+ researching: sonnet
57
+ discussing: sonnet
58
+ architecting: opus
59
+ planning: opus
60
+ executing: sonnet
61
+ verifying: haiku
62
+ reviewing: sonnet
@@ -0,0 +1,51 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "title": "forge-slice-runner phase report",
4
+ "description": "Machine-readable phase result the runner reads to decide the next action. JSON-Schema form of phase-report.yml (kept in lockstep). Passed to `claude -p --json-schema` so a headless phase's structured output mirrors the on-disk phase-report.yml it writes.",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["verdict", "interrupt", "interrupt_payload", "checks", "usage", "evidence"],
8
+ "properties": {
9
+ "verdict": {
10
+ "type": "string",
11
+ "enum": ["proceed", "park", "halt", "done"],
12
+ "description": "What the runner does next. proceed=advance; park=§8 interrupt, stop mid-slice; halt=failed own checks after retry, stop with evidence; done=last phase, emit done ping."
13
+ },
14
+ "interrupt": {
15
+ "type": "string",
16
+ "enum": ["none", "ambiguity", "irreversible", "fork", "budget"],
17
+ "description": "The §8 interrupt type when verdict==park, else none. Exactly the five-type surface."
18
+ },
19
+ "interrupt_payload": {
20
+ "type": "string",
21
+ "description": "Question text for ambiguity/irreversible/budget; a sketch/mockup PATH for a fork. Empty when interrupt==none."
22
+ },
23
+ "checks": {
24
+ "type": "object",
25
+ "additionalProperties": false,
26
+ "required": ["passed", "failed"],
27
+ "properties": {
28
+ "passed": { "type": "integer", "minimum": 0 },
29
+ "failed": { "type": "integer", "minimum": 0 }
30
+ },
31
+ "description": "Counts from the phase's must_haves.truths check: exit codes."
32
+ },
33
+ "usage": {
34
+ "type": "object",
35
+ "additionalProperties": false,
36
+ "required": ["tokens", "cost_usd", "model", "wall_clock_s", "retry_count"],
37
+ "properties": {
38
+ "tokens": { "type": "integer", "minimum": 0 },
39
+ "cost_usd": { "type": "number", "minimum": 0 },
40
+ "model": { "type": "string" },
41
+ "wall_clock_s": { "type": "number", "minimum": 0 },
42
+ "retry_count": { "type": "integer", "minimum": 0 }
43
+ },
44
+ "description": "Per-phase receipts. Feeds B5 instrumentation + step-5 spend seam."
45
+ },
46
+ "evidence": {
47
+ "type": "string",
48
+ "description": "Pointer to the fuller execution-summary.md / verification-report.md and key commit shas."
49
+ }
50
+ }
51
+ }
@@ -0,0 +1,58 @@
1
+ # phase-report.yml — the contract every headless phase writes and the runner reads.
2
+ #
3
+ # A headless phase (a fresh `claude -p` run driven by forge-slice-runner.sh) has
4
+ # no chat back-channel: executing's Rule-4-STOP, verifying's UNCERTAIN, and design
5
+ # forks must all become a MACHINE-READABLE interrupt the runner turns into a §8
6
+ # ping. This file is that machine-readable surface. The phase writes it to its
7
+ # work dir as its LAST action; the runner reads `verdict`+`interrupt` to decide
8
+ # what happens next, and `usage` feeds B5 instrumentation + step 5's spend seam.
9
+ #
10
+ # It LAYERS ON TOP of the fuller reports (execution-summary.md, verification-
11
+ # report.md) — it does not replace them; `evidence` points at those. Schema locked
12
+ # in context.md M29 (2026-07-15). Keep any change in lockstep with
13
+ # phase-report.schema.json (the --json-schema form) and assert-report-schema.sh.
14
+ #
15
+ # ---------------------------------------------------------------------------
16
+ # The example below is a valid, minimal "proceed" report. Copy + overwrite.
17
+
18
+ verdict: proceed
19
+ # verdict — REQUIRED. What the runner does next. One of:
20
+ # proceed — phase met its goal; advance to the next phase.
21
+ # park — phase hit a §8 interrupt and cannot continue unattended; the runner
22
+ # pings the operator and stops mid-slice. `interrupt` MUST be set.
23
+ # halt — phase failed its own checks after its one retry; the runner stops
24
+ # early on the slice-end channel with failure evidence attached.
25
+ # done — this was the last phase of the slice; the runner emits the done ping.
26
+
27
+ interrupt: none
28
+ # interrupt — REQUIRED. The §8 interrupt type when verdict==park, else `none`.
29
+ # Exactly the five-type surface (context.md M29 / brief §8), nothing else:
30
+ # none | ambiguity | irreversible | fork | budget
31
+
32
+ interrupt_payload: ""
33
+ # interrupt_payload — REQUIRED (may be empty string when interrupt==none).
34
+ # For ambiguity/irreversible/budget: the question text to put in the ping.
35
+ # For a `fork` (design decision): a PATH to a sketch/mockup the operator LOOKS AT —
36
+ # never prose describing a visual (brief §8: a fork arrives as something to see).
37
+
38
+ checks:
39
+ passed: 0
40
+ failed: 0
41
+ # checks — REQUIRED. Counts from the phase's own must_haves.truths `check:` exit
42
+ # codes (0 = passed). `failed > 0` with verdict==proceed is a contradiction the
43
+ # runner treats as halt-worthy.
44
+
45
+ usage:
46
+ tokens: 0
47
+ cost_usd: 0
48
+ model: ""
49
+ wall_clock_s: 0
50
+ retry_count: 0
51
+ # usage — REQUIRED. Per-phase receipts. `retry_count` is how many fresh-context
52
+ # retries this phase took (0 = passed first try, max 1 under the halt-after-one
53
+ # policy). Feeds B5 instrumentation and step 5's spend-attribution seam. `model`
54
+ # is read from config per phase-type but NOT routed yet (the step-5 seam).
55
+
56
+ evidence: ""
57
+ # evidence — REQUIRED (may be empty). A pointer to the fuller reports and key
58
+ # commit shas produced this phase, e.g. "execution-summary.md; commits abc123,def456".