pikakit 1.0.7 → 1.0.9
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/README.md +10 -6
- package/bin/kit.mjs +6 -2
- package/bin/lib/commands/help.js +13 -8
- package/bin/lib/config.js +4 -2
- package/lib/agent-cli/lib/ab-testing.js +508 -0
- package/lib/agent-cli/lib/causality-engine.js +623 -0
- package/lib/agent-cli/lib/dashboard-data.js +365 -0
- package/lib/agent-cli/lib/fix.js +1 -1
- package/lib/agent-cli/lib/metrics-collector.js +523 -0
- package/lib/agent-cli/lib/metrics-schema.js +410 -0
- package/lib/agent-cli/lib/precision-skill-generator.js +584 -0
- package/lib/agent-cli/lib/recall.js +1 -1
- package/lib/agent-cli/lib/reinforcement.js +610 -0
- package/lib/agent-cli/lib/ui/index.js +37 -14
- package/package.json +4 -2
- package/lib/agent-cli/lib/auto-learn.js +0 -319
- package/lib/agent-cli/scripts/adaptive_engine.js +0 -381
- package/lib/agent-cli/scripts/error_sensor.js +0 -565
- package/lib/agent-cli/scripts/learn_from_failure.js +0 -225
- package/lib/agent-cli/scripts/pattern_analyzer.js +0 -781
- package/lib/agent-cli/scripts/skill_injector.js +0 -387
- package/lib/agent-cli/scripts/success_sensor.js +0 -500
- package/lib/agent-cli/scripts/user_correction_sensor.js +0 -426
- package/lib/agent-cli/services/auto-learn-service.js +0 -247
|
@@ -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
|
+
};
|