solidity-argus 0.3.7 → 0.5.7

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 (108) hide show
  1. package/AGENTS.md +13 -6
  2. package/README.md +24 -12
  3. package/package.json +7 -3
  4. package/skills/checklists/cyfrin-best-practices-runtime/SKILL.md +1 -0
  5. package/skills/checklists/cyfrin-best-practices-upgrades/SKILL.md +1 -0
  6. package/skills/checklists/cyfrin-defi-core/SKILL.md +1 -0
  7. package/skills/checklists/cyfrin-defi-integrations/SKILL.md +1 -0
  8. package/skills/checklists/cyfrin-gas/SKILL.md +1 -0
  9. package/skills/checklists/general-audit/SKILL.md +1 -0
  10. package/skills/methodology/audit-workflow/SKILL.md +1 -0
  11. package/skills/methodology/report-template/SKILL.md +1 -0
  12. package/skills/methodology/severity-classification/SKILL.md +1 -0
  13. package/skills/protocol-patterns/amm-dex/SKILL.md +1 -0
  14. package/skills/protocol-patterns/bridges-cross-chain/SKILL.md +1 -0
  15. package/skills/protocol-patterns/dao-governance/SKILL.md +1 -0
  16. package/skills/protocol-patterns/lending-borrowing/SKILL.md +1 -0
  17. package/skills/protocol-patterns/staking-vesting/SKILL.md +1 -0
  18. package/skills/vulnerability-patterns/flash-loan-attacks/SKILL.md +0 -50
  19. package/skills/vulnerability-patterns/oracle-manipulation/SKILL.md +0 -63
  20. package/src/agents/argus-prompt.ts +98 -33
  21. package/src/agents/pythia-prompt.ts +24 -2
  22. package/src/agents/scribe-prompt.ts +34 -10
  23. package/src/agents/sentinel-prompt.ts +19 -0
  24. package/src/agents/themis-prompt.ts +110 -0
  25. package/src/cli/commands/doctor.ts +29 -17
  26. package/src/cli/commands/install.ts +74 -33
  27. package/src/config/loader.ts +29 -5
  28. package/src/config/schema.ts +45 -45
  29. package/src/constants/defaults.ts +1 -0
  30. package/src/create-hooks.ts +806 -173
  31. package/src/create-managers.ts +4 -2
  32. package/src/create-tools.ts +5 -1
  33. package/src/features/audit-enforcer/audit-enforcer.ts +1 -11
  34. package/src/features/background-agent/background-manager.ts +32 -5
  35. package/src/features/error-recovery/tool-error-recovery.ts +1 -0
  36. package/src/features/persistent-state/audit-state-manager.ts +272 -29
  37. package/src/features/persistent-state/event-sink.ts +96 -25
  38. package/src/features/persistent-state/findings-materializer.ts +68 -2
  39. package/src/features/persistent-state/global-run-index.ts +86 -8
  40. package/src/features/persistent-state/index.ts +7 -1
  41. package/src/features/persistent-state/run-finalizer.ts +116 -7
  42. package/src/features/persistent-state/run-pruner.ts +93 -0
  43. package/src/hooks/agent-tracker.ts +14 -2
  44. package/src/hooks/compaction-hook.ts +7 -16
  45. package/src/hooks/config-handler.ts +83 -29
  46. package/src/hooks/context-budget.ts +4 -5
  47. package/src/hooks/event-hook.ts +213 -57
  48. package/src/hooks/knowledge-sync-hook.ts +2 -3
  49. package/src/hooks/safe-create-hook.ts +13 -1
  50. package/src/hooks/system-prompt-hook.ts +20 -39
  51. package/src/hooks/tool-tracking-hook.ts +602 -323
  52. package/src/index.ts +15 -1
  53. package/src/knowledge/scvd-client.ts +2 -4
  54. package/src/knowledge/scvd-errors.ts +25 -2
  55. package/src/knowledge/scvd-index.ts +7 -5
  56. package/src/knowledge/scvd-sync.ts +6 -6
  57. package/src/managers/types.ts +20 -2
  58. package/src/shared/agent-names.ts +23 -0
  59. package/src/shared/audit-artifact-resolver.ts +8 -3
  60. package/src/shared/audit-phases.ts +12 -0
  61. package/src/shared/cache-paths.ts +41 -0
  62. package/src/shared/drop-diagnostics.ts +2 -2
  63. package/src/shared/forge-errors.ts +31 -0
  64. package/src/shared/forge-runner.ts +30 -0
  65. package/src/shared/format-error.ts +3 -0
  66. package/src/shared/index.ts +9 -0
  67. package/src/shared/key-tools.ts +39 -0
  68. package/src/shared/logger.ts +7 -7
  69. package/src/shared/path-containment.ts +25 -0
  70. package/src/shared/path-utils.ts +11 -0
  71. package/src/shared/report-path-resolver.ts +4 -2
  72. package/src/shared/safe-emit.ts +24 -0
  73. package/src/shared/token-utils.ts +5 -0
  74. package/src/shared/type-guards.ts +8 -0
  75. package/src/shared/validation-constants.ts +52 -0
  76. package/src/skills/analysis/cluster.ts +1 -114
  77. package/src/skills/analysis/normalize.ts +2 -114
  78. package/src/skills/analysis/stopwords.ts +109 -0
  79. package/src/skills/argus-skill-resolver.ts +6 -3
  80. package/src/solodit-lifecycle.ts +153 -37
  81. package/src/state/adapters.ts +60 -66
  82. package/src/state/finding-aggregation.ts +6 -8
  83. package/src/state/finding-fingerprint.ts +1 -1
  84. package/src/state/finding-store.ts +31 -9
  85. package/src/state/index.ts +1 -1
  86. package/src/state/projectors.ts +27 -19
  87. package/src/state/schemas.ts +8 -32
  88. package/src/state/types.ts +3 -0
  89. package/src/tools/contract-analyzer-tool.ts +4 -6
  90. package/src/tools/forge-coverage-tool.ts +10 -35
  91. package/src/tools/forge-fuzz-tool.ts +21 -51
  92. package/src/tools/forge-test-tool.ts +25 -47
  93. package/src/tools/gas-analysis-tool.ts +12 -41
  94. package/src/tools/pattern-checker-tool.ts +37 -15
  95. package/src/tools/pattern-loader.ts +18 -4
  96. package/src/tools/persist-deduped-tool.ts +94 -0
  97. package/src/tools/proxy-detection-tool.ts +35 -34
  98. package/src/tools/read-findings-tool.ts +390 -0
  99. package/src/tools/record-finding-tool.ts +130 -25
  100. package/src/tools/report-generator-tool.ts +475 -327
  101. package/src/tools/report-preflight.ts +5 -1
  102. package/src/tools/slither-tool.ts +55 -16
  103. package/src/tools/solodit-search-tool.ts +260 -112
  104. package/src/tools/sync-knowledge-tool.ts +2 -3
  105. package/src/utils/solidity-parser.ts +39 -24
  106. package/src/features/migration/index.ts +0 -14
  107. package/src/features/migration/migration-adapter.ts +0 -151
  108. package/src/features/migration/parity-telemetry.ts +0 -133
@@ -1,57 +1,98 @@
1
- import { existsSync, readFileSync, writeFileSync } from "node:fs"
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"
2
2
  import { homedir } from "node:os"
3
- import { join } from "node:path"
3
+ import { dirname, join } from "node:path"
4
4
  import { cliOutput } from "../cli-output"
5
+ import { confirm } from "../tui-prompts"
5
6
  import type { CliCommand } from "../types"
6
7
 
7
8
  const GREEN = "\x1b[32m"
8
9
  const YELLOW = "\x1b[33m"
9
10
  const RESET = "\x1b[0m"
10
11
 
12
+ function resolveHome(homeOverride?: string): string {
13
+ if (homeOverride && homeOverride.length > 0) return homeOverride
14
+ const envHome = process.env.HOME ?? process.env.USERPROFILE
15
+ if (envHome && envHome.length > 0) return envHome
16
+ return homedir()
17
+ }
18
+
19
+ function localConfigPath(): string {
20
+ return join(process.cwd(), "opencode.json")
21
+ }
22
+
23
+ function globalConfigPath(homeOverride?: string): string {
24
+ return join(resolveHome(homeOverride), ".config", "opencode", "opencode.json")
25
+ }
26
+
11
27
  export function findOpencodeConfig(homeOverride?: string): string | null {
12
- const cwd = process.cwd()
13
- const localPath = join(cwd, "opencode.json")
14
- if (existsSync(localPath)) return localPath
28
+ const local = localConfigPath()
29
+ if (existsSync(local)) return local
15
30
 
16
- const home = homeOverride ?? homedir()
17
- const globalPath = join(home, ".config", "opencode", "opencode.json")
18
- if (existsSync(globalPath)) return globalPath
31
+ const global = globalConfigPath(homeOverride)
32
+ if (existsSync(global)) return global
19
33
 
20
34
  return null
21
35
  }
22
36
 
37
+ function addPluginToConfig(configPath: string): { added: boolean; ok: boolean } {
38
+ try {
39
+ let config: Record<string, unknown>
40
+ if (existsSync(configPath)) {
41
+ const content = readFileSync(configPath, "utf-8")
42
+ config = JSON.parse(content)
43
+ } else {
44
+ mkdirSync(dirname(configPath), { recursive: true })
45
+ config = {}
46
+ }
47
+
48
+ const plugins = Array.isArray(config.plugin) ? (config.plugin as string[]) : []
49
+ if (plugins.includes("solidity-argus")) {
50
+ cliOutput.log(`${GREEN}✓${RESET} solidity-argus already registered in ${configPath}`)
51
+ return { added: false, ok: true }
52
+ }
53
+
54
+ plugins.push("solidity-argus")
55
+ config.plugin = plugins
56
+ writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`)
57
+ cliOutput.log(`${GREEN}✓${RESET} Added solidity-argus to ${configPath}`)
58
+ return { added: true, ok: true }
59
+ } catch (_error) {
60
+ cliOutput.error(`${YELLOW}⚠${RESET} Failed to update ${configPath}`)
61
+ return { added: false, ok: false }
62
+ }
63
+ }
64
+
23
65
  export const installCommand: CliCommand = {
24
66
  name: "install",
25
- description: "Register solidity-argus in your OpenCode config",
26
- async execute(_args: string[]): Promise<number> {
27
- const configPath = findOpencodeConfig()
28
-
29
- if (!configPath) {
30
- cliOutput.error(
31
- `${YELLOW}⚠${RESET} opencode.json not found — create one first, or run: opencode init`,
32
- )
33
- return 1
34
- }
67
+ description: "Register solidity-argus in your OpenCode config (use --global for ~/.config/opencode)",
68
+ async execute(args: string[]): Promise<number> {
69
+ const isGlobal = args.includes("--global") || args.includes("-g")
70
+ const local = localConfigPath()
35
71
 
36
- try {
37
- const content = readFileSync(configPath, "utf-8")
38
- const config = JSON.parse(content)
39
- const plugins: string[] = config.plugin ?? []
72
+ if (existsSync(local) && !isGlobal) {
73
+ return addPluginToConfig(local).ok ? 0 : 1
74
+ }
40
75
 
41
- if (plugins.includes("solidity-argus")) {
42
- cliOutput.log(`${GREEN}✓${RESET} solidity-argus already registered in ${configPath}`)
43
- return 0
44
- }
76
+ if (isGlobal) {
77
+ return addPluginToConfig(globalConfigPath()).ok ? 0 : 1
78
+ }
45
79
 
46
- plugins.push("solidity-argus")
47
- config.plugin = plugins
48
- writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`)
80
+ const global = globalConfigPath()
81
+ cliOutput.warn(
82
+ `${YELLOW}⚠${RESET} No opencode.json found in current directory (${process.cwd()}).`,
83
+ )
84
+ cliOutput.warn(
85
+ ` Installing globally would write to ${global} and load solidity-argus in EVERY OpenCode session.`,
86
+ )
87
+ cliOutput.warn(` To install globally on purpose, re-run with: argus install --global`)
88
+ cliOutput.warn(` To install for this project, first create an opencode.json in this directory.`)
49
89
 
50
- cliOutput.log(`${GREEN}✓${RESET} Added solidity-argus to ${configPath}`)
90
+ const proceed = await confirm("Install globally anyway?", false)
91
+ if (!proceed) {
92
+ cliOutput.log("Aborted. No changes made.")
51
93
  return 0
52
- } catch (_error) {
53
- cliOutput.error(`${YELLOW}⚠${RESET} Failed to update ${configPath}`)
54
- return 1
55
94
  }
95
+
96
+ return addPluginToConfig(global).ok ? 0 : 1
56
97
  },
57
98
  }
@@ -1,5 +1,6 @@
1
1
  import { homedir } from "node:os"
2
2
  import { join } from "node:path"
3
+ import type { z } from "zod"
3
4
  import { deepMerge } from "../shared/deep-merge"
4
5
  import { detectConfigFile, readJsoncFile } from "../shared/file-utils"
5
6
  import { createLogger } from "../shared/logger"
@@ -11,15 +12,38 @@ export function _mergeConfigs(
11
12
  projectRaw: Record<string, unknown> | null,
12
13
  ): ArgusConfig {
13
14
  const logger = createLogger()
14
- const merged = deepMerge(userRaw ?? {}, projectRaw ?? {})
15
+ const merged = deepMerge(userRaw ?? {}, projectRaw ?? {}) as Record<string, unknown>
15
16
 
16
17
  const result = ArgusConfigSchema.safeParse(merged)
17
- if (!result.success) {
18
- logger.warn("Invalid argus config, using defaults:", result.error.message)
19
- return ArgusConfigSchema.parse({})
18
+ if (result.success) {
19
+ return result.data
20
20
  }
21
21
 
22
- return result.data
22
+ // Warn about unknown keys (typos like 'disbled_hooks' instead of 'disabled_hooks')
23
+ const knownKeys = new Set(Object.keys(ArgusConfigSchema.shape))
24
+ for (const key of Object.keys(merged)) {
25
+ if (!knownKeys.has(key)) {
26
+ logger.warn(`Unknown config key '${key}' — did you mean a known field? Ignoring.`)
27
+ }
28
+ }
29
+
30
+ const invalidFields: string[] = []
31
+ const sanitized: Record<string, unknown> = {}
32
+
33
+ for (const [key, fieldSchema] of Object.entries(ArgusConfigSchema.shape)) {
34
+ if (key in merged) {
35
+ const fieldResult = (fieldSchema as z.ZodTypeAny).safeParse(merged[key])
36
+ if (fieldResult.success) {
37
+ sanitized[key] = fieldResult.data
38
+ } else {
39
+ invalidFields.push(key)
40
+ const issues = fieldResult.error.issues.map((i) => i.message).join(", ")
41
+ logger.error(`Invalid config field '${key}': ${issues}. Using default.`)
42
+ }
43
+ }
44
+ }
45
+
46
+ return ArgusConfigSchema.parse(sanitized)
23
47
  }
24
48
 
25
49
  export function loadArgusConfig(projectDir: string): ArgusConfig {
@@ -3,8 +3,9 @@ import { z } from "zod"
3
3
  const AgentConfigSchema = z.object({
4
4
  model: z.string().optional(),
5
5
  steps: z.number().positive().optional(),
6
- permission: z.record(z.string(), z.any()).optional(),
6
+ permission: z.record(z.string(), z.unknown()).optional(),
7
7
  tools: z.record(z.string(), z.boolean()).optional(),
8
+ temperature: z.number().min(0).max(2).optional(),
8
9
  })
9
10
 
10
11
  const ToolsConfigSchema = z.object({
@@ -36,55 +37,54 @@ const ReportingConfigSchema = z.object({
36
37
 
37
38
  const SoloditConfigSchema = z.object({
38
39
  enabled: z.boolean().default(true),
39
- port: z.number().default(3000),
40
+ port: z.number().default(54173),
40
41
  })
41
42
 
42
43
  const BackgroundConfigSchema = z.object({
43
44
  max_concurrent: z.number().positive().default(3),
44
45
  })
45
46
 
46
- const MigrationConfigSchema = z.object({
47
- mode: z.enum(["legacy", "dual", "strict"]).default("legacy"),
48
- })
49
-
50
- export const ArgusConfigSchema = z.object({
51
- agents: z
52
- .object({
53
- argus: AgentConfigSchema.default({}),
54
- sentinel: AgentConfigSchema.default({}),
55
- pythia: AgentConfigSchema.default({}),
56
- scribe: AgentConfigSchema.default({}),
57
- })
58
- .default({
59
- argus: {},
60
- sentinel: {},
61
- pythia: {},
62
- scribe: {},
47
+ export const ArgusConfigSchema = z
48
+ .object({
49
+ agents: z
50
+ .object({
51
+ argus: AgentConfigSchema.default({}),
52
+ sentinel: AgentConfigSchema.default({}),
53
+ pythia: AgentConfigSchema.default({}),
54
+ scribe: AgentConfigSchema.default({}),
55
+ themis: AgentConfigSchema.optional().default({}),
56
+ })
57
+ .default({
58
+ argus: {},
59
+ sentinel: {},
60
+ pythia: {},
61
+ scribe: {},
62
+ themis: {},
63
+ }),
64
+ tools: ToolsConfigSchema.default({}),
65
+ knowledge: KnowledgeConfigSchema.default({
66
+ scvd: {
67
+ enabled: true,
68
+ apiUrl: "https://api.scvd.dev",
69
+ },
70
+ autoSync: true,
71
+ skillPrecedence: "bundled-first",
72
+ }),
73
+ reporting: ReportingConfigSchema.default({
74
+ format: "markdown",
75
+ severityThreshold: "low",
76
+ gasAnalysis: false,
77
+ output_dir: ".argus/reports/",
63
78
  }),
64
- tools: ToolsConfigSchema.default({}),
65
- knowledge: KnowledgeConfigSchema.default({
66
- scvd: {
79
+ solodit: SoloditConfigSchema.default({
67
80
  enabled: true,
68
- apiUrl: "https://api.scvd.dev",
69
- },
70
- autoSync: true,
71
- skillPrecedence: "bundled-first",
72
- }),
73
- reporting: ReportingConfigSchema.default({
74
- format: "markdown",
75
- severityThreshold: "low",
76
- gasAnalysis: false,
77
- output_dir: ".argus/reports/",
78
- }),
79
- solodit: SoloditConfigSchema.default({
80
- enabled: true,
81
- port: 3000,
82
- }),
83
- disabled_hooks: z.array(z.string()).default([]),
84
- hooks: z.record(z.string(), z.any()).default({}),
85
- cli: z.record(z.string(), z.any()).default({}),
86
- background: BackgroundConfigSchema.default({
87
- max_concurrent: 3,
88
- }),
89
- migration: MigrationConfigSchema.optional(),
90
- })
81
+ port: 54173,
82
+ }),
83
+ disabled_hooks: z.array(z.string()).default([]),
84
+ hooks: z.record(z.string(), z.unknown()).default({}),
85
+ cli: z.record(z.string(), z.unknown()).default({}),
86
+ background: BackgroundConfigSchema.default({
87
+ max_concurrent: 3,
88
+ }),
89
+ })
90
+ .strict()
@@ -3,6 +3,7 @@ export const DEFAULT_MODELS = {
3
3
  sentinel: "anthropic/claude-sonnet-4-6",
4
4
  pythia: "anthropic/claude-sonnet-4-6",
5
5
  scribe: "anthropic/claude-sonnet-4-6",
6
+ themis: "openai/gpt-5.4",
6
7
  } as const
7
8
 
8
9
  export const DEFAULT_STEPS = 50 as const