pikakit 1.0.8 → 1.0.10

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,623 @@
1
+ /**
2
+ * AutoLearn v6.0 - Causality Engine
3
+ *
4
+ * Analyzes WHY something succeeded or failed, not just WHAT happened.
5
+ * Uses semantic diff and impact scoring to extract causal patterns.
6
+ *
7
+ * Key concepts:
8
+ * - Root Cause Analysis: What caused the outcome?
9
+ * - Semantic Diff: What actually changed (not just line diff)?
10
+ * - Impact Scoring: How significant was this change?
11
+ * - Causal Pattern: IF [context] AND [action] THEN [outcome] BECAUSE [cause]
12
+ *
13
+ * @version 6.0.0
14
+ * @author PikaKit
15
+ */
16
+
17
+ import fs from 'fs';
18
+ import path from 'path';
19
+ import { recordPatternEvent } from './metrics-collector.js';
20
+
21
+ // ============================================================================
22
+ // CONFIGURATION
23
+ // ============================================================================
24
+
25
+ const KNOWLEDGE_DIR = path.join(process.cwd(), '.agent', 'knowledge');
26
+ const CAUSAL_PATTERNS_FILE = path.join(KNOWLEDGE_DIR, 'causal-patterns.json');
27
+
28
+ // Impact thresholds
29
+ const IMPACT_THRESHOLDS = {
30
+ LOW: 0.3,
31
+ MEDIUM: 0.6,
32
+ HIGH: 0.8,
33
+ CRITICAL: 0.95
34
+ };
35
+
36
+ // ============================================================================
37
+ // CAUSAL PATTERN DATA STRUCTURE
38
+ // ============================================================================
39
+
40
+ /**
41
+ * @typedef {Object} CausalPattern
42
+ * @property {string} id - Unique pattern ID
43
+ * @property {string} pattern - Regex pattern to detect
44
+ * @property {Object} context - When this pattern applies
45
+ * @property {string} cause - Root cause explanation
46
+ * @property {string} effect - What happens if violated
47
+ * @property {number} confidence - 0.0 to 1.0
48
+ * @property {Array} evidence - Task IDs that support this pattern
49
+ * @property {string} firstDetected - ISO date
50
+ * @property {string} lastUpdated - ISO date
51
+ * @property {Object} fix - How to fix if detected
52
+ */
53
+
54
+ // ============================================================================
55
+ // ROOT CAUSE ANALYZER
56
+ // ============================================================================
57
+
58
+ /**
59
+ * Known error patterns for root cause matching
60
+ */
61
+ const ROOT_CAUSE_PATTERNS = {
62
+ // ESC key issues
63
+ 'esc_key_conflict': {
64
+ triggers: ['ESC', 'escape', 'key handling', 'Clack', 'customSelect'],
65
+ cause: 'Keyboard event handler conflict',
66
+ category: 'ui'
67
+ },
68
+
69
+ // Import issues
70
+ 'missing_import': {
71
+ triggers: ['is not defined', 'cannot find', 'ReferenceError', 'import'],
72
+ cause: 'Missing or incorrect import statement',
73
+ category: 'dependency'
74
+ },
75
+
76
+ // Type issues
77
+ 'type_mismatch': {
78
+ triggers: ['TypeError', 'undefined is not', 'null is not', 'cannot read property'],
79
+ cause: 'Type mismatch or null/undefined access',
80
+ category: 'type_safety'
81
+ },
82
+
83
+ // Async issues
84
+ 'async_timing': {
85
+ triggers: ['Promise', 'async', 'await', 'timeout', 'race condition'],
86
+ cause: 'Asynchronous timing or race condition',
87
+ category: 'async'
88
+ },
89
+
90
+ // State issues
91
+ 'state_corruption': {
92
+ triggers: ['state', 'setState', 'useState', 'undefined state', 'stale'],
93
+ cause: 'State management issue or stale state',
94
+ category: 'state'
95
+ },
96
+
97
+ // File system
98
+ 'file_not_found': {
99
+ triggers: ['ENOENT', 'no such file', 'file not found', 'cannot find module'],
100
+ cause: 'File or module not found',
101
+ category: 'filesystem'
102
+ },
103
+
104
+ // Permission
105
+ 'permission_denied': {
106
+ triggers: ['EACCES', 'permission denied', 'access denied', 'unauthorized'],
107
+ cause: 'Insufficient permissions',
108
+ category: 'security'
109
+ },
110
+
111
+ // Syntax
112
+ 'syntax_error': {
113
+ triggers: ['SyntaxError', 'Unexpected token', 'parsing error'],
114
+ cause: 'Invalid syntax',
115
+ category: 'syntax'
116
+ }
117
+ };
118
+
119
+ /**
120
+ * Analyze error message to find root cause
121
+ * @param {string} errorMessage - Error message or context
122
+ * @param {Object} context - Additional context
123
+ * @returns {Object} - Root cause analysis
124
+ */
125
+ export function analyzeRootCause(errorMessage, context = {}) {
126
+ const message = errorMessage.toLowerCase();
127
+ const results = [];
128
+
129
+ // Check against known patterns
130
+ for (const [causeId, pattern] of Object.entries(ROOT_CAUSE_PATTERNS)) {
131
+ const matchCount = pattern.triggers.filter(t =>
132
+ message.includes(t.toLowerCase())
133
+ ).length;
134
+
135
+ if (matchCount > 0) {
136
+ const confidence = Math.min(0.5 + (matchCount * 0.15), 0.95);
137
+ results.push({
138
+ causeId,
139
+ cause: pattern.cause,
140
+ category: pattern.category,
141
+ confidence,
142
+ matchedTriggers: matchCount
143
+ });
144
+ }
145
+ }
146
+
147
+ // Sort by confidence
148
+ results.sort((a, b) => b.confidence - a.confidence);
149
+
150
+ return {
151
+ primaryCause: results[0] || { causeId: 'unknown', cause: 'Unknown root cause', confidence: 0.3 },
152
+ allCauses: results,
153
+ context,
154
+ analyzedAt: new Date().toISOString()
155
+ };
156
+ }
157
+
158
+ // ============================================================================
159
+ // SEMANTIC DIFF ANALYZER
160
+ // ============================================================================
161
+
162
+ /**
163
+ * Semantic diff types
164
+ */
165
+ const DIFF_TYPES = {
166
+ ADD_IMPORT: 'add_import',
167
+ REMOVE_IMPORT: 'remove_import',
168
+ CHANGE_FUNCTION: 'change_function',
169
+ ADD_ERROR_HANDLING: 'add_error_handling',
170
+ FIX_TYPE: 'fix_type',
171
+ REPLACE_API: 'replace_api',
172
+ ADD_VALIDATION: 'add_validation',
173
+ REFACTOR: 'refactor',
174
+ OTHER: 'other'
175
+ };
176
+
177
+ /**
178
+ * Analyze what semantically changed between two versions
179
+ * @param {string} before - Content before change
180
+ * @param {string} after - Content after change
181
+ * @param {Object} context - File context
182
+ * @returns {Object} - Semantic diff analysis
183
+ */
184
+ export function analyzeSemanticDiff(before, after, context = {}) {
185
+ const changes = [];
186
+ const beforeLines = before.split('\n');
187
+ const afterLines = after.split('\n');
188
+
189
+ // Detect import changes
190
+ const beforeImports = extractImports(before);
191
+ const afterImports = extractImports(after);
192
+
193
+ const addedImports = afterImports.filter(i => !beforeImports.includes(i));
194
+ const removedImports = beforeImports.filter(i => !afterImports.includes(i));
195
+
196
+ if (addedImports.length > 0) {
197
+ changes.push({
198
+ type: DIFF_TYPES.ADD_IMPORT,
199
+ details: addedImports,
200
+ impact: calculateImpact(DIFF_TYPES.ADD_IMPORT, addedImports.length)
201
+ });
202
+ }
203
+
204
+ if (removedImports.length > 0) {
205
+ changes.push({
206
+ type: DIFF_TYPES.REMOVE_IMPORT,
207
+ details: removedImports,
208
+ impact: calculateImpact(DIFF_TYPES.REMOVE_IMPORT, removedImports.length)
209
+ });
210
+ }
211
+
212
+ // Detect error handling added
213
+ const beforeTryCatch = (before.match(/try\s*\{/g) || []).length;
214
+ const afterTryCatch = (after.match(/try\s*\{/g) || []).length;
215
+
216
+ if (afterTryCatch > beforeTryCatch) {
217
+ changes.push({
218
+ type: DIFF_TYPES.ADD_ERROR_HANDLING,
219
+ details: { added: afterTryCatch - beforeTryCatch },
220
+ impact: calculateImpact(DIFF_TYPES.ADD_ERROR_HANDLING, 1)
221
+ });
222
+ }
223
+
224
+ // Detect API replacements (e.g., customSelect → p.select)
225
+ const apiReplacements = detectAPIReplacements(before, after);
226
+ if (apiReplacements.length > 0) {
227
+ changes.push({
228
+ type: DIFF_TYPES.REPLACE_API,
229
+ details: apiReplacements,
230
+ impact: calculateImpact(DIFF_TYPES.REPLACE_API, apiReplacements.length)
231
+ });
232
+ }
233
+
234
+ // Detect type annotations added
235
+ const beforeTypes = (before.match(/:\s*\w+(\[\])?(\s*\||\s*=|\s*[,;)])/g) || []).length;
236
+ const afterTypes = (after.match(/:\s*\w+(\[\])?(\s*\||\s*=|\s*[,;)])/g) || []).length;
237
+
238
+ if (afterTypes > beforeTypes) {
239
+ changes.push({
240
+ type: DIFF_TYPES.FIX_TYPE,
241
+ details: { added: afterTypes - beforeTypes },
242
+ impact: calculateImpact(DIFF_TYPES.FIX_TYPE, afterTypes - beforeTypes)
243
+ });
244
+ }
245
+
246
+ // Calculate overall significance
247
+ const totalImpact = changes.reduce((sum, c) => sum + c.impact, 0) / Math.max(changes.length, 1);
248
+
249
+ return {
250
+ changes,
251
+ totalChanges: changes.length,
252
+ overallImpact: Math.min(totalImpact, 1.0),
253
+ linesDiff: Math.abs(afterLines.length - beforeLines.length),
254
+ context,
255
+ analyzedAt: new Date().toISOString()
256
+ };
257
+ }
258
+
259
+ /**
260
+ * Extract import statements from code
261
+ * @param {string} code - Source code
262
+ * @returns {Array<string>} - Import statements
263
+ */
264
+ function extractImports(code) {
265
+ const imports = [];
266
+
267
+ // ES6 imports
268
+ const es6Matches = code.match(/import\s+.*?\s+from\s+['"]([^'"]+)['"]/g) || [];
269
+ imports.push(...es6Matches);
270
+
271
+ // CommonJS requires
272
+ const cjsMatches = code.match(/require\s*\(['"]([^'"]+)['"]\)/g) || [];
273
+ imports.push(...cjsMatches);
274
+
275
+ return imports;
276
+ }
277
+
278
+ /**
279
+ * Detect API replacements between versions
280
+ * @param {string} before - Code before
281
+ * @param {string} after - Code after
282
+ * @returns {Array} - Detected replacements
283
+ */
284
+ function detectAPIReplacements(before, after) {
285
+ const replacements = [];
286
+
287
+ // Known replacements to check
288
+ const knownReplacements = [
289
+ { old: 'customSelect', new: 'p.select' },
290
+ { old: 'console.log', new: 'logger.debug' },
291
+ { old: 'var ', new: 'const ' },
292
+ { old: 'let ', new: 'const ' },
293
+ { old: 'function ', new: '=>' },
294
+ { old: '.then(', new: 'await ' }
295
+ ];
296
+
297
+ for (const rep of knownReplacements) {
298
+ const oldCount = (before.match(new RegExp(escapeRegex(rep.old), 'g')) || []).length;
299
+ const newCount = (after.match(new RegExp(escapeRegex(rep.old), 'g')) || []).length;
300
+ const newApiCount = (after.match(new RegExp(escapeRegex(rep.new), 'g')) || []).length;
301
+
302
+ if (oldCount > newCount && newApiCount > 0) {
303
+ replacements.push({
304
+ from: rep.old,
305
+ to: rep.new,
306
+ count: oldCount - newCount
307
+ });
308
+ }
309
+ }
310
+
311
+ return replacements;
312
+ }
313
+
314
+ /**
315
+ * Escape regex special characters
316
+ */
317
+ function escapeRegex(string) {
318
+ return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
319
+ }
320
+
321
+ // ============================================================================
322
+ // IMPACT SCORER
323
+ // ============================================================================
324
+
325
+ /**
326
+ * Calculate impact score for a change type
327
+ * @param {string} changeType - Type of change
328
+ * @param {number} count - Number of occurrences
329
+ * @returns {number} - Impact score 0.0 to 1.0
330
+ */
331
+ function calculateImpact(changeType, count) {
332
+ const baseImpacts = {
333
+ [DIFF_TYPES.ADD_IMPORT]: 0.2,
334
+ [DIFF_TYPES.REMOVE_IMPORT]: 0.3,
335
+ [DIFF_TYPES.CHANGE_FUNCTION]: 0.5,
336
+ [DIFF_TYPES.ADD_ERROR_HANDLING]: 0.6,
337
+ [DIFF_TYPES.FIX_TYPE]: 0.4,
338
+ [DIFF_TYPES.REPLACE_API]: 0.7,
339
+ [DIFF_TYPES.ADD_VALIDATION]: 0.5,
340
+ [DIFF_TYPES.REFACTOR]: 0.4,
341
+ [DIFF_TYPES.OTHER]: 0.2
342
+ };
343
+
344
+ const base = baseImpacts[changeType] || 0.2;
345
+ const multiplier = Math.log10(count + 1) + 1; // Logarithmic scaling
346
+
347
+ return Math.min(base * multiplier, 1.0);
348
+ }
349
+
350
+ /**
351
+ * Score overall impact of a task outcome
352
+ * @param {Object} outcome - Task outcome details
353
+ * @returns {Object} - Impact score with breakdown
354
+ */
355
+ export function scoreImpact(outcome) {
356
+ let score = 0;
357
+ const factors = [];
358
+
359
+ // Factor 1: Task success/failure
360
+ if (outcome.success) {
361
+ score += 0.3;
362
+ factors.push({ factor: 'task_success', contribution: 0.3 });
363
+ } else {
364
+ score += 0.1;
365
+ factors.push({ factor: 'task_failure', contribution: 0.1 });
366
+ }
367
+
368
+ // Factor 2: First-time success
369
+ if (outcome.firstAttempt && outcome.success) {
370
+ score += 0.2;
371
+ factors.push({ factor: 'first_time_success', contribution: 0.2 });
372
+ }
373
+
374
+ // Factor 3: Skill involvement
375
+ if (outcome.skillApplied) {
376
+ const skillImpact = outcome.skillHelped ? 0.3 : -0.1;
377
+ score += skillImpact;
378
+ factors.push({ factor: 'skill_effectiveness', contribution: skillImpact });
379
+ }
380
+
381
+ // Factor 4: Error prevention
382
+ if (outcome.errorPrevented) {
383
+ score += 0.3;
384
+ factors.push({ factor: 'error_prevented', contribution: 0.3 });
385
+ }
386
+
387
+ // Factor 5: Time efficiency
388
+ if (outcome.fasterThanAverage) {
389
+ score += 0.1;
390
+ factors.push({ factor: 'time_efficiency', contribution: 0.1 });
391
+ }
392
+
393
+ // Normalize to 0-1
394
+ score = Math.max(0, Math.min(score, 1.0));
395
+
396
+ // Classify impact level
397
+ let level;
398
+ if (score >= IMPACT_THRESHOLDS.CRITICAL) level = 'critical';
399
+ else if (score >= IMPACT_THRESHOLDS.HIGH) level = 'high';
400
+ else if (score >= IMPACT_THRESHOLDS.MEDIUM) level = 'medium';
401
+ else level = 'low';
402
+
403
+ return {
404
+ score,
405
+ level,
406
+ factors,
407
+ analyzedAt: new Date().toISOString()
408
+ };
409
+ }
410
+
411
+ // ============================================================================
412
+ // CAUSAL PATTERN EXTRACTION
413
+ // ============================================================================
414
+
415
+ /**
416
+ * Extract a causal pattern from task outcome
417
+ * @param {Object} taskData - Full task data with context, actions, outcome
418
+ * @returns {CausalPattern|null} - Extracted pattern or null
419
+ */
420
+ export function extractCausalPattern(taskData) {
421
+ const { context, actions, outcome, error } = taskData;
422
+
423
+ // Analyze root cause if failed
424
+ let rootCause = null;
425
+ if (!outcome.success && error) {
426
+ rootCause = analyzeRootCause(error, context);
427
+ }
428
+
429
+ // Analyze what changed if we have before/after
430
+ let semanticDiff = null;
431
+ if (taskData.before && taskData.after) {
432
+ semanticDiff = analyzeSemanticDiff(taskData.before, taskData.after, context);
433
+ }
434
+
435
+ // Score impact
436
+ const impact = scoreImpact(outcome);
437
+
438
+ // Only create pattern if significant enough
439
+ if (impact.score < IMPACT_THRESHOLDS.MEDIUM) {
440
+ return null;
441
+ }
442
+
443
+ // Generate pattern
444
+ const pattern = {
445
+ id: `CP-${Date.now()}`,
446
+
447
+ // What to detect
448
+ pattern: rootCause?.primaryCause?.causeId || semanticDiff?.changes?.[0]?.type || 'unknown',
449
+
450
+ // When this applies
451
+ context: {
452
+ fileTypes: context.fileTypes || [],
453
+ framework: context.framework || null,
454
+ domain: context.domain || null
455
+ },
456
+
457
+ // Root cause (WHY)
458
+ cause: rootCause?.primaryCause?.cause || 'Pattern derived from successful task',
459
+ effect: rootCause ? 'Task failure' : 'Task success',
460
+
461
+ // Confidence
462
+ confidence: calculatePatternConfidence(rootCause, semanticDiff, impact),
463
+
464
+ // Evidence
465
+ evidence: [{
466
+ taskId: taskData.taskId,
467
+ outcome: outcome.success ? 'success' : 'failure',
468
+ timestamp: new Date().toISOString()
469
+ }],
470
+
471
+ // Dates
472
+ firstDetected: new Date().toISOString(),
473
+ lastUpdated: new Date().toISOString(),
474
+
475
+ // Fix (if we know what fixed it)
476
+ fix: semanticDiff?.changes?.[0]
477
+ ? { type: semanticDiff.changes[0].type, details: semanticDiff.changes[0].details }
478
+ : null,
479
+
480
+ // Analytics
481
+ impact: impact.score,
482
+ impactLevel: impact.level
483
+ };
484
+
485
+ // Record pattern event for metrics
486
+ recordPatternEvent({
487
+ type: outcome.success ? 'true_positive' : 'false_negative',
488
+ confidence: pattern.confidence,
489
+ newPattern: true
490
+ });
491
+
492
+ return pattern;
493
+ }
494
+
495
+ /**
496
+ * Calculate confidence for a causal pattern
497
+ */
498
+ function calculatePatternConfidence(rootCause, semanticDiff, impact) {
499
+ let confidence = 0.5; // Base
500
+
501
+ // Root cause adds confidence
502
+ if (rootCause?.primaryCause?.confidence) {
503
+ confidence += rootCause.primaryCause.confidence * 0.3;
504
+ }
505
+
506
+ // Clear semantic diff adds confidence
507
+ if (semanticDiff?.changes?.length > 0) {
508
+ confidence += 0.2;
509
+ }
510
+
511
+ // High impact adds confidence
512
+ confidence += impact.score * 0.2;
513
+
514
+ return Math.min(confidence, 0.95);
515
+ }
516
+
517
+ // ============================================================================
518
+ // CAUSAL PATTERNS STORAGE
519
+ // ============================================================================
520
+
521
+ /**
522
+ * Load causal patterns from disk
523
+ * @returns {Array<CausalPattern>} - Array of causal patterns
524
+ */
525
+ export function loadCausalPatterns() {
526
+ try {
527
+ if (!fs.existsSync(CAUSAL_PATTERNS_FILE)) {
528
+ return [];
529
+ }
530
+
531
+ const content = fs.readFileSync(CAUSAL_PATTERNS_FILE, 'utf8');
532
+ const data = JSON.parse(content);
533
+ return data.patterns || [];
534
+ } catch (error) {
535
+ console.error('Error loading causal patterns:', error.message);
536
+ return [];
537
+ }
538
+ }
539
+
540
+ /**
541
+ * Save causal pattern to disk
542
+ * @param {CausalPattern} pattern - Pattern to save
543
+ */
544
+ export function saveCausalPattern(pattern) {
545
+ try {
546
+ if (!fs.existsSync(KNOWLEDGE_DIR)) {
547
+ fs.mkdirSync(KNOWLEDGE_DIR, { recursive: true });
548
+ }
549
+
550
+ const patterns = loadCausalPatterns();
551
+
552
+ // Check for duplicate
553
+ const existingIndex = patterns.findIndex(p => p.id === pattern.id);
554
+ if (existingIndex >= 0) {
555
+ patterns[existingIndex] = pattern;
556
+ } else {
557
+ patterns.push(pattern);
558
+ }
559
+
560
+ const data = {
561
+ version: '6.0.0',
562
+ updatedAt: new Date().toISOString(),
563
+ patterns
564
+ };
565
+
566
+ fs.writeFileSync(CAUSAL_PATTERNS_FILE, JSON.stringify(data, null, 2), 'utf8');
567
+ } catch (error) {
568
+ console.error('Error saving causal pattern:', error.message);
569
+ }
570
+ }
571
+
572
+ /**
573
+ * Update confidence of existing pattern based on new evidence
574
+ * @param {string} patternId - Pattern ID
575
+ * @param {Object} evidence - New evidence
576
+ * @param {boolean} positive - Whether evidence supports pattern
577
+ */
578
+ export function reinforcePattern(patternId, evidence, positive) {
579
+ const patterns = loadCausalPatterns();
580
+ const pattern = patterns.find(p => p.id === patternId);
581
+
582
+ if (!pattern) return null;
583
+
584
+ // Add evidence
585
+ pattern.evidence.push({
586
+ ...evidence,
587
+ positive,
588
+ timestamp: new Date().toISOString()
589
+ });
590
+
591
+ // Adjust confidence
592
+ const delta = positive ? 0.05 : -0.1;
593
+ pattern.confidence = Math.max(0.1, Math.min(pattern.confidence + delta, 0.99));
594
+ pattern.lastUpdated = new Date().toISOString();
595
+
596
+ // Save
597
+ saveCausalPattern(pattern);
598
+
599
+ // Record for metrics
600
+ recordPatternEvent({
601
+ type: positive ? 'true_positive' : 'false_positive',
602
+ confidence: pattern.confidence,
603
+ newPattern: false
604
+ });
605
+
606
+ return pattern;
607
+ }
608
+
609
+ // ============================================================================
610
+ // EXPORTS
611
+ // ============================================================================
612
+
613
+ export default {
614
+ analyzeRootCause,
615
+ analyzeSemanticDiff,
616
+ scoreImpact,
617
+ extractCausalPattern,
618
+ loadCausalPatterns,
619
+ saveCausalPattern,
620
+ reinforcePattern,
621
+ DIFF_TYPES,
622
+ IMPACT_THRESHOLDS
623
+ };