forge-cc 0.1.35 → 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.
- package/dist/spec/interview.d.ts +39 -19
- package/dist/spec/interview.js +138 -201
- package/dist/spec/interview.js.map +1 -1
- package/package.json +1 -1
- package/skills/forge-spec.md +55 -34
package/dist/spec/interview.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
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
|
|
6
|
-
*
|
|
7
|
-
*
|
|
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
|
|
@@ -47,13 +47,15 @@ export interface InterviewAnswer {
|
|
|
47
47
|
answer: string;
|
|
48
48
|
timestamp: number;
|
|
49
49
|
}
|
|
50
|
+
/** Coverage level for a section */
|
|
51
|
+
export type CoverageLevel = "none" | "thin" | "moderate" | "thorough";
|
|
50
52
|
/** Completeness status for a single section */
|
|
51
53
|
export interface SectionStatus {
|
|
52
54
|
section: PRDSection;
|
|
53
55
|
answeredCount: number;
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
56
|
+
totalWords: number;
|
|
57
|
+
coverageLevel: CoverageLevel;
|
|
58
|
+
hasGaps: boolean;
|
|
57
59
|
}
|
|
58
60
|
/** Full interview state — serializable, passed in/out of every function */
|
|
59
61
|
export interface InterviewState {
|
|
@@ -77,24 +79,41 @@ export interface InterviewSummary {
|
|
|
77
79
|
question: string;
|
|
78
80
|
answer: string;
|
|
79
81
|
}>;
|
|
80
|
-
|
|
82
|
+
coverageLevel: CoverageLevel;
|
|
81
83
|
}>;
|
|
82
84
|
scanResults: ScanAllResult;
|
|
83
85
|
}
|
|
86
|
+
/** Per-section coverage detail */
|
|
87
|
+
export interface SectionCoverage {
|
|
88
|
+
section: PRDSection;
|
|
89
|
+
label: string;
|
|
90
|
+
answeredCount: number;
|
|
91
|
+
totalWords: number;
|
|
92
|
+
coverageLevel: CoverageLevel;
|
|
93
|
+
/** All subtopics for this section */
|
|
94
|
+
topics: string[];
|
|
95
|
+
/** Topics that appear in answers (simple substring match) */
|
|
96
|
+
mentionedTopics: string[];
|
|
97
|
+
/** topics − mentionedTopics */
|
|
98
|
+
uncoveredTopics: string[];
|
|
99
|
+
}
|
|
100
|
+
/** Full coverage analysis across all sections */
|
|
101
|
+
export interface CoverageAnalysis {
|
|
102
|
+
sections: SectionCoverage[];
|
|
103
|
+
overallLevel: CoverageLevel;
|
|
104
|
+
hasGaps: boolean;
|
|
105
|
+
}
|
|
106
|
+
export declare const SECTION_TOPICS: Record<PRDSection, string[]>;
|
|
84
107
|
/**
|
|
85
108
|
* Initialize a new interview with codebase context.
|
|
86
109
|
* The scan results inform the recommendations attached to questions.
|
|
87
110
|
*/
|
|
88
111
|
export declare function createInterview(projectName: string, scanResults: ScanAllResult): InterviewState;
|
|
89
112
|
/**
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
* Returns questions and the updated state (with new questions appended).
|
|
113
|
+
* Register a question the LLM asked, for summary/tracking purposes.
|
|
114
|
+
* The LLM drives question generation — this just records what was asked.
|
|
93
115
|
*/
|
|
94
|
-
export declare function
|
|
95
|
-
questions: InterviewQuestion[];
|
|
96
|
-
state: InterviewState;
|
|
97
|
-
};
|
|
116
|
+
export declare function addQuestion(state: InterviewState, section: PRDSection, text: string, context: string, depth?: number): InterviewState;
|
|
98
117
|
/**
|
|
99
118
|
* Record the user's answer and return updated state.
|
|
100
119
|
*/
|
|
@@ -108,14 +127,15 @@ export declare function shouldUpdateDraft(state: InterviewState): boolean;
|
|
|
108
127
|
* Reset the draft update counter (call after updating the PRD draft).
|
|
109
128
|
*/
|
|
110
129
|
export declare function markDraftUpdated(state: InterviewState): InterviewState;
|
|
111
|
-
/**
|
|
112
|
-
* Returns true when all sections have met their minimum answer thresholds.
|
|
113
|
-
*/
|
|
114
|
-
export declare function isComplete(state: InterviewState): boolean;
|
|
115
130
|
/**
|
|
116
131
|
* Get the completeness status for all sections.
|
|
117
132
|
*/
|
|
118
133
|
export declare function getSectionStatuses(state: InterviewState): SectionStatus[];
|
|
134
|
+
/**
|
|
135
|
+
* Get detailed coverage analysis for all sections, including topic-level detail.
|
|
136
|
+
* This is the primary tool for the LLM to decide what to ask next.
|
|
137
|
+
*/
|
|
138
|
+
export declare function getCoverageAnalysis(state: InterviewState): CoverageAnalysis;
|
|
119
139
|
/**
|
|
120
140
|
* Build a structured summary of everything gathered, for PRD generation.
|
|
121
141
|
*/
|
package/dist/spec/interview.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
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
|
|
6
|
-
*
|
|
7
|
-
*
|
|
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
|
-
//
|
|
37
|
+
// Section Topics — subtopic checklists the LLM uses for coverage analysis
|
|
38
38
|
// ---------------------------------------------------------------------------
|
|
39
|
-
const
|
|
40
|
-
problem_and_goals:
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
|
100
|
+
// Question Registration (LLM registers questions it asks for tracking)
|
|
66
101
|
// ---------------------------------------------------------------------------
|
|
67
102
|
/**
|
|
68
|
-
*
|
|
69
|
-
*
|
|
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
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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,
|
|
111
|
-
nextQuestionId:
|
|
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
|
-
//
|
|
166
|
+
// Section Statuses
|
|
165
167
|
// ---------------------------------------------------------------------------
|
|
166
168
|
/**
|
|
167
|
-
*
|
|
169
|
+
* Compute the coverage level for a section based on answer count, word count,
|
|
170
|
+
* and topic coverage.
|
|
168
171
|
*/
|
|
169
|
-
|
|
170
|
-
|
|
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
|
|
181
|
-
const
|
|
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
|
-
|
|
186
|
-
|
|
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
|
-
|
|
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":"AAAA;;;;;;;;;;;;;;;GAeG;AAIH,8EAA8E;AAC9E,QAAQ;AACR,8EAA8E;AAE9E,qCAAqC;AACrC,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,mBAAmB;IACnB,cAAc;IACd,oBAAoB;IACpB,OAAO;IACP,YAAY;CACJ,CAAC;AAGX,6CAA6C;AAC7C,MAAM,CAAC,MAAM,cAAc,GAA+B;IACxD,iBAAiB,EAAE,iBAAiB;IACpC,YAAY,EAAE,cAAc;IAC5B,kBAAkB,EAAE,oBAAoB;IACxC,KAAK,EAAE,OAAO;IACd,UAAU,EAAE,YAAY;CACzB,CAAC;
|
|
1
|
+
{"version":3,"file":"interview.js","sourceRoot":"","sources":["../../src/spec/interview.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/skills/forge-spec.md
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
-
**
|
|
137
|
+
**Question Format:**
|
|
105
138
|
|
|
106
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|