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.
- package/README.md +1 -1
- package/bin/lib/commands/help.js +0 -4
- package/bin/lib/commands/install.js +90 -9
- package/bin/lib/ui.js +1 -1
- package/lib/agent-cli/__tests__/adaptive_engine.test.js +190 -0
- package/lib/agent-cli/__tests__/integration/cross_script.test.js +222 -0
- package/lib/agent-cli/__tests__/integration/full_cycle.test.js +230 -0
- package/lib/agent-cli/__tests__/pattern_analyzer.test.js +173 -0
- package/lib/agent-cli/__tests__/pre_execution_check.test.js +167 -0
- package/lib/agent-cli/__tests__/skill_injector.test.js +191 -0
- package/lib/agent-cli/bin/agent.js +191 -0
- package/lib/agent-cli/dashboard/dashboard_server.js +340 -0
- package/lib/agent-cli/dashboard/index.html +538 -0
- package/lib/agent-cli/lib/audit.js +154 -0
- package/lib/agent-cli/lib/audit.test.js +100 -0
- package/lib/agent-cli/lib/auto-learn.js +319 -0
- package/lib/agent-cli/lib/auto_preview.py +148 -0
- package/lib/agent-cli/lib/backup.js +138 -0
- package/lib/agent-cli/lib/backup.test.js +78 -0
- package/lib/agent-cli/lib/checklist.py +222 -0
- package/lib/agent-cli/lib/cognitive-lesson.js +476 -0
- package/lib/agent-cli/lib/completion.js +149 -0
- package/lib/agent-cli/lib/config.js +35 -0
- package/lib/agent-cli/lib/eslint-fix.js +238 -0
- package/lib/agent-cli/lib/evolution-signal.js +215 -0
- package/lib/agent-cli/lib/export.js +86 -0
- package/lib/agent-cli/lib/export.test.js +65 -0
- package/lib/agent-cli/lib/fix.js +337 -0
- package/lib/agent-cli/lib/fix.test.js +80 -0
- package/lib/agent-cli/lib/gemini-export.js +83 -0
- package/lib/agent-cli/lib/generate-registry.js +42 -0
- package/lib/agent-cli/lib/hooks/install-hooks.js +152 -0
- package/lib/agent-cli/lib/hooks/lint-learn.js +172 -0
- package/lib/agent-cli/lib/ignore.js +116 -0
- package/lib/agent-cli/lib/ignore.test.js +58 -0
- package/lib/agent-cli/lib/init.js +124 -0
- package/lib/agent-cli/lib/learn.js +255 -0
- package/lib/agent-cli/lib/learn.test.js +70 -0
- package/lib/agent-cli/lib/migrate-to-v4.js +322 -0
- package/lib/agent-cli/lib/proposals.js +199 -0
- package/lib/agent-cli/lib/proposals.test.js +56 -0
- package/lib/agent-cli/lib/recall.js +820 -0
- package/lib/agent-cli/lib/recall.test.js +107 -0
- package/lib/agent-cli/lib/selfevolution-bridge.js +167 -0
- package/lib/agent-cli/lib/session_manager.py +120 -0
- package/lib/agent-cli/lib/settings.js +227 -0
- package/lib/agent-cli/lib/skill-learn.js +296 -0
- package/lib/agent-cli/lib/stats.js +132 -0
- package/lib/agent-cli/lib/stats.test.js +94 -0
- package/lib/agent-cli/lib/types.js +33 -0
- package/lib/agent-cli/lib/ui/audit-ui.js +146 -0
- package/lib/agent-cli/lib/ui/backup-ui.js +107 -0
- package/lib/agent-cli/lib/ui/clack-helpers.js +317 -0
- package/lib/agent-cli/lib/ui/common.js +83 -0
- package/lib/agent-cli/lib/ui/completion-ui.js +126 -0
- package/lib/agent-cli/lib/ui/custom-select.js +69 -0
- package/lib/agent-cli/lib/ui/dashboard-ui.js +222 -0
- package/lib/agent-cli/lib/ui/evolution-signals-ui.js +107 -0
- package/lib/agent-cli/lib/ui/export-ui.js +94 -0
- package/lib/agent-cli/lib/ui/fix-all-ui.js +191 -0
- package/lib/agent-cli/lib/ui/help-ui.js +49 -0
- package/lib/agent-cli/lib/ui/index.js +199 -0
- package/lib/agent-cli/lib/ui/init-ui.js +56 -0
- package/lib/agent-cli/lib/ui/knowledge-ui.js +55 -0
- package/lib/agent-cli/lib/ui/learn-ui.js +706 -0
- package/lib/agent-cli/lib/ui/lessons-ui.js +148 -0
- package/lib/agent-cli/lib/ui/pretty.js +145 -0
- package/lib/agent-cli/lib/ui/proposals-ui.js +99 -0
- package/lib/agent-cli/lib/ui/recall-ui.js +342 -0
- package/lib/agent-cli/lib/ui/routing-demo.js +79 -0
- package/lib/agent-cli/lib/ui/routing-ui.js +325 -0
- package/lib/agent-cli/lib/ui/settings-ui.js +381 -0
- package/lib/agent-cli/lib/ui/stats-ui.js +123 -0
- package/lib/agent-cli/lib/ui/watch-ui.js +236 -0
- package/lib/agent-cli/lib/verify_all.py +327 -0
- package/lib/agent-cli/lib/watcher.js +181 -0
- package/lib/agent-cli/lib/watcher.test.js +85 -0
- package/lib/agent-cli/package.json +51 -0
- package/lib/agent-cli/scripts/adaptive_engine.js +381 -0
- package/lib/agent-cli/scripts/dashboard_server.js +224 -0
- package/lib/agent-cli/scripts/error_sensor.js +565 -0
- package/lib/agent-cli/scripts/learn_from_failure.js +225 -0
- package/lib/agent-cli/scripts/pattern_analyzer.js +781 -0
- package/lib/agent-cli/scripts/pre_execution_check.js +623 -0
- package/lib/agent-cli/scripts/rule_sharing.js +374 -0
- package/lib/agent-cli/scripts/skill_injector.js +387 -0
- package/lib/agent-cli/scripts/success_sensor.js +500 -0
- package/lib/agent-cli/scripts/user_correction_sensor.js +426 -0
- package/lib/agent-cli/services/auto-learn-service.js +247 -0
- package/lib/agent-cli/src/MIGRATION.md +418 -0
- package/lib/agent-cli/src/README.md +367 -0
- package/lib/agent-cli/src/core/evolution/evolution-signal.js +42 -0
- package/lib/agent-cli/src/core/evolution/index.js +17 -0
- package/lib/agent-cli/src/core/evolution/review-gate.js +40 -0
- package/lib/agent-cli/src/core/evolution/signal-detector.js +137 -0
- package/lib/agent-cli/src/core/evolution/signal-queue.js +79 -0
- package/lib/agent-cli/src/core/evolution/threshold-checker.js +79 -0
- package/lib/agent-cli/src/core/index.js +15 -0
- package/lib/agent-cli/src/core/learning/cognitive-enhancer.js +282 -0
- package/lib/agent-cli/src/core/learning/index.js +12 -0
- package/lib/agent-cli/src/core/learning/lesson-synthesizer.js +83 -0
- package/lib/agent-cli/src/core/scanning/index.js +14 -0
- package/lib/agent-cli/src/data/index.js +13 -0
- package/lib/agent-cli/src/data/repositories/index.js +8 -0
- package/lib/agent-cli/src/data/repositories/lesson-repository.js +130 -0
- package/lib/agent-cli/src/data/repositories/signal-repository.js +119 -0
- package/lib/agent-cli/src/data/storage/index.js +8 -0
- package/lib/agent-cli/src/data/storage/json-storage.js +64 -0
- package/lib/agent-cli/src/data/storage/yaml-storage.js +66 -0
- package/lib/agent-cli/src/infrastructure/index.js +13 -0
- package/lib/agent-cli/src/presentation/formatters/skill-formatter.js +232 -0
- package/lib/agent-cli/src/services/export-service.js +162 -0
- package/lib/agent-cli/src/services/index.js +13 -0
- package/lib/agent-cli/src/services/learning-service.js +99 -0
- package/lib/agent-cli/types/index.d.ts +343 -0
- package/lib/agent-cli/utils/benchmark.js +269 -0
- package/lib/agent-cli/utils/logger.js +303 -0
- package/lib/agent-cli/utils/ml_patterns.js +300 -0
- package/lib/agent-cli/utils/recovery.js +312 -0
- package/lib/agent-cli/utils/telemetry.js +290 -0
- package/lib/agentskillskit-cli/README.md +21 -0
- package/{node_modules/agentskillskit-cli/bin → lib/agentskillskit-cli}/ag-smart.js +15 -15
- package/lib/agentskillskit-cli/package.json +51 -0
- package/package.json +19 -9
- /package/bin/{cli.js → kit.js} +0 -0
- /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
|
+
}
|