solidity-argus 0.1.7 → 0.2.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/README.md +161 -1
- package/package.json +5 -2
- package/skills/README.md +63 -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 +11 -0
- package/skills/manifests/solodit.json +9 -0
- package/skills/manifests/sunweb3sec.json +11 -0
- package/skills/manifests/trailofbits.json +9 -0
- package/skills/methodology/audit-workflow/SKILL.md +3 -0
- package/skills/patterns/access-control.yaml +31 -0
- package/skills/patterns/erc4626.yaml +29 -0
- package/skills/patterns/flash-loan.yaml +20 -0
- package/skills/patterns/oracle.yaml +30 -0
- package/skills/patterns/proxy.yaml +30 -0
- package/skills/patterns/reentrancy.yaml +30 -0
- package/skills/patterns/signature.yaml +31 -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 +13 -0
- package/skills/vulnerability-patterns/authorization-txorigin/SKILL.md +6 -0
- package/skills/vulnerability-patterns/delegatecall-untrusted-callee/SKILL.md +6 -0
- package/skills/vulnerability-patterns/dos-revert/SKILL.md +13 -1
- package/skills/vulnerability-patterns/flash-loan-attacks/SKILL.md +12 -0
- package/skills/vulnerability-patterns/oracle-manipulation/SKILL.md +13 -0
- package/skills/vulnerability-patterns/overflow-underflow/SKILL.md +10 -1
- package/skills/vulnerability-patterns/reentrancy/SKILL.md +13 -0
- package/skills/vulnerability-patterns/signature-malleability/SKILL.md +9 -0
- package/skills/vulnerability-patterns/unchecked-return-values/SKILL.md +11 -0
- package/src/agents/argus-prompt.ts +7 -7
- package/src/agents/pythia-prompt.ts +11 -11
- package/src/agents/scribe-prompt.ts +6 -6
- package/src/agents/sentinel-prompt.ts +7 -7
- package/src/cli/cli-output.ts +16 -0
- package/src/cli/cli-program.ts +9 -5
- package/src/cli/commands/doctor.ts +274 -16
- package/src/cli/commands/init.ts +5 -5
- package/src/cli/commands/install.ts +5 -5
- package/src/cli/commands/lint-skills.ts +114 -0
- package/src/cli/tui-prompts.ts +4 -2
- package/src/config/schema.ts +2 -0
- package/src/create-hooks.ts +141 -32
- package/src/create-tools.ts +2 -0
- package/src/features/error-recovery/session-recovery.ts +7 -1
- package/src/features/error-recovery/tool-error-recovery.ts +74 -19
- package/src/features/persistent-state/audit-state-manager.ts +36 -13
- package/src/hooks/agent-tracker.ts +53 -0
- package/src/hooks/compaction-hook.ts +46 -37
- package/src/hooks/config-handler.ts +22 -9
- package/src/hooks/context-budget.ts +45 -0
- package/src/hooks/event-hook-v2.ts +8 -2
- package/src/hooks/event-hook.ts +5 -4
- package/src/hooks/knowledge-sync-hook.ts +2 -1
- package/src/hooks/recon-context-builder.ts +66 -0
- package/src/hooks/safe-create-hook.ts +4 -5
- package/src/hooks/system-prompt-hook.ts +92 -221
- package/src/hooks/tool-tracking-hook.ts +108 -9
- package/src/hooks/types.ts +0 -1
- package/src/index.ts +28 -6
- package/src/knowledge/retry.ts +53 -0
- package/src/knowledge/scvd-client.ts +37 -10
- package/src/knowledge/scvd-errors.ts +89 -0
- package/src/knowledge/scvd-index.ts +53 -3
- package/src/knowledge/scvd-sync.ts +205 -34
- package/src/knowledge/source-manifest.ts +102 -0
- package/src/plugin-interface.ts +11 -3
- package/src/shared/binary-utils.ts +1 -0
- package/src/shared/logger.ts +78 -17
- package/src/skills/argus-skill-resolver.ts +226 -0
- package/src/skills/skill-schema.ts +98 -0
- package/src/state/audit-state.ts +2 -0
- package/src/state/types.ts +32 -1
- package/src/tools/argus-skill-load-tool.ts +73 -0
- package/src/tools/pattern-checker-tool.ts +56 -12
- package/src/tools/pattern-loader.ts +183 -0
- package/src/tools/pattern-schema.ts +51 -0
- package/src/tools/report-generator-tool.ts +134 -11
- package/src/tools/slither-tool.ts +61 -19
- package/src/tools/solodit-search-tool.ts +92 -14
- package/src/utils/audit-artifact-detector.ts +119 -0
- package/src/utils/dependency-scanner.ts +93 -0
- package/src/utils/project-detector.ts +128 -26
- package/src/utils/solidity-parser.ts +20 -4
- package/src/utils/solodit-health.ts +29 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context budget allocation for Argus agents.
|
|
3
|
+
*
|
|
4
|
+
* Provides per-agent token budgets for system-prompt injection sizing.
|
|
5
|
+
* Under context pressure (>70%), budgets are reduced by 50% to prevent
|
|
6
|
+
* context window overflow during long audits.
|
|
7
|
+
*
|
|
8
|
+
* Decoupled from system-prompt-hook.ts — consumed by the hook when available.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const ARGUS_BUDGET = 2000
|
|
12
|
+
const SUBAGENT_BUDGET = 1000
|
|
13
|
+
const PRESSURE_THRESHOLD = 0.70
|
|
14
|
+
const PRESSURE_REDUCTION = 0.5
|
|
15
|
+
|
|
16
|
+
const ARGUS_AGENTS = new Set(["argus"])
|
|
17
|
+
const SUBAGENTS = new Set(["sentinel", "pythia", "scribe"])
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Returns the token budget for a given agent, adjusted for context pressure.
|
|
21
|
+
*
|
|
22
|
+
* @param agent - Agent name (e.g. "argus", "sentinel", "pythia", "scribe")
|
|
23
|
+
* @param contextPressure - Current context usage ratio (0.0–1.0), from ContextMonitor
|
|
24
|
+
* @returns Token budget in tokens. 0 for non-Argus agents.
|
|
25
|
+
*/
|
|
26
|
+
export function getTokenBudgetForAgent(
|
|
27
|
+
agent: string,
|
|
28
|
+
contextPressure: number = 0,
|
|
29
|
+
): number {
|
|
30
|
+
let budget: number
|
|
31
|
+
|
|
32
|
+
if (ARGUS_AGENTS.has(agent)) {
|
|
33
|
+
budget = ARGUS_BUDGET
|
|
34
|
+
} else if (SUBAGENTS.has(agent)) {
|
|
35
|
+
budget = SUBAGENT_BUDGET
|
|
36
|
+
} else {
|
|
37
|
+
return 0
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (contextPressure > PRESSURE_THRESHOLD) {
|
|
41
|
+
budget = Math.floor(budget * PRESSURE_REDUCTION)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return budget
|
|
45
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AuditState
|
|
1
|
+
import type { AuditState } from "../state/types"
|
|
2
2
|
import { createAuditState } from "../state/audit-state"
|
|
3
3
|
import { createLogger } from "../shared/logger"
|
|
4
4
|
|
|
@@ -19,6 +19,7 @@ export type EventSubHandler = (event: {
|
|
|
19
19
|
type: string
|
|
20
20
|
sessionId?: string
|
|
21
21
|
auditState: AuditState | null
|
|
22
|
+
setAuditState: (state: AuditState | null) => void
|
|
22
23
|
}) => Promise<void>
|
|
23
24
|
|
|
24
25
|
export function createEventHookV2(
|
|
@@ -82,7 +83,12 @@ export function createEventHookV2(
|
|
|
82
83
|
|
|
83
84
|
for (const handler of subHandlers) {
|
|
84
85
|
try {
|
|
85
|
-
await handler({
|
|
86
|
+
await handler({
|
|
87
|
+
type,
|
|
88
|
+
sessionId,
|
|
89
|
+
auditState: currentAuditState,
|
|
90
|
+
setAuditState,
|
|
91
|
+
})
|
|
86
92
|
} catch (error) {
|
|
87
93
|
logger.error(`Sub-handler failed for event ${type}:`, error)
|
|
88
94
|
}
|
package/src/hooks/event-hook.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { AuditState } from "../state/types"
|
|
2
2
|
import { createAuditState } from "../state/audit-state"
|
|
3
|
+
import { createLogger } from "../shared/logger"
|
|
3
4
|
|
|
4
5
|
export type EventHookFn = (input: {
|
|
5
6
|
event: { type: string; sessionId?: string }
|
|
@@ -38,8 +39,8 @@ export function createEventHook(projectDir?: string): {
|
|
|
38
39
|
|
|
39
40
|
case "session.idle": {
|
|
40
41
|
if (currentAuditState) {
|
|
41
|
-
|
|
42
|
-
`[
|
|
42
|
+
createLogger().debug(
|
|
43
|
+
`[state] Session idle — phase: ${currentAuditState.currentPhase}, findings: ${currentAuditState.findings.length}, contracts: ${currentAuditState.contractsReviewed.length}`
|
|
43
44
|
)
|
|
44
45
|
}
|
|
45
46
|
break
|
|
@@ -47,8 +48,8 @@ export function createEventHook(projectDir?: string): {
|
|
|
47
48
|
|
|
48
49
|
case "session.error": {
|
|
49
50
|
if (currentAuditState) {
|
|
50
|
-
|
|
51
|
-
`
|
|
51
|
+
createLogger().error(
|
|
52
|
+
`Session error — state snapshot: ${JSON.stringify({
|
|
52
53
|
sessionId: currentAuditState.sessionId,
|
|
53
54
|
phase: currentAuditState.currentPhase,
|
|
54
55
|
findingsCount: currentAuditState.findings.length,
|
|
@@ -3,6 +3,7 @@ import path from "node:path"
|
|
|
3
3
|
import { ScvdClient } from "../knowledge/scvd-client"
|
|
4
4
|
import { syncIncremental, type SyncResult } from "../knowledge/scvd-sync"
|
|
5
5
|
import type { ArgusConfig } from "../config/types"
|
|
6
|
+
import { createLogger } from "../shared/logger"
|
|
6
7
|
|
|
7
8
|
export type KnowledgeSyncDependencies = {
|
|
8
9
|
createClient?: (apiUrl: string) => unknown
|
|
@@ -18,7 +19,7 @@ function defaultDependencies(): Required<KnowledgeSyncDependencies> {
|
|
|
18
19
|
syncIncrementalFn: async (client: unknown, indexPath: string) =>
|
|
19
20
|
syncIncremental(client as ScvdClient, indexPath),
|
|
20
21
|
log: (message: string) => {
|
|
21
|
-
|
|
22
|
+
createLogger().info(message)
|
|
22
23
|
},
|
|
23
24
|
}
|
|
24
25
|
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { ProjectConfig } from "../utils/project-detector"
|
|
2
|
+
import type { DependencyRisk } from "../utils/dependency-scanner"
|
|
3
|
+
import type { AuditArtifact } from "../utils/audit-artifact-detector"
|
|
4
|
+
|
|
5
|
+
export interface ReconContext {
|
|
6
|
+
projectConfig: ProjectConfig | null
|
|
7
|
+
dependencyRisks: DependencyRisk[]
|
|
8
|
+
auditArtifacts: AuditArtifact[]
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Builds an XML-like reconnaissance context block from project data.
|
|
13
|
+
* Returns null if no data is available (all fields empty/null).
|
|
14
|
+
*
|
|
15
|
+
* The block is injected into compaction output so Argus agents retain
|
|
16
|
+
* project intelligence across context window compressions.
|
|
17
|
+
*/
|
|
18
|
+
export function buildReconContextBlock(recon: ReconContext): string | null {
|
|
19
|
+
if (
|
|
20
|
+
!recon.projectConfig &&
|
|
21
|
+
recon.dependencyRisks.length === 0 &&
|
|
22
|
+
recon.auditArtifacts.length === 0
|
|
23
|
+
) {
|
|
24
|
+
return null
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const lines: string[] = ["<argus-recon>"]
|
|
28
|
+
|
|
29
|
+
if (recon.projectConfig) {
|
|
30
|
+
const frameworks: string[] = []
|
|
31
|
+
if (recon.projectConfig.hasFoundry) frameworks.push("Foundry")
|
|
32
|
+
if (recon.projectConfig.hasHardhat) frameworks.push("Hardhat")
|
|
33
|
+
if (frameworks.length > 0) {
|
|
34
|
+
lines.push(`Framework: ${frameworks.join(", ")}`)
|
|
35
|
+
}
|
|
36
|
+
if (recon.projectConfig.optimizer) {
|
|
37
|
+
lines.push(`Optimizer: runs=${recon.projectConfig.optimizer.runs}`)
|
|
38
|
+
}
|
|
39
|
+
if (recon.projectConfig.evmVersion) {
|
|
40
|
+
lines.push(`EVM Version: ${recon.projectConfig.evmVersion}`)
|
|
41
|
+
}
|
|
42
|
+
if (recon.projectConfig.isUpgradeable) {
|
|
43
|
+
lines.push(`Upgradeable: yes`)
|
|
44
|
+
}
|
|
45
|
+
if (recon.projectConfig.profiles && recon.projectConfig.profiles.length > 0) {
|
|
46
|
+
lines.push(`Profiles: ${recon.projectConfig.profiles.join(", ")}`)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (recon.dependencyRisks.length > 0) {
|
|
51
|
+
lines.push("Dependency Risks:")
|
|
52
|
+
for (const risk of recon.dependencyRisks.slice(0, 5)) {
|
|
53
|
+
lines.push(` - ${risk.package}@${risk.version}: ${risk.risk}`)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (recon.auditArtifacts.length > 0) {
|
|
58
|
+
lines.push("Existing Audit Artifacts:")
|
|
59
|
+
for (const artifact of recon.auditArtifacts.slice(0, 5)) {
|
|
60
|
+
lines.push(` - ${artifact.type}: ${artifact.path}`)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
lines.push("</argus-recon>")
|
|
65
|
+
return lines.join("\n")
|
|
66
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { createLogger } from "../shared/logger"
|
|
2
|
+
|
|
1
3
|
export function safeCreateHook<T>(
|
|
2
4
|
factory: () => T,
|
|
3
5
|
hookName: string
|
|
@@ -5,11 +7,8 @@ export function safeCreateHook<T>(
|
|
|
5
7
|
try {
|
|
6
8
|
return factory();
|
|
7
9
|
} catch (error) {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
error instanceof Error ? error.message : String(error)
|
|
11
|
-
}`
|
|
12
|
-
);
|
|
10
|
+
const logger = createLogger()
|
|
11
|
+
logger.error(`Failed to create hook "${hookName}": ${error instanceof Error ? error.message : String(error)}`)
|
|
13
12
|
return undefined;
|
|
14
13
|
}
|
|
15
14
|
}
|
|
@@ -1,257 +1,128 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
import type { AuditState, FindingSeverity } from "../state/types"
|
|
2
|
+
|
|
3
|
+
const DEFAULT_TOKEN_BUDGET = 2000
|
|
4
|
+
const TOKENS_PER_CHAR = 4
|
|
5
|
+
|
|
6
|
+
export interface SystemPromptHookDeps {
|
|
7
|
+
getAuditState: () => AuditState | null
|
|
8
|
+
getAgentForSession: (sessionID: string) => string | undefined
|
|
9
|
+
isArgusAgent: (sessionID: string) => boolean
|
|
10
|
+
getContextPressure?: (systemText: string) => number
|
|
11
|
+
getTokenBudget?: (agent: string, contextPressure: number) => number
|
|
12
|
+
getEnforcerReminder?: (state: AuditState) => string | null
|
|
13
|
+
getReconBlock?: () => string | null
|
|
10
14
|
}
|
|
11
15
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
16
|
+
const FALLBACK_DIRECTIVES: Record<string, string> = {
|
|
17
|
+
slither:
|
|
18
|
+
"DO NOT re-attempt argus_slither_analyze. Use `argus_analyze_contract` and `argus_check_patterns` instead. Note limitation in report.",
|
|
19
|
+
forge:
|
|
20
|
+
"DO NOT re-attempt argus_forge_test or argus_forge_fuzz. Verify findings via manual code tracing. Note limitation in report.",
|
|
21
|
+
solodit:
|
|
22
|
+
"DO NOT re-attempt argus_solodit_search. Use `argus_check_patterns` with local rules. Note limitation in report.",
|
|
15
23
|
}
|
|
16
24
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
25
|
+
export function buildFallbackDirectives(unavailableTools: string[]): string[] {
|
|
26
|
+
const directives: string[] = []
|
|
27
|
+
for (const tool of unavailableTools) {
|
|
28
|
+
const directive = FALLBACK_DIRECTIVES[tool]
|
|
29
|
+
if (directive) directives.push(directive)
|
|
30
|
+
}
|
|
31
|
+
return directives
|
|
21
32
|
}
|
|
22
33
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Checks if the given directory contains a Solidity project
|
|
27
|
-
* by looking for foundry.toml or hardhat.config.{js,ts}
|
|
28
|
-
*/
|
|
29
|
-
async function isSolidityProject(cwd: string): Promise<boolean> {
|
|
30
|
-
const checks = [
|
|
31
|
-
Bun.file(join(cwd, "foundry.toml")).exists(),
|
|
32
|
-
Bun.file(join(cwd, "hardhat.config.js")).exists(),
|
|
33
|
-
Bun.file(join(cwd, "hardhat.config.ts")).exists(),
|
|
34
|
-
];
|
|
35
|
-
|
|
36
|
-
const results = await Promise.all(checks);
|
|
37
|
-
return results.some(Boolean);
|
|
34
|
+
export function estimateTokens(text: string): number {
|
|
35
|
+
return Math.ceil(text.length / TOKENS_PER_CHAR)
|
|
38
36
|
}
|
|
39
37
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const counts: Record<FindingSeverity, number> = {
|
|
38
|
+
export function buildDynamicContext(
|
|
39
|
+
auditState: AuditState,
|
|
40
|
+
agent: string,
|
|
41
|
+
tokenBudget: number = DEFAULT_TOKEN_BUDGET,
|
|
42
|
+
): string {
|
|
43
|
+
const severityCounts: Record<FindingSeverity, number> = {
|
|
47
44
|
Critical: 0,
|
|
48
45
|
High: 0,
|
|
49
46
|
Medium: 0,
|
|
50
47
|
Low: 0,
|
|
51
48
|
Informational: 0,
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
for (const finding of findings) {
|
|
55
|
-
counts[finding.severity]++;
|
|
56
49
|
}
|
|
57
50
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Builds the audit state summary section for the injected context
|
|
63
|
-
*/
|
|
64
|
-
function buildAuditStateSummary(state: AuditState | null): string {
|
|
65
|
-
if (!state) {
|
|
66
|
-
return "No active audit session. Use @argus to start an audit.";
|
|
51
|
+
for (const finding of auditState.findings) {
|
|
52
|
+
severityCounts[finding.severity]++
|
|
67
53
|
}
|
|
68
54
|
|
|
69
|
-
const
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
`
|
|
81
|
-
|
|
82
|
-
].join("\n");
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function collectSkillNamesFromRoot(rootPath: string): string[] {
|
|
86
|
-
if (!existsSync(rootPath)) {
|
|
87
|
-
return [];
|
|
55
|
+
const tools = auditState.toolsExecuted.map((tool) => tool.tool).join(", ") || "none"
|
|
56
|
+
const unavailable = auditState.unavailableTools ?? []
|
|
57
|
+
const lines: string[] = [
|
|
58
|
+
`<argus-context agent="${agent}">`,
|
|
59
|
+
`Phase: ${auditState.currentPhase}`,
|
|
60
|
+
`Contracts: ${auditState.contractsReviewed.length} reviewed`,
|
|
61
|
+
`Findings: Critical=${severityCounts.Critical} High=${severityCounts.High} Medium=${severityCounts.Medium} Low=${severityCounts.Low} Info=${severityCounts.Informational}`,
|
|
62
|
+
`Tools: ${tools}`,
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
if (unavailable.length > 0) {
|
|
66
|
+
lines.push(`Unavailable: ${unavailable.join(", ")}`)
|
|
67
|
+
lines.push(...buildFallbackDirectives(unavailable))
|
|
88
68
|
}
|
|
89
69
|
|
|
90
|
-
|
|
91
|
-
const names = new Set<string>();
|
|
70
|
+
lines.push("</argus-context>")
|
|
92
71
|
|
|
93
|
-
|
|
94
|
-
if (!rootEntry.isDirectory()) continue;
|
|
72
|
+
let summary = lines.join("\n")
|
|
95
73
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
const categoryDir = join(rootPath, rootEntry.name);
|
|
103
|
-
const categoryEntries = readdirSync(categoryDir, { withFileTypes: true });
|
|
104
|
-
|
|
105
|
-
for (const categoryEntry of categoryEntries) {
|
|
106
|
-
if (!categoryEntry.isDirectory()) continue;
|
|
107
|
-
const categorySkill = join(categoryDir, categoryEntry.name, "SKILL.md");
|
|
108
|
-
if (existsSync(categorySkill)) {
|
|
109
|
-
names.add(`${rootEntry.name}/${categoryEntry.name}`);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
74
|
+
if (estimateTokens(summary) > tokenBudget) {
|
|
75
|
+
summary = [
|
|
76
|
+
`<argus-context agent="${agent}">`,
|
|
77
|
+
`Phase: ${auditState.currentPhase} | Findings: ${auditState.findings.length} | Contracts: ${auditState.contractsReviewed.length}`,
|
|
78
|
+
"</argus-context>",
|
|
79
|
+
].join("\n")
|
|
112
80
|
}
|
|
113
81
|
|
|
114
|
-
return
|
|
82
|
+
return summary
|
|
115
83
|
}
|
|
116
84
|
|
|
117
|
-
function
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
const roots: string[] = [];
|
|
125
|
-
|
|
126
|
-
for (const pluginEntry of pluginEntries) {
|
|
127
|
-
if (!pluginEntry.isDirectory()) continue;
|
|
128
|
-
const pluginSkillsRoot = join(pluginsDir, pluginEntry.name, "skills");
|
|
129
|
-
if (existsSync(pluginSkillsRoot)) {
|
|
130
|
-
roots.push(pluginSkillsRoot);
|
|
85
|
+
export function createSystemPromptHook(deps: SystemPromptHookDeps) {
|
|
86
|
+
return async (
|
|
87
|
+
input: { sessionID?: string; model: unknown },
|
|
88
|
+
output: { system: string[] },
|
|
89
|
+
): Promise<void> => {
|
|
90
|
+
if (!input.sessionID) {
|
|
91
|
+
return
|
|
131
92
|
}
|
|
132
|
-
}
|
|
133
93
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
function toSkillIndexEntry(skillNames: string[]): SkillIndexEntry {
|
|
138
|
-
return {
|
|
139
|
-
count: skillNames.length,
|
|
140
|
-
sample: skillNames.slice(0, 3),
|
|
141
|
-
};
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
function buildSkillIndexSnapshot(args: {
|
|
145
|
-
argusConfig?: ArgusConfig;
|
|
146
|
-
projectDir?: string;
|
|
147
|
-
}): SkillIndexSnapshot {
|
|
148
|
-
const bundledRoot = resolve(import.meta.dir, "../../skills");
|
|
149
|
-
const bundledSkills = collectSkillNamesFromRoot(bundledRoot);
|
|
150
|
-
|
|
151
|
-
const trailOfBitsSkills = new Set<string>();
|
|
152
|
-
for (const tobRoot of getTrailOfBitsSkillRoots()) {
|
|
153
|
-
for (const name of collectSkillNamesFromRoot(tobRoot)) {
|
|
154
|
-
trailOfBitsSkills.add(name);
|
|
94
|
+
if (!deps.isArgusAgent(input.sessionID)) {
|
|
95
|
+
return
|
|
155
96
|
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
const customDir = args.argusConfig?.knowledge?.customSkillsDir;
|
|
159
|
-
const customRoot = customDir
|
|
160
|
-
? customDir.startsWith("/")
|
|
161
|
-
? customDir
|
|
162
|
-
: resolve(args.projectDir ?? process.cwd(), customDir)
|
|
163
|
-
: undefined;
|
|
164
|
-
const customSkills = customRoot ? collectSkillNamesFromRoot(customRoot) : [];
|
|
165
|
-
|
|
166
|
-
return {
|
|
167
|
-
bundled: toSkillIndexEntry(bundledSkills),
|
|
168
|
-
trailOfBits: toSkillIndexEntry(Array.from(trailOfBitsSkills).sort()),
|
|
169
|
-
custom: toSkillIndexEntry(customSkills),
|
|
170
|
-
};
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
function formatSkillSample(entry: SkillIndexEntry): string {
|
|
174
|
-
return entry.sample.length > 0 ? entry.sample.join(", ") : "none";
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* Builds the full audit context block to inject into the system prompt.
|
|
179
|
-
* Designed to be concise (500-800 tokens).
|
|
180
|
-
*/
|
|
181
|
-
function buildAuditContextBlock(
|
|
182
|
-
state: AuditState | null,
|
|
183
|
-
skillIndex: SkillIndexSnapshot
|
|
184
|
-
): string {
|
|
185
|
-
return `
|
|
186
|
-
<argus-context>
|
|
187
|
-
## Solidity Audit Context
|
|
188
|
-
|
|
189
|
-
### Severity Classification
|
|
190
|
-
- **Critical**: Direct theft/freezing of funds, unauthorized admin access, contract destruction
|
|
191
|
-
- **High**: Indirect fund loss, business logic manipulation, DoS on critical functions
|
|
192
|
-
- **Medium**: Degraded functionality, edge-case bugs, partial DoS, poor validation
|
|
193
|
-
- **Low**: Code quality issues, suboptimal patterns, missing events, minor logic issues
|
|
194
|
-
- **Informational**: Gas optimizations, style suggestions, best practices, non-security notes
|
|
195
97
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
- \`argus_analyze_contract\`: Generate deep structural profile of a contract
|
|
201
|
-
- \`argus_check_patterns\`: Scan code against known vulnerability pattern library
|
|
202
|
-
- \`argus_solodit_search\`: Search real-world audit reports and known vulnerabilities
|
|
203
|
-
- \`argus_generate_report\`: Compile findings into structured audit report
|
|
204
|
-
- \`argus_sync_knowledge\`: Update local vulnerability database (SCVD)
|
|
205
|
-
|
|
206
|
-
### Available Skills
|
|
207
|
-
- \`vulnerability-patterns/*\`: 35+ exploit classes (reentrancy, oracle manipulation, flash loans)
|
|
208
|
-
- \`protocol-patterns/*\`: AMM/DEX, lending, bridges, governance, staking-specific heuristics
|
|
209
|
-
- \`methodology/*\`: audit workflow, severity calibration, and report structure guidance
|
|
210
|
-
- \`checklists/*\`: structured review checklists for upgrades, integrations, and DeFi best practices
|
|
211
|
-
- \`references/*\`: exploit references and vulnerable examples for historical precedent
|
|
212
|
-
- Trail of Bits skills include both audit-relevant and general engineering skills; prioritize security-audit-oriented skills
|
|
213
|
-
|
|
214
|
-
### Skill Index Snapshot
|
|
215
|
-
- Bundled skills: ${skillIndex.bundled.count} (examples: ${formatSkillSample(skillIndex.bundled)})
|
|
216
|
-
- Trail of Bits skills: ${skillIndex.trailOfBits.count} (examples: ${formatSkillSample(skillIndex.trailOfBits)})
|
|
217
|
-
- Custom project skills: ${skillIndex.custom.count} (examples: ${formatSkillSample(skillIndex.custom)})
|
|
218
|
-
- Use the \`skill\` tool with exact skill names from the catalog
|
|
219
|
-
|
|
220
|
-
### Audit State
|
|
221
|
-
${buildAuditStateSummary(state)}
|
|
98
|
+
const auditState = deps.getAuditState()
|
|
99
|
+
if (!auditState) {
|
|
100
|
+
return
|
|
101
|
+
}
|
|
222
102
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
}
|
|
103
|
+
const agent = deps.getAgentForSession(input.sessionID)
|
|
104
|
+
if (!agent) {
|
|
105
|
+
return
|
|
106
|
+
}
|
|
228
107
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
*
|
|
233
|
-
* @param getAuditState - Accessor function for current audit state (may return null)
|
|
234
|
-
* @returns Async transform function compatible with OpenCode's experimental.chat.system.transform
|
|
235
|
-
*/
|
|
236
|
-
export function createSystemPromptHook(
|
|
237
|
-
getAuditState: () => AuditState | null,
|
|
238
|
-
options?: { argusConfig?: ArgusConfig; projectDir?: string }
|
|
239
|
-
): (input: SystemPromptInput) => Promise<string | null> {
|
|
240
|
-
const skillIndex = buildSkillIndexSnapshot({
|
|
241
|
-
argusConfig: options?.argusConfig,
|
|
242
|
-
projectDir: options?.projectDir,
|
|
243
|
-
});
|
|
108
|
+
const currentSystem = output.system.join("\n")
|
|
109
|
+
const pressure = deps.getContextPressure?.(currentSystem) ?? 0
|
|
110
|
+
const budget = deps.getTokenBudget?.(agent, pressure) ?? DEFAULT_TOKEN_BUDGET
|
|
244
111
|
|
|
245
|
-
|
|
246
|
-
const isSolidity = await isSolidityProject(input.cwd);
|
|
112
|
+
output.system.push(buildDynamicContext(auditState, agent, budget))
|
|
247
113
|
|
|
248
|
-
if (
|
|
249
|
-
|
|
114
|
+
if (deps.getReconBlock) {
|
|
115
|
+
const reconBlock = deps.getReconBlock()
|
|
116
|
+
if (reconBlock && estimateTokens(reconBlock) <= budget) {
|
|
117
|
+
output.system.push(reconBlock)
|
|
118
|
+
}
|
|
250
119
|
}
|
|
251
120
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
121
|
+
if (agent === "argus" && deps.getEnforcerReminder) {
|
|
122
|
+
const reminder = deps.getEnforcerReminder(auditState)
|
|
123
|
+
if (reminder) {
|
|
124
|
+
output.system.push(reminder)
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
255
128
|
}
|
|
256
|
-
|
|
257
|
-
export default createSystemPromptHook;
|