forge-orkes 0.17.0 → 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.
- package/bin/create-forge.js +136 -17
- package/package.json +1 -1
- package/template/.claude/agents/executor.md +2 -1
- package/template/.claude/agents/planner.md +1 -1
- package/template/.claude/agents/reviewer.md +1 -1
- package/template/.claude/agents/verifier.md +1 -1
- package/template/.claude/skills/architecting/SKILL.md +3 -2
- package/template/.claude/skills/discussing/SKILL.md +5 -4
- package/template/.claude/skills/executing/SKILL.md +10 -9
- package/template/.claude/skills/forge/SKILL.md +45 -28
- package/template/.claude/skills/initializing/SKILL.md +5 -9
- package/template/.claude/skills/planning/SKILL.md +3 -2
- package/template/.claude/skills/quick-tasking/SKILL.md +2 -2
- package/template/.claude/skills/researching/SKILL.md +3 -2
- package/template/.claude/skills/reviewing/SKILL.md +4 -3
- package/template/.claude/skills/upgrading/SKILL.md +62 -2
- package/template/.claude/skills/verifying/SKILL.md +5 -4
- package/template/.forge/gitignore +30 -0
- package/template/.forge/migrations/0.17.0-cross-layer-contracts.md +110 -0
- package/template/.forge/migrations/0.19.0-worktree-safe-state.md +99 -0
- package/template/.forge/templates/state/desire-path.yml +31 -0
- package/template/.forge/templates/state/index.yml +23 -50
- package/template/.forge/templates/state/milestone.yml +15 -12
- package/template/CLAUDE.md +22 -2
package/bin/create-forge.js
CHANGED
|
@@ -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)) {
|
|
@@ -318,28 +337,111 @@ function detectLegacyRequirementsLayout() {
|
|
|
318
337
|
}
|
|
319
338
|
|
|
320
339
|
/**
|
|
321
|
-
*
|
|
322
|
-
*
|
|
323
|
-
*
|
|
340
|
+
* Detect a pre-0.17.0 project.yml that has no `layers:` field. Cross-layer
|
|
341
|
+
* contract detection (planning Step 6.1) reads `layers:`; without it the planner
|
|
342
|
+
* falls back to a coarse top-level-directory heuristic. Returns true if the field
|
|
343
|
+
* is absent and no contract index has been seeded. False if project.yml is missing
|
|
344
|
+
* (not yet initialized) or already migrated.
|
|
345
|
+
*/
|
|
346
|
+
function detectMissingLayersConfig() {
|
|
347
|
+
const projectYml = path.join(targetDir, '.forge', 'project.yml');
|
|
348
|
+
if (!fs.existsSync(projectYml)) return false;
|
|
349
|
+
|
|
350
|
+
let content;
|
|
351
|
+
try {
|
|
352
|
+
content = fs.readFileSync(projectYml, 'utf-8');
|
|
353
|
+
} catch {
|
|
354
|
+
return false;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Already declared a layers: key (any value, including []) → migrated.
|
|
358
|
+
if (/^layers:/m.test(content)) return false;
|
|
359
|
+
// A seeded contract index implies layers were declared at init → migrated.
|
|
360
|
+
if (fs.existsSync(path.join(targetDir, '.forge', 'contracts', 'index.yml'))) return false;
|
|
361
|
+
|
|
362
|
+
return true;
|
|
363
|
+
}
|
|
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
|
+
|
|
391
|
+
/**
|
|
392
|
+
* After upgrade, surface any detected legacy file layouts the new framework
|
|
393
|
+
* version no longer writes, or new config fields older projects lack.
|
|
394
|
+
* Non-interactive — prints a warning with a pointer to the migration guide.
|
|
395
|
+
* Add new detection blocks here as future versions change layout.
|
|
324
396
|
*/
|
|
325
397
|
function runPostUpgradeMigrationChecks() {
|
|
326
398
|
const legacyReqs = detectLegacyRequirementsLayout();
|
|
327
|
-
if (legacyReqs.length
|
|
399
|
+
if (legacyReqs.length > 0) {
|
|
400
|
+
console.log(' ⚠ Pre-0.10.0 requirements layout detected');
|
|
401
|
+
console.log(' ─────────────────────────────────────────');
|
|
402
|
+
console.log(' Found:');
|
|
403
|
+
for (const p of legacyReqs) {
|
|
404
|
+
console.log(` ${p}`);
|
|
405
|
+
}
|
|
406
|
+
console.log();
|
|
407
|
+
console.log(' Forge 0.10.0+ uses per-milestone files at .forge/requirements/m{N}.yml.');
|
|
408
|
+
console.log(' Migration guide: .forge/migrations/0.10.0-per-milestone-requirements.md');
|
|
409
|
+
console.log();
|
|
410
|
+
console.log(' Run the migration before your next planning cycle. In Claude Code:');
|
|
411
|
+
console.log(' /forge then hand the guide to quick-tasking, or invoke Skill(quick-tasking)');
|
|
412
|
+
console.log(' with the guide path as the task definition.');
|
|
413
|
+
console.log();
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
if (detectMissingLayersConfig()) {
|
|
417
|
+
console.log(' ⚠ Cross-layer contracts: project.yml has no `layers:` field (Forge 0.17.0)');
|
|
418
|
+
console.log(' ──────────────────────────────────────────────────────────────────────');
|
|
419
|
+
console.log(' Forge 0.17.0 added cross-layer contract detection (planning Step 6.1),');
|
|
420
|
+
console.log(' which reads `layers:` from .forge/project.yml. Your project.yml predates');
|
|
421
|
+
console.log(' this field, so planning falls back to a coarse top-level-directory heuristic.');
|
|
422
|
+
console.log();
|
|
423
|
+
console.log(' Migration guide: .forge/migrations/0.17.0-cross-layer-contracts.md');
|
|
424
|
+
console.log();
|
|
425
|
+
console.log(' Quick fix: add a `layers:` list to .forge/project.yml — or `layers: []`');
|
|
426
|
+
console.log(' for a single-layer project to silence this notice. In Claude Code:');
|
|
427
|
+
console.log(' /forge then hand the guide to quick-tasking.');
|
|
428
|
+
console.log();
|
|
429
|
+
}
|
|
328
430
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
console.log(
|
|
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();
|
|
334
444
|
}
|
|
335
|
-
console.log();
|
|
336
|
-
console.log(' Forge 0.10.0+ uses per-milestone files at .forge/requirements/m{N}.yml.');
|
|
337
|
-
console.log(' Migration guide: .forge/migrations/0.10.0-per-milestone-requirements.md');
|
|
338
|
-
console.log();
|
|
339
|
-
console.log(' Run the migration before your next planning cycle. In Claude Code:');
|
|
340
|
-
console.log(' /forge then hand the guide to quick-tasking, or invoke Skill(quick-tasking)');
|
|
341
|
-
console.log(' with the guide path as the task definition.');
|
|
342
|
-
console.log();
|
|
343
445
|
}
|
|
344
446
|
|
|
345
447
|
async function upgrade() {
|
|
@@ -391,6 +493,23 @@ async function upgrade() {
|
|
|
391
493
|
results.removed.push(...dirResult.removed);
|
|
392
494
|
}
|
|
393
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
|
+
|
|
394
513
|
// 3. Smart-merge CLAUDE.md using section markers
|
|
395
514
|
const claudeStatus = mergeClaudeMd();
|
|
396
515
|
if (claudeStatus === 'replaced' || claudeStatus === 'appended') {
|
package/package.json
CHANGED
|
@@ -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),
|
|
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. **
|
|
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
|
-
-
|
|
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
|
-
-
|
|
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. **
|
|
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 plan
|
|
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.
|
|
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
|
-
|
|
254
|
-
- **Repeated deviations**: Same rule, same reason, more than once → `deviation_pattern`
|
|
255
|
-
- **User corrections**: Repeated correction matching a prior one → `user_correction
|
|
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
|
|
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.
|
|
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,17 +33,24 @@ Check state files:
|
|
|
17
33
|
3. Neither → init/tier detection.
|
|
18
34
|
|
|
19
35
|
**With `state/index.yml`:**
|
|
20
|
-
1.
|
|
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
|
-
- Found → auto-select: *"Resuming {id}: [{name}] -- {current.status}, {
|
|
39
|
+
- Found → auto-select: *"Resuming {id}: [{name}] -- {current.status}, {percent}%"*
|
|
24
40
|
- No match → *"No match for '{arg}'. Active:"*
|
|
25
41
|
3. **Multiple (no arg):** Show active + not_started milestones with status + `last_updated`. Default most recent. After main list, show **Deferred:** section (id, name, frozen status `was: {current.status}`, defer date, reason). *"{N} active. Most recent: [{name}] ({current.status}, {date}). This one, or switch?"*
|
|
26
|
-
4. **One:** Auto-select. *"Resuming: [{name}] -- {current.status}, {
|
|
42
|
+
4. **One:** Auto-select. *"Resuming: [{name}] -- {current.status}, {percent}%"*
|
|
27
43
|
5. **None active:** If deferred exist, mention them: *"No active milestones. {N} deferred — 'resume milestone {id}' to reactivate."* Else → init or create.
|
|
28
44
|
6. Load `milestone-{id}.yml`
|
|
29
|
-
7. **Route on `current.status`, NOT `
|
|
30
|
-
8. Report + **immediately route** (Step 3). Show: `current.status`, phase labels (Executed/Verified/Pending/In progress -- **never "Complete" for unverified**), `
|
|
45
|
+
7. **Route on `current.status`, NOT `{percent}`.** Complete only at `complete`. 100% phases ≠ done -- verifying + reviewing must run.
|
|
46
|
+
8. Report + **immediately route** (Step 3). Show: `current.status`, phase labels (Executed/Verified/Pending/In progress -- **never "Complete" for unverified**), `{percent}`. **No menus.** Position → action → invoke.
|
|
47
|
+
|
|
48
|
+
**`{percent}` is derived on read — never stored.** Compute it, don't look it up:
|
|
49
|
+
- `total` = number of phases listed for this milestone in `.forge/roadmap.yml` (the milestone's `phases:` list length).
|
|
50
|
+
- `done` = `current.status == complete` → `total`; else `max(0, current.phase - 1)` (phases before the active one count as done).
|
|
51
|
+
- `{percent}` = `total > 0` → `round(done / total * 100)`; `total` unknown/0 → omit the percent from the line.
|
|
52
|
+
|
|
53
|
+
Uses only signals that stay current (`current.phase`, `current.status`, the roadmap phase list). Old milestone files may still carry a vestigial `progress:` block — ignore it; derive anyway.
|
|
31
54
|
|
|
32
55
|
Beads enabled (`forge.beads_integration: true`) → `bd prime`. Optional.
|
|
33
56
|
|
|
@@ -50,14 +73,8 @@ Check `.forge/refactor-backlog.yml`:
|
|
|
50
73
|
- `milestone.name: "Promoted from {R-id}: {backlog item title}"`
|
|
51
74
|
- `milestone.origin: {R-id}`
|
|
52
75
|
- `current.tier: standard`, `current.status: researching`
|
|
53
|
-
- `
|
|
54
|
-
3.
|
|
55
|
-
```yaml
|
|
56
|
-
- id: m-{R-id}
|
|
57
|
-
name: "Promoted from {R-id}: {title}"
|
|
58
|
-
status: active
|
|
59
|
-
last_updated: "{ISO date}"
|
|
60
|
-
```
|
|
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.
|
|
61
78
|
4. Append to `.forge/roadmap.yml` — one milestone entry + one phase (`refactor-{R-id}`, `requirements_source: refactor-backlog.yml#{R-id}`, `dependencies: []`).
|
|
62
79
|
5. Update `.forge/refactor-backlog.yml` item: `status: in_progress`, add `promoted_to: m-{R-id}`.
|
|
63
80
|
6. Confirm: *"Promoted {R-id} → milestone m-{R-id}. State + roadmap created. Routing to researching."*
|
|
@@ -65,16 +82,17 @@ Check `.forge/refactor-backlog.yml`:
|
|
|
65
82
|
```text
|
|
66
83
|
backlog pickup → effort: standard
|
|
67
84
|
├─ write milestone-{m-R-id}.yml (origin: {R-id})
|
|
68
|
-
├─
|
|
85
|
+
├─ rollup index.yml + append roadmap.yml
|
|
69
86
|
├─ flip backlog item (in_progress + promoted_to)
|
|
70
87
|
└─ fall through → Standard tier routing
|
|
71
88
|
```
|
|
72
89
|
|
|
73
90
|
Downstream skills (researching, discussing, planning, executing, verifying, reviewing) see a normal milestone — no special branching.
|
|
74
91
|
|
|
75
|
-
Check
|
|
76
|
-
- *"Recurring: [{
|
|
77
|
-
- Agree → apply
|
|
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.)
|
|
78
96
|
|
|
79
97
|
### 1.3 Interface Check
|
|
80
98
|
|
|
@@ -105,19 +123,19 @@ No match → fall through to Step 2B (tier detection).
|
|
|
105
123
|
|
|
106
124
|
1. Validate: milestone in `index.yml`, status not `deferred`
|
|
107
125
|
2. Extract reason (text after "—"/"-" or second sentence). None → ask: *"Reason for deferring?"*
|
|
108
|
-
3. Update `
|
|
109
|
-
4. Update `milestone-{id}.yml`:
|
|
126
|
+
3. Update `milestone-{id}.yml`:
|
|
110
127
|
- `lifecycle.deferred_at` = ISO 8601 now
|
|
111
128
|
- `lifecycle.deferred_reason` = reason
|
|
112
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).
|
|
113
131
|
5. Update `roadmap.yml`: all phases belonging to this milestone → `status: deferred`
|
|
114
132
|
6. Confirm: *"Deferred milestone {id}: {name}. Was {status}, frozen at {current.status}. Reason: {reason}."*
|
|
115
133
|
|
|
116
134
|
### Resume Operation
|
|
117
135
|
|
|
118
136
|
1. Validate: milestone in `index.yml`, status is `deferred`
|
|
119
|
-
2. Update `
|
|
120
|
-
3.
|
|
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.
|
|
121
139
|
4. Confirm + route: *"Resumed milestone {id}: {name}. Picking up at {current.status}."*
|
|
122
140
|
5. Fall through to normal routing (Step 3) — `current.status` already frozen at correct position
|
|
123
141
|
|
|
@@ -138,10 +156,9 @@ No match → fall through to Step 2B (tier detection).
|
|
|
138
156
|
c. Copy phase dirs → archive `phases/`
|
|
139
157
|
d. Copy research file → archive `research.md` (if exists)
|
|
140
158
|
e. Copy audit files → archive `audits/` (if exists)
|
|
141
|
-
f. Remove originals after successful copy
|
|
142
|
-
g. Remove milestone
|
|
143
|
-
h.
|
|
144
|
-
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
|
|
145
162
|
5. Confirm: *"Archived milestone {id}: {name} → .forge/archive/milestone-{id}/. Removed from active state."*
|
|
146
163
|
|
|
147
164
|
**Safety:** Copy-then-delete, not move. If copy fails, originals intact.
|
|
@@ -196,7 +213,7 @@ Tier + state → invoke via `Skill` tool. All phases use `Skill()`.
|
|
|
196
213
|
|
|
197
214
|
1. Read `milestone-{id}.yml` → 2. Check advancement → 3. `current.status` → skill → 4. Brief + route:
|
|
198
215
|
|
|
199
|
-
*"Milestone {id}: {name} | {current.status} → {next} ({model}) | {
|
|
216
|
+
*"Milestone {id}: {name} | {current.status} → {next} ({model}) | {percent}% | Routing..."*
|
|
200
217
|
|
|
201
218
|
Briefing, not prompt -- default = forward.
|
|
202
219
|
|
|
@@ -291,7 +308,7 @@ Phase transitions = clear boundaries. **Recommend `/clear`** after writing state
|
|
|
291
308
|
| architecting | ADRs, data models, API contracts | planning |
|
|
292
309
|
| planning | `.forge/phases/m{M}-{N}-{name}/`, `.forge/requirements/m{N}.yml`, roadmap.yml | executing |
|
|
293
310
|
| executing | Committed code, execution summary, state | verifying |
|
|
294
|
-
| verifying | Verification report, desire paths | reviewing |
|
|
311
|
+
| verifying | Verification report, desire-path files (`state/desire-paths/`) | reviewing |
|
|
295
312
|
|
|
296
313
|
### Context Loading on Resume
|
|
297
314
|
|
|
@@ -308,4 +325,4 @@ Branches (any tier, on-demand): debugging (stuck) · designing (UI) · securing
|
|
|
308
325
|
Phase boundaries: `[clear]` recommended between phases to reset context.
|
|
309
326
|
```
|
|
310
327
|
|
|
311
|
-
Update `milestone-{id}.yml`
|
|
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/
|
|
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") ->
|
|
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.
|
|
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,8 +70,8 @@ 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
|
|
74
|
-
4. Report: fix description, files changed,
|
|
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
|
+
4. Report: fix description, files changed, current position
|
|
75
75
|
|
|
76
76
|
### Without Milestone
|
|
77
77
|
|
|
@@ -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. **
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
|
@@ -40,7 +40,9 @@ For each framework-owned file in the source template:
|
|
|
40
40
|
|
|
41
41
|
## Step 4: Sync Template-Only Files
|
|
42
42
|
|
|
43
|
-
Same process as Step 3 for `.forge/templates/**`.
|
|
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
|
+
|
|
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.
|
|
44
46
|
|
|
45
47
|
## Step 5: Handle Merge-Owned Files
|
|
46
48
|
|
|
@@ -116,6 +118,64 @@ Run the migration now? (yes/no/show guide)
|
|
|
116
118
|
- **show guide** → read and display the file, then re-ask
|
|
117
119
|
- **no** → note in upgrade report. Skills will continue reading the legacy path as deprecated; new writes still go to the new path, causing split-brain state. Recommend running migration before next planning cycle.
|
|
118
120
|
|
|
121
|
+
### Pre-0.17.0 missing `layers:` field
|
|
122
|
+
|
|
123
|
+
Run from project root:
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
grep -q '^layers:' .forge/project.yml || echo "no layers field"
|
|
127
|
+
ls .forge/contracts/index.yml 2>/dev/null
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
If `project.yml` has no `layers:` key AND `.forge/contracts/index.yml` is absent, surface:
|
|
131
|
+
|
|
132
|
+
```
|
|
133
|
+
Cross-layer contracts: project.yml predates the `layers:` field (Forge 0.17.0)
|
|
134
|
+
─────────────────────────────────────────────────────────────────────────────
|
|
135
|
+
Forge 0.17.0 added cross-layer contract detection (planning Step 6.1), which
|
|
136
|
+
reads `layers:` from .forge/project.yml. Without it, planning falls back to a
|
|
137
|
+
coarse top-level-directory heuristic.
|
|
138
|
+
|
|
139
|
+
A migration guide is available at: .forge/migrations/0.17.0-cross-layer-contracts.md
|
|
140
|
+
|
|
141
|
+
Add the layers field now? (yes/no/show guide)
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
- **yes** → invoke `quick-tasking` skill, hand it the migration guide as the task definition (identify layers, write `layers:` to project.yml, seed `.forge/contracts/index.yml` for multi-layer projects)
|
|
145
|
+
- **show guide** → read and display the file, then re-ask
|
|
146
|
+
- **no** → note in upgrade report. Planning still works on the fallback heuristic; cross-layer detection is just imprecise until `layers:` is declared. A single-layer project can set `layers: []` to opt out and clear the notice.
|
|
147
|
+
|
|
148
|
+
`upgrading` never edits `.forge/project.yml` directly — this is the only path that adds the field, via `quick-tasking`.
|
|
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
|
+
|
|
119
179
|
### Future migrations
|
|
120
180
|
|
|
121
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.
|
|
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?
|
|
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
|
|
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. **
|
|
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,110 @@
|
|
|
1
|
+
# Migration Guide: Cross-Layer Contracts (Forge 0.17.0)
|
|
2
|
+
|
|
3
|
+
Forge 0.17.0 adds **cross-layer contract detection** to planning (Step 6.1). When a phase introduces or changes an interface that one architectural layer *produces* and another *consumes*, the planner pins the shape in a `contract.md` so an agent building one layer in isolation never has to guess the other's shape.
|
|
4
|
+
|
|
5
|
+
The detector reads a new `layers:` field from `.forge/project.yml`. Projects created before 0.17.0 have no `layers:` field, so the detector falls back to a coarse top-level-directory heuristic. This guide adds the field (and, for genuinely layered projects, a durable contract index) so detection runs precisely. Safe for an agent to run autonomously.
|
|
6
|
+
|
|
7
|
+
## Why
|
|
8
|
+
|
|
9
|
+
`upgrading` syncs framework files and reference templates but, by design, **never edits your live `.forge/project.yml`** — that is user-owned state. So the new `layers:` field is not added automatically on upgrade. Until it exists, planning Step 6.1 cannot read declared layers and falls back to guessing from top-level source directories, which is noisy and imprecise.
|
|
10
|
+
|
|
11
|
+
Adding `layers:` once makes the detection deterministic. A single-layer project sets `layers: []` to opt out cleanly and silence the upgrade notice.
|
|
12
|
+
|
|
13
|
+
## Prerequisites
|
|
14
|
+
|
|
15
|
+
1. Forge framework files upgraded to ≥0.17.0 (run `npx forge-orkes upgrade`, or `Skill(upgrading)` for a local dev sync).
|
|
16
|
+
2. Working tree clean or changes committed — this touches `project.yml` and optionally adds one new file.
|
|
17
|
+
|
|
18
|
+
## Detection
|
|
19
|
+
|
|
20
|
+
Run from project root. Migration is suggested if `layers:` is absent:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
# No `layers:` key in project.yml → migration applies
|
|
24
|
+
grep -q '^layers:' .forge/project.yml || echo "no layers field — migrate"
|
|
25
|
+
|
|
26
|
+
# Already has a contract index? Then layers were declared at init — likely done.
|
|
27
|
+
ls .forge/contracts/index.yml 2>/dev/null
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
If `project.yml` already contains a `layers:` key (any value, including `[]`), migration is complete — stop here.
|
|
31
|
+
|
|
32
|
+
## Migration steps
|
|
33
|
+
|
|
34
|
+
### 1. Identify the project's layers
|
|
35
|
+
|
|
36
|
+
A **layer** is a directory whose code is *produced for* or *consumed by* another across a typed boundary — `engine ↔ ui`, `core ↔ plugins`, `api ↔ web`, `native ↔ bindings`. A single cohesive codebase with no internal producer→consumer boundary is **not** layered.
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
ls -d */ src/*/ 2>/dev/null # top-level + src subdirs as candidates
|
|
40
|
+
# Look for cross-boundary imports: ui importing engine types, generated bindings,
|
|
41
|
+
# ABI/descriptor/schema files shared between directories.
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Decisive litmus: *"Would an agent building one directory in isolation have to guess the shape another directory owns?"* If no — it is not a cross-layer boundary.
|
|
45
|
+
|
|
46
|
+
### 2. Add the `layers:` field to project.yml
|
|
47
|
+
|
|
48
|
+
**Multi-layer project** — list each layer as `{name, path}`:
|
|
49
|
+
|
|
50
|
+
```yaml
|
|
51
|
+
layers:
|
|
52
|
+
- name: engine
|
|
53
|
+
path: src/engine/
|
|
54
|
+
- name: ui
|
|
55
|
+
path: src/ui/
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**Single-layer project** — declare empty to opt out and silence the upgrade notice:
|
|
59
|
+
|
|
60
|
+
```yaml
|
|
61
|
+
layers: []
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Place it near the top of `project.yml` (the template puts it just after `tech_stack`). Keep the file under the 5 KB size gate.
|
|
65
|
+
|
|
66
|
+
### 3. (Multi-layer only) Seed the durable contract index
|
|
67
|
+
|
|
68
|
+
Standing integration points and their governing ADRs live in `.forge/contracts/index.yml`. Create it from the template so planning Step 6.1 can map an integration point to its authoritative ADR:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
mkdir -p .forge/contracts
|
|
72
|
+
cp .forge/templates/contracts-index.yml .forge/contracts/index.yml
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Then edit `.forge/contracts/index.yml`:
|
|
76
|
+
- Fill `layers:` to match what you wrote in `project.yml`.
|
|
77
|
+
- Add one `integration_points:` entry per standing boundary, each pointing at its governing ADR in `.forge/decisions/`. Leave `integration_points: []` if you have no ADRs yet — the first cross-layer phase will populate it.
|
|
78
|
+
|
|
79
|
+
Single-layer projects skip this step entirely.
|
|
80
|
+
|
|
81
|
+
## Post-migration verification
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
# Should print the layers key (non-empty list, or [])
|
|
85
|
+
grep -A4 '^layers:' .forge/project.yml
|
|
86
|
+
|
|
87
|
+
# Multi-layer projects: index exists and is not the unfilled template
|
|
88
|
+
test -f .forge/contracts/index.yml && echo "contract index present"
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Commit as a single change:
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
chore(forge): declare layers for cross-layer contract detection (0.17.0)
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## What changes downstream
|
|
98
|
+
|
|
99
|
+
After migration, planning Step 6.1 reads declared layers instead of guessing:
|
|
100
|
+
|
|
101
|
+
- **planning** classifies each phase: Tier 0 (no cross-layer delta), Tier 1 (pin `contract.md`, one plan), or Tier 2 (split into producer/consumer plans against a frozen contract).
|
|
102
|
+
- **executing** runs a seam check on Tier-2 phases — merges the layer plans and verifies the producer's emitted shape matches what the consumer built against.
|
|
103
|
+
- **reviewing** folds each phase `contract.md` delta into its governing ADR (`status: absorbed`) at milestone close.
|
|
104
|
+
- `upgrading` continues to protect `.forge/project.yml` and `.forge/contracts/` as user-owned — it will not overwrite your `layers:` field on future upgrades.
|
|
105
|
+
|
|
106
|
+
A project that stays `layers: []` keeps single-plan decomposition everywhere — the detector is a no-op, exactly as before 0.17.0.
|
|
107
|
+
|
|
108
|
+
## Rollback
|
|
109
|
+
|
|
110
|
+
Reversible by `git revert` of the migration commit. Removing the `layers:` field returns planning to the top-level-directory fallback; no other state is affected.
|
|
@@ -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
|
-
#
|
|
3
|
-
# This file
|
|
4
|
-
|
|
5
|
-
|
|
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: "" #
|
|
8
|
-
status: not_started # not_started | active | deferred | complete
|
|
9
|
-
last_updated: null #
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
#
|
|
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,18 +21,16 @@ 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
|
-
progress
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
# The authoritative workflow position is current.status — not this percentage.
|
|
29
|
-
# Agents: do NOT set overall_percent above 100 or use it to determine if a milestone is "done".
|
|
30
|
-
# A milestone is only done when current.status == "complete".
|
|
26
|
+
# NO stored progress block. Progress percent is DERIVED on read, never stored.
|
|
27
|
+
# The forge skill computes it from genuinely-maintained signals:
|
|
28
|
+
# total = number of phases listed for this milestone in roadmap.yml
|
|
29
|
+
# done = current.phase - 1 (phases before the active one), or total when current.status == complete
|
|
30
|
+
# percent = round(done / total * 100)
|
|
31
|
+
# Storing a percent here drifts — agents reliably advance current.phase/current.status
|
|
32
|
+
# but did not reliably recompute a stored percentage. current.status is authoritative
|
|
33
|
+
# for "is it done?" (only `complete` means done); the derived percent is display-only.
|
|
31
34
|
|
|
32
35
|
decisions: # Locked decisions (synced from context.md)
|
|
33
36
|
- decision: ""
|
package/template/CLAUDE.md
CHANGED
|
@@ -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` —
|
|
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 -->
|