@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.
- package/.project/chats/current/2025-10-05_INTELLIGENT-ANSWER-ANALYSIS.md +649 -0
- package/.project/chats/current/2025-10-05_MULTI-IDE-IMPROVEMENTS.md +415 -0
- package/.project/chats/current/SESSION-STATUS.md +166 -71
- package/.project/docs/ROADMAP.md +150 -17
- package/CHANGELOG.md +243 -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/init.js +137 -8
- package/lib/frameworks/interviewer.js +59 -0
- 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,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;
|