pikakit 1.0.18 → 1.0.20
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,358 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Knowledge Storage v6.0 - Unified Schema
|
|
3
|
+
*
|
|
4
|
+
* Single source of truth for all lessons in PikaKit AutoLearn system.
|
|
5
|
+
* Consolidates legacy lessons-learned.yaml, mistakes.yaml, and improvements.yaml
|
|
6
|
+
*
|
|
7
|
+
* @version 6.0.0
|
|
8
|
+
* @author PikaKit
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import fs from 'fs';
|
|
12
|
+
import path from 'path';
|
|
13
|
+
import yaml from 'js-yaml';
|
|
14
|
+
|
|
15
|
+
// ============================================================================
|
|
16
|
+
// CONFIGURATION
|
|
17
|
+
// ============================================================================
|
|
18
|
+
|
|
19
|
+
const KNOWLEDGE_DIR = path.join(process.cwd(), '.agent', 'knowledge');
|
|
20
|
+
const KNOWLEDGE_FILE = path.join(KNOWLEDGE_DIR, 'knowledge.yaml');
|
|
21
|
+
|
|
22
|
+
// Legacy files (for backward compatibility)
|
|
23
|
+
const LEGACY_FILES = {
|
|
24
|
+
lessonsLearned: path.join(KNOWLEDGE_DIR, 'lessons-learned.yaml'),
|
|
25
|
+
mistakes: path.join(KNOWLEDGE_DIR, 'mistakes.yaml'),
|
|
26
|
+
improvements: path.join(KNOWLEDGE_DIR, 'improvements.yaml')
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// ============================================================================
|
|
30
|
+
// SCHEMA DEFINITION
|
|
31
|
+
// ============================================================================
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @typedef {Object} Lesson
|
|
35
|
+
* @property {string} id - Unique identifier (LESSON-XXX)
|
|
36
|
+
* @property {string} type - Type: 'mistake' | 'improvement' | 'pattern'
|
|
37
|
+
* @property {string} pattern - Regex pattern to match
|
|
38
|
+
* @property {string} message - Human-readable explanation
|
|
39
|
+
* @property {string} severity - 'ERROR' | 'WARNING' | 'INFO'
|
|
40
|
+
* @property {string} intent - 'prevent' | 'optimize' | 'inform'
|
|
41
|
+
* @property {number} confidence - 0.0 to 1.0
|
|
42
|
+
* @property {string} maturity - 'learning' | 'stable'
|
|
43
|
+
* @property {number} hitCount - Number of times pattern was matched
|
|
44
|
+
* @property {string|null} lastHit - ISO8601 timestamp of last match
|
|
45
|
+
* @property {string[]} excludePaths - Glob patterns for excluded paths
|
|
46
|
+
* @property {string[]} tags - Category tags
|
|
47
|
+
* @property {Object|null} autoFix - Auto-fix configuration
|
|
48
|
+
*/
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* @typedef {Object} KnowledgeBase
|
|
52
|
+
* @property {number} version - Schema version (6.0)
|
|
53
|
+
* @property {string} createdAt - ISO8601 creation timestamp
|
|
54
|
+
* @property {string} updatedAt - ISO8601 last update timestamp
|
|
55
|
+
* @property {Lesson[]} lessons - Array of lessons
|
|
56
|
+
*/
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Create empty knowledge base with v6 schema
|
|
60
|
+
* @returns {KnowledgeBase}
|
|
61
|
+
*/
|
|
62
|
+
export function createEmptyKnowledge() {
|
|
63
|
+
return {
|
|
64
|
+
version: 6.0,
|
|
65
|
+
createdAt: new Date().toISOString(),
|
|
66
|
+
updatedAt: new Date().toISOString(),
|
|
67
|
+
lessons: []
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ============================================================================
|
|
72
|
+
// LOADING
|
|
73
|
+
// ============================================================================
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Load knowledge base with automatic version detection
|
|
77
|
+
* Priority: v6 (knowledge.yaml) > v4 (mistakes + improvements) > v3 (lessons-learned)
|
|
78
|
+
*
|
|
79
|
+
* @returns {KnowledgeBase}
|
|
80
|
+
*/
|
|
81
|
+
export function loadKnowledge() {
|
|
82
|
+
try {
|
|
83
|
+
// Priority 1: v6 unified format
|
|
84
|
+
if (fs.existsSync(KNOWLEDGE_FILE)) {
|
|
85
|
+
const content = fs.readFileSync(KNOWLEDGE_FILE, 'utf8');
|
|
86
|
+
const data = yaml.load(content) || createEmptyKnowledge();
|
|
87
|
+
return data;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Priority 2: v4 cognitive format (mistakes + improvements)
|
|
91
|
+
const hasV4 = fs.existsSync(LEGACY_FILES.mistakes) ||
|
|
92
|
+
fs.existsSync(LEGACY_FILES.improvements);
|
|
93
|
+
|
|
94
|
+
if (hasV4) {
|
|
95
|
+
return loadV4Legacy();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Priority 3: v3 legacy format
|
|
99
|
+
if (fs.existsSync(LEGACY_FILES.lessonsLearned)) {
|
|
100
|
+
return loadV3Legacy();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// No data - return empty
|
|
104
|
+
return createEmptyKnowledge();
|
|
105
|
+
|
|
106
|
+
} catch (error) {
|
|
107
|
+
console.error('[knowledge] Error loading:', error.message);
|
|
108
|
+
return createEmptyKnowledge();
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Load from v4 legacy files (mistakes.yaml + improvements.yaml)
|
|
114
|
+
* @returns {KnowledgeBase}
|
|
115
|
+
*/
|
|
116
|
+
function loadV4Legacy() {
|
|
117
|
+
const knowledge = createEmptyKnowledge();
|
|
118
|
+
|
|
119
|
+
// Load mistakes
|
|
120
|
+
if (fs.existsSync(LEGACY_FILES.mistakes)) {
|
|
121
|
+
try {
|
|
122
|
+
const data = yaml.load(fs.readFileSync(LEGACY_FILES.mistakes, 'utf8'));
|
|
123
|
+
const mistakes = (data.mistakes || []).map(m => ({
|
|
124
|
+
id: m.id,
|
|
125
|
+
type: 'mistake',
|
|
126
|
+
pattern: m.pattern,
|
|
127
|
+
message: m.message,
|
|
128
|
+
severity: m.severity || 'WARNING',
|
|
129
|
+
intent: 'prevent',
|
|
130
|
+
confidence: m.cognitive?.confidence || 0.5,
|
|
131
|
+
maturity: m.cognitive?.maturity || 'learning',
|
|
132
|
+
hitCount: m.hitCount || 0,
|
|
133
|
+
lastHit: m.lastHit || null,
|
|
134
|
+
excludePaths: m.excludePaths || [],
|
|
135
|
+
tags: m.tags || [],
|
|
136
|
+
autoFix: m.autoFix || null
|
|
137
|
+
}));
|
|
138
|
+
knowledge.lessons.push(...mistakes);
|
|
139
|
+
} catch (e) {
|
|
140
|
+
console.error('[knowledge] Error loading mistakes.yaml:', e.message);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Load improvements
|
|
145
|
+
if (fs.existsSync(LEGACY_FILES.improvements)) {
|
|
146
|
+
try {
|
|
147
|
+
const data = yaml.load(fs.readFileSync(LEGACY_FILES.improvements, 'utf8'));
|
|
148
|
+
const improvements = (data.improvements || []).map(i => ({
|
|
149
|
+
id: i.id,
|
|
150
|
+
type: 'improvement',
|
|
151
|
+
pattern: i.pattern,
|
|
152
|
+
message: i.message,
|
|
153
|
+
severity: 'INFO',
|
|
154
|
+
intent: 'optimize',
|
|
155
|
+
confidence: i.cognitive?.confidence || 0.5,
|
|
156
|
+
maturity: i.cognitive?.maturity || 'learning',
|
|
157
|
+
hitCount: i.hitCount || i.appliedCount || 0,
|
|
158
|
+
lastHit: i.lastHit || i.lastApplied || null,
|
|
159
|
+
excludePaths: i.excludePaths || [],
|
|
160
|
+
tags: i.tags || [],
|
|
161
|
+
autoFix: null
|
|
162
|
+
}));
|
|
163
|
+
knowledge.lessons.push(...improvements);
|
|
164
|
+
} catch (e) {
|
|
165
|
+
console.error('[knowledge] Error loading improvements.yaml:', e.message);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return knowledge;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Load from v3 legacy file (lessons-learned.yaml)
|
|
174
|
+
* @returns {KnowledgeBase}
|
|
175
|
+
*/
|
|
176
|
+
function loadV3Legacy() {
|
|
177
|
+
const knowledge = createEmptyKnowledge();
|
|
178
|
+
|
|
179
|
+
try {
|
|
180
|
+
const data = yaml.load(fs.readFileSync(LEGACY_FILES.lessonsLearned, 'utf8'));
|
|
181
|
+
const lessons = (data.lessons || []).map(l => ({
|
|
182
|
+
id: l.id,
|
|
183
|
+
type: l.severity === 'ERROR' ? 'mistake' : 'pattern',
|
|
184
|
+
pattern: l.pattern,
|
|
185
|
+
message: l.message,
|
|
186
|
+
severity: l.severity || 'WARNING',
|
|
187
|
+
intent: l.severity === 'ERROR' ? 'prevent' : 'inform',
|
|
188
|
+
confidence: l.hitCount > 10 ? 0.9 : 0.5,
|
|
189
|
+
maturity: l.hitCount > 10 ? 'stable' : 'learning',
|
|
190
|
+
hitCount: l.hitCount || 0,
|
|
191
|
+
lastHit: l.lastHit || null,
|
|
192
|
+
excludePaths: [],
|
|
193
|
+
tags: [l.category || 'general'],
|
|
194
|
+
autoFix: null
|
|
195
|
+
}));
|
|
196
|
+
knowledge.lessons.push(...lessons);
|
|
197
|
+
} catch (e) {
|
|
198
|
+
console.error('[knowledge] Error loading lessons-learned.yaml:', e.message);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return knowledge;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// ============================================================================
|
|
205
|
+
// SAVING
|
|
206
|
+
// ============================================================================
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Save knowledge base to unified format
|
|
210
|
+
* @param {KnowledgeBase} knowledge
|
|
211
|
+
*/
|
|
212
|
+
export function saveKnowledge(knowledge) {
|
|
213
|
+
try {
|
|
214
|
+
// Ensure directory exists
|
|
215
|
+
if (!fs.existsSync(KNOWLEDGE_DIR)) {
|
|
216
|
+
fs.mkdirSync(KNOWLEDGE_DIR, { recursive: true });
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Update timestamp
|
|
220
|
+
knowledge.updatedAt = new Date().toISOString();
|
|
221
|
+
knowledge.version = 6.0;
|
|
222
|
+
|
|
223
|
+
// Write to file
|
|
224
|
+
const yamlContent = yaml.dump(knowledge, {
|
|
225
|
+
lineWidth: -1,
|
|
226
|
+
quotingType: '"',
|
|
227
|
+
forceQuotes: false
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
fs.writeFileSync(KNOWLEDGE_FILE, yamlContent, 'utf8');
|
|
231
|
+
|
|
232
|
+
return true;
|
|
233
|
+
} catch (error) {
|
|
234
|
+
console.error('[knowledge] Error saving:', error.message);
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// ============================================================================
|
|
240
|
+
// OPERATIONS
|
|
241
|
+
// ============================================================================
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Add a new lesson
|
|
245
|
+
* @param {Lesson} lesson
|
|
246
|
+
* @returns {boolean}
|
|
247
|
+
*/
|
|
248
|
+
export function addLesson(lesson) {
|
|
249
|
+
const knowledge = loadKnowledge();
|
|
250
|
+
|
|
251
|
+
// Check for duplicate ID
|
|
252
|
+
if (knowledge.lessons.some(l => l.id === lesson.id)) {
|
|
253
|
+
console.warn(`[knowledge] Lesson ${lesson.id} already exists`);
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
knowledge.lessons.push(lesson);
|
|
258
|
+
return saveKnowledge(knowledge);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Update an existing lesson
|
|
263
|
+
* @param {string} id
|
|
264
|
+
* @param {Partial<Lesson>} updates
|
|
265
|
+
* @returns {boolean}
|
|
266
|
+
*/
|
|
267
|
+
export function updateLesson(id, updates) {
|
|
268
|
+
const knowledge = loadKnowledge();
|
|
269
|
+
const index = knowledge.lessons.findIndex(l => l.id === id);
|
|
270
|
+
|
|
271
|
+
if (index === -1) {
|
|
272
|
+
console.warn(`[knowledge] Lesson ${id} not found`);
|
|
273
|
+
return false;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
knowledge.lessons[index] = { ...knowledge.lessons[index], ...updates };
|
|
277
|
+
return saveKnowledge(knowledge);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Remove a lesson
|
|
282
|
+
* @param {string} id
|
|
283
|
+
* @returns {boolean}
|
|
284
|
+
*/
|
|
285
|
+
export function removeLesson(id) {
|
|
286
|
+
const knowledge = loadKnowledge();
|
|
287
|
+
const initialLength = knowledge.lessons.length;
|
|
288
|
+
knowledge.lessons = knowledge.lessons.filter(l => l.id !== id);
|
|
289
|
+
|
|
290
|
+
if (knowledge.lessons.length === initialLength) {
|
|
291
|
+
console.warn(`[knowledge] Lesson ${id} not found`);
|
|
292
|
+
return false;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return saveKnowledge(knowledge);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Get lessons by type
|
|
300
|
+
* @param {'mistake' | 'improvement' | 'pattern'} type
|
|
301
|
+
* @returns {Lesson[]}
|
|
302
|
+
*/
|
|
303
|
+
export function getLessonsByType(type) {
|
|
304
|
+
const knowledge = loadKnowledge();
|
|
305
|
+
return knowledge.lessons.filter(l => l.type === type);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Get total lesson count
|
|
310
|
+
* @returns {number}
|
|
311
|
+
*/
|
|
312
|
+
export function getLessonCount() {
|
|
313
|
+
const knowledge = loadKnowledge();
|
|
314
|
+
return knowledge.lessons.length;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Record a hit on a lesson
|
|
319
|
+
* @param {string} id
|
|
320
|
+
* @returns {boolean}
|
|
321
|
+
*/
|
|
322
|
+
export function recordHit(id) {
|
|
323
|
+
const knowledge = loadKnowledge();
|
|
324
|
+
const lesson = knowledge.lessons.find(l => l.id === id);
|
|
325
|
+
|
|
326
|
+
if (!lesson) {
|
|
327
|
+
return false;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
lesson.hitCount = (lesson.hitCount || 0) + 1;
|
|
331
|
+
lesson.lastHit = new Date().toISOString();
|
|
332
|
+
|
|
333
|
+
// Auto-update maturity based on hits
|
|
334
|
+
if (lesson.hitCount >= 10) {
|
|
335
|
+
lesson.maturity = 'stable';
|
|
336
|
+
lesson.confidence = Math.min(0.95, lesson.confidence + 0.05);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
return saveKnowledge(knowledge);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// ============================================================================
|
|
343
|
+
// EXPORTS
|
|
344
|
+
// ============================================================================
|
|
345
|
+
|
|
346
|
+
export default {
|
|
347
|
+
loadKnowledge,
|
|
348
|
+
saveKnowledge,
|
|
349
|
+
addLesson,
|
|
350
|
+
updateLesson,
|
|
351
|
+
removeLesson,
|
|
352
|
+
getLessonsByType,
|
|
353
|
+
getLessonCount,
|
|
354
|
+
recordHit,
|
|
355
|
+
createEmptyKnowledge,
|
|
356
|
+
KNOWLEDGE_FILE,
|
|
357
|
+
LEGACY_FILES
|
|
358
|
+
};
|
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Migration Script: v3/v4 → v6
|
|
4
|
+
*
|
|
5
|
+
* Migrates legacy knowledge files to unified knowledge.yaml format.
|
|
6
|
+
*
|
|
7
|
+
* Source files:
|
|
8
|
+
* - lessons-learned.yaml (v3)
|
|
9
|
+
* - mistakes.yaml (v4)
|
|
10
|
+
* - improvements.yaml (v4)
|
|
11
|
+
*
|
|
12
|
+
* Target:
|
|
13
|
+
* - knowledge.yaml (v6)
|
|
14
|
+
*
|
|
15
|
+
* Usage:
|
|
16
|
+
* node migrate-to-v6.js
|
|
17
|
+
* node migrate-to-v6.js --dry-run
|
|
18
|
+
*
|
|
19
|
+
* @version 6.0.0
|
|
20
|
+
* @author PikaKit
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import fs from 'fs';
|
|
24
|
+
import path from 'path';
|
|
25
|
+
import yaml from 'js-yaml';
|
|
26
|
+
|
|
27
|
+
// ============================================================================
|
|
28
|
+
// CONFIGURATION
|
|
29
|
+
// ============================================================================
|
|
30
|
+
|
|
31
|
+
const KNOWLEDGE_DIR = path.join(process.cwd(), '.agent', 'knowledge');
|
|
32
|
+
const BACKUP_DIR = path.join(KNOWLEDGE_DIR, 'backup');
|
|
33
|
+
|
|
34
|
+
const FILES = {
|
|
35
|
+
lessonsLearned: path.join(KNOWLEDGE_DIR, 'lessons-learned.yaml'),
|
|
36
|
+
mistakes: path.join(KNOWLEDGE_DIR, 'mistakes.yaml'),
|
|
37
|
+
improvements: path.join(KNOWLEDGE_DIR, 'improvements.yaml'),
|
|
38
|
+
knowledge: path.join(KNOWLEDGE_DIR, 'knowledge.yaml')
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const isDryRun = process.argv.includes('--dry-run');
|
|
42
|
+
|
|
43
|
+
// ============================================================================
|
|
44
|
+
// LOGGING
|
|
45
|
+
// ============================================================================
|
|
46
|
+
|
|
47
|
+
const log = {
|
|
48
|
+
info: (msg) => console.log(` ℹ️ ${msg}`),
|
|
49
|
+
success: (msg) => console.log(` ✅ ${msg}`),
|
|
50
|
+
warning: (msg) => console.log(` ⚠️ ${msg}`),
|
|
51
|
+
error: (msg) => console.log(` ❌ ${msg}`),
|
|
52
|
+
step: (n, msg) => console.log(`\n📌 Step ${n}: ${msg}`)
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// ============================================================================
|
|
56
|
+
// BACKUP
|
|
57
|
+
// ============================================================================
|
|
58
|
+
|
|
59
|
+
function backupFiles() {
|
|
60
|
+
log.step(1, 'Backing up existing files');
|
|
61
|
+
|
|
62
|
+
if (!fs.existsSync(BACKUP_DIR)) {
|
|
63
|
+
fs.mkdirSync(BACKUP_DIR, { recursive: true });
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
67
|
+
let backedUp = 0;
|
|
68
|
+
|
|
69
|
+
for (const [name, filepath] of Object.entries(FILES)) {
|
|
70
|
+
if (name === 'knowledge') continue; // Don't backup target
|
|
71
|
+
|
|
72
|
+
if (fs.existsSync(filepath)) {
|
|
73
|
+
const backupPath = path.join(BACKUP_DIR, `${path.basename(filepath)}.${timestamp}.bak`);
|
|
74
|
+
|
|
75
|
+
if (!isDryRun) {
|
|
76
|
+
fs.copyFileSync(filepath, backupPath);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
log.success(`Backed up ${path.basename(filepath)}`);
|
|
80
|
+
backedUp++;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (backedUp === 0) {
|
|
85
|
+
log.warning('No files to backup');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// ============================================================================
|
|
92
|
+
// LOADING LEGACY DATA
|
|
93
|
+
// ============================================================================
|
|
94
|
+
|
|
95
|
+
function loadLegacyData() {
|
|
96
|
+
log.step(2, 'Loading legacy data');
|
|
97
|
+
|
|
98
|
+
const lessons = [];
|
|
99
|
+
const stats = { v3: 0, v4Mistakes: 0, v4Improvements: 0 };
|
|
100
|
+
|
|
101
|
+
// Load v3 lessons-learned.yaml
|
|
102
|
+
if (fs.existsSync(FILES.lessonsLearned)) {
|
|
103
|
+
try {
|
|
104
|
+
const data = yaml.load(fs.readFileSync(FILES.lessonsLearned, 'utf8'));
|
|
105
|
+
const v3Lessons = (data.lessons || []).map(l => ({
|
|
106
|
+
id: l.id,
|
|
107
|
+
type: l.severity === 'ERROR' ? 'mistake' : 'pattern',
|
|
108
|
+
pattern: l.pattern,
|
|
109
|
+
message: l.message,
|
|
110
|
+
severity: l.severity || 'WARNING',
|
|
111
|
+
intent: l.severity === 'ERROR' ? 'prevent' : 'inform',
|
|
112
|
+
confidence: l.hitCount > 10 ? 0.9 : 0.5,
|
|
113
|
+
maturity: l.hitCount > 10 ? 'stable' : 'learning',
|
|
114
|
+
hitCount: l.hitCount || 0,
|
|
115
|
+
lastHit: l.lastHit || null,
|
|
116
|
+
excludePaths: [],
|
|
117
|
+
tags: [l.category || 'general'],
|
|
118
|
+
autoFix: null,
|
|
119
|
+
_source: 'lessons-learned.yaml'
|
|
120
|
+
}));
|
|
121
|
+
lessons.push(...v3Lessons);
|
|
122
|
+
stats.v3 = v3Lessons.length;
|
|
123
|
+
log.success(`Loaded ${v3Lessons.length} lesson(s) from lessons-learned.yaml`);
|
|
124
|
+
} catch (e) {
|
|
125
|
+
log.error(`Failed to load lessons-learned.yaml: ${e.message}`);
|
|
126
|
+
}
|
|
127
|
+
} else {
|
|
128
|
+
log.info('lessons-learned.yaml not found, skipping');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Load v4 mistakes.yaml
|
|
132
|
+
if (fs.existsSync(FILES.mistakes)) {
|
|
133
|
+
try {
|
|
134
|
+
const data = yaml.load(fs.readFileSync(FILES.mistakes, 'utf8'));
|
|
135
|
+
const mistakes = (data.mistakes || []).map(m => ({
|
|
136
|
+
id: m.id,
|
|
137
|
+
type: 'mistake',
|
|
138
|
+
pattern: m.pattern,
|
|
139
|
+
message: m.message,
|
|
140
|
+
severity: m.severity || 'WARNING',
|
|
141
|
+
intent: 'prevent',
|
|
142
|
+
confidence: m.cognitive?.confidence || 0.5,
|
|
143
|
+
maturity: m.cognitive?.maturity || 'learning',
|
|
144
|
+
hitCount: m.hitCount || 0,
|
|
145
|
+
lastHit: m.lastHit || null,
|
|
146
|
+
excludePaths: m.excludePaths || [],
|
|
147
|
+
tags: m.tags || [],
|
|
148
|
+
autoFix: m.autoFix || null,
|
|
149
|
+
_source: 'mistakes.yaml'
|
|
150
|
+
}));
|
|
151
|
+
lessons.push(...mistakes);
|
|
152
|
+
stats.v4Mistakes = mistakes.length;
|
|
153
|
+
log.success(`Loaded ${mistakes.length} mistake(s) from mistakes.yaml`);
|
|
154
|
+
} catch (e) {
|
|
155
|
+
log.error(`Failed to load mistakes.yaml: ${e.message}`);
|
|
156
|
+
}
|
|
157
|
+
} else {
|
|
158
|
+
log.info('mistakes.yaml not found, skipping');
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Load v4 improvements.yaml
|
|
162
|
+
if (fs.existsSync(FILES.improvements)) {
|
|
163
|
+
try {
|
|
164
|
+
const data = yaml.load(fs.readFileSync(FILES.improvements, 'utf8'));
|
|
165
|
+
const improvements = (data.improvements || []).map(i => ({
|
|
166
|
+
id: i.id,
|
|
167
|
+
type: 'improvement',
|
|
168
|
+
pattern: i.pattern,
|
|
169
|
+
message: i.message,
|
|
170
|
+
severity: 'INFO',
|
|
171
|
+
intent: 'optimize',
|
|
172
|
+
confidence: i.cognitive?.confidence || 0.5,
|
|
173
|
+
maturity: i.cognitive?.maturity || 'learning',
|
|
174
|
+
hitCount: i.hitCount || i.appliedCount || 0,
|
|
175
|
+
lastHit: i.lastHit || i.lastApplied || null,
|
|
176
|
+
excludePaths: i.excludePaths || [],
|
|
177
|
+
tags: i.tags || [],
|
|
178
|
+
autoFix: null,
|
|
179
|
+
_source: 'improvements.yaml'
|
|
180
|
+
}));
|
|
181
|
+
lessons.push(...improvements);
|
|
182
|
+
stats.v4Improvements = improvements.length;
|
|
183
|
+
log.success(`Loaded ${improvements.length} improvement(s) from improvements.yaml`);
|
|
184
|
+
} catch (e) {
|
|
185
|
+
log.error(`Failed to load improvements.yaml: ${e.message}`);
|
|
186
|
+
}
|
|
187
|
+
} else {
|
|
188
|
+
log.info('improvements.yaml not found, skipping');
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return { lessons, stats };
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// ============================================================================
|
|
195
|
+
// DEDUPLICATION
|
|
196
|
+
// ============================================================================
|
|
197
|
+
|
|
198
|
+
function deduplicateLessons(lessons) {
|
|
199
|
+
log.step(3, 'Deduplicating lessons');
|
|
200
|
+
|
|
201
|
+
const seen = new Map();
|
|
202
|
+
const duplicates = [];
|
|
203
|
+
|
|
204
|
+
for (const lesson of lessons) {
|
|
205
|
+
// Check by pattern (primary dedup key)
|
|
206
|
+
if (seen.has(lesson.pattern)) {
|
|
207
|
+
const existing = seen.get(lesson.pattern);
|
|
208
|
+
duplicates.push({
|
|
209
|
+
kept: existing.id,
|
|
210
|
+
removed: lesson.id,
|
|
211
|
+
pattern: lesson.pattern
|
|
212
|
+
});
|
|
213
|
+
// Keep the one with more hits
|
|
214
|
+
if (lesson.hitCount > existing.hitCount) {
|
|
215
|
+
seen.set(lesson.pattern, lesson);
|
|
216
|
+
}
|
|
217
|
+
} else {
|
|
218
|
+
seen.set(lesson.pattern, lesson);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const deduped = Array.from(seen.values());
|
|
223
|
+
|
|
224
|
+
if (duplicates.length > 0) {
|
|
225
|
+
log.warning(`Found ${duplicates.length} duplicate(s), keeping highest hitCount`);
|
|
226
|
+
duplicates.forEach(d => {
|
|
227
|
+
log.info(` Merged ${d.removed} into ${d.kept}`);
|
|
228
|
+
});
|
|
229
|
+
} else {
|
|
230
|
+
log.success('No duplicates found');
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return deduped;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// ============================================================================
|
|
237
|
+
// RENUMBER IDS
|
|
238
|
+
// ============================================================================
|
|
239
|
+
|
|
240
|
+
function renumberLessons(lessons) {
|
|
241
|
+
log.step(4, 'Renumbering lesson IDs');
|
|
242
|
+
|
|
243
|
+
return lessons.map((lesson, index) => {
|
|
244
|
+
// Remove internal _source field
|
|
245
|
+
const { _source, ...cleanLesson } = lesson;
|
|
246
|
+
|
|
247
|
+
// Assign new sequential ID
|
|
248
|
+
cleanLesson.id = `LESSON-${String(index + 1).padStart(3, '0')}`;
|
|
249
|
+
|
|
250
|
+
return cleanLesson;
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// ============================================================================
|
|
255
|
+
// SAVE
|
|
256
|
+
// ============================================================================
|
|
257
|
+
|
|
258
|
+
function saveKnowledge(lessons) {
|
|
259
|
+
log.step(5, 'Saving unified knowledge.yaml');
|
|
260
|
+
|
|
261
|
+
const knowledge = {
|
|
262
|
+
version: 6.0,
|
|
263
|
+
createdAt: new Date().toISOString(),
|
|
264
|
+
updatedAt: new Date().toISOString(),
|
|
265
|
+
lessons
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
const yamlContent = yaml.dump(knowledge, {
|
|
269
|
+
lineWidth: -1,
|
|
270
|
+
quotingType: '"',
|
|
271
|
+
forceQuotes: false
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
if (isDryRun) {
|
|
275
|
+
log.info('[DRY RUN] Would write to knowledge.yaml');
|
|
276
|
+
console.log('\n--- Preview of knowledge.yaml ---');
|
|
277
|
+
console.log(yamlContent.slice(0, 1000) + (yamlContent.length > 1000 ? '\n...' : ''));
|
|
278
|
+
} else {
|
|
279
|
+
fs.writeFileSync(FILES.knowledge, yamlContent, 'utf8');
|
|
280
|
+
log.success(`Saved ${lessons.length} lesson(s) to knowledge.yaml`);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
return true;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// ============================================================================
|
|
287
|
+
// VERIFY
|
|
288
|
+
// ============================================================================
|
|
289
|
+
|
|
290
|
+
function verifyMigration(expectedCount) {
|
|
291
|
+
log.step(6, 'Verifying migration');
|
|
292
|
+
|
|
293
|
+
if (isDryRun) {
|
|
294
|
+
log.info('[DRY RUN] Skipping verification');
|
|
295
|
+
return true;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (!fs.existsSync(FILES.knowledge)) {
|
|
299
|
+
log.error('knowledge.yaml not created');
|
|
300
|
+
return false;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const data = yaml.load(fs.readFileSync(FILES.knowledge, 'utf8'));
|
|
304
|
+
const actualCount = data.lessons?.length || 0;
|
|
305
|
+
|
|
306
|
+
if (actualCount !== expectedCount) {
|
|
307
|
+
log.error(`Count mismatch: expected ${expectedCount}, got ${actualCount}`);
|
|
308
|
+
return false;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
log.success(`Verified: ${actualCount} lessons in knowledge.yaml`);
|
|
312
|
+
log.success(`Version: ${data.version}`);
|
|
313
|
+
|
|
314
|
+
return true;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// ============================================================================
|
|
318
|
+
// MAIN
|
|
319
|
+
// ============================================================================
|
|
320
|
+
|
|
321
|
+
async function main() {
|
|
322
|
+
console.log('\n🔄 Knowledge Storage Migration: v3/v4 → v6');
|
|
323
|
+
console.log('='.repeat(50));
|
|
324
|
+
|
|
325
|
+
if (isDryRun) {
|
|
326
|
+
console.log('🔍 DRY RUN MODE - No changes will be made\n');
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Step 1: Backup
|
|
330
|
+
backupFiles();
|
|
331
|
+
|
|
332
|
+
// Step 2: Load legacy data
|
|
333
|
+
const { lessons, stats } = loadLegacyData();
|
|
334
|
+
|
|
335
|
+
if (lessons.length === 0) {
|
|
336
|
+
log.warning('No lessons found to migrate');
|
|
337
|
+
console.log('\n✅ Migration complete (nothing to migrate)');
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Step 3: Deduplicate
|
|
342
|
+
const deduped = deduplicateLessons(lessons);
|
|
343
|
+
|
|
344
|
+
// Step 4: Renumber
|
|
345
|
+
const renumbered = renumberLessons(deduped);
|
|
346
|
+
|
|
347
|
+
// Step 5: Save
|
|
348
|
+
saveKnowledge(renumbered);
|
|
349
|
+
|
|
350
|
+
// Step 6: Verify
|
|
351
|
+
const verified = verifyMigration(renumbered.length);
|
|
352
|
+
|
|
353
|
+
// Summary
|
|
354
|
+
console.log('\n' + '='.repeat(50));
|
|
355
|
+
console.log('📊 Migration Summary');
|
|
356
|
+
console.log('='.repeat(50));
|
|
357
|
+
console.log(` Source files:`);
|
|
358
|
+
console.log(` - lessons-learned.yaml: ${stats.v3} lesson(s)`);
|
|
359
|
+
console.log(` - mistakes.yaml: ${stats.v4Mistakes} mistake(s)`);
|
|
360
|
+
console.log(` - improvements.yaml: ${stats.v4Improvements} improvement(s)`);
|
|
361
|
+
console.log(` Total loaded: ${lessons.length}`);
|
|
362
|
+
console.log(` After dedup: ${deduped.length}`);
|
|
363
|
+
console.log(` Final count: ${renumbered.length}`);
|
|
364
|
+
console.log(` Verified: ${verified ? '✅ Yes' : '❌ No'}`);
|
|
365
|
+
|
|
366
|
+
if (isDryRun) {
|
|
367
|
+
console.log('\n🔍 This was a dry run. Run without --dry-run to apply changes.');
|
|
368
|
+
} else {
|
|
369
|
+
console.log('\n✅ Migration complete!');
|
|
370
|
+
console.log('📁 Backup files saved to: .agent/knowledge/backup/');
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
main().catch(e => {
|
|
375
|
+
log.error(`Migration failed: ${e.message}`);
|
|
376
|
+
process.exit(1);
|
|
377
|
+
});
|
|
@@ -23,6 +23,7 @@ import pretty from "./ui/pretty.js";
|
|
|
23
23
|
import * as p from "@clack/prompts";
|
|
24
24
|
import pc from "picocolors";
|
|
25
25
|
import { checkEvolutionThreshold, queueEvolutionSignal } from "./evolution-signal.js";
|
|
26
|
+
import { loadKnowledge as loadKnowledgeV6, saveKnowledge as saveKnowledgeV6 } from "./knowledge.js";
|
|
26
27
|
|
|
27
28
|
// ============================================================================
|
|
28
29
|
// PERCEPTION LAYER - Cognitive Enhancements
|
|
@@ -142,66 +143,25 @@ function createCognitiveLesson(rawLesson) {
|
|
|
142
143
|
// ============================================================================
|
|
143
144
|
|
|
144
145
|
/**
|
|
145
|
-
* Load knowledge base from
|
|
146
|
-
*
|
|
146
|
+
* Load knowledge base from unified v6 format
|
|
147
|
+
* With backward compatibility for v3.x and v4.x
|
|
147
148
|
* @returns {{ lessons: Array<{ id: string, pattern: string, message: string, severity: string, hitCount?: number, lastHit?: string }>, version?: number }}
|
|
148
149
|
*/
|
|
149
150
|
export function loadKnowledge() {
|
|
150
151
|
try {
|
|
151
|
-
//
|
|
152
|
-
const
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
const improvements = fs.existsSync(improvementsPath)
|
|
163
|
-
? yaml.load(fs.readFileSync(improvementsPath, 'utf8'))
|
|
164
|
-
: { improvements: [] };
|
|
165
|
-
|
|
166
|
-
// Flatten and apply cognitive transformation
|
|
167
|
-
const rawLessons = [
|
|
168
|
-
...(mistakes.mistakes || []).map(m => ({
|
|
169
|
-
id: m.id,
|
|
170
|
-
pattern: m.pattern,
|
|
171
|
-
message: m.message,
|
|
172
|
-
severity: m.severity || 'WARNING',
|
|
173
|
-
hitCount: m.hitCount || 0,
|
|
174
|
-
lastHit: m.lastHit,
|
|
175
|
-
excludePaths: m.excludePaths,
|
|
176
|
-
tags: m.tags,
|
|
177
|
-
autoFix: m.autoFix, // Preserve autoFix for Fix All
|
|
178
|
-
type: 'mistake', // Mark for tracking
|
|
179
|
-
})),
|
|
180
|
-
...(improvements.improvements || []).map(i => ({
|
|
181
|
-
id: i.id,
|
|
182
|
-
pattern: i.pattern,
|
|
183
|
-
message: i.message,
|
|
184
|
-
severity: 'INFO', // Improvements are informational
|
|
185
|
-
hitCount: i.appliedCount || 0,
|
|
186
|
-
lastHit: i.lastApplied,
|
|
187
|
-
excludePaths: i.excludePaths, // PRESERVE excludePaths!
|
|
188
|
-
tags: i.tags,
|
|
189
|
-
type: 'improvement', // Mark for tracking
|
|
190
|
-
}))
|
|
191
|
-
];
|
|
192
|
-
|
|
193
|
-
// Apply cognitive transformation to create agent-level lessons
|
|
194
|
-
const lessons = rawLessons.map(createCognitiveLesson);
|
|
195
|
-
|
|
196
|
-
return { lessons, version: 4.0 };
|
|
197
|
-
}
|
|
152
|
+
// Use unified v6 loader (handles all versions internally)
|
|
153
|
+
const knowledge = loadKnowledgeV6();
|
|
154
|
+
|
|
155
|
+
// Transform lessons for cognitive processing
|
|
156
|
+
const lessons = knowledge.lessons.map(lesson => {
|
|
157
|
+
// Apply cognitive transformation if not already present
|
|
158
|
+
if (!lesson.cognitive) {
|
|
159
|
+
return createCognitiveLesson(lesson);
|
|
160
|
+
}
|
|
161
|
+
return lesson;
|
|
162
|
+
});
|
|
198
163
|
|
|
199
|
-
|
|
200
|
-
if (!fs.existsSync(LESSONS_PATH)) {
|
|
201
|
-
return { lessons: [], version: 1 };
|
|
202
|
-
}
|
|
203
|
-
const content = fs.readFileSync(LESSONS_PATH, "utf8");
|
|
204
|
-
return yaml.load(content) || { lessons: [], version: 1 };
|
|
164
|
+
return { lessons, version: knowledge.version || 6.0 };
|
|
205
165
|
} catch (error) {
|
|
206
166
|
if (DEBUG) console.error("Error loading knowledge:", error.message);
|
|
207
167
|
return { lessons: [], version: 1 };
|
|
@@ -209,48 +169,20 @@ export function loadKnowledge() {
|
|
|
209
169
|
}
|
|
210
170
|
|
|
211
171
|
/**
|
|
212
|
-
* Save knowledge base to
|
|
213
|
-
* Supports both v3.x and v4.x formats
|
|
172
|
+
* Save knowledge base to unified v6 format
|
|
214
173
|
* @param {{ lessons: Array, version?: number }} data
|
|
215
174
|
*/
|
|
216
175
|
export function saveKnowledge(data) {
|
|
217
176
|
try {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
});
|
|
226
|
-
const improvements = data.lessons.filter(l => l.type === 'improvement').map(i => {
|
|
227
|
-
const { type, severity, ...rest } = i; // Remove type and severity
|
|
228
|
-
return {
|
|
229
|
-
...rest,
|
|
230
|
-
excludePaths: i.excludePaths || [], // PRESERVE excludePaths explicitly
|
|
231
|
-
appliedCount: rest.hitCount,
|
|
232
|
-
lastApplied: rest.lastHit,
|
|
233
|
-
};
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
// Save mistakes
|
|
237
|
-
if (mistakes.length > 0) {
|
|
238
|
-
const mistakesPath = path.join(KNOWLEDGE_DIR, 'mistakes.yaml');
|
|
239
|
-
const mistakesData = yaml.dump({ version: 4.0, mistakes }, { lineWidth: -1 });
|
|
240
|
-
fs.writeFileSync(mistakesPath, mistakesData, 'utf8');
|
|
241
|
-
}
|
|
177
|
+
// Convert to v6 format
|
|
178
|
+
const knowledge = {
|
|
179
|
+
version: 6.0,
|
|
180
|
+
createdAt: data.createdAt || new Date().toISOString(),
|
|
181
|
+
updatedAt: new Date().toISOString(),
|
|
182
|
+
lessons: data.lessons
|
|
183
|
+
};
|
|
242
184
|
|
|
243
|
-
|
|
244
|
-
if (improvements.length > 0) {
|
|
245
|
-
const improvementsPath = path.join(KNOWLEDGE_DIR, 'improvements.yaml');
|
|
246
|
-
const improvementsData = yaml.dump({ version: 4.0, improvements }, { lineWidth: -1 });
|
|
247
|
-
fs.writeFileSync(improvementsPath, improvementsData, 'utf8');
|
|
248
|
-
}
|
|
249
|
-
} else {
|
|
250
|
-
// v3.x: Save to lessons-learned.yaml
|
|
251
|
-
const str = yaml.dump(data, { lineWidth: -1 });
|
|
252
|
-
fs.writeFileSync(LESSONS_PATH, str, "utf8");
|
|
253
|
-
}
|
|
185
|
+
saveKnowledgeV6(knowledge);
|
|
254
186
|
} catch (error) {
|
|
255
187
|
console.error("❌ Failed to save knowledge base:", error.message);
|
|
256
188
|
}
|
|
@@ -42,8 +42,21 @@ const projectRoot = findProjectRoot();
|
|
|
42
42
|
const dashboardPath = path.join(__dirname, '..', 'dashboard');
|
|
43
43
|
const knowledgeDir = path.join(projectRoot, '.agent', 'knowledge');
|
|
44
44
|
|
|
45
|
-
// Read lessons count
|
|
45
|
+
// Read lessons count - prioritize v6 format
|
|
46
46
|
function getLessonsCount() {
|
|
47
|
+
// Priority 1: v6 unified format
|
|
48
|
+
const knowledgePath = path.join(knowledgeDir, 'knowledge.yaml');
|
|
49
|
+
if (fs.existsSync(knowledgePath)) {
|
|
50
|
+
try {
|
|
51
|
+
const content = fs.readFileSync(knowledgePath, 'utf-8');
|
|
52
|
+
const data = yaml.load(content);
|
|
53
|
+
return data.lessons?.length || 0;
|
|
54
|
+
} catch (e) {
|
|
55
|
+
// Fall through to legacy
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Priority 2: Legacy files
|
|
47
60
|
let count = 0;
|
|
48
61
|
const files = ['lessons-learned.yaml', 'mistakes.yaml', 'improvements.yaml'];
|
|
49
62
|
|
|
@@ -52,7 +65,6 @@ function getLessonsCount() {
|
|
|
52
65
|
if (fs.existsSync(filePath)) {
|
|
53
66
|
try {
|
|
54
67
|
const content = fs.readFileSync(filePath, 'utf-8');
|
|
55
|
-
// Simple YAML parsing - count items in lessons/mistakes/improvements arrays
|
|
56
68
|
const matches = content.match(/^\s*-\s+id:/gm);
|
|
57
69
|
if (matches) count += matches.length;
|
|
58
70
|
} catch (e) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pikakit",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.20",
|
|
4
4
|
"description": "Enterprise-grade Agent Skill Manager with Antigravity Skills support, Progressive Disclosure detection, and semantic routing validation",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "pikakit <pikakit@gmail.com>",
|
|
@@ -1,322 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Migration Script: v3.x → v4.x Cognitive Lesson Engine
|
|
4
|
-
*
|
|
5
|
-
* This script:
|
|
6
|
-
* 1. Backs up current lessons-learned.yaml
|
|
7
|
-
* 2. Classifies lessons into mistakes vs improvements
|
|
8
|
-
* 3. Generates mistakes.yaml and improvements.yaml
|
|
9
|
-
* 4. Preserves all existing data
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import fs from 'fs';
|
|
13
|
-
import path from 'path';
|
|
14
|
-
import yaml from 'js-yaml';
|
|
15
|
-
import { fileURLToPath } from 'url';
|
|
16
|
-
|
|
17
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
18
|
-
const PROJECT_ROOT = path.join(__dirname, '../../..');
|
|
19
|
-
const KNOWLEDGE_DIR = path.join(PROJECT_ROOT, '.agent/knowledge');
|
|
20
|
-
const LESSONS_PATH = path.join(KNOWLEDGE_DIR, 'lessons-learned.yaml');
|
|
21
|
-
const BACKUP_DIR = path.join(KNOWLEDGE_DIR, '_migration_backup');
|
|
22
|
-
const MISTAKES_PATH = path.join(KNOWLEDGE_DIR, 'mistakes.yaml');
|
|
23
|
-
const IMPROVEMENTS_PATH = path.join(KNOWLEDGE_DIR, 'improvements.yaml');
|
|
24
|
-
|
|
25
|
-
// ============================================================================
|
|
26
|
-
// STEP 1: Backup Current Data
|
|
27
|
-
// ============================================================================
|
|
28
|
-
|
|
29
|
-
function backupCurrentLessons() {
|
|
30
|
-
console.log('📦 Step 1: Backing up current lessons...');
|
|
31
|
-
|
|
32
|
-
if (!fs.existsSync(LESSONS_PATH)) {
|
|
33
|
-
console.log('⚠️ No lessons-learned.yaml found, skipping backup');
|
|
34
|
-
return null;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// Create backup directory
|
|
38
|
-
fs.mkdirSync(BACKUP_DIR, { recursive: true });
|
|
39
|
-
|
|
40
|
-
// Backup with timestamp
|
|
41
|
-
const timestamp = new Date().toISOString().replace(/:/g, '-');
|
|
42
|
-
const backupPath = path.join(BACKUP_DIR, `lessons-learned_${timestamp}.yaml`);
|
|
43
|
-
|
|
44
|
-
fs.copyFileSync(LESSONS_PATH, backupPath);
|
|
45
|
-
console.log(`✅ Backup created: ${backupPath}`);
|
|
46
|
-
|
|
47
|
-
// Load and return data
|
|
48
|
-
const content = fs.readFileSync(LESSONS_PATH, 'utf8');
|
|
49
|
-
return yaml.load(content);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// ============================================================================
|
|
53
|
-
// STEP 2: Classification Logic
|
|
54
|
-
// ============================================================================
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Auto-classify lesson as mistake or improvement
|
|
58
|
-
* Conservative: default to mistake (safer assumption)
|
|
59
|
-
*/
|
|
60
|
-
function classifyLesson(lesson) {
|
|
61
|
-
const message = lesson.message.toLowerCase();
|
|
62
|
-
|
|
63
|
-
// Keywords that indicate improvement/best practice
|
|
64
|
-
const improvementKeywords = [
|
|
65
|
-
'use ', 'always use', 'prefer ', 'should use',
|
|
66
|
-
'best practice', 'recommended', 'instead use',
|
|
67
|
-
'better to', 'proper way', 'correct approach'
|
|
68
|
-
];
|
|
69
|
-
|
|
70
|
-
// Keywords that indicate mistake/anti-pattern
|
|
71
|
-
const mistakeKeywords = [
|
|
72
|
-
'never', 'avoid', 'don\'t', 'do not', 'incorrect',
|
|
73
|
-
'wrong', 'bad', 'conflicts', 'causes', 'breaks'
|
|
74
|
-
];
|
|
75
|
-
|
|
76
|
-
const hasMistakeKeyword = mistakeKeywords.some(kw => message.includes(kw));
|
|
77
|
-
const hasImprovementKeyword = improvementKeywords.some(kw => message.includes(kw));
|
|
78
|
-
|
|
79
|
-
// Both keywords present - need manual review
|
|
80
|
-
if (hasMistakeKeyword && hasImprovementKeyword) {
|
|
81
|
-
return 'mixed';
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// Clear improvement
|
|
85
|
-
if (hasImprovementKeyword && !hasMistakeKeyword) {
|
|
86
|
-
return 'improvement';
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// Default: mistake (conservative)
|
|
90
|
-
return 'mistake';
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Extract tags from lesson data
|
|
95
|
-
* Tags used for grouping into Cognitive Lessons
|
|
96
|
-
*/
|
|
97
|
-
function extractTags(lesson) {
|
|
98
|
-
const tags = new Set();
|
|
99
|
-
|
|
100
|
-
// Add category as tag
|
|
101
|
-
if (lesson.category) {
|
|
102
|
-
tags.add(lesson.category);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// Extract from pattern
|
|
106
|
-
const pattern = lesson.pattern.toLowerCase();
|
|
107
|
-
if (pattern.includes('select')) tags.add('cli-navigation');
|
|
108
|
-
if (pattern.includes('menu')) tags.add('ux');
|
|
109
|
-
if (pattern.includes('rename') || pattern.includes('move') || pattern.includes('rebrand')) {
|
|
110
|
-
tags.add('file-safety');
|
|
111
|
-
tags.add('rebranding');
|
|
112
|
-
}
|
|
113
|
-
if (pattern.includes('import')) tags.add('imports');
|
|
114
|
-
if (pattern.includes('recursive')) tags.add('architecture');
|
|
115
|
-
|
|
116
|
-
// Extract from message
|
|
117
|
-
const message = lesson.message.toLowerCase();
|
|
118
|
-
if (message.includes('esc')) tags.add('cli-navigation');
|
|
119
|
-
if (message.includes('clack')) tags.add('clack-framework');
|
|
120
|
-
if (message.includes('menu')) tags.add('ux');
|
|
121
|
-
if (message.includes('security')) tags.add('security');
|
|
122
|
-
if (message.includes('performance')) tags.add('performance');
|
|
123
|
-
|
|
124
|
-
return Array.from(tags);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Split mixed lessons into mistake + improvement
|
|
129
|
-
*/
|
|
130
|
-
function splitMixedLesson(lesson) {
|
|
131
|
-
const message = lesson.message;
|
|
132
|
-
|
|
133
|
-
// Try to extract both parts
|
|
134
|
-
// Pattern: "NEVER X. Use Y instead"
|
|
135
|
-
const neverMatch = message.match(/(NEVER|Don't|Avoid)\s+([^.]+)\./i);
|
|
136
|
-
const useMatch = message.match(/(Use|Always use|Instead use)\s+([^.]+)/i);
|
|
137
|
-
|
|
138
|
-
const mistake = {
|
|
139
|
-
...lesson,
|
|
140
|
-
id: lesson.id.replace('LEARN', 'MISTAKE'),
|
|
141
|
-
title: neverMatch ? neverMatch[0] : message.split('.')[0],
|
|
142
|
-
message: neverMatch ? neverMatch[0] : message.split('.')[0],
|
|
143
|
-
};
|
|
144
|
-
|
|
145
|
-
let improvement = null;
|
|
146
|
-
if (useMatch) {
|
|
147
|
-
improvement = {
|
|
148
|
-
id: lesson.id.replace('LEARN', 'IMPROVE'),
|
|
149
|
-
title: useMatch[0],
|
|
150
|
-
message: useMatch[0],
|
|
151
|
-
pattern: lesson.pattern,
|
|
152
|
-
priority: lesson.severity === 'ERROR' ? 'HIGH' : 'MEDIUM',
|
|
153
|
-
tags: extractTags(lesson),
|
|
154
|
-
added: lesson.addedAt,
|
|
155
|
-
appliedCount: 0,
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
return { mistake, improvement };
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// ============================================================================
|
|
163
|
-
// STEP 3: Migration Logic
|
|
164
|
-
// ============================================================================
|
|
165
|
-
|
|
166
|
-
function migrateToV4(oldData) {
|
|
167
|
-
console.log('\n🔄 Step 2: Classifying lessons...');
|
|
168
|
-
|
|
169
|
-
const mistakes = [];
|
|
170
|
-
const improvements = [];
|
|
171
|
-
const needsReview = [];
|
|
172
|
-
|
|
173
|
-
oldData.lessons.forEach((lesson, index) => {
|
|
174
|
-
const classification = classifyLesson(lesson);
|
|
175
|
-
const tags = extractTags(lesson);
|
|
176
|
-
|
|
177
|
-
console.log(` ${lesson.id}: ${classification} (tags: ${tags.join(', ')})`);
|
|
178
|
-
|
|
179
|
-
if (classification === 'mistake') {
|
|
180
|
-
mistakes.push({
|
|
181
|
-
id: lesson.id.replace('LEARN', 'MISTAKE'),
|
|
182
|
-
title: lesson.message.split('.')[0], // First sentence as title
|
|
183
|
-
pattern: lesson.pattern,
|
|
184
|
-
message: lesson.message,
|
|
185
|
-
severity: lesson.severity,
|
|
186
|
-
tags,
|
|
187
|
-
context: lesson.category,
|
|
188
|
-
hitCount: lesson.hitCount || 0,
|
|
189
|
-
lastHit: lesson.lastHit,
|
|
190
|
-
added: lesson.addedAt,
|
|
191
|
-
source: lesson.source,
|
|
192
|
-
excludePaths: lesson.excludePaths || [],
|
|
193
|
-
});
|
|
194
|
-
} else if (classification === 'improvement') {
|
|
195
|
-
improvements.push({
|
|
196
|
-
id: lesson.id.replace('LEARN', 'IMPROVE'),
|
|
197
|
-
title: lesson.message.split('.')[0],
|
|
198
|
-
pattern: lesson.pattern,
|
|
199
|
-
message: lesson.message,
|
|
200
|
-
priority: lesson.severity === 'ERROR' ? 'HIGH' : 'MEDIUM',
|
|
201
|
-
tags,
|
|
202
|
-
added: lesson.addedAt,
|
|
203
|
-
appliedCount: lesson.hitCount || 0,
|
|
204
|
-
lastApplied: lesson.lastHit,
|
|
205
|
-
source: lesson.source,
|
|
206
|
-
});
|
|
207
|
-
} else {
|
|
208
|
-
// Mixed - split into both
|
|
209
|
-
const { mistake, improvement } = splitMixedLesson(lesson);
|
|
210
|
-
mistakes.push({
|
|
211
|
-
...mistake,
|
|
212
|
-
severity: lesson.severity,
|
|
213
|
-
tags,
|
|
214
|
-
hitCount: lesson.hitCount || 0,
|
|
215
|
-
lastHit: lesson.lastHit,
|
|
216
|
-
added: lesson.addedAt,
|
|
217
|
-
source: lesson.source,
|
|
218
|
-
excludePaths: lesson.excludePaths || [],
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
if (improvement) {
|
|
222
|
-
improvements.push({
|
|
223
|
-
...improvement,
|
|
224
|
-
source: lesson.source,
|
|
225
|
-
});
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
needsReview.push({
|
|
229
|
-
...lesson,
|
|
230
|
-
reason: 'Mixed mistake + improvement keywords, auto-split but needs verification',
|
|
231
|
-
});
|
|
232
|
-
}
|
|
233
|
-
});
|
|
234
|
-
|
|
235
|
-
console.log(`\n✅ Classification complete:`);
|
|
236
|
-
console.log(` ${mistakes.length} mistakes`);
|
|
237
|
-
console.log(` ${improvements.length} improvements`);
|
|
238
|
-
console.log(` ${needsReview.length} need manual review`);
|
|
239
|
-
|
|
240
|
-
return { mistakes, improvements, needsReview };
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
// ============================================================================
|
|
244
|
-
// STEP 4: Save New Files
|
|
245
|
-
// ============================================================================
|
|
246
|
-
|
|
247
|
-
function saveYAML(filename, data) {
|
|
248
|
-
const filepath = path.join(KNOWLEDGE_DIR, filename);
|
|
249
|
-
const yamlStr = yaml.dump(data, {
|
|
250
|
-
lineWidth: -1,
|
|
251
|
-
noRefs: true,
|
|
252
|
-
sortKeys: false,
|
|
253
|
-
});
|
|
254
|
-
fs.writeFileSync(filepath, yamlStr, 'utf8');
|
|
255
|
-
console.log(`✅ Created: ${filename}`);
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
function saveNewStructure(result) {
|
|
259
|
-
console.log('\n📝 Step 3: Creating new data files...');
|
|
260
|
-
|
|
261
|
-
// Save mistakes
|
|
262
|
-
saveYAML('mistakes.yaml', {
|
|
263
|
-
version: 4.0,
|
|
264
|
-
mistakes: result.mistakes,
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
// Save improvements
|
|
268
|
-
saveYAML('improvements.yaml', {
|
|
269
|
-
version: 4.0,
|
|
270
|
-
improvements: result.improvements,
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
// Save review queue if needed
|
|
274
|
-
if (result.needsReview.length > 0) {
|
|
275
|
-
saveYAML('_needs_review.yaml', {
|
|
276
|
-
note: 'These lessons contained both mistake and improvement keywords and were auto-split. Please review.',
|
|
277
|
-
items: result.needsReview,
|
|
278
|
-
});
|
|
279
|
-
console.log(`⚠️ ${result.needsReview.length} items saved to _needs_review.yaml`);
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
// ============================================================================
|
|
284
|
-
// MAIN MIGRATION
|
|
285
|
-
// ============================================================================
|
|
286
|
-
|
|
287
|
-
async function main() {
|
|
288
|
-
console.log('🧠 Cognitive Lesson Engine v4.x Migration\n');
|
|
289
|
-
console.log('This script will transform your lessons into the new architecture.');
|
|
290
|
-
console.log('All existing data will be preserved in backups.\n');
|
|
291
|
-
|
|
292
|
-
try {
|
|
293
|
-
// Step 1: Backup
|
|
294
|
-
const oldData = backupCurrentLessons();
|
|
295
|
-
|
|
296
|
-
if (!oldData || !oldData.lessons || oldData.lessons.length === 0) {
|
|
297
|
-
console.log('❌ No lessons found to migrate.');
|
|
298
|
-
return;
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
// Step 2: Classify
|
|
302
|
-
const result = migrateToV4(oldData);
|
|
303
|
-
|
|
304
|
-
// Step 3: Save
|
|
305
|
-
saveNewStructure(result);
|
|
306
|
-
|
|
307
|
-
// Step 4: Summary
|
|
308
|
-
console.log('\n🎉 Migration complete!');
|
|
309
|
-
console.log('\nNext steps:');
|
|
310
|
-
console.log(' 1. Review _needs_review.yaml (if exists)');
|
|
311
|
-
console.log(' 2. Test with: node packages/cli/lib/ui/index.js');
|
|
312
|
-
console.log(' 3. Original file preserved in _migration_backup/');
|
|
313
|
-
console.log('\n✅ You can now use the Cognitive Lesson Engine!');
|
|
314
|
-
|
|
315
|
-
} catch (error) {
|
|
316
|
-
console.error('\n❌ Migration failed:', error.message);
|
|
317
|
-
console.error(error.stack);
|
|
318
|
-
process.exit(1);
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
main();
|