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,584 @@
1
+ /**
2
+ * AutoLearn v6.0 - Precision Skill Generator
3
+ *
4
+ * Generates accurate, context-aware skills from patterns.
5
+ * Uses context fingerprinting and evidence validation.
6
+ *
7
+ * Key concepts:
8
+ * - Context Fingerprint: WHO + WHAT + WHERE + WHEN to apply
9
+ * - Evidence-Based: Requires minimum evidence before skill creation
10
+ * - Auto-Versioning: Skills have semantic versions
11
+ * - Lifecycle: Create → Evaluate → Prune
12
+ *
13
+ * @version 6.0.0
14
+ * @author PikaKit
15
+ */
16
+
17
+ import fs from 'fs';
18
+ import path from 'path';
19
+ import { recordSkillEvent } from './metrics-collector.js';
20
+ import { shouldPromote, getReinforcementState } from './reinforcement.js';
21
+
22
+ // ============================================================================
23
+ // CONFIGURATION
24
+ // ============================================================================
25
+
26
+ const SKILLS_DIR = path.join(process.cwd(), '.agent', 'skills', 'auto-generated');
27
+ const SKILL_REGISTRY = path.join(SKILLS_DIR, 'registry.json');
28
+
29
+ // Minimum requirements for skill generation
30
+ const SKILL_REQUIREMENTS = {
31
+ MIN_CONFIDENCE: 0.80,
32
+ MIN_EVIDENCE: 5,
33
+ MIN_POSITIVE_REINFORCEMENTS: 3
34
+ };
35
+
36
+ // ============================================================================
37
+ // CONTEXT FINGERPRINTING
38
+ // ============================================================================
39
+
40
+ /**
41
+ * Create a context fingerprint for a pattern
42
+ * @param {Object} pattern - Pattern with context
43
+ * @returns {Object} - Context fingerprint
44
+ */
45
+ export function createContextFingerprint(pattern) {
46
+ const context = pattern.context || {};
47
+
48
+ return {
49
+ // WHO: What type of files/code
50
+ filePatterns: context.fileTypes || extractFilePatterns(pattern),
51
+ language: context.language || detectLanguage(pattern),
52
+
53
+ // WHAT: What is being detected
54
+ patternType: pattern.patternType || 'general',
55
+ category: pattern.category || categorizePattern(pattern),
56
+
57
+ // WHERE: In what context
58
+ framework: context.framework || null,
59
+ library: context.library || null,
60
+ domain: context.domain || null,
61
+
62
+ // WHEN: Conditions for application
63
+ conditions: extractConditions(pattern),
64
+ excludePaths: pattern.excludePaths || [],
65
+
66
+ // HOW CONFIDENT
67
+ confidence: pattern.confidence,
68
+ evidenceCount: pattern.evidence?.length || pattern.hitCount || 0
69
+ };
70
+ }
71
+
72
+ /**
73
+ * Extract file patterns from pattern regex
74
+ */
75
+ function extractFilePatterns(pattern) {
76
+ const patterns = ['*.js', '*.ts', '*.tsx', '*.jsx'];
77
+
78
+ // Check for specific file type hints
79
+ if (pattern.pattern?.includes('tsx') || pattern.pattern?.includes('jsx')) {
80
+ return ['*.tsx', '*.jsx'];
81
+ }
82
+ if (pattern.pattern?.includes('import')) {
83
+ return patterns;
84
+ }
85
+
86
+ return patterns;
87
+ }
88
+
89
+ /**
90
+ * Detect language from pattern
91
+ */
92
+ function detectLanguage(pattern) {
93
+ const p = pattern.pattern || '';
94
+
95
+ if (p.includes('interface') || p.includes(': ')) return 'typescript';
96
+ if (p.includes('useState') || p.includes('useEffect')) return 'react';
97
+ if (p.includes('def ') || p.includes('import ')) return 'python';
98
+
99
+ return 'javascript';
100
+ }
101
+
102
+ /**
103
+ * Categorize pattern based on content
104
+ */
105
+ function categorizePattern(pattern) {
106
+ const p = (pattern.pattern || '').toLowerCase();
107
+ const m = (pattern.message || '').toLowerCase();
108
+
109
+ if (p.includes('security') || m.includes('security') || p.includes('eval')) {
110
+ return 'security';
111
+ }
112
+ if (p.includes('import') || p.includes('require')) {
113
+ return 'dependency';
114
+ }
115
+ if (p.includes('console') || p.includes('debugger')) {
116
+ return 'quality';
117
+ }
118
+ if (p.includes('async') || p.includes('await')) {
119
+ return 'async';
120
+ }
121
+ if (p.includes('type') || p.includes('interface')) {
122
+ return 'type_safety';
123
+ }
124
+
125
+ return 'general';
126
+ }
127
+
128
+ /**
129
+ * Extract conditions from pattern
130
+ */
131
+ function extractConditions(pattern) {
132
+ const conditions = [];
133
+
134
+ if (pattern.cause) {
135
+ conditions.push({ type: 'cause', value: pattern.cause });
136
+ }
137
+
138
+ if (pattern.context?.framework) {
139
+ conditions.push({ type: 'framework', value: pattern.context.framework });
140
+ }
141
+
142
+ return conditions;
143
+ }
144
+
145
+ // ============================================================================
146
+ // SKILL GENERATION
147
+ // ============================================================================
148
+
149
+ /**
150
+ * Check if pattern is ready to become a skill
151
+ * @param {Object} pattern - Pattern to check
152
+ * @returns {Object} - Readiness check result
153
+ */
154
+ export function checkSkillReadiness(pattern) {
155
+ const checks = {
156
+ confidence: pattern.confidence >= SKILL_REQUIREMENTS.MIN_CONFIDENCE,
157
+ evidence: (pattern.evidence?.length || pattern.hitCount || 0) >= SKILL_REQUIREMENTS.MIN_EVIDENCE,
158
+ reinforcement: checkPositiveReinforcement(pattern)
159
+ };
160
+
161
+ const ready = Object.values(checks).every(Boolean);
162
+ const missing = Object.entries(checks)
163
+ .filter(([, passed]) => !passed)
164
+ .map(([name]) => name);
165
+
166
+ return {
167
+ ready,
168
+ checks,
169
+ missing,
170
+ requirements: SKILL_REQUIREMENTS
171
+ };
172
+ }
173
+
174
+ /**
175
+ * Check if pattern has enough positive reinforcement
176
+ */
177
+ function checkPositiveReinforcement(pattern) {
178
+ const history = pattern.reinforcementHistory || [];
179
+ const recentPositive = history.slice(-10).filter(r => r.delta > 0).length;
180
+ return recentPositive >= SKILL_REQUIREMENTS.MIN_POSITIVE_REINFORCEMENTS;
181
+ }
182
+
183
+ /**
184
+ * Generate a precision skill from a pattern
185
+ * @param {Object} pattern - Validated pattern
186
+ * @returns {Object} - Generated skill
187
+ */
188
+ export function generateSkill(pattern) {
189
+ const readiness = checkSkillReadiness(pattern);
190
+ if (!readiness.ready) {
191
+ return { success: false, reason: 'Pattern not ready', missing: readiness.missing };
192
+ }
193
+
194
+ const fingerprint = createContextFingerprint(pattern);
195
+ const skillId = generateSkillId(pattern);
196
+ const version = '1.0.0';
197
+
198
+ const skill = {
199
+ // Metadata
200
+ id: skillId,
201
+ version,
202
+ createdAt: new Date().toISOString(),
203
+ updatedAt: new Date().toISOString(),
204
+ sourcePatternId: pattern.id,
205
+
206
+ // Human-readable
207
+ name: generateSkillName(pattern),
208
+ description: pattern.message || 'Auto-generated skill',
209
+
210
+ // Context Fingerprint (WHEN to apply)
211
+ appliesWhen: {
212
+ filePatterns: fingerprint.filePatterns,
213
+ language: fingerprint.language,
214
+ framework: fingerprint.framework,
215
+ conditions: fingerprint.conditions
216
+ },
217
+
218
+ // Detection (WHAT to check)
219
+ detection: {
220
+ pattern: pattern.pattern,
221
+ patternType: fingerprint.patternType,
222
+ category: fingerprint.category
223
+ },
224
+
225
+ // Root Cause (WHY it's important)
226
+ cause: {
227
+ root: pattern.cause || 'Learned from past experience',
228
+ effect: pattern.effect || 'May cause issues',
229
+ evidenceCount: fingerprint.evidenceCount
230
+ },
231
+
232
+ // Fix (HOW to resolve)
233
+ fix: pattern.fix || pattern.autoFix || null,
234
+
235
+ // Metrics
236
+ metrics: {
237
+ confidence: pattern.confidence,
238
+ effectiveness: 0, // Tracked over time
239
+ timesApplied: 0,
240
+ timesHelped: 0,
241
+ lastReinforcement: pattern.lastReinforcement || null
242
+ },
243
+
244
+ // Lifecycle
245
+ lifecycle: {
246
+ state: 'active',
247
+ stateChangedAt: new Date().toISOString(),
248
+ evaluationCount: 0
249
+ }
250
+ };
251
+
252
+ // Generate SKILL.md content
253
+ const skillMd = generateSkillMarkdown(skill);
254
+
255
+ // Save skill
256
+ saveSkill(skill, skillMd);
257
+
258
+ // Record for metrics
259
+ recordSkillEvent({
260
+ type: 'created',
261
+ creationTime: 1 // Placeholder
262
+ });
263
+
264
+ return { success: true, skill, skillMd };
265
+ }
266
+
267
+ /**
268
+ * Generate unique skill ID
269
+ */
270
+ function generateSkillId(pattern) {
271
+ const category = categorizePattern(pattern);
272
+ const timestamp = Date.now().toString(36).toUpperCase();
273
+ return `SKILL-AUTO-${category.toUpperCase()}-${timestamp}`;
274
+ }
275
+
276
+ /**
277
+ * Generate human-readable skill name
278
+ */
279
+ function generateSkillName(pattern) {
280
+ const category = categorizePattern(pattern);
281
+ const action = pattern.message?.split(' ').slice(0, 3).join(' ') || 'Auto-learned';
282
+ return `${category}: ${action}`;
283
+ }
284
+
285
+ /**
286
+ * Generate SKILL.md content
287
+ */
288
+ function generateSkillMarkdown(skill) {
289
+ return `---
290
+ name: ${skill.name}
291
+ id: ${skill.id}
292
+ version: ${skill.version}
293
+ description: ${skill.description}
294
+ triggers:
295
+ ${skill.appliesWhen.filePatterns.map(p => ` - "${p}"`).join('\n')}
296
+ auto_generated: true
297
+ source_pattern: ${skill.sourcePatternId}
298
+ created: ${skill.createdAt}
299
+ ---
300
+
301
+ # ${skill.name}
302
+
303
+ > Auto-generated skill from pattern learning
304
+
305
+ ## When This Applies
306
+
307
+ | Condition | Value |
308
+ |-----------|-------|
309
+ | File Patterns | ${skill.appliesWhen.filePatterns.join(', ')} |
310
+ | Language | ${skill.appliesWhen.language || 'Any'} |
311
+ | Framework | ${skill.appliesWhen.framework || 'Any'} |
312
+
313
+ ## What to Detect
314
+
315
+ \`\`\`regex
316
+ ${skill.detection.pattern}
317
+ \`\`\`
318
+
319
+ **Category**: ${skill.detection.category}
320
+
321
+ ## Why It Matters
322
+
323
+ **Root Cause**: ${skill.cause.root}
324
+
325
+ **Potential Effect**: ${skill.cause.effect}
326
+
327
+ **Evidence**: ${skill.cause.evidenceCount} occurrences recorded
328
+
329
+ ## How to Fix
330
+
331
+ ${skill.fix ? `
332
+ **Fix Type**: ${skill.fix.type || 'Manual'}
333
+
334
+ ${skill.fix.details ? `\`\`\`
335
+ ${JSON.stringify(skill.fix.details, null, 2)}
336
+ \`\`\`` : 'Follow the guidance above to resolve.'}
337
+ ` : 'Manual review and correction required.'}
338
+
339
+ ## Metrics
340
+
341
+ | Metric | Value |
342
+ |--------|-------|
343
+ | Confidence | ${(skill.metrics.confidence * 100).toFixed(1)}% |
344
+ | Times Applied | ${skill.metrics.timesApplied} |
345
+ | Effectiveness | ${(skill.metrics.effectiveness * 100).toFixed(1)}% |
346
+
347
+ ---
348
+
349
+ *This skill was auto-generated by AutoLearn v6.0*
350
+ `;
351
+ }
352
+
353
+ // ============================================================================
354
+ // SKILL STORAGE
355
+ // ============================================================================
356
+
357
+ /**
358
+ * Save skill to disk
359
+ * @param {Object} skill - Skill object
360
+ * @param {string} skillMd - SKILL.md content
361
+ */
362
+ function saveSkill(skill, skillMd) {
363
+ try {
364
+ // Create directory
365
+ const skillDir = path.join(SKILLS_DIR, skill.id.toLowerCase());
366
+ if (!fs.existsSync(skillDir)) {
367
+ fs.mkdirSync(skillDir, { recursive: true });
368
+ }
369
+
370
+ // Save SKILL.md
371
+ fs.writeFileSync(path.join(skillDir, 'SKILL.md'), skillMd, 'utf8');
372
+
373
+ // Save skill.json
374
+ fs.writeFileSync(
375
+ path.join(skillDir, 'skill.json'),
376
+ JSON.stringify(skill, null, 2),
377
+ 'utf8'
378
+ );
379
+
380
+ // Update registry
381
+ updateRegistry(skill);
382
+
383
+ } catch (error) {
384
+ console.error('Error saving skill:', error.message);
385
+ }
386
+ }
387
+
388
+ /**
389
+ * Update skill registry
390
+ */
391
+ function updateRegistry(skill) {
392
+ try {
393
+ if (!fs.existsSync(SKILLS_DIR)) {
394
+ fs.mkdirSync(SKILLS_DIR, { recursive: true });
395
+ }
396
+
397
+ let registry = [];
398
+ if (fs.existsSync(SKILL_REGISTRY)) {
399
+ registry = JSON.parse(fs.readFileSync(SKILL_REGISTRY, 'utf8'));
400
+ }
401
+
402
+ const existingIndex = registry.findIndex(s => s.id === skill.id);
403
+ const entry = {
404
+ id: skill.id,
405
+ name: skill.name,
406
+ version: skill.version,
407
+ category: skill.detection.category,
408
+ confidence: skill.metrics.confidence,
409
+ state: skill.lifecycle.state,
410
+ updatedAt: skill.updatedAt
411
+ };
412
+
413
+ if (existingIndex >= 0) {
414
+ registry[existingIndex] = entry;
415
+ } else {
416
+ registry.push(entry);
417
+ }
418
+
419
+ fs.writeFileSync(SKILL_REGISTRY, JSON.stringify(registry, null, 2), 'utf8');
420
+ } catch (error) {
421
+ console.error('Error updating registry:', error.message);
422
+ }
423
+ }
424
+
425
+ /**
426
+ * Load all auto-generated skills
427
+ * @returns {Array} - Array of skills
428
+ */
429
+ export function loadAutoSkills() {
430
+ try {
431
+ if (!fs.existsSync(SKILL_REGISTRY)) return [];
432
+ return JSON.parse(fs.readFileSync(SKILL_REGISTRY, 'utf8'));
433
+ } catch {
434
+ return [];
435
+ }
436
+ }
437
+
438
+ /**
439
+ * Load a specific skill
440
+ * @param {string} skillId - Skill ID
441
+ * @returns {Object|null} - Skill or null
442
+ */
443
+ export function loadSkill(skillId) {
444
+ try {
445
+ const skillPath = path.join(SKILLS_DIR, skillId.toLowerCase(), 'skill.json');
446
+ if (!fs.existsSync(skillPath)) return null;
447
+ return JSON.parse(fs.readFileSync(skillPath, 'utf8'));
448
+ } catch {
449
+ return null;
450
+ }
451
+ }
452
+
453
+ // ============================================================================
454
+ // SKILL LIFECYCLE
455
+ // ============================================================================
456
+
457
+ /**
458
+ * Evaluate skill effectiveness
459
+ * @param {string} skillId - Skill ID
460
+ * @param {Object} outcome - Task outcome where skill was applied
461
+ * @returns {Object} - Updated skill metrics
462
+ */
463
+ export function evaluateSkill(skillId, outcome) {
464
+ const skill = loadSkill(skillId);
465
+ if (!skill) return null;
466
+
467
+ skill.metrics.timesApplied++;
468
+
469
+ if (outcome.success && outcome.skillHelped) {
470
+ skill.metrics.timesHelped++;
471
+ }
472
+
473
+ // Recalculate effectiveness
474
+ skill.metrics.effectiveness = skill.metrics.timesApplied > 0
475
+ ? skill.metrics.timesHelped / skill.metrics.timesApplied
476
+ : 0;
477
+
478
+ skill.lifecycle.evaluationCount++;
479
+ skill.updatedAt = new Date().toISOString();
480
+
481
+ // Check if skill should be demoted
482
+ if (skill.metrics.effectiveness < 0.3 && skill.metrics.timesApplied >= 10) {
483
+ skill.lifecycle.state = 'underperforming';
484
+ skill.lifecycle.stateChangedAt = new Date().toISOString();
485
+ }
486
+
487
+ // Update file
488
+ const skillDir = path.join(SKILLS_DIR, skillId.toLowerCase());
489
+ fs.writeFileSync(
490
+ path.join(skillDir, 'skill.json'),
491
+ JSON.stringify(skill, null, 2),
492
+ 'utf8'
493
+ );
494
+ updateRegistry(skill);
495
+
496
+ return skill;
497
+ }
498
+
499
+ /**
500
+ * Prune underperforming skills
501
+ * @returns {Object} - Prune results
502
+ */
503
+ export function pruneUnderperformingSkills() {
504
+ const skills = loadAutoSkills();
505
+ const pruned = [];
506
+
507
+ for (const skillEntry of skills) {
508
+ const skill = loadSkill(skillEntry.id);
509
+ if (!skill) continue;
510
+
511
+ // Prune if underperforming for too long
512
+ if (skill.lifecycle.state === 'underperforming' && skill.lifecycle.evaluationCount >= 20) {
513
+ skill.lifecycle.state = 'pruned';
514
+ skill.lifecycle.stateChangedAt = new Date().toISOString();
515
+
516
+ // Move to archive instead of delete
517
+ archiveSkill(skill);
518
+ pruned.push(skill.id);
519
+
520
+ recordSkillEvent({ type: 'pruned' });
521
+ }
522
+ }
523
+
524
+ return { pruned };
525
+ }
526
+
527
+ /**
528
+ * Archive a skill (soft delete)
529
+ */
530
+ function archiveSkill(skill) {
531
+ try {
532
+ const archiveDir = path.join(SKILLS_DIR, '_archive');
533
+ if (!fs.existsSync(archiveDir)) {
534
+ fs.mkdirSync(archiveDir, { recursive: true });
535
+ }
536
+
537
+ const skillDir = path.join(SKILLS_DIR, skill.id.toLowerCase());
538
+ const archivePath = path.join(archiveDir, skill.id.toLowerCase());
539
+
540
+ if (fs.existsSync(skillDir)) {
541
+ fs.renameSync(skillDir, archivePath);
542
+ }
543
+
544
+ // Remove from registry
545
+ const registry = loadAutoSkills().filter(s => s.id !== skill.id);
546
+ fs.writeFileSync(SKILL_REGISTRY, JSON.stringify(registry, null, 2), 'utf8');
547
+ } catch (error) {
548
+ console.error('Error archiving skill:', error.message);
549
+ }
550
+ }
551
+
552
+ /**
553
+ * Get skill statistics
554
+ * @returns {Object} - Statistics
555
+ */
556
+ export function getSkillStats() {
557
+ const skills = loadAutoSkills();
558
+
559
+ return {
560
+ total: skills.length,
561
+ active: skills.filter(s => s.state === 'active').length,
562
+ underperforming: skills.filter(s => s.state === 'underperforming').length,
563
+ avgConfidence: skills.length > 0
564
+ ? skills.reduce((sum, s) => sum + s.confidence, 0) / skills.length
565
+ : 0,
566
+ categories: [...new Set(skills.map(s => s.category))]
567
+ };
568
+ }
569
+
570
+ // ============================================================================
571
+ // EXPORTS
572
+ // ============================================================================
573
+
574
+ export default {
575
+ createContextFingerprint,
576
+ checkSkillReadiness,
577
+ generateSkill,
578
+ loadAutoSkills,
579
+ loadSkill,
580
+ evaluateSkill,
581
+ pruneUnderperformingSkills,
582
+ getSkillStats,
583
+ SKILL_REQUIREMENTS
584
+ };
@@ -815,6 +815,6 @@ export function restoreFromBackup(originalPath, backupPath) {
815
815
  }
816
816
 
817
817
  // Run if executed directly
818
- if (process.argv[1].includes("recall")) {
818
+ if (process.argv[1]?.includes("recall")) {
819
819
  main();
820
820
  }