solidity-argus 0.1.8 → 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.
- package/AGENTS.md +3 -3
- package/README.md +229 -13
- package/package.json +37 -8
- package/skills/INVENTORY.md +88 -57
- package/skills/README.md +72 -6
- package/skills/case-studies/beanstalk-governance/SKILL.md +52 -0
- package/skills/case-studies/bzx-flash-loan/SKILL.md +53 -0
- package/skills/case-studies/cream-finance/SKILL.md +52 -0
- package/skills/case-studies/curve-reentrancy/SKILL.md +52 -0
- package/skills/case-studies/dao-hack/SKILL.md +51 -0
- package/skills/case-studies/euler-finance/SKILL.md +52 -0
- package/skills/case-studies/harvest-finance/SKILL.md +52 -0
- package/skills/case-studies/level-finance/SKILL.md +51 -0
- package/skills/case-studies/mango-markets/SKILL.md +53 -0
- package/skills/case-studies/nomad-bridge/SKILL.md +51 -0
- package/skills/case-studies/parity-multisig/SKILL.md +55 -0
- package/skills/case-studies/poly-network/SKILL.md +51 -0
- package/skills/case-studies/rari-fuse/SKILL.md +51 -0
- package/skills/case-studies/ronin-bridge/SKILL.md +52 -0
- package/skills/case-studies/wormhole-bridge/SKILL.md +51 -0
- package/skills/checklists/cyfrin-defi-core/SKILL.md +3 -0
- package/skills/manifests/cyfrin.json +16 -0
- package/skills/manifests/defifofum.json +25 -0
- package/skills/manifests/kadenzipfel.json +48 -0
- package/skills/manifests/scvd.json +9 -0
- package/skills/manifests/smartbugs.json +9 -0
- package/skills/manifests/solodit.json +9 -0
- package/skills/manifests/sunweb3sec.json +9 -0
- package/skills/manifests/trailofbits.json +9 -0
- package/skills/methodology/audit-workflow/SKILL.md +3 -0
- package/skills/protocol-patterns/amm-dex/SKILL.md +3 -0
- package/skills/references/exploit-reference/SKILL.md +3 -0
- package/skills/vulnerability-patterns/access-control/SKILL.md +27 -0
- package/skills/vulnerability-patterns/arbitrary-storage-location/SKILL.md +13 -1
- package/skills/vulnerability-patterns/assert-violation/SKILL.md +8 -1
- package/skills/vulnerability-patterns/asserting-contract-from-code-size/SKILL.md +12 -1
- package/skills/vulnerability-patterns/authorization-txorigin/SKILL.md +8 -1
- package/skills/vulnerability-patterns/cross-chain-bridge-vulnerabilities/SKILL.md +217 -0
- package/skills/vulnerability-patterns/default-visibility/SKILL.md +13 -1
- package/skills/vulnerability-patterns/delegatecall-untrusted-callee/SKILL.md +8 -1
- package/skills/vulnerability-patterns/dos-gas-limit/SKILL.md +8 -1
- package/skills/vulnerability-patterns/dos-revert/SKILL.md +14 -1
- package/skills/vulnerability-patterns/erc4626-exchange-rate-manipulation/SKILL.md +64 -0
- package/skills/vulnerability-patterns/fee-on-transfer-tokens/SKILL.md +93 -0
- package/skills/vulnerability-patterns/flash-loan-attacks/SKILL.md +13 -0
- package/skills/vulnerability-patterns/floating-pragma/SKILL.md +8 -1
- package/skills/vulnerability-patterns/front-running-attacks/SKILL.md +209 -0
- package/skills/vulnerability-patterns/gas-optimization-patterns/SKILL.md +203 -0
- package/skills/vulnerability-patterns/governance-attacks/SKILL.md +208 -0
- package/skills/vulnerability-patterns/hash-collision/SKILL.md +8 -1
- package/skills/vulnerability-patterns/inadherence-to-standards/SKILL.md +12 -1
- package/skills/vulnerability-patterns/incorrect-constructor/SKILL.md +8 -1
- package/skills/vulnerability-patterns/incorrect-inheritance-order/SKILL.md +8 -1
- package/skills/vulnerability-patterns/insufficient-gas-griefing/SKILL.md +12 -1
- package/skills/vulnerability-patterns/lack-of-precision/SKILL.md +7 -1
- package/skills/vulnerability-patterns/logic-errors/SKILL.md +10 -0
- package/skills/vulnerability-patterns/missing-parameter-bounds/SKILL.md +44 -0
- package/skills/vulnerability-patterns/missing-protection-signature-replay/SKILL.md +17 -1
- package/skills/vulnerability-patterns/msgvalue-loop/SKILL.md +12 -1
- package/skills/vulnerability-patterns/off-by-one/SKILL.md +7 -1
- package/skills/vulnerability-patterns/oracle-manipulation/SKILL.md +22 -0
- package/skills/vulnerability-patterns/outdated-compiler-version/SKILL.md +8 -1
- package/skills/vulnerability-patterns/overflow-underflow/SKILL.md +11 -1
- package/skills/vulnerability-patterns/proxy-vulnerabilities/SKILL.md +209 -0
- package/skills/vulnerability-patterns/reentrancy/SKILL.md +22 -0
- package/skills/vulnerability-patterns/shadowing-state-variables/SKILL.md +8 -1
- package/skills/vulnerability-patterns/share-accounting-desynchronization/SKILL.md +44 -0
- package/skills/vulnerability-patterns/signature-malleability/SKILL.md +11 -1
- package/skills/vulnerability-patterns/stateful-parameter-update-drift/SKILL.md +44 -0
- package/skills/vulnerability-patterns/unbounded-return-data/SKILL.md +12 -1
- package/skills/vulnerability-patterns/unchecked-return-values/SKILL.md +13 -1
- package/skills/vulnerability-patterns/unencrypted-private-data-on-chain/SKILL.md +8 -1
- package/skills/vulnerability-patterns/unexpected-ecrecover-null-address/SKILL.md +8 -1
- package/skills/vulnerability-patterns/uninitialized-storage-pointer/SKILL.md +8 -1
- package/skills/vulnerability-patterns/unsafe-erc20-transfers/SKILL.md +132 -0
- package/skills/vulnerability-patterns/unsafe-low-level-call/SKILL.md +12 -1
- package/skills/vulnerability-patterns/unsecure-signatures/SKILL.md +12 -1
- package/skills/vulnerability-patterns/unsupported-opcodes/SKILL.md +11 -1
- package/skills/vulnerability-patterns/unused-variables/SKILL.md +8 -1
- package/skills/vulnerability-patterns/use-of-deprecated-functions/SKILL.md +8 -1
- package/skills/vulnerability-patterns/weak-sources-randomness/SKILL.md +8 -1
- package/skills/vulnerability-patterns/weird-tokens/SKILL.md +10 -0
- package/skills/vulnerability-patterns/zero-address-misconfiguration/SKILL.md +48 -0
- package/src/agents/argus-prompt.ts +27 -10
- package/src/agents/pythia-prompt.ts +7 -8
- package/src/agents/scribe-prompt.ts +10 -5
- package/src/agents/sentinel-prompt.ts +36 -7
- package/src/cli/cli-output.ts +16 -0
- package/src/cli/cli-program.ts +29 -22
- package/src/cli/commands/check-skills.ts +135 -0
- package/src/cli/commands/doctor.ts +303 -23
- package/src/cli/commands/init.ts +8 -6
- package/src/cli/commands/install.ts +10 -8
- package/src/cli/commands/lint-skills.ts +118 -0
- package/src/cli/index.ts +5 -5
- package/src/cli/tui-prompts.ts +4 -2
- package/src/cli/types.ts +3 -3
- package/src/config/index.ts +1 -1
- package/src/config/loader.ts +4 -6
- package/src/config/schema.ts +6 -5
- package/src/config/types.ts +2 -2
- package/src/constants/defaults.ts +2 -0
- package/src/create-hooks.ts +225 -29
- package/src/create-managers.ts +10 -8
- package/src/create-tools.ts +14 -8
- package/src/features/background-agent/background-manager.ts +93 -87
- package/src/features/background-agent/index.ts +1 -1
- package/src/features/context-monitor/context-monitor.ts +3 -3
- package/src/features/context-monitor/index.ts +2 -2
- package/src/features/error-recovery/session-recovery.ts +2 -4
- package/src/features/error-recovery/tool-error-recovery.ts +79 -19
- package/src/features/index.ts +5 -5
- package/src/features/persistent-state/audit-state-manager.ts +158 -52
- package/src/features/persistent-state/global-run-index.ts +38 -0
- package/src/features/persistent-state/index.ts +1 -1
- package/src/features/persistent-state/run-journal.ts +86 -0
- package/src/hooks/agent-tracker.ts +53 -0
- package/src/hooks/compaction-hook.ts +46 -37
- package/src/hooks/config-handler.ts +31 -11
- package/src/hooks/context-budget.ts +42 -0
- package/src/hooks/event-hook.ts +48 -23
- package/src/hooks/hook-system.ts +4 -4
- package/src/hooks/index.ts +5 -5
- package/src/hooks/knowledge-sync-hook.ts +19 -21
- package/src/hooks/recon-context-builder.ts +66 -0
- package/src/hooks/safe-create-hook.ts +9 -11
- package/src/hooks/system-prompt-hook.ts +128 -0
- package/src/hooks/tool-tracking-hook.ts +162 -29
- package/src/hooks/types.ts +2 -1
- package/src/index.ts +23 -13
- package/src/knowledge/retry.ts +53 -0
- package/src/knowledge/scvd-client.ts +103 -83
- package/src/knowledge/scvd-errors.ts +89 -0
- package/src/knowledge/scvd-index.ts +110 -62
- package/src/knowledge/scvd-sync.ts +223 -47
- package/src/knowledge/source-manifest.ts +102 -0
- package/src/managers/index.ts +1 -1
- package/src/managers/types.ts +19 -14
- package/src/plugin-interface.ts +19 -8
- package/src/shared/binary-utils.ts +44 -34
- package/src/shared/deep-merge.ts +55 -36
- package/src/shared/file-utils.ts +21 -19
- package/src/shared/index.ts +11 -5
- package/src/shared/jsonc-parser.ts +123 -28
- package/src/shared/logger.ts +91 -17
- package/src/shared/project-utils.ts +30 -0
- package/src/skills/analysis/cluster.ts +414 -0
- package/src/skills/analysis/gates.ts +227 -0
- package/src/skills/analysis/index.ts +33 -0
- package/src/skills/analysis/normalize.ts +217 -0
- package/src/skills/analysis/similarity.ts +224 -0
- package/src/skills/argus-skill-resolver.ts +237 -0
- package/src/skills/skill-schema.ts +99 -0
- package/src/solodit-lifecycle.ts +202 -0
- package/src/state/audit-state.ts +10 -8
- package/src/state/finding-store.ts +68 -55
- package/src/state/types.ts +96 -44
- package/src/tools/argus-skill-load-tool.ts +78 -0
- package/src/tools/contract-analyzer-tool.ts +60 -77
- package/src/tools/forge-coverage-tool.ts +226 -0
- package/src/tools/forge-fuzz-tool.ts +127 -127
- package/src/tools/forge-test-tool.ts +153 -157
- package/src/tools/gas-analysis-tool.ts +264 -0
- package/src/tools/pattern-checker-tool.ts +206 -167
- package/src/tools/pattern-loader.ts +77 -0
- package/src/tools/pattern-schema.ts +51 -0
- package/src/tools/proxy-detection-tool.ts +224 -0
- package/src/tools/report-generator-tool.ts +333 -142
- package/src/tools/slither-tool.ts +300 -210
- package/src/tools/solodit-search-tool.ts +255 -80
- package/src/tools/sync-knowledge-tool.ts +7 -11
- package/src/utils/audit-artifact-detector.ts +118 -0
- package/src/utils/dependency-scanner.ts +93 -0
- package/src/utils/project-detector.ts +175 -86
- package/src/utils/solidity-parser.ts +112 -67
- package/src/utils/solodit-health.ts +29 -0
- package/src/hooks/event-hook-v2.ts +0 -99
- package/src/state/plugin-state.ts +0 -14
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { AuditState, FindingSeverity } from "../state/types"
|
|
2
1
|
import type { FindingStore } from "../state/finding-store"
|
|
3
2
|
import { createFindingStore } from "../state/finding-store"
|
|
3
|
+
import type { AuditState, FindingSeverity, FuzzCounterexample, SoloditResult } from "../state/types"
|
|
4
4
|
|
|
5
5
|
type ToolHookInput = {
|
|
6
6
|
tool: string
|
|
@@ -8,6 +8,11 @@ type ToolHookInput = {
|
|
|
8
8
|
result: string
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
type ToolExecutionMetadata = {
|
|
12
|
+
tool: string
|
|
13
|
+
findingsCount: number
|
|
14
|
+
}
|
|
15
|
+
|
|
11
16
|
const VALID_SEVERITIES: ReadonlySet<string> = new Set([
|
|
12
17
|
"Critical",
|
|
13
18
|
"High",
|
|
@@ -16,11 +21,7 @@ const VALID_SEVERITIES: ReadonlySet<string> = new Set([
|
|
|
16
21
|
"Informational",
|
|
17
22
|
])
|
|
18
23
|
|
|
19
|
-
const VALID_CONFIDENCES: ReadonlySet<string> = new Set([
|
|
20
|
-
"High",
|
|
21
|
-
"Medium",
|
|
22
|
-
"Low",
|
|
23
|
-
])
|
|
24
|
+
const VALID_CONFIDENCES: ReadonlySet<string> = new Set(["High", "Medium", "Low"])
|
|
24
25
|
|
|
25
26
|
function toSeverity(value: unknown): FindingSeverity {
|
|
26
27
|
if (typeof value === "string" && VALID_SEVERITIES.has(value)) {
|
|
@@ -55,10 +56,7 @@ function toRecord(value: unknown): Record<string, unknown> | undefined {
|
|
|
55
56
|
return undefined
|
|
56
57
|
}
|
|
57
58
|
|
|
58
|
-
function processSlitherResult(
|
|
59
|
-
parsed: Record<string, unknown>,
|
|
60
|
-
store: FindingStore
|
|
61
|
-
): number {
|
|
59
|
+
function processSlitherResult(parsed: Record<string, unknown>, store: FindingStore): number {
|
|
62
60
|
const findings = parsed.findings
|
|
63
61
|
if (!Array.isArray(findings)) return 0
|
|
64
62
|
|
|
@@ -96,10 +94,7 @@ function processSlitherResult(
|
|
|
96
94
|
return count
|
|
97
95
|
}
|
|
98
96
|
|
|
99
|
-
function processPatternResult(
|
|
100
|
-
parsed: Record<string, unknown>,
|
|
101
|
-
store: FindingStore
|
|
102
|
-
): number {
|
|
97
|
+
function processPatternResult(parsed: Record<string, unknown>, store: FindingStore): number {
|
|
103
98
|
const sources = parsed.sources
|
|
104
99
|
if (!Array.isArray(sources)) return 0
|
|
105
100
|
|
|
@@ -145,10 +140,7 @@ function processPatternResult(
|
|
|
145
140
|
return count
|
|
146
141
|
}
|
|
147
142
|
|
|
148
|
-
function processContractAnalyzerResult(
|
|
149
|
-
parsed: Record<string, unknown>,
|
|
150
|
-
state: AuditState
|
|
151
|
-
): void {
|
|
143
|
+
function processContractAnalyzerResult(parsed: Record<string, unknown>, state: AuditState): void {
|
|
152
144
|
// Handle direct ContractProfile format (actual tool output)
|
|
153
145
|
if (typeof parsed.filePath === "string") {
|
|
154
146
|
if (!state.contractsReviewed.includes(parsed.filePath)) {
|
|
@@ -166,16 +158,82 @@ function processContractAnalyzerResult(
|
|
|
166
158
|
}
|
|
167
159
|
}
|
|
168
160
|
|
|
169
|
-
function
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
161
|
+
function processFuzzResult(parsed: Record<string, unknown>, state: AuditState): void {
|
|
162
|
+
const counterexamples = parsed.counterexamples
|
|
163
|
+
if (!Array.isArray(counterexamples) || counterexamples.length === 0) return
|
|
164
|
+
|
|
165
|
+
const totalRuns = typeof parsed.totalRuns === "number" ? parsed.totalRuns : 0
|
|
166
|
+
|
|
167
|
+
state.fuzzCounterexamples ??= []
|
|
168
|
+
|
|
169
|
+
for (const raw of counterexamples) {
|
|
170
|
+
const ce = toRecord(raw)
|
|
171
|
+
if (!ce) continue
|
|
172
|
+
|
|
173
|
+
const testName = ce.testName
|
|
174
|
+
if (typeof testName !== "string") continue
|
|
175
|
+
|
|
176
|
+
const rawInputs = ce.inputs
|
|
177
|
+
const inputs = Array.isArray(rawInputs)
|
|
178
|
+
? rawInputs.map(String)
|
|
179
|
+
: (() => {
|
|
180
|
+
const rec = toRecord(rawInputs)
|
|
181
|
+
return rec ? Object.values(rec).map(String) : []
|
|
182
|
+
})()
|
|
183
|
+
|
|
184
|
+
const entry: FuzzCounterexample = {
|
|
185
|
+
testName,
|
|
186
|
+
inputs,
|
|
187
|
+
runs: totalRuns,
|
|
188
|
+
timestamp: Date.now(),
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (typeof ce.revertReason === "string") {
|
|
192
|
+
entry.revertReason = ce.revertReason
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
state.fuzzCounterexamples.push(entry)
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function processSoloditResult(parsed: Record<string, unknown>, state: AuditState): void {
|
|
200
|
+
const query = typeof parsed.query === "string" ? parsed.query : ""
|
|
201
|
+
const results = Array.isArray(parsed.results) ? parsed.results : []
|
|
202
|
+
const totalFound = typeof parsed.totalFound === "number" ? parsed.totalFound : results.length
|
|
203
|
+
|
|
204
|
+
const topResults: SoloditResult["topResults"] = results.slice(0, 5).map((raw) => {
|
|
205
|
+
const r = toRecord(raw)
|
|
206
|
+
return {
|
|
207
|
+
title: typeof r?.title === "string" ? r.title : "",
|
|
208
|
+
severity: typeof r?.severity === "string" ? r.severity : "",
|
|
209
|
+
url: typeof r?.url === "string" ? r.url : "",
|
|
210
|
+
protocol: typeof r?.protocol === "string" ? r.protocol : "",
|
|
211
|
+
}
|
|
212
|
+
})
|
|
178
213
|
|
|
214
|
+
state.soloditResults ??= []
|
|
215
|
+
state.soloditResults.push({
|
|
216
|
+
query,
|
|
217
|
+
timestamp: Date.now(),
|
|
218
|
+
resultCount: totalFound,
|
|
219
|
+
topResults,
|
|
220
|
+
})
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Records a tool execution in the audit state.
|
|
225
|
+
*
|
|
226
|
+
* Multiple entries per tool name are allowed — if the same tool runs multiple times
|
|
227
|
+
* (e.g., argus_slither_analyze on different targets), each execution is recorded
|
|
228
|
+
* with its own findingsCount.
|
|
229
|
+
*
|
|
230
|
+
* Timing limitation: startTime and endTime are both set to Date.now() because this
|
|
231
|
+
* hook fires in the tool.execute.after phase, after execution has already completed.
|
|
232
|
+
* We cannot capture the actual start time. This is a known limitation of the hook
|
|
233
|
+
* architecture. For accurate timing, the hook would need to fire in tool.execute.before
|
|
234
|
+
* and tool.execute.after phases separately.
|
|
235
|
+
*/
|
|
236
|
+
function recordToolExecution(state: AuditState, toolName: string, findingsCount: number): void {
|
|
179
237
|
const now = Date.now()
|
|
180
238
|
state.toolsExecuted.push({
|
|
181
239
|
tool: toolName,
|
|
@@ -194,7 +252,8 @@ function recordToolExecution(
|
|
|
194
252
|
* Findings are deduplicated via the FindingStore (by check+file+lines).
|
|
195
253
|
*/
|
|
196
254
|
export function createToolTrackingHook(
|
|
197
|
-
getAuditState: () => AuditState | null
|
|
255
|
+
getAuditState: () => AuditState | null,
|
|
256
|
+
onStateChanged?: (metadata: ToolExecutionMetadata) => void,
|
|
198
257
|
): (input: ToolHookInput) => Promise<void> {
|
|
199
258
|
const storesByState = new WeakMap<AuditState, FindingStore>()
|
|
200
259
|
|
|
@@ -221,6 +280,22 @@ export function createToolTrackingHook(
|
|
|
221
280
|
|
|
222
281
|
const { state: auditState, store } = resolved
|
|
223
282
|
|
|
283
|
+
// Handle argus_skill_load first — it returns markdown, not JSON
|
|
284
|
+
if (input.tool === "argus_skill_load") {
|
|
285
|
+
// Extract skill name from markdown header: "## Argus Skill: {name} [Source: ...]"
|
|
286
|
+
const nameMatch = input.result.match(/^##\s+Argus Skill:\s+(.+?)(?:\s+\[|$)/m)
|
|
287
|
+
const skillName = nameMatch?.[1]?.trim()
|
|
288
|
+
if (skillName) {
|
|
289
|
+
auditState.skillsLoaded ??= []
|
|
290
|
+
if (!auditState.skillsLoaded.includes(skillName)) {
|
|
291
|
+
auditState.skillsLoaded.push(skillName)
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
recordToolExecution(auditState, input.tool, 0)
|
|
295
|
+
onStateChanged?.({ tool: input.tool, findingsCount: 0 })
|
|
296
|
+
return
|
|
297
|
+
}
|
|
298
|
+
|
|
224
299
|
let parsed: unknown
|
|
225
300
|
try {
|
|
226
301
|
parsed = JSON.parse(input.result)
|
|
@@ -243,12 +318,70 @@ export function createToolTrackingHook(
|
|
|
243
318
|
case "argus_analyze_contract":
|
|
244
319
|
processContractAnalyzerResult(record, auditState)
|
|
245
320
|
break
|
|
321
|
+
case "argus_solodit_search":
|
|
322
|
+
processSoloditResult(record, auditState)
|
|
323
|
+
break
|
|
246
324
|
case "argus_forge_test":
|
|
325
|
+
break
|
|
247
326
|
case "argus_forge_fuzz":
|
|
248
|
-
|
|
327
|
+
processFuzzResult(record, auditState)
|
|
328
|
+
break
|
|
329
|
+
case "argus_generate_report": {
|
|
330
|
+
auditState.reportGenerated = true
|
|
331
|
+
break
|
|
332
|
+
}
|
|
333
|
+
case "argus_sync_knowledge": {
|
|
334
|
+
const success = record.success === true
|
|
335
|
+
auditState.knowledgeSynced = { success, timestamp: Date.now() }
|
|
336
|
+
break
|
|
337
|
+
}
|
|
338
|
+
case "argus_forge_coverage": {
|
|
339
|
+
const reportObj = toRecord(record.report)
|
|
340
|
+
const files = reportObj?.files
|
|
341
|
+
if (Array.isArray(files)) {
|
|
342
|
+
auditState.coverageReport = {
|
|
343
|
+
files: files
|
|
344
|
+
.filter((f): f is Record<string, unknown> => !!f && typeof f === "object")
|
|
345
|
+
.map((f) => ({
|
|
346
|
+
path: typeof f.path === "string" ? f.path : "unknown",
|
|
347
|
+
linesPct: typeof f.linesPct === "number" ? f.linesPct : 0,
|
|
348
|
+
statementsPct: typeof f.statementsPct === "number" ? f.statementsPct : 0,
|
|
349
|
+
branchesPct: typeof f.branchesPct === "number" ? f.branchesPct : 0,
|
|
350
|
+
functionsPct: typeof f.functionsPct === "number" ? f.functionsPct : 0,
|
|
351
|
+
})),
|
|
352
|
+
}
|
|
353
|
+
}
|
|
249
354
|
break
|
|
355
|
+
}
|
|
356
|
+
case "argus_proxy_detection": {
|
|
357
|
+
if (record.isProxy === true) {
|
|
358
|
+
auditState.proxyContracts ??= []
|
|
359
|
+
auditState.proxyContracts.push({
|
|
360
|
+
file: typeof record.file === "string" ? record.file : "unknown",
|
|
361
|
+
proxyType: typeof record.proxyType === "string" ? record.proxyType : "unknown",
|
|
362
|
+
indicators: Array.isArray(record.indicators)
|
|
363
|
+
? record.indicators.filter((i): i is string => typeof i === "string")
|
|
364
|
+
: [],
|
|
365
|
+
})
|
|
366
|
+
}
|
|
367
|
+
break
|
|
368
|
+
}
|
|
369
|
+
case "argus_gas_analysis": {
|
|
370
|
+
const hotspots = record.hotspots
|
|
371
|
+
if (Array.isArray(hotspots)) {
|
|
372
|
+
auditState.gasHotspots = hotspots
|
|
373
|
+
.filter((h): h is Record<string, unknown> => !!h && typeof h === "object")
|
|
374
|
+
.map((h) => ({
|
|
375
|
+
contract: typeof h.contract === "string" ? h.contract : "unknown",
|
|
376
|
+
function: typeof h.function === "string" ? h.function : "unknown",
|
|
377
|
+
avgGas: typeof h.avgGas === "number" ? h.avgGas : 0,
|
|
378
|
+
}))
|
|
379
|
+
}
|
|
380
|
+
break
|
|
381
|
+
}
|
|
250
382
|
}
|
|
251
383
|
|
|
252
384
|
recordToolExecution(auditState, input.tool, findingsCount)
|
|
385
|
+
onStateChanged?.({ tool: input.tool, findingsCount })
|
|
253
386
|
}
|
|
254
387
|
}
|
package/src/hooks/types.ts
CHANGED
package/src/index.ts
CHANGED
|
@@ -1,20 +1,12 @@
|
|
|
1
1
|
import type { Plugin } from "@opencode-ai/plugin"
|
|
2
2
|
import { loadArgusConfig } from "./config/loader"
|
|
3
|
-
import { createHookGuard } from "./hooks/hook-system"
|
|
4
|
-
import { createTools } from "./create-tools"
|
|
5
3
|
import { createHooks } from "./create-hooks"
|
|
6
4
|
import { createManagers } from "./create-managers"
|
|
5
|
+
import { createTools } from "./create-tools"
|
|
6
|
+
import type { Dispatcher } from "./features/background-agent/background-manager"
|
|
7
|
+
import { createHookGuard } from "./hooks/hook-system"
|
|
7
8
|
import { createPluginInterface } from "./plugin-interface"
|
|
8
|
-
|
|
9
|
-
function startSoloditMcp(port: number): void {
|
|
10
|
-
const child = Bun.spawn(["npx", "-y", "@lyuboslavlyubenov/solodit-mcp"], {
|
|
11
|
-
stdin: "ignore",
|
|
12
|
-
stdout: "ignore",
|
|
13
|
-
stderr: "ignore",
|
|
14
|
-
env: { ...process.env, PORT: String(port) },
|
|
15
|
-
})
|
|
16
|
-
child.unref()
|
|
17
|
-
}
|
|
9
|
+
import { startSoloditMcp } from "./solodit-lifecycle"
|
|
18
10
|
|
|
19
11
|
const ArgusPlugin: Plugin = async (ctx) => {
|
|
20
12
|
const projectDir = ctx.directory ?? process.cwd()
|
|
@@ -25,7 +17,25 @@ const ArgusPlugin: Plugin = async (ctx) => {
|
|
|
25
17
|
}
|
|
26
18
|
|
|
27
19
|
const isHookEnabled = createHookGuard(config.disabled_hooks)
|
|
28
|
-
const
|
|
20
|
+
const taskCandidate = (ctx as Record<string, unknown>).task
|
|
21
|
+
const backgroundDispatcher: Dispatcher | undefined =
|
|
22
|
+
typeof taskCandidate === "function"
|
|
23
|
+
? async (agentName: string, prompt: string) => {
|
|
24
|
+
const result = await taskCandidate(agentName, prompt)
|
|
25
|
+
if (typeof result === "string") {
|
|
26
|
+
return result
|
|
27
|
+
}
|
|
28
|
+
if (typeof result === "object" && result !== null) {
|
|
29
|
+
const taskId = (result as Record<string, unknown>).task_id
|
|
30
|
+
if (typeof taskId === "string") {
|
|
31
|
+
return taskId
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return `task-${Date.now()}`
|
|
35
|
+
}
|
|
36
|
+
: undefined
|
|
37
|
+
|
|
38
|
+
const managers = createManagers({ projectDir, config, backgroundDispatcher })
|
|
29
39
|
const tools = createTools(config)
|
|
30
40
|
const hooks = createHooks({ config, managers, projectDir, isHookEnabled })
|
|
31
41
|
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export interface RetryOptions<T> {
|
|
2
|
+
maxAttempts: number
|
|
3
|
+
baseDelayMs: number
|
|
4
|
+
shouldRetry: (error: unknown) => boolean
|
|
5
|
+
onRetry?: (attempt: number, error: unknown) => void
|
|
6
|
+
_valueType?: T
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface RetryResult<T> {
|
|
10
|
+
success: boolean
|
|
11
|
+
value?: T
|
|
12
|
+
error?: unknown
|
|
13
|
+
attempts: number
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function sleep(delayMs: number): Promise<void> {
|
|
17
|
+
return new Promise((resolve) => setTimeout(resolve, delayMs))
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function withRetry<T>(
|
|
21
|
+
fn: () => Promise<T>,
|
|
22
|
+
options: RetryOptions<T>,
|
|
23
|
+
): Promise<RetryResult<T>> {
|
|
24
|
+
const maxAttempts = options.maxAttempts > 0 ? options.maxAttempts : 1
|
|
25
|
+
let lastError: unknown
|
|
26
|
+
|
|
27
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
28
|
+
try {
|
|
29
|
+
const value = await fn()
|
|
30
|
+
return { success: true, value, attempts: attempt }
|
|
31
|
+
} catch (error) {
|
|
32
|
+
lastError = error
|
|
33
|
+
const canRetry = attempt < maxAttempts && options.shouldRetry(error)
|
|
34
|
+
|
|
35
|
+
if (!canRetry) {
|
|
36
|
+
return { success: false, error, attempts: attempt }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (options.onRetry) {
|
|
40
|
+
options.onRetry(attempt, error)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const delay = options.baseDelayMs * 2 ** (attempt - 1)
|
|
44
|
+
await sleep(delay)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
success: false,
|
|
50
|
+
error: lastError,
|
|
51
|
+
attempts: maxAttempts,
|
|
52
|
+
}
|
|
53
|
+
}
|