add-skill-kit 3.2.3 → 3.2.5

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