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.
Files changed (87) hide show
  1. package/README.md +161 -1
  2. package/package.json +5 -2
  3. package/skills/README.md +63 -0
  4. package/skills/checklists/cyfrin-defi-core/SKILL.md +3 -0
  5. package/skills/manifests/cyfrin.json +16 -0
  6. package/skills/manifests/defifofum.json +25 -0
  7. package/skills/manifests/kadenzipfel.json +48 -0
  8. package/skills/manifests/scvd.json +9 -0
  9. package/skills/manifests/smartbugs.json +11 -0
  10. package/skills/manifests/solodit.json +9 -0
  11. package/skills/manifests/sunweb3sec.json +11 -0
  12. package/skills/manifests/trailofbits.json +9 -0
  13. package/skills/methodology/audit-workflow/SKILL.md +3 -0
  14. package/skills/patterns/access-control.yaml +31 -0
  15. package/skills/patterns/erc4626.yaml +29 -0
  16. package/skills/patterns/flash-loan.yaml +20 -0
  17. package/skills/patterns/oracle.yaml +30 -0
  18. package/skills/patterns/proxy.yaml +30 -0
  19. package/skills/patterns/reentrancy.yaml +30 -0
  20. package/skills/patterns/signature.yaml +31 -0
  21. package/skills/protocol-patterns/amm-dex/SKILL.md +3 -0
  22. package/skills/references/exploit-reference/SKILL.md +3 -0
  23. package/skills/vulnerability-patterns/access-control/SKILL.md +13 -0
  24. package/skills/vulnerability-patterns/authorization-txorigin/SKILL.md +6 -0
  25. package/skills/vulnerability-patterns/delegatecall-untrusted-callee/SKILL.md +6 -0
  26. package/skills/vulnerability-patterns/dos-revert/SKILL.md +13 -1
  27. package/skills/vulnerability-patterns/flash-loan-attacks/SKILL.md +12 -0
  28. package/skills/vulnerability-patterns/oracle-manipulation/SKILL.md +13 -0
  29. package/skills/vulnerability-patterns/overflow-underflow/SKILL.md +10 -1
  30. package/skills/vulnerability-patterns/reentrancy/SKILL.md +13 -0
  31. package/skills/vulnerability-patterns/signature-malleability/SKILL.md +9 -0
  32. package/skills/vulnerability-patterns/unchecked-return-values/SKILL.md +11 -0
  33. package/src/agents/argus-prompt.ts +7 -7
  34. package/src/agents/pythia-prompt.ts +11 -11
  35. package/src/agents/scribe-prompt.ts +6 -6
  36. package/src/agents/sentinel-prompt.ts +7 -7
  37. package/src/cli/cli-output.ts +16 -0
  38. package/src/cli/cli-program.ts +9 -5
  39. package/src/cli/commands/doctor.ts +274 -16
  40. package/src/cli/commands/init.ts +5 -5
  41. package/src/cli/commands/install.ts +5 -5
  42. package/src/cli/commands/lint-skills.ts +114 -0
  43. package/src/cli/tui-prompts.ts +4 -2
  44. package/src/config/schema.ts +2 -0
  45. package/src/create-hooks.ts +141 -32
  46. package/src/create-tools.ts +2 -0
  47. package/src/features/error-recovery/session-recovery.ts +7 -1
  48. package/src/features/error-recovery/tool-error-recovery.ts +74 -19
  49. package/src/features/persistent-state/audit-state-manager.ts +36 -13
  50. package/src/hooks/agent-tracker.ts +53 -0
  51. package/src/hooks/compaction-hook.ts +46 -37
  52. package/src/hooks/config-handler.ts +22 -9
  53. package/src/hooks/context-budget.ts +45 -0
  54. package/src/hooks/event-hook-v2.ts +8 -2
  55. package/src/hooks/event-hook.ts +5 -4
  56. package/src/hooks/knowledge-sync-hook.ts +2 -1
  57. package/src/hooks/recon-context-builder.ts +66 -0
  58. package/src/hooks/safe-create-hook.ts +4 -5
  59. package/src/hooks/system-prompt-hook.ts +92 -221
  60. package/src/hooks/tool-tracking-hook.ts +108 -9
  61. package/src/hooks/types.ts +0 -1
  62. package/src/index.ts +28 -6
  63. package/src/knowledge/retry.ts +53 -0
  64. package/src/knowledge/scvd-client.ts +37 -10
  65. package/src/knowledge/scvd-errors.ts +89 -0
  66. package/src/knowledge/scvd-index.ts +53 -3
  67. package/src/knowledge/scvd-sync.ts +205 -34
  68. package/src/knowledge/source-manifest.ts +102 -0
  69. package/src/plugin-interface.ts +11 -3
  70. package/src/shared/binary-utils.ts +1 -0
  71. package/src/shared/logger.ts +78 -17
  72. package/src/skills/argus-skill-resolver.ts +226 -0
  73. package/src/skills/skill-schema.ts +98 -0
  74. package/src/state/audit-state.ts +2 -0
  75. package/src/state/types.ts +32 -1
  76. package/src/tools/argus-skill-load-tool.ts +73 -0
  77. package/src/tools/pattern-checker-tool.ts +56 -12
  78. package/src/tools/pattern-loader.ts +183 -0
  79. package/src/tools/pattern-schema.ts +51 -0
  80. package/src/tools/report-generator-tool.ts +134 -11
  81. package/src/tools/slither-tool.ts +61 -19
  82. package/src/tools/solodit-search-tool.ts +92 -14
  83. package/src/utils/audit-artifact-detector.ts +119 -0
  84. package/src/utils/dependency-scanner.ts +93 -0
  85. package/src/utils/project-detector.ts +128 -26
  86. package/src/utils/solidity-parser.ts +20 -4
  87. 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, AuditPhase } from "../state/types"
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({ type, sessionId, auditState: currentAuditState })
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
  }
@@ -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
- console.error(
42
- `[argus-state] Session idle — phase: ${currentAuditState.currentPhase}, findings: ${currentAuditState.findings.length}, contracts: ${currentAuditState.contractsReviewed.length}`
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
- console.error(
51
- `[argus-error] Session error — state snapshot: ${JSON.stringify({
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
- console.error(message)
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
- console.error(
9
- `[argus-hook-error] Failed to create hook "${hookName}": ${
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 { existsSync, readdirSync } from "node:fs";
2
- import { homedir } from "node:os";
3
- import { join, resolve } from "node:path";
4
- import type { ArgusConfig } from "../config/types";
5
- import type { AuditState, FindingSeverity } from "../state/types";
6
-
7
- interface SystemPromptInput {
8
- system: string;
9
- cwd: string;
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
- interface SkillIndexEntry {
13
- count: number;
14
- sample: string[];
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
- interface SkillIndexSnapshot {
18
- bundled: SkillIndexEntry;
19
- trailOfBits: SkillIndexEntry;
20
- custom: SkillIndexEntry;
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
- const TOB_CACHE_DIR = join(homedir(), ".cache", "solidity-argus", "trailofbits-skills");
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
- * Counts findings by severity from the audit state
42
- */
43
- function countFindingsBySeverity(
44
- findings: AuditState["findings"]
45
- ): Record<FindingSeverity, number> {
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
- return counts;
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 counts = countFindingsBySeverity(state.findings);
70
- const scopeList =
71
- state.scope.length > 0 ? state.scope.join(", ") : "not defined";
72
- const reviewedList =
73
- state.contractsReviewed.length > 0
74
- ? state.contractsReviewed.join(", ")
75
- : "none yet";
76
-
77
- return [
78
- `Phase: ${state.currentPhase}`,
79
- `Scope: ${scopeList}`,
80
- `Contracts reviewed: ${reviewedList}`,
81
- `Findings: ${state.findings.length} total — Critical: ${counts.Critical}, High: ${counts.High}, Medium: ${counts.Medium}, Low: ${counts.Low}, Info: ${counts.Informational}`,
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
- const rootEntries = readdirSync(rootPath, { withFileTypes: true });
91
- const names = new Set<string>();
70
+ lines.push("</argus-context>")
92
71
 
93
- for (const rootEntry of rootEntries) {
94
- if (!rootEntry.isDirectory()) continue;
72
+ let summary = lines.join("\n")
95
73
 
96
- const directSkill = join(rootPath, rootEntry.name, "SKILL.md");
97
- if (existsSync(directSkill)) {
98
- names.add(rootEntry.name);
99
- continue;
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 Array.from(names).sort();
82
+ return summary
115
83
  }
116
84
 
117
- function getTrailOfBitsSkillRoots(): string[] {
118
- const pluginsDir = join(TOB_CACHE_DIR, "plugins");
119
- if (!existsSync(pluginsDir)) {
120
- return [];
121
- }
122
-
123
- const pluginEntries = readdirSync(pluginsDir, { withFileTypes: true });
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
- return roots;
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
- ### Available Argus Tools
197
- - \`argus_slither_analyze\`: Run Slither static analysis on Solidity codebase
198
- - \`argus_forge_test\`: Execute Foundry/Forge tests for vulnerability verification
199
- - \`argus_forge_fuzz\`: Fuzz specific functions to discover edge cases
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
- ### Quick Reference
224
- Use @argus for full audits, @sentinel for testing, @pythia for research, @scribe for reports.
225
- Severity must follow classification above. Do not inflate severity.
226
- </argus-context>`.trim();
227
- }
103
+ const agent = deps.getAgentForSession(input.sessionID)
104
+ if (!agent) {
105
+ return
106
+ }
228
107
 
229
- /**
230
- * Factory function that creates a system prompt transform hook.
231
- * The hook injects Solidity audit context when working in a Solidity project.
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
- return async (input: SystemPromptInput): Promise<string | null> => {
246
- const isSolidity = await isSolidityProject(input.cwd);
112
+ output.system.push(buildDynamicContext(auditState, agent, budget))
247
113
 
248
- if (!isSolidity) {
249
- return null;
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
- const auditState = getAuditState();
253
- return buildAuditContextBlock(auditState, skillIndex);
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;