forge-orkes 0.79.0 → 0.80.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 (25) hide show
  1. package/package.json +1 -1
  2. package/template/.claude/hooks/forge-context-migrate.sh +297 -0
  3. package/template/.claude/hooks/forge-size-gate.sh +175 -0
  4. package/template/.claude/hooks/forge-state-rollup.sh +135 -2
  5. package/template/.claude/hooks/skill-gate-tiers.txt +6 -0
  6. package/template/.claude/hooks/tests/forge-context-migrate.test.sh +166 -0
  7. package/template/.claude/hooks/tests/forge-size-gate.test.sh +132 -0
  8. package/template/.claude/settings.json +5 -1
  9. package/template/.claude/skills/discussing/SKILL.md +30 -31
  10. package/template/.claude/skills/executing/SKILL.md +3 -3
  11. package/template/.claude/skills/forge/SKILL.md +112 -170
  12. package/template/.claude/skills/forge/references/manual-fallback-procedures.md +54 -0
  13. package/template/.claude/skills/planning/SKILL.md +11 -9
  14. package/template/.claude/skills/researching/SKILL.md +2 -0
  15. package/template/.claude/skills/verifying/SKILL.md +2 -2
  16. package/template/.forge/FORGE.md +79 -91
  17. package/template/.forge/checks/forge-jarvis-awareness.sh +46 -2
  18. package/template/.forge/checks/forge-jarvis-relay.sh +64 -16
  19. package/template/.forge/checks/tests/forge-jarvis-awareness.test.sh +58 -0
  20. package/template/.forge/checks/tests/forge-jarvis-relay.test.sh +50 -0
  21. package/template/.forge/migrations/0.80.0-context-md-sharding.md +158 -0
  22. package/template/.forge/migrations/0.80.0-size-gate-wiring.md +145 -0
  23. package/template/.forge/templates/constitution.md +1 -1
  24. package/template/.forge/templates/context-milestone.md +30 -0
  25. package/template/.forge/templates/slice-runner/config.yml +7 -1
@@ -9,9 +9,7 @@ Entry point. Detect tier, route skills, manage transitions. New projects → ini
9
9
 
10
10
  ## Step 1: Read State
11
11
 
12
- **Preflight (multi-tree visibility) — run before everything in this section.**
13
-
14
- The presence of live Forge worktrees changes the meaning of "which milestone am I editing" — the operator needs to see the picture before any state edit. Cheap to compute, free if there's nothing to show.
12
+ **Preflight (multi-tree visibility) — run before everything in this section.** Live Forge worktrees change what "editing this milestone" means, so surface them before any state edit. Cheap to compute; free if nothing to show.
15
13
 
16
14
  ```bash
17
15
  # This session's checkout — main or a worktree? --git-dir == --git-common-dir → main; differ → linked worktree.
@@ -28,70 +26,53 @@ git worktree list --porcelain 2>/dev/null \
28
26
  ```
29
27
 
30
28
  **If `in_main_checkout: true`** AND any Forge worktrees are reported:
31
- - Surface them at the top of the boot output, one line each: *"m{id} is live in worktree `{path}` on branch `{branch}`. Its milestone state is **owned there** — selecting it from main triggers the Live-Worktree Ownership Gate (1.1a). Shared-doc edits here (`context.md`, `refactor-backlog.yml`) won't be visible to it until it rebases or pulls."*
32
- - Surfacing continues to Rollup + selection — but it is **no longer purely informational**. Enforcement is deferred to **1.1a**, which fires only for the *selected* milestone (booting from main to work an unrelated milestone stays free). Record which worktrees were reported here so 1.1a can match the selection against them. (See FORGE.md State Ownership for the full taxonomy.)
33
-
34
- **If `in_main_checkout: false`** (this session is itself a worktree) AND `forge.worktree_rebase_check: true` in `.forge/project.yml` (default `false` — opt-in):
35
- - Diff this worktree's `HEAD` against `origin/main` (or `main` if no remote) across **two** surfaces:
36
- - **(a) shared `.forge/` state** — the **shared-mutable** + **stable-shared** files per the FORGE.md taxonomy: `context.md`, `refactor-backlog.yml`, `project.yml`, `constitution.md`, `design-system.md`, `roadmap.yml`, `requirements/m{N}.yml`, `FORGE.md`.
37
- - **(b) framework surface** — `.claude/skills/`, `.claude/agents/`, `.forge/FORGE.md` — the files a `/upgrading` run changes. This catches a **framework upgrade that landed on main after this worktree branched**: `upgrading` Step 8's producer-side offer is the primary path, but a worktree created (or forgotten) since the upgrade only finds out here. (Gitignored framework files — `.mcp.json`, experimental hooks — won't show in the diff; they propagate via the experimental installer, not git.)
38
- - Any differ → surface: *"`{file}` has changed on main since this worktree branched. Pull just this file (`git restore --source main -- {file}`), rebase, or skip?"* — operator decides. For a framework-surface (b) hit, lead with rebase (a partial restore of skills/agents is rarely what you want).
39
- - Setting absent or `false` → no-op.
40
-
41
- **Integration flag check (consumer — worktree only).** When `in_main_checkout: false`, check whether a checkpoint publish has landed work on main that this worktree should pull (FORGE.md → Stream Integration Checkpoints). Event-driven, not polled — read the flag first; act only if it is set:
29
+ - Surface each at the top of boot: *"m{id} is live in worktree `{path}` on branch `{branch}`. Its milestone state is **owned there** — selecting it from main triggers the Live-Worktree Ownership Gate (1.1a). Shared-doc edits here (`context.md`, `refactor-backlog.yml`) won't be visible to it until it rebases or pulls."*
30
+ - Not purely informational enforcement defers to **1.1a**, which fires only for the *selected* milestone. Record which worktrees were reported so 1.1a can match against them. (FORGE.md State Ownership.)
42
31
 
43
- - Read `.git/forge/integration-pending` (in the git common dir — set by a producer's checkpoint publish, shared across this machine's worktrees). Absent, or its main sha already seen by this worktree → **skip** (no fetch, no scan). This is the cheap gate: no flag → no work.
44
- - Flag set with an unseen sha confirm against the ref: `behind=$(git rev-list --count HEAD..main 2>/dev/null)` (one targeted `git fetch origin main` first if a remote and the local ref is stale). `behind` is 0 mark seen, skip.
45
- - `behind > 0` and the worktree has **no divergent local commits** (`git rev-list --count main..HEAD` is 0) **fast-forward automatically**: `git merge --ff-only main`. A fast-forward is conflict-free by definition, so do it without asking; report *"pulled {N} commit(s) from main (checkpoint integration)."* Mark the sha seen.
46
- - `behind > 0` **and** the worktree has diverged **surface, do not auto-rebase**: *"main advanced on {surfaces} since this worktree branched — rebase onto main? (`git rebase main`)"* — rebase can conflict; the operator decides.
32
+ **If `in_main_checkout: false`** (this session is a worktree) AND `forge.worktree_rebase_check: true` in `project.yml` (default `false`):
33
+ - Diff `HEAD` against `origin/main`/`main` across two surfaces: **(a)** shared `.forge/` state `context.md`, `refactor-backlog.yml`, `project.yml`, `constitution.md`, `design-system.md`, `roadmap.yml`, `requirements/m{N}.yml`, `FORGE.md`; **(b)** framework surface `.claude/skills/`, `.claude/agents/`, `.forge/FORGE.md`, the files `/upgrading` changes (catches a framework upgrade landed since this worktree branched; gitignored framework files like `.mcp.json` propagate via the experimental installer instead).
34
+ - Any differ → *"`{file}` has changed on main since this worktree branched. Pull just this file (`git restore --source main -- {file}`), rebase, or skip?"* lead with rebase for a (b) hit.
35
+ - Absent/`false` → no-op.
47
36
 
48
- **Integration flag check (consumer — main checkout).** When `in_main_checkout: true`, the same flag tells the operator's **canonical** checkout that a checkpoint pushed work to `origin/main` it hasn't pulled the other half of the producer's strand-safe publish (FORGE.md → Stream Integration Checkpoints; forge#13). Without this, local main sits behind origin after every checkpoint and the next local commit silently diverges it. Same event-driven gate:
37
+ **Integration flag check (worktree).** When `in_main_checkout: false`, a checkpoint publish may have landed work on main this worktree should pull (FORGE.md → Stream Integration Checkpoints). Event-driven:
38
+ - Read `.git/forge/integration-pending`. Absent/already-seen sha → **skip**.
39
+ - Unseen sha → `behind=$(git rev-list --count HEAD..main 2>/dev/null)` (fetch first if stale). 0 → mark seen, skip.
40
+ - `behind > 0`, no divergent local commits → **fast-forward automatically**: `git merge --ff-only main`; report *"pulled {N} commit(s) from main (checkpoint integration)."*
41
+ - `behind > 0` and diverged → **surface, never auto-rebase**: *"main advanced on {surfaces} since this worktree branched — rebase onto main? (`git rebase main`)"*
49
42
 
50
- - Read `.git/forge/integration-pending`. Absent, or its sha already seen by this checkout **skip** (no fetch). No flag no work.
51
- - Flag set with an unseen sha → `git fetch origin main` (if a remote), then `behind=$(git rev-list --count main..origin/main 2>/dev/null)` (local `main` vs the freshly-fetched `origin/main`). `behind` is 0 mark seen, skip.
52
- - `behind > 0` and local main has **not** diverged (`git rev-list --count origin/main..main` is 0) → **fast-forward automatically** (the producer's strand guard guarantees this is a true ff): `git merge --ff-only origin/main` on `main`. Report *"fast-forwarded local main +{N} from origin (checkpoint integration)."* Mark seen.
53
- - `behind > 0` **and** local main has diverged → **surface, never auto-merge**: *"local main and origin/main have diverged ({A} local / {B} remote commits) — this shouldn't happen if checkpoints used the strand guard; reconcile with `git fetch` then `git merge origin/main` (inspect first). Do NOT `git pull --ff-only` (it fails on divergence)."* The operator integrates.
43
+ **Integration flag check (main checkout).** Same flag, other direction: tells canonical main a checkpoint pushed to `origin/main` it hasn't pulled (forge#13) without this, main silently diverges on the next commit:
44
+ - Read the flag. Absent/seen → skip.
45
+ - Unseen `git fetch origin main`, `behind=$(git rev-list --count main..origin/main 2>/dev/null)`. 0 mark seen, skip.
46
+ - `behind > 0`, not diverged → **fast-forward automatically**: `git merge --ff-only origin/main`. Report *"fast-forwarded local main +{N} from origin (checkpoint integration)."*
47
+ - `behind > 0` and diverged → **surface, never auto-merge**: *"local main and origin/main have diverged ({A} local / {B} remote commits) — this shouldn't happen if checkpoints used the strand guard; reconcile with `git fetch` then `git merge origin/main` (inspect first). Do NOT `git pull --ff-only` (it fails on divergence)."*
54
48
 
55
- Cross-machine note: the flag file is machine-local, so a worktree (or the primary checkout) on another laptop relies on the opt-in `forge.worktree_rebase_check` / a periodic fetch instead — eventual, not instant.
49
+ Cross-machine: the flag is machine-local another laptop relies on `forge.worktree_rebase_check` or a periodic fetch instead.
56
50
 
57
- **Worktree path convention check (advisory — main or worktree).** A recorded worktree path is trusted *verbatim* everywhere (FORGE.md → "Recorded path is authoritative"), so a path created **outside** the convention a manual `git worktree add ../forge-worktrees/m115`, or pre-convention state — is honored unchallenged by the 1.1a gate and by `chief-of-staff` adoption. This check surfaces that drift. It never blocks and never moves anything.
51
+ **Worktree path convention check (advisory).** A recorded worktree path is trusted verbatim everywhere (FORGE.md → "Recorded path is authoritative"), so drift outside the convention goes unchallenged elsewhere; this check surfaces it, never blocks or moves anything.
52
+ - Resolve the root: `orchestration.worktree_root` (relative → repo root; absolute/`~` verbatim); unset → `{repo-parent}/{repo-basename}-worktrees`.
53
+ - For each recorded path in play this boot, compare its parent dir to the root.
54
+ - Mismatch → *"recorded worktree path `{path}` for m{id} is not under the convention root `{root}` — likely a manual `git worktree add` or pre-convention state. Fix: `git worktree move {path} {root}/{anchor}` then update the recorded path."*
55
+ - Under the root, or no recorded path → silent.
58
56
 
59
- - Resolve the convention root: `orchestration.worktree_root` from `project.yml` (relative against the **repo root**; absolute / leading-`~` honored verbatim); if unset, `{repo-parent}/{repo-basename}-worktrees`.
60
- - For each recorded worktree path in play this boot `lifecycle.worktree_path` of any surfaced live milestone, plus any stream `runtime.worktree` compare its **parent dir** to the resolved root.
61
- - Mismatch one advisory line: *"recorded worktree path `{path}` for m{id} is not under the convention root `{root}` likely a manual `git worktree add` or pre-convention state. Fix: `git worktree move {path} {root}/{anchor}` then update the recorded path. (Advisory; honors `orchestration.worktree_root`.)"*
62
- - Parent dir under the root, or no recorded path → silent. An explicit `orchestration.worktree_root` is honored by construction (the override **is** the root compared against), so a deliberately relocated root never false-warns.
57
+ **ID reservation preflight (advisory).** The cross-worktree ID Reservation Protocol only protects numbers `.forge/reservations.yml` actually carries:
58
+ - **Absent registry, IDs exist.** Missing `reservations.yml` but IDs already allocated (`FR-`/`NFR-`/`DEF-` in `requirements/*.yml`, or `ADR-NNN` in `.forge/decisions/`) nudge once: *"No `reservations.yml` yet, but this project already allocates IDs. Concurrent worktrees will collide on the next number. Reserve your next ID via the protocol, or backfill existing IDs now."*
59
+ - **Backfill scan.** With the registry present, any landed ID missing a reservation entry list once: *"{N} ID(s) are in the tree but not in `reservations.yml` ({ids}) still respected (next = max+1), but backfilling keeps the registry the single audit trail."* Surface only, never auto-write.
63
60
 
64
- **ID reservation preflight (advisory main or worktree).** The cross-worktree ID Reservation Protocol (FORGE.md, ADR-016) only protects numbers if `.forge/reservations.yml` actually carries them. Two cheap checks, both advisory never block:
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
- - **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.
61
+ **Stream Rollup (derived, rendered by the same hook).** `.forge/streams/active.yml` regenerates from stream files + active milestones by the same **`forge-state-rollup.sh`** hook (milestone rollup first, then the join). Includes the **step-0 auto-close sweep** (FORGE.md → Stream Rollup): a milestone-backed stream whose milestone is `complete`, not yet `closed`, with no live worktree auto-closes (`status: closed`, `merge.readiness: merged`) before the join, main-checkout-only. (A `complete` milestone with a still-live worktree is left for the finalize path in 1.1a, not swept.) **READ the cache; never hand-edit or regenerate by hand.** Git-ignored render-on-read cache since 0.53.0. **Fallback:** no `[forge-state-rollup]` summary → run `.claude/hooks/forge-state-rollup.sh` yourself once; executable absent → `references/manual-fallback-procedures.md`'s Stream Rollup section.
67
62
 
68
- **Stream Rollup (active.yml is derived — rendered by the same hook).** `.forge/streams/active.yml` is regenerated from the stream files + active milestones by the **`forge-state-rollup.sh` SessionStart hook**, in the same mechanical pass that renders `index.yml` (the hook runs the milestone rollup first, then the join — so the Stream Rollup reads a fresh `index.yml` for implicit-row coverage). This includes the **step-0 completed-stream auto-close sweep** (FORGE.md → Stream Rollup): a milestone-backed stream whose milestone is now `complete`, not already `closed`, with no live worktree, is auto-closed (`status: closed`, `merge.readiness: merged`) *before* the join — the hook runs this **only in the main checkout** (it edits committed stream files), and announces each on stderr: *"auto-closed stale stream {id} — milestone 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 derivedREAD the hook's freshly-rendered cache; never hand-edit it, and do not regenerate it by hand.** Since 0.53.0 it is a **git-ignored render-on-read cache** (never committed); the hook renders it in any checkout. **Fallback:** if no `[forge-state-rollup]` boot summary appeared (older CLI / non-Claude-Code adapter / unwired settings), run `.claude/hooks/forge-state-rollup.sh` yourself once (idempotent), then read it; only if the executable is absent, roll up by hand per FORGE.md → Stream Rollup. No `streams/` dir → the hook skips it.
69
-
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.
63
+ **Ready-to-merge nudge.** From the swept `active.yml`, streams with `coordination: ready` or a non-empty `merge_queue` not auto-publishing via checkpoints *"{N} stream(s) ready to mergerun `/chief-of-staff` (merge safe) to integrate."* Pointer only cadence logic lives in the Chief's Merge Cadence Check.
71
64
 
72
65
  ### 1.0 State Rollup (index.yml is derived — rendered mechanically at boot)
73
66
 
74
- `index.yml` is a **derived registry**, regenerated from the milestone files (plus each row's git-computed `release_state`) by the **`forge-state-rollup.sh` SessionStart hook** — mechanically, at every session start, **before** this skill runs. **Never hand-edit `index.yml`.** Since 0.53.0 it is a **git-ignored render-on-read cache** (never committed): rendering it is a local-cache write, so any session may render its own copy (see FORGE.md → State Ownership, Derived class).
75
-
76
- **The hook is the rollup; this skill only READS the freshly-rendered cache.** Making the rollup an executable invoked by a hook — not prose the agent performs — is the fix for issue #22 (pain-6: advisory rules fail). A boot session used to read a stale cache and reconstruct truth by hand when project memory carried an "index is stale, work around it" habit; a mechanical hook removes that trust dependency. Do **not** regenerate `index.yml` by hand — the hook already did. Look for its one-line boot summary (`[forge-state-rollup] rendered index.yml …`) in context; then read the cache.
67
+ `index.yml` is a **derived registry**, regenerated from the milestone files (plus each row's git-computed `release_state`) by the **`forge-state-rollup.sh` SessionStart hook** — mechanically, before this skill runs. **Never hand-edit it.** Since 0.53.0 it's a **git-ignored render-on-read cache** (never committed) any session may render its own copy (FORGE.md → State Ownership, Derived class).
77
68
 
78
- **Fallback only when the hook did NOT run.** On an older Claude Code CLI without SessionStart, a non-Claude-Code adapter, or a repo whose `.claude/settings.json` lacks the SessionStart wiring, no summary line appears. Only then run the executable yourself once — `.claude/hooks/forge-state-rollup.sh` (add `--project {worktree_path}` when the recorded path differs from cwd) — and read its output. It is deterministic + idempotent, so running it when the hook already ran is a harmless no-op. If the executable itself is absent (pre-0.75.0 install), fall back to the manual procedure below.
69
+ **The hook is the rollup; this skill only READS the freshly-rendered cache** fix for issue #22 (pain-6: advisory rules fail; a boot session used to reconstruct a stale cache by hand). Do **not** regenerate `index.yml` by hand. Look for its boot summary (`[forge-state-rollup] rendered index.yml …`); then read the cache.
79
70
 
80
- **Manual procedure (executable absent only — the executable's faithful spec):**
81
- 1. Glob `.forge/state/milestone-*.yml`.
82
- 2. For each, read `milestone.id`, `milestone.name`, `current.status`, `lifecycle.{deferred_at, resumed_at}`, and the last-touched date as `current.last_updated` → else legacy `progress.last_update` → else null. (The fallback keeps pre-0.19.0 milestone files self-sourcing without editing them; active milestones gain `current.last_updated` on their next transition.)
83
- 3. Derive the registry `status`:
84
- - **deferred** — `lifecycle.deferred_at` set and not superseded by a later `resumed_at`
85
- - **complete** — `current.status == complete`
86
- - **not_started** — `current.status == not_started`
87
- - **active** — otherwise
88
- 4. Render each row's `release_state`, then rewrite `index.yml` `milestones:` (sorted by id) as `{id, name, status, last_updated, release_state}` (date from step 2's fallback). No other keys.
89
- - **Fold is the source.** Run `.claude/hooks/forge-release-fold.sh m-{id}` per row and write its stdout `release_state=` value verbatim (`unmerged|merged|validated`) — agent prose never re-derives `release_state`; the fold binary is the only source.
90
- - **Degrade, don't block.** Hook missing or exit 2 → write `release_state: unknown`, emit ONE advisory line — *"release fold unavailable for m-{id} — release_state: unknown"* — and continue. A broken fold must never break boot.
71
+ **Fallback only when the hook did NOT run.** On an older Claude Code CLI without SessionStart, a non-Claude-Code adapter, or a repo whose `.claude/settings.json` lacks the SessionStart wiring, no summary line appears. Only then run the executable yourself once`.claude/hooks/forge-state-rollup.sh` (add `--project {worktree_path}` when the recorded path differs from cwd) — and read its output. It is deterministic + idempotent, so running it when the hook already ran is a harmless no-op. **If the executable itself is absent** (pre-0.75.0 install; `[ -x .claude/hooks/forge-state-rollup.sh ]` fails) → load `.claude/skills/forge/references/manual-fallback-procedures.md` and follow its State Rollup section before continuing.
91
72
 
92
73
  Output is a pure function of the milestone files, so two sessions regenerating it produce identical bytes — it never needs a hand-merge. **The `forge-state-rollup.sh` hook renders the cache in any checkout (main OR worktree — it is a gitignored local write); worktree agents still never write the *committed* derived-source state** (they edit only their own `milestone-{id}.yml`; the Stream Rollup's step-0 sweep, which edits committed stream files, is main-checkout-only inside the hook).
93
74
 
94
- **"Run Rollup (1.0)" (used below after a milestone CRUD) = run the executable.** The SessionStart hook fires only at boot, so after a mid-session promote/defer/resume/delete you must re-render the caches yourself: run `.claude/hooks/forge-state-rollup.sh` (it does both the milestone rollup **and** the Stream Rollup in one idempotent pass; add `--project {worktree_path}` if the recorded path differs from cwd), then read the result. This replaces the old "hand-regenerate `index.yml`" step everywhere the phrase appears. Executable absent (pre-0.75.0) → fall back to the manual procedure above.
75
+ **"Run Rollup (1.0)" (used below after a milestone CRUD) = run the executable.** The SessionStart hook fires only at boot, so after a mid-session promote/defer/resume/delete you must re-render the caches yourself: run `.claude/hooks/forge-state-rollup.sh` (it does both the milestone rollup **and** the Stream Rollup in one idempotent pass; add `--project {worktree_path}` if the recorded path differs from cwd), then read the result. Executable absent (pre-0.75.0) → `references/manual-fallback-procedures.md`.
95
76
 
96
77
  ### 1.1 Milestone Selection
97
78
 
@@ -101,73 +82,67 @@ Check state files:
101
82
  3. Neither → init/tier detection.
102
83
 
103
84
  **With `state/index.yml`:**
104
- 1. **Read the freshly-rendered `index.yml`** (the SessionStart hook already regenerated it this boot — §1.0) for active milestones. No `[forge-state-rollup]` summary appeared → run the executable once first (§1.0 fallback), then read.
105
- 2. **Check arg first.** `/forge 2` or `/forge "Auth system"`:
106
- - Match IDs (exact) or names (case-insensitive substring)
107
- - Found auto-select: *"Resuming {id}: [{name}] -- {current.status}, {percent}%"*
108
- - No match *"No match for '{arg}'. Active:"* (an explicit-but-unmatched arg does **not** fall through to the branch anchor honor the no-match prompt)
109
- 3. **Branch anchor (worktree milestone).** Only when **no arg** was given. The worktree's git branch is a deterministic milestone signal — consult it *before* the timestamp default so a worktree resolves to *its own* milestone, not merely the most-recently-touched one. Run `git branch --show-current`, then match in priority order:
110
- - **Exact** branch equals any milestone's `lifecycle.worktree_branch` (a Forge-managed worktree from `orchestrating`).
111
- - **Id token** — branch name contains a milestone id as a **prefixed**, delimited token: `m-{id}` or `milestone-{id}`, bounded by `/`, `-`, `_`, or start/end of string (e.g. `feat/m-RT01-pipeline-observability` → `m-RT01`). **Never match a bare number** — `feat/fix-issue-2` must not anchor to milestone `2`. Forge controls both milestone ids and worktree branch names, so the prefix is always available. Ambiguous (≥2 distinct milestone ids in the branch name) → no anchor; fall through to step 4.
112
- - Matched milestone is `active`/`not_started` → **hard-select it** (skip the timestamp default): *"Anchored to {id}: [{name}] from worktree branch `{branch}` -- {current.status}, {percent}%."* → go to step 7.
113
- - Matched milestone is `deferred`/`complete` → **do not auto-resume.** Surface and ask: *"Worktree branch `{branch}` points at {id}: [{name}], but it's {status}. Resume it, or pick another?"* → fall through to step 4 (never silently reactivate frozen/finished work).
85
+ 1. **Read the freshly-rendered `index.yml`** (hook already regenerated it this boot — §1.0) for active milestones. No `[forge-state-rollup]` summary → run the executable once first (§1.0 fallback), then read.
86
+ 2. **Check arg first.** `/forge 2` or `/forge "Auth system"`: match IDs (exact) or names (case-insensitive substring). Found → auto-select: *"Resuming {id}: [{name}] -- {current.status}, {percent}%"*. No match → *"No match for '{arg}'. Active:"* (does **not** fall through to the branch anchor).
87
+ 3. **Branch anchor (worktree → milestone), only when no arg was given.** Consult the git branch *before* the timestamp default so a worktree resolves to its own milestone. Run `git branch --show-current`, match in priority order:
88
+ - **Exact** branch equals a milestone's `lifecycle.worktree_branch` (Forge-managed, from `orchestrating`).
89
+ - **Id token** branch contains a milestone id as a **prefixed**, delimited token: `m-{id}`/`milestone-{id}`, bounded by `/`, `-`, `_`, or start/end (e.g. `feat/m-RT01-pipeline-observability` `m-RT01`). **Never match a bare number** (`feat/fix-issue-2` milestone `2`). Ambiguous (≥2 ids in the name) → no anchor, fall through to step 4.
90
+ - `active`/`not_started` match → **hard-select** (skip timestamp default): *"Anchored to {id}: [{name}] from worktree branch `{branch}` -- {current.status}, {percent}%."* step 7.
91
+ - `deferred`/`complete` match → **do not auto-resume**: *"Worktree branch `{branch}` points at {id}: [{name}], but it's {status}. Resume it, or pick another?"* → fall through to step 4.
114
92
  - No branch, detached HEAD, or no match → fall through to step 4.
115
- 4. **Multiple (no arg, no anchor):** Show active + not_started milestones with status + `last_updated`. Default most recent. After main list, show **Deferred:** section (id, name, frozen status `was: {current.status}`, defer date, reason). *"{N} active. Most recent: [{name}] ({current.status}, {date}). This one, or switch?"*
93
+ 4. **Multiple (no arg, no anchor):** Show active + not_started with status + `last_updated`, default most recent, then a **Deferred:** section (id, name, `was: {current.status}`, defer date, reason). *"{N} active. Most recent: [{name}] ({current.status}, {date}). This one, or switch?"*
116
94
  5. **One:** Auto-select. *"Resuming: [{name}] -- {current.status}, {percent}%"*
117
- 6. **None active:** If deferred exist, mention them: *"No active milestones. {N} deferred — 'resume milestone {id}' to reactivate."* Else init or create.
118
- 7. Load `milestone-{id}.yml`, then run the **Live-Worktree Ownership Gate (1.1a)** — it can block routing before any milestone-owned write.
119
- 8. **Route on `current.status`, NOT `{percent}`.** Complete only at `complete`. 100% phases ≠ done -- verifying + reviewing must run.
120
- 9. Report + **immediately route** (Step 3). Show: `current.status`, phase labels (Executed/Verified/Pending/In progress -- **never "Complete" for unverified**), `{percent}`. **No menus.** Position → action → invoke.
95
+ 6. **None active:** deferred exist *"No active milestones. {N} deferred — 'resume milestone {id}' to reactivate."*; else init or create.
96
+ 7. Load `milestone-{id}.yml`, run the **Live-Worktree Ownership Gate (1.1a)** — can block routing before any milestone-owned write.
97
+ 8. **Route on `current.status`, NOT `{percent}`.** Complete only at `complete`; 100% phases ≠ done verifying + reviewing must run.
98
+ 9. Report + **immediately route** (Step 3): `current.status`, phase labels (Executed/Verified/Pending/In progress **never "Complete" for unverified**), `{percent}`. **No menus.** Position → action → invoke.
121
99
 
122
- **`{percent}` is derived on read — never stored.** Compute it, don't look it up:
123
- - `total` = number of phases listed for this milestone in `.forge/roadmap.yml` (the milestone's `phases:` list length).
124
- - `done` = `current.status == complete` → `total`; else `max(0, current.phase - 1)` (phases before the active one count as done).
125
- - `{percent}` = `total > 0` → `round(done / total * 100)`; `total` unknown/0 → omit the percent from the line.
100
+ **`{percent}` is derived on read — never stored:**
101
+ - `total` = phases listed for this milestone in `.forge/roadmap.yml`.
102
+ - `done` = `complete` → `total`; else `max(0, current.phase - 1)`.
103
+ - `{percent}` = `total > 0` → `round(done / total * 100)`; else omit.
126
104
 
127
- Uses only signals that stay current (`current.phase`, `current.status`, the roadmap phase list). Old milestone files may still carry a vestigial `progress:` block — ignore it; derive anyway.
105
+ Old milestone files may carry a vestigial `progress:` block — ignore it, derive anyway.
128
106
 
129
- Beads enabled (`forge.beads_integration: true`) → `bd prime`. Optional.
107
+ Beads enabled → `bd prime` (optional).
130
108
 
131
- Notion board adapter (opt-in): `board.enabled: true` in `project.yml` **and** a Notion MCP server present → log *"board render (attended)"* and, **after the State + Stream rollups**, run the board **render** — the `notion-integration` skill's `render()` (`forge-board-render.sh --emit`, then upsert agent-owned props by `Forge ID` over the session MCP) then `pull_inbox()` (the backlog inbox): if the board holds Forge-ID-less rows, surface ONE advisory line — *"inbox has N items, top: {X} — take one in?"* ordered by the operator's `Priority`/`Queue Position`. This rides the **existing** intake moment (no new ceremony — the Gate-6 named risk); taking an item in is the normal act (`EnterWorktree` + milestone declaration, Step 3), and nothing materializes silently (M21). Config without MCP → log *"board adapter inactive (MCP absent)"*; neither → silent. When active, `render()` also fires at each phase **handoff** (the State Commit Protocol checkpoint) to push agent-owned state to the board. **Advisory — never blocks boot, handoff, or commit** (FR-175): an emit/render failure logs one line and boot continues. (The unattended render is `forge-board-render.sh --deliver` under post-merge CI — R11, not a boot concern.)
109
+ Notion board adapter (opt-in): `board.enabled: true` **and** a Notion MCP server present → log *"board render (attended)"*, after the State + Stream rollups run the board **render** (`notion-integration`'s `render()` `forge-board-render.sh --emit`, then upsert agent-owned props by `Forge ID` over the session MCP), then `pull_inbox()`: Forge-ID-less rows *"inbox has N items, top: {X} — take one in?"* (ordered by `Priority`/`Queue Position`). Rides the existing intake moment; taking an item in is the normal act (`EnterWorktree` + milestone declaration, Step 3) nothing materializes silently (M21). Config without MCP → log *"board adapter inactive (MCP absent)"*; neither → silent. `render()` also fires at each phase **handoff** to push agent-owned state to the board. **Advisory — never blocks** (FR-175): a failure logs one line, boot continues.
132
110
 
133
- Read `.forge/context.md`. **Needs Resolution** unchecked → warn: *"{N} unresolved discrepancies."* Quick proceeds; Standard/Full blocked at planning.
111
+ Read `.forge/context.md`. **Needs Resolution** unchecked → warn *"{N} unresolved discrepancies."* Quick proceeds; Standard/Full blocked at planning.
134
112
 
135
113
  ### 1.1a Live-Worktree Ownership Gate (enforced)
136
114
 
137
- Single-owner-mutable state must be written by exactly one checkout. When this session is on **main** and the **selected** milestone has a **live worktree**, writing its state from here forks the cursor — main records one `current.status`, the worktree another, and a later resume anchored to the worktree silently contradicts main's claim. FORGE.md State Ownership forbids this; this gate is where the entrypoint **enforces** it, before routing to any skill that writes milestone-owned files. (See FORGE.md → State Ownership.)
115
+ Single-owner-mutable state must be written by exactly one checkout. When this session is on **main** and the **selected** milestone has a **live worktree**, writing its state from here forks the cursor — main records one `current.status`, the worktree another, and a later resume anchored to the worktree silently contradicts main's claim. FORGE.md State Ownership forbids this; this gate **enforces** it before routing to any skill that writes milestone-owned files. (FORGE.md → State Ownership.)
138
116
 
139
- **Trigger.** `in_main_checkout: true` (from the Step 1 preflight) **AND** the selected milestone has a live worktree — any of:
140
- - the preflight's `git worktree list` reported a `forge/m-{id}-*` / `forge/{id}` branch for this milestone; or
141
- - `milestone-{id}.yml` `lifecycle.worktree_mode` is `active` or `degraded` and a `lifecycle.worktree_path` is recorded; or
117
+ **Trigger.** `in_main_checkout: true` (Step 1 preflight) **AND** the selected milestone has a live worktree — any of:
118
+ - the preflight's `git worktree list` reported a `forge/m-{id}-*`/`forge/{id}` branch for this milestone; or
119
+ - `milestone-{id}.yml` `lifecycle.worktree_mode` is `active`/`degraded` with a `lifecycle.worktree_path` recorded; or
142
120
  - `.forge/streams/active.yml` shows a stream for this milestone with a `runtime.worktree`.
143
121
 
144
- Resolve the worktree path from the recorded `lifecycle.worktree_path` / `runtime.worktree`, or — if absent — from the base **Worktree Convention** (FORGE.md).
122
+ Resolve the worktree path from the recorded `lifecycle.worktree_path`/`runtime.worktree`, or the base **Worktree Convention** (FORGE.md) if absent.
145
123
 
146
- **Completed milestone + live worktree → finalize, not gate (precedence).** The gate assumes the live worktree holds *in-progress, owned* work. A worktree whose milestone file is already `current.status: complete` it set complete and merged to main, then a fresh main boot lands here — needs the **opposite** of a write-block: it needs wrapping up. So when the selected milestone is `complete` **and** still has a live worktree, do **not** raise the four-option ownership gate. Surface the finalize offer instead: *"m{id} is complete but its worktree `{path}` (`{branch}`) is still live — roll up `index.yml` and tear down the worktree + branch? [yes / keep]"*.
147
- - **yes** → confirm, then `git worktree remove {path}`, delete the branch, clear `lifecycle.worktree_*` on the milestone, and run **Rollup (1.0)**. If a stream file backs this milestone, also close it clear `runtime.worktree`, set `stream.status: closed` (or prompt) and run the **Stream Rollup** so `active.yml` drops the row pointing at the torn-down worktree. (Never auto-remove without the explicit yes.)
148
- - **keep** → leave it; impose no write-block — a `complete` milestone has no live cursor left to fork.
124
+ **Completed milestone + live worktree → finalize, not gate (precedence).** A worktree whose milestone is already `current.status: complete` needs wrapping up, not a write-block. When the selected milestone is `complete` **and** still has a live worktree, skip the four-option gate and surface the finalize offer: *"m{id} is complete but its worktree `{path}` (`{branch}`) is still live — roll up `index.yml` and tear down the worktree + branch? [yes / keep]"*
125
+ - **yes** → confirm, `git worktree remove {path}`, delete the branch, clear `lifecycle.worktree_*`, run **Rollup (1.0)**. If a stream backs this milestone, close it too (clear `runtime.worktree`, set `stream.status: closed` or prompt) and run the **Stream Rollup**. Never auto-remove without the explicit yes.
126
+ - **keep** → leave it; no write-block — `complete` has no live cursor left to fork.
149
127
 
150
- The gate (below) and this finalize path are mutually exclusive on `current.status`: `complete` → finalize; any other status with a live worktree → gate.
128
+ `current.status` decides which path applies: `complete` → finalize; any other status with a live worktree → gate.
151
129
 
152
- **When it does NOT trigger** (no-op — continue to routing): no live worktree for the selected milestone; or this session **is** the owning worktree (`in_main_checkout: false` and the branch anchors to this milestone); or the milestone has a recorded, still-current `lifecycle.ownership_override` (see option 4).
130
+ **When it does NOT trigger** (continue to routing): no live worktree for the selected milestone; this session **is** the owning worktree; or a recorded, still-current `lifecycle.ownership_override` exists (option 4).
153
131
 
154
- **When it triggers — STOP. Do not route** to `planning`, `executing`, `verifying`, `reviewing`, `quick-tasking` against this milestone, and do not write any of:
155
- - `.forge/state/milestone-{id}.yml`
156
- - `.forge/phases/milestone-{id}/`
157
- - `.forge/research/milestone-{id}.md`
132
+ **When it triggers — STOP. Do not route** to `planning`, `executing`, `verifying`, `reviewing`, `quick-tasking` against this milestone, and do not write `.forge/state/milestone-{id}.yml`, `.forge/phases/milestone-{id}/`, or `.forge/research/milestone-{id}.md`.
158
133
 
159
134
  Present the four resolutions and wait for an explicit choice:
160
135
 
161
- 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:
162
- - **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.
163
- - **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.
164
- - **On an EnterWorktree error/rejection** (not just tool absence) — e.g. a worktree→worktree relocation rejects the call under the tool's path constraint (ADR-010 roots live outside `.claude/worktrees/`) — fall back to the same printed `cd` instruction above, exactly as if the tool were unavailable.
165
- - Exit is always `ExitWorktree({action: "keep"})` — Forge never lets the native tool **remove** a worktree; teardown stays option 3 / `git worktree remove`.
166
- 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.
167
- 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.**
168
- 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.)
136
+ 1. **Enter the worktree** *(recommended)* — relocate this session into the worktree in place. Canonical **Native Worktree Entry** contract, reused verbatim at `chief-of-staff` resume and `orchestrating` hand-back:
137
+ - **`EnterWorktree` available** call `EnterWorktree({path: {worktree_path}})` — always `{path}` into the recorded `lifecycle.worktree_path`, never `{name}`. Log `Relocated into worktree {worktree_path} ({branch})` independent of any confirmation prompt, then continue routing from inside the worktree writes now land in the owner.
138
+ - **`EnterWorktree` unavailable** (older CLI, non-Claude-Code adapter) print the exact command (`cd {worktree_path}` then `/forge`); re-running there anchors to this milestone. This session does nothing further.
139
+ - **EnterWorktree error/rejection** (not tool absence) — e.g. a worktree→worktree relocation rejects under the path constraint (ADR-010 roots live outside `.claude/worktrees/`) — same printed `cd` fallback.
140
+ - Exit is always `ExitWorktree({action: "keep"})` — Forge never lets the native tool remove a worktree; teardown stays option 3.
141
+ 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.
142
+ 3. **Retire the worktree** — only if dead/superseded. Close the stream, `git worktree remove {worktree_path}`, clear `lifecycle.worktree_*`. **Confirm before removing; never auto-remove.**
143
+ 4. **Recorded override** — write from main anyway: `lifecycle.ownership_override: {at: {ISO}, from: main, reason: "{operator's reason}"}` into `milestone-{id}.yml`, echo it in the boot line, carry it into the next `reviewing` report. The **only** way past the block, never silent. (Scoped to this milestone; clear once retired/synced.)
169
144
 
170
- **Shared project docs** (`context.md`, `refactor-backlog.yml`, `project.yml`, `constitution.md`, `roadmap.yml`, `requirements/m{N}.yml`) stay writable from main even when the gate is up — but warn they won't be visible to the live worktree until it pulls/rebases (same note as the preflight).
145
+ **Shared project docs** (`context.md`, `refactor-backlog.yml`, `project.yml`, `constitution.md`, `roadmap.yml`, `requirements/m{N}.yml`) stay writable from main even with the gate up — but won't be visible to the live worktree until it pulls/rebases.
171
146
 
172
147
  An agent must never present "override" as a peer default. Lead with option 1; reach for 4 only on the operator's explicit instruction with a reason.
173
148
 
@@ -175,25 +150,18 @@ An agent must never present "override" as a peer default. Lead with option 1; re
175
150
 
176
151
  Check `.forge/refactor-backlog.yml`:
177
152
  - *"{N} refactors ({Q} quick, {S} standard). Tackle first?"*
178
- - Pick item → branch on `effort`:
179
- - `effort: quick` → `quick-tasking`, set `status: in_progress`. Unchanged.
180
- - `effort: standard` → **Promote to milestone** (procedure below), then fall through to Step 3 Standard routing.
153
+ - Pick item → branch on `effort`: `quick` → `quick-tasking`, set `status: in_progress`; `standard` → **Promote to milestone** (below), then fall through to Step 3 Standard routing.
181
154
  - Decline → proceed.
182
155
 
183
156
  **Promotion procedure (effort: standard only):**
184
157
 
185
158
  1. Read backlog item `id` (e.g. `R61-002`). Synthesize milestone id `m-{R-id}` (e.g. `m-R61-002`).
186
- 2. **Re-check status before writing anything.** Re-read the item's current `status`/`promoted_to` from `.forge/refactor-backlog.yml` (the copy read for the "{N} refactors" prompt may be stale). If `status: in_progress` or `promoted_to` is already set, short-circuit: *"{R-id} already in_progress (promoted to {promoted_to})."* — no declaration, no milestone file, no roadmap append.
187
- 3. **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.
188
- 4. Copy `.forge/templates/state/milestone.yml` → `.forge/state/milestone-{m-R-id}.yml`. Populate:
189
- - `milestone.id: m-{R-id}`
190
- - `milestone.name: "Promoted from {R-id}: {backlog item title}"`
191
- - `milestone.origin: {R-id}`
192
- - `current.tier: standard`, `current.status: researching`
193
- - `current.last_updated: {ISO date}`
194
- 5. 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.
159
+ 2. **Re-check status before writing anything** — re-read the item's current `status`/`promoted_to` (the earlier read may be stale). Already `in_progress`/`promoted_to` set short-circuit: *"{R-id} already in_progress (promoted to {promoted_to})."* — no declaration, no milestone file, no roadmap append.
160
+ 3. **Enter the worktree first** (R10, before Step 3's routing gate fires don't assume one exists): on main, `EnterWorktree` now; if already entered, this is simply the first write. 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`, best-known `ownership.owned_surfaces`/`shared_surfaces`) as the worktree's first commit and fast-forward-push it (FORGE.md's Stream Integration Checkpoints intake-declaration trigger).
161
+ 4. Copy `.forge/templates/state/milestone.yml` → `.forge/state/milestone-{m-R-id}.yml`. Populate `milestone.id: m-{R-id}`, `milestone.name: "Promoted from {R-id}: {backlog item title}"`, `milestone.origin: {R-id}`, `current.tier: standard`, `current.status: researching`, `current.last_updated: {ISO date}`.
162
+ 5. Run **Rollup (1.0)** — regenerates `index.yml` (never hand-append). 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, and it scans live + archived milestones so a burned id is never reused.
195
163
  6. Append to `.forge/roadmap.yml` — one milestone entry + one phase (`refactor-{R-id}`, `requirements_source: refactor-backlog.yml#{R-id}`, `dependencies: []`).
196
- 7. Update `.forge/refactor-backlog.yml` item: `status: in_progress`, add `promoted_to: m-{R-id}`.
164
+ 7. Update the backlog item: `status: in_progress`, add `promoted_to: m-{R-id}`.
197
165
  8. Confirm: *"Promoted {R-id} → milestone m-{R-id}. State + roadmap created. Routing to researching."*
198
166
 
199
167
  ```text
@@ -204,30 +172,30 @@ backlog pickup → effort: standard
204
172
  └─ fall through → Standard tier routing
205
173
  ```
206
174
 
207
- Downstream skills (researching, discussing, planning, executing, verifying, reviewing) see a normal milestone — no special branching.
175
+ Downstream skills see a normal milestone — no special branching.
208
176
 
209
- **Desire-path review — `forge` boot is the SINGLE review owner** (verifying *captures* but does not surface; see ADR-012). Glob `.forge/state/desire-paths/*.yml`, group by `type` + normalized `note`, count each group. Read each file's `scope` (absent ⇒ `project`).
177
+ **Desire-path review — `forge` boot is the SINGLE review owner** (verifying captures, never surfaces; ADR-012). Glob `.forge/state/desire-paths/*.yml`, group by `type` + normalized `note`, count each group; read each file's `scope` (absent ⇒ `project`).
210
178
 
211
- **Skip declined groups first.** Glob `.forge/state/desire-paths/declined/*.yml`; skip any group whose key (`type` + normalized `note`) already has a decline file — it stays dismissed, no re-nag.
179
+ **Skip declined groups first.** Glob `.forge/state/desire-paths/declined/*.yml`; a group whose key already has a decline file stays dismissed no re-nag.
212
180
 
213
- **Surface at 3+.** For each remaining group with 3+ files (declined groups already skipped), **load `desire-paths-review.md` in this skill dir and follow it** — it carries the concrete fix-suggestion map (per `type`), the Apply/decline mechanics (archive to `resolved/` on apply; write a `declined/` file on decline — no re-nag), and the upstream transport for `scope: framework` signals (portable block + optional `gh issue` against `forge.upstream_repo`). Groups under 3 (the common boot) → nothing to surface; skip the load. (Occurrence count is derived from file count no counter to reset.)
181
+ **Surface at 3+.** Each remaining group with 3+ files **load `desire-paths-review.md` in this skill dir and follow it** (fix-suggestion map per `type`, Apply/decline mechanics, upstream transport for `scope: framework` signals). Under 3 (the common boot) → skip the load. (Count = file count; no counter to reset.)
214
182
 
215
183
  ### 1.2a Prototype hygiene (parked-rot guard)
216
184
 
217
- The Prototyping tier writes an always-present state file (`.forge/prototypes/{slug}.md`), so a `parked` prototype can accumulate silently — the rot risk of a durable artifact. Cheap boot check, advisory, never blocks:
185
+ The Prototyping tier writes an always-present state file (`.forge/prototypes/{slug}.md`), so a `parked` prototype can accumulate silently. Cheap boot check, advisory, never blocks:
218
186
 
219
- - No `.forge/prototypes/` dir → skip (nothing to surface). This is the common boot.
220
- - Glob `.forge/prototypes/*.md` (NOT `archive/` — graduated/discarded are already terminal and archived). For each, read `status`.
221
- - Any `status: parked` with `parked` (or `created`) older than **30 days** → one line each: *"Prototype `{slug}` ({title}) parked {N}d — resume (`/forge prototype {slug}`), graduate, or discard? Parked mockups rot."* Surface only; the operator decides. `exploring` prototypes are live work in progress — do not nag them.
187
+ - No `.forge/prototypes/` dir → skip (the common boot).
188
+ - Glob `.forge/prototypes/*.md` (NOT `archive/` — terminal statuses already live there). For each, read `status`.
189
+ - `status: parked` (or `created`) older than **30 days** → one line each: *"Prototype `{slug}` ({title}) parked {N}d — resume (`/forge prototype {slug}`), graduate, or discard? Parked mockups rot."* Surface only. `exploring` prototypes are live work — do not nag them.
222
190
  - Nothing stale → silent.
223
191
 
224
192
  ### 1.3 Interface Check
225
193
 
226
194
  Check `interface` in `project.yml`:
227
- - Field present → skip. (If scalar `none` found, normalize to `[none]`; see `.forge/templates/interface-detection.md` for empty-field canon.)
228
- - Field absent → detect using `.forge/templates/interface-detection.md`. No match → write `[none]`.
195
+ - Present → skip. (Scalar `none` found normalize to `[none]`; see `.forge/templates/interface-detection.md` for empty-field canon.)
196
+ - Absent → detect via `.forge/templates/interface-detection.md`. No match → write `[none]`.
229
197
  - Prompt: *"No `interface` field found. Detected: [{list}]. Add to project.yml? (yes/no)"*
230
- - Yes → write `interface: [{list}]` to project.yml. No → skip for this session.
198
+ - Yes → write `interface: [{list}]`. No → skip for this session.
231
199
 
232
200
  No `project.yml` + Standard/Full → **Step 2A**. State exists → **Step 2B**.
233
201
 
@@ -249,24 +217,15 @@ No match → fall through to Step 2B (tier detection).
249
217
 
250
218
  ### Chief / Stream Operations
251
219
 
252
- 1. Invoke `Skill(chief-of-staff)` directly.
253
- 2. Do not enter tier detection. The Chief is project-level traffic control, not
254
- a replacement for Forge's Quick, Standard, or Full tiers.
255
- 3. Use Chief only for explicit stream intents, concurrent application work,
256
- mid-flow pivots, delegated work, conflict detection, merge ordering, or
257
- closing streams.
258
- 4. Ordinary single-stream feature work still follows normal tier detection.
259
- Once inside a stream, the stream uses Quick/Standard/Full for execution.
220
+ 1. Invoke `Skill(chief-of-staff)` directly — do not enter tier detection. The Chief is project-level traffic control, not a replacement for Quick/Standard/Full.
221
+ 2. Use it only for explicit stream intents, concurrent application work, mid-flow pivots, delegated work, conflict detection, merge ordering, or closing streams.
222
+ 3. Ordinary single-stream feature work still follows normal tier detection; once inside a stream, the stream uses Quick/Standard/Full for execution.
260
223
 
261
- Examples:
262
- - "show streams" → `Skill(chief-of-staff)` summarizes `.forge/streams/active.yml`
263
- - "start stream settings cleanup" → `Skill(chief-of-staff)` snapshots current stream, creates a new stream, and checks conflicts
264
- - "merge safe?" → `Skill(chief-of-staff)` lists ready streams that can land without blocking conflicts
224
+ Examples: "show streams" → summarizes `.forge/streams/active.yml`; "start stream settings cleanup" → snapshots the current stream, creates a new one, checks conflicts; "merge safe?" → lists ready streams that can land without blocking conflicts.
265
225
 
266
226
  ### Show Deferred
267
227
 
268
- 1. Invoke `Skill(deferred)` directly.
269
- 2. Do not enter tier detection. Do not prompt for milestone. The deferred skill is read-only and self-contained.
228
+ 1. Invoke `Skill(deferred)` directly. Do not enter tier detection or prompt for milestone — it's read-only and self-contained.
270
229
 
271
230
  ### Prototype Ops
272
231
 
@@ -296,27 +255,13 @@ Prototypes are the disposable in-app mockup tier (`.forge/prototypes/{slug}.md`;
296
255
 
297
256
  ### Delete (Archive) Operation
298
257
 
299
- 1. Validate: milestone in `index.yml` (any status — active, deferred, complete, not_started)
300
- 2. Build manifest — scan for associated files:
301
- - `.forge/state/milestone-{id}.yml`
302
- - `.forge/phases/milestone-{id}/` (the milestone's phase dir holds all `{phase}-{name}/` subdirs)
303
- - `.forge/research/milestone-{id}.md` (if exists)
304
- - `.forge/audits/milestone-{id}-*` (if exists)
305
- - Note: `.forge/context.md` is shared — do NOT archive or modify. M{id} decisions stay as history.
306
- 3. Show manifest + confirm:
307
- *"Archiving milestone {id}: {name}\n - {list files}\n → .forge/archive/milestone-{id}/\n\nProceed?"*
308
- 4. On confirm:
309
- a. Create `.forge/archive/milestone-{id}/`
310
- b. Copy `milestone-{id}.yml` → archive as `state.yml`
311
- c. Copy phase dirs → archive `phases/`
312
- d. Copy research file → archive `research.md` (if exists)
313
- e. Copy audit files → archive `audits/` (if exists)
314
- f. Remove originals after successful copy (including `milestone-{id}.yml`)
315
- g. Remove milestone + its phases from `roadmap.yml`
316
- h. Run **Rollup (1.0)** — `index.yml` regenerates without the removed milestone
258
+ 1. Validate: milestone in `index.yml` (any status).
259
+ 2. Build manifest: `.forge/state/milestone-{id}.yml`, `.forge/phases/milestone-{id}/` (holds all `{phase}-{name}/` subdirs), `.forge/research/milestone-{id}.md` (if exists), `.forge/audits/milestone-{id}-*` (if exists). `.forge/context.md` is shared do NOT archive or modify; M{id} decisions stay as history.
260
+ 3. Show manifest + confirm: *"Archiving milestone {id}: {name}\n - {list files}\n → .forge/archive/milestone-{id}/\n\nProceed?"*
261
+ 4. On confirm: create `.forge/archive/milestone-{id}/`; copy `milestone-{id}.yml` → `state.yml`, phase dirs `phases/`, research file → `research.md` (if exists), audit files → `audits/` (if exists); remove originals after successful copy; remove the milestone + its phases from `roadmap.yml`; run **Rollup (1.0)**.
317
262
  5. Confirm: *"Archived milestone {id}: {name} → .forge/archive/milestone-{id}/. Removed from active state."*
318
263
 
319
- **Safety:** Copy-then-delete, not move. If copy fails, originals intact.
264
+ **Safety:** copy-then-delete a failed copy leaves originals intact.
320
265
 
321
266
  ### Parent Session Model Advisory
322
267
 
@@ -333,7 +278,7 @@ No `project.yml` → `Skill(initializing)`. Brownfield/greenfield, absorption, s
333
278
  ANY: "mock up"/"mockup"/"sketch"/"prototype"/"draw up"/"iterate on the UI"/"explore how X could look"/"design iteration", **disposable** in-app visual exploration behind an alpha/feature flag, no requirements locked yet
334
279
  → `prototyping`
335
280
 
336
- Distinguishing signal: the deliverable is *a thing to look at and react to*, not shippable code. If the operator wants to **see how something might look before deciding to build it**, it's Prototyping even if it touches many files. Real UI work with known requirements is Standard + `designing` (which gates on quality/verification); Prototyping deliberately skips that gate. When genuinely ambiguous, ask: *"Is this a throwaway mockup to react to, or the real build?"*
281
+ Distinguishing signal: the deliverable is *a thing to look at and react to*, not shippable code if the operator wants to see how something might look before deciding to build it, it's Prototyping even if it touches many files. Real UI work with known requirements is Standard + `designing` (which gates on quality); Prototyping skips that gate. Ambiguous ask: *"Is this a throwaway mockup to react to, or the real build?"*
337
282
 
338
283
  ### Quick
339
284
  ANY: single file, typo/formatting, config/env, dep bump, <50 lines, "quick"/"small"/"minor"
@@ -355,7 +300,7 @@ ANY: new project, major arch change, multi-phase, multi-subsystem, days of work,
355
300
  ANY: "review"/"advise"/"assess"/"evaluate" + specific system/component/feature, scope unclear, "what do you think about"
356
301
  → No `project.yml` → init first. Then `discussing` (no milestone — tier detected post-discussion).
357
302
 
358
- Can't know upfront if "review X" → "it's fine" or "restructure 6 files." Discussion determines scope. Discussing skill detects tier at convergence, creates milestone if code changes needed.
303
+ Can't know upfront if "review X" → "it's fine" or "restructure 6 files" discussion determines scope; `discussing` detects tier at convergence, creates a milestone if needed.
359
304
 
360
305
  ### User Override
361
306
  "Use Quick/Standard/Full tier" → honor.
@@ -364,21 +309,18 @@ Can't know upfront if "review X" → "it's fine" or "restructure 6 files." Discu
364
309
 
365
310
  Tier + state → invoke via `Skill` tool. All phases use `Skill()`.
366
311
 
367
- **CRITICAL: NEVER `EnterPlanMode`.** "Planning" = `Skill(planning)`. Native plan mode writes wrong format, bypasses gates + state.
312
+ **CRITICAL: NEVER `EnterPlanMode`.** `Skill(planning)` only — native plan mode writes the wrong format, bypasses gates + state.
368
313
 
369
- **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.
314
+ **CRITICAL: New work enters its worktree before anything else (R10).** Before invoking `researching`/`discussing`/`planning`/`quick-tasking`/`prototyping` for a request with no milestone yet (including `not_started` about to detect tier), call `EnterWorktree` (Worktree Convention, FORGE.md) first main stays for orchestration and reads. Applies to every tier: one `EnterWorktree` call, no heavier ceremony — a Quick fix or mockup gets a worktree but no milestone/stream declaration. `discussing`/`planning`/`researching` write `.forge` state too (context.md, plans, research notes); per P17, skipping this once left untracked droppings on main that broke other worktrees' landings, so no phase skips it. Milestone/stream intake is the deliberate exception (FORGE.md → Stream Integration Checkpoints, declaration/content split, B3'). Read-only sessions need no worktree. Governs only the FIRST write of new work resuming an in-flight milestone routes through the Live-Worktree Ownership Gate (1.1a)/branch-anchor path (1.1) instead.
370
315
 
371
- **Experimental — M10 orchestration backend.** Chief/Streams is the preferred
372
- user-facing orchestration model. M10 remains an optional backend for streams
373
- that need worktree isolation, merge-queue discipline, or experimental file
374
- claims. Route through `Skill(orchestrating)` first when **all** hold:
375
- - **Selected:** Project Chief chooses M10 for a stream, user explicitly asks for multi-agent/worktree mode, or legacy `orchestration.auto` is enabled.
316
+ **Experimental — M10 orchestration backend.** Chief/Streams is the preferred user-facing model; M10 is an optional backend for streams needing worktree isolation, merge-queue discipline, or experimental file claims. Route through `Skill(orchestrating)` first when **all** hold:
317
+ - **Selected:** Project Chief chooses M10, user explicitly asks for multi-agent/worktree mode, or legacy `orchestration.auto` is enabled.
376
318
  - **Installed:** `.claude/skills/orchestrating/` present + `forge-orchestrator` in `.mcp.json` + `.claude/hooks/forge-claim-check.sh` present.
377
- - **Not opted out:** `orchestration.auto` in `project.yml` ≠ `false` for legacy auto-routing; explicit Project Chief selection can still use M10 when installed.
378
- - **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.)
379
- - **Not already in a session:** active milestone has no `lifecycle.worktree_mode: active|degraded` (don't re-bootstrap inside a live session).
319
+ - **Not opted out:** `orchestration.auto` in `project.yml` ≠ `false` (explicit Chief selection can still use M10 when installed).
320
+ - **Tier is Standard or Full.** Quick never auto-orchestrates — the orchestration backend (file-claims, merge queue, bootstrap) is the wrong trade for a typo (a Quick fix already runs in a plain worktree per R10; what it skips is the M10 machinery, not the worktree).
321
+ - **Not already in a session:** milestone has no `lifecycle.worktree_mode: active|degraded` (don't re-bootstrap a live session).
380
322
 
381
- `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.
323
+ `orchestrating` bootstrap is the safety net — incompatible repo → it refuses, writes `worktree_mode: refused`, `forge` continues single-agent. Absent install → standard routing. Manual `Skill(orchestrating)` still works for explicit control; `orchestration.auto: false` opts out of legacy auto-routing.
382
324
 
383
325
  ### Auto-Routing (Always Deterministic)
384
326
 
@@ -397,13 +339,13 @@ Read `models` from `project.yml`:
397
339
  2. `models.default` → fallback
398
340
  3. Inherit parent session
399
341
 
400
- Resolve `{model}` for `{next}` skill using precedence above. Show only on mismatch:
342
+ Resolve `{model}` for `{next}` skill via that precedence. Show only on mismatch:
401
343
 
402
344
  *"Session: {current_model}. {next} expects {model} (from {source}) — use `/model {model}` to switch."*
403
345
 
404
- Where `{source}` = `skills.{name}` | `models.default` | `parent session`. Suppress when session model matches expected.
346
+ `{source}` = `skills.{name}` | `models.default` | `parent session`. Suppress when session model matches.
405
347
 
406
- **Model routing is advisory.** All phases use `Skill()`. Display expected model at each transition. Mismatch → suggest `/model {expected}`. Review gate logs model used.
348
+ **Advisory.** Display expected model at each transition; mismatch → suggest `/model {expected}`. Review gate logs model used.
407
349
 
408
350
  | Skill | Model | Why |
409
351
  |-------|-------|-----|
@@ -0,0 +1,54 @@
1
+ # Manual Fallback Procedures (`forge-state-rollup.sh` Executable Absent)
2
+
3
+ Loaded from `forge` boot Step 1 **only when `.claude/hooks/forge-state-rollup.sh`
4
+ is confirmed absent** (pre-0.75.0 install): `[ -x .claude/hooks/forge-state-rollup.sh ]`
5
+ fails. The common case — hook present, ran, its `[forge-state-rollup]` summary
6
+ appeared in context — never loads this file; read the cache and continue.
7
+
8
+ ## State Rollup (`index.yml`) — manual procedure
9
+
10
+ The executable's faithful spec, for hand-execution only when it does not exist:
11
+
12
+ 1. Glob `.forge/state/milestone-*.yml`.
13
+ 2. For each, read `milestone.id`, `milestone.name`, `current.status`,
14
+ `lifecycle.{deferred_at, resumed_at}`, and the last-touched date as
15
+ `current.last_updated` → else legacy `progress.last_update` → else null.
16
+ (Keeps pre-0.19.0 milestone files self-sourcing without editing them; active
17
+ milestones gain `current.last_updated` on their next transition.)
18
+ 3. Derive the registry `status`:
19
+ - **deferred** — `lifecycle.deferred_at` set and not superseded by a later `resumed_at`
20
+ - **complete** — `current.status == complete`
21
+ - **not_started** — `current.status == not_started`
22
+ - **active** — otherwise
23
+ 4. Render each row's `release_state`, then rewrite `index.yml` `milestones:`
24
+ (sorted by id) as `{id, name, status, last_updated, release_state}` (date
25
+ from step 2's fallback). No other keys.
26
+ - **Fold is the source.** Run `.claude/hooks/forge-release-fold.sh m-{id}`
27
+ per row and write its stdout `release_state=` value verbatim
28
+ (`unmerged|merged|validated`) — agent prose never re-derives
29
+ `release_state`; the fold binary is the only source.
30
+ - **Degrade, don't block.** Hook missing or exit 2 → write
31
+ `release_state: unknown`, emit ONE advisory line — *"release fold
32
+ unavailable for m-{id} — release_state: unknown"* — and continue. A
33
+ broken fold must never break boot.
34
+
35
+ Output is a pure function of the milestone files, so two sessions regenerating
36
+ it independently produce identical bytes — it never needs a hand-merge.
37
+
38
+ ## After a mid-session milestone CRUD
39
+
40
+ The SessionStart hook only fires at boot. After a mid-session promote / defer /
41
+ resume / delete, re-render by hand: run `.claude/hooks/forge-state-rollup.sh`
42
+ yourself (it does the milestone rollup AND the Stream Rollup in one idempotent
43
+ pass; add `--project {worktree_path}` if the recorded path differs from cwd),
44
+ then read the result. Executable still absent → repeat the manual procedure
45
+ above instead.
46
+
47
+ ## Stream Rollup (`active.yml`) — manual procedure
48
+
49
+ No separate numbered procedure exists beyond FORGE.md → Stream Rollup's join
50
+ description — reconstruct `active.yml` from that spec by hand: per-stream
51
+ files contribute coordination state, active milestones contribute `phase`
52
+ (a stream-file-less active milestone gets a derived `implicit` row),
53
+ `merge_queue` derives from `merge.readiness: ready`. This is the rare path;
54
+ almost no install needs it.