forge-cc 0.1.34 → 0.1.36

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.
@@ -1,10 +1,10 @@
1
1
  /**
2
- * Adaptive interview engine for spec generation.
2
+ * Interview state tracker and coverage analyzer for spec generation.
3
3
  *
4
4
  * Pure logic — no side effects, no I/O. All state is passed in and returned.
5
- * The interview leads with recommendations derived from codebase scan results,
6
- * follows interesting threads based on user responses, and determines when
7
- * enough info has been gathered for each PRD section.
5
+ * The LLM (via the /forge:spec skill) drives question generation using scan
6
+ * results, prior answers, and coverage analysis. This module tracks state,
7
+ * analyzes coverage gaps, and provides structured summaries for PRD generation.
8
8
  *
9
9
  * **Rendering contract:** {@link InterviewQuestion} objects are designed to be
10
10
  * rendered via Claude Code's **AskUserQuestion** tool with structured
@@ -34,14 +34,49 @@ export const SECTION_LABELS = {
34
34
  milestones: "Milestones",
35
35
  };
36
36
  // ---------------------------------------------------------------------------
37
- // Minimum answer thresholds per section
37
+ // Section Topics subtopic checklists the LLM uses for coverage analysis
38
38
  // ---------------------------------------------------------------------------
39
- const MIN_ANSWERS = {
40
- problem_and_goals: 2,
41
- user_stories: 2,
42
- technical_approach: 1,
43
- scope: 1,
44
- milestones: 1,
39
+ export const SECTION_TOPICS = {
40
+ problem_and_goals: [
41
+ "core problem",
42
+ "desired outcome",
43
+ "success criteria",
44
+ "impact/urgency",
45
+ "current workarounds",
46
+ "who feels the pain",
47
+ ],
48
+ user_stories: [
49
+ "primary users",
50
+ "user workflows step-by-step",
51
+ "secondary users",
52
+ "edge cases",
53
+ "permissions/roles",
54
+ "error states",
55
+ ],
56
+ technical_approach: [
57
+ "architecture pattern",
58
+ "data model/schema",
59
+ "APIs/integrations",
60
+ "auth/security",
61
+ "performance requirements",
62
+ "error handling",
63
+ "existing code to leverage",
64
+ ],
65
+ scope: [
66
+ "in scope boundaries",
67
+ "out of scope",
68
+ "sacred files/areas",
69
+ "constraints",
70
+ "future phases explicitly deferred",
71
+ ],
72
+ milestones: [
73
+ "breakdown into chunks",
74
+ "dependencies between milestones",
75
+ "sizing (fits in one agent context?)",
76
+ "verification criteria",
77
+ "delivery order",
78
+ "risk areas",
79
+ ],
45
80
  };
46
81
  // ---------------------------------------------------------------------------
47
82
  // Interview Creation
@@ -62,61 +97,28 @@ export function createInterview(projectName, scanResults) {
62
97
  };
63
98
  }
64
99
  // ---------------------------------------------------------------------------
65
- // Question Generation
100
+ // Question Registration (LLM registers questions it asks for tracking)
66
101
  // ---------------------------------------------------------------------------
67
102
  /**
68
- * Generate 1-3 next questions based on current state.
69
- * Prioritizes sections with the most gaps, leads with recommendations.
70
- * Returns questions and the updated state (with new questions appended).
103
+ * Register a question the LLM asked, for summary/tracking purposes.
104
+ * The LLM drives question generation this just records what was asked.
71
105
  */
72
- export function generateNextQuestions(state) {
73
- const statuses = getSectionStatuses(state);
74
- const pendingFollowUp = findFollowUpOpportunities(state);
75
- const newQuestions = [];
76
- let nextId = state.nextQuestionId;
77
- // Priority 1: Follow up on interesting threads (max 1 follow-up per batch)
78
- if (pendingFollowUp.length > 0 && newQuestions.length < 3) {
79
- const followUp = pendingFollowUp[0];
80
- newQuestions.push({
81
- id: `q${nextId++}`,
82
- section: followUp.section,
83
- text: followUp.text,
84
- context: followUp.context,
85
- depth: followUp.depth,
86
- });
87
- }
88
- // Priority 2: Ask about incomplete sections in priority order
89
- for (const section of PRD_SECTIONS) {
90
- if (newQuestions.length >= 3)
91
- break;
92
- const status = statuses.find((s) => s.section === section);
93
- if (status?.isComplete)
94
- continue;
95
- // Skip if we already have a question for this section in this batch
96
- if (newQuestions.some((q) => q.section === section))
97
- continue;
98
- const question = generateSectionQuestion(state, section, nextId);
99
- if (question) {
100
- newQuestions.push(question);
101
- nextId++;
102
- }
103
- }
104
- // If we generated nothing (everything complete), return empty
105
- if (newQuestions.length === 0) {
106
- return { questions: [], state };
107
- }
108
- const updatedState = {
106
+ export function addQuestion(state, section, text, context, depth = 0) {
107
+ const question = {
108
+ id: `q${state.nextQuestionId}`,
109
+ section,
110
+ text,
111
+ context,
112
+ depth,
113
+ };
114
+ return {
109
115
  ...state,
110
- questions: [...state.questions, ...newQuestions],
111
- nextQuestionId: nextId,
116
+ questions: [...state.questions, question],
117
+ nextQuestionId: state.nextQuestionId + 1,
112
118
  askedSections: [
113
- ...new Set([
114
- ...state.askedSections,
115
- ...newQuestions.map((q) => q.section),
116
- ]),
119
+ ...new Set([...state.askedSections, section]),
117
120
  ],
118
121
  };
119
- return { questions: newQuestions, state: updatedState };
120
122
  }
121
123
  // ---------------------------------------------------------------------------
122
124
  // Answer Recording
@@ -161,31 +163,96 @@ export function markDraftUpdated(state) {
161
163
  };
162
164
  }
163
165
  // ---------------------------------------------------------------------------
164
- // Completeness Check
166
+ // Section Statuses
165
167
  // ---------------------------------------------------------------------------
166
168
  /**
167
- * Returns true when all sections have met their minimum answer thresholds.
169
+ * Compute the coverage level for a section based on answer count, word count,
170
+ * and topic coverage.
168
171
  */
169
- export function isComplete(state) {
170
- return getSectionStatuses(state).every((s) => s.isComplete);
172
+ function computeCoverageLevel(answeredCount, totalWords, topics, mentionedTopics) {
173
+ if (answeredCount === 0)
174
+ return "none";
175
+ if (answeredCount === 1 || totalWords < 50)
176
+ return "thin";
177
+ const mostTopicsMentioned = topics.length === 0 || mentionedTopics.length >= topics.length * 0.6;
178
+ if (answeredCount >= 4 || (totalWords >= 200 && mostTopicsMentioned)) {
179
+ return "thorough";
180
+ }
181
+ if (answeredCount >= 2 && totalWords >= 50)
182
+ return "moderate";
183
+ return "thin";
184
+ }
185
+ /**
186
+ * Find which SECTION_TOPICS appear in the section's answers (simple substring match).
187
+ */
188
+ function findMentionedTopics(answers, topics) {
189
+ const combined = answers.join(" ").toLowerCase();
190
+ return topics.filter((topic) => combined.includes(topic.toLowerCase()));
171
191
  }
172
- // ---------------------------------------------------------------------------
173
- // Section Statuses
174
- // ---------------------------------------------------------------------------
175
192
  /**
176
193
  * Get the completeness status for all sections.
177
194
  */
178
195
  export function getSectionStatuses(state) {
179
196
  return PRD_SECTIONS.map((section) => {
180
- const answeredCount = state.answers.filter((a) => a.section === section).length;
181
- const minRequired = MIN_ANSWERS[section];
197
+ const sectionAnswers = state.answers.filter((a) => a.section === section);
198
+ const answeredCount = sectionAnswers.length;
199
+ const totalWords = sectionAnswers.reduce((sum, a) => sum + a.answer.split(/\s+/).filter(Boolean).length, 0);
200
+ const topics = SECTION_TOPICS[section];
201
+ const mentionedTopics = findMentionedTopics(sectionAnswers.map((a) => a.answer), topics);
202
+ const uncoveredTopics = topics.filter((t) => !mentionedTopics.includes(t));
203
+ const coverageLevel = computeCoverageLevel(answeredCount, totalWords, topics, mentionedTopics);
204
+ return {
205
+ section,
206
+ answeredCount,
207
+ totalWords,
208
+ coverageLevel,
209
+ hasGaps: uncoveredTopics.length > 0,
210
+ };
211
+ });
212
+ }
213
+ // ---------------------------------------------------------------------------
214
+ // Coverage Analysis
215
+ // ---------------------------------------------------------------------------
216
+ /**
217
+ * Get detailed coverage analysis for all sections, including topic-level detail.
218
+ * This is the primary tool for the LLM to decide what to ask next.
219
+ */
220
+ export function getCoverageAnalysis(state) {
221
+ const sections = PRD_SECTIONS.map((section) => {
222
+ const sectionAnswers = state.answers.filter((a) => a.section === section);
223
+ const answeredCount = sectionAnswers.length;
224
+ const totalWords = sectionAnswers.reduce((sum, a) => sum + a.answer.split(/\s+/).filter(Boolean).length, 0);
225
+ const topics = SECTION_TOPICS[section];
226
+ const mentionedTopics = findMentionedTopics(sectionAnswers.map((a) => a.answer), topics);
227
+ const uncoveredTopics = topics.filter((t) => !mentionedTopics.includes(t));
228
+ const coverageLevel = computeCoverageLevel(answeredCount, totalWords, topics, mentionedTopics);
182
229
  return {
183
230
  section,
231
+ label: SECTION_LABELS[section],
184
232
  answeredCount,
185
- minRequired,
186
- isComplete: answeredCount >= minRequired,
233
+ totalWords,
234
+ coverageLevel,
235
+ topics,
236
+ mentionedTopics,
237
+ uncoveredTopics,
187
238
  };
188
239
  });
240
+ const levels = sections.map((s) => s.coverageLevel);
241
+ const hasGaps = sections.some((s) => s.uncoveredTopics.length > 0);
242
+ let overallLevel;
243
+ if (levels.every((l) => l === "thorough")) {
244
+ overallLevel = "thorough";
245
+ }
246
+ else if (levels.every((l) => l === "thorough" || l === "moderate")) {
247
+ overallLevel = "moderate";
248
+ }
249
+ else if (levels.some((l) => l !== "none")) {
250
+ overallLevel = "thin";
251
+ }
252
+ else {
253
+ overallLevel = "none";
254
+ }
255
+ return { sections, overallLevel, hasGaps };
189
256
  }
190
257
  // ---------------------------------------------------------------------------
191
258
  // Interview Summary
@@ -208,7 +275,7 @@ export function getInterviewSummary(state) {
208
275
  answer: a.answer,
209
276
  };
210
277
  }),
211
- isComplete: status.isComplete,
278
+ coverageLevel: status.coverageLevel,
212
279
  };
213
280
  }
214
281
  return {
@@ -217,134 +284,4 @@ export function getInterviewSummary(state) {
217
284
  scanResults: state.scanResults,
218
285
  };
219
286
  }
220
- // ---------------------------------------------------------------------------
221
- // Internal Helpers
222
- // ---------------------------------------------------------------------------
223
- /**
224
- * Generate a question for a specific section based on codebase context.
225
- * Returns null if no meaningful question can be generated.
226
- */
227
- function generateSectionQuestion(state, section, nextId) {
228
- const answeredCount = state.answers.filter((a) => a.section === section).length;
229
- const scan = state.scanResults;
230
- const q = (text, context) => ({
231
- id: `q${nextId}`,
232
- section,
233
- text,
234
- context,
235
- depth: 0,
236
- });
237
- switch (section) {
238
- case "problem_and_goals": {
239
- if (answeredCount === 0) {
240
- const fw = scan.structure.frameworks.length > 0 ? scan.structure.frameworks.join(", ") : null;
241
- return q("What problem does this project solve? What's the desired outcome when it's done?", `I found a ${scan.structure.language} project${fw ? ` using ${fw}` : ""}. Help me understand what you're building and why.`);
242
- }
243
- return q("How will you know this project is successful? What does 'done' look like?", "I want to define clear success criteria for the PRD.");
244
- }
245
- case "user_stories": {
246
- if (answeredCount === 0) {
247
- const pageRoutes = scan.routes.routes.filter((r) => r.type === "page");
248
- const apiRoutes = scan.routes.routes.filter((r) => r.type === "api");
249
- const hasRoutes = pageRoutes.length > 0;
250
- const hasAPI = apiRoutes.length > 0;
251
- const routeContext = hasRoutes
252
- ? `I see ${pageRoutes.length} page(s) and ${apiRoutes.length} API route(s).`
253
- : hasAPI
254
- ? `I see ${apiRoutes.length} API route(s) but no pages.`
255
- : "I don't see existing routes yet.";
256
- return q("Who are the primary users of this feature? What do they need to accomplish?", `${routeContext} Understanding the users will help me structure milestones around their journeys.`);
257
- }
258
- return q("Are there secondary users or admin workflows to consider?", "Capturing all user types early prevents scope creep later.");
259
- }
260
- case "technical_approach": {
261
- const fw = scan.structure.frameworks;
262
- const fwLabel = fw.length > 0 ? fw.join(", ") : null;
263
- const models = scan.dataAPIs.dataModels;
264
- const hasDB = models.length > 0;
265
- const dbLabel = hasDB
266
- ? models.slice(0, 3).map((m) => m.name).join(", ")
267
- : "";
268
- if (answeredCount === 0) {
269
- return q("Are there specific technical decisions already made? Any constraints on architecture, APIs, or data storage?", `Current stack: ${scan.structure.language}${fwLabel ? `/${fwLabel}` : ""}${hasDB ? `, data models: [${dbLabel}]` : ""}. I'll build the technical approach around existing decisions.`);
270
- }
271
- return null; // One answer usually sufficient for technical approach
272
- }
273
- case "scope": {
274
- const configFiles = scan.structure.configFiles;
275
- if (answeredCount === 0) {
276
- return q("What's explicitly OUT of scope? Are there any sacred files or areas of the codebase that should not be touched?", configFiles.length > 0
277
- ? `Key config files I found: ${configFiles.slice(0, 8).join(", ")}. Knowing what's off-limits helps me write safer milestones.`
278
- : "Defining boundaries early prevents scope creep.");
279
- }
280
- return null;
281
- }
282
- case "milestones": {
283
- if (answeredCount === 0) {
284
- return q("How would you break this work into deliverable chunks? Any natural phases or dependencies between pieces?", "I'll structure Linear milestones around your phasing. Each milestone should be independently shippable.");
285
- }
286
- return null;
287
- }
288
- }
289
- }
290
- /**
291
- * Find opportunities for follow-up questions based on recent answers.
292
- * Looks for keywords/patterns that suggest deeper exploration would be valuable.
293
- */
294
- function findFollowUpOpportunities(state) {
295
- const opportunities = [];
296
- // Only consider the last 3 answers for follow-ups
297
- const recentAnswers = state.answers.slice(-3);
298
- for (const answer of recentAnswers) {
299
- const question = state.questions.find((q) => q.id === answer.questionId);
300
- if (!question)
301
- continue;
302
- // Don't follow up on follow-ups beyond depth 2
303
- if (question.depth >= 2)
304
- continue;
305
- // Don't generate follow-ups for questions we've already followed up on
306
- const hasFollowUp = state.questions.some((q) => q.depth > question.depth &&
307
- q.section === question.section &&
308
- state.questions.indexOf(q) > state.questions.indexOf(question));
309
- if (hasFollowUp)
310
- continue;
311
- const lower = answer.answer.toLowerCase();
312
- // Detect mentions of migration/breaking changes — worth digging into
313
- if ((lower.includes("migrat") || lower.includes("breaking")) &&
314
- question.section !== "scope") {
315
- opportunities.push({
316
- section: "scope",
317
- text: "You mentioned migration/breaking changes. What existing data or APIs need to be preserved? Any backward compatibility requirements?",
318
- context: `Based on your answer about ${SECTION_LABELS[question.section]}.`,
319
- depth: question.depth + 1,
320
- });
321
- }
322
- // Detect mentions of multiple user types — worth expanding user stories
323
- if ((lower.includes("admin") ||
324
- lower.includes("different user") ||
325
- lower.includes("roles")) &&
326
- answer.section === "user_stories") {
327
- opportunities.push({
328
- section: "user_stories",
329
- text: "You mentioned different user types/roles. Can you walk me through the key workflow for each type?",
330
- context: "Multiple user types often need separate milestone planning.",
331
- depth: question.depth + 1,
332
- });
333
- }
334
- // Detect mentions of external services — worth clarifying integration scope
335
- if ((lower.includes("api") ||
336
- lower.includes("third-party") ||
337
- lower.includes("external") ||
338
- lower.includes("integration")) &&
339
- answer.section === "technical_approach") {
340
- opportunities.push({
341
- section: "technical_approach",
342
- text: "You mentioned external integrations. Which services are critical path vs nice-to-have? Any rate limits or auth concerns?",
343
- context: "External dependencies often need their own milestone.",
344
- depth: question.depth + 1,
345
- });
346
- }
347
- }
348
- return opportunities;
349
- }
350
287
  //# sourceMappingURL=interview.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"interview.js","sourceRoot":"","sources":["../../src/spec/interview.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"interview.js","sourceRoot":"","sources":["../../src/spec/interview.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "forge-cc",
3
- "version": "0.1.34",
3
+ "version": "0.1.36",
4
4
  "description": "Pre-PR verification harness for Claude Code agents — gate runner + CLI + MCP server",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -418,6 +418,14 @@ TeamDelete to clean up team resources.
418
418
 
419
419
  #### If this IS the last milestone:
420
420
 
421
+ **Fetch Linear issue identifiers** (if Linear is configured):
422
+
423
+ ```bash
424
+ npx forge linear-sync list-issues --slug {slug}
425
+ ```
426
+
427
+ This outputs a JSON array of identifiers (e.g., `["MSIG-123", "MSIG-124"]`). If the command returns identifiers, include them in the PR body as a `Closes` line. If no Linear project ID is configured or the command returns nothing, omit the "Linear Issues" section.
428
+
421
429
  Create a pull request:
422
430
 
423
431
  ```bash
@@ -430,6 +438,10 @@ gh pr create --title "feat: {Project Name}" --body "$(cat <<'EOF'
430
438
  - [x] Milestone 2: {name}
431
439
  ...
432
440
 
441
+ ## Linear Issues
442
+
443
+ Closes {TEAM-1}, {TEAM-2}, ...
444
+
433
445
  ## Verification
434
446
  All milestones passed forge verification (types, lint, tests) and agent team review.
435
447
 
@@ -439,6 +451,8 @@ EOF
439
451
  )"
440
452
  ```
441
453
 
454
+ The "Linear Issues" section with `Closes` keywords enables Linear's GitHub integration to auto-close the linked issues when the PR is merged. Omit this section entirely if no identifiers were found.
455
+
442
456
  **Codex Review Gate:**
443
457
 
444
458
  After `gh pr create` succeeds, poll for Codex review comments:
@@ -472,6 +486,8 @@ Then shut down the agent team and print:
472
486
  - Codex review: {resolved/no comments/not configured}
473
487
 
474
488
  The PR is ready for review.
489
+
490
+ **After the PR is merged:** Run `npx forge linear-sync done --slug {slug}` to transition all issues and the project to "Done" in Linear. Or add this to your CI merge pipeline.
475
491
  ```
476
492
 
477
493
  **IMPORTANT:** Do NOT merge the PR automatically. Merging is a hard-to-reverse action that requires explicit user confirmation. Always stop here and let the user decide when to merge.
@@ -73,15 +73,48 @@ If the current working directory does not look like a project (no package.json,
73
73
 
74
74
  > This directory doesn't look like a project root. Should I scan here, or provide a path to the project?
75
75
 
76
- ### Step 3 — Adaptive Interview
76
+ ### Step 3 — LLM-Driven Adaptive Interview
77
77
 
78
- Conduct an adaptive interview using the interview engine logic from `src/spec/interview.ts`. The interview covers 5 sections in priority order:
78
+ You (the LLM) drive the interview. The interview engine (`src/spec/interview.ts`) is a state tracker and coverage analyzer — you decide what to ask and when to stop. Use `createInterview()` to initialize state, `addQuestion()` to register each question you ask, `recordAnswer()` to record responses, and `getCoverageAnalysis()` to assess coverage gaps.
79
79
 
80
- 1. **Problem & Goals** — what problem, desired outcome, success criteria
81
- 2. **User Stories** — primary users, workflows, secondary users
82
- 3. **Technical Approach** — architecture decisions, constraints, stack
83
- 4. **Scope** — what's out, sacred files, boundaries
84
- 5. **Milestones** — phasing, dependencies, delivery chunks
80
+ The interview covers 5 sections:
81
+
82
+ 1. **Problem & Goals** — core problem, desired outcome, success criteria, impact/urgency, current workarounds, who feels the pain
83
+ 2. **User Stories** — primary users, user workflows step-by-step, secondary users, edge cases, permissions/roles, error states
84
+ 3. **Technical Approach** — architecture pattern, data model/schema, APIs/integrations, auth/security, performance requirements, error handling, existing code to leverage
85
+ 4. **Scope** — in scope boundaries, out of scope, sacred files/areas, constraints, future phases explicitly deferred
86
+ 5. **Milestones** — breakdown into chunks, dependencies between milestones, sizing (fits in one agent context?), verification criteria, delivery order, risk areas
87
+
88
+ **Interview Loop — You Drive:**
89
+
90
+ 1. Initialize interview state with `createInterview(projectName, scanResults)`
91
+ 2. Start with a broad opening question for Problem & Goals, informed by scan results
92
+ 3. After each answer:
93
+ - Assess coverage gaps mentally (which topics are uncovered? which answers were vague?)
94
+ - Generate the next question based on: scan results, all prior Q&A, what's still ambiguous
95
+ - Probe deeper on vague or short answers — don't accept "TBD" or one-liners for important topics
96
+ - Move to the next section when the current one has thorough coverage
97
+ - Revisit earlier sections if later answers reveal new info
98
+ 4. Register each question with `addQuestion(state, section, text, context, depth)` for tracking
99
+ 5. Record each answer with `recordAnswer(state, questionId, answer)`
100
+
101
+ **Question Principles — Encode These:**
102
+
103
+ - Ask about edge cases, error states, and "what could go wrong"
104
+ - When the user mentions an integration, ask about auth, rate limits, failure modes
105
+ - When the user describes a workflow, walk through it step-by-step
106
+ - For milestones, actively challenge sizing — "is this too big for one context window?"
107
+ - Don't ask yes/no questions — ask "how" and "what" questions that elicit detail
108
+ - Circle back to earlier sections when new info surfaces
109
+ - Follow interesting threads: if the user mentions migration, breaking changes, multiple user types, or external services, dig deeper
110
+
111
+ **Stop Condition:**
112
+
113
+ You determine you have enough detail for a thorough PRD across ALL sections, with no significant ambiguity remaining. Before transitioning to Step 4, print a coverage summary showing the final state of each section.
114
+
115
+ **Early Exit:**
116
+
117
+ If the user says "stop", "that's enough", "skip", or "generate it" at any time, respect that and move to Step 4 with what you have.
85
118
 
86
119
  **Milestone Sizing Constraint (Hard Rule):**
87
120
 
@@ -101,40 +134,28 @@ If milestones have explicit dependencies, include `**dependsOn:** 1, 2` in the m
101
134
 
102
135
  Independent milestones enable parallel execution via `/forge:go`, which creates separate worktrees for each parallel milestone.
103
136
 
104
- **Interview Rules:**
137
+ **Question Format:**
105
138
 
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:
139
+ Mix AskUserQuestion (for structured choices) and conversational questions (for open-ended probing):
111
140
 
112
- ```
113
- Progress: [##---] Problem & Goals (2/2) | User Stories (0/2) | Technical (0/1) | Scope (0/1) | Milestones (0/1)
114
- ```
115
-
116
- - **Update the PRD draft every 2-3 answers.** Write the current draft to `.planning/prds/{project-slug}.md`. Tell the user:
117
-
118
- > Updated PRD draft at `.planning/prds/{slug}.md` — you can review it anytime.
141
+ - **Use AskUserQuestion** when there are clear option sets (architecture choices, yes/no with detail, picking from scan-derived options). Provide 2-4 options plus "Other (I'll describe)".
142
+ - **Use conversational questions** when probing for depth, asking "how" or "what" questions, or exploring topics that don't have predefined options.
143
+ - **NEVER present questions as numbered text lists.** Each structured question gets its own AskUserQuestion call.
144
+ - **Lead with recommendations.** Every question includes context from the codebase scan. Never ask a blank "what do you want to build?" question.
119
145
 
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.
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.
146
+ **Progress Display:**
122
147
 
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:
148
+ After each answer, show a compact coverage status:
126
149
 
127
150
  ```
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)"
151
+ Progress: Problem & Goals [thorough] | User Stories [moderate] | Technical [thin] | Scope [none] | Milestones [none]
135
152
  ```
136
153
 
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.
154
+ **Draft Updates:**
155
+
156
+ - **Update the PRD draft every 2-3 answers** (use `shouldUpdateDraft(state)` to check, `markDraftUpdated(state)` after writing). Write to `.planning/prds/{project-slug}.md`. Tell the user:
157
+
158
+ > Updated PRD draft at `.planning/prds/{slug}.md` — you can review it anytime.
138
159
 
139
160
  **Do NOT do this (anti-pattern):**
140
161
 
@@ -147,7 +168,7 @@ If the user selects "Other (I'll describe)", prompt them for a free-text answer
147
168
  > Answer by number...
148
169
  ```
149
170
 
150
- This numbered-text format is explicitly prohibited. Always use AskUserQuestion.
171
+ This numbered-text format is explicitly prohibited. Always use AskUserQuestion for structured choices.
151
172
 
152
173
  ### Step 4 — Generate PRD
153
174