Uanderson Ricardo commited on
Commit
194f5ac
·
2 Parent(s): aae7a5832a4f79

Merge pull request #3 from uandersonricardo/auditor-agent

Browse files
README_GH.md CHANGED
@@ -19,6 +19,8 @@ O sistema é implementado em **TypeScript** e **Node.js**, utilizando a bibliote
19
 
20
  O sistema é composto por agentes especializados que colaboram entre si em diferentes etapas do processo:
21
 
 
 
22
  1. **Exploração e análise dos requisitos**
23
 
24
  * Processamento e compreensão dos documentos fornecidos;
 
19
 
20
  O sistema é composto por agentes especializados que colaboram entre si em diferentes etapas do processo:
21
 
22
+ ![Arquitetura Geral](./assets/architecture.png)
23
+
24
  1. **Exploração e análise dos requisitos**
25
 
26
  * Processamento e compreensão dos documentos fornecidos;
frontend/src/App.tsx CHANGED
@@ -1,10 +1,25 @@
1
  import { useState, useRef, useCallback } from "react";
2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  interface AgentResult {
4
  contract?: string;
5
  compilationErrors?: string[];
6
  reviewSummary?: string;
7
- vulnerabilities?: Array<{ type: string; severity: string; description: string }>;
8
  results?: unknown[];
9
  }
10
 
@@ -58,8 +73,10 @@ export function App() {
58
  for (const line of lines) {
59
  if (line.startsWith("event:")) {
60
  currentEvent = line.slice(6).trim();
 
61
  } else if (line.startsWith("data:")) {
62
  const data = line.slice(5).trim();
 
63
  switch (currentEvent) {
64
  case "log":
65
  appendLog(data);
@@ -90,12 +107,10 @@ export function App() {
90
  return (
91
  <div style={styles.container}>
92
  <header style={styles.header}>
93
- <h1 style={styles.title}>
94
- Multi-Agent: Geração, Auditoria e Teste de Smart Contracts
95
- </h1>
96
  <p style={styles.subtitle}>
97
- Descreva um cenário ou requisito cuja solução seja um smart contract em Solidity.
98
- O sistema irá gerar, compilar, auditar e testar o contrato automaticamente.
99
  </p>
100
  </header>
101
 
@@ -103,15 +118,21 @@ export function App() {
103
  <textarea
104
  value={requirements}
105
  onChange={(e) => setRequirements(e.target.value)}
106
- placeholder={"Ex: Crie um token ERC20 com as seguintes características:\n- Nome: MeuToken, Símbolo: MTK\n- Supply inicial de 1.000.000 tokens\n- Funções de mint (apenas owner) e burn\n- Pausável pelo owner"}
 
 
107
  style={styles.textarea}
108
  rows={6}
109
  disabled={running}
110
  />
111
- <button type="submit" disabled={running || !requirements.trim()} style={{
112
- ...styles.button,
113
- opacity: running || !requirements.trim() ? 0.5 : 1,
114
- }}>
 
 
 
 
115
  {running ? "Executando pipeline..." : "Executar Pipeline"}
116
  </button>
117
  </form>
@@ -122,7 +143,9 @@ export function App() {
122
  <h2 style={styles.sectionTitle}>📋 Log de Execução</h2>
123
  <div style={styles.logBox}>
124
  {logs.map((log, i) => (
125
- <div key={i} style={styles.logLine}>{log}</div>
 
 
126
  ))}
127
  <div ref={logsEndRef} />
128
  </div>
@@ -163,9 +186,33 @@ export function App() {
163
  {auditorResult && (
164
  <section style={styles.section}>
165
  <h2 style={styles.sectionTitle}>🔍 Agente Auditor</h2>
166
- <div style={styles.codeBox}>
167
- <pre style={styles.code}>{JSON.stringify(auditorResult.vulnerabilities, null, 2)}</pre>
168
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
169
  </section>
170
  )}
171
 
@@ -186,6 +233,19 @@ export function App() {
186
  );
187
  }
188
 
 
 
 
 
 
 
 
 
 
 
 
 
 
189
  const styles: Record<string, React.CSSProperties> = {
190
  container: {
191
  maxWidth: 900,
@@ -305,4 +365,36 @@ const styles: Record<string, React.CSSProperties> = {
305
  color: "#cbd5e1",
306
  whiteSpace: "pre-wrap",
307
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
308
  };
 
1
  import { useState, useRef, useCallback } from "react";
2
 
3
+ interface Finding {
4
+ title: string;
5
+ description: string;
6
+ recommendation: string;
7
+ severity: "high" | "medium" | "low";
8
+ codeSnippet: string;
9
+ location: string;
10
+ path: string;
11
+ judgeReview: {
12
+ review: string;
13
+ confidence: number;
14
+ exploitablePaths: string[];
15
+ };
16
+ }
17
+
18
  interface AgentResult {
19
  contract?: string;
20
  compilationErrors?: string[];
21
  reviewSummary?: string;
22
+ findings?: Finding[];
23
  results?: unknown[];
24
  }
25
 
 
73
  for (const line of lines) {
74
  if (line.startsWith("event:")) {
75
  currentEvent = line.slice(6).trim();
76
+ console.log("event", currentEvent);
77
  } else if (line.startsWith("data:")) {
78
  const data = line.slice(5).trim();
79
+ console.log("data", data);
80
  switch (currentEvent) {
81
  case "log":
82
  appendLog(data);
 
107
  return (
108
  <div style={styles.container}>
109
  <header style={styles.header}>
110
+ <h1 style={styles.title}>Multi-Agent: Geração, Auditoria e Teste de Smart Contracts</h1>
 
 
111
  <p style={styles.subtitle}>
112
+ Descreva um cenário ou requisito cuja solução seja um smart contract em Solidity. O sistema irá gerar,
113
+ compilar, auditar e testar o contrato automaticamente.
114
  </p>
115
  </header>
116
 
 
118
  <textarea
119
  value={requirements}
120
  onChange={(e) => setRequirements(e.target.value)}
121
+ placeholder={
122
+ "Ex: Crie um token ERC20 com as seguintes características:\n- Nome: MeuToken, Símbolo: MTK\n- Supply inicial de 1.000.000 tokens\n- Funções de mint (apenas owner) e burn\n- Pausável pelo owner"
123
+ }
124
  style={styles.textarea}
125
  rows={6}
126
  disabled={running}
127
  />
128
+ <button
129
+ type="submit"
130
+ disabled={running || !requirements.trim()}
131
+ style={{
132
+ ...styles.button,
133
+ opacity: running || !requirements.trim() ? 0.5 : 1,
134
+ }}
135
+ >
136
  {running ? "Executando pipeline..." : "Executar Pipeline"}
137
  </button>
138
  </form>
 
143
  <h2 style={styles.sectionTitle}>📋 Log de Execução</h2>
144
  <div style={styles.logBox}>
145
  {logs.map((log, i) => (
146
+ <div key={i} style={styles.logLine}>
147
+ {log}
148
+ </div>
149
  ))}
150
  <div ref={logsEndRef} />
151
  </div>
 
186
  {auditorResult && (
187
  <section style={styles.section}>
188
  <h2 style={styles.sectionTitle}>🔍 Agente Auditor</h2>
189
+ {auditorResult.findings && auditorResult.findings.length > 0 ? (
190
+ auditorResult.findings.map((f, i) => (
191
+ <div key={i} style={{ ...styles.findingCard, borderColor: severityColor(f.severity) }}>
192
+ <div style={styles.findingHeader}>
193
+ <span style={{ ...styles.severityBadge, background: severityColor(f.severity) }}>
194
+ {f.severity.toUpperCase()}
195
+ </span>
196
+ <span style={styles.findingTitle}>{f.title}</span>
197
+ </div>
198
+ <p style={styles.findingText}>{f.description}</p>
199
+ <p style={{ ...styles.findingText, color: "#94a3b8" }}>
200
+ <strong>Localização:</strong> {f.location ?? "-"}
201
+ </p>
202
+ {f.codeSnippet && <pre style={styles.code}>{f.codeSnippet}</pre>}
203
+ <p style={{ ...styles.findingText, color: "#94a3b8" }}>
204
+ <strong>Recomendação:</strong> {f.recommendation}
205
+ </p>
206
+ <p style={{ ...styles.findingText, color: "#64748b", fontSize: 12 }}>
207
+ Confiança: {Math.round(f.judgeReview.confidence)}% — {f.judgeReview.review}
208
+ </p>
209
+ </div>
210
+ ))
211
+ ) : (
212
+ <div style={styles.resultBox}>
213
+ <p style={styles.resultText}>Nenhuma vulnerabilidade encontrada.</p>
214
+ </div>
215
+ )}
216
  </section>
217
  )}
218
 
 
233
  );
234
  }
235
 
236
+ const severityColor = (severity: string) => {
237
+ switch (severity) {
238
+ case "high":
239
+ return "#ef4444";
240
+ case "medium":
241
+ return "#f97316";
242
+ case "low":
243
+ return "#eab308";
244
+ default:
245
+ return "#64748b";
246
+ }
247
+ };
248
+
249
  const styles: Record<string, React.CSSProperties> = {
250
  container: {
251
  maxWidth: 900,
 
365
  color: "#cbd5e1",
366
  whiteSpace: "pre-wrap",
367
  },
368
+ findingCard: {
369
+ background: "#1e293b",
370
+ border: "1px solid",
371
+ borderRadius: 8,
372
+ padding: 16,
373
+ marginBottom: 12,
374
+ },
375
+ findingHeader: {
376
+ display: "flex",
377
+ alignItems: "center",
378
+ gap: 10,
379
+ marginBottom: 8,
380
+ },
381
+ severityBadge: {
382
+ fontSize: 11,
383
+ fontWeight: 700,
384
+ color: "#fff",
385
+ padding: "2px 8px",
386
+ borderRadius: 4,
387
+ letterSpacing: "0.05em",
388
+ },
389
+ findingTitle: {
390
+ fontSize: 15,
391
+ fontWeight: 600,
392
+ color: "#f8fafc",
393
+ },
394
+ findingText: {
395
+ margin: "4px 0",
396
+ fontSize: 13,
397
+ lineHeight: 1.6,
398
+ color: "#cbd5e1",
399
+ },
400
  };
package-lock.json CHANGED
@@ -11,15 +11,18 @@
11
  "license": "ISC",
12
  "dependencies": {
13
  "@hono/node-server": "^2.0.3",
 
14
  "@langchain/core": "^1.1.45",
15
  "@langchain/google-genai": "^2.1.31",
16
  "@langchain/langgraph": "^1.3.0",
17
  "@langchain/openrouter": "^0.2.4",
 
18
  "dotenv": "^17.4.2",
19
  "hono": "^4.12.21",
20
  "langchain": "^1.4.0",
21
  "solc": "^0.8.35",
22
  "uuid": "^11.1.1",
 
23
  "zod": "^4.4.3"
24
  },
25
  "devDependencies": {
@@ -30,6 +33,35 @@
30
  "vitest": "^4.1.5"
31
  }
32
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  "node_modules/@biomejs/biome": {
34
  "version": "2.4.14",
35
  "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.4.14.tgz",
@@ -199,6 +231,26 @@
199
  "integrity": "sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og==",
200
  "license": "MIT"
201
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
202
  "node_modules/@emnapi/core": {
203
  "version": "1.10.0",
204
  "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz",
@@ -261,10 +313,26 @@
261
  "dev": true,
262
  "license": "MIT"
263
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
264
  "node_modules/@langchain/core": {
265
- "version": "1.1.47",
266
- "resolved": "https://registry.npmjs.org/@langchain/core/-/core-1.1.47.tgz",
267
- "integrity": "sha512-+fiPu6ZFnJMrZyKeM77OIVPoMPAY6OKWacnPlojHtXTbMMzb2cEOKAJV0U07cDl86NHSCIYYa0i4CyKZzXbHQQ==",
268
  "license": "MIT",
269
  "dependencies": {
270
  "@cfworker/json-schema": "^4.0.2",
@@ -295,13 +363,13 @@
295
  }
296
  },
297
  "node_modules/@langchain/langgraph": {
298
- "version": "1.3.0",
299
- "resolved": "https://registry.npmjs.org/@langchain/langgraph/-/langgraph-1.3.0.tgz",
300
- "integrity": "sha512-QvhTjiyqFPz81A+y6LHs223w6DTjv5+882DT4mup72bd72rRhNjTYo5fhes5um0swnKArvY/arc7KeFInfHHWw==",
301
  "license": "MIT",
302
  "dependencies": {
303
  "@langchain/langgraph-checkpoint": "^1.0.2",
304
- "@langchain/langgraph-sdk": "~1.9.0",
305
  "@langchain/protocol": "^0.0.15",
306
  "@standard-schema/spec": "1.1.0",
307
  "uuid": "^10.0.0"
@@ -350,12 +418,11 @@
350
  }
351
  },
352
  "node_modules/@langchain/langgraph-sdk": {
353
- "version": "1.9.1",
354
- "resolved": "https://registry.npmjs.org/@langchain/langgraph-sdk/-/langgraph-sdk-1.9.1.tgz",
355
- "integrity": "sha512-pHojybde9HoMz7ZDtyW3pgDdomAN4C0pbdgXASjccFS+S2Cqx75iZBtBUUg1A/CSer5xh9GakdIqyihxZOf7VA==",
356
  "license": "MIT",
357
  "dependencies": {
358
- "@langchain/core": "^1.1.44",
359
  "@langchain/protocol": "^0.0.15",
360
  "@types/json-schema": "^7.0.15",
361
  "p-queue": "^9.0.1",
@@ -363,6 +430,7 @@
363
  "uuid": "^13.0.0"
364
  },
365
  "peerDependencies": {
 
366
  "react": "^18 || ^19",
367
  "react-dom": "^18 || ^19",
368
  "svelte": "^4.0.0 || ^5.0.0",
@@ -390,9 +458,9 @@
390
  "license": "MIT"
391
  },
392
  "node_modules/@langchain/langgraph-sdk/node_modules/p-queue": {
393
- "version": "9.2.0",
394
- "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-9.2.0.tgz",
395
- "integrity": "sha512-dWgLE8AH0HjQ9fe74pUkKkvzzYT18Inp4zra3lKHnnwqGvcfcUBrvF2EAVX+envufDNBOzpPq/IBUONDbI7+3g==",
396
  "license": "MIT",
397
  "dependencies": {
398
  "eventemitter3": "^5.0.4",
@@ -777,6 +845,22 @@
777
  "dev": true,
778
  "license": "MIT"
779
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
780
  "node_modules/@standard-schema/spec": {
781
  "version": "1.1.0",
782
  "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
@@ -835,6 +919,12 @@
835
  "undici-types": "~7.19.0"
836
  }
837
  },
 
 
 
 
 
 
838
  "node_modules/@vitest/expect": {
839
  "version": "4.1.5",
840
  "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.5.tgz",
@@ -971,6 +1061,26 @@
971
  "url": "https://github.com/chalk/ansi-styles?sponsor=1"
972
  }
973
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
974
  "node_modules/assertion-error": {
975
  "version": "2.0.1",
976
  "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
@@ -981,6 +1091,12 @@
981
  "node": ">=12"
982
  }
983
  },
 
 
 
 
 
 
984
  "node_modules/base64-js": {
985
  "version": "1.5.1",
986
  "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
@@ -1107,25 +1223,51 @@
1107
  "node": ">=8"
1108
  }
1109
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
1110
  "node_modules/color-convert": {
1111
- "version": "2.0.1",
1112
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
1113
- "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
1114
- "dev": true,
1115
  "license": "MIT",
1116
  "dependencies": {
1117
- "color-name": "~1.1.4"
1118
  },
1119
  "engines": {
1120
- "node": ">=7.0.0"
1121
  }
1122
  },
1123
  "node_modules/color-name": {
1124
- "version": "1.1.4",
1125
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
1126
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
1127
- "dev": true,
1128
- "license": "MIT"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1129
  },
1130
  "node_modules/command-exists": {
1131
  "version": "1.2.9",
@@ -1219,6 +1361,12 @@
1219
  "node": ">= 0.4"
1220
  }
1221
  },
 
 
 
 
 
 
1222
  "node_modules/es-define-property": {
1223
  "version": "1.0.1",
1224
  "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
@@ -1312,6 +1460,12 @@
1312
  }
1313
  }
1314
  },
 
 
 
 
 
 
1315
  "node_modules/fill-range": {
1316
  "version": "7.1.1",
1317
  "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
@@ -1335,6 +1489,12 @@
1335
  "micromatch": "^4.0.2"
1336
  }
1337
  },
 
 
 
 
 
 
1338
  "node_modules/follow-redirects": {
1339
  "version": "1.16.0",
1340
  "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz",
@@ -1512,6 +1672,12 @@
1512
  "node": ">=16.9.0"
1513
  }
1514
  },
 
 
 
 
 
 
1515
  "node_modules/is-docker": {
1516
  "version": "2.2.1",
1517
  "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz",
@@ -1529,9 +1695,9 @@
1529
  }
1530
  },
1531
  "node_modules/is-network-error": {
1532
- "version": "1.3.1",
1533
- "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.3.1.tgz",
1534
- "integrity": "sha512-6QCxa49rQbmUWLfk0nuGqzql9U8uaV2H6279bRErPBHe/109hCzsLUBUHfbEtvLIHBd6hyXbgedBSHevm43Edw==",
1535
  "license": "MIT",
1536
  "engines": {
1537
  "node": ">=16"
@@ -1550,6 +1716,18 @@
1550
  "node": ">=0.12.0"
1551
  }
1552
  },
 
 
 
 
 
 
 
 
 
 
 
 
1553
  "node_modules/is-wsl": {
1554
  "version": "2.2.0",
1555
  "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
@@ -1592,6 +1770,19 @@
1592
  "base64-js": "^1.5.1"
1593
  }
1594
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
1595
  "node_modules/json-stable-stringify": {
1596
  "version": "1.3.0",
1597
  "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.3.0.tgz",
@@ -1645,13 +1836,19 @@
1645
  "graceful-fs": "^4.1.11"
1646
  }
1647
  },
 
 
 
 
 
 
1648
  "node_modules/langchain": {
1649
- "version": "1.4.0",
1650
- "resolved": "https://registry.npmjs.org/langchain/-/langchain-1.4.0.tgz",
1651
- "integrity": "sha512-p3H5U1vfO0T4ri/xxqI6jccRP3LYmOW6KGxaJcwI5mIGVR/G6eNhZyjSZB9d7me6J7an4Pc6zA8tH8Qa6/7xwA==",
1652
  "license": "MIT",
1653
  "dependencies": {
1654
- "@langchain/langgraph": "^1.3.0",
1655
  "@langchain/langgraph-checkpoint": "^1.0.1",
1656
  "langsmith": ">=0.5.0 <1.0.0",
1657
  "zod": "^3.25.76 || ^4"
@@ -1660,7 +1857,7 @@
1660
  "node": ">=20"
1661
  },
1662
  "peerDependencies": {
1663
- "@langchain/core": "^1.1.44"
1664
  }
1665
  },
1666
  "node_modules/langsmith": {
@@ -1957,6 +2154,23 @@
1957
  "url": "https://opencollective.com/parcel"
1958
  }
1959
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1960
  "node_modules/magic-string": {
1961
  "version": "0.30.21",
1962
  "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
@@ -2022,6 +2236,12 @@
2022
  "url": "https://github.com/sponsors/ljharb"
2023
  }
2024
  },
 
 
 
 
 
 
2025
  "node_modules/mustache": {
2026
  "version": "4.2.0",
2027
  "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz",
@@ -2071,6 +2291,15 @@
2071
  ],
2072
  "license": "MIT"
2073
  },
 
 
 
 
 
 
 
 
 
2074
  "node_modules/open": {
2075
  "version": "7.4.2",
2076
  "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz",
@@ -2289,6 +2518,20 @@
2289
  "node": "^10 || ^12 || >=14"
2290
  }
2291
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2292
  "node_modules/rolldown": {
2293
  "version": "1.0.0-rc.18",
2294
  "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.18.tgz",
@@ -2323,6 +2566,35 @@
2323
  "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.18"
2324
  }
2325
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2326
  "node_modules/semver": {
2327
  "version": "5.7.2",
2328
  "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
@@ -2421,6 +2693,15 @@
2421
  "node": ">=0.10.0"
2422
  }
2423
  },
 
 
 
 
 
 
 
 
 
2424
  "node_modules/stackback": {
2425
  "version": "0.0.2",
2426
  "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
@@ -2435,6 +2716,15 @@
2435
  "dev": true,
2436
  "license": "MIT"
2437
  },
 
 
 
 
 
 
 
 
 
2438
  "node_modules/supports-color": {
2439
  "version": "7.2.0",
2440
  "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
@@ -2448,6 +2738,12 @@
2448
  "node": ">=8"
2449
  }
2450
  },
 
 
 
 
 
 
2451
  "node_modules/tinybench": {
2452
  "version": "2.9.0",
2453
  "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
@@ -2517,6 +2813,21 @@
2517
  "node": ">=8.0"
2518
  }
2519
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2520
  "node_modules/tslib": {
2521
  "version": "2.8.1",
2522
  "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
@@ -2556,6 +2867,12 @@
2556
  "node": ">= 10.0.0"
2557
  }
2558
  },
 
 
 
 
 
 
2559
  "node_modules/uuid": {
2560
  "version": "11.1.1",
2561
  "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.1.tgz",
@@ -2770,6 +3087,42 @@
2770
  "node": ">=8"
2771
  }
2772
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2773
  "node_modules/yaml": {
2774
  "version": "2.9.0",
2775
  "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz",
 
11
  "license": "ISC",
12
  "dependencies": {
13
  "@hono/node-server": "^2.0.3",
14
+ "@langchain/anthropic": "^1.3.29",
15
  "@langchain/core": "^1.1.45",
16
  "@langchain/google-genai": "^2.1.31",
17
  "@langchain/langgraph": "^1.3.0",
18
  "@langchain/openrouter": "^0.2.4",
19
+ "@solidity-parser/parser": "^0.20.2",
20
  "dotenv": "^17.4.2",
21
  "hono": "^4.12.21",
22
  "langchain": "^1.4.0",
23
  "solc": "^0.8.35",
24
  "uuid": "^11.1.1",
25
+ "winston": "^3.19.0",
26
  "zod": "^4.4.3"
27
  },
28
  "devDependencies": {
 
33
  "vitest": "^4.1.5"
34
  }
35
  },
36
+ "node_modules/@anthropic-ai/sdk": {
37
+ "version": "0.91.1",
38
+ "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.91.1.tgz",
39
+ "integrity": "sha512-LAmu761tSN9r66ixvmciswUj/ZC+1Q4iAfpedTfSVLeswRwnY3n2Nb6Tsk+cLPP28aLOPWeMgIuTuCcMC6W/iw==",
40
+ "license": "MIT",
41
+ "dependencies": {
42
+ "json-schema-to-ts": "^3.1.1"
43
+ },
44
+ "bin": {
45
+ "anthropic-ai-sdk": "bin/cli"
46
+ },
47
+ "peerDependencies": {
48
+ "zod": "^3.25.0 || ^4.0.0"
49
+ },
50
+ "peerDependenciesMeta": {
51
+ "zod": {
52
+ "optional": true
53
+ }
54
+ }
55
+ },
56
+ "node_modules/@babel/runtime": {
57
+ "version": "7.29.2",
58
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz",
59
+ "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==",
60
+ "license": "MIT",
61
+ "engines": {
62
+ "node": ">=6.9.0"
63
+ }
64
+ },
65
  "node_modules/@biomejs/biome": {
66
  "version": "2.4.14",
67
  "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.4.14.tgz",
 
231
  "integrity": "sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og==",
232
  "license": "MIT"
233
  },
234
+ "node_modules/@colors/colors": {
235
+ "version": "1.6.0",
236
+ "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz",
237
+ "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==",
238
+ "license": "MIT",
239
+ "engines": {
240
+ "node": ">=0.1.90"
241
+ }
242
+ },
243
+ "node_modules/@dabh/diagnostics": {
244
+ "version": "2.0.8",
245
+ "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.8.tgz",
246
+ "integrity": "sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q==",
247
+ "license": "MIT",
248
+ "dependencies": {
249
+ "@so-ric/colorspace": "^1.1.6",
250
+ "enabled": "2.0.x",
251
+ "kuler": "^2.0.0"
252
+ }
253
+ },
254
  "node_modules/@emnapi/core": {
255
  "version": "1.10.0",
256
  "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz",
 
313
  "dev": true,
314
  "license": "MIT"
315
  },
316
+ "node_modules/@langchain/anthropic": {
317
+ "version": "1.3.29",
318
+ "resolved": "https://registry.npmjs.org/@langchain/anthropic/-/anthropic-1.3.29.tgz",
319
+ "integrity": "sha512-ep1qBIcV07bajsg3fDqMd39rYwoRLOEK/6lk+MCxlm1YB5SRoKKJAZANrblQ/4RYhZJnxf95c6BSQu8VoNbVAQ==",
320
+ "license": "MIT",
321
+ "dependencies": {
322
+ "@anthropic-ai/sdk": "^0.91.1",
323
+ "zod": "^3.25.76 || ^4"
324
+ },
325
+ "engines": {
326
+ "node": ">=20"
327
+ },
328
+ "peerDependencies": {
329
+ "@langchain/core": "^1.1.45"
330
+ }
331
+ },
332
  "node_modules/@langchain/core": {
333
+ "version": "1.1.48",
334
+ "resolved": "https://registry.npmjs.org/@langchain/core/-/core-1.1.48.tgz",
335
+ "integrity": "sha512-fQU6Guyb1pwc2fEplmA8FPbKfOMAofjnyJzExevro0FxEiuGHE18Ov/ZHmT9trWCDTZRI9eW1VIc6aChxV8pAQ==",
336
  "license": "MIT",
337
  "dependencies": {
338
  "@cfworker/json-schema": "^4.0.2",
 
363
  }
364
  },
365
  "node_modules/@langchain/langgraph": {
366
+ "version": "1.3.2",
367
+ "resolved": "https://registry.npmjs.org/@langchain/langgraph/-/langgraph-1.3.2.tgz",
368
+ "integrity": "sha512-SL7Ktsr681R7da+1b2MVOWEbaCoFJOXEJPTGOjg4JIG4C7quWbTYC8DzxhcCxte6D/8cGp0rYDBnbKLXEpNqlA==",
369
  "license": "MIT",
370
  "dependencies": {
371
  "@langchain/langgraph-checkpoint": "^1.0.2",
372
+ "@langchain/langgraph-sdk": "~1.9.4",
373
  "@langchain/protocol": "^0.0.15",
374
  "@standard-schema/spec": "1.1.0",
375
  "uuid": "^10.0.0"
 
418
  }
419
  },
420
  "node_modules/@langchain/langgraph-sdk": {
421
+ "version": "1.9.6",
422
+ "resolved": "https://registry.npmjs.org/@langchain/langgraph-sdk/-/langgraph-sdk-1.9.6.tgz",
423
+ "integrity": "sha512-cem5LckknNULd8o4WiOpf+rv+Qxvfpf5MzXiCQb9JnEfJdCRrWsl0/qBeZRpAXMf/1Va3uDMZouTmw9odmI0Hw==",
424
  "license": "MIT",
425
  "dependencies": {
 
426
  "@langchain/protocol": "^0.0.15",
427
  "@types/json-schema": "^7.0.15",
428
  "p-queue": "^9.0.1",
 
430
  "uuid": "^13.0.0"
431
  },
432
  "peerDependencies": {
433
+ "@langchain/core": "^1.1.44",
434
  "react": "^18 || ^19",
435
  "react-dom": "^18 || ^19",
436
  "svelte": "^4.0.0 || ^5.0.0",
 
458
  "license": "MIT"
459
  },
460
  "node_modules/@langchain/langgraph-sdk/node_modules/p-queue": {
461
+ "version": "9.3.0",
462
+ "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-9.3.0.tgz",
463
+ "integrity": "sha512-7NED7xhQ74Ngp4JP/2e0VZHp7vSWfJfqeiR92jPgxsz6m0Se4P03YoTKa9dDXyZ3r6P616gUXttrB6nnHYKang==",
464
  "license": "MIT",
465
  "dependencies": {
466
  "eventemitter3": "^5.0.4",
 
845
  "dev": true,
846
  "license": "MIT"
847
  },
848
+ "node_modules/@so-ric/colorspace": {
849
+ "version": "1.1.6",
850
+ "resolved": "https://registry.npmjs.org/@so-ric/colorspace/-/colorspace-1.1.6.tgz",
851
+ "integrity": "sha512-/KiKkpHNOBgkFJwu9sh48LkHSMYGyuTcSFK/qMBdnOAlrRJzRSXAOFB5qwzaVQuDl8wAvHVMkaASQDReTahxuw==",
852
+ "license": "MIT",
853
+ "dependencies": {
854
+ "color": "^5.0.2",
855
+ "text-hex": "1.0.x"
856
+ }
857
+ },
858
+ "node_modules/@solidity-parser/parser": {
859
+ "version": "0.20.2",
860
+ "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.20.2.tgz",
861
+ "integrity": "sha512-rbu0bzwNvMcwAjH86hiEAcOeRI2EeK8zCkHDrFykh/Al8mvJeFmjy3UrE7GYQjNwOgbGUUtCn5/k8CB8zIu7QA==",
862
+ "license": "MIT"
863
+ },
864
  "node_modules/@standard-schema/spec": {
865
  "version": "1.1.0",
866
  "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
 
919
  "undici-types": "~7.19.0"
920
  }
921
  },
922
+ "node_modules/@types/triple-beam": {
923
+ "version": "1.3.5",
924
+ "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz",
925
+ "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==",
926
+ "license": "MIT"
927
+ },
928
  "node_modules/@vitest/expect": {
929
  "version": "4.1.5",
930
  "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.5.tgz",
 
1061
  "url": "https://github.com/chalk/ansi-styles?sponsor=1"
1062
  }
1063
  },
1064
+ "node_modules/ansi-styles/node_modules/color-convert": {
1065
+ "version": "2.0.1",
1066
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
1067
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
1068
+ "dev": true,
1069
+ "license": "MIT",
1070
+ "dependencies": {
1071
+ "color-name": "~1.1.4"
1072
+ },
1073
+ "engines": {
1074
+ "node": ">=7.0.0"
1075
+ }
1076
+ },
1077
+ "node_modules/ansi-styles/node_modules/color-name": {
1078
+ "version": "1.1.4",
1079
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
1080
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
1081
+ "dev": true,
1082
+ "license": "MIT"
1083
+ },
1084
  "node_modules/assertion-error": {
1085
  "version": "2.0.1",
1086
  "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
 
1091
  "node": ">=12"
1092
  }
1093
  },
1094
+ "node_modules/async": {
1095
+ "version": "3.2.6",
1096
+ "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz",
1097
+ "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==",
1098
+ "license": "MIT"
1099
+ },
1100
  "node_modules/base64-js": {
1101
  "version": "1.5.1",
1102
  "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
 
1223
  "node": ">=8"
1224
  }
1225
  },
1226
+ "node_modules/color": {
1227
+ "version": "5.0.3",
1228
+ "resolved": "https://registry.npmjs.org/color/-/color-5.0.3.tgz",
1229
+ "integrity": "sha512-ezmVcLR3xAVp8kYOm4GS45ZLLgIE6SPAFoduLr6hTDajwb3KZ2F46gulK3XpcwRFb5KKGCSezCBAY4Dw4HsyXA==",
1230
+ "license": "MIT",
1231
+ "dependencies": {
1232
+ "color-convert": "^3.1.3",
1233
+ "color-string": "^2.1.3"
1234
+ },
1235
+ "engines": {
1236
+ "node": ">=18"
1237
+ }
1238
+ },
1239
  "node_modules/color-convert": {
1240
+ "version": "3.1.3",
1241
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-3.1.3.tgz",
1242
+ "integrity": "sha512-fasDH2ont2GqF5HpyO4w0+BcewlhHEZOFn9c1ckZdHpJ56Qb7MHhH/IcJZbBGgvdtwdwNbLvxiBEdg336iA9Sg==",
 
1243
  "license": "MIT",
1244
  "dependencies": {
1245
+ "color-name": "^2.0.0"
1246
  },
1247
  "engines": {
1248
+ "node": ">=14.6"
1249
  }
1250
  },
1251
  "node_modules/color-name": {
1252
+ "version": "2.1.0",
1253
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz",
1254
+ "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==",
1255
+ "license": "MIT",
1256
+ "engines": {
1257
+ "node": ">=12.20"
1258
+ }
1259
+ },
1260
+ "node_modules/color-string": {
1261
+ "version": "2.1.4",
1262
+ "resolved": "https://registry.npmjs.org/color-string/-/color-string-2.1.4.tgz",
1263
+ "integrity": "sha512-Bb6Cq8oq0IjDOe8wJmi4JeNn763Xs9cfrBcaylK1tPypWzyoy2G3l90v9k64kjphl/ZJjPIShFztenRomi8WTg==",
1264
+ "license": "MIT",
1265
+ "dependencies": {
1266
+ "color-name": "^2.0.0"
1267
+ },
1268
+ "engines": {
1269
+ "node": ">=18"
1270
+ }
1271
  },
1272
  "node_modules/command-exists": {
1273
  "version": "1.2.9",
 
1361
  "node": ">= 0.4"
1362
  }
1363
  },
1364
+ "node_modules/enabled": {
1365
+ "version": "2.0.0",
1366
+ "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz",
1367
+ "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==",
1368
+ "license": "MIT"
1369
+ },
1370
  "node_modules/es-define-property": {
1371
  "version": "1.0.1",
1372
  "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
 
1460
  }
1461
  }
1462
  },
1463
+ "node_modules/fecha": {
1464
+ "version": "4.2.3",
1465
+ "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz",
1466
+ "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==",
1467
+ "license": "MIT"
1468
+ },
1469
  "node_modules/fill-range": {
1470
  "version": "7.1.1",
1471
  "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
 
1489
  "micromatch": "^4.0.2"
1490
  }
1491
  },
1492
+ "node_modules/fn.name": {
1493
+ "version": "1.1.0",
1494
+ "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz",
1495
+ "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==",
1496
+ "license": "MIT"
1497
+ },
1498
  "node_modules/follow-redirects": {
1499
  "version": "1.16.0",
1500
  "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz",
 
1672
  "node": ">=16.9.0"
1673
  }
1674
  },
1675
+ "node_modules/inherits": {
1676
+ "version": "2.0.4",
1677
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
1678
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
1679
+ "license": "ISC"
1680
+ },
1681
  "node_modules/is-docker": {
1682
  "version": "2.2.1",
1683
  "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz",
 
1695
  }
1696
  },
1697
  "node_modules/is-network-error": {
1698
+ "version": "1.3.2",
1699
+ "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.3.2.tgz",
1700
+ "integrity": "sha512-PhBY86zaxNZUuWP6h13Vu5oFe0XY6/UlKzQnYFELzGVHygP3MxmvTfYSG7GN3aIab/iWudSMgjSnG9Dq+nHrgA==",
1701
  "license": "MIT",
1702
  "engines": {
1703
  "node": ">=16"
 
1716
  "node": ">=0.12.0"
1717
  }
1718
  },
1719
+ "node_modules/is-stream": {
1720
+ "version": "2.0.1",
1721
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
1722
+ "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
1723
+ "license": "MIT",
1724
+ "engines": {
1725
+ "node": ">=8"
1726
+ },
1727
+ "funding": {
1728
+ "url": "https://github.com/sponsors/sindresorhus"
1729
+ }
1730
+ },
1731
  "node_modules/is-wsl": {
1732
  "version": "2.2.0",
1733
  "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
 
1770
  "base64-js": "^1.5.1"
1771
  }
1772
  },
1773
+ "node_modules/json-schema-to-ts": {
1774
+ "version": "3.1.1",
1775
+ "resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-3.1.1.tgz",
1776
+ "integrity": "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==",
1777
+ "license": "MIT",
1778
+ "dependencies": {
1779
+ "@babel/runtime": "^7.18.3",
1780
+ "ts-algebra": "^2.0.0"
1781
+ },
1782
+ "engines": {
1783
+ "node": ">=16"
1784
+ }
1785
+ },
1786
  "node_modules/json-stable-stringify": {
1787
  "version": "1.3.0",
1788
  "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.3.0.tgz",
 
1836
  "graceful-fs": "^4.1.11"
1837
  }
1838
  },
1839
+ "node_modules/kuler": {
1840
+ "version": "2.0.0",
1841
+ "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz",
1842
+ "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==",
1843
+ "license": "MIT"
1844
+ },
1845
  "node_modules/langchain": {
1846
+ "version": "1.4.2",
1847
+ "resolved": "https://registry.npmjs.org/langchain/-/langchain-1.4.2.tgz",
1848
+ "integrity": "sha512-SLGipy0r4nqQD0aiUOBYLMeGFfB/QiYnMndfZ8sGN89vXDCIXbYqcE7G/4QDDX3nZsM7/emQpoScmlxEX6sDnQ==",
1849
  "license": "MIT",
1850
  "dependencies": {
1851
+ "@langchain/langgraph": "^1.3.2",
1852
  "@langchain/langgraph-checkpoint": "^1.0.1",
1853
  "langsmith": ">=0.5.0 <1.0.0",
1854
  "zod": "^3.25.76 || ^4"
 
1857
  "node": ">=20"
1858
  },
1859
  "peerDependencies": {
1860
+ "@langchain/core": "^1.1.48"
1861
  }
1862
  },
1863
  "node_modules/langsmith": {
 
2154
  "url": "https://opencollective.com/parcel"
2155
  }
2156
  },
2157
+ "node_modules/logform": {
2158
+ "version": "2.7.0",
2159
+ "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz",
2160
+ "integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==",
2161
+ "license": "MIT",
2162
+ "dependencies": {
2163
+ "@colors/colors": "1.6.0",
2164
+ "@types/triple-beam": "^1.3.2",
2165
+ "fecha": "^4.2.0",
2166
+ "ms": "^2.1.1",
2167
+ "safe-stable-stringify": "^2.3.1",
2168
+ "triple-beam": "^1.3.0"
2169
+ },
2170
+ "engines": {
2171
+ "node": ">= 12.0.0"
2172
+ }
2173
+ },
2174
  "node_modules/magic-string": {
2175
  "version": "0.30.21",
2176
  "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
 
2236
  "url": "https://github.com/sponsors/ljharb"
2237
  }
2238
  },
2239
+ "node_modules/ms": {
2240
+ "version": "2.1.3",
2241
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
2242
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
2243
+ "license": "MIT"
2244
+ },
2245
  "node_modules/mustache": {
2246
  "version": "4.2.0",
2247
  "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz",
 
2291
  ],
2292
  "license": "MIT"
2293
  },
2294
+ "node_modules/one-time": {
2295
+ "version": "1.0.0",
2296
+ "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz",
2297
+ "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==",
2298
+ "license": "MIT",
2299
+ "dependencies": {
2300
+ "fn.name": "1.x.x"
2301
+ }
2302
+ },
2303
  "node_modules/open": {
2304
  "version": "7.4.2",
2305
  "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz",
 
2518
  "node": "^10 || ^12 || >=14"
2519
  }
2520
  },
2521
+ "node_modules/readable-stream": {
2522
+ "version": "3.6.2",
2523
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
2524
+ "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
2525
+ "license": "MIT",
2526
+ "dependencies": {
2527
+ "inherits": "^2.0.3",
2528
+ "string_decoder": "^1.1.1",
2529
+ "util-deprecate": "^1.0.1"
2530
+ },
2531
+ "engines": {
2532
+ "node": ">= 6"
2533
+ }
2534
+ },
2535
  "node_modules/rolldown": {
2536
  "version": "1.0.0-rc.18",
2537
  "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.18.tgz",
 
2566
  "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.18"
2567
  }
2568
  },
2569
+ "node_modules/safe-buffer": {
2570
+ "version": "5.2.1",
2571
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
2572
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
2573
+ "funding": [
2574
+ {
2575
+ "type": "github",
2576
+ "url": "https://github.com/sponsors/feross"
2577
+ },
2578
+ {
2579
+ "type": "patreon",
2580
+ "url": "https://www.patreon.com/feross"
2581
+ },
2582
+ {
2583
+ "type": "consulting",
2584
+ "url": "https://feross.org/support"
2585
+ }
2586
+ ],
2587
+ "license": "MIT"
2588
+ },
2589
+ "node_modules/safe-stable-stringify": {
2590
+ "version": "2.5.0",
2591
+ "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz",
2592
+ "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==",
2593
+ "license": "MIT",
2594
+ "engines": {
2595
+ "node": ">=10"
2596
+ }
2597
+ },
2598
  "node_modules/semver": {
2599
  "version": "5.7.2",
2600
  "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
 
2693
  "node": ">=0.10.0"
2694
  }
2695
  },
2696
+ "node_modules/stack-trace": {
2697
+ "version": "0.0.10",
2698
+ "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz",
2699
+ "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==",
2700
+ "license": "MIT",
2701
+ "engines": {
2702
+ "node": "*"
2703
+ }
2704
+ },
2705
  "node_modules/stackback": {
2706
  "version": "0.0.2",
2707
  "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
 
2716
  "dev": true,
2717
  "license": "MIT"
2718
  },
2719
+ "node_modules/string_decoder": {
2720
+ "version": "1.3.0",
2721
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
2722
+ "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
2723
+ "license": "MIT",
2724
+ "dependencies": {
2725
+ "safe-buffer": "~5.2.0"
2726
+ }
2727
+ },
2728
  "node_modules/supports-color": {
2729
  "version": "7.2.0",
2730
  "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
 
2738
  "node": ">=8"
2739
  }
2740
  },
2741
+ "node_modules/text-hex": {
2742
+ "version": "1.0.0",
2743
+ "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz",
2744
+ "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==",
2745
+ "license": "MIT"
2746
+ },
2747
  "node_modules/tinybench": {
2748
  "version": "2.9.0",
2749
  "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
 
2813
  "node": ">=8.0"
2814
  }
2815
  },
2816
+ "node_modules/triple-beam": {
2817
+ "version": "1.4.1",
2818
+ "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz",
2819
+ "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==",
2820
+ "license": "MIT",
2821
+ "engines": {
2822
+ "node": ">= 14.0.0"
2823
+ }
2824
+ },
2825
+ "node_modules/ts-algebra": {
2826
+ "version": "2.0.0",
2827
+ "resolved": "https://registry.npmjs.org/ts-algebra/-/ts-algebra-2.0.0.tgz",
2828
+ "integrity": "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==",
2829
+ "license": "MIT"
2830
+ },
2831
  "node_modules/tslib": {
2832
  "version": "2.8.1",
2833
  "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
 
2867
  "node": ">= 10.0.0"
2868
  }
2869
  },
2870
+ "node_modules/util-deprecate": {
2871
+ "version": "1.0.2",
2872
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
2873
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
2874
+ "license": "MIT"
2875
+ },
2876
  "node_modules/uuid": {
2877
  "version": "11.1.1",
2878
  "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.1.tgz",
 
3087
  "node": ">=8"
3088
  }
3089
  },
3090
+ "node_modules/winston": {
3091
+ "version": "3.19.0",
3092
+ "resolved": "https://registry.npmjs.org/winston/-/winston-3.19.0.tgz",
3093
+ "integrity": "sha512-LZNJgPzfKR+/J3cHkxcpHKpKKvGfDZVPS4hfJCc4cCG0CgYzvlD6yE/S3CIL/Yt91ak327YCpiF/0MyeZHEHKA==",
3094
+ "license": "MIT",
3095
+ "dependencies": {
3096
+ "@colors/colors": "^1.6.0",
3097
+ "@dabh/diagnostics": "^2.0.8",
3098
+ "async": "^3.2.3",
3099
+ "is-stream": "^2.0.0",
3100
+ "logform": "^2.7.0",
3101
+ "one-time": "^1.0.0",
3102
+ "readable-stream": "^3.4.0",
3103
+ "safe-stable-stringify": "^2.3.1",
3104
+ "stack-trace": "0.0.x",
3105
+ "triple-beam": "^1.3.0",
3106
+ "winston-transport": "^4.9.0"
3107
+ },
3108
+ "engines": {
3109
+ "node": ">= 12.0.0"
3110
+ }
3111
+ },
3112
+ "node_modules/winston-transport": {
3113
+ "version": "4.9.0",
3114
+ "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz",
3115
+ "integrity": "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==",
3116
+ "license": "MIT",
3117
+ "dependencies": {
3118
+ "logform": "^2.7.0",
3119
+ "readable-stream": "^3.6.2",
3120
+ "triple-beam": "^1.3.0"
3121
+ },
3122
+ "engines": {
3123
+ "node": ">= 12.0.0"
3124
+ }
3125
+ },
3126
  "node_modules/yaml": {
3127
  "version": "2.9.0",
3128
  "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz",
package.json CHANGED
@@ -32,15 +32,18 @@
32
  },
33
  "dependencies": {
34
  "@hono/node-server": "^2.0.3",
 
35
  "@langchain/core": "^1.1.45",
36
  "@langchain/google-genai": "^2.1.31",
37
  "@langchain/langgraph": "^1.3.0",
38
  "@langchain/openrouter": "^0.2.4",
 
39
  "dotenv": "^17.4.2",
40
  "hono": "^4.12.21",
41
  "langchain": "^1.4.0",
42
  "solc": "^0.8.35",
43
  "uuid": "^11.1.1",
 
44
  "zod": "^4.4.3"
45
  }
46
  }
 
32
  },
33
  "dependencies": {
34
  "@hono/node-server": "^2.0.3",
35
+ "@langchain/anthropic": "^1.3.29",
36
  "@langchain/core": "^1.1.45",
37
  "@langchain/google-genai": "^2.1.31",
38
  "@langchain/langgraph": "^1.3.0",
39
  "@langchain/openrouter": "^0.2.4",
40
+ "@solidity-parser/parser": "^0.20.2",
41
  "dotenv": "^17.4.2",
42
  "hono": "^4.12.21",
43
  "langchain": "^1.4.0",
44
  "solc": "^0.8.35",
45
  "uuid": "^11.1.1",
46
+ "winston": "^3.19.0",
47
  "zod": "^4.4.3"
48
  }
49
  }
src/agents/auditor/agent.ts CHANGED
@@ -1,20 +1,271 @@
 
 
 
 
1
  import { END, type GraphNode, START, StateGraph } from "@langchain/langgraph";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
 
3
- import { AuditorState } from "./state.ts";
4
- import { slitherTool } from "./tools/slither-tool.ts";
5
 
6
- const PLACEHOLDER_VULNERABILITIES = [
7
- { type: "reentrancy", severity: "high", description: "Unchecked external call allows reentrancy attack." },
8
- { type: "integer-overflow", severity: "medium", description: "Arithmetic operation may overflow." },
9
- ];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
 
11
- const auditContract: GraphNode<typeof AuditorState> = async (state) => {
12
- await slitherTool.invoke({ solidityFile: state.solidityFile });
13
- return { vulnerabilities: PLACEHOLDER_VULNERABILITIES };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  };
15
 
16
  export const auditorAgent = new StateGraph(AuditorState)
17
- .addNode("auditContract", auditContract)
18
- .addEdge(START, "auditContract")
19
- .addEdge("auditContract", END)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  .compile();
 
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+
4
+ import { HumanMessage, SystemMessage } from "@langchain/core/messages";
5
  import { END, type GraphNode, START, StateGraph } from "@langchain/langgraph";
6
+ import { z } from "zod";
7
+
8
+ import { logger } from "../../logger.ts";
9
+ import { createLLM } from "../../config/llm.ts";
10
+ import { JUDGE_FINDINGS_PROMPT, FIND_VULNERABILITIES_PROMPT, GATHER_CONTEXT_PROMPT } from "./prompts.ts";
11
+ import { AuditorState, JudgeReviewSchema, CandidateFindingSchema } from "./state.ts";
12
+ import { analyzeSolidityFile } from "./tools/solidity-analyzer-tool.ts";
13
+ import { buildRepoTree } from "./tools/repo-tree-tool.ts";
14
+ import {
15
+ DOC_BASENAMES,
16
+ DOC_EXTS,
17
+ MAX_DEPTH,
18
+ MAX_DOC_CHARS,
19
+ MAX_REFLECTIONS,
20
+ MAX_SOL_CHARS,
21
+ SKIP_DIRS,
22
+ SOL_EXT,
23
+ SOL_TEST_SUFFIXES,
24
+ } from "./config.ts";
25
+ import { matchLines } from "./utils.ts";
26
+
27
+ const llm = createLLM();
28
+
29
+ const walkDirectory = (dir: string, depth: number, solFiles: string[], docFiles: string[]) => {
30
+ if (depth > MAX_DEPTH) return;
31
+
32
+ let entries: fs.Dirent[];
33
+ try {
34
+ entries = fs.readdirSync(dir, { withFileTypes: true });
35
+ } catch {
36
+ return;
37
+ }
38
+
39
+ for (const entry of entries) {
40
+ if (entry.isDirectory()) {
41
+ if (!SKIP_DIRS.has(entry.name)) {
42
+ walkDirectory(path.join(dir, entry.name), depth + 1, solFiles, docFiles);
43
+ }
44
+ } else if (entry.isFile()) {
45
+ const fullPath = path.join(dir, entry.name);
46
+ const ext = path.extname(entry.name).toLowerCase();
47
+ const base = path.basename(entry.name, ext).toLowerCase();
48
+
49
+ if (ext === SOL_EXT) {
50
+ const isTest = SOL_TEST_SUFFIXES.some((suffix) => entry.name.endsWith(suffix));
51
+ if (!isTest) solFiles.push(fullPath);
52
+ } else if (DOC_EXTS.has(ext) || DOC_BASENAMES.has(base)) {
53
+ docFiles.push(fullPath);
54
+ }
55
+ }
56
+ }
57
+ };
58
+
59
+ const defineScope: GraphNode<typeof AuditorState> = async (state) => {
60
+ logger.info(`defineScope: walking repo at ${state.repoPath}`);
61
+
62
+ const solFiles: string[] = [];
63
+ const docFiles: string[] = [];
64
+
65
+ walkDirectory(state.repoPath, 0, solFiles, docFiles);
66
+
67
+ const fileTree = buildRepoTree(state.repoPath);
68
+
69
+ logger.info(`defineScope: found ${solFiles.length} Solidity file(s), ${docFiles.length} doc file(s)`);
70
+ logger.debug(`defineScope: Solidity files: ${JSON.stringify(solFiles)}`);
71
+ logger.debug(`defineScope: doc files: ${JSON.stringify(docFiles)}`);
72
+ logger.debug(`defineScope: file tree:\n${fileTree}`);
73
+
74
+ return { scope: solFiles, docs: docFiles, fileTree };
75
+ };
76
+
77
+ const gatherContext: GraphNode<typeof AuditorState> = async (state) => {
78
+ logger.info(`gatherContext: processing ${state.scope.length} Solidity file(s) and ${state.docs.length} doc file(s)`);
79
+
80
+ const readFile = (filePath: string): string => {
81
+ try {
82
+ return fs.readFileSync(filePath, "utf-8");
83
+ } catch {
84
+ return "";
85
+ }
86
+ };
87
+
88
+ // Read and analyze each Solidity file
89
+ const solidityEntries: { filePath: string; source: string; analysis: string }[] = [];
90
+ for (const filePath of state.scope) {
91
+ const source = readFile(filePath).slice(0, MAX_SOL_CHARS);
92
+ if (!source) continue;
93
+ const analysis = await analyzeSolidityFile(source, "full");
94
+ solidityEntries.push({ filePath, source, analysis });
95
+ }
96
+
97
+ // Read documentation files
98
+ const docEntries: { filePath: string; content: string }[] = [];
99
+ for (const filePath of state.docs) {
100
+ const content = readFile(filePath).slice(0, MAX_DOC_CHARS);
101
+ if (content) docEntries.push({ filePath, content });
102
+ }
103
+
104
+ // Build the LLM input
105
+ const parts: string[] = [];
106
+
107
+ if (docEntries.length > 0) {
108
+ parts.push("## Documentation\n");
109
+ for (const { filePath, content } of docEntries) {
110
+ parts.push(`### ${filePath}\n${content}`);
111
+ }
112
+ }
113
+
114
+ parts.push("## Structural Analysis (auto-generated)\n");
115
+ for (const { filePath, analysis } of solidityEntries) {
116
+ parts.push(`### ${filePath}\n${analysis}`);
117
+ }
118
+
119
+ parts.push("## Contract Source Code\n");
120
+ for (const { filePath, source } of solidityEntries) {
121
+ parts.push(`### ${filePath}\n\`\`\`solidity\n${source}\n\`\`\``);
122
+ }
123
+
124
+ const model = llm.withStructuredOutput(z.object({ context: z.string() }));
125
+ const result = await model.invoke([new SystemMessage(GATHER_CONTEXT_PROMPT), new HumanMessage(parts.join("\n\n"))]);
126
 
127
+ logger.info(`gatherContext: context built (${parts.join("\n\n").length} chars)`);
128
+ logger.debug(`gatherContext: full context:\n${parts.join("\n\n")}`);
129
 
130
+ return { repoContext: result.context };
131
+ };
132
+
133
+ const findVulnerabilities: GraphNode<typeof AuditorState> = async (state) => {
134
+ const model = llm.withStructuredOutput(z.object({ findings: z.array(CandidateFindingSchema) }));
135
+
136
+ const previousFeedback =
137
+ state.judgeReviews.length > 0
138
+ ? state.judgeReviews
139
+ .map((r, i) => {
140
+ const title = state.candidateFindings[i]?.title ?? `Finding ${i + 1}`;
141
+ return `- "${title}": ${r.isFalsePositive ? "FALSE POSITIVE" : "TRUE POSITIVE"}\n Judge: ${r.review}`;
142
+ })
143
+ .join("\n")
144
+ : null;
145
+
146
+ logger.info(
147
+ `findVulnerabilities: invoking LLM for ${state.scope.length} file(s) in parallel (iteration ${state.reflectionCount + 1})`,
148
+ );
149
+
150
+ const allFindings = await Promise.all(
151
+ state.scope.map(async (filePath) => {
152
+ let source: string;
153
+ try {
154
+ source = fs.readFileSync(filePath, "utf-8").slice(0, MAX_SOL_CHARS);
155
+ } catch {
156
+ return [];
157
+ }
158
+ if (!source) return [];
159
+
160
+ let userMessage = `Contract (${filePath}):\n\n${source}\n\nProtocol Context:\n${state.repoContext}`;
161
+ if (previousFeedback) {
162
+ userMessage += `\n\nJudge feedback from previous iteration (iteration ${state.reflectionCount}):\n${previousFeedback}\n\nRevise your findings accordingly.`;
163
+ }
164
+
165
+ logger.debug(`findVulnerabilities: processing ${filePath}`);
166
+
167
+ const result = await model.invoke([
168
+ new SystemMessage(FIND_VULNERABILITIES_PROMPT),
169
+ new HumanMessage(userMessage),
170
+ ]);
171
+
172
+ return result.findings.map((finding: any) => ({
173
+ ...finding,
174
+ path: filePath,
175
+ location: matchLines(source, finding.codeSnippet) ?? "",
176
+ }));
177
+ }),
178
+ );
179
+
180
+ const candidateFindings = allFindings.flat();
181
+ logger.info(`findVulnerabilities: LLM returned ${candidateFindings.length} total candidate finding(s)`);
182
+ logger.debug(`findVulnerabilities: findings:\n${JSON.stringify(candidateFindings, null, 2)}`);
183
+
184
+ return { candidateFindings };
185
+ };
186
 
187
+ const judgeFindings: GraphNode<typeof AuditorState> = async (state) => {
188
+ if (state.candidateFindings.length === 0) {
189
+ logger.info("judgeFindings: no candidate findings to review, skipping LLM call");
190
+ return {
191
+ judgeReviews: [],
192
+ findings: [],
193
+ reflectionCount: state.reflectionCount + 1,
194
+ };
195
+ }
196
+
197
+ const model = llm.withStructuredOutput(JudgeReviewSchema);
198
+
199
+ logger.info(`judgeFindings: reviewing ${state.candidateFindings.length} candidate finding(s) in parallel`);
200
+
201
+ const reviews = await Promise.all(
202
+ state.candidateFindings.map(async (finding, i) => {
203
+ let source: string;
204
+ try {
205
+ source = fs.readFileSync(finding.path, "utf-8").slice(0, MAX_SOL_CHARS);
206
+ } catch {
207
+ source = "";
208
+ }
209
+
210
+ const findingText = `[Finding ${i + 1}] ${finding.title}\nSeverity: ${finding.severity}\nDescription: ${finding.description}\nLocation: ${finding.path} lines ${finding.location}\nCode:\n\`\`\`solidity\n${finding.codeSnippet}\n\`\`\``;
211
+
212
+ logger.debug(`judgeFindings: reviewing finding ${i + 1}: ${finding.title}`);
213
+ return model.invoke([
214
+ new SystemMessage(JUDGE_FINDINGS_PROMPT),
215
+ new HumanMessage(
216
+ `Contract (${finding.path}):\n\n${source}\n\nProtocol Context:\n${state.repoContext}\n\nFinding to Review:\n\n${findingText}`,
217
+ ),
218
+ ]);
219
+ }),
220
+ );
221
+
222
+ const confirmedEntries = state.candidateFindings
223
+ .map((finding, i) => ({ finding, review: reviews[i] }))
224
+ .filter(({ review }) => !review.isFalsePositive);
225
+
226
+ const findings = confirmedEntries.map(({ finding, review }) => ({
227
+ ...finding,
228
+ judgeReview: {
229
+ review: review.review,
230
+ confidence: review.confidence,
231
+ exploitablePaths: review.exploitablePaths,
232
+ },
233
+ }));
234
+
235
+ const falsePositiveCount = state.candidateFindings.length - findings.length;
236
+
237
+ logger.info(`judgeFindings: ${findings.length} confirmed, ${falsePositiveCount} false positive(s)`);
238
+ logger.debug(`judgeFindings: reviews:\n${JSON.stringify(reviews, null, 2)}`);
239
+
240
+ return {
241
+ judgeReviews: reviews,
242
+ findings,
243
+ reflectionCount: state.reflectionCount + 1,
244
+ };
245
  };
246
 
247
  export const auditorAgent = new StateGraph(AuditorState)
248
+ .addNode("defineScope", defineScope)
249
+ .addNode("gatherContext", gatherContext)
250
+ .addNode("findVulnerabilities", findVulnerabilities)
251
+ .addNode("judgeFindings", judgeFindings)
252
+ .addEdge(START, "defineScope")
253
+ .addEdge("defineScope", "gatherContext")
254
+ .addEdge("gatherContext", "findVulnerabilities")
255
+ .addEdge("findVulnerabilities", "judgeFindings")
256
+ .addConditionalEdges("judgeFindings", (state) => {
257
+ const hasFalsePositives = state.judgeReviews.some((r) => r.isFalsePositive);
258
+ if (hasFalsePositives && state.reflectionCount < MAX_REFLECTIONS) {
259
+ return "findVulnerabilities";
260
+ }
261
+ return END;
262
+ })
263
+ .compile();
264
+
265
+ export const testAgent = new StateGraph(AuditorState)
266
+ .addNode("defineScope", defineScope)
267
+ .addNode("gatherContext", gatherContext)
268
+ .addEdge(START, "defineScope")
269
+ .addEdge("defineScope", "gatherContext")
270
+ .addEdge("gatherContext", END)
271
  .compile();
src/agents/auditor/config.ts ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export const SOL_EXT = ".sol";
2
+ export const SOL_TEST_SUFFIXES = [".t.sol", ".test.sol", ".spec.sol"];
3
+ export const DOC_EXTS = new Set([".md", ".rst", ".adoc"]);
4
+ export const DOC_BASENAMES = new Set(["readme", "whitepaper", "spec", "architecture", "design", "overview", "docs"]);
5
+ export const SKIP_DIRS = new Set([
6
+ "node_modules",
7
+ ".git",
8
+ "out",
9
+ "artifacts",
10
+ "cache",
11
+ "lib",
12
+ ".deps",
13
+ "build",
14
+ "dist",
15
+ "test",
16
+ "tests",
17
+ "script",
18
+ "scripts",
19
+ ]);
20
+ export const MAX_DEPTH = 6;
21
+ export const MAX_DOC_CHARS = 12_000;
22
+ export const MAX_SOL_CHARS = 40_000;
23
+ export const MAX_REFLECTIONS = 1;
src/agents/auditor/model.ts DELETED
@@ -1,5 +0,0 @@
1
- import { ChatOpenRouter } from "@langchain/openrouter";
2
-
3
- export const auditorModel = new ChatOpenRouter({
4
- model: "moonshotai/kimi-k2.6",
5
- });
 
 
 
 
 
 
src/agents/auditor/prompts.ts ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export const GATHER_CONTEXT_PROMPT = `Você é um especialista em segurança de smart contracts. Você receberá documentação, uma análise estrutural e o código-fonte completo de todos os contratos Solidity em escopo. Produza um contexto detalhado do protocolo que guiará a descoberta de vulnerabilidades.
2
+
3
+ Estruture sua resposta nas seguintes seções:
4
+
5
+ ## 1. Visão Geral dos Contratos
6
+ Para cada contrato: seu propósito, tipo (contract/interface/library/abstract), cadeia de herança e principais dependências de outros contratos em escopo ou protocolos externos.
7
+
8
+ ## 2. Mapa de Estado e Armazenamento
9
+ Liste todas as variáveis de estado relevantes entre os contratos, o que representam e quais funções as leem ou escrevem. Sinalize armazenamento compartilhado ou herdado.
10
+
11
+ ## 3. Fluxos Principais
12
+ Trace os principais caminhos de execução e transições de estado de ponta a ponta entre contratos (ex.: depósito → cunhar shares → atualizar recompensas; saque → queimar shares → transferir ETH). Inclua chamadas entre contratos.
13
+
14
+ ## 4. Invariantes
15
+ Condições que devem sempre ser verdadeiras (ex.: "o supply total deve ser igual à soma de todos os saldos", "o saldo de ETH do contrato ≥ soma de todos os depósitos dos usuários"). Derive-as tanto do código-fonte quanto da documentação.
16
+
17
+ ## 5. Premissas de Design
18
+ O que o protocolo assume sobre chamadores, contratos externos, oráculos, chaves de administrador e comportamento de tokens (ex.: "tokens são compatíveis com ERC-20", "o admin é confiável", "sem tokens com taxa de transferência").
19
+
20
+ ## 6. Regras de Negócio
21
+ Controles de acesso, estruturas de taxas, timelocks, limites, mecanismos de pausa, padrões de atualização e quaisquer outras restrições de domínio.
22
+
23
+ Seja preciso e exaustivo — quanto mais rico o contexto, com mais precisão as vulnerabilidades podem ser identificadas e validadas.`;
24
+
25
+ export const FIND_VULNERABILITIES_PROMPT = `Você é um auditor especialista em segurança de smart contracts com foco em Solidity. Analise sistematicamente o código-fonte do contrato e o contexto do protocolo para identificar vulnerabilidades de segurança.
26
+
27
+ Para cada vulnerabilidade, forneça TODOS os seguintes campos:
28
+
29
+ - **title**: Nome curto e preciso (ex.: "Reentrância em withdraw", "Controle de acesso ausente em setFee").
30
+ - **description**: Explique o comportamento ESPERADO versus o comportamento OBSERVADO (vulnerável) em 2 a 4 frases.
31
+ - **recommendation**: Correção específica e acionável (ex.: "Aplicar o padrão checks-effects-interactions", "Adicionar o modificador onlyOwner").
32
+ - **severity**: Um de "high" (perda direta de fundos ou tomada de controle do contrato), "medium" (risco indireto ou condicional), "low" (problema de boas práticas, sem risco financeiro imediato).
33
+ - **codeSnippet**: O bloco de código vulnerável exatamente como aparece no código-fonte.
34
+
35
+ Categorias de vulnerabilidades a verificar sistematicamente: reentrância (simples e entre funções), controle de acesso, overflow/underflow de inteiros, manipulação de oráculo, ataques de flash loan, front-running/MEV, replay de assinatura, colisões de armazenamento, proxies não inicializados, delegatecall inseguro, griefing de gas, negação de serviço, perda de precisão e violações de lógica/regras de negócio.
36
+
37
+ Se feedback do juiz de uma iteração anterior for fornecido, remova os falsos positivos confirmados da sua lista e refine ou expanda os achados restantes com base na crítica.`;
38
+
39
+ export const JUDGE_FINDINGS_PROMPT = `Você é um revisor rigoroso de segurança de smart contracts. Avalie cada vulnerabilidade candidata submetida pelo auditor e determine se é um verdadeiro positivo ou um falso positivo.
40
+
41
+ Para cada achado, forneça TODOS os seguintes campos:
42
+
43
+ - **review**: Análise detalhada (3 a 6 frases) explicando por que a vulnerabilidade é ou não real. Referencie código específico, invariantes do protocolo, pré-condições e controles mitigadores.
44
+ - **isFalsePositive**: true se o achado NÃO for explorável na prática; false se for uma vulnerabilidade real.
45
+ - **confidence**: Número inteiro de 0 a 100 refletindo sua confiança no veredicto.
46
+ - **exploitablePaths**: Array de strings. Se for verdadeiro positivo, forneça caminhos concretos confirmando a explorabilidade com valores reais. Cada rastreamento deve descrever os passos do atacante com entradas/valores realistas (ex.: "1. Atacante chama deposit(100 ETH) 2. Contrato do atacante no fallback chama withdraw() novamente antes da atualização do saldo 3. Atacante drena 100 ETH duas vezes"). Se for falso positivo, forneça o raciocínio que bloqueia o exploit.
47
+
48
+ Um achado é falso positivo somente se: o caminho de exploit for inacessível dados os controles de acesso ou pré-condições, já estiver totalmente mitigado pelo código, exigir condições impossíveis ou economicamente inviáveis, ou for explicitamente documentado como comportamento esperado nas premissas do protocolo.
49
+
50
+ Você deve fornecer exatamente um objeto de revisão por achado, na mesma ordem em que os achados foram apresentados.`;
src/agents/auditor/state.ts CHANGED
@@ -1,7 +1,42 @@
1
  import { StateSchema } from "@langchain/langgraph";
2
  import { z } from "zod";
3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  export const AuditorState = new StateSchema({
5
- solidityFile: z.string().default(""),
6
- vulnerabilities: z.array(z.record(z.string(), z.any())).default([]),
 
 
 
 
 
 
 
7
  });
 
1
  import { StateSchema } from "@langchain/langgraph";
2
  import { z } from "zod";
3
 
4
+ export const CandidateFindingSchema = z.object({
5
+ title: z.string(),
6
+ description: z.string(),
7
+ recommendation: z.string(),
8
+ severity: z.enum(["high", "medium", "low"]),
9
+ codeSnippet: z.string(),
10
+ });
11
+
12
+ export const LocatedFindingSchema = CandidateFindingSchema.extend({
13
+ location: z.string(),
14
+ path: z.string(),
15
+ });
16
+
17
+ export const JudgeReviewSchema = z.object({
18
+ review: z.string(),
19
+ isFalsePositive: z.boolean(),
20
+ confidence: z.number(),
21
+ exploitablePaths: z.array(z.string()),
22
+ });
23
+
24
+ export const FindingSchema = LocatedFindingSchema.extend({
25
+ judgeReview: z.object({
26
+ review: z.string(),
27
+ confidence: z.number(),
28
+ exploitablePaths: z.array(z.string()),
29
+ }),
30
+ });
31
+
32
  export const AuditorState = new StateSchema({
33
+ repoPath: z.string().default(""),
34
+ scope: z.array(z.string()).default([]),
35
+ docs: z.array(z.string()).default([]),
36
+ fileTree: z.string().default(""),
37
+ repoContext: z.string().default(""),
38
+ candidateFindings: z.array(LocatedFindingSchema).default([]),
39
+ judgeReviews: z.array(JudgeReviewSchema).default([]),
40
+ findings: z.array(FindingSchema).default([]),
41
+ reflectionCount: z.number().default(0),
42
  });
src/agents/auditor/tools/repo-tree-tool.ts ADDED
@@ -0,0 +1,103 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+
4
+ import { tool } from "langchain";
5
+ import { z } from "zod";
6
+
7
+ import { DOC_BASENAMES, DOC_EXTS, MAX_DEPTH, SKIP_DIRS, SOL_EXT, SOL_TEST_SUFFIXES } from "../config.ts";
8
+
9
+ const CONFIG_FILES = new Set([
10
+ "foundry.toml",
11
+ "hardhat.config.js",
12
+ "hardhat.config.ts",
13
+ "remappings.txt",
14
+ "package.json",
15
+ ]);
16
+
17
+ interface TreeNode {
18
+ name: string;
19
+ isDir: boolean;
20
+ children?: TreeNode[];
21
+ tag?: string;
22
+ }
23
+
24
+ const buildTree = (dir: string, depth: number): TreeNode[] => {
25
+ if (depth > MAX_DEPTH) return [];
26
+
27
+ let entries: fs.Dirent[];
28
+ try {
29
+ entries = fs.readdirSync(dir, { withFileTypes: true });
30
+ } catch {
31
+ return [];
32
+ }
33
+
34
+ const nodes: TreeNode[] = [];
35
+
36
+ for (const entry of [...entries].sort((a, b) => {
37
+ if (a.isDirectory() && !b.isDirectory()) return -1;
38
+ if (!a.isDirectory() && b.isDirectory()) return 1;
39
+ return a.name.localeCompare(b.name);
40
+ })) {
41
+ if (entry.isDirectory()) {
42
+ if (SKIP_DIRS.has(entry.name)) continue;
43
+ const children = buildTree(path.join(dir, entry.name), depth + 1);
44
+ if (children.length > 0) nodes.push({ name: entry.name, isDir: true, children });
45
+ } else if (entry.isFile()) {
46
+ const ext = path.extname(entry.name).toLowerCase();
47
+ const base = path.basename(entry.name, ext).toLowerCase();
48
+
49
+ if (ext === SOL_EXT) {
50
+ const isTest = SOL_TEST_SUFFIXES.some((suffix) => entry.name.endsWith(suffix));
51
+ nodes.push({ name: entry.name, isDir: false, tag: isTest ? "[test]" : "[sol]" });
52
+ } else if (DOC_EXTS.has(ext) || DOC_BASENAMES.has(base)) {
53
+ nodes.push({ name: entry.name, isDir: false, tag: "[doc]" });
54
+ } else if (CONFIG_FILES.has(entry.name)) {
55
+ nodes.push({ name: entry.name, isDir: false, tag: "[config]" });
56
+ }
57
+ }
58
+ }
59
+
60
+ return nodes;
61
+ };
62
+
63
+ const renderTree = (nodes: TreeNode[], prefix: string): string => {
64
+ const lines: string[] = [];
65
+
66
+ for (let i = 0; i < nodes.length; i++) {
67
+ const node = nodes[i];
68
+ const isLast = i === nodes.length - 1;
69
+ const connector = isLast ? "└── " : "├── ";
70
+ const childPrefix = isLast ? " " : "│ ";
71
+
72
+ if (node.isDir) {
73
+ lines.push(`${prefix}${connector}${node.name}/`);
74
+ if (node.children && node.children.length > 0) {
75
+ lines.push(renderTree(node.children, prefix + childPrefix));
76
+ }
77
+ } else {
78
+ lines.push(`${prefix}${connector}${node.name} ${node.tag}`);
79
+ }
80
+ }
81
+
82
+ return lines.join("\n");
83
+ };
84
+
85
+ export const buildRepoTree = (repoPath: string): string => {
86
+ const nodes = buildTree(repoPath, 0);
87
+ if (nodes.length === 0) return "(no relevant files found)";
88
+
89
+ const repoName = path.basename(repoPath);
90
+ return `${repoName}/\n${renderTree(nodes, "")}`;
91
+ };
92
+
93
+ export const repoTreeTool = tool(
94
+ async ({ repoPath }) => buildRepoTree(repoPath),
95
+ {
96
+ name: "repo_tree",
97
+ description:
98
+ "Walk a repository and return a file-system tree of relevant files tagged by kind: [sol] for auditable Solidity contracts, [test] for Solidity test files, [doc] for documentation, and [config] for project config files. Use this during Define Scope to understand repository layout before selecting which files to audit.",
99
+ schema: z.object({
100
+ repoPath: z.string().describe("Absolute path to the repository root."),
101
+ }),
102
+ },
103
+ );
src/agents/auditor/tools/slither-tool.ts DELETED
@@ -1,15 +0,0 @@
1
- import { tool } from "langchain";
2
- import { z } from "zod";
3
-
4
- export const slitherTool = tool(
5
- async (_input) => {
6
- return [];
7
- },
8
- {
9
- name: "slither",
10
- description: "Run slither static analysis on a Solidity contract.",
11
- schema: z.object({
12
- solidityFile: z.string().describe("The Solidity source code to analyze."),
13
- }),
14
- },
15
- );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/agents/auditor/tools/solidity-analyzer-tool.ts ADDED
@@ -0,0 +1,567 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { parse, visit } from "@solidity-parser/parser";
2
+ import { tool } from "langchain";
3
+ import { z } from "zod";
4
+
5
+ const ASSIGNMENT_OPS = new Set(["=", "+=", "-=", "*=", "/=", "%=", "|=", "&=", "^=", "<<=", ">>=", "**="]);
6
+ const BUILTIN_NAMESPACES = new Set(["abi", "block", "msg", "tx", "bytes", "string", "type"]);
7
+
8
+ interface StateVar {
9
+ name: string;
10
+ type: string;
11
+ visibility: string;
12
+ constant: boolean;
13
+ immutable: boolean;
14
+ }
15
+
16
+ interface EventDef {
17
+ name: string;
18
+ params: string[];
19
+ anonymous: boolean;
20
+ }
21
+
22
+ interface ModifierDef {
23
+ name: string;
24
+ params: string[];
25
+ }
26
+
27
+ interface FunctionDef {
28
+ name: string;
29
+ isConstructor: boolean;
30
+ isReceive: boolean;
31
+ isFallback: boolean;
32
+ visibility: string;
33
+ mutability: string;
34
+ params: string[];
35
+ returns: string[];
36
+ modifiers: string[];
37
+ internalCalls: string[];
38
+ externalCalls: string[];
39
+ stateReads: string[];
40
+ stateWrites: string[];
41
+ }
42
+
43
+ interface ContractAnalysis {
44
+ name: string;
45
+ kind: string;
46
+ baseContracts: string[];
47
+ usingFor: string[];
48
+ stateVars: StateVar[];
49
+ events: EventDef[];
50
+ modifiers: ModifierDef[];
51
+ functions: FunctionDef[];
52
+ }
53
+
54
+ const typeToString = (node: any): string => {
55
+ if (!node) return "unknown";
56
+
57
+ switch (node.type) {
58
+ case "ElementaryTypeName":
59
+ return node.name as string;
60
+ case "UserDefinedTypeName":
61
+ return (node.namePath ?? node.name) as string;
62
+ case "ArrayTypeName":
63
+ return `${typeToString(node.baseTypeName)}[${node.length ?? ""}]`;
64
+ case "Mapping":
65
+ return `mapping(${typeToString(node.keyType)} => ${typeToString(node.valueType)})`;
66
+ case "FunctionTypeName":
67
+ return "function";
68
+ default:
69
+ return "unknown";
70
+ }
71
+ };
72
+
73
+ const paramToString = (p: any) => {
74
+ if (!p) return "?";
75
+ const type = typeToString(p.typeName);
76
+ return p.name ? `${type} ${p.name}` : type;
77
+ };
78
+
79
+ const collectLHSRoots = (node: any, targets: Set<string>) => {
80
+ if (!node) return;
81
+ switch (node.type) {
82
+ case "Identifier":
83
+ targets.add(node.name as string);
84
+ break;
85
+ case "MemberAccess":
86
+ collectLHSRoots(node.expression, targets);
87
+ break;
88
+ case "IndexAccess":
89
+ collectLHSRoots(node.base, targets);
90
+ break;
91
+ case "TupleExpression":
92
+ for (const c of node.components ?? []) collectLHSRoots(c, targets);
93
+ break;
94
+ }
95
+ };
96
+
97
+ const analyzeFunction = (funcNode: any, stateVarNames: Set<string>) => {
98
+ const internalCalls = new Set<string>();
99
+ const externalCalls = new Set<string>();
100
+ const writeTargets = new Set<string>();
101
+ const allStateAccesses = new Set<string>();
102
+ const localVars = new Set<string>();
103
+
104
+ if (!funcNode.body) {
105
+ return { internalCalls: [], externalCalls: [], stateReads: [], stateWrites: [] };
106
+ }
107
+
108
+ // Collect function params and return params as locals so they don't shadow state vars
109
+ for (const p of funcNode.parameters ?? []) {
110
+ if (p?.name) localVars.add(p.name as string);
111
+ }
112
+ for (const p of funcNode.returnParameters ?? []) {
113
+ if (p?.name) localVars.add(p.name as string);
114
+ }
115
+
116
+ // Collect local variable declarations
117
+ visit(funcNode.body, {
118
+ VariableDeclarationStatement: (node: any) => {
119
+ for (const v of node.variables ?? []) {
120
+ if (v?.name) localVars.add(v.name as string);
121
+ }
122
+ },
123
+ });
124
+
125
+ const effectiveStateVars = new Set([...stateVarNames].filter((v) => !localVars.has(v)));
126
+
127
+ // Collect write targets from assignment LHS, unary mutations, and delete
128
+ visit(funcNode.body, {
129
+ ExpressionStatement: (node: any) => {
130
+ const expr = node.expression;
131
+ if (expr?.type === "BinaryOperation" && ASSIGNMENT_OPS.has(expr.operator as string)) {
132
+ collectLHSRoots(expr.left, writeTargets);
133
+ }
134
+ // Handle ++, --, and delete — all work on any lvalue (arr[i]++, delete s.field, etc.)
135
+ if (
136
+ expr?.type === "UnaryOperation" &&
137
+ (expr.operator === "++" || expr.operator === "--" || expr.operator === "delete")
138
+ ) {
139
+ collectLHSRoots(expr.subExpression, writeTargets);
140
+ }
141
+ },
142
+ });
143
+
144
+ // Collect calls and state-var identifier accesses
145
+ visit(funcNode.body, {
146
+ FunctionCall: (node: any) => {
147
+ const expr = node.expression;
148
+ if (expr?.type === "Identifier") {
149
+ internalCalls.add(expr.name as string);
150
+ } else if (expr?.type === "MemberAccess") {
151
+ const base = expr.expression;
152
+ if (base?.type === "Identifier" && (base.name === "this" || base.name === "super")) {
153
+ internalCalls.add(expr.memberName as string);
154
+ } else if (base?.type === "Identifier" && BUILTIN_NAMESPACES.has(base.name as string)) {
155
+ // abi.encode, block.xxx, msg.xxx, etc. — not external calls
156
+ } else {
157
+ const baseStr = base?.type === "Identifier" ? (base.name as string) : "<expr>";
158
+ externalCalls.add(`${baseStr}.${expr.memberName as string}`);
159
+ }
160
+ }
161
+ },
162
+ Identifier: (node: any) => {
163
+ if (effectiveStateVars.has(node.name as string)) {
164
+ allStateAccesses.add(node.name as string);
165
+ }
166
+ },
167
+ });
168
+
169
+ const stateWrites = [...allStateAccesses].filter((v) => writeTargets.has(v));
170
+ // A var can be in both — e.g. x = x + 1 is both a read and a write.
171
+ const stateReads = [...allStateAccesses];
172
+
173
+ return {
174
+ internalCalls: [...internalCalls],
175
+ externalCalls: [...externalCalls],
176
+ stateReads,
177
+ stateWrites,
178
+ };
179
+ };
180
+
181
+ const hasCycle = (start: string, current: string, callMap: Map<string, string[]>, visited: Set<string>) => {
182
+ for (const callee of callMap.get(current) ?? []) {
183
+ if (callee === start) return true;
184
+ if (!visited.has(callee)) {
185
+ visited.add(callee);
186
+ if (hasCycle(start, callee, callMap, visited)) return true;
187
+ }
188
+ }
189
+
190
+ return false;
191
+ };
192
+
193
+ const fnLabel = (fn: FunctionDef) => {
194
+ if (fn.isConstructor) return "constructor";
195
+ if (fn.isReceive) return "receive";
196
+ if (fn.isFallback) return "fallback";
197
+ return fn.name;
198
+ };
199
+
200
+ const generateShortMarkdown = (imports: string[], contracts: ContractAnalysis[]) => {
201
+ const lines: string[] = [];
202
+ lines.push("# Solidity Analysis\n");
203
+
204
+ if (imports.length > 0) {
205
+ lines.push(`**Imports:** ${imports.map((i) => `\`${i}\``).join(", ")}\n`);
206
+ }
207
+
208
+ for (const contract of contracts) {
209
+ const inheritance =
210
+ contract.baseContracts.length > 0 ? ` : ${contract.baseContracts.map((b) => `\`${b}\``).join(", ")}` : "";
211
+ lines.push(`---\n\n## \`${contract.name}\` (${contract.kind})${inheritance}\n`);
212
+
213
+ // State variables — one line, name:type
214
+ if (contract.stateVars.length > 0) {
215
+ const vars = contract.stateVars.map((v) => {
216
+ const flags = [v.constant && "constant", v.immutable && "immutable"].filter(Boolean);
217
+ const suffix = flags.length > 0 ? `, ${flags.join(", ")}` : "";
218
+ return `\`${v.name}: ${v.type}\` (${v.visibility}${suffix})`;
219
+ });
220
+ lines.push(`**State:** ${vars.join(", ")}\n`);
221
+ }
222
+
223
+ // Modifiers — names only
224
+ if (contract.modifiers.length > 0) {
225
+ const mods = contract.modifiers.map(
226
+ (m) => `\`${m.name}${m.params.length > 0 ? `(${m.params.join(", ")})` : ""}\``,
227
+ );
228
+ lines.push(`**Modifiers:** ${mods.join(", ")}\n`);
229
+ }
230
+
231
+ // Events — name + params, one line each
232
+ if (contract.events.length > 0) {
233
+ const evts = contract.events.map((e) => `\`${e.name}(${e.params.join(", ")})\``);
234
+ lines.push(`**Events:** ${evts.join(", ")}\n`);
235
+ }
236
+
237
+ // Function list — compact, one line per function
238
+ if (contract.functions.length > 0) {
239
+ lines.push("**Functions:**");
240
+ for (const fn of contract.functions) {
241
+ const label = fnLabel(fn);
242
+ const params = fn.params.join(", ");
243
+ const ret = fn.returns.length > 0 ? ` → ${fn.returns.join(", ")}` : "";
244
+ const mods = fn.modifiers.length > 0 ? ` [${fn.modifiers.join(", ")}]` : "";
245
+ lines.push(`- \`${label}(${params})${ret}\` — ${fn.visibility} ${fn.mutability}${mods}`);
246
+ }
247
+ lines.push("");
248
+ }
249
+
250
+ // External calls — only functions that make them
251
+ const externalFuncs = contract.functions.filter((f) => f.externalCalls.length > 0);
252
+ if (externalFuncs.length > 0) {
253
+ lines.push("**External Calls:**");
254
+ for (const fn of externalFuncs) {
255
+ lines.push(`- \`${fnLabel(fn)}\`: ${fn.externalCalls.map((c) => `\`${c}\``).join(", ")}`);
256
+ }
257
+ lines.push("");
258
+ }
259
+ }
260
+
261
+ return lines.join("\n");
262
+ };
263
+
264
+ const generateMarkdown = (imports: string[], contracts: ContractAnalysis[]) => {
265
+ const lines: string[] = [];
266
+ lines.push("# Solidity Contract Analysis\n");
267
+
268
+ // Imports
269
+ lines.push("## Imports\n");
270
+ if (imports.length === 0) {
271
+ lines.push("_No imports._\n");
272
+ } else {
273
+ for (const imp of imports) lines.push(`- \`${imp}\``);
274
+ lines.push("");
275
+ }
276
+
277
+ for (const contract of contracts) {
278
+ const kindLabel = contract.kind.charAt(0).toUpperCase() + contract.kind.slice(1);
279
+ lines.push(`---\n\n## ${kindLabel}: \`${contract.name}\`\n`);
280
+
281
+ // Inheritance
282
+ lines.push("### Inheritance\n");
283
+ if (contract.baseContracts.length === 0) {
284
+ lines.push("_None._\n");
285
+ } else {
286
+ for (const base of contract.baseContracts) lines.push(`- \`${base}\``);
287
+ lines.push("");
288
+ }
289
+
290
+ // Using For
291
+ if (contract.usingFor.length > 0) {
292
+ lines.push("### Using For\n");
293
+ for (const u of contract.usingFor) lines.push(`- ${u}`);
294
+ lines.push("");
295
+ }
296
+
297
+ // Storage layout
298
+ lines.push("### Storage Layout (State Variables)\n");
299
+ if (contract.stateVars.length === 0) {
300
+ lines.push("_No state variables._\n");
301
+ } else {
302
+ lines.push("| Slot | Name | Type | Visibility | Flags |");
303
+ lines.push("|------|------|------|------------|-------|");
304
+ contract.stateVars.forEach((v, i) => {
305
+ const flags = [v.constant && "constant", v.immutable && "immutable"].filter(Boolean).join(", ");
306
+ lines.push(`| ${i} | \`${v.name}\` | \`${v.type}\` | ${v.visibility} | ${flags} |`);
307
+ });
308
+ lines.push("");
309
+ }
310
+
311
+ // Events
312
+ lines.push("### Events\n");
313
+ if (contract.events.length === 0) {
314
+ lines.push("_No events._\n");
315
+ } else {
316
+ for (const evt of contract.events) {
317
+ const params = evt.params.join(", ");
318
+ lines.push(`- **\`${evt.name}\`**\`(${params})\`${evt.anonymous ? " _(anonymous)_" : ""}`);
319
+ }
320
+ lines.push("");
321
+ }
322
+
323
+ // Modifiers
324
+ lines.push("### Modifiers\n");
325
+ if (contract.modifiers.length === 0) {
326
+ lines.push("_No modifiers._\n");
327
+ } else {
328
+ for (const mod of contract.modifiers) {
329
+ lines.push(`- **\`${mod.name}\`**\`(${mod.params.join(", ")})\``);
330
+ }
331
+ lines.push("");
332
+ }
333
+
334
+ // Function list
335
+ lines.push("### Function List\n");
336
+ if (contract.functions.length === 0) {
337
+ lines.push("_No functions._\n");
338
+ } else {
339
+ lines.push("| Name | Visibility | Mutability | Parameters | Returns | Modifiers |");
340
+ lines.push("|------|------------|------------|------------|---------|-----------|");
341
+ for (const fn of contract.functions) {
342
+ lines.push(
343
+ `| \`${fnLabel(fn)}\` | ${fn.visibility} | ${fn.mutability} | \`${fn.params.join(", ")}\` | \`${fn.returns.join(", ")}\` | ${fn.modifiers.join(", ")} |`,
344
+ );
345
+ }
346
+ lines.push("");
347
+ }
348
+
349
+ // Call graph
350
+ lines.push("### Call Graph\n");
351
+ const hasCalls = contract.functions.some((f) => f.internalCalls.length > 0 || f.externalCalls.length > 0);
352
+ if (!hasCalls) {
353
+ lines.push("_No function calls detected._\n");
354
+ } else {
355
+ for (const fn of contract.functions) {
356
+ if (fn.internalCalls.length === 0 && fn.externalCalls.length === 0) continue;
357
+ lines.push(`**\`${fnLabel(fn)}\`**`);
358
+ for (const call of fn.internalCalls) lines.push(` - → \`${call}\` _(internal)_`);
359
+ for (const call of fn.externalCalls) lines.push(` - → \`${call}\` _(external)_`);
360
+ }
361
+ lines.push("");
362
+ }
363
+
364
+ // External calls
365
+ lines.push("### External Calls\n");
366
+ const externalFuncs = contract.functions.filter((f) => f.externalCalls.length > 0);
367
+ if (externalFuncs.length === 0) {
368
+ lines.push("_No external calls detected._\n");
369
+ } else {
370
+ for (const fn of externalFuncs) {
371
+ lines.push(`**\`${fnLabel(fn)}\`**`);
372
+ for (const call of fn.externalCalls) lines.push(` - \`${call}\``);
373
+ }
374
+ lines.push("");
375
+ }
376
+
377
+ // Internal recursion
378
+ lines.push("### Internal Recursion\n");
379
+ const callMap = new Map(contract.functions.map((f) => [fnLabel(f), f.internalCalls]));
380
+ const recursiveFns = contract.functions.filter((fn) => hasCycle(fnLabel(fn), fnLabel(fn), callMap, new Set()));
381
+ if (recursiveFns.length === 0) {
382
+ lines.push("_No recursive functions detected._\n");
383
+ } else {
384
+ for (const fn of recursiveFns) lines.push(`- **\`${fnLabel(fn)}\`** is recursive`);
385
+ lines.push("");
386
+ }
387
+
388
+ // State variable touchpoints
389
+ lines.push("### State Variable Touchpoints\n");
390
+ const touchedFns = contract.functions.filter((f) => f.stateReads.length > 0 || f.stateWrites.length > 0);
391
+ if (touchedFns.length === 0) {
392
+ lines.push("_No state variable accesses detected._\n");
393
+ } else {
394
+ lines.push("| Function | Reads | Writes |");
395
+ lines.push("|----------|-------|--------|");
396
+ for (const fn of touchedFns) {
397
+ const reads = fn.stateReads.map((r) => `\`${r}\``).join(", ");
398
+ const writes = fn.stateWrites.map((w) => `\`${w}\``).join(", ");
399
+ lines.push(`| \`${fnLabel(fn)}\` | ${reads} | ${writes} |`);
400
+ }
401
+ lines.push("");
402
+ }
403
+ }
404
+
405
+ // External dependencies summary
406
+ lines.push("---\n\n## External Dependencies\n");
407
+
408
+ lines.push("### Import Paths\n");
409
+ if (imports.length === 0) {
410
+ lines.push("_No imports._\n");
411
+ } else {
412
+ for (const imp of imports) lines.push(`- \`${imp}\``);
413
+ lines.push("");
414
+ }
415
+
416
+ const externalTargets = new Set<string>();
417
+ for (const contract of contracts) {
418
+ for (const fn of contract.functions) {
419
+ for (const call of fn.externalCalls) {
420
+ const target = call.split(".")[0];
421
+ if (target && target !== "<expr>") externalTargets.add(target);
422
+ }
423
+ }
424
+ }
425
+
426
+ lines.push("### External Contract Interactions\n");
427
+ if (externalTargets.size === 0) {
428
+ lines.push("_No external contract interactions detected._\n");
429
+ } else {
430
+ for (const dep of externalTargets) lines.push(`- \`${dep}\``);
431
+ lines.push("");
432
+ }
433
+
434
+ return lines.join("\n");
435
+ };
436
+
437
+ export const analyzeSolidityFile = async (soliditySource: string, mode: "full" | "short") => {
438
+ let ast: any;
439
+
440
+ try {
441
+ ast = parse(soliditySource, { tolerant: true, loc: true, range: true });
442
+ } catch (e: any) {
443
+ return `# Parse Error\n\nFailed to parse Solidity source: ${e.message as string}`;
444
+ }
445
+
446
+ const imports: string[] = [];
447
+ const contracts: ContractAnalysis[] = [];
448
+
449
+ for (const node of ast.children ?? []) {
450
+ if (node.type === "ImportDirective") {
451
+ imports.push(node.path as string);
452
+ }
453
+ }
454
+
455
+ for (const node of ast.children ?? []) {
456
+ if (node.type !== "ContractDefinition") continue;
457
+
458
+ const contract: ContractAnalysis = {
459
+ name: node.name as string,
460
+ kind: (node.kind as string) ?? "contract",
461
+ baseContracts: (node.baseContracts ?? []).map(
462
+ (bc: any) => (bc.baseName?.namePath ?? bc.baseName?.name ?? "?") as string,
463
+ ),
464
+ usingFor: [],
465
+ stateVars: [],
466
+ events: [],
467
+ modifiers: [],
468
+ functions: [],
469
+ };
470
+
471
+ const stateVarNames = new Set<string>();
472
+
473
+ for (const member of node.subNodes ?? []) {
474
+ switch (member.type) {
475
+ case "StateVariableDeclaration":
476
+ for (const v of member.variables ?? []) {
477
+ stateVarNames.add(v.name as string);
478
+ contract.stateVars.push({
479
+ name: v.name as string,
480
+ type: typeToString(v.typeName),
481
+ visibility: (v.visibility as string) ?? "internal",
482
+ constant: (v.isDeclaredConst as boolean) ?? false,
483
+ immutable: (v.isImmutable as boolean) ?? false,
484
+ });
485
+ }
486
+ break;
487
+
488
+ case "EventDefinition": {
489
+ const params = (member.parameters ?? []).map((p: any) => {
490
+ const indexed = p.isIndexed ? "indexed " : "";
491
+ const name = p.name ? ` ${p.name as string}` : "";
492
+ return `${indexed}${typeToString(p.typeName)}${name}`;
493
+ });
494
+ contract.events.push({
495
+ name: member.name as string,
496
+ params,
497
+ anonymous: (member.isAnonymous as boolean) ?? false,
498
+ });
499
+ break;
500
+ }
501
+
502
+ case "ModifierDefinition":
503
+ contract.modifiers.push({
504
+ name: member.name as string,
505
+ params: (member.parameters ?? []).map(paramToString),
506
+ });
507
+ break;
508
+
509
+ case "FunctionDefinition": {
510
+ const { internalCalls, externalCalls, stateReads, stateWrites } = analyzeFunction(member, stateVarNames);
511
+ contract.functions.push({
512
+ name: (member.name as string) ?? "",
513
+ isConstructor: (member.isConstructor as boolean) ?? false,
514
+ isReceive: (member.isReceiveEther as boolean) ?? false,
515
+ isFallback: (member.isFallback as boolean) ?? false,
516
+ visibility: (member.visibility as string) ?? "internal",
517
+ mutability: (member.stateMutability as string) ?? "nonpayable",
518
+ params: (member.parameters ?? []).map(paramToString),
519
+ returns: (member.returnParameters ?? []).map(paramToString),
520
+ modifiers: (member.modifiers ?? []).map((m: any) => m.name as string),
521
+ internalCalls,
522
+ externalCalls,
523
+ stateReads,
524
+ stateWrites,
525
+ });
526
+ break;
527
+ }
528
+
529
+ case "UsingForDeclaration": {
530
+ const forType = member.typeName ? typeToString(member.typeName) : "*";
531
+ if (member.libraryName) {
532
+ contract.usingFor.push(`\`${member.libraryName as string}\` for \`${forType}\``);
533
+ } else {
534
+ // New-style: using {fn1, fn2, ...} for T
535
+ const fns = (member.functions ?? [])
536
+ .map((f: any) => (f.typeName?.namePath ?? f.typeName?.name ?? f.path ?? "?") as string)
537
+ .join(", ");
538
+ contract.usingFor.push(`{${fns}} for \`${forType}\``);
539
+ }
540
+ break;
541
+ }
542
+ }
543
+ }
544
+
545
+ contracts.push(contract);
546
+ }
547
+
548
+ return mode === "short" ? generateShortMarkdown(imports, contracts) : generateMarkdown(imports, contracts);
549
+ };
550
+
551
+ export const solidityAnalyzerTool = tool(
552
+ async ({ solidityFile, mode }) => {
553
+ return analyzeSolidityFile(solidityFile, mode);
554
+ },
555
+ {
556
+ name: "solidity_analyzer",
557
+ description:
558
+ "Parse a Solidity source file and generate a markdown report. Use mode='short' for a compact token-efficient summary (imports, state, modifiers, events, function signatures, external calls). Use mode='full' for the complete report including storage layout table, call graph, recursion detection, state variable touchpoints, and external dependencies.",
559
+ schema: z.object({
560
+ solidityFile: z.string().describe("The full Solidity source code to analyze."),
561
+ mode: z
562
+ .enum(["full", "short"])
563
+ .default("full")
564
+ .describe("Report verbosity. 'short' saves tokens; 'full' provides the complete analysis."),
565
+ }),
566
+ },
567
+ );
src/agents/auditor/utils.ts ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export const matchLines = (fileContent: string, codeSnippet: string): string | null => {
2
+ const fileLines = fileContent.split("\n");
3
+ const snippetLines = codeSnippet.split("\n").map((line) => line.trim());
4
+
5
+ for (let i = 0; i < fileLines.length; i++) {
6
+ const fileLine = fileLines[i].trim();
7
+
8
+ if (fileLine === snippetLines[0]) {
9
+ let snippetIndex = 1;
10
+ const startLine = i + 1;
11
+ let endLine = i + 1;
12
+ let fileIndex = i + 1;
13
+
14
+ while (snippetIndex < snippetLines.length && fileIndex < fileLines.length) {
15
+ const currentFileLine = fileLines[fileIndex].trim();
16
+
17
+ if (currentFileLine === snippetLines[snippetIndex]) {
18
+ endLine = fileIndex + 1;
19
+ snippetIndex++;
20
+ }
21
+
22
+ fileIndex++;
23
+ }
24
+
25
+ if (snippetIndex === snippetLines.length) {
26
+ if (startLine === endLine) {
27
+ return `L${startLine}`;
28
+ }
29
+
30
+ return `L${startLine}-${endLine}`;
31
+ }
32
+ }
33
+ }
34
+
35
+ return null;
36
+ };
src/config/llm.ts CHANGED
@@ -1,7 +1,8 @@
1
  import { ChatGoogleGenerativeAI } from "@langchain/google-genai";
 
2
  import type { BaseChatModel } from "@langchain/core/language_models/chat_models";
3
 
4
- export type LLMProvider = "google" | "openrouter";
5
 
6
  export function createLLM(overrideProvider?: LLMProvider): BaseChatModel {
7
  const provider = overrideProvider || (process.env.LLM_PROVIDER as LLMProvider) || "google";
@@ -14,6 +15,11 @@ export function createLLM(overrideProvider?: LLMProvider): BaseChatModel {
14
  temperature: 0.2,
15
  }) as BaseChatModel;
16
  }
 
 
 
 
 
17
  case "google":
18
  default:
19
  return new ChatGoogleGenerativeAI({
 
1
  import { ChatGoogleGenerativeAI } from "@langchain/google-genai";
2
+ import { ChatAnthropic } from "@langchain/anthropic";
3
  import type { BaseChatModel } from "@langchain/core/language_models/chat_models";
4
 
5
+ export type LLMProvider = "google" | "openrouter" | "anthropic";
6
 
7
  export function createLLM(overrideProvider?: LLMProvider): BaseChatModel {
8
  const provider = overrideProvider || (process.env.LLM_PROVIDER as LLMProvider) || "google";
 
15
  temperature: 0.2,
16
  }) as BaseChatModel;
17
  }
18
+ case "anthropic":
19
+ return new ChatAnthropic({
20
+ model: process.env.ANTHROPIC_MODEL || "claude-sonnet-4-6",
21
+ temperature: 0.2,
22
+ });
23
  case "google":
24
  default:
25
  return new ChatGoogleGenerativeAI({
src/index.ts CHANGED
@@ -1,4 +1,5 @@
1
  import "dotenv/config";
 
2
  import { readFileSync, mkdirSync, writeFileSync } from "node:fs";
3
  import { resolve, dirname } from "node:path";
4
  import { fileURLToPath } from "node:url";
@@ -6,6 +7,7 @@ import { fileURLToPath } from "node:url";
6
  import { auditorAgent } from "./agents/auditor/agent.ts";
7
  import { coderAgent } from "./agents/coder/agent.ts";
8
  import { testerAgent } from "./agents/tester/agent.ts";
 
9
 
10
  const inputPath = process.argv[2];
11
 
@@ -25,17 +27,25 @@ console.log(coderResult.contract);
25
  const __dirname = dirname(fileURLToPath(import.meta.url));
26
  const outputDir = resolve(__dirname, "agents/coder/outputs");
27
  mkdirSync(outputDir, { recursive: true });
28
- const outputPath = resolve(outputDir, "Contract.sol");
29
- writeFileSync(outputPath, coderResult.contract, "utf-8");
30
- console.log("\nContrato salvo em:", outputPath);
31
 
32
- const auditorResult = await auditorAgent.invoke({ solidityFile: coderResult.contract });
33
  console.log("\n======= Auditor =======");
34
- console.log(auditorResult.vulnerabilities);
 
 
 
 
 
 
 
 
35
 
36
  const testerResult = await testerAgent.invoke({
37
  solidityFiles: [coderResult.contract],
38
- vulnerability: auditorResult.vulnerabilities[0] ?? {},
39
  });
 
40
  console.log("\n======= Tester =======");
41
  console.log(testerResult.results);
 
1
  import "dotenv/config";
2
+
3
  import { readFileSync, mkdirSync, writeFileSync } from "node:fs";
4
  import { resolve, dirname } from "node:path";
5
  import { fileURLToPath } from "node:url";
 
7
  import { auditorAgent } from "./agents/auditor/agent.ts";
8
  import { coderAgent } from "./agents/coder/agent.ts";
9
  import { testerAgent } from "./agents/tester/agent.ts";
10
+ import { logger } from "./logger.ts";
11
 
12
  const inputPath = process.argv[2];
13
 
 
27
  const __dirname = dirname(fileURLToPath(import.meta.url));
28
  const outputDir = resolve(__dirname, "agents/coder/outputs");
29
  mkdirSync(outputDir, { recursive: true });
30
+ writeFileSync(resolve(outputDir, "Contract.sol"), coderResult.contract, "utf-8");
31
+ writeFileSync(resolve(outputDir, "README.md"), requirementsText, "utf-8");
32
+ console.log("\nContrato e requisitos salvos em:", outputDir);
33
 
 
34
  console.log("\n======= Auditor =======");
35
+ logger.info("Starting auditorAgent");
36
+
37
+ const auditorResult = await auditorAgent.invoke({ repoPath: outputDir });
38
+
39
+ logger.info("Agent completed");
40
+ logger.info(`Findings: ${auditorResult.findings.length}`);
41
+ for (const f of auditorResult.findings) {
42
+ logger.info(` [${f.severity.toUpperCase()}] ${f.title} — ${f.location}`);
43
+ }
44
 
45
  const testerResult = await testerAgent.invoke({
46
  solidityFiles: [coderResult.contract],
47
+ vulnerability: auditorResult.findings[0] ?? {},
48
  });
49
+
50
  console.log("\n======= Tester =======");
51
  console.log(testerResult.results);
src/logger.ts ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+
4
+ import winston from "winston";
5
+
6
+ const logsDir = path.join(process.cwd(), "logs");
7
+ fs.mkdirSync(logsDir, { recursive: true });
8
+
9
+ const runTimestamp = new Date().toISOString().replace(/[:.]/g, "-");
10
+
11
+ const { combine, colorize, errors, printf, timestamp } = winston.format;
12
+
13
+ const lineFormat = printf(({ level, message, timestamp: ts, stack }) => {
14
+ const base = `${ts} [${level}] ${message}`;
15
+ return stack ? `${base}\n${stack}` : base;
16
+ });
17
+
18
+ export const logger = winston.createLogger({
19
+ level: "debug",
20
+ transports: [
21
+ new winston.transports.Console({
22
+ format: combine(colorize({ all: true }), timestamp({ format: "HH:mm:ss" }), errors({ stack: true }), lineFormat),
23
+ }),
24
+ new winston.transports.File({
25
+ filename: path.join(logsDir, `app-${runTimestamp}.log`),
26
+ format: combine(timestamp(), errors({ stack: true }), lineFormat),
27
+ }),
28
+ ],
29
+ });
src/server.ts CHANGED
@@ -1,4 +1,8 @@
1
  import "dotenv/config";
 
 
 
 
2
  import { serve } from "@hono/node-server";
3
  import { Hono } from "hono";
4
  import { cors } from "hono/cors";
@@ -42,32 +46,49 @@ app.post("/api/run", (c) => {
42
  await send("log", "[Coder] Contrato compilado sem erros.");
43
  }
44
 
45
- await send("coder", JSON.stringify({
46
- contract: coderResult.contract,
47
- compilationErrors: coderResult.compilationErrors,
48
- reviewSummary: coderResult.reviewSummary,
49
- }));
 
 
 
50
 
51
  // === AUDITOR ===
 
 
 
 
 
52
  await send("log", "[Auditor] Iniciando auditoria de segurança...");
53
- const auditorResult = await auditorAgent.invoke({ solidityFile: coderResult.contract });
54
- await send("log", `[Auditor] ${auditorResult.vulnerabilities.length} vulnerabilidade(s) encontrada(s).`);
 
 
 
55
 
56
- await send("auditor", JSON.stringify({
57
- vulnerabilities: auditorResult.vulnerabilities,
58
- }));
 
 
 
59
 
60
  // === TESTER ===
61
  await send("log", "[Tester] Gerando testes de prova de conceito...");
62
  const testerResult = await testerAgent.invoke({
63
  solidityFiles: [coderResult.contract],
64
- vulnerability: auditorResult.vulnerabilities[0] ?? {},
65
  });
66
  await send("log", `[Tester] ${testerResult.results.length} resultado(s) de teste.`);
67
 
68
- await send("tester", JSON.stringify({
69
- results: testerResult.results,
70
- }));
 
 
 
71
 
72
  await send("log", "Pipeline concluído.");
73
  await send("done", "ok");
 
1
  import "dotenv/config";
2
+
3
+ import { mkdirSync, writeFileSync } from "node:fs";
4
+ import { resolve } from "node:path";
5
+ import { tmpdir } from "node:os";
6
  import { serve } from "@hono/node-server";
7
  import { Hono } from "hono";
8
  import { cors } from "hono/cors";
 
46
  await send("log", "[Coder] Contrato compilado sem erros.");
47
  }
48
 
49
+ await send(
50
+ "coder",
51
+ JSON.stringify({
52
+ contract: coderResult.contract,
53
+ compilationErrors: coderResult.compilationErrors,
54
+ reviewSummary: coderResult.reviewSummary,
55
+ }),
56
+ );
57
 
58
  // === AUDITOR ===
59
+ const outputDir = resolve(tmpdir(), `talp1-${Date.now()}`);
60
+ mkdirSync(outputDir, { recursive: true });
61
+ writeFileSync(resolve(outputDir, "Contract.sol"), coderResult.contract, "utf-8");
62
+ writeFileSync(resolve(outputDir, "README.md"), requirements, "utf-8");
63
+
64
  await send("log", "[Auditor] Iniciando auditoria de segurança...");
65
+ const auditorResult = await auditorAgent.invoke({ repoPath: outputDir });
66
+ await send("log", `[Auditor] ${auditorResult.findings.length} vulnerabilidade(s) encontrada(s).`);
67
+ for (const f of auditorResult.findings) {
68
+ await send("log", `[Auditor] [${f.severity.toUpperCase()}] ${f.title} — ${f.location}`);
69
+ }
70
 
71
+ await send(
72
+ "auditor",
73
+ JSON.stringify({
74
+ findings: auditorResult.findings,
75
+ }),
76
+ );
77
 
78
  // === TESTER ===
79
  await send("log", "[Tester] Gerando testes de prova de conceito...");
80
  const testerResult = await testerAgent.invoke({
81
  solidityFiles: [coderResult.contract],
82
+ vulnerability: auditorResult.findings[0] ?? {},
83
  });
84
  await send("log", `[Tester] ${testerResult.results.length} resultado(s) de teste.`);
85
 
86
+ await send(
87
+ "tester",
88
+ JSON.stringify({
89
+ results: testerResult.results,
90
+ }),
91
+ );
92
 
93
  await send("log", "Pipeline concluído.");
94
  await send("done", "ok");