@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,262 @@
1
+ const chalk = require('chalk');
2
+ const AnswerAnalyzer = require('./answer-analyzer');
3
+ const KnowledgeGraph = require('./knowledge-graph');
4
+ const QuestionMapper = require('./question-mapper');
5
+
6
+ /**
7
+ * Dynamic Question Pipeline Manager
8
+ *
9
+ * Orchestrates the intelligent question system:
10
+ * 1. Analyzes each answer for multiple information types
11
+ * 2. Builds knowledge graph of what we know
12
+ * 3. Dynamically skips/reorders questions based on knowledge
13
+ * 4. Reduces user pain by avoiding redundant questions
14
+ *
15
+ * Integration point for the interviewer.
16
+ */
17
+ class DynamicPipeline {
18
+ constructor(sessionPath, aiClient, options = {}) {
19
+ this.sessionPath = sessionPath;
20
+ this.aiClient = aiClient;
21
+ this.options = {
22
+ enabled: options.enabled !== false, // Enabled by default
23
+ minSkipConfidence: options.minSkipConfidence || 75,
24
+ showAnalysis: options.showAnalysis !== false,
25
+ verbose: options.verbose || false
26
+ };
27
+
28
+ // Initialize components
29
+ this.analyzer = new AnswerAnalyzer(aiClient);
30
+ this.knowledgeGraph = new KnowledgeGraph(sessionPath);
31
+ this.mapper = new QuestionMapper();
32
+
33
+ // Stats
34
+ this.stats = {
35
+ questionsAsked: 0,
36
+ questionsSkipped: 0,
37
+ informationExtracted: 0,
38
+ timeSaved: 0
39
+ };
40
+ }
41
+
42
+ /**
43
+ * Initialize the pipeline (load existing knowledge graph if resuming)
44
+ */
45
+ async initialize() {
46
+ const loaded = await this.knowledgeGraph.load();
47
+
48
+ if (loaded && this.options.verbose) {
49
+ const stats = this.knowledgeGraph.getStats();
50
+ console.log(chalk.cyan(`\n📊 Resuming with ${stats.totalItems} pieces of knowledge\n`));
51
+ }
52
+
53
+ return loaded;
54
+ }
55
+
56
+ /**
57
+ * Process an answer after the user provides it
58
+ * Extracts information and updates knowledge graph
59
+ *
60
+ * @param {string} questionId - Question ID
61
+ * @param {string} questionText - The question text
62
+ * @param {string} answer - User's answer
63
+ * @returns {Promise<Object>} - { extracted: [], summary: [] }
64
+ */
65
+ async processAnswer(questionId, questionText, answer) {
66
+ if (!this.options.enabled) {
67
+ return { extracted: [], summary: [] };
68
+ }
69
+
70
+ try {
71
+ // Analyze answer for multiple information types
72
+ const extracted = await this.analyzer.analyzeAnswer(questionText, answer, questionId);
73
+
74
+ // Update knowledge graph
75
+ if (extracted.length > 0) {
76
+ this.knowledgeGraph.add(extracted);
77
+ this.stats.informationExtracted += extracted.length;
78
+
79
+ // Save knowledge graph
80
+ await this.knowledgeGraph.save();
81
+
82
+ // Get summary for display
83
+ const summary = this.analyzer.getSummary(extracted);
84
+
85
+ if (this.options.showAnalysis && extracted.length > 0) {
86
+ console.log(chalk.gray(`\n 📚 Learned: ${summary.slice(0, 2).join(', ')}`));
87
+ }
88
+
89
+ return { extracted, summary };
90
+ }
91
+
92
+ return { extracted: [], summary: [] };
93
+ } catch (error) {
94
+ // Don't fail the interview if analysis fails
95
+ if (this.options.verbose) {
96
+ console.log(chalk.yellow(`⚠️ Analysis failed: ${error.message}`));
97
+ }
98
+ return { extracted: [], summary: [] };
99
+ }
100
+ }
101
+
102
+ /**
103
+ * Check if a question should be skipped based on knowledge graph
104
+ *
105
+ * @param {Object} question - Question object
106
+ * @returns {Object} - { shouldSkip: boolean, reason: string, confidence: number }
107
+ */
108
+ shouldSkipQuestion(question) {
109
+ if (!this.options.enabled) {
110
+ return { shouldSkip: false, reason: '', confidence: 0 };
111
+ }
112
+
113
+ const skipInfo = this.mapper.canSkipQuestion(
114
+ question,
115
+ this.knowledgeGraph,
116
+ this.options.minSkipConfidence
117
+ );
118
+
119
+ if (skipInfo.canSkip) {
120
+ this.stats.questionsSkipped++;
121
+ this.stats.timeSaved += 1.5; // Assume 1.5 min per question
122
+ } else {
123
+ this.stats.questionsAsked++;
124
+ }
125
+
126
+ return {
127
+ shouldSkip: skipInfo.canSkip,
128
+ reason: skipInfo.reason,
129
+ confidence: skipInfo.satisfiedTypes.length > 0
130
+ ? Math.round(
131
+ skipInfo.satisfiedTypes.reduce((sum, t) => sum + t.confidence, 0) /
132
+ skipInfo.satisfiedTypes.length
133
+ )
134
+ : 0,
135
+ partial: skipInfo.satisfiedTypes.length > 0 && !skipInfo.canSkip
136
+ };
137
+ }
138
+
139
+ /**
140
+ * Reorder remaining questions based on knowledge graph
141
+ *
142
+ * @param {Array} questions - Array of question objects
143
+ * @returns {Array} - Reordered questions with metadata
144
+ */
145
+ reorderQuestions(questions) {
146
+ if (!this.options.enabled) {
147
+ return questions.map(q => ({ question: q, relevanceScore: 100, skipInfo: null }));
148
+ }
149
+
150
+ return this.mapper.reorderQuestions(questions, this.knowledgeGraph);
151
+ }
152
+
153
+ /**
154
+ * Get filtering summary before interview starts
155
+ *
156
+ * @param {Array} questions - All questions
157
+ * @returns {Object} - Summary statistics
158
+ */
159
+ getFilteringSummary(questions) {
160
+ if (!this.options.enabled) {
161
+ return {
162
+ total: questions.length,
163
+ canSkip: 0,
164
+ partial: 0,
165
+ needed: questions.length,
166
+ estimatedTimeSaved: 0
167
+ };
168
+ }
169
+
170
+ return this.mapper.getStats(questions, this.knowledgeGraph);
171
+ }
172
+
173
+ /**
174
+ * Get current knowledge summary for display
175
+ *
176
+ * @returns {Array} - Array of knowledge items with icons and confidence
177
+ */
178
+ getKnowledgeSummary() {
179
+ return this.knowledgeGraph.getDisplaySummary();
180
+ }
181
+
182
+ /**
183
+ * Show knowledge summary to user
184
+ */
185
+ displayKnowledgeSummary() {
186
+ const summary = this.getKnowledgeSummary();
187
+
188
+ if (summary.length > 0) {
189
+ console.log(chalk.cyan('\n📚 What I\'ve learned so far:\n'));
190
+
191
+ summary.slice(0, 5).forEach(item => {
192
+ const confidenceColor = item.confidence >= 85 ? chalk.green :
193
+ item.confidence >= 70 ? chalk.cyan :
194
+ chalk.yellow;
195
+
196
+ console.log(
197
+ ` ${item.icon} ${chalk.white(item.type)}: ` +
198
+ confidenceColor(`${item.confidence}% confidence`)
199
+ );
200
+ });
201
+
202
+ if (summary.length > 5) {
203
+ console.log(chalk.gray(` ...and ${summary.length - 5} more\n`));
204
+ } else {
205
+ console.log('');
206
+ }
207
+ }
208
+ }
209
+
210
+ /**
211
+ * Get final statistics
212
+ */
213
+ getStats() {
214
+ const knowledgeStats = this.knowledgeGraph.getStats();
215
+
216
+ return {
217
+ ...this.stats,
218
+ knowledgeItems: knowledgeStats.totalItems,
219
+ highConfidenceItems: knowledgeStats.highConfidenceItems,
220
+ informationTypes: knowledgeStats.types
221
+ };
222
+ }
223
+
224
+ /**
225
+ * Display final statistics
226
+ */
227
+ displayFinalStats() {
228
+ if (!this.options.enabled) return;
229
+
230
+ const stats = this.getStats();
231
+
232
+ console.log(chalk.cyan('\n📊 Intelligent Question System Stats:\n'));
233
+ console.log(chalk.white(` Questions asked: ${stats.questionsAsked}`));
234
+ console.log(chalk.green(` Questions skipped: ${stats.questionsSkipped}`));
235
+ console.log(chalk.blue(` Information pieces extracted: ${stats.informationExtracted}`));
236
+ console.log(chalk.magenta(` High-confidence knowledge: ${stats.highConfidenceItems}`));
237
+ console.log(chalk.yellow(` Estimated time saved: ~${Math.round(stats.timeSaved)} minutes\n`));
238
+ }
239
+
240
+ /**
241
+ * Save final state
242
+ */
243
+ async save() {
244
+ await this.knowledgeGraph.save();
245
+ }
246
+
247
+ /**
248
+ * Enable/disable the pipeline
249
+ */
250
+ setEnabled(enabled) {
251
+ this.options.enabled = enabled;
252
+ }
253
+
254
+ /**
255
+ * Check if enabled
256
+ */
257
+ isEnabled() {
258
+ return this.options.enabled;
259
+ }
260
+ }
261
+
262
+ module.exports = DynamicPipeline;
@@ -0,0 +1,227 @@
1
+ const fs = require('fs-extra');
2
+ const path = require('path');
3
+ const chalk = require('chalk');
4
+
5
+ /**
6
+ * Knowledge Graph
7
+ *
8
+ * Tracks all information extracted from user answers.
9
+ * Maintains confidence scores and sources for each piece of information.
10
+ * Used to determine which questions can be skipped or reordered.
11
+ */
12
+ class KnowledgeGraph {
13
+ constructor(sessionPath) {
14
+ this.sessionPath = sessionPath;
15
+ this.knowledge = new Map(); // type -> array of knowledge items
16
+ this.filePath = path.join(sessionPath, '_knowledge_graph.json');
17
+ }
18
+
19
+ /**
20
+ * Add extracted information to the knowledge graph
21
+ */
22
+ add(extractedInfo) {
23
+ extractedInfo.forEach(item => {
24
+ if (!this.knowledge.has(item.type)) {
25
+ this.knowledge.set(item.type, []);
26
+ }
27
+
28
+ // Check if we already have similar information
29
+ const existing = this.knowledge.get(item.type);
30
+ const similar = existing.find(e =>
31
+ this.calculateSimilarity(e.content, item.content) > 0.7
32
+ );
33
+
34
+ if (similar) {
35
+ // Update if new confidence is higher
36
+ if (item.confidence > similar.confidence) {
37
+ similar.confidence = item.confidence;
38
+ similar.content = item.content;
39
+ similar.sources = [...new Set([...similar.sources, item.source])];
40
+ } else {
41
+ // Just add source
42
+ similar.sources = [...new Set([...similar.sources, item.source])];
43
+ }
44
+ } else {
45
+ // New information
46
+ existing.push({
47
+ ...item,
48
+ sources: [item.source],
49
+ addedAt: new Date().toISOString()
50
+ });
51
+ }
52
+ });
53
+ }
54
+
55
+ /**
56
+ * Check if we have information of a specific type with sufficient confidence
57
+ */
58
+ has(informationType, minConfidence = 70) {
59
+ if (!this.knowledge.has(informationType)) {
60
+ return false;
61
+ }
62
+
63
+ const items = this.knowledge.get(informationType);
64
+ return items.some(item => item.confidence >= minConfidence);
65
+ }
66
+
67
+ /**
68
+ * Get all information of a specific type
69
+ */
70
+ get(informationType) {
71
+ return this.knowledge.get(informationType) || [];
72
+ }
73
+
74
+ /**
75
+ * Get the highest confidence score for a type
76
+ */
77
+ getConfidence(informationType) {
78
+ const items = this.get(informationType);
79
+ if (items.length === 0) return 0;
80
+
81
+ return Math.max(...items.map(item => item.confidence));
82
+ }
83
+
84
+ /**
85
+ * Get all information as a summary object
86
+ */
87
+ getSummary() {
88
+ const summary = {};
89
+
90
+ for (const [type, items] of this.knowledge.entries()) {
91
+ summary[type] = {
92
+ count: items.length,
93
+ maxConfidence: Math.max(...items.map(i => i.confidence)),
94
+ sources: [...new Set(items.flatMap(i => i.sources))]
95
+ };
96
+ }
97
+
98
+ return summary;
99
+ }
100
+
101
+ /**
102
+ * Get information to display to user
103
+ */
104
+ getDisplaySummary() {
105
+ const lines = [];
106
+
107
+ for (const [type, items] of this.knowledge.entries()) {
108
+ const maxConfidence = Math.max(...items.map(i => i.confidence));
109
+ if (maxConfidence >= 60) { // Only show reasonably confident items
110
+ const typeLabel = type.replace(/_/g, ' ');
111
+ const icon = this.getIconForType(type);
112
+ lines.push({
113
+ type: typeLabel,
114
+ icon,
115
+ confidence: maxConfidence,
116
+ count: items.length
117
+ });
118
+ }
119
+ }
120
+
121
+ // Sort by confidence
122
+ lines.sort((a, b) => b.confidence - a.confidence);
123
+
124
+ return lines;
125
+ }
126
+
127
+ /**
128
+ * Get icon for information type
129
+ */
130
+ getIconForType(type) {
131
+ const icons = {
132
+ tech_stack: '🔧',
133
+ architecture: '🏗️',
134
+ project_goal: '🎯',
135
+ target_users: '👥',
136
+ features: '✨',
137
+ platform: '💻',
138
+ constraints: '⚠️',
139
+ timeline: '⏰',
140
+ team_size: '👨‍👩‍👧‍👦',
141
+ deployment: '🚀',
142
+ security: '🔒',
143
+ performance: '⚡'
144
+ };
145
+ return icons[type] || '📌';
146
+ }
147
+
148
+ /**
149
+ * Calculate text similarity (simple Jaccard similarity)
150
+ */
151
+ calculateSimilarity(text1, text2) {
152
+ const words1 = new Set(text1.toLowerCase().split(/\s+/));
153
+ const words2 = new Set(text2.toLowerCase().split(/\s+/));
154
+
155
+ const intersection = new Set([...words1].filter(x => words2.has(x)));
156
+ const union = new Set([...words1, ...words2]);
157
+
158
+ return intersection.size / union.size;
159
+ }
160
+
161
+ /**
162
+ * Save knowledge graph to disk
163
+ */
164
+ async save() {
165
+ const data = {
166
+ savedAt: new Date().toISOString(),
167
+ knowledge: Array.from(this.knowledge.entries()).map(([type, items]) => ({
168
+ type,
169
+ items
170
+ }))
171
+ };
172
+
173
+ await fs.writeJson(this.filePath, data, { spaces: 2 });
174
+ }
175
+
176
+ /**
177
+ * Load knowledge graph from disk
178
+ */
179
+ async load() {
180
+ try {
181
+ if (await fs.pathExists(this.filePath)) {
182
+ const data = await fs.readJson(this.filePath);
183
+
184
+ this.knowledge = new Map(
185
+ data.knowledge.map(({ type, items }) => [type, items])
186
+ );
187
+
188
+ return true;
189
+ }
190
+ } catch (error) {
191
+ // Failed to load, start fresh
192
+ }
193
+
194
+ return false;
195
+ }
196
+
197
+ /**
198
+ * Get statistics about the knowledge graph
199
+ */
200
+ getStats() {
201
+ let totalItems = 0;
202
+ let highConfidenceItems = 0;
203
+ const types = [];
204
+
205
+ for (const [type, items] of this.knowledge.entries()) {
206
+ totalItems += items.length;
207
+ highConfidenceItems += items.filter(i => i.confidence >= 80).length;
208
+ types.push(type);
209
+ }
210
+
211
+ return {
212
+ totalItems,
213
+ highConfidenceItems,
214
+ types: types.length,
215
+ typeList: types
216
+ };
217
+ }
218
+
219
+ /**
220
+ * Clear all knowledge
221
+ */
222
+ clear() {
223
+ this.knowledge.clear();
224
+ }
225
+ }
226
+
227
+ module.exports = KnowledgeGraph;