forge-orkes 0.19.2 → 0.23.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 +93 -55
- package/package.json +1 -1
- package/template/.claude/agents/executor.md +2 -2
- package/template/.claude/agents/planner.md +1 -1
- package/template/.claude/agents/verifier.md +2 -2
- package/template/.claude/skills/architecting/SKILL.md +3 -3
- package/template/.claude/skills/discussing/SKILL.md +1 -1
- package/template/.claude/skills/executing/SKILL.md +14 -4
- package/template/.claude/skills/forge/SKILL.md +9 -3
- package/template/.claude/skills/planning/SKILL.md +11 -10
- package/template/.claude/skills/quick-tasking/SKILL.md +3 -2
- package/template/.claude/skills/reviewing/SKILL.md +16 -1
- package/template/.claude/skills/upgrading/SKILL.md +93 -9
- package/template/.claude/skills/verifying/SKILL.md +1 -1
- package/template/{CLAUDE.md → .forge/FORGE.md} +16 -4
- package/template/.forge/gitignore +1 -1
- package/template/.forge/migrations/0.20.0-nested-phase-layout.md +168 -0
- package/template/.forge/migrations/0.22.0-backlog-compaction.md +186 -0
- package/template/.forge/releases.yml +29 -0
- package/template/.forge/templates/contract.md +1 -1
- package/template/.forge/templates/framework-absorption/gsd.md +1 -1
- package/template/.forge/templates/plan.md +2 -2
- package/template/.forge/templates/refactor-backlog.yml +9 -4
- package/template/.forge/templates/roadmap.yml +4 -3
package/bin/create-forge.js
CHANGED
|
@@ -8,8 +8,23 @@ const templateDir = path.join(__dirname, '..', 'template');
|
|
|
8
8
|
const targetDir = process.cwd();
|
|
9
9
|
const pkgVersion = require('../package.json').version;
|
|
10
10
|
|
|
11
|
-
// ---
|
|
12
|
-
|
|
11
|
+
// --- CLAUDE.md import management ---
|
|
12
|
+
|
|
13
|
+
// The framework prose lives in .forge/FORGE.md (a framework-owned file). CLAUDE.md
|
|
14
|
+
// itself only carries a single native Claude Code memory import line pointing at it,
|
|
15
|
+
// so CLAUDE.md belongs entirely to the user and upgrades never rewrite their content.
|
|
16
|
+
const FORGE_IMPORT_LINE = '@.forge/FORGE.md';
|
|
17
|
+
// Tolerate trailing whitespace / CRLF on the import line so we never duplicate it.
|
|
18
|
+
const FORGE_IMPORT_RE = /^@\.forge\/FORGE\.md\s*$/m;
|
|
19
|
+
// Stub written when no CLAUDE.md exists yet: one explanatory comment + the import line.
|
|
20
|
+
const CLAUDE_STUB =
|
|
21
|
+
'<!-- Forge loads its workflow context from the import below. ' +
|
|
22
|
+
'The rest of this file is yours — add project instructions freely. -->\n' +
|
|
23
|
+
FORGE_IMPORT_LINE + '\n';
|
|
24
|
+
|
|
25
|
+
// Legacy section markers. These survive SOLELY as migration detection for pre-0.20
|
|
26
|
+
// installs whose CLAUDE.md still embeds the framework prose between these markers.
|
|
27
|
+
// Forge no longer writes markers — new installs get the @import line instead.
|
|
13
28
|
const FORGE_START = '<!-- forge:start -->';
|
|
14
29
|
const FORGE_END = '<!-- forge:end -->';
|
|
15
30
|
|
|
@@ -185,60 +200,61 @@ function upgradeDir(relDir, { autoClean = false } = {}) {
|
|
|
185
200
|
}
|
|
186
201
|
|
|
187
202
|
/**
|
|
188
|
-
*
|
|
189
|
-
*
|
|
190
|
-
*
|
|
191
|
-
*
|
|
192
|
-
*
|
|
203
|
+
* Guarantee CLAUDE.md carries the single `@.forge/FORGE.md` import line, migrating
|
|
204
|
+
* any legacy embedded forge section out in one pass. The framework prose itself lives
|
|
205
|
+
* in .forge/FORGE.md (synced separately) — this function never writes prose into
|
|
206
|
+
* CLAUDE.md and never touches user content outside the legacy forge section.
|
|
207
|
+
*
|
|
208
|
+
* no CLAUDE.md → write the stub (comment + import) → 'created'
|
|
209
|
+
* marker section present → replace section in place with the import line → 'migrated'
|
|
210
|
+
* legacy `# Forge` header (no markers) → strip header-to-EOF, append import line → 'migrated'
|
|
211
|
+
* import line already present → 'unchanged'
|
|
212
|
+
* none of the above → append comment + import line at EOF → 'appended'
|
|
213
|
+
*
|
|
214
|
+
* Newline cleanup is confined to the seam of the replaced region only — a global
|
|
215
|
+
* collapse would mutate user content, which upgrades must never do.
|
|
193
216
|
*/
|
|
194
|
-
function
|
|
195
|
-
const srcPath = path.join(templateDir, 'CLAUDE.md');
|
|
217
|
+
function ensureClaudeMdImport() {
|
|
196
218
|
const destPath = path.join(targetDir, 'CLAUDE.md');
|
|
197
219
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
const forgeContent = fs.readFileSync(srcPath, 'utf-8');
|
|
201
|
-
|
|
202
|
-
// No existing CLAUDE.md — just copy
|
|
220
|
+
// No existing CLAUDE.md — write the stub.
|
|
203
221
|
if (!fs.existsSync(destPath)) {
|
|
204
|
-
fs.writeFileSync(destPath,
|
|
222
|
+
fs.writeFileSync(destPath, CLAUDE_STUB);
|
|
205
223
|
return 'created';
|
|
206
224
|
}
|
|
207
225
|
|
|
208
226
|
const existing = fs.readFileSync(destPath, 'utf-8');
|
|
209
227
|
|
|
210
|
-
// Check if content is already identical
|
|
211
|
-
if (existing === forgeContent) return 'unchanged';
|
|
212
|
-
|
|
213
228
|
const startIdx = existing.indexOf(FORGE_START);
|
|
214
229
|
const endIdx = existing.indexOf(FORGE_END);
|
|
215
230
|
|
|
216
231
|
if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
|
|
217
|
-
//
|
|
232
|
+
// Legacy marker section — replace it in place with the import line (position preserved).
|
|
218
233
|
const before = existing.substring(0, startIdx);
|
|
219
234
|
const after = existing.substring(endIdx + FORGE_END.length);
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
fs.writeFileSync(destPath,
|
|
225
|
-
return '
|
|
235
|
+
// Collapse blank-line runs only at the two seams we just created, never globally.
|
|
236
|
+
const merged = (before + FORGE_IMPORT_LINE + after)
|
|
237
|
+
.replace(/\n{3,}(@\.forge\/FORGE\.md)/, '\n\n$1')
|
|
238
|
+
.replace(/(@\.forge\/FORGE\.md)\n{3,}/, '$1\n\n');
|
|
239
|
+
fs.writeFileSync(destPath, merged);
|
|
240
|
+
return 'migrated';
|
|
226
241
|
}
|
|
227
242
|
|
|
228
|
-
// No markers
|
|
243
|
+
// No markers, but a legacy `# Forge` header — old installs appended prose to EOF.
|
|
229
244
|
const forgeHeaderIdx = existing.indexOf('# Forge\n');
|
|
230
245
|
if (forgeHeaderIdx !== -1) {
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
const cleaned = merged.replace(/\n{3,}/g, '\n\n');
|
|
236
|
-
fs.writeFileSync(destPath, cleaned);
|
|
237
|
-
return 'replaced';
|
|
246
|
+
const before = existing.substring(0, forgeHeaderIdx).trimEnd();
|
|
247
|
+
const merged = (before ? before + '\n\n' : '') + FORGE_IMPORT_LINE + '\n';
|
|
248
|
+
fs.writeFileSync(destPath, merged);
|
|
249
|
+
return 'migrated';
|
|
238
250
|
}
|
|
239
251
|
|
|
240
|
-
//
|
|
241
|
-
|
|
252
|
+
// Import line already present (tolerating trailing whitespace/CRLF) — nothing to do.
|
|
253
|
+
if (FORGE_IMPORT_RE.test(existing)) return 'unchanged';
|
|
254
|
+
|
|
255
|
+
// User-authored CLAUDE.md with no forge content and no import line — append it.
|
|
256
|
+
const merged =
|
|
257
|
+
existing.trimEnd() + '\n\n' + CLAUDE_STUB;
|
|
242
258
|
fs.writeFileSync(destPath, merged);
|
|
243
259
|
return 'appended';
|
|
244
260
|
}
|
|
@@ -290,23 +306,6 @@ function isForgeInstalled() {
|
|
|
290
306
|
async function install() {
|
|
291
307
|
console.log('\n Forge - Meta-prompting framework for Claude Code\n');
|
|
292
308
|
|
|
293
|
-
// Handle CLAUDE.md — use smart merge
|
|
294
|
-
const claudeStatus = mergeClaudeMd();
|
|
295
|
-
switch (claudeStatus) {
|
|
296
|
-
case 'created':
|
|
297
|
-
console.log(' Created CLAUDE.md');
|
|
298
|
-
break;
|
|
299
|
-
case 'replaced':
|
|
300
|
-
console.log(' Updated Forge section in CLAUDE.md (user content preserved)');
|
|
301
|
-
break;
|
|
302
|
-
case 'appended':
|
|
303
|
-
console.log(' Appended Forge config to existing CLAUDE.md');
|
|
304
|
-
break;
|
|
305
|
-
case 'unchanged':
|
|
306
|
-
console.log(' CLAUDE.md already up to date');
|
|
307
|
-
break;
|
|
308
|
-
}
|
|
309
|
-
|
|
310
309
|
// Copy .claude/ directory
|
|
311
310
|
const srcClaude = path.join(templateDir, '.claude');
|
|
312
311
|
const destClaude = path.join(targetDir, '.claude');
|
|
@@ -323,6 +322,23 @@ async function install() {
|
|
|
323
322
|
// the template ships it as `gitignore`. Materialize the real dotfile here.
|
|
324
323
|
materializeForgeGitignore(destForge);
|
|
325
324
|
|
|
325
|
+
// Guarantee CLAUDE.md imports the framework prose (now on disk at .forge/FORGE.md).
|
|
326
|
+
const claudeStatus = ensureClaudeMdImport();
|
|
327
|
+
switch (claudeStatus) {
|
|
328
|
+
case 'created':
|
|
329
|
+
console.log(' Created CLAUDE.md (@.forge/FORGE.md import stub)');
|
|
330
|
+
break;
|
|
331
|
+
case 'migrated':
|
|
332
|
+
console.log(' Migrated CLAUDE.md: forge section extracted to .forge/FORGE.md (@import)');
|
|
333
|
+
break;
|
|
334
|
+
case 'appended':
|
|
335
|
+
console.log(' Added @.forge/FORGE.md import line to existing CLAUDE.md');
|
|
336
|
+
break;
|
|
337
|
+
case 'unchanged':
|
|
338
|
+
console.log(' CLAUDE.md import line already present');
|
|
339
|
+
break;
|
|
340
|
+
}
|
|
341
|
+
|
|
326
342
|
// Stamp version from package.json into settings.json
|
|
327
343
|
const settingsPath = path.join(targetDir, SETTINGS_FILE);
|
|
328
344
|
if (fs.existsSync(settingsPath)) {
|
|
@@ -558,10 +574,32 @@ async function upgrade() {
|
|
|
558
574
|
}
|
|
559
575
|
}
|
|
560
576
|
|
|
561
|
-
//
|
|
562
|
-
|
|
563
|
-
|
|
577
|
+
// 2c. Sync the framework prose file .forge/FORGE.md (framework-owned single file,
|
|
578
|
+
// same overwrite-when-different pattern as 2b). MUST run before the CLAUDE.md pass
|
|
579
|
+
// so a migrated project's import line never points at a missing file.
|
|
580
|
+
const fmSrc = path.join(templateDir, '.forge', 'FORGE.md');
|
|
581
|
+
const fmDest = path.join(targetDir, '.forge', 'FORGE.md');
|
|
582
|
+
if (fs.existsSync(fmSrc)) {
|
|
583
|
+
const newContent = fs.readFileSync(fmSrc, 'utf-8');
|
|
584
|
+
const existed = fs.existsSync(fmDest);
|
|
585
|
+
const oldContent = existed ? fs.readFileSync(fmDest, 'utf-8') : null;
|
|
586
|
+
if (oldContent !== newContent) {
|
|
587
|
+
fs.mkdirSync(path.dirname(fmDest), { recursive: true });
|
|
588
|
+
fs.writeFileSync(fmDest, newContent);
|
|
589
|
+
results[existed ? 'updated' : 'added'].push('.forge/FORGE.md');
|
|
590
|
+
} else {
|
|
591
|
+
results.unchanged.push('.forge/FORGE.md');
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// 3. Guarantee the CLAUDE.md import line; auto-migrate any legacy embedded section.
|
|
596
|
+
const claudeStatus = ensureClaudeMdImport();
|
|
597
|
+
if (claudeStatus === 'migrated') {
|
|
598
|
+
results.updated.push('CLAUDE.md');
|
|
599
|
+
console.log(' CLAUDE.md migrated: forge section extracted to .forge/FORGE.md (@import)');
|
|
600
|
+
} else if (claudeStatus === 'appended') {
|
|
564
601
|
results.updated.push('CLAUDE.md');
|
|
602
|
+
console.log(' CLAUDE.md: @.forge/FORGE.md import line restored');
|
|
565
603
|
} else if (claudeStatus === 'unchanged') {
|
|
566
604
|
results.unchanged.push('CLAUDE.md');
|
|
567
605
|
} else if (claudeStatus === 'created') {
|
package/package.json
CHANGED
|
@@ -15,7 +15,7 @@ Execute plan tasks. Full dev tools, strict deviation rules. Plan says X, build X
|
|
|
15
15
|
| Task (sub-executors) | `git add .` or `git add -A` |
|
|
16
16
|
|
|
17
17
|
## Input
|
|
18
|
-
Plan: `.forge/phases/
|
|
18
|
+
Plan: `.forge/phases/milestone-{id}/{phase}-{name}/plan.md`, context: `.forge/context.md`, state: `.forge/state/milestone-{id}.yml`.
|
|
19
19
|
|
|
20
20
|
## Output
|
|
21
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.
|
|
@@ -41,7 +41,7 @@ Need to deviate from plan?
|
|
|
41
41
|
|
|
42
42
|
### 1. Read Plan
|
|
43
43
|
```
|
|
44
|
-
Read: .forge/phases/
|
|
44
|
+
Read: .forge/phases/milestone-{id}/{phase}-{name}/plan.md
|
|
45
45
|
Read: .forge/context.md
|
|
46
46
|
Read: .forge/state/milestone-{id}.yml
|
|
47
47
|
```
|
|
@@ -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/
|
|
21
|
+
`.forge/` files: `phases/milestone-{id}/{phase}-{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
|
|
|
@@ -14,7 +14,7 @@ Goal-backward: start from must_haves, confirm existence, substance, wiring. Repo
|
|
|
14
14
|
| Task (parallel sub-verifiers) | Fixing code (report — Executor fixes) |
|
|
15
15
|
|
|
16
16
|
## Input
|
|
17
|
-
Plan+must_haves (`.forge/phases/
|
|
17
|
+
Plan+must_haves (`.forge/phases/milestone-{id}/{phase}-{name}/plan.md`), execution summary, source (read-only), milestone state.
|
|
18
18
|
|
|
19
19
|
## Output
|
|
20
20
|
|
|
@@ -57,7 +57,7 @@ Run: {n} | Passed: {n} | Failed: {n} | Coverage: {if available}
|
|
|
57
57
|
|
|
58
58
|
### 1. Load Criteria
|
|
59
59
|
```
|
|
60
|
-
Read: .forge/phases/
|
|
60
|
+
Read: .forge/phases/milestone-{id}/{phase}-{name}/plan.md → extract must_haves
|
|
61
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)
|
|
@@ -101,7 +101,7 @@ Common conflicts:
|
|
|
101
101
|
4. Consider indexing for frequent queries
|
|
102
102
|
5. Plan for evolution — prefer nullable fields over rigid schemas
|
|
103
103
|
|
|
104
|
-
Document in `.forge/phases/
|
|
104
|
+
Document in `.forge/phases/milestone-{id}/{phase}-{name}/data-model.md`.
|
|
105
105
|
|
|
106
106
|
## API Contract Design
|
|
107
107
|
|
|
@@ -111,7 +111,7 @@ Document in `.forge/phases/m{M}-{N}-{name}/data-model.md`.
|
|
|
111
111
|
4. Add pagination for list endpoints
|
|
112
112
|
5. Plan per-endpoint auth requirements
|
|
113
113
|
|
|
114
|
-
Document in `.forge/phases/
|
|
114
|
+
Document in `.forge/phases/milestone-{id}/{phase}-{name}/contracts/`.
|
|
115
115
|
|
|
116
116
|
## Output
|
|
117
117
|
|
|
@@ -123,7 +123,7 @@ Document in `.forge/phases/m{M}-{N}-{name}/contracts/`.
|
|
|
123
123
|
|
|
124
124
|
## Phase Handoff
|
|
125
125
|
|
|
126
|
-
1. **Persist** — Confirm ADRs in `.forge/decisions/`, models and contracts in `.forge/phases/
|
|
126
|
+
1. **Persist** — Confirm ADRs in `.forge/decisions/`, models and contracts in `.forge/phases/milestone-{id}/{phase}-{name}/`
|
|
127
127
|
2. **Update state** — Set `current.status` to `planning` in `.forge/state/milestone-{id}.yml`
|
|
128
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
129
|
4. **Recommend context clear:**
|
|
@@ -198,7 +198,7 @@ options:
|
|
|
198
198
|
### Step 1: Load Context
|
|
199
199
|
|
|
200
200
|
```
|
|
201
|
-
Read: .forge/phases/
|
|
201
|
+
Read: .forge/phases/milestone-{id}/{phase}-{name}/plan-{NN}.md → the plan
|
|
202
202
|
Read: .forge/requirements/m{N}.yml → source requirements (current milestone)
|
|
203
203
|
Read: .forge/context.md → locked decisions
|
|
204
204
|
Read: .forge/constitution.md → active gates
|
|
@@ -6,7 +6,7 @@ description: "Build to plan with atomic commits, deviation rules, and context en
|
|
|
6
6
|
# Executing
|
|
7
7
|
|
|
8
8
|
## Pre-Execution Checklist
|
|
9
|
-
- [ ] Plan exists (`.forge/phases/
|
|
9
|
+
- [ ] Plan exists (`.forge/phases/milestone-{id}/{phase}-{name}/plan-{NN}.md`)
|
|
10
10
|
- [ ] Context.md locked decisions noted
|
|
11
11
|
- [ ] Constitution.md gates satisfied
|
|
12
12
|
- [ ] Milestone state updated to `status: executing`
|
|
@@ -78,7 +78,7 @@ For each task in the plan:
|
|
|
78
78
|
## TDD Flow (task type="tdd")
|
|
79
79
|
|
|
80
80
|
**With Test Spec:**
|
|
81
|
-
1. Copy spec from `.forge/phases/
|
|
81
|
+
1. Copy spec from `.forge/phases/milestone-{id}/{phase}-{name}/specs/` to test directory
|
|
82
82
|
2. **RED:** Remove `skip` markers. Confirm failure. Commit: `test({scope}): activate spec tests for {feature}`
|
|
83
83
|
3. **GREEN:** Minimal code to pass. Repeat per test.
|
|
84
84
|
4. **REFACTOR:** Clean up. Commit: `feat({scope}): implement {feature}`
|
|
@@ -105,6 +105,16 @@ feat(auth-01): implement JWT-based login
|
|
|
105
105
|
- Include integration test for login flow
|
|
106
106
|
```
|
|
107
107
|
|
|
108
|
+
## Version Bump Protocol
|
|
109
|
+
|
|
110
|
+
When a task bumps the project version (`package.json` etc.) or adds a CHANGELOG entry:
|
|
111
|
+
|
|
112
|
+
1. **Read the reserved number from `.forge/releases.yml`** — write ONLY the version this milestone reserved (Version Reservation Protocol, FORGE.md). Never invent a number at delivery.
|
|
113
|
+
2. **No reservation? Reserve now** (planning predated the registry): `git pull`, take `max(version) + bump-level`, append one `{milestone, version, bump, reserved_at, summary}` entry, **commit + push the reservation BEFORE editing the version file** so a parallel session sees it taken.
|
|
114
|
+
3. **Then** write the version + CHANGELOG entry in the normal atomic commit.
|
|
115
|
+
|
|
116
|
+
This is what stops two parallel milestone sessions from grabbing the same number or clobbering each other's version edit — the registry, not the file, is the source of truth for "what number is mine."
|
|
117
|
+
|
|
108
118
|
## Multi-Agent Claim Convention [Experimental — M10]
|
|
109
119
|
|
|
110
120
|
**Trigger:** active milestone state has `lifecycle.worktree_mode: active` (set by `orchestrating` skill). If absent or any other value → skip this section entirely; single-agent behavior unchanged.
|
|
@@ -116,7 +126,7 @@ forge_claim_files {
|
|
|
116
126
|
session_id: lifecycle.session_id,
|
|
117
127
|
files: [<absolute paths from task <files> manifest>],
|
|
118
128
|
ttl_seconds: 1800,
|
|
119
|
-
reason: "executing m{
|
|
129
|
+
reason: "executing m{id} phase {phase} task <name>"
|
|
120
130
|
}
|
|
121
131
|
```
|
|
122
132
|
|
|
@@ -221,7 +231,7 @@ Run all non-advisory verification commands once after commit. 1 retry max.
|
|
|
221
231
|
After completing all tasks in a plan:
|
|
222
232
|
|
|
223
233
|
```markdown
|
|
224
|
-
# Execution Summary:
|
|
234
|
+
# Execution Summary: milestone-{id}/{phase}-{name}, Plan {NN}
|
|
225
235
|
|
|
226
236
|
## Completed Tasks
|
|
227
237
|
1. [Task name] — [one-line result]
|
|
@@ -144,7 +144,7 @@ No match → fall through to Step 2B (tier detection).
|
|
|
144
144
|
1. Validate: milestone in `index.yml` (any status — active, deferred, complete, not_started)
|
|
145
145
|
2. Build manifest — scan for associated files:
|
|
146
146
|
- `.forge/state/milestone-{id}.yml`
|
|
147
|
-
- `.forge/phases/
|
|
147
|
+
- `.forge/phases/milestone-{id}/` (the milestone's phase dir — holds all `{phase}-{name}/` subdirs)
|
|
148
148
|
- `.forge/research/milestone-{id}.md` (if exists)
|
|
149
149
|
- `.forge/audits/milestone-{id}-*` (if exists)
|
|
150
150
|
- Note: `.forge/context.md` is shared — do NOT archive or modify. M{id} decisions stay as history.
|
|
@@ -205,7 +205,13 @@ Tier + state → invoke via `Skill` tool. All phases use `Skill()`.
|
|
|
205
205
|
|
|
206
206
|
**CRITICAL: NEVER `EnterPlanMode`.** "Planning" = `Skill(planning)`. Native plan mode writes wrong format, bypasses gates + state.
|
|
207
207
|
|
|
208
|
-
**Experimental
|
|
208
|
+
**Experimental — M10 orchestration (auto-routes when installed).** Installing M10 *is* the consent: `forge` then auto-routes Standard/Full work through `orchestrating` **before** `executing` — no separate invocation to remember. Route through `Skill(orchestrating)` first when **all** hold:
|
|
209
|
+
- **Installed:** `.claude/skills/orchestrating/` present + `forge-orchestrator` in `.mcp.json` + `.claude/hooks/forge-claim-check.sh` present.
|
|
210
|
+
- **Not opted out:** `orchestration.auto` in `project.yml` ≠ `false` (absent = on; install = consent per ADR-001).
|
|
211
|
+
- **Tier is Standard or Full.** Quick never auto-orchestrates (a worktree per typo is the wrong trade).
|
|
212
|
+
- **Not already in a session:** active milestone has no `lifecycle.worktree_mode: active|degraded` (don't re-bootstrap inside a live session).
|
|
213
|
+
|
|
214
|
+
`orchestrating` bootstrap stays the safety net — incompatible repo → it refuses, writes `worktree_mode: refused`, and `forge` continues single-agent. Absent install → standard routing. Manual `Skill(orchestrating)` still works for explicit control; set `orchestration.auto: false` in `project.yml` to opt back out.
|
|
209
215
|
|
|
210
216
|
### Auto-Routing (Always Deterministic)
|
|
211
217
|
|
|
@@ -306,7 +312,7 @@ Phase transitions = clear boundaries. **Recommend `/clear`** after writing state
|
|
|
306
312
|
| researching | `.forge/research/milestone-{id}.md` | discussing |
|
|
307
313
|
| discussing | `.forge/context.md` (locked decisions, deferrals, discretion) | planning |
|
|
308
314
|
| architecting | ADRs, data models, API contracts | planning |
|
|
309
|
-
| planning | `.forge/phases/
|
|
315
|
+
| planning | `.forge/phases/milestone-{id}/{phase}-{name}/`, `.forge/requirements/m{N}.yml`, roadmap.yml | executing |
|
|
310
316
|
| executing | Committed code, execution summary, state | verifying |
|
|
311
317
|
| verifying | Verification report, desire-path files (`state/desire-paths/`) | reviewing |
|
|
312
318
|
|
|
@@ -141,7 +141,7 @@ Before decomposing, classify whether this phase crosses a layer boundary with a
|
|
|
141
141
|
|
|
142
142
|
**Two contract tiers:**
|
|
143
143
|
- **Durable** = the standing layer API. Lives in ADRs (`.forge/decisions/`) + constitution, indexed in `.forge/contracts/index.yml` (integration-point -> governing ADR). Stable + unchanged -> agents read the ADR; no per-phase artifact.
|
|
144
|
-
- **Per-phase delta** = the specific new/changed shape THIS phase introduces. Pinned in `.forge/phases/
|
|
144
|
+
- **Per-phase delta** = the specific new/changed shape THIS phase introduces. Pinned in `.forge/phases/milestone-{id}/{phase}-{name}/contract.md` (from `.forge/templates/contract.md`); references its governing ADR; folded back into that ADR on landing.
|
|
145
145
|
|
|
146
146
|
**Classify:**
|
|
147
147
|
|
|
@@ -176,9 +176,9 @@ Plan naming reflects the slice: `plan-01-user-signs-up.md`, not `plan-01-models.
|
|
|
176
176
|
|
|
177
177
|
#### File Layout
|
|
178
178
|
|
|
179
|
-
1. `.forge/templates/plan.md` -> `.forge/phases/
|
|
180
|
-
- `{
|
|
181
|
-
- Ex: `.forge/phases/
|
|
179
|
+
1. `.forge/templates/plan.md` -> `.forge/phases/milestone-{id}/{phase}-{name}/plan-{NN}.md`
|
|
180
|
+
- `{id}`=milestone, `{phase}`=phase# (preserved verbatim, NOT renumbered), `{name}`=kebab, `{NN}`=seq
|
|
181
|
+
- Ex: `.forge/phases/milestone-3/2-providers/plan-01.md`
|
|
182
182
|
2. Frontmatter: phase, plan#, wave, deps, `slice_exception:` (optional, see Core Principle)
|
|
183
183
|
3. must_haves:
|
|
184
184
|
- **Truths:** User-observable outcomes (3-7). MUST be phrased as something the user can see, click, or receive -- not "model X exists" or "table Y created".
|
|
@@ -270,7 +270,7 @@ None -> skip to Step 8.
|
|
|
270
270
|
|
|
271
271
|
### Generate Specs
|
|
272
272
|
|
|
273
|
-
**Location:** `.forge/phases/
|
|
273
|
+
**Location:** `.forge/phases/milestone-{id}/{phase}-{name}/specs/`
|
|
274
274
|
Test files (`.test.ts`, `_test.go`, `test_*.py`):
|
|
275
275
|
1. Import from `<files>` path
|
|
276
276
|
2. Behavior from requirements (happy, edge, error)
|
|
@@ -278,7 +278,7 @@ Test files (`.test.ts`, `_test.go`, `test_*.py`):
|
|
|
278
278
|
4. Mark pending (`it.skip()`, `@pytest.mark.skip`, `t.Skip()`)
|
|
279
279
|
|
|
280
280
|
```typescript
|
|
281
|
-
// Example: .forge/phases/
|
|
281
|
+
// Example: .forge/phases/milestone-1/1-auth/specs/login-endpoint.test.ts
|
|
282
282
|
describe('POST /api/auth/login', () => {
|
|
283
283
|
it.skip('returns 200 with valid token for correct credentials', () => {
|
|
284
284
|
// From FR-001: "User can log in with email and password"
|
|
@@ -306,7 +306,7 @@ Specs -> type becomes `tdd`:
|
|
|
306
306
|
<task type="tdd">
|
|
307
307
|
<name>Implement login endpoint</name>
|
|
308
308
|
<files>src/api/auth/login.ts</files>
|
|
309
|
-
<spec>.forge/phases/
|
|
309
|
+
<spec>.forge/phases/milestone-1/1-auth/specs/login-endpoint.test.ts</spec>
|
|
310
310
|
<action>Make all tests in the spec file pass. Remove skip markers as you implement.</action>
|
|
311
311
|
<verify>All spec tests pass: npm test -- login-endpoint</verify>
|
|
312
312
|
<done>All skip markers removed, all tests green</done>
|
|
@@ -381,6 +381,7 @@ Done when approved.
|
|
|
381
381
|
## Handoff
|
|
382
382
|
|
|
383
383
|
1. **Persist** -- plans `.forge/phases/`, reqs `.forge/requirements/m{N}.yml`, roadmap `.forge/roadmap.yml`, context `.forge/context.md`
|
|
384
|
-
2. **
|
|
385
|
-
3. **State
|
|
386
|
-
4.
|
|
384
|
+
2. **Reserve version (if delivery bumps the version)** -- if any plan in this milestone will bump `package.json`/the project version, append ONE entry to `.forge/releases.yml` (Version Reservation Protocol, FORGE.md): pull first, set `version` = highest reserved `max()` bumped by this change's semver level, append `{milestone, version, bump, reserved_at, summary}`. **Never edit prior entries.** No version bump in scope → skip. Surface the reserved number to the user.
|
|
385
|
+
3. **State** -- `current.status` = `executing` in `.forge/state/milestone-{id}.yml`
|
|
386
|
+
4. **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 .`). The `releases.yml` reservation rides in this commit — **push it** so a parallel session sees the number taken before they reserve.
|
|
387
|
+
5. *"Plan written and synced. `/clear` then `/forge` to continue."*
|
|
@@ -70,8 +70,9 @@ 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.
|
|
74
|
-
4.
|
|
73
|
+
3. **If this task worked a refactor-backlog item:** in `.forge/refactor-backlog.yml` set that item's `status: done`, write `completed` (today, ISO) + `completed_by` (commit sha / one-line), THEN run the same **compaction-on-write** as `reviewing` Step 7 → "Status vocab + compaction-on-write" (normalize statuses → archive terminal items to `.forge/refactor-backlog-archive.yml` → leave only actionable items). Compaction is idempotent. (See reviewing Step 7 for the canonical rule — not repeated here.) This closes the loop: a worked item leaves the working backlog instead of lingering as `pending`.
|
|
74
|
+
4. 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 .`).
|
|
75
|
+
5. Report: fix description, files changed, current position
|
|
75
76
|
|
|
76
77
|
### Without Milestone
|
|
77
78
|
|
|
@@ -333,6 +333,21 @@ Missing? Create from `.forge/templates/refactor-backlog.yml`.
|
|
|
333
333
|
|
|
334
334
|
**When deferring an individual refactor item** (status flipped to `deferred`, as opposed to milestone-wide defer which goes through the forge skill): write `deferred_at` (ISO date today) and `deferred_reason` (one-line) siblings to `status: deferred`. Pre-existing items without these fields parse fine — lazy migration. These feed the `deferred` aggregator skill.
|
|
335
335
|
|
|
336
|
+
### Status vocab + compaction-on-write
|
|
337
|
+
|
|
338
|
+
**Canonical status vocab — the ONLY allowed values:** `pending | in_progress | done | dismissed | deferred`. Actionable = `pending|in_progress|deferred`. Terminal = `done|dismissed`.
|
|
339
|
+
|
|
340
|
+
**Normalization map** (apply before any write): `completed`,`complete`,`closed` → `done`; `wont_fix`,`stale` → `dismissed`. Any other non-canonical value (`null`, free text, `promoted_to_m{N}`, …) → leave in place, surface for human triage in the review output (`"Backlog has {N} item(s) with unrecognized status — triage"`). Never silently drop.
|
|
341
|
+
|
|
342
|
+
**Compaction-on-write** — run EVERY time you write the backlog, after appending new items:
|
|
343
|
+
1. Normalize statuses via the map above.
|
|
344
|
+
2. Move every terminal (`done|dismissed`) item — full record, intact — by **append** to `.forge/refactor-backlog-archive.yml` (create with a one-line header if absent: `# Forge Refactor Backlog — Archive (resolved/dismissed items). Append-only.`).
|
|
345
|
+
3. Remove those items from `.forge/refactor-backlog.yml` so the working file holds only actionable + un-triaged items.
|
|
346
|
+
|
|
347
|
+
Idempotent: a backlog with no terminal items and all-canonical statuses is left byte-unchanged. No item is ever lost — terminal items relocate, they are not deleted.
|
|
348
|
+
|
|
349
|
+
**Size-gate check (advisory).** After compaction, if the working file still exceeds the FORGE.md size gate (150 KB), emit: `"Refactor backlog over size gate ({size}) even after compaction — {N} actionable items. Consider triaging stale pending items or promoting a cluster to Standard tier. Advisory — does not block."` (A busy project with hundreds of unworked pending items legitimately trips this; the signal is "triage the actionable backlog", not "archiving failed".)
|
|
350
|
+
|
|
336
351
|
### Route
|
|
337
352
|
|
|
338
353
|
**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`.
|
|
@@ -375,7 +390,7 @@ If the milestone being completed has `milestone.origin: {R-id}` set (promoted fr
|
|
|
375
390
|
|
|
376
391
|
If the milestone's phases produced `contract.md` files (planning Step 6.1 Tier 1/2), close their lifecycle before completing the milestone. The durable contract is the ADR; the per-phase `contract.md` is a working delta that must be folded back in.
|
|
377
392
|
|
|
378
|
-
1. Glob `.forge/phases/
|
|
393
|
+
1. Glob `.forge/phases/milestone-{id}/*/contract.md`.
|
|
379
394
|
2. For each contract not yet `absorbed` (Tier-2 lands at `ratified` after the executing seam check; Tier-1 lands at `proposed` — both fold the same way now that the phase is verified):
|
|
380
395
|
- Fold `delta` into its `governing_adr` — amend the ADR in `.forge/decisions/`, or supersede it (`Status: Superseded by ADR-{NNN}`) if the shape changed materially.
|
|
381
396
|
- If a **new** integration point was introduced, add it to `.forge/contracts/index.yml` `integration_points:` (id, produces, consumes, governing_adr, summary).
|
|
@@ -26,8 +26,9 @@ Read the source version (`{source}/packages/create-forge/package.json` `version`
|
|
|
26
26
|
|
|
27
27
|
| Category | Paths | Behavior |
|
|
28
28
|
|----------|-------|----------|
|
|
29
|
-
| **Framework-owned** | `.claude/agents/*.md`, `.claude/skills/*/SKILL.md` | Overwrite |
|
|
30
|
-
| **
|
|
29
|
+
| **Framework-owned** | `.claude/agents/*.md`, `.claude/skills/*/SKILL.md`, `.forge/FORGE.md` | Overwrite |
|
|
30
|
+
| **Import-managed** | `CLAUDE.md` | Never overwrite content; migrate legacy forge sections + guarantee the `@.forge/FORGE.md` import line (Step 5) |
|
|
31
|
+
| **Merge-owned** | `.claude/settings.json` | Smart-merge `forge.*` keys only (Step 5) |
|
|
31
32
|
| **Template-only** | `.forge/templates/**`, `.forge/migrations/**`, `.forge/gitignore` → `.forge/.gitignore` | Overwrite |
|
|
32
33
|
|
|
33
34
|
**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`.
|
|
@@ -50,11 +51,19 @@ Same process as Step 3 for `.forge/templates/**` and `.forge/migrations/**`. (Ma
|
|
|
50
51
|
|
|
51
52
|
**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
53
|
|
|
53
|
-
|
|
54
|
+
**Sync the framework prose file `.forge/FORGE.md`** (framework-owned single file — it holds the `# Forge` prose, formerly embedded in CLAUDE.md). Copy/refresh `{source}/packages/create-forge/template/.forge/FORGE.md` → `.forge/FORGE.md` (overwrite when different; report added/updated/unchanged). **Run this BEFORE the Step 5 CLAUDE.md pass** so a migrated import line never points at a missing file.
|
|
54
55
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
## Step 5: Handle Import-Managed + Merge-Owned Files
|
|
57
|
+
|
|
58
|
+
**`CLAUDE.md` (import-managed — one-pass auto-migration, no prompt):** mirror the npm bin's `ensureClaudeMdImport()` case-for-case. The framework prose lives in `.forge/FORGE.md` (synced in Step 4) — never write prose into CLAUDE.md, and never modify user content outside a legacy forge section.
|
|
59
|
+
|
|
60
|
+
1. **No `CLAUDE.md`** → write the stub: one explanatory comment + the `@.forge/FORGE.md` import line. Report **created**.
|
|
61
|
+
2. **Legacy `<!-- forge:start -->` / `<!-- forge:end -->` markers present** → replace the marker section *in place* with the `@.forge/FORGE.md` import line (position preserved), collapsing blank-line runs only at the two seams — never globally. Report **migrated**.
|
|
62
|
+
3. **No markers, but a legacy `# Forge` header** (old installs appended prose to EOF) → strip from the header to end-of-file, append the import line. Report **migrated**.
|
|
63
|
+
4. **Import line already present** (tolerating trailing whitespace / CRLF) → leave untouched. Report **unchanged**.
|
|
64
|
+
5. **User-authored CLAUDE.md, no forge content, no import line** → append the comment + import line at EOF. Report **restored** (the import-line guarantee — Forge always loads its context).
|
|
65
|
+
|
|
66
|
+
No manual-merge prompt for forge sections; migration is silent except the report line. User content outside the forge section is never modified, and the import line is never duplicated.
|
|
58
67
|
|
|
59
68
|
**`.claude/settings.json`:**
|
|
60
69
|
1. Read source and local versions
|
|
@@ -82,13 +91,17 @@ Removed from template: {N} files
|
|
|
82
91
|
- .claude/agents/old-agent.md (still in your project)
|
|
83
92
|
- ...
|
|
84
93
|
|
|
85
|
-
|
|
86
|
-
- CLAUDE.md (
|
|
87
|
-
|
|
94
|
+
Migrated: {N} files
|
|
95
|
+
- CLAUDE.md (forge section extracted to .forge/FORGE.md — @import)
|
|
96
|
+
|
|
97
|
+
Restored: {N} files
|
|
98
|
+
- CLAUDE.md (@.forge/FORGE.md import line re-appended)
|
|
88
99
|
|
|
89
100
|
Unchanged: {N} files
|
|
90
101
|
```
|
|
91
102
|
|
|
103
|
+
The `Migrated:` line appears only when a legacy marker/`# Forge` section was extracted this run; `Restored:` only when a missing import line was re-appended. A steady-state upgrade lists `CLAUDE.md` under Unchanged.
|
|
104
|
+
|
|
92
105
|
## Step 7: Post-Upgrade Migration Checks
|
|
93
106
|
|
|
94
107
|
After sync completes, detect legacy file layouts that the new framework version no longer writes but may still read in compatibility mode. For each match, surface a migration prompt — do not auto-migrate.
|
|
@@ -182,6 +195,77 @@ Migrate now? (yes/no/show guide)
|
|
|
182
195
|
|
|
183
196
|
`upgrading` never edits `.forge/state/` directly — migration runs via `quick-tasking`.
|
|
184
197
|
|
|
198
|
+
### Pre-0.20.0 flat phase dirs
|
|
199
|
+
|
|
200
|
+
Run from project root:
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
ls .forge/phases/ | grep -E '^m[0-9]+-[0-9]+-' && echo "flat layout"
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
If any dir matching `m{id}-{phase}-{name}` sits directly under `.forge/phases/`, surface:
|
|
207
|
+
|
|
208
|
+
```
|
|
209
|
+
Nested phase layout: phase dirs are flat under .forge/phases/ (Forge 0.20.0)
|
|
210
|
+
──────────────────────────────────────────────────────────────────────────
|
|
211
|
+
Forge 0.20.0 nests phase dirs one level per milestone:
|
|
212
|
+
m{id}-{phase}-{name}/ → milestone-{id}/{phase}-{name}/
|
|
213
|
+
This keeps `ls .forge/phases/` scoped per-milestone instead of dumping every
|
|
214
|
+
phase across all milestones into context. Phase numbers are preserved verbatim
|
|
215
|
+
(nothing is renumbered); the state cursor stays valid.
|
|
216
|
+
|
|
217
|
+
{N} flat phase dir(s) detected.
|
|
218
|
+
A migration guide is available at: .forge/migrations/0.20.0-nested-phase-layout.md
|
|
219
|
+
(canonical: https://github.com/Attuned-Media/forge/blob/main/docs/migrations/0.20.0-nested-phase-layout.md)
|
|
220
|
+
|
|
221
|
+
Migrate now? (yes/no/show guide)
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
- **yes** → invoke `quick-tasking` skill, hand it the migration guide as the task definition (`git mv` each flat dir to `milestone-{id}/{phase}-{name}/`, preserving phase numbers; rewrite any `milestone_dir:` in roadmap.yml; verify plan-file count unchanged).
|
|
225
|
+
- **show guide** → read and display the file, then re-ask
|
|
226
|
+
- **no** → note in upgrade report. Skills compose the new nested path on write while flat dirs still exist on disk, so new and old plans split across two layouts (split-brain) until migrated. Recommend running migration before the next planning cycle.
|
|
227
|
+
|
|
228
|
+
`upgrading` never moves phase dirs directly — migration runs via `quick-tasking`.
|
|
229
|
+
|
|
230
|
+
### Pre-0.22.0 unbounded refactor-backlog
|
|
231
|
+
|
|
232
|
+
Run from project root (backlog present only):
|
|
233
|
+
|
|
234
|
+
```bash
|
|
235
|
+
F=.forge/refactor-backlog.yml
|
|
236
|
+
[ -f "$F" ] || exit 0
|
|
237
|
+
kb=$(( $(wc -c < "$F") / 1024 ))
|
|
238
|
+
terminal=$(grep -cE 'status:[[:space:]]*(done|dismissed|completed|complete|closed|wont_fix|stale)' "$F")
|
|
239
|
+
noncanon=$(grep -oE 'status:[[:space:]]*[A-Za-z_]+' "$F" \
|
|
240
|
+
| grep -vE 'status:[[:space:]]*(pending|in_progress|done|dismissed|deferred)$' | wc -l)
|
|
241
|
+
# bloat if: kb > 150 OR terminal > 0 OR noncanon > 0
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
Surface the prompt when `kb > 150` OR `terminal > 0` OR `noncanon > 0`, printing the three detected numbers:
|
|
245
|
+
|
|
246
|
+
```
|
|
247
|
+
Refactor-backlog compaction available (Forge 0.22.0)
|
|
248
|
+
────────────────────────────────────────────────────
|
|
249
|
+
Pre-0.22.0 the refactor backlog was append-only — resolved items were never
|
|
250
|
+
pruned and statuses drifted (completed/complete/closed/wont_fix/stale + junk).
|
|
251
|
+
0.22.0 archives terminal items to .forge/refactor-backlog-archive.yml and
|
|
252
|
+
enforces the canonical vocab (pending|in_progress|done|dismissed|deferred).
|
|
253
|
+
|
|
254
|
+
Detected: {kb} KB · {terminal} terminal item(s) · {noncanon} non-canonical status(es).
|
|
255
|
+
A migration guide is available at: .forge/migrations/0.22.0-backlog-compaction.md
|
|
256
|
+
(canonical: https://github.com/Attuned-Media/forge/blob/main/docs/migrations/0.22.0-backlog-compaction.md)
|
|
257
|
+
|
|
258
|
+
Migrate now? (yes/no/show guide)
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
- **yes** → invoke `quick-tasking` skill, hand it the migration guide as the task definition (snapshot item count, normalize statuses, split actionable/terminal/triage, archive terminal items to `.forge/refactor-backlog-archive.yml`, rewrite the working file, verify item-count conservation before committing).
|
|
262
|
+
- **show guide** → read and display the file, then re-ask
|
|
263
|
+
- **no** → note in upgrade report. The backlog keeps growing and statuses stay drifted until migrated; reviewing/quick-tasking will compact on their next write regardless, but a bloated file lingers until then. Recommend running before the next review cycle.
|
|
264
|
+
|
|
265
|
+
No-op guarantee: detection exits clean (no prompt) when the backlog is absent, under the gate, has zero terminal items, and all statuses are canonical. (Pending-item count is **not** a trigger — a busy-but-clean backlog is left alone.)
|
|
266
|
+
|
|
267
|
+
`upgrading` never edits the backlog directly — migration runs via `quick-tasking`.
|
|
268
|
+
|
|
185
269
|
### Future migrations
|
|
186
270
|
|
|
187
271
|
Add new detection blocks here for each Forge version that changes file layout. Pattern:
|
|
@@ -18,7 +18,7 @@ After `/clear`, load with fresh eyes — don't carry the executor's assumptions:
|
|
|
18
18
|
```
|
|
19
19
|
Read: .forge/state/milestone-{id}.yml → current phase, plans completed
|
|
20
20
|
Read: .forge/project.yml → tech stack (for running tests)
|
|
21
|
-
Read: .forge/phases/
|
|
21
|
+
Read: .forge/phases/milestone-{id}/{phase}-{name}/plan-{NN}.md → must_haves (truths, artifacts, key_links)
|
|
22
22
|
Read: .forge/context.md → locked decisions
|
|
23
23
|
Read: .forge/requirements/m{N}.yml → requirement IDs for coverage check (current milestone, resolved from state)
|
|
24
24
|
Read: .forge/deferred-issues.md → known pre-existing failures (if exists; treat as advisory)
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
<!-- forge:start -->
|
|
2
1
|
# Forge
|
|
3
2
|
|
|
4
3
|
Lean meta-prompting framework for Claude Code. Context engineering + constitutional governance on native primitives.
|
|
@@ -73,6 +72,7 @@ Auto-detects complexity. Override: "Use Quick/Standard/Full tier."
|
|
|
73
72
|
| `requirements/m{N}.yml` | 50 KB/file | Prevents scope creep |
|
|
74
73
|
| `plan.md` | 30 KB | Keeps executor context <50% |
|
|
75
74
|
| `constitution.md` | 10 KB | Gates must be scannable |
|
|
75
|
+
| `refactor-backlog.yml` | 150 KB | Working backlog stays scannable; terminal items archive to `refactor-backlog-archive.yml` (advisory — flags, never blocks) |
|
|
76
76
|
|
|
77
77
|
### Fresh Agent Pattern
|
|
78
78
|
Task touches 20+ files or complex subsystem → spawn fresh executor with isolated context. Prevents context rot.
|
|
@@ -135,8 +135,10 @@ State lives in `.forge/`:
|
|
|
135
135
|
- `state/desire-paths/` — Append-only framework-usage observations, one file per observation. Occurrence counts derived by globbing (no mutable counter).
|
|
136
136
|
- `context.md` — Locked decisions + deferred ideas (discuss phase)
|
|
137
137
|
- `research/milestone-{id}.md` — Research findings snapshot (dated, immutable)
|
|
138
|
-
- `phases/
|
|
139
|
-
- `refactor-backlog.yml` — Refactoring catalog, worked via quick-tasking
|
|
138
|
+
- `phases/milestone-{id}/{phase}-{name}/plan-{NN}.md` — Task plans with must_haves frontmatter (`{id}`=milestone, `{phase}`=phase# preserved verbatim, `{name}`=kebab, `{NN}`=seq)
|
|
139
|
+
- `refactor-backlog.yml` — Refactoring catalog (actionable items only), worked via quick-tasking. Canonical status vocab: `pending | in_progress | done | dismissed | deferred`. Terminal items auto-archive on the next reviewing/quick-tasking write (compaction-on-write).
|
|
140
|
+
- `refactor-backlog-archive.yml` — Append-only terminal-item archive (`done`/`dismissed`); keeps the audit trail out of the working backlog.
|
|
141
|
+
- `releases.yml` — Append-only version-reservation registry (cross-session coordination point; see Version Reservation Protocol)
|
|
140
142
|
- `archive/milestone-{id}/` — Archived milestone artifacts (archive-delete)
|
|
141
143
|
|
|
142
144
|
**Milestones** group phases into concurrent streams. Own state file — no conflicts across sessions. Can be deferred (frozen in place) or archive-deleted.
|
|
@@ -162,6 +164,17 @@ State-sync commits are separate from per-task code commits (which stay atomic du
|
|
|
162
164
|
- `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
165
|
- Same-milestone parallel work is out of scope, guarded by the M10 claim layer.
|
|
164
166
|
|
|
167
|
+
### Version Reservation Protocol
|
|
168
|
+
|
|
169
|
+
The project version + CHANGELOG slot are **shared resources** — when two milestones run in parallel sessions, each independently bumping `package.json`/version and adding a CHANGELOG entry collides. `.forge/releases.yml` removes the number from per-milestone scope.
|
|
170
|
+
|
|
171
|
+
- **Reserve early, at planning.** When a milestone's delivery will bump the version, the `planning` skill appends ONE entry to `.forge/releases.yml`: `{milestone, version, bump, reserved_at, summary}`. The reserved version = highest `version:` in the file bumped by the change's semver level (capability → minor; fix/doc → patch — see version-bump criteria below).
|
|
172
|
+
- **Append-only — never edit prior entries.** Distinct lines = no cross-session conflict. Commit + push the reservation BEFORE editing the version file, so a parallel session pulls and sees the number taken.
|
|
173
|
+
- **Delivery never invents a number.** The `executing`/delivery step writes only the version reserved in `releases.yml`, then its CHANGELOG entry. If no reservation exists (milestone planned before this file), it reserves at delivery: pull, take `max() + bump`, append, push, then write.
|
|
174
|
+
- **Race resolution.** Two near-simultaneous reservations: the second push rebases onto the first (append-only → clean), re-reads `max()`, takes the next number.
|
|
175
|
+
|
|
176
|
+
**Version-bump criteria (0.x projects):** patch (`0.x.y`) = bug fix, doc tweak, sync-only, refinement. Minor (`0.x.0`) = new skill/agent/routing-row/auto-trigger/state-artifact/capability. Major (`x.0.0`) = post-1.0 breaking change. Default to minor when in doubt — a capability addition shipped as a patch drifts the version away from truth.
|
|
177
|
+
|
|
165
178
|
## Deviation Rules
|
|
166
179
|
|
|
167
180
|
**Full definitions:** `.claude/agents/executor.md`.
|
|
@@ -207,4 +220,3 @@ One commit per task. Format: `{type}({scope}): {description}`
|
|
|
207
220
|
Types: `feat`, `fix`, `test`, `refactor`, `chore`, `docs`
|
|
208
221
|
Never `git add .` or `git add -A` — stage individually.
|
|
209
222
|
Phase handoffs additionally emit one scoped `chore(forge): sync state …` commit (see State Commit Protocol) so `.forge/` state never drifts out of git.
|
|
210
|
-
<!-- forge:end -->
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
#
|
|
3
3
|
# Everything under .forge/ IS durable and committed to version control, so
|
|
4
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
|
|
5
|
+
# FORGE.md project.yml constitution.md design-system.md context.md roadmap.yml
|
|
6
6
|
# requirements/ phases/ research/ decisions/ contracts/ audits/
|
|
7
7
|
# refactor-backlog.yml state/ templates/ migrations/
|
|
8
8
|
#
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# Migration Guide: Nested Phase Layout (Forge 0.20.0)
|
|
2
|
+
|
|
3
|
+
Applies to projects initialized before 0.20.0 that have one or more flat phase
|
|
4
|
+
directories under `.forge/phases/`.
|
|
5
|
+
|
|
6
|
+
## Why
|
|
7
|
+
|
|
8
|
+
Phase directories used to live **flat** under `.forge/phases/`, one dir per
|
|
9
|
+
phase, named `m{milestone}-{phase}-{name}/`:
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
.forge/phases/
|
|
13
|
+
m1-1-schema-and-templates/
|
|
14
|
+
m1-2-skill-and-agent-updates/
|
|
15
|
+
m9-13-schema-and-templates/
|
|
16
|
+
m10-01-orchestration/
|
|
17
|
+
...
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
At a handful of milestones this is fine. At ~100 milestones the flat directory
|
|
21
|
+
becomes a context tax: any `ls .forge/phases/` or glob over it dumps hundreds of
|
|
22
|
+
sibling entries into an agent's window, most of them irrelevant to the milestone
|
|
23
|
+
being worked. Per Article XI (context is sacred), listings should scope to the
|
|
24
|
+
milestone in hand.
|
|
25
|
+
|
|
26
|
+
0.20.0 **nests** phase dirs one level under a per-milestone parent:
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
.forge/phases/
|
|
30
|
+
milestone-1/
|
|
31
|
+
1-schema-and-templates/
|
|
32
|
+
2-skill-and-agent-updates/
|
|
33
|
+
milestone-9/
|
|
34
|
+
13-schema-and-templates/
|
|
35
|
+
milestone-10/
|
|
36
|
+
01-orchestration/
|
|
37
|
+
...
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Now `ls .forge/phases/milestone-9/` shows only milestone 9's phases.
|
|
41
|
+
|
|
42
|
+
## What does NOT change
|
|
43
|
+
|
|
44
|
+
- **Phase numbers are preserved verbatim — nothing is renumbered.**
|
|
45
|
+
`m9-13-schema-and-templates` → `milestone-9/13-schema-and-templates`. The
|
|
46
|
+
phase number `13` stays `13`. (Phase numbers in this framework are globally
|
|
47
|
+
sequential, not per-milestone — that is intentional and untouched.)
|
|
48
|
+
- **State cursor and roadmap need no edits to phase numbers.** The cursor stores
|
|
49
|
+
`current.phase` (int) + `current.phase_name` (string), never a path. Skills
|
|
50
|
+
compose the path at runtime. Because the phase number and name are unchanged,
|
|
51
|
+
every existing cursor stays valid — only the folder territory moves.
|
|
52
|
+
- **Plan file contents are untouched.** Each `plan-{NN}.md` is byte-identical
|
|
53
|
+
after migration; only its parent path changes.
|
|
54
|
+
|
|
55
|
+
## Detection
|
|
56
|
+
|
|
57
|
+
A flat (un-migrated) layout has directories matching `m{id}-{phase}-{name}`
|
|
58
|
+
**directly** under `.forge/phases/`:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
ls .forge/phases/ | grep -E '^m[0-9]+-[0-9]+-' && echo "FLAT — migrate" || echo "nested — no-op"
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Already-nested projects have only `milestone-{id}/` dirs at that level and the
|
|
65
|
+
check prints `nested — no-op`.
|
|
66
|
+
|
|
67
|
+
## Defensive parse
|
|
68
|
+
|
|
69
|
+
The migration must tolerate **variable-width** milestone IDs **and**
|
|
70
|
+
**variable-width** phase numbers. Real-world dirs include single-digit
|
|
71
|
+
(`m1-1-x`), zero-padded (`m10-01-x`), and double-digit phase numbers
|
|
72
|
+
(`m7-11-x`, `m9-13-x`). Use this regex, anchored to the dir basename:
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
^m([0-9]+)-([0-9]+)-(.+)$
|
|
76
|
+
│ │ └─ capture 3: kebab-case phase name
|
|
77
|
+
│ └─ capture 2: phase number (preserve verbatim, including any zero-pad)
|
|
78
|
+
└─ capture 1: milestone id
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Capture group 2 is copied through **as written** — do not strip a leading zero,
|
|
82
|
+
do not renumber.
|
|
83
|
+
|
|
84
|
+
## Mapping
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
m{ID}-{PHASE}-{name}/ → milestone-{ID}/{PHASE}-{name}/
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Examples:
|
|
91
|
+
|
|
92
|
+
| Flat | Nested |
|
|
93
|
+
|------|--------|
|
|
94
|
+
| `m1-1-schema-and-templates/` | `milestone-1/1-schema-and-templates/` |
|
|
95
|
+
| `m9-13-schema-and-templates/` | `milestone-9/13-schema-and-templates/` |
|
|
96
|
+
| `m10-01-orchestration/` | `milestone-10/01-orchestration/` |
|
|
97
|
+
| `m7-11-refactor-promotion-and-audit/` | `milestone-7/11-refactor-promotion-and-audit/` |
|
|
98
|
+
|
|
99
|
+
One milestone parent may receive multiple phase subdirs (e.g. `milestone-1/`
|
|
100
|
+
gets `1-…`, `2-…`, `3-…`).
|
|
101
|
+
|
|
102
|
+
## Migration steps
|
|
103
|
+
|
|
104
|
+
### 1. Secure current state first
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
git add .forge/
|
|
108
|
+
git commit -m "chore(forge): secure .forge state before 0.20.0 migration"
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### 2. Move each flat dir to its nested home
|
|
112
|
+
|
|
113
|
+
Use `git mv` so history follows the files and `git status` records a rename
|
|
114
|
+
(not delete+add):
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
cd .forge/phases
|
|
118
|
+
for d in m[0-9]*-[0-9]*-*/; do
|
|
119
|
+
d=${d%/}
|
|
120
|
+
[[ $d =~ ^m([0-9]+)-([0-9]+)-(.+)$ ]] || continue
|
|
121
|
+
id=${BASH_REMATCH[1]}; phase=${BASH_REMATCH[2]}; name=${BASH_REMATCH[3]}
|
|
122
|
+
mkdir -p "milestone-$id"
|
|
123
|
+
git mv "$d" "milestone-$id/$phase-$name"
|
|
124
|
+
done
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
(Run from the repo so `git mv` resolves; adjust the `cd` to your `.forge/phases`
|
|
128
|
+
path. The loop preserves `phase` verbatim — no renumbering.)
|
|
129
|
+
|
|
130
|
+
### 3. Update roadmap `milestone_dir` references (if present)
|
|
131
|
+
|
|
132
|
+
Some roadmaps pin an explicit `milestone_dir:` per phase. Rewrite any flat value
|
|
133
|
+
to the nested form, e.g.:
|
|
134
|
+
|
|
135
|
+
```yaml
|
|
136
|
+
milestone_dir: "m10-01-orchestration" # before
|
|
137
|
+
milestone_dir: "milestone-10/01-orchestration" # after
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Phase numbers in the roadmap are **not** edited — only the path string.
|
|
141
|
+
|
|
142
|
+
### 4. Verify
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
# No flat dirs remain directly under phases/:
|
|
146
|
+
ls .forge/phases/ | grep -E '^m[0-9]+-[0-9]+-' || echo CLEAN
|
|
147
|
+
|
|
148
|
+
# Plan-file count unchanged (compare to your pre-migration count):
|
|
149
|
+
find .forge/phases -name 'plan*.md' | wc -l
|
|
150
|
+
|
|
151
|
+
# git tracks the moves as renames, not delete+add:
|
|
152
|
+
git status --short # expect R lines, no D + ?? pairs
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### 5. Commit
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
git add .forge/
|
|
159
|
+
git commit -m "chore(forge): migrate phase dirs to nested layout (0.20.0)"
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## Notes
|
|
163
|
+
|
|
164
|
+
- Idempotent: re-running detection on a nested project is a no-op (`CLEAN`).
|
|
165
|
+
- No data loss: this is a pure rename. If `find … plan*.md | wc -l` differs
|
|
166
|
+
before vs. after, **stop and investigate** — a dir failed to parse.
|
|
167
|
+
- The `upgrading` skill (Step 7) detects the flat layout after an upgrade and
|
|
168
|
+
offers to run this migration via `quick-tasking`. It never auto-migrates.
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# Migration Guide: Refactor-Backlog Compaction (Forge 0.22.0)
|
|
2
|
+
|
|
3
|
+
Applies to projects whose `.forge/refactor-backlog.yml` has grown unbounded
|
|
4
|
+
(terminal items never pruned) and/or has drifted off the canonical status
|
|
5
|
+
vocabulary.
|
|
6
|
+
|
|
7
|
+
## Why
|
|
8
|
+
|
|
9
|
+
Before 0.22.0 the refactor backlog was **append-only** — the `reviewing` skill
|
|
10
|
+
added items (`Next ID = max + 1`) but nothing ever removed resolved ones. Over a
|
|
11
|
+
project's life, `done`/`dismissed` items accumulate forever, and the `status`
|
|
12
|
+
field drifts into synonyms (`completed`, `complete`, `closed`) and junk
|
|
13
|
+
(`null`, free text). One real project reached **9,256 lines / 439 items**, of
|
|
14
|
+
which only ~220 were actionable — the rest was resolved-item sediment.
|
|
15
|
+
|
|
16
|
+
Per Article XI (context is sacred), the working backlog must stay scannable. A
|
|
17
|
+
multi-thousand-line YAML file is a context tax on every `reviewing`/`deferred`
|
|
18
|
+
read and is impossible to triage by hand.
|
|
19
|
+
|
|
20
|
+
0.22.0 keeps the working file **actionable-only**: terminal items move to an
|
|
21
|
+
append-only archive, and statuses are normalized to a single vocabulary.
|
|
22
|
+
|
|
23
|
+
## What changes
|
|
24
|
+
|
|
25
|
+
- Terminal items (`done` | `dismissed`) move — full record intact — to
|
|
26
|
+
`.forge/refactor-backlog-archive.yml` (append-only).
|
|
27
|
+
- Statuses normalize to the canonical set:
|
|
28
|
+
`pending | in_progress | done | dismissed | deferred`.
|
|
29
|
+
- The working `.forge/refactor-backlog.yml` keeps only **actionable**
|
|
30
|
+
(`pending | in_progress | deferred`) and any **un-triaged** items.
|
|
31
|
+
|
|
32
|
+
## What does NOT change
|
|
33
|
+
|
|
34
|
+
- **No item is deleted.** Terminal items are relocated to the archive, not
|
|
35
|
+
dropped. The full record (`completed`, `completed_by`, `dismissed_reason`,
|
|
36
|
+
dates) is preserved — the audit trail just lives out of the working file.
|
|
37
|
+
- **IDs are stable.** `R042` stays `R042` in whichever file it lands in.
|
|
38
|
+
- **Actionable items are content-stable** apart from status normalization
|
|
39
|
+
(a legacy `completed` only ever appears on a terminal item, so actionable
|
|
40
|
+
items are typically byte-unchanged).
|
|
41
|
+
- **`deferred` items stay in the working file** with their `deferred_at` /
|
|
42
|
+
`deferred_reason` — they are actionable (awaiting a revisit), not terminal.
|
|
43
|
+
|
|
44
|
+
## Status normalization map
|
|
45
|
+
|
|
46
|
+
Apply before splitting. Fold each legacy value to its canonical equivalent:
|
|
47
|
+
|
|
48
|
+
| Legacy value(s) | Canonical |
|
|
49
|
+
|-----------------|-----------|
|
|
50
|
+
| `completed`, `complete`, `closed` | `done` |
|
|
51
|
+
| `wont_fix`, `stale` | `dismissed` |
|
|
52
|
+
| `pending`, `in_progress`, `done`, `dismissed`, `deferred` | (unchanged) |
|
|
53
|
+
| anything else (`null`, `RegistrationStatus`, free text, `promoted_to_m{N}`) | **TRIAGE** |
|
|
54
|
+
|
|
55
|
+
A `TRIAGE` value is **never** auto-resolved. Leave the item in the working file
|
|
56
|
+
and surface it for a human decision: *"{N} item(s) have an unrecognized status —
|
|
57
|
+
triage."* Silently dropping or guessing is forbidden.
|
|
58
|
+
|
|
59
|
+
## Detection
|
|
60
|
+
|
|
61
|
+
Bloat/drift exists if the file is over the 150 KB size gate, OR any terminal
|
|
62
|
+
item is present, OR any non-canonical status appears:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
F=.forge/refactor-backlog.yml
|
|
66
|
+
[ -f "$F" ] || { echo "no backlog — no-op"; exit 0; }
|
|
67
|
+
kb=$(( $(wc -c < "$F") / 1024 ))
|
|
68
|
+
terminal=$(grep -cE 'status:[[:space:]]*(done|dismissed|completed|complete|closed|wont_fix|stale)' "$F")
|
|
69
|
+
noncanon=$(grep -oE 'status:[[:space:]]*[A-Za-z_]+' "$F" \
|
|
70
|
+
| grep -vE 'status:[[:space:]]*(pending|in_progress|done|dismissed|deferred)$' | wc -l)
|
|
71
|
+
echo "size=${kb}KB terminal=$terminal noncanon=$noncanon"
|
|
72
|
+
# bloat if: kb > 150 OR terminal > 0 OR noncanon > 0
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
A clean backlog (under the gate, zero terminal, all-canonical) prints
|
|
76
|
+
`size=… terminal=0 noncanon=0` → **no-op**. The detector keys off bloat that
|
|
77
|
+
compaction actually fixes, **not** the count of pending items — a busy-but-clean
|
|
78
|
+
backlog is left alone.
|
|
79
|
+
|
|
80
|
+
## ⚠️ Environment gotcha (read before scripting)
|
|
81
|
+
|
|
82
|
+
The Bash tool runs **zsh**, where `$BASH_REMATCH` / `=~` capture groups **do not
|
|
83
|
+
populate** — a regex-capture loop written for bash silently produces empty
|
|
84
|
+
captures and can corrupt output. Two rules:
|
|
85
|
+
|
|
86
|
+
1. **Prefer a single-pass YAML-aware split over per-line regex.** This migration
|
|
87
|
+
relocates list items between two YAML files — operate on whole item blocks,
|
|
88
|
+
not field-by-field string surgery.
|
|
89
|
+
2. If you must use `=~` capture, **wrap the loop in `bash -c '…'`** so it runs
|
|
90
|
+
under bash, not zsh.
|
|
91
|
+
|
|
92
|
+
And the cardinal rule from prior migrations: **verify item count before == after
|
|
93
|
+
+ archived.** If the totals don't conserve, STOP and recover from git.
|
|
94
|
+
|
|
95
|
+
## Procedure
|
|
96
|
+
|
|
97
|
+
### 1. Secure current state first
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
git add .forge/refactor-backlog.yml
|
|
101
|
+
git commit -m "chore(forge): secure backlog before 0.22.0 compaction"
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### 2. Snapshot the original item count
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
N_BEFORE=$(grep -cE '^[[:space:]]*-?[[:space:]]*id:' .forge/refactor-backlog.yml)
|
|
108
|
+
echo "items before: $N_BEFORE"
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### 3. Normalize statuses
|
|
112
|
+
|
|
113
|
+
Fold synonyms per the map above (`completed`/`complete`/`closed` → `done`;
|
|
114
|
+
`wont_fix`/`stale` → `dismissed`). Record any value that is still non-canonical
|
|
115
|
+
after normalization as the **TRIAGE** set — do not change it.
|
|
116
|
+
|
|
117
|
+
### 4. Split into three buckets
|
|
118
|
+
|
|
119
|
+
Walk the item list once and bucket each item by its (normalized) status:
|
|
120
|
+
|
|
121
|
+
- **actionable** → `pending | in_progress | deferred`
|
|
122
|
+
- **terminal** → `done | dismissed`
|
|
123
|
+
- **triage** → anything else (unrecognized)
|
|
124
|
+
|
|
125
|
+
Operate on whole item blocks (an item starts at `- id:` and runs until the next
|
|
126
|
+
`- id:` or EOF). Preserve every field verbatim.
|
|
127
|
+
|
|
128
|
+
### 5. Write the archive
|
|
129
|
+
|
|
130
|
+
Append the **terminal** bucket (full records) to
|
|
131
|
+
`.forge/refactor-backlog-archive.yml`. Create it if absent with a header:
|
|
132
|
+
|
|
133
|
+
```yaml
|
|
134
|
+
# Forge Refactor Backlog — Archive (resolved/dismissed items). Append-only.
|
|
135
|
+
# Items relocated here by compaction; full record preserved for audit trail.
|
|
136
|
+
items:
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### 6. Rewrite the working file
|
|
140
|
+
|
|
141
|
+
Rewrite `.forge/refactor-backlog.yml` with the **actionable + triage** buckets
|
|
142
|
+
only (triage stays visible until a human resolves it). Keep the template header.
|
|
143
|
+
|
|
144
|
+
### 7. Verify conservation — the hard gate
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
A=$(grep -cE '^[[:space:]]*-?[[:space:]]*id:' .forge/refactor-backlog.yml) # actionable + triage
|
|
148
|
+
B=$(grep -cE '^[[:space:]]*-?[[:space:]]*id:' .forge/refactor-backlog-archive.yml) # archived (subtract its template example if any)
|
|
149
|
+
echo "working=$A archived=$B sum=$((A+B)) before=$N_BEFORE"
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
`working + archived` **must equal** `N_BEFORE`. If it does not, **STOP** — do not
|
|
153
|
+
commit; `git checkout .forge/refactor-backlog.yml` and investigate (a malformed
|
|
154
|
+
item block failed to parse).
|
|
155
|
+
|
|
156
|
+
### 8. Commit
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
git add .forge/refactor-backlog.yml .forge/refactor-backlog-archive.yml
|
|
160
|
+
git commit -m "chore(forge): compact refactor backlog — archive terminal items (0.22.0)"
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## After migration
|
|
164
|
+
|
|
165
|
+
- The working file may still exceed the 150 KB gate if the project has hundreds
|
|
166
|
+
of genuinely-unworked `pending` items. That is a legitimate **triage** signal
|
|
167
|
+
("work or dismiss stale items / promote a cluster to Standard tier"), **not** a
|
|
168
|
+
failed migration. Compaction archives resolved items; it does not invent triage
|
|
169
|
+
decisions about open ones.
|
|
170
|
+
- From here on, `reviewing` Step 7 and `quick-tasking` compact on every write —
|
|
171
|
+
the backlog stays bounded without re-running this migration.
|
|
172
|
+
|
|
173
|
+
## Rollback
|
|
174
|
+
|
|
175
|
+
The whole operation is one commit over two files. `git revert <sha>` (or
|
|
176
|
+
`git checkout <pre-migration-sha> -- .forge/refactor-backlog.yml` and delete the
|
|
177
|
+
archive) fully reverses it — nothing was destroyed.
|
|
178
|
+
|
|
179
|
+
## Notes
|
|
180
|
+
|
|
181
|
+
- Idempotent: re-running on an already-compacted backlog archives nothing and
|
|
182
|
+
leaves the working file byte-unchanged.
|
|
183
|
+
- The `upgrading` skill (Step 7) detects bloat/drift after an upgrade and offers
|
|
184
|
+
to run this migration via `quick-tasking`. It never edits the backlog directly.
|
|
185
|
+
- Canonical copy of this guide:
|
|
186
|
+
https://github.com/Attuned-Media/forge/blob/main/docs/migrations/0.22.0-backlog-compaction.md
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Forge Release Registry — version reservations across concurrent milestones
|
|
2
|
+
#
|
|
3
|
+
# PURPOSE: remove the version number + CHANGELOG slot from per-milestone scope so
|
|
4
|
+
# two milestones running in parallel sessions never collide on your project's
|
|
5
|
+
# version or pick the same number. This is the coordination point both sessions
|
|
6
|
+
# read and append to.
|
|
7
|
+
#
|
|
8
|
+
# COMMITTED + APPEND-ONLY. Each milestone appends exactly ONE entry when it
|
|
9
|
+
# reserves its version (at planning, or at delivery if planning predates this
|
|
10
|
+
# file). Appending — never editing prior entries — is what keeps two sessions
|
|
11
|
+
# from conflicting: distinct lines, no contention. Commit and push the
|
|
12
|
+
# reservation so a parallel session pulls and sees numbers already taken.
|
|
13
|
+
#
|
|
14
|
+
# RULE: the next free version = highest `version:` in this file, bumped by the
|
|
15
|
+
# change's semver level (capability = minor; fix/doc = patch). Delivery NEVER
|
|
16
|
+
# writes a project version that is not reserved here first.
|
|
17
|
+
#
|
|
18
|
+
# RACE NOTE: reserve EARLY (at planning), commit + push the reservation BEFORE
|
|
19
|
+
# editing the version. If two sessions reserve near-simultaneously, the second
|
|
20
|
+
# push rebases onto the first (append-only → no conflict) and re-reads max() to
|
|
21
|
+
# take the next number.
|
|
22
|
+
|
|
23
|
+
releases: []
|
|
24
|
+
# Example entry shape (delete this comment once you add a real one):
|
|
25
|
+
# - milestone: 1
|
|
26
|
+
# version: "0.2.0"
|
|
27
|
+
# bump: minor
|
|
28
|
+
# reserved_at: "YYYY-MM-DD"
|
|
29
|
+
# summary: "one-line description of the milestone"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Phase Contract: {integration point}
|
|
2
2
|
|
|
3
|
-
Copy to `.forge/phases/
|
|
3
|
+
Copy to `.forge/phases/milestone-{id}/{phase}-{name}/contract.md` when planning **Step 6.1** detects a cross-layer delta (Tier 1 or 2). Pins the NEW or CHANGED interface shape the producing and consuming layers must agree on for this phase, so an agent building one layer in isolation does not have to guess the other's shape.
|
|
4
4
|
|
|
5
5
|
Lifecycle: pinned by the producer plan (NNa) BEFORE the consumer plan (NNb) builds against it -> ratified at the Tier-2 gate -> folded into the governing ADR when the phase lands (`status: absorbed`). The durable contract is the ADR; this file is the working delta on top of it.
|
|
6
6
|
|
|
@@ -22,7 +22,7 @@ CONTEXT.md with "NON-NEGOTIABLE" or "DEFERRED" sections
|
|
|
22
22
|
| `ROADMAP.md` | `.forge/roadmap.yml` | Extract phases, milestones. Add dependency references between phases. Convert to YAML. |
|
|
23
23
|
| `STATE.md` | `.forge/state/index.yml` + `.forge/state/milestone-{id}.yml` | Extract current phase number, progress, active blockers, recent decisions. Split into global index and per-milestone state. |
|
|
24
24
|
| `CONTEXT.md` | `.forge/context.md` | NON-NEGOTIABLE → Locked Decisions. DEFERRED → Deferred Ideas. DISCRETION → Discretion Areas. Minimal format change needed. |
|
|
25
|
-
| `PLAN.md` | `.forge/phases/
|
|
25
|
+
| `PLAN.md` | `.forge/phases/milestone-{id}/{phase}-{name}/plan.md` | Keep XML task format. Add `must_haves` YAML frontmatter. Split per-phase if combined. |
|
|
26
26
|
| `references/ui-brand.md` | `.forge/design-system.md` | Extract component rules, brand guidelines. Convert to component mapping table format. |
|
|
27
27
|
| `references/tdd.md` | `.forge/constitution.md` Article II | Inform Test-First article gates with project-specific testing approach. |
|
|
28
28
|
| `references/verification-patterns.md` | Informs `verifying` skill | Extract project-specific verification patterns. Add to constitution or context. |
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Phase Plan Template
|
|
2
2
|
|
|
3
|
-
Copy to `.forge/phases/
|
|
4
|
-
`{
|
|
3
|
+
Copy to `.forge/phases/milestone-{id}/{phase}-{name}/plan-{NN}.md` for each plan in a phase.
|
|
4
|
+
`{id}` = milestone ID, `{phase}` = phase number (preserved verbatim, never renumbered), `{name}` = kebab-case phase name, `{NN}` = plan sequence within the phase.
|
|
5
5
|
|
|
6
6
|
---
|
|
7
7
|
|
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
# Forge Refactor Backlog
|
|
2
|
-
# Auto-managed by the
|
|
3
|
-
#
|
|
1
|
+
# Forge Refactor Backlog — actionable items only.
|
|
2
|
+
# Auto-managed by the reviewing skill. Items added after milestone audits;
|
|
3
|
+
# worked via quick-tasking (effort: quick) or Standard tier (effort: standard).
|
|
4
4
|
#
|
|
5
|
-
# Status vocab: pending | in_progress | done | dismissed | deferred
|
|
5
|
+
# Status vocab (the ONLY allowed values): pending | in_progress | done | dismissed | deferred
|
|
6
|
+
# Actionable = pending | in_progress | deferred Terminal = done | dismissed
|
|
7
|
+
# Compaction-on-write: every reviewing/quick-tasking write normalizes statuses and
|
|
8
|
+
# moves terminal items (full record) to .forge/refactor-backlog-archive.yml, so this
|
|
9
|
+
# file holds only actionable items. No item is deleted — terminal items relocate.
|
|
10
|
+
# Legacy synonyms are normalized: completed/complete/closed → done; wont_fix/stale → dismissed.
|
|
6
11
|
# Optional fields (lazy migration — only required on entries set to status: deferred going forward):
|
|
7
12
|
# deferred_at: ISO date when status flipped to deferred
|
|
8
13
|
# deferred_reason: one-line why; revisited when status changes
|
|
@@ -21,9 +21,10 @@ roadmap:
|
|
|
21
21
|
name: "" # e.g., "MVP", "Admin Dashboard"
|
|
22
22
|
phases: [] # List of phase numbers belonging to this milestone
|
|
23
23
|
|
|
24
|
-
# Phases
|
|
25
|
-
#
|
|
26
|
-
#
|
|
24
|
+
# Phases belong to milestones (see each milestone's `phases:` list). Phase numbers
|
|
25
|
+
# are preserved verbatim and never renumbered.
|
|
26
|
+
# Directory naming: .forge/phases/milestone-{milestone_id}/{phase_num}-{name}/
|
|
27
|
+
# Example: milestone-1/1-foundation/, milestone-1/2-auth/, milestone-2/3-dashboard/
|
|
27
28
|
phases:
|
|
28
29
|
- id: 1
|
|
29
30
|
name: "" # Vertical slice name, e.g., "User can sign up"
|