pikakit 3.0.5 → 3.7.2

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.
Files changed (100) hide show
  1. package/README.md +1 -1
  2. package/bin/lib/commands/install.js +119 -242
  3. package/package.json +3 -4
  4. package/lib/agent-cli/bin/agent.js +0 -187
  5. package/lib/agent-cli/dashboard/dashboard_server.js +0 -312
  6. package/lib/agent-cli/lib/ab-testing.js +0 -364
  7. package/lib/agent-cli/lib/audit.js +0 -154
  8. package/lib/agent-cli/lib/audit.test.js +0 -100
  9. package/lib/agent-cli/lib/auto-learn.js +0 -319
  10. package/lib/agent-cli/lib/backup.js +0 -138
  11. package/lib/agent-cli/lib/backup.test.js +0 -78
  12. package/lib/agent-cli/lib/causality-engine.js +0 -331
  13. package/lib/agent-cli/lib/cognitive-lesson.js +0 -476
  14. package/lib/agent-cli/lib/completion.js +0 -149
  15. package/lib/agent-cli/lib/config.js +0 -35
  16. package/lib/agent-cli/lib/dashboard-data.js +0 -380
  17. package/lib/agent-cli/lib/eslint-fix.js +0 -238
  18. package/lib/agent-cli/lib/evolution-signal.js +0 -215
  19. package/lib/agent-cli/lib/export.js +0 -86
  20. package/lib/agent-cli/lib/export.test.js +0 -65
  21. package/lib/agent-cli/lib/fix.js +0 -337
  22. package/lib/agent-cli/lib/fix.test.js +0 -80
  23. package/lib/agent-cli/lib/gemini-export.js +0 -83
  24. package/lib/agent-cli/lib/generate-registry.js +0 -42
  25. package/lib/agent-cli/lib/hooks/install-hooks.js +0 -152
  26. package/lib/agent-cli/lib/hooks/lint-learn.js +0 -172
  27. package/lib/agent-cli/lib/icons.js +0 -93
  28. package/lib/agent-cli/lib/ignore.js +0 -116
  29. package/lib/agent-cli/lib/ignore.test.js +0 -58
  30. package/lib/agent-cli/lib/init.js +0 -124
  31. package/lib/agent-cli/lib/knowledge-index.js +0 -326
  32. package/lib/agent-cli/lib/knowledge-metrics.js +0 -335
  33. package/lib/agent-cli/lib/knowledge-retention.js +0 -398
  34. package/lib/agent-cli/lib/knowledge-validator.js +0 -312
  35. package/lib/agent-cli/lib/learn.js +0 -255
  36. package/lib/agent-cli/lib/learn.test.js +0 -70
  37. package/lib/agent-cli/lib/metrics-collector.js +0 -410
  38. package/lib/agent-cli/lib/proposals.js +0 -199
  39. package/lib/agent-cli/lib/proposals.test.js +0 -56
  40. package/lib/agent-cli/lib/recall.js +0 -835
  41. package/lib/agent-cli/lib/recall.test.js +0 -107
  42. package/lib/agent-cli/lib/reinforcement.js +0 -299
  43. package/lib/agent-cli/lib/selfevolution-bridge.js +0 -167
  44. package/lib/agent-cli/lib/settings.js +0 -203
  45. package/lib/agent-cli/lib/skill-generator.js +0 -379
  46. package/lib/agent-cli/lib/skill-learn.js +0 -296
  47. package/lib/agent-cli/lib/stats.js +0 -132
  48. package/lib/agent-cli/lib/stats.test.js +0 -94
  49. package/lib/agent-cli/lib/types.js +0 -33
  50. package/lib/agent-cli/lib/ui/audit-ui.js +0 -146
  51. package/lib/agent-cli/lib/ui/backup-ui.js +0 -107
  52. package/lib/agent-cli/lib/ui/clack-helpers.js +0 -317
  53. package/lib/agent-cli/lib/ui/common.js +0 -83
  54. package/lib/agent-cli/lib/ui/completion-ui.js +0 -126
  55. package/lib/agent-cli/lib/ui/custom-select.js +0 -69
  56. package/lib/agent-cli/lib/ui/dashboard-ui.js +0 -222
  57. package/lib/agent-cli/lib/ui/evolution-signals-ui.js +0 -107
  58. package/lib/agent-cli/lib/ui/export-ui.js +0 -94
  59. package/lib/agent-cli/lib/ui/fix-all-ui.js +0 -191
  60. package/lib/agent-cli/lib/ui/help-ui.js +0 -49
  61. package/lib/agent-cli/lib/ui/index.js +0 -199
  62. package/lib/agent-cli/lib/ui/init-ui.js +0 -56
  63. package/lib/agent-cli/lib/ui/knowledge-ui.js +0 -55
  64. package/lib/agent-cli/lib/ui/learn-ui.js +0 -706
  65. package/lib/agent-cli/lib/ui/lessons-ui.js +0 -148
  66. package/lib/agent-cli/lib/ui/pretty.js +0 -145
  67. package/lib/agent-cli/lib/ui/proposals-ui.js +0 -99
  68. package/lib/agent-cli/lib/ui/recall-ui.js +0 -342
  69. package/lib/agent-cli/lib/ui/routing-demo.js +0 -79
  70. package/lib/agent-cli/lib/ui/routing-ui.js +0 -325
  71. package/lib/agent-cli/lib/ui/settings-ui.js +0 -381
  72. package/lib/agent-cli/lib/ui/stats-ui.js +0 -123
  73. package/lib/agent-cli/lib/ui/watch-ui.js +0 -236
  74. package/lib/agent-cli/lib/watcher.js +0 -181
  75. package/lib/agent-cli/lib/watcher.test.js +0 -85
  76. package/lib/agent-cli/src/MIGRATION.md +0 -418
  77. package/lib/agent-cli/src/README.md +0 -367
  78. package/lib/agent-cli/src/core/evolution/evolution-signal.js +0 -42
  79. package/lib/agent-cli/src/core/evolution/index.js +0 -17
  80. package/lib/agent-cli/src/core/evolution/review-gate.js +0 -40
  81. package/lib/agent-cli/src/core/evolution/signal-detector.js +0 -137
  82. package/lib/agent-cli/src/core/evolution/signal-queue.js +0 -79
  83. package/lib/agent-cli/src/core/evolution/threshold-checker.js +0 -79
  84. package/lib/agent-cli/src/core/index.js +0 -15
  85. package/lib/agent-cli/src/core/learning/cognitive-enhancer.js +0 -282
  86. package/lib/agent-cli/src/core/learning/index.js +0 -12
  87. package/lib/agent-cli/src/core/learning/lesson-synthesizer.js +0 -83
  88. package/lib/agent-cli/src/core/scanning/index.js +0 -14
  89. package/lib/agent-cli/src/data/index.js +0 -13
  90. package/lib/agent-cli/src/data/repositories/index.js +0 -8
  91. package/lib/agent-cli/src/data/repositories/lesson-repository.js +0 -130
  92. package/lib/agent-cli/src/data/repositories/signal-repository.js +0 -119
  93. package/lib/agent-cli/src/data/storage/index.js +0 -8
  94. package/lib/agent-cli/src/data/storage/json-storage.js +0 -64
  95. package/lib/agent-cli/src/data/storage/yaml-storage.js +0 -66
  96. package/lib/agent-cli/src/infrastructure/index.js +0 -13
  97. package/lib/agent-cli/src/presentation/formatters/skill-formatter.js +0 -232
  98. package/lib/agent-cli/src/services/export-service.js +0 -162
  99. package/lib/agent-cli/src/services/index.js +0 -13
  100. package/lib/agent-cli/src/services/learning-service.js +0 -99
@@ -1,835 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Smart Recall Script - ESM Version (Production-Ready)
4
- *
5
- * The "Memory" script. Checks code against learned lessons.
6
- * Features:
7
- * - Streaming file scanner (multiple files)
8
- * - Context-aware pattern matching (shows line numbers)
9
- * - Hit tracking for frequency analysis
10
- *
11
- * Usage:
12
- * node recall.js <file_path>
13
- * node recall.js <directory> --recursive
14
- */
15
-
16
- import fs from "fs";
17
- import path from "path";
18
- import yaml from "js-yaml";
19
- import ora from "ora";
20
- import { KNOWLEDGE_DIR, LESSONS_PATH, DEBUG, cwd, VERSION } from "./config.js";
21
- import { icons, safeText } from "./icons.js";
22
- import { loadIgnorePatterns, isIgnored } from "./ignore.js";
23
- import pretty from "./ui/pretty.js";
24
- import * as p from "@clack/prompts";
25
- import pc from "picocolors";
26
- import { checkEvolutionThreshold, queueEvolutionSignal } from "./evolution-signal.js";
27
- import { isAutoUpdatingEnabled, getUpdateThreshold } from "./settings.js";
28
-
29
- // ============================================================================
30
- // PERCEPTION LAYER - Cognitive Enhancements
31
- // ============================================================================
32
-
33
- /**
34
- * Infer intent from lesson metadata
35
- * @param {object} lesson - Raw lesson from YAML
36
- * @returns {string} - Intent: 'prevent', 'warn', 'optimize'
37
- */
38
- function inferIntent(lesson) {
39
- if (lesson.type === 'mistake') {
40
- return lesson.severity === 'ERROR' ? 'prevent' : 'warn';
41
- }
42
- return 'optimize'; // improvements
43
- }
44
-
45
- /**
46
- * Classify pattern type for better context
47
- * @param {string} pattern - Regex pattern
48
- * @returns {string} - Pattern type: 'dependency', 'structure', 'quality', 'security', 'performance', 'general'
49
- */
50
- function classifyPattern(pattern) {
51
- if (!pattern) return 'general';
52
-
53
- // Security patterns
54
- if (pattern.includes('eval') || pattern.includes('innerHTML') || pattern.includes('dangerouslySetInnerHTML')) {
55
- return 'security';
56
- }
57
-
58
- // Dependency patterns
59
- if (pattern.includes('import') || pattern.includes('require') || pattern.includes('from')) {
60
- return 'dependency';
61
- }
62
-
63
- // Structure patterns
64
- if (pattern.includes('function') || pattern.includes('class') || pattern.includes('const') || pattern.includes('let')) {
65
- return 'structure';
66
- }
67
-
68
- // Quality patterns
69
- if (pattern.includes('console') || pattern.includes('debugger') || pattern.includes('TODO')) {
70
- return 'quality';
71
- }
72
-
73
- // Performance patterns
74
- if (pattern.includes('loop') || pattern.includes('forEach') || pattern.includes('map')) {
75
- return 'performance';
76
- }
77
-
78
- return 'general';
79
- }
80
-
81
- /**
82
- * Infer applicability context from tags
83
- * @param {Array<string>} tags - Lesson tags
84
- * @returns {object} - Context: { scope, appliesTo }
85
- */
86
- function inferApplicability(tags = []) {
87
- return {
88
- scope: tags.includes('global') ? 'global' : 'local',
89
- appliesTo: tags.filter(t =>
90
- t.includes('file') ||
91
- t.includes('component') ||
92
- t.includes('module')
93
- )
94
- };
95
- }
96
-
97
- /**
98
- * Calculate confidence score based on lesson metadata
99
- * @param {object} lesson - Lesson with hitCount
100
- * @returns {number} - Confidence: 0.0 to 1.0
101
- */
102
- function calculateConfidence(lesson) {
103
- const hitCount = lesson.hitCount || 0;
104
-
105
- // Confidence increases with hits (logarithmic scale)
106
- if (hitCount === 0) return 0.3; // Default for new lessons
107
- if (hitCount < 3) return 0.5;
108
- if (hitCount < 10) return 0.7;
109
- if (hitCount < 30) return 0.85;
110
- return 0.95; // High confidence for well-established patterns
111
- }
112
-
113
- /**
114
- * Transform raw lesson into cognitive lesson with intent & context
115
- * @param {object} rawLesson - Raw lesson from YAML
116
- * @returns {object} - Cognitive lesson with enhanced metadata
117
- */
118
- function createCognitiveLesson(rawLesson) {
119
- return {
120
- ...rawLesson,
121
-
122
- // Intent inference
123
- intent: inferIntent(rawLesson),
124
-
125
- // Pattern classification
126
- patternType: classifyPattern(rawLesson.pattern),
127
-
128
- // Context awareness
129
- context: {
130
- appliesTo: inferApplicability(rawLesson.tags),
131
- scope: (rawLesson.tags || []).includes('global') ? 'global' : 'local'
132
- },
133
-
134
- // Cognitive metadata
135
- cognitive: {
136
- maturity: (rawLesson.hitCount || 0) > 10 ? 'stable' : 'learning',
137
- confidence: calculateConfidence(rawLesson)
138
- }
139
- };
140
- }
141
-
142
- // ============================================================================
143
- // KNOWLEDGE BASE
144
- // ============================================================================
145
-
146
- /**
147
- * Load knowledge base from YAML file
148
- * Supports both v3.x (lessons-learned.yaml) and v4.x (cognitive lessons)
149
- * @returns {{ lessons: Array<{ id: string, pattern: string, message: string, severity: string, hitCount?: number, lastHit?: string }>, version?: number }}
150
- */
151
- export function loadKnowledge() {
152
- try {
153
- // Check for v4.x cognitive structure first
154
- const mistakesPath = path.join(KNOWLEDGE_DIR, 'mistakes.yaml');
155
- const improvementsPath = path.join(KNOWLEDGE_DIR, 'improvements.yaml');
156
-
157
- const hasV4 = fs.existsSync(mistakesPath) || fs.existsSync(improvementsPath);
158
-
159
- if (hasV4) {
160
- // v4.x: Load cognitive lessons and flatten for scanning
161
- const mistakes = fs.existsSync(mistakesPath)
162
- ? yaml.load(fs.readFileSync(mistakesPath, 'utf8'))
163
- : { mistakes: [] };
164
- const improvements = fs.existsSync(improvementsPath)
165
- ? yaml.load(fs.readFileSync(improvementsPath, 'utf8'))
166
- : { improvements: [] };
167
-
168
- // Flatten and apply cognitive transformation
169
- const rawLessons = [
170
- ...(mistakes.mistakes || []).map(m => ({
171
- id: m.id,
172
- pattern: m.pattern,
173
- message: m.message,
174
- severity: m.severity || 'WARNING',
175
- hitCount: m.hitCount || 0,
176
- lastHit: m.lastHit,
177
- excludePaths: m.excludePaths,
178
- tags: m.tags,
179
- autoFix: m.autoFix, // Preserve autoFix for Fix All
180
- type: 'mistake', // Mark for tracking
181
- })),
182
- ...(improvements.improvements || []).map(i => ({
183
- id: i.id,
184
- pattern: i.pattern,
185
- message: i.message,
186
- severity: 'INFO', // Improvements are informational
187
- hitCount: i.appliedCount || 0,
188
- lastHit: i.lastApplied,
189
- excludePaths: i.excludePaths, // PRESERVE excludePaths!
190
- tags: i.tags,
191
- type: 'improvement', // Mark for tracking
192
- }))
193
- ];
194
-
195
- // Apply cognitive transformation to create agent-level lessons
196
- const lessons = rawLessons.map(createCognitiveLesson);
197
-
198
- return { lessons, version: 4.0 };
199
- }
200
-
201
- // Fallback to v3.x YAML
202
- if (fs.existsSync(LESSONS_PATH)) {
203
- const content = fs.readFileSync(LESSONS_PATH, "utf8");
204
- return yaml.load(content) || { lessons: [], version: 1 };
205
- }
206
-
207
- // Fallback to v3.x JSON (lessons-learned.json)
208
- const jsonPath = LESSONS_PATH.replace('.yaml', '.json');
209
- if (fs.existsSync(jsonPath)) {
210
- const content = fs.readFileSync(jsonPath, "utf8");
211
- const data = JSON.parse(content);
212
- return { lessons: data.lessons || [], version: 3 };
213
- }
214
-
215
- return { lessons: [], version: 1 };
216
- } catch (error) {
217
- if (DEBUG) console.error("Error loading knowledge:", error.message);
218
- return { lessons: [], version: 1 };
219
- }
220
- }
221
-
222
- /**
223
- * Save knowledge base to YAML file
224
- * Supports both v3.x and v4.x formats
225
- * @param {{ lessons: Array, version?: number }} data
226
- */
227
- export function saveKnowledge(data) {
228
- try {
229
- fs.mkdirSync(KNOWLEDGE_DIR, { recursive: true });
230
-
231
- // v4.x: Save to mistakes.yaml and improvements.yaml
232
- if (data.version === 4.0) {
233
- const mistakes = data.lessons.filter(l => l.type === 'mistake').map(m => {
234
- const { type, ...rest } = m; // Remove type field
235
- return rest;
236
- });
237
- const improvements = data.lessons.filter(l => l.type === 'improvement').map(i => {
238
- const { type, severity, ...rest } = i; // Remove type and severity
239
- return {
240
- ...rest,
241
- excludePaths: i.excludePaths || [], // PRESERVE excludePaths explicitly
242
- appliedCount: rest.hitCount,
243
- lastApplied: rest.lastHit,
244
- };
245
- });
246
-
247
- // Save mistakes
248
- if (mistakes.length > 0) {
249
- const mistakesPath = path.join(KNOWLEDGE_DIR, 'mistakes.yaml');
250
- const mistakesData = yaml.dump({ version: 4.0, mistakes }, { lineWidth: -1 });
251
- fs.writeFileSync(mistakesPath, mistakesData, 'utf8');
252
- }
253
-
254
- // Save improvements
255
- if (improvements.length > 0) {
256
- const improvementsPath = path.join(KNOWLEDGE_DIR, 'improvements.yaml');
257
- const improvementsData = yaml.dump({ version: 4.0, improvements }, { lineWidth: -1 });
258
- fs.writeFileSync(improvementsPath, improvementsData, 'utf8');
259
- }
260
- } else {
261
- // v3.x: Save to lessons-learned.yaml
262
- const str = yaml.dump(data, { lineWidth: -1 });
263
- fs.writeFileSync(LESSONS_PATH, str, "utf8");
264
- }
265
- } catch (error) {
266
- console.error("❌ Failed to save knowledge base:", error.message);
267
- }
268
- }
269
-
270
- // ============================================================================
271
- // FILE SCANNING
272
- // ============================================================================
273
-
274
- /**
275
- * Scan a single file against learned patterns
276
- * @param {string} filePath - Path to file to scan
277
- * @param {{ lessons: Array }} db - Knowledge base
278
- * @param {boolean} updateHits - Whether to update hit counts
279
- * @returns {{ file: string, violations: Array<{ lesson: object, matches: Array<{ line: number, content: string }> }> }}
280
- */
281
- export function scanFile(filePath, db, updateHits = false) {
282
- const violations = [];
283
-
284
- if (!fs.existsSync(filePath)) {
285
- return { file: filePath, violations: [], error: "File not found" };
286
- }
287
-
288
- const content = fs.readFileSync(filePath, "utf8");
289
- const lines = content.split("\n");
290
-
291
- if (!db.lessons || db.lessons.length === 0) {
292
- return { file: filePath, violations: [] };
293
- }
294
-
295
- db.lessons.forEach(lesson => {
296
- // Skip if no valid pattern
297
- if (!lesson.pattern) return;
298
-
299
- // Check excludePaths - skip this lesson for excluded paths using glob patterns
300
- if (lesson.excludePaths && Array.isArray(lesson.excludePaths) && lesson.excludePaths.length > 0) {
301
- if (isIgnored(filePath, lesson.excludePaths)) {
302
- return; // Skip this lesson for this file
303
- }
304
- }
305
-
306
- try {
307
- const regex = new RegExp(lesson.pattern, "g");
308
- const matches = [];
309
-
310
- lines.forEach((line, idx) => {
311
- if (regex.test(line)) {
312
- matches.push({
313
- line: idx + 1,
314
- content: line.trim().substring(0, 80)
315
- });
316
- regex.lastIndex = 0; // Reset for next test
317
- }
318
- });
319
-
320
- if (matches.length > 0) {
321
- violations.push({ lesson, matches });
322
-
323
- // Track hit count
324
- if (updateHits) {
325
- lesson.hitCount = (lesson.hitCount || 0) + matches.length;
326
- lesson.lastHit = new Date().toISOString();
327
-
328
- // Check evolution threshold (Phase 2: Signal Layer)
329
- const threshold = getUpdateThreshold(); // Get from settings
330
- const evolutionCheck = checkEvolutionThreshold(lesson, threshold);
331
-
332
- if (evolutionCheck.ready) {
333
- // Queue evolution signal (doesn't evolve yet, just signals)
334
- queueEvolutionSignal(lesson.id, evolutionCheck, {
335
- triggerEvent: 'violation',
336
- file: filePath,
337
- matchCount: matches.length,
338
- timestamp: Date.now()
339
- });
340
-
341
- if (DEBUG) {
342
- console.log(`📡 Evolution signal queued for [${lesson.id}]: ${evolutionCheck.reason} (confidence: ${evolutionCheck.confidence.toFixed(2)})`);
343
- }
344
- }
345
-
346
- // Auto-escalation: WARNING → ERROR after threshold violations
347
- // ONLY if Auto-Updating is ENABLED in settings
348
- if (isAutoUpdatingEnabled() && lesson.severity === "WARNING" && lesson.hitCount >= threshold && !lesson.autoEscalated) {
349
- lesson.severity = "ERROR";
350
- lesson.autoEscalated = true;
351
- console.log(`⚡ Auto-escalated [${lesson.id}] to ERROR (${lesson.hitCount} hits, threshold: ${threshold})`);
352
- }
353
- }
354
- }
355
- } catch (e) {
356
- if (DEBUG) console.error(`Invalid regex in lesson ${lesson.id}:`, e.message);
357
- }
358
- });
359
-
360
- return { file: filePath, violations };
361
- }
362
-
363
- /**
364
- * Scan multiple files in a directory recursively
365
- * @param {string} dirPath - Directory to scan
366
- * @param {{ lessons: Array }} db - Knowledge base
367
- * @param {string[]} extensions - File extensions to scan
368
- * @returns {{ results: Array<{ file: string, violations: Array }>, ignoredCount: number, totalScanned: number }}
369
- */
370
- export function scanDirectory(dirPath, db, extensions = [".js", ".ts", ".tsx", ".jsx"]) {
371
- const results = [];
372
- const ignorePatterns = loadIgnorePatterns(dirPath);
373
- let ignoredCount = 0;
374
- let totalScanned = 0;
375
-
376
- function walk(dir) {
377
- let entries;
378
- try {
379
- entries = fs.readdirSync(dir, { withFileTypes: true });
380
- } catch (e) {
381
- return; // Skip unreadable directories
382
- }
383
-
384
- for (const entry of entries) {
385
- const fullPath = path.join(dir, entry.name);
386
- const relativePath = path.relative(dirPath, fullPath);
387
-
388
- // Check .agentignore patterns
389
- if (isIgnored(relativePath, ignorePatterns)) {
390
- ignoredCount++;
391
- continue;
392
- }
393
-
394
- if (entry.isDirectory()) {
395
- walk(fullPath);
396
- } else if (entry.isFile()) {
397
- const ext = path.extname(entry.name);
398
- if (extensions.includes(ext)) {
399
- totalScanned++;
400
- const result = scanFile(fullPath, db, true);
401
- if (result.violations.length > 0) {
402
- results.push(result);
403
- }
404
- }
405
- }
406
- }
407
- }
408
-
409
- if (fs.statSync(dirPath).isDirectory()) {
410
- walk(dirPath);
411
- } else {
412
- totalScanned++;
413
- results.push(scanFile(dirPath, db, true));
414
- }
415
-
416
- return { results, ignoredCount, totalScanned };
417
- }
418
-
419
- // ============================================================================
420
- // REPORTING
421
- // ============================================================================
422
-
423
- /**
424
- * Print scan results in a readable format
425
- * @param {Array} results - Scan results
426
- */
427
- export function printResults(results) {
428
- let totalViolations = 0;
429
- let errorCount = 0;
430
- let warningCount = 0;
431
-
432
- results.forEach(result => {
433
- if (result.violations.length === 0) return;
434
-
435
- console.log(`\n📄 ${path.relative(process.cwd(), result.file)}`);
436
-
437
- result.violations.forEach(({ lesson, matches }) => {
438
- totalViolations += matches.length;
439
- if (lesson.severity === "ERROR") errorCount += matches.length;
440
- else warningCount += matches.length;
441
-
442
- const icon = lesson.severity === "ERROR" ? "❌" : "⚠️";
443
- console.log(` ${icon} [${lesson.id}] ${lesson.message}`);
444
-
445
- matches.forEach(m => {
446
- console.log(` L${m.line}: ${m.content}`);
447
- });
448
- });
449
- });
450
-
451
- // Return stats only - summary is handled by pretty.showScanSummary
452
- return { total: totalViolations, errors: errorCount, warnings: warningCount };
453
- }
454
-
455
- // ============================================================================
456
- // CLI
457
- // ============================================================================
458
-
459
- function main() {
460
- const args = process.argv.slice(2);
461
- const jsonMode = args.includes("--json");
462
-
463
- if (args.length === 0 || args.includes("--help")) {
464
- console.log(`
465
- 🧠 Smart Recall - Memory Check Tool
466
-
467
- Usage:
468
- recall <file> Check single file
469
- recall <directory> Check all files in directory
470
- recall --staged Check git staged files only
471
-
472
- Options:
473
- --json Output JSON for CI/CD
474
- --help Show this help
475
- `);
476
- process.exit(0);
477
- }
478
-
479
- const target = args.find(a => !a.startsWith("--")) || ".";
480
- const db = loadKnowledge();
481
-
482
- if (!db.lessons || db.lessons.length === 0) {
483
- console.log("ℹ️ No lessons learned yet. Use 'agent learn' to add patterns.");
484
- process.exit(0);
485
- }
486
-
487
- // Scan first
488
- const { results, ignoredCount, totalScanned } = scanDirectory(target, db);
489
- const stats = printResults(results);
490
-
491
- // JSON output mode for CI/CD
492
- if (jsonMode) {
493
- const output = {
494
- version: VERSION,
495
- filesScanned: totalScanned,
496
- ignored: ignoredCount,
497
- violations: stats.total,
498
- errors: stats.errors,
499
- warnings: stats.warnings,
500
- passed: stats.errors === 0,
501
- details: results.filter(r => r.violations.length > 0).map(r => ({
502
- file: r.file,
503
- violations: r.violations.map(v => ({
504
- id: v.lesson.id,
505
- severity: v.lesson.severity,
506
- message: v.lesson.message,
507
- matches: v.matches.length
508
- }))
509
- }))
510
- };
511
- console.log(JSON.stringify(output, null, 2));
512
- process.exit(stats.errors > 0 ? 1 : 0);
513
- }
514
-
515
- // Show Clack-based summary (consistent with CLI)
516
- p.intro(pc.cyan(`${icons.brain} PikaKit v${VERSION}`));
517
-
518
- // Save updated hit counts
519
- saveKnowledge(db);
520
-
521
- // Summary using Clack
522
- const summaryLines = [
523
- `${pc.green("✓")} ${totalScanned} file(s) scanned`,
524
- `${pc.dim("›")} ${ignoredCount} paths ignored`,
525
- stats.total > 0
526
- ? `${pc.red("✗")} ${stats.total} violation(s) found`
527
- : `${pc.green("✓")} No violations found`
528
- ];
529
-
530
- if (stats.total === 0) {
531
- summaryLines.push("");
532
- summaryLines.push(pc.green(`All clear! Your code looks great. ${icons.party}`));
533
- }
534
-
535
- p.note(summaryLines.join("\n"), pc.dim("Memory check completed ✓"));
536
-
537
- if (stats.errors > 0) {
538
- process.exit(1);
539
- } else {
540
- process.exit(0);
541
- }
542
- }
543
-
544
- // ============================================================================
545
- // STRUCTURED SCAN (for Scan All / Fix All architecture)
546
- // ============================================================================
547
-
548
- /**
549
- * Sanitize filename for use in issue ID
550
- * @param {string} filepath - File path
551
- * @returns {string} Sanitized filename
552
- */
553
- function sanitizeFilename(filepath) {
554
- return filepath
555
- .replace(/[^a-zA-Z0-9]/g, '_')
556
- .replace(/_+/g, '_')
557
- .substring(0, 50);
558
- }
559
-
560
- /**
561
- * Scan directory and generate structured output with issue IDs
562
- * This is the read-only analyzer for Scan All
563
- * @param {string} targetPath - Path to scan
564
- * @param {{ lessons: Array }} db - Knowledge base
565
- * @returns {{ scanId: string, timestamp: number, issues: Array, summary: object }}
566
- */
567
- export function scanDirectoryStructured(targetPath, db) {
568
- // Run standard scan
569
- const { results, ignoredCount, totalScanned } = scanDirectory(targetPath, db);
570
-
571
- // Generate structured issues with unique IDs
572
- const issues = [];
573
- results.forEach(fileResult => {
574
- fileResult.violations.forEach(({ lesson, matches }) => {
575
- matches.forEach(match => {
576
- const issueId = `${lesson.id}_${sanitizeFilename(fileResult.file)}_L${match.line}`;
577
- issues.push({
578
- id: issueId,
579
- lessonId: lesson.id,
580
- type: lesson.type || 'unknown',
581
- severity: lesson.severity || 'WARNING',
582
- file: fileResult.file,
583
- line: match.line,
584
- message: lesson.message,
585
- pattern: lesson.pattern,
586
- matchedCode: match.content,
587
- fixable: !!lesson.autoFix, // Check if lesson has autoFix strategy
588
- confidence: 0.95
589
- });
590
- });
591
- });
592
- });
593
-
594
- const scanResult = {
595
- scanId: `scan_${Date.now()}`,
596
- timestamp: Date.now(),
597
- projectPath: path.resolve(targetPath),
598
- totalIssues: issues.length,
599
- summary: {
600
- errors: issues.filter(i => i.severity === 'ERROR').length,
601
- warnings: issues.filter(i => i.severity !== 'ERROR').length,
602
- filesScanned: totalScanned,
603
- ignoredFiles: ignoredCount
604
- },
605
- issues
606
- };
607
-
608
- return scanResult;
609
- }
610
-
611
- /**
612
- * Save scan result to disk
613
- * @param {object} scanResult - Scan result object
614
- */
615
- export function saveScanResult(scanResult) {
616
- const scansDir = path.join(KNOWLEDGE_DIR, 'scans');
617
-
618
- // Create scans directory if it doesn't exist
619
- if (!fs.existsSync(scansDir)) {
620
- fs.mkdirSync(scansDir, { recursive: true });
621
- }
622
-
623
- const filename = `${scanResult.scanId}.json`;
624
- const filepath = path.join(scansDir, filename);
625
-
626
- fs.writeFileSync(filepath, JSON.stringify(scanResult, null, 2));
627
-
628
- // Keep only last 10 scans
629
- cleanOldScans(scansDir, 10);
630
- }
631
-
632
- /**
633
- * Load scan result from disk
634
- * @param {string} scanId - Scan ID to load
635
- * @returns {object|null} Scan result or null if not found
636
- */
637
- export function loadScanResult(scanId) {
638
- const filepath = path.join(KNOWLEDGE_DIR, 'scans', `${scanId}.json`);
639
-
640
- if (!fs.existsSync(filepath)) {
641
- return null;
642
- }
643
-
644
- return JSON.parse(fs.readFileSync(filepath, 'utf8'));
645
- }
646
-
647
- /**
648
- * Load most recent scan result
649
- * @returns {object|null} Most recent scan result or null
650
- */
651
- export function loadMostRecentScan() {
652
- const scansDir = path.join(KNOWLEDGE_DIR, 'scans');
653
-
654
- if (!fs.existsSync(scansDir)) {
655
- return null;
656
- }
657
-
658
- const files = fs.readdirSync(scansDir)
659
- .filter(f => f.endsWith('.json'))
660
- .map(f => ({
661
- name: f,
662
- path: path.join(scansDir, f),
663
- mtime: fs.statSync(path.join(scansDir, f)).mtime
664
- }))
665
- .sort((a, b) => b.mtime - a.mtime);
666
-
667
- if (files.length === 0) {
668
- return null;
669
- }
670
-
671
- return JSON.parse(fs.readFileSync(files[0].path, 'utf8'));
672
- }
673
-
674
- /**
675
- * Clean old scan files, keeping only the most recent N
676
- * @param {string} scansDir - Scans directory path
677
- * @param {number} keepCount - Number of scans to keep
678
- */
679
- function cleanOldScans(scansDir, keepCount) {
680
- const files = fs.readdirSync(scansDir)
681
- .filter(f => f.endsWith('.json'))
682
- .map(f => ({
683
- name: f,
684
- path: path.join(scansDir, f),
685
- mtime: fs.statSync(path.join(scansDir, f)).mtime
686
- }))
687
- .sort((a, b) => b.mtime - a.mtime);
688
-
689
- // Delete files beyond keepCount
690
- files.slice(keepCount).forEach(file => {
691
- try {
692
- fs.unlinkSync(file.path);
693
- } catch (e) {
694
- // Ignore errors
695
- }
696
- });
697
- }
698
-
699
- // ============================================================================
700
- // AUTO-FIX LOGIC (for Fix All automation)
701
- // ============================================================================
702
-
703
- /**
704
- * Apply auto-fix to a file based on issue and lesson strategy
705
- * @param {object} issue - Issue object from scan with file, line, etc.
706
- * @param {object} lesson - Lesson with autoFix strategy
707
- * @returns {{ success: boolean, backup?: string, error?: string, details?: string }}
708
- */
709
- export function applyFix(issue, lesson) {
710
- if (!lesson.autoFix) {
711
- return { success: false, error: 'No auto-fix strategy defined' };
712
- }
713
-
714
- const { file, line } = issue;
715
- const { autoFix } = lesson;
716
-
717
- try {
718
- // 1. Create backup
719
- const backup = createBackupFile(file);
720
-
721
- // 2. Read file
722
- const content = fs.readFileSync(file, 'utf8');
723
- const lines = content.split('\n');
724
-
725
- // 3. Apply fix based on type
726
- let fixed = false;
727
- let details = '';
728
-
729
- switch (autoFix.type) {
730
- case 'replace':
731
- const searchRegex = new RegExp(autoFix.search, 'g');
732
- const originalLine = lines[line - 1];
733
- lines[line - 1] = lines[line - 1].replace(
734
- searchRegex,
735
- autoFix.replace
736
- );
737
- fixed = lines[line - 1] !== originalLine;
738
- details = `Replaced: ${originalLine.trim()} → ${lines[line - 1].trim()}`;
739
- break;
740
-
741
- case 'regex-replace':
742
- const findRegex = new RegExp(autoFix.find, 'g');
743
- const origLine = lines[line - 1];
744
- lines[line - 1] = lines[line - 1].replace(
745
- findRegex,
746
- autoFix.replace
747
- );
748
- fixed = lines[line - 1] !== origLine;
749
- details = `Replaced: ${origLine.trim()} → ${lines[line - 1].trim()}`;
750
- break;
751
-
752
- case 'comment-warning':
753
- const targetLine = line - 1;
754
- const indent = lines[targetLine].match(/^\s*/)[0];
755
- lines.splice(targetLine, 0, indent + autoFix.comment);
756
- fixed = true;
757
- details = `Added warning comment above line ${line}`;
758
- break;
759
-
760
- case 'insert':
761
- const pos = autoFix.position === 'before' ? line - 1 : line;
762
- lines.splice(pos, 0, autoFix.content);
763
- fixed = true;
764
- details = `Inserted: ${autoFix.content}`;
765
- break;
766
-
767
- case 'delete':
768
- const deletedLine = lines[line - 1];
769
- lines.splice(line - 1, 1);
770
- fixed = true;
771
- details = `Deleted: ${deletedLine.trim()}`;
772
- break;
773
-
774
- default:
775
- return { success: false, error: `Unknown fix type: ${autoFix.type}` };
776
- }
777
-
778
- if (!fixed) {
779
- return { success: false, error: 'Fix did not modify file' };
780
- }
781
-
782
- // 4. Write back
783
- fs.writeFileSync(file, lines.join('\n'), 'utf8');
784
-
785
- return { success: true, backup, details };
786
-
787
- } catch (e) {
788
- return { success: false, error: e.message };
789
- }
790
- }
791
-
792
- /**
793
- * Create backup of file before fixing
794
- * @param {string} filepath - File to backup
795
- * @returns {string} Backup filepath
796
- */
797
- function createBackupFile(filepath) {
798
- const backupDir = path.join(KNOWLEDGE_DIR, 'fix-backups');
799
- if (!fs.existsSync(backupDir)) {
800
- fs.mkdirSync(backupDir, { recursive: true });
801
- }
802
-
803
- const timestamp = Date.now();
804
- const filename = path.basename(filepath);
805
- const backupPath = path.join(backupDir, `${filename}.${timestamp}.bak`);
806
-
807
- fs.copyFileSync(filepath, backupPath);
808
-
809
- return backupPath;
810
- }
811
-
812
- /**
813
- * Restore file from backup
814
- * @param {string} originalPath - Original file path
815
- * @param {string} backupPath - Backup file path
816
- * @returns {boolean} True if restored successfully
817
- */
818
- export function restoreFromBackup(originalPath, backupPath) {
819
- try {
820
- if (fs.existsSync(backupPath)) {
821
- fs.copyFileSync(backupPath, originalPath);
822
- fs.unlinkSync(backupPath);
823
- return true;
824
- }
825
- return false;
826
- } catch (e) {
827
- console.error(`Failed to restore from backup: ${e.message}`);
828
- return false;
829
- }
830
- }
831
-
832
- // Run if executed directly
833
- if (process.argv[1].includes("recall")) {
834
- main();
835
- }