solidity-argus 0.1.8 → 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 +229 -13
- package/package.json +37 -8
- package/skills/INVENTORY.md +88 -57
- package/skills/README.md +72 -6
- 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/checklists/cyfrin-defi-core/SKILL.md +3 -0
- package/skills/manifests/cyfrin.json +16 -0
- package/skills/manifests/defifofum.json +25 -0
- package/skills/manifests/kadenzipfel.json +48 -0
- package/skills/manifests/scvd.json +9 -0
- package/skills/manifests/smartbugs.json +9 -0
- package/skills/manifests/solodit.json +9 -0
- package/skills/manifests/sunweb3sec.json +9 -0
- package/skills/manifests/trailofbits.json +9 -0
- package/skills/methodology/audit-workflow/SKILL.md +3 -0
- package/skills/protocol-patterns/amm-dex/SKILL.md +3 -0
- package/skills/references/exploit-reference/SKILL.md +3 -0
- package/skills/vulnerability-patterns/access-control/SKILL.md +27 -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 +8 -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 +8 -1
- package/skills/vulnerability-patterns/dos-gas-limit/SKILL.md +8 -1
- package/skills/vulnerability-patterns/dos-revert/SKILL.md +14 -1
- 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 +13 -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 +22 -0
- package/skills/vulnerability-patterns/outdated-compiler-version/SKILL.md +8 -1
- package/skills/vulnerability-patterns/overflow-underflow/SKILL.md +11 -1
- package/skills/vulnerability-patterns/proxy-vulnerabilities/SKILL.md +209 -0
- package/skills/vulnerability-patterns/reentrancy/SKILL.md +22 -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 +11 -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 +13 -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 +27 -10
- package/src/agents/pythia-prompt.ts +7 -8
- package/src/agents/scribe-prompt.ts +10 -5
- package/src/agents/sentinel-prompt.ts +36 -7
- package/src/cli/cli-output.ts +16 -0
- package/src/cli/cli-program.ts +29 -22
- package/src/cli/commands/check-skills.ts +135 -0
- package/src/cli/commands/doctor.ts +303 -23
- package/src/cli/commands/init.ts +8 -6
- package/src/cli/commands/install.ts +10 -8
- package/src/cli/commands/lint-skills.ts +118 -0
- package/src/cli/index.ts +5 -5
- package/src/cli/tui-prompts.ts +4 -2
- 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 +6 -5
- package/src/config/types.ts +2 -2
- package/src/constants/defaults.ts +2 -0
- package/src/create-hooks.ts +225 -29
- package/src/create-managers.ts +10 -8
- package/src/create-tools.ts +14 -8
- 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 +79 -19
- package/src/features/index.ts +5 -5
- package/src/features/persistent-state/audit-state-manager.ts +158 -52
- 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/agent-tracker.ts +53 -0
- package/src/hooks/compaction-hook.ts +46 -37
- package/src/hooks/config-handler.ts +31 -11
- package/src/hooks/context-budget.ts +42 -0
- package/src/hooks/event-hook.ts +48 -23
- package/src/hooks/hook-system.ts +4 -4
- package/src/hooks/index.ts +5 -5
- package/src/hooks/knowledge-sync-hook.ts +19 -21
- package/src/hooks/recon-context-builder.ts +66 -0
- package/src/hooks/safe-create-hook.ts +9 -11
- package/src/hooks/system-prompt-hook.ts +128 -0
- package/src/hooks/tool-tracking-hook.ts +162 -29
- package/src/hooks/types.ts +2 -1
- package/src/index.ts +23 -13
- package/src/knowledge/retry.ts +53 -0
- package/src/knowledge/scvd-client.ts +103 -83
- package/src/knowledge/scvd-errors.ts +89 -0
- package/src/knowledge/scvd-index.ts +110 -62
- package/src/knowledge/scvd-sync.ts +223 -47
- package/src/knowledge/source-manifest.ts +102 -0
- package/src/managers/index.ts +1 -1
- package/src/managers/types.ts +19 -14
- package/src/plugin-interface.ts +19 -8
- package/src/shared/binary-utils.ts +44 -34
- 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 +91 -17
- 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 +237 -0
- package/src/skills/skill-schema.ts +99 -0
- package/src/solodit-lifecycle.ts +202 -0
- package/src/state/audit-state.ts +10 -8
- package/src/state/finding-store.ts +68 -55
- package/src/state/types.ts +96 -44
- package/src/tools/argus-skill-load-tool.ts +78 -0
- 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 +206 -167
- package/src/tools/pattern-loader.ts +77 -0
- package/src/tools/pattern-schema.ts +51 -0
- package/src/tools/proxy-detection-tool.ts +224 -0
- package/src/tools/report-generator-tool.ts +333 -142
- package/src/tools/slither-tool.ts +300 -210
- package/src/tools/solodit-search-tool.ts +255 -80
- package/src/tools/sync-knowledge-tool.ts +7 -11
- package/src/utils/audit-artifact-detector.ts +118 -0
- package/src/utils/dependency-scanner.ts +93 -0
- package/src/utils/project-detector.ts +175 -86
- package/src/utils/solidity-parser.ts +112 -67
- package/src/utils/solodit-health.ts +29 -0
- package/src/hooks/event-hook-v2.ts +0 -99
- package/src/state/plugin-state.ts +0 -14
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { readdirSync, readFileSync } from "node:fs"
|
|
2
|
+
import { join } from "node:path"
|
|
3
|
+
import { createLogger } from "../../shared/logger"
|
|
4
|
+
import type { CliCommand } from "../types"
|
|
5
|
+
|
|
6
|
+
const logger = createLogger()
|
|
7
|
+
|
|
8
|
+
import { loadArgusConfig } from "../../config/loader"
|
|
9
|
+
import { resolveSkillRoots } from "../../skills/argus-skill-resolver"
|
|
10
|
+
import { parseFrontmatter, validateSkillFrontmatter } from "../../skills/skill-schema"
|
|
11
|
+
import { cliOutput } from "../cli-output"
|
|
12
|
+
|
|
13
|
+
const GREEN = "\x1b[32m"
|
|
14
|
+
const RED = "\x1b[31m"
|
|
15
|
+
const RESET = "\x1b[0m"
|
|
16
|
+
|
|
17
|
+
function findSkillFiles(dir: string, maxDepth = 8): string[] {
|
|
18
|
+
const files: string[] = []
|
|
19
|
+
const stack: Array<{ path: string; depth: number }> = [{ path: dir, depth: 0 }]
|
|
20
|
+
|
|
21
|
+
while (stack.length > 0) {
|
|
22
|
+
const current = stack.pop()
|
|
23
|
+
if (!current || current.depth > maxDepth) continue
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
const entries = readdirSync(current.path, { withFileTypes: true })
|
|
27
|
+
for (const entry of entries) {
|
|
28
|
+
const fullPath = join(current.path, entry.name)
|
|
29
|
+
if (entry.isDirectory()) {
|
|
30
|
+
stack.push({ path: fullPath, depth: current.depth + 1 })
|
|
31
|
+
} else if (entry.isFile() && entry.name.toUpperCase() === "SKILL.MD") {
|
|
32
|
+
files.push(fullPath)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
} catch {}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return files
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface LintResult {
|
|
42
|
+
valid: number
|
|
43
|
+
invalid: number
|
|
44
|
+
skipped: number
|
|
45
|
+
errors: Array<{ file: string; errors: string[] }>
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function lintSkillFiles(skillFiles: Array<{ path: string; content: string }>): LintResult {
|
|
49
|
+
let valid = 0
|
|
50
|
+
let invalid = 0
|
|
51
|
+
let skipped = 0
|
|
52
|
+
const errors: Array<{ file: string; errors: string[] }> = []
|
|
53
|
+
|
|
54
|
+
for (const { path, content } of skillFiles) {
|
|
55
|
+
const fm = parseFrontmatter(content)
|
|
56
|
+
if (!fm) {
|
|
57
|
+
skipped++
|
|
58
|
+
continue
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const result = validateSkillFrontmatter(fm)
|
|
62
|
+
if (result.success) {
|
|
63
|
+
valid++
|
|
64
|
+
} else {
|
|
65
|
+
invalid++
|
|
66
|
+
errors.push({ file: path, errors: result.errors })
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return { valid, invalid, skipped, errors }
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export const lintSkillsCommand: CliCommand = {
|
|
74
|
+
name: "lint-skills",
|
|
75
|
+
description: "Validate all SKILL.md files against schema",
|
|
76
|
+
async execute(): Promise<number> {
|
|
77
|
+
const cwd = process.cwd()
|
|
78
|
+
let config: ReturnType<typeof loadArgusConfig> | undefined
|
|
79
|
+
try {
|
|
80
|
+
config = loadArgusConfig(cwd)
|
|
81
|
+
} catch {
|
|
82
|
+
logger.debug("Config load failed, using defaults")
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const roots = resolveSkillRoots(cwd, config)
|
|
86
|
+
const skillFiles: Array<{ path: string; content: string }> = []
|
|
87
|
+
|
|
88
|
+
for (const root of roots) {
|
|
89
|
+
const files = findSkillFiles(root.path)
|
|
90
|
+
for (const file of files) {
|
|
91
|
+
try {
|
|
92
|
+
skillFiles.push({ path: file, content: readFileSync(file, "utf8") })
|
|
93
|
+
} catch {
|
|
94
|
+
logger.debug("Skipping unreadable skill file")
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const result = lintSkillFiles(skillFiles)
|
|
100
|
+
|
|
101
|
+
cliOutput.log(
|
|
102
|
+
`Skill Lint: ${result.valid} valid, ${result.invalid} invalid, ${result.skipped} skipped (no frontmatter)`,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
if (result.errors.length > 0) {
|
|
106
|
+
for (const { file, errors } of result.errors) {
|
|
107
|
+
cliOutput.log(`\n${RED}✗${RESET} ${file}`)
|
|
108
|
+
for (const err of errors) {
|
|
109
|
+
cliOutput.log(` - ${err}`)
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
} else if (result.valid > 0) {
|
|
113
|
+
cliOutput.log(`${GREEN}✓${RESET} All skills pass schema validation`)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return result.invalid > 0 ? 1 : 0
|
|
117
|
+
},
|
|
118
|
+
}
|
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/tui-prompts.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { cliOutput } from "./cli-output"
|
|
2
|
+
|
|
1
3
|
const NON_INTERACTIVE =
|
|
2
4
|
!process.stdin.isTTY || process.env.CI === "true" || process.env.ARGUS_NON_INTERACTIVE === "true"
|
|
3
5
|
|
|
@@ -29,10 +31,10 @@ export async function select(
|
|
|
29
31
|
): Promise<string> {
|
|
30
32
|
if (NON_INTERACTIVE) return options[defaultIndex] ?? options[0] ?? ""
|
|
31
33
|
|
|
32
|
-
|
|
34
|
+
cliOutput.log(message)
|
|
33
35
|
for (let i = 0; i < options.length; i++) {
|
|
34
36
|
const marker = i === defaultIndex ? ">" : " "
|
|
35
|
-
|
|
37
|
+
cliOutput.log(` ${marker} ${i + 1}. ${options[i]}`)
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
return new Promise((resolve) => {
|
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
|
})
|
|
@@ -23,17 +24,16 @@ const KnowledgeConfigSchema = z.object({
|
|
|
23
24
|
}),
|
|
24
25
|
autoSync: z.boolean().default(true),
|
|
25
26
|
customSkillsDir: z.string().optional(),
|
|
27
|
+
skillPrecedence: z.enum(["bundled-first", "custom-first"]).default("bundled-first"),
|
|
26
28
|
})
|
|
27
29
|
|
|
28
30
|
const ReportingConfigSchema = z.object({
|
|
29
31
|
format: z.enum(["markdown"]).default("markdown"),
|
|
30
|
-
severityThreshold: z
|
|
31
|
-
.enum(["critical", "high", "medium", "low", "informational"])
|
|
32
|
-
.default("low"),
|
|
32
|
+
severityThreshold: z.enum(["critical", "high", "medium", "low", "informational"]).default("low"),
|
|
33
33
|
gasAnalysis: z.boolean().default(false),
|
|
34
34
|
})
|
|
35
35
|
|
|
36
|
-
const
|
|
36
|
+
const SoloditConfigSchema = z.object({
|
|
37
37
|
enabled: z.boolean().default(true),
|
|
38
38
|
port: z.number().default(3000),
|
|
39
39
|
})
|
|
@@ -63,13 +63,14 @@ export const ArgusConfigSchema = z.object({
|
|
|
63
63
|
apiUrl: "https://api.scvd.dev",
|
|
64
64
|
},
|
|
65
65
|
autoSync: true,
|
|
66
|
+
skillPrecedence: "bundled-first",
|
|
66
67
|
}),
|
|
67
68
|
reporting: ReportingConfigSchema.default({
|
|
68
69
|
format: "markdown",
|
|
69
70
|
severityThreshold: "low",
|
|
70
71
|
gasAnalysis: false,
|
|
71
72
|
}),
|
|
72
|
-
solodit:
|
|
73
|
+
solodit: SoloditConfigSchema.default({
|
|
73
74
|
enabled: true,
|
|
74
75
|
port: 3000,
|
|
75
76
|
}),
|
package/src/config/types.ts
CHANGED
package/src/create-hooks.ts
CHANGED
|
@@ -1,25 +1,71 @@
|
|
|
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
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
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"
|
|
6
14
|
import { createCompactionHook } from "./hooks/compaction-hook"
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
15
|
+
import { createConfigHandler } from "./hooks/config-handler"
|
|
16
|
+
import { getTokenBudgetForAgent } from "./hooks/context-budget"
|
|
17
|
+
import { createEventHook } from "./hooks/event-hook"
|
|
18
|
+
import type { ReconContext } from "./hooks/recon-context-builder"
|
|
19
|
+
import { buildReconContextBlock } from "./hooks/recon-context-builder"
|
|
9
20
|
import { safeCreateHook } from "./hooks/safe-create-hook"
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
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"
|
|
26
|
+
import type { AuditState } from "./state/types"
|
|
27
|
+
import { detectAuditArtifacts } from "./utils/audit-artifact-detector"
|
|
28
|
+
import { detectProject, type ProjectConfig } from "./utils/project-detector"
|
|
29
|
+
|
|
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
|
|
38
|
+
|
|
39
|
+
export function getAgentForSession(sessionID: string): string | undefined {
|
|
40
|
+
return _agentTrackerRef?.getAgentForSession(sessionID)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function isArgusAgent(sessionID: string): boolean {
|
|
44
|
+
return _agentTrackerRef?.isArgusAgent(sessionID) ?? false
|
|
45
|
+
}
|
|
13
46
|
|
|
14
47
|
export type Hooks = Pick<
|
|
15
48
|
PluginHooks,
|
|
16
49
|
| "config"
|
|
50
|
+
| "chat.params"
|
|
51
|
+
| "chat.message"
|
|
17
52
|
| "experimental.chat.system.transform"
|
|
18
53
|
| "experimental.session.compacting"
|
|
19
54
|
| "tool.execute.after"
|
|
20
55
|
| "event"
|
|
21
56
|
>
|
|
22
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Creates the hook handlers for the Argus plugin.
|
|
60
|
+
*
|
|
61
|
+
* Context Delivery Strategy:
|
|
62
|
+
* - Prompt: Static agent identity (src/agents/*-prompt.ts) — methodology, personality, tool instructions
|
|
63
|
+
* - Hook: Dynamic state injection via experimental.chat.system.transform — audit progress, findings, phase
|
|
64
|
+
* - Skill-load: On-demand knowledge via argus_skill_load tool — vulnerability patterns, protocol knowledge
|
|
65
|
+
*
|
|
66
|
+
* The system.transform hook injects dynamic audit context only for Argus-family agents
|
|
67
|
+
* (argus, sentinel, pythia, scribe). Non-audit agents receive no injection.
|
|
68
|
+
*/
|
|
23
69
|
export function createHooks(args: {
|
|
24
70
|
config: ArgusConfig
|
|
25
71
|
managers: Managers
|
|
@@ -28,28 +74,118 @@ export function createHooks(args: {
|
|
|
28
74
|
}): Hooks {
|
|
29
75
|
const { config, managers, projectDir, isHookEnabled } = args
|
|
30
76
|
const { auditStateManager, backgroundManager } = managers
|
|
77
|
+
const agentTracker = createAgentTracker()
|
|
78
|
+
_agentTrackerRef = agentTracker
|
|
31
79
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
80
|
+
const contextMonitor = createContextMonitor()
|
|
81
|
+
const sessionRecoveryHandler = createSessionRecoveryHandler(auditStateManager)
|
|
82
|
+
const debouncedSave = createDebouncedSave(auditStateManager.save)
|
|
83
|
+
const runJournal = createRunJournal(projectDir)
|
|
84
|
+
let auditStateGetter: (() => AuditState | null) | undefined
|
|
85
|
+
const toolErrorRecoveryHandler = createToolErrorRecoveryHandler(
|
|
86
|
+
() => auditStateGetter?.() ?? null,
|
|
87
|
+
(patch) => auditStateManager.update(patch),
|
|
88
|
+
)
|
|
89
|
+
const outputTruncator = createToolOutputTruncator()
|
|
35
90
|
|
|
36
|
-
|
|
37
|
-
|
|
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, [
|
|
98
|
+
async ({ type, sessionId, auditState, setAuditState: setState }) => {
|
|
38
99
|
if (type === "session.created") {
|
|
39
|
-
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
|
+
|
|
40
114
|
if (recoveredState) {
|
|
41
115
|
setState(recoveredState)
|
|
42
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
|
+
|
|
43
138
|
return
|
|
44
139
|
}
|
|
45
140
|
|
|
46
141
|
if (type === "session.idle" && auditState) {
|
|
47
|
-
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
|
+
|
|
48
175
|
return
|
|
49
176
|
}
|
|
50
177
|
|
|
51
178
|
if (type === "session.deleted") {
|
|
52
|
-
|
|
179
|
+
if (sessionId) {
|
|
180
|
+
agentTracker.clearSession(sessionId)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
await auditStateManager.archive()
|
|
184
|
+
runJournal.log({
|
|
185
|
+
type: "session.deleted",
|
|
186
|
+
timestamp: Date.now(),
|
|
187
|
+
archived: true,
|
|
188
|
+
})
|
|
53
189
|
}
|
|
54
190
|
},
|
|
55
191
|
async ({ type, sessionId, setAuditState: setState }) => {
|
|
@@ -62,26 +198,88 @@ export function createHooks(args: {
|
|
|
62
198
|
},
|
|
63
199
|
])
|
|
64
200
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
201
|
+
auditStateGetter = getAuditState
|
|
202
|
+
|
|
203
|
+
const initialState = auditStateManager.get()
|
|
204
|
+
if (initialState) {
|
|
205
|
+
setAuditState(initialState)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const auditEnforcer = createAuditEnforcer()
|
|
209
|
+
|
|
210
|
+
const systemPromptHook = createSystemPromptHook({
|
|
211
|
+
getAuditState,
|
|
212
|
+
getAgentForSession: agentTracker.getAgentForSession,
|
|
213
|
+
isArgusAgent: agentTracker.isArgusAgent,
|
|
214
|
+
getContextPressure: (systemText: string) => {
|
|
215
|
+
const status = contextMonitor.getContextStatus(systemText, getAuditState())
|
|
216
|
+
return status.usage
|
|
217
|
+
},
|
|
218
|
+
getTokenBudget: getTokenBudgetForAgent,
|
|
219
|
+
getEnforcerReminder: auditEnforcer,
|
|
220
|
+
getReconBlock: () =>
|
|
221
|
+
buildReconContextBlock({
|
|
222
|
+
projectConfig: reconProjectConfig,
|
|
223
|
+
dependencyRisks: reconProjectConfig?.dependencyRisks ?? [],
|
|
224
|
+
auditArtifacts: detectAuditArtifacts(projectDir),
|
|
225
|
+
}),
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
let reconProjectConfig: ProjectConfig | null = null
|
|
229
|
+
|
|
230
|
+
detectProject(projectDir)
|
|
231
|
+
.then((config) => {
|
|
232
|
+
reconProjectConfig = config
|
|
233
|
+
})
|
|
234
|
+
.catch(() => {
|
|
235
|
+
logger.debug("Project detection failed, using fallback recon context")
|
|
236
|
+
})
|
|
69
237
|
|
|
70
|
-
|
|
71
|
-
|
|
238
|
+
const getReconContext = (): ReconContext => ({
|
|
239
|
+
projectConfig: reconProjectConfig,
|
|
240
|
+
dependencyRisks: reconProjectConfig?.dependencyRisks ?? [],
|
|
241
|
+
auditArtifacts: detectAuditArtifacts(projectDir),
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
const compactionHook = isHookEnabled("compaction")
|
|
245
|
+
? safeCreateHook(() => createCompactionHook(getAuditState, getReconContext), "compaction")
|
|
72
246
|
: undefined
|
|
73
247
|
|
|
74
248
|
const toolTrackingHook = isHookEnabled("tool-tracking")
|
|
75
|
-
? 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
|
+
)
|
|
76
266
|
: undefined
|
|
77
267
|
|
|
78
268
|
const safeEventHook = isHookEnabled("event")
|
|
79
269
|
? safeCreateHook(() => eventHook, "event")
|
|
80
270
|
: undefined
|
|
81
271
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
272
|
+
return {
|
|
273
|
+
config: createConfigHandler(config, projectDir),
|
|
274
|
+
"chat.params": async (input) => {
|
|
275
|
+
agentTracker.chatParamsHook(input)
|
|
276
|
+
},
|
|
277
|
+
"chat.message": async (input) => {
|
|
278
|
+
agentTracker.chatMessageHook(input)
|
|
279
|
+
},
|
|
280
|
+
"experimental.chat.system.transform": async (input, output) => {
|
|
281
|
+
await systemPromptHook(input, output)
|
|
282
|
+
},
|
|
85
283
|
"experimental.session.compacting": compactionHook
|
|
86
284
|
? async (_input, output) => {
|
|
87
285
|
const block = await compactionHook({ summary: output.context.join("\n") })
|
|
@@ -101,9 +299,7 @@ export function createHooks(args: {
|
|
|
101
299
|
result: output.output,
|
|
102
300
|
})
|
|
103
301
|
|
|
104
|
-
const outputWithHint = recoveryHint
|
|
105
|
-
? `${output.output}${recoveryHint}`
|
|
106
|
-
: output.output
|
|
302
|
+
const outputWithHint = recoveryHint ? `${output.output}${recoveryHint}` : output.output
|
|
107
303
|
output.output = outputTruncator(outputWithHint)
|
|
108
304
|
}
|
|
109
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,29 +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
15
|
|
|
12
|
-
export function createTools(
|
|
13
|
-
config: ArgusConfig,
|
|
14
|
-
): Record<string, ToolDefinition> {
|
|
16
|
+
export function createTools(config: ArgusConfig): Record<string, ToolDefinition> {
|
|
15
17
|
const tools: Record<string, ToolDefinition> = {
|
|
16
18
|
argus_slither_analyze: slitherTool,
|
|
17
19
|
argus_forge_test: forgeTestTool,
|
|
20
|
+
argus_gas_analysis: gasAnalysisTool,
|
|
18
21
|
argus_forge_fuzz: forgeFuzzTool,
|
|
22
|
+
argus_forge_coverage: forgeCoverageTool,
|
|
19
23
|
argus_analyze_contract: contractAnalyzerTool,
|
|
20
24
|
argus_check_patterns: patternCheckerTool,
|
|
25
|
+
argus_proxy_detection: proxyDetectionTool,
|
|
26
|
+
argus_skill_load: argusSkillLoadTool,
|
|
21
27
|
argus_generate_report: reportGeneratorTool,
|
|
22
28
|
argus_sync_knowledge: syncKnowledgeTool,
|
|
23
29
|
}
|
|
24
30
|
|
|
25
31
|
if (config.solodit?.enabled !== false) {
|
|
26
|
-
tools.argus_solodit_search =
|
|
32
|
+
tools.argus_solodit_search = createSoloditSearchTool(config.solodit?.port ?? 3000)
|
|
27
33
|
}
|
|
28
34
|
|
|
29
35
|
return tools
|