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.
- package/package.json +1 -1
- package/template/.claude/hooks/forge-context-migrate.sh +297 -0
- package/template/.claude/hooks/forge-size-gate.sh +175 -0
- package/template/.claude/hooks/forge-state-rollup.sh +135 -2
- package/template/.claude/hooks/skill-gate-tiers.txt +6 -0
- package/template/.claude/hooks/tests/forge-context-migrate.test.sh +166 -0
- package/template/.claude/hooks/tests/forge-size-gate.test.sh +132 -0
- package/template/.claude/settings.json +5 -1
- package/template/.claude/skills/discussing/SKILL.md +30 -31
- package/template/.claude/skills/executing/SKILL.md +3 -3
- package/template/.claude/skills/forge/SKILL.md +112 -170
- package/template/.claude/skills/forge/references/manual-fallback-procedures.md +54 -0
- package/template/.claude/skills/planning/SKILL.md +11 -9
- package/template/.claude/skills/researching/SKILL.md +2 -0
- package/template/.claude/skills/verifying/SKILL.md +2 -2
- package/template/.forge/FORGE.md +79 -91
- package/template/.forge/checks/forge-jarvis-awareness.sh +46 -2
- package/template/.forge/checks/forge-jarvis-relay.sh +64 -16
- package/template/.forge/checks/tests/forge-jarvis-awareness.test.sh +58 -0
- package/template/.forge/checks/tests/forge-jarvis-relay.test.sh +50 -0
- package/template/.forge/migrations/0.80.0-context-md-sharding.md +158 -0
- package/template/.forge/migrations/0.80.0-size-gate-wiring.md +145 -0
- package/template/.forge/templates/constitution.md +1 -1
- package/template/.forge/templates/context-milestone.md +30 -0
- 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
|
|
32
|
-
-
|
|
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
|
-
|
|
44
|
-
-
|
|
45
|
-
-
|
|
46
|
-
- `
|
|
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 (
|
|
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
|
-
|
|
51
|
-
-
|
|
52
|
-
-
|
|
53
|
-
- `behind > 0
|
|
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
|
|
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
|
|
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
|
-
|
|
60
|
-
-
|
|
61
|
-
-
|
|
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
|
-
**
|
|
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
|
-
**
|
|
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 merge — run `/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,
|
|
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
|
-
**
|
|
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
|
-
**
|
|
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.
|
|
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`** (
|
|
105
|
-
2. **Check arg first.** `/forge 2` or `/forge "Auth system"`:
|
|
106
|
-
|
|
107
|
-
-
|
|
108
|
-
-
|
|
109
|
-
|
|
110
|
-
- **
|
|
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
|
|
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:**
|
|
118
|
-
7. Load `milestone-{id}.yml`,
|
|
119
|
-
8. **Route on `current.status`, NOT `{percent}`.** Complete only at `complete
|
|
120
|
-
9. Report + **immediately route** (Step 3)
|
|
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
|
|
123
|
-
- `total` =
|
|
124
|
-
- `done` = `
|
|
125
|
-
- `{percent}` = `total > 0` → `round(done / total * 100)`;
|
|
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
|
-
|
|
105
|
+
Old milestone files may carry a vestigial `progress:` block — ignore it, derive anyway.
|
|
128
106
|
|
|
129
|
-
Beads enabled
|
|
107
|
+
Beads enabled → `bd prime` (optional).
|
|
130
108
|
|
|
131
|
-
Notion board adapter (opt-in): `board.enabled: true`
|
|
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
|
|
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
|
|
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` (
|
|
140
|
-
- the preflight's `git worktree list` reported a `forge/m-{id}
|
|
141
|
-
- `milestone-{id}.yml` `lifecycle.worktree_mode` is `active
|
|
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
|
|
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).**
|
|
147
|
-
- **yes** → confirm,
|
|
148
|
-
- **keep** → leave it;
|
|
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
|
-
|
|
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** (
|
|
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
|
|
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
|
|
162
|
-
-
|
|
163
|
-
-
|
|
164
|
-
- **
|
|
165
|
-
- Exit is always `ExitWorktree({action: "keep"})` — Forge never lets the native tool
|
|
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.
|
|
167
|
-
3. **Retire the worktree** — only if
|
|
168
|
-
4. **Recorded override** —
|
|
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
|
|
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
|
|
187
|
-
3. **Enter the worktree first (R10
|
|
188
|
-
4. Copy `.forge/templates/state/milestone.yml` → `.forge/state/milestone-{m-R-id}.yml`. Populate:
|
|
189
|
-
|
|
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
|
|
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
|
|
175
|
+
Downstream skills see a normal milestone — no special branching.
|
|
208
176
|
|
|
209
|
-
**Desire-path review — `forge` boot is the SINGLE review owner** (verifying
|
|
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`;
|
|
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+.**
|
|
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
|
|
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 (
|
|
220
|
-
- Glob `.forge/prototypes/*.md` (NOT `archive/` —
|
|
221
|
-
-
|
|
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
|
-
-
|
|
228
|
-
-
|
|
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}]
|
|
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.
|
|
254
|
-
|
|
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
|
|
300
|
-
2. Build manifest —
|
|
301
|
-
|
|
302
|
-
|
|
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:**
|
|
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
|
|
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
|
|
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`.**
|
|
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).**
|
|
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
|
-
|
|
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`
|
|
378
|
-
- **Tier is Standard or Full.** Quick never auto-orchestrates — the
|
|
379
|
-
- **Not already in a 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
|
|
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
|
|
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
|
-
|
|
346
|
+
`{source}` = `skills.{name}` | `models.default` | `parent session`. Suppress when session model matches.
|
|
405
347
|
|
|
406
|
-
**
|
|
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.
|