forge-orkes 0.18.1 → 0.19.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.
@@ -44,6 +44,21 @@ function copyDirRecursive(src, dest) {
44
44
  return count;
45
45
  }
46
46
 
47
+ /**
48
+ * npm strips files literally named `.gitignore` from a published tarball (it
49
+ * treats them as ignore-rules, not content), so the template ships its forge
50
+ * gitignore as `gitignore`. Rename it to the real `.forge/.gitignore` dotfile
51
+ * in the destination. Idempotent: if `gitignore` is absent it does nothing; if
52
+ * a `.gitignore` already exists it is overwritten with the shipped canonical one.
53
+ */
54
+ function materializeForgeGitignore(destForge) {
55
+ const shipped = path.join(destForge, 'gitignore');
56
+ const real = path.join(destForge, '.gitignore');
57
+ if (fs.existsSync(shipped)) {
58
+ fs.renameSync(shipped, real);
59
+ }
60
+ }
61
+
47
62
  function prompt(question) {
48
63
  const rl = readline.createInterface({
49
64
  input: process.stdin,
@@ -275,6 +290,10 @@ async function install() {
275
290
  const forgeCount = copyDirRecursive(srcForge, destForge);
276
291
  console.log(` Installed .forge/templates/ (${forgeCount} files)`);
277
292
 
293
+ // npm strips files literally named `.gitignore` from the published tarball, so
294
+ // the template ships it as `gitignore`. Materialize the real dotfile here.
295
+ materializeForgeGitignore(destForge);
296
+
278
297
  // Stamp version from package.json into settings.json
279
298
  const settingsPath = path.join(targetDir, SETTINGS_FILE);
280
299
  if (fs.existsSync(settingsPath)) {
@@ -343,6 +362,32 @@ function detectMissingLayersConfig() {
343
362
  return true;
344
363
  }
345
364
 
365
+ /**
366
+ * Detect a pre-0.19.0 state/index.yml: 0.19.0 makes index.yml a thin derived
367
+ * registry (regenerated by rollup) and moves desire_paths to append-only files.
368
+ * A legacy index.yml still carries desire_paths:/metrics:/embedded narrative, or
369
+ * is large. Returns true if index.yml exists and looks legacy; false if missing
370
+ * (not yet initialized) or already a slim registry.
371
+ */
372
+ function detectLegacyStateIndex() {
373
+ const indexYml = path.join(targetDir, '.forge', 'state', 'index.yml');
374
+ if (!fs.existsSync(indexYml)) return false;
375
+
376
+ let content;
377
+ try {
378
+ content = fs.readFileSync(indexYml, 'utf-8');
379
+ } catch {
380
+ return false;
381
+ }
382
+
383
+ // Legacy markers: shared accumulators or per-milestone narrative drifted in.
384
+ if (/^\s*desire_paths:|^\s*metrics:|current_status:/m.test(content)) return true;
385
+ // A slim registry is small; KBs of index.yml means narrative crept in.
386
+ if (Buffer.byteLength(content, 'utf-8') > 4096) return true;
387
+
388
+ return false;
389
+ }
390
+
346
391
  /**
347
392
  * After upgrade, surface any detected legacy file layouts the new framework
348
393
  * version no longer writes, or new config fields older projects lack.
@@ -382,6 +427,21 @@ function runPostUpgradeMigrationChecks() {
382
427
  console.log(' /forge then hand the guide to quick-tasking.');
383
428
  console.log();
384
429
  }
430
+
431
+ if (detectLegacyStateIndex()) {
432
+ console.log(' ⚠ Worktree-safe state: legacy state/index.yml detected (Forge 0.19.0)');
433
+ console.log(' ────────────────────────────────────────────────────────────────────');
434
+ console.log(' Forge 0.19.0 makes index.yml a derived registry (regenerated by rollup),');
435
+ console.log(' moves desire_paths to append-only files, and commits .forge/ state at every');
436
+ console.log(' phase handoff. Your index.yml predates this — it still carries desire_paths/');
437
+ console.log(' metrics or embedded narrative, which conflicts across git worktrees.');
438
+ console.log();
439
+ console.log(' Migration guide: .forge/migrations/0.19.0-worktree-safe-state.md');
440
+ console.log();
441
+ console.log(' This migration edits user-owned state, so it is guided — never automatic.');
442
+ console.log(' In Claude Code: /forge then hand the guide to quick-tasking.');
443
+ console.log();
444
+ }
385
445
  }
386
446
 
387
447
  async function upgrade() {
@@ -433,6 +493,23 @@ async function upgrade() {
433
493
  results.removed.push(...dirResult.removed);
434
494
  }
435
495
 
496
+ // 2b. Sync the forge .gitignore. Shipped as `gitignore` (npm strips dotfiles
497
+ // named .gitignore); materialize/refresh it as `.forge/.gitignore`.
498
+ const giSrc = path.join(templateDir, '.forge', 'gitignore');
499
+ const giDest = path.join(targetDir, '.forge', '.gitignore');
500
+ if (fs.existsSync(giSrc)) {
501
+ const newContent = fs.readFileSync(giSrc, 'utf-8');
502
+ const existed = fs.existsSync(giDest);
503
+ const oldContent = existed ? fs.readFileSync(giDest, 'utf-8') : null;
504
+ if (oldContent !== newContent) {
505
+ fs.mkdirSync(path.dirname(giDest), { recursive: true });
506
+ fs.writeFileSync(giDest, newContent);
507
+ results[existed ? 'updated' : 'added'].push('.forge/.gitignore');
508
+ } else {
509
+ results.unchanged.push('.forge/.gitignore');
510
+ }
511
+ }
512
+
436
513
  // 3. Smart-merge CLAUDE.md using section markers
437
514
  const claudeStatus = mergeClaudeMd();
438
515
  if (claudeStatus === 'replaced' || claudeStatus === 'appended') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "forge-orkes",
3
- "version": "0.18.1",
3
+ "version": "0.19.0",
4
4
  "description": "Set up the Forge meta-prompting framework for Claude Code in your project",
5
5
  "bin": {
6
6
  "create-forge": "./bin/create-forge.js"
@@ -18,7 +18,7 @@ Execute plan tasks. Full dev tools, strict deviation rules. Plan says X, build X
18
18
  Plan: `.forge/phases/m{M}-{N}-{name}/plan.md`, context: `.forge/context.md`, state: `.forge/state/milestone-{id}.yml`.
19
19
 
20
20
  ## Output
21
- Committed code, updated `milestone-{id}.yml` (progress/deviations), updated `index.yml` (timestamp), execution summary.
21
+ Committed code, updated `milestone-{id}.yml` (progress/deviations), execution summary. **Never write `index.yml`** — it is a derived registry; `milestone-{id}.yml` is the single source of truth, regenerated into the registry by the `forge` rollup. The phase-boundary state-sync commit (State Commit Protocol, CLAUDE.md) is run by the `executing` skill at handoff — you do per-task code commits and update the milestone file.
22
22
 
23
23
  ## Deviation Rules
24
24
 
@@ -67,6 +67,7 @@ One change per commit. Stage specific files. Rules 1,3 get own commits. Test bef
67
67
  20+ files -> spawn fresh sub-agents via Task. Coordinate via milestone state. Approaching limits -> summarize, spawn fresh. State in `.forge/state/milestone-{id}.yml`.
68
68
 
69
69
  ### 6. Update State
70
+ Write to `.forge/state/milestone-{id}.yml` only (advance `current` cursor + set `current.last_updated`). Never `index.yml`.
70
71
  ```yaml
71
72
  progress:
72
73
  - task: "{task name}"
@@ -18,7 +18,7 @@ Research -> actionable plans. Every plan passes constitutional gates with verifi
18
18
  Research findings, `.forge/templates/project.yml`, `constitution.md`, `context.md`, `state/milestone-{id}.yml` (if resuming).
19
19
 
20
20
  ## Output
21
- `.forge/` files: `phases/m{M}-{N}-{name}/plan.md` (XML tasks), `specs/`, `requirements/m{N}.yml`, `context.md`, `state/milestone-{id}.yml`.
21
+ `.forge/` files: `phases/m{M}-{N}-{name}/plan.md` (XML tasks), `specs/`, `requirements/m{N}.yml`, `context.md`, `state/milestone-{id}.yml`. **Never write `index.yml`** — it is a derived registry; `milestone-{id}.yml` is the single source of truth. The `planning` skill runs the state-sync commit at handoff (State Commit Protocol, CLAUDE.md).
22
22
 
23
23
  ## Process
24
24
 
@@ -27,7 +27,7 @@ Supplied by the reviewing skill at spawn:
27
27
 
28
28
  Additional context to read on start:
29
29
  - `.forge/project.yml` — stack, framework, database, dependencies
30
- - `.forge/state/milestone-{id}.yml` — milestone id + name
30
+ - `.forge/state/milestone-{id}.yml` — milestone id + name (source of truth; index.yml is a derived registry)
31
31
  - `.forge/constitution.md` — active gates (if present)
32
32
  - `.forge/deferred-issues.md` — pre-existing failures (architecture mode only)
33
33
 
@@ -58,7 +58,7 @@ Run: {n} | Passed: {n} | Failed: {n} | Coverage: {if available}
58
58
  ### 1. Load Criteria
59
59
  ```
60
60
  Read: .forge/phases/m{M}-{N}-{name}/plan.md → extract must_haves
61
- Read: .forge/state/milestone-{id}.yml → reported progress
61
+ Read: .forge/state/milestone-{id}.yml → reported progress (the source of truth; index.yml is a derived registry — read the milestone file, not index)
62
62
  Read: .forge/context.md → locked decisions
63
63
  Read: .forge/deferred-issues.md → known pre-existing failures (if exists; treat as advisory)
64
64
  ```
@@ -125,6 +125,7 @@ Document in `.forge/phases/m{M}-{N}-{name}/contracts/`.
125
125
 
126
126
  1. **Persist** — Confirm ADRs in `.forge/decisions/`, models and contracts in `.forge/phases/m{M}-{N}-{name}/`
127
127
  2. **Update state** — Set `current.status` to `planning` in `.forge/state/milestone-{id}.yml`
128
- 3. **Recommend context clear:**
128
+ 3. **State-sync commit** (State Commit Protocol): `git add .forge/` then `git commit -m "chore(forge): sync state after architecting — m{N} {phase-name}"` (scoped; never `git add .`).
129
+ 4. **Recommend context clear:**
129
130
 
130
- *"Architecture decisions written. `/clear` then `/forge` to continue with planning."*
131
+ *"Architecture decisions written and synced. `/clear` then `/forge` to continue with planning."*
@@ -294,14 +294,14 @@ If no milestone exists (advisory discussion — forge routed here without tier/m
294
294
  - Multi-file, refactor, feature, service changes → **Standard**
295
295
  - Major architectural, multi-subsystem, multi-phase → **Full**
296
296
  3. **Create milestone.**
297
- - Read `.forge/state/index.yml` (create from `.forge/templates/state/index.yml` if missing)
298
- - Next ID = max existing ID + 1 (or 1 if none)
297
+ - Next ID = (max id across existing `.forge/state/milestone-*.yml`) + 1 (or 1 if none)
299
298
  - Create `milestone-{id}.yml` from `.forge/templates/state/milestone.yml`:
300
299
  - `milestone.id` = {id}, `milestone.name` = brief summary of discussion topic
301
300
  - `current.tier` = detected tier
302
301
  - `current.status` = `planning` (Standard) / `architecting` (Full) / `not_started` (Quick)
302
+ - `current.last_updated` = now
303
303
  - `decisions[]` = locked decisions from `context.md`
304
- - Add entry to `index.yml`: `id`, `name`, `status: active`, `last_updated: now`
304
+ - Regenerate `.forge/state/index.yml` via the `forge` **Rollup** (read all `milestone-*.yml` → rewrite registry). Do not hand-add the entry. (Create `index.yml` from `.forge/templates/state/index.yml` first if missing.)
305
305
  4. **Update `context.md`** — scope decisions under `### M{id} — {name} (locked {date})`
306
306
  5. **Quick tier?** → *"Scope is Quick. `/clear` then `/forge {id}` for quick-tasking."* End.
307
307
  6. Proceed to Step B with new milestone.
@@ -323,4 +323,5 @@ If milestone exists → skip to Step B.
323
323
  - Add any unresolved items to `## Needs Resolution`
324
324
  - If `context.md` already exists (post-planning discussion), update relevant sections + log amendments
325
325
  2. **Update state** -- `current.status` = `planning` (`architecting` for Full) in milestone yml
326
- 3. **Recommend clear:** *"State written and verified ({N} decisions in context.md). `/clear` then `/forge` for {planning/architecting}."*
326
+ 3. **State-sync commit** (State Commit Protocol): `git add .forge/` then `git commit -m "chore(forge): sync state after discussing — m{N} {name}"` (scoped; never `git add .`).
327
+ 4. **Recommend clear:** *"State synced and verified ({N} decisions in context.md). `/clear` then `/forge` for {planning/architecting}."*
@@ -241,19 +241,19 @@ Do NOT list test failures here — pre-existing failures belong in deferred-issu
241
241
  ```
242
242
 
243
243
  ## State Updates
244
- 1. Update `.forge/state/milestone-{id}.yml` — advance `current` cursor (`current.plan`/`current.task`, and `current.phase` when a phase completes). Do **not** write a progress percent — it is derived on read by the `forge` skill from `current.phase` vs the roadmap phase count.
244
+ 1. Update `.forge/state/milestone-{id}.yml` — advance the `current` cursor (`current.plan`/`current.task`, and `current.phase` when a phase completes) and set `current.last_updated`. Do **not** write a progress percent — it is derived on read by the `forge` skill from `current.phase` vs the roadmap phase count.
245
245
  2. Record deviations in milestone state
246
- 3. Update `.forge/state/index.yml` — set `last_updated` timestamp
246
+ 3. Do **not** write `.forge/state/index.yml` — it is a derived registry; the `forge` rollup regenerates it. Worktree agents write only their own milestone file.
247
247
  4. All plans in phase complete → transition to `verifying`
248
248
 
249
249
  **When deferring an individual phase or task** (writing `status: deferred` or `deferred: true` to a phase/task entry inside the milestone state file — as opposed to milestone-wide defer which goes through the forge skill's lifecycle flow): write `deferred_at` (ISO date today) and `deferred_reason` (one-line) siblings. Old entries without these fields parse fine — lazy migration. These feed the `deferred` aggregator skill.
250
250
 
251
251
  ## Desire Path Signals
252
252
 
253
- Log to `.forge/state/index.yml desire_paths` (global, not per-milestone):
254
- - **Repeated deviations**: Same rule, same reason, more than once → `deviation_pattern`
255
- - **User corrections**: Repeated correction matching a prior one → `user_correction`, increment count
256
- - **Agent struggles**: Multiple attempts or user guidance needed → `agent_struggle`
253
+ Append **one file per observation** to `.forge/state/desire-paths/` — copy `.forge/templates/state/desire-path.yml`, name it `{YYYY-MM-DD}-{type}-{milestone}-{slug}.yml`. Never write `index.yml` and never keep a counter; recurrence is derived by globbing (so concurrent worktrees only ever add files).
254
+ - **Repeated deviations**: Same rule, same reason, more than once → `type: deviation_pattern`
255
+ - **User corrections**: Repeated correction matching a prior one → `type: user_correction`
256
+ - **Agent struggles**: Multiple attempts or user guidance needed → `type: agent_struggle`
257
257
 
258
258
  ## Cross-Layer Seam Check
259
259
 
@@ -269,7 +269,8 @@ After **both** layer plans are committed, the executing flow owns one final **se
269
269
  5. Leave the contract at `status: ratified` — the `reviewing` skill folds `delta` into the governing ADR (`status: absorbed`) at milestone landing. Do **not** absorb here.
270
270
 
271
271
  ## Phase Handoff
272
- 1. Confirm persistence — summary documented, commits made, state updated, desire paths logged
272
+ 1. Confirm persistence — summary documented, commits made, state updated, desire-path files written
273
273
  2. **Run the Cross-Layer Seam Check** (above) if this phase was a Tier-2 contract split
274
- 3. Set `current.status` to `verifying`
275
- 4. Recommend: *"Tasks committed, state updated. `/clear` then `/forge` to continue with verifying."*
274
+ 3. Set `current.status` to `verifying` in `.forge/state/milestone-{id}.yml`
275
+ 4. **State-sync commit** (State Commit Protocol): `git add .forge/` then `git commit -m "chore(forge): sync state after executing m{N} {phase-name}"`. Scoped — never `git add .`. Per-task code commits already landed; this captures the cursor + desire-path files at the phase boundary.
276
+ 5. Recommend: *"Tasks committed, state synced. `/clear` then `/forge` to continue with verifying."*
@@ -9,6 +9,22 @@ Entry point. Detect tier, route skills, manage transitions. New projects → ini
9
9
 
10
10
  ## Step 1: Read State
11
11
 
12
+ ### 1.0 State Rollup (index.yml is derived)
13
+
14
+ `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`.**
15
+
16
+ Rollup procedure (deterministic + idempotent):
17
+ 1. Glob `.forge/state/milestone-*.yml`.
18
+ 2. For each, read `milestone.id`, `milestone.name`, `current.status`, `lifecycle.{deferred_at, resumed_at}`, `current.last_updated`.
19
+ 3. Derive the registry `status`:
20
+ - **deferred** — `lifecycle.deferred_at` set and not superseded by a later `resumed_at`
21
+ - **complete** — `current.status == complete`
22
+ - **not_started** — `current.status == not_started`
23
+ - **active** — otherwise
24
+ 4. Rewrite `index.yml` `milestones:` (sorted by id) as `{id, name, status, last_updated: current.last_updated}`. No other keys.
25
+
26
+ Output is a pure function of the milestone files, so two sessions regenerating it produce identical bytes — it never needs a hand-merge. **Only the main/orchestrator session runs rollup; worktree agents never write `index.yml`** (they edit only their own `milestone-{id}.yml`).
27
+
12
28
  ### 1.1 Milestone Selection
13
29
 
14
30
  Check state files:
@@ -17,7 +33,7 @@ Check state files:
17
33
  3. Neither → init/tier detection.
18
34
 
19
35
  **With `state/index.yml`:**
20
- 1. Read for active milestones
36
+ 1. Run **Rollup (1.0)** to regenerate `index.yml` from the milestone files, then read it for active milestones
21
37
  2. **Check arg first.** `/forge 2` or `/forge "Auth system"`:
22
38
  - Match IDs (exact) or names (case-insensitive substring)
23
39
  - Found → auto-select: *"Resuming {id}: [{name}] -- {current.status}, {percent}%"*
@@ -57,13 +73,8 @@ Check `.forge/refactor-backlog.yml`:
57
73
  - `milestone.name: "Promoted from {R-id}: {backlog item title}"`
58
74
  - `milestone.origin: {R-id}`
59
75
  - `current.tier: standard`, `current.status: researching`
60
- 3. Append to `.forge/state/index.yml` `milestones:`:
61
- ```yaml
62
- - id: m-{R-id}
63
- name: "Promoted from {R-id}: {title}"
64
- status: active
65
- last_updated: "{ISO date}"
66
- ```
76
+ - `current.last_updated: {ISO date}`
77
+ 3. Run **Rollup (1.0)** — regenerates `index.yml` from the new milestone file (do not hand-append the registry entry). New milestone id is `m-{R-id}` from step 1; for numeric ids, allocate by scanning existing `milestone-*.yml` for the max id + 1.
67
78
  4. Append to `.forge/roadmap.yml` — one milestone entry + one phase (`refactor-{R-id}`, `requirements_source: refactor-backlog.yml#{R-id}`, `dependencies: []`).
68
79
  5. Update `.forge/refactor-backlog.yml` item: `status: in_progress`, add `promoted_to: m-{R-id}`.
69
80
  6. Confirm: *"Promoted {R-id} → milestone m-{R-id}. State + roadmap created. Routing to researching."*
@@ -71,16 +82,17 @@ Check `.forge/refactor-backlog.yml`:
71
82
  ```text
72
83
  backlog pickup → effort: standard
73
84
  ├─ write milestone-{m-R-id}.yml (origin: {R-id})
74
- ├─ append index.yml + roadmap.yml
85
+ ├─ rollup index.yml + append roadmap.yml
75
86
  ├─ flip backlog item (in_progress + promoted_to)
76
87
  └─ fall through → Standard tier routing
77
88
  ```
78
89
 
79
90
  Downstream skills (researching, discussing, planning, executing, verifying, reviewing) see a normal milestone — no special branching.
80
91
 
81
- Check `desire_paths` 3+ occurrences:
82
- - *"Recurring: [{description}] ({N}x). Fix via [suggestion]?"*
83
- - Agree → apply, reset. Decline → note, don't nag.
92
+ Check desire paths for 3+ occurrences: glob `.forge/state/desire-paths/*.yml`, group by `type` + normalized `note`, count each group.
93
+ - 3+ in a group → *"Recurring: [{note}] ({N}x). Fix via [suggestion]?"*
94
+ - Agree → apply; archive the group's files to `.forge/state/desire-paths/resolved/`. Decline → note, don't nag.
95
+ - (Occurrence count is derived from file count — there is no counter to reset.)
84
96
 
85
97
  ### 1.3 Interface Check
86
98
 
@@ -111,19 +123,19 @@ No match → fall through to Step 2B (tier detection).
111
123
 
112
124
  1. Validate: milestone in `index.yml`, status not `deferred`
113
125
  2. Extract reason (text after "—"/"-" or second sentence). None → ask: *"Reason for deferring?"*
114
- 3. Update `index.yml`: `status: deferred`, `last_updated`
115
- 4. Update `milestone-{id}.yml`:
126
+ 3. Update `milestone-{id}.yml`:
116
127
  - `lifecycle.deferred_at` = ISO 8601 now
117
128
  - `lifecycle.deferred_reason` = reason
118
129
  - `lifecycle.status_before_defer` = copy of `current.status`
130
+ 4. Run **Rollup (1.0)** — `index.yml` now derives `status: deferred` from the milestone file (do not hand-edit it).
119
131
  5. Update `roadmap.yml`: all phases belonging to this milestone → `status: deferred`
120
132
  6. Confirm: *"Deferred milestone {id}: {name}. Was {status}, frozen at {current.status}. Reason: {reason}."*
121
133
 
122
134
  ### Resume Operation
123
135
 
124
136
  1. Validate: milestone in `index.yml`, status is `deferred`
125
- 2. Update `index.yml`: `status: active`, `last_updated`
126
- 3. Update `milestone-{id}.yml`: `lifecycle.resumed_at` = ISO 8601 now
137
+ 2. Update `milestone-{id}.yml`: `lifecycle.resumed_at` = ISO 8601 now
138
+ 3. Run **Rollup (1.0)** — `index.yml` now derives `status: active` from the milestone file.
127
139
  4. Confirm + route: *"Resumed milestone {id}: {name}. Picking up at {current.status}."*
128
140
  5. Fall through to normal routing (Step 3) — `current.status` already frozen at correct position
129
141
 
@@ -144,10 +156,9 @@ No match → fall through to Step 2B (tier detection).
144
156
  c. Copy phase dirs → archive `phases/`
145
157
  d. Copy research file → archive `research.md` (if exists)
146
158
  e. Copy audit files → archive `audits/` (if exists)
147
- f. Remove originals after successful copy
148
- g. Remove milestone entry from `index.yml`
149
- h. Remove milestone + its phases from `roadmap.yml`
150
- i. Update `index.yml` `last_updated`
159
+ f. Remove originals after successful copy (including `milestone-{id}.yml`)
160
+ g. Remove milestone + its phases from `roadmap.yml`
161
+ h. Run **Rollup (1.0)** `index.yml` regenerates without the removed milestone
151
162
  5. Confirm: *"Archived milestone {id}: {name} → .forge/archive/milestone-{id}/. Removed from active state."*
152
163
 
153
164
  **Safety:** Copy-then-delete, not move. If copy fails, originals intact.
@@ -297,7 +308,7 @@ Phase transitions = clear boundaries. **Recommend `/clear`** after writing state
297
308
  | architecting | ADRs, data models, API contracts | planning |
298
309
  | planning | `.forge/phases/m{M}-{N}-{name}/`, `.forge/requirements/m{N}.yml`, roadmap.yml | executing |
299
310
  | executing | Committed code, execution summary, state | verifying |
300
- | verifying | Verification report, desire paths | reviewing |
311
+ | verifying | Verification report, desire-path files (`state/desire-paths/`) | reviewing |
301
312
 
302
313
  ### Context Loading on Resume
303
314
 
@@ -314,4 +325,4 @@ Branches (any tier, on-demand): debugging (stuck) · designing (UI) · securing
314
325
  Phase boundaries: `[clear]` recommended between phases to reset context.
315
326
  ```
316
327
 
317
- Update `milestone-{id}.yml` + `index.yml` `last_updated` at each transition.
328
+ Update `milestone-{id}.yml` at each transition (set `current.last_updated`). `index.yml` is **not** hand-edited — it is regenerated by Rollup (1.0) on the next `forge` run.
@@ -338,15 +338,7 @@ User selects per stack.
338
338
  3. Write `.forge/design-system.md` (if configured)
339
339
  4. Write `.forge/contracts/index.yml` (only if `layers:` has 2+ entries) — copy `.forge/templates/contracts-index.yml`, fill `layers:` from the confirmed list, leave `integration_points:` empty (first cross-layer phase populates them via planning Step 6.1)
340
340
  5. Init state:
341
- - `.forge/state/index.yml`:
342
- ```yaml
343
- milestones:
344
- - id: 1
345
- name: "{project name}"
346
- status: active # not_started | active | deferred | complete
347
- last_updated: "{date}"
348
- ```
349
- - `.forge/state/milestone-1.yml`:
341
+ - `.forge/state/milestone-1.yml` (the source of truth):
350
342
  ```yaml
351
343
  milestone:
352
344
  id: 1
@@ -358,8 +350,12 @@ User selects per stack.
358
350
  plan: null
359
351
  task: null
360
352
  status: not_started
353
+ last_updated: "{date}"
361
354
  ```
355
+ - `.forge/state/index.yml` — regenerate via the `forge` **Rollup** from the milestone file (do not hand-write the registry). It will derive `status: not_started` for milestone 1.
356
+ - `.forge/state/desire-paths/` — create the (empty) directory for append-only observations.
362
357
  6. Templates as needed
358
+ 7. **State-sync commit**: `git add .forge/` then `git commit -m "chore(forge): initialize forge state"` (scoped; never `git add .`) — so the new project's state is in git from the start.
363
359
 
364
360
  *"Initialized. Ready?"*
365
361
 
@@ -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") -> log to `state/index.yml` `desire_paths` (recurring overrides tune the threshold), fall back to Tier 1.
159
+ Block the split until confirmed. Override ("keep it one plan") -> append a `type: tier_override` file to `.forge/state/desire-paths/` (recurring overrides tune the threshold), 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
 
@@ -382,4 +382,5 @@ Done when approved.
382
382
 
383
383
  1. **Persist** -- plans `.forge/phases/`, reqs `.forge/requirements/m{N}.yml`, roadmap `.forge/roadmap.yml`, context `.forge/context.md`
384
384
  2. **State** -- `current.status` = `executing` in `.forge/state/milestone-{id}.yml`
385
- 3. *"Plan written. `/clear` then `/forge` to continue."*
385
+ 3. **State-sync commit** (State Commit Protocol): `git add .forge/` then `git commit -m "chore(forge): sync state after planning — m{N} {phase-name}"` (scoped; never `git add .`).
386
+ 4. *"Plan written and synced. `/clear` then `/forge` to continue."*
@@ -70,7 +70,7 @@ Invoked via forge routing (mid-workflow or refactor-backlog item with milestone
70
70
 
71
71
  1. Read `.forge/state/milestone-{id}.yml` for current position
72
72
  2. Follow standard workflow (above)
73
- 3. After commit: update milestone state — advance the `current` cursor if the task moved position, log deviations if any Rule 1-3 applied. Do **not** write a progress percent (derived on read by `forge`).
73
+ 3. After commit: update `milestone-{id}.yml` — advance the `current` cursor if the task moved position and set `current.last_updated`; log deviations if any Rule 1-3 applied. Do **not** write a progress percent (derived on read by `forge`) and do **not** write `index.yml` (derived). Then **state-sync commit**: `git add .forge/` && `git commit -m "chore(forge): sync state after quick-task — m{N}"` (scoped; never `git add .`).
74
74
  4. Report: fix description, files changed, current position
75
75
 
76
76
  ### Without Milestone
@@ -125,6 +125,7 @@ Artifact uses the Finding Format above, with two adjustments: prepend `Date: {YY
125
125
 
126
126
  1. **Write artifact** (see Research Artifact section above).
127
127
  2. **Update state** — Set `current.status` to `discussing` in `.forge/state/milestone-{id}.yml`
128
- 3. **Recommend context clear:**
128
+ 3. **State-sync commit** (State Commit Protocol): `git add .forge/` then `git commit -m "chore(forge): sync state after researching — m{N} {phase-name}"` (scoped; never `git add .`).
129
+ 4. **Recommend context clear:**
129
130
 
130
- *"Research complete. State written. `/clear` then `/forge` to continue with discussing."*
131
+ *"Research complete. State synced. `/clear` then `/forge` to continue with discussing."*
@@ -335,7 +335,7 @@ Missing? Create from `.forge/templates/refactor-backlog.yml`.
335
335
 
336
336
  ### Route
337
337
 
338
- **HEALTHY/WARNINGS (accepted):** `current.status: complete` in milestone + index. *"Milestone [{name}] complete. {N} backlog items."* Beads: `bd complete`.
338
+ **HEALTHY/WARNINGS (accepted):** set `current.status: complete` in `milestone-{id}.yml`, then regenerate `index.yml` via the `forge` **Rollup** (do not hand-edit index). *"Milestone [{name}] complete. {N} backlog items."* Beads: `bd complete`.
339
339
 
340
340
  **CRITICAL:** Don't complete. A) Fix->`planning` fix mode->re-verify->re-review. B) Accept risk->doc in report->complete.
341
341
 
@@ -387,5 +387,6 @@ If the milestone's phases produced `contract.md` files (planning Step 6.1 Tier 1
387
387
  1. Confirm report + backlog
388
388
  2. **Run promoted-milestone completion hook** (above) if `milestone.origin` set
389
389
  3. **Run Contract Landing** (above) for any cross-layer phases — fold ratified contracts into their ADRs
390
- 4. Set `current.status: complete` and `current.completed_at: "<ISO 8601 timestamp>"`
391
- 5. *"Milestone [{name}] complete. Report: `.forge/audits/milestone-{id}-health-report.md`. {N} backlog items. `/forge` or backlog."*
390
+ 4. Set `current.status: complete` and `current.completed_at: "<ISO 8601 timestamp>"` in `milestone-{id}.yml`, then regenerate `index.yml` via the `forge` **Rollup**
391
+ 5. **State-sync commit** (State Commit Protocol): `git add .forge/` then `git commit -m "chore(forge): sync state after reviewing — m{N} complete"` (scoped; never `git add .`).
392
+ 6. *"Milestone [{name}] complete. Report: `.forge/audits/milestone-{id}-health-report.md`. {N} backlog items. `/forge` or backlog."*
@@ -24,7 +24,7 @@ Template directory: `{source}/packages/create-forge/template/`.
24
24
  |----------|-------|----------|
25
25
  | **Framework-owned** | `.claude/agents/*.md`, `.claude/skills/*/SKILL.md` | Overwrite |
26
26
  | **Merge-owned** | `CLAUDE.md`, `.claude/settings.json` | Never auto-overwrite |
27
- | **Template-only** | `.forge/templates/**`, `.forge/migrations/**` | Overwrite |
27
+ | **Template-only** | `.forge/templates/**`, `.forge/migrations/**`, `.forge/gitignore` → `.forge/.gitignore` | Overwrite |
28
28
 
29
29
  **Never touch** user-generated files: `.forge/project.yml`, `.forge/state/`, `.forge/constitution.md`, `.forge/context.md`, `.forge/requirements/`, `.forge/roadmap.yml`, `.forge/design-system.md`, `.forge/refactor-backlog.yml`.
30
30
 
@@ -42,6 +42,8 @@ For each framework-owned file in the source template:
42
42
 
43
43
  Same process as Step 3 for `.forge/templates/**` and `.forge/migrations/**`. (Matches the npm bin's template-only dirs — keeps migration guides installed so the Step 7 pointers resolve via the in-Claude sync route, not just `npx forge-orkes upgrade`.)
44
44
 
45
+ **Also sync the forge gitignore:** the source ships it as `.forge/gitignore` (npm strips files literally named `.gitignore` from a published tarball). Copy/refresh the source's `.forge/gitignore` into the project as `.forge/.gitignore` (overwrite — it is framework-owned). Report added/updated/unchanged like any template-only file.
46
+
45
47
  ## Step 5: Handle Merge-Owned Files
46
48
 
47
49
  **`CLAUDE.md`:**
@@ -145,6 +147,35 @@ Add the layers field now? (yes/no/show guide)
145
147
 
146
148
  `upgrading` never edits `.forge/project.yml` directly — this is the only path that adds the field, via `quick-tasking`.
147
149
 
150
+ ### Pre-0.19.0 legacy `state/index.yml`
151
+
152
+ Run from project root:
153
+
154
+ ```bash
155
+ grep -qE 'desire_paths:|metrics:|current_status:' .forge/state/index.yml && echo "legacy index"
156
+ wc -c .forge/state/index.yml # slim registry is a few hundred bytes; KBs = narrative drifted in
157
+ ```
158
+
159
+ If `state/index.yml` carries `desire_paths:`/`metrics:`/embedded narrative or is large, surface:
160
+
161
+ ```
162
+ Worktree-safe state: state/index.yml predates the derived registry (Forge 0.19.0)
163
+ ─────────────────────────────────────────────────────────────────────────────────
164
+ Forge 0.19.0 makes index.yml a derived registry (regenerated by rollup), moves
165
+ desire_paths to append-only files, and commits .forge/ at every phase handoff.
166
+ A legacy index.yml conflicts across git worktrees.
167
+
168
+ A migration guide is available at: .forge/migrations/0.19.0-worktree-safe-state.md
169
+
170
+ Migrate now? (yes/no/show guide)
171
+ ```
172
+
173
+ - **yes** → invoke `quick-tasking` skill, hand it the migration guide as the task definition (secure state, slim index.yml, split desire_paths into files, drop metrics, regenerate via rollup). This edits user-owned state — confirm each lossy step (the narrative extraction) with the user.
174
+ - **show guide** → read and display the file, then re-ask
175
+ - **no** → note in upgrade report. Skills still run against a legacy index.yml; it just won't gain worktree conflict-safety until migrated.
176
+
177
+ `upgrading` never edits `.forge/state/` directly — migration runs via `quick-tasking`.
178
+
148
179
  ### Future migrations
149
180
 
150
181
  Add new detection blocks here for each Forge version that changes file layout. Pattern:
@@ -246,7 +246,7 @@ After gap closure:
246
246
 
247
247
  ## Desire Paths Retrospective
248
248
 
249
- After verification completes (PASSED or GAPS FOUND), run a quick retrospective on framework usage patterns. Update `.forge/state/index.yml desire_paths` (global, not per-milestone).
249
+ After verification completes (PASSED or GAPS FOUND), run a quick retrospective on framework usage patterns. Record each signal as **one new file** under `.forge/state/desire-paths/` (copy `.forge/templates/state/desire-path.yml`, name `{YYYY-MM-DD}-{type}-{milestone}-{slug}.yml`). Never write `index.yml`; recurrence is derived by globbing.
250
250
 
251
251
  ### Collect Signals
252
252
 
@@ -259,7 +259,7 @@ After verification completes (PASSED or GAPS FOUND), run a quick retrospective o
259
259
 
260
260
  **3. Skipped steps**: User ask to skip workflow steps? Repeated skips = friction without value.
261
261
 
262
- **4. Recurring friction**: Same problem from previous sessions? Check prior `desire_paths`. Increment counts.
262
+ **4. Recurring friction**: Same problem from previous sessions? Glob `.forge/state/desire-paths/` for matching `type` + `note`; if it recurs, add another observation file (count is the file count — do not edit existing files).
263
263
 
264
264
  **5. Agent struggles**: Agent need multiple attempts or human intervention? Log task type and failure pattern.
265
265
 
@@ -289,8 +289,9 @@ Only suggest at 3+ occurrences. One-off issues are noise.
289
289
 
290
290
  After PASSED verdict:
291
291
 
292
- 1. **Persist** — Confirm verification results documented, desire paths logged to `.forge/state/index.yml`
292
+ 1. **Persist** — Confirm verification report documented, desire-path files written to `.forge/state/desire-paths/`
293
293
  2. **Update state** — Set `current.status` to `reviewing` in `.forge/state/milestone-{id}.yml`
294
- 3. **Recommend clear:** *"State written. `/clear` then `/forge` for reviewing."*
294
+ 3. **State-sync commit** (State Commit Protocol): `git add .forge/` then `git commit -m "chore(forge): sync state after verifying — m{N} {phase-name}"` (scoped; never `git add .`).
295
+ 4. **Recommend clear:** *"State synced. `/clear` then `/forge` for reviewing."*
295
296
 
296
297
  If GAPS found, route back to planning in gap-closure mode. Context clear applies after re-verified PASSED verdict.
@@ -0,0 +1,30 @@
1
+ # Forge state — durable vs ephemeral.
2
+ #
3
+ # Everything under .forge/ IS durable and committed to version control, so
4
+ # project state survives machine loss and can be pulled from any clone:
5
+ # project.yml constitution.md design-system.md context.md roadmap.yml
6
+ # requirements/ phases/ research/ decisions/ contracts/ audits/
7
+ # refactor-backlog.yml state/ templates/ migrations/
8
+ #
9
+ # The entries below are the ONLY ephemeral exceptions — session- or
10
+ # machine-local artifacts that must never be committed.
11
+
12
+ # Active-skill marker (session-specific, created by hooks)
13
+ .active-skill
14
+
15
+ # Upgrade artifacts (generated by npx forge-orkes upgrade)
16
+ upgrade/
17
+
18
+ # Local dev source path (user-specific)
19
+ dev-source
20
+
21
+ # OS cruft
22
+ .DS_Store
23
+
24
+ # M10 orchestrator runtime (per-machine; not part of repo)
25
+ .mcp-server/claims.db
26
+ .mcp-server/*.pid
27
+ .mcp-server/node_modules/
28
+
29
+ # Orchestration locks
30
+ *.lock
@@ -0,0 +1,99 @@
1
+ # Migration Guide: Worktree-Safe Durable State (Forge 0.19.0)
2
+
3
+ Applies to projects initialized before 0.19.0 — especially any project run across multiple git worktrees.
4
+
5
+ ## Why
6
+
7
+ Two problems this release fixes:
8
+
9
+ 1. **State drifted out of git.** Skills wrote `.forge/` state to disk but never committed it on a cadence, so milestone progress could live only on your local machine — lost on hardware failure, unrecoverable from a fresh clone.
10
+ 2. **`index.yml` conflicted across worktrees.** The global `state/index.yml` was hand-edited on every cursor advance and accumulated per-milestone narrative + shared `desire_paths` lists, so parallel worktrees collided on merge.
11
+
12
+ 0.19.0 makes `index.yml` a **derived registry** (regenerated by rollup from the per-milestone files), moves desire-paths to **append-only files**, and adds a **State Commit Protocol** (every phase handoff commits `.forge/`).
13
+
14
+ ## Prerequisites
15
+
16
+ - Forge 0.19.0 installed (`npx forge-orkes upgrade`).
17
+ - A clean-ish working tree (commit or stash unrelated changes first).
18
+ - This migration edits **user-owned state** (`index.yml`, `desire_paths`), so it is **guided / human-confirmed** — never run blind. Narrative extraction in step 2 is a judgment call.
19
+
20
+ ## Detection
21
+
22
+ ```bash
23
+ # Bloated/legacy index.yml — large, or still carrying desire_paths/metrics/narrative
24
+ wc -c .forge/state/index.yml # » a few hundred bytes once migrated; KBs means legacy
25
+ grep -nE 'desire_paths:|metrics:|current_status:' .forge/state/index.yml # any hit → migrate
26
+ ```
27
+
28
+ The installer also prints a notice after `upgrade` when it detects a legacy `index.yml`.
29
+
30
+ ## Migration steps
31
+
32
+ ### 1. Secure current state first
33
+
34
+ Before changing anything, get today's state into git so nothing is lost mid-migration:
35
+
36
+ ```bash
37
+ git add .forge/
38
+ git commit -m "chore(forge): secure .forge state before 0.19.0 migration"
39
+ ```
40
+
41
+ ### 2. Slim `index.yml` to the derived registry
42
+
43
+ Move any per-milestone narrative that drifted into `index.yml` (e.g. paragraph-long `current_status:` values) **out** to the relevant `state/milestone-{id}.yml` prose fields or the milestone's audit report. Reduce each `milestones:` entry to exactly:
44
+
45
+ ```yaml
46
+ milestones:
47
+ - id: 1
48
+ name: "…"
49
+ status: complete # not_started | active | deferred | complete
50
+ last_updated: "…"
51
+ ```
52
+
53
+ This is the lossy/judgment step — do it with the agent and confirm each move. The header in `.forge/templates/state/index.yml` shows the target shape.
54
+
55
+ ### 3. Split `desire_paths:` into append-only files
56
+
57
+ For each entry under the old `desire_paths:` lists, create one file under `.forge/state/desire-paths/` from `.forge/templates/state/desire-path.yml`:
58
+
59
+ ```
60
+ .forge/state/desire-paths/{YYYY-MM-DD}-{type}-{milestone}-{slug}.yml
61
+ ```
62
+
63
+ Map the old list name to `type` (`deviation_patterns→deviation_pattern`, `tier_overrides→tier_override`, etc.). An old `occurrences: N` becomes **N files** (or one file plus a note — recurrence is now derived by counting files, not a stored number). Then delete the `desire_paths:` block from `index.yml`.
64
+
65
+ ### 4. Drop `metrics:`
66
+
67
+ Delete the `metrics:` block from `index.yml` (it had no writers). If you ever want the numbers, derive them from `git log`.
68
+
69
+ ### 5. Regenerate and commit
70
+
71
+ Run `/forge` once — its rollup step regenerates `index.yml` deterministically from the milestone files. Confirm it matches your slimmed version, then commit:
72
+
73
+ ```bash
74
+ git add .forge/
75
+ git commit -m "chore(forge): migrate to worktree-safe state (0.19.0)"
76
+ ```
77
+
78
+ ## Post-migration verification
79
+
80
+ ```bash
81
+ # index.yml is small and registry-only
82
+ grep -qE 'desire_paths:|metrics:|current_status:' .forge/state/index.yml && echo "STILL LEGACY" || echo "OK: registry-only"
83
+
84
+ # rollup is idempotent — running /forge twice produces no diff to index.yml
85
+ git diff --quiet .forge/state/index.yml && echo "OK: stable" || echo "rollup changed index — commit it"
86
+
87
+ # desire-paths now live as files
88
+ ls .forge/state/desire-paths/ 2>/dev/null
89
+ ```
90
+
91
+ ## What changes downstream
92
+
93
+ - Every phase handoff now emits a `chore(forge): sync state …` commit — your history gains small, regular state commits separate from code commits.
94
+ - Worktree/parallel agents write only their own `milestone-{id}.yml` (+ append desire-path files); `index.yml` is regenerated by the orchestrator/`forge` rollup, so it no longer conflicts.
95
+ - One agent per milestone — same-milestone parallel work is out of scope (the M10 claim layer guards it).
96
+
97
+ ## Rollback
98
+
99
+ This migration only restructures state files; it does not touch code. To roll back, `git revert` the two migration commits (steps 1 and 5) — your pre-migration `index.yml` returns intact. The 0.19.0 skills will still run against a legacy `index.yml` (they just won't get the conflict-safety benefit until migrated).
@@ -0,0 +1,31 @@
1
+ # Forge Desire-Path Observation — APPEND-ONLY
2
+ #
3
+ # One observation = one new file under state/desire-paths/. NEVER edit an
4
+ # existing observation in place and NEVER keep a mutable occurrence counter —
5
+ # that is what used to make state/index.yml conflict across worktrees.
6
+ #
7
+ # Filename: state/desire-paths/{YYYY-MM-DD}-{type}-{milestone}-{slug}.yml
8
+ # e.g. state/desire-paths/2026-06-10-recurring_friction-m7-uncommitted-state.yml
9
+ # The date+type+milestone+slug make the name unique, so concurrent agents in
10
+ # different worktrees only ever ADD files — git never has to merge content.
11
+ #
12
+ # Occurrence counts are DERIVED: the `forge` skill and the `verifying`
13
+ # retrospective glob this directory and group by (type + normalized note).
14
+ # A pattern appearing in 3+ files is a candidate for framework evolution.
15
+
16
+ type: "" # deviation_pattern | tier_override | skipped_step |
17
+ # recurring_friction | agent_struggle | user_correction
18
+ milestone: "" # e.g. "m7" (or "global" if not milestone-scoped)
19
+ skill: "" # skill that observed it (executing, planning, verifying, ...)
20
+ first_seen: null # ISO 8601 date this observation was recorded
21
+ related_files: [] # source files involved, if any
22
+ note: "" # one-line description — the grouping key for occurrence counts
23
+
24
+ # Optional type-specific detail (fill what applies to `type`):
25
+ detail:
26
+ detected: "" # tier_override: what Forge detected
27
+ overridden_to: "" # tier_override: what the user chose
28
+ rule: null # deviation_pattern: 1 | 2 | 3
29
+ correction: "" # user_correction: the repeated correction
30
+ failure_pattern: "" # agent_struggle: how the task fails
31
+ step: "" # skipped_step: the step skipped
@@ -1,51 +1,24 @@
1
- # Forge Global State — Cross-Milestone Index
2
- # Auto-managed by agents. Do not edit manually unless recovering from errors.
3
- # This file tracks global concerns. Per-milestone state lives in state/milestone-{id}.yml.
4
-
5
- milestones: # Active milestones and their high-level status
1
+ # Forge Global State — Cross-Milestone Index (DERIVED)
2
+ #
3
+ # This file is REGENERATED by the `forge` skill's rollup step from
4
+ # state/milestone-*.yml. Do NOT hand-edit it, and do NOT add per-milestone
5
+ # cursor data or narrative here — that lives in state/milestone-{id}.yml.
6
+ #
7
+ # Worktree / parallel agent sessions MUST NOT write this file. Editing only the
8
+ # per-milestone files (different files = no git conflict) and regenerating this
9
+ # registry from them is what makes Forge state safe across many worktrees.
10
+ #
11
+ # Rollup is deterministic + idempotent: regenerating from the same milestone
12
+ # files yields identical bytes, so concurrent regenerations never conflict.
13
+
14
+ milestones: # Registry rolled up from state/milestone-{id}.yml
6
15
  - id: 1
7
- name: "" # Human-readable milestone name from roadmap
8
- status: not_started # not_started | active | deferred | complete
9
- last_updated: null # ISO 8601 timestamp — used for resume default selection
10
-
11
- metrics:
12
- total_commits: 0
13
- total_files_modified: 0
14
- verification_passes: 0
15
- verification_failures: 0
16
- checkpoints_hit: 0
17
-
18
- # Desire Paths — Patterns in how the framework is actually used
19
- # Collected automatically by agents. Reviewed during verification retrospective.
20
- # When a pattern appears 3+ times, it becomes a candidate for framework evolution.
21
- # Desire paths are GLOBAL — they track framework usage across all milestones.
22
- desire_paths:
23
- deviation_patterns: [] # Repeated Rule 1/2/3 deviations (same type, same area)
24
- # - pattern: "" # e.g., "Rule 2: missing null checks in API handlers"
25
- # occurrences: 0
26
- # first_seen: null
27
- # last_seen: null
28
-
29
- tier_overrides: [] # User overriding auto-detected tier
30
- # - detected: "" # What Forge detected
31
- # overridden_to: "" # What user chose instead
32
- # reason: "" # Why (if stated)
33
-
34
- skipped_steps: [] # Steps users consistently skip or rush through
35
- # - step: "" # e.g., "constitutional gate check"
36
- # skill: ""
37
- # times_skipped: 0
38
-
39
- recurring_friction: [] # Same problem appearing across sessions
40
- # - description: "" # e.g., "Design system violations in form components"
41
- # occurrences: 0
42
- # related_files: []
43
-
44
- agent_struggles: [] # Tasks where agents consistently fail or need retries
45
- # - task_type: "" # e.g., "Responsive layout implementation"
46
- # failure_pattern: ""
47
- # occurrences: 0
48
-
49
- user_corrections: [] # User correcting agent output in the same way repeatedly
50
- # - correction: "" # e.g., "Always adds 'use client' directive"
51
- # occurrences: 0
16
+ name: "" # mirrors milestone-{id}.yml milestone.name
17
+ status: not_started # mirrors current.status: not_started | active | deferred | complete
18
+ last_updated: null # mirrors current.last_updated — used for resume default selection
19
+
20
+ # NOTE — removed from this file by design (M11):
21
+ # metrics: had zero writers; derive from `git log` if ever needed.
22
+ # desire_paths: now append-only files under state/desire-paths/ (one per
23
+ # observation) so concurrent agents never collide. Occurrence
24
+ # counts are derived by globbing that directory, not mutated here.
@@ -1,7 +1,12 @@
1
1
  # Forge Milestone State — Per-Milestone Cursor
2
2
  # Auto-managed by agents. Do not edit manually unless recovering from errors.
3
3
  # Copy to .forge/state/milestone-{id}.yml for each active milestone.
4
- # This file tracks one milestone's position. Global state lives in state/index.yml.
4
+ #
5
+ # SINGLE SOURCE OF TRUTH for this milestone. Exactly one agent/session owns this
6
+ # file at a time (enforced by the M10 claim layer). state/index.yml is DERIVED
7
+ # FROM this file by the `forge` rollup step — never the other way around.
8
+ # Because each milestone has its own file, agents on different milestones never
9
+ # touch the same file, so parallel worktrees produce no git conflicts here.
5
10
 
6
11
  milestone:
7
12
  id: null # Milestone ID from roadmap
@@ -16,6 +21,7 @@ current:
16
21
  task: null # Current task number within plan
17
22
  status: not_started # not_started | researching | discussing | planning | executing | verifying | reviewing | complete
18
23
  completed_at: null # ISO 8601 timestamp — set when status transitions to complete
24
+ last_updated: null # ISO 8601 — set at each transition; rolled up into index.yml registry for resume ordering
19
25
 
20
26
  # NO stored progress block. Progress percent is DERIVED on read, never stored.
21
27
  # The forge skill computes it from genuinely-maintained signals:
@@ -130,8 +130,9 @@ State lives in `.forge/`:
130
130
  - `design-system.md` — Component mapping table
131
131
  - `requirements/m{N}.yml` — Per-milestone structured requirements with `[NEEDS CLARIFICATION]` markers. **FR-IDs, DEF-IDs, and NFR-IDs are globally unique across all milestone files** — `FR-001` may exist in exactly one `m{N}.yml`. Before adding a new ID, scan `.forge/requirements/*.yml` for the highest in-use number and continue the sequence. On collision (e.g. during a migration), keep the older milestone's ID and renumber the newer. Concurrent milestones each own their file — no cross-stream contention on file writes, but ID space is shared. Functional requirements may carry M9 e2e gate fields (`e2e`, `observable_outcome`, `observable_outcome_hash`, `validated`) — lazy migration, absent fields default to `e2e:false`/`validated:false`.
132
132
  - `roadmap.yml` — Phases, milestones, dependencies
133
- - `state/index.yml` — Global: active milestones, desire_paths, metrics
134
- - `state/milestone-{id}.yml` — Per-milestone cursor: position, progress, decisions, blockers
133
+ - `state/index.yml` — DERIVED registry rolled up from milestone files (id, name, status, last_updated). Never hand-edited; never written by worktree agents.
134
+ - `state/milestone-{id}.yml` — Per-milestone cursor (single source of truth): position, progress, decisions, blockers. One owner at a time.
135
+ - `state/desire-paths/` — Append-only framework-usage observations, one file per observation. Occurrence counts derived by globbing (no mutable counter).
135
136
  - `context.md` — Locked decisions + deferred ideas (discuss phase)
136
137
  - `research/milestone-{id}.md` — Research findings snapshot (dated, immutable)
137
138
  - `phases/m{M}-{N}-{name}/plan-{NN}.md` — Task plans with must_haves frontmatter
@@ -143,6 +144,24 @@ State lives in `.forge/`:
143
144
  **Format**: YAML for machine state, Markdown for human content.
144
145
  **`current.status` is authoritative.** Complete only at `current.status == complete`. 100% tasks ≠ done — still needs verifying + reviewing.
145
146
 
147
+ ### State Commit Protocol
148
+
149
+ `.forge/` durable state is **committed**, not just written to disk — so project state survives machine loss and is pullable from any clone. At every phase **Handoff**, after writing state, the completing skill runs one scoped state-sync commit:
150
+
151
+ ```
152
+ git add .forge/ # scoped — respects .gitignore; never `git add .`
153
+ git commit -m "chore(forge): sync state after {phase} — m{N} {phase-name}"
154
+ ```
155
+
156
+ State-sync commits are separate from per-task code commits (which stay atomic during executing).
157
+
158
+ ### State Ownership (multi-worktree safety)
159
+
160
+ - `state/milestone-{id}.yml` is the **single source of truth**; exactly one agent owns it at a time.
161
+ - Worktree / parallel agents write **only** their own milestone file and **append** desire-path files. They **never** write `index.yml`.
162
+ - `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.
163
+ - Same-milestone parallel work is out of scope, guarded by the M10 claim layer.
164
+
146
165
  ## Deviation Rules
147
166
 
148
167
  **Full definitions:** `.claude/agents/executor.md`.
@@ -187,4 +206,5 @@ With Beads installed: `bd prime` (session context), `bd ready` (unblocked tasks)
187
206
  One commit per task. Format: `{type}({scope}): {description}`
188
207
  Types: `feat`, `fix`, `test`, `refactor`, `chore`, `docs`
189
208
  Never `git add .` or `git add -A` — stage individually.
209
+ Phase handoffs additionally emit one scoped `chore(forge): sync state …` commit (see State Commit Protocol) so `.forge/` state never drifts out of git.
190
210
  <!-- forge:end -->