forge-cc 0.1.5 → 0.1.7

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.
Files changed (49) hide show
  1. package/README.md +50 -3
  2. package/dist/cli.js +295 -9
  3. package/dist/cli.js.map +1 -1
  4. package/dist/go/auto-chain.d.ts +44 -5
  5. package/dist/go/auto-chain.js +236 -81
  6. package/dist/go/auto-chain.js.map +1 -1
  7. package/dist/go/executor.d.ts +2 -0
  8. package/dist/go/executor.js.map +1 -1
  9. package/dist/hooks/pre-commit.js +9 -3
  10. package/dist/hooks/pre-commit.js.map +1 -1
  11. package/dist/reporter/human.d.ts +5 -0
  12. package/dist/reporter/human.js +30 -0
  13. package/dist/reporter/human.js.map +1 -1
  14. package/dist/spec/generator.d.ts +20 -0
  15. package/dist/spec/generator.js +23 -2
  16. package/dist/spec/generator.js.map +1 -1
  17. package/dist/spec/interview.d.ts +20 -2
  18. package/dist/spec/interview.js +8 -0
  19. package/dist/spec/interview.js.map +1 -1
  20. package/dist/spec/scanner.d.ts +34 -0
  21. package/dist/spec/scanner.js +93 -0
  22. package/dist/spec/scanner.js.map +1 -1
  23. package/dist/spec/templates.d.ts +34 -12
  24. package/dist/spec/templates.js +8 -0
  25. package/dist/spec/templates.js.map +1 -1
  26. package/dist/utils/platform.d.ts +29 -0
  27. package/dist/utils/platform.js +90 -0
  28. package/dist/utils/platform.js.map +1 -0
  29. package/dist/worktree/identity.d.ts +9 -0
  30. package/dist/worktree/identity.js +32 -0
  31. package/dist/worktree/identity.js.map +1 -0
  32. package/dist/worktree/manager.d.ts +70 -0
  33. package/dist/worktree/manager.js +217 -0
  34. package/dist/worktree/manager.js.map +1 -0
  35. package/dist/worktree/parallel.d.ts +87 -0
  36. package/dist/worktree/parallel.js +328 -0
  37. package/dist/worktree/parallel.js.map +1 -0
  38. package/dist/worktree/session.d.ts +64 -0
  39. package/dist/worktree/session.js +193 -0
  40. package/dist/worktree/session.js.map +1 -0
  41. package/dist/worktree/state-merge.d.ts +43 -0
  42. package/dist/worktree/state-merge.js +162 -0
  43. package/dist/worktree/state-merge.js.map +1 -0
  44. package/hooks/pre-commit-verify.js +9 -3
  45. package/package.json +1 -1
  46. package/skills/forge-go.md +57 -14
  47. package/skills/forge-setup.md +28 -2
  48. package/skills/forge-spec.md +51 -13
  49. package/skills/forge-update.md +21 -1
@@ -0,0 +1,162 @@
1
+ import { readFileSync, existsSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { atomicWriteFileSync } from "../utils/platform.js";
4
+ // ---------------------------------------------------------------------------
5
+ // mergeSessionState
6
+ // ---------------------------------------------------------------------------
7
+ /**
8
+ * Merge state from a completed worktree session back to the main repo.
9
+ *
10
+ * - STATE.md: updates the milestone row with completion status and the
11
+ * Last Session date.
12
+ * - ROADMAP.md: marks the milestone row as complete with the given date.
13
+ *
14
+ * Uses synchronous I/O throughout — consistent with all other worktree modules.
15
+ *
16
+ * @param mainRepoDir - Absolute path to the main repository
17
+ * @param worktreeDir - Absolute path to the completed worktree
18
+ * @param milestoneNumber - The milestone that was completed
19
+ * @param completionDate - Date string (YYYY-MM-DD) for the completion
20
+ */
21
+ export function mergeSessionState(mainRepoDir, worktreeDir, milestoneNumber, completionDate) {
22
+ const warnings = [];
23
+ let stateUpdated = false;
24
+ let roadmapUpdated = false;
25
+ const completionStatus = `Complete (${completionDate})`;
26
+ // --- Check that the worktree has planning files --------------------------
27
+ const worktreeStatePath = join(worktreeDir, ".planning", "STATE.md");
28
+ const worktreeRoadmapPath = join(worktreeDir, ".planning", "ROADMAP.md");
29
+ if (!existsSync(worktreeStatePath)) {
30
+ warnings.push(`Worktree STATE.md not found at ${worktreeStatePath} — skipping state merge`);
31
+ }
32
+ if (!existsSync(worktreeRoadmapPath)) {
33
+ warnings.push(`Worktree ROADMAP.md not found at ${worktreeRoadmapPath} — skipping roadmap merge`);
34
+ }
35
+ // --- Update main repo STATE.md ------------------------------------------
36
+ const mainStatePath = join(mainRepoDir, ".planning", "STATE.md");
37
+ if (existsSync(mainStatePath)) {
38
+ const milestoneUpdated = updateStateMilestoneRow(mainStatePath, milestoneNumber, completionStatus);
39
+ if (milestoneUpdated) {
40
+ stateUpdated = true;
41
+ }
42
+ else {
43
+ warnings.push(`Milestone ${milestoneNumber} row not found in main repo STATE.md — no state update performed`);
44
+ }
45
+ }
46
+ else {
47
+ warnings.push(`Main repo STATE.md not found at ${mainStatePath} — cannot update state`);
48
+ }
49
+ // --- Update main repo ROADMAP.md ----------------------------------------
50
+ const mainRoadmapPath = join(mainRepoDir, ".planning", "ROADMAP.md");
51
+ if (existsSync(mainRoadmapPath)) {
52
+ const milestoneUpdated = updateRoadmapMilestoneStatus(mainRoadmapPath, milestoneNumber, completionStatus);
53
+ if (milestoneUpdated) {
54
+ roadmapUpdated = true;
55
+ }
56
+ else {
57
+ warnings.push(`Milestone ${milestoneNumber} row not found in main repo ROADMAP.md — no roadmap update performed`);
58
+ }
59
+ }
60
+ else {
61
+ warnings.push(`Main repo ROADMAP.md not found at ${mainRoadmapPath} — cannot update roadmap`);
62
+ }
63
+ return { stateUpdated, roadmapUpdated, warnings };
64
+ }
65
+ // ---------------------------------------------------------------------------
66
+ // updateRoadmapMilestoneStatus
67
+ // ---------------------------------------------------------------------------
68
+ /**
69
+ * Update a specific milestone row in a ROADMAP.md file.
70
+ * Uses structured line-by-line parsing (not regex replace) to safely
71
+ * update the status column of the milestone's table row.
72
+ *
73
+ * @param roadmapPath - Absolute path to ROADMAP.md
74
+ * @param milestoneNumber - Which milestone to update
75
+ * @param newStatus - New status string (e.g., "Complete (2026-02-15)")
76
+ * @returns true if the milestone was found and updated
77
+ */
78
+ export function updateRoadmapMilestoneStatus(roadmapPath, milestoneNumber, newStatus) {
79
+ const content = readFileSync(roadmapPath, "utf-8");
80
+ const lines = content.split("\n");
81
+ let found = false;
82
+ for (let i = 0; i < lines.length; i++) {
83
+ const line = lines[i];
84
+ // Match table rows: | <number> | <name> | <status> |
85
+ // Cells are separated by |. Split and check the first data cell.
86
+ if (!line.trimStart().startsWith("|"))
87
+ continue;
88
+ const cells = line.split("|");
89
+ // A valid table row split by | produces: ["", " cell1 ", " cell2 ", " cell3 ", ""]
90
+ // We need at least 4 separators (5 segments) for a 3-column table row.
91
+ if (cells.length < 5)
92
+ continue;
93
+ const firstCell = cells[1].trim();
94
+ const parsedNumber = parseInt(firstCell, 10);
95
+ if (isNaN(parsedNumber) || parsedNumber !== milestoneNumber)
96
+ continue;
97
+ // Check if this is already completed — if so, last completer wins with warning.
98
+ const currentStatus = cells[3].trim();
99
+ const alreadyComplete = currentStatus.toLowerCase().startsWith("complete");
100
+ // Update the status cell (index 3), preserving cell padding.
101
+ cells[3] = ` ${newStatus} `;
102
+ lines[i] = cells.join("|");
103
+ found = true;
104
+ if (alreadyComplete) {
105
+ // Last completer wins — overwrite is intentional but callers may
106
+ // want to know. We still return true since the update was applied.
107
+ }
108
+ break;
109
+ }
110
+ if (found) {
111
+ atomicWriteFileSync(roadmapPath, lines.join("\n"));
112
+ }
113
+ return found;
114
+ }
115
+ // ---------------------------------------------------------------------------
116
+ // updateStateMilestoneRow
117
+ // ---------------------------------------------------------------------------
118
+ /**
119
+ * Update the milestone progress table in STATE.md.
120
+ * Reads the current STATE.md, finds the milestone row, updates its status.
121
+ * Also updates the `**Last Session:**` date if present.
122
+ * Preserves all other content.
123
+ *
124
+ * @param statePath - Absolute path to STATE.md
125
+ * @param milestoneNumber - Which milestone to update
126
+ * @param newStatus - New status string
127
+ * @returns true if the milestone was found and updated
128
+ */
129
+ export function updateStateMilestoneRow(statePath, milestoneNumber, newStatus) {
130
+ const content = readFileSync(statePath, "utf-8");
131
+ const lines = content.split("\n");
132
+ let milestoneFound = false;
133
+ // Extract the date from the status string for Last Session update.
134
+ // Status format is typically "Complete (YYYY-MM-DD)".
135
+ const dateMatch = newStatus.match(/\((\d{4}-\d{2}-\d{2})\)/);
136
+ const completionDate = dateMatch ? dateMatch[1] : null;
137
+ for (let i = 0; i < lines.length; i++) {
138
+ const line = lines[i];
139
+ // --- Update milestone table row ----------------------------------------
140
+ if (line.trimStart().startsWith("|")) {
141
+ const cells = line.split("|");
142
+ if (cells.length >= 5) {
143
+ const firstCell = cells[1].trim();
144
+ const parsedNumber = parseInt(firstCell, 10);
145
+ if (!isNaN(parsedNumber) && parsedNumber === milestoneNumber) {
146
+ cells[3] = ` ${newStatus} `;
147
+ lines[i] = cells.join("|");
148
+ milestoneFound = true;
149
+ }
150
+ }
151
+ }
152
+ // --- Update Last Session date ------------------------------------------
153
+ if (completionDate && line.match(/\*\*Last Session:\*\*/)) {
154
+ lines[i] = line.replace(/(\*\*Last Session:\*\*\s*)\S+/, `$1${completionDate}`);
155
+ }
156
+ }
157
+ if (milestoneFound) {
158
+ atomicWriteFileSync(statePath, lines.join("\n"));
159
+ }
160
+ return milestoneFound;
161
+ }
162
+ //# sourceMappingURL=state-merge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state-merge.js","sourceRoot":"","sources":["../../src/worktree/state-merge.ts"],"names":[],"mappings":"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"}
@@ -35,8 +35,9 @@ function checkPreCommit(hookData) {
35
35
  const projectDir = process.cwd();
36
36
 
37
37
  // Check 1: Wrong branch protection
38
+ let branch = "unknown";
38
39
  try {
39
- const branch = execSync("git branch --show-current", {
40
+ branch = execSync("git branch --show-current", {
40
41
  encoding: "utf-8",
41
42
  }).trim();
42
43
  if (branch === "main" || branch === "master") {
@@ -49,8 +50,13 @@ function checkPreCommit(hookData) {
49
50
  // Can't determine branch — allow
50
51
  }
51
52
 
52
- // Check 2: Verify cache exists
53
- const cachePath = join(projectDir, ".forge", "last-verify.json");
53
+ // Check 2: Verify cache exists — per-branch first, fall back to legacy path
54
+ const slug = branch.replace(/\//g, "-").toLowerCase();
55
+ const perBranchCachePath = join(projectDir, ".forge", "verify-cache", `${slug}.json`);
56
+ const legacyCachePath = join(projectDir, ".forge", "last-verify.json");
57
+ const cachePath = existsSync(perBranchCachePath)
58
+ ? perBranchCachePath
59
+ : legacyCachePath;
54
60
  if (!existsSync(cachePath)) {
55
61
  return {
56
62
  decision: "block",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "forge-cc",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "Pre-PR verification harness for Claude Code agents — gate runner + CLI + MCP server",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -38,6 +38,8 @@ If all milestones are complete:
38
38
 
39
39
  If `--auto` was passed as an argument, set mode to auto and skip the prompt.
40
40
 
41
+ If `--single` was passed as an argument, set mode to single and skip the prompt.
42
+
41
43
  Otherwise: **your very next tool call MUST be AskUserQuestion.** No file reads, no git commands, no exploration — ask the user first. Use exactly these parameters:
42
44
 
43
45
  - question: "How should this project be executed?"
@@ -65,6 +67,12 @@ Verify the execution environment is ready:
65
67
 
66
68
  > You have uncommitted changes. Commit or stash them before running /forge:go.
67
69
 
70
+ 5. **Milestone size:** Count waves and agents in the milestone. If >3 waves or >6 total agents, warn:
71
+
72
+ > Warning: This milestone has {N} waves and {M} agents. Large milestones risk context overflow. Consider splitting before execution.
73
+
74
+ This is a pre-flight warning, not a hard abort — the user can choose to proceed. But the warning should be prominent so they can split the milestone first if needed.
75
+
68
76
  Print the pre-flight summary:
69
77
 
70
78
  ```
@@ -78,6 +86,21 @@ Print the pre-flight summary:
78
86
  Ready to execute Milestone 4.
79
87
  ```
80
88
 
89
+ ### Step 2.5 — Session Isolation (Automatic)
90
+
91
+ The execution engine automatically creates a git worktree for isolated execution. This happens transparently — you don't need to manage it manually.
92
+
93
+ **What happens behind the scenes:**
94
+ 1. A worktree is created at `../.forge-wt/<repo>/<session-id>/` based on the feature branch
95
+ 2. A session is registered in `.forge/sessions.json`
96
+ 3. All wave execution happens inside the worktree directory
97
+ 4. After completion, changes are merged back to the feature branch
98
+ 5. The worktree and session are cleaned up
99
+
100
+ **Why:** Multiple users or terminals can run `/forge:go` simultaneously without corrupting each other's work. Each session gets an isolated copy of the codebase.
101
+
102
+ **If worktree creation fails:** The engine falls back to running in the main working directory (original behavior). A warning is printed but execution continues.
103
+
81
104
  ### Step 3 — Execute Waves
82
105
 
83
106
  Parse the milestone section from the PRD. Each milestone contains waves with agent definitions:
@@ -261,7 +284,7 @@ After milestone completion, determine the next action:
261
284
  - Verification: PASSED
262
285
  - Branch: {branch} (pushed)
263
286
 
264
- **Next:** Run `/clear` to reset context, then `/forge:go` for Milestone {N+1}: {Next Milestone Name}.
287
+ **Next:** Run `/clear` then `/forge:go` for Milestone {N+1}, or exit and run `npx forge run` to auto-chain all remaining milestones.
265
288
  ```
266
289
 
267
290
  #### If this IS the last milestone:
@@ -303,27 +326,46 @@ The PR is ready for review.
303
326
 
304
327
  ### Auto Mode
305
328
 
306
- When the user selects "Auto (all milestones)" in Step 1.5 or invokes with `--auto` (e.g., `/forge:go --auto`), chain all remaining milestones with context resets between each.
329
+ When the user selects "Auto (all milestones)" in Step 1 Part B or invokes with `--auto` (e.g., `/forge:go --auto`):
330
+
331
+ Print the following instructions and then **stop** (do not execute a milestone):
307
332
 
308
- After each milestone completes (Step 5-7):
333
+ ```
334
+ ## Auto Mode — Fresh Context Execution
309
335
 
310
- 1. Print the completion summary for the milestone.
311
- 2. Print instructions for the context reset:
336
+ Auto mode runs each milestone in a fresh Claude session for maximum quality.
312
337
 
313
- ```
314
- ## Context Reset for Milestone {N+1}
338
+ **To start:** Exit this Claude session (Ctrl+C), then run in your terminal:
315
339
 
316
- Milestone {N} is complete and committed. Starting fresh context for the next milestone.
340
+ npx forge run
317
341
 
318
- To continue autonomously, start a new session and run:
319
- > /forge:go --auto
342
+ **What happens:**
343
+ - Each milestone gets a fresh Claude session (no context rot)
344
+ - Output streams inline to your terminal
345
+ - Stops on completion, failure, or stall
346
+ - Resume after failure: fix the issue, run `npx forge run` again
320
347
 
321
- The new session will read STATE.md (just updated) and pick up at Milestone {N+1}.
322
- ```
348
+ **Requires:** claude CLI on PATH, --dangerously-skip-permissions (automatic)
349
+ ```
350
+
351
+ **IMPORTANT:** Auto mode does NOT execute milestones in the current session. It redirects the user to `npx forge run`, which handles spawning fresh Claude sessions per milestone via the Ralph Loop pattern.
323
352
 
324
- **IMPORTANT:** Auto mode does NOT continue in the same context window. Each milestone gets a fresh context (the Ralph Loop pattern). The `--auto` flag simply means "after completing this milestone, print the instructions for continuing" rather than "execute everything in one session."
353
+ ### Parallel Milestones (dependsOn)
354
+
355
+ When milestones specify `dependsOn` fields in the PRD, the scheduler can identify which milestones are independent and run them in parallel:
356
+
357
+ - Milestones with no `dependsOn` (or `dependsOn: []`) can run in the first wave
358
+ - Milestones that depend on completed milestones become ready as dependencies finish
359
+ - The scheduler builds a DAG and groups milestones into execution waves
360
+
361
+ Example PRD milestone with dependencies:
362
+ ```
363
+ ### Milestone 3: Integration Layer
364
+ **dependsOn:** 1, 2
365
+ **Goal:** Combine components from M1 and M2...
366
+ ```
325
367
 
326
- This prevents context rot each milestone starts with clean context reading CLAUDE.md + STATE.md + current milestone section only (~20% of context window).
368
+ If no `dependsOn` fields are present, milestones execute sequentially (backward compatible).
327
369
 
328
370
  ### Step 9 — Linear Issue Start (On Milestone Begin)
329
371
 
@@ -347,3 +389,4 @@ If Linear is not configured, skip silently.
347
389
  > Milestone {N} has no wave definitions. Update the PRD with agent assignments before running /forge:go.
348
390
  - **Already on correct milestone:** If STATE.md's current milestone matches the target, proceed normally (this is the expected case).
349
391
  - **Linear auth fails:** Warn but continue execution. Linear sync is not blocking.
392
+ - **Worktree conflict:** If the worktree directory already exists (e.g., from a crashed session), the engine attempts `npx forge cleanup` first. If that fails, it falls back to main directory execution.
@@ -93,7 +93,32 @@ Check if `~/.claude/CLAUDE.md` exists:
93
93
  - **If it does not exist:** Create it using `globalClaudeMdTemplate()` from the templates.
94
94
  - **If it exists:** Leave it alone. Do not overwrite the user's global config.
95
95
 
96
- ### Step 6 — Install Hooks
96
+ ### Step 6 — Install Skills
97
+
98
+ Copy all forge skills to `~/.claude/commands/forge/` so they're discoverable via `/forge:*`:
99
+
100
+ ```bash
101
+ mkdir -p ~/.claude/commands/forge
102
+ ```
103
+
104
+ Find the installed forge-cc package and copy skill files, stripping the `forge-` prefix:
105
+
106
+ ```bash
107
+ SKILLS_DIR="$(dirname "$(which forge)")/../lib/node_modules/forge-cc/skills"
108
+ # Fallback: check local node_modules
109
+ if [ ! -d "$SKILLS_DIR" ]; then
110
+ SKILLS_DIR="node_modules/forge-cc/skills"
111
+ fi
112
+
113
+ for f in "$SKILLS_DIR"/forge-*.md; do
114
+ name=$(basename "$f" | sed 's/^forge-//')
115
+ cp "$f" ~/.claude/commands/forge/"$name"
116
+ done
117
+ ```
118
+
119
+ Print: "Installed forge skills to ~/.claude/commands/forge/"
120
+
121
+ ### Step 7 — Install Hooks
97
122
 
98
123
  Check if the user has a `.claude/settings.json` or `.claude/settings.local.json` in the project:
99
124
 
@@ -126,7 +151,7 @@ If a settings file already exists, inform the user:
126
151
  > Settings file already exists. To add the version-check hook manually, add this to your hooks config:
127
152
  > `"command": "node node_modules/forge-cc/hooks/version-check.js"`
128
153
 
129
- ### Step 7 — Summary
154
+ ### Step 8 — Summary
130
155
 
131
156
  Print a summary of everything that was created or updated:
132
157
 
@@ -138,6 +163,7 @@ Print a summary of everything that was created or updated:
138
163
  **Gates:** {comma-separated list}
139
164
 
140
165
  ### Files Created/Updated
166
+ - ~/.claude/commands/forge/*.md ✓ (skills)
141
167
  - .forge.json ✓
142
168
  - CLAUDE.md ✓
143
169
  - .planning/STATE.md ✓
@@ -83,12 +83,31 @@ Conduct an adaptive interview using the interview engine logic from `src/spec/in
83
83
  4. **Scope** — what's out, sacred files, boundaries
84
84
  5. **Milestones** — phasing, dependencies, delivery chunks
85
85
 
86
+ **Milestone Sizing Constraint (Hard Rule):**
87
+
88
+ Each milestone MUST be completable in one main agent context window. If a milestone requires more than ~4 agents across 2-3 waves, split it. This is non-negotiable — large milestones cause context overflow and execution failures. When interviewing about milestones, actively recommend splitting any milestone that looks too large.
89
+
90
+ **Milestone Dependencies (dependsOn):**
91
+
92
+ During the milestones phase of the interview, ask about milestone dependencies using AskUserQuestion. For each milestone after the first, ask:
93
+
94
+ - question: "Does Milestone {N} depend on any previous milestones?"
95
+ - options:
96
+ - "No dependencies — can start immediately"
97
+ - "Depends on Milestone {N-1} (sequential)"
98
+ - "Depends on specific milestones (I'll specify)"
99
+
100
+ If milestones have explicit dependencies, include `**dependsOn:** 1, 2` in the milestone section of the PRD. If no dependencies are specified, omit the field (backward compatible — treated as sequential).
101
+
102
+ Independent milestones enable parallel execution via `/forge:go`, which creates separate worktrees for each parallel milestone.
103
+
86
104
  **Interview Rules:**
87
105
 
88
- - **Lead with recommendations.** Every question includes context from the codebase scan. Never ask a blank "what do you want to build?" question.
89
- - **Ask 1-3 questions per round.** Present them as a numbered list. The user can answer all at once or pick which to answer.
90
- - **Follow interesting threads.** If the user mentions migration, breaking changes, multiple user types, or external integrations, follow up with targeted questions.
91
- - **Show progress.** After each answer round, show a compact status:
106
+ - **NEVER present questions as numbered text — always use AskUserQuestion with 2-4 options per question.** Every interview question MUST be delivered via Claude Code's AskUserQuestion tool with structured multiple-choice options. Do not print numbered lists of questions for the user to answer in free text.
107
+ - **Lead with recommendations.** Every question includes context from the codebase scan as the question text. Never ask a blank "what do you want to build?" question.
108
+ - **Ask 1 question at a time via AskUserQuestion.** Each question gets its own AskUserQuestion call with 2-4 predefined options derived from codebase scan context and common patterns. Always include a final option like "Other (I'll describe)" to allow the user to provide a custom answer.
109
+ - **Follow interesting threads.** If the user's selection mentions migration, breaking changes, multiple user types, or external integrations, follow up with targeted AskUserQuestion calls.
110
+ - **Show progress.** After each answer round, show a compact status as text output:
92
111
 
93
112
  ```
94
113
  Progress: [##---] Problem & Goals (2/2) | User Stories (0/2) | Technical (0/1) | Scope (0/1) | Milestones (0/1)
@@ -101,22 +120,35 @@ Progress: [##---] Problem & Goals (2/2) | User Stories (0/2) | Technical (0/1) |
101
120
  - **Stop when complete.** When all sections have enough info (Problem 2+, Users 2+, Technical 1+, Scope 1+, Milestones 1+), move to Step 4. Don't drag the interview out.
102
121
  - **Allow early exit.** If the user says "that's enough", "skip", or "generate it", respect that and move to Step 4 with what you have.
103
122
 
104
- **Question Format:**
123
+ **Question Format (AskUserQuestion):**
124
+
125
+ Each interview question MUST use AskUserQuestion. Build the question text from codebase scan context and the section being asked about. Provide 2-4 options that reflect likely answers based on the scan results, plus a free-text escape hatch. Example:
105
126
 
106
127
  ```
107
- ### Round N
128
+ AskUserQuestion:
129
+ question: "[Section] Context from scan or previous answers. Question text here?"
130
+ options:
131
+ - "Option A — a likely answer based on scan findings"
132
+ - "Option B — another plausible direction"
133
+ - "Option C — a third possibility (if applicable)"
134
+ - "Other (I'll describe)"
135
+ ```
136
+
137
+ If the user selects "Other (I'll describe)", prompt them for a free-text answer using a follow-up AskUserQuestion or accept their typed response.
108
138
 
109
- Based on your codebase scan, I have some questions:
139
+ **Do NOT do this (anti-pattern):**
110
140
 
111
- 1. **[Section]** Context from scan or previous answers.
112
- Question text here?
141
+ ```
142
+ ### Round N
113
143
 
114
- 2. **[Section]** Context observation.
115
- Question text here?
144
+ 1. **[Section]** Question text?
145
+ 2. **[Section]** Question text?
116
146
 
117
- > Answer by number (e.g., "1. My answer to first question. 2. Second answer") or answer all at once.
147
+ > Answer by number...
118
148
  ```
119
149
 
150
+ This numbered-text format is explicitly prohibited. Always use AskUserQuestion.
151
+
120
152
  ### Step 4 — Generate PRD
121
153
 
122
154
  Using all gathered interview answers and codebase scan results, generate the final PRD. Use the generator module at `src/spec/generator.ts`:
@@ -154,9 +186,13 @@ The PRD should follow this structure:
154
186
  - [ ] Issue title — brief description
155
187
 
156
188
  ### Milestone 2: {Name}
189
+ **dependsOn:** 1
190
+ **Goal:** {What this delivers}
157
191
  ...
158
192
  ```
159
193
 
194
+ **Milestone sizing check:** Before finalizing, review each milestone against the sizing constraint. Every milestone MUST fit in one agent context window (~4 agents across 2-3 waves max). If any milestone exceeds this, split it into smaller milestones before writing the final PRD. Set `maxContextWindowFit: true` on all milestones — if you cannot make a milestone fit, flag it as `maxContextWindowFit: false` and warn the user.
195
+
160
196
  Write the final PRD to `.planning/prds/{project-slug}.md`. Tell the user:
161
197
 
162
198
  > Final PRD written to `.planning/prds/{slug}.md`.
@@ -234,13 +270,15 @@ After sync, print the handoff prompt:
234
270
  PRD: `.planning/prds/{slug}.md`
235
271
  Linear: {project URL}
236
272
 
237
- **Next step:** Run `/forge:go` to start executing Milestone 1. The execution engine will:
273
+ **Next step:** Run `/forge:go` for one milestone at a time, or exit and run `npx forge run` to execute all milestones autonomously. The execution engine will:
238
274
  - Read the PRD and milestone plan
239
275
  - Spawn agent teams for each issue
240
276
  - Verify each change with forge-mcp gates
241
277
  - Open PRs and transition issues automatically
242
278
  ```
243
279
 
280
+ **Note:** `/forge:go` now uses git worktrees for session isolation. Multiple users can run `/forge:go` on different milestones simultaneously without conflicts.
281
+
244
282
  ## Edge Cases
245
283
 
246
284
  - **No Linear connection:** Warn the user. Still generate the PRD locally — skip the Linear sync steps.
@@ -48,7 +48,27 @@ Verify the update succeeded:
48
48
  npm list -g forge-cc --json 2>/dev/null | node -e "const d=require('fs').readFileSync('/dev/stdin','utf8');const j=JSON.parse(d);console.log(j.dependencies?.['forge-cc']?.version||'failed')"
49
49
  ```
50
50
 
51
- ### Step 4 — Post-Update Check
51
+ ### Step 4 — Re-sync Skills
52
+
53
+ After updating, copy the latest skills to `~/.claude/commands/forge/`:
54
+
55
+ ```bash
56
+ mkdir -p ~/.claude/commands/forge
57
+
58
+ SKILLS_DIR="$(dirname "$(which forge)")/../lib/node_modules/forge-cc/skills"
59
+ if [ ! -d "$SKILLS_DIR" ]; then
60
+ SKILLS_DIR="node_modules/forge-cc/skills"
61
+ fi
62
+
63
+ for f in "$SKILLS_DIR"/forge-*.md; do
64
+ name=$(basename "$f" | sed 's/^forge-//')
65
+ cp "$f" ~/.claude/commands/forge/"$name"
66
+ done
67
+ ```
68
+
69
+ Print: "Synced skills to ~/.claude/commands/forge/"
70
+
71
+ ### Step 5 — Post-Update Check
52
72
 
53
73
  After updating, check if the current project's forge files need refreshing:
54
74