pikakit 3.0.4 → 3.0.5
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/lib/agent-cli/dashboard/dashboard_server.js +1 -1
- package/lib/agent-cli/lib/ab-testing.js +364 -0
- package/lib/agent-cli/lib/causality-engine.js +331 -0
- package/lib/agent-cli/lib/config.js +1 -1
- package/lib/agent-cli/lib/dashboard-data.js +380 -0
- package/lib/agent-cli/lib/metrics-collector.js +410 -0
- package/lib/agent-cli/lib/reinforcement.js +299 -0
- package/lib/agent-cli/lib/skill-generator.js +379 -0
- package/package.json +1 -1
|
@@ -67,7 +67,7 @@ async function loadModules() {
|
|
|
67
67
|
causalityEngine = await safeImport(path.join(libPath, 'causality-engine.js'));
|
|
68
68
|
skillGenerator = await safeImport(path.join(libPath, 'skill-generator.js'));
|
|
69
69
|
abTesting = await safeImport(path.join(libPath, 'ab-testing.js'));
|
|
70
|
-
reinforcement = await safeImport(path.join(libPath, 'reinforcement
|
|
70
|
+
reinforcement = await safeImport(path.join(libPath, 'reinforcement.js'));
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
// ============================================================================
|
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A/B Testing v7.0 - Pattern Experiment Framework
|
|
3
|
+
*
|
|
4
|
+
* Manages A/B tests for comparing pattern effectiveness.
|
|
5
|
+
* Tracks experiment results and determines winners.
|
|
6
|
+
*
|
|
7
|
+
* @version 7.0.0
|
|
8
|
+
* @author PikaKit
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import fs from 'fs';
|
|
12
|
+
import path from 'path';
|
|
13
|
+
import { fileURLToPath } from 'url';
|
|
14
|
+
|
|
15
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
16
|
+
const __dirname = path.dirname(__filename);
|
|
17
|
+
|
|
18
|
+
// Find project root
|
|
19
|
+
function findProjectRoot() {
|
|
20
|
+
let dir = process.cwd();
|
|
21
|
+
while (dir !== path.dirname(dir)) {
|
|
22
|
+
if (fs.existsSync(path.join(dir, '.agent'))) return dir;
|
|
23
|
+
if (fs.existsSync(path.join(dir, 'package.json'))) return dir;
|
|
24
|
+
dir = path.dirname(dir);
|
|
25
|
+
}
|
|
26
|
+
return process.cwd();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const projectRoot = findProjectRoot();
|
|
30
|
+
const METRICS_DIR = path.join(projectRoot, '.agent', 'metrics');
|
|
31
|
+
const AB_TESTS_FILE = path.join(METRICS_DIR, 'ab-tests.json');
|
|
32
|
+
|
|
33
|
+
// ============================================================================
|
|
34
|
+
// DATA STRUCTURES
|
|
35
|
+
// ============================================================================
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* A/B Test structure:
|
|
39
|
+
* {
|
|
40
|
+
* id: string,
|
|
41
|
+
* name: string,
|
|
42
|
+
* description: string,
|
|
43
|
+
* variantA: { id: string, patternId: string, description: string },
|
|
44
|
+
* variantB: { id: string, patternId: string, description: string },
|
|
45
|
+
* status: 'running' | 'completed' | 'paused',
|
|
46
|
+
* startDate: string,
|
|
47
|
+
* endDate: string | null,
|
|
48
|
+
* results: {
|
|
49
|
+
* variantA: { impressions: number, successes: number },
|
|
50
|
+
* variantB: { impressions: number, successes: number }
|
|
51
|
+
* },
|
|
52
|
+
* winner: 'A' | 'B' | null,
|
|
53
|
+
* confidence: number
|
|
54
|
+
* }
|
|
55
|
+
*/
|
|
56
|
+
|
|
57
|
+
// ============================================================================
|
|
58
|
+
// DATA LOADERS
|
|
59
|
+
// ============================================================================
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Ensure metrics directory exists
|
|
63
|
+
*/
|
|
64
|
+
function ensureMetricsDir() {
|
|
65
|
+
if (!fs.existsSync(METRICS_DIR)) {
|
|
66
|
+
fs.mkdirSync(METRICS_DIR, { recursive: true });
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Load A/B tests data
|
|
72
|
+
*/
|
|
73
|
+
function loadABTestsData() {
|
|
74
|
+
if (fs.existsSync(AB_TESTS_FILE)) {
|
|
75
|
+
try {
|
|
76
|
+
return JSON.parse(fs.readFileSync(AB_TESTS_FILE, 'utf8'));
|
|
77
|
+
} catch (e) {
|
|
78
|
+
return getDefaultData();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return getDefaultData();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Save A/B tests data
|
|
86
|
+
*/
|
|
87
|
+
function saveABTestsData(data) {
|
|
88
|
+
ensureMetricsDir();
|
|
89
|
+
data.lastUpdated = new Date().toISOString();
|
|
90
|
+
fs.writeFileSync(AB_TESTS_FILE, JSON.stringify(data, null, 2));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Get default data structure
|
|
95
|
+
*/
|
|
96
|
+
function getDefaultData() {
|
|
97
|
+
return {
|
|
98
|
+
tests: [],
|
|
99
|
+
lastUpdated: new Date().toISOString(),
|
|
100
|
+
totalCompleted: 0
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ============================================================================
|
|
105
|
+
// TEST MANAGEMENT
|
|
106
|
+
// ============================================================================
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Create a new A/B test
|
|
110
|
+
*/
|
|
111
|
+
export function createTest(name, description, variantA, variantB) {
|
|
112
|
+
const data = loadABTestsData();
|
|
113
|
+
|
|
114
|
+
const test = {
|
|
115
|
+
id: `test-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
|
|
116
|
+
name,
|
|
117
|
+
description,
|
|
118
|
+
variantA: {
|
|
119
|
+
id: 'A',
|
|
120
|
+
...variantA
|
|
121
|
+
},
|
|
122
|
+
variantB: {
|
|
123
|
+
id: 'B',
|
|
124
|
+
...variantB
|
|
125
|
+
},
|
|
126
|
+
status: 'running',
|
|
127
|
+
startDate: new Date().toISOString(),
|
|
128
|
+
endDate: null,
|
|
129
|
+
results: {
|
|
130
|
+
variantA: { impressions: 0, successes: 0 },
|
|
131
|
+
variantB: { impressions: 0, successes: 0 }
|
|
132
|
+
},
|
|
133
|
+
winner: null,
|
|
134
|
+
confidence: 0
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
data.tests.push(test);
|
|
138
|
+
saveABTestsData(data);
|
|
139
|
+
|
|
140
|
+
return test;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Record an impression for a variant
|
|
145
|
+
*/
|
|
146
|
+
export function recordImpression(testId, variant, success = false) {
|
|
147
|
+
const data = loadABTestsData();
|
|
148
|
+
const test = data.tests.find(t => t.id === testId);
|
|
149
|
+
|
|
150
|
+
if (!test || test.status !== 'running') {
|
|
151
|
+
return { success: false, error: 'Test not found or not running' };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const variantKey = variant.toUpperCase() === 'A' ? 'variantA' : 'variantB';
|
|
155
|
+
test.results[variantKey].impressions += 1;
|
|
156
|
+
|
|
157
|
+
if (success) {
|
|
158
|
+
test.results[variantKey].successes += 1;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Recalculate confidence
|
|
162
|
+
updateTestConfidence(test);
|
|
163
|
+
|
|
164
|
+
// Auto-complete if enough data
|
|
165
|
+
const minImpressions = 30;
|
|
166
|
+
if (test.results.variantA.impressions >= minImpressions &&
|
|
167
|
+
test.results.variantB.impressions >= minImpressions &&
|
|
168
|
+
test.confidence >= 0.95) {
|
|
169
|
+
completeTest(testId);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
saveABTestsData(data);
|
|
173
|
+
|
|
174
|
+
return { success: true, test };
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Update test confidence using simple statistical calculation
|
|
179
|
+
*/
|
|
180
|
+
function updateTestConfidence(test) {
|
|
181
|
+
const rA = test.results.variantA;
|
|
182
|
+
const rB = test.results.variantB;
|
|
183
|
+
|
|
184
|
+
if (rA.impressions === 0 || rB.impressions === 0) {
|
|
185
|
+
test.confidence = 0;
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const successRateA = rA.successes / rA.impressions;
|
|
190
|
+
const successRateB = rB.successes / rB.impressions;
|
|
191
|
+
|
|
192
|
+
// Simple confidence calculation based on sample size and difference
|
|
193
|
+
const n = Math.min(rA.impressions, rB.impressions);
|
|
194
|
+
const diff = Math.abs(successRateA - successRateB);
|
|
195
|
+
|
|
196
|
+
// Higher sample size and larger difference = higher confidence
|
|
197
|
+
test.confidence = Math.min(
|
|
198
|
+
(n / 50) * (diff / 0.2), // Scale by sample size and effect size
|
|
199
|
+
0.99
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
// Determine preliminary winner
|
|
203
|
+
if (test.confidence >= 0.8) {
|
|
204
|
+
test.winner = successRateA > successRateB ? 'A' : 'B';
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Complete a test
|
|
210
|
+
*/
|
|
211
|
+
export function completeTest(testId) {
|
|
212
|
+
const data = loadABTestsData();
|
|
213
|
+
const test = data.tests.find(t => t.id === testId);
|
|
214
|
+
|
|
215
|
+
if (!test) {
|
|
216
|
+
return { success: false, error: 'Test not found' };
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
test.status = 'completed';
|
|
220
|
+
test.endDate = new Date().toISOString();
|
|
221
|
+
|
|
222
|
+
// Determine final winner
|
|
223
|
+
const rA = test.results.variantA;
|
|
224
|
+
const rB = test.results.variantB;
|
|
225
|
+
|
|
226
|
+
if (rA.impressions > 0 && rB.impressions > 0) {
|
|
227
|
+
const successRateA = rA.successes / rA.impressions;
|
|
228
|
+
const successRateB = rB.successes / rB.impressions;
|
|
229
|
+
test.winner = successRateA >= successRateB ? 'A' : 'B';
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
data.totalCompleted = (data.totalCompleted || 0) + 1;
|
|
233
|
+
saveABTestsData(data);
|
|
234
|
+
|
|
235
|
+
return { success: true, test };
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Pause a test
|
|
240
|
+
*/
|
|
241
|
+
export function pauseTest(testId) {
|
|
242
|
+
const data = loadABTestsData();
|
|
243
|
+
const test = data.tests.find(t => t.id === testId);
|
|
244
|
+
|
|
245
|
+
if (!test) {
|
|
246
|
+
return { success: false, error: 'Test not found' };
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
test.status = 'paused';
|
|
250
|
+
saveABTestsData(data);
|
|
251
|
+
|
|
252
|
+
return { success: true, test };
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Resume a test
|
|
257
|
+
*/
|
|
258
|
+
export function resumeTest(testId) {
|
|
259
|
+
const data = loadABTestsData();
|
|
260
|
+
const test = data.tests.find(t => t.id === testId);
|
|
261
|
+
|
|
262
|
+
if (!test || test.status === 'completed') {
|
|
263
|
+
return { success: false, error: 'Test not found or already completed' };
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
test.status = 'running';
|
|
267
|
+
saveABTestsData(data);
|
|
268
|
+
|
|
269
|
+
return { success: true, test };
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// ============================================================================
|
|
273
|
+
// QUERIES
|
|
274
|
+
// ============================================================================
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Get all active tests
|
|
278
|
+
*/
|
|
279
|
+
export function getActiveTests() {
|
|
280
|
+
const data = loadABTestsData();
|
|
281
|
+
return data.tests.filter(t => t.status === 'running');
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Get completed tests
|
|
286
|
+
*/
|
|
287
|
+
export function getCompletedTests() {
|
|
288
|
+
const data = loadABTestsData();
|
|
289
|
+
return data.tests.filter(t => t.status === 'completed');
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Get all tests
|
|
294
|
+
*/
|
|
295
|
+
export function getAllTests() {
|
|
296
|
+
const data = loadABTestsData();
|
|
297
|
+
return data.tests;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Get test by ID
|
|
302
|
+
*/
|
|
303
|
+
export function getTest(testId) {
|
|
304
|
+
const data = loadABTestsData();
|
|
305
|
+
return data.tests.find(t => t.id === testId);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Get A/B testing statistics
|
|
310
|
+
*/
|
|
311
|
+
export function getABTestStats() {
|
|
312
|
+
const data = loadABTestsData();
|
|
313
|
+
const active = data.tests.filter(t => t.status === 'running');
|
|
314
|
+
const completed = data.tests.filter(t => t.status === 'completed');
|
|
315
|
+
|
|
316
|
+
// Calculate win rate for variant A vs B
|
|
317
|
+
let aWins = 0;
|
|
318
|
+
let bWins = 0;
|
|
319
|
+
|
|
320
|
+
for (const test of completed) {
|
|
321
|
+
if (test.winner === 'A') aWins++;
|
|
322
|
+
else if (test.winner === 'B') bWins++;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
return {
|
|
326
|
+
running: active.length,
|
|
327
|
+
completed: completed.length,
|
|
328
|
+
total: data.tests.length,
|
|
329
|
+
variantAWinRate: completed.length > 0 ? Math.round((aWins / completed.length) * 100) : 0,
|
|
330
|
+
variantBWinRate: completed.length > 0 ? Math.round((bWins / completed.length) * 100) : 0
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Select variant for a test (randomized)
|
|
336
|
+
*/
|
|
337
|
+
export function selectVariant(testId) {
|
|
338
|
+
const test = getTest(testId);
|
|
339
|
+
|
|
340
|
+
if (!test || test.status !== 'running') {
|
|
341
|
+
return null;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Simple 50/50 split
|
|
345
|
+
return Math.random() < 0.5 ? 'A' : 'B';
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// ============================================================================
|
|
349
|
+
// EXPORTS
|
|
350
|
+
// ============================================================================
|
|
351
|
+
|
|
352
|
+
export default {
|
|
353
|
+
createTest,
|
|
354
|
+
recordImpression,
|
|
355
|
+
completeTest,
|
|
356
|
+
pauseTest,
|
|
357
|
+
resumeTest,
|
|
358
|
+
getActiveTests,
|
|
359
|
+
getCompletedTests,
|
|
360
|
+
getAllTests,
|
|
361
|
+
getTest,
|
|
362
|
+
getABTestStats,
|
|
363
|
+
selectVariant
|
|
364
|
+
};
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Causality Engine v7.0 - Pattern Analysis
|
|
3
|
+
*
|
|
4
|
+
* Analyzes cause-effect patterns from lessons and errors.
|
|
5
|
+
* Provides root cause analysis and pattern detection.
|
|
6
|
+
*
|
|
7
|
+
* @version 7.0.0
|
|
8
|
+
* @author PikaKit
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import fs from 'fs';
|
|
12
|
+
import path from 'path';
|
|
13
|
+
import { fileURLToPath } from 'url';
|
|
14
|
+
|
|
15
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
16
|
+
const __dirname = path.dirname(__filename);
|
|
17
|
+
|
|
18
|
+
// Find project root
|
|
19
|
+
function findProjectRoot() {
|
|
20
|
+
let dir = process.cwd();
|
|
21
|
+
while (dir !== path.dirname(dir)) {
|
|
22
|
+
if (fs.existsSync(path.join(dir, '.agent'))) return dir;
|
|
23
|
+
if (fs.existsSync(path.join(dir, 'package.json'))) return dir;
|
|
24
|
+
dir = path.dirname(dir);
|
|
25
|
+
}
|
|
26
|
+
return process.cwd();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const projectRoot = findProjectRoot();
|
|
30
|
+
const KNOWLEDGE_DIR = path.join(projectRoot, '.agent', 'knowledge');
|
|
31
|
+
const PATTERNS_FILE = path.join(KNOWLEDGE_DIR, 'causal-patterns.json');
|
|
32
|
+
|
|
33
|
+
// ============================================================================
|
|
34
|
+
// PATTERN DATA STRUCTURES
|
|
35
|
+
// ============================================================================
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Pattern structure:
|
|
39
|
+
* {
|
|
40
|
+
* id: string,
|
|
41
|
+
* cause: string, // What triggered the error
|
|
42
|
+
* effect: string, // What was the result
|
|
43
|
+
* resolution: string, // How it was fixed
|
|
44
|
+
* confidence: number, // 0-1 confidence score
|
|
45
|
+
* occurrences: number, // How many times seen
|
|
46
|
+
* lastSeen: string, // ISO timestamp
|
|
47
|
+
* category: string, // Category of pattern
|
|
48
|
+
* tags: string[] // Related tags
|
|
49
|
+
* }
|
|
50
|
+
*/
|
|
51
|
+
|
|
52
|
+
// ============================================================================
|
|
53
|
+
// DATA LOADERS
|
|
54
|
+
// ============================================================================
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Load lessons from knowledge base
|
|
58
|
+
*/
|
|
59
|
+
function loadLessons() {
|
|
60
|
+
const paths = [
|
|
61
|
+
path.join(KNOWLEDGE_DIR, 'lessons-learned.json'),
|
|
62
|
+
path.join(KNOWLEDGE_DIR, 'lessons-learned.yaml')
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
for (const filePath of paths) {
|
|
66
|
+
if (fs.existsSync(filePath)) {
|
|
67
|
+
try {
|
|
68
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
69
|
+
if (filePath.endsWith('.json')) {
|
|
70
|
+
const data = JSON.parse(content);
|
|
71
|
+
return data.lessons || [];
|
|
72
|
+
}
|
|
73
|
+
} catch (e) {
|
|
74
|
+
// Continue to next path
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return [];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Load causal patterns from file
|
|
83
|
+
*/
|
|
84
|
+
export function loadCausalPatterns() {
|
|
85
|
+
if (fs.existsSync(PATTERNS_FILE)) {
|
|
86
|
+
try {
|
|
87
|
+
const data = JSON.parse(fs.readFileSync(PATTERNS_FILE, 'utf8'));
|
|
88
|
+
return data.patterns || [];
|
|
89
|
+
} catch (e) {
|
|
90
|
+
return [];
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Generate patterns from lessons if no patterns file
|
|
95
|
+
return generatePatternsFromLessons();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Save causal patterns
|
|
100
|
+
*/
|
|
101
|
+
function saveCausalPatterns(patterns) {
|
|
102
|
+
const dir = path.dirname(PATTERNS_FILE);
|
|
103
|
+
if (!fs.existsSync(dir)) {
|
|
104
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
fs.writeFileSync(PATTERNS_FILE, JSON.stringify({
|
|
108
|
+
patterns,
|
|
109
|
+
lastUpdated: new Date().toISOString(),
|
|
110
|
+
version: '7.0.0'
|
|
111
|
+
}, null, 2));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Generate patterns from existing lessons
|
|
116
|
+
*/
|
|
117
|
+
function generatePatternsFromLessons() {
|
|
118
|
+
const lessons = loadLessons();
|
|
119
|
+
const patterns = [];
|
|
120
|
+
|
|
121
|
+
for (const lesson of lessons) {
|
|
122
|
+
const pattern = {
|
|
123
|
+
id: lesson.id || `pattern-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
|
|
124
|
+
cause: lesson.trigger || lesson.pattern || lesson.cause || 'Unknown cause',
|
|
125
|
+
effect: lesson.effect || lesson.message || 'Error occurred',
|
|
126
|
+
resolution: lesson.resolution || lesson.fix || lesson.solution || 'Apply learned fix',
|
|
127
|
+
confidence: parseFloat(lesson.confidence) || 0.7,
|
|
128
|
+
occurrences: parseInt(lesson.occurrences) || 1,
|
|
129
|
+
lastSeen: lesson.lastSeen || lesson.created || new Date().toISOString(),
|
|
130
|
+
category: lesson.category || categorizePattern(lesson),
|
|
131
|
+
tags: lesson.tags || extractTags(lesson)
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
patterns.push(pattern);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Save generated patterns
|
|
138
|
+
if (patterns.length > 0) {
|
|
139
|
+
saveCausalPatterns(patterns);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return patterns;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Categorize a pattern based on its content
|
|
147
|
+
*/
|
|
148
|
+
function categorizePattern(lesson) {
|
|
149
|
+
const content = JSON.stringify(lesson).toLowerCase();
|
|
150
|
+
|
|
151
|
+
if (content.includes('import') || content.includes('module')) return 'import-error';
|
|
152
|
+
if (content.includes('type') || content.includes('typescript')) return 'type-error';
|
|
153
|
+
if (content.includes('syntax')) return 'syntax-error';
|
|
154
|
+
if (content.includes('null') || content.includes('undefined')) return 'null-reference';
|
|
155
|
+
if (content.includes('async') || content.includes('await') || content.includes('promise')) return 'async-error';
|
|
156
|
+
if (content.includes('api') || content.includes('fetch') || content.includes('request')) return 'api-error';
|
|
157
|
+
if (content.includes('file') || content.includes('path')) return 'file-error';
|
|
158
|
+
if (content.includes('config') || content.includes('env')) return 'config-error';
|
|
159
|
+
|
|
160
|
+
return 'general';
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Extract tags from lesson content
|
|
165
|
+
*/
|
|
166
|
+
function extractTags(lesson) {
|
|
167
|
+
const tags = [];
|
|
168
|
+
const content = JSON.stringify(lesson).toLowerCase();
|
|
169
|
+
|
|
170
|
+
const tagPatterns = [
|
|
171
|
+
'javascript', 'typescript', 'react', 'node', 'npm',
|
|
172
|
+
'import', 'export', 'async', 'await', 'promise',
|
|
173
|
+
'error', 'warning', 'critical', 'fix', 'workaround'
|
|
174
|
+
];
|
|
175
|
+
|
|
176
|
+
for (const tag of tagPatterns) {
|
|
177
|
+
if (content.includes(tag)) {
|
|
178
|
+
tags.push(tag);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return tags.slice(0, 5); // Limit to 5 tags
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// ============================================================================
|
|
186
|
+
// PATTERN ANALYSIS
|
|
187
|
+
// ============================================================================
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Get pattern count
|
|
191
|
+
*/
|
|
192
|
+
export function getPatternCount() {
|
|
193
|
+
const patterns = loadCausalPatterns();
|
|
194
|
+
return patterns.length;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Analyze an error and find matching patterns
|
|
199
|
+
*/
|
|
200
|
+
export function analyzeError(errorMessage, context = {}) {
|
|
201
|
+
const patterns = loadCausalPatterns();
|
|
202
|
+
const matches = [];
|
|
203
|
+
|
|
204
|
+
const errorLower = errorMessage.toLowerCase();
|
|
205
|
+
|
|
206
|
+
for (const pattern of patterns) {
|
|
207
|
+
let score = 0;
|
|
208
|
+
|
|
209
|
+
// Check cause match
|
|
210
|
+
if (pattern.cause && errorLower.includes(pattern.cause.toLowerCase())) {
|
|
211
|
+
score += 0.4;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Check category match
|
|
215
|
+
if (pattern.category && errorLower.includes(pattern.category.replace('-', ' '))) {
|
|
216
|
+
score += 0.2;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Check tags match
|
|
220
|
+
for (const tag of pattern.tags || []) {
|
|
221
|
+
if (errorLower.includes(tag)) {
|
|
222
|
+
score += 0.1;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Boost by confidence and occurrences
|
|
227
|
+
score *= pattern.confidence || 0.5;
|
|
228
|
+
score *= Math.min(pattern.occurrences || 1, 5) / 5;
|
|
229
|
+
|
|
230
|
+
if (score > 0.1) {
|
|
231
|
+
matches.push({
|
|
232
|
+
pattern,
|
|
233
|
+
score,
|
|
234
|
+
suggestedResolution: pattern.resolution
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Sort by score descending
|
|
240
|
+
matches.sort((a, b) => b.score - a.score);
|
|
241
|
+
|
|
242
|
+
return {
|
|
243
|
+
matches: matches.slice(0, 5),
|
|
244
|
+
analyzed: true,
|
|
245
|
+
timestamp: new Date().toISOString()
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Add a new causal pattern
|
|
251
|
+
*/
|
|
252
|
+
export function addPattern(cause, effect, resolution, metadata = {}) {
|
|
253
|
+
const patterns = loadCausalPatterns();
|
|
254
|
+
|
|
255
|
+
// Check for existing similar pattern
|
|
256
|
+
const existing = patterns.find(p =>
|
|
257
|
+
p.cause.toLowerCase() === cause.toLowerCase()
|
|
258
|
+
);
|
|
259
|
+
|
|
260
|
+
if (existing) {
|
|
261
|
+
// Update existing pattern
|
|
262
|
+
existing.occurrences = (existing.occurrences || 1) + 1;
|
|
263
|
+
existing.lastSeen = new Date().toISOString();
|
|
264
|
+
existing.confidence = Math.min((existing.confidence || 0.7) + 0.05, 1);
|
|
265
|
+
saveCausalPatterns(patterns);
|
|
266
|
+
return existing;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Create new pattern
|
|
270
|
+
const newPattern = {
|
|
271
|
+
id: `pattern-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
|
|
272
|
+
cause,
|
|
273
|
+
effect,
|
|
274
|
+
resolution,
|
|
275
|
+
confidence: metadata.confidence || 0.7,
|
|
276
|
+
occurrences: 1,
|
|
277
|
+
lastSeen: new Date().toISOString(),
|
|
278
|
+
category: metadata.category || 'general',
|
|
279
|
+
tags: metadata.tags || []
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
patterns.push(newPattern);
|
|
283
|
+
saveCausalPatterns(patterns);
|
|
284
|
+
|
|
285
|
+
return newPattern;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Get patterns by category
|
|
290
|
+
*/
|
|
291
|
+
export function getPatternsByCategory(category) {
|
|
292
|
+
const patterns = loadCausalPatterns();
|
|
293
|
+
return patterns.filter(p => p.category === category);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Get pattern statistics
|
|
298
|
+
*/
|
|
299
|
+
export function getPatternStats() {
|
|
300
|
+
const patterns = loadCausalPatterns();
|
|
301
|
+
|
|
302
|
+
const categories = {};
|
|
303
|
+
let totalOccurrences = 0;
|
|
304
|
+
let avgConfidence = 0;
|
|
305
|
+
|
|
306
|
+
for (const pattern of patterns) {
|
|
307
|
+
categories[pattern.category] = (categories[pattern.category] || 0) + 1;
|
|
308
|
+
totalOccurrences += pattern.occurrences || 1;
|
|
309
|
+
avgConfidence += pattern.confidence || 0.5;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return {
|
|
313
|
+
totalPatterns: patterns.length,
|
|
314
|
+
byCategory: categories,
|
|
315
|
+
totalOccurrences,
|
|
316
|
+
avgConfidence: patterns.length > 0 ? avgConfidence / patterns.length : 0
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// ============================================================================
|
|
321
|
+
// EXPORTS
|
|
322
|
+
// ============================================================================
|
|
323
|
+
|
|
324
|
+
export default {
|
|
325
|
+
loadCausalPatterns,
|
|
326
|
+
getPatternCount,
|
|
327
|
+
analyzeError,
|
|
328
|
+
addPattern,
|
|
329
|
+
getPatternsByCategory,
|
|
330
|
+
getPatternStats
|
|
331
|
+
};
|
|
@@ -27,7 +27,7 @@ export const RULES_DIR = path.join(AGENT_DIR, "rules");
|
|
|
27
27
|
/** CLI version - read from package.json */
|
|
28
28
|
export const VERSION = (() => {
|
|
29
29
|
try { return require("../package.json").version; }
|
|
30
|
-
catch { return "3.0.
|
|
30
|
+
catch { return "3.0.5"; }
|
|
31
31
|
})();
|
|
32
32
|
|
|
33
33
|
/** Debug mode */
|