@iservu-inc/adf-cli 0.4.36 → 0.5.1

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;
@@ -67,21 +67,108 @@ async function init(options) {
67
67
  const hasContent = await hasMeaningfulContent(adfDir);
68
68
 
69
69
  if (hasContent) {
70
- const { overwrite } = await inquirer.prompt([
70
+ // Show what exists
71
+ const existingContent = await getExistingContent(adfDir);
72
+ console.log(chalk.cyan('\n📦 Existing ADF Project Detected\n'));
73
+
74
+ if (existingContent.sessions > 0) {
75
+ console.log(chalk.gray(` Sessions: ${existingContent.sessions} session(s)`));
76
+ }
77
+ if (existingContent.outputs > 0) {
78
+ console.log(chalk.gray(` Outputs: ${existingContent.outputs} file(s)`));
79
+ }
80
+ if (existingContent.learning) {
81
+ console.log(chalk.gray(` Learning data: Present`));
82
+ }
83
+ console.log('');
84
+
85
+ const { action } = await inquirer.prompt([
71
86
  {
72
- type: 'confirm',
73
- name: 'overwrite',
74
- message: chalk.yellow('.adf directory already exists with sessions. Overwrite?'),
75
- default: false
87
+ type: 'list',
88
+ name: 'action',
89
+ message: 'What would you like to do?',
90
+ choices: [
91
+ {
92
+ name: 'Continue with Existing Project',
93
+ value: 'continue',
94
+ short: 'Continue'
95
+ },
96
+ {
97
+ name: 'Reset this Project (delete all data)',
98
+ value: 'reset',
99
+ short: 'Reset'
100
+ },
101
+ new inquirer.Separator(),
102
+ {
103
+ name: chalk.gray('← Don\'t change & Exit'),
104
+ value: 'exit',
105
+ short: 'Exit'
106
+ }
107
+ ],
108
+ default: 'continue'
76
109
  }
77
110
  ]);
78
111
 
79
- if (!overwrite) {
80
- console.log(chalk.yellow('\n Initialization cancelled.\n'));
112
+ if (action === 'exit') {
113
+ console.log(chalk.gray('\n Exited without changes.\n'));
81
114
  return;
82
115
  }
83
116
 
84
- await fs.remove(adfDir);
117
+ if (action === 'reset') {
118
+ // Confirm deletion
119
+ const { confirmReset } = await inquirer.prompt([
120
+ {
121
+ type: 'confirm',
122
+ name: 'confirmReset',
123
+ message: chalk.red('⚠️ This will permanently delete all sessions and data. Continue?'),
124
+ default: false
125
+ }
126
+ ]);
127
+
128
+ if (!confirmReset) {
129
+ console.log(chalk.gray('\n← Reset cancelled. Exited without changes.\n'));
130
+ return;
131
+ }
132
+
133
+ await fs.remove(adfDir);
134
+ console.log(chalk.yellow('\n✓ Project reset. Starting fresh...\n'));
135
+ } else if (action === 'continue') {
136
+ // Continue with existing project - show sessions and prompt to resume
137
+ console.log(chalk.green('\n✓ Continuing with existing project...\n'));
138
+
139
+ // The SessionManager.promptToResume() was already called above (line 29)
140
+ // But it returned null (no resumable sessions), so let them see what exists
141
+ // and choose to start a new session or view existing outputs
142
+
143
+ const { continueAction } = await inquirer.prompt([
144
+ {
145
+ type: 'list',
146
+ name: 'continueAction',
147
+ message: 'What would you like to do?',
148
+ choices: [
149
+ {
150
+ name: 'Start a new session (keeps existing data)',
151
+ value: 'new-session',
152
+ short: 'New Session'
153
+ },
154
+ {
155
+ name: chalk.gray('← Exit'),
156
+ value: 'exit',
157
+ short: 'Exit'
158
+ }
159
+ ],
160
+ default: 'new-session'
161
+ }
162
+ ]);
163
+
164
+ if (continueAction === 'exit') {
165
+ console.log(chalk.gray('\n← Exited.\n'));
166
+ return;
167
+ }
168
+
169
+ // Continue to start a new session below (fall through)
170
+ console.log('');
171
+ }
85
172
  } else {
86
173
  // Only .env file exists - safe to continue without prompting
87
174
  console.log(chalk.gray('✓ Using existing .adf directory\n'));
@@ -218,4 +305,46 @@ async function hasMeaningfulContent(adfDir) {
218
305
  }
219
306
  }
220
307
 
308
+ /**
309
+ * Get detailed information about existing ADF content
310
+ * Returns object with counts and details
311
+ */
312
+ async function getExistingContent(adfDir) {
313
+ const result = {
314
+ sessions: 0,
315
+ outputs: 0,
316
+ learning: false,
317
+ details: []
318
+ };
319
+
320
+ try {
321
+ const contents = await fs.readdir(adfDir);
322
+
323
+ for (const item of contents) {
324
+ const itemPath = path.join(adfDir, item);
325
+ const stats = await fs.stat(itemPath);
326
+
327
+ if (stats.isDirectory()) {
328
+ if (item === 'sessions') {
329
+ // Count session directories
330
+ const sessions = await fs.readdir(itemPath);
331
+ result.sessions = sessions.filter(s => !s.startsWith('.')).length;
332
+ } else if (item === 'outputs') {
333
+ // Count output files
334
+ const outputs = await fs.readdir(itemPath);
335
+ result.outputs = outputs.filter(o => !o.startsWith('.')).length;
336
+ } else if (item === 'learning') {
337
+ // Check for learning data
338
+ const learningFiles = await fs.readdir(itemPath);
339
+ result.learning = learningFiles.length > 0;
340
+ }
341
+ }
342
+ }
343
+
344
+ return result;
345
+ } catch (error) {
346
+ return result;
347
+ }
348
+ }
349
+
221
350
  module.exports = init;
@@ -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'));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iservu-inc/adf-cli",
3
- "version": "0.4.36",
3
+ "version": "0.5.1",
4
4
  "description": "CLI tool for AgentDevFramework - AI-assisted development framework with multi-provider AI support",
5
5
  "main": "index.js",
6
6
  "bin": {