@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.
- package/.project/chats/current/2025-10-05_MULTI-IDE-IMPROVEMENTS.md +415 -0
- package/.project/docs/ROADMAP.md +150 -17
- package/CHANGELOG.md +220 -0
- package/lib/analysis/answer-analyzer.js +304 -0
- package/lib/analysis/dynamic-pipeline.js +262 -0
- package/lib/analysis/knowledge-graph.js +227 -0
- package/lib/analysis/question-mapper.js +293 -0
- package/lib/commands/config.js +127 -4
- package/lib/frameworks/interviewer.js +59 -0
- package/lib/generators/cursor-generator.js +77 -6
- package/lib/generators/vscode-generator.js +75 -6
- package/package.json +1 -1
- package/tests/answer-analyzer.test.js +262 -0
- package/tests/dynamic-pipeline.test.js +332 -0
- package/tests/knowledge-graph.test.js +322 -0
- package/tests/question-mapper.test.js +342 -0
- /package/.project/chats/{current → complete}/2025-10-04_CRITICAL-MODEL-FETCHING-BUG.md +0 -0
- /package/.project/chats/{current → complete}/2025-10-04_PHASE-4-2-COMPLETION-AND-ROADMAP.md +0 -0
- /package/.project/chats/{current → complete}/2025-10-04_PHASE-4-2-LEARNING-SYSTEM.md +0 -0
|
@@ -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;
|
package/lib/commands/config.js
CHANGED
|
@@ -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
|
-
${
|
|
81
|
+
${goal || 'See PRP for complete details'}
|
|
64
82
|
|
|
65
83
|
## Tech Stack
|
|
66
84
|
|
|
67
|
-
${
|
|
85
|
+
${techStack || 'See PRP for tech stack details'}
|
|
68
86
|
|
|
69
87
|
## Implementation Blueprint
|
|
70
88
|
|
|
71
|
-
${
|
|
89
|
+
${blueprint || 'See PRP for implementation details'}
|
|
72
90
|
|
|
73
91
|
## Success Criteria
|
|
74
92
|
|
|
75
|
-
${
|
|
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;
|