solidity-argus 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (167) hide show
  1. package/AGENTS.md +3 -3
  2. package/README.md +93 -37
  3. package/package.json +33 -7
  4. package/skills/INVENTORY.md +88 -57
  5. package/skills/README.md +26 -23
  6. package/skills/case-studies/beanstalk-governance/SKILL.md +52 -0
  7. package/skills/case-studies/bzx-flash-loan/SKILL.md +53 -0
  8. package/skills/case-studies/cream-finance/SKILL.md +52 -0
  9. package/skills/case-studies/curve-reentrancy/SKILL.md +52 -0
  10. package/skills/case-studies/dao-hack/SKILL.md +51 -0
  11. package/skills/case-studies/euler-finance/SKILL.md +52 -0
  12. package/skills/case-studies/harvest-finance/SKILL.md +52 -0
  13. package/skills/case-studies/level-finance/SKILL.md +51 -0
  14. package/skills/case-studies/mango-markets/SKILL.md +53 -0
  15. package/skills/case-studies/nomad-bridge/SKILL.md +51 -0
  16. package/skills/case-studies/parity-multisig/SKILL.md +55 -0
  17. package/skills/case-studies/poly-network/SKILL.md +51 -0
  18. package/skills/case-studies/rari-fuse/SKILL.md +51 -0
  19. package/skills/case-studies/ronin-bridge/SKILL.md +52 -0
  20. package/skills/case-studies/wormhole-bridge/SKILL.md +51 -0
  21. package/skills/manifests/smartbugs.json +1 -3
  22. package/skills/manifests/sunweb3sec.json +1 -3
  23. package/skills/vulnerability-patterns/access-control/SKILL.md +14 -0
  24. package/skills/vulnerability-patterns/arbitrary-storage-location/SKILL.md +13 -1
  25. package/skills/vulnerability-patterns/assert-violation/SKILL.md +8 -1
  26. package/skills/vulnerability-patterns/asserting-contract-from-code-size/SKILL.md +12 -1
  27. package/skills/vulnerability-patterns/authorization-txorigin/SKILL.md +2 -1
  28. package/skills/vulnerability-patterns/cross-chain-bridge-vulnerabilities/SKILL.md +217 -0
  29. package/skills/vulnerability-patterns/default-visibility/SKILL.md +13 -1
  30. package/skills/vulnerability-patterns/delegatecall-untrusted-callee/SKILL.md +2 -1
  31. package/skills/vulnerability-patterns/dos-gas-limit/SKILL.md +8 -1
  32. package/skills/vulnerability-patterns/dos-revert/SKILL.md +1 -0
  33. package/skills/vulnerability-patterns/erc4626-exchange-rate-manipulation/SKILL.md +64 -0
  34. package/skills/vulnerability-patterns/fee-on-transfer-tokens/SKILL.md +93 -0
  35. package/skills/vulnerability-patterns/flash-loan-attacks/SKILL.md +1 -0
  36. package/skills/vulnerability-patterns/floating-pragma/SKILL.md +8 -1
  37. package/skills/vulnerability-patterns/front-running-attacks/SKILL.md +209 -0
  38. package/skills/vulnerability-patterns/gas-optimization-patterns/SKILL.md +203 -0
  39. package/skills/vulnerability-patterns/governance-attacks/SKILL.md +208 -0
  40. package/skills/vulnerability-patterns/hash-collision/SKILL.md +8 -1
  41. package/skills/vulnerability-patterns/inadherence-to-standards/SKILL.md +12 -1
  42. package/skills/vulnerability-patterns/incorrect-constructor/SKILL.md +8 -1
  43. package/skills/vulnerability-patterns/incorrect-inheritance-order/SKILL.md +8 -1
  44. package/skills/vulnerability-patterns/insufficient-gas-griefing/SKILL.md +12 -1
  45. package/skills/vulnerability-patterns/lack-of-precision/SKILL.md +7 -1
  46. package/skills/vulnerability-patterns/logic-errors/SKILL.md +10 -0
  47. package/skills/vulnerability-patterns/missing-parameter-bounds/SKILL.md +44 -0
  48. package/skills/vulnerability-patterns/missing-protection-signature-replay/SKILL.md +17 -1
  49. package/skills/vulnerability-patterns/msgvalue-loop/SKILL.md +12 -1
  50. package/skills/vulnerability-patterns/off-by-one/SKILL.md +7 -1
  51. package/skills/vulnerability-patterns/oracle-manipulation/SKILL.md +9 -0
  52. package/skills/vulnerability-patterns/outdated-compiler-version/SKILL.md +8 -1
  53. package/skills/vulnerability-patterns/overflow-underflow/SKILL.md +1 -0
  54. package/skills/vulnerability-patterns/proxy-vulnerabilities/SKILL.md +209 -0
  55. package/skills/vulnerability-patterns/reentrancy/SKILL.md +9 -0
  56. package/skills/vulnerability-patterns/shadowing-state-variables/SKILL.md +8 -1
  57. package/skills/vulnerability-patterns/share-accounting-desynchronization/SKILL.md +44 -0
  58. package/skills/vulnerability-patterns/signature-malleability/SKILL.md +2 -1
  59. package/skills/vulnerability-patterns/stateful-parameter-update-drift/SKILL.md +44 -0
  60. package/skills/vulnerability-patterns/unbounded-return-data/SKILL.md +12 -1
  61. package/skills/vulnerability-patterns/unchecked-return-values/SKILL.md +2 -1
  62. package/skills/vulnerability-patterns/unencrypted-private-data-on-chain/SKILL.md +8 -1
  63. package/skills/vulnerability-patterns/unexpected-ecrecover-null-address/SKILL.md +8 -1
  64. package/skills/vulnerability-patterns/uninitialized-storage-pointer/SKILL.md +8 -1
  65. package/skills/vulnerability-patterns/unsafe-erc20-transfers/SKILL.md +132 -0
  66. package/skills/vulnerability-patterns/unsafe-low-level-call/SKILL.md +12 -1
  67. package/skills/vulnerability-patterns/unsecure-signatures/SKILL.md +12 -1
  68. package/skills/vulnerability-patterns/unsupported-opcodes/SKILL.md +11 -1
  69. package/skills/vulnerability-patterns/unused-variables/SKILL.md +8 -1
  70. package/skills/vulnerability-patterns/use-of-deprecated-functions/SKILL.md +8 -1
  71. package/skills/vulnerability-patterns/weak-sources-randomness/SKILL.md +8 -1
  72. package/skills/vulnerability-patterns/weird-tokens/SKILL.md +10 -0
  73. package/skills/vulnerability-patterns/zero-address-misconfiguration/SKILL.md +48 -0
  74. package/src/agents/argus-prompt.ts +24 -7
  75. package/src/agents/pythia-prompt.ts +3 -4
  76. package/src/agents/scribe-prompt.ts +7 -2
  77. package/src/agents/sentinel-prompt.ts +32 -3
  78. package/src/cli/cli-program.ts +29 -26
  79. package/src/cli/commands/check-skills.ts +135 -0
  80. package/src/cli/commands/doctor.ts +48 -26
  81. package/src/cli/commands/init.ts +5 -3
  82. package/src/cli/commands/install.ts +7 -5
  83. package/src/cli/commands/lint-skills.ts +16 -12
  84. package/src/cli/index.ts +5 -5
  85. package/src/cli/types.ts +3 -3
  86. package/src/config/index.ts +1 -1
  87. package/src/config/loader.ts +4 -6
  88. package/src/config/schema.ts +4 -5
  89. package/src/config/types.ts +2 -2
  90. package/src/constants/defaults.ts +2 -0
  91. package/src/create-hooks.ts +145 -34
  92. package/src/create-managers.ts +10 -8
  93. package/src/create-tools.ts +13 -9
  94. package/src/features/background-agent/background-manager.ts +93 -87
  95. package/src/features/background-agent/index.ts +1 -1
  96. package/src/features/context-monitor/context-monitor.ts +3 -3
  97. package/src/features/context-monitor/index.ts +2 -2
  98. package/src/features/error-recovery/session-recovery.ts +2 -4
  99. package/src/features/error-recovery/tool-error-recovery.ts +12 -7
  100. package/src/features/index.ts +5 -5
  101. package/src/features/persistent-state/audit-state-manager.ts +143 -60
  102. package/src/features/persistent-state/global-run-index.ts +38 -0
  103. package/src/features/persistent-state/index.ts +1 -1
  104. package/src/features/persistent-state/run-journal.ts +86 -0
  105. package/src/hooks/config-handler.ts +28 -11
  106. package/src/hooks/context-budget.ts +2 -5
  107. package/src/hooks/event-hook.ts +47 -23
  108. package/src/hooks/hook-system.ts +4 -4
  109. package/src/hooks/index.ts +5 -5
  110. package/src/hooks/knowledge-sync-hook.ts +18 -21
  111. package/src/hooks/recon-context-builder.ts +2 -2
  112. package/src/hooks/safe-create-hook.ts +6 -7
  113. package/src/hooks/tool-tracking-hook.ts +104 -50
  114. package/src/hooks/types.ts +2 -1
  115. package/src/index.ts +23 -36
  116. package/src/knowledge/retry.ts +22 -22
  117. package/src/knowledge/scvd-client.ts +88 -95
  118. package/src/knowledge/scvd-errors.ts +35 -35
  119. package/src/knowledge/scvd-index.ts +78 -80
  120. package/src/knowledge/scvd-sync.ts +106 -101
  121. package/src/managers/index.ts +1 -1
  122. package/src/managers/types.ts +19 -14
  123. package/src/plugin-interface.ts +7 -9
  124. package/src/shared/binary-utils.ts +44 -35
  125. package/src/shared/deep-merge.ts +55 -36
  126. package/src/shared/file-utils.ts +21 -19
  127. package/src/shared/index.ts +11 -5
  128. package/src/shared/jsonc-parser.ts +123 -28
  129. package/src/shared/logger.ts +16 -3
  130. package/src/shared/project-utils.ts +30 -0
  131. package/src/skills/analysis/cluster.ts +414 -0
  132. package/src/skills/analysis/gates.ts +227 -0
  133. package/src/skills/analysis/index.ts +33 -0
  134. package/src/skills/analysis/normalize.ts +217 -0
  135. package/src/skills/analysis/similarity.ts +224 -0
  136. package/src/skills/argus-skill-resolver.ts +17 -6
  137. package/src/skills/skill-schema.ts +11 -10
  138. package/src/solodit-lifecycle.ts +202 -0
  139. package/src/state/audit-state.ts +8 -8
  140. package/src/state/finding-store.ts +68 -55
  141. package/src/state/types.ts +88 -67
  142. package/src/tools/argus-skill-load-tool.ts +12 -7
  143. package/src/tools/contract-analyzer-tool.ts +60 -77
  144. package/src/tools/forge-coverage-tool.ts +226 -0
  145. package/src/tools/forge-fuzz-tool.ts +127 -127
  146. package/src/tools/forge-test-tool.ts +153 -157
  147. package/src/tools/gas-analysis-tool.ts +264 -0
  148. package/src/tools/pattern-checker-tool.ts +185 -190
  149. package/src/tools/pattern-loader.ts +5 -111
  150. package/src/tools/proxy-detection-tool.ts +224 -0
  151. package/src/tools/report-generator-tool.ts +268 -200
  152. package/src/tools/slither-tool.ts +266 -218
  153. package/src/tools/solodit-search-tool.ts +216 -119
  154. package/src/tools/sync-knowledge-tool.ts +7 -11
  155. package/src/utils/audit-artifact-detector.ts +28 -29
  156. package/src/utils/dependency-scanner.ts +37 -37
  157. package/src/utils/project-detector.ts +111 -124
  158. package/src/utils/solidity-parser.ts +103 -74
  159. package/skills/patterns/access-control.yaml +0 -31
  160. package/skills/patterns/erc4626.yaml +0 -29
  161. package/skills/patterns/flash-loan.yaml +0 -20
  162. package/skills/patterns/oracle.yaml +0 -30
  163. package/skills/patterns/proxy.yaml +0 -30
  164. package/skills/patterns/reentrancy.yaml +0 -30
  165. package/skills/patterns/signature.yaml +0 -31
  166. package/src/hooks/event-hook-v2.ts +0 -99
  167. package/src/state/plugin-state.ts +0 -14
@@ -1,109 +1,78 @@
1
- import os from "node:os";
2
- import { readdirSync, readFileSync, statSync } from "node:fs";
3
- import { dirname, extname, join, resolve } from "node:path";
4
- import { tool, type ToolContext } from "@opencode-ai/plugin";
1
+ import { readdirSync, readFileSync, statSync } from "node:fs"
2
+ import os from "node:os"
3
+ import { dirname, extname, join, resolve } from "node:path"
4
+ import { type ToolContext, tool } from "@opencode-ai/plugin"
5
5
  import {
6
6
  loadIndex,
7
- searchIndex,
8
7
  type ScvdIndex,
9
8
  type ScvdIndexEntry,
10
- } from "../knowledge/scvd-index";
11
- import {
12
- extractDetectionRulesFromSkills,
13
- loadPatternPacks,
14
- } from "./pattern-loader";
15
- import type { PatternDefinition } from "./pattern-schema";
9
+ searchIndex,
10
+ } from "../knowledge/scvd-index"
11
+ import { createLogger } from "../shared/logger"
12
+ import { extractDetectionRulesFromSkills } from "./pattern-loader"
13
+ import type { PatternDefinition } from "./pattern-schema"
14
+
15
+ const logger = createLogger()
16
16
 
17
- export type PatternSource = "builtin" | "yaml" | "skill";
17
+ export type PatternSource = "skill"
18
18
 
19
19
  export interface Match {
20
- pattern: string;
21
- severity: "Critical" | "High" | "Medium" | "Low" | "Informational";
22
- file: string;
23
- lines: [number, number];
24
- description: string;
25
- exploitReference?: string;
26
- patternSource?: PatternSource;
27
- category?: string;
20
+ pattern: string
21
+ severity: "Critical" | "High" | "Medium" | "Low" | "Informational"
22
+ file: string
23
+ lines: [number, number]
24
+ description: string
25
+ exploitReference?: string
26
+ patternSource?: PatternSource
27
+ category?: string
28
28
  }
29
29
 
30
30
  export interface MatchSource {
31
- source: string;
32
- matches: Match[];
31
+ source: string
32
+ matches: Match[]
33
33
  }
34
34
 
35
35
  export interface PatternCheckResult {
36
- sources: MatchSource[];
37
- patternsChecked: number;
38
- executionTime: number;
39
- target: string;
40
- patternVersion?: string;
36
+ success: boolean
37
+ error?: string
38
+ matches: Match[]
39
+ summary: {
40
+ total: number
41
+ bySeverity: Record<string, number>
42
+ byCategory: Record<string, number>
43
+ }
44
+ sources: MatchSource[]
45
+ patternsChecked: number
46
+ executionTime: number
47
+ target: string
48
+ patternVersion?: string
41
49
  }
42
50
 
43
51
  type PatternCheckArgs = {
44
- target: string;
45
- patterns?: string[];
46
- include_scvd?: boolean;
47
- };
52
+ target: string
53
+ patterns?: string[]
54
+ include_scvd?: boolean
55
+ }
48
56
 
49
57
  type PatternCheckDependencies = {
50
- loadIndexFn?: (filePath: string) => Promise<ScvdIndex | null>;
58
+ loadIndexFn?: (filePath: string) => Promise<ScvdIndex | null>
51
59
  searchIndexFn?: (
52
60
  index: ScvdIndex,
53
- query: { swc?: string; severity?: string; keyword?: string; limit?: number }
54
- ) => ScvdIndexEntry[];
55
- };
56
-
57
- type BuiltinPattern = {
58
- name: string;
59
- category: string;
60
- severity: Match["severity"];
61
- regex: RegExp;
62
- description: string;
63
- exploitReference?: string;
64
- source?: PatternSource;
65
- };
66
-
67
- export const PATTERN_PACK_VERSION = "1.0.0";
68
-
69
- const BUILTIN_PATTERNS: BuiltinPattern[] = [
70
- {
71
- name: "reentrancy",
72
- category: "reentrancy",
73
- severity: "High",
74
- regex: /\.call\{value:/,
75
- description: "Potential reentrancy: ETH transfer via low-level call",
76
- exploitReference: "DAO hack ($60M), 2016",
77
- },
78
- {
79
- name: "tx-origin-auth",
80
- category: "access-control",
81
- severity: "High",
82
- regex: /tx\.origin/,
83
- description: "Use of tx.origin for authorization - vulnerable to phishing",
84
- },
85
- {
86
- name: "selfdestruct",
87
- category: "access-control",
88
- severity: "High",
89
- regex: /selfdestruct\(|suicide\(/,
90
- description: "Contract uses selfdestruct - can destroy contract",
91
- },
92
- {
93
- name: "delegatecall",
94
- category: "delegatecall",
95
- severity: "High",
96
- regex: /\.delegatecall\(/,
97
- description: "Use of delegatecall - can overwrite storage",
98
- },
99
- {
100
- name: "missing-zero-check",
101
- category: "access-control",
102
- severity: "Medium",
103
- regex: /address\(0\)/,
104
- description: "Potential missing zero-address validation",
105
- },
106
- ];
61
+ query: { swc?: string; severity?: string; keyword?: string; limit?: number },
62
+ ) => ScvdIndexEntry[]
63
+ }
64
+
65
+ type LoadedPattern = {
66
+ name: string
67
+ category: string
68
+ severity: Match["severity"]
69
+ regex: RegExp
70
+ description: string
71
+ exploitReference?: string
72
+ source?: PatternSource
73
+ }
74
+
75
+ export const PATTERN_PACK_VERSION = "1.0.0"
107
76
 
108
77
  const CATEGORY_TO_SWC: Record<string, string[]> = {
109
78
  reentrancy: ["SWC-107"],
@@ -112,24 +81,25 @@ const CATEGORY_TO_SWC: Record<string, string[]> = {
112
81
  delegatecall: ["SWC-112"],
113
82
  "signature-replay": ["SWC-121"],
114
83
  "integer-overflow": ["SWC-101"],
115
- };
116
-
117
- const PATTERN_NAME_TO_CATEGORY = new Map(
118
- BUILTIN_PATTERNS.map((pattern) => [pattern.name, pattern.category])
119
- );
84
+ governance: ["SWC-105", "SWC-106"],
85
+ "front-running": ["SWC-114"],
86
+ "logic-error": ["SWC-101", "SWC-116"],
87
+ "gas-optimization": ["SWC-128"],
88
+ dos: ["SWC-128"],
89
+ }
120
90
 
121
91
  function normalizeSeverity(value: string): Match["severity"] {
122
- if (value === "Critical") return "Critical";
123
- if (value === "High") return "High";
124
- if (value === "Medium") return "Medium";
125
- if (value === "Low") return "Low";
126
- return "Informational";
92
+ if (value === "Critical") return "Critical"
93
+ if (value === "High") return "High"
94
+ if (value === "Medium") return "Medium"
95
+ if (value === "Low") return "Low"
96
+ return "Informational"
127
97
  }
128
98
 
129
99
  function normalizePatternDefinitions(
130
100
  patterns: PatternDefinition[],
131
- source: PatternSource
132
- ): BuiltinPattern[] {
101
+ source: PatternSource,
102
+ ): LoadedPattern[] {
133
103
  return patterns.map((patternDef) => ({
134
104
  name: patternDef.name,
135
105
  category: patternDef.category,
@@ -138,55 +108,55 @@ function normalizePatternDefinitions(
138
108
  description: patternDef.description,
139
109
  ...(patternDef.exploit_ref ? { exploitReference: patternDef.exploit_ref } : {}),
140
110
  source,
141
- }));
111
+ }))
142
112
  }
143
113
 
144
114
  function uniqueScvdEntries(entries: ScvdIndexEntry[]): ScvdIndexEntry[] {
145
- const deduped = new Map<string, ScvdIndexEntry>();
115
+ const deduped = new Map<string, ScvdIndexEntry>()
146
116
  for (const entry of entries) {
147
- deduped.set(entry.id, entry);
117
+ deduped.set(entry.id, entry)
148
118
  }
149
- return Array.from(deduped.values());
119
+ return Array.from(deduped.values())
150
120
  }
151
121
 
152
122
  async function collectScvdMatches(
153
123
  matches: Match[],
154
- dependencies: Required<PatternCheckDependencies>
124
+ dependencies: Required<PatternCheckDependencies>,
155
125
  ): Promise<Match[]> {
156
- const detectedCategories = new Set<string>();
126
+ const detectedCategories = new Set<string>()
157
127
  for (const match of matches) {
158
- const category = match.category ?? PATTERN_NAME_TO_CATEGORY.get(match.pattern);
128
+ const category = match.category
159
129
  if (category) {
160
- detectedCategories.add(category);
130
+ detectedCategories.add(category)
161
131
  }
162
132
  }
163
133
 
164
134
  if (detectedCategories.size === 0) {
165
- return [];
135
+ return []
166
136
  }
167
137
 
168
- const swcCodes = new Set<string>();
138
+ const swcCodes = new Set<string>()
169
139
  for (const category of detectedCategories) {
170
- const mappedSwcs = CATEGORY_TO_SWC[category] ?? [];
140
+ const mappedSwcs = CATEGORY_TO_SWC[category] ?? []
171
141
  for (const swcCode of mappedSwcs) {
172
- swcCodes.add(swcCode);
142
+ swcCodes.add(swcCode)
173
143
  }
174
144
  }
175
145
 
176
146
  if (swcCodes.size === 0) {
177
- return [];
147
+ return []
178
148
  }
179
149
 
180
- const indexPath = join(os.homedir(), ".cache", "solidity-argus", "scvd-index.json");
181
- const index = await dependencies.loadIndexFn(indexPath);
150
+ const indexPath = join(os.homedir(), ".cache", "solidity-argus", "scvd-index.json")
151
+ const index = await dependencies.loadIndexFn(indexPath)
182
152
 
183
153
  if (!index) {
184
- return [];
154
+ return []
185
155
  }
186
156
 
187
- const entries: ScvdIndexEntry[] = [];
157
+ const entries: ScvdIndexEntry[] = []
188
158
  for (const swcCode of swcCodes) {
189
- entries.push(...dependencies.searchIndexFn(index, { swc: swcCode }));
159
+ entries.push(...dependencies.searchIndexFn(index, { swc: swcCode }))
190
160
  }
191
161
 
192
162
  return uniqueScvdEntries(entries).map((entry) => ({
@@ -196,83 +166,86 @@ async function collectScvdMatches(
196
166
  lines: [1, 1],
197
167
  description: entry.title,
198
168
  exploitReference: entry.repoUrl,
199
- }));
169
+ }))
200
170
  }
201
171
 
202
172
  function collectSolidityFiles(target: string, maxDepth = 8): string[] {
203
- const absoluteTarget = resolve(target);
204
- let stats: ReturnType<typeof statSync>;
173
+ const absoluteTarget = resolve(target)
174
+ let stats: ReturnType<typeof statSync>
205
175
 
206
176
  try {
207
- stats = statSync(absoluteTarget);
177
+ stats = statSync(absoluteTarget)
208
178
  } catch {
209
- throw new Error(`Target does not exist: ${target}`);
179
+ return []
210
180
  }
211
181
 
212
182
  if (stats.isFile()) {
213
- return extname(absoluteTarget) === ".sol" ? [absoluteTarget] : [];
183
+ return extname(absoluteTarget) === ".sol" ? [absoluteTarget] : []
214
184
  }
215
185
 
216
186
  if (!stats.isDirectory()) {
217
- return [];
187
+ return []
218
188
  }
219
189
 
220
- const discovered: string[] = [];
221
- const stack: Array<{ path: string; depth: number }> = [{ path: absoluteTarget, depth: 0 }];
190
+ const discovered: string[] = []
191
+ const stack: Array<{ path: string; depth: number }> = [{ path: absoluteTarget, depth: 0 }]
222
192
 
223
193
  while (stack.length > 0) {
224
- const current = stack.pop();
194
+ const current = stack.pop()
225
195
  if (!current || current.depth > maxDepth) {
226
- continue;
196
+ continue
227
197
  }
228
198
 
229
- const entries = readdirSync(current.path, { withFileTypes: true });
199
+ const entries = readdirSync(current.path, { withFileTypes: true })
230
200
  for (const entry of entries) {
231
- const fullPath = resolve(current.path, entry.name);
201
+ const fullPath = resolve(current.path, entry.name)
232
202
  if (entry.isDirectory()) {
233
- stack.push({ path: fullPath, depth: current.depth + 1 });
234
- continue;
203
+ stack.push({ path: fullPath, depth: current.depth + 1 })
204
+ continue
235
205
  }
236
206
 
237
207
  if (entry.isFile() && extname(entry.name) === ".sol") {
238
- discovered.push(fullPath);
208
+ discovered.push(fullPath)
239
209
  }
240
210
  }
241
211
  }
242
212
 
243
- return discovered;
213
+ return discovered
244
214
  }
245
215
 
246
216
  function lineNumberAt(content: string, index: number): number {
247
217
  if (index <= 0) {
248
- return 1;
218
+ return 1
249
219
  }
250
220
 
251
- let line = 1;
221
+ let line = 1
252
222
  for (let i = 0; i < index && i < content.length; i += 1) {
253
223
  if (content[i] === "\n") {
254
- line += 1;
224
+ line += 1
255
225
  }
256
226
  }
257
- return line;
227
+ return line
258
228
  }
259
229
 
260
230
  function lineWindow(content: string, index: number): [number, number] {
261
- const linesCount = content.split("\n").length;
262
- const line = lineNumberAt(content, index);
263
- const start = Math.max(1, line - 5);
264
- const end = Math.min(linesCount, line + 5);
265
- return [start, end];
231
+ const linesCount = content.split("\n").length
232
+ const line = lineNumberAt(content, index)
233
+ const start = Math.max(1, line - 5)
234
+ const end = Math.min(linesCount, line + 5)
235
+ return [start, end]
266
236
  }
267
237
 
268
- function findMatches(file: string, patterns: BuiltinPattern[]): Match[] {
269
- const content = readFileSync(file, "utf8");
270
- const matches: Match[] = [];
238
+ function findMatches(file: string, patterns: LoadedPattern[]): Match[] {
239
+ const content = readFileSync(file, "utf8")
240
+ const matches: Match[] = []
271
241
 
272
242
  for (const pattern of patterns) {
273
- const regex = new RegExp(pattern.regex.source, pattern.regex.flags.includes("g") ? pattern.regex.flags : `${pattern.regex.flags}g`);
243
+ const regex = new RegExp(
244
+ pattern.regex.source,
245
+ pattern.regex.flags.includes("g") ? pattern.regex.flags : `${pattern.regex.flags}g`,
246
+ )
274
247
  for (const found of content.matchAll(regex)) {
275
- const index = found.index ?? 0;
248
+ const index = found.index ?? 0
276
249
  matches.push({
277
250
  pattern: pattern.name,
278
251
  severity: pattern.severity,
@@ -280,63 +253,70 @@ function findMatches(file: string, patterns: BuiltinPattern[]): Match[] {
280
253
  lines: lineWindow(content, index),
281
254
  description: pattern.description,
282
255
  exploitReference: pattern.exploitReference,
283
- patternSource: pattern.source ?? "builtin",
256
+ patternSource: pattern.source ?? "skill",
284
257
  category: pattern.category,
285
- });
258
+ })
286
259
  }
287
260
  }
288
261
 
289
- return matches;
262
+ return matches
290
263
  }
291
264
 
292
265
  function selectPatterns(
293
- availablePatterns: BuiltinPattern[],
294
- categories?: string[]
295
- ): BuiltinPattern[] {
266
+ availablePatterns: LoadedPattern[],
267
+ categories?: string[],
268
+ ): LoadedPattern[] {
296
269
  if (!categories || categories.length === 0) {
297
- return availablePatterns;
270
+ return availablePatterns
298
271
  }
299
272
 
300
- const set = new Set(categories);
301
- return availablePatterns.filter((pattern) => set.has(pattern.category));
273
+ const set = new Set(categories)
274
+ return availablePatterns.filter((pattern) => set.has(pattern.category))
302
275
  }
303
276
 
304
277
  export async function executePatternCheck(
305
278
  args: PatternCheckArgs,
306
279
  context: ToolContext,
307
- deps: PatternCheckDependencies = {}
280
+ deps: PatternCheckDependencies = {},
308
281
  ): Promise<PatternCheckResult> {
309
282
  const dependencies: Required<PatternCheckDependencies> = {
310
283
  loadIndexFn: loadIndex,
311
284
  searchIndexFn: searchIndex,
312
285
  ...deps,
313
- };
286
+ }
314
287
 
315
- const startedAt = Date.now();
316
- context.metadata({ title: `Pattern check: ${args.target}` });
288
+ const startedAt = Date.now()
289
+ context.metadata({ title: `Pattern check: ${args.target}` })
317
290
 
318
- const skillsDir = join(dirname(dirname(__dirname)), "skills");
319
- const yamlPatterns = loadPatternPacks(join(skillsDir, "patterns"));
320
- const skillDetectionRules = extractDetectionRulesFromSkills(skillsDir);
291
+ const skillsDir = join(dirname(dirname(__dirname)), "skills")
292
+ const skillDetectionRules = extractDetectionRulesFromSkills(skillsDir)
321
293
 
322
- const allPatterns: BuiltinPattern[] = [
323
- ...BUILTIN_PATTERNS.map((pattern) => ({ ...pattern, source: "builtin" as const })),
324
- ...normalizePatternDefinitions(yamlPatterns, "yaml"),
294
+ const allPatterns: LoadedPattern[] = [
325
295
  ...normalizePatternDefinitions(skillDetectionRules, "skill"),
326
- ];
296
+ ]
327
297
 
328
- const selectedPatterns = selectPatterns(allPatterns, args.patterns);
329
- const solidityFiles = collectSolidityFiles(args.target);
298
+ const selectedPatterns = selectPatterns(allPatterns, args.patterns)
299
+ const solidityFiles = collectSolidityFiles(args.target)
330
300
  if (solidityFiles.length === 0) {
331
- throw new Error(`No Solidity files found for target: ${args.target}`);
301
+ return {
302
+ success: false,
303
+ error: `No Solidity files found for target: ${args.target}`,
304
+ matches: [],
305
+ summary: { total: 0, bySeverity: {}, byCategory: {} },
306
+ sources: [],
307
+ patternsChecked: selectedPatterns.length,
308
+ executionTime: Date.now() - startedAt,
309
+ target: args.target,
310
+ patternVersion: PATTERN_PACK_VERSION,
311
+ }
332
312
  }
333
313
 
334
- const sourceMatches: Match[] = [];
314
+ const sourceMatches: Match[] = []
335
315
  for (const solidityFile of solidityFiles) {
336
316
  if (context.abort.aborted) {
337
- throw new Error("pattern check aborted");
317
+ throw new Error("pattern check aborted")
338
318
  }
339
- sourceMatches.push(...findMatches(solidityFile, selectedPatterns));
319
+ sourceMatches.push(...findMatches(solidityFile, selectedPatterns))
340
320
  }
341
321
 
342
322
  const sources: MatchSource[] = [
@@ -344,27 +324,42 @@ export async function executePatternCheck(
344
324
  source: "pattern-db",
345
325
  matches: sourceMatches,
346
326
  },
347
- ];
348
-
349
- if (args.include_scvd === true) {
350
- try {
351
- const scvdMatches = await collectScvdMatches(sourceMatches, dependencies);
352
- if (scvdMatches.length > 0) {
353
- sources.push({
354
- source: "scvd",
355
- matches: scvdMatches,
356
- });
357
- }
358
- } catch (_e) { /* non-critical: SCVD enrichment is best-effort */ }
359
- }
327
+ ]
328
+
329
+ if (args.include_scvd === true) {
330
+ try {
331
+ const scvdMatches = await collectScvdMatches(sourceMatches, dependencies)
332
+ if (scvdMatches.length > 0) {
333
+ sources.push({
334
+ source: "scvd",
335
+ matches: scvdMatches,
336
+ })
337
+ }
338
+ } catch (_e) {
339
+ logger.debug("SCVD enrichment failed, continuing without SCVD matches")
340
+ }
341
+ }
342
+
343
+ const allMatches = sources.flatMap((s) => s.matches)
344
+ const bySeverity: Record<string, number> = {}
345
+ const byCategory: Record<string, number> = {}
346
+ for (const m of allMatches) {
347
+ bySeverity[m.severity] = (bySeverity[m.severity] ?? 0) + 1
348
+ if (m.category) {
349
+ byCategory[m.category] = (byCategory[m.category] ?? 0) + 1
350
+ }
351
+ }
360
352
 
361
353
  return {
354
+ success: true,
355
+ matches: allMatches,
356
+ summary: { total: allMatches.length, bySeverity, byCategory },
362
357
  sources,
363
358
  patternsChecked: selectedPatterns.length,
364
359
  executionTime: Date.now() - startedAt,
365
360
  target: args.target,
366
361
  patternVersion: PATTERN_PACK_VERSION,
367
- };
362
+ }
368
363
  }
369
364
 
370
365
  export const patternCheckerTool = tool({
@@ -375,7 +370,7 @@ export const patternCheckerTool = tool({
375
370
  include_scvd: tool.schema.boolean().default(true),
376
371
  },
377
372
  async execute(args, context) {
378
- const result = await executePatternCheck(args, context);
379
- return JSON.stringify(result);
373
+ const result = await executePatternCheck(args, context)
374
+ return JSON.stringify(result)
380
375
  },
381
- });
376
+ })