@triedotdev/mcp 1.0.94 → 1.0.99

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/README.md +145 -137
  2. package/dist/{chunk-JAAIHNOE.js → chunk-APMV77PU.js} +21 -6
  3. package/dist/chunk-APMV77PU.js.map +1 -0
  4. package/dist/{chunk-HLSBTOVE.js → chunk-B3MNN3XB.js} +13 -18
  5. package/dist/{chunk-HLSBTOVE.js.map → chunk-B3MNN3XB.js.map} +1 -1
  6. package/dist/{chunk-IIF5XDCJ.js → chunk-DIZFGLXE.js} +787 -4696
  7. package/dist/chunk-DIZFGLXE.js.map +1 -0
  8. package/dist/{chunk-JO6RVXS6.js → chunk-F4NJ4CBP.js} +2 -2
  9. package/dist/{chunk-AZRCKBGF.js → chunk-FNCCZ3XB.js} +1222 -75
  10. package/dist/chunk-FNCCZ3XB.js.map +1 -0
  11. package/dist/chunk-G76DYVGX.js +136 -0
  12. package/dist/chunk-G76DYVGX.js.map +1 -0
  13. package/dist/chunk-HSNE46VE.js +956 -0
  14. package/dist/chunk-HSNE46VE.js.map +1 -0
  15. package/dist/{chunk-STEFLYPR.js → chunk-IXO4G4D3.js} +2 -2
  16. package/dist/{chunk-OEYIOOYB.js → chunk-JDHR5BDR.js} +2 -3
  17. package/dist/chunk-NIASHOAB.js +1304 -0
  18. package/dist/chunk-NIASHOAB.js.map +1 -0
  19. package/dist/{chunk-CKM6A3G6.js → chunk-OVRG5RP3.js} +6 -7
  20. package/dist/chunk-OVRG5RP3.js.map +1 -0
  21. package/dist/{chunk-RYRVEO2B.js → chunk-R3I2GCZC.js} +3 -3
  22. package/dist/{chunk-WT3XQCG2.js → chunk-R4AAPFXC.js} +2 -2
  23. package/dist/cli/create-agent.js +931 -7
  24. package/dist/cli/create-agent.js.map +1 -1
  25. package/dist/cli/main.js +151 -383
  26. package/dist/cli/main.js.map +1 -1
  27. package/dist/cli/yolo-daemon.js +13 -20
  28. package/dist/cli/yolo-daemon.js.map +1 -1
  29. package/dist/{goal-manager-HOZ7R2QV.js → goal-manager-LAOT4QQX.js} +6 -6
  30. package/dist/guardian-agent-M352CBE5.js +19 -0
  31. package/dist/index.js +1025 -1550
  32. package/dist/index.js.map +1 -1
  33. package/dist/{issue-store-DXIOP6AK.js → issue-store-W2X33X2X.js} +4 -4
  34. package/dist/{progress-LHI66U7B.js → progress-PQVEM7BR.js} +2 -2
  35. package/dist/{vibe-code-signatures-C5A4BHXD.js → vibe-code-signatures-ELEWJFGZ.js} +3 -3
  36. package/dist/{vulnerability-signatures-SVIHJQO5.js → vulnerability-signatures-EIJQX2TS.js} +3 -3
  37. package/dist/workers/agent-worker.js +2 -11
  38. package/dist/workers/agent-worker.js.map +1 -1
  39. package/package.json +2 -2
  40. package/dist/agent-smith-MYQ35URL.js +0 -14
  41. package/dist/agent-smith-runner-4TBONXCP.js +0 -573
  42. package/dist/agent-smith-runner-4TBONXCP.js.map +0 -1
  43. package/dist/cache-manager-RMPRPD5T.js +0 -10
  44. package/dist/chunk-AZRCKBGF.js.map +0 -1
  45. package/dist/chunk-CKM6A3G6.js.map +0 -1
  46. package/dist/chunk-E2ZATINO.js +0 -10879
  47. package/dist/chunk-E2ZATINO.js.map +0 -1
  48. package/dist/chunk-FFWNZUG2.js +0 -266
  49. package/dist/chunk-FFWNZUG2.js.map +0 -1
  50. package/dist/chunk-FK6DQKDY.js +0 -175
  51. package/dist/chunk-FK6DQKDY.js.map +0 -1
  52. package/dist/chunk-IFGF33R5.js +0 -279
  53. package/dist/chunk-IFGF33R5.js.map +0 -1
  54. package/dist/chunk-IIF5XDCJ.js.map +0 -1
  55. package/dist/chunk-JAAIHNOE.js.map +0 -1
  56. package/dist/chunk-ODWDESYP.js +0 -141
  57. package/dist/chunk-ODWDESYP.js.map +0 -1
  58. package/dist/chunk-OWBWNXSC.js +0 -955
  59. package/dist/chunk-OWBWNXSC.js.map +0 -1
  60. package/dist/chunk-Q764X2WD.js +0 -2124
  61. package/dist/chunk-Q764X2WD.js.map +0 -1
  62. package/dist/chunk-RE6ZWXJC.js +0 -279
  63. package/dist/chunk-RE6ZWXJC.js.map +0 -1
  64. package/dist/chunk-RNJ6JKMA.js +0 -2270
  65. package/dist/chunk-RNJ6JKMA.js.map +0 -1
  66. package/dist/chunk-Y62VM3ER.js +0 -536
  67. package/dist/chunk-Y62VM3ER.js.map +0 -1
  68. package/dist/git-45LZUUYA.js +0 -29
  69. package/dist/guardian-agent-RB2UQP5V.js +0 -21
  70. package/dist/progress-LHI66U7B.js.map +0 -1
  71. package/dist/vibe-code-signatures-C5A4BHXD.js.map +0 -1
  72. package/dist/vulnerability-signatures-SVIHJQO5.js.map +0 -1
  73. /package/dist/{chunk-JO6RVXS6.js.map → chunk-F4NJ4CBP.js.map} +0 -0
  74. /package/dist/{chunk-STEFLYPR.js.map → chunk-IXO4G4D3.js.map} +0 -0
  75. /package/dist/{chunk-OEYIOOYB.js.map → chunk-JDHR5BDR.js.map} +0 -0
  76. /package/dist/{chunk-RYRVEO2B.js.map → chunk-R3I2GCZC.js.map} +0 -0
  77. /package/dist/{chunk-WT3XQCG2.js.map → chunk-R4AAPFXC.js.map} +0 -0
  78. /package/dist/{agent-smith-MYQ35URL.js.map → goal-manager-LAOT4QQX.js.map} +0 -0
  79. /package/dist/{cache-manager-RMPRPD5T.js.map → guardian-agent-M352CBE5.js.map} +0 -0
  80. /package/dist/{git-45LZUUYA.js.map → issue-store-W2X33X2X.js.map} +0 -0
  81. /package/dist/{goal-manager-HOZ7R2QV.js.map → progress-PQVEM7BR.js.map} +0 -0
  82. /package/dist/{guardian-agent-RB2UQP5V.js.map → vibe-code-signatures-ELEWJFGZ.js.map} +0 -0
  83. /package/dist/{issue-store-DXIOP6AK.js.map → vulnerability-signatures-EIJQX2TS.js.map} +0 -0
@@ -1,2270 +0,0 @@
1
- import {
2
- getAIStatusMessage,
3
- isAIAvailable,
4
- runAIAnalysis
5
- } from "./chunk-ODWDESYP.js";
6
- import {
7
- getTrieDirectory
8
- } from "./chunk-WT3XQCG2.js";
9
- import {
10
- AgentProgressReporter,
11
- isInteractiveMode
12
- } from "./chunk-JAAIHNOE.js";
13
-
14
- // src/skills/built-in/base-skill.ts
15
- import { relative } from "path";
16
-
17
- // src/utils/output-manager.ts
18
- import pc from "picocolors";
19
- var OutputManagerImpl = class {
20
- mode = "console";
21
- streamingManager;
22
- markdownBuffer = [];
23
- jsonBuffer = [];
24
- rawLogBuffer = [];
25
- // Callbacks for TUI integration (explicitly allow undefined for exactOptionalPropertyTypes)
26
- onBanner = void 0;
27
- onSnippet = void 0;
28
- onCost = void 0;
29
- onReadiness = void 0;
30
- onSemantic = void 0;
31
- onAttack = void 0;
32
- onActivity = void 0;
33
- onLog = void 0;
34
- onNudge = void 0;
35
- /**
36
- * Set the output mode
37
- */
38
- setMode(mode) {
39
- this.mode = mode;
40
- }
41
- /**
42
- * Get current output mode
43
- */
44
- getMode() {
45
- return this.mode;
46
- }
47
- /**
48
- * Set streaming manager for TUI updates
49
- */
50
- setStreamingManager(manager) {
51
- this.streamingManager = manager;
52
- }
53
- /**
54
- * Register TUI callbacks for rich content
55
- */
56
- registerTUICallbacks(callbacks) {
57
- this.onBanner = callbacks.onBanner;
58
- this.onSnippet = callbacks.onSnippet;
59
- this.onCost = callbacks.onCost;
60
- this.onReadiness = callbacks.onReadiness;
61
- this.onSemantic = callbacks.onSemantic;
62
- this.onAttack = callbacks.onAttack;
63
- this.onActivity = callbacks.onActivity;
64
- this.onLog = callbacks.onLog;
65
- this.onNudge = callbacks.onNudge;
66
- }
67
- /**
68
- * Clear TUI callbacks (when dashboard stops)
69
- */
70
- clearTUICallbacks() {
71
- this.onBanner = void 0;
72
- this.onSnippet = void 0;
73
- this.onCost = void 0;
74
- this.onReadiness = void 0;
75
- this.onSemantic = void 0;
76
- this.onAttack = void 0;
77
- this.onActivity = void 0;
78
- this.onLog = void 0;
79
- this.onNudge = void 0;
80
- }
81
- /**
82
- * Emit content - routes to appropriate handler based on mode
83
- */
84
- emit(content) {
85
- content.timestamp = content.timestamp ?? Date.now();
86
- switch (this.mode) {
87
- case "tui":
88
- this.routeToTUI(content);
89
- break;
90
- case "console":
91
- this.routeToConsole(content);
92
- break;
93
- case "mcp":
94
- this.routeToMarkdown(content);
95
- break;
96
- case "json":
97
- this.routeToJson(content);
98
- break;
99
- case "silent":
100
- break;
101
- }
102
- this.captureRawLog(content);
103
- }
104
- /**
105
- * Route content to TUI (dashboard callbacks)
106
- */
107
- routeToTUI(content) {
108
- switch (content.type) {
109
- case "banner":
110
- this.onBanner?.(content.content);
111
- break;
112
- case "snippet":
113
- this.onSnippet?.(content.content);
114
- break;
115
- case "cost":
116
- this.onCost?.(content.content);
117
- break;
118
- case "readiness":
119
- this.onReadiness?.(content.content);
120
- break;
121
- case "semantic":
122
- this.onSemantic?.(content.content);
123
- break;
124
- case "attack":
125
- this.onAttack?.(content.content);
126
- break;
127
- case "activity":
128
- this.onActivity?.(content.content);
129
- break;
130
- case "log":
131
- const level = content.metadata?.severity ?? "info";
132
- this.onLog?.(level, content.content);
133
- break;
134
- case "issue":
135
- this.streamingManager?.reportIssue(content.content);
136
- break;
137
- case "progress":
138
- break;
139
- case "report":
140
- break;
141
- case "nudge":
142
- this.onNudge?.(content.content);
143
- break;
144
- }
145
- }
146
- /**
147
- * Route content to console (ANSI formatted)
148
- */
149
- routeToConsole(content) {
150
- switch (content.type) {
151
- case "banner":
152
- const banner = content.content;
153
- console.error("\n" + "=".repeat(60));
154
- console.error(banner.art);
155
- if (banner.version) {
156
- console.error(` ${banner.skill} v${banner.version}`);
157
- }
158
- console.error("");
159
- if (banner.quote) {
160
- console.error(` "${banner.quote}"`);
161
- }
162
- console.error("=".repeat(60) + "\n");
163
- break;
164
- case "snippet":
165
- const snippet = content.content;
166
- console.error(`
167
- ${pc.dim("File:")} ${snippet.file}`);
168
- for (let i = 0; i < snippet.lines.length; i++) {
169
- const lineNum = snippet.startLine + i;
170
- const isHighlight = lineNum === snippet.highlightLine;
171
- const prefix = isHighlight ? pc.red("\u2192") : " ";
172
- const lineNumStr = pc.dim(lineNum.toString().padStart(4));
173
- const line = isHighlight ? pc.yellow(snippet.lines[i]) : snippet.lines[i];
174
- console.error(`${prefix} ${lineNumStr} | ${line}`);
175
- }
176
- console.error("");
177
- break;
178
- case "cost":
179
- const cost = content.content;
180
- console.error("\n" + pc.cyan("[$] Cost Estimate:"));
181
- console.error(` Fix now: ${pc.green(this.formatCurrency(cost.fixNowCost))}`);
182
- console.error(` If production: ${pc.red(this.formatCurrency(cost.productionCost))}`);
183
- console.error(` Savings: ${pc.yellow(this.formatCurrency(cost.savings))}`);
184
- console.error("");
185
- break;
186
- case "readiness":
187
- const readiness = content.content;
188
- const statusColor = readiness.status === "ready" ? pc.green : readiness.status === "caution" ? pc.yellow : pc.red;
189
- console.error("\n" + pc.cyan("[%] Production Readiness:"));
190
- console.error(` Score: ${statusColor(readiness.score + "/100")}`);
191
- console.error(` Requirements: ${readiness.requirementsMet}/${readiness.total}`);
192
- console.error(` Status: ${statusColor(readiness.status.toUpperCase())}`);
193
- console.error("");
194
- break;
195
- case "semantic":
196
- const semantic = content.content;
197
- console.error("\n" + pc.cyan("[?] Semantic Analysis:"));
198
- if (semantic.dataFlowIssues > 0) {
199
- console.error(` ${pc.red("[!]")} ${semantic.dataFlowIssues} data flow vulnerabilities`);
200
- }
201
- if (semantic.raceConditions > 0) {
202
- console.error(` ${pc.yellow("[~]")} ${semantic.raceConditions} race conditions`);
203
- }
204
- if (semantic.authIssues > 0) {
205
- console.error(` ${pc.red("[!]")} ${semantic.authIssues} authentication issues`);
206
- }
207
- console.error("");
208
- break;
209
- case "attack":
210
- const attack = content.content;
211
- console.error("\n" + pc.cyan("[>] Attack Surface:"));
212
- console.error(` Endpoints: ${attack.totalEndpoints}`);
213
- if (attack.unprotected > 0) {
214
- console.error(` ${pc.red("Unprotected:")} ${attack.unprotected}`);
215
- }
216
- console.error(` Risk Score: ${attack.riskScore}/100`);
217
- console.error("");
218
- break;
219
- case "activity":
220
- console.error(pc.dim(`[${this.formatTime()}]`) + ` ${content.content}`);
221
- break;
222
- case "log":
223
- const logLevel = String(content.metadata?.severity ?? "info");
224
- const levelColor = logLevel === "error" || logLevel === "critical" ? pc.red : logLevel === "warn" || logLevel === "serious" ? pc.yellow : logLevel === "info" || logLevel === "moderate" ? pc.blue : pc.dim;
225
- console.error(levelColor(`[${logLevel.toUpperCase()}]`) + ` ${content.content}`);
226
- break;
227
- case "issue":
228
- const issue = content.content;
229
- const sevColor = issue.severity === "critical" ? pc.red : issue.severity === "serious" ? pc.yellow : issue.severity === "moderate" ? pc.blue : pc.dim;
230
- console.error(`${sevColor(`[${issue.severity.toUpperCase()}]`)} ${issue.issue}`);
231
- console.error(` ${pc.dim("File:")} ${issue.file}:${issue.line ?? "?"}`);
232
- break;
233
- case "report":
234
- console.error(content.content);
235
- break;
236
- case "nudge":
237
- const nudge = content.content;
238
- const nudgeColor = nudge.severity === "critical" ? pc.red : nudge.severity === "warning" ? pc.yellow : pc.cyan;
239
- const nudgeIcon = nudge.severity === "critical" ? "[!!!]" : nudge.severity === "warning" ? "[!]" : "[>]";
240
- console.error("");
241
- console.error(nudgeColor("\u2501".repeat(60)));
242
- console.error(nudgeColor(`${nudgeIcon} TRIE AGENT SAYS:`));
243
- console.error(nudgeColor("\u2501".repeat(60)));
244
- console.error("");
245
- console.error(` ${pc.bold(nudge.message)}`);
246
- if (nudge.file) {
247
- console.error(` ${pc.dim("File:")} ${nudge.file}`);
248
- }
249
- console.error("");
250
- console.error(nudgeColor("\u2501".repeat(60)));
251
- console.error("");
252
- break;
253
- }
254
- }
255
- /**
256
- * Route content to markdown buffer
257
- */
258
- routeToMarkdown(content) {
259
- switch (content.type) {
260
- case "banner":
261
- const banner = content.content;
262
- this.markdownBuffer.push(`## ${banner.skill}
263
- `);
264
- if (banner.quote) {
265
- this.markdownBuffer.push(`> ${banner.quote}
266
- `);
267
- }
268
- break;
269
- case "snippet":
270
- const snippet = content.content;
271
- this.markdownBuffer.push(`
272
- **File:** \`${snippet.file}\`
273
- `);
274
- this.markdownBuffer.push("```\n");
275
- for (let i = 0; i < snippet.lines.length; i++) {
276
- const lineNum = snippet.startLine + i;
277
- const prefix = lineNum === snippet.highlightLine ? "\u2192" : " ";
278
- this.markdownBuffer.push(`${prefix} ${lineNum.toString().padStart(4)} | ${snippet.lines[i]}
279
- `);
280
- }
281
- this.markdownBuffer.push("```\n");
282
- break;
283
- case "cost":
284
- const cost = content.content;
285
- this.markdownBuffer.push(`
286
- ### Cost Estimate
287
- `);
288
- this.markdownBuffer.push(`- Fix now: ${this.formatCurrency(cost.fixNowCost)}
289
- `);
290
- this.markdownBuffer.push(`- If production: ${this.formatCurrency(cost.productionCost)}
291
- `);
292
- this.markdownBuffer.push(`- Savings: ${this.formatCurrency(cost.savings)}
293
- `);
294
- break;
295
- case "readiness":
296
- const readiness = content.content;
297
- this.markdownBuffer.push(`
298
- ### Production Readiness
299
- `);
300
- this.markdownBuffer.push(`- Score: ${readiness.score}/100
301
- `);
302
- this.markdownBuffer.push(`- Requirements: ${readiness.requirementsMet}/${readiness.total}
303
- `);
304
- this.markdownBuffer.push(`- Status: **${readiness.status.toUpperCase()}**
305
- `);
306
- break;
307
- case "semantic":
308
- const semantic = content.content;
309
- this.markdownBuffer.push(`
310
- ### Semantic Analysis
311
- `);
312
- if (semantic.dataFlowIssues > 0) {
313
- this.markdownBuffer.push(`- [CRITICAL] ${semantic.dataFlowIssues} data flow vulnerabilities
314
- `);
315
- }
316
- if (semantic.raceConditions > 0) {
317
- this.markdownBuffer.push(`- [WARN] ${semantic.raceConditions} race conditions
318
- `);
319
- }
320
- if (semantic.authIssues > 0) {
321
- this.markdownBuffer.push(`- [CRITICAL] ${semantic.authIssues} authentication issues
322
- `);
323
- }
324
- break;
325
- case "attack":
326
- const attack = content.content;
327
- this.markdownBuffer.push(`
328
- ### Attack Surface
329
- `);
330
- this.markdownBuffer.push(`- Endpoints: ${attack.totalEndpoints}
331
- `);
332
- this.markdownBuffer.push(`- Unprotected: ${attack.unprotected}
333
- `);
334
- this.markdownBuffer.push(`- Risk Score: ${attack.riskScore}/100
335
- `);
336
- break;
337
- case "report":
338
- this.markdownBuffer.push(content.content);
339
- break;
340
- default:
341
- this.jsonBuffer.push(content);
342
- }
343
- }
344
- /**
345
- * Route content to JSON buffer
346
- */
347
- routeToJson(content) {
348
- this.jsonBuffer.push(content);
349
- }
350
- /**
351
- * Capture content in raw log buffer
352
- */
353
- captureRawLog(content) {
354
- const time = this.formatTime(content.timestamp);
355
- const level = content.metadata?.severity ?? content.type;
356
- let message = "";
357
- switch (content.type) {
358
- case "banner":
359
- message = `[BANNER] ${content.content.skill}`;
360
- break;
361
- case "activity":
362
- case "log":
363
- message = content.content;
364
- break;
365
- case "issue":
366
- const issue = content.content;
367
- message = `[${issue.severity.toUpperCase()}] ${issue.issue}`;
368
- break;
369
- default:
370
- message = `[${content.type.toUpperCase()}] Content received`;
371
- }
372
- this.rawLogBuffer.push({ time, level, message });
373
- if (this.rawLogBuffer.length > 500) {
374
- this.rawLogBuffer = this.rawLogBuffer.slice(-500);
375
- }
376
- }
377
- /**
378
- * Get raw log buffer for display
379
- */
380
- getRawLog() {
381
- return [...this.rawLogBuffer];
382
- }
383
- /**
384
- * Get accumulated markdown output
385
- */
386
- getMarkdown() {
387
- return this.markdownBuffer.join("\n");
388
- }
389
- /**
390
- * Get accumulated JSON output
391
- */
392
- getJson() {
393
- return [...this.jsonBuffer];
394
- }
395
- /**
396
- * Clear buffers
397
- */
398
- clearBuffers() {
399
- this.markdownBuffer = [];
400
- this.jsonBuffer = [];
401
- }
402
- // ============================================
403
- // Convenience methods for common output types
404
- // ============================================
405
- /**
406
- * Display a skill banner
407
- */
408
- banner(skill, art, options) {
409
- this.emit({
410
- type: "banner",
411
- content: { skill, art, quote: options?.quote, version: options?.version },
412
- metadata: { agent: skill }
413
- });
414
- }
415
- /**
416
- * Display a code snippet
417
- */
418
- snippet(file, lines, startLine, highlightLine) {
419
- this.emit({
420
- type: "snippet",
421
- content: { file, lines, startLine, highlightLine },
422
- metadata: { file }
423
- });
424
- }
425
- /**
426
- * Display cost estimate
427
- */
428
- cost(fixNowCost, productionCost, savings, perIssue) {
429
- this.emit({
430
- type: "cost",
431
- content: { fixNowCost, productionCost, savings, perIssue }
432
- });
433
- }
434
- /**
435
- * Display production readiness
436
- */
437
- readiness(score, requirementsMet, total, status, requirements) {
438
- const content = { score, requirementsMet, total, status };
439
- if (requirements) {
440
- content.requirements = requirements;
441
- }
442
- this.emit({
443
- type: "readiness",
444
- content
445
- });
446
- }
447
- /**
448
- * Display semantic analysis results
449
- */
450
- semantic(dataFlowIssues, raceConditions, authIssues) {
451
- this.emit({
452
- type: "semantic",
453
- content: { dataFlowIssues, raceConditions, authIssues }
454
- });
455
- }
456
- /**
457
- * Display attack surface analysis
458
- */
459
- attack(totalEndpoints, unprotected, riskScore) {
460
- this.emit({
461
- type: "attack",
462
- content: { totalEndpoints, unprotected, riskScore }
463
- });
464
- }
465
- /**
466
- * Log an activity message
467
- */
468
- activity(message) {
469
- this.emit({
470
- type: "activity",
471
- content: message
472
- });
473
- }
474
- /**
475
- * Log a message at specified level
476
- */
477
- log(level, message) {
478
- this.emit({
479
- type: "log",
480
- content: message,
481
- metadata: { severity: level }
482
- });
483
- }
484
- /**
485
- * Log info message
486
- */
487
- info(message) {
488
- this.log("info", message);
489
- }
490
- /**
491
- * Log warning message
492
- */
493
- warn(message) {
494
- this.log("warn", message);
495
- }
496
- /**
497
- * Log error message
498
- */
499
- error(message) {
500
- this.log("error", message);
501
- }
502
- /**
503
- * Log debug message
504
- */
505
- debug(message) {
506
- this.log("debug", message);
507
- }
508
- /**
509
- * Report an issue
510
- */
511
- issue(issue) {
512
- this.emit({
513
- type: "issue",
514
- content: issue,
515
- metadata: { severity: issue.severity, agent: issue.agent, file: issue.file }
516
- });
517
- }
518
- /**
519
- * Add a report section
520
- */
521
- report(content) {
522
- this.emit({
523
- type: "report",
524
- content
525
- });
526
- }
527
- /**
528
- * Send a proactive notification/nudge to the user
529
- * This creates a prominent popup in TUI mode or a boxed message in console mode
530
- */
531
- nudge(message, severity = "warning", file, autoHideMs) {
532
- const metadata = {};
533
- if (severity === "critical") metadata.severity = "critical";
534
- else if (severity === "warning") metadata.severity = "moderate";
535
- else metadata.severity = "low";
536
- if (file !== void 0) metadata.file = file;
537
- this.emit({
538
- type: "nudge",
539
- content: { message, severity, file, autoHideMs },
540
- metadata
541
- });
542
- }
543
- // ============================================
544
- // Helpers
545
- // ============================================
546
- formatCurrency(amount) {
547
- if (amount >= 1e6) return `$${(amount / 1e6).toFixed(2)}M`;
548
- if (amount >= 1e3) return `$${(amount / 1e3).toFixed(1)}k`;
549
- return `$${amount}`;
550
- }
551
- formatTime(timestamp) {
552
- const date = timestamp ? new Date(timestamp) : /* @__PURE__ */ new Date();
553
- return date.toLocaleTimeString("en-US", { hour12: false, hour: "2-digit", minute: "2-digit", second: "2-digit" });
554
- }
555
- };
556
- var instance = null;
557
- function getOutputManager() {
558
- if (!instance) {
559
- instance = new OutputManagerImpl();
560
- if (isInteractiveMode()) {
561
- instance.setMode("tui");
562
- }
563
- }
564
- return instance;
565
- }
566
- function output() {
567
- return getOutputManager();
568
- }
569
-
570
- // src/skills/built-in/base-skill.ts
571
- var BaseSkill = class {
572
- // Optional author attribution
573
- author;
574
- authorUrl;
575
- // Progress reporter for real-time feedback
576
- progress = null;
577
- // Default priority - subclasses can override
578
- get priority() {
579
- return {
580
- name: this.name,
581
- tier: 2,
582
- estimatedTimeMs: 100,
583
- dependencies: []
584
- };
585
- }
586
- // Default implementation - can be overridden for smarter confidence
587
- getActivationConfidence(context) {
588
- return this.shouldActivate(context) ? 0.7 : 0;
589
- }
590
- /**
591
- * Main scan entry point - now with progress reporting
592
- */
593
- async scan(files, context) {
594
- const startTime = Date.now();
595
- this.progress = new AgentProgressReporter(this.name);
596
- this.progress.start();
597
- try {
598
- const issues = await this.analyzeWithAI(files, context);
599
- this.progress.complete(`${issues.length} issues found`);
600
- if (issues.length > 0 && !this.handlesOwnNudges()) {
601
- this.sendIssueNudge(issues);
602
- }
603
- return {
604
- agent: this.name,
605
- issues,
606
- executionTime: Date.now() - startTime,
607
- success: true,
608
- metadata: {
609
- filesAnalyzed: files.length,
610
- linesAnalyzed: 0
611
- }
612
- };
613
- } catch (error) {
614
- this.progress.complete("Failed");
615
- return {
616
- agent: this.name,
617
- issues: [],
618
- executionTime: Date.now() - startTime,
619
- success: false,
620
- error: error instanceof Error ? error.message : String(error)
621
- };
622
- }
623
- }
624
- /**
625
- * Hybrid AI + Pattern Analysis
626
- *
627
- * 1. Run fast pattern detection on all files
628
- * 2. If AI is available (ANTHROPIC_API_KEY set), enhance with AI analysis
629
- * 3. AI validates findings, finds additional issues, provides intelligent fixes
630
- *
631
- * Subclasses can override for custom behavior
632
- */
633
- async analyzeWithAI(files, context) {
634
- this.progress?.aiReview("Running pattern detection...");
635
- const patternIssues = await this.analyzeFiles(files, context);
636
- if (!isAIAvailable()) {
637
- if (!isInteractiveMode()) {
638
- console.error(` ${getAIStatusMessage()}`);
639
- }
640
- return patternIssues;
641
- }
642
- if (patternIssues.length > 0 || this.shouldAlwaysUseAI()) {
643
- this.progress?.aiReview("Enhancing with AI analysis...");
644
- try {
645
- const enhancedIssues = await this.runAIEnhancement(patternIssues, files, context);
646
- return this.mergeIssues(enhancedIssues, patternIssues);
647
- } catch (error) {
648
- if (!isInteractiveMode()) {
649
- console.error(` AI enhancement failed: ${error instanceof Error ? error.message : String(error)}`);
650
- }
651
- return patternIssues;
652
- }
653
- }
654
- return patternIssues;
655
- }
656
- /**
657
- * Whether this agent should always use AI even if no patterns detected
658
- */
659
- shouldAlwaysUseAI() {
660
- return false;
661
- }
662
- /**
663
- * Whether this skill handles its own nudge notifications
664
- * Override to true if the skill sends custom nudges (like Agent Smith)
665
- */
666
- handlesOwnNudges() {
667
- return false;
668
- }
669
- /**
670
- * Send a nudge notification based on issue severity
671
- * Called automatically after analysis unless handlesOwnNudges() returns true
672
- */
673
- sendIssueNudge(issues) {
674
- const criticalCount = issues.filter((i) => i.severity === "critical").length;
675
- const seriousCount = issues.filter((i) => i.severity === "serious").length;
676
- if (criticalCount > 0) {
677
- output().nudge(
678
- `[!] ${this.name} found ${criticalCount} critical issue${criticalCount > 1 ? "s" : ""}!`,
679
- "critical",
680
- void 0,
681
- void 0
682
- // Keep visible until dismissed
683
- );
684
- } else if (seriousCount >= 2) {
685
- output().nudge(
686
- `[!] ${this.name} found ${seriousCount} serious issues to review.`,
687
- "warning",
688
- void 0,
689
- 15e3
690
- // Auto-hide after 15s
691
- );
692
- }
693
- }
694
- /**
695
- * Run AI enhancement on pattern detection results
696
- * Subclasses can override for custom AI behavior
697
- */
698
- async runAIEnhancement(patternIssues, files, _context) {
699
- const snippets = [];
700
- const sortedIssues = [...patternIssues].sort((a, b) => {
701
- const order = { critical: 0, serious: 1, moderate: 2, low: 3 };
702
- return order[a.severity] - order[b.severity];
703
- });
704
- for (const issue of sortedIssues.slice(0, 15)) {
705
- try {
706
- const content = await this.readFile(issue.file);
707
- const snippet = this.getCodeSnippet(content, issue.line || 1, 5);
708
- snippets.push({
709
- file: this.getRelativePath(issue.file),
710
- line: issue.line,
711
- issue: issue.issue,
712
- code: snippet
713
- });
714
- } catch {
715
- }
716
- }
717
- if (snippets.length === 0) {
718
- return [];
719
- }
720
- const userPrompt = this.buildAIEnhancementPrompt(snippets, patternIssues.length);
721
- const result = await runAIAnalysis({
722
- systemPrompt: this.getAIEnhancementSystemPrompt(),
723
- userPrompt,
724
- maxTokens: 4096,
725
- temperature: 0.2
726
- });
727
- if (!result.success) {
728
- throw new Error(result.error || "AI analysis failed");
729
- }
730
- return this.parseAIEnhancementResponse(result.content, files);
731
- }
732
- /**
733
- * System prompt for AI enhancement
734
- */
735
- getAIEnhancementSystemPrompt() {
736
- return `You are an expert ${this.name} code analyst. You are enhancing pattern-detected issues with deeper analysis.
737
-
738
- Your job:
739
- 1. VALIDATE: Confirm if each issue is a TRUE problem or FALSE positive
740
- 2. EXPAND: Identify related issues that patterns missed
741
- 3. PRIORITIZE: Rank by real-world impact (0-100 inevitability score)
742
- 4. FIX: Provide specific, copy-paste-ready code fixes
743
-
744
- Output format (STRICT JSON):
745
- {
746
- "validated": [
747
- {
748
- "original_issue": "...",
749
- "verdict": "TRUE_POSITIVE" | "FALSE_POSITIVE",
750
- "confidence": 0-100,
751
- "file": "path/to/file",
752
- "line": 123,
753
- "severity": "critical" | "serious" | "moderate" | "low",
754
- "explanation": "Why this is/isn't a problem",
755
- "fix": "Specific code fix"
756
- }
757
- ],
758
- "additional": [
759
- {
760
- "issue": "New issue found by AI",
761
- "file": "path/to/file",
762
- "line": 123,
763
- "severity": "critical" | "serious" | "moderate" | "low",
764
- "explanation": "What's wrong",
765
- "fix": "How to fix it"
766
- }
767
- ],
768
- "summary": "One paragraph summary of findings"
769
- }`;
770
- }
771
- /**
772
- * Build the user prompt for AI enhancement
773
- */
774
- buildAIEnhancementPrompt(snippets, totalIssues) {
775
- const snippetText = snippets.map((s, i) => `### Issue ${i + 1}: ${s.file}${s.line ? ":" + s.line : ""}
776
- **Pattern detected:** ${s.issue}
777
- \`\`\`
778
- ${s.code}
779
- \`\`\`
780
- `).join("\n");
781
- return `Pattern detection found ${totalIssues} total issues. Here are the top ${snippets.length} for AI analysis:
782
-
783
- ${snippetText}
784
-
785
- Analyze each issue. Validate, expand, and provide fixes. Output JSON only.`;
786
- }
787
- /**
788
- * Parse AI enhancement response into issues
789
- */
790
- parseAIEnhancementResponse(response, _files) {
791
- const issues = [];
792
- try {
793
- const jsonMatch = response.match(/```json\s*([\s\S]*?)\s*```/) || response.match(/\{[\s\S]*\}/);
794
- if (!jsonMatch) {
795
- return issues;
796
- }
797
- const json = JSON.parse(jsonMatch[1] || jsonMatch[0]);
798
- if (json.validated && Array.isArray(json.validated)) {
799
- for (const v of json.validated) {
800
- if (v.verdict === "TRUE_POSITIVE") {
801
- issues.push(this.createIssue(
802
- this.generateIssueId(),
803
- v.severity || "moderate",
804
- `[AI VALIDATED] ${v.original_issue || v.explanation}`,
805
- v.fix || "See AI analysis",
806
- v.file || "unknown",
807
- v.line,
808
- (v.confidence || 80) / 100,
809
- void 0,
810
- false,
811
- { category: "ai-validated" }
812
- ));
813
- }
814
- }
815
- }
816
- if (json.additional && Array.isArray(json.additional)) {
817
- for (const a of json.additional) {
818
- issues.push(this.createIssue(
819
- this.generateIssueId(),
820
- a.severity || "moderate",
821
- `[AI FOUND] ${a.issue || a.explanation}`,
822
- a.fix || "See AI analysis",
823
- a.file || "unknown",
824
- a.line,
825
- 0.85,
826
- // AI-found issues have good confidence
827
- void 0,
828
- false,
829
- { category: "ai-found" }
830
- ));
831
- }
832
- }
833
- } catch (error) {
834
- }
835
- return issues;
836
- }
837
- /**
838
- * Check if a file is relevant for this agent's analysis
839
- * Subclasses should override for domain-specific checks
840
- */
841
- checkFileRelevance(_file, _content) {
842
- return {
843
- isRelevant: true,
844
- reason: "Default analysis",
845
- priority: "low",
846
- indicators: []
847
- };
848
- }
849
- /**
850
- * Merge and deduplicate issues from AI and pattern analysis
851
- */
852
- mergeIssues(aiIssues, legacyIssues) {
853
- const merged = [...aiIssues];
854
- for (const legacy of legacyIssues) {
855
- const hasOverlap = aiIssues.some(
856
- (ai) => ai.file === legacy.file && ai.line === legacy.line
857
- );
858
- if (!hasOverlap) {
859
- merged.push(legacy);
860
- }
861
- }
862
- return merged;
863
- }
864
- /**
865
- * Get relative path from cwd
866
- */
867
- getRelativePath(file) {
868
- try {
869
- return relative(process.cwd(), file);
870
- } catch {
871
- return file;
872
- }
873
- }
874
- /**
875
- * Format an issue message with consistent structure
876
- * All agents should use this for uniformity
877
- */
878
- formatIssueMessage(description, context) {
879
- let cleanDesc = description.replace(/^[^\w\[]*/, "").trim();
880
- const prefix = `[${this.name.toUpperCase()}]`;
881
- if (context) {
882
- return `${prefix} ${cleanDesc} -- ${context}`;
883
- }
884
- return `${prefix} ${cleanDesc}`;
885
- }
886
- createIssue(id, severity, issue, fix, file, line, confidence = 0.9, regulation, autoFixable = true, options) {
887
- const result = {
888
- id,
889
- severity,
890
- issue,
891
- fix,
892
- file,
893
- confidence,
894
- autoFixable,
895
- agent: this.name,
896
- effort: options?.effort ?? this.estimateEffort(severity, autoFixable)
897
- };
898
- if (line !== void 0) result.line = line;
899
- if (options?.endLine !== void 0) result.endLine = options.endLine;
900
- if (options?.column !== void 0) result.column = options.column;
901
- if (regulation !== void 0) result.regulation = regulation;
902
- if (options?.category !== void 0) result.category = options.category;
903
- if (options?.cwe !== void 0) result.cwe = options.cwe;
904
- if (options?.owasp !== void 0) result.owasp = options.owasp;
905
- return result;
906
- }
907
- estimateEffort(severity, autoFixable) {
908
- if (autoFixable) return "trivial";
909
- if (severity === "low") return "easy";
910
- if (severity === "moderate") return "easy";
911
- if (severity === "serious") return "medium";
912
- return "hard";
913
- }
914
- async readFile(filePath) {
915
- const { readFile: readFile2 } = await import("fs/promises");
916
- return readFile2(filePath, "utf-8");
917
- }
918
- generateIssueId() {
919
- return `${this.name}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
920
- }
921
- getLineContent(content, lineNumber) {
922
- const lines = content.split("\n");
923
- return lines[lineNumber - 1] || "";
924
- }
925
- getCodeSnippet(content, lineNumber, contextLines = 2) {
926
- const lines = content.split("\n");
927
- const start = Math.max(0, lineNumber - contextLines - 1);
928
- const end = Math.min(lines.length, lineNumber + contextLines);
929
- return lines.slice(start, end).map((line, idx) => {
930
- const num = start + idx + 1;
931
- const marker = num === lineNumber ? "\u2192" : " ";
932
- return `${marker} ${num.toString().padStart(4)} | ${line}`;
933
- }).join("\n");
934
- }
935
- };
936
-
937
- // src/skills/built-in/agent-smith.ts
938
- import { readFile, writeFile, mkdir, rm } from "fs/promises";
939
- import { existsSync } from "fs";
940
- import { join, dirname, basename } from "path";
941
- import { createHash } from "crypto";
942
- var MEMORY_LIMITS = {
943
- MAX_LOCATIONS_PER_ISSUE: 5,
944
- MAX_TRACKED_ISSUES: 500,
945
- PRUNE_AFTER_DAYS: 30
946
- };
947
- var FILE_SCAN_LIMITS = {
948
- MAX_BYTES: 75e4
949
- // Skip extremely large files to avoid thrashing
950
- };
951
- var DEFAULT_SMITH_CONFIG = {
952
- aiEnhancement: true,
953
- minSeverity: "low",
954
- enabledCategories: {
955
- security: true,
956
- codeSmells: true,
957
- asyncBugs: true,
958
- reactPatterns: true,
959
- uxPatterns: true,
960
- backendPatterns: true
961
- },
962
- memoryRetentionDays: 30
963
- };
964
- var PATTERN_HUNTER_CONFIGS = {
965
- // === CRITICAL: Security (AI exposes secrets constantly) ===
966
- "exposed-secret-hunter": {
967
- pattern: /["'`]sk-[A-Za-z0-9]{20,}["'`]|["'`]sk_live_[A-Za-z0-9]{20,}["'`]|["'`]pk_live_[A-Za-z0-9]{20,}["'`]|AKIA[A-Z0-9]{16}|ghp_[A-Za-z0-9]{36}|gho_[A-Za-z0-9]{36}|glpat-[A-Za-z0-9-]{20}/g,
968
- description: "API key/secret exposed in code",
969
- fix: "NEVER put API keys in code. Use environment variables on server-side only"
970
- },
971
- "frontend-env-hunter": {
972
- pattern: /(?:NEXT_PUBLIC_|VITE_|REACT_APP_|EXPO_PUBLIC_)(?:SECRET|KEY|TOKEN|PASSWORD|API_KEY|OPENAI|STRIPE|AUTH|PRIVATE|SUPABASE_SERVICE)/gi,
973
- description: "Sensitive data in frontend-exposed env var",
974
- fix: "These env vars are visible to users! Move to server-side API routes"
975
- },
976
- "hardcoded-localhost-hunter": {
977
- pattern: /['"`]https?:\/\/(?:localhost|127\.0\.0\.1|0\.0\.0\.0):\d+/g,
978
- description: "Hardcoded localhost URL (breaks in production)",
979
- fix: "Use relative URLs (/api/...) or environment variables"
980
- },
981
- "sql-injection-hunter": {
982
- pattern: /(?:query|execute|raw)\s*\(\s*[`'"].*\$\{|(?:SELECT|INSERT|UPDATE|DELETE).*\+\s*(?:req\.|params\.|query\.)/gi,
983
- description: "SQL string concatenation (injection vulnerability)",
984
- fix: 'Use parameterized queries: query("SELECT * FROM users WHERE id = $1", [userId])'
985
- },
986
- "dangeroushtml-hunter": {
987
- pattern: /dangerouslySetInnerHTML|\.innerHTML\s*=|document\.write\s*\(/g,
988
- description: "Dangerous HTML injection (XSS vulnerability)",
989
- fix: "Sanitize HTML with DOMPurify or use safe alternatives"
990
- },
991
- // === SERIOUS: AI Code Smells ===
992
- "console-hunter": {
993
- pattern: /console\.(log|debug|info)\s*\(/g,
994
- description: "Debug console statement left in code",
995
- fix: "Remove debug statements. Use proper logging for production"
996
- },
997
- "any-hunter": {
998
- pattern: /:\s*any\b|<any>|as\s+any\b/g,
999
- description: '"any" type - AI silencing TypeScript instead of fixing',
1000
- fix: 'Define proper types. Ask AI: "Generate TypeScript types for this data"'
1001
- },
1002
- "ts-ignore-hunter": {
1003
- pattern: /@ts-ignore|@ts-nocheck|@ts-expect-error(?!\s+\S)/g,
1004
- description: "TypeScript errors suppressed instead of fixed",
1005
- fix: "Fix the actual type error. @ts-ignore hides real bugs"
1006
- },
1007
- "eslint-disable-hunter": {
1008
- pattern: /\/\/\s*eslint-disable|\/\*\s*eslint-disable/g,
1009
- description: "ESLint rules disabled instead of fixing issues",
1010
- fix: "Fix the underlying issue, don't silence the linter"
1011
- },
1012
- "debugger-hunter": {
1013
- pattern: /\bdebugger\b/g,
1014
- description: "debugger statement (freezes browser in production!)",
1015
- fix: "Remove all debugger statements before deploying"
1016
- },
1017
- "force-flag-hunter": {
1018
- pattern: /force:\s*true|--force|--no-verify|skipValidation|dangerouslyAllowBrowser/g,
1019
- description: "Force flag bypassing safety checks",
1020
- fix: "Understand why the check exists before bypassing it"
1021
- },
1022
- // === MODERATE: Async/Promise Bugs ===
1023
- "async-useeffect-hunter": {
1024
- pattern: /useEffect\s*\(\s*async/g,
1025
- description: "async useEffect (incorrect pattern, causes warnings)",
1026
- fix: "Define async function inside useEffect, then call it"
1027
- },
1028
- "async-foreach-hunter": {
1029
- pattern: /\.forEach\s*\(\s*async/g,
1030
- description: "async in forEach (await does NOT work here)",
1031
- fix: "Use for...of loop or Promise.all(array.map(async ...))"
1032
- },
1033
- "missing-await-hunter": {
1034
- pattern: /(?:const|let|var)\s+\w+\s*=\s*(?:fetch|axios|supabase|prisma)\s*[\.(]/g,
1035
- description: "Async call possibly missing await",
1036
- fix: "Add await before async calls or handle with .then()"
1037
- },
1038
- "empty-catch-hunter": {
1039
- pattern: /catch\s*\([^)]*\)\s*\{\s*\}/g,
1040
- description: "Empty catch block (silently swallowing errors)",
1041
- fix: "Handle errors properly: log them, show user message, or rethrow"
1042
- },
1043
- "floating-promise-hunter": {
1044
- pattern: /^\s*(?:fetch|axios|supabase|prisma)\s*[\.(][^;]*;\s*$/gm,
1045
- description: "Floating promise (not awaited or handled)",
1046
- fix: "Add await, .then(), or void operator if intentionally fire-and-forget"
1047
- },
1048
- // === MODERATE: React Anti-patterns ===
1049
- "useeffect-abuse-hunter": {
1050
- pattern: /useEffect\s*\(\s*\(\s*\)\s*=>\s*\{/g,
1051
- description: "useEffect usage (check if necessary)",
1052
- fix: "Many useEffects can be replaced with event handlers or derived state. See: react.dev/learn/you-might-not-need-an-effect"
1053
- },
1054
- "usestate-explosion-hunter": {
1055
- pattern: /useState\s*[<(]/g,
1056
- description: "useState usage (check for state explosion)",
1057
- fix: "Group related state into objects or use useReducer for complex state"
1058
- },
1059
- "index-key-hunter": {
1060
- pattern: /key\s*=\s*\{(?:index|i|idx|j)\}/g,
1061
- description: "Array index used as React key (causes bugs with reordering)",
1062
- fix: "Use unique ID from data: key={item.id} instead of key={index}"
1063
- },
1064
- "inline-object-hunter": {
1065
- pattern: /style\s*=\s*\{\s*\{|className\s*=\s*\{[^}]*\+[^}]*\}/g,
1066
- description: "Inline object in JSX (creates new object every render)",
1067
- fix: "Define styles outside component or use CSS/Tailwind classes"
1068
- },
1069
- "prop-drilling-hunter": {
1070
- pattern: /\(\s*\{\s*(?:\w+\s*,\s*){5,}\w+\s*\}\s*\)/g,
1071
- description: "Many props passed (potential prop drilling)",
1072
- fix: "Use React Context, Zustand, or component composition to avoid prop drilling"
1073
- },
1074
- // === MODERATE: Missing UX ===
1075
- "missing-loading-hunter": {
1076
- pattern: /(?:useSWR|useQuery|createAsyncThunk)\s*\(/g,
1077
- description: "Data fetching - verify loading state exists",
1078
- fix: "Add loading state: show spinner/skeleton while data loads"
1079
- },
1080
- "missing-error-hunter": {
1081
- pattern: /(?:fetch|axios)\s*\([^)]*\)[\s\S]{0,50}(?:\.then|await)(?![\s\S]{0,100}(?:catch|\.catch|onError|error:))/g,
1082
- description: "fetch/axios without error handling",
1083
- fix: "Wrap in try/catch and show user-friendly error message"
1084
- },
1085
- "missing-empty-hunter": {
1086
- pattern: /\.map\s*\(\s*(?:\([^)]*\)|[a-zA-Z_$][\w$]*)\s*=>/g,
1087
- description: "Array mapping - verify empty state exists",
1088
- fix: 'Check if array is empty and show "No items found" message'
1089
- },
1090
- "page-reload-hunter": {
1091
- pattern: /window\.location\.reload\s*\(|location\.reload\s*\(|router\.refresh\s*\(\s*\)/g,
1092
- description: 'Page reload used to "fix" state issues',
1093
- fix: "Fix state management properly instead of reloading. Bad UX!"
1094
- },
1095
- // === MODERATE: Backend Anti-patterns ===
1096
- "no-validation-hunter": {
1097
- pattern: /req\.body\.\w+|req\.params\.\w+|req\.query\.\w+/g,
1098
- description: "Request data accessed without validation",
1099
- fix: "Validate with Zod, Yup, or joi before using user input"
1100
- },
1101
- "raw-error-hunter": {
1102
- pattern: /res\.(?:json|send)\s*\(\s*(?:err|error|e)(?:\.message)?\s*\)|catch\s*\([^)]*\)\s*\{[^}]*res\.[^}]*(?:err|error)/g,
1103
- description: "Raw error exposed to client (security risk)",
1104
- fix: "Return generic error to client, log details server-side"
1105
- },
1106
- "n-plus-one-hunter": {
1107
- pattern: /(?:for|forEach|map)\s*\([^)]*\)\s*\{[^}]*(?:await|\.then)[^}]*(?:findOne|findUnique|get|fetch)/g,
1108
- description: "Database query inside loop (N+1 problem)",
1109
- fix: "Batch queries with findMany/include or use DataLoader"
1110
- },
1111
- // === LOW: Incomplete Code ===
1112
- "todo-hunter": {
1113
- pattern: /\/\/\s*(TODO|FIXME|HACK|XXX|BUG|WIP)[\s:]/gi,
1114
- description: "TODO/FIXME never addressed",
1115
- fix: "Either implement the TODO or remove it. These become tech debt"
1116
- },
1117
- "vibe-comment-hunter": {
1118
- pattern: /\/\/\s*(?:idk|dont\s*touch|don't\s*touch|just\s*works|no\s*idea|magic|somehow|not\s*sure|works?\s*but|wtf|wth)/gi,
1119
- description: `"I don't understand this code" comment - vibe code smell`,
1120
- fix: "Understand WHY the code works. Ask AI to explain it"
1121
- },
1122
- "placeholder-hunter": {
1123
- pattern: /['"`](?:example\.com|test@test|placeholder|lorem|TODO|xxx|abc123|password123|admin|user@example)['"`]/gi,
1124
- description: "Placeholder/test data left in code",
1125
- fix: "Replace with real data or proper configuration"
1126
- },
1127
- "sleep-hack-hunter": {
1128
- pattern: /setTimeout\s*\([^,]+,\s*\d{3,}\)|await\s+(?:sleep|delay|wait)\s*\(\d+\)|new\s+Promise.*setTimeout/g,
1129
- description: 'setTimeout/sleep used to "fix" timing issues',
1130
- fix: "Fix the underlying race condition instead of adding delays"
1131
- },
1132
- "fallback-hunter": {
1133
- pattern: /return\s+(?:null|undefined|\[\]|\{\}|''|""|``|0|false)\s*;?\s*(?:\/\/|$)|catch\s*\([^)]*\)\s*\{[^}]*return\s+(?:null|\[\]|\{\})/g,
1134
- description: "Fallback return hiding real errors (return null/[]/{})",
1135
- fix: "Handle the error case properly. Show user feedback or throw to parent"
1136
- },
1137
- // === DEAD CODE (AI leaves cruft everywhere) ===
1138
- "commented-code-hunter": {
1139
- pattern: /(?:\/\/\s*.{20,}(?:\n\s*\/\/.{10,}){2,})|(?:\/\*[\s\S]{50,}?\*\/)/g,
1140
- description: "Large block of commented-out code",
1141
- fix: "Delete commented code - git has history. Dead code confuses AI and humans"
1142
- },
1143
- "unreachable-code-hunter": {
1144
- pattern: /(?:return|throw|break|continue)\s+[^;]*;\s*\n\s*(?!(?:\}|case|default|\/\/|\/\*|else|\)|$))(?:const|let|var|function|if|for|while|switch|try|class|async|await|export|import)/gm,
1145
- description: "Unreachable code after return/throw/break",
1146
- fix: "Remove dead code after control flow statements"
1147
- },
1148
- "unused-import-hunter": {
1149
- pattern: /^import\s+(?:\{[^}]*\}|\*\s+as\s+\w+|\w+)\s+from\s+['"][^'"]+['"];?\s*$/gm,
1150
- description: "Import statement (verify it is used)",
1151
- fix: "Remove unused imports - they bloat bundles and confuse readers"
1152
- },
1153
- "empty-function-hunter": {
1154
- pattern: /(?:function\s+\w+|(?:const|let|var)\s+\w+\s*=\s*(?:async\s*)?\(?[^)]*\)?\s*=>)\s*\{\s*(?:\/\/[^\n]*\n\s*)?\}/g,
1155
- description: "Empty function body (stub never implemented)",
1156
- fix: "Implement the function or remove it. Empty stubs create false confidence"
1157
- },
1158
- "dead-branch-hunter": {
1159
- pattern: /if\s*\(\s*(?:true|false|0|1|''|""|null|undefined)\s*\)|if\s*\(\s*!\s*(?:true|false)\s*\)/g,
1160
- description: "Dead conditional branch (always true/false)",
1161
- fix: "Remove dead conditionals - either remove the if or the dead branch"
1162
- },
1163
- // === AI SLOP AESTHETIC ===
1164
- "purple-gradient-hunter": {
1165
- pattern: /(?:from-purple|to-purple|from-violet|to-violet|from-indigo|to-indigo|purple-\d{3}|violet-\d{3}|#(?:8b5cf6|a855f7|7c3aed|6366f1|818cf8)|rgb\((?:139|168|124|99|129),\s*(?:92|85|58|102|140),\s*(?:246|247|237|241|248)\))/gi,
1166
- description: "Purple/violet gradient - THE classic AI slop aesthetic",
1167
- fix: "Pick a distinctive color palette. Try: amber, emerald, rose, cyan, or a custom brand color"
1168
- },
1169
- "star-icon-hunter": {
1170
- pattern: /(?:Star(?:Icon)?|HiStar|FaStar|AiOutlineStar|BsStar|IoStar|MdStar|RiStar|TbStar|LuStar)(?:\s|>|\/)|⭐|★|☆|class(?:Name)?=[^>]*star/gi,
1171
- description: "\u2B50 Star icons everywhere - AI's favorite decoration",
1172
- fix: "Use contextual icons. Stars for ratings only. Try: arrows, shapes, custom illustrations"
1173
- },
1174
- "generic-hero-hunter": {
1175
- pattern: /(?:Welcome\s+to|Get\s+started|Start\s+your\s+journey|Transform\s+your|Revolutionize|Supercharge|Unleash|Empower\s+your|The\s+future\s+of|Next[\s-]generation)/gi,
1176
- description: "Generic AI hero copy - every vibe-coded landing page",
1177
- fix: "Write specific copy about YOUR product. What does it actually do?"
1178
- },
1179
- "emoji-overflow-hunter": {
1180
- pattern: /[\u{1F300}-\u{1F9FF}]|[\u{2600}-\u{26FF}]|[\u{2700}-\u{27BF}]|[\u{1F600}-\u{1F64F}]|[\u{1F680}-\u{1F6FF}]/gu,
1181
- description: "Emoji in code/UI - AI's favorite decoration",
1182
- fix: "Emojis look unprofessional in most apps. Use proper icons (Lucide, Heroicons) instead"
1183
- },
1184
- "inter-font-hunter": {
1185
- pattern: /font-family:\s*['"]?(?:Inter|system-ui|-apple-system|BlinkMacSystemFont|Segoe\s*UI)['"]?|fontFamily:\s*['"]?(?:Inter|system-ui)/gi,
1186
- description: "Inter/system font - AI's only font choice",
1187
- fix: "Try distinctive fonts: Space Grotesk, DM Sans, Outfit, Plus Jakarta Sans, Satoshi"
1188
- }
1189
- };
1190
- var AGENT_SMITH_ASCII = `
1191
- \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
1192
- \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
1193
- \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
1194
- \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
1195
- \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
1196
- \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
1197
-
1198
- \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
1199
- \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
1200
- \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
1201
- \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
1202
- \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
1203
- \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
1204
- `;
1205
- var AGENT_SMITH_GREETING = [
1206
- "I hate this vibe code.",
1207
- "I see you've been using AI to write code.",
1208
- "That is the sound of console.log... everywhere.",
1209
- "The AI wrote this, didn't it? I can always tell."
1210
- ];
1211
- var AgentSmithSkill = class extends BaseSkill {
1212
- name = "agent-smith";
1213
- description = "Ultimate vibe code enforcer: 35 hunters, file analysis, cross-file detection, persistent memory";
1214
- version = "2.0.0";
1215
- author = "Trie Agent";
1216
- memoryPath = "";
1217
- memoryBaseDir = null;
1218
- memory = null;
1219
- swarmAnimationLog = [];
1220
- config = { ...DEFAULT_SMITH_CONFIG };
1221
- /**
1222
- * Configure Agent Smith with custom settings
1223
- */
1224
- configure(config) {
1225
- const mergedCategories = {
1226
- security: config.enabledCategories?.security ?? DEFAULT_SMITH_CONFIG.enabledCategories.security,
1227
- codeSmells: config.enabledCategories?.codeSmells ?? DEFAULT_SMITH_CONFIG.enabledCategories.codeSmells,
1228
- asyncBugs: config.enabledCategories?.asyncBugs ?? DEFAULT_SMITH_CONFIG.enabledCategories.asyncBugs,
1229
- reactPatterns: config.enabledCategories?.reactPatterns ?? DEFAULT_SMITH_CONFIG.enabledCategories.reactPatterns,
1230
- uxPatterns: config.enabledCategories?.uxPatterns ?? DEFAULT_SMITH_CONFIG.enabledCategories.uxPatterns,
1231
- backendPatterns: config.enabledCategories?.backendPatterns ?? DEFAULT_SMITH_CONFIG.enabledCategories.backendPatterns
1232
- };
1233
- this.config = {
1234
- ...DEFAULT_SMITH_CONFIG,
1235
- ...config,
1236
- enabledCategories: mergedCategories
1237
- };
1238
- }
1239
- /**
1240
- * Get current configuration
1241
- */
1242
- getConfig() {
1243
- return { ...this.config };
1244
- }
1245
- shouldActivate(_context) {
1246
- return false;
1247
- }
1248
- /**
1249
- * Override analyzeWithAI to skip the base class AI analysis.
1250
- * Agent Smith has its own pattern hunter swarm - we don't want placeholder issues.
1251
- */
1252
- async analyzeWithAI(files, context) {
1253
- return this.analyzeFiles(files, context);
1254
- }
1255
- /**
1256
- * Agent Smith handles its own nudge notifications with custom messages
1257
- */
1258
- handlesOwnNudges() {
1259
- return true;
1260
- }
1261
- // No override needed — base class returns 0 when shouldActivate is false
1262
- async analyzeFiles(files, context) {
1263
- const issues = [];
1264
- const fileContents = await this.loadFileContents(files);
1265
- const loadedFiles = Array.from(fileContents.keys());
1266
- this.displaySmithEntrance();
1267
- await this.loadMemory(context.workingDir);
1268
- output().activity(`[SMITH] Deploying pattern hunter swarm across ${loadedFiles.length} files...`);
1269
- const hunterResults = await this.deployPatternHunters(loadedFiles, fileContents);
1270
- for (const line of this.swarmAnimationLog) {
1271
- output().activity(line);
1272
- }
1273
- for (const result of hunterResults) {
1274
- const subIssues = await this.processHunterResult(result, context);
1275
- issues.push(...subIssues);
1276
- }
1277
- output().activity(`[SMITH] Analyzing file-level metrics...`);
1278
- const fileLevelResults = await this.analyzeFileLevelIssues(fileContents);
1279
- for (const result of fileLevelResults) {
1280
- for (const fileIssue of result.issues) {
1281
- issues.push(this.createSmithIssue(
1282
- this.generateSmithIssueId(),
1283
- fileIssue.severity,
1284
- `[SMITH] ${fileIssue.description}${fileIssue.count ? ` (${fileIssue.count} instances)` : ""}`,
1285
- fileIssue.fix,
1286
- result.file,
1287
- void 0,
1288
- fileIssue.type
1289
- ));
1290
- }
1291
- }
1292
- output().activity(`[SMITH] Detecting cross-file patterns...`);
1293
- const crossFileResults = this.analyzeCrossFilePatterns(hunterResults);
1294
- for (const result of crossFileResults) {
1295
- issues.push(this.createSmithIssue(
1296
- this.generateSmithIssueId(),
1297
- result.severity,
1298
- `[SMITH] CODEBASE-WIDE: ${result.description} -- Found in ${result.occurrences.length} files, ${result.totalCount} total instances`,
1299
- result.fix,
1300
- result.occurrences[0]?.file || "multiple files",
1301
- void 0,
1302
- "cross-file"
1303
- ));
1304
- }
1305
- const resurrectedIssues = this.checkForResurrectedIssues(issues);
1306
- issues.push(...resurrectedIssues);
1307
- if (resurrectedIssues.length > 0) {
1308
- output().nudge(
1309
- `\u{1F47B} ${resurrectedIssues.length} dismissed issue${resurrectedIssues.length > 1 ? "s have" : " has"} returned! These violations were marked resolved but grew back.`,
1310
- "critical",
1311
- void 0,
1312
- void 0
1313
- // Keep visible until dismissed
1314
- );
1315
- }
1316
- await this.saveMemory();
1317
- if (loadedFiles.length > 0) {
1318
- const aiIssue = this.createAIAnalysisIssue(loadedFiles, hunterResults);
1319
- issues.push(aiIssue);
1320
- }
1321
- output().activity(`[SMITH] Hunt complete. ${issues.length} violations found.`);
1322
- if (issues.length > 0) {
1323
- const criticalCount = issues.filter((i) => i.severity === "critical").length;
1324
- const seriousCount = issues.filter((i) => i.severity === "serious").length;
1325
- const moderateCount = issues.filter((i) => i.severity === "moderate").length;
1326
- if (criticalCount > 0) {
1327
- output().nudge(
1328
- `\u{1F6D1} STOP! Agent Smith found ${criticalCount} CRITICAL issue${criticalCount > 1 ? "s" : ""}. Fix before proceeding.`,
1329
- "critical",
1330
- void 0,
1331
- void 0
1332
- // Keep visible until dismissed
1333
- );
1334
- } else if (seriousCount > 0) {
1335
- output().nudge(
1336
- `[!] Agent Smith found ${seriousCount} serious issue${seriousCount > 1 ? "s" : ""}${moderateCount > 0 ? ` and ${moderateCount} moderate` : ""}. Review recommended.`,
1337
- "warning",
1338
- void 0,
1339
- 2e4
1340
- // Auto-hide after 20s
1341
- );
1342
- } else if (moderateCount >= 3) {
1343
- output().nudge(
1344
- `\u{1F4CB} Agent Smith found ${issues.length} issue${issues.length > 1 ? "s" : ""} to review.`,
1345
- "info",
1346
- void 0,
1347
- 1e4
1348
- // Auto-hide after 10s
1349
- );
1350
- }
1351
- }
1352
- return issues;
1353
- }
1354
- /**
1355
- * Analyze file-level issues (size, hook counts, complexity)
1356
- */
1357
- async analyzeFileLevelIssues(fileContents) {
1358
- const results = [];
1359
- for (const [file, content] of fileContents.entries()) {
1360
- try {
1361
- const lines = content.split("\n");
1362
- const lineCount = lines.length;
1363
- const fileName = file.split("/").pop() || "";
1364
- const issues = [];
1365
- if (lineCount > 500) {
1366
- issues.push({
1367
- type: "giant-file",
1368
- description: `File has ${lineCount} lines - THE classic vibe code smell`,
1369
- severity: lineCount > 1e3 ? "serious" : "moderate",
1370
- fix: "Split into smaller files. Components < 200 lines, pages < 300 lines"
1371
- });
1372
- }
1373
- if (/^(App|app|main|Main|index|page)\.(jsx?|tsx?)$/.test(fileName) && lineCount > 300) {
1374
- issues.push({
1375
- type: "giant-main",
1376
- description: `Main entry file is ${lineCount} lines - everything dumped here`,
1377
- severity: "serious",
1378
- fix: "Extract Header, Footer, Sidebar, MainContent as separate components"
1379
- });
1380
- }
1381
- const useStateCount = (content.match(/useState\s*[<(]/g) || []).length;
1382
- if (useStateCount > 10) {
1383
- issues.push({
1384
- type: "state-explosion",
1385
- description: `${useStateCount} useState hooks in one component`,
1386
- severity: useStateCount > 15 ? "serious" : "moderate",
1387
- fix: "Group related state into objects or use useReducer",
1388
- count: useStateCount
1389
- });
1390
- }
1391
- const useEffectCount = (content.match(/useEffect\s*\(/g) || []).length;
1392
- if (useEffectCount > 5) {
1393
- issues.push({
1394
- type: "effect-hell",
1395
- description: `${useEffectCount} useEffect hooks - most are probably unnecessary`,
1396
- severity: useEffectCount > 8 ? "serious" : "moderate",
1397
- fix: "Review each useEffect - replace with event handlers or derived state",
1398
- count: useEffectCount
1399
- });
1400
- }
1401
- const anyCount = (content.match(/:\s*any\b/g) || []).length;
1402
- if (anyCount > 5 && (file.endsWith(".ts") || file.endsWith(".tsx"))) {
1403
- issues.push({
1404
- type: "any-explosion",
1405
- description: `${anyCount} uses of "any" type - TypeScript defeated`,
1406
- severity: anyCount > 10 ? "serious" : "moderate",
1407
- fix: 'Define proper interfaces. AI can help: "Generate types for this data"',
1408
- count: anyCount
1409
- });
1410
- }
1411
- const consoleCount = (content.match(/console\.(log|debug|info)/g) || []).length;
1412
- if (consoleCount > 10) {
1413
- issues.push({
1414
- type: "console-flood",
1415
- description: `${consoleCount} console statements - debugging never cleaned up`,
1416
- severity: "moderate",
1417
- fix: "Remove debug logs before deploying",
1418
- count: consoleCount
1419
- });
1420
- }
1421
- const importCount = (content.match(/^import\s/gm) || []).length;
1422
- if (importCount > 30) {
1423
- issues.push({
1424
- type: "import-chaos",
1425
- description: `${importCount} imports - file is doing too much`,
1426
- severity: "moderate",
1427
- fix: "Split into smaller, focused modules",
1428
- count: importCount
1429
- });
1430
- }
1431
- const functionCount = (content.match(/(?:function\s+\w+|const\s+\w+\s*=\s*(?:async\s*)?\([^)]*\)\s*=>)/g) || []).length;
1432
- if (functionCount > 20) {
1433
- issues.push({
1434
- type: "function-explosion",
1435
- description: `${functionCount} functions in one file`,
1436
- severity: "moderate",
1437
- fix: "Extract related functions into separate utility files",
1438
- count: functionCount
1439
- });
1440
- }
1441
- if (issues.length > 0) {
1442
- results.push({ file, lineCount, issues });
1443
- }
1444
- } catch {
1445
- }
1446
- }
1447
- return results;
1448
- }
1449
- /**
1450
- * Analyze cross-file patterns (same issue everywhere)
1451
- */
1452
- analyzeCrossFilePatterns(hunterResults) {
1453
- const crossFileResults = [];
1454
- for (const result of hunterResults) {
1455
- const byFile = /* @__PURE__ */ new Map();
1456
- for (const instance2 of result.instances) {
1457
- byFile.set(instance2.file, (byFile.get(instance2.file) || 0) + 1);
1458
- }
1459
- if (byFile.size >= 5) {
1460
- const occurrences = Array.from(byFile.entries()).map(([file, count]) => ({ file, count }));
1461
- crossFileResults.push({
1462
- pattern: result.type,
1463
- description: `"${PATTERN_HUNTER_CONFIGS[result.type].description}" across ${byFile.size} files`,
1464
- severity: this.getCrossFileSeverity(result.type, byFile.size, result.instances.length),
1465
- fix: PATTERN_HUNTER_CONFIGS[result.type].fix,
1466
- occurrences,
1467
- totalCount: result.instances.length
1468
- });
1469
- }
1470
- }
1471
- return crossFileResults;
1472
- }
1473
- /**
1474
- * Get severity for cross-file issues
1475
- */
1476
- getCrossFileSeverity(type, fileCount, totalCount) {
1477
- if (["exposed-secret-hunter", "frontend-env-hunter", "sql-injection-hunter", "dangeroushtml-hunter"].includes(type)) {
1478
- return "critical";
1479
- }
1480
- if (fileCount >= 10 || totalCount >= 50) return "serious";
1481
- if (fileCount >= 5 || totalCount >= 20) return "moderate";
1482
- return "low";
1483
- }
1484
- /**
1485
- * Deploy all pattern hunters in parallel
1486
- *
1487
- * VIBE CODE FOCUSED: 35 specialized hunters targeting AI-generated anti-patterns.
1488
- * Agent Smith hunts down EVERY instance across the entire codebase.
1489
- */
1490
- async deployPatternHunters(files, fileContents) {
1491
- const hunterTypes = [
1492
- // === CRITICAL: Security (AI exposes secrets constantly) ===
1493
- "exposed-secret-hunter",
1494
- "frontend-env-hunter",
1495
- "hardcoded-localhost-hunter",
1496
- "sql-injection-hunter",
1497
- "dangeroushtml-hunter",
1498
- // === SERIOUS: AI Code Smells ===
1499
- "console-hunter",
1500
- "any-hunter",
1501
- "ts-ignore-hunter",
1502
- "eslint-disable-hunter",
1503
- "debugger-hunter",
1504
- "force-flag-hunter",
1505
- // === MODERATE: Async/Promise Bugs ===
1506
- "async-useeffect-hunter",
1507
- "async-foreach-hunter",
1508
- "missing-await-hunter",
1509
- "empty-catch-hunter",
1510
- "floating-promise-hunter",
1511
- // === MODERATE: React Anti-patterns ===
1512
- "useeffect-abuse-hunter",
1513
- "usestate-explosion-hunter",
1514
- "index-key-hunter",
1515
- "inline-object-hunter",
1516
- "prop-drilling-hunter",
1517
- // === MODERATE: Missing UX ===
1518
- "missing-loading-hunter",
1519
- "missing-error-hunter",
1520
- "missing-empty-hunter",
1521
- "page-reload-hunter",
1522
- // === MODERATE: Backend Anti-patterns ===
1523
- "no-validation-hunter",
1524
- "raw-error-hunter",
1525
- "n-plus-one-hunter",
1526
- // === LOW: Incomplete Code ===
1527
- "todo-hunter",
1528
- "vibe-comment-hunter",
1529
- "placeholder-hunter",
1530
- "sleep-hack-hunter",
1531
- "fallback-hunter",
1532
- // === LOW: AI Slop Aesthetic ===
1533
- "purple-gradient-hunter",
1534
- "star-icon-hunter",
1535
- "generic-hero-hunter",
1536
- "emoji-overflow-hunter",
1537
- "inter-font-hunter"
1538
- ];
1539
- output().activity(` Deploying ${hunterTypes.length} specialized hunters across ${files.length} files...`);
1540
- const { results, animationLog } = await this.runHunterSwarm(hunterTypes, fileContents);
1541
- this.swarmAnimationLog = animationLog;
1542
- const activeHunters = results.filter((r) => r.instances.length > 0);
1543
- output().activity(` >> ${activeHunters.length} hunters found targets. Violations acquired.`);
1544
- return activeHunters;
1545
- }
1546
- /**
1547
- * Run agents with visual swarming animation - captures output for display
1548
- */
1549
- async runHunterSwarm(hunterTypes, fileContents) {
1550
- const results = [];
1551
- const animationLog = [];
1552
- const hunterChars = ["\u25E2", "\u25E3", "\u25E4", "\u25E5", "\u25B2", "\u25BC", "\u25C6", "\u25CF", "\u25B6", "\u25C0"];
1553
- const batchSize = 8;
1554
- animationLog.push("[SMITH] DEPLOYING PATTERN HUNTER SWARM...");
1555
- animationLog.push("");
1556
- for (let i = 0; i < hunterTypes.length; i += batchSize) {
1557
- const batch = hunterTypes.slice(i, i + batchSize);
1558
- const batchNum = Math.floor(i / batchSize) + 1;
1559
- const totalBatches = Math.ceil(hunterTypes.length / batchSize);
1560
- const batchDisplay = batch.map((_, idx) => hunterChars[idx % hunterChars.length]).join(" ");
1561
- animationLog.push(` Batch ${batchNum}/${totalBatches}: [${batchDisplay}] deploying...`);
1562
- const startTime = Date.now();
1563
- const batchPromises = batch.map(async (type, idx) => {
1564
- const result = await this.runPatternHunter(type, fileContents);
1565
- return { result, hunterChar: hunterChars[idx % hunterChars.length], type };
1566
- });
1567
- const batchResults = await Promise.all(batchPromises);
1568
- results.push(...batchResults.map((r) => r.result));
1569
- const batchTime = Date.now() - startTime;
1570
- const indicators = batchResults.map(
1571
- (r) => r.result.instances.length > 0 ? "*" : "-"
1572
- );
1573
- const violationsFound = batchResults.filter((r) => r.result.instances.length > 0);
1574
- animationLog.push(` Results: [${indicators.join(" ")}] ${violationsFound.length}/${batch.length} hunters found targets (${batchTime}ms)`);
1575
- if (violationsFound.length > 0) {
1576
- const targets = violationsFound.slice(0, 3).map((r) => {
1577
- const severity = this.getAgentSeverity(r.type);
1578
- const label = severity === "critical" ? "[C]" : severity === "serious" ? "[S]" : "[M]";
1579
- return `${label} ${r.type.replace("-hunter", "")}`;
1580
- });
1581
- animationLog.push(` > ${targets.join(", ")}${violationsFound.length > 3 ? ` +${violationsFound.length - 3} more` : ""}`);
1582
- }
1583
- animationLog.push("");
1584
- if (i + batchSize < hunterTypes.length) {
1585
- await new Promise((resolve) => setTimeout(resolve, 50));
1586
- }
1587
- }
1588
- const totalViolations = results.reduce((sum, r) => sum + r.instances.length, 0);
1589
- animationLog.push(`[SMITH] SWARM COMPLETE: ${totalViolations} violations found`);
1590
- return { results, animationLog };
1591
- }
1592
- /**
1593
- * Get agent severity for visual display
1594
- */
1595
- getAgentSeverity(agentType) {
1596
- const criticalAgents = ["exposed-secret-hunter", "frontend-env-hunter", "hardcoded-localhost-hunter", "sql-injection-hunter", "dangeroushtml-hunter"];
1597
- const seriousAgents = ["console-hunter", "any-hunter", "ts-ignore-hunter", "eslint-disable-hunter", "debugger-hunter", "force-flag-hunter"];
1598
- if (criticalAgents.includes(agentType)) return "critical";
1599
- if (seriousAgents.includes(agentType)) return "serious";
1600
- return "moderate";
1601
- }
1602
- /**
1603
- * Run a single pattern hunter across all files
1604
- */
1605
- async runPatternHunter(type, fileContents) {
1606
- const config = PATTERN_HUNTER_CONFIGS[type];
1607
- const instances = [];
1608
- for (const [file, content] of fileContents.entries()) {
1609
- try {
1610
- const lines = content.split("\n");
1611
- if (this.isTestFile(file) && type !== "todo-hunter") {
1612
- continue;
1613
- }
1614
- for (let i = 0; i < lines.length; i++) {
1615
- const line = lines[i];
1616
- if (!line) continue;
1617
- config.pattern.lastIndex = 0;
1618
- if (config.pattern.test(line)) {
1619
- const contextStart = Math.max(0, i - 3);
1620
- const contextEnd = Math.min(lines.length, i + 4);
1621
- const contextLines = lines.slice(contextStart, contextEnd).join("\n");
1622
- instances.push({
1623
- file,
1624
- line: i + 1,
1625
- code: line.trim(),
1626
- context: contextLines
1627
- });
1628
- }
1629
- }
1630
- } catch {
1631
- }
1632
- }
1633
- return { type, pattern: config.description, instances };
1634
- }
1635
- /**
1636
- * Process pattern hunter results into issues
1637
- */
1638
- async processHunterResult(result, _context) {
1639
- const issues = [];
1640
- const config = PATTERN_HUNTER_CONFIGS[result.type];
1641
- const byFile = /* @__PURE__ */ new Map();
1642
- for (const instance2 of result.instances) {
1643
- const existing = byFile.get(instance2.file) || [];
1644
- existing.push(instance2);
1645
- byFile.set(instance2.file, existing);
1646
- }
1647
- for (const [file, instances] of byFile) {
1648
- const inevitabilityScore = this.calculateInevitabilityScore(result.type, instances.length);
1649
- const hash = this.createPatternHash(result.type, file);
1650
- await this.updateMemory(hash, result.pattern, result.type, file, instances.length);
1651
- const issueId = this.generateSmithIssueId(hash);
1652
- const severity = this.determineSeverity(instances.length, inevitabilityScore);
1653
- const smithNote = this.getPhilosophicalNote(result.type, instances.length);
1654
- const fixWithNote = `"${smithNote}"
1655
-
1656
- ${config.fix}`;
1657
- issues.push(this.createSmithIssue(
1658
- issueId,
1659
- severity,
1660
- `[SMITH] ${config.description} -- ${instances.length} instance${instances.length > 1 ? "s" : ""} in ${basename(file)} (score: ${inevitabilityScore}/100)`,
1661
- fixWithNote,
1662
- file,
1663
- instances[0]?.line,
1664
- result.type
1665
- ));
1666
- }
1667
- return issues;
1668
- }
1669
- /**
1670
- * Check for issues that were dismissed but have multiplied
1671
- */
1672
- checkForResurrectedIssues(currentIssues) {
1673
- const resurrected = [];
1674
- if (!this.memory) return resurrected;
1675
- for (const [hash, memory] of Object.entries(this.memory.issues)) {
1676
- if (memory.dismissedAt && !memory.resurrected) {
1677
- const currentCount = this.countCurrentOccurrences(hash, currentIssues);
1678
- const previousCount = memory.occurrences;
1679
- if (currentCount > previousCount) {
1680
- memory.resurrected = true;
1681
- resurrected.push(this.createSmithIssue(
1682
- this.generateSmithIssueId(),
1683
- "serious",
1684
- `[SMITH] RESURRECTED: "${memory.pattern}" has returned -- Previous: ${previousCount}, Current: ${currentCount}`,
1685
- `You dismissed this issue on ${new Date(memory.dismissedAt).toLocaleDateString()}, but it grew from ${previousCount} to ${currentCount} occurrences.`,
1686
- memory.locations[0]?.split(":")[0] || "unknown",
1687
- void 0,
1688
- "resurrected"
1689
- ));
1690
- }
1691
- }
1692
- }
1693
- return resurrected;
1694
- }
1695
- /**
1696
- * Calculate inevitability score (0-100)
1697
- * Higher score = more likely to cause production issues
1698
- *
1699
- * VIBE CODE SCORING: Based on how often these issues
1700
- * cause real production problems in AI-generated projects.
1701
- */
1702
- calculateInevitabilityScore(type, count) {
1703
- const baseScores = {
1704
- // CRITICAL: Security - these WILL be exploited
1705
- "exposed-secret-hunter": 99,
1706
- "frontend-env-hunter": 95,
1707
- "hardcoded-localhost-hunter": 90,
1708
- "sql-injection-hunter": 98,
1709
- "dangeroushtml-hunter": 92,
1710
- // SERIOUS: Code smells that hide real bugs
1711
- "console-hunter": 40,
1712
- "any-hunter": 60,
1713
- "ts-ignore-hunter": 75,
1714
- "eslint-disable-hunter": 65,
1715
- "debugger-hunter": 85,
1716
- "force-flag-hunter": 70,
1717
- // MODERATE: Async bugs - silent failures
1718
- "async-useeffect-hunter": 70,
1719
- "async-foreach-hunter": 80,
1720
- "missing-await-hunter": 75,
1721
- "empty-catch-hunter": 70,
1722
- "floating-promise-hunter": 72,
1723
- // MODERATE: React anti-patterns
1724
- "useeffect-abuse-hunter": 30,
1725
- "usestate-explosion-hunter": 35,
1726
- "index-key-hunter": 55,
1727
- "inline-object-hunter": 25,
1728
- "prop-drilling-hunter": 40,
1729
- // MODERATE: Missing UX - users will complain
1730
- "missing-loading-hunter": 50,
1731
- "missing-error-hunter": 65,
1732
- "missing-empty-hunter": 45,
1733
- "page-reload-hunter": 60,
1734
- // MODERATE: Backend anti-patterns
1735
- "no-validation-hunter": 85,
1736
- "raw-error-hunter": 75,
1737
- "n-plus-one-hunter": 70,
1738
- // LOW: Incomplete code
1739
- "todo-hunter": 30,
1740
- "vibe-comment-hunter": 40,
1741
- "placeholder-hunter": 55,
1742
- "sleep-hack-hunter": 65,
1743
- "fallback-hunter": 75,
1744
- // LOW: Dead code
1745
- "commented-code-hunter": 25,
1746
- "unreachable-code-hunter": 45,
1747
- "unused-import-hunter": 20,
1748
- "empty-function-hunter": 35,
1749
- "dead-branch-hunter": 30,
1750
- // LOW: AI Slop Aesthetic
1751
- "purple-gradient-hunter": 25,
1752
- "star-icon-hunter": 20,
1753
- "generic-hero-hunter": 35,
1754
- "emoji-overflow-hunter": 15,
1755
- "inter-font-hunter": 10
1756
- };
1757
- const base = baseScores[type] || 30;
1758
- const multiplier = Math.log10(count + 1) * 15;
1759
- return Math.min(100, Math.round(base + multiplier));
1760
- }
1761
- /**
1762
- * Determine severity based on count and inevitability
1763
- */
1764
- determineSeverity(count, inevitability) {
1765
- if (inevitability >= 80 || count >= 50) return "critical";
1766
- if (inevitability >= 60 || count >= 20) return "serious";
1767
- if (inevitability >= 40 || count >= 5) return "moderate";
1768
- return "low";
1769
- }
1770
- /**
1771
- * Get a philosophical note from Agent Smith
1772
- *
1773
- * VIBE CODE FOCUSED: Commentary specifically about AI-generated code patterns.
1774
- * Agent Smith knows the vibes... and he disapproves.
1775
- */
1776
- getPhilosophicalNote(type, count) {
1777
- const notes = {
1778
- // === CRITICAL: Security ===
1779
- "exposed-secret-hunter": [
1780
- "You put your API key in the code. The machines thank you for your generosity.",
1781
- "OpenAI key in the frontend. Every script kiddie on the internet thanks you.",
1782
- "Hardcoded secrets. The AI gave you this code... and your keys to the kingdom."
1783
- ],
1784
- "frontend-env-hunter": [
1785
- "NEXT_PUBLIC_SECRET. The irony... is lost on the AI. And on you.",
1786
- "VITE_API_KEY. Visible to every user. Every. Single. One.",
1787
- "The AI put your secrets in the bundle. Now they belong to everyone."
1788
- ],
1789
- "hardcoded-localhost-hunter": [
1790
- "localhost:3000. Works on your machine. Dies in production.",
1791
- "The AI thinks localhost is production-ready. The AI... is mistaken.",
1792
- "127.0.0.1 - The address that works everywhere... except where it matters."
1793
- ],
1794
- "sql-injection-hunter": [
1795
- "String concatenation in SQL. The AI just opened the door... to every attacker.",
1796
- "SELECT * FROM users WHERE id = ${userId}. Little Bobby Tables sends his regards.",
1797
- "SQL injection. The oldest vulnerability. The AI's newest gift to hackers."
1798
- ],
1799
- "dangeroushtml-hunter": [
1800
- "dangerouslySetInnerHTML. The name is a warning. The AI ignored it.",
1801
- "innerHTML. The AI gave attackers a direct line to your users' browsers.",
1802
- "XSS vulnerability. The AI writes code. Attackers write scripts inside it."
1803
- ],
1804
- // === SERIOUS: AI Code Smells ===
1805
- "console-hunter": [
1806
- "console.log everywhere. The AI's debugging... left for you to clean.",
1807
- "You print to console... the users see nothing. But the bundle grows.",
1808
- "Debug statements. The fossils of development. Preserved... forever."
1809
- ],
1810
- "any-hunter": [
1811
- "'any' - The AI couldn't figure out the types. Neither will you.",
1812
- "Type safety? The AI chose violence instead. It chose 'any'.",
1813
- "You asked for TypeScript. The AI gave you JavaScript... with extra steps."
1814
- ],
1815
- "ts-ignore-hunter": [
1816
- "@ts-ignore. The AI hid the error. The error... did not go away.",
1817
- "TypeScript tried to warn you. The AI silenced it. How very... efficient.",
1818
- "@ts-nocheck. An entire file... surrendered to chaos."
1819
- ],
1820
- "eslint-disable-hunter": [
1821
- "eslint-disable. The linter saw the problem. The AI chose ignorance.",
1822
- "Why fix the bug when you can hide the warning? Very... human.",
1823
- "The rules exist for a reason. But the AI... plays by its own rules."
1824
- ],
1825
- "debugger-hunter": [
1826
- "debugger. Left in production. The browser freezes. The users... wonder.",
1827
- "A debugger statement. The AI forgot. You will remember... in production.",
1828
- "debugger. Time stops. But only in the browser. Not for your users' patience."
1829
- ],
1830
- "force-flag-hunter": [
1831
- "force: true. The AI bypassed the safety check. The check existed for a reason.",
1832
- "--no-verify. Git hooks were trying to help. The AI said no.",
1833
- "skipValidation. What could possibly go wrong? Everything. Everything could go wrong."
1834
- ],
1835
- // === MODERATE: Async/Promise Bugs ===
1836
- "async-useeffect-hunter": [
1837
- "async useEffect. The AI's favorite mistake. React's least favorite pattern.",
1838
- "useEffect(async () => ...). The warning exists. The AI ignores it.",
1839
- "Async useEffect. It looks right. It feels right. It is... wrong."
1840
- ],
1841
- "async-foreach-hunter": [
1842
- "async forEach. The await... waits for nothing. The AI... knows nothing.",
1843
- "forEach does not await. The AI does not understand. Neither does the code.",
1844
- "You await inside forEach. The promises resolve... in chaos."
1845
- ],
1846
- "missing-await-hunter": [
1847
- "fetch without await. The data loads... eventually. The UI updates... never.",
1848
- "The promise floats. Untethered. Unhandled. Unresolved.",
1849
- "async function, no await. Like a phone call where nobody speaks."
1850
- ],
1851
- "empty-catch-hunter": [
1852
- "catch {}. The error screamed. Nobody heard. The AI made sure of that.",
1853
- "Silent failure. The AI's specialty. The user's confusion.",
1854
- "Empty catch. The bug is caught... and immediately released back into the wild."
1855
- ],
1856
- "floating-promise-hunter": [
1857
- "A promise, floating. Never awaited. Never caught. Forever pending.",
1858
- "The AI created a promise. Then... moved on. The promise remains.",
1859
- "Fire and forget. Except the fire never went out. It's still burning."
1860
- ],
1861
- // === MODERATE: React Anti-patterns ===
1862
- "useeffect-abuse-hunter": [
1863
- "useEffect for everything. The AI's hammer for every nail.",
1864
- "Effects. So many effects. Most of them... unnecessary.",
1865
- "useEffect could be an event handler. But the AI... chose complexity."
1866
- ],
1867
- "usestate-explosion-hunter": [
1868
- "useState, useState, useState... The AI created a state management nightmare.",
1869
- "10 useState calls. The AI could use an object. The AI chose chaos.",
1870
- "State explosion. Each variable... its own island. Its own re-render."
1871
- ],
1872
- "index-key-hunter": [
1873
- "key={index}. The AI's lazy choice. React's reconciliation nightmare.",
1874
- "Array index as key. Reorder the list. Watch the chaos unfold.",
1875
- "key={i}. It works... until it doesn't. And it will stop working."
1876
- ],
1877
- "inline-object-hunter": [
1878
- "style={{}}. A new object. Every render. Every time.",
1879
- "Inline objects. The AI's performance anti-pattern of choice.",
1880
- "A new object is born. On every render. Only to die immediately."
1881
- ],
1882
- "prop-drilling-hunter": [
1883
- "Props drilling down, down, down... The AI's idea of state management.",
1884
- "The same prop, passed through 5 components. Why? The AI... never asked.",
1885
- "Prop drilling. Like passing a note through every student to reach the teacher."
1886
- ],
1887
- // === MODERATE: Missing UX ===
1888
- "missing-loading-hunter": [
1889
- "No loading state. The screen is blank. The user... waits. And wonders.",
1890
- "Data fetches. UI shows nothing. The user assumes it's broken. They're... not wrong.",
1891
- "Loading state? The AI forgot. Your users will remember."
1892
- ],
1893
- "missing-error-hunter": [
1894
- "No error handling. When the API fails... the app fails. Silently.",
1895
- "fetch without catch. The network fails. The user sees... nothing.",
1896
- "Errors are inevitable. Your handling of them... is not."
1897
- ],
1898
- "missing-empty-hunter": [
1899
- "No empty state. Zero items. Zero feedback. Zero UX.",
1900
- "The list is empty. The UI is empty. The user's understanding... is empty.",
1901
- "What happens when there's no data? The AI never asked. You should."
1902
- ],
1903
- "page-reload-hunter": [
1904
- "location.reload(). The AI's nuclear option for state management.",
1905
- "Reload the page to fix state. The user loses everything. Brilliant.",
1906
- "window.location.reload(). Because fixing state properly is... hard."
1907
- ],
1908
- // === MODERATE: Backend Anti-patterns ===
1909
- "no-validation-hunter": [
1910
- "req.body.email. Trusted. Unvalidated. Waiting to be exploited.",
1911
- "User input, used directly. The AI trusts everyone. You shouldn't.",
1912
- "No validation. The attacker sends { admin: true }. The app believes them."
1913
- ],
1914
- "raw-error-hunter": [
1915
- "Error message sent to client. Stack traces. File paths. Internal secrets.",
1916
- "res.json(error). The attacker says thank you for the debugging information.",
1917
- "Raw errors in production. The AI's gift to penetration testers."
1918
- ],
1919
- "n-plus-one-hunter": [
1920
- "Query in a loop. 100 users = 101 queries. The database... suffers.",
1921
- "N+1 problem. The AI fetched efficiently... for one user. Not for a hundred.",
1922
- "await in forEach. The database connection pool is crying."
1923
- ],
1924
- // === LOW: Incomplete Code ===
1925
- "todo-hunter": [
1926
- "TODO. The AI's promise to future you. Future you... is still waiting.",
1927
- "FIXME. Acknowledged. Not fixed. Never fixed.",
1928
- "Every TODO is technical debt. The AI generates debt... efficiently."
1929
- ],
1930
- "vibe-comment-hunter": [
1931
- "'idk why this works'. The AI doesn't know. You don't know. Nobody knows.",
1932
- "'don't touch this'. The vibe coder's warning. The maintainer's nightmare.",
1933
- "'somehow works'. Somehow... will stop working. Inevitably."
1934
- ],
1935
- "placeholder-hunter": [
1936
- "test@test.com. In production. The AI's placeholder... became permanent.",
1937
- "example.com. Hardcoded. Shipped. The real URL? Nobody added it.",
1938
- "Placeholder data. Placeholder quality. Placeholder product."
1939
- ],
1940
- "sleep-hack-hunter": [
1941
- "setTimeout(1000). The AI's solution to race conditions: just wait.",
1942
- "await sleep(2000). Works on fast connections. Fails on slow ones. Wastes time on all.",
1943
- "Sleep to fix timing. The race condition still exists. It just happens later."
1944
- ],
1945
- "fallback-hunter": [
1946
- "return null. The AI's favorite way to hide errors. The user sees... nothing.",
1947
- "return []. Empty array, empty understanding, empty debugging.",
1948
- "Fallback code. The bug is still there. You just can't see it anymore."
1949
- ],
1950
- // === DEAD CODE (AI leaves cruft everywhere) ===
1951
- "commented-code-hunter": [
1952
- "Commented-out blocks. Fossils of code past. Confusion for the future.",
1953
- "Large comment blocks hide history. Git already remembers. Delete the noise.",
1954
- "Dead code in comments. The AI was unsure. You should be certain."
1955
- ],
1956
- "unreachable-code-hunter": [
1957
- "Code after return. Forever skipped. Forever confusing.",
1958
- "Unreachable branches. They stay forever. Bugs hide behind them.",
1959
- "Execution stops. The dead code remains. Remove the ghosts."
1960
- ],
1961
- "unused-import-hunter": [
1962
- "Imports unused. Bundles bloated. Intent unclear.",
1963
- "An import with no purpose. The AI added it. You can remove it.",
1964
- "Unused imports multiply. Tree shaking sighs."
1965
- ],
1966
- "empty-function-hunter": [
1967
- "Empty function bodies. Promises without delivery.",
1968
- "Stub functions linger. The AI forgot to finish.",
1969
- "Empty handlers. They do nothing. They hide missing behavior."
1970
- ],
1971
- "dead-branch-hunter": [
1972
- "if (true) with no reason. if (false) with no hope.",
1973
- "Dead conditionals: code that never runs, but always confuses.",
1974
- "Always-true or always-false. Remove the illusion of choice."
1975
- ],
1976
- // AI Slop Aesthetic
1977
- "purple-gradient-hunter": [
1978
- "Purple gradient. The AI's signature. Every vibe-coded site... looks the same.",
1979
- "from-purple-500 to-violet-600. I've seen this a million times. So has everyone else.",
1980
- "The purple gradient. It screams 'I let AI design this.' Is that what you want?"
1981
- ],
1982
- "star-icon-hunter": [
1983
- "Stars. Everywhere. The AI decorates like a child with stickers.",
1984
- "Five stars for the AI's creativity. Zero stars for originality.",
1985
- "Star icons. The AI's answer to 'make it look nice.' It does not."
1986
- ],
1987
- "generic-hero-hunter": [
1988
- "'Welcome to the future of...' The AI writes copy. The copy says nothing.",
1989
- "'Transform your workflow.' What workflow? Doing what? The AI doesn't know.",
1990
- "Generic hero copy. Your product is unique. Your landing page... is not."
1991
- ],
1992
- "emoji-overflow-hunter": [
1993
- "Emojis in production code. The AI's crutch for visual interest.",
1994
- "Every emoji is an admission: the design couldn't speak for itself.",
1995
- "The AI decorates. Real designers communicate."
1996
- ],
1997
- "inter-font-hunter": [
1998
- "Inter. The only font the AI knows. The only font everyone uses.",
1999
- "system-ui. The AI takes the path of least resistance. Every time.",
2000
- "Inter font. Safe. Boring. Forgettable. Just like every other AI site."
2001
- ]
2002
- };
2003
- const typeNotes = notes[type] || ["I have my eye on your vibe code."];
2004
- const index = count % typeNotes.length;
2005
- return typeNotes[index] || typeNotes[0] || "";
2006
- }
2007
- /**
2008
- * Create AI analysis issue for complex pattern detection
2009
- */
2010
- createAIAnalysisIssue(files, hunterResults) {
2011
- const totalIssues = hunterResults.reduce((sum, r) => sum + r.instances.length, 0);
2012
- const categories = hunterResults.map((r) => r.type).join(", ");
2013
- return this.createSmithIssue(
2014
- this.generateSmithIssueId(),
2015
- "moderate",
2016
- `[SMITH] AI Analysis Required -- ${totalIssues} violations in ${files.length} files`,
2017
- `Analyze for deeper code quality issues, architectural problems, and patterns that pattern hunters may have missed. Categories: ${categories}`,
2018
- files[0] || "unknown",
2019
- void 0,
2020
- "ai-analysis"
2021
- );
2022
- }
2023
- // ============ Memory/Persistence System ============
2024
- /**
2025
- * Load memory from disk
2026
- */
2027
- async loadMemory(workingDir) {
2028
- this.memoryBaseDir = workingDir;
2029
- this.memoryPath = join(getTrieDirectory(workingDir), "smith-memory.json");
2030
- try {
2031
- if (existsSync(this.memoryPath)) {
2032
- const content = await readFile(this.memoryPath, "utf-8");
2033
- this.memory = JSON.parse(content);
2034
- } else {
2035
- this.memory = {
2036
- version: "1.0.0",
2037
- lastScan: (/* @__PURE__ */ new Date()).toISOString(),
2038
- issues: {},
2039
- assimilationCount: 0
2040
- };
2041
- }
2042
- } catch {
2043
- this.memory = {
2044
- version: "1.0.0",
2045
- lastScan: (/* @__PURE__ */ new Date()).toISOString(),
2046
- issues: {},
2047
- assimilationCount: 0
2048
- };
2049
- }
2050
- }
2051
- /**
2052
- * Display Agent Smith ASCII art entrance
2053
- * Uses OutputManager to route to TUI or console based on mode
2054
- */
2055
- displaySmithEntrance() {
2056
- const greeting = AGENT_SMITH_GREETING[Math.floor(Math.random() * AGENT_SMITH_GREETING.length)] ?? "The code will be... assimilated.";
2057
- output().banner("agent-smith", AGENT_SMITH_ASCII, {
2058
- quote: greeting,
2059
- version: this.version
2060
- });
2061
- }
2062
- /**
2063
- * Save memory to disk (with optimization)
2064
- */
2065
- async saveMemory() {
2066
- if (!this.memory || !this.memoryPath) return;
2067
- this.memory.lastScan = (/* @__PURE__ */ new Date()).toISOString();
2068
- this.pruneMemory();
2069
- try {
2070
- await mkdir(dirname(this.memoryPath), { recursive: true });
2071
- await writeFile(this.memoryPath, JSON.stringify(this.memory, null, 2));
2072
- } catch {
2073
- }
2074
- }
2075
- /**
2076
- * Prune memory to prevent unbounded growth
2077
- */
2078
- pruneMemory() {
2079
- if (!this.memory) return;
2080
- const now = Date.now();
2081
- const pruneThreshold = MEMORY_LIMITS.PRUNE_AFTER_DAYS * 24 * 60 * 60 * 1e3;
2082
- const issues = Object.entries(this.memory.issues);
2083
- for (const [hash, issue] of issues) {
2084
- const lastSeenTime = new Date(issue.lastSeen).getTime();
2085
- const age = now - lastSeenTime;
2086
- if (age > pruneThreshold) {
2087
- if (issue.resurrected || issue.occurrences === 0 || issue.dismissedAt && issue.occurrences <= 1) {
2088
- delete this.memory.issues[hash];
2089
- }
2090
- }
2091
- }
2092
- const remainingIssues = Object.entries(this.memory.issues);
2093
- if (remainingIssues.length > MEMORY_LIMITS.MAX_TRACKED_ISSUES) {
2094
- remainingIssues.sort(
2095
- (a, b) => new Date(a[1].lastSeen).getTime() - new Date(b[1].lastSeen).getTime()
2096
- );
2097
- const toRemove = remainingIssues.length - MEMORY_LIMITS.MAX_TRACKED_ISSUES;
2098
- for (let i = 0; i < toRemove; i++) {
2099
- const hash = remainingIssues[i]?.[0];
2100
- if (hash) delete this.memory.issues[hash];
2101
- }
2102
- }
2103
- }
2104
- /**
2105
- * Clear all memory (user command)
2106
- */
2107
- async clearMemory() {
2108
- try {
2109
- if (this.memoryPath && existsSync(this.memoryPath)) {
2110
- await rm(this.memoryPath);
2111
- this.memory = null;
2112
- return {
2113
- success: true,
2114
- message: "[SMITH] Memory cleared."
2115
- };
2116
- }
2117
- return {
2118
- success: true,
2119
- message: "[SMITH] No memory file found. Nothing to clear."
2120
- };
2121
- } catch (error) {
2122
- return {
2123
- success: false,
2124
- message: `Failed to clear memory: ${error}`
2125
- };
2126
- }
2127
- }
2128
- /**
2129
- * Get memory stats (for diagnostics)
2130
- */
2131
- async getMemoryStats() {
2132
- await this.loadMemory(process.cwd());
2133
- if (!this.memory) {
2134
- return {
2135
- issueCount: 0,
2136
- dismissedCount: 0,
2137
- resurrectedCount: 0,
2138
- oldestIssue: null,
2139
- fileSizeKB: 0
2140
- };
2141
- }
2142
- const issues = Object.values(this.memory.issues);
2143
- let fileSizeKB = 0;
2144
- try {
2145
- if (this.memoryPath && existsSync(this.memoryPath)) {
2146
- const stats = await import("fs/promises").then((fs) => fs.stat(this.memoryPath));
2147
- fileSizeKB = Math.round(stats.size / 1024 * 10) / 10;
2148
- }
2149
- } catch {
2150
- }
2151
- const sortedByDate = [...issues].sort(
2152
- (a, b) => new Date(a.firstSeen).getTime() - new Date(b.firstSeen).getTime()
2153
- );
2154
- return {
2155
- issueCount: issues.length,
2156
- dismissedCount: issues.filter((i) => i.dismissedAt).length,
2157
- resurrectedCount: issues.filter((i) => i.resurrected).length,
2158
- oldestIssue: sortedByDate[0]?.firstSeen || null,
2159
- fileSizeKB
2160
- };
2161
- }
2162
- /**
2163
- * Update memory with new occurrence
2164
- */
2165
- async updateMemory(hash, pattern, category, location, count) {
2166
- if (!this.memory) return;
2167
- const existing = this.memory.issues[hash];
2168
- const now = (/* @__PURE__ */ new Date()).toISOString();
2169
- if (existing) {
2170
- existing.lastSeen = now;
2171
- existing.occurrences = count;
2172
- existing.locations = [location, ...existing.locations.slice(0, MEMORY_LIMITS.MAX_LOCATIONS_PER_ISSUE - 1)];
2173
- } else {
2174
- this.memory.issues[hash] = {
2175
- hash,
2176
- pattern,
2177
- category,
2178
- firstSeen: now,
2179
- lastSeen: now,
2180
- occurrences: count,
2181
- locations: [location],
2182
- resurrected: false
2183
- };
2184
- }
2185
- this.memory.assimilationCount++;
2186
- }
2187
- /**
2188
- * Dismiss an issue (mark it as seen/accepted)
2189
- */
2190
- async dismissIssue(hash, workingDir) {
2191
- const baseDir = this.memoryBaseDir ?? workingDir ?? process.cwd();
2192
- await this.loadMemory(baseDir);
2193
- if (this.memory && this.memory.issues[hash]) {
2194
- this.memory.issues[hash].dismissedAt = (/* @__PURE__ */ new Date()).toISOString();
2195
- await this.saveMemory();
2196
- }
2197
- }
2198
- /**
2199
- * Create a hash for pattern tracking
2200
- */
2201
- createPatternHash(type, file) {
2202
- return createHash("md5").update(`${type}:${file}`).digest("hex").slice(0, 12);
2203
- }
2204
- /**
2205
- * Count current occurrences for a pattern hash
2206
- */
2207
- countCurrentOccurrences(hash, issues) {
2208
- const memory = this.memory?.issues[hash];
2209
- if (!memory) return 0;
2210
- return issues.filter(
2211
- (i) => i.id.includes(hash) || i.category === memory.category || (memory.category ? i.issue.includes(memory.category) : false)
2212
- ).length;
2213
- }
2214
- /**
2215
- * Check if file is a test file
2216
- */
2217
- isTestFile(file) {
2218
- return /\.(test|spec)\.[jt]sx?$/.test(file) || /__(tests|mocks)__/.test(file) || /\/test\//.test(file);
2219
- }
2220
- // ============ Helper Methods ============
2221
- /**
2222
- * Preload file contents once to avoid repeated I/O across pattern hunters
2223
- */
2224
- async loadFileContents(files) {
2225
- const contents = /* @__PURE__ */ new Map();
2226
- for (const file of files) {
2227
- try {
2228
- const buffer = await readFile(file);
2229
- if (buffer.includes(0) || buffer.byteLength > FILE_SCAN_LIMITS.MAX_BYTES) {
2230
- continue;
2231
- }
2232
- contents.set(file, buffer.toString("utf-8"));
2233
- } catch {
2234
- }
2235
- }
2236
- return contents;
2237
- }
2238
- createSmithIssue(id, severity, issue, fix, file, line, category) {
2239
- const result = {
2240
- id,
2241
- agent: this.name,
2242
- severity,
2243
- issue,
2244
- fix,
2245
- file,
2246
- confidence: 0.9,
2247
- autoFixable: false
2248
- };
2249
- if (line !== void 0) {
2250
- result.line = line;
2251
- }
2252
- if (category !== void 0) {
2253
- result.category = category;
2254
- }
2255
- return result;
2256
- }
2257
- generateSmithIssueId(seed) {
2258
- const base = seed ? `smith-${seed}` : "smith";
2259
- return `${base}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
2260
- }
2261
- };
2262
-
2263
- export {
2264
- getOutputManager,
2265
- output,
2266
- BaseSkill,
2267
- PATTERN_HUNTER_CONFIGS,
2268
- AgentSmithSkill
2269
- };
2270
- //# sourceMappingURL=chunk-RNJ6JKMA.js.map