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.
- package/AGENTS.md +3 -3
- package/README.md +93 -37
- package/package.json +33 -7
- package/skills/INVENTORY.md +88 -57
- package/skills/README.md +26 -23
- package/skills/case-studies/beanstalk-governance/SKILL.md +52 -0
- package/skills/case-studies/bzx-flash-loan/SKILL.md +53 -0
- package/skills/case-studies/cream-finance/SKILL.md +52 -0
- package/skills/case-studies/curve-reentrancy/SKILL.md +52 -0
- package/skills/case-studies/dao-hack/SKILL.md +51 -0
- package/skills/case-studies/euler-finance/SKILL.md +52 -0
- package/skills/case-studies/harvest-finance/SKILL.md +52 -0
- package/skills/case-studies/level-finance/SKILL.md +51 -0
- package/skills/case-studies/mango-markets/SKILL.md +53 -0
- package/skills/case-studies/nomad-bridge/SKILL.md +51 -0
- package/skills/case-studies/parity-multisig/SKILL.md +55 -0
- package/skills/case-studies/poly-network/SKILL.md +51 -0
- package/skills/case-studies/rari-fuse/SKILL.md +51 -0
- package/skills/case-studies/ronin-bridge/SKILL.md +52 -0
- package/skills/case-studies/wormhole-bridge/SKILL.md +51 -0
- package/skills/manifests/smartbugs.json +1 -3
- package/skills/manifests/sunweb3sec.json +1 -3
- package/skills/vulnerability-patterns/access-control/SKILL.md +14 -0
- package/skills/vulnerability-patterns/arbitrary-storage-location/SKILL.md +13 -1
- package/skills/vulnerability-patterns/assert-violation/SKILL.md +8 -1
- package/skills/vulnerability-patterns/asserting-contract-from-code-size/SKILL.md +12 -1
- package/skills/vulnerability-patterns/authorization-txorigin/SKILL.md +2 -1
- package/skills/vulnerability-patterns/cross-chain-bridge-vulnerabilities/SKILL.md +217 -0
- package/skills/vulnerability-patterns/default-visibility/SKILL.md +13 -1
- package/skills/vulnerability-patterns/delegatecall-untrusted-callee/SKILL.md +2 -1
- package/skills/vulnerability-patterns/dos-gas-limit/SKILL.md +8 -1
- package/skills/vulnerability-patterns/dos-revert/SKILL.md +1 -0
- package/skills/vulnerability-patterns/erc4626-exchange-rate-manipulation/SKILL.md +64 -0
- package/skills/vulnerability-patterns/fee-on-transfer-tokens/SKILL.md +93 -0
- package/skills/vulnerability-patterns/flash-loan-attacks/SKILL.md +1 -0
- package/skills/vulnerability-patterns/floating-pragma/SKILL.md +8 -1
- package/skills/vulnerability-patterns/front-running-attacks/SKILL.md +209 -0
- package/skills/vulnerability-patterns/gas-optimization-patterns/SKILL.md +203 -0
- package/skills/vulnerability-patterns/governance-attacks/SKILL.md +208 -0
- package/skills/vulnerability-patterns/hash-collision/SKILL.md +8 -1
- package/skills/vulnerability-patterns/inadherence-to-standards/SKILL.md +12 -1
- package/skills/vulnerability-patterns/incorrect-constructor/SKILL.md +8 -1
- package/skills/vulnerability-patterns/incorrect-inheritance-order/SKILL.md +8 -1
- package/skills/vulnerability-patterns/insufficient-gas-griefing/SKILL.md +12 -1
- package/skills/vulnerability-patterns/lack-of-precision/SKILL.md +7 -1
- package/skills/vulnerability-patterns/logic-errors/SKILL.md +10 -0
- package/skills/vulnerability-patterns/missing-parameter-bounds/SKILL.md +44 -0
- package/skills/vulnerability-patterns/missing-protection-signature-replay/SKILL.md +17 -1
- package/skills/vulnerability-patterns/msgvalue-loop/SKILL.md +12 -1
- package/skills/vulnerability-patterns/off-by-one/SKILL.md +7 -1
- package/skills/vulnerability-patterns/oracle-manipulation/SKILL.md +9 -0
- package/skills/vulnerability-patterns/outdated-compiler-version/SKILL.md +8 -1
- package/skills/vulnerability-patterns/overflow-underflow/SKILL.md +1 -0
- package/skills/vulnerability-patterns/proxy-vulnerabilities/SKILL.md +209 -0
- package/skills/vulnerability-patterns/reentrancy/SKILL.md +9 -0
- package/skills/vulnerability-patterns/shadowing-state-variables/SKILL.md +8 -1
- package/skills/vulnerability-patterns/share-accounting-desynchronization/SKILL.md +44 -0
- package/skills/vulnerability-patterns/signature-malleability/SKILL.md +2 -1
- package/skills/vulnerability-patterns/stateful-parameter-update-drift/SKILL.md +44 -0
- package/skills/vulnerability-patterns/unbounded-return-data/SKILL.md +12 -1
- package/skills/vulnerability-patterns/unchecked-return-values/SKILL.md +2 -1
- package/skills/vulnerability-patterns/unencrypted-private-data-on-chain/SKILL.md +8 -1
- package/skills/vulnerability-patterns/unexpected-ecrecover-null-address/SKILL.md +8 -1
- package/skills/vulnerability-patterns/uninitialized-storage-pointer/SKILL.md +8 -1
- package/skills/vulnerability-patterns/unsafe-erc20-transfers/SKILL.md +132 -0
- package/skills/vulnerability-patterns/unsafe-low-level-call/SKILL.md +12 -1
- package/skills/vulnerability-patterns/unsecure-signatures/SKILL.md +12 -1
- package/skills/vulnerability-patterns/unsupported-opcodes/SKILL.md +11 -1
- package/skills/vulnerability-patterns/unused-variables/SKILL.md +8 -1
- package/skills/vulnerability-patterns/use-of-deprecated-functions/SKILL.md +8 -1
- package/skills/vulnerability-patterns/weak-sources-randomness/SKILL.md +8 -1
- package/skills/vulnerability-patterns/weird-tokens/SKILL.md +10 -0
- package/skills/vulnerability-patterns/zero-address-misconfiguration/SKILL.md +48 -0
- package/src/agents/argus-prompt.ts +24 -7
- package/src/agents/pythia-prompt.ts +3 -4
- package/src/agents/scribe-prompt.ts +7 -2
- package/src/agents/sentinel-prompt.ts +32 -3
- package/src/cli/cli-program.ts +29 -26
- package/src/cli/commands/check-skills.ts +135 -0
- package/src/cli/commands/doctor.ts +48 -26
- package/src/cli/commands/init.ts +5 -3
- package/src/cli/commands/install.ts +7 -5
- package/src/cli/commands/lint-skills.ts +16 -12
- package/src/cli/index.ts +5 -5
- package/src/cli/types.ts +3 -3
- package/src/config/index.ts +1 -1
- package/src/config/loader.ts +4 -6
- package/src/config/schema.ts +4 -5
- package/src/config/types.ts +2 -2
- package/src/constants/defaults.ts +2 -0
- package/src/create-hooks.ts +145 -34
- package/src/create-managers.ts +10 -8
- package/src/create-tools.ts +13 -9
- package/src/features/background-agent/background-manager.ts +93 -87
- package/src/features/background-agent/index.ts +1 -1
- package/src/features/context-monitor/context-monitor.ts +3 -3
- package/src/features/context-monitor/index.ts +2 -2
- package/src/features/error-recovery/session-recovery.ts +2 -4
- package/src/features/error-recovery/tool-error-recovery.ts +12 -7
- package/src/features/index.ts +5 -5
- package/src/features/persistent-state/audit-state-manager.ts +143 -60
- package/src/features/persistent-state/global-run-index.ts +38 -0
- package/src/features/persistent-state/index.ts +1 -1
- package/src/features/persistent-state/run-journal.ts +86 -0
- package/src/hooks/config-handler.ts +28 -11
- package/src/hooks/context-budget.ts +2 -5
- package/src/hooks/event-hook.ts +47 -23
- package/src/hooks/hook-system.ts +4 -4
- package/src/hooks/index.ts +5 -5
- package/src/hooks/knowledge-sync-hook.ts +18 -21
- package/src/hooks/recon-context-builder.ts +2 -2
- package/src/hooks/safe-create-hook.ts +6 -7
- package/src/hooks/tool-tracking-hook.ts +104 -50
- package/src/hooks/types.ts +2 -1
- package/src/index.ts +23 -36
- package/src/knowledge/retry.ts +22 -22
- package/src/knowledge/scvd-client.ts +88 -95
- package/src/knowledge/scvd-errors.ts +35 -35
- package/src/knowledge/scvd-index.ts +78 -80
- package/src/knowledge/scvd-sync.ts +106 -101
- package/src/managers/index.ts +1 -1
- package/src/managers/types.ts +19 -14
- package/src/plugin-interface.ts +7 -9
- package/src/shared/binary-utils.ts +44 -35
- package/src/shared/deep-merge.ts +55 -36
- package/src/shared/file-utils.ts +21 -19
- package/src/shared/index.ts +11 -5
- package/src/shared/jsonc-parser.ts +123 -28
- package/src/shared/logger.ts +16 -3
- package/src/shared/project-utils.ts +30 -0
- package/src/skills/analysis/cluster.ts +414 -0
- package/src/skills/analysis/gates.ts +227 -0
- package/src/skills/analysis/index.ts +33 -0
- package/src/skills/analysis/normalize.ts +217 -0
- package/src/skills/analysis/similarity.ts +224 -0
- package/src/skills/argus-skill-resolver.ts +17 -6
- package/src/skills/skill-schema.ts +11 -10
- package/src/solodit-lifecycle.ts +202 -0
- package/src/state/audit-state.ts +8 -8
- package/src/state/finding-store.ts +68 -55
- package/src/state/types.ts +88 -67
- package/src/tools/argus-skill-load-tool.ts +12 -7
- package/src/tools/contract-analyzer-tool.ts +60 -77
- package/src/tools/forge-coverage-tool.ts +226 -0
- package/src/tools/forge-fuzz-tool.ts +127 -127
- package/src/tools/forge-test-tool.ts +153 -157
- package/src/tools/gas-analysis-tool.ts +264 -0
- package/src/tools/pattern-checker-tool.ts +185 -190
- package/src/tools/pattern-loader.ts +5 -111
- package/src/tools/proxy-detection-tool.ts +224 -0
- package/src/tools/report-generator-tool.ts +268 -200
- package/src/tools/slither-tool.ts +266 -218
- package/src/tools/solodit-search-tool.ts +216 -119
- package/src/tools/sync-knowledge-tool.ts +7 -11
- package/src/utils/audit-artifact-detector.ts +28 -29
- package/src/utils/dependency-scanner.ts +37 -37
- package/src/utils/project-detector.ts +111 -124
- package/src/utils/solidity-parser.ts +103 -74
- package/skills/patterns/access-control.yaml +0 -31
- package/skills/patterns/erc4626.yaml +0 -29
- package/skills/patterns/flash-loan.yaml +0 -20
- package/skills/patterns/oracle.yaml +0 -30
- package/skills/patterns/proxy.yaml +0 -30
- package/skills/patterns/reentrancy.yaml +0 -30
- package/skills/patterns/signature.yaml +0 -31
- package/src/hooks/event-hook-v2.ts +0 -99
- package/src/state/plugin-state.ts +0 -14
package/src/cli/commands/init.ts
CHANGED
|
@@ -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(
|
|
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(
|
|
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
|
|
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(
|
|
26
|
+
async execute(_args: string[]): Promise<number> {
|
|
27
27
|
const configPath = findOpencodeConfig()
|
|
28
28
|
|
|
29
29
|
if (!configPath) {
|
|
30
|
-
cliOutput.error(
|
|
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 (
|
|
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 {
|
|
2
|
-
import {
|
|
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
|
|
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() &&
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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(
|
|
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
package/src/config/index.ts
CHANGED
package/src/config/loader.ts
CHANGED
|
@@ -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
|
}
|
package/src/config/schema.ts
CHANGED
|
@@ -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
|
|
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:
|
|
73
|
+
solodit: SoloditConfigSchema.default({
|
|
75
74
|
enabled: true,
|
|
76
75
|
port: 3000,
|
|
77
76
|
}),
|
package/src/config/types.ts
CHANGED
package/src/create-hooks.ts
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
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
|
|
40
|
+
return _agentTrackerRef?.getAgentForSession(sessionID)
|
|
28
41
|
}
|
|
29
42
|
|
|
30
43
|
export function isArgusAgent(sessionID: string): boolean {
|
|
31
|
-
return
|
|
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
|
-
|
|
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(
|
|
85
|
+
const toolErrorRecoveryHandler = createToolErrorRecoveryHandler(
|
|
86
|
+
() => auditStateGetter?.() ?? null,
|
|
87
|
+
(patch) => auditStateManager.update(patch),
|
|
88
|
+
)
|
|
71
89
|
const outputTruncator = createToolOutputTruncator()
|
|
72
90
|
|
|
73
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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: () =>
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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
|
-
|
|
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(
|
|
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,
|
package/src/create-managers.ts
CHANGED
|
@@ -1,23 +1,25 @@
|
|
|
1
1
|
import type { ArgusConfig } from "./config/types"
|
|
2
|
-
import type {
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
`Background dispatch not wired: ${agentName} (${prompt.slice(0, 50)}...)
|
|
18
|
-
|
|
19
|
-
|
|
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)
|
package/src/create-tools.ts
CHANGED
|
@@ -1,31 +1,35 @@
|
|
|
1
1
|
import type { ToolDefinition } from "@opencode-ai/plugin"
|
|
2
2
|
import type { ArgusConfig } from "./config/types"
|
|
3
|
-
import {
|
|
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 {
|
|
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 =
|
|
32
|
+
tools.argus_solodit_search = createSoloditSearchTool(config.solodit?.port ?? 3000)
|
|
29
33
|
}
|
|
30
34
|
|
|
31
35
|
return tools
|