@iservu-inc/adf-cli 0.4.35 → 0.5.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.
@@ -0,0 +1,293 @@
1
+ /**
2
+ * Question Mapper
3
+ *
4
+ * Maps each question to the information types it's designed to gather.
5
+ * This allows the system to determine which questions can be skipped
6
+ * if that information has already been extracted from previous answers.
7
+ *
8
+ * For example:
9
+ * - "What are you building?" -> [PROJECT_GOAL, TECH_STACK, FEATURES, PLATFORM]
10
+ * - "What tech stack will you use?" -> [TECH_STACK, ARCHITECTURE]
11
+ * - "Who are your users?" -> [TARGET_USERS, PROJECT_GOAL]
12
+ */
13
+ class QuestionMapper {
14
+ constructor() {
15
+ // Information types (should match AnswerAnalyzer)
16
+ this.INFO_TYPES = {
17
+ TECH_STACK: 'tech_stack',
18
+ ARCHITECTURE: 'architecture',
19
+ PROJECT_GOAL: 'project_goal',
20
+ TARGET_USERS: 'target_users',
21
+ FEATURES: 'features',
22
+ CONSTRAINTS: 'constraints',
23
+ TIMELINE: 'timeline',
24
+ TEAM_SIZE: 'team_size',
25
+ PLATFORM: 'platform',
26
+ DEPLOYMENT: 'deployment',
27
+ SECURITY: 'security',
28
+ PERFORMANCE: 'performance'
29
+ };
30
+
31
+ // Build mappings based on question patterns
32
+ this.buildMappings();
33
+ }
34
+
35
+ /**
36
+ * Build mappings for common question patterns
37
+ */
38
+ buildMappings() {
39
+ this.patterns = [
40
+ // Goal/Purpose questions
41
+ {
42
+ keywords: ['goal', 'building', 'purpose', 'create', 'develop', 'project'],
43
+ types: [this.INFO_TYPES.PROJECT_GOAL],
44
+ priority: 1 // Higher priority = more fundamental question
45
+ },
46
+
47
+ // Tech Stack questions
48
+ {
49
+ keywords: ['tech', 'stack', 'technology', 'framework', 'language', 'tools'],
50
+ types: [this.INFO_TYPES.TECH_STACK],
51
+ priority: 2
52
+ },
53
+
54
+ // Architecture questions
55
+ {
56
+ keywords: ['architecture', 'structure', 'design', 'organize', 'components'],
57
+ types: [this.INFO_TYPES.ARCHITECTURE],
58
+ priority: 2
59
+ },
60
+
61
+ // User questions
62
+ {
63
+ keywords: ['users', 'audience', 'customers', 'personas', 'who will use'],
64
+ types: [this.INFO_TYPES.TARGET_USERS],
65
+ priority: 3
66
+ },
67
+
68
+ // Feature questions
69
+ {
70
+ keywords: ['features', 'functionality', 'capabilities', 'do', 'functions'],
71
+ types: [this.INFO_TYPES.FEATURES],
72
+ priority: 3
73
+ },
74
+
75
+ // Platform questions
76
+ {
77
+ keywords: ['platform', 'web', 'mobile', 'desktop', 'where', 'run'],
78
+ types: [this.INFO_TYPES.PLATFORM],
79
+ priority: 2
80
+ },
81
+
82
+ // Timeline questions
83
+ {
84
+ keywords: ['timeline', 'deadline', 'when', 'schedule', 'launch'],
85
+ types: [this.INFO_TYPES.TIMELINE],
86
+ priority: 4
87
+ },
88
+
89
+ // Team questions
90
+ {
91
+ keywords: ['team', 'developers', 'people', 'resources', 'who'],
92
+ types: [this.INFO_TYPES.TEAM_SIZE],
93
+ priority: 4
94
+ },
95
+
96
+ // Constraints questions
97
+ {
98
+ keywords: ['constraints', 'limitations', 'requirements', 'must', 'cannot'],
99
+ types: [this.INFO_TYPES.CONSTRAINTS],
100
+ priority: 3
101
+ },
102
+
103
+ // Deployment questions
104
+ {
105
+ keywords: ['deploy', 'hosting', 'infrastructure', 'production', 'server'],
106
+ types: [this.INFO_TYPES.DEPLOYMENT, this.INFO_TYPES.TECH_STACK],
107
+ priority: 4
108
+ },
109
+
110
+ // Security questions
111
+ {
112
+ keywords: ['security', 'authentication', 'authorization', 'secure', 'privacy'],
113
+ types: [this.INFO_TYPES.SECURITY],
114
+ priority: 3
115
+ },
116
+
117
+ // Performance questions
118
+ {
119
+ keywords: ['performance', 'speed', 'scale', 'optimization', 'fast'],
120
+ types: [this.INFO_TYPES.PERFORMANCE],
121
+ priority: 4
122
+ }
123
+ ];
124
+ }
125
+
126
+ /**
127
+ * Map a question to information types it gathers
128
+ *
129
+ * @param {Object} question - Question object with id and text
130
+ * @returns {Object} - { types: [], priority: number, confidence: number }
131
+ */
132
+ mapQuestion(question) {
133
+ const questionText = (question.text || '').toLowerCase();
134
+ const questionId = (question.id || '').toLowerCase();
135
+ const combinedText = `${questionText} ${questionId}`;
136
+
137
+ const matchedTypes = new Set();
138
+ let highestPriority = 5; // Lower number = higher priority
139
+
140
+ // Check each pattern
141
+ this.patterns.forEach(pattern => {
142
+ const matches = pattern.keywords.some(keyword =>
143
+ combinedText.includes(keyword)
144
+ );
145
+
146
+ if (matches) {
147
+ pattern.types.forEach(type => matchedTypes.add(type));
148
+ if (pattern.priority < highestPriority) {
149
+ highestPriority = pattern.priority;
150
+ }
151
+ }
152
+ });
153
+
154
+ // If no matches found, make a best guess based on question structure
155
+ if (matchedTypes.size === 0) {
156
+ matchedTypes.add(this.INFO_TYPES.PROJECT_GOAL); // Default assumption
157
+ }
158
+
159
+ return {
160
+ types: Array.from(matchedTypes),
161
+ priority: highestPriority,
162
+ confidence: matchedTypes.size > 0 ? 85 : 40 // How confident are we in this mapping
163
+ };
164
+ }
165
+
166
+ /**
167
+ * Check if a question's information types are already satisfied in the knowledge graph
168
+ *
169
+ * @param {Object} question - Question object
170
+ * @param {KnowledgeGraph} knowledgeGraph - Knowledge graph instance
171
+ * @param {number} minConfidence - Minimum confidence threshold (default 70)
172
+ * @returns {Object} - { canSkip: boolean, reason: string, satisfiedTypes: [], missingTypes: [] }
173
+ */
174
+ canSkipQuestion(question, knowledgeGraph, minConfidence = 70) {
175
+ const mapping = this.mapQuestion(question);
176
+ const satisfiedTypes = [];
177
+ const missingTypes = [];
178
+
179
+ mapping.types.forEach(type => {
180
+ if (knowledgeGraph.has(type, minConfidence)) {
181
+ satisfiedTypes.push({
182
+ type,
183
+ confidence: knowledgeGraph.getConfidence(type)
184
+ });
185
+ } else {
186
+ missingTypes.push(type);
187
+ }
188
+ });
189
+
190
+ // Can skip if ALL required types are satisfied
191
+ const canSkip = missingTypes.length === 0 && satisfiedTypes.length > 0;
192
+
193
+ let reason = '';
194
+ if (canSkip) {
195
+ const typeLabels = satisfiedTypes.map(t => t.type.replace(/_/g, ' ')).join(', ');
196
+ const avgConfidence = Math.round(
197
+ satisfiedTypes.reduce((sum, t) => sum + t.confidence, 0) / satisfiedTypes.length
198
+ );
199
+ reason = `Already have: ${typeLabels} (${avgConfidence}% confidence)`;
200
+ } else if (satisfiedTypes.length > 0) {
201
+ const partial = satisfiedTypes.map(t => t.type.replace(/_/g, ' ')).join(', ');
202
+ const missing = missingTypes.map(t => t.replace(/_/g, ' ')).join(', ');
203
+ reason = `Partial: have ${partial}, need ${missing}`;
204
+ }
205
+
206
+ return {
207
+ canSkip,
208
+ reason,
209
+ satisfiedTypes,
210
+ missingTypes,
211
+ questionPriority: mapping.priority
212
+ };
213
+ }
214
+
215
+ /**
216
+ * Reorder questions based on knowledge graph
217
+ * Prioritize questions that gather missing information
218
+ *
219
+ * @param {Array} questions - Array of question objects
220
+ * @param {KnowledgeGraph} knowledgeGraph - Knowledge graph instance
221
+ * @returns {Array} - Reordered questions with skip recommendations
222
+ */
223
+ reorderQuestions(questions, knowledgeGraph) {
224
+ const scoredQuestions = questions.map(question => {
225
+ const skipInfo = this.canSkipQuestion(question, knowledgeGraph);
226
+
227
+ // Calculate relevance score
228
+ // Higher score = more important to ask
229
+ let score = 100;
230
+
231
+ if (skipInfo.canSkip) {
232
+ // Can skip entirely
233
+ score = 0;
234
+ } else if (skipInfo.satisfiedTypes.length > 0) {
235
+ // Partially satisfied, lower priority
236
+ const percentSatisfied = skipInfo.satisfiedTypes.length /
237
+ (skipInfo.satisfiedTypes.length + skipInfo.missingTypes.length);
238
+ score = Math.round((1 - percentSatisfied) * 100);
239
+ } else {
240
+ // Not satisfied at all, full priority
241
+ score = 100;
242
+ }
243
+
244
+ // Boost score for high-priority questions
245
+ if (skipInfo.questionPriority === 1) {
246
+ score = Math.min(100, score * 1.3);
247
+ } else if (skipInfo.questionPriority === 2) {
248
+ score = Math.min(100, score * 1.15);
249
+ }
250
+
251
+ return {
252
+ question,
253
+ skipInfo,
254
+ relevanceScore: score
255
+ };
256
+ });
257
+
258
+ // Sort by relevance score (highest first)
259
+ scoredQuestions.sort((a, b) => b.relevanceScore - a.relevanceScore);
260
+
261
+ return scoredQuestions;
262
+ }
263
+
264
+ /**
265
+ * Get statistics about question mappings
266
+ */
267
+ getStats(questions, knowledgeGraph) {
268
+ let canSkip = 0;
269
+ let partial = 0;
270
+ let needed = 0;
271
+
272
+ questions.forEach(question => {
273
+ const skipInfo = this.canSkipQuestion(question, knowledgeGraph);
274
+ if (skipInfo.canSkip) {
275
+ canSkip++;
276
+ } else if (skipInfo.satisfiedTypes.length > 0) {
277
+ partial++;
278
+ } else {
279
+ needed++;
280
+ }
281
+ });
282
+
283
+ return {
284
+ total: questions.length,
285
+ canSkip,
286
+ partial,
287
+ needed,
288
+ estimatedTimeSaved: canSkip * 1.5 // Assume 1.5 min per question
289
+ };
290
+ }
291
+ }
292
+
293
+ module.exports = QuestionMapper;
@@ -15,14 +15,16 @@ const CONFIG_CATEGORIES = {
15
15
  description: 'Configure AI provider (Anthropic, OpenAI, Google Gemini, OpenRouter)',
16
16
  value: 'ai-provider'
17
17
  },
18
+ IDE_DEPLOYMENT: {
19
+ name: 'IDE Deployment',
20
+ description: 'Deploy requirements to IDEs (Windsurf, Cursor, VSCode, etc.)',
21
+ value: 'ide-deployment'
22
+ },
18
23
  LEARNING_SYSTEM: {
19
24
  name: 'Learning System',
20
25
  description: 'Manage interview learning data and preferences',
21
26
  value: 'learning'
22
27
  }
23
- // Future config categories can be added here:
24
- // PROJECT_SETTINGS: { name: 'Project Settings', description: '...', value: 'project' },
25
- // DEPLOYMENT: { name: 'Deployment Preferences', description: '...', value: 'deployment' },
26
28
  };
27
29
 
28
30
  /**
@@ -118,6 +120,7 @@ async function config() {
118
120
 
119
121
  // Check configuration status for all categories
120
122
  const aiStatus = await isAIConfigured(cwd);
123
+ const deploymentStatus = await getDeploymentStatus(cwd);
121
124
  const learningStatus = await getLearningStatus(cwd);
122
125
 
123
126
  // Build choices with status indicators
@@ -127,6 +130,11 @@ async function config() {
127
130
  value: CONFIG_CATEGORIES.AI_PROVIDER.value,
128
131
  short: CONFIG_CATEGORIES.AI_PROVIDER.name
129
132
  },
133
+ {
134
+ name: `${CONFIG_CATEGORIES.IDE_DEPLOYMENT.name} - ${displayDeploymentStatus(deploymentStatus)}`,
135
+ value: CONFIG_CATEGORIES.IDE_DEPLOYMENT.value,
136
+ short: CONFIG_CATEGORIES.IDE_DEPLOYMENT.name
137
+ },
130
138
  {
131
139
  name: `${CONFIG_CATEGORIES.LEARNING_SYSTEM.name} - ${displayLearningStatus(learningStatus)}`,
132
140
  value: CONFIG_CATEGORIES.LEARNING_SYSTEM.value,
@@ -160,11 +168,14 @@ async function config() {
160
168
  await configureAIProviderCategory(cwd, aiStatus);
161
169
  break;
162
170
 
171
+ case 'ide-deployment':
172
+ await configureIDEDeploymentCategory(cwd, deploymentStatus);
173
+ break;
174
+
163
175
  case 'learning':
164
176
  await configureLearningCategory(cwd, learningStatus);
165
177
  break;
166
178
 
167
- // Future categories will be handled here
168
179
  default:
169
180
  console.log(chalk.red('\n❌ Configuration category not implemented yet.\n'));
170
181
  }
@@ -207,6 +218,118 @@ async function configureAIProviderCategory(cwd, aiStatus) {
207
218
  }
208
219
  }
209
220
 
221
+ /**
222
+ * Check deployment status
223
+ */
224
+ async function getDeploymentStatus(projectPath = process.cwd()) {
225
+ const adfDir = path.join(projectPath, '.adf');
226
+
227
+ if (!await fs.pathExists(adfDir)) {
228
+ return { hasSession: false, tools: [] };
229
+ }
230
+
231
+ // Check for latest session
232
+ const sessionsDir = path.join(adfDir, 'sessions');
233
+ if (!await fs.pathExists(sessionsDir)) {
234
+ return { hasSession: false, tools: [] };
235
+ }
236
+
237
+ const sessions = await fs.readdir(sessionsDir);
238
+ if (sessions.length === 0) {
239
+ return { hasSession: false, tools: [] };
240
+ }
241
+
242
+ // Check which IDE tools have been deployed
243
+ const deployedTools = [];
244
+ const ideMarkers = [
245
+ { name: 'Windsurf', path: '.windsurf/rules' },
246
+ { name: 'Cursor', path: '.cursor/rules' },
247
+ { name: 'VSCode', path: '.github/copilot-instructions.md' },
248
+ { name: 'Claude Code', path: '.claude/commands' }
249
+ ];
250
+
251
+ for (const marker of ideMarkers) {
252
+ if (await fs.pathExists(path.join(projectPath, marker.path))) {
253
+ deployedTools.push(marker.name);
254
+ }
255
+ }
256
+
257
+ return {
258
+ hasSession: true,
259
+ sessionCount: sessions.length,
260
+ tools: deployedTools
261
+ };
262
+ }
263
+
264
+ /**
265
+ * Display deployment status
266
+ */
267
+ function displayDeploymentStatus(status) {
268
+ if (status.hasSession && status.tools.length > 0) {
269
+ return `${chalk.green('✓ Deployed')} ${chalk.gray(`(${status.tools.join(', ')})`)}`;
270
+ } else if (status.hasSession) {
271
+ return chalk.yellow('○ Session exists, not deployed');
272
+ } else {
273
+ return chalk.gray('○ No sessions yet');
274
+ }
275
+ }
276
+
277
+ /**
278
+ * Configure IDE Deployment category
279
+ */
280
+ async function configureIDEDeploymentCategory(cwd, deploymentStatus) {
281
+ const { deployToTool } = require('./deploy');
282
+
283
+ console.log(chalk.gray('\n' + '─'.repeat(60) + '\n'));
284
+
285
+ if (!deploymentStatus.hasSession) {
286
+ console.log(chalk.yellow('⚠️ No ADF sessions found.\n'));
287
+ console.log(chalk.gray('You need to run `adf init` first to create requirements.'));
288
+ console.log(chalk.gray('Then you can deploy them to your IDE.\n'));
289
+ return;
290
+ }
291
+
292
+ console.log(chalk.cyan(`Found ${deploymentStatus.sessionCount} session(s)\n`));
293
+
294
+ if (deploymentStatus.tools.length > 0) {
295
+ console.log(chalk.green('✓ Already deployed to:'));
296
+ deploymentStatus.tools.forEach(tool => {
297
+ console.log(chalk.gray(` • ${tool}`));
298
+ });
299
+ console.log('');
300
+ }
301
+
302
+ // Ask which tools to deploy to
303
+ const { tools } = await inquirer.prompt([
304
+ {
305
+ type: 'checkbox',
306
+ name: 'tools',
307
+ message: 'Select IDEs to deploy to (space to select, enter to confirm):',
308
+ choices: [
309
+ { name: 'Windsurf', value: 'windsurf', checked: deploymentStatus.tools.includes('Windsurf') },
310
+ { name: 'Cursor', value: 'cursor', checked: deploymentStatus.tools.includes('Cursor') },
311
+ { name: 'VSCode/Copilot', value: 'vscode', checked: deploymentStatus.tools.includes('VSCode') },
312
+ { name: 'Claude Code', value: 'claude-code', checked: deploymentStatus.tools.includes('Claude Code') },
313
+ { name: 'Gemini CLI', value: 'gemini-cli' }
314
+ ],
315
+ validate: (answer) => {
316
+ if (answer.length === 0) {
317
+ return 'You must choose at least one IDE.';
318
+ }
319
+ return true;
320
+ }
321
+ }
322
+ ]);
323
+
324
+ // Deploy to each selected tool
325
+ for (const tool of tools) {
326
+ console.log('');
327
+ await deployToTool(tool, { silent: false });
328
+ }
329
+
330
+ console.log(chalk.green.bold('\n✅ Deployment complete!\n'));
331
+ }
332
+
210
333
  /**
211
334
  * Configure Learning System category
212
335
  */
@@ -12,6 +12,7 @@ const { SkipTracker } = require('../learning/skip-tracker');
12
12
  const { detectPatterns } = require('../learning/pattern-detector');
13
13
  const { updateLearnedRules, getActiveRules, getRuleExplanations } = require('../learning/rule-generator');
14
14
  const { getLearningConfig } = require('../learning/storage');
15
+ const DynamicPipeline = require('../analysis/dynamic-pipeline');
15
16
 
16
17
  /**
17
18
  * Conversational AI Interviewer
@@ -47,6 +48,7 @@ class Interviewer {
47
48
  this.aiClient = null; // Will be initialized in start()
48
49
  this.skipTracker = null; // Will be initialized in start() with project context
49
50
  this.learnedRules = []; // Will be loaded in start()
51
+ this.dynamicPipeline = null; // Will be initialized in start() with AI client
50
52
  }
51
53
 
52
54
  generateSessionId() {
@@ -125,6 +127,15 @@ class Interviewer {
125
127
  // Allow interview to continue without AI (graceful degradation)
126
128
  }
127
129
 
130
+ // Initialize Dynamic Pipeline (Intelligent Question System)
131
+ this.dynamicPipeline = new DynamicPipeline(this.sessionPath, this.aiClient, {
132
+ enabled: true,
133
+ minSkipConfidence: 75,
134
+ showAnalysis: true,
135
+ verbose: false
136
+ });
137
+ await this.dynamicPipeline.initialize();
138
+
128
139
  // Create session directory
129
140
  await fs.ensureDir(this.sessionPath);
130
141
  await fs.ensureDir(path.join(this.sessionPath, 'qa-responses'));
@@ -318,6 +329,11 @@ class Interviewer {
318
329
 
319
330
  // Save block answers
320
331
  await this.saveBlockAnswers(block);
332
+
333
+ // Show knowledge summary every 2 blocks
334
+ if (this.dynamicPipeline && (i + 1) % 2 === 0 && i + 1 < questionBlocks.length) {
335
+ this.dynamicPipeline.displayKnowledgeSummary();
336
+ }
321
337
  }
322
338
 
323
339
  // Generate framework outputs
@@ -354,6 +370,11 @@ class Interviewer {
354
370
  // Mark session as complete
355
371
  await this.progressTracker.complete();
356
372
 
373
+ // Display Dynamic Pipeline stats
374
+ if (this.dynamicPipeline) {
375
+ this.dynamicPipeline.displayFinalStats();
376
+ }
377
+
357
378
  console.log(chalk.green.bold('\n✨ Requirements gathering complete!\n'));
358
379
  console.log(chalk.cyan(`📁 Session saved to: .adf/sessions/${this.sessionId}/\n`));
359
380
 
@@ -470,9 +491,38 @@ class Interviewer {
470
491
  async askBlockQuestions(block, currentBlock, totalBlocks) {
471
492
  const blockAnswers = {};
472
493
  let questionsAnswered = 0;
494
+ let questionsSkipped = 0;
473
495
 
474
496
  for (let i = 0; i < block.questions.length; i++) {
475
497
  const question = block.questions[i];
498
+
499
+ // Check if we should skip this question based on knowledge graph
500
+ if (this.dynamicPipeline) {
501
+ const skipCheck = this.dynamicPipeline.shouldSkipQuestion(question);
502
+
503
+ if (skipCheck.shouldSkip) {
504
+ console.log(chalk.cyan(`Question ${i + 1}/${block.questions.length}`) + chalk.gray(` (Block ${currentBlock}/${totalBlocks})`) + '\n');
505
+ console.log(chalk.gray('━'.repeat(60)));
506
+ console.log(chalk.yellow(`\n⏭️ Skipping: ${question.text}`));
507
+ console.log(chalk.green(` ✓ ${skipCheck.reason}\n`));
508
+ console.log(chalk.gray('━'.repeat(60)) + '\n');
509
+
510
+ questionsSkipped++;
511
+
512
+ // Log skip to transcript
513
+ this.transcript.push({
514
+ type: 'question-skipped-intelligent',
515
+ question: question.text,
516
+ questionId: question.id,
517
+ reason: skipCheck.reason,
518
+ confidence: skipCheck.confidence,
519
+ timestamp: new Date().toISOString()
520
+ });
521
+
522
+ continue;
523
+ }
524
+ }
525
+
476
526
  const answer = await this.askQuestion(question, i + 1, block.questions.length, currentBlock, totalBlocks);
477
527
 
478
528
  if (answer === null) {
@@ -494,6 +544,10 @@ class Interviewer {
494
544
  });
495
545
  }
496
546
 
547
+ if (questionsSkipped > 0 && questionsAnswered > 0) {
548
+ console.log(chalk.cyan(`\n📊 Block Summary: ${questionsAnswered} answered, ${questionsSkipped} intelligently skipped\n`));
549
+ }
550
+
497
551
  return questionsAnswered;
498
552
  }
499
553
 
@@ -583,6 +637,11 @@ class Interviewer {
583
637
  });
584
638
  }
585
639
 
640
+ // Process answer with Dynamic Pipeline (Phase 4.4)
641
+ if (this.dynamicPipeline) {
642
+ await this.dynamicPipeline.processAnswer(question.id, question.text, answer);
643
+ }
644
+
586
645
  // Check if answer is comprehensive enough to skip follow-ups
587
646
  if (qualityMetrics.canSkipFollowUps) {
588
647
  console.log(chalk.green('\n✓ Saved\n'));
@@ -42,7 +42,7 @@ class CursorGenerator extends ToolConfigGenerator {
42
42
  await this.ensureDir('.cursor');
43
43
 
44
44
  const content = this.framework === 'rapid' ?
45
- this.generateRulesPRP() :
45
+ await this.generateRulesPRP() :
46
46
  this.framework === 'balanced' ?
47
47
  this.generateRulesBalanced() :
48
48
  this.generateRulesBMAD();
@@ -50,9 +50,27 @@ class CursorGenerator extends ToolConfigGenerator {
50
50
  return await this.writeToProject('.cursor/rules', content);
51
51
  }
52
52
 
53
- generateRulesPRP() {
53
+ async generateRulesPRP() {
54
54
  const projectName = this.getProjectName();
55
55
  const sections = this.outputs.sections || {};
56
+ const answers = await this.loadSessionAnswers();
57
+
58
+ // Extract content with fallback to answers
59
+ let goal = sections['1._goal_definition'] || sections['goal_definition'];
60
+ let techStack = this.extractTechStack(sections);
61
+ let blueprint = sections['4._implementation_blueprint'] || sections['implementation_blueprint'];
62
+ let validation = sections['5._validation'] || sections['validation'];
63
+
64
+ // Fallback to extracted answers if sections are empty
65
+ if (!goal || goal.length < 20) {
66
+ const whatBuilding = this.extractWhatBuildingFromAnswers(answers);
67
+ if (whatBuilding) goal = whatBuilding;
68
+ }
69
+
70
+ if (!techStack || techStack.includes('See framework')) {
71
+ const techFromAnswers = this.extractTechStackFromAnswers(answers);
72
+ if (techFromAnswers) techStack = techFromAnswers;
73
+ }
56
74
 
57
75
  return `# ${projectName} - Cursor Rules
58
76
 
@@ -60,19 +78,19 @@ You are a senior developer working on ${projectName}.
60
78
 
61
79
  ## Project Goal
62
80
 
63
- ${sections['1._goal_definition'] || sections['goal_definition'] || 'See PRP for details'}
81
+ ${goal || 'See PRP for complete details'}
64
82
 
65
83
  ## Tech Stack
66
84
 
67
- ${this.extractTechStack(sections)}
85
+ ${techStack || 'See PRP for tech stack details'}
68
86
 
69
87
  ## Implementation Blueprint
70
88
 
71
- ${sections['4._implementation_blueprint'] || sections['implementation_blueprint'] || 'See PRP for details'}
89
+ ${blueprint || 'See PRP for implementation details'}
72
90
 
73
91
  ## Success Criteria
74
92
 
75
- ${sections['5._validation'] || sections['validation'] || 'See PRP for validation criteria'}
93
+ ${validation || 'See PRP for validation criteria'}
76
94
 
77
95
  ## Before Implementing Features
78
96
 
@@ -369,6 +387,59 @@ You can delete this .cursorrules file.
369
387
  return '0.3.0';
370
388
  }
371
389
  }
390
+
391
+ /**
392
+ * Load session answers from _progress.json
393
+ */
394
+ async loadSessionAnswers() {
395
+ const fs = require('fs-extra');
396
+ const path = require('path');
397
+ const progressPath = path.join(this.sessionPath, '_progress.json');
398
+
399
+ try {
400
+ if (await fs.pathExists(progressPath)) {
401
+ const progress = await fs.readJson(progressPath);
402
+ return progress.answers || {};
403
+ }
404
+ } catch (error) {
405
+ // Fall back to empty if can't load
406
+ }
407
+
408
+ return {};
409
+ }
410
+
411
+ /**
412
+ * Extract tech stack from any answer
413
+ */
414
+ extractTechStackFromAnswers(answers) {
415
+ for (const [questionId, answer] of Object.entries(answers)) {
416
+ if (typeof answer === 'string') {
417
+ const lower = answer.toLowerCase();
418
+ if (lower.includes('react') || lower.includes('vue') || lower.includes('angular') ||
419
+ lower.includes('node') || lower.includes('python') || lower.includes('next') ||
420
+ lower.includes('postgres') || lower.includes('mongo') || lower.includes('mysql')) {
421
+ return answer;
422
+ }
423
+ }
424
+ }
425
+ return null;
426
+ }
427
+
428
+ /**
429
+ * Extract "what are you building" from any answer
430
+ */
431
+ extractWhatBuildingFromAnswers(answers) {
432
+ for (const [questionId, answer] of Object.entries(answers)) {
433
+ if (questionId.toLowerCase().includes('goal') ||
434
+ questionId.toLowerCase().includes('building') ||
435
+ questionId.toLowerCase().includes('project')) {
436
+ if (typeof answer === 'string' && answer.length > 20) {
437
+ return answer;
438
+ }
439
+ }
440
+ }
441
+ return null;
442
+ }
372
443
  }
373
444
 
374
445
  module.exports = CursorGenerator;