forge-orkes 0.18.1 → 0.19.2
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 +134 -1
- 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 +33 -22
- 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 +1 -1
- 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 +38 -1
- package/template/.claude/skills/verifying/SKILL.md +5 -4
- package/template/.forge/gitignore +30 -0
- package/template/.forge/migrations/0.19.0-worktree-safe-state.md +110 -0
- package/template/.forge/templates/state/desire-path.yml +31 -0
- package/template/.forge/templates/state/index.yml +24 -50
- package/template/.forge/templates/state/milestone.yml +7 -1
- package/template/CLAUDE.md +22 -2
package/bin/create-forge.js
CHANGED
|
@@ -18,6 +18,29 @@ const FORGE_END = '<!-- forge:end -->';
|
|
|
18
18
|
// Framework-owned: Forge controls these entirely
|
|
19
19
|
const FRAMEWORK_OWNED_DIRS = ['.claude/agents', '.claude/skills'];
|
|
20
20
|
|
|
21
|
+
// Experimental / opt-in skills installed separately (e.g. from experimental/m10).
|
|
22
|
+
// They are NOT in the shipped base template, so the framework-owned auto-clean
|
|
23
|
+
// would otherwise delete them on upgrade. Preserve them instead.
|
|
24
|
+
const EXPERIMENTAL_SKILL_PATHS = ['.claude/skills/orchestrating'];
|
|
25
|
+
|
|
26
|
+
function isExperimentalSkillPath(displayPath) {
|
|
27
|
+
const norm = displayPath.split(path.sep).join('/');
|
|
28
|
+
return EXPERIMENTAL_SKILL_PATHS.some(
|
|
29
|
+
(p) => norm === p || norm.startsWith(p + '/')
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Compare dotted numeric versions (e.g. "0.19.1"). Returns 1 if a>b, -1 if a<b, 0 if equal.
|
|
34
|
+
function compareVersions(a, b) {
|
|
35
|
+
const pa = String(a).split('.').map((n) => parseInt(n, 10) || 0);
|
|
36
|
+
const pb = String(b).split('.').map((n) => parseInt(n, 10) || 0);
|
|
37
|
+
for (let i = 0; i < 3; i++) {
|
|
38
|
+
if ((pa[i] || 0) > (pb[i] || 0)) return 1;
|
|
39
|
+
if ((pa[i] || 0) < (pb[i] || 0)) return -1;
|
|
40
|
+
}
|
|
41
|
+
return 0;
|
|
42
|
+
}
|
|
43
|
+
|
|
21
44
|
// Template-only: reference templates Forge controls
|
|
22
45
|
const TEMPLATE_ONLY_DIRS = ['.forge/templates', '.forge/migrations'];
|
|
23
46
|
|
|
@@ -44,6 +67,21 @@ function copyDirRecursive(src, dest) {
|
|
|
44
67
|
return count;
|
|
45
68
|
}
|
|
46
69
|
|
|
70
|
+
/**
|
|
71
|
+
* npm strips files literally named `.gitignore` from a published tarball (it
|
|
72
|
+
* treats them as ignore-rules, not content), so the template ships its forge
|
|
73
|
+
* gitignore as `gitignore`. Rename it to the real `.forge/.gitignore` dotfile
|
|
74
|
+
* in the destination. Idempotent: if `gitignore` is absent it does nothing; if
|
|
75
|
+
* a `.gitignore` already exists it is overwritten with the shipped canonical one.
|
|
76
|
+
*/
|
|
77
|
+
function materializeForgeGitignore(destForge) {
|
|
78
|
+
const shipped = path.join(destForge, 'gitignore');
|
|
79
|
+
const real = path.join(destForge, '.gitignore');
|
|
80
|
+
if (fs.existsSync(shipped)) {
|
|
81
|
+
fs.renameSync(shipped, real);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
47
85
|
function prompt(question) {
|
|
48
86
|
const rl = readline.createInterface({
|
|
49
87
|
input: process.stdin,
|
|
@@ -91,7 +129,7 @@ function upgradeDir(relDir, { autoClean = false } = {}) {
|
|
|
91
129
|
const srcDir = path.join(templateDir, relDir);
|
|
92
130
|
const destDir = path.join(targetDir, relDir);
|
|
93
131
|
|
|
94
|
-
const result = { updated: [], added: [], unchanged: [], removed: [] };
|
|
132
|
+
const result = { updated: [], added: [], unchanged: [], removed: [], preserved: [] };
|
|
95
133
|
|
|
96
134
|
if (!fs.existsSync(srcDir)) return result;
|
|
97
135
|
|
|
@@ -123,6 +161,12 @@ function upgradeDir(relDir, { autoClean = false } = {}) {
|
|
|
123
161
|
for (const rel of destFiles) {
|
|
124
162
|
const srcPath = path.join(srcDir, rel);
|
|
125
163
|
if (!fs.existsSync(srcPath)) {
|
|
164
|
+
const displayPath = path.join(relDir, rel);
|
|
165
|
+
// Never auto-clean opt-in experimental skills — they live outside the base template.
|
|
166
|
+
if (autoClean && isExperimentalSkillPath(displayPath)) {
|
|
167
|
+
result.preserved.push(displayPath);
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
126
170
|
const destPath = path.join(destDir, rel);
|
|
127
171
|
if (autoClean) {
|
|
128
172
|
fs.unlinkSync(destPath);
|
|
@@ -275,6 +319,10 @@ async function install() {
|
|
|
275
319
|
const forgeCount = copyDirRecursive(srcForge, destForge);
|
|
276
320
|
console.log(` Installed .forge/templates/ (${forgeCount} files)`);
|
|
277
321
|
|
|
322
|
+
// npm strips files literally named `.gitignore` from the published tarball, so
|
|
323
|
+
// the template ships it as `gitignore`. Materialize the real dotfile here.
|
|
324
|
+
materializeForgeGitignore(destForge);
|
|
325
|
+
|
|
278
326
|
// Stamp version from package.json into settings.json
|
|
279
327
|
const settingsPath = path.join(targetDir, SETTINGS_FILE);
|
|
280
328
|
if (fs.existsSync(settingsPath)) {
|
|
@@ -343,6 +391,32 @@ function detectMissingLayersConfig() {
|
|
|
343
391
|
return true;
|
|
344
392
|
}
|
|
345
393
|
|
|
394
|
+
/**
|
|
395
|
+
* Detect a pre-0.19.0 state/index.yml: 0.19.0 makes index.yml a thin derived
|
|
396
|
+
* registry (regenerated by rollup) and moves desire_paths to append-only files.
|
|
397
|
+
* A legacy index.yml still carries desire_paths:/metrics:/embedded narrative, or
|
|
398
|
+
* is large. Returns true if index.yml exists and looks legacy; false if missing
|
|
399
|
+
* (not yet initialized) or already a slim registry.
|
|
400
|
+
*/
|
|
401
|
+
function detectLegacyStateIndex() {
|
|
402
|
+
const indexYml = path.join(targetDir, '.forge', 'state', 'index.yml');
|
|
403
|
+
if (!fs.existsSync(indexYml)) return false;
|
|
404
|
+
|
|
405
|
+
let content;
|
|
406
|
+
try {
|
|
407
|
+
content = fs.readFileSync(indexYml, 'utf-8');
|
|
408
|
+
} catch {
|
|
409
|
+
return false;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// Legacy markers: shared accumulators or per-milestone narrative drifted in.
|
|
413
|
+
if (/^\s*(desire_paths|metrics|current_status):/m.test(content)) return true;
|
|
414
|
+
// A slim registry is small; KBs of index.yml means narrative crept in.
|
|
415
|
+
if (Buffer.byteLength(content, 'utf-8') > 4096) return true;
|
|
416
|
+
|
|
417
|
+
return false;
|
|
418
|
+
}
|
|
419
|
+
|
|
346
420
|
/**
|
|
347
421
|
* After upgrade, surface any detected legacy file layouts the new framework
|
|
348
422
|
* version no longer writes, or new config fields older projects lack.
|
|
@@ -382,6 +456,21 @@ function runPostUpgradeMigrationChecks() {
|
|
|
382
456
|
console.log(' /forge then hand the guide to quick-tasking.');
|
|
383
457
|
console.log();
|
|
384
458
|
}
|
|
459
|
+
|
|
460
|
+
if (detectLegacyStateIndex()) {
|
|
461
|
+
console.log(' ⚠ Worktree-safe state: legacy state/index.yml detected (Forge 0.19.0)');
|
|
462
|
+
console.log(' ────────────────────────────────────────────────────────────────────');
|
|
463
|
+
console.log(' Forge 0.19.0 makes index.yml a derived registry (regenerated by rollup),');
|
|
464
|
+
console.log(' moves desire_paths to append-only files, and commits .forge/ state at every');
|
|
465
|
+
console.log(' phase handoff. Your index.yml predates this — it still carries desire_paths/');
|
|
466
|
+
console.log(' metrics or embedded narrative, which conflicts across git worktrees.');
|
|
467
|
+
console.log();
|
|
468
|
+
console.log(' Migration guide: .forge/migrations/0.19.0-worktree-safe-state.md');
|
|
469
|
+
console.log();
|
|
470
|
+
console.log(' This migration edits user-owned state, so it is guided — never automatic.');
|
|
471
|
+
console.log(' In Claude Code: /forge then hand the guide to quick-tasking.');
|
|
472
|
+
console.log();
|
|
473
|
+
}
|
|
385
474
|
}
|
|
386
475
|
|
|
387
476
|
async function upgrade() {
|
|
@@ -408,11 +497,28 @@ async function upgrade() {
|
|
|
408
497
|
console.log(` Installed: v${installedVersion}`);
|
|
409
498
|
console.log(` Available: v${pkgVersion}\n`);
|
|
410
499
|
|
|
500
|
+
// Downgrade guard: refuse to roll backward (overwrites newer framework files
|
|
501
|
+
// with older ones and deletes files newer versions added). Almost always a
|
|
502
|
+
// stale npx cache serving an old forge-orkes. Override with --force.
|
|
503
|
+
const force = process.argv.includes('--force');
|
|
504
|
+
if (
|
|
505
|
+
installedVersion !== 'unknown' &&
|
|
506
|
+
compareVersions(pkgVersion, installedVersion) < 0 &&
|
|
507
|
+
!force
|
|
508
|
+
) {
|
|
509
|
+
console.error(` ✖ Refusing to downgrade: installed v${installedVersion} is newer than available v${pkgVersion}.`);
|
|
510
|
+
console.error(` This usually means a stale npx cache served an old forge-orkes.`);
|
|
511
|
+
console.error(` Fix: npx forge-orkes@latest upgrade (or clear the cache: rm -rf ~/.npm/_npx)`);
|
|
512
|
+
console.error(` To downgrade intentionally: forge-orkes upgrade --force\n`);
|
|
513
|
+
process.exit(1);
|
|
514
|
+
}
|
|
515
|
+
|
|
411
516
|
const results = {
|
|
412
517
|
updated: [],
|
|
413
518
|
added: [],
|
|
414
519
|
unchanged: [],
|
|
415
520
|
removed: [],
|
|
521
|
+
preserved: [],
|
|
416
522
|
};
|
|
417
523
|
|
|
418
524
|
// 1. Process framework-owned directories (auto-clean stale files)
|
|
@@ -422,6 +528,7 @@ async function upgrade() {
|
|
|
422
528
|
results.added.push(...dirResult.added);
|
|
423
529
|
results.unchanged.push(...dirResult.unchanged);
|
|
424
530
|
results.removed.push(...dirResult.removed);
|
|
531
|
+
results.preserved.push(...dirResult.preserved);
|
|
425
532
|
}
|
|
426
533
|
|
|
427
534
|
// 2. Process template-only directories
|
|
@@ -431,6 +538,24 @@ async function upgrade() {
|
|
|
431
538
|
results.added.push(...dirResult.added);
|
|
432
539
|
results.unchanged.push(...dirResult.unchanged);
|
|
433
540
|
results.removed.push(...dirResult.removed);
|
|
541
|
+
results.preserved.push(...dirResult.preserved);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// 2b. Sync the forge .gitignore. Shipped as `gitignore` (npm strips dotfiles
|
|
545
|
+
// named .gitignore); materialize/refresh it as `.forge/.gitignore`.
|
|
546
|
+
const giSrc = path.join(templateDir, '.forge', 'gitignore');
|
|
547
|
+
const giDest = path.join(targetDir, '.forge', '.gitignore');
|
|
548
|
+
if (fs.existsSync(giSrc)) {
|
|
549
|
+
const newContent = fs.readFileSync(giSrc, 'utf-8');
|
|
550
|
+
const existed = fs.existsSync(giDest);
|
|
551
|
+
const oldContent = existed ? fs.readFileSync(giDest, 'utf-8') : null;
|
|
552
|
+
if (oldContent !== newContent) {
|
|
553
|
+
fs.mkdirSync(path.dirname(giDest), { recursive: true });
|
|
554
|
+
fs.writeFileSync(giDest, newContent);
|
|
555
|
+
results[existed ? 'updated' : 'added'].push('.forge/.gitignore');
|
|
556
|
+
} else {
|
|
557
|
+
results.unchanged.push('.forge/.gitignore');
|
|
558
|
+
}
|
|
434
559
|
}
|
|
435
560
|
|
|
436
561
|
// 3. Smart-merge CLAUDE.md using section markers
|
|
@@ -483,6 +608,14 @@ async function upgrade() {
|
|
|
483
608
|
console.log();
|
|
484
609
|
}
|
|
485
610
|
|
|
611
|
+
if (results.preserved.length > 0) {
|
|
612
|
+
console.log(` Preserved (${results.preserved.length}):`);
|
|
613
|
+
for (const f of results.preserved) {
|
|
614
|
+
console.log(` ${f} (kept — opt-in experimental skill)`);
|
|
615
|
+
}
|
|
616
|
+
console.log();
|
|
617
|
+
}
|
|
618
|
+
|
|
486
619
|
console.log(` Upgraded to v${pkgVersion}\n`);
|
|
487
620
|
|
|
488
621
|
runPostUpgradeMigrationChecks();
|
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 `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.
|
|
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}`, and the last-touched date as `current.last_updated` → else legacy `progress.last_update` → else null. (The fallback keeps pre-0.19.0 milestone files self-sourcing without editing them; active milestones gain `current.last_updated` on their next transition.)
|
|
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}` (date from step 2's fallback). 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.
|
|
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
|
-
|
|
61
|
-
|
|
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
|
-
├─
|
|
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
|
|
82
|
-
- *"Recurring: [{
|
|
83
|
-
- 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.)
|
|
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 `
|
|
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 `
|
|
126
|
-
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.
|
|
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
|
|
149
|
-
h.
|
|
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`
|
|
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,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
|
|
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. **
|
|
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."*
|
|
@@ -18,16 +18,22 @@ Check if `.forge/dev-source` exists in the project root.
|
|
|
18
18
|
|
|
19
19
|
Template directory: `{source}/packages/create-forge/template/`.
|
|
20
20
|
|
|
21
|
+
### Downgrade guard
|
|
22
|
+
|
|
23
|
+
Read the source version (`{source}/packages/create-forge/package.json` `version`) and the installed version (`.claude/settings.json` `forge.version`). **If source < installed, STOP** — do not sync. Report: *"Refusing to downgrade: installed v{installed} is newer than source v{source}. Point dev-source at a newer checkout, or confirm an intentional downgrade."* Only proceed on explicit user override. (Rolling backward overwrites newer framework files and deletes files newer versions added — and with `.claude/` often gitignored, it is unrecoverable.)
|
|
24
|
+
|
|
21
25
|
## Step 2: File Classification
|
|
22
26
|
|
|
23
27
|
| Category | Paths | Behavior |
|
|
24
28
|
|----------|-------|----------|
|
|
25
29
|
| **Framework-owned** | `.claude/agents/*.md`, `.claude/skills/*/SKILL.md` | Overwrite |
|
|
26
30
|
| **Merge-owned** | `CLAUDE.md`, `.claude/settings.json` | Never auto-overwrite |
|
|
27
|
-
| **Template-only** | `.forge/templates/**`, `.forge/migrations
|
|
31
|
+
| **Template-only** | `.forge/templates/**`, `.forge/migrations/**`, `.forge/gitignore` → `.forge/.gitignore` | Overwrite |
|
|
28
32
|
|
|
29
33
|
**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
34
|
|
|
35
|
+
**Preserve experimental skills.** Opt-in skills installed separately (e.g. `.claude/skills/orchestrating/` from `experimental/m10/`) are not in the base template. Never flag them as "removed from template" or delete them — leave them untouched.
|
|
36
|
+
|
|
31
37
|
## Step 3: Sync Framework-Owned Files
|
|
32
38
|
|
|
33
39
|
For each framework-owned file in the source template:
|
|
@@ -42,6 +48,8 @@ For each framework-owned file in the source template:
|
|
|
42
48
|
|
|
43
49
|
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
50
|
|
|
51
|
+
**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.
|
|
52
|
+
|
|
45
53
|
## Step 5: Handle Merge-Owned Files
|
|
46
54
|
|
|
47
55
|
**`CLAUDE.md`:**
|
|
@@ -145,6 +153,35 @@ Add the layers field now? (yes/no/show guide)
|
|
|
145
153
|
|
|
146
154
|
`upgrading` never edits `.forge/project.yml` directly — this is the only path that adds the field, via `quick-tasking`.
|
|
147
155
|
|
|
156
|
+
### Pre-0.19.0 legacy `state/index.yml`
|
|
157
|
+
|
|
158
|
+
Run from project root:
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
grep -qE '^[[:space:]]*(desire_paths|metrics|current_status):' .forge/state/index.yml && echo "legacy index"
|
|
162
|
+
wc -c .forge/state/index.yml # slim registry is a few hundred bytes; KBs = narrative drifted in
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
If `state/index.yml` carries `desire_paths:`/`metrics:`/embedded narrative or is large, surface:
|
|
166
|
+
|
|
167
|
+
```
|
|
168
|
+
Worktree-safe state: state/index.yml predates the derived registry (Forge 0.19.0)
|
|
169
|
+
─────────────────────────────────────────────────────────────────────────────────
|
|
170
|
+
Forge 0.19.0 makes index.yml a derived registry (regenerated by rollup), moves
|
|
171
|
+
desire_paths to append-only files, and commits .forge/ at every phase handoff.
|
|
172
|
+
A legacy index.yml conflicts across git worktrees.
|
|
173
|
+
|
|
174
|
+
A migration guide is available at: .forge/migrations/0.19.0-worktree-safe-state.md
|
|
175
|
+
|
|
176
|
+
Migrate now? (yes/no/show guide)
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
- **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.
|
|
180
|
+
- **show guide** → read and display the file, then re-ask
|
|
181
|
+
- **no** → note in upgrade report. Skills still run against a legacy index.yml; it just won't gain worktree conflict-safety until migrated.
|
|
182
|
+
|
|
183
|
+
`upgrading` never edits `.forge/state/` directly — migration runs via `quick-tasking`.
|
|
184
|
+
|
|
148
185
|
### Future migrations
|
|
149
186
|
|
|
150
187
|
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: 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 the metrics/desire-paths/narrative blocks
|
|
24
|
+
wc -c .forge/state/index.yml # » a few hundred bytes once migrated; KBs means legacy
|
|
25
|
+
# Anchored so the file's own explanatory comments don't false-positive:
|
|
26
|
+
grep -nE '^[[:space:]]*(desire_paths|metrics|current_status):' .forge/state/index.yml # any hit → migrate
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
The installer also prints a notice after `upgrade` when it detects a legacy `index.yml`.
|
|
30
|
+
|
|
31
|
+
## Migration steps
|
|
32
|
+
|
|
33
|
+
### 1. Secure current state first
|
|
34
|
+
|
|
35
|
+
Before changing anything, get today's state into git so nothing is lost mid-migration:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
git add .forge/
|
|
39
|
+
git commit -m "chore(forge): secure .forge state before 0.19.0 migration"
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### 2. Slim `index.yml` to the derived registry
|
|
43
|
+
|
|
44
|
+
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:
|
|
45
|
+
|
|
46
|
+
```yaml
|
|
47
|
+
milestones:
|
|
48
|
+
- id: 1
|
|
49
|
+
name: "…"
|
|
50
|
+
status: complete # not_started | active | deferred | complete
|
|
51
|
+
last_updated: "…"
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
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.
|
|
55
|
+
|
|
56
|
+
### 3. Split `desire_paths:` into append-only files
|
|
57
|
+
|
|
58
|
+
For each entry under the old `desire_paths:` lists, create one file under `.forge/state/desire-paths/` from `.forge/templates/state/desire-path.yml`:
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
.forge/state/desire-paths/{YYYY-MM-DD}-{type}-{milestone}-{slug}.yml
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Map the old list name to `type` (`deviation_patterns→deviation_pattern`, `tier_overrides→tier_override`, etc.). Migrate **one file per entry** — do *not* fabricate one-file-per-occurrence. The old `occurrences: N` is only an aggregate; collapse it to a single file and keep the count in a field (e.g. `detail.historical_occurrences: N`) if you want it. Forward recurrence is counted from *new* files.
|
|
65
|
+
|
|
66
|
+
**Resolved vs open:**
|
|
67
|
+
- **Still-open / could-recur** → file under `.forge/state/desire-paths/` (counts toward the future 3+ detection).
|
|
68
|
+
- **Already resolved** (pattern fixed / framework already evolved) → file under `.forge/state/desire-paths/resolved/`. The active check globs `desire-paths/*.yml` (top level only), so `resolved/` is preserved for the audit trail but excluded from the count — matching how the `forge` skill archives resolved groups.
|
|
69
|
+
|
|
70
|
+
Then delete the `desire_paths:` block from `index.yml`.
|
|
71
|
+
|
|
72
|
+
### 4. Drop `metrics:`
|
|
73
|
+
|
|
74
|
+
Delete the `metrics:` block from `index.yml` (it had no writers). If you ever want the numbers, derive them from `git log`.
|
|
75
|
+
|
|
76
|
+
### 5. Regenerate and commit
|
|
77
|
+
|
|
78
|
+
Run `/forge` once — its rollup step regenerates `index.yml` deterministically from the milestone files. Confirm it matches your slimmed version, then commit:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
git add .forge/
|
|
82
|
+
git commit -m "chore(forge): migrate to worktree-safe state (0.19.0)"
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Post-migration verification
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
# index.yml is registry-only — parse the YAML and check actual keys (a text grep
|
|
89
|
+
# false-positives on the file's explanatory comments). Uses ruby (no pip yaml needed):
|
|
90
|
+
ruby -ryaml -e 'd=YAML.load_file(".forge/state/index.yml"); bad=d.key?("metrics")||d.key?("desire_paths")||(d["milestones"]||[]).any?{|m| m.is_a?(Hash) && (m.key?("current_status")||m.key?("overall_percent"))}; puts bad ? "STILL LEGACY" : "OK: registry-only"'
|
|
91
|
+
|
|
92
|
+
# (grep alternative — anchored + comment-stripped so it doesn't match the header)
|
|
93
|
+
grep -vE '^[[:space:]]*#' .forge/state/index.yml | grep -qE '^[[:space:]]*(desire_paths|metrics|current_status):' && echo "STILL LEGACY" || echo "OK: registry-only"
|
|
94
|
+
|
|
95
|
+
# rollup is idempotent — running /forge twice produces no diff to index.yml
|
|
96
|
+
git diff --quiet .forge/state/index.yml && echo "OK: stable" || echo "rollup changed index — commit it"
|
|
97
|
+
|
|
98
|
+
# desire-paths now live as files (active at top level, resolved/ archived separately)
|
|
99
|
+
ls .forge/state/desire-paths/ .forge/state/desire-paths/resolved/ 2>/dev/null
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## What changes downstream
|
|
103
|
+
|
|
104
|
+
- Every phase handoff now emits a `chore(forge): sync state …` commit — your history gains small, regular state commits separate from code commits.
|
|
105
|
+
- 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.
|
|
106
|
+
- One agent per milestone — same-milestone parallel work is out of scope (the M10 claim layer guards it).
|
|
107
|
+
|
|
108
|
+
## Rollback
|
|
109
|
+
|
|
110
|
+
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,25 @@
|
|
|
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
|
-
|
|
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). (Tokens below are spaced to
|
|
21
|
+
# avoid tripping naive legacy-detection greps that scan for "<key>:".)
|
|
22
|
+
# metrics — had zero writers; derive from `git log` if ever needed.
|
|
23
|
+
# desire-paths — now append-only files under state/desire-paths/ (one per
|
|
24
|
+
# observation) so concurrent agents never collide. Occurrence
|
|
25
|
+
# 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,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:
|
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 -->
|