solidity-argus 0.2.0 → 0.3.2

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 (169) hide show
  1. package/AGENTS.md +3 -3
  2. package/README.md +93 -37
  3. package/package.json +34 -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 +34 -7
  75. package/src/agents/pythia-prompt.ts +13 -4
  76. package/src/agents/scribe-prompt.ts +20 -2
  77. package/src/agents/sentinel-prompt.ts +45 -5
  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 +6 -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/system-prompt-hook.ts +18 -1
  114. package/src/hooks/tool-tracking-hook.ts +110 -51
  115. package/src/hooks/types.ts +2 -1
  116. package/src/index.ts +24 -37
  117. package/src/knowledge/retry.ts +22 -22
  118. package/src/knowledge/scvd-client.ts +88 -95
  119. package/src/knowledge/scvd-errors.ts +35 -35
  120. package/src/knowledge/scvd-index.ts +78 -80
  121. package/src/knowledge/scvd-sync.ts +106 -101
  122. package/src/managers/index.ts +1 -1
  123. package/src/managers/types.ts +19 -14
  124. package/src/plugin-interface.ts +7 -9
  125. package/src/shared/binary-utils.ts +44 -35
  126. package/src/shared/deep-merge.ts +55 -36
  127. package/src/shared/file-utils.ts +21 -19
  128. package/src/shared/index.ts +11 -5
  129. package/src/shared/jsonc-parser.ts +123 -28
  130. package/src/shared/logger.ts +16 -3
  131. package/src/shared/project-utils.ts +30 -0
  132. package/src/skills/analysis/cluster.ts +414 -0
  133. package/src/skills/analysis/gates.ts +227 -0
  134. package/src/skills/analysis/index.ts +33 -0
  135. package/src/skills/analysis/normalize.ts +217 -0
  136. package/src/skills/analysis/similarity.ts +224 -0
  137. package/src/skills/argus-skill-resolver.ts +17 -6
  138. package/src/skills/skill-schema.ts +11 -10
  139. package/src/solodit-lifecycle.ts +203 -0
  140. package/src/state/audit-state.ts +8 -8
  141. package/src/state/finding-store.ts +68 -55
  142. package/src/state/types.ts +88 -67
  143. package/src/tools/argus-skill-load-tool.ts +12 -7
  144. package/src/tools/contract-analyzer-tool.ts +142 -77
  145. package/src/tools/forge-coverage-tool.ts +226 -0
  146. package/src/tools/forge-fuzz-tool.ts +127 -127
  147. package/src/tools/forge-test-tool.ts +201 -158
  148. package/src/tools/gas-analysis-tool.ts +264 -0
  149. package/src/tools/pattern-checker-tool.ts +203 -191
  150. package/src/tools/pattern-loader.ts +5 -111
  151. package/src/tools/pattern-schema.ts +3 -0
  152. package/src/tools/proxy-detection-tool.ts +224 -0
  153. package/src/tools/report-generator-tool.ts +305 -206
  154. package/src/tools/slither-tool.ts +266 -218
  155. package/src/tools/solodit-search-tool.ts +235 -119
  156. package/src/tools/sync-knowledge-tool.ts +7 -11
  157. package/src/utils/audit-artifact-detector.ts +28 -29
  158. package/src/utils/dependency-scanner.ts +37 -37
  159. package/src/utils/project-detector.ts +111 -124
  160. package/src/utils/solidity-parser.ts +175 -75
  161. package/skills/patterns/access-control.yaml +0 -31
  162. package/skills/patterns/erc4626.yaml +0 -29
  163. package/skills/patterns/flash-loan.yaml +0 -20
  164. package/skills/patterns/oracle.yaml +0 -30
  165. package/skills/patterns/proxy.yaml +0 -30
  166. package/skills/patterns/reentrancy.yaml +0 -30
  167. package/skills/patterns/signature.yaml +0 -31
  168. package/src/hooks/event-hook-v2.ts +0 -99
  169. package/src/state/plugin-state.ts +0 -14
@@ -0,0 +1,203 @@
1
+ import { withRetry } from "./knowledge/retry"
2
+ import { createLogger } from "./shared/logger"
3
+ import { checkSoloditHealth } from "./utils/solodit-health"
4
+
5
+ interface SoloditChildProcess {
6
+ kill(signal?: number): void
7
+ unref(): void
8
+ readonly exited: Promise<number | null>
9
+ }
10
+
11
+ let soloditChild: SoloditChildProcess | null = null
12
+ let monitorTimer: ReturnType<typeof setInterval> | null = null
13
+ let isRestarting = false
14
+
15
+ /** Whether the Solodit MCP server is currently available for tool calls. */
16
+ export let soloditAvailable = false
17
+
18
+ const DEFAULT_RESTART_SETTLE_MS = 2_000
19
+ const DEFAULT_RETRY_BASE_DELAY_MS = 1_000
20
+ const HEALTH_CHECK_INTERVAL_MS = 60_000
21
+
22
+ let restartSettleMs = DEFAULT_RESTART_SETTLE_MS
23
+ let retryBaseDelayMs = DEFAULT_RETRY_BASE_DELAY_MS
24
+
25
+ const defaultSpawnFn = (port: number): SoloditChildProcess =>
26
+ Bun.spawn(["npx", "-y", "@lyuboslavlyubenov/solodit-mcp"], {
27
+ stdin: "ignore",
28
+ stdout: "ignore",
29
+ stderr: "ignore",
30
+ env: { ...process.env, PORT: String(port) },
31
+ })
32
+
33
+ let spawnFn: (port: number) => SoloditChildProcess = defaultSpawnFn
34
+
35
+ /** Override internal timing and spawn for testing. */
36
+ export function _setTestConfig(config: {
37
+ restartSettleMs?: number
38
+ retryBaseDelayMs?: number
39
+ spawnFn?: (port: number) => SoloditChildProcess
40
+ }): void {
41
+ if (config.restartSettleMs !== undefined) restartSettleMs = config.restartSettleMs
42
+ if (config.retryBaseDelayMs !== undefined) retryBaseDelayMs = config.retryBaseDelayMs
43
+ if (config.spawnFn !== undefined) spawnFn = config.spawnFn
44
+ }
45
+
46
+ function spawnSoloditChild(port: number): SoloditChildProcess {
47
+ const child = spawnFn(port)
48
+ child.unref()
49
+ return child
50
+ }
51
+
52
+ function trackChildExit(child: SoloditChildProcess): void {
53
+ const logger = createLogger()
54
+ child.exited.then((code) => {
55
+ if (code !== 0 && code !== null) {
56
+ logger.warn(`Solodit MCP exited with code ${code}`)
57
+ }
58
+ if (soloditChild === child) {
59
+ soloditChild = null
60
+ }
61
+ })
62
+ }
63
+
64
+ async function restartSoloditMcp(port: number): Promise<boolean> {
65
+ const logger = createLogger()
66
+
67
+ if (soloditChild) {
68
+ try {
69
+ soloditChild.kill()
70
+ } catch {
71
+ logger.debug("Solodit MCP process already dead")
72
+ }
73
+ soloditChild = null
74
+ }
75
+
76
+ try {
77
+ soloditChild = spawnSoloditChild(port)
78
+ trackChildExit(soloditChild)
79
+ } catch (err) {
80
+ logger.warn("Failed to spawn Solodit MCP:", err)
81
+ return false
82
+ }
83
+
84
+ await Bun.sleep(restartSettleMs)
85
+
86
+ const result = await withRetry(
87
+ async () => {
88
+ const health = await checkSoloditHealth(port, true)
89
+ if (!health.reachable) throw new Error("Solodit not reachable after restart")
90
+ return health
91
+ },
92
+ {
93
+ maxAttempts: 3,
94
+ baseDelayMs: retryBaseDelayMs,
95
+ shouldRetry: () => true,
96
+ onRetry: (attempt) => logger.debug(`Solodit restart health retry ${attempt}`),
97
+ },
98
+ )
99
+
100
+ if (result.success) {
101
+ soloditAvailable = true
102
+ logger.info("Solodit MCP restarted successfully")
103
+ return true
104
+ }
105
+
106
+ logger.warn("Solodit MCP restart failed — will retry next cycle")
107
+ return false
108
+ }
109
+
110
+ export async function _runMonitoringCycle(port: number): Promise<void> {
111
+ if (isRestarting) return
112
+ const logger = createLogger()
113
+ try {
114
+ const health = await checkSoloditHealth(port, true)
115
+ if (health.reachable) {
116
+ if (!soloditAvailable) {
117
+ soloditAvailable = true
118
+ logger.info("Solodit MCP recovered — now available")
119
+ }
120
+ } else if (soloditAvailable) {
121
+ soloditAvailable = false
122
+ logger.warn("Solodit MCP health check failed, attempting restart...")
123
+ isRestarting = true
124
+ try {
125
+ await restartSoloditMcp(port)
126
+ } finally {
127
+ isRestarting = false
128
+ }
129
+ }
130
+ } catch {
131
+ logger.debug("Monitoring cycle encountered an error")
132
+ }
133
+ }
134
+
135
+ function startMonitoring(port: number): void {
136
+ if (monitorTimer) return
137
+ monitorTimer = setInterval(() => {
138
+ _runMonitoringCycle(port)
139
+ }, HEALTH_CHECK_INTERVAL_MS)
140
+ if (monitorTimer && typeof (monitorTimer as NodeJS.Timeout).unref === "function") {
141
+ ;(monitorTimer as NodeJS.Timeout).unref()
142
+ }
143
+ }
144
+
145
+ /** Stop periodic health monitoring. */
146
+ export function stopSoloditMonitoring(): void {
147
+ if (monitorTimer) {
148
+ clearInterval(monitorTimer)
149
+ monitorTimer = null
150
+ }
151
+ }
152
+
153
+ /** Reset all Solodit state — for testing only. */
154
+ export function _resetSoloditState(): void {
155
+ stopSoloditMonitoring()
156
+ soloditAvailable = false
157
+ isRestarting = false
158
+ restartSettleMs = DEFAULT_RESTART_SETTLE_MS
159
+ retryBaseDelayMs = DEFAULT_RETRY_BASE_DELAY_MS
160
+ spawnFn = defaultSpawnFn
161
+ if (soloditChild) {
162
+ try {
163
+ soloditChild.kill()
164
+ } catch {
165
+ createLogger().debug("Failed to kill Solodit MCP on reset")
166
+ }
167
+ soloditChild = null
168
+ }
169
+ }
170
+
171
+ export async function startSoloditMcp(port: number): Promise<void> {
172
+ const logger = createLogger()
173
+
174
+ const health = await checkSoloditHealth(port, true)
175
+ if (health.reachable) {
176
+ logger.debug(`Solodit MCP already running on port ${port} — skipping spawn`)
177
+ soloditAvailable = true
178
+ startMonitoring(port)
179
+ return
180
+ }
181
+
182
+ soloditChild = spawnSoloditChild(port)
183
+ trackChildExit(soloditChild)
184
+
185
+ const deadline = AbortSignal.timeout(5000)
186
+ const delays = [1000, 2000]
187
+ for (const delay of delays) {
188
+ if (deadline.aborted) break
189
+ await Bun.sleep(delay)
190
+ if (deadline.aborted) break
191
+ const healthResult = await checkSoloditHealth(port, true)
192
+ if (healthResult.reachable) {
193
+ soloditAvailable = true
194
+ logger.debug(`Solodit MCP healthy on port ${port}`)
195
+ break
196
+ }
197
+ }
198
+ if (!soloditAvailable) {
199
+ logger.warn(`Solodit MCP not reachable after startup — monitoring will retry`)
200
+ }
201
+
202
+ startMonitoring(port)
203
+ }
@@ -1,14 +1,14 @@
1
- import { randomUUID } from "crypto";
2
- import type { AuditState } from "./types";
3
- import { createFindingStore, type FindingStore } from "./finding-store";
1
+ import { randomUUID } from "node:crypto"
2
+ import { createFindingStore, type FindingStore } from "./finding-store"
3
+ import type { AuditState } from "./types"
4
4
 
5
5
  /**
6
6
  * Factory function to create a new audit state instance (NOT singleton)
7
7
  * Each call creates a fresh state with a unique session ID
8
8
  */
9
9
  export function createAuditState(projectDir: string): {
10
- state: AuditState;
11
- store: FindingStore;
10
+ state: AuditState
11
+ store: FindingStore
12
12
  } {
13
13
  const state: AuditState = {
14
14
  sessionId: randomUUID(),
@@ -21,9 +21,9 @@ export function createAuditState(projectDir: string): {
21
21
  startTime: Date.now(),
22
22
  soloditResults: [],
23
23
  fuzzCounterexamples: [],
24
- };
24
+ }
25
25
 
26
- const store = createFindingStore(state);
26
+ const store = createFindingStore(state)
27
27
 
28
- return { state, store };
28
+ return { state, store }
29
29
  }
@@ -1,84 +1,98 @@
1
- import type { Finding, FindingSeverity, AuditState } from "./types";
2
- import { createHash } from "crypto";
1
+ import { createHash } from "node:crypto"
2
+ import type { AuditState, Finding, FindingSeverity } from "./types"
3
3
 
4
4
  export interface FindingStore {
5
- addFinding(finding: Omit<Finding, "id">): Finding;
6
- getFindings(filter?: {
7
- severity?: FindingSeverity;
8
- source?: Finding["source"];
9
- }): Finding[];
10
- hasFinding(check: string, file: string, lines: [number, number]): boolean;
11
- serialize(): string;
5
+ addFinding(finding: Omit<Finding, "id">): Finding
6
+ getFindings(filter?: { severity?: FindingSeverity; source?: Finding["source"] }): Finding[]
7
+ hasFinding(check: string, file: string, lines: [number, number]): boolean
8
+ serialize(): string
12
9
  }
13
10
 
14
11
  /**
15
12
  * Creates a finding store with deduplication by check+file+lines
16
13
  * Deduplication key: `${check}:${file}:${lines[0]}-${lines[1]}`
17
14
  */
15
+ function isValidHydrationFinding(f: unknown): f is Finding {
16
+ if (typeof f !== "object" || f === null) return false
17
+ const obj = f as Record<string, unknown>
18
+ return (
19
+ typeof obj.check === "string" &&
20
+ obj.check.length > 0 &&
21
+ typeof obj.file === "string" &&
22
+ obj.file.length > 0 &&
23
+ Array.isArray(obj.lines) &&
24
+ obj.lines.length === 2 &&
25
+ typeof obj.lines[0] === "number" &&
26
+ typeof obj.lines[1] === "number"
27
+ )
28
+ }
29
+
18
30
  export function createFindingStore(state: AuditState): FindingStore {
19
- const findingMap = new Map<string, Finding>();
20
-
21
- function generateId(
22
- check: string,
23
- file: string,
24
- lines: [number, number]
25
- ): string {
26
- const key = `${check}:${file}:${lines[0]}-${lines[1]}`;
31
+ const findingMap = new Map<string, Finding>()
32
+
33
+ function generateId(check: string, file: string, lines: [number, number]): string {
34
+ const key = `${check}:${file}:${lines[0]}-${lines[1]}`
27
35
  // Use deterministic hash for stable IDs
28
- return createHash("sha256").update(key).digest("hex").substring(0, 16);
36
+ return createHash("sha256").update(key).digest("hex").substring(0, 16)
37
+ }
38
+
39
+ // Hydrate findingMap from persisted state.findings
40
+ for (const f of state.findings) {
41
+ if (!isValidHydrationFinding(f)) continue
42
+ const id = generateId(f.check, f.file, f.lines)
43
+ if (!findingMap.has(id)) {
44
+ findingMap.set(id, { ...f, id })
45
+ }
29
46
  }
30
47
 
31
48
  function addFinding(finding: Omit<Finding, "id">): Finding {
32
- const id = generateId(finding.check, finding.file, finding.lines);
49
+ const id = generateId(finding.check, finding.file, finding.lines)
33
50
 
34
51
  // Check if finding already exists (deduplication)
35
- if (findingMap.has(id)) {
36
- return findingMap.get(id)!;
52
+ const existing = findingMap.get(id)
53
+ if (existing) {
54
+ return existing
37
55
  }
38
56
 
39
57
  const newFinding: Finding = {
40
58
  ...finding,
41
59
  id,
42
- };
60
+ }
43
61
 
44
- findingMap.set(id, newFinding);
45
- state.findings.push(newFinding);
62
+ findingMap.set(id, newFinding)
63
+ state.findings.push(newFinding)
46
64
 
47
- return newFinding;
65
+ return newFinding
48
66
  }
49
67
 
50
68
  function getFindings(filter?: {
51
- severity?: FindingSeverity;
52
- source?: Finding["source"];
69
+ severity?: FindingSeverity
70
+ source?: Finding["source"]
53
71
  }): Finding[] {
54
72
  if (!filter) {
55
- return Array.from(findingMap.values());
73
+ return Array.from(findingMap.values())
56
74
  }
57
75
 
58
76
  return Array.from(findingMap.values()).filter((finding) => {
59
77
  if (filter.severity && finding.severity !== filter.severity) {
60
- return false;
78
+ return false
61
79
  }
62
80
  if (filter.source && finding.source !== filter.source) {
63
- return false;
81
+ return false
64
82
  }
65
- return true;
66
- });
83
+ return true
84
+ })
67
85
  }
68
86
 
69
- function hasFinding(
70
- check: string,
71
- file: string,
72
- lines: [number, number]
73
- ): boolean {
74
- const id = generateId(check, file, lines);
75
- return findingMap.has(id);
87
+ function hasFinding(check: string, file: string, lines: [number, number]): boolean {
88
+ const id = generateId(check, file, lines)
89
+ return findingMap.has(id)
76
90
  }
77
91
 
78
92
  function serialize(): string {
79
- const findings = Array.from(findingMap.values());
80
- const contractCount = state.contractsReviewed.length;
81
- const findingCount = findings.length;
93
+ const findings = Array.from(findingMap.values())
94
+ const contractCount = state.contractsReviewed.length
95
+ const findingCount = findings.length
82
96
 
83
97
  // Count by severity
84
98
  const severityCounts: Record<FindingSeverity, number> = {
@@ -87,34 +101,33 @@ export function createFindingStore(state: AuditState): FindingStore {
87
101
  Medium: 0,
88
102
  Low: 0,
89
103
  Informational: 0,
90
- };
104
+ }
91
105
 
92
106
  findings.forEach((finding) => {
93
- severityCounts[finding.severity]++;
94
- });
107
+ severityCounts[finding.severity]++
108
+ })
95
109
 
96
110
  // Build severity string
97
- const severityParts: string[] = [];
111
+ const severityParts: string[] = []
98
112
  if (severityCounts.Critical > 0) {
99
- severityParts.push(`${severityCounts.Critical} Critical`);
113
+ severityParts.push(`${severityCounts.Critical} Critical`)
100
114
  }
101
115
  if (severityCounts.High > 0) {
102
- severityParts.push(`${severityCounts.High} High`);
116
+ severityParts.push(`${severityCounts.High} High`)
103
117
  }
104
118
  if (severityCounts.Medium > 0) {
105
- severityParts.push(`${severityCounts.Medium} Medium`);
119
+ severityParts.push(`${severityCounts.Medium} Medium`)
106
120
  }
107
121
  if (severityCounts.Low > 0) {
108
- severityParts.push(`${severityCounts.Low} Low`);
122
+ severityParts.push(`${severityCounts.Low} Low`)
109
123
  }
110
124
  if (severityCounts.Informational > 0) {
111
- severityParts.push(`${severityCounts.Informational} Informational`);
125
+ severityParts.push(`${severityCounts.Informational} Informational`)
112
126
  }
113
127
 
114
- const severityStr =
115
- severityParts.length > 0 ? ` (${severityParts.join(", ")})` : "";
128
+ const severityStr = severityParts.length > 0 ? ` (${severityParts.join(", ")})` : ""
116
129
 
117
- return `Contracts: ${contractCount}, Findings: ${findingCount}${severityStr}, Phase: ${state.currentPhase}`;
130
+ return `Contracts: ${contractCount}, Findings: ${findingCount}${severityStr}, Phase: ${state.currentPhase}`
118
131
  }
119
132
 
120
133
  return {
@@ -122,5 +135,5 @@ export function createFindingStore(state: AuditState): FindingStore {
122
135
  getFindings,
123
136
  hasFinding,
124
137
  serialize,
125
- };
138
+ }
126
139
  }
@@ -1,92 +1,113 @@
1
- export type FindingSeverity = "Critical" | "High" | "Medium" | "Low" | "Informational";
2
- export type AuditPhase = "reconnaissance" | "scanning" | "manual-review" | "attack-surface" | "research" | "testing" | "reporting" | "complete";
1
+ export type FindingSeverity = "Critical" | "High" | "Medium" | "Low" | "Informational"
2
+ export type AuditPhase =
3
+ | "reconnaissance"
4
+ | "scanning"
5
+ | "manual-review"
6
+ | "attack-surface"
7
+ | "research"
8
+ | "testing"
9
+ | "reporting"
10
+ | "complete"
3
11
 
4
12
  export interface Finding {
5
- id: string; // unique hash: check+file+lines
6
- check: string; // detector name e.g. "reentrancy-eth"
7
- severity: FindingSeverity;
8
- confidence: "High" | "Medium" | "Low";
9
- description: string;
10
- file: string; // relative file path
11
- lines: [number, number]; // [start, end]
12
- source: "slither" | "manual" | "pattern" | "scvd" | "solodit" | "fuzz";
13
- remediation?: string;
14
- exploitReference?: string;
13
+ id: string // unique hash: check+file+lines
14
+ check: string // detector name e.g. "reentrancy-eth"
15
+ severity: FindingSeverity
16
+ confidence: "High" | "Medium" | "Low"
17
+ description: string
18
+ file: string // relative file path
19
+ lines: [number, number] // [start, end]
20
+ source: "slither" | "manual" | "pattern" | "scvd" | "solodit" | "fuzz"
21
+ remediation?: string
22
+ exploitReference?: string
15
23
  provenance?: {
16
- timestamp: number;
17
- toolVersion?: string;
18
- phase?: AuditPhase;
19
- };
24
+ timestamp: number
25
+ toolVersion?: string
26
+ phase?: AuditPhase
27
+ }
20
28
  }
21
29
 
22
30
  export interface SoloditResult {
23
- query: string;
24
- timestamp: number;
25
- resultCount: number;
31
+ query: string
32
+ timestamp: number
33
+ resultCount: number
26
34
  topResults: Array<{
27
- title: string;
28
- severity: string;
29
- url: string;
30
- protocol: string;
31
- }>;
35
+ title: string
36
+ severity: string
37
+ url: string
38
+ protocol: string
39
+ }>
32
40
  }
33
41
 
34
42
  export interface FuzzCounterexample {
35
- testName: string;
36
- inputs: string[];
37
- revertReason?: string;
38
- runs: number;
39
- seed?: number;
40
- timestamp: number;
43
+ testName: string
44
+ inputs: string[]
45
+ revertReason?: string
46
+ runs: number
47
+ seed?: number
48
+ timestamp: number
41
49
  }
42
50
 
43
51
  export interface ContractProfile {
44
- name: string;
45
- filePath: string;
52
+ name: string
53
+ filePath: string
46
54
  functions: Array<{
47
- name: string;
48
- visibility: string;
49
- mutability: string;
50
- modifiers: string[];
51
- }>;
55
+ name: string
56
+ visibility: string
57
+ mutability: string
58
+ modifiers: string[]
59
+ }>
52
60
  stateVars: Array<{
53
- name: string;
54
- type: string;
55
- visibility: string;
56
- }>;
57
- inheritance: string[];
58
- accessControlPattern?: "ownable" | "access-control" | "custom" | "none";
59
- externalCalls: string[];
60
- riskIndicators: string[];
61
- error?: string;
61
+ name: string
62
+ type: string
63
+ visibility: string
64
+ }>
65
+ inheritance: string[]
66
+ accessControlPattern?: "ownable" | "access-control" | "custom" | "none"
67
+ externalCalls: string[]
68
+ riskIndicators: string[]
69
+ error?: string
62
70
  }
63
71
 
64
72
  export interface ToolExecution {
65
- tool: string;
66
- startTime: number;
67
- endTime?: number;
68
- success: boolean;
69
- findingsCount: number;
73
+ tool: string
74
+ startTime: number
75
+ endTime?: number
76
+ success: boolean
77
+ findingsCount: number
70
78
  }
71
79
 
72
80
  export interface AuditState {
73
- sessionId: string;
74
- projectDir: string;
75
- contractsReviewed: string[];
76
- findings: Finding[];
77
- toolsExecuted: ToolExecution[];
78
- currentPhase: AuditPhase;
79
- scope: string[];
80
- startTime: number;
81
- soloditResults?: SoloditResult[];
82
- fuzzCounterexamples?: FuzzCounterexample[];
83
- patternVersion?: string;
84
- skillsLoaded?: string[];
85
- unavailableTools?: string[];
81
+ sessionId: string
82
+ projectDir: string
83
+ contractsReviewed: string[]
84
+ findings: Finding[]
85
+ toolsExecuted: ToolExecution[]
86
+ currentPhase: AuditPhase
87
+ scope: string[]
88
+ startTime: number
89
+ soloditResults?: SoloditResult[]
90
+ fuzzCounterexamples?: FuzzCounterexample[]
91
+ patternVersion?: string
92
+ skillsLoaded?: string[]
93
+ unavailableTools?: string[]
94
+ reportGenerated?: boolean
95
+ knowledgeSynced?: { success: boolean; timestamp: number }
96
+ coverageReport?: {
97
+ files: Array<{
98
+ path: string
99
+ linesPct: number
100
+ statementsPct: number
101
+ branchesPct: number
102
+ functionsPct: number
103
+ }>
104
+ }
105
+ gasHotspots?: Array<{ contract: string; function: string; avgGas: number }>
106
+ proxyContracts?: Array<{ file: string; proxyType: string; indicators: string[] }>
86
107
  }
87
108
 
88
109
  export interface PersistentAuditState extends AuditState {
89
- savedAt: number;
90
- version: string;
91
- filePath: string;
110
+ savedAt: number
111
+ version: string
112
+ filePath: string
92
113
  }
@@ -1,6 +1,7 @@
1
- import { tool, type ToolContext } from "@opencode-ai/plugin"
1
+ import { type ToolContext, tool } from "@opencode-ai/plugin"
2
2
  import { loadArgusConfig } from "../config/loader"
3
3
  import type { ArgusConfig } from "../config/types"
4
+ import { resolveProjectDir } from "../shared/project-utils"
4
5
  import { normalizeSkillName, resolveArgusSkills } from "../skills/argus-skill-resolver"
5
6
 
6
7
  type ArgusSkillLoadArgs = {
@@ -15,9 +16,9 @@ type ArgusSkillLoadDependencies = {
15
16
  export async function executeArgusSkillLoad(
16
17
  args: ArgusSkillLoadArgs,
17
18
  context: ToolContext,
18
- deps: ArgusSkillLoadDependencies = {}
19
+ deps: ArgusSkillLoadDependencies = {},
19
20
  ): Promise<string> {
20
- const projectDir = context.directory ?? context.worktree ?? process.cwd()
21
+ const projectDir = resolveProjectDir(context)
21
22
  const loadConfig = deps.loadConfig ?? loadArgusConfig
22
23
  const resolveSkills = deps.resolveSkills ?? resolveArgusSkills
23
24
 
@@ -35,7 +36,7 @@ export async function executeArgusSkillLoad(
35
36
  if (!skill) {
36
37
  const available = Array.from(skills.keys()).sort().join(", ")
37
38
  throw new Error(
38
- `Argus skill "${args.name}" not found (normalized: "${normalizedName}"). Available Argus skills: ${available || "none"}`
39
+ `Argus skill "${args.name}" not found (normalized: "${normalizedName}"). Available Argus skills: ${available || "none"}`,
39
40
  )
40
41
  }
41
42
 
@@ -44,7 +45,8 @@ export async function executeArgusSkillLoad(
44
45
  if (skill.source_url) provenanceParts.push(skill.source_url)
45
46
  if (skill.imported_at) provenanceParts.push(`Imported: ${skill.imported_at}`)
46
47
 
47
- const provenanceLine = provenanceParts.length > 0 ? `[Provenance: ${provenanceParts.join(" | ")}]` : ""
48
+ const provenanceLine =
49
+ provenanceParts.length > 0 ? `[Provenance: ${provenanceParts.join(" | ")}]` : ""
48
50
 
49
51
  return [
50
52
  `## Argus Skill: ${skill.name} [Source: ${skill.source}]`,
@@ -61,11 +63,14 @@ export async function executeArgusSkillLoad(
61
63
  }
62
64
 
63
65
  export const argusSkillLoadTool = tool({
64
- description: "Load Argus security skill content with OMO-compatible discovery and native fallback.",
66
+ description:
67
+ "Load Argus security skill content with OMO-compatible discovery and native fallback.",
65
68
  args: {
66
69
  name: tool.schema
67
70
  .string()
68
- .describe("Skill name (e.g., reentrancy, oracle-manipulation, or vulnerability-patterns/reentrancy)."),
71
+ .describe(
72
+ "Skill name (e.g., reentrancy, oracle-manipulation, or vulnerability-patterns/reentrancy).",
73
+ ),
69
74
  },
70
75
  async execute(args, context) {
71
76
  return executeArgusSkillLoad(args, context)