Uanderson Silva commited on
Commit
52c3560
Β·
1 Parent(s): 48a8015

add repo tree tool

Browse files
src/agents/auditor/agent.ts CHANGED
@@ -10,6 +10,7 @@ import { judgeFindingsModel, findVulnerabilitiesModel, gatherContextModel } from
10
  import { JUDGE_FINDINGS_PROMPT, FIND_VULNERABILITIES_PROMPT, GATHER_CONTEXT_PROMPT } from "./prompts.ts";
11
  import { AuditorState, ReviewSchema, PartialFindingSchema } from "./state.ts";
12
  import { analyzeSolidityFile } from "./tools/solidity-analyzer-tool.ts";
 
13
  import {
14
  DOC_BASENAMES,
15
  DOC_EXTS,
@@ -60,11 +61,14 @@ const defineScope: GraphNode<typeof AuditorState> = async (state) => {
60
 
61
  walkDirectory(state.repoPath, 0, solFiles, docFiles);
62
 
 
 
63
  logger.info(`defineScope: found ${solFiles.length} Solidity file(s), ${docFiles.length} doc file(s)`);
64
  logger.debug(`defineScope: Solidity files: ${JSON.stringify(solFiles)}`);
65
  logger.debug(`defineScope: doc files: ${JSON.stringify(docFiles)}`);
 
66
 
67
- return { scope: solFiles, docs: docFiles };
68
  };
69
 
70
  const gatherContext: GraphNode<typeof AuditorState> = async (state) => {
 
10
  import { JUDGE_FINDINGS_PROMPT, FIND_VULNERABILITIES_PROMPT, GATHER_CONTEXT_PROMPT } from "./prompts.ts";
11
  import { AuditorState, ReviewSchema, PartialFindingSchema } 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,
 
61
 
62
  walkDirectory(state.repoPath, 0, solFiles, docFiles);
63
 
64
+ const fileTree = buildRepoTree(state.repoPath);
65
+
66
  logger.info(`defineScope: found ${solFiles.length} Solidity file(s), ${docFiles.length} doc file(s)`);
67
  logger.debug(`defineScope: Solidity files: ${JSON.stringify(solFiles)}`);
68
  logger.debug(`defineScope: doc files: ${JSON.stringify(docFiles)}`);
69
+ logger.debug(`defineScope: file tree:\n${fileTree}`);
70
 
71
+ return { scope: solFiles, docs: docFiles, fileTree };
72
  };
73
 
74
  const gatherContext: GraphNode<typeof AuditorState> = async (state) => {
src/agents/auditor/state.ts CHANGED
@@ -31,6 +31,7 @@ export const AuditorState = new StateSchema({
31
  solidityFile: z.string().default(""),
32
  scope: z.array(z.string()).default([]),
33
  docs: z.array(z.string()).default([]),
 
34
  repoContext: z.string().default(""),
35
  candidateFindings: z.array(FindingSchema).default([]),
36
  judgeReviews: z.array(ReviewSchema).default([]),
 
31
  solidityFile: z.string().default(""),
32
  scope: z.array(z.string()).default([]),
33
  docs: z.array(z.string()).default([]),
34
+ fileTree: z.string().default(""),
35
  repoContext: z.string().default(""),
36
  candidateFindings: z.array(FindingSchema).default([]),
37
  judgeReviews: z.array(ReviewSchema).default([]),
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
+ );