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.
@@ -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-loop.js'));
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.4"; }
30
+ catch { return "3.0.5"; }
31
31
  })();
32
32
 
33
33
  /** Debug mode */