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
@@ -1,34 +1,47 @@
1
+ import { join } from "node:path"
1
2
  import type { Hooks as PluginHooks } from "@opencode-ai/plugin"
2
3
  import type { ArgusConfig } from "./config/types"
3
- import type { Managers } from "./managers/types"
4
- import type { HookName } from "./hooks/types"
5
- import { createConfigHandler } from "./hooks/config-handler"
6
- import { createCompactionHook } from "./hooks/compaction-hook"
7
- import { createToolTrackingHook } from "./hooks/tool-tracking-hook"
8
- import { createEventHookV2 } from "./hooks/event-hook-v2"
9
- import { createAgentTracker } from "./hooks/agent-tracker"
10
- import { createSystemPromptHook } from "./hooks/system-prompt-hook"
11
- import { safeCreateHook } from "./hooks/safe-create-hook"
12
- import { createContextMonitor, createToolOutputTruncator } from "./features/context-monitor"
13
4
  import { createAuditEnforcer } from "./features/audit-enforcer/audit-enforcer"
5
+ import { createContextMonitor, createToolOutputTruncator } from "./features/context-monitor"
6
+ import {
7
+ createSessionRecoveryHandler,
8
+ createToolErrorRecoveryHandler,
9
+ } from "./features/error-recovery"
10
+ import { createDebouncedSave } from "./features/persistent-state/audit-state-manager"
11
+ import { recordRun } from "./features/persistent-state/global-run-index"
12
+ import { createRunJournal } from "./features/persistent-state/run-journal"
13
+ import { createAgentTracker } from "./hooks/agent-tracker"
14
+ import { createCompactionHook } from "./hooks/compaction-hook"
15
+ import { createConfigHandler } from "./hooks/config-handler"
14
16
  import { getTokenBudgetForAgent } from "./hooks/context-budget"
15
- import { createSessionRecoveryHandler } from "./features/error-recovery"
16
- import { createToolErrorRecoveryHandler } from "./features/error-recovery"
17
- import { detectProject } from "./utils/project-detector"
18
- import type { ProjectConfig } from "./utils/project-detector"
19
- import { detectAuditArtifacts } from "./utils/audit-artifact-detector"
17
+ import { createEventHook } from "./hooks/event-hook"
20
18
  import type { ReconContext } from "./hooks/recon-context-builder"
21
19
  import { buildReconContextBlock } from "./hooks/recon-context-builder"
20
+ import { safeCreateHook } from "./hooks/safe-create-hook"
21
+ import { createSystemPromptHook } from "./hooks/system-prompt-hook"
22
+ import { createToolTrackingHook } from "./hooks/tool-tracking-hook"
23
+ import type { HookName } from "./hooks/types"
24
+ import type { Managers } from "./managers/types"
25
+ import { createLogger } from "./shared/logger"
22
26
  import type { AuditState } from "./state/types"
27
+ import { detectAuditArtifacts } from "./utils/audit-artifact-detector"
28
+ import { detectProject, type ProjectConfig } from "./utils/project-detector"
23
29
 
24
- let latestAgentTracker: ReturnType<typeof createAgentTracker> | undefined
30
+ const logger = createLogger()
31
+
32
+ export type AgentTrackerRef = {
33
+ getAgentForSession(sessionID: string): string | undefined
34
+ isArgusAgent(sessionID: string): boolean
35
+ }
36
+
37
+ let _agentTrackerRef: AgentTrackerRef | undefined
25
38
 
26
39
  export function getAgentForSession(sessionID: string): string | undefined {
27
- return latestAgentTracker?.getAgentForSession(sessionID)
40
+ return _agentTrackerRef?.getAgentForSession(sessionID)
28
41
  }
29
42
 
30
43
  export function isArgusAgent(sessionID: string): boolean {
31
- return latestAgentTracker?.isArgusAgent(sessionID) ?? false
44
+ return _agentTrackerRef?.isArgusAgent(sessionID) ?? false
32
45
  }
33
46
 
34
47
  export type Hooks = Pick<
@@ -62,26 +75,103 @@ export function createHooks(args: {
62
75
  const { config, managers, projectDir, isHookEnabled } = args
63
76
  const { auditStateManager, backgroundManager } = managers
64
77
  const agentTracker = createAgentTracker()
65
- latestAgentTracker = agentTracker
78
+ _agentTrackerRef = agentTracker
66
79
 
67
80
  const contextMonitor = createContextMonitor()
68
81
  const sessionRecoveryHandler = createSessionRecoveryHandler(auditStateManager)
82
+ const debouncedSave = createDebouncedSave(auditStateManager.save)
83
+ const runJournal = createRunJournal(projectDir)
69
84
  let auditStateGetter: (() => AuditState | null) | undefined
70
- const toolErrorRecoveryHandler = createToolErrorRecoveryHandler(() => auditStateGetter?.() ?? null)
85
+ const toolErrorRecoveryHandler = createToolErrorRecoveryHandler(
86
+ () => auditStateGetter?.() ?? null,
87
+ (patch) => auditStateManager.update(patch),
88
+ )
71
89
  const outputTruncator = createToolOutputTruncator()
72
90
 
73
- const { hook: eventHook, getAuditState, setAuditState } = createEventHookV2(projectDir, [
91
+ // Sub-handlers run sequentially. The state persistence handler MUST be first:
92
+ // it loads persisted state on session.created, overriding the fresh default.
93
+ const {
94
+ hook: eventHook,
95
+ getAuditState,
96
+ setAuditState,
97
+ } = createEventHook(projectDir, [
74
98
  async ({ type, sessionId, auditState, setAuditState: setState }) => {
75
99
  if (type === "session.created") {
76
- const recoveredState = await auditStateManager.load()
100
+ const timestamp = Date.now()
101
+ let recoveredState: AuditState | null = null
102
+
103
+ try {
104
+ recoveredState = await auditStateManager.load()
105
+ } finally {
106
+ runJournal.log({
107
+ type: "state.loaded",
108
+ timestamp,
109
+ success: recoveredState !== null,
110
+ findingsCount: recoveredState?.findings.length ?? 0,
111
+ })
112
+ }
113
+
77
114
  if (recoveredState) {
78
115
  setState(recoveredState)
79
116
  }
117
+
118
+ runJournal.log({
119
+ type: "session.created",
120
+ sessionId,
121
+ timestamp: Date.now(),
122
+ })
123
+
124
+ const effectiveState = recoveredState ?? auditStateManager.get()
125
+ if (effectiveState) {
126
+ void recordRun({
127
+ runId: effectiveState.sessionId,
128
+ opencodeSessionId: sessionId,
129
+ projectDir: effectiveState.projectDir,
130
+ statePath: join(effectiveState.projectDir, ".opencode", "argus-state.json"),
131
+ journalPath: join(effectiveState.projectDir, ".opencode", "argus-journal.jsonl"),
132
+ startedAt: effectiveState.startTime,
133
+ phase: effectiveState.currentPhase,
134
+ findingsCount: effectiveState.findings.length,
135
+ })
136
+ }
137
+
80
138
  return
81
139
  }
82
140
 
83
141
  if (type === "session.idle" && auditState) {
84
- await auditStateManager.save(auditState)
142
+ await debouncedSave.flush()
143
+
144
+ let saveSuccess = true
145
+ try {
146
+ await auditStateManager.save(auditState)
147
+ } catch {
148
+ saveSuccess = false
149
+ } finally {
150
+ runJournal.log({
151
+ type: "state.saved",
152
+ timestamp: Date.now(),
153
+ success: saveSuccess,
154
+ })
155
+ }
156
+
157
+ runJournal.log({
158
+ type: "session.idle",
159
+ timestamp: Date.now(),
160
+ findingsCount: auditState.findings.length,
161
+ toolsExecutedCount: auditState.toolsExecuted.length,
162
+ })
163
+
164
+ void recordRun({
165
+ runId: auditState.sessionId,
166
+ opencodeSessionId: sessionId,
167
+ projectDir: auditState.projectDir,
168
+ statePath: join(auditState.projectDir, ".opencode", "argus-state.json"),
169
+ journalPath: join(auditState.projectDir, ".opencode", "argus-journal.jsonl"),
170
+ startedAt: auditState.startTime,
171
+ phase: auditState.currentPhase,
172
+ findingsCount: auditState.findings.length,
173
+ })
174
+
85
175
  return
86
176
  }
87
177
 
@@ -89,7 +179,13 @@ export function createHooks(args: {
89
179
  if (sessionId) {
90
180
  agentTracker.clearSession(sessionId)
91
181
  }
92
- await auditStateManager.reset()
182
+
183
+ await auditStateManager.archive()
184
+ runJournal.log({
185
+ type: "session.deleted",
186
+ timestamp: Date.now(),
187
+ archived: true,
188
+ })
93
189
  }
94
190
  },
95
191
  async ({ type, sessionId, setAuditState: setState }) => {
@@ -121,11 +217,12 @@ export function createHooks(args: {
121
217
  },
122
218
  getTokenBudget: getTokenBudgetForAgent,
123
219
  getEnforcerReminder: auditEnforcer,
124
- getReconBlock: () => buildReconContextBlock({
125
- projectConfig: reconProjectConfig,
126
- dependencyRisks: reconProjectConfig?.dependencyRisks ?? [],
127
- auditArtifacts: detectAuditArtifacts(projectDir),
128
- }),
220
+ getReconBlock: () =>
221
+ buildReconContextBlock({
222
+ projectConfig: reconProjectConfig,
223
+ dependencyRisks: reconProjectConfig?.dependencyRisks ?? [],
224
+ auditArtifacts: detectAuditArtifacts(projectDir),
225
+ }),
129
226
  })
130
227
 
131
228
  let reconProjectConfig: ProjectConfig | null = null
@@ -135,7 +232,7 @@ export function createHooks(args: {
135
232
  reconProjectConfig = config
136
233
  })
137
234
  .catch(() => {
138
- // Silent fallback audit artifacts remain available
235
+ logger.debug("Project detection failed, using fallback recon context")
139
236
  })
140
237
 
141
238
  const getReconContext = (): ReconContext => ({
@@ -149,7 +246,23 @@ export function createHooks(args: {
149
246
  : undefined
150
247
 
151
248
  const toolTrackingHook = isHookEnabled("tool-tracking")
152
- ? safeCreateHook(() => createToolTrackingHook(getAuditState), "tool-tracking")
249
+ ? safeCreateHook(
250
+ () =>
251
+ createToolTrackingHook(getAuditState, ({ tool, findingsCount }) => {
252
+ const currentState = getAuditState()
253
+ if (currentState) {
254
+ debouncedSave.save(currentState)
255
+ }
256
+
257
+ runJournal.log({
258
+ type: "tool.executed",
259
+ tool,
260
+ timestamp: Date.now(),
261
+ findingsCount,
262
+ })
263
+ }),
264
+ "tool-tracking",
265
+ )
153
266
  : undefined
154
267
 
155
268
  const safeEventHook = isHookEnabled("event")
@@ -186,9 +299,7 @@ export function createHooks(args: {
186
299
  result: output.output,
187
300
  })
188
301
 
189
- const outputWithHint = recoveryHint
190
- ? `${output.output}${recoveryHint}`
191
- : output.output
302
+ const outputWithHint = recoveryHint ? `${output.output}${recoveryHint}` : output.output
192
303
  output.output = outputTruncator(outputWithHint)
193
304
  }
194
305
  : undefined,
@@ -1,23 +1,25 @@
1
1
  import type { ArgusConfig } from "./config/types"
2
- import type { Managers } from "./managers/types"
2
+ import type { Dispatcher } from "./features/background-agent/background-manager"
3
3
  import { createBackgroundManager } from "./features/background-agent/background-manager"
4
4
  import { createAuditStateManager } from "./features/persistent-state/audit-state-manager"
5
+ import type { Managers } from "./managers/types"
5
6
  import { createLogger } from "./shared/logger"
6
7
 
7
8
  export function createManagers(args: {
8
9
  projectDir: string
9
10
  config: ArgusConfig
11
+ backgroundDispatcher?: Dispatcher
10
12
  }): Managers {
11
- const { projectDir } = args
13
+ const { projectDir, config, backgroundDispatcher } = args
12
14
  const logger = createLogger()
13
15
 
14
16
  const backgroundManager = createBackgroundManager(
15
- async (agentName: string, prompt: string) => {
16
- logger.warn(
17
- `Background dispatch not wired: ${agentName} (${prompt.slice(0, 50)}...)`,
18
- )
19
- return `noop-${Date.now()}`
20
- },
17
+ backgroundDispatcher ??
18
+ (async (agentName: string, prompt: string) => {
19
+ logger.warn(`Background dispatch not wired: ${agentName} (${prompt.slice(0, 50)}...)`)
20
+ return `noop-${Date.now()}`
21
+ }),
22
+ { maxConcurrent: config.background?.max_concurrent ?? 3 },
21
23
  )
22
24
 
23
25
  const auditStateManager = createAuditStateManager(projectDir)
@@ -1,31 +1,35 @@
1
1
  import type { ToolDefinition } from "@opencode-ai/plugin"
2
2
  import type { ArgusConfig } from "./config/types"
3
- import { slitherTool } from "./tools/slither-tool"
4
- import { forgeTestTool } from "./tools/forge-test-tool"
5
- import { forgeFuzzTool } from "./tools/forge-fuzz-tool"
3
+ import { argusSkillLoadTool } from "./tools/argus-skill-load-tool"
6
4
  import { contractAnalyzerTool } from "./tools/contract-analyzer-tool"
5
+ import { forgeCoverageTool } from "./tools/forge-coverage-tool"
6
+ import { forgeFuzzTool } from "./tools/forge-fuzz-tool"
7
+ import { forgeTestTool } from "./tools/forge-test-tool"
8
+ import { gasAnalysisTool } from "./tools/gas-analysis-tool"
7
9
  import { patternCheckerTool } from "./tools/pattern-checker-tool"
8
- import { soloditSearchTool } from "./tools/solodit-search-tool"
10
+ import { proxyDetectionTool } from "./tools/proxy-detection-tool"
9
11
  import { reportGeneratorTool } from "./tools/report-generator-tool"
12
+ import { slitherTool } from "./tools/slither-tool"
13
+ import { createSoloditSearchTool } from "./tools/solodit-search-tool"
10
14
  import { syncKnowledgeTool } from "./tools/sync-knowledge-tool"
11
- import { argusSkillLoadTool } from "./tools/argus-skill-load-tool"
12
15
 
13
- export function createTools(
14
- config: ArgusConfig,
15
- ): Record<string, ToolDefinition> {
16
+ export function createTools(config: ArgusConfig): Record<string, ToolDefinition> {
16
17
  const tools: Record<string, ToolDefinition> = {
17
18
  argus_slither_analyze: slitherTool,
18
19
  argus_forge_test: forgeTestTool,
20
+ argus_gas_analysis: gasAnalysisTool,
19
21
  argus_forge_fuzz: forgeFuzzTool,
22
+ argus_forge_coverage: forgeCoverageTool,
20
23
  argus_analyze_contract: contractAnalyzerTool,
21
24
  argus_check_patterns: patternCheckerTool,
25
+ argus_proxy_detection: proxyDetectionTool,
22
26
  argus_skill_load: argusSkillLoadTool,
23
27
  argus_generate_report: reportGeneratorTool,
24
28
  argus_sync_knowledge: syncKnowledgeTool,
25
29
  }
26
30
 
27
31
  if (config.solodit?.enabled !== false) {
28
- tools.argus_solodit_search = soloditSearchTool
32
+ tools.argus_solodit_search = createSoloditSearchTool(config.solodit?.port ?? 3000)
29
33
  }
30
34
 
31
35
  return tools
@@ -1,122 +1,125 @@
1
- import type { BackgroundManager } from "../../managers/types";
2
- import { createLogger } from "../../shared/logger";
1
+ import type { BackgroundManager } from "../../managers/types"
2
+ import { createLogger } from "../../shared/logger"
3
3
 
4
- type TaskStatus = "queued" | "running" | "completed" | "failed" | "cancelled";
5
- type CompletionCallback = (taskId: string, result: unknown) => void;
4
+ type TaskStatus = "queued" | "running" | "completed" | "failed" | "cancelled"
5
+ type CompletionCallback = (taskId: string, result: unknown) => void
6
6
 
7
7
  export interface BackgroundTaskOptions {
8
- priority?: number;
9
- max_concurrent?: number;
8
+ priority?: number
9
+ max_concurrent?: number
10
10
  }
11
11
 
12
12
  export interface BackgroundManagerWithTaskCallbacks extends BackgroundManager {
13
- dispatch(agentName: string, prompt: string, options?: BackgroundTaskOptions): string;
14
- onComplete(callback: CompletionCallback): void;
15
- onComplete(taskId: string, callback: CompletionCallback): void;
13
+ dispatch(agentName: string, prompt: string, options?: BackgroundTaskOptions): string
14
+ onComplete(callback: CompletionCallback): void
15
+ onComplete(taskId: string, callback: CompletionCallback): void
16
16
  }
17
17
 
18
18
  interface TaskInfo {
19
- status: TaskStatus;
20
- agentName: string;
21
- prompt: string;
22
- options?: BackgroundTaskOptions;
23
- result?: unknown;
24
- error?: unknown;
25
- callbacks: Set<CompletionCallback>;
19
+ status: TaskStatus
20
+ agentName: string
21
+ prompt: string
22
+ options?: BackgroundTaskOptions
23
+ result?: unknown
24
+ error?: unknown
25
+ callbacks: Set<CompletionCallback>
26
26
  }
27
27
 
28
- type Dispatcher = (
28
+ export type Dispatcher = (
29
29
  agentName: string,
30
30
  prompt: string,
31
31
  options?: BackgroundTaskOptions,
32
- ) => Promise<string>;
32
+ ) => Promise<string>
33
33
 
34
34
  export function createBackgroundManager(
35
- dispatcher: Dispatcher,
35
+ initialDispatcher: Dispatcher,
36
+ options?: { maxConcurrent?: number },
36
37
  ): BackgroundManagerWithTaskCallbacks {
37
- const logger = createLogger();
38
- const tasks = new Map<string, TaskInfo>();
39
- const queue: string[] = [];
40
- const globalCallbacks = new Set<CompletionCallback>();
41
-
42
- let runningCount = 0;
43
- let maxConcurrent = 3;
44
- let taskCount = 0;
38
+ const logger = createLogger()
39
+ const tasks = new Map<string, TaskInfo>()
40
+ const queue: string[] = []
41
+ const globalCallbacks = new Set<CompletionCallback>()
42
+
43
+ const dispatcher = initialDispatcher
44
+ let runningCount = 0
45
+ const maxConcurrent = options?.maxConcurrent ?? 3
46
+ let taskCount = 0
47
+
48
+ function safeInvokeCallback(callback: CompletionCallback, taskId: string, result: unknown): void {
49
+ try {
50
+ callback(taskId, result)
51
+ } catch (error: unknown) {
52
+ logger.error(`Background callback failed: ${taskId}`, error)
53
+ }
54
+ }
45
55
 
46
56
  function invokeCallbacks(taskId: string, result: unknown): void {
47
- const task = tasks.get(taskId);
57
+ const task = tasks.get(taskId)
48
58
  if (!task) {
49
- return;
59
+ return
50
60
  }
51
61
 
52
62
  for (const callback of globalCallbacks) {
53
- callback(taskId, result);
63
+ safeInvokeCallback(callback, taskId, result)
54
64
  }
55
65
 
56
66
  for (const callback of task.callbacks) {
57
- callback(taskId, result);
67
+ safeInvokeCallback(callback, taskId, result)
58
68
  }
59
69
 
60
- task.callbacks.clear();
70
+ task.callbacks.clear()
61
71
  }
62
72
 
63
73
  function processQueue(): void {
64
74
  while (runningCount < maxConcurrent && queue.length > 0) {
65
- const nextTaskId = queue.shift();
75
+ const nextTaskId = queue.shift()
66
76
 
67
77
  if (!nextTaskId) {
68
- return;
78
+ return
69
79
  }
70
80
 
71
- const task = tasks.get(nextTaskId);
81
+ const task = tasks.get(nextTaskId)
72
82
  if (!task || task.status === "cancelled") {
73
- continue;
83
+ continue
74
84
  }
75
85
 
76
- task.status = "running";
77
- runningCount += 1;
86
+ task.status = "running"
87
+ runningCount += 1
78
88
 
79
89
  dispatcher(task.agentName, task.prompt, task.options)
80
90
  .then((result) => {
81
- const currentTask = tasks.get(nextTaskId);
91
+ const currentTask = tasks.get(nextTaskId)
82
92
 
83
93
  if (!currentTask || currentTask.status === "cancelled") {
84
- return;
94
+ return
85
95
  }
86
96
 
87
- currentTask.status = "completed";
88
- currentTask.result = result;
89
- invokeCallbacks(nextTaskId, result);
97
+ currentTask.status = "completed"
98
+ currentTask.result = result
99
+ invokeCallbacks(nextTaskId, result)
90
100
  })
91
101
  .catch((error: unknown) => {
92
- const currentTask = tasks.get(nextTaskId);
102
+ const currentTask = tasks.get(nextTaskId)
93
103
 
94
104
  if (!currentTask || currentTask.status === "cancelled") {
95
- return;
105
+ return
96
106
  }
97
107
 
98
- currentTask.status = "failed";
99
- currentTask.error = error;
100
- logger.error(`Background task failed: ${nextTaskId}`, error);
108
+ currentTask.status = "failed"
109
+ currentTask.error = error
110
+ logger.error(`Background task failed: ${nextTaskId}`, error)
111
+ invokeCallbacks(nextTaskId, error)
101
112
  })
102
113
  .finally(() => {
103
- runningCount = Math.max(0, runningCount - 1);
104
- processQueue();
105
- });
114
+ runningCount = Math.max(0, runningCount - 1)
115
+ processQueue()
116
+ })
106
117
  }
107
118
  }
108
119
 
109
- function dispatch(
110
- agentName: string,
111
- prompt: string,
112
- options?: BackgroundTaskOptions,
113
- ): string {
114
- if (typeof options?.max_concurrent === "number" && options.max_concurrent > 0) {
115
- maxConcurrent = options.max_concurrent;
116
- }
117
-
118
- taskCount += 1;
119
- const taskId = `task-${taskCount}`;
120
+ function dispatch(agentName: string, prompt: string, options?: BackgroundTaskOptions): string {
121
+ taskCount += 1
122
+ const taskId = `task-${taskCount}`
120
123
 
121
124
  tasks.set(taskId, {
122
125
  status: "queued",
@@ -124,70 +127,73 @@ export function createBackgroundManager(
124
127
  prompt,
125
128
  options,
126
129
  callbacks: new Set<CompletionCallback>(),
127
- });
130
+ })
128
131
 
129
- queue.push(taskId);
130
- processQueue();
132
+ queue.push(taskId)
133
+ processQueue()
131
134
 
132
- return taskId;
135
+ return taskId
133
136
  }
134
137
 
135
138
  function cancel(taskId: string): void {
136
- const task = tasks.get(taskId);
139
+ const task = tasks.get(taskId)
137
140
  if (!task) {
138
- return;
141
+ return
139
142
  }
140
143
 
141
- task.status = "cancelled";
144
+ task.status = "cancelled"
142
145
 
143
- const queuedTaskIndex = queue.indexOf(taskId);
146
+ const queuedTaskIndex = queue.indexOf(taskId)
144
147
  if (queuedTaskIndex >= 0) {
145
- queue.splice(queuedTaskIndex, 1);
148
+ queue.splice(queuedTaskIndex, 1)
146
149
  }
147
150
  }
148
151
 
149
152
  function getResult(taskId: string): Promise<unknown> {
150
- const task = tasks.get(taskId);
153
+ const task = tasks.get(taskId)
151
154
  if (!task || task.status !== "completed") {
152
- return Promise.resolve(undefined);
155
+ return Promise.resolve(undefined)
153
156
  }
154
157
 
155
- return Promise.resolve(task.result);
158
+ return Promise.resolve(task.result)
156
159
  }
157
160
 
158
- function onComplete(taskIdOrCallback: string | CompletionCallback, callback?: CompletionCallback): void {
161
+ function onComplete(
162
+ taskIdOrCallback: string | CompletionCallback,
163
+ callback?: CompletionCallback,
164
+ ): void {
159
165
  if (typeof taskIdOrCallback === "function") {
160
- globalCallbacks.add(taskIdOrCallback);
161
- return;
166
+ globalCallbacks.add(taskIdOrCallback)
167
+ return
162
168
  }
163
169
 
164
170
  if (!callback) {
165
- return;
171
+ return
166
172
  }
167
173
 
168
- const task = tasks.get(taskIdOrCallback);
174
+ const task = tasks.get(taskIdOrCallback)
169
175
  if (!task) {
170
- return;
176
+ return
171
177
  }
172
178
 
173
179
  if (task.status === "completed") {
174
- callback(taskIdOrCallback, task.result);
175
- return;
180
+ safeInvokeCallback(callback, taskIdOrCallback, task.result)
181
+ return
176
182
  }
177
183
 
178
- task.callbacks.add(callback);
184
+ task.callbacks.add(callback)
179
185
  }
180
186
 
181
187
  function getActiveCount(): number {
182
- let activeCount = 0;
188
+ let activeCount = 0
183
189
 
184
190
  for (const task of tasks.values()) {
185
191
  if (task.status === "queued" || task.status === "running") {
186
- activeCount += 1;
192
+ activeCount += 1
187
193
  }
188
194
  }
189
195
 
190
- return activeCount;
196
+ return activeCount
191
197
  }
192
198
 
193
199
  return {
@@ -196,5 +202,5 @@ export function createBackgroundManager(
196
202
  getResult,
197
203
  onComplete,
198
204
  getActiveCount,
199
- };
205
+ }
200
206
  }
@@ -1 +1 @@
1
- export * from "./background-manager";
1
+ export * from "./background-manager"
@@ -1,8 +1,8 @@
1
- import type { AuditState } from "../../state/types"
2
1
  import { createLogger } from "../../shared/logger"
2
+ import type { AuditState } from "../../state/types"
3
3
 
4
4
  const DEFAULT_MAX_TOKENS = 200_000
5
- const REMINDER_THRESHOLD = 0.70
5
+ const REMINDER_THRESHOLD = 0.7
6
6
  const COMPACTION_THRESHOLD = 0.85
7
7
 
8
8
  export interface ContextMonitorConfig {
@@ -19,7 +19,7 @@ export function createContextMonitor(config: ContextMonitorConfig = {}) {
19
19
 
20
20
  function getContextStatus(
21
21
  systemText: string,
22
- auditState: AuditState | null,
22
+ _auditState: AuditState | null,
23
23
  ): { usage: number; reminder: string | null; shouldCompact: boolean } {
24
24
  const tokens = estimateTokens(systemText)
25
25
  const usage = tokens / maxTokens
@@ -1,4 +1,4 @@
1
- export { createContextMonitor } from "./context-monitor"
2
1
  export type { ContextMonitorConfig } from "./context-monitor"
3
- export { createToolOutputTruncator } from "./tool-output-truncator"
2
+ export { createContextMonitor } from "./context-monitor"
4
3
  export type { TruncatorConfig } from "./tool-output-truncator"
4
+ export { createToolOutputTruncator } from "./tool-output-truncator"
@@ -1,10 +1,8 @@
1
- import type { AuditState } from "../../state/types"
2
1
  import type { AuditStateManager } from "../../managers/types"
3
2
  import { createLogger } from "../../shared/logger"
3
+ import type { AuditState } from "../../state/types"
4
4
 
5
- export function createSessionRecoveryHandler(
6
- auditStateManager: AuditStateManager,
7
- ) {
5
+ export function createSessionRecoveryHandler(auditStateManager: AuditStateManager) {
8
6
  const logger = createLogger()
9
7
 
10
8
  return async (event: {