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.
- package/AGENTS.md +13 -6
- package/README.md +24 -12
- package/package.json +7 -3
- package/skills/checklists/cyfrin-best-practices-runtime/SKILL.md +1 -0
- package/skills/checklists/cyfrin-best-practices-upgrades/SKILL.md +1 -0
- package/skills/checklists/cyfrin-defi-core/SKILL.md +1 -0
- package/skills/checklists/cyfrin-defi-integrations/SKILL.md +1 -0
- package/skills/checklists/cyfrin-gas/SKILL.md +1 -0
- package/skills/checklists/general-audit/SKILL.md +1 -0
- package/skills/methodology/audit-workflow/SKILL.md +1 -0
- package/skills/methodology/report-template/SKILL.md +1 -0
- package/skills/methodology/severity-classification/SKILL.md +1 -0
- package/skills/protocol-patterns/amm-dex/SKILL.md +1 -0
- package/skills/protocol-patterns/bridges-cross-chain/SKILL.md +1 -0
- package/skills/protocol-patterns/dao-governance/SKILL.md +1 -0
- package/skills/protocol-patterns/lending-borrowing/SKILL.md +1 -0
- package/skills/protocol-patterns/staking-vesting/SKILL.md +1 -0
- package/skills/vulnerability-patterns/flash-loan-attacks/SKILL.md +0 -50
- package/skills/vulnerability-patterns/oracle-manipulation/SKILL.md +0 -63
- package/src/agents/argus-prompt.ts +98 -33
- package/src/agents/pythia-prompt.ts +24 -2
- package/src/agents/scribe-prompt.ts +34 -10
- package/src/agents/sentinel-prompt.ts +19 -0
- package/src/agents/themis-prompt.ts +110 -0
- package/src/cli/commands/doctor.ts +29 -17
- package/src/cli/commands/install.ts +74 -33
- package/src/config/loader.ts +29 -5
- package/src/config/schema.ts +45 -45
- package/src/constants/defaults.ts +1 -0
- package/src/create-hooks.ts +806 -173
- package/src/create-managers.ts +4 -2
- package/src/create-tools.ts +5 -1
- package/src/features/audit-enforcer/audit-enforcer.ts +1 -11
- package/src/features/background-agent/background-manager.ts +32 -5
- package/src/features/error-recovery/tool-error-recovery.ts +1 -0
- package/src/features/persistent-state/audit-state-manager.ts +272 -29
- package/src/features/persistent-state/event-sink.ts +96 -25
- package/src/features/persistent-state/findings-materializer.ts +68 -2
- package/src/features/persistent-state/global-run-index.ts +86 -8
- package/src/features/persistent-state/index.ts +7 -1
- package/src/features/persistent-state/run-finalizer.ts +116 -7
- package/src/features/persistent-state/run-pruner.ts +93 -0
- package/src/hooks/agent-tracker.ts +14 -2
- package/src/hooks/compaction-hook.ts +7 -16
- package/src/hooks/config-handler.ts +83 -29
- package/src/hooks/context-budget.ts +4 -5
- package/src/hooks/event-hook.ts +213 -57
- package/src/hooks/knowledge-sync-hook.ts +2 -3
- package/src/hooks/safe-create-hook.ts +13 -1
- package/src/hooks/system-prompt-hook.ts +20 -39
- package/src/hooks/tool-tracking-hook.ts +602 -323
- package/src/index.ts +15 -1
- package/src/knowledge/scvd-client.ts +2 -4
- package/src/knowledge/scvd-errors.ts +25 -2
- package/src/knowledge/scvd-index.ts +7 -5
- package/src/knowledge/scvd-sync.ts +6 -6
- package/src/managers/types.ts +20 -2
- package/src/shared/agent-names.ts +23 -0
- package/src/shared/audit-artifact-resolver.ts +8 -3
- package/src/shared/audit-phases.ts +12 -0
- package/src/shared/cache-paths.ts +41 -0
- package/src/shared/drop-diagnostics.ts +2 -2
- package/src/shared/forge-errors.ts +31 -0
- package/src/shared/forge-runner.ts +30 -0
- package/src/shared/format-error.ts +3 -0
- package/src/shared/index.ts +9 -0
- package/src/shared/key-tools.ts +39 -0
- package/src/shared/logger.ts +7 -7
- package/src/shared/path-containment.ts +25 -0
- package/src/shared/path-utils.ts +11 -0
- package/src/shared/report-path-resolver.ts +4 -2
- package/src/shared/safe-emit.ts +24 -0
- package/src/shared/token-utils.ts +5 -0
- package/src/shared/type-guards.ts +8 -0
- package/src/shared/validation-constants.ts +52 -0
- package/src/skills/analysis/cluster.ts +1 -114
- package/src/skills/analysis/normalize.ts +2 -114
- package/src/skills/analysis/stopwords.ts +109 -0
- package/src/skills/argus-skill-resolver.ts +6 -3
- package/src/solodit-lifecycle.ts +153 -37
- package/src/state/adapters.ts +60 -66
- package/src/state/finding-aggregation.ts +6 -8
- package/src/state/finding-fingerprint.ts +1 -1
- package/src/state/finding-store.ts +31 -9
- package/src/state/index.ts +1 -1
- package/src/state/projectors.ts +27 -19
- package/src/state/schemas.ts +8 -32
- package/src/state/types.ts +3 -0
- package/src/tools/contract-analyzer-tool.ts +4 -6
- package/src/tools/forge-coverage-tool.ts +10 -35
- package/src/tools/forge-fuzz-tool.ts +21 -51
- package/src/tools/forge-test-tool.ts +25 -47
- package/src/tools/gas-analysis-tool.ts +12 -41
- package/src/tools/pattern-checker-tool.ts +37 -15
- package/src/tools/pattern-loader.ts +18 -4
- package/src/tools/persist-deduped-tool.ts +94 -0
- package/src/tools/proxy-detection-tool.ts +35 -34
- package/src/tools/read-findings-tool.ts +390 -0
- package/src/tools/record-finding-tool.ts +130 -25
- package/src/tools/report-generator-tool.ts +475 -327
- package/src/tools/report-preflight.ts +5 -1
- package/src/tools/slither-tool.ts +55 -16
- package/src/tools/solodit-search-tool.ts +260 -112
- package/src/tools/sync-knowledge-tool.ts +2 -3
- package/src/utils/solidity-parser.ts +39 -24
- package/src/features/migration/index.ts +0 -14
- package/src/features/migration/migration-adapter.ts +0 -151
- 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
|
|
13
|
-
|
|
14
|
-
if (existsSync(localPath)) return localPath
|
|
28
|
+
const local = localConfigPath()
|
|
29
|
+
if (existsSync(local)) return local
|
|
15
30
|
|
|
16
|
-
const
|
|
17
|
-
|
|
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(
|
|
27
|
-
const
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const plugins: string[] = config.plugin ?? []
|
|
72
|
+
if (existsSync(local) && !isGlobal) {
|
|
73
|
+
return addPluginToConfig(local).ok ? 0 : 1
|
|
74
|
+
}
|
|
40
75
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
}
|
|
76
|
+
if (isGlobal) {
|
|
77
|
+
return addPluginToConfig(globalConfigPath()).ok ? 0 : 1
|
|
78
|
+
}
|
|
45
79
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
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
|
}
|
package/src/config/loader.ts
CHANGED
|
@@ -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 (
|
|
18
|
-
|
|
19
|
-
return ArgusConfigSchema.parse({})
|
|
18
|
+
if (result.success) {
|
|
19
|
+
return result.data
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
|
|
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 {
|
package/src/config/schema.ts
CHANGED
|
@@ -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.
|
|
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(
|
|
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
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
|
|
65
|
-
knowledge: KnowledgeConfigSchema.default({
|
|
66
|
-
scvd: {
|
|
79
|
+
solodit: SoloditConfigSchema.default({
|
|
67
80
|
enabled: true,
|
|
68
|
-
|
|
69
|
-
},
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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()
|