forge-orkes 0.28.0 → 0.30.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/skills/discussing/SKILL.md +10 -0
- package/template/.claude/skills/executing/SKILL.md +16 -0
- package/template/.claude/skills/forge/SKILL.md +54 -3
- package/template/.claude/skills/planning/SKILL.md +10 -1
- package/template/.claude/skills/quick-tasking/SKILL.md +2 -0
- package/template/.claude/skills/reviewing/SKILL.md +6 -0
- package/template/.claude/skills/upgrading/SKILL.md +10 -0
- package/template/.claude/skills/verifying/SKILL.md +3 -17
- package/template/.forge/FORGE.md +31 -2
- package/template/.forge/migrations/0.29.0-shared-state-taxonomy.md +68 -0
- package/template/.forge/templates/project.yml +8 -0
- package/template/.forge/templates/state/desire-path.yml +18 -3
package/package.json
CHANGED
|
@@ -42,13 +42,23 @@ Never accumulate decisions in working memory. Never batch writes to convergence.
|
|
|
42
42
|
|
|
43
43
|
### Write Protocol
|
|
44
44
|
|
|
45
|
+
`context.md` is **shared mutable** state per FORGE.md State Ownership — writes follow two structural rules:
|
|
46
|
+
- **Scope to your milestone's block.** Different milestone blocks → git merges cleanly across worktrees.
|
|
47
|
+
- **Append-only within a block.** When superseding an older decision, *strike through* (`~~old decision~~`) and append the new line below — never rewrite in place. Same-line concurrent edits become impossible by construction; reading the history of a decision becomes possible by inspection.
|
|
48
|
+
|
|
45
49
|
**On first decision of the session:**
|
|
46
50
|
- Check if `.forge/context.md` exists. If not → create it from `.forge/templates/context.md`
|
|
47
51
|
- Add a milestone heading inside `## Locked Decisions`: `### M{id} — {name} (drafting)` (use "M?" if no milestone yet)
|
|
52
|
+
- **Cross-tree liveness check (informational).** If editing an existing block for milestone `{id}`, run `git worktree list --porcelain | awk '/^branch / && $2 ~ /refs\/heads\/forge\/m-'"$id"'(-|$)/ {print}'`. Any hit means m{id} is **live in another worktree** — surface one line: *"m{id} is live in worktree at `{path}`. Edits land cleanly on main but won't be visible to that worktree until it rebases or pulls."* Proceed with the write — main is often the canonical author of `context.md`, this is a heads-up, not a block.
|
|
48
53
|
|
|
49
54
|
**On each confirmed decision:**
|
|
50
55
|
- Append under the milestone heading: `- **[Topic]**: [Decision]. Reason: [Why]`
|
|
51
56
|
|
|
57
|
+
**On superseding a prior decision (in your own milestone's block):**
|
|
58
|
+
- Find the original line. Wrap it in `~~ ~~` (markdown strikethrough). DO NOT delete it.
|
|
59
|
+
- Append the new decision on a new line below it: `- **[Topic]**: [Decision]. Reason: [Why]. Supersedes ~~prior on {date}~~.`
|
|
60
|
+
- Why: shared-mutable append-only rule (FORGE.md State Ownership). Decisions stay readable as history; concurrent writes from another worktree on a different bullet can't conflict with this one.
|
|
61
|
+
|
|
52
62
|
**On deferred ideas** (user says "not now" / "later" / "skip"):
|
|
53
63
|
- Append to `## Deferred Ideas` immediately: `- [Idea] — Deferred because: [reason]`
|
|
54
64
|
|
|
@@ -11,6 +11,7 @@ description: "Build to plan with atomic commits, deviation rules, and context en
|
|
|
11
11
|
- [ ] Constitution.md gates satisfied
|
|
12
12
|
- [ ] Milestone state updated to `status: executing`
|
|
13
13
|
- [ ] Baseline snapshot captured (see below)
|
|
14
|
+
- [ ] Plan anchors re-derived against HEAD (see below)
|
|
14
15
|
|
|
15
16
|
## Baseline Snapshot
|
|
16
17
|
|
|
@@ -23,6 +24,19 @@ Run **before the first task begins**. Makes failure causality mechanical — no
|
|
|
23
24
|
|
|
24
25
|
Skip only if re-entering an in-progress execution and `deferred-issues.md` already documents all current failures.
|
|
25
26
|
|
|
27
|
+
## Plan Anchor Re-Derivation
|
|
28
|
+
|
|
29
|
+
Run **once, before the first task** (alongside the baseline snapshot). Plans snapshot concrete codebase literals — symbol names, `file:line` references, contract/type shapes, dependency versions — at planning time. Between planning and execution the codebase moves, and a stale literal silently misdirects the task ("plan said v5, HEAD is v6").
|
|
30
|
+
|
|
31
|
+
Re-derive each anchor the plan names against current HEAD:
|
|
32
|
+
|
|
33
|
+
1. Scan plan `<files>`, `<action>`, `must_haves`, and any `contract.md` for concrete literals: symbol/function names, `path:line` references, contract shapes, pinned versions.
|
|
34
|
+
2. Resolve each against HEAD — grep the symbol, open the file, read the installed version.
|
|
35
|
+
3. **Drifted** (symbol moved/renamed, line shifted, version bumped, shape changed) → reconcile the plan text to the live value *before* acting. Reconciled here, not discovered mid-task.
|
|
36
|
+
4. **Gone** (symbol/file deleted, contract removed) → not a drift but a broken planning assumption → **STOP under Rule 4**; the slice may no longer hold.
|
|
37
|
+
|
|
38
|
+
Cheap pass, high leverage. Skip only when re-entering an in-progress execution already past its first task.
|
|
39
|
+
|
|
26
40
|
## Deviation Rules
|
|
27
41
|
|
|
28
42
|
**Full definitions:** `.claude/agents/executor.md`. Decision order: **Rule 4 first** (architectural → STOP, ask user), then Rule 1 (bugs), Rule 2 (critical gaps), Rule 3 (infra blockers). Uncertain → Rule 4.
|
|
@@ -265,6 +279,8 @@ Append **one file per observation** to `.forge/state/desire-paths/` — copy `.f
|
|
|
265
279
|
- **User corrections**: Repeated correction matching a prior one → `type: user_correction`
|
|
266
280
|
- **Agent struggles**: Multiple attempts or user guidance needed → `type: agent_struggle`
|
|
267
281
|
|
|
282
|
+
**Set `scope` at capture** (see ADR-012): `framework` when the fix this signal implies targets Forge's own skills/templates (a skill's check, a plan/tier heuristic, a template); `project` when it targets user-owned files (`constitution.md`, `design-system.md`, `context.md`, project setup). Record `suggestion_target` when you know the file/skill it touches. `scope: framework` is what later lets `forge` review route the signal upstream instead of trapping it in this project.
|
|
283
|
+
|
|
268
284
|
## Cross-Layer Seam Check
|
|
269
285
|
|
|
270
286
|
**Trigger:** the phase was split by planning Step 6.1 into a **Tier-2** producer plan-NNa + consumer plan-NNb (both carry a `contract:` frontmatter path pointing at the same `contract.md`). Single-plan / Tier-1 (`layer:` tag, no split, contract honored inline) → skip; no seam check needed.
|
|
@@ -9,6 +9,33 @@ 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.
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
# This session's checkout — main or a worktree? --git-dir == --git-common-dir → main; differ → linked worktree.
|
|
18
|
+
this_dir=$(git rev-parse --git-dir 2>/dev/null) || exit 0
|
|
19
|
+
common_dir=$(git rev-parse --git-common-dir)
|
|
20
|
+
in_main_checkout=$([ "$this_dir" = "$common_dir" ] && echo true || echo false)
|
|
21
|
+
|
|
22
|
+
# Forge worktrees = branch matches forge/m-*
|
|
23
|
+
git worktree list --porcelain 2>/dev/null \
|
|
24
|
+
| awk '
|
|
25
|
+
/^worktree / { path=$2 }
|
|
26
|
+
/^branch / { br=$2; if (br ~ /refs\/heads\/forge\/m-/) print path " " br }
|
|
27
|
+
'
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**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}`. Edits to its milestone state belong there; shared-doc edits here (`context.md`, `refactor-backlog.yml`) won't be visible to it until it rebases or pulls."*
|
|
32
|
+
- This is informational — `forge` continues to Rollup + selection. The operator now knows. (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 `.forge/` between this worktree's `HEAD` and `origin/main` (or `main` if no remote), restricted to 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`).
|
|
36
|
+
- 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.
|
|
37
|
+
- Setting absent or `false` → no-op.
|
|
38
|
+
|
|
12
39
|
### 1.0 State Rollup (index.yml is derived)
|
|
13
40
|
|
|
14
41
|
`index.yml` is a **derived registry** — regenerate it from the milestone files before reading, and after any milestone CRUD (promote/defer/resume/delete). **Never hand-edit `index.yml`.**
|
|
@@ -95,11 +122,35 @@ backlog pickup → effort: standard
|
|
|
95
122
|
|
|
96
123
|
Downstream skills (researching, discussing, planning, executing, verifying, reviewing) see a normal milestone — no special branching.
|
|
97
124
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
125
|
+
**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`).
|
|
126
|
+
|
|
127
|
+
**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.
|
|
128
|
+
|
|
129
|
+
**Surface at 3+.** For each remaining group with 3+ files, name a CONCRETE fix from this table (co-located here so the reader that acts carries the map):
|
|
130
|
+
|
|
131
|
+
| Type | Suggested evolution |
|
|
132
|
+
|------|---------------------|
|
|
133
|
+
| `deviation_pattern` | Add a pre-check to planning, or a new constitutional article |
|
|
134
|
+
| `tier_override` | Adjust tier-detection heuristics in the forge skill |
|
|
135
|
+
| `skipped_step` | Make the step optional, or merge it into another |
|
|
136
|
+
| `recurring_friction` | Add guidance to the relevant skill, or create a template |
|
|
137
|
+
| `agent_struggle` | Add examples or anti-patterns to the relevant skill |
|
|
138
|
+
| `user_correction` | Add a rule to constitution / context.md / the relevant skill |
|
|
139
|
+
|
|
140
|
+
Prompt: *"Recurring: [{note}] ({N}x, scope:{scope}). Fix: [concrete suggestion from table]. Apply / decline?"*
|
|
141
|
+
|
|
142
|
+
- **Agree** → apply the fix; archive the group's files to `.forge/state/desire-paths/resolved/`.
|
|
143
|
+
- **Decline** → write ONE file `.forge/state/desire-paths/declined/{date}-{type}-{slug}.yml` with `{group_key, declined_at, reason}`, so it never re-prompts. (Replaces the old "note, don't nag" — which wrote nothing, so the 3+ files re-nagged every boot.)
|
|
101
144
|
- (Occurrence count is derived from file count — there is no counter to reset.)
|
|
102
145
|
|
|
146
|
+
**Upstream transport — for `scope: framework` groups only** (ADR-012). A framework-scope signal's fix targets Forge's own skills/templates, which live in the Forge repo, not this project. So in addition to the Apply/decline prompt above:
|
|
147
|
+
|
|
148
|
+
1. **Always** write a portable, self-contained block to `.forge/state/desire-paths/upstream/{date}-{type}-{slug}.md` — pattern, occurrence count, the sibling observation filenames, the suggested evolution (from the table), and `suggestion_target`. Formatted to paste cleanly into a GH issue. This is the durable floor; it survives even with no network/tooling.
|
|
149
|
+
2. **Resolve `forge.upstream_repo`** from `project.yml`; if absent, default to `https://github.com/zayneupton/forge`. If resolvable **and** `gh` is on PATH → offer (don't auto-run — it publishes externally): `gh issue create --repo {upstream_repo} --title "[desire-path] {note}" --body-file {block}`. **The GH issue is the real close** — tracked, central, survives machine loss.
|
|
150
|
+
3. If `gh`/url unavailable → note the block is on disk and harvestable by `upgrading` (which runs from a local Forge clone) into `{source}/docs/desire-paths-inbox/`.
|
|
151
|
+
|
|
152
|
+
`project`-scope groups keep the in-project flow above — no upstream. Applying a framework fix locally (when this *is* the Forge repo) and routing it upstream are not mutually exclusive: in a user project you route; in the Forge repo you apply.
|
|
153
|
+
|
|
103
154
|
### 1.3 Interface Check
|
|
104
155
|
|
|
105
156
|
Check `interface` in `project.yml`:
|
|
@@ -156,7 +156,7 @@ Before decomposing, classify whether this phase crosses a layer boundary with a
|
|
|
156
156
|
**Tier-2 ratify gate** (the ONLY interruption; frame as contract-correctness, not "parallelize y/n"):
|
|
157
157
|
> *"This phase changes the {integration point} contract ({governing ADR}). Delta: [summary]. plan-NNa ({producer}) pins it; plan-NNb ({consumer}) builds against it in parallel. Is this contract shape correct?"*
|
|
158
158
|
|
|
159
|
-
Block the split until confirmed. Override ("keep it one plan") -> append a `type: tier_override` file to `.forge/state/desire-paths/` (
|
|
159
|
+
Block the split until confirmed. Override ("keep it one plan") -> append a `type: tier_override` file to `.forge/state/desire-paths/` with `scope: framework` (the fix would tune Forge's own Tier-2 threshold/heuristics — see ADR-012; recurring overrides route upstream), fall back to Tier 1.
|
|
160
160
|
|
|
161
161
|
**Integration (Tier 2):** layer plans build isolated (per-layer worktrees). The phase's final task is a **seam check** owned by the executing flow (NOT a standing agent): merge the layer branches, verify the shape the producer emits matches what the consumer built against, per `contract.md`.
|
|
162
162
|
|
|
@@ -196,6 +196,15 @@ Plan naming reflects the slice: `plan-01-user-signs-up.md`, not `plan-01-models.
|
|
|
196
196
|
</task>
|
|
197
197
|
```
|
|
198
198
|
|
|
199
|
+
#### Plan Anchors (drift-resistant references)
|
|
200
|
+
|
|
201
|
+
Tasks reference concrete code — symbols, paths, line numbers, contract shapes, versions. These are **anchors**: snapshotted at planning time, relied on at execution time, *after* the codebase has moved. Make them re-derivable instead of fragile:
|
|
202
|
+
|
|
203
|
+
- Reference by **symbol/intent** (`the signup handler in src/api/signup.ts`), not by bare line number (`src/api/signup.ts:42`). Line numbers drift on the next edit; symbols survive refactors.
|
|
204
|
+
- For a version or contract shape that *must* be pinned, state the literal **and** its source (`react 18 — per package.json at plan time`) so executing can re-derive it against HEAD.
|
|
205
|
+
|
|
206
|
+
Executing re-derives all anchors against HEAD before the first task (see executing → Plan Anchor Re-Derivation). Planning's job is to make that possible: name the symbol, cite the source.
|
|
207
|
+
|
|
199
208
|
### Task Sizing
|
|
200
209
|
|
|
201
210
|
| Duration | Action |
|
|
@@ -60,6 +60,8 @@ Example: `fix(docs): correct typo in API reference`
|
|
|
60
60
|
### 6. Done
|
|
61
61
|
No verification ceremony. Move on.
|
|
62
62
|
|
|
63
|
+
**Desire-path check (only when `.forge/state/desire-paths/` exists).** A Quick-tier streak (`quick-tasking → commit → done`) never boots `forge` or runs `verifying`, so recurring signals would pile up unreviewed. Cheap catch: glob `.forge/state/desire-paths/*.yml`, group by `type` + normalized `note`, and if any group hits 3+ (and has no decline file under `declined/`), surface it using **forge Step 1.2's review procedure** (the canonical table + Apply/decline + decline-ledger live there — do not duplicate them). One glob, surface if 3+, done. Standalone (no forge state) → skip.
|
|
64
|
+
|
|
63
65
|
## Milestone Context
|
|
64
66
|
|
|
65
67
|
Quick-tasking handles two contexts. Existing workflow steps (Identify → Validate → Execute → Test → Commit → Done) apply in both — this section adds state management around them.
|
|
@@ -312,6 +312,12 @@ Deferred issues triage: If `.forge/deferred-issues.md` has `status: pending` ite
|
|
|
312
312
|
|
|
313
313
|
### Backlog
|
|
314
314
|
|
|
315
|
+
`refactor-backlog.yml` is **shared mutable** state per FORGE.md State Ownership — many milestones append over time. Two rules apply:
|
|
316
|
+
- **Items carry `milestone:` scoping** (already enforced by the schema below) — concurrent appends from different milestones touch different items, so git merges cleanly.
|
|
317
|
+
- **Existing items are append-only** in spirit: change `status:` and add `completed:`/`deferred_at:` rather than rewriting the item's body. Compaction-on-write (below) handles physical relocation of terminal items.
|
|
318
|
+
|
|
319
|
+
**Cross-tree liveness check (informational).** Before writing, run `git worktree list --porcelain | awk '/^branch / && $2 ~ /refs\/heads\/forge\/m-/ {print}'`. Any non-empty output means another milestone is live in a worktree — surface one line: *"{N} other Forge milestone(s) live in worktrees ({list}). Backlog appends here land cleanly on main but won't be visible to those worktrees until they rebase."* Proceed with the write — reviewing on main is the canonical author. (Two reviewing sessions writing in parallel from different worktrees rely on the milestone-scoped append rule above, not on this check.)
|
|
320
|
+
|
|
315
321
|
Read `.forge/refactor-backlog.yml`. Next ID = max + 1. Append:
|
|
316
322
|
|
|
317
323
|
```yaml
|
|
@@ -18,6 +18,16 @@ Check if `.forge/dev-source` exists in the project root.
|
|
|
18
18
|
|
|
19
19
|
Template directory: `{source}/packages/create-forge/template/`.
|
|
20
20
|
|
|
21
|
+
### Harvest upstream desire paths (advisory)
|
|
22
|
+
|
|
23
|
+
`upgrading` is the one skill that bridges this project and a local Forge clone (`{source}`), so it harvests framework-scope desire paths the project has queued (ADR-012). After resolving `{source}`:
|
|
24
|
+
|
|
25
|
+
1. Glob this project's `.forge/state/desire-paths/upstream/*.md`. None → skip silently.
|
|
26
|
+
2. Any present → list them and offer to copy each into `{source}/docs/desire-paths-inbox/` (create the dir if missing) — a **tracked** location in the Forge repo where it won't be lost on machine swap.
|
|
27
|
+
3. On copy → move the project-side block to `.forge/state/desire-paths/upstream/harvested/` so it isn't re-offered. Report counts like other steps.
|
|
28
|
+
|
|
29
|
+
Advisory — never blocks the sync. This is the **convenience tier**: the primary close is the GH issue offered by `forge` review at capture time. Harvest just catches anything written when `gh`/the repo URL wasn't available.
|
|
30
|
+
|
|
21
31
|
### Downgrade guard
|
|
22
32
|
|
|
23
33
|
Read the source version (`{source}/packages/create-forge/package.json` `version`) and the installed version (`.claude/settings.json` `forge.version`). **If source < installed, STOP** — do not sync. Report: *"Refusing to downgrade: installed v{installed} is newer than source v{source}. Point dev-source at a newer checkout, or confirm an intentional downgrade."* Only proceed on explicit user override. (Rolling backward overwrites newer framework files and deletes files newer versions added — and with `.claude/` often gitignored, it is unrecoverable.)
|
|
@@ -278,25 +278,11 @@ After verification completes (PASSED or GAPS FOUND), run a quick retrospective o
|
|
|
278
278
|
|
|
279
279
|
**6. User corrections**: User correct the same thing multiple times? Implicit rules that should become explicit.
|
|
280
280
|
|
|
281
|
-
|
|
281
|
+
**Set `scope` on each observation** (see ADR-012): `framework` when the fix targets Forge's own skills/templates (tier heuristics, a skill's step, a template); `project` when it targets user-owned files (`constitution.md`, `design-system.md`, `context.md`). Record `suggestion_target` when known. This classifies the signal at write time so review can route `framework` signals upstream. Tier-override signals (#2) and the "constitution needs a new article" deviation case (#1) are usually `framework` and `project` respectively — judge by the file the fix touches, not the type.
|
|
282
282
|
|
|
283
|
-
|
|
283
|
+
**Surfacing is owned by `forge` boot — not here** (single review owner, ADR-012). `verifying` only *records* observations; it does not surface recommendations, prompt for fixes, or archive. The 3+ aggregation, the type→evolution suggestion table, the decline-ledger, and upstream routing all live in `forge` Step 1.2. Write the observation files (with `scope`) and move on — the next `forge` boot reviews them.
|
|
284
284
|
|
|
285
|
-
|
|
286
|
-
|-------------|-------------------|
|
|
287
|
-
| Repeated deviations | Add pre-check to planning, or new constitutional article |
|
|
288
|
-
| Tier overrides | Adjust detection heuristics in forge skill |
|
|
289
|
-
| Skipped steps | Make step optional, or merge into another step |
|
|
290
|
-
| Recurring friction | Add guidance to relevant skill, or create a template |
|
|
291
|
-
| Agent struggles | Add examples or anti-patterns to the relevant skill |
|
|
292
|
-
| User corrections | Add rule to constitution, context.md, or relevant skill |
|
|
293
|
-
|
|
294
|
-
Propose concrete actions:
|
|
295
|
-
- *"Add 'always use Card component for content containers' to design-system.md?"*
|
|
296
|
-
- *"Add null-check verification step to planning template?"*
|
|
297
|
-
- *"Make research phase optional for Standard tier in this project?"*
|
|
298
|
-
|
|
299
|
-
Only suggest at 3+ occurrences. One-off issues are noise.
|
|
285
|
+
> Why retired: two surfacers (`forge` + `verifying`) disagreed, and `verifying` proposed without archiving, so its patterns re-surfaced forever. One owner fixes both.
|
|
300
286
|
|
|
301
287
|
## Phase Handoff
|
|
302
288
|
|
package/template/.forge/FORGE.md
CHANGED
|
@@ -135,7 +135,7 @@ State lives in `.forge/`:
|
|
|
135
135
|
- `roadmap.yml` — Phases, milestones, dependencies
|
|
136
136
|
- `state/index.yml` — DERIVED registry rolled up from milestone files (id, name, status, last_updated). Never hand-edited; never written by worktree agents.
|
|
137
137
|
- `state/milestone-{id}.yml` — Per-milestone cursor (single source of truth): position, progress, decisions, blockers. One owner at a time.
|
|
138
|
-
- `state/desire-paths/` — Append-only framework-usage observations, one file per observation. Occurrence counts derived by globbing (no mutable counter).
|
|
138
|
+
- `state/desire-paths/` — Append-only framework-usage observations, one file per observation. Occurrence counts derived by globbing (no mutable counter). Each carries `scope: project | framework` (set at capture; absent ⇒ project). **`forge` boot is the single review owner**: at 3+ occurrences it surfaces a concrete fix from the co-located suggestion table, persists declines to `declined/` (no re-nag), and for `scope: framework` signals routes them upstream — a portable block to `upstream/` plus a `gh issue` offer against `forge.upstream_repo`. This closes Principle 7's capture→act loop; `verifying` captures but no longer surfaces. See [ADR-012](../docs/decisions/ADR-012-upstream-desire-path-feedback-channel.md).
|
|
139
139
|
- `context.md` — Locked decisions + deferred ideas (discuss phase)
|
|
140
140
|
- `research/milestone-{id}.md` — Research findings snapshot (dated, immutable)
|
|
141
141
|
- `phases/milestone-{id}/{phase}-{name}/plan-{NN}.md` — Task plans with must_haves frontmatter (`{id}`=milestone, `{phase}`=phase# preserved verbatim, `{name}`=kebab, `{NN}`=seq)
|
|
@@ -162,9 +162,38 @@ State-sync commits are separate from per-task code commits (which stay atomic du
|
|
|
162
162
|
|
|
163
163
|
### State Ownership (multi-worktree safety)
|
|
164
164
|
|
|
165
|
-
|
|
165
|
+
`.forge/` is **code-like**: it branches with the repo, syncs via git, atomic-commits with the work it describes. Worktrees get their own `.forge/` checkout by construction — that's not configuration, that's git. The framework's job is to specify how each *kind* of `.forge/` file behaves under that model.
|
|
166
|
+
|
|
167
|
+
**Every `.forge/` artifact falls into one of five sharing classes.** The class determines the write rule across worktrees:
|
|
168
|
+
|
|
169
|
+
| Class | Files | Write rule |
|
|
170
|
+
|---|---|---|
|
|
171
|
+
| **Single-owner mutable** | `state/milestone-{id}.yml`, `phases/milestone-{id}/`, `research/milestone-{id}.md` | Exactly one writer at a time — the worktree (or main session) currently driving that milestone. **Other worktrees never touch it**, not even to read-then-write — they pull from main if they need it. |
|
|
172
|
+
| **Derived** | `state/index.yml` | Never hand-edited. Regenerated by Rollup 1.0 from `milestone-*.yml`. Only the main/orchestrator session runs rollup. |
|
|
173
|
+
| **Append-only shared** | `releases.yml` (version reservations), `state/desire-paths/*` (one file per observation) | Many writers OK; structure prevents collision. `releases.yml` uses the Version Reservation Protocol (append + commit + push before depending on the slot); desire-paths use distinct filenames so two writers never touch the same file. |
|
|
174
|
+
| **Shared mutable** | `context.md`, `refactor-backlog.yml`, `requirements/m{N}.yml` (ID space `FR-`/`DEF-`/`NFR-` is globally shared even though each milestone owns its file) | Two rules: **(a) Write only to YOUR milestone's block.** Different milestone blocks → git merges cleanly. **(b) Within a block, append-only.** Locked decisions get *struck through* (`~~old~~`) rather than rewritten in place; new sub-bullets appended below existing ones. Same-line concurrent edits become impossible by construction. |
|
|
175
|
+
| **Stable shared** | `project.yml`, `constitution.md`, `design-system.md`, `roadmap.yml`, `FORGE.md` | Rarely changes after init. Canonical on main. Worktrees lag and pull on rebase. No special protocol — git handles it because the conflict surface is tiny. |
|
|
176
|
+
|
|
177
|
+
**Cross-tree write awareness.** When a session edits a shared-mutable file's block whose milestone is **live in a different worktree** (e.g. main writes to `context.md`'s `## M9` block while m9 is running in a worktree), the edit lands cleanly on main but is **invisible to the worktree until it pulls/rebases**. This is identical to how `src/foo.ts` works under branches — there's nothing pathological about it; it's branch divergence. The framework surfaces the situation so the operator can decide:
|
|
178
|
+
|
|
179
|
+
- **`forge` Step 1** in main checks `git worktree list` and surfaces live Forge worktrees at boot — *"m9 is live in worktree X at e94b6bb. Edits to m9 state belong there; shared docs edited here need a sync."*
|
|
180
|
+
- **`discussing`** (before writing to `context.md`'s milestone block) and **`reviewing`** (before writing to `refactor-backlog.yml`) emit a one-line warning when the target block's milestone is live elsewhere. The warning informs, never blocks — main is often the canonical author of these files.
|
|
181
|
+
- **Worktree-side rebase check** (opt-in via `forge.worktree_rebase_check: true` in `project.yml`) — when `forge` boots inside a worktree, it diffs `.forge/` between the worktree's HEAD and `main`; if shared-mutable files have changed on main, it offers `git restore --source main -- <files>` or a rebase.
|
|
182
|
+
|
|
183
|
+
**Why not global `.forge/`?** A common instinct is "make `.forge/` shared so context propagates automatically." It breaks four load-bearing invariants:
|
|
184
|
+
1. **Decisions are contracts** — they're versioned with the code they govern; severing them from branches breaks the contract.
|
|
185
|
+
2. **State survives machine loss** — the multi-laptop pull-from-any-clone story depends on `.forge/` being in git.
|
|
186
|
+
3. **History time-travel** — `git checkout` an old commit and you see the plan/decisions as they were; global state makes that impossible.
|
|
187
|
+
4. **Git is the concurrency detector** — without it, two sessions writing the same field becomes silent last-write-wins.
|
|
188
|
+
|
|
189
|
+
Per-worktree `.forge/` with a sharing taxonomy is the model. See [ADR-011](../docs/decisions/ADR-011-shared-state-taxonomy.md) for the longer version.
|
|
190
|
+
|
|
191
|
+
### State Ownership rules (quick reference)
|
|
192
|
+
|
|
193
|
+
- `state/milestone-{id}.yml` is the **single source of truth**; exactly one agent owns it at a time. **Other worktrees never write it** — promoted from advice to explicit rule, because catching yourself before the first wrong edit is the part the framework can help with.
|
|
166
194
|
- Worktree / parallel agents write **only** their own milestone file and **append** desire-path files. They **never** write `index.yml`.
|
|
167
195
|
- `index.yml` is **regenerated by rollup** (read every `milestone-*.yml` → rewrite the registry) by the main/orchestrator session — on `forge` resume and at `orchestrating` teardown. The rollup is deterministic + idempotent, so it **is** the reconcile step — never a hand-merge.
|
|
196
|
+
- For shared-mutable files: write only your milestone's block; within a block, append-only (strike through, don't rewrite).
|
|
168
197
|
- Same-milestone parallel work is out of scope, guarded by the M10 claim layer.
|
|
169
198
|
|
|
170
199
|
### Version Reservation Protocol
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Migration Guide: Shared-state taxonomy (Forge 0.29.0)
|
|
2
|
+
|
|
3
|
+
Applies to any Forge project that uses git worktrees (M10 or hand-rolled). This is a **documentation + skill-behavior** change, not a state-file change — there is nothing in `.forge/` to migrate. Read this once, then the new skill behavior catches the situations.
|
|
4
|
+
|
|
5
|
+
## What changed and why
|
|
6
|
+
|
|
7
|
+
Forge's `State Ownership (multi-worktree safety)` section in `FORGE.md` previously documented two file classes — `state/milestone-{id}.yml` (single-owner) and `state/index.yml` (derived) — and was silent on every other file in `.forge/`. In practice three more classes exist, and the riskiest one (shared mutable — `context.md`, `refactor-backlog.yml`) had no rule.
|
|
8
|
+
|
|
9
|
+
A worked example from canvaz on 2026-06-15: a main-session edit to `context.md`'s `## M9` block was invisible to the live m9 worktree, because the worktree was branched off main earlier and reads `.forge/` from its own checkout. The operator had to deduce the sync gap from first principles, and nearly committed `milestone-9.yml` edits from main on the first attempt before catching themselves.
|
|
10
|
+
|
|
11
|
+
0.29.0 closes the gap with **taxonomy + visibility + structural rules**, not with a state migration. See [ADR-011](../decisions/ADR-011-shared-state-taxonomy.md) for the longer version (including why a "global `.forge/`" approach is wrong).
|
|
12
|
+
|
|
13
|
+
## The 5-class taxonomy (read this once)
|
|
14
|
+
|
|
15
|
+
| Class | Examples | Write rule |
|
|
16
|
+
|---|---|---|
|
|
17
|
+
| **Single-owner mutable** | `state/milestone-{id}.yml`, `phases/milestone-{id}/`, `research/milestone-{id}.md` | One writer at a time; other worktrees **never** touch it. Explicit rule. |
|
|
18
|
+
| **Derived** | `state/index.yml` | Never hand-edited; regenerated by Rollup 1.0. Only main/orchestrator runs rollup. |
|
|
19
|
+
| **Append-only shared** | `releases.yml`, `state/desire-paths/*` | Many writers OK; structure prevents collision. Append + commit + push before depending on the slot. |
|
|
20
|
+
| **Shared mutable** | `context.md`, `refactor-backlog.yml`, `requirements/m{N}.yml` | (a) Write only to YOUR milestone's block. (b) Within a block, append-only — strike through (`~~old~~`) superseded decisions instead of rewriting in place. |
|
|
21
|
+
| **Stable shared** | `project.yml`, `constitution.md`, `design-system.md`, `roadmap.yml`, `FORGE.md` | Rarely changes after init; canonical on main; worktrees lag and pull on rebase. No protocol — git handles it. |
|
|
22
|
+
|
|
23
|
+
The full canonical version lives in `FORGE.md` → `State Ownership`.
|
|
24
|
+
|
|
25
|
+
## What the framework now does automatically
|
|
26
|
+
|
|
27
|
+
After upgrading to 0.29.0:
|
|
28
|
+
|
|
29
|
+
- **`forge` Step 1 preflight (main checkout):** before Rollup, surfaces any live Forge worktrees and their owning milestones — *"m9 is live in worktree X at e94b6bb. Edits to its milestone state belong there; shared docs edited here need a sync."* No prescription, just visibility.
|
|
30
|
+
- **`discussing`** emits a one-line warning before writing to `context.md`'s milestone block if that milestone is live in another worktree.
|
|
31
|
+
- **`reviewing`** emits the same warning before appending to `refactor-backlog.yml` if any milestone is live in a worktree.
|
|
32
|
+
- **`discussing`** also documents the supersede protocol (strike-through old decisions, append new ones below) so concurrent edits across worktrees stay mergeable.
|
|
33
|
+
|
|
34
|
+
## Optional: enable the worktree-side rebase check
|
|
35
|
+
|
|
36
|
+
Opt-in; default off. When enabled, `forge` boot **inside a worktree** diffs `.forge/` against `main` and offers to pull individual shared-mutable / stable-shared files when they've moved.
|
|
37
|
+
|
|
38
|
+
Add to `.forge/project.yml`:
|
|
39
|
+
|
|
40
|
+
```yaml
|
|
41
|
+
forge:
|
|
42
|
+
worktree_rebase_check: true
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Trade-off: adds one git diff per `forge` boot inside worktrees. Worth it for projects that frequently update `context.md` / `constitution.md` on main while worktrees are mid-flight; overkill for projects where the main checkout is mostly read-only.
|
|
46
|
+
|
|
47
|
+
## Existing decisions in context.md — do nothing
|
|
48
|
+
|
|
49
|
+
The taxonomy is descriptive: every existing decision in `context.md` already fits the new rules (it lives under a milestone heading, was append-only in practice). No rewrite needed. **New** writes follow the protocol (use strike-through to supersede).
|
|
50
|
+
|
|
51
|
+
If a past edit broke the rule (decision rewritten in place), don't go back and reconstruct strike-throughs — the history is in git. Just follow the rule going forward.
|
|
52
|
+
|
|
53
|
+
## Detection (none needed)
|
|
54
|
+
|
|
55
|
+
There's nothing to detect on disk. The framework changes are in skills and `FORGE.md`. Upgrade and you have them.
|
|
56
|
+
|
|
57
|
+
## Validation
|
|
58
|
+
|
|
59
|
+
After `npx forge-orkes upgrade`:
|
|
60
|
+
|
|
61
|
+
- `grep "5-class\|sharing classes" .forge/FORGE.md` returns hits in the `State Ownership` section.
|
|
62
|
+
- `grep "cross-tree liveness\|append-only" .claude/skills/discussing/SKILL.md` returns the Write Protocol additions.
|
|
63
|
+
- `grep "worktree-liveness\|worktree_rebase_check" .claude/skills/forge/SKILL.md` returns the Step 1 preflight.
|
|
64
|
+
|
|
65
|
+
Then, if you have a worktree active:
|
|
66
|
+
|
|
67
|
+
- `forge` in the main checkout surfaces the live worktree at the top of its boot output.
|
|
68
|
+
- Begin a discussion in main that touches a worktree-live milestone → `discussing` emits the cross-tree warning before its first write.
|
|
@@ -101,6 +101,14 @@ orchestration: # M10 multi-agent orchestration (experiment
|
|
|
101
101
|
# ../wt/<repo> — shared dir with per-repo subfolder
|
|
102
102
|
# Do NOT point this inside the repo working tree (file watchers + indexers will recurse into the worktree).
|
|
103
103
|
|
|
104
|
+
forge: # Forge framework-level settings (all optional)
|
|
105
|
+
# upstream_repo: "https://github.com/zayneupton/forge"
|
|
106
|
+
# Where framework-scope desire paths (ADR-012) file issues when `gh`
|
|
107
|
+
# is available. Default if absent: the canonical Forge repo, so this
|
|
108
|
+
# works configless. Uncomment only to retarget a fork/mirror.
|
|
109
|
+
# worktree_rebase_check: true # Opt-in: when forge boots inside a worktree, diff .forge/ vs main and
|
|
110
|
+
# offer to rebase/restore shared-mutable files (FORGE.md State Ownership).
|
|
111
|
+
|
|
104
112
|
risks: # What could go wrong?
|
|
105
113
|
- risk: ""
|
|
106
114
|
mitigation: ""
|
|
@@ -9,12 +9,27 @@
|
|
|
9
9
|
# The date+type+milestone+slug make the name unique, so concurrent agents in
|
|
10
10
|
# different worktrees only ever ADD files — git never has to merge content.
|
|
11
11
|
#
|
|
12
|
-
# Occurrence counts are DERIVED: the `forge` skill
|
|
13
|
-
#
|
|
14
|
-
#
|
|
12
|
+
# Occurrence counts are DERIVED: the `forge` skill globs this directory and
|
|
13
|
+
# groups by (type + normalized note). A pattern appearing in 3+ files is a
|
|
14
|
+
# candidate for framework evolution, surfaced at `forge` boot — the single
|
|
15
|
+
# review owner (see ADR-012).
|
|
16
|
+
#
|
|
17
|
+
# SCOPE decides where a 3+ pattern's fix lands:
|
|
18
|
+
# project = fix targets user-owned files (constitution.md, design-system.md,
|
|
19
|
+
# context.md). Stays in-project.
|
|
20
|
+
# framework = fix targets Forge's own skills/templates. Eligible for UPSTREAM
|
|
21
|
+
# transport — forge review writes a portable block to
|
|
22
|
+
# state/desire-paths/upstream/ and (when gh + forge.upstream_repo
|
|
23
|
+
# are available) offers a GH issue against the Forge repo.
|
|
24
|
+
# Lazy migration: a file with no `scope` is read as `project`.
|
|
15
25
|
|
|
16
26
|
type: "" # deviation_pattern | tier_override | skipped_step |
|
|
17
27
|
# recurring_friction | agent_struggle | user_correction
|
|
28
|
+
scope: "" # project | framework — set AT CAPTURE by the writing
|
|
29
|
+
# skill, from the file its suggested fix targets.
|
|
30
|
+
# Absent ⇒ project (lazy migration).
|
|
31
|
+
suggestion_target: "" # optional: the skill/file the fix would touch
|
|
32
|
+
# (e.g. "planning/executing skills", "constitution.md")
|
|
18
33
|
milestone: "" # e.g. "m7" (or "global" if not milestone-scoped)
|
|
19
34
|
skill: "" # skill that observed it (executing, planning, verifying, ...)
|
|
20
35
|
first_seen: null # ISO 8601 date this observation was recorded
|