@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.
@@ -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