solidity-argus 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (167) hide show
  1. package/AGENTS.md +3 -3
  2. package/README.md +93 -37
  3. package/package.json +33 -7
  4. package/skills/INVENTORY.md +88 -57
  5. package/skills/README.md +26 -23
  6. package/skills/case-studies/beanstalk-governance/SKILL.md +52 -0
  7. package/skills/case-studies/bzx-flash-loan/SKILL.md +53 -0
  8. package/skills/case-studies/cream-finance/SKILL.md +52 -0
  9. package/skills/case-studies/curve-reentrancy/SKILL.md +52 -0
  10. package/skills/case-studies/dao-hack/SKILL.md +51 -0
  11. package/skills/case-studies/euler-finance/SKILL.md +52 -0
  12. package/skills/case-studies/harvest-finance/SKILL.md +52 -0
  13. package/skills/case-studies/level-finance/SKILL.md +51 -0
  14. package/skills/case-studies/mango-markets/SKILL.md +53 -0
  15. package/skills/case-studies/nomad-bridge/SKILL.md +51 -0
  16. package/skills/case-studies/parity-multisig/SKILL.md +55 -0
  17. package/skills/case-studies/poly-network/SKILL.md +51 -0
  18. package/skills/case-studies/rari-fuse/SKILL.md +51 -0
  19. package/skills/case-studies/ronin-bridge/SKILL.md +52 -0
  20. package/skills/case-studies/wormhole-bridge/SKILL.md +51 -0
  21. package/skills/manifests/smartbugs.json +1 -3
  22. package/skills/manifests/sunweb3sec.json +1 -3
  23. package/skills/vulnerability-patterns/access-control/SKILL.md +14 -0
  24. package/skills/vulnerability-patterns/arbitrary-storage-location/SKILL.md +13 -1
  25. package/skills/vulnerability-patterns/assert-violation/SKILL.md +8 -1
  26. package/skills/vulnerability-patterns/asserting-contract-from-code-size/SKILL.md +12 -1
  27. package/skills/vulnerability-patterns/authorization-txorigin/SKILL.md +2 -1
  28. package/skills/vulnerability-patterns/cross-chain-bridge-vulnerabilities/SKILL.md +217 -0
  29. package/skills/vulnerability-patterns/default-visibility/SKILL.md +13 -1
  30. package/skills/vulnerability-patterns/delegatecall-untrusted-callee/SKILL.md +2 -1
  31. package/skills/vulnerability-patterns/dos-gas-limit/SKILL.md +8 -1
  32. package/skills/vulnerability-patterns/dos-revert/SKILL.md +1 -0
  33. package/skills/vulnerability-patterns/erc4626-exchange-rate-manipulation/SKILL.md +64 -0
  34. package/skills/vulnerability-patterns/fee-on-transfer-tokens/SKILL.md +93 -0
  35. package/skills/vulnerability-patterns/flash-loan-attacks/SKILL.md +1 -0
  36. package/skills/vulnerability-patterns/floating-pragma/SKILL.md +8 -1
  37. package/skills/vulnerability-patterns/front-running-attacks/SKILL.md +209 -0
  38. package/skills/vulnerability-patterns/gas-optimization-patterns/SKILL.md +203 -0
  39. package/skills/vulnerability-patterns/governance-attacks/SKILL.md +208 -0
  40. package/skills/vulnerability-patterns/hash-collision/SKILL.md +8 -1
  41. package/skills/vulnerability-patterns/inadherence-to-standards/SKILL.md +12 -1
  42. package/skills/vulnerability-patterns/incorrect-constructor/SKILL.md +8 -1
  43. package/skills/vulnerability-patterns/incorrect-inheritance-order/SKILL.md +8 -1
  44. package/skills/vulnerability-patterns/insufficient-gas-griefing/SKILL.md +12 -1
  45. package/skills/vulnerability-patterns/lack-of-precision/SKILL.md +7 -1
  46. package/skills/vulnerability-patterns/logic-errors/SKILL.md +10 -0
  47. package/skills/vulnerability-patterns/missing-parameter-bounds/SKILL.md +44 -0
  48. package/skills/vulnerability-patterns/missing-protection-signature-replay/SKILL.md +17 -1
  49. package/skills/vulnerability-patterns/msgvalue-loop/SKILL.md +12 -1
  50. package/skills/vulnerability-patterns/off-by-one/SKILL.md +7 -1
  51. package/skills/vulnerability-patterns/oracle-manipulation/SKILL.md +9 -0
  52. package/skills/vulnerability-patterns/outdated-compiler-version/SKILL.md +8 -1
  53. package/skills/vulnerability-patterns/overflow-underflow/SKILL.md +1 -0
  54. package/skills/vulnerability-patterns/proxy-vulnerabilities/SKILL.md +209 -0
  55. package/skills/vulnerability-patterns/reentrancy/SKILL.md +9 -0
  56. package/skills/vulnerability-patterns/shadowing-state-variables/SKILL.md +8 -1
  57. package/skills/vulnerability-patterns/share-accounting-desynchronization/SKILL.md +44 -0
  58. package/skills/vulnerability-patterns/signature-malleability/SKILL.md +2 -1
  59. package/skills/vulnerability-patterns/stateful-parameter-update-drift/SKILL.md +44 -0
  60. package/skills/vulnerability-patterns/unbounded-return-data/SKILL.md +12 -1
  61. package/skills/vulnerability-patterns/unchecked-return-values/SKILL.md +2 -1
  62. package/skills/vulnerability-patterns/unencrypted-private-data-on-chain/SKILL.md +8 -1
  63. package/skills/vulnerability-patterns/unexpected-ecrecover-null-address/SKILL.md +8 -1
  64. package/skills/vulnerability-patterns/uninitialized-storage-pointer/SKILL.md +8 -1
  65. package/skills/vulnerability-patterns/unsafe-erc20-transfers/SKILL.md +132 -0
  66. package/skills/vulnerability-patterns/unsafe-low-level-call/SKILL.md +12 -1
  67. package/skills/vulnerability-patterns/unsecure-signatures/SKILL.md +12 -1
  68. package/skills/vulnerability-patterns/unsupported-opcodes/SKILL.md +11 -1
  69. package/skills/vulnerability-patterns/unused-variables/SKILL.md +8 -1
  70. package/skills/vulnerability-patterns/use-of-deprecated-functions/SKILL.md +8 -1
  71. package/skills/vulnerability-patterns/weak-sources-randomness/SKILL.md +8 -1
  72. package/skills/vulnerability-patterns/weird-tokens/SKILL.md +10 -0
  73. package/skills/vulnerability-patterns/zero-address-misconfiguration/SKILL.md +48 -0
  74. package/src/agents/argus-prompt.ts +24 -7
  75. package/src/agents/pythia-prompt.ts +3 -4
  76. package/src/agents/scribe-prompt.ts +7 -2
  77. package/src/agents/sentinel-prompt.ts +32 -3
  78. package/src/cli/cli-program.ts +29 -26
  79. package/src/cli/commands/check-skills.ts +135 -0
  80. package/src/cli/commands/doctor.ts +48 -26
  81. package/src/cli/commands/init.ts +5 -3
  82. package/src/cli/commands/install.ts +7 -5
  83. package/src/cli/commands/lint-skills.ts +16 -12
  84. package/src/cli/index.ts +5 -5
  85. package/src/cli/types.ts +3 -3
  86. package/src/config/index.ts +1 -1
  87. package/src/config/loader.ts +4 -6
  88. package/src/config/schema.ts +4 -5
  89. package/src/config/types.ts +2 -2
  90. package/src/constants/defaults.ts +2 -0
  91. package/src/create-hooks.ts +145 -34
  92. package/src/create-managers.ts +10 -8
  93. package/src/create-tools.ts +13 -9
  94. package/src/features/background-agent/background-manager.ts +93 -87
  95. package/src/features/background-agent/index.ts +1 -1
  96. package/src/features/context-monitor/context-monitor.ts +3 -3
  97. package/src/features/context-monitor/index.ts +2 -2
  98. package/src/features/error-recovery/session-recovery.ts +2 -4
  99. package/src/features/error-recovery/tool-error-recovery.ts +12 -7
  100. package/src/features/index.ts +5 -5
  101. package/src/features/persistent-state/audit-state-manager.ts +143 -60
  102. package/src/features/persistent-state/global-run-index.ts +38 -0
  103. package/src/features/persistent-state/index.ts +1 -1
  104. package/src/features/persistent-state/run-journal.ts +86 -0
  105. package/src/hooks/config-handler.ts +28 -11
  106. package/src/hooks/context-budget.ts +2 -5
  107. package/src/hooks/event-hook.ts +47 -23
  108. package/src/hooks/hook-system.ts +4 -4
  109. package/src/hooks/index.ts +5 -5
  110. package/src/hooks/knowledge-sync-hook.ts +18 -21
  111. package/src/hooks/recon-context-builder.ts +2 -2
  112. package/src/hooks/safe-create-hook.ts +6 -7
  113. package/src/hooks/tool-tracking-hook.ts +104 -50
  114. package/src/hooks/types.ts +2 -1
  115. package/src/index.ts +23 -36
  116. package/src/knowledge/retry.ts +22 -22
  117. package/src/knowledge/scvd-client.ts +88 -95
  118. package/src/knowledge/scvd-errors.ts +35 -35
  119. package/src/knowledge/scvd-index.ts +78 -80
  120. package/src/knowledge/scvd-sync.ts +106 -101
  121. package/src/managers/index.ts +1 -1
  122. package/src/managers/types.ts +19 -14
  123. package/src/plugin-interface.ts +7 -9
  124. package/src/shared/binary-utils.ts +44 -35
  125. package/src/shared/deep-merge.ts +55 -36
  126. package/src/shared/file-utils.ts +21 -19
  127. package/src/shared/index.ts +11 -5
  128. package/src/shared/jsonc-parser.ts +123 -28
  129. package/src/shared/logger.ts +16 -3
  130. package/src/shared/project-utils.ts +30 -0
  131. package/src/skills/analysis/cluster.ts +414 -0
  132. package/src/skills/analysis/gates.ts +227 -0
  133. package/src/skills/analysis/index.ts +33 -0
  134. package/src/skills/analysis/normalize.ts +217 -0
  135. package/src/skills/analysis/similarity.ts +224 -0
  136. package/src/skills/argus-skill-resolver.ts +17 -6
  137. package/src/skills/skill-schema.ts +11 -10
  138. package/src/solodit-lifecycle.ts +202 -0
  139. package/src/state/audit-state.ts +8 -8
  140. package/src/state/finding-store.ts +68 -55
  141. package/src/state/types.ts +88 -67
  142. package/src/tools/argus-skill-load-tool.ts +12 -7
  143. package/src/tools/contract-analyzer-tool.ts +60 -77
  144. package/src/tools/forge-coverage-tool.ts +226 -0
  145. package/src/tools/forge-fuzz-tool.ts +127 -127
  146. package/src/tools/forge-test-tool.ts +153 -157
  147. package/src/tools/gas-analysis-tool.ts +264 -0
  148. package/src/tools/pattern-checker-tool.ts +185 -190
  149. package/src/tools/pattern-loader.ts +5 -111
  150. package/src/tools/proxy-detection-tool.ts +224 -0
  151. package/src/tools/report-generator-tool.ts +268 -200
  152. package/src/tools/slither-tool.ts +266 -218
  153. package/src/tools/solodit-search-tool.ts +216 -119
  154. package/src/tools/sync-knowledge-tool.ts +7 -11
  155. package/src/utils/audit-artifact-detector.ts +28 -29
  156. package/src/utils/dependency-scanner.ts +37 -37
  157. package/src/utils/project-detector.ts +111 -124
  158. package/src/utils/solidity-parser.ts +103 -74
  159. package/skills/patterns/access-control.yaml +0 -31
  160. package/skills/patterns/erc4626.yaml +0 -29
  161. package/skills/patterns/flash-loan.yaml +0 -20
  162. package/skills/patterns/oracle.yaml +0 -30
  163. package/skills/patterns/proxy.yaml +0 -30
  164. package/skills/patterns/reentrancy.yaml +0 -30
  165. package/skills/patterns/signature.yaml +0 -31
  166. package/src/hooks/event-hook-v2.ts +0 -99
  167. package/src/state/plugin-state.ts +0 -14
@@ -1,7 +1,7 @@
1
1
  import { existsSync, mkdirSync, writeFileSync } from "node:fs"
2
2
  import { join } from "node:path"
3
- import type { CliCommand } from "../types"
4
3
  import { cliOutput } from "../cli-output"
4
+ import type { CliCommand } from "../types"
5
5
 
6
6
  const GREEN = "\x1b[32m"
7
7
  const YELLOW = "\x1b[33m"
@@ -18,13 +18,15 @@ const DEFAULT_CONFIG = {
18
18
  export const initCommand: CliCommand = {
19
19
  name: "init",
20
20
  description: "Initialize Argus configuration for this project",
21
- async execute(args: string[]): Promise<number> {
21
+ async execute(_args: string[]): Promise<number> {
22
22
  const cwd = process.cwd()
23
23
  const configDir = join(cwd, ".opencode")
24
24
  const configPath = join(configDir, "solidity-argus.json")
25
25
 
26
26
  if (existsSync(configPath)) {
27
- cliOutput.error(`${YELLOW}⚠${RESET} Config already exists: ${configPath} — remove it first if you want to reinitialize.`)
27
+ cliOutput.error(
28
+ `${YELLOW}⚠${RESET} Config already exists: ${configPath} — remove it first if you want to reinitialize.`,
29
+ )
28
30
  return 1
29
31
  }
30
32
 
@@ -1,8 +1,8 @@
1
1
  import { existsSync, readFileSync, writeFileSync } from "node:fs"
2
- import { join } from "node:path"
3
2
  import { homedir } from "node:os"
4
- import type { CliCommand } from "../types"
3
+ import { join } from "node:path"
5
4
  import { cliOutput } from "../cli-output"
5
+ import type { CliCommand } from "../types"
6
6
 
7
7
  const GREEN = "\x1b[32m"
8
8
  const YELLOW = "\x1b[33m"
@@ -23,11 +23,13 @@ export function findOpencodeConfig(homeOverride?: string): string | null {
23
23
  export const installCommand: CliCommand = {
24
24
  name: "install",
25
25
  description: "Register solidity-argus in your OpenCode config",
26
- async execute(args: string[]): Promise<number> {
26
+ async execute(_args: string[]): Promise<number> {
27
27
  const configPath = findOpencodeConfig()
28
28
 
29
29
  if (!configPath) {
30
- cliOutput.error(`${YELLOW}⚠${RESET} opencode.json not found — create one first, or run: opencode init`)
30
+ cliOutput.error(
31
+ `${YELLOW}⚠${RESET} opencode.json not found — create one first, or run: opencode init`,
32
+ )
31
33
  return 1
32
34
  }
33
35
 
@@ -47,7 +49,7 @@ export const installCommand: CliCommand = {
47
49
 
48
50
  cliOutput.log(`${GREEN}✓${RESET} Added solidity-argus to ${configPath}`)
49
51
  return 0
50
- } catch (error) {
52
+ } catch (_error) {
51
53
  cliOutput.error(`${YELLOW}⚠${RESET} Failed to update ${configPath}`)
52
54
  return 1
53
55
  }
@@ -1,16 +1,20 @@
1
- import { readFileSync, readdirSync } from "node:fs"
2
- import { extname, join } from "node:path"
1
+ import { readdirSync, readFileSync } from "node:fs"
2
+ import { join } from "node:path"
3
+ import { createLogger } from "../../shared/logger"
3
4
  import type { CliCommand } from "../types"
5
+
6
+ const logger = createLogger()
7
+
8
+ import { loadArgusConfig } from "../../config/loader"
4
9
  import { resolveSkillRoots } from "../../skills/argus-skill-resolver"
5
10
  import { parseFrontmatter, validateSkillFrontmatter } from "../../skills/skill-schema"
6
- import { loadArgusConfig } from "../../config/loader"
7
11
  import { cliOutput } from "../cli-output"
8
12
 
9
13
  const GREEN = "\x1b[32m"
10
14
  const RED = "\x1b[31m"
11
15
  const RESET = "\x1b[0m"
12
16
 
13
- function findMarkdownFiles(dir: string, maxDepth = 8): string[] {
17
+ function findSkillFiles(dir: string, maxDepth = 8): string[] {
14
18
  const files: string[] = []
15
19
  const stack: Array<{ path: string; depth: number }> = [{ path: dir, depth: 0 }]
16
20
 
@@ -24,13 +28,11 @@ function findMarkdownFiles(dir: string, maxDepth = 8): string[] {
24
28
  const fullPath = join(current.path, entry.name)
25
29
  if (entry.isDirectory()) {
26
30
  stack.push({ path: fullPath, depth: current.depth + 1 })
27
- } else if (entry.isFile() && extname(entry.name).toLowerCase() === ".md") {
31
+ } else if (entry.isFile() && entry.name.toUpperCase() === "SKILL.MD") {
28
32
  files.push(fullPath)
29
33
  }
30
34
  }
31
- } catch {
32
- continue
33
- }
35
+ } catch {}
34
36
  }
35
37
 
36
38
  return files
@@ -77,26 +79,28 @@ export const lintSkillsCommand: CliCommand = {
77
79
  try {
78
80
  config = loadArgusConfig(cwd)
79
81
  } catch {
80
- // fallback to undefined, resolveSkillRoots handles this
82
+ logger.debug("Config load failed, using defaults")
81
83
  }
82
84
 
83
85
  const roots = resolveSkillRoots(cwd, config)
84
86
  const skillFiles: Array<{ path: string; content: string }> = []
85
87
 
86
88
  for (const root of roots) {
87
- const files = findMarkdownFiles(root.path)
89
+ const files = findSkillFiles(root.path)
88
90
  for (const file of files) {
89
91
  try {
90
92
  skillFiles.push({ path: file, content: readFileSync(file, "utf8") })
91
93
  } catch {
92
- // continue on read errors
94
+ logger.debug("Skipping unreadable skill file")
93
95
  }
94
96
  }
95
97
  }
96
98
 
97
99
  const result = lintSkillFiles(skillFiles)
98
100
 
99
- cliOutput.log(`Skill Lint: ${result.valid} valid, ${result.invalid} invalid, ${result.skipped} skipped (no frontmatter)`)
101
+ cliOutput.log(
102
+ `Skill Lint: ${result.valid} valid, ${result.invalid} invalid, ${result.skipped} skipped (no frontmatter)`,
103
+ )
100
104
 
101
105
  if (result.errors.length > 0) {
102
106
  for (const { file, errors } of result.errors) {
package/src/cli/index.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env bun
2
- import { createCliProgram } from "./cli-program";
2
+ import { createCliProgram } from "./cli-program"
3
3
 
4
- const program = createCliProgram();
5
- const args = Bun.argv.slice(2);
6
- const exitCode = await program.dispatch(args);
4
+ const program = createCliProgram()
5
+ const args = Bun.argv.slice(2)
6
+ const exitCode = await program.dispatch(args)
7
7
 
8
8
  // Set exit code without process.exit() so stdout flushes before termination
9
- process.exitCode = exitCode;
9
+ process.exitCode = exitCode
package/src/cli/types.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  * Defines the contract for all CLI subcommands
4
4
  */
5
5
  export interface CliCommand {
6
- name: string;
7
- description: string;
8
- execute: (args: string[]) => Promise<number>;
6
+ name: string
7
+ description: string
8
+ execute: (args: string[]) => Promise<number>
9
9
  }
@@ -1,3 +1,3 @@
1
+ export { loadArgusConfig } from "./loader"
1
2
  export { ArgusConfigSchema } from "./schema"
2
3
  export type { ArgusConfig } from "./types"
3
- export { loadArgusConfig } from "./loader"
@@ -1,10 +1,10 @@
1
1
  import { homedir } from "node:os"
2
2
  import { join } from "node:path"
3
- import { ArgusConfigSchema } from "./schema"
4
- import type { ArgusConfig } from "./types"
5
- import { detectConfigFile, readJsoncFile } from "../shared/file-utils"
6
3
  import { deepMerge } from "../shared/deep-merge"
4
+ import { detectConfigFile, readJsoncFile } from "../shared/file-utils"
7
5
  import { createLogger } from "../shared/logger"
6
+ import { ArgusConfigSchema } from "./schema"
7
+ import type { ArgusConfig } from "./types"
8
8
 
9
9
  export function _mergeConfigs(
10
10
  userRaw: Record<string, unknown> | null,
@@ -28,9 +28,7 @@ export function loadArgusConfig(projectDir: string): ArgusConfig {
28
28
  const userRaw = userConfigInfo.path ? readJsoncFile(userConfigInfo.path) : null
29
29
 
30
30
  const projectConfigInfo = detectConfigFile(projectDir)
31
- const projectRaw = projectConfigInfo.path
32
- ? readJsoncFile(projectConfigInfo.path)
33
- : null
31
+ const projectRaw = projectConfigInfo.path ? readJsoncFile(projectConfigInfo.path) : null
34
32
 
35
33
  return _mergeConfigs(userRaw, projectRaw)
36
34
  }
@@ -2,6 +2,7 @@ import { z } from "zod"
2
2
 
3
3
  const AgentConfigSchema = z.object({
4
4
  model: z.string().optional(),
5
+ steps: z.number().positive().optional(),
5
6
  permission: z.record(z.string(), z.any()).optional(),
6
7
  tools: z.record(z.string(), z.boolean()).optional(),
7
8
  })
@@ -28,13 +29,11 @@ const KnowledgeConfigSchema = z.object({
28
29
 
29
30
  const ReportingConfigSchema = z.object({
30
31
  format: z.enum(["markdown"]).default("markdown"),
31
- severityThreshold: z
32
- .enum(["critical", "high", "medium", "low", "informational"])
33
- .default("low"),
32
+ severityThreshold: z.enum(["critical", "high", "medium", "low", "informational"]).default("low"),
34
33
  gasAnalysis: z.boolean().default(false),
35
34
  })
36
35
 
37
- const SolditConfigSchema = z.object({
36
+ const SoloditConfigSchema = z.object({
38
37
  enabled: z.boolean().default(true),
39
38
  port: z.number().default(3000),
40
39
  })
@@ -71,7 +70,7 @@ export const ArgusConfigSchema = z.object({
71
70
  severityThreshold: "low",
72
71
  gasAnalysis: false,
73
72
  }),
74
- solodit: SolditConfigSchema.default({
73
+ solodit: SoloditConfigSchema.default({
75
74
  enabled: true,
76
75
  port: 3000,
77
76
  }),
@@ -1,4 +1,4 @@
1
- import { z } from "zod"
2
- import { ArgusConfigSchema } from "./schema"
1
+ import type { z } from "zod"
2
+ import type { ArgusConfigSchema } from "./schema"
3
3
 
4
4
  export type ArgusConfig = z.infer<typeof ArgusConfigSchema>
@@ -4,3 +4,5 @@ export const DEFAULT_MODELS = {
4
4
  pythia: "anthropic/claude-sonnet-4-6",
5
5
  scribe: "anthropic/claude-sonnet-4-6",
6
6
  } as const
7
+
8
+ export const DEFAULT_STEPS = 50 as const
@@ -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