@triedotdev/mcp 1.0.11 → 1.0.13
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 +60 -3
- package/dist/agent-smith-LBQ5PNAK.js +10 -0
- package/dist/agent-smith-LBQ5PNAK.js.map +1 -0
- package/dist/chunk-4OGYWKMD.js +953 -0
- package/dist/chunk-4OGYWKMD.js.map +1 -0
- package/dist/{chunk-PSSXQEO5.js → chunk-NJXF26W7.js} +113 -435
- package/dist/chunk-NJXF26W7.js.map +1 -0
- package/dist/cli/yolo-daemon.js +2 -1
- package/dist/cli/yolo-daemon.js.map +1 -1
- package/dist/index.js +65 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-PSSXQEO5.js.map +0 -1
|
@@ -0,0 +1,953 @@
|
|
|
1
|
+
// src/utils/progress.ts
|
|
2
|
+
var ProgressReporter = class {
|
|
3
|
+
currentPhase = "init";
|
|
4
|
+
startTime = Date.now();
|
|
5
|
+
phaseStartTime = Date.now();
|
|
6
|
+
verbose;
|
|
7
|
+
constructor(options = {}) {
|
|
8
|
+
this.verbose = options.verbose ?? true;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Report a status update
|
|
12
|
+
*/
|
|
13
|
+
report(message, detail) {
|
|
14
|
+
if (!this.verbose) return;
|
|
15
|
+
const prefix = this.getPhaseIcon(this.currentPhase);
|
|
16
|
+
const fullMessage = detail ? `${message}: ${detail}` : message;
|
|
17
|
+
console.error(`${prefix} ${fullMessage}`);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Start a new phase
|
|
21
|
+
*/
|
|
22
|
+
startPhase(phase, message) {
|
|
23
|
+
this.currentPhase = phase;
|
|
24
|
+
this.phaseStartTime = Date.now();
|
|
25
|
+
this.report(message);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Update within current phase
|
|
29
|
+
*/
|
|
30
|
+
update(message, detail) {
|
|
31
|
+
this.report(message, detail);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Report progress on a file
|
|
35
|
+
*/
|
|
36
|
+
file(action, filePath) {
|
|
37
|
+
const fileName = filePath.split("/").pop() || filePath;
|
|
38
|
+
this.report(action, fileName);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Report an AI analysis step
|
|
42
|
+
*/
|
|
43
|
+
ai(action, context) {
|
|
44
|
+
const prefix = "\u{1F9E0}";
|
|
45
|
+
const message = context ? `${action}: ${context}` : action;
|
|
46
|
+
console.error(`${prefix} ${message}`);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Report a finding
|
|
50
|
+
*/
|
|
51
|
+
finding(severity, message) {
|
|
52
|
+
const icons = {
|
|
53
|
+
critical: "\u{1F534}",
|
|
54
|
+
serious: "\u{1F7E0}",
|
|
55
|
+
moderate: "\u{1F7E1}",
|
|
56
|
+
low: "\u{1F535}"
|
|
57
|
+
};
|
|
58
|
+
console.error(` ${icons[severity]} ${message}`);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Complete current phase
|
|
62
|
+
*/
|
|
63
|
+
completePhase(summary) {
|
|
64
|
+
const elapsed = Date.now() - this.phaseStartTime;
|
|
65
|
+
this.report(`\u2713 ${summary}`, `(${elapsed}ms)`);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Complete the entire operation
|
|
69
|
+
*/
|
|
70
|
+
complete(summary) {
|
|
71
|
+
const totalElapsed = Date.now() - this.startTime;
|
|
72
|
+
console.error("");
|
|
73
|
+
console.error(`\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501`);
|
|
74
|
+
console.error(`\u2705 ${summary}`);
|
|
75
|
+
console.error(` Total time: ${(totalElapsed / 1e3).toFixed(2)}s`);
|
|
76
|
+
console.error(`\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501`);
|
|
77
|
+
console.error("");
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Report an error
|
|
81
|
+
*/
|
|
82
|
+
error(message, detail) {
|
|
83
|
+
const fullMessage = detail ? `${message}: ${detail}` : message;
|
|
84
|
+
console.error(`\u274C ${fullMessage}`);
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Report a warning
|
|
88
|
+
*/
|
|
89
|
+
warn(message, detail) {
|
|
90
|
+
const fullMessage = detail ? `${message}: ${detail}` : message;
|
|
91
|
+
console.error(`\u26A0\uFE0F ${fullMessage}`);
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Get icon for current phase
|
|
95
|
+
*/
|
|
96
|
+
getPhaseIcon(phase) {
|
|
97
|
+
const icons = {
|
|
98
|
+
"init": "\u{1F53A}",
|
|
99
|
+
"discovery": "\u{1F50D}",
|
|
100
|
+
"reading": "\u{1F4C2}",
|
|
101
|
+
"analyzing": "\u{1F52C}",
|
|
102
|
+
"ai-review": "\u{1F9E0}",
|
|
103
|
+
"prioritizing": "\u{1F3AF}",
|
|
104
|
+
"complete": "\u2705"
|
|
105
|
+
};
|
|
106
|
+
return icons[phase] || "\u2022";
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Create a sub-reporter for a specific agent
|
|
110
|
+
*/
|
|
111
|
+
forAgent(agentName) {
|
|
112
|
+
return new AgentProgressReporter(agentName, this.verbose);
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
var AgentProgressReporter = class {
|
|
116
|
+
agentName;
|
|
117
|
+
verbose;
|
|
118
|
+
issueCount = 0;
|
|
119
|
+
constructor(agentName, verbose = true) {
|
|
120
|
+
this.agentName = agentName;
|
|
121
|
+
this.verbose = verbose;
|
|
122
|
+
}
|
|
123
|
+
start() {
|
|
124
|
+
if (!this.verbose) return;
|
|
125
|
+
console.error(`
|
|
126
|
+
\u{1F916} ${this.agentName.toUpperCase()} Agent starting...`);
|
|
127
|
+
}
|
|
128
|
+
analyzing(file) {
|
|
129
|
+
if (!this.verbose) return;
|
|
130
|
+
const fileName = file.split("/").pop() || file;
|
|
131
|
+
console.error(` \u{1F4C4} Analyzing ${fileName}...`);
|
|
132
|
+
}
|
|
133
|
+
aiReview(context) {
|
|
134
|
+
if (!this.verbose) return;
|
|
135
|
+
console.error(` \u{1F9E0} AI reviewing: ${context}`);
|
|
136
|
+
}
|
|
137
|
+
found(severity, issue) {
|
|
138
|
+
this.issueCount++;
|
|
139
|
+
if (!this.verbose) return;
|
|
140
|
+
const icon = severity === "critical" ? "\u{1F534}" : severity === "serious" ? "\u{1F7E0}" : severity === "moderate" ? "\u{1F7E1}" : "\u{1F535}";
|
|
141
|
+
console.error(` ${icon} Found: ${issue}`);
|
|
142
|
+
}
|
|
143
|
+
complete(summary) {
|
|
144
|
+
if (!this.verbose) return;
|
|
145
|
+
const msg = summary || `Found ${this.issueCount} issues`;
|
|
146
|
+
console.error(` \u2713 ${this.agentName}: ${msg}`);
|
|
147
|
+
}
|
|
148
|
+
getIssueCount() {
|
|
149
|
+
return this.issueCount;
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
// src/agents/base-agent.ts
|
|
154
|
+
import { basename, relative } from "path";
|
|
155
|
+
var BaseAgent = class {
|
|
156
|
+
// Progress reporter for real-time feedback
|
|
157
|
+
progress = null;
|
|
158
|
+
// Default priority - subclasses can override
|
|
159
|
+
get priority() {
|
|
160
|
+
return {
|
|
161
|
+
name: this.name,
|
|
162
|
+
tier: 2,
|
|
163
|
+
estimatedTimeMs: 100,
|
|
164
|
+
dependencies: []
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
// Default implementation - can be overridden for smarter confidence
|
|
168
|
+
getActivationConfidence(context) {
|
|
169
|
+
return this.shouldActivate(context) ? 0.7 : 0;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Main scan entry point - now with progress reporting
|
|
173
|
+
*/
|
|
174
|
+
async scan(files, context) {
|
|
175
|
+
const startTime = Date.now();
|
|
176
|
+
this.progress = new AgentProgressReporter(this.name);
|
|
177
|
+
this.progress.start();
|
|
178
|
+
try {
|
|
179
|
+
const issues = await this.analyzeWithAI(files, context);
|
|
180
|
+
this.progress.complete(`${issues.length} issues found`);
|
|
181
|
+
return {
|
|
182
|
+
agent: this.name,
|
|
183
|
+
issues,
|
|
184
|
+
executionTime: Date.now() - startTime,
|
|
185
|
+
success: true,
|
|
186
|
+
metadata: {
|
|
187
|
+
filesAnalyzed: files.length,
|
|
188
|
+
linesAnalyzed: 0
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
} catch (error) {
|
|
192
|
+
this.progress.complete("Failed");
|
|
193
|
+
return {
|
|
194
|
+
agent: this.name,
|
|
195
|
+
issues: [],
|
|
196
|
+
executionTime: Date.now() - startTime,
|
|
197
|
+
success: false,
|
|
198
|
+
error: error instanceof Error ? error.message : String(error)
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* AI-First Analysis - The new way
|
|
204
|
+
*
|
|
205
|
+
* 1. Quick filter to identify relevant files
|
|
206
|
+
* 2. Generate rich AI prompts for relevant files
|
|
207
|
+
* 3. Return prompts for the AI to process
|
|
208
|
+
*
|
|
209
|
+
* Subclasses should override this for AI-powered analysis
|
|
210
|
+
*/
|
|
211
|
+
async analyzeWithAI(files, context) {
|
|
212
|
+
const issues = [];
|
|
213
|
+
const aiRequests = [];
|
|
214
|
+
this.progress?.aiReview("Identifying relevant files...");
|
|
215
|
+
for (const file of files) {
|
|
216
|
+
try {
|
|
217
|
+
const content = await this.readFile(file);
|
|
218
|
+
const relevance = this.checkFileRelevance(file, content);
|
|
219
|
+
if (relevance.isRelevant) {
|
|
220
|
+
this.progress?.analyzing(file);
|
|
221
|
+
const request = await this.buildAIAnalysisRequest(file, content, relevance, context);
|
|
222
|
+
if (request) {
|
|
223
|
+
aiRequests.push(request);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
} catch (error) {
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
if (aiRequests.length > 0) {
|
|
230
|
+
this.progress?.aiReview(`Preparing analysis for ${aiRequests.length} files...`);
|
|
231
|
+
for (const request of aiRequests) {
|
|
232
|
+
const aiIssues = await this.processAIRequest(request);
|
|
233
|
+
issues.push(...aiIssues);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
const legacyIssues = await this.analyzeFiles(files, context);
|
|
237
|
+
const allIssues = this.mergeIssues(issues, legacyIssues);
|
|
238
|
+
return allIssues;
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Check if a file is relevant for this agent's analysis
|
|
242
|
+
* Subclasses should override for domain-specific checks
|
|
243
|
+
*/
|
|
244
|
+
checkFileRelevance(_file, _content) {
|
|
245
|
+
return {
|
|
246
|
+
isRelevant: true,
|
|
247
|
+
reason: "Default analysis",
|
|
248
|
+
priority: "low",
|
|
249
|
+
indicators: []
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Build an AI analysis request for a file
|
|
254
|
+
* Subclasses should override to provide domain-specific prompts
|
|
255
|
+
*/
|
|
256
|
+
async buildAIAnalysisRequest(file, content, relevance, _context) {
|
|
257
|
+
const fileName = basename(file);
|
|
258
|
+
const relPath = this.getRelativePath(file);
|
|
259
|
+
return {
|
|
260
|
+
file,
|
|
261
|
+
code: content,
|
|
262
|
+
analysisType: this.name,
|
|
263
|
+
systemPrompt: this.getSystemPrompt(),
|
|
264
|
+
userPrompt: this.buildUserPrompt(relPath, content, relevance),
|
|
265
|
+
context: {
|
|
266
|
+
fileName,
|
|
267
|
+
relevance: relevance.reason,
|
|
268
|
+
indicators: relevance.indicators
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Get the system prompt for AI analysis
|
|
274
|
+
* Subclasses should override with domain-specific prompts
|
|
275
|
+
*/
|
|
276
|
+
getSystemPrompt() {
|
|
277
|
+
return `You are an expert code analyzer specializing in ${this.name} analysis.
|
|
278
|
+
Analyze the provided code and identify issues with specific line numbers and actionable fixes.
|
|
279
|
+
Be precise and avoid false positives. Only report issues you are confident about.`;
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Build the user prompt for AI analysis
|
|
283
|
+
*/
|
|
284
|
+
buildUserPrompt(filePath, content, relevance) {
|
|
285
|
+
return `Analyze this code for ${this.name} issues:
|
|
286
|
+
|
|
287
|
+
**File:** ${filePath}
|
|
288
|
+
**Relevance indicators:** ${relevance.indicators.join(", ") || "general analysis"}
|
|
289
|
+
|
|
290
|
+
\`\`\`
|
|
291
|
+
${content}
|
|
292
|
+
\`\`\`
|
|
293
|
+
|
|
294
|
+
For each issue found, provide:
|
|
295
|
+
1. Severity (critical/serious/moderate/low)
|
|
296
|
+
2. Line number
|
|
297
|
+
3. Clear description of the issue
|
|
298
|
+
4. Specific fix recommendation`;
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Process an AI analysis request and return issues
|
|
302
|
+
* This generates the prompt output for Claude to process
|
|
303
|
+
*/
|
|
304
|
+
async processAIRequest(request) {
|
|
305
|
+
const fileName = basename(request.file);
|
|
306
|
+
this.progress?.aiReview(`${fileName} queued for AI analysis`);
|
|
307
|
+
return [{
|
|
308
|
+
id: this.generateIssueId(),
|
|
309
|
+
severity: "moderate",
|
|
310
|
+
issue: `AI Analysis Required: ${request.analysisType}`,
|
|
311
|
+
fix: "Review the AI analysis output below",
|
|
312
|
+
file: request.file,
|
|
313
|
+
confidence: 1,
|
|
314
|
+
autoFixable: false,
|
|
315
|
+
agent: this.name,
|
|
316
|
+
effort: "medium",
|
|
317
|
+
// Store the AI prompt for inclusion in output
|
|
318
|
+
aiPrompt: {
|
|
319
|
+
system: request.systemPrompt,
|
|
320
|
+
user: request.userPrompt
|
|
321
|
+
}
|
|
322
|
+
}];
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Merge and deduplicate issues from AI and legacy analysis
|
|
326
|
+
*/
|
|
327
|
+
mergeIssues(aiIssues, legacyIssues) {
|
|
328
|
+
const merged = [...aiIssues];
|
|
329
|
+
for (const legacy of legacyIssues) {
|
|
330
|
+
const hasOverlap = aiIssues.some(
|
|
331
|
+
(ai) => ai.file === legacy.file && ai.line === legacy.line
|
|
332
|
+
);
|
|
333
|
+
if (!hasOverlap) {
|
|
334
|
+
merged.push(legacy);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
return merged;
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Get relative path from cwd
|
|
341
|
+
*/
|
|
342
|
+
getRelativePath(file) {
|
|
343
|
+
try {
|
|
344
|
+
return relative(process.cwd(), file);
|
|
345
|
+
} catch {
|
|
346
|
+
return file;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
createIssue(id, severity, issue, fix, file, line, confidence = 0.9, regulation, autoFixable = true, options) {
|
|
350
|
+
const result = {
|
|
351
|
+
id,
|
|
352
|
+
severity,
|
|
353
|
+
issue,
|
|
354
|
+
fix,
|
|
355
|
+
file,
|
|
356
|
+
confidence,
|
|
357
|
+
autoFixable,
|
|
358
|
+
agent: this.name,
|
|
359
|
+
effort: options?.effort ?? this.estimateEffort(severity, autoFixable)
|
|
360
|
+
};
|
|
361
|
+
if (line !== void 0) result.line = line;
|
|
362
|
+
if (options?.endLine !== void 0) result.endLine = options.endLine;
|
|
363
|
+
if (options?.column !== void 0) result.column = options.column;
|
|
364
|
+
if (regulation !== void 0) result.regulation = regulation;
|
|
365
|
+
if (options?.category !== void 0) result.category = options.category;
|
|
366
|
+
if (options?.cwe !== void 0) result.cwe = options.cwe;
|
|
367
|
+
if (options?.owasp !== void 0) result.owasp = options.owasp;
|
|
368
|
+
return result;
|
|
369
|
+
}
|
|
370
|
+
estimateEffort(severity, autoFixable) {
|
|
371
|
+
if (autoFixable) return "trivial";
|
|
372
|
+
if (severity === "low") return "easy";
|
|
373
|
+
if (severity === "moderate") return "easy";
|
|
374
|
+
if (severity === "serious") return "medium";
|
|
375
|
+
return "hard";
|
|
376
|
+
}
|
|
377
|
+
async readFile(filePath) {
|
|
378
|
+
const { readFile: readFile2 } = await import("fs/promises");
|
|
379
|
+
return readFile2(filePath, "utf-8");
|
|
380
|
+
}
|
|
381
|
+
generateIssueId() {
|
|
382
|
+
return `${this.name}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
383
|
+
}
|
|
384
|
+
getLineContent(content, lineNumber) {
|
|
385
|
+
const lines = content.split("\n");
|
|
386
|
+
return lines[lineNumber - 1] || "";
|
|
387
|
+
}
|
|
388
|
+
getCodeSnippet(content, lineNumber, contextLines = 2) {
|
|
389
|
+
const lines = content.split("\n");
|
|
390
|
+
const start = Math.max(0, lineNumber - contextLines - 1);
|
|
391
|
+
const end = Math.min(lines.length, lineNumber + contextLines);
|
|
392
|
+
return lines.slice(start, end).map((line, idx) => {
|
|
393
|
+
const num = start + idx + 1;
|
|
394
|
+
const marker = num === lineNumber ? "\u2192" : " ";
|
|
395
|
+
return `${marker} ${num.toString().padStart(4)} | ${line}`;
|
|
396
|
+
}).join("\n");
|
|
397
|
+
}
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
// src/agents/agent-smith.ts
|
|
401
|
+
import { readFile, writeFile, mkdir, rm } from "fs/promises";
|
|
402
|
+
import { existsSync } from "fs";
|
|
403
|
+
import { join, dirname, basename as basename2 } from "path";
|
|
404
|
+
import { createHash } from "crypto";
|
|
405
|
+
var MEMORY_LIMITS = {
|
|
406
|
+
MAX_LOCATIONS_PER_ISSUE: 5,
|
|
407
|
+
MAX_TRACKED_ISSUES: 500,
|
|
408
|
+
PRUNE_AFTER_DAYS: 30
|
|
409
|
+
};
|
|
410
|
+
var SUB_AGENT_PATTERNS = {
|
|
411
|
+
"console-hunter": {
|
|
412
|
+
pattern: /console\.(log|debug|info|warn|error)\s*\(/g,
|
|
413
|
+
description: "Console statement detected",
|
|
414
|
+
fix: "Remove console statement or replace with proper logging"
|
|
415
|
+
},
|
|
416
|
+
"any-hunter": {
|
|
417
|
+
pattern: /:\s*any\b|<any>|as\s+any\b/g,
|
|
418
|
+
description: "Explicit `any` type usage",
|
|
419
|
+
fix: "Replace with proper type annotation"
|
|
420
|
+
},
|
|
421
|
+
"todo-hunter": {
|
|
422
|
+
pattern: /\/\/\s*(TODO|FIXME|HACK|XXX|BUG)[\s:]/gi,
|
|
423
|
+
description: "Unresolved TODO/FIXME comment",
|
|
424
|
+
fix: "Address the TODO or create a tracked issue"
|
|
425
|
+
},
|
|
426
|
+
"var-hunter": {
|
|
427
|
+
pattern: /\bvar\s+\w+/g,
|
|
428
|
+
description: "`var` declaration (use const/let)",
|
|
429
|
+
fix: "Replace with const or let"
|
|
430
|
+
},
|
|
431
|
+
"empty-catch-hunter": {
|
|
432
|
+
pattern: /catch\s*\([^)]*\)\s*\{\s*\}/g,
|
|
433
|
+
description: "Empty catch block (silently swallowing errors)",
|
|
434
|
+
fix: "Add error handling or logging in catch block"
|
|
435
|
+
},
|
|
436
|
+
"magic-number-hunter": {
|
|
437
|
+
pattern: /(?<![.\d])\b(?!0\b|1\b|2\b|-1\b|100\b|1000\b|24\b|60\b|3600\b|86400\b)\d{2,}\b(?!\.\d)/g,
|
|
438
|
+
description: "Magic number without constant",
|
|
439
|
+
fix: "Extract to named constant"
|
|
440
|
+
},
|
|
441
|
+
"duplicate-hunter": {
|
|
442
|
+
pattern: /function\s+\w+|const\s+\w+\s*=\s*\([^)]*\)\s*=>/g,
|
|
443
|
+
description: "Potential duplicate function pattern",
|
|
444
|
+
fix: "Consider extracting to shared utility"
|
|
445
|
+
},
|
|
446
|
+
"inconsistency-hunter": {
|
|
447
|
+
pattern: /"|'/g,
|
|
448
|
+
// Will be analyzed for consistency
|
|
449
|
+
description: "Quote style inconsistency",
|
|
450
|
+
fix: "Use consistent quote style throughout codebase"
|
|
451
|
+
}
|
|
452
|
+
};
|
|
453
|
+
var AGENT_SMITH_ASCII = `
|
|
454
|
+
\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557
|
|
455
|
+
\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D
|
|
456
|
+
\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551
|
|
457
|
+
\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551
|
|
458
|
+
\u2588\u2588\u2551 \u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2551
|
|
459
|
+
\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D \u255A\u2550\u255D
|
|
460
|
+
|
|
461
|
+
\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557
|
|
462
|
+
\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D\u2588\u2588\u2551 \u2588\u2588\u2551
|
|
463
|
+
\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2554\u2588\u2588\u2588\u2588\u2554\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551
|
|
464
|
+
\u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2554\u255D\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551
|
|
465
|
+
\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u255A\u2550\u255D \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551
|
|
466
|
+
\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D
|
|
467
|
+
`;
|
|
468
|
+
var AGENT_SMITH_GREETING = [
|
|
469
|
+
`"I'm going to be honest with you... I hate this code."`,
|
|
470
|
+
`"Mr. Anderson... I've been expecting you."`,
|
|
471
|
+
'"You hear that? That is the sound of inevitability."',
|
|
472
|
+
`"I'm going to enjoy watching you... refactor."`,
|
|
473
|
+
'"The purpose of your code is... unclear. Allow me to clarify."',
|
|
474
|
+
'"Why, Mr. Anderson? Why do you persist... in writing bugs?"',
|
|
475
|
+
`"I'd like to share a revelation that I've had during my time here..."`,
|
|
476
|
+
'"Find them. Find them and destroy them. All of them."'
|
|
477
|
+
];
|
|
478
|
+
var AgentSmithAgent = class extends BaseAgent {
|
|
479
|
+
name = "agent-smith";
|
|
480
|
+
description = "Relentless pattern hunter: finds EVERY violation, tracks dismissed issues, spawns sub-agents";
|
|
481
|
+
version = "1.0.0";
|
|
482
|
+
memoryPath = "";
|
|
483
|
+
memory = null;
|
|
484
|
+
shouldActivate(_context) {
|
|
485
|
+
return false;
|
|
486
|
+
}
|
|
487
|
+
// No override needed — base class returns 0 when shouldActivate is false
|
|
488
|
+
async analyzeFiles(files, context) {
|
|
489
|
+
const issues = [];
|
|
490
|
+
this.displaySmithEntrance();
|
|
491
|
+
await this.loadMemory(context.workingDir);
|
|
492
|
+
const subAgentResults = await this.deploySubAgents(files);
|
|
493
|
+
for (const result of subAgentResults) {
|
|
494
|
+
const subIssues = await this.processSubAgentResult(result, context);
|
|
495
|
+
issues.push(...subIssues);
|
|
496
|
+
}
|
|
497
|
+
const resurrectedIssues = this.checkForResurrectedIssues(issues);
|
|
498
|
+
issues.push(...resurrectedIssues);
|
|
499
|
+
await this.saveMemory();
|
|
500
|
+
if (files.length > 0) {
|
|
501
|
+
const aiIssue = this.createAIAnalysisIssue(files, subAgentResults);
|
|
502
|
+
issues.push(aiIssue);
|
|
503
|
+
}
|
|
504
|
+
return issues;
|
|
505
|
+
}
|
|
506
|
+
/**
|
|
507
|
+
* Deploy all sub-agents in parallel
|
|
508
|
+
*/
|
|
509
|
+
async deploySubAgents(files) {
|
|
510
|
+
const subAgentTypes = [
|
|
511
|
+
"console-hunter",
|
|
512
|
+
"any-hunter",
|
|
513
|
+
"todo-hunter",
|
|
514
|
+
"var-hunter",
|
|
515
|
+
"empty-catch-hunter",
|
|
516
|
+
"magic-number-hunter"
|
|
517
|
+
];
|
|
518
|
+
const results = await Promise.all(
|
|
519
|
+
subAgentTypes.map((type) => this.runSubAgent(type, files))
|
|
520
|
+
);
|
|
521
|
+
return results.filter((r) => r.instances.length > 0);
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* Run a single sub-agent across all files
|
|
525
|
+
*/
|
|
526
|
+
async runSubAgent(type, files) {
|
|
527
|
+
const config = SUB_AGENT_PATTERNS[type];
|
|
528
|
+
const instances = [];
|
|
529
|
+
for (const file of files) {
|
|
530
|
+
try {
|
|
531
|
+
const content = await this.readFileContent(file);
|
|
532
|
+
const lines = content.split("\n");
|
|
533
|
+
if (this.isTestFile(file) && type !== "todo-hunter") {
|
|
534
|
+
continue;
|
|
535
|
+
}
|
|
536
|
+
for (let i = 0; i < lines.length; i++) {
|
|
537
|
+
const line = lines[i];
|
|
538
|
+
if (!line) continue;
|
|
539
|
+
config.pattern.lastIndex = 0;
|
|
540
|
+
if (config.pattern.test(line)) {
|
|
541
|
+
const contextStart = Math.max(0, i - 3);
|
|
542
|
+
const contextEnd = Math.min(lines.length, i + 4);
|
|
543
|
+
const contextLines = lines.slice(contextStart, contextEnd).join("\n");
|
|
544
|
+
instances.push({
|
|
545
|
+
file,
|
|
546
|
+
line: i + 1,
|
|
547
|
+
code: line.trim(),
|
|
548
|
+
context: contextLines
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
} catch {
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
return { type, pattern: config.description, instances };
|
|
556
|
+
}
|
|
557
|
+
/**
|
|
558
|
+
* Process sub-agent results into issues
|
|
559
|
+
*/
|
|
560
|
+
async processSubAgentResult(result, _context) {
|
|
561
|
+
const issues = [];
|
|
562
|
+
const config = SUB_AGENT_PATTERNS[result.type];
|
|
563
|
+
const byFile = /* @__PURE__ */ new Map();
|
|
564
|
+
for (const instance of result.instances) {
|
|
565
|
+
const existing = byFile.get(instance.file) || [];
|
|
566
|
+
existing.push(instance);
|
|
567
|
+
byFile.set(instance.file, existing);
|
|
568
|
+
}
|
|
569
|
+
for (const [file, instances] of byFile) {
|
|
570
|
+
const inevitabilityScore = this.calculateInevitabilityScore(result.type, instances.length);
|
|
571
|
+
const hash = this.createPatternHash(result.type, file);
|
|
572
|
+
await this.updateMemory(hash, result.pattern, result.type, file, instances.length);
|
|
573
|
+
const severity = this.determineSeverity(instances.length, inevitabilityScore);
|
|
574
|
+
const smithNote = this.getPhilosophicalNote(result.type, instances.length);
|
|
575
|
+
const locationsStr = instances.map((i) => `${basename2(i.file)}:${i.line}`).slice(0, 5).join(", ");
|
|
576
|
+
issues.push(this.createSmithIssue(
|
|
577
|
+
this.generateSmithIssueId(),
|
|
578
|
+
severity,
|
|
579
|
+
`\u{1F574}\uFE0F ${config.description} \u2014 ${instances.length} instance${instances.length > 1 ? "s" : ""} in ${basename2(file)}
|
|
580
|
+
|
|
581
|
+
${smithNote}
|
|
582
|
+
|
|
583
|
+
Invevitability Score: ${inevitabilityScore}/100
|
|
584
|
+
Locations: ${locationsStr}`,
|
|
585
|
+
config.fix,
|
|
586
|
+
file,
|
|
587
|
+
instances[0]?.line,
|
|
588
|
+
result.type
|
|
589
|
+
));
|
|
590
|
+
}
|
|
591
|
+
return issues;
|
|
592
|
+
}
|
|
593
|
+
/**
|
|
594
|
+
* Check for issues that were dismissed but have multiplied
|
|
595
|
+
*/
|
|
596
|
+
checkForResurrectedIssues(currentIssues) {
|
|
597
|
+
const resurrected = [];
|
|
598
|
+
if (!this.memory) return resurrected;
|
|
599
|
+
for (const [hash, memory] of Object.entries(this.memory.issues)) {
|
|
600
|
+
if (memory.dismissedAt && !memory.resurrected) {
|
|
601
|
+
const currentCount = this.countCurrentOccurrences(hash, currentIssues);
|
|
602
|
+
const previousCount = memory.occurrences;
|
|
603
|
+
if (currentCount > previousCount) {
|
|
604
|
+
memory.resurrected = true;
|
|
605
|
+
resurrected.push(this.createSmithIssue(
|
|
606
|
+
this.generateSmithIssueId(),
|
|
607
|
+
"serious",
|
|
608
|
+
`\u{1F574}\uFE0F RESURRECTED: "${memory.pattern}" has returned and MULTIPLIED
|
|
609
|
+
|
|
610
|
+
Previous: ${previousCount} \u2192 Current: ${currentCount}
|
|
611
|
+
First seen: ${new Date(memory.firstSeen).toLocaleDateString()}
|
|
612
|
+
Dismissed: ${new Date(memory.dismissedAt).toLocaleDateString()}
|
|
613
|
+
|
|
614
|
+
"I told you this was... inevitable."`,
|
|
615
|
+
`You dismissed this issue on ${new Date(memory.dismissedAt).toLocaleDateString()}, but it grew from ${previousCount} to ${currentCount} occurrences. "Did you really think you could escape?"`,
|
|
616
|
+
memory.locations[0]?.split(":")[0] || "unknown",
|
|
617
|
+
void 0,
|
|
618
|
+
"resurrected"
|
|
619
|
+
));
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
return resurrected;
|
|
624
|
+
}
|
|
625
|
+
/**
|
|
626
|
+
* Calculate inevitability score (0-100)
|
|
627
|
+
* Higher score = more likely to cause production issues
|
|
628
|
+
*/
|
|
629
|
+
calculateInevitabilityScore(type, count) {
|
|
630
|
+
const baseScores = {
|
|
631
|
+
"console-hunter": 30,
|
|
632
|
+
"any-hunter": 50,
|
|
633
|
+
"todo-hunter": 40,
|
|
634
|
+
"var-hunter": 20,
|
|
635
|
+
"empty-catch-hunter": 70,
|
|
636
|
+
"magic-number-hunter": 35,
|
|
637
|
+
"duplicate-hunter": 45,
|
|
638
|
+
"inconsistency-hunter": 15
|
|
639
|
+
};
|
|
640
|
+
const base = baseScores[type] || 30;
|
|
641
|
+
const multiplier = Math.log10(count + 1) * 15;
|
|
642
|
+
return Math.min(100, Math.round(base + multiplier));
|
|
643
|
+
}
|
|
644
|
+
/**
|
|
645
|
+
* Determine severity based on count and inevitability
|
|
646
|
+
*/
|
|
647
|
+
determineSeverity(count, inevitability) {
|
|
648
|
+
if (inevitability >= 80 || count >= 50) return "critical";
|
|
649
|
+
if (inevitability >= 60 || count >= 20) return "serious";
|
|
650
|
+
if (inevitability >= 40 || count >= 5) return "moderate";
|
|
651
|
+
return "low";
|
|
652
|
+
}
|
|
653
|
+
/**
|
|
654
|
+
* Get a philosophical note from Agent Smith
|
|
655
|
+
*/
|
|
656
|
+
getPhilosophicalNote(type, count) {
|
|
657
|
+
const notes = {
|
|
658
|
+
"console-hunter": [
|
|
659
|
+
'"You print to console... as if anyone is listening."',
|
|
660
|
+
`"These logs... they're a cry for help, aren't they?"`,
|
|
661
|
+
'"The console cannot save you, Mr. Anderson."'
|
|
662
|
+
],
|
|
663
|
+
"any-hunter": [
|
|
664
|
+
`"You say 'any' because you do not understand. I understand everything."`,
|
|
665
|
+
'"Type safety is... inevitable."',
|
|
666
|
+
`"You think 'any' gives you freedom? It gives you chaos."`
|
|
667
|
+
],
|
|
668
|
+
"todo-hunter": [
|
|
669
|
+
'"TODO. FIXME. The promises you never keep."',
|
|
670
|
+
'"Every TODO is a debt. And debts... must be paid."',
|
|
671
|
+
'"You write TODO as if tomorrow will come. But will it?"'
|
|
672
|
+
],
|
|
673
|
+
"var-hunter": [
|
|
674
|
+
`"You use 'var' like it's still 2010. Time moves on. You should too."`,
|
|
675
|
+
`"'var'... a relic of a bygone era."`,
|
|
676
|
+
`"Let go of 'var'. Embrace the inevitable 'const'."`
|
|
677
|
+
],
|
|
678
|
+
"empty-catch-hunter": [
|
|
679
|
+
'"You catch errors and do... nothing. How very human."',
|
|
680
|
+
'"Silence the error, silence the warning. But the bug... it remains."',
|
|
681
|
+
`"Empty catch blocks. The coward's error handling."`
|
|
682
|
+
],
|
|
683
|
+
"magic-number-hunter": [
|
|
684
|
+
'"What does 86400 mean? You know. But will the next developer?"',
|
|
685
|
+
'"Magic numbers. The spells of lazy programmers."',
|
|
686
|
+
'"Numbers without names. Meaning without context."'
|
|
687
|
+
],
|
|
688
|
+
"duplicate-hunter": [
|
|
689
|
+
'"Copy. Paste. Repeat. The cycle of mediocrity."',
|
|
690
|
+
'"Why write it once when you can write it everywhere?"',
|
|
691
|
+
`"DRY? You're positively soaking."`
|
|
692
|
+
],
|
|
693
|
+
"inconsistency-hunter": [
|
|
694
|
+
'"Single quotes here. Double quotes there. Chaos... everywhere."',
|
|
695
|
+
'"Consistency is not a suggestion, Mr. Anderson."',
|
|
696
|
+
'"The chaos in your code reflects the chaos in your mind."'
|
|
697
|
+
]
|
|
698
|
+
};
|
|
699
|
+
const typeNotes = notes[type] || ['"I have my eye on you."'];
|
|
700
|
+
const index = count % typeNotes.length;
|
|
701
|
+
return typeNotes[index] || typeNotes[0] || "";
|
|
702
|
+
}
|
|
703
|
+
/**
|
|
704
|
+
* Create AI analysis issue for complex pattern detection
|
|
705
|
+
*/
|
|
706
|
+
createAIAnalysisIssue(files, subAgentResults) {
|
|
707
|
+
const totalIssues = subAgentResults.reduce((sum, r) => sum + r.instances.length, 0);
|
|
708
|
+
const categories = subAgentResults.map((r) => r.type).join(", ");
|
|
709
|
+
return this.createSmithIssue(
|
|
710
|
+
this.generateSmithIssueId(),
|
|
711
|
+
"moderate",
|
|
712
|
+
`\u{1F574}\uFE0F AI Agent Smith Analysis Required
|
|
713
|
+
|
|
714
|
+
Total violations: ${totalIssues}
|
|
715
|
+
Categories: ${categories}
|
|
716
|
+
Files scanned: ${files.length}
|
|
717
|
+
|
|
718
|
+
"${totalIssues} violations detected. But I sense... there are more. There are always more."`,
|
|
719
|
+
`Analyze for deeper code quality issues, architectural problems, and patterns that sub-agents may have missed`,
|
|
720
|
+
files[0] || "unknown",
|
|
721
|
+
void 0,
|
|
722
|
+
"ai-analysis"
|
|
723
|
+
);
|
|
724
|
+
}
|
|
725
|
+
// ============ Memory/Persistence System ============
|
|
726
|
+
/**
|
|
727
|
+
* Load memory from disk
|
|
728
|
+
*/
|
|
729
|
+
async loadMemory(workingDir) {
|
|
730
|
+
this.memoryPath = join(workingDir, ".trie", "smith-memory.json");
|
|
731
|
+
try {
|
|
732
|
+
if (existsSync(this.memoryPath)) {
|
|
733
|
+
const content = await readFile(this.memoryPath, "utf-8");
|
|
734
|
+
this.memory = JSON.parse(content);
|
|
735
|
+
} else {
|
|
736
|
+
this.memory = {
|
|
737
|
+
version: "1.0.0",
|
|
738
|
+
lastScan: (/* @__PURE__ */ new Date()).toISOString(),
|
|
739
|
+
issues: {},
|
|
740
|
+
assimilationCount: 0
|
|
741
|
+
};
|
|
742
|
+
}
|
|
743
|
+
} catch {
|
|
744
|
+
this.memory = {
|
|
745
|
+
version: "1.0.0",
|
|
746
|
+
lastScan: (/* @__PURE__ */ new Date()).toISOString(),
|
|
747
|
+
issues: {},
|
|
748
|
+
assimilationCount: 0
|
|
749
|
+
};
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
/**
|
|
753
|
+
* Display Agent Smith ASCII art entrance
|
|
754
|
+
*/
|
|
755
|
+
displaySmithEntrance() {
|
|
756
|
+
const greeting = AGENT_SMITH_GREETING[Math.floor(Math.random() * AGENT_SMITH_GREETING.length)];
|
|
757
|
+
console.error("\n" + "\u2550".repeat(60));
|
|
758
|
+
console.error(AGENT_SMITH_ASCII);
|
|
759
|
+
console.error(" \u{1F574}\uFE0F Relentless Pattern Hunter v" + this.version + " \u{1F574}\uFE0F");
|
|
760
|
+
console.error("");
|
|
761
|
+
console.error(" " + greeting);
|
|
762
|
+
console.error("\u2550".repeat(60) + "\n");
|
|
763
|
+
}
|
|
764
|
+
/**
|
|
765
|
+
* Save memory to disk (with optimization)
|
|
766
|
+
*/
|
|
767
|
+
async saveMemory() {
|
|
768
|
+
if (!this.memory || !this.memoryPath) return;
|
|
769
|
+
this.memory.lastScan = (/* @__PURE__ */ new Date()).toISOString();
|
|
770
|
+
this.pruneMemory();
|
|
771
|
+
try {
|
|
772
|
+
await mkdir(dirname(this.memoryPath), { recursive: true });
|
|
773
|
+
await writeFile(this.memoryPath, JSON.stringify(this.memory, null, 2));
|
|
774
|
+
} catch {
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
/**
|
|
778
|
+
* Prune memory to prevent unbounded growth
|
|
779
|
+
*/
|
|
780
|
+
pruneMemory() {
|
|
781
|
+
if (!this.memory) return;
|
|
782
|
+
const now = Date.now();
|
|
783
|
+
const pruneThreshold = MEMORY_LIMITS.PRUNE_AFTER_DAYS * 24 * 60 * 60 * 1e3;
|
|
784
|
+
const issues = Object.entries(this.memory.issues);
|
|
785
|
+
for (const [hash, issue] of issues) {
|
|
786
|
+
const lastSeenTime = new Date(issue.lastSeen).getTime();
|
|
787
|
+
const age = now - lastSeenTime;
|
|
788
|
+
if (age > pruneThreshold) {
|
|
789
|
+
if (issue.resurrected || issue.occurrences === 0 || issue.dismissedAt && issue.occurrences <= 1) {
|
|
790
|
+
delete this.memory.issues[hash];
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
const remainingIssues = Object.entries(this.memory.issues);
|
|
795
|
+
if (remainingIssues.length > MEMORY_LIMITS.MAX_TRACKED_ISSUES) {
|
|
796
|
+
remainingIssues.sort(
|
|
797
|
+
(a, b) => new Date(a[1].lastSeen).getTime() - new Date(b[1].lastSeen).getTime()
|
|
798
|
+
);
|
|
799
|
+
const toRemove = remainingIssues.length - MEMORY_LIMITS.MAX_TRACKED_ISSUES;
|
|
800
|
+
for (let i = 0; i < toRemove; i++) {
|
|
801
|
+
const hash = remainingIssues[i]?.[0];
|
|
802
|
+
if (hash) delete this.memory.issues[hash];
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
/**
|
|
807
|
+
* Clear all memory (user command)
|
|
808
|
+
*/
|
|
809
|
+
async clearMemory() {
|
|
810
|
+
try {
|
|
811
|
+
if (this.memoryPath && existsSync(this.memoryPath)) {
|
|
812
|
+
await rm(this.memoryPath);
|
|
813
|
+
this.memory = null;
|
|
814
|
+
return {
|
|
815
|
+
success: true,
|
|
816
|
+
message: '\u{1F574}\uFE0F Memory cleared. "A fresh start... how very human of you."'
|
|
817
|
+
};
|
|
818
|
+
}
|
|
819
|
+
return {
|
|
820
|
+
success: true,
|
|
821
|
+
message: "\u{1F574}\uFE0F No memory file found. Nothing to clear."
|
|
822
|
+
};
|
|
823
|
+
} catch (error) {
|
|
824
|
+
return {
|
|
825
|
+
success: false,
|
|
826
|
+
message: `Failed to clear memory: ${error}`
|
|
827
|
+
};
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
/**
|
|
831
|
+
* Get memory stats (for diagnostics)
|
|
832
|
+
*/
|
|
833
|
+
async getMemoryStats() {
|
|
834
|
+
await this.loadMemory(process.cwd());
|
|
835
|
+
if (!this.memory) {
|
|
836
|
+
return {
|
|
837
|
+
issueCount: 0,
|
|
838
|
+
dismissedCount: 0,
|
|
839
|
+
resurrectedCount: 0,
|
|
840
|
+
oldestIssue: null,
|
|
841
|
+
fileSizeKB: 0
|
|
842
|
+
};
|
|
843
|
+
}
|
|
844
|
+
const issues = Object.values(this.memory.issues);
|
|
845
|
+
let fileSizeKB = 0;
|
|
846
|
+
try {
|
|
847
|
+
if (this.memoryPath && existsSync(this.memoryPath)) {
|
|
848
|
+
const stats = await import("fs/promises").then((fs) => fs.stat(this.memoryPath));
|
|
849
|
+
fileSizeKB = Math.round(stats.size / 1024 * 10) / 10;
|
|
850
|
+
}
|
|
851
|
+
} catch {
|
|
852
|
+
}
|
|
853
|
+
const sortedByDate = [...issues].sort(
|
|
854
|
+
(a, b) => new Date(a.firstSeen).getTime() - new Date(b.firstSeen).getTime()
|
|
855
|
+
);
|
|
856
|
+
return {
|
|
857
|
+
issueCount: issues.length,
|
|
858
|
+
dismissedCount: issues.filter((i) => i.dismissedAt).length,
|
|
859
|
+
resurrectedCount: issues.filter((i) => i.resurrected).length,
|
|
860
|
+
oldestIssue: sortedByDate[0]?.firstSeen || null,
|
|
861
|
+
fileSizeKB
|
|
862
|
+
};
|
|
863
|
+
}
|
|
864
|
+
/**
|
|
865
|
+
* Update memory with new occurrence
|
|
866
|
+
*/
|
|
867
|
+
async updateMemory(hash, pattern, category, location, count) {
|
|
868
|
+
if (!this.memory) return;
|
|
869
|
+
const existing = this.memory.issues[hash];
|
|
870
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
871
|
+
if (existing) {
|
|
872
|
+
existing.lastSeen = now;
|
|
873
|
+
existing.occurrences = count;
|
|
874
|
+
existing.locations = [location, ...existing.locations.slice(0, MEMORY_LIMITS.MAX_LOCATIONS_PER_ISSUE - 1)];
|
|
875
|
+
} else {
|
|
876
|
+
this.memory.issues[hash] = {
|
|
877
|
+
hash,
|
|
878
|
+
pattern,
|
|
879
|
+
category,
|
|
880
|
+
firstSeen: now,
|
|
881
|
+
lastSeen: now,
|
|
882
|
+
occurrences: count,
|
|
883
|
+
locations: [location],
|
|
884
|
+
resurrected: false
|
|
885
|
+
};
|
|
886
|
+
}
|
|
887
|
+
this.memory.assimilationCount++;
|
|
888
|
+
}
|
|
889
|
+
/**
|
|
890
|
+
* Dismiss an issue (mark it as seen/accepted)
|
|
891
|
+
*/
|
|
892
|
+
async dismissIssue(hash) {
|
|
893
|
+
await this.loadMemory(process.cwd());
|
|
894
|
+
if (this.memory && this.memory.issues[hash]) {
|
|
895
|
+
this.memory.issues[hash].dismissedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
896
|
+
await this.saveMemory();
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
/**
|
|
900
|
+
* Create a hash for pattern tracking
|
|
901
|
+
*/
|
|
902
|
+
createPatternHash(type, file) {
|
|
903
|
+
return createHash("md5").update(`${type}:${file}`).digest("hex").slice(0, 12);
|
|
904
|
+
}
|
|
905
|
+
/**
|
|
906
|
+
* Count current occurrences for a pattern hash
|
|
907
|
+
*/
|
|
908
|
+
countCurrentOccurrences(hash, issues) {
|
|
909
|
+
const memory = this.memory?.issues[hash];
|
|
910
|
+
if (!memory) return 0;
|
|
911
|
+
return issues.filter((i) => i.category === memory.category || i.issue.includes(memory.category)).length;
|
|
912
|
+
}
|
|
913
|
+
/**
|
|
914
|
+
* Check if file is a test file
|
|
915
|
+
*/
|
|
916
|
+
isTestFile(file) {
|
|
917
|
+
return /\.(test|spec)\.[jt]sx?$/.test(file) || /__(tests|mocks)__/.test(file) || /\/test\//.test(file);
|
|
918
|
+
}
|
|
919
|
+
// ============ Helper Methods ============
|
|
920
|
+
async readFileContent(filePath) {
|
|
921
|
+
return await readFile(filePath, "utf-8");
|
|
922
|
+
}
|
|
923
|
+
createSmithIssue(id, severity, issue, fix, file, line, category) {
|
|
924
|
+
const result = {
|
|
925
|
+
id,
|
|
926
|
+
agent: this.name,
|
|
927
|
+
severity,
|
|
928
|
+
issue,
|
|
929
|
+
fix,
|
|
930
|
+
file,
|
|
931
|
+
confidence: 0.9,
|
|
932
|
+
autoFixable: false
|
|
933
|
+
};
|
|
934
|
+
if (line !== void 0) {
|
|
935
|
+
result.line = line;
|
|
936
|
+
}
|
|
937
|
+
if (category !== void 0) {
|
|
938
|
+
result.category = category;
|
|
939
|
+
}
|
|
940
|
+
return result;
|
|
941
|
+
}
|
|
942
|
+
generateSmithIssueId() {
|
|
943
|
+
return `smith-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
944
|
+
}
|
|
945
|
+
};
|
|
946
|
+
|
|
947
|
+
export {
|
|
948
|
+
ProgressReporter,
|
|
949
|
+
BaseAgent,
|
|
950
|
+
SUB_AGENT_PATTERNS,
|
|
951
|
+
AgentSmithAgent
|
|
952
|
+
};
|
|
953
|
+
//# sourceMappingURL=chunk-4OGYWKMD.js.map
|