@iservu-inc/adf-cli 0.3.6 → 0.4.12
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 → complete}/2025-10-03_AI-PROVIDER-INTEGRATION.md +34 -35
- package/.project/chats/complete/2025-10-04_CONFIG-COMMAND.md +503 -0
- package/.project/chats/current/2025-10-04_PHASE-4-1-SMART-FILTERING.md +381 -0
- package/.project/chats/current/SESSION-STATUS.md +68 -27
- package/.project/docs/PHASE-4-2-LEARNING-SYSTEM.md +881 -0
- package/.project/docs/SMART-FILTERING-SYSTEM.md +385 -0
- package/.project/docs/goals/PROJECT-VISION.md +32 -10
- package/CHANGELOG.md +109 -1
- package/README.md +476 -381
- package/lib/analyzers/project-analyzer.js +380 -0
- package/lib/commands/config.js +68 -1
- package/lib/filters/question-filter.js +480 -0
- package/lib/frameworks/interviewer.js +184 -3
- package/lib/learning/learning-manager.js +447 -0
- package/lib/learning/pattern-detector.js +376 -0
- package/lib/learning/rule-generator.js +304 -0
- package/lib/learning/skip-tracker.js +260 -0
- package/lib/learning/storage.js +296 -0
- package/package.json +70 -69
- package/tests/learning-storage.test.js +184 -0
- package/tests/pattern-detector.test.js +297 -0
- package/tests/project-analyzer.test.js +221 -0
- package/tests/question-filter.test.js +297 -0
- package/tests/skip-tracker.test.js +198 -0
- /package/.project/chats/{current → complete}/2025-10-03_FRAMEWORK-UPDATE-SYSTEM.md +0 -0
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
const { v4: uuidv4 } = require('uuid');
|
|
2
|
+
const storage = require('./storage');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Skip Tracker - Records user skip and answer behavior
|
|
6
|
+
*
|
|
7
|
+
* Tracks:
|
|
8
|
+
* - Questions skipped manually
|
|
9
|
+
* - Questions skipped by filtering
|
|
10
|
+
* - Answer metadata (length, quality scores)
|
|
11
|
+
* - Time spent on questions
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
class SkipTracker {
|
|
15
|
+
constructor(projectPath, sessionMetadata = {}) {
|
|
16
|
+
this.projectPath = projectPath;
|
|
17
|
+
this.sessionId = uuidv4();
|
|
18
|
+
this.sessionMetadata = sessionMetadata;
|
|
19
|
+
this.sessionData = {
|
|
20
|
+
sessionId: this.sessionId,
|
|
21
|
+
timestamp: new Date().toISOString(),
|
|
22
|
+
projectType: sessionMetadata.projectType || 'unknown',
|
|
23
|
+
frameworks: sessionMetadata.frameworks || [],
|
|
24
|
+
languages: sessionMetadata.languages || [],
|
|
25
|
+
skips: [],
|
|
26
|
+
answers: []
|
|
27
|
+
};
|
|
28
|
+
this.questionStartTimes = {};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Record when a question is shown to the user
|
|
33
|
+
* @param {string} questionId - Question identifier
|
|
34
|
+
*/
|
|
35
|
+
startQuestion(questionId) {
|
|
36
|
+
this.questionStartTimes[questionId] = Date.now();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Get time spent on a question in seconds
|
|
41
|
+
* @param {string} questionId - Question identifier
|
|
42
|
+
* @returns {number} Time in seconds
|
|
43
|
+
*/
|
|
44
|
+
getTimeSpent(questionId) {
|
|
45
|
+
if (!this.questionStartTimes[questionId]) {
|
|
46
|
+
return 0;
|
|
47
|
+
}
|
|
48
|
+
const elapsed = Date.now() - this.questionStartTimes[questionId];
|
|
49
|
+
return Math.round(elapsed / 100) / 10; // Round to 1 decimal
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Record a skip event
|
|
54
|
+
* @param {Object} question - Question object
|
|
55
|
+
* @param {string} reason - 'manual' | 'filtered'
|
|
56
|
+
* @param {Object} metadata - Additional metadata
|
|
57
|
+
*/
|
|
58
|
+
recordSkip(question, reason = 'manual', metadata = {}) {
|
|
59
|
+
const skipEvent = {
|
|
60
|
+
questionId: question.id,
|
|
61
|
+
text: question.text,
|
|
62
|
+
category: question.category || null,
|
|
63
|
+
phase: question.phase || null,
|
|
64
|
+
action: 'skipped',
|
|
65
|
+
reason,
|
|
66
|
+
timeViewed: this.getTimeSpent(question.id),
|
|
67
|
+
relevanceScore: question.relevance || null,
|
|
68
|
+
skipReason: question.skipReason || null,
|
|
69
|
+
...metadata
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
this.sessionData.skips.push(skipEvent);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Record an answer event
|
|
77
|
+
* @param {Object} question - Question object
|
|
78
|
+
* @param {string} answer - User's answer
|
|
79
|
+
* @param {Object} metadata - Additional metadata (quality scores, etc.)
|
|
80
|
+
*/
|
|
81
|
+
recordAnswer(question, answer, metadata = {}) {
|
|
82
|
+
const answerEvent = {
|
|
83
|
+
questionId: question.id,
|
|
84
|
+
text: question.text,
|
|
85
|
+
category: question.category || null,
|
|
86
|
+
phase: question.phase || null,
|
|
87
|
+
action: 'answered',
|
|
88
|
+
answerLength: answer.length,
|
|
89
|
+
wordCount: answer.split(/\s+/).filter(w => w.length > 0).length,
|
|
90
|
+
timeSpent: this.getTimeSpent(question.id),
|
|
91
|
+
qualityScore: metadata.qualityScore || null,
|
|
92
|
+
richness: metadata.richness || null,
|
|
93
|
+
relevanceScore: question.relevance || null,
|
|
94
|
+
...metadata
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
this.sessionData.answers.push(answerEvent);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Record multiple filtered questions (bulk operation)
|
|
102
|
+
* @param {Array} filteredQuestions - Array of filtered question objects
|
|
103
|
+
*/
|
|
104
|
+
recordFilteredQuestions(filteredQuestions) {
|
|
105
|
+
for (const question of filteredQuestions) {
|
|
106
|
+
this.recordSkip(question, 'filtered', {
|
|
107
|
+
filterReason: question.reason || 'Low relevance score'
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Get session summary statistics
|
|
114
|
+
* @returns {Object} Session summary
|
|
115
|
+
*/
|
|
116
|
+
getSessionSummary() {
|
|
117
|
+
return {
|
|
118
|
+
sessionId: this.sessionId,
|
|
119
|
+
timestamp: this.sessionData.timestamp,
|
|
120
|
+
projectType: this.sessionData.projectType,
|
|
121
|
+
frameworks: this.sessionData.frameworks,
|
|
122
|
+
totalSkips: this.sessionData.skips.length,
|
|
123
|
+
manualSkips: this.sessionData.skips.filter(s => s.reason === 'manual').length,
|
|
124
|
+
filteredSkips: this.sessionData.skips.filter(s => s.reason === 'filtered').length,
|
|
125
|
+
totalAnswers: this.sessionData.answers.length,
|
|
126
|
+
avgAnswerLength: this.sessionData.answers.length > 0
|
|
127
|
+
? Math.round(this.sessionData.answers.reduce((sum, a) => sum + a.wordCount, 0) / this.sessionData.answers.length)
|
|
128
|
+
: 0
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Save session data to storage
|
|
134
|
+
*/
|
|
135
|
+
async saveSession() {
|
|
136
|
+
try {
|
|
137
|
+
// Save skip history
|
|
138
|
+
await storage.appendToLearningHistory(
|
|
139
|
+
this.projectPath,
|
|
140
|
+
'skip-history.json',
|
|
141
|
+
this.sessionData,
|
|
142
|
+
'sessions'
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
// Save answer history
|
|
146
|
+
const answerSessionData = {
|
|
147
|
+
sessionId: this.sessionId,
|
|
148
|
+
timestamp: this.sessionData.timestamp,
|
|
149
|
+
projectType: this.sessionData.projectType,
|
|
150
|
+
frameworks: this.sessionData.frameworks,
|
|
151
|
+
answers: this.sessionData.answers
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
await storage.appendToLearningHistory(
|
|
155
|
+
this.projectPath,
|
|
156
|
+
'answer-history.json',
|
|
157
|
+
answerSessionData,
|
|
158
|
+
'sessions'
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
// Update statistics
|
|
162
|
+
await storage.updateLearningStats(this.projectPath, {
|
|
163
|
+
totalSessions: (await storage.getLearningStats(this.projectPath)).totalSessions + 1,
|
|
164
|
+
totalSkips: (await storage.getLearningStats(this.projectPath)).totalSkips + this.sessionData.skips.length,
|
|
165
|
+
totalAnswers: (await storage.getLearningStats(this.projectPath)).totalAnswers + this.sessionData.answers.length
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
return true;
|
|
169
|
+
} catch (error) {
|
|
170
|
+
console.error('Error saving skip tracker session:', error.message);
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Get current session data (for debugging/testing)
|
|
177
|
+
* @returns {Object} Current session data
|
|
178
|
+
*/
|
|
179
|
+
getSessionData() {
|
|
180
|
+
return this.sessionData;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Helper function to analyze skip patterns from history
|
|
186
|
+
* @param {string} projectPath - Project root path
|
|
187
|
+
* @returns {Promise<Object>} Skip pattern analysis
|
|
188
|
+
*/
|
|
189
|
+
async function analyzeSkipPatterns(projectPath) {
|
|
190
|
+
const skipHistory = await storage.getSkipHistory(projectPath);
|
|
191
|
+
|
|
192
|
+
if (!skipHistory || !skipHistory.sessions || skipHistory.sessions.length === 0) {
|
|
193
|
+
return {
|
|
194
|
+
totalSessions: 0,
|
|
195
|
+
totalSkips: 0,
|
|
196
|
+
manualSkips: 0,
|
|
197
|
+
filteredSkips: 0,
|
|
198
|
+
mostSkippedQuestions: [],
|
|
199
|
+
mostSkippedCategories: []
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Count skips by question
|
|
204
|
+
const questionSkipCounts = {};
|
|
205
|
+
const categorySkipCounts = {};
|
|
206
|
+
let totalManualSkips = 0;
|
|
207
|
+
let totalFilteredSkips = 0;
|
|
208
|
+
|
|
209
|
+
for (const session of skipHistory.sessions) {
|
|
210
|
+
for (const skip of session.skips || []) {
|
|
211
|
+
// Count by question
|
|
212
|
+
if (!questionSkipCounts[skip.questionId]) {
|
|
213
|
+
questionSkipCounts[skip.questionId] = {
|
|
214
|
+
questionId: skip.questionId,
|
|
215
|
+
text: skip.text,
|
|
216
|
+
count: 0,
|
|
217
|
+
category: skip.category
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
questionSkipCounts[skip.questionId].count++;
|
|
221
|
+
|
|
222
|
+
// Count by category
|
|
223
|
+
if (skip.category) {
|
|
224
|
+
categorySkipCounts[skip.category] = (categorySkipCounts[skip.category] || 0) + 1;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Count by reason
|
|
228
|
+
if (skip.reason === 'manual') {
|
|
229
|
+
totalManualSkips++;
|
|
230
|
+
} else if (skip.reason === 'filtered') {
|
|
231
|
+
totalFilteredSkips++;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Sort and get top questions
|
|
237
|
+
const mostSkippedQuestions = Object.values(questionSkipCounts)
|
|
238
|
+
.sort((a, b) => b.count - a.count)
|
|
239
|
+
.slice(0, 10);
|
|
240
|
+
|
|
241
|
+
// Sort and get top categories
|
|
242
|
+
const mostSkippedCategories = Object.entries(categorySkipCounts)
|
|
243
|
+
.map(([category, count]) => ({ category, count }))
|
|
244
|
+
.sort((a, b) => b.count - a.count)
|
|
245
|
+
.slice(0, 5);
|
|
246
|
+
|
|
247
|
+
return {
|
|
248
|
+
totalSessions: skipHistory.sessions.length,
|
|
249
|
+
totalSkips: totalManualSkips + totalFilteredSkips,
|
|
250
|
+
manualSkips: totalManualSkips,
|
|
251
|
+
filteredSkips: totalFilteredSkips,
|
|
252
|
+
mostSkippedQuestions,
|
|
253
|
+
mostSkippedCategories
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
module.exports = {
|
|
258
|
+
SkipTracker,
|
|
259
|
+
analyzeSkipPatterns
|
|
260
|
+
};
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Learning Storage - Manages learning data persistence
|
|
6
|
+
*
|
|
7
|
+
* Handles all file I/O for the learning system with:
|
|
8
|
+
* - Atomic writes (write to temp, then rename)
|
|
9
|
+
* - Directory creation
|
|
10
|
+
* - Data validation
|
|
11
|
+
* - Error handling
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Get the learning directory path
|
|
16
|
+
* @param {string} projectPath - Project root path
|
|
17
|
+
* @returns {string} Learning directory path
|
|
18
|
+
*/
|
|
19
|
+
function getLearningPath(projectPath) {
|
|
20
|
+
return path.join(projectPath, '.adf', 'learning');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Ensure learning directory exists
|
|
25
|
+
* @param {string} projectPath - Project root path
|
|
26
|
+
*/
|
|
27
|
+
async function ensureLearningDirectory(projectPath) {
|
|
28
|
+
const learningPath = getLearningPath(projectPath);
|
|
29
|
+
await fs.ensureDir(learningPath);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Read learning data file
|
|
34
|
+
* @param {string} projectPath - Project root path
|
|
35
|
+
* @param {string} filename - File name (e.g., 'skip-history.json')
|
|
36
|
+
* @returns {Promise<Object|null>} Data object or null if doesn't exist
|
|
37
|
+
*/
|
|
38
|
+
async function readLearningData(projectPath, filename) {
|
|
39
|
+
try {
|
|
40
|
+
const learningPath = getLearningPath(projectPath);
|
|
41
|
+
const filePath = path.join(learningPath, filename);
|
|
42
|
+
|
|
43
|
+
if (!await fs.pathExists(filePath)) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const data = await fs.readJSON(filePath);
|
|
48
|
+
return data;
|
|
49
|
+
} catch (error) {
|
|
50
|
+
console.warn(`Warning: Could not read ${filename}: ${error.message}`);
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Write learning data file atomically
|
|
57
|
+
* @param {string} projectPath - Project root path
|
|
58
|
+
* @param {string} filename - File name
|
|
59
|
+
* @param {Object} data - Data to write
|
|
60
|
+
*/
|
|
61
|
+
async function writeLearningData(projectPath, filename, data) {
|
|
62
|
+
try {
|
|
63
|
+
await ensureLearningDirectory(projectPath);
|
|
64
|
+
|
|
65
|
+
const learningPath = getLearningPath(projectPath);
|
|
66
|
+
const filePath = path.join(learningPath, filename);
|
|
67
|
+
const tempPath = filePath + '.tmp';
|
|
68
|
+
|
|
69
|
+
// Write to temp file first (atomic operation)
|
|
70
|
+
await fs.writeJSON(tempPath, data, { spaces: 2 });
|
|
71
|
+
|
|
72
|
+
// Rename temp to actual file (atomic on most filesystems)
|
|
73
|
+
await fs.rename(tempPath, filePath);
|
|
74
|
+
} catch (error) {
|
|
75
|
+
console.error(`Error writing ${filename}: ${error.message}`);
|
|
76
|
+
throw error;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Append to learning history file
|
|
82
|
+
* @param {string} projectPath - Project root path
|
|
83
|
+
* @param {string} filename - File name
|
|
84
|
+
* @param {Object} newEntry - New entry to append
|
|
85
|
+
* @param {string} arrayKey - Key for the array to append to (e.g., 'sessions')
|
|
86
|
+
*/
|
|
87
|
+
async function appendToLearningHistory(projectPath, filename, newEntry, arrayKey = 'sessions') {
|
|
88
|
+
try {
|
|
89
|
+
// Read existing data
|
|
90
|
+
let data = await readLearningData(projectPath, filename);
|
|
91
|
+
|
|
92
|
+
if (!data) {
|
|
93
|
+
// Initialize if doesn't exist
|
|
94
|
+
data = {
|
|
95
|
+
version: '1.0',
|
|
96
|
+
[arrayKey]: []
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Append new entry
|
|
101
|
+
if (!data[arrayKey]) {
|
|
102
|
+
data[arrayKey] = [];
|
|
103
|
+
}
|
|
104
|
+
data[arrayKey].push(newEntry);
|
|
105
|
+
|
|
106
|
+
// Write back
|
|
107
|
+
await writeLearningData(projectPath, filename, data);
|
|
108
|
+
} catch (error) {
|
|
109
|
+
console.error(`Error appending to ${filename}: ${error.message}`);
|
|
110
|
+
throw error;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Get skip history
|
|
116
|
+
* @param {string} projectPath - Project root path
|
|
117
|
+
* @returns {Promise<Object>} Skip history data
|
|
118
|
+
*/
|
|
119
|
+
async function getSkipHistory(projectPath) {
|
|
120
|
+
const data = await readLearningData(projectPath, 'skip-history.json');
|
|
121
|
+
return data || { version: '1.0', sessions: [] };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Get answer history
|
|
126
|
+
* @param {string} projectPath - Project root path
|
|
127
|
+
* @returns {Promise<Object>} Answer history data
|
|
128
|
+
*/
|
|
129
|
+
async function getAnswerHistory(projectPath) {
|
|
130
|
+
const data = await readLearningData(projectPath, 'answer-history.json');
|
|
131
|
+
return data || { version: '1.0', sessions: [] };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Get detected patterns
|
|
136
|
+
* @param {string} projectPath - Project root path
|
|
137
|
+
* @returns {Promise<Object>} Patterns data
|
|
138
|
+
*/
|
|
139
|
+
async function getPatterns(projectPath) {
|
|
140
|
+
const data = await readLearningData(projectPath, 'patterns.json');
|
|
141
|
+
return data || { version: '1.0', lastUpdated: null, patterns: [] };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Save patterns
|
|
146
|
+
* @param {string} projectPath - Project root path
|
|
147
|
+
* @param {Object} patterns - Patterns data
|
|
148
|
+
*/
|
|
149
|
+
async function savePatterns(projectPath, patterns) {
|
|
150
|
+
patterns.lastUpdated = new Date().toISOString();
|
|
151
|
+
await writeLearningData(projectPath, 'patterns.json', patterns);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Get learned rules
|
|
156
|
+
* @param {string} projectPath - Project root path
|
|
157
|
+
* @returns {Promise<Object>} Learned rules data
|
|
158
|
+
*/
|
|
159
|
+
async function getLearnedRules(projectPath) {
|
|
160
|
+
const data = await readLearningData(projectPath, 'learned-rules.json');
|
|
161
|
+
return data || { version: '1.0', rules: [] };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Save learned rules
|
|
166
|
+
* @param {string} projectPath - Project root path
|
|
167
|
+
* @param {Object} rules - Rules data
|
|
168
|
+
*/
|
|
169
|
+
async function saveLearnedRules(projectPath, rules) {
|
|
170
|
+
await writeLearningData(projectPath, 'learned-rules.json', rules);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Get learning configuration
|
|
175
|
+
* @param {string} projectPath - Project root path
|
|
176
|
+
* @returns {Promise<Object>} Config data
|
|
177
|
+
*/
|
|
178
|
+
async function getLearningConfig(projectPath) {
|
|
179
|
+
const data = await readLearningData(projectPath, 'config.json');
|
|
180
|
+
return data || {
|
|
181
|
+
version: '1.0',
|
|
182
|
+
enabled: true,
|
|
183
|
+
trackSkips: true,
|
|
184
|
+
trackAnswers: true,
|
|
185
|
+
detectPatterns: true,
|
|
186
|
+
applyLearnedFilters: true,
|
|
187
|
+
shareAnonymousPatterns: false,
|
|
188
|
+
minSessionsForPattern: 3,
|
|
189
|
+
minConfidenceForAutoFilter: 75
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Save learning configuration
|
|
195
|
+
* @param {string} projectPath - Project root path
|
|
196
|
+
* @param {Object} config - Config data
|
|
197
|
+
*/
|
|
198
|
+
async function saveLearningConfig(projectPath, config) {
|
|
199
|
+
await writeLearningData(projectPath, 'config.json', config);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Get learning statistics
|
|
204
|
+
* @param {string} projectPath - Project root path
|
|
205
|
+
* @returns {Promise<Object>} Statistics data
|
|
206
|
+
*/
|
|
207
|
+
async function getLearningStats(projectPath) {
|
|
208
|
+
const data = await readLearningData(projectPath, 'stats.json');
|
|
209
|
+
return data || {
|
|
210
|
+
version: '1.0',
|
|
211
|
+
totalSessions: 0,
|
|
212
|
+
totalSkips: 0,
|
|
213
|
+
totalAnswers: 0,
|
|
214
|
+
patternsDetected: 0,
|
|
215
|
+
rulesApplied: 0,
|
|
216
|
+
lastUpdated: null
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Update learning statistics
|
|
222
|
+
* @param {string} projectPath - Project root path
|
|
223
|
+
* @param {Object} updates - Statistics updates
|
|
224
|
+
*/
|
|
225
|
+
async function updateLearningStats(projectPath, updates) {
|
|
226
|
+
const stats = await getLearningStats(projectPath);
|
|
227
|
+
|
|
228
|
+
Object.assign(stats, updates);
|
|
229
|
+
stats.lastUpdated = new Date().toISOString();
|
|
230
|
+
|
|
231
|
+
await writeLearningData(projectPath, 'stats.json', stats);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Clear all learning data
|
|
236
|
+
* @param {string} projectPath - Project root path
|
|
237
|
+
*/
|
|
238
|
+
async function clearLearningData(projectPath) {
|
|
239
|
+
const learningPath = getLearningPath(projectPath);
|
|
240
|
+
|
|
241
|
+
if (await fs.pathExists(learningPath)) {
|
|
242
|
+
// Remove all JSON files but keep directory
|
|
243
|
+
const files = await fs.readdir(learningPath);
|
|
244
|
+
for (const file of files) {
|
|
245
|
+
if (file.endsWith('.json')) {
|
|
246
|
+
await fs.remove(path.join(learningPath, file));
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Get learning data size in bytes
|
|
254
|
+
* @param {string} projectPath - Project root path
|
|
255
|
+
* @returns {Promise<number>} Total size in bytes
|
|
256
|
+
*/
|
|
257
|
+
async function getLearningDataSize(projectPath) {
|
|
258
|
+
const learningPath = getLearningPath(projectPath);
|
|
259
|
+
|
|
260
|
+
if (!await fs.pathExists(learningPath)) {
|
|
261
|
+
return 0;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
let totalSize = 0;
|
|
265
|
+
const files = await fs.readdir(learningPath);
|
|
266
|
+
|
|
267
|
+
for (const file of files) {
|
|
268
|
+
if (file.endsWith('.json')) {
|
|
269
|
+
const filePath = path.join(learningPath, file);
|
|
270
|
+
const stats = await fs.stat(filePath);
|
|
271
|
+
totalSize += stats.size;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return totalSize;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
module.exports = {
|
|
279
|
+
getLearningPath,
|
|
280
|
+
ensureLearningDirectory,
|
|
281
|
+
readLearningData,
|
|
282
|
+
writeLearningData,
|
|
283
|
+
appendToLearningHistory,
|
|
284
|
+
getSkipHistory,
|
|
285
|
+
getAnswerHistory,
|
|
286
|
+
getPatterns,
|
|
287
|
+
savePatterns,
|
|
288
|
+
getLearnedRules,
|
|
289
|
+
saveLearnedRules,
|
|
290
|
+
getLearningConfig,
|
|
291
|
+
saveLearningConfig,
|
|
292
|
+
getLearningStats,
|
|
293
|
+
updateLearningStats,
|
|
294
|
+
clearLearningData,
|
|
295
|
+
getLearningDataSize
|
|
296
|
+
};
|
package/package.json
CHANGED
|
@@ -1,69 +1,70 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@iservu-inc/adf-cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "CLI tool for AgentDevFramework - AI-assisted development framework with multi-provider AI support",
|
|
5
|
-
"main": "index.js",
|
|
6
|
-
"bin": {
|
|
7
|
-
"adf": "bin/adf.js"
|
|
8
|
-
},
|
|
9
|
-
"scripts": {
|
|
10
|
-
"test": "jest --coverage",
|
|
11
|
-
"test:watch": "jest --watch"
|
|
12
|
-
},
|
|
13
|
-
"keywords": [
|
|
14
|
-
"cli",
|
|
15
|
-
"framework",
|
|
16
|
-
"ai",
|
|
17
|
-
"development",
|
|
18
|
-
"agent",
|
|
19
|
-
"bmad",
|
|
20
|
-
"spec-kit",
|
|
21
|
-
"context-engineering",
|
|
22
|
-
"prp",
|
|
23
|
-
"agentic",
|
|
24
|
-
"tdd",
|
|
25
|
-
"windsurf",
|
|
26
|
-
"cursor",
|
|
27
|
-
"claude-code",
|
|
28
|
-
"anthropic",
|
|
29
|
-
"openai",
|
|
30
|
-
"gemini",
|
|
31
|
-
"openrouter",
|
|
32
|
-
"llm",
|
|
33
|
-
"requirements-gathering"
|
|
34
|
-
],
|
|
35
|
-
"author": "iServU",
|
|
36
|
-
"license": "MIT",
|
|
37
|
-
"dependencies": {
|
|
38
|
-
"@anthropic-ai/sdk": "^0.32.0",
|
|
39
|
-
"@google/generative-ai": "^0.21.0",
|
|
40
|
-
"chalk": "^4.1.2",
|
|
41
|
-
"commander": "^11.1.0",
|
|
42
|
-
"dotenv": "^17.2.3",
|
|
43
|
-
"fs-extra": "^11.2.0",
|
|
44
|
-
"inquirer": "^8.2.5",
|
|
45
|
-
"inquirer-autocomplete-prompt": "^2.0.1",
|
|
46
|
-
"node-fetch": "^2.7.0",
|
|
47
|
-
"openai": "^4.73.0",
|
|
48
|
-
"ora": "^5.4.1"
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
"
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
"
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
"
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
"
|
|
68
|
-
|
|
69
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@iservu-inc/adf-cli",
|
|
3
|
+
"version": "0.4.12",
|
|
4
|
+
"description": "CLI tool for AgentDevFramework - AI-assisted development framework with multi-provider AI support",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"adf": "bin/adf.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "jest --coverage",
|
|
11
|
+
"test:watch": "jest --watch"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"cli",
|
|
15
|
+
"framework",
|
|
16
|
+
"ai",
|
|
17
|
+
"development",
|
|
18
|
+
"agent",
|
|
19
|
+
"bmad",
|
|
20
|
+
"spec-kit",
|
|
21
|
+
"context-engineering",
|
|
22
|
+
"prp",
|
|
23
|
+
"agentic",
|
|
24
|
+
"tdd",
|
|
25
|
+
"windsurf",
|
|
26
|
+
"cursor",
|
|
27
|
+
"claude-code",
|
|
28
|
+
"anthropic",
|
|
29
|
+
"openai",
|
|
30
|
+
"gemini",
|
|
31
|
+
"openrouter",
|
|
32
|
+
"llm",
|
|
33
|
+
"requirements-gathering"
|
|
34
|
+
],
|
|
35
|
+
"author": "iServU",
|
|
36
|
+
"license": "MIT",
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@anthropic-ai/sdk": "^0.32.0",
|
|
39
|
+
"@google/generative-ai": "^0.21.0",
|
|
40
|
+
"chalk": "^4.1.2",
|
|
41
|
+
"commander": "^11.1.0",
|
|
42
|
+
"dotenv": "^17.2.3",
|
|
43
|
+
"fs-extra": "^11.2.0",
|
|
44
|
+
"inquirer": "^8.2.5",
|
|
45
|
+
"inquirer-autocomplete-prompt": "^2.0.1",
|
|
46
|
+
"node-fetch": "^2.7.0",
|
|
47
|
+
"openai": "^4.73.0",
|
|
48
|
+
"ora": "^5.4.1",
|
|
49
|
+
"uuid": "^9.0.1"
|
|
50
|
+
},
|
|
51
|
+
"engines": {
|
|
52
|
+
"node": ">=18.0.0",
|
|
53
|
+
"npm": ">=9.0.0"
|
|
54
|
+
},
|
|
55
|
+
"repository": {
|
|
56
|
+
"type": "git",
|
|
57
|
+
"url": "git+https://github.com/iservu/adf-cli.git"
|
|
58
|
+
},
|
|
59
|
+
"bugs": {
|
|
60
|
+
"url": "https://github.com/iservu/adf-cli/issues"
|
|
61
|
+
},
|
|
62
|
+
"homepage": "https://github.com/iservu/adf-cli#readme",
|
|
63
|
+
"publishConfig": {
|
|
64
|
+
"access": "public"
|
|
65
|
+
},
|
|
66
|
+
"devDependencies": {
|
|
67
|
+
"@types/node": "^24.6.2",
|
|
68
|
+
"jest": "^30.2.0"
|
|
69
|
+
}
|
|
70
|
+
}
|